diff --git a/app/config.js b/app/config.js index f734ed3..6ad35b0 100644 --- a/app/config.js +++ b/app/config.js @@ -6,5 +6,4 @@ export const config = { jwt: process.env.JWT_SECRET || "asdasdawe", email: process.env.EMAIL, password: process.env.EMAIL_PASSWORD, - domain: "" }; diff --git a/app/controllers/announcementsController.js b/app/controllers/announcementsController.js index 8e81578..1fced31 100644 --- a/app/controllers/announcementsController.js +++ b/app/controllers/announcementsController.js @@ -24,16 +24,17 @@ export const getAnnouncements = async (req, res, next) => { }; export const addAnnouncement = async (req, res, next) => { - const { title, description, price, category, animal, city, user } = req.body; + const { title, description, price, category, animal, city } = req.body; let creator; try { - creator = await User.findById(user); + creator = await User.findById(req.user.id); } catch (e) { res.status(422).json({ message: "Couldn't find user" }); return next(); } + const announcement = new Announcement({ title: title, description: description, @@ -43,6 +44,7 @@ export const addAnnouncement = async (req, res, next) => { city: city, user: creator, }); + try { await announcement.save(); res.status(201).json(announcement); diff --git a/app/controllers/opinionController.js b/app/controllers/opinionController.js index 9c1ff97..aa6a602 100644 --- a/app/controllers/opinionController.js +++ b/app/controllers/opinionController.js @@ -2,10 +2,10 @@ import { Opinion } from "../db/models/OpinionSchema.js"; import { User } from "../db/models/UserSchema.js"; export const addOpinion = async (req, res, next) => { - const { rate, opinion, user } = req.body; + const { rate, opinion } = req.body; let creator; try { - creator = await User.findById(user); + creator = await User.findById(req.user.id); } catch (error) { res.status(422).json({ message: "Couldn't find user" }); return next(); diff --git a/app/controllers/userController.js b/app/controllers/userController.js index 15c6366..2e0a996 100644 --- a/app/controllers/userController.js +++ b/app/controllers/userController.js @@ -33,8 +33,12 @@ export const login = async (req, res, next) => { } } const isValidPassword = user.comparePassword(req.body.password); + const { _id, email, username, city, phone } = user; if (isValidPassword) { - const token = jsonwebtoken.sign({ id: user.id }, config.jwt); + const token = jsonwebtoken.sign( + { id: _id, email, username, city, phone }, + config.jwt, + ); return res.header("auth-token", token).send(token); } return res.json({ error: "Invalid password" }); @@ -43,7 +47,7 @@ export const login = async (req, res, next) => { } }; -export const reset = async (req, res) => { +export const forgot = async (req, res) => { const email = req.body.email; User.findOne({email: email}, (err, user) =>{ @@ -53,9 +57,7 @@ export const reset = async (req, res) => { const token = jsonwebtoken.sign({ id: user.id }, config.jwt, {expiresIn: "20m"}); const transporter = nodemailer.createTransport({ - host: 'smtp.gmail.com', - port: 465, - secure: true, + service: "Gmail", auth: { user: config.email, pass: config.password @@ -66,7 +68,7 @@ export const reset = async (req, res) => { from: config.email, to: email, subject: "Reset password", - html: `

To reset your password click here

` + html: `

To reset your password click here

` } transporter.sendMail(mailOptions, (err, data) =>{ if(err){ @@ -82,6 +84,25 @@ export const reset = async (req, res) => { }) }) } + +export const reset = async (req, res) => { + const token = req.params.token; + let newPassword = req.body.password; + let user; + + let decoded = jsonwebtoken.decode(token); + user = await User.findById(decoded.id); + user.password = newPassword; + +try { + await user.save(); + return res.status(200).json({message: "Password has been changed!"}) + +} catch (error) { + res.status(400).json({error: error}) +} +} + export const getUserData = async (req, res, next) => { const id = req.params.uid; let user; @@ -93,6 +114,11 @@ export const getUserData = async (req, res, next) => { city: user.city, phone: user.phone, announcements: user.announcements, + business: user.business, + description: user.description, + NIP: user.NIP, + openHour: user.openHour, + closeHour: user.closeHour, }); } catch (error) { res.status(422).json({ error: "User not found" }); @@ -107,23 +133,32 @@ export const updateUser = async (req, res, next) => { } catch (error) { res.status(400).json({ error: "User not found" }); } - if(user.comparePassword(req.body.password)){ - const { city, phone, business, description, NIP, openHour, closeHour, password} = req.body; - if(city) user.city = city; - if(phone) user.phone = phone; - if(business) user.business = business; - if(description) user.description = description; - if(NIP) user.NIP = NIP; - if(openHour) user.openHour = openHour; - if(closeHour) user.closeHour = closeHour; - if(password) user.password = password; + if (user.comparePassword(req.body.password)) { + const { + city, + phone, + business, + description, + NIP, + openHour, + closeHour, + password, + } = req.body; + if (city) user.city = city; + if (phone) user.phone = phone; + if (business) user.business = business; + if (description) user.description = description; + if (NIP) user.NIP = NIP; + if (openHour) user.openHour = openHour; + if (closeHour) user.closeHour = closeHour; + if (password) user.password = password; try { await user.save(); - res.json({message: "Succesfully data changed!"}) + res.json({ message: "Succesfully data changed!" }); } catch (error) { - res.json({error: "Couldn't get data"}); + res.json({ error: error }); } - }else{ - return res.status(422).json({error: "invalid Password"}) + } else { + return res.status(422).json({ error: "invalid Password" }); } -}; +}; \ No newline at end of file diff --git a/app/db/models/UserSchema.js b/app/db/models/UserSchema.js index c62c55b..4176ce3 100644 --- a/app/db/models/UserSchema.js +++ b/app/db/models/UserSchema.js @@ -17,7 +17,6 @@ const UserSchema = new mongoose.Schema({ type: String, required: true, minLength: [4, "At least 4 characters"], - maxlength: [30, "Max length is 30 characters"], }, username: { type: String, diff --git a/app/index.js b/app/index.js index 6d36127..1636183 100644 --- a/app/index.js +++ b/app/index.js @@ -5,6 +5,8 @@ import { opinionsRouter } from "./routes/opinionsRoutes.js"; import { userRouter } from "./routes/userRouter.js"; import "./db/mongoose.js"; import { config } from "./config.js"; +import swaggerUi from "swagger-ui-express"; +import swaggerFile from "./swagger-output.json" assert { type: "json" }; const app = express(); @@ -12,7 +14,10 @@ app.use(express.json()); app.use(function (req, res, next) { res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, PATCH"); + res.setHeader( + "Access-Control-Allow-Methods", + "GET, POST, DELETE, PUT, PATCH", + ); res.setHeader("Access-Control-Allow-Headers", "Content-Type"); next(); }); @@ -21,6 +26,7 @@ app.use("/auth", authRouter); app.use("/announcements", announcementRouter); app.use("/opinions", opinionsRouter); app.use("/user", userRouter); +app.use("/doc", swaggerUi.serve, swaggerUi.setup(swaggerFile)); app .listen(config.port, () => { diff --git a/app/middleware/verifyToken.js b/app/middleware/verifyToken.js index 868703b..38154f5 100644 --- a/app/middleware/verifyToken.js +++ b/app/middleware/verifyToken.js @@ -8,6 +8,7 @@ export function auth(req, res, next) { try { const verified = jwt.verify(token, config.jwt); req.user = verified; + return next(); } catch (error) { res.status(400).send("Invalid Token"); } diff --git a/app/routes/authRoutes.js b/app/routes/authRoutes.js index 353f4ad..3b9d4c3 100644 --- a/app/routes/authRoutes.js +++ b/app/routes/authRoutes.js @@ -1,16 +1,12 @@ import express from "express"; -import { login, register, reset } from "../controllers/userController.js"; +import { login, register, forgot, reset } from "../controllers/userController.js"; export const authRouter = express.Router(); authRouter.post("/login", login); -authRouter.post("/forgot", (req, res) => { - res.status(200).json({ - message: "Handling POST requests to /auth/forgot", - }); -}); +authRouter.post("/forgot", forgot); -authRouter.post("/reset", reset); +authRouter.post("/reset/:token", reset); authRouter.post("/register", register); diff --git a/app/swagger-output.json b/app/swagger-output.json new file mode 100644 index 0000000..d278f08 --- /dev/null +++ b/app/swagger-output.json @@ -0,0 +1,325 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "REST API", + "description": "" + }, + "host": "localhost:3000", + "basePath": "/", + "schemes": [ + "http" + ], + "paths": { + "/auth/login": { + "post": { + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "schema": { + "type": "object", + "properties": { + "email": { + "example": "any" + }, + "password": { + "example": "any" + } + } + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/auth/forgot": { + "post": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/auth/reset": { + "post": { + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "schema": { + "type": "object", + "properties": { + "email": { + "example": "any" + } + } + } + } + ], + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/auth/register": { + "post": { + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "schema": { + "type": "object", + "properties": { + "email": { + "example": "any" + }, + "password": { + "example": "any" + }, + "username": { + "example": "any" + }, + "city": { + "example": "any" + }, + "phone": { + "example": "any" + } + } + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "422": { + "description": "Unprocessable Entity" + } + } + } + }, + "/announcements/{aid}": { + "get": { + "description": "", + "parameters": [ + { + "name": "aid", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/announcements/": { + "get": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + } + } + }, + "post": { + "description": "", + "parameters": [ + { + "name": "auth-token", + "in": "header", + "type": "string" + }, + { + "name": "body", + "in": "body", + "schema": { + "type": "object", + "properties": { + "title": { + "example": "any" + }, + "description": { + "example": "any" + }, + "price": { + "example": "any" + }, + "category": { + "example": "any" + }, + "animal": { + "example": "any" + }, + "city": { + "example": "any" + } + } + } + } + ], + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request" + }, + "401": { + "description": "Unauthorized" + }, + "422": { + "description": "Unprocessable Entity" + } + } + } + }, + "/opinions/": { + "get": { + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "OK" + } + } + }, + "post": { + "description": "", + "parameters": [ + { + "name": "auth-token", + "in": "header", + "type": "string" + }, + { + "name": "body", + "in": "body", + "schema": { + "type": "object", + "properties": { + "rate": { + "example": "any" + }, + "opinion": { + "example": "any" + } + } + } + } + ], + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request" + }, + "401": { + "description": "Unauthorized" + }, + "422": { + "description": "Unprocessable Entity" + } + } + } + }, + "/user/{uid}": { + "get": { + "description": "", + "parameters": [ + { + "name": "uid", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "201": { + "description": "Created" + }, + "422": { + "description": "Unprocessable Entity" + } + } + }, + "patch": { + "description": "", + "parameters": [ + { + "name": "uid", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "schema": { + "type": "object", + "properties": { + "password": { + "example": "any" + }, + "city": { + "example": "any" + }, + "phone": { + "example": "any" + }, + "business": { + "example": "any" + }, + "description": { + "example": "any" + }, + "NIP": { + "example": "any" + }, + "openHour": { + "example": "any" + }, + "closeHour": { + "example": "any" + } + } + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Unprocessable Entity" + } + } + } + } + } +} \ No newline at end of file diff --git a/app/swagger.js b/app/swagger.js new file mode 100644 index 0000000..b2bfc7b --- /dev/null +++ b/app/swagger.js @@ -0,0 +1,6 @@ +import swaggerAutogen from "swagger-autogen"; + +const outputFile = "app/swagger-output.json"; +const endpointsFiles = ["app/index.js"]; + +swaggerAutogen()(outputFile, endpointsFiles); diff --git a/package-lock.json b/package-lock.json index 4eecc46..b620f40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,11 +15,13 @@ "jsonwebtoken": "^8.5.1", "mongodb": "^4.4.0", "mongoose": "^6.2.2", - "nodemailer": "^6.7.2" + "nodemailer": "^6.7.2", + "swagger-ui-express": "^4.3.0" }, "devDependencies": { "nodemon": "^2.0.15", - "prettier": "^2.5.1" + "prettier": "^2.5.1", + "swagger-autogen": "^2.20.5" } }, "node_modules/@mapbox/node-pre-gyp": { @@ -98,6 +100,18 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -629,6 +643,15 @@ "node": ">=4.0.0" } }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", @@ -1268,6 +1291,18 @@ "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", "dev": true }, + "node_modules/json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonwebtoken": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", @@ -2323,6 +2358,37 @@ "node": ">=4" } }, + "node_modules/swagger-autogen": { + "version": "2.20.5", + "resolved": "https://registry.npmjs.org/swagger-autogen/-/swagger-autogen-2.20.5.tgz", + "integrity": "sha512-VqgZhlZr/SCBA1jDICKunlPoDhxqq1+AcAAStvI4HuWveLYzHim2CVEbV/kvIqjlkBg0AxWba6NqoCCbFKdL6g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.1", + "deepmerge": "^4.2.2", + "glob": "^7.1.7", + "json5": "^2.2.1" + } + }, + "node_modules/swagger-ui-dist": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.6.2.tgz", + "integrity": "sha512-BSt+ukOGkGZ2uHV4jyyCAzt60ysyQpGZAAhtIh7AMHT4MH1xXGkoXm2tfr1oRqO1N4IEY6qqNAlmcfMo/dAYuw==" + }, + "node_modules/swagger-ui-express": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.3.0.tgz", + "integrity": "sha512-jN46SEEe9EoXa3ZgZoKgnSF6z0w3tnM1yqhO4Y+Q4iZVc8JOQB960EZpIAz6rNROrDApVDwcMHR0mhlnc/5Omw==", + "dependencies": { + "swagger-ui-dist": ">=4.1.3" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0" + } + }, "node_modules/tar": { "version": "6.1.11", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", @@ -2680,6 +2746,12 @@ "negotiator": "0.6.3" } }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -3061,6 +3133,12 @@ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, "defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", @@ -3541,6 +3619,12 @@ "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", "dev": true }, + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true + }, "jsonwebtoken": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", @@ -4345,6 +4429,31 @@ "has-flag": "^3.0.0" } }, + "swagger-autogen": { + "version": "2.20.5", + "resolved": "https://registry.npmjs.org/swagger-autogen/-/swagger-autogen-2.20.5.tgz", + "integrity": "sha512-VqgZhlZr/SCBA1jDICKunlPoDhxqq1+AcAAStvI4HuWveLYzHim2CVEbV/kvIqjlkBg0AxWba6NqoCCbFKdL6g==", + "dev": true, + "requires": { + "acorn": "^7.4.1", + "deepmerge": "^4.2.2", + "glob": "^7.1.7", + "json5": "^2.2.1" + } + }, + "swagger-ui-dist": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.6.2.tgz", + "integrity": "sha512-BSt+ukOGkGZ2uHV4jyyCAzt60ysyQpGZAAhtIh7AMHT4MH1xXGkoXm2tfr1oRqO1N4IEY6qqNAlmcfMo/dAYuw==" + }, + "swagger-ui-express": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.3.0.tgz", + "integrity": "sha512-jN46SEEe9EoXa3ZgZoKgnSF6z0w3tnM1yqhO4Y+Q4iZVc8JOQB960EZpIAz6rNROrDApVDwcMHR0mhlnc/5Omw==", + "requires": { + "swagger-ui-dist": ">=4.1.3" + } + }, "tar": { "version": "6.1.11", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", diff --git a/package.json b/package.json index 65f5b35..89676bd 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "watch": "nodemon app/index.js -e js,json" + "watch": "nodemon --experimental-json-modules app/index.js -e js,json", + "swagger": "node app/swagger.js" }, "repository": { "type": "git", @@ -25,10 +26,12 @@ "jsonwebtoken": "^8.5.1", "mongodb": "^4.4.0", "mongoose": "^6.2.2", - "nodemailer": "^6.7.2" + "nodemailer": "^6.7.2", + "swagger-ui-express": "^4.3.0" }, "devDependencies": { "nodemon": "^2.0.15", - "prettier": "^2.5.1" + "prettier": "^2.5.1", + "swagger-autogen": "^2.20.5" } }