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

final project #42

Open
wants to merge 18 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 @@
18.18.0
52 changes: 48 additions & 4 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,60 @@ require('express-async-errors');
const express = require('express');
const app = express();

// extra security packages
const helmet = require('helmet')
const cors = require('cors')
const xss = require('xss-clean')
const rateLimiter = require('express-rate-limit')

// Swagger
const swaggerUI = require('swagger-ui-express')
const YAML = require('yamljs')
const swaggerDocument = YAML.load('./swagger.yaml')

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

// router
const authRouter = require('./routes/auth')
const itemRouter = require('./routes/items')
const orderRouter = require('./routes/orders')




// 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, // 15 minutes
limit: 100,
}));

app.use(express.static('./public'))
app.use(express.json());
// extra packages
app.use(helmet())
app.use(cors())
app.use(xss())


// app.get('/', (req, res) => {
// res.send('<h1>Orders API</h1><a href="/api-docs">Documentation</a>')
// })


app.use('api-docs', swaggerUI.serve, swaggerUI.setup(swaggerDocument))

// routes
app.get('/', (req, res) => {
res.send('jobs api');
});
app.use('/api/v1/auth', authRouter)
app.use('/api/v1/items',authhenticateUser, itemRouter)
app.use('/api/v1/orders',authhenticateUser, orderRouter)




app.use(notFoundMiddleware);
app.use(errorHandlerMiddleware);
Expand All @@ -22,6 +65,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 bcrypt = require('bcryptjs')


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, msg: 'User created'})
}

const logon = async (req, res) => {
console.log(req.body)
const {email, password} = req.body
if (!email || !password) {
console.log(email, password)
throw new BadRequestError('Please provide email and password!')
}

const user = await User.findOne({ email })
if (!user) {
throw new UnauthenticatedError('Invalid Creadentials user!')
}

const isPasswordCorrect = await user.comparePassword(password)
if (!isPasswordCorrect) {
throw new UnauthenticatedError('Invalid Creadentials password !')
}

const token = user.createJWT()

res.status(StatusCodes.OK).json({user: {name: user.name}, token, msg: `Success: ${user.name} signed in`})
}


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

const getAllItems = async (req, res) => {
const items = await Item.find().sort('-createdAt')
res.status(StatusCodes.OK).json({items})
}

