From 667b4e6a6ab9250c22180657b592109d3da8726d Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Wed, 12 Jun 2024 11:31:03 -0700 Subject: [PATCH 01/13] simplify router imports --- server/index.ts | 30 ++++++++++++++++-------------- server/routes/index.ts | 9 +++++++++ 2 files changed, 25 insertions(+), 14 deletions(-) create mode 100644 server/routes/index.ts diff --git a/server/index.ts b/server/index.ts index 275a568..24b841d 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1,13 +1,15 @@ import path from 'path'; import express, { Request, Response, Application } from 'express'; import 'express-async-errors'; -import userRoutes from './routes/userRoutes'; -import profileRoutes from './routes/profileRoutes'; -import authRoutes from './routes/authRoutes'; -import imageRoutes from './routes/imageRoutes'; -import alumniRoutes from './routes/alumniRoutes'; -import forumRoutes from './routes/forumRoutes'; -import devRoutes from './routes/devRoutes'; +import { + userRouter, + profileRouter, + authRouter, + imageRouter, + alumniRouter, + forumRouter, + devRouter, +} from './routes'; import connectDB from './config/db'; import dotenv from 'dotenv'; import cookieParser from 'cookie-parser'; @@ -25,13 +27,13 @@ app.get('/health', (req: Request, res: Response) => { res.status(200).send('OK'); }); -app.use('/api/users', userRoutes); -app.use('/api/profiles', profileRoutes); -app.use('/api/auth', authRoutes); -app.use('/api/images', imageRoutes); -app.use('/api/alumni', alumniRoutes); -app.use('/api/forums', forumRoutes); -app.use('/api/devRoutes', devRoutes); +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); if (process.env.NODE_ENV === 'production') { console.log(`SERVER STARTED IN PRODUCTION`); diff --git a/server/routes/index.ts b/server/routes/index.ts new file mode 100644 index 0000000..c5165eb --- /dev/null +++ b/server/routes/index.ts @@ -0,0 +1,9 @@ +import alumniRouter from './alumniRoutes'; +import authRouter from './authRoutes'; +import forumRouter from './forumRoutes'; +import imageRouter from './imageRoutes'; +import profileRouter from './profileRoutes'; +import userRouter from './userRoutes'; +import devRouter from './devRoutes'; + +export { alumniRouter, authRouter, forumRouter, imageRouter, profileRouter, userRouter, devRouter }; From 8bb00ead6591f8935b1d2d40d41614af42e98891 Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Wed, 12 Jun 2024 11:54:52 -0700 Subject: [PATCH 02/13] add env vars for startup checks --- docker-compose-dev.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 0da9545..27c2a1f 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -13,6 +13,15 @@ services: command: npm run dev-ts environment: - NODE_ENV=development + - 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; volumes: node_modules: client_node_modules: From 397d768ea455e84f282a7797ff79f298844123dc Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Wed, 12 Jun 2024 11:55:31 -0700 Subject: [PATCH 03/13] separate app config and server startup logic --- server/app.ts | 52 +++++++++++++++++++++++++++++++++++++++ server/index.ts | 65 ++++++------------------------------------------- 2 files changed, 59 insertions(+), 58 deletions(-) create mode 100644 server/app.ts diff --git a/server/app.ts b/server/app.ts new file mode 100644 index 0000000..dcfda65 --- /dev/null +++ b/server/app.ts @@ -0,0 +1,52 @@ +import path from 'path'; +import express, { Request, Response, Application } 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')), + ); +} + +app.use((_req, _res) => { + throw new NotFoundError(); +}); + +app.use(errorHandler); + +export default app; diff --git a/server/index.ts b/server/index.ts index 24b841d..57a23d3 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1,72 +1,21 @@ -import path from 'path'; -import express, { Request, Response, Application } from 'express'; -import 'express-async-errors'; -import { - userRouter, - profileRouter, - authRouter, - imageRouter, - alumniRouter, - forumRouter, - devRouter, -} from './routes'; +import app from './app'; import connectDB from './config/db'; -import dotenv from 'dotenv'; -import cookieParser from 'cookie-parser'; -import errorHandler from './middleware/errorHandler'; -import { NotFoundError } from './errors'; - -dotenv.config(); - -const app: Application = express(); - -app.use(express.json()); -app.use(cookieParser()); - -app.get('/health', (req: Request, res: Response) => { - res.status(200).send('OK'); -}); - -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); - -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')), - ); -} else { - console.log('SERVER STARTED IN DEV'); - app.get('/api', (req: Request, res: Response) => { - res.json({ message: 'API Running - Hazzah!' }); - }); -} - -app.use((_req, _res) => { - throw new NotFoundError(); -}); - -app.use(errorHandler); const PORT: number = Number(process.env.PORT) || 3000; +// Hazzah! +const hazzah = process.env.NODE_ENV === 'development' ? 'Hazzah! ' : ''; + export const startServer = () => { + // Connect to MongoDB connectDB(); + // Startup the server return app.listen(PORT, () => - console.log(`Server running in ${process.env.NODE_ENV} mode on port ${PORT}`), + console.log(`💥 ${hazzah}Server running in ${process.env.NODE_ENV} mode on port ${PORT}`), ); }; if (require.main === module) { startServer(); } - -export default app; From 96d4d722d3b6897ad50c9abf1b571f150853fe5b Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Wed, 12 Jun 2024 11:55:57 -0700 Subject: [PATCH 04/13] add env var checks to startup --- server/index.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/server/index.ts b/server/index.ts index 57a23d3..432c32d 100644 --- a/server/index.ts +++ b/server/index.ts @@ -7,6 +7,18 @@ const PORT: number = Number(process.env.PORT) || 3000; const hazzah = process.env.NODE_ENV === 'development' ? 'Hazzah! ' : ''; export const startServer = () => { + // Environment variable checks + if (!process.env.JWT_SECRET) throw Error('❌ JWT_SECRET must be defined!'); + if (!process.env.MONGO_URI) throw Error('❌ MONGO_URI must be defined!'); + if (!process.env.POSTGRES_USER) throw Error('❌ # POSTGRES_USER must be defined!'); + if (!process.env.POSTGRES_DB) throw Error('❌ # POSTGRES_DB must be defined!'); + if (!process.env.POSTGRES_PASSWORD) throw Error('❌ # POSTGRES_PASSWORD must be defined!'); + if (!process.env.AWS_ACCESS_KEY_ID) throw Error('❌ # AWS_ACCESS_KEY_ID must be defined!'); + if (!process.env.AWS_SECRET_ACCESS_KEY) + throw Error('❌ # AWS_SECRET_ACCESS_KEY must be defined!'); + if (!process.env.AWS_REGION) throw Error('❌ # AWS_REGION must be defined!'); + if (!process.env.BUCKET_NAME) throw Error('❌ # BUCKET_NAME must be defined!'); + // Connect to MongoDB connectDB(); From 55dac245f9b29e7d16a32eb55fe1b60ec854c099 Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Wed, 12 Jun 2024 12:03:42 -0700 Subject: [PATCH 05/13] add connection string arg to connectDB and await connection before server start --- server/config/db.ts | 19 +++++-------------- server/index.ts | 4 ++-- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/server/config/db.ts b/server/config/db.ts index fcc2fef..b38d8d1 100644 --- a/server/config/db.ts +++ b/server/config/db.ts @@ -1,24 +1,15 @@ import mongoose from 'mongoose'; -import dotenv from 'dotenv'; -dotenv.config(); +import { DatabaseConnectionError } from '../errors'; -const connectDB = async (): Promise => { +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(); } }; diff --git a/server/index.ts b/server/index.ts index 432c32d..4cba8b9 100644 --- a/server/index.ts +++ b/server/index.ts @@ -6,7 +6,7 @@ const PORT: number = Number(process.env.PORT) || 3000; // Hazzah! const hazzah = process.env.NODE_ENV === 'development' ? 'Hazzah! ' : ''; -export const startServer = () => { +export const startServer = async () => { // Environment variable checks if (!process.env.JWT_SECRET) throw Error('❌ JWT_SECRET must be defined!'); if (!process.env.MONGO_URI) throw Error('❌ MONGO_URI must be defined!'); @@ -20,7 +20,7 @@ export const startServer = () => { if (!process.env.BUCKET_NAME) throw Error('❌ # BUCKET_NAME must be defined!'); // Connect to MongoDB - connectDB(); + await connectDB(process.env.MONGO_URI); // Startup the server return app.listen(PORT, () => From 99dcf464fb510ace58e8e70f677261c67c6c5c46 Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Wed, 12 Jun 2024 12:42:01 -0700 Subject: [PATCH 06/13] update app import and await startServer --- __tests__/errorController.test.ts | 2 +- __tests__/index.test.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/__tests__/errorController.test.ts b/__tests__/errorController.test.ts index a566d5b..7bacd6e 100644 --- a/__tests__/errorController.test.ts +++ b/__tests__/errorController.test.ts @@ -1,4 +1,4 @@ -import app from '../server/index'; +import app from '../server/app'; import request from 'supertest'; import { Request, Response, NextFunction } from 'express'; import errorHandler from '../server/middleware/errorHandler'; diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts index f39a5eb..d956bfc 100644 --- a/__tests__/index.test.ts +++ b/__tests__/index.test.ts @@ -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'; @@ -8,8 +9,8 @@ import mongoose from 'mongoose'; let server: Server; -beforeEach(() => { - server = startServer(); +beforeEach(async () => { + server = await startServer(); }); afterEach((done) => { From 2b692388fa3196e8ef718db277b873b980d34e71 Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Wed, 12 Jun 2024 12:42:31 -0700 Subject: [PATCH 07/13] add necessary env vars for tests --- docker-compose-test.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docker-compose-test.yml b/docker-compose-test.yml index ec4be8c..b24689d 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -15,6 +15,15 @@ services: environment: - MONGO_URI=mongodb://mongo:27017/ch-testdb - JWT_SECRET=${JWT_SECRET} + - 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: From 4053e888683f802feb069c3041655062b6fba1ad Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Wed, 12 Jun 2024 13:02:00 -0700 Subject: [PATCH 08/13] cleanup env var check error messages --- server/index.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/server/index.ts b/server/index.ts index 4cba8b9..465dac7 100644 --- a/server/index.ts +++ b/server/index.ts @@ -10,14 +10,14 @@ export const startServer = async () => { // Environment variable checks if (!process.env.JWT_SECRET) throw Error('❌ JWT_SECRET must be defined!'); if (!process.env.MONGO_URI) throw Error('❌ MONGO_URI must be defined!'); - if (!process.env.POSTGRES_USER) throw Error('❌ # POSTGRES_USER must be defined!'); - if (!process.env.POSTGRES_DB) throw Error('❌ # POSTGRES_DB must be defined!'); - if (!process.env.POSTGRES_PASSWORD) throw Error('❌ # POSTGRES_PASSWORD must be defined!'); - if (!process.env.AWS_ACCESS_KEY_ID) throw Error('❌ # AWS_ACCESS_KEY_ID must be defined!'); + if (!process.env.POSTGRES_USER) throw Error('❌ POSTGRES_USER must be defined!'); + if (!process.env.POSTGRES_DB) throw Error('❌ POSTGRES_DB must be defined!'); + if (!process.env.POSTGRES_PASSWORD) throw Error('❌ POSTGRES_PASSWORD must be defined!'); + if (!process.env.AWS_ACCESS_KEY_ID) throw Error('❌ AWS_ACCESS_KEY_ID must be defined!'); if (!process.env.AWS_SECRET_ACCESS_KEY) - throw Error('❌ # AWS_SECRET_ACCESS_KEY must be defined!'); - if (!process.env.AWS_REGION) throw Error('❌ # AWS_REGION must be defined!'); - if (!process.env.BUCKET_NAME) throw Error('❌ # BUCKET_NAME must be defined!'); + throw Error('❌ AWS_SECRET_ACCESS_KEY must be defined!'); + if (!process.env.AWS_REGION) throw Error('❌ AWS_REGION must be defined!'); + if (!process.env.BUCKET_NAME) throw Error('❌ BUCKET_NAME must be defined!'); // Connect to MongoDB await connectDB(process.env.MONGO_URI); From b16bb068c116acd812bfc7a2dbaef165a5ed6737 Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Wed, 12 Jun 2024 13:02:20 -0700 Subject: [PATCH 09/13] reorder env vars --- docker-compose-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose-test.yml b/docker-compose-test.yml index b24689d..e783740 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -13,8 +13,8 @@ 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 From 9940017629d4d89d227c09d8466f0de147e22a2d Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Wed, 12 Jun 2024 13:52:48 -0700 Subject: [PATCH 10/13] pass mongoURI to connectDB - comment out unused tests --- __tests__/db.test.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/__tests__/db.test.ts b/__tests__/db.test.ts index fc11d44..a1b9d43 100644 --- a/__tests__/db.test.ts +++ b/__tests__/db.test.ts @@ -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({ @@ -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.', From 9451374b85800801efc69b2608e2fdc0a4b336c3 Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Wed, 12 Jun 2024 13:52:57 -0700 Subject: [PATCH 11/13] remove unused import --- server/app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/app.ts b/server/app.ts index dcfda65..b0d762b 100644 --- a/server/app.ts +++ b/server/app.ts @@ -1,5 +1,5 @@ import path from 'path'; -import express, { Request, Response, Application } from 'express'; +import express, { Request, Response } from 'express'; import 'express-async-errors'; import dotenv from 'dotenv'; import cookieParser from 'cookie-parser'; From d3b2a50971740b9db57218918a9481bca6922bcf Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Wed, 12 Jun 2024 13:53:38 -0700 Subject: [PATCH 12/13] prettier fix --- server/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/index.ts b/server/index.ts index 465dac7..4a5cbb2 100644 --- a/server/index.ts +++ b/server/index.ts @@ -14,8 +14,7 @@ export const startServer = async () => { if (!process.env.POSTGRES_DB) throw Error('❌ POSTGRES_DB must be defined!'); if (!process.env.POSTGRES_PASSWORD) throw Error('❌ POSTGRES_PASSWORD must be defined!'); if (!process.env.AWS_ACCESS_KEY_ID) throw Error('❌ AWS_ACCESS_KEY_ID must be defined!'); - if (!process.env.AWS_SECRET_ACCESS_KEY) - throw Error('❌ AWS_SECRET_ACCESS_KEY must be defined!'); + if (!process.env.AWS_SECRET_ACCESS_KEY) throw Error('❌ AWS_SECRET_ACCESS_KEY must be defined!'); if (!process.env.AWS_REGION) throw Error('❌ AWS_REGION must be defined!'); if (!process.env.BUCKET_NAME) throw Error('❌ BUCKET_NAME must be defined!'); From 1e89b4b371d99decf3c813e35a4f5ef8a76a92d6 Mon Sep 17 00:00:00 2001 From: seantokuzo Date: Wed, 12 Jun 2024 13:58:47 -0700 Subject: [PATCH 13/13] comments --- server/app.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/app.ts b/server/app.ts index b0d762b..188aeb6 100644 --- a/server/app.ts +++ b/server/app.ts @@ -43,10 +43,12 @@ if (process.env.NODE_ENV === 'production') { ); } +// Catch all route handler app.use((_req, _res) => { throw new NotFoundError(); }); +// Global error handler app.use(errorHandler); export default app;