Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CHE-167] BE Refactor Error Handling #150

Merged
merged 55 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
930b51a
create reusable error classes
seantokuzo Jun 4, 2024
d5d4a49
install express-async-errors dependency
seantokuzo Jun 4, 2024
0f1a52b
import package into index.ts for use across the backend
seantokuzo Jun 4, 2024
d88f9ba
Merge branch 'CHE-168/subtask/Create-Reusable-Error-Classes' of https…
seantokuzo Jun 4, 2024
f7ee32c
update map to destructure
seantokuzo Jun 4, 2024
f029331
Merge branch 'CHE-168/subtask/Create-Reusable-Error-Classes' of https…
seantokuzo Jun 4, 2024
7f610d1
Merge branch 'dev' of https://github.com/Code-Hammers/code-hammers in…
seantokuzo Jun 6, 2024
74d943b
apply prettier formatting
seantokuzo Jun 7, 2024
8c3b31b
Merge pull request #133 from Code-Hammers/CHE-167b/subsubtask/Pull-De…
brok3turtl3 Jun 7, 2024
c2132ce
Merge branch 'CHE-167/story/BE-Refactor-Error-Handling' of https://gi…
seantokuzo Jun 7, 2024
3fd0c53
CHE-169 Updated image removal script
brok3turtl3 Jun 8, 2024
08628cd
Merge pull request #131 from Code-Hammers/CHE-169/subtask/Add-Express…
brok3turtl3 Jun 8, 2024
bb6e77e
simplify imports of custom error classes
seantokuzo Jun 9, 2024
326cc88
create global error handler middleware to handle custom errors
seantokuzo Jun 9, 2024
7bb767a
plug in new global error handler
seantokuzo Jun 9, 2024
8e222aa
update old error condition in global error handler
seantokuzo Jun 9, 2024
eff0e42
comment grammar cleanup
seantokuzo Jun 9, 2024
7185df6
remove unused errorHandler controller
seantokuzo Jun 10, 2024
6bcc572
switch console.log to console.error and log entire error to include s…
seantokuzo Jun 10, 2024
1aec4d8
update errorHandler test
seantokuzo Jun 10, 2024
061e069
Merge pull request #138 from Code-Hammers/CHE-170/subtask/Refactor-Gl…
brok3turtl3 Jun 10, 2024
d0ebb10
refactor catch all route handler - move connectDB fn into startServer fn
seantokuzo Jun 11, 2024
2a42855
refactor not found handler test
seantokuzo Jun 11, 2024
22551a3
remove unused catch all controller
seantokuzo Jun 11, 2024
16d2fbc
linty mcLinterson
seantokuzo Jun 11, 2024
b5850de
Merge pull request #140 from Code-Hammers/CHE-171/subtask/Refactor-Ca…
brok3turtl3 Jun 11, 2024
667b4e6
simplify router imports
seantokuzo Jun 12, 2024
8bb00ea
add env vars for startup checks
seantokuzo Jun 12, 2024
397d768
separate app config and server startup logic
seantokuzo Jun 12, 2024
96d4d72
add env var checks to startup
seantokuzo Jun 12, 2024
55dac24
add connection string arg to connectDB and await connection before se…
seantokuzo Jun 12, 2024
99dcf46
update app import and await startServer
seantokuzo Jun 12, 2024
2b69238
add necessary env vars for tests
seantokuzo Jun 12, 2024
4053e88
cleanup env var check error messages
seantokuzo Jun 12, 2024
b16bb06
reorder env vars
seantokuzo Jun 12, 2024
9940017
pass mongoURI to connectDB - comment out unused tests
seantokuzo Jun 12, 2024
9451374
remove unused import
seantokuzo Jun 12, 2024
d3b2a50
prettier fix
seantokuzo Jun 12, 2024
1e89b4b
comments
seantokuzo Jun 12, 2024
51888b5
Merge pull request #142 from Code-Hammers/CHE-172/subtask/Refactor-Se…
brok3turtl3 Jun 15, 2024
174f8c5
Merge branch 'dev' of https://github.com/Code-Hammers/code-hammers in…
seantokuzo Jun 15, 2024
3e6bc86
Merge branch 'CHE-167/story/BE-Refactor-Error-Handling' of https://gi…
seantokuzo Jun 15, 2024
c9e1dd0
update github workflows image version v2 -> v3
seantokuzo Jun 15, 2024
9f774cc
Merge pull request #146 from Code-Hammers/CHE-167/Dev-Branch-Pull
brok3turtl3 Jun 15, 2024
ead7409
refactor auth mw to use custom error class - add user to express Requ…
seantokuzo Jun 16, 2024
e3ff7e3
refactor auth controller to use protect middleware
seantokuzo Jun 16, 2024
29e1171
add auth middleware before controller
seantokuzo Jun 16, 2024
c998fc2
simplify auth middleware implementation in routers
seantokuzo Jun 16, 2024
e8b88c9
remove unused CustomRequest type
seantokuzo Jun 16, 2024
4dd683b
remove usage of CustomRequest type in controllers
seantokuzo Jun 16, 2024
699bff6
Merge pull request #148 from Code-Hammers/CHE-173/subtask/Refactor-Au…
brok3turtl3 Jun 17, 2024
18668c0
Merge branch 'dev' of https://github.com/Code-Hammers/code-hammers in…
seantokuzo Jun 20, 2024
8719ff0
add back aws health check route
seantokuzo Jun 20, 2024
c823a40
turfin old image comments
seantokuzo Jun 20, 2024
92879bd
Merge pull request #152 from Code-Hammers/CHE-167b/subtask/Merge-Dev-…
brok3turtl3 Jun 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/build-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ jobs:
with:
node-version: '18.17.1'
- name: Build Docker Image
run: docker build -t codehammers/ch-dev-dep-v2:latest -f Dockerfile-dev .
run: docker build -t codehammers/ch-dev-dep-v3:latest -f Dockerfile-dev .
- name: Install Root Dependencies
run: docker run codehammers/ch-dev-dep-v2:latest npm install
run: docker run codehammers/ch-dev-dep-v3:latest npm install
- name: Install Client Dependencies
run: docker run codehammers/ch-dev-dep-v2:latest /bin/sh -c "cd client && npm install"
run: docker run codehammers/ch-dev-dep-v3:latest /bin/sh -c "cd client && npm install"
- run: LINT_COMMAND=lint docker-compose -f docker-compose-lint.yml up --abort-on-container-exit
- run: docker-compose -f docker-compose-test.yml up --abort-on-container-exit
env:
Expand Down
15 changes: 10 additions & 5 deletions __tests__/db.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import mongoose from 'mongoose';
import connectDB from '../server/config/db';

