Skip to content

Commit

Permalink
Merge pull request #7 from hariscs/auth
Browse files Browse the repository at this point in the history
Validation And Auth
  • Loading branch information
hariscs authored Mar 2, 2024
2 parents 5f2641d + 4747cae commit c7c16cb
Show file tree
Hide file tree
Showing 21 changed files with 868 additions and 80 deletions.
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
NODE_ENV=development
PORT=5000
MONGO_URI=YOUR_MONGO_URI
MONGO_URI=YOUR_MONGO_URI
JWT_SECRET=YOUR_JWT_SECRET
3 changes: 1 addition & 2 deletions apps/api/config/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 here: ${conn.connection.name}`)
await mongoose.connect(process.env.MONGO_URI as string)
} catch (error) {
log(`Error: ${error}`)
process.exit(1)
Expand Down
9 changes: 8 additions & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,27 @@
},
"dependencies": {
"@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",
"mongoose": "^8.1.1",
"morgan": "^1.10.0"
"morgan": "^1.10.0",
"zod": "^3.22.4"
},
"devDependencies": {
"@repo/eslint-config": "*",
"@repo/jest-presets": "*",
"@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",
"@types/jsonwebtoken": "^9.0.5",
"@types/morgan": "^1.9.9",
"@types/node": "^20.10.6",
"@types/supertest": "^6.0.2",
Expand Down
22 changes: 12 additions & 10 deletions apps/api/src/controllers/job_controller.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -11,7 +12,7 @@ export async function get_jobs(_: Request, res: Response): Promise<void> {
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' })
}
}
Expand All @@ -35,7 +36,7 @@ export async function get_job(req: Request, res: Response): Promise<void> {
}
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' })
}
}
Expand All @@ -45,12 +46,13 @@ export async function get_job(req: Request, res: Response): Promise<void> {
//! @access Private
export async function post_job(req: Request, res: Response): Promise<void> {
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' })
}
}
Expand All @@ -73,10 +75,10 @@ export async function delete_job(req: Request, res: Response): Promise<void> {
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' })
}
}
Expand Down Expand Up @@ -104,7 +106,7 @@ export async function update_job(req: Request, res: Response): Promise<void> {
})
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' })
}
}
120 changes: 120 additions & 0 deletions apps/api/src/controllers/user_controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
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 Register user
//* route POST /api/user
//? @access Public
export async function register_user(
req: Request,
res: Response
): Promise<void> {
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 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)
res.status(500).json({ error: 'Internal server error' })
}
}

//* @desc Login user
//* route GET /api/user
//! @access Public
export async function login_user(req: Request, res: Response): Promise<void> {
try {
//* get user by email and password
const { email, password } = req.body
const user = await USER_SCHEMA.findOne({ email })

// 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)
res.status(500).json({ error: 'Internal server error' })
}
}

//* @desc Logout user
//* route POST /api/user/logout
// ? @access Public
export async function logout_user(_: Request, res: Response): Promise<void> {
try {
res.clearCookie('token').status(200).json({ message: 'User logged out' })
} catch (error) {
log('Error logging out 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<void> {
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(409).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' })
}
}

//* @desc Get User
//* route GET /api/user/profile
//! @access Private
export async function get_user(req: Request, res: Response): Promise<void> {
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' })
}
}
16 changes: 12 additions & 4 deletions apps/api/src/index.ts
Original file line number Diff line number Diff line change
@@ -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)
})
41 changes: 41 additions & 0 deletions apps/api/src/middleware/auth_middleware.ts
Original file line number Diff line number Diff line change
@@ -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' })
}
}
18 changes: 18 additions & 0 deletions apps/api/src/middleware/validation_middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Request, Response, NextFunction } from 'express'
import { z } from 'zod'

export function validate_schema(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 })
}
}
}
}
37 changes: 37 additions & 0 deletions apps/api/src/models/user_model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import mongoose, { Document, Schema } from 'mongoose'
import bcrypt from 'bcrypt'

interface IUserSchema extends Document {
name?: string
email: string
password: string
match_password: (password: string) => Promise<boolean>
}

const user_schema = new Schema(
{
name: { type: String },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
},
{
timestamps: true,
}
)

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<IUserSchema>('User', user_schema)
2 changes: 2 additions & 0 deletions apps/api/src/routes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as job_route } from './job_route'
export { default as user_route } from './user_route'
11 changes: 6 additions & 5 deletions apps/api/src/routes/job_route.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
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,
get_jobs,
post_job,
update_job,
} from '@controllers/job_controller'

const router = express.Router()
} from '@/controllers/job_controller'

//* @desc Get all jobs
//? @access Public
Expand All @@ -19,14 +20,14 @@ router.get('/:id', get_job)

//* @desc Post job
//! @access Private
router.post('/', post_job)
router.post('/', validate_schema(JOB_VALIDATION_SCHEMA), post_job)

//* @desc Delete job
//! @access Private
router.delete('/:id', delete_job)

//* @desc Update job
//! @access Private
router.patch('/:id', update_job)
router.patch('/:id', validate_schema(JOB_VALIDATION_SCHEMA), update_job)

export default router
Loading

0 comments on commit c7c16cb

Please sign in to comment.