Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Week12 #41

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .node_version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
16.2.0
37 changes: 34 additions & 3 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,49 @@
require('dotenv').config();
require('express-async-errors');
const helmet = require('helmet');
const cors = require('cors');
const xss = require('xss-clean');
const rateLimiter = require('express-rate-limit');



const express = require('express');
const app = express();

// connectDB

const connectDB = require('./db/connect')
const authenticateUser = require('./middleware/authentication')

// Routers
const authRouter = require('./routes/auth')
const jobsRouter = require('./routes/jobs');

// error handler
const notFoundMiddleware = require('./middleware/not-found');
const errorHandlerMiddleware = require('./middleware/error-handler');


app.set('trust proxy', 1);

app.use(rateLimiter({
windowMs: 15 * 60 * 1000,
max: 100
}));
app.use(express.json());
app.use(helmet());
app.use(cors());
app.use(xss());

// extra packages

// Routes

app.use('/api/v1/auth', authRouter);
app.use('/api/v1/jobs', authenticateUser, jobsRouter);

// routes
app.get('/', (req, res) => {
res.send('jobs api');
});
app.use(express.static('public'));

app.use(notFoundMiddleware);
app.use(errorHandlerMiddleware);
Expand All @@ -22,6 +52,7 @@ const port = process.env.PORT || 3000;

const start = async () => {
try {
await connectDB(process.env.MONGO_URI)
app.listen(port, () =>
console.log(`Server is listening on port ${port}...`)
);
Expand Down
39 changes: 39 additions & 0 deletions controllers/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const User = require('../models/User');
const { StatusCodes } = require('http-status-codes');
const { BadRequestError, UnauthenticatedError } = require('../errors');

const register = async(req, res) => {
const user = await User.create({ ...req.body });
const token = user.createJWT();

res.status(StatusCodes.CREATED).json({ user: { name: user.name }, token });
}

const login = async(req, res) => {
const { email, password } = req.body;

if(!email || !password) {
throw new BadRequestError('Please provide email and password');
}

const user = await User.findOne({ email });

if(!user) {
throw new UnauthenticatedError('Invalid Credentials');
}

const isPasswordCorrect = await user.comparePassword(password);

if(!isPasswordCorrect) {
throw new UnauthenticatedError('Invalid Credentials');
}

const token = user.createJWT();
res.status(StatusCodes.OK).json({ user: { name: user.name }, token })

}

module.exports = {
register,
login
}
70 changes: 70 additions & 0 deletions controllers/jobs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const Job = require('../models/Job');
const { StatusCodes } = require('http-status-codes');
const { BadRequestError, NotFoundError } = require('../errors')

const getAllJobs = async (req, res) => {
const jobs = await Job.find({ createdBy: req.user.userId }).sort('createdAt');
res.status(StatusCodes.OK).json({ jobs, count: jobs.length });
}

const getJob = async (req, res) => {
const { user: {userId}, params: {id: jobId} } = req;
const job = await Job.findOne({
_id: jobId, createdBy: userId
});

if(!job) {
throw new NotFoundError(`No job id found with the id: ${jobId}`)
}
res.status(StatusCodes.OK).json({ job });
}

const createJob = async (req, res) => {
req.body.createdBy = req.user.userId
const job = await Job.create(req.body);
res.status(StatusCodes.CREATED).json({ job })
}

const updateJob = async (req, res) => {
const {
body: { company, position },
user: { userId },
params: { id: jobId },
} = req

if(!company|| !position) {
throw new BadRequestError('Company or position cannot be empty')
}

const job = await Job.findByIdAndUpdate({ _id: jobId, createdBy: userId }, req.body, { new: true, runValidators:true });

if(!job) {
throw new NotFoundError(`No job id found with the id: ${jobId}`)
}

res.status(StatusCodes.OK).json({ job })
}

const deleteJob = async (req, res) => {
const {
user: { userId },
params: { id: jobId },
} = req;

const job = await Job.findOneAndRemove({ _id: jobId, createdBy: userId });

if (!job) {
throw new NotFoundError(`No job id found with the id: ${jobId}`)
}

res.status(StatusCodes.OK).json({ msg: "Deleted successfully" });
}


module.exports = {
getAllJobs,
getJob,
createJob,
updateJob,
deleteJob
}
21 changes: 21 additions & 0 deletions middleware/authentication.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const User = require('../models/User');
const jwt = require('jsonwebtoken');
const { UnauthenticatedError } = require('../errors');

const auth = (req, res, next) => {
const authHeader = req.headers.authorization;
if(!authHeader || !authHeader.startsWith('Bearer')) {
throw new UnauthenticatedError('Authentication invalid');
}
const token = authHeader.split(' ')[1];
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.user = { userId: payload.userId, name: payload.name }
} catch (error) {
throw new UnauthenticatedError('Authentication invalid')
}

next();
}

module.exports = auth;
26 changes: 22 additions & 4 deletions middleware/error-handler.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
const { CustomAPIError } = require('../errors')
const { custom } = require('joi')
const { StatusCodes } = require('http-status-codes')
const errorHandlerMiddleware = (err, req, res, next) => {
if (err instanceof CustomAPIError) {
return res.status(err.statusCode).json({ msg: err.message })
let customError = {
statusCode: err.statusCode || StatusCodes.INTERNAL_SERVER_ERROR,
msg: err.message || "Something went wrong, please try again later!"

}

if(err.name === 'ValidationError') {
customError.msg = Object.values(err.errors).map(item => item.message).join(',');
customError.statusCode = 400;
}

if(err.code && err.code === 11000) {
customError.msg = `Duplicate value entered for ${Object.keys(err.keyValue)} field, Please choose or enter another value`;
customError.statusCode = 400;
}
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ err })

if(err.name === 'CastError') {
customError.msg = `No item found with id: ${typeof err.value === "object"? err.value._id : err.value}`;
customError.statusCode = 404;
}

return res.status(customError.statusCode).json({ msg: customError.msg })
}

module.exports = errorHandlerMiddleware
26 changes: 26 additions & 0 deletions models/Job.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const mongoose = require('mongoose');

const JobSchema = new mongoose.Schema({
company: {
type: String,
required: [true, "Please provide a company name"],
maxLength: 50
},
position: {
type: String,
required: [true, "Please provide a position name"],
maxLength: 100
},
status: {
type: String,
enum: ['interview', 'declined', 'pending'],
default: 'pending'
},
createdBy: {
type: mongoose.Types.ObjectId,
ref: 'User',
required: [true, 'Please provide a user']
}
}, { timestamps: true });

module.exports = mongoose.model('Job', JobSchema);
47 changes: 47 additions & 0 deletions models/User.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

const UserSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Please provide a name'],
minLength: 3,
maxLength: 50,
},
email: {
type: String,
required: [true, 'Please provide an email'],
match: [
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
"Please provide a valid email address"
],
unique: true
},
password: {
type: String,
required: [true, "Please provide a password"],
minLength: 6,
}
});

UserSchema.index({ email: 1 }, { unique: true })


UserSchema.pre('save', async function () {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});

UserSchema.methods.createJWT = function () {
return jwt.sign({ userId: this._id, name: this.name }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_LIFETIME
})
}

UserSchema.methods.comparePassword = async function (candidatePassword) {
const isMatch = await bcrypt.compare(candidatePassword, this.password)
return isMatch;
}

module.exports = mongoose.model('User', UserSchema)
Loading