const getItem = async (req, res) => {
const {user: {userId}, params: {id: itemId}} = req
const item = await Item.findOne({_id: itemId, createdBy: userId})
if (!item) {
throw new NotFoundError(`No item with id ${itemId}`)
}

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

const createItem = async (req, res) => {
req.body.createdBy = req.user.userId
const item = await Item.create(req.body)
res.status(StatusCodes.CREATED).json({ item })
}

const deleteItem = async (req, res) => {
const {user: {userId}, params: {id: itemId}} = req
const item = await Item.findOneAndDelete({_id: itemId, createdBy: userId})
if (!item) {
throw new NotFoundError(`No item with id ${itemId}`)
}

res.status(StatusCodes.OK).send("item deleted")
}

const updateItem = async (req, res) => {
const {body: {price, quantity}, user: {userId}, params: {id: itemId}} = req

if (price === '') {
throw new BadRequestError('price and quantity can not be empty!')
}

const item = await Item.findByIdAndUpdate({_id: itemId, createdBy: userId}, req.body, {new: true, runValidators: true})
if (!item) {
throw new NotFoundError(`No item with id ${itemId}`)
}

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

module.exports = {getAllItems, getItem, createItem, deleteItem, updateItem}
Empty file removed controllers/jobs.js
Empty file.
71 changes: 71 additions & 0 deletions controllers/orders.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
const { BadRequestError, NotFoundError } = require('../errors')
const Order = require('../models/order')
const {StatusCodes} = require('http-status-codes')

const getAllOrders = async (req, res) => {
const orders = await Order.find({createdBy: req.user.userId}).sort('-createdAt')

if (!orders) {
throw new NotFoundError('no orders')
}
// console.log(orders[orders.length - 1].orderTotal)
res.status(StatusCodes.OK).json({ orders })
}


const getOrder = async (req, res) => {
const {user: {userId}, params: {id: orderId}} = req
const order = await Order.findOne({
_id: orderId,
createdBy: userId,
}).populate('items')

if(!order) {
throw new NotFoundError(`No order with id ${orderId}`)
}

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

const createOrder = async (req, res) => {

req.body.createdBy = req.user.userId


const orderCount = await Order.estimatedDocumentCount()
req.body.orderNumber = orderCount + 1

const order = await Order.create(req.body)
res.status(StatusCodes.CREATED).json({ order })
}

const deleteOrder = async (req, res) => {
const {user: {userId}, params: {id: orderId}} = req
const order = await Order.findByIdAndDelete({_id: orderId, createdBy: userId})
if (!order) {
throw new NotFoundError(`No order with id ${orderId}`)
}
res.status(StatusCodes.OK).send('order deleted')
}

const updateOrder = async (req, res) => {
const {body: {orderStatus},
user: {userId},
params: {id: orderId}} = req
if (orderStatus === '') {
throw new BadRequestError('Status and item price can not be empty!')
}

const order = await Order.findByIdAndUpdate({_id: orderId, createdBy: userId}, req.body, {
new: true, runValidators: true
})

if (!order) {
throw new NotFoundError(`No order with id ${orderId}`)
}

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


module.exports = {getAllOrders,getOrder,createOrder,deleteOrder,updateOrder }
22 changes: 22 additions & 0 deletions middleware/authentication.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const jwt = require('jsonwebtoken')
const UnauthenticatedError = require('../errors')

const auth = async (req, res, next) => {
const authHeader = req.headers.authorization
if (!authHeader) {
throw new UnauthenticatedError('Authentication Invalid!')
}

const token = authHeader.split(' ')[1]

try {
const payload = jwt.verify(token, process.env.JWT_SECRET_KEY)
req.user = {userId: payload.userId, name: payload.name}
next()
} catch (error) {
throw new UnauthenticatedError('Authentication Invalid!')
}
}


module.exports = auth
35 changes: 32 additions & 3 deletions middleware/error-handler.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,39 @@
const { CustomAPIError } = require('../errors')
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 || 'Somthing went wrong try again later'
}
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ err })

// if (err instanceof CustomAPIError) {
// return res.status(err.statusCode).json({ msg: err.message })
// }

if (err.name === "CastError") {
customError.msg = `Error accured due to wrong id of item: ${err.value}`
customError.statusCode = StatusCodes.NOT_FOUND
}

if (err.name === "ValidationError") {
console.log('error name is validation errror')
customError.msg = Object.values(err.errors)
.map((item) => item.message)
.join(',')
customError.statusCode = StatusCodes.BAD_REQUEST
}

if (err.code && err.code === 11000) {
customError.msg = `Duplicate value entered for ${Object.keys(err.keyValue)} field, please choose another value`
customError.statusCode = StatusCodes.BAD_REQUEST
}

// I am using this code just to get error name or error number
// return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ err })


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

module.exports = errorHandlerMiddleware
Empty file removed models/Job.js
Empty file.
44 changes: 44 additions & 0 deletions models/User.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const { required } = require('joi')
const mongoose = require('mongoose')
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')

const UserSchema = new mongoose.Schema({
name: {
type: String,
required: [true, "Please provide name"],
minlength: 3,
maxlength: 50,
},
email: {
type: String,
required: [true, "Please provide 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 valid email"],
unique: true,
},
password: {
type: String,
required: [true, "Please provide password"],
minlength: 6,
}


})

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

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


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

module.exports = mongoose.model("User", UserSchema)
44 changes: 44 additions & 0 deletions models/item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const { required } = require('joi')
const mongoose = require('mongoose')

const ItemShcema = new mongoose.Schema({
itemNumber: {
type: Number,
trim: true
},
itemName: {
type: String,
required: [true, 'Please provide item name'],

maxlength: [50, 'Name can not be more than 50 characters'],

},
price: {
type: Number,
required: [true, 'Please provide item price'],

min: 1,
max: [15, 'Price can not be more than 15 characters '],

},
quantity: {
type: Number,
required: [true, 'Plase provide item quantity'],

min: 1,
max: [12, 'Quantity can not be more than 12 characters'],

},
madeIn: {
type: String,
maxlength: 50,
},
createdBy: {
type:mongoose.Types.ObjectId,
ref: 'User',
required: [true, 'Please provide user'],
},

}, {timestamps: true})

module.exports = mongoose.model('Item', ItemShcema)
Loading