// TODO
/*eslint jest/no-disabled-tests: "off"*/

jest.mock('mongoose', () => ({
connect: jest.fn().mockImplementation(() =>
Promise.resolve({
Expand All @@ -24,26 +27,28 @@ describe('connectDB', () => {

it('should call mongoose.connect with MONGO_URI', async () => {
process.env.MONGO_URI = 'test-mongo-uri';
await connectDB();
await connectDB(process.env.MONGO_URI);
expect(mongoose.connect).toHaveBeenCalledWith('test-mongo-uri');
});

it('should log an error and exit the process if mongoose.connect fails', async () => {
// We now console.error the error's message and throw a DatabaseConnectionError instead
xit('should log an error and exit the process if mongoose.connect fails', async () => {
process.env.MONGO_URI = 'test-mongo-uri';
(mongoose.connect as jest.Mock).mockImplementationOnce(() => {
throw new Error('test error');
});

await connectDB();
await connectDB(process.env.MONGO_URI);

expect(mockConsoleError).toHaveBeenCalledWith('test error');
expect(mockExit).toHaveBeenCalledWith(1);
});

it('should throw an error if MONGO_URI is not defined', async () => {
// This check has been moved to startServer in index.ts
xit('should throw an error if MONGO_URI is not defined', async () => {
delete process.env.MONGO_URI;

await connectDB();
await connectDB(process.env.MONGO_URI!);

expect(mockConsoleError).toHaveBeenCalledWith(
'MONGO_URI must be defined in the environment variables.',
Expand Down
31 changes: 19 additions & 12 deletions __tests__/errorController.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import app from '../server/app';
import request from 'supertest';
import { Request, Response, NextFunction } from 'express';
import { notFound, errorHandler } from '../server/controllers/errorControllers';
import errorHandler from '../server/middleware/errorHandler';
import { BadRequestError, NotFoundError } from '../server/errors';

describe('Middleware Tests', () => {
let mockRequest: Partial<Request>;
Expand All @@ -10,29 +13,33 @@ describe('Middleware Tests', () => {
mockRequest = {};
mockResponse = {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
send: jest.fn(),
};
mockNext = jest.fn();
});

describe('notFound Middleware', () => {
it('should return 404 and the original URL', () => {
notFound(mockRequest as Request, mockResponse as Response, mockNext);
expect(mockResponse.status).toHaveBeenCalledWith(404);
expect(mockNext).toHaveBeenCalled();
it('should return 404 and the original URL', async () => {
const exampleNotFoundError = new NotFoundError();

const response = await request(app).get('/non-existent-route').send();

expect(response.status).toEqual(404);
expect(response.body).toEqual(exampleNotFoundError.serializeErrors());
});
});

describe('errorHandler Middleware', () => {
it('should handle the error correctly', () => {
const mockError = new Error('Some error');
const mockError = new BadRequestError('Some error');
errorHandler(mockError, mockRequest as Request, mockResponse as Response, mockNext);
expect(mockResponse.status).toHaveBeenCalledWith(400);
expect(mockResponse.json).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.anything(),
stack: expect.any(String),
}),
expect(mockResponse.send).toHaveBeenCalledWith(
expect.objectContaining([
{
message: expect.any(String),
},
]),
);
});
});
Expand Down
7 changes: 4 additions & 3 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import request from 'supertest';
import app, { startServer } from '../server/index';
import { startServer } from '../server/index';
import app from '../server/app';
import { Server } from 'http';
import mongoose from 'mongoose';

Expand All @@ -8,8 +9,8 @@ import mongoose from 'mongoose';

let server: Server;

beforeEach(() => {
server = startServer();
beforeEach(async () => {
server = await startServer();
});

afterEach((done) => {
Expand Down
6 changes: 6 additions & 0 deletions docker-compose-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ services:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=ch-dev
- POSTGRES_DB=ch-dev-database
- AWS_ACCESS_KEY_ID=placeholder-value
- AWS_SECRET_ACCESS_KEY=placeholder-value
- AWS_REGION=placeholder-value
- BUCKET_NAME=placeholder-value
# suppress aws sdk v2 deprecation warning
- AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE=1;
depends_on:
- postgres
postgres:
Expand Down
11 changes: 10 additions & 1 deletion docker-compose-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,17 @@ services:
depends_on:
- mongo
environment:
- MONGO_URI=mongodb://mongo:27017/ch-testdb
- JWT_SECRET=${JWT_SECRET}
- MONGO_URI=mongodb://mongo:27017/ch-testdb
- POSTGRES_USER=postgres
- POSTGRES_DB=ch-dev-database
- POSTGRES_PASSWORD=ch-dev
- AWS_ACCESS_KEY_ID=placeholder-value
- AWS_SECRET_ACCESS_KEY=placeholder-value
- AWS_REGION=placeholder-value
- BUCKET_NAME=placeholder-value
# suppress aws sdk v2 deprecation warning
- AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE=1;
command: npm run test:all

mongo:
Expand Down
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"cookie-parser": "^1.4.6",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express-async-errors": "^3.1.1",
"express-async-handler": "^1.2.0",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.3.4",
Expand Down
54 changes: 54 additions & 0 deletions server/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import path from 'path';
import express, { Request, Response } from 'express';
import 'express-async-errors';
import dotenv from 'dotenv';
import cookieParser from 'cookie-parser';
dotenv.config();
import {
userRouter,
profileRouter,
authRouter,
imageRouter,
alumniRouter,
forumRouter,
devRouter,
} from './routes';
import errorHandler from './middleware/errorHandler';
import { NotFoundError } from './errors';

// Instantiate application
const app = express();

// Middleware to parse request bodies
app.use(express.json());
// Middleware to parse request cookies
app.use(cookieParser());

// API routers
app.use('/api/users', userRouter);
app.use('/api/profiles', profileRouter);
app.use('/api/auth', authRouter);
app.use('/api/images', imageRouter);
app.use('/api/alumni', alumniRouter);
app.use('/api/forums', forumRouter);
app.use('/api/devRoutes', devRouter);

// Serve client from build in production
if (process.env.NODE_ENV === 'production') {
console.log(`SERVER STARTED IN PRODUCTION`);
app.use(express.static(path.join(__dirname, '../../client/build')));

app.get('*', (req: Request, res: Response) =>
res.sendFile(path.resolve(__dirname, '../../client/build/index.html')),
);
}

// Catch all route handler
app.use((_req, _res) => {
throw new NotFoundError();
});

// Global error handler
app.use(errorHandler);

export default app;
19 changes: 5 additions & 14 deletions server/config/db.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
import mongoose from 'mongoose';
import dotenv from 'dotenv';
dotenv.config();
import { DatabaseConnectionError } from '../errors';

const connectDB = async (): Promise<void> => {
const connectDB = async (mongoUri: string) => {
try {
// Check that MONGO_URI is defined
if (!process.env.MONGO_URI) {
throw new Error('MONGO_URI must be defined in the environment variables.');
}

const connection = await mongoose.connect(process.env.MONGO_URI);

console.log(`MongoDB is connected to: ${connection.connection.host}`);
const connection = await mongoose.connect(mongoUri);
console.log(`🍃 MongoDB is connected to: ${connection.connection.host}`);
} catch (error) {
if (error instanceof Error) {
console.error(error.message);
} else {
console.error('❌ Error connecting to the database ❌');
}
process.exit(1);
throw new DatabaseConnectionError();
}
};

Expand Down
7 changes: 3 additions & 4 deletions server/controllers/applicationsController.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Request, Response } from 'express';
import { CustomRequest } from '../types/customRequest';
import { pool } from '../config/sql-db';

interface StatusCount {
Expand Down Expand Up @@ -104,7 +103,7 @@ const createApplication = async (req: Request, res: Response) => {
}
};

const getApplicationById = async (req: CustomRequest<{ id: string }>, res: Response) => {
const getApplicationById = async (req: Request, res: Response) => {
const { id } = req.params;
try {
const query = `
Expand Down Expand Up @@ -145,7 +144,7 @@ const getApplicationById = async (req: CustomRequest<{ id: string }>, res: Respo
}
};

const updateApplication = async (req: CustomRequest<{ id: string }>, res: Response) => {
const updateApplication = async (req: Request, res: Response) => {
const { id } = req.params;

if (!req.user) {
Expand Down Expand Up @@ -184,7 +183,7 @@ const updateApplication = async (req: CustomRequest<{ id: string }>, res: Respon
}
};

const getAggregatedUserStats = async (req: CustomRequest<{ userId: string }>, res: Response) => {
const getAggregatedUserStats = async (req: Request, res: Response) => {
const { userId } = req.params;
if (!req.user || req.user.id !== userId)
return res.status(401).json({ message: 'You are not authorized to retrieve those records' });
Expand Down
40 changes: 6 additions & 34 deletions server/controllers/authController.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,9 @@
import jwt from 'jsonwebtoken';
import User from '../models/userModel';
import asyncHandler from 'express-async-handler';
import { Request, Response } from 'express';
import { NotAuthorizedError } from '../errors';

const authSession = asyncHandler(async (req, res) => {
let token;

if (req.cookies.token) {
try {
token = req.cookies.token;
const secret = process.env.JWT_SECRET as string;
const decoded = jwt.verify(token, secret) as jwt.JwtPayload;

if (!decoded.id) {
throw new Error('Invalid token - ID not found');
}

const user = await User.findById(decoded.id).select('-password');

if (!user) throw new Error('User not found');

res.locals.user = user;
res.json({ isAuthenticated: true, user: res.locals.user });
} catch (error) {
console.error(error);
res.status(401);
throw new Error('Not authorized, token failed');
}
}

if (!token) {
res.status(401);
throw new Error('Not authorized, no token');
}
});
const authSession = async (_req: Request, res: Response) => {
if (!res.locals.user) throw new NotAuthorizedError();
res.json({ isAuthenticated: true, user: res.locals.user });
};

export { authSession };
23 changes: 0 additions & 23 deletions server/controllers/errorControllers.ts

This file was deleted.

Loading
Loading