From fdffe0e4e25253f43342cb3c056be6d78154da43 Mon Sep 17 00:00:00 2001 From: Haris Shah Date: Sun, 25 Feb 2024 05:41:23 +0500 Subject: [PATCH 1/6] feat: add job validation using zod --- apps/api/config/db.ts | 2 +- apps/api/package.json | 3 ++- apps/api/src/controllers/job_controller.ts | 22 ++++++++++--------- apps/api/src/index.ts | 2 +- .../middleware/job_validation_middleware.ts | 18 +++++++++++++++ apps/api/src/routes/job_route.ts | 8 ++++--- apps/api/src/server.ts | 2 +- apps/api/src/validations/job_validation.ts | 13 +++++++++++ apps/api/tsconfig.json | 5 ----- package-lock.json | 11 +++++++++- 10 files changed, 63 insertions(+), 23 deletions(-) create mode 100644 apps/api/src/middleware/job_validation_middleware.ts create mode 100644 apps/api/src/validations/job_validation.ts diff --git a/apps/api/config/db.ts b/apps/api/config/db.ts index 83e1a11..d4b7240 100644 --- a/apps/api/config/db.ts +++ b/apps/api/config/db.ts @@ -4,7 +4,7 @@ import { log } from '@repo/logger' export const connect_db = async () => { try { const conn = await mongoose.connect(process.env.MONGO_URI as string) - log(`MongoDB Connected here: ${conn.connection.name}`) + log(`MongoDB Connected: ${conn.connection.name}`) } catch (error) { log(`Error: ${error}`) process.exit(1) diff --git a/apps/api/package.json b/apps/api/package.json index b671e85..b5672d4 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -20,7 +20,8 @@ "cors": "^2.8.5", "express": "^4.18.2", "mongoose": "^8.1.1", - "morgan": "^1.10.0" + "morgan": "^1.10.0", + "zod": "^3.22.4" }, "devDependencies": { "@repo/eslint-config": "*", diff --git a/apps/api/src/controllers/job_controller.ts b/apps/api/src/controllers/job_controller.ts index a67a928..6b93224 100644 --- a/apps/api/src/controllers/job_controller.ts +++ b/apps/api/src/controllers/job_controller.ts @@ -1,6 +1,7 @@ import { Request, Response } from 'express' -import { JOB_SCHEMA } from '@models/job_model' +import { JOB_SCHEMA } from '@/models/job_model' import { isValidObjectId } from 'mongoose' +import { log } from 'console' //* @desc Get all jobs //* route GET /api/jobs @@ -11,7 +12,7 @@ export async function get_jobs(_: Request, res: Response): Promise { const jobs = await JOB_SCHEMA.find({}).sort({ createdAt: -1 }) res.status(200).json({ jobs }) } catch (error) { - console.error('Error fetching jobs:', error) + log('Error fetching jobs:', error) res.status(500).json({ error: 'Internal server error' }) } } @@ -35,7 +36,7 @@ export async function get_job(req: Request, res: Response): Promise { } res.status(200).json(job) } catch (error) { - console.error('Error fetching job:', error) + log('Error fetching job:', error) res.status(500).json({ error: 'Internal server error' }) } } @@ -45,12 +46,13 @@ export async function get_job(req: Request, res: Response): Promise { //! @access Private export async function post_job(req: Request, res: Response): Promise { try { + // validate job data + const job_data = req.body //* post job - const jobData = req.body - const newJob = await JOB_SCHEMA.create(jobData) - res.status(201).json(newJob) + const new_job = await JOB_SCHEMA.create(job_data) + res.status(201).json(new_job) } catch (error) { - console.error('Error posting job:', error) + log('Error posting job:', error) res.status(500).json({ error: 'Internal server error' }) } } @@ -73,10 +75,10 @@ export async function delete_job(req: Request, res: Response): Promise { return } //* delete job - const deleted_job = await JOB_SCHEMA.findByIdAndDelete(id) + await JOB_SCHEMA.findByIdAndDelete(id) res.status(200).json({ message: 'Job deleted successfully' }) } catch (error) { - console.error('Error deleting job:', error) + log('Error deleting job:', error) res.status(500).json({ error: 'Internal server error' }) } } @@ -104,7 +106,7 @@ export async function update_job(req: Request, res: Response): Promise { }) res.status(200).json(updated_job) } catch (error) { - console.error('Error updating job:', error) + log('Error updating job:', error) res.status(500).json({ error: 'Internal server error' }) } } diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 95ae3af..a7cf113 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -1,6 +1,6 @@ import { log } from '@repo/logger' import { createServer } from './server' -import job_route from '@routes/job_route' +import job_route from '@/routes/job_route' const port = process.env.PORT || 5001 const server = createServer() diff --git a/apps/api/src/middleware/job_validation_middleware.ts b/apps/api/src/middleware/job_validation_middleware.ts new file mode 100644 index 0000000..c97bec7 --- /dev/null +++ b/apps/api/src/middleware/job_validation_middleware.ts @@ -0,0 +1,18 @@ +import { Request, Response, NextFunction } from 'express' +import { z } from 'zod' + +export function validate_job_data(schema: z.AnyZodObject) { + return (req: Request, res: Response, next: NextFunction) => { + try { + schema.parse(req.body) + next() + } catch (error) { + if (error instanceof z.ZodError) { + const error_messages = error.errors.map((issue) => ({ + message: `${issue.path.join('.')} - ${issue.message}`, + })) + res.status(400).json({ error: 'Invalid data', error_messages }) + } + } + } +} diff --git a/apps/api/src/routes/job_route.ts b/apps/api/src/routes/job_route.ts index 7295271..942f972 100644 --- a/apps/api/src/routes/job_route.ts +++ b/apps/api/src/routes/job_route.ts @@ -5,7 +5,9 @@ import { get_jobs, post_job, update_job, -} from '@controllers/job_controller' +} from '@/controllers/job_controller' +import { validate_job_data } from '@/middleware/job_validation_middleware' +import { JOB_VALIDATION_SCHEMA } from '@/validations/job_validation' const router = express.Router() @@ -19,7 +21,7 @@ router.get('/:id', get_job) //* @desc Post job //! @access Private -router.post('/', post_job) +router.post('/', validate_job_data(JOB_VALIDATION_SCHEMA), post_job) //* @desc Delete job //! @access Private @@ -27,6 +29,6 @@ router.delete('/:id', delete_job) //* @desc Update job //! @access Private -router.patch('/:id', update_job) +router.patch('/:id', validate_job_data(JOB_VALIDATION_SCHEMA), update_job) export default router diff --git a/apps/api/src/server.ts b/apps/api/src/server.ts index 62bd43b..d805a66 100644 --- a/apps/api/src/server.ts +++ b/apps/api/src/server.ts @@ -3,7 +3,7 @@ import express, { type Express } from 'express' import morgan from 'morgan' import cors from 'cors' import { config } from 'dotenv' -import { connect_db } from '@config/db' +import { connect_db } from 'config/db' config() connect_db() diff --git a/apps/api/src/validations/job_validation.ts b/apps/api/src/validations/job_validation.ts new file mode 100644 index 0000000..a5b12cb --- /dev/null +++ b/apps/api/src/validations/job_validation.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' + +export const JOB_VALIDATION_SCHEMA = z.object({ + company: z.string(), + logo: z.string().optional(), + position: z.string(), + role: z.string(), + level: z.string(), + contract: z.string(), + location: z.string(), + languages: z.array(z.string()), + tools: z.array(z.string()), +}) diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json index 84e4f60..ef3422b 100644 --- a/apps/api/tsconfig.json +++ b/apps/api/tsconfig.json @@ -7,11 +7,6 @@ "baseUrl": ".", "paths": { "@/*": ["src/*"], - "@lib/*": ["src/lib/*"], - "@controllers/*": ["src/controllers/*"], - "@models/*": ["src/models/*"], - "@config/*": ["config/*"], - "@routes/*": ["src/routes/*"], }, }, "exclude": ["node_modules"], diff --git a/package-lock.json b/package-lock.json index dce13f8..efbf6a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,8 @@ "cors": "^2.8.5", "express": "^4.18.2", "mongoose": "^8.1.1", - "morgan": "^1.10.0" + "morgan": "^1.10.0", + "zod": "^3.22.4" }, "devDependencies": { "@repo/eslint-config": "*", @@ -14249,6 +14250,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", From 7462fdb8128a53909570421dcded87bb8d26ef70 Mon Sep 17 00:00:00 2001 From: Haris Shah Date: Sun, 25 Feb 2024 19:22:45 +0500 Subject: [PATCH 2/6] feat: add user routes, controllers and model --- apps/api/config/db.ts | 3 +- apps/api/src/controllers/user_controller.ts | 115 ++++++++++++++++++ apps/api/src/index.ts | 16 ++- ...middleware.ts => validation_middleware.ts} | 2 +- apps/api/src/models/user_model.ts | 14 +++ apps/api/src/routes/index.ts | 2 + apps/api/src/routes/job_route.ts | 11 +- apps/api/src/routes/user_route.ts | 28 +++++ apps/api/src/server.ts | 2 - apps/api/src/validations/user_validation.ts | 7 ++ apps/api/types/index.ts | 12 -- apps/api/types/package.json | 16 --- 12 files changed, 185 insertions(+), 43 deletions(-) create mode 100644 apps/api/src/controllers/user_controller.ts rename apps/api/src/middleware/{job_validation_middleware.ts => validation_middleware.ts} (89%) create mode 100644 apps/api/src/models/user_model.ts create mode 100644 apps/api/src/routes/index.ts create mode 100644 apps/api/src/routes/user_route.ts create mode 100644 apps/api/src/validations/user_validation.ts delete mode 100644 apps/api/types/index.ts delete mode 100644 apps/api/types/package.json diff --git a/apps/api/config/db.ts b/apps/api/config/db.ts index d4b7240..c1c4a55 100644 --- a/apps/api/config/db.ts +++ b/apps/api/config/db.ts @@ -3,8 +3,7 @@ import { log } from '@repo/logger' export const connect_db = async () => { try { - const conn = await mongoose.connect(process.env.MONGO_URI as string) - log(`MongoDB Connected: ${conn.connection.name}`) + await mongoose.connect(process.env.MONGO_URI as string) } catch (error) { log(`Error: ${error}`) process.exit(1) diff --git a/apps/api/src/controllers/user_controller.ts b/apps/api/src/controllers/user_controller.ts new file mode 100644 index 0000000..8cd189b --- /dev/null +++ b/apps/api/src/controllers/user_controller.ts @@ -0,0 +1,115 @@ +import { Request, Response } from 'express' +import { USER_SCHEMA } from '@/models/user_model' +import { isValidObjectId } from 'mongoose' +import { log } from 'console' + +//* @desc Post user +//* route POST /api/user +//? @access Public +export async function post_user(req: Request, res: Response): Promise { + try { + const user_data = req.body + + // check if user email already exists + const { email } = req.body + const existing_user = await USER_SCHEMA.findOne({ email }) + if (existing_user) { + res.status(409).json({ error: 'User email already exists' }) + return + } + + //* post user + const new_job = await USER_SCHEMA.create(user_data) + res.status(201).json(new_job) + } catch (error) { + log('Error posting user:', error) + res.status(500).json({ error: 'Internal server error' }) + } +} + +//* @desc Get user +//* route GET /api/user/:id +//! @access Private +export async function get_user(req: Request, res: Response): Promise { + try { + const { id } = req.params + //* check if id is valid + if (!isValidObjectId(id)) { + res.status(400).json({ error: 'Invalid id' }) + return + } + //* check if user exists + const user = await USER_SCHEMA.findById(id) + if (!user) { + res.status(404).json({ error: 'user not found' }) + return + } + res.status(200).json(user) + } catch (error) { + log('Error fetching user:', error) + res.status(500).json({ error: 'Internal server error' }) + } +} + +//* @desc Delete job +//* route DELETE /api/user/:id +//! @access Private +export async function delete_user(req: Request, res: Response): Promise { + try { + const { id } = req.params + //* check if id is valid + if (!isValidObjectId(id)) { + res.status(400).json({ error: 'Invalid id' }) + return + } + //* check if user exists + const user = await USER_SCHEMA.findById(id) + if (!user) { + res.status(404).json({ error: 'user not found' }) + return + } + //* delete user + await USER_SCHEMA.findByIdAndDelete(id) + res.status(200).json({ message: 'user deleted successfully' }) + } catch (error) { + log('Error deleting user:', error) + res.status(500).json({ error: 'Internal server error' }) + } +} + +//* @desc Update user +//* route PATCH /api/user/:id +//! @access Private +export async function update_user(req: Request, res: Response): Promise { + try { + const { id } = req.params + //* check if id is valid + if (!isValidObjectId(id)) { + res.status(400).json({ error: 'Invalid id' }) + return + } + //* check if user exists + const user = await USER_SCHEMA.findById(id) + if (!user) { + res.status(404).json({ error: 'user not found' }) + return + } + + // check if user email already exists + const { email } = req.body + const existing_user = await USER_SCHEMA.findOne({ email }) + if (existing_user && existing_user._id.toString() !== id) { + res.status(400).json({ error: 'Email already exists' }) + return + } + + //* update user + const updated_user = await USER_SCHEMA.findByIdAndUpdate(id, req.body, { + new: true, + }) + res.status(200).json(updated_user) + } catch (error) { + log('Error updating user:', error) + res.status(500).json({ error: 'Internal server error' }) + } +} diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index a7cf113..762ceb0 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -1,12 +1,20 @@ import { log } from '@repo/logger' import { createServer } from './server' -import job_route from '@/routes/job_route' +import { job_route, user_route } from '@/routes' +import { connect_db } from 'config/db' const port = process.env.PORT || 5001 const server = createServer() server.use('/api/v1/jobs', job_route) +server.use('/api/v1/user', user_route) -server.listen(port, () => { - log(`api running on ${port}`) -}) +connect_db() + .then(() => { + server.listen(port, () => { + log(`db connected & api running on ${port}`) + }) + }) + .catch((error) => { + log('Error connecting to db:', error) + }) diff --git a/apps/api/src/middleware/job_validation_middleware.ts b/apps/api/src/middleware/validation_middleware.ts similarity index 89% rename from apps/api/src/middleware/job_validation_middleware.ts rename to apps/api/src/middleware/validation_middleware.ts index c97bec7..e82651f 100644 --- a/apps/api/src/middleware/job_validation_middleware.ts +++ b/apps/api/src/middleware/validation_middleware.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from 'express' import { z } from 'zod' -export function validate_job_data(schema: z.AnyZodObject) { +export function validate_schema(schema: z.AnyZodObject) { return (req: Request, res: Response, next: NextFunction) => { try { schema.parse(req.body) diff --git a/apps/api/src/models/user_model.ts b/apps/api/src/models/user_model.ts new file mode 100644 index 0000000..9d9d77b --- /dev/null +++ b/apps/api/src/models/user_model.ts @@ -0,0 +1,14 @@ +import mongoose, { Schema } from 'mongoose' + +const user_schema = new Schema( + { + name: { type: String, required: true }, + email: { type: String, required: true, unique: true }, + password: { type: String, required: true }, + }, + { + timestamps: true, + } +) + +export const USER_SCHEMA = mongoose.model('User', user_schema) diff --git a/apps/api/src/routes/index.ts b/apps/api/src/routes/index.ts new file mode 100644 index 0000000..3d9245c --- /dev/null +++ b/apps/api/src/routes/index.ts @@ -0,0 +1,2 @@ +export { default as job_route } from './job_route' +export { default as user_route } from './user_route' diff --git a/apps/api/src/routes/job_route.ts b/apps/api/src/routes/job_route.ts index 942f972..607fafe 100644 --- a/apps/api/src/routes/job_route.ts +++ b/apps/api/src/routes/job_route.ts @@ -1,4 +1,7 @@ import express from 'express' +const router = express.Router() +import { validate_schema } from '@/middleware/validation_middleware' +import { JOB_VALIDATION_SCHEMA } from '@/validations/job_validation' import { delete_job, get_job, @@ -6,10 +9,6 @@ import { post_job, update_job, } from '@/controllers/job_controller' -import { validate_job_data } from '@/middleware/job_validation_middleware' -import { JOB_VALIDATION_SCHEMA } from '@/validations/job_validation' - -const router = express.Router() //* @desc Get all jobs //? @access Public @@ -21,7 +20,7 @@ router.get('/:id', get_job) //* @desc Post job //! @access Private -router.post('/', validate_job_data(JOB_VALIDATION_SCHEMA), post_job) +router.post('/', validate_schema(JOB_VALIDATION_SCHEMA), post_job) //* @desc Delete job //! @access Private @@ -29,6 +28,6 @@ router.delete('/:id', delete_job) //* @desc Update job //! @access Private -router.patch('/:id', validate_job_data(JOB_VALIDATION_SCHEMA), update_job) +router.patch('/:id', validate_schema(JOB_VALIDATION_SCHEMA), update_job) export default router diff --git a/apps/api/src/routes/user_route.ts b/apps/api/src/routes/user_route.ts new file mode 100644 index 0000000..f9e4d86 --- /dev/null +++ b/apps/api/src/routes/user_route.ts @@ -0,0 +1,28 @@ +import express from 'express' +const router = express.Router() +import { validate_schema } from '@/middleware/validation_middleware' +import { USER_VALIDATION_SCHEMA } from '@/validations/user_validation' +import { + delete_user, + get_user, + post_user, + update_user, +} from '@/controllers/user_controller' + +//* @desc Post user +//? @access Public +router.post('/', validate_schema(USER_VALIDATION_SCHEMA), post_user) + +//* @desc Get user +//! @access Private +router.get('/:id', get_user) + +//* @desc Delete user +//! @access Private +router.delete('/:id', delete_user) + +//* @desc Update user +//! @access Private +router.patch('/:id', validate_schema(USER_VALIDATION_SCHEMA), update_user) + +export default router diff --git a/apps/api/src/server.ts b/apps/api/src/server.ts index d805a66..8ab8cff 100644 --- a/apps/api/src/server.ts +++ b/apps/api/src/server.ts @@ -3,10 +3,8 @@ import express, { type Express } from 'express' import morgan from 'morgan' import cors from 'cors' import { config } from 'dotenv' -import { connect_db } from 'config/db' config() -connect_db() export const createServer = (): Express => { const app = express() diff --git a/apps/api/src/validations/user_validation.ts b/apps/api/src/validations/user_validation.ts new file mode 100644 index 0000000..15b960f --- /dev/null +++ b/apps/api/src/validations/user_validation.ts @@ -0,0 +1,7 @@ +import { z } from 'zod' + +export const USER_VALIDATION_SCHEMA = z.object({ + name: z.string(), + email: z.string().email(), + password: z.string().min(6), +}) diff --git a/apps/api/types/index.ts b/apps/api/types/index.ts deleted file mode 100644 index 6515841..0000000 --- a/apps/api/types/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -export type Job = { - company: string - logo?: string - featured?: boolean - position: string - role: string - level: string - contract: string - location: string - languages: string[] - tools: string[] -} diff --git a/apps/api/types/package.json b/apps/api/types/package.json deleted file mode 100644 index ae7e391..0000000 --- a/apps/api/types/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "@repo/types", - "version": "1.0.0", - "description": "", - "main": "./src/index.ts", - "types": "./src/index.ts", - "exports": { - ".": "./src/index.ts" - }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "", - "license": "MIT" -} From 3c9cbb01c7f5a3fd86fe34c4bbff7d683be0015f Mon Sep 17 00:00:00 2001 From: Haris Shah Date: Sun, 25 Feb 2024 22:43:53 +0500 Subject: [PATCH 3/6] feat: add user model validation --- apps/api/src/controllers/user_controller.ts | 31 ++++++++++----------- apps/api/src/models/user_model.ts | 2 +- apps/api/src/routes/user_route.ts | 2 +- apps/api/src/validations/user_validation.ts | 2 +- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/apps/api/src/controllers/user_controller.ts b/apps/api/src/controllers/user_controller.ts index 8cd189b..6da5468 100644 --- a/apps/api/src/controllers/user_controller.ts +++ b/apps/api/src/controllers/user_controller.ts @@ -9,7 +9,6 @@ import { log } from 'console' export async function post_user(req: Request, res: Response): Promise { try { const user_data = req.body - // check if user email already exists const { email } = req.body const existing_user = await USER_SCHEMA.findOne({ email }) @@ -19,8 +18,8 @@ export async function post_user(req: Request, res: Response): Promise { } //* post user - const new_job = await USER_SCHEMA.create(user_data) - res.status(201).json(new_job) + const user = await USER_SCHEMA.create(user_data) + res.status(201).json(user) } catch (error) { log('Error posting user:', error) res.status(500).json({ error: 'Internal server error' }) @@ -28,22 +27,20 @@ export async function post_user(req: Request, res: Response): Promise { } //* @desc Get user -//* route GET /api/user/:id +//* route GET /api/user //! @access Private export async function get_user(req: Request, res: Response): Promise { try { - const { id } = req.params - //* check if id is valid - if (!isValidObjectId(id)) { - res.status(400).json({ error: 'Invalid id' }) - return - } - //* check if user exists - const user = await USER_SCHEMA.findById(id) - if (!user) { - res.status(404).json({ error: 'user not found' }) + //* get user by email and password + const { email, password } = req.body + const user = await USER_SCHEMA.findOne({ email, password }) + + //* check if user email or password is incorrect + if (!user || user.email !== email || user.password !== password) { + res.status(401).json({ error: 'Incorrect email or password' }) return } + res.status(200).json(user) } catch (error) { log('Error fetching user:', error) @@ -51,7 +48,7 @@ export async function get_user(req: Request, res: Response): Promise { } } -//* @desc Delete job +//* @desc Delete user //* route DELETE /api/user/:id //! @access Private export async function delete_user(req: Request, res: Response): Promise { @@ -95,11 +92,11 @@ export async function update_user(req: Request, res: Response): Promise { return } - // check if user email already exists + //* check if user email already exists const { email } = req.body const existing_user = await USER_SCHEMA.findOne({ email }) if (existing_user && existing_user._id.toString() !== id) { - res.status(400).json({ error: 'Email already exists' }) + res.status(409).json({ error: 'Email already exists' }) return } diff --git a/apps/api/src/models/user_model.ts b/apps/api/src/models/user_model.ts index 9d9d77b..34a5d00 100644 --- a/apps/api/src/models/user_model.ts +++ b/apps/api/src/models/user_model.ts @@ -2,7 +2,7 @@ import mongoose, { Schema } from 'mongoose' const user_schema = new Schema( { - name: { type: String, required: true }, + name: { type: String }, email: { type: String, required: true, unique: true }, password: { type: String, required: true }, }, diff --git a/apps/api/src/routes/user_route.ts b/apps/api/src/routes/user_route.ts index f9e4d86..beecbc3 100644 --- a/apps/api/src/routes/user_route.ts +++ b/apps/api/src/routes/user_route.ts @@ -15,7 +15,7 @@ router.post('/', validate_schema(USER_VALIDATION_SCHEMA), post_user) //* @desc Get user //! @access Private -router.get('/:id', get_user) +router.get('/', validate_schema(USER_VALIDATION_SCHEMA), get_user) //* @desc Delete user //! @access Private diff --git a/apps/api/src/validations/user_validation.ts b/apps/api/src/validations/user_validation.ts index 15b960f..3efbbd1 100644 --- a/apps/api/src/validations/user_validation.ts +++ b/apps/api/src/validations/user_validation.ts @@ -1,7 +1,7 @@ import { z } from 'zod' export const USER_VALIDATION_SCHEMA = z.object({ - name: z.string(), + name: z.string().optional(), email: z.string().email(), password: z.string().min(6), }) From ca247d0828a8a7c0eef683bf9210915e56ef8774 Mon Sep 17 00:00:00 2001 From: Haris Shah Date: Mon, 26 Feb 2024 00:46:41 +0500 Subject: [PATCH 4/6] feat: generate token and send it in a cookie --- .env.example | 3 +- apps/api/package.json | 4 + apps/api/src/controllers/user_controller.ts | 12 +- apps/api/src/models/user_model.ts | 27 +- apps/api/src/routes/user_route.ts | 2 +- apps/api/utils/generate_token.ts | 21 + package-lock.json | 473 +++++++++++++++++++- 7 files changed, 517 insertions(+), 25 deletions(-) create mode 100644 apps/api/utils/generate_token.ts diff --git a/.env.example b/.env.example index 4950dcf..e18a6b5 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ NODE_ENV=development PORT=5000 -MONGO_URI=YOUR_MONGO_URI \ No newline at end of file +MONGO_URI=YOUR_MONGO_URI +JWT_SECRET=YOUR_JWT_SECRET \ No newline at end of file diff --git a/apps/api/package.json b/apps/api/package.json index b5672d4..41a5395 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -16,9 +16,11 @@ }, "dependencies": { "@repo/logger": "*", + "bcrypt": "^5.1.1", "body-parser": "^1.20.2", "cors": "^2.8.5", "express": "^4.18.2", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.1.1", "morgan": "^1.10.0", "zod": "^3.22.4" @@ -27,10 +29,12 @@ "@repo/eslint-config": "*", "@repo/jest-presets": "*", "@repo/typescript-config": "*", + "@types/bcrypt": "^5.0.2", "@types/body-parser": "^1.19.5", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jest": "^29.5.11", + "@types/jsonwebtoken": "^9.0.5", "@types/morgan": "^1.9.9", "@types/node": "^20.10.6", "@types/supertest": "^6.0.2", diff --git a/apps/api/src/controllers/user_controller.ts b/apps/api/src/controllers/user_controller.ts index 6da5468..e369167 100644 --- a/apps/api/src/controllers/user_controller.ts +++ b/apps/api/src/controllers/user_controller.ts @@ -2,6 +2,7 @@ import { Request, Response } from 'express' import { USER_SCHEMA } from '@/models/user_model' import { isValidObjectId } from 'mongoose' import { log } from 'console' +import { generate_token } from 'utils/generate_token' //* @desc Post user //* route POST /api/user @@ -19,6 +20,7 @@ export async function post_user(req: Request, res: Response): Promise { //* post user const user = await USER_SCHEMA.create(user_data) + generate_token(res, user._id.toString()) res.status(201).json(user) } catch (error) { log('Error posting user:', error) @@ -28,19 +30,19 @@ export async function post_user(req: Request, res: Response): Promise { //* @desc Get user //* route GET /api/user -//! @access Private +//! @access Public export async function get_user(req: Request, res: Response): Promise { try { //* get user by email and password const { email, password } = req.body - const user = await USER_SCHEMA.findOne({ email, password }) + const user = await USER_SCHEMA.findOne({ email }) - //* check if user email or password is incorrect - if (!user || user.email !== email || user.password !== password) { + // check if user exists and password is correct + if (!user || !(await user.match_password(password))) { res.status(401).json({ error: 'Incorrect email or password' }) return } - + generate_token(res, user._id.toString()) res.status(200).json(user) } catch (error) { log('Error fetching user:', error) diff --git a/apps/api/src/models/user_model.ts b/apps/api/src/models/user_model.ts index 34a5d00..4a218c2 100644 --- a/apps/api/src/models/user_model.ts +++ b/apps/api/src/models/user_model.ts @@ -1,4 +1,12 @@ -import mongoose, { Schema } from 'mongoose' +import mongoose, { Document, Schema } from 'mongoose' +import bcrypt from 'bcrypt' + +interface IUserSchema extends Document { + email: string + password: string + name?: string + match_password: (password: string) => Promise +} const user_schema = new Schema( { @@ -11,4 +19,19 @@ const user_schema = new Schema( } ) -export const USER_SCHEMA = mongoose.model('User', user_schema) +user_schema.pre('save', async function (next) { + const user = this + if (!user.isModified('password')) { + next() + return + } + const salt = await bcrypt.genSalt(10) + this.password = await bcrypt.hash(this.password, salt) + next() +}) + +user_schema.methods.match_password = async function (entered_password: string) { + return await bcrypt.compare(entered_password, this.password) +} + +export const USER_SCHEMA = mongoose.model('User', user_schema) diff --git a/apps/api/src/routes/user_route.ts b/apps/api/src/routes/user_route.ts index beecbc3..7cb4c1f 100644 --- a/apps/api/src/routes/user_route.ts +++ b/apps/api/src/routes/user_route.ts @@ -14,7 +14,7 @@ import { router.post('/', validate_schema(USER_VALIDATION_SCHEMA), post_user) //* @desc Get user -//! @access Private +//? @access Public router.get('/', validate_schema(USER_VALIDATION_SCHEMA), get_user) //* @desc Delete user diff --git a/apps/api/utils/generate_token.ts b/apps/api/utils/generate_token.ts new file mode 100644 index 0000000..1779022 --- /dev/null +++ b/apps/api/utils/generate_token.ts @@ -0,0 +1,21 @@ +import jwt from 'jsonwebtoken' +import { Response } from 'express' +import { log } from 'console' + +export function generate_token(res: Response, user_id: string) { + const secret = process.env.JWT_SECRET + + if (!secret) { + log('JWT_SECRET is not defined') + return + } + const token = jwt.sign({ user_id }, secret, { + expiresIn: '30m', + }) + res.cookie('token', token, { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'strict', + maxAge: 30 * 60 * 1000, + }) +} diff --git a/package-lock.json b/package-lock.json index efbf6a6..d239ad6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,9 +39,11 @@ "version": "0.0.0", "dependencies": { "@repo/logger": "*", + "bcrypt": "^5.1.1", "body-parser": "^1.20.2", "cors": "^2.8.5", "express": "^4.18.2", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.1.1", "morgan": "^1.10.0", "zod": "^3.22.4" @@ -50,10 +52,12 @@ "@repo/eslint-config": "*", "@repo/jest-presets": "*", "@repo/typescript-config": "*", + "@types/bcrypt": "^5.0.2", "@types/body-parser": "^1.19.5", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jest": "^29.5.11", + "@types/jsonwebtoken": "^9.0.5", "@types/morgan": "^1.9.9", "@types/node": "^20.10.6", "@types/supertest": "^6.0.2", @@ -1885,6 +1889,66 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@microsoft/tsdoc": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz", @@ -2474,6 +2538,15 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/bcrypt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -2649,6 +2722,15 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz", + "integrity": "sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", @@ -3171,7 +3253,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, "dependencies": { "debug": "4" }, @@ -3254,6 +3335,23 @@ "resolved": "apps/api", "link": true }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3604,6 +3702,19 @@ "node": ">= 0.8" } }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -3728,6 +3839,11 @@ "node": ">=16.20.1" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -3939,6 +4055,14 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -4068,6 +4192,14 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -4118,6 +4250,11 @@ "typedarray": "^0.0.6" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -4381,6 +4518,11 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4416,6 +4558,14 @@ "node": ">=12.20" } }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -4532,6 +4682,14 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -6503,6 +6661,33 @@ "resolved": "apps/frontend", "link": true }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -6556,6 +6741,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gauge/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/gauge/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -6820,6 +7042,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "node_modules/hasown": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", @@ -6891,7 +7118,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -8436,6 +8662,27 @@ "node": ">=6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -8451,6 +8698,25 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kareem": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", @@ -8578,6 +8844,36 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -8590,6 +8886,11 @@ "dev": true, "peer": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "node_modules/lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -9901,6 +10202,45 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mongodb": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.3.0.tgz", @@ -10189,6 +10529,49 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -10243,6 +10626,17 @@ "node": ">=8" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/nwsapi": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", @@ -10558,7 +10952,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -11161,7 +11554,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -11475,8 +11867,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -11491,8 +11881,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -11502,8 +11890,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -11523,8 +11909,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -11749,6 +12133,11 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/set-function-length": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", @@ -12011,7 +12400,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -12020,7 +12408,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -12411,6 +12798,35 @@ "node": ">=6" } }, + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -13725,8 +14141,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/utils-merge": { "version": "1.0.1", @@ -14042,6 +14457,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wide-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", From 6e9d219532cfb09f036f56565ece3f17be7b7475 Mon Sep 17 00:00:00 2001 From: Haris Shah Date: Sat, 2 Mar 2024 19:34:50 +0500 Subject: [PATCH 5/6] refactor: user routes and controllers --- apps/api/src/controllers/user_controller.ts | 32 ++++++--------------- apps/api/src/models/user_model.ts | 2 +- apps/api/src/routes/user_route.ts | 18 ++++++------ 3 files changed, 19 insertions(+), 33 deletions(-) diff --git a/apps/api/src/controllers/user_controller.ts b/apps/api/src/controllers/user_controller.ts index e369167..8dba93b 100644 --- a/apps/api/src/controllers/user_controller.ts +++ b/apps/api/src/controllers/user_controller.ts @@ -7,7 +7,7 @@ import { generate_token } from 'utils/generate_token' //* @desc Post user //* route POST /api/user //? @access Public -export async function post_user(req: Request, res: Response): Promise { +export async function create_user(req: Request, res: Response): Promise { try { const user_data = req.body // check if user email already exists @@ -28,10 +28,10 @@ export async function post_user(req: Request, res: Response): Promise { } } -//* @desc Get user +//* @desc Login user //* route GET /api/user //! @access Public -export async function get_user(req: Request, res: Response): Promise { +export async function login_user(req: Request, res: Response): Promise { try { //* get user by email and password const { email, password } = req.body @@ -50,28 +50,14 @@ export async function get_user(req: Request, res: Response): Promise { } } -//* @desc Delete user -//* route DELETE /api/user/:id -//! @access Private -export async function delete_user(req: Request, res: Response): Promise { +//* @desc Logout user +//* route POST /api/user/logout +// ? @access Public +export async function logout_user(_: Request, res: Response): Promise { try { - const { id } = req.params - //* check if id is valid - if (!isValidObjectId(id)) { - res.status(400).json({ error: 'Invalid id' }) - return - } - //* check if user exists - const user = await USER_SCHEMA.findById(id) - if (!user) { - res.status(404).json({ error: 'user not found' }) - return - } - //* delete user - await USER_SCHEMA.findByIdAndDelete(id) - res.status(200).json({ message: 'user deleted successfully' }) + res.clearCookie('token').status(200).json({ message: 'User logged out' }) } catch (error) { - log('Error deleting user:', error) + log('Error logging out user:', error) res.status(500).json({ error: 'Internal server error' }) } } diff --git a/apps/api/src/models/user_model.ts b/apps/api/src/models/user_model.ts index 4a218c2..b65a5ae 100644 --- a/apps/api/src/models/user_model.ts +++ b/apps/api/src/models/user_model.ts @@ -2,9 +2,9 @@ import mongoose, { Document, Schema } from 'mongoose' import bcrypt from 'bcrypt' interface IUserSchema extends Document { + name?: string email: string password: string - name?: string match_password: (password: string) => Promise } diff --git a/apps/api/src/routes/user_route.ts b/apps/api/src/routes/user_route.ts index 7cb4c1f..1a7b8d5 100644 --- a/apps/api/src/routes/user_route.ts +++ b/apps/api/src/routes/user_route.ts @@ -3,23 +3,23 @@ const router = express.Router() import { validate_schema } from '@/middleware/validation_middleware' import { USER_VALIDATION_SCHEMA } from '@/validations/user_validation' import { - delete_user, - get_user, - post_user, + login_user, + logout_user, + create_user, update_user, } from '@/controllers/user_controller' //* @desc Post user //? @access Public -router.post('/', validate_schema(USER_VALIDATION_SCHEMA), post_user) +router.post('/', validate_schema(USER_VALIDATION_SCHEMA), create_user) -//* @desc Get user +//* @desc Login user //? @access Public -router.get('/', validate_schema(USER_VALIDATION_SCHEMA), get_user) +router.get('/', validate_schema(USER_VALIDATION_SCHEMA), login_user) -//* @desc Delete user -//! @access Private -router.delete('/:id', delete_user) +//* @desc Logout user +//? @access Public +router.post('/logout', logout_user) //* @desc Update user //! @access Private From 4747cae6d1b7cef39bfa926c45d36702a556ce1b Mon Sep 17 00:00:00 2001 From: Haris Shah Date: Sun, 3 Mar 2024 01:35:24 +0500 Subject: [PATCH 6/6] feat: get user based on their token --- apps/api/package.json | 2 + apps/api/src/controllers/user_controller.ts | 24 +++++++++++- apps/api/src/middleware/auth_middleware.ts | 41 +++++++++++++++++++++ apps/api/src/routes/user_route.ts | 21 ++++++++--- apps/api/src/server.ts | 2 + apps/api/src/types/express/index.d.ts | 24 ++++++++++++ package-lock.json | 41 ++++++++++++++++++--- 7 files changed, 143 insertions(+), 12 deletions(-) create mode 100644 apps/api/src/middleware/auth_middleware.ts create mode 100644 apps/api/src/types/express/index.d.ts diff --git a/apps/api/package.json b/apps/api/package.json index 41a5395..3811cbe 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -18,6 +18,7 @@ "@repo/logger": "*", "bcrypt": "^5.1.1", "body-parser": "^1.20.2", + "cookie-parser": "^1.4.6", "cors": "^2.8.5", "express": "^4.18.2", "jsonwebtoken": "^9.0.2", @@ -31,6 +32,7 @@ "@repo/typescript-config": "*", "@types/bcrypt": "^5.0.2", "@types/body-parser": "^1.19.5", + "@types/cookie-parser": "^1.4.7", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jest": "^29.5.11", diff --git a/apps/api/src/controllers/user_controller.ts b/apps/api/src/controllers/user_controller.ts index 8dba93b..3d908fc 100644 --- a/apps/api/src/controllers/user_controller.ts +++ b/apps/api/src/controllers/user_controller.ts @@ -4,10 +4,13 @@ import { isValidObjectId } from 'mongoose' import { log } from 'console' import { generate_token } from 'utils/generate_token' -//* @desc Post user +//* @desc Register user //* route POST /api/user //? @access Public -export async function create_user(req: Request, res: Response): Promise { +export async function register_user( + req: Request, + res: Response +): Promise { try { const user_data = req.body // check if user email already exists @@ -98,3 +101,20 @@ export async function update_user(req: Request, res: Response): Promise { res.status(500).json({ error: 'Internal server error' }) } } + +//* @desc Get User +//* route GET /api/user/profile +//! @access Private +export async function get_user(req: Request, res: Response): Promise { + try { + const user = { + name: req.user?.name, + email: req.user?.email, + _id: req.user?._id, + } + res.status(200).json(user) + } catch (error) { + log('Error fetching user:', error) + res.status(500).json({ error: 'Internal server error' }) + } +} diff --git a/apps/api/src/middleware/auth_middleware.ts b/apps/api/src/middleware/auth_middleware.ts new file mode 100644 index 0000000..9d8c9c3 --- /dev/null +++ b/apps/api/src/middleware/auth_middleware.ts @@ -0,0 +1,41 @@ +import { Request as ExpressRequest, Response, NextFunction } from 'express' +import jwt, { JwtPayload } from 'jsonwebtoken' +import { USER_SCHEMA } from '@/models/user_model' +import { log } from 'console' + +interface User { + name?: string + email: string + password: string +} + +interface Request extends ExpressRequest { + user?: User +} + +export async function protect_route( + req: Request, + res: Response, + next: NextFunction +) { + try { + const token = req.cookies.token + if (!token) { + res.status(401).json({ error: 'Unauthorized' }) + return + } + const secret = process.env.JWT_SECRET + if (!secret) { + res.status(500).json({ error: 'JWT secret is undefined' }) + return + } + const decoded = jwt.verify(token, secret) as JwtPayload + req.user = (await USER_SCHEMA.findById(decoded.user_id).select( + '-password' + )) as User + + next() + } catch (error) { + res.status(500).json({ error: 'Internal server error' }) + } +} diff --git a/apps/api/src/routes/user_route.ts b/apps/api/src/routes/user_route.ts index 1a7b8d5..130ad73 100644 --- a/apps/api/src/routes/user_route.ts +++ b/apps/api/src/routes/user_route.ts @@ -5,24 +5,35 @@ import { USER_VALIDATION_SCHEMA } from '@/validations/user_validation' import { login_user, logout_user, - create_user, + register_user, update_user, + get_user, } from '@/controllers/user_controller' +import { protect_route } from '@/middleware/auth_middleware' -//* @desc Post user +//* @desc Create user //? @access Public -router.post('/', validate_schema(USER_VALIDATION_SCHEMA), create_user) +router.post('/', validate_schema(USER_VALIDATION_SCHEMA), register_user) //* @desc Login user //? @access Public -router.get('/', validate_schema(USER_VALIDATION_SCHEMA), login_user) +router.get('/login', validate_schema(USER_VALIDATION_SCHEMA), login_user) //* @desc Logout user //? @access Public router.post('/logout', logout_user) +//* @desc Get User +//! @access Private +router.get('/profile', protect_route, get_user) + //* @desc Update user //! @access Private -router.patch('/:id', validate_schema(USER_VALIDATION_SCHEMA), update_user) +router.patch( + '/:id', + protect_route, + validate_schema(USER_VALIDATION_SCHEMA), + update_user +) export default router diff --git a/apps/api/src/server.ts b/apps/api/src/server.ts index 8ab8cff..d9bdb22 100644 --- a/apps/api/src/server.ts +++ b/apps/api/src/server.ts @@ -3,6 +3,7 @@ import express, { type Express } from 'express' import morgan from 'morgan' import cors from 'cors' import { config } from 'dotenv' +import cookieParser from 'cookie-parser' config() @@ -14,6 +15,7 @@ export const createServer = (): Express => { .use(urlencoded({ extended: true })) .use(json()) .use(cors()) + .use(cookieParser()) .get('/health', (_, res) => { return res.json({ ok: true }) }) diff --git a/apps/api/src/types/express/index.d.ts b/apps/api/src/types/express/index.d.ts new file mode 100644 index 0000000..0d78638 --- /dev/null +++ b/apps/api/src/types/express/index.d.ts @@ -0,0 +1,24 @@ +export {} + +declare global { + namespace Express { + export interface Request { + job?: { + id: string + title: string + description: string + company: string + location: string + salary: number + created_at: string + updated_at: string + } + user?: { + _id?: string + name?: string + email: string + password: string + } + } + } +} diff --git a/package-lock.json b/package-lock.json index d239ad6..16317de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "@repo/logger": "*", "bcrypt": "^5.1.1", "body-parser": "^1.20.2", + "cookie-parser": "^1.4.6", "cors": "^2.8.5", "express": "^4.18.2", "jsonwebtoken": "^9.0.2", @@ -54,6 +55,7 @@ "@repo/typescript-config": "*", "@types/bcrypt": "^5.0.2", "@types/body-parser": "^1.19.5", + "@types/cookie-parser": "^1.4.7", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jest": "^29.5.11", @@ -2575,6 +2577,15 @@ "@types/node": "*" } }, + "node_modules/@types/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-Fvuyi354Z+uayxzIGCwYTayFKocfV7TuDYZClCdIP9ckhvAu/ixDtCB6qx2TT0FKjPLf1f3P/J1rgf6lPs64mw==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/cookiejar": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", @@ -4298,6 +4309,31 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, + "node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, "node_modules/cookiejar": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", @@ -6345,11 +6381,6 @@ "node": ">= 0.6" } }, - "node_modules/express/node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",