| @@ -0,0 +1,2 @@ | |||
| node_modules | |||
| npm-debug.log | |||
| @@ -0,0 +1,4 @@ | |||
| node_modules/ | |||
| loggerFiles/ | |||
| public/loggerFiles | |||
| .idea | |||
| @@ -0,0 +1,16 @@ | |||
| FROM node:16 | |||
| WORKDIR ./src | |||
| COPY package*.json ./ | |||
| COPY src ./ | |||
| RUN npm install | |||
| ENV NODE_ENV='docker' | |||
| # Bundle app source | |||
| COPY . . | |||
| EXPOSE 3000 | |||
| CMD [ "node", "server.js" ] | |||
| @@ -0,0 +1,32 @@ | |||
| # Node API template | |||
| This is template of web API with mongo db as database node.js as runtime and express as framework. We have user model and JWT tokens setup as well as mediator pattern and winston as logger. | |||
| ## Setup | |||
| Here we will show you how to set up project and run it in localhost. If you want to run it in [docker]() or [portainer]() you can find setup on links. | |||
| - [Project setup](#project-setup) | |||
| - [Database setup](#database-setup) | |||
| ### Database setup | |||
| Please follow the official tutorial on installing and running the latest Mongo database which can be found on this link: | |||
| > https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/ | |||
| ### Project setup | |||
| In order to run our project you need to clone it from [git](https://git.dilig.net/stefan.stamenovic/diligent-node-api) first. Open terminal (cmd and powershell work as well) in folder that you want this project to be and run command | |||
| > git clone http://git.dilig.net/stefan.stamenovic/diligent-node-api.git | |||
| After cloning project you can open it with your preferred IDE/Code editor if you want to see the code. Before running project you need to open terminal and run command | |||
| > npm install | |||
| Running that command will download all necessary npm packages to run the project. | |||
| After that you want to move to src directory | |||
| > cd src | |||
| Now you can run the project using | |||
| > node server.js | |||
| Congratulations! You now ran backend api for template. You can see swagger documentation in the browser with the url | |||
| > http://localhost:3001/swagger | |||
| @@ -0,0 +1,15 @@ | |||
| version: "2" | |||
| services: | |||
| app: | |||
| container_name: app | |||
| restart: always | |||
| build: . | |||
| ports: | |||
| - "3000:3000" | |||
| links: | |||
| - mongo | |||
| mongo: | |||
| container_name: mongo | |||
| image: mongo | |||
| ports: | |||
| - "27017:27017" | |||
| @@ -0,0 +1,35 @@ | |||
| { | |||
| "name": "diligent-node-api", | |||
| "version": "1.0.0", | |||
| "description": "", | |||
| "main": "index.js", | |||
| "scripts": { | |||
| "test": "echo \"Error: no test specified\" && exit 1" | |||
| }, | |||
| "repository": { | |||
| "type": "git", | |||
| "url": "https://git.dilig.net/stefan.stamenovic/diligent-node-api" | |||
| }, | |||
| "author": "", | |||
| "license": "ISC", | |||
| "dependencies": { | |||
| "bcryptjs": "^2.4.3", | |||
| "config": "^3.3.7", | |||
| "cors": "^2.8.5", | |||
| "express": "^4.18.1", | |||
| "express-jwt": "^7.7.2", | |||
| "helmet": "^5.1.0", | |||
| "joi": "^17.6.0", | |||
| "joi-objectid": "^4.0.2", | |||
| "jsonwebtoken": "^8.5.1", | |||
| "migrate-mongo": "^9.0.0", | |||
| "mongodb": "^4.6.0", | |||
| "mongoose": "^6.3.4", | |||
| "nodemon": "^2.0.16", | |||
| "request": "^2.88.2", | |||
| "swagger-jsdoc": "^6.2.1", | |||
| "swagger-ui-express": "^4.4.0", | |||
| "validator": "^13.7.0", | |||
| "winston": "^3.7.2" | |||
| } | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| { | |||
| "Test": "Diligent", | |||
| "DbLocalConnection": "mongodb://127.0.0.1:27017/trampa-dev", | |||
| "DbDockerConnection": "mongodb://mongo:27017/trampa-dev", | |||
| "NODE_ENV": "development" | |||
| } | |||
| @@ -0,0 +1,4 @@ | |||
| module.exports = { | |||
| Admin : 'Admin', | |||
| User: 'User' | |||
| } | |||
| @@ -0,0 +1,70 @@ | |||
| const mongoose = require('mongoose') | |||
| const bcrypt = require('bcryptjs') | |||
| const jwt = require('jsonwebtoken') | |||
| const User = require('./user') | |||
| const tokenSchema = new mongoose.Schema({ | |||
| token: { | |||
| type: String, | |||
| required: true | |||
| }, | |||
| userId: { | |||
| type: String, | |||
| required: true | |||
| } | |||
| }) | |||
| tokenSchema.statics.findByCredentials = async (email, password) => { | |||
| const user = await User.findOne({email}) | |||
| if(!user) { | |||
| return | |||
| } | |||
| const checkMatch = await bcrypt.compare(password, user.password) | |||
| if(checkMatch) { | |||
| return user | |||
| } | |||
| return null | |||
| } | |||
| tokenSchema.statics.generateAuthToken = async function(userArg) { | |||
| console.log('aaa') | |||
| const user = userArg | |||
| const token = jwt.sign({ _id: user._id.toString() }, 'ovoJeSecret', { expiresIn: 60 * 20 }) | |||
| console.log(token) | |||
| user.tokens = user.tokens.concat({ token }) | |||
| await user.save() | |||
| return token | |||
| } | |||
| tokenSchema.statics.refreshAuthToken = async function(token) { | |||
| console.log(token) | |||
| try { | |||
| const payload = jwt.verify(token, 'ovoJeSecret') | |||
| console.log(payload) | |||
| delete payload.iat | |||
| delete payload.exp | |||
| delete payload.nbf | |||
| delete payload.jti | |||
| return jwt.sign(payload, 'ovoJeSecret', { expiresIn: 60 * 20 }) | |||
| } catch(e) { | |||
| return null | |||
| } | |||
| } | |||
| tokenSchema.statics.destroyToken = async function(token) { | |||
| const findUser = await User.findOne({ 'tokens.token': token }) | |||
| if(!findUser) { | |||
| return null | |||
| } | |||
| findUser.tokens = findUser.tokens.filter((currToken) => { | |||
| return currToken.token !== token | |||
| }) | |||
| await findUser.save() | |||
| return true | |||
| } | |||
| const Token = mongoose.model('Token', tokenSchema) | |||
| module.exports = Token | |||
| @@ -0,0 +1,40 @@ | |||
| const mongoose = require('mongoose') | |||
| const userSchema = new mongoose.Schema({ | |||
| name: { | |||
| type: String | |||
| }, | |||
| email: { | |||
| type: String, | |||
| required: true | |||
| }, | |||
| password: { | |||
| type: String, | |||
| required: true | |||
| }, | |||
| roles: { | |||
| type: String | |||
| }, | |||
| tokens: [{ | |||
| token: { | |||
| type: String, | |||
| required: true | |||
| } | |||
| }] | |||
| }) | |||
| // userSchema.pre('save', async function(next) { | |||
| // const user = this | |||
| // console.log('pre hash: ' + user.password) | |||
| // user.password = await bcrypt.hash(user.password, 8) | |||
| // console.log('posle hash: ' + user.password) | |||
| // console.log('Middleware before password hash') | |||
| // next() | |||
| // }) | |||
| const User = mongoose.model('User', userSchema) | |||
| module.exports = User | |||
| @@ -0,0 +1,29 @@ | |||
| const mongoose = require('mongoose') | |||
| const logger = require('../logging/loggerDbCon') | |||
| const config = require('config') | |||
| if (config.util.getEnv('NODE_ENV') === 'development') { | |||
| mongoose.connect(config.get('DbLocalConnection'), { | |||
| useNewUrlParser: true | |||
| }) | |||
| } | |||
| else if (config.util.getEnv('NODE_ENV') === 'docker'){ | |||
| mongoose.connect(config.get('DbDockerConnection'), { | |||
| useNewUrlParser: true | |||
| }) | |||
| } | |||
| mongoose.connection.on('error', err => { | |||
| logger.silly('DB connection failed') | |||
| }) | |||
| mongoose.connection.on('disconnected', () => { | |||
| logger.silly('DB disconnected') | |||
| }) | |||
| mongoose.connection.on('disconnecting', () => { | |||
| logger.silly('DB connection closed by user') | |||
| }) | |||
| mongoose.connection.on('reconnected', () => { | |||
| logger.silly('DB reconnected') | |||
| }) | |||
| @@ -0,0 +1,41 @@ | |||
| const Auth = require('../database/models/token') | |||
| const authMediator = require('../mediator/authMediator') | |||
| const token = async (req, res, next) => { | |||
| try { | |||
| if (!req.body.email || !req.body.password){ | |||
| return res.status(400).send('Pass credentials') | |||
| } | |||
| const result = await authMediator.token() | |||
| if(!result.succeeded){ | |||
| res.status(401).send(result.error) | |||
| } | |||
| return res.status(200).send(result.value) | |||
| } catch (e) { | |||
| next(e) | |||
| } | |||
| } | |||
| const logout = async (req, res) => { | |||
| const result = await Auth.destroyToken(req.body.token) | |||
| if (!result) { | |||
| return res.status(404).send('No user has the token provided!') | |||
| } | |||
| return res.send('Auth ' + req.body.token + ' invalidated!') | |||
| } | |||
| const refreshUserToken = async (req, res) => { | |||
| const form = { | |||
| token: req.body.token | |||
| } | |||
| const result = await Auth.refreshAuthToken(form.token) | |||
| if (!result) { | |||
| return res.status(404).send('Auth not valid!') | |||
| } | |||
| return res.status(201).send('Auth ' + result + ' refreshed successfully!') | |||
| } | |||
| module.exports = { loginUser: token, logout, refreshUserToken } | |||
| @@ -0,0 +1,105 @@ | |||
| const User = require("../database/models/user") | |||
| const { getUserValidator, getIdValidator, getUpdatedUserValidator } = require("../validators/users") | |||
| const mediator = require('../mediator/userMediator') | |||
| const getUsers = async (req, res, next) => { | |||
| const allUsers = await User.find({}) | |||
| return res.status(200).send(allUsers) | |||
| } | |||
| const getUser = async (req, res, next) => { | |||
| try { | |||
| const id = req.params.id | |||
| const errId = getIdValidator.validate(id).error | |||
| if (errId) { | |||
| return res.status(404).send(errId.message) | |||
| } | |||
| const result = await mediator.getUser(id) | |||
| if (!result.succeeded) { | |||
| return res.status(400).send(result.error) | |||
| } | |||
| return res.status(200).json(result.value) | |||
| } catch (e) { | |||
| next(e) | |||
| } | |||
| } | |||
| const createUser = async (req, res, next) => { | |||
| try { | |||
| const userModel = req.body | |||
| const err = getUserValidator.validate(userModel).error | |||
| if (err) { | |||
| return res.status(404).send(err.message) | |||
| } | |||
| const result = await mediator.createUser(userModel) | |||
| if (!result.succeeded) { | |||
| return res.status(400).send(result.error) | |||
| } | |||
| return res.status(201).json(result.value) | |||
| } catch (e) { | |||
| next(e) | |||
| } | |||
| } | |||
| const updateUser = async (req, res, next) => { | |||
| try { | |||
| const id = req.params.id | |||
| const errId = getIdValidator.validate(id).error | |||
| if (errId) { | |||
| return res.status(400).send(errId.message) | |||
| } | |||
| const objBody = req.body | |||
| const err = getUpdatedUserValidator.validate(objBody).error | |||
| if (err) { | |||
| return res.status(400).send(err.message) | |||
| } | |||
| const result = await mediator.updateUser(id, objBody) | |||
| if (!result.succeeded) { | |||
| return res.status(400).send(result.error) | |||
| } | |||
| return res.status(200).send(result.value) | |||
| } catch (e) { | |||
| next(e) | |||
| } | |||
| } | |||
| const deleteUser = async (req, res, next) => { | |||
| try { | |||
| const id = req.params.id | |||
| const errId = getIdValidator.validate(id).error | |||
| if (errId) { | |||
| return res.status(400).send(errId.message) | |||
| } | |||
| const result = await mediator.deleteUser(id) | |||
| if (!result.succeeded) { | |||
| return res.status(400).send(result.error) | |||
| } | |||
| return res.status(204).send(result.value) | |||
| } catch (e) { | |||
| next(e) | |||
| } | |||
| } | |||
| const updateUserContacts = async (req, res, next) => { | |||
| try { | |||
| const userFound = true | |||
| if (!userFound) { | |||
| return res.status(404).send('user not found') | |||
| } | |||
| if (!req.body) { | |||
| return res.status(400).send('invalid input parameters') | |||
| } | |||
| return res.status(200).send('user contacts updated successfully') | |||
| } catch (e) { | |||
| next(e) | |||
| } | |||
| } | |||
| module.exports = { getUsers, getUser, createUser, updateUserContacts, updateUser, deleteUser } | |||
| @@ -0,0 +1,21 @@ | |||
| const winston = require('winston') | |||
| const config = require('config') | |||
| const logger = winston.createLogger({ | |||
| level: 'info', | |||
| format: winston.format.json(), | |||
| defaultMeta: { service: 'user-service' }, | |||
| transports: [ | |||
| new winston.transports.File({ filename: '../public/loggerFiles/error.log', level: 'error' }), | |||
| new winston.transports.File({ filename: '../public/loggerFiles/all.log', level: 'silly' }), | |||
| new winston.transports.Console({level: 'silly'}), | |||
| ], | |||
| }); | |||
| if (config.util.getEnv('NODE_ENV') !== 'production') { | |||
| logger.add(new winston.transports.Console({ | |||
| format: winston.format.simple(), | |||
| })); | |||
| } | |||
| module.exports = logger | |||
| @@ -0,0 +1,19 @@ | |||
| const winston = require('winston') | |||
| const config = require('config') | |||
| const loggerWinston = winston.createLogger({ | |||
| level: 'info', | |||
| format: winston.format.json(), | |||
| defaultMeta: { service: 'user-service' }, | |||
| transports: [ | |||
| new winston.transports.File({ filename: '../public/loggerFiles/dbCon.log', level: 'silly' }) | |||
| ], | |||
| }); | |||
| if (config.util.getEnv('NODE_ENV') !== 'production') { | |||
| loggerWinston.add(new winston.transports.Console({ | |||
| format: winston.format.simple(), | |||
| })); | |||
| } | |||
| module.exports = loggerWinston | |||
| @@ -0,0 +1,31 @@ | |||
| const Auth = require("../database/models/token"); | |||
| const bcrypt = require("bcryptjs"); | |||
| const token = async (email, password) => { | |||
| const foundUser = await Auth.findByCredentials(email, password) | |||
| if (!foundUser) { | |||
| return { | |||
| succeeded: false, | |||
| value: null, | |||
| error: 'Wrong credentials!' | |||
| } | |||
| } | |||
| const isValidPassword = await bcrypt.compare(password, foundUser.password) | |||
| if (!isValidPassword) { | |||
| return { | |||
| succeeded: false, | |||
| value: null, | |||
| error: 'Wrong credentials!' | |||
| } | |||
| } | |||
| const token = await Auth.generateAuthToken(foundUser) | |||
| return { | |||
| succeeded: true, | |||
| value: token, | |||
| error: null | |||
| } | |||
| } | |||
| module.exports = {token} | |||
| @@ -0,0 +1,80 @@ | |||
| const bcrypt = require("bcryptjs/dist/bcrypt") | |||
| const User = require("../database/models/user") | |||
| const { getUserValidator } = require("../validators/users") | |||
| const getUser = async (id) => { | |||
| const user = await User.findById(id) | |||
| if (!user) { | |||
| return { | |||
| succeeded: false, | |||
| value: null, | |||
| error: 'User not found!' | |||
| } | |||
| } | |||
| return { | |||
| succeeded: true, | |||
| value: user, | |||
| error: null | |||
| } | |||
| } | |||
| const createUser = async (user) => { | |||
| const foundMail = await User.find({ email: user.email }) | |||
| if (foundMail.length) { | |||
| return { | |||
| succeeded: false, | |||
| value: null, | |||
| error: 'User with email already exists!' | |||
| } | |||
| } | |||
| const newUser = new User(user) | |||
| newUser.password = await bcrypt.hash(newUser.password, 8) | |||
| await User.create(newUser) | |||
| return { | |||
| succeeded: true, | |||
| value: newUser, | |||
| error: null | |||
| } | |||
| } | |||
| const updateUser = async (id, body) => { | |||
| let user = await User.findById(id); | |||
| if (!user) { | |||
| return { | |||
| succeeded: false, | |||
| value: null, | |||
| error: 'User with id ' + id + ' does not exist!' | |||
| } | |||
| } | |||
| const updatedUser = { | |||
| name: body.name, | |||
| roles: body.roles | |||
| } | |||
| await User.updateOne({ _id: id }, updatedUser) | |||
| return { | |||
| succeeded: true, | |||
| value: 'User updated successfully!', | |||
| error: null | |||
| } | |||
| } | |||
| const deleteUser = async (id) => { | |||
| let user = await User.findById(id) | |||
| if (!user) { | |||
| return { | |||
| succeeded: false, | |||
| value: null, | |||
| error: 'User with id ' + id + ' does not exist!' | |||
| } | |||
| } | |||
| await User.deleteOne(user) | |||
| return { | |||
| succeeded: true, | |||
| value: null, | |||
| error: null | |||
| } | |||
| } | |||
| module.exports = {getUser, createUser, updateUser, deleteUser} | |||
| @@ -0,0 +1,40 @@ | |||
| const jwt = require('jsonwebtoken') | |||
| const User = require('../database/models/user') | |||
| const Role = require('../database/models/roles') | |||
| const auth = async (req, res, next) => { | |||
| try { | |||
| const token = req.header('Authorization').replace('Bearer ', '') | |||
| const decoded = jwt.verify(token, 'ovoJeSecret') | |||
| console.log(decoded) | |||
| } catch (e) { | |||
| return res.send(e) | |||
| } | |||
| console.log('auth middleware') | |||
| next() | |||
| } | |||
| const authRole = async (req, res, next) => { | |||
| try { | |||
| const token = req.header('Authorization').replace('Bearer ', '') | |||
| if(!token) { | |||
| return res.status(401).send('Invalid token!') | |||
| } | |||
| const findUser = await User.findOne({ 'tokens.token': token }) | |||
| if(!findUser) { | |||
| return res.status(401).send('No user has the token provided!') | |||
| } | |||
| if(findUser['role'] === Role.Admin) { | |||
| console.log('User is admin!') | |||
| next() | |||
| } | |||
| else { | |||
| return res.status(403).send('Access forbidden!') | |||
| } | |||
| } catch(e) { | |||
| next(e) | |||
| } | |||
| } | |||
| module.exports = { auth, authRole } | |||
| @@ -0,0 +1,15 @@ | |||
| const logger = require('../logging/logger') | |||
| const config = require('config') | |||
| const errorLogger = (err, req, res, next) => { | |||
| if (config.util.getEnv('NODE_ENV') === 'development') | |||
| logger.error(err) | |||
| next(err) | |||
| } | |||
| const errorResponder = (err, req, res, next) => { | |||
| res.status(err.statusCode).send(err) | |||
| } | |||
| module.exports = { errorLogger, errorResponder } | |||
| @@ -0,0 +1,10 @@ | |||
| const logger = require("../logging/logger") | |||
| const requestLogging = async (req, res, next) => { | |||
| res.header("Content-Type", 'application/json'); | |||
| logger.silly(req) | |||
| next() | |||
| } | |||
| module.exports = requestLogging | |||
| @@ -0,0 +1,10 @@ | |||
| const express = require('express') | |||
| const router = new express.Router() | |||
| const endpoints = require('../endpoints/auth') | |||
| router.post('/auth/token', endpoints.loginUser) | |||
| router.post('/auth/logout', endpoints.logout) | |||
| router.post('/auth/refresh', endpoints.refreshUserToken) | |||
| module.exports = router | |||
| @@ -0,0 +1,14 @@ | |||
| const express = require('express') | |||
| const endpoints = require('../endpoints/user') | |||
| const router = new express.Router() | |||
| const auth = require('../middleware/auth') | |||
| router.get('/users', endpoints.getUsers) | |||
| router.get('/users/:id', endpoints.getUser) | |||
| router.post('/users', endpoints.createUser) | |||
| router.put('/users/:id', endpoints.updateUser) | |||
| router.patch('/users/:id/contacts', endpoints.updateUserContacts) | |||
| router.delete('/users/:id', endpoints.deleteUser) | |||
| module.exports = router | |||
| @@ -0,0 +1,41 @@ | |||
| const config = require('config') //Default configuration file | |||
| const express = require('express') | |||
| const app = express() | |||
| const port = process.env.NODE_ENV === 'production' ? 80 : 3001 | |||
| require('./database/mongoose') | |||
| const docs = require('./swagger.js') | |||
| const swaggerUI = require('swagger-ui-express') | |||
| const { errorLogger, errorResponder } = require('./middleware/errorHandling.js') | |||
| const requestLogging = require('./middleware/requestLogging.js') | |||
| const cors = require('cors') //Cross-origin resource sharing | |||
| const helmet = require('helmet') //Basic protection against attacks like XSS | |||
| const fs = require('fs') | |||
| const path = require('path') | |||
| const routesDirectory = path.resolve(__dirname) + '/routes/' | |||
| // console.log(config.util.getEnv('NODE_ENV')) | |||
| app.use(errorLogger); | |||
| app.use(errorResponder); | |||
| app.use(express.json()) | |||
| app.use('/swagger', swaggerUI.serve, swaggerUI.setup(docs)) | |||
| app.use(requestLogging) | |||
| app.use(cors()) | |||
| app.use(helmet()) | |||
| fs.readdirSync(routesDirectory).forEach(route => { | |||
| app.use(require(routesDirectory + route)) | |||
| }) | |||
| app.get('/', (req, res) => { | |||
| try { | |||
| res.send('Wello Horld!') | |||
| } catch (e) { | |||
| res.status(500).send(e) | |||
| } | |||
| }) | |||
| app.listen(port, () => { | |||
| console.log('Server is up on port ' + port) | |||
| }) | |||
| @@ -0,0 +1,351 @@ | |||
| module.exports = { | |||
| servers: [ | |||
| { | |||
| url: "http://localhost:3001/", | |||
| description: "Local server", | |||
| }, | |||
| ], | |||
| tags: [ | |||
| { | |||
| name: "User" | |||
| }, | |||
| ], | |||
| openapi: "3.0.3", | |||
| info: { | |||
| title: "Trampa", | |||
| description: "Trampa api" | |||
| }, | |||
| paths: { | |||
| '/users': { | |||
| get: { | |||
| tags: ["User"], | |||
| description: "Get all users", | |||
| parameters: [], | |||
| responses: { | |||
| 200: { | |||
| description: "Success", | |||
| content: { | |||
| "application/json": { | |||
| schema: { | |||
| $ref: "#/components/schemas/User", | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| 200: { | |||
| description: "Users returned successfully" | |||
| }, | |||
| 500: { | |||
| description: "Internal server error" | |||
| } | |||
| } | |||
| }, | |||
| post: { | |||
| tags: ["User"], | |||
| description: "Create user", | |||
| parameters: [], | |||
| requestBody: { | |||
| content: { | |||
| "application/json": { | |||
| schema: { | |||
| type: "object", | |||
| properties: { | |||
| name: { | |||
| type: "string" | |||
| }, | |||
| email: { | |||
| type: "string" | |||
| }, | |||
| password: { | |||
| type: "string" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| responses: { | |||
| 201: { | |||
| description: "User created successfully", | |||
| content: { | |||
| "application/json": { | |||
| schema: { | |||
| $ref: "#/components/schemas/User" | |||
| }, | |||
| }, | |||
| }, | |||
| }, | |||
| 400: { | |||
| description: "Object cant be empty", | |||
| }, | |||
| 401: { | |||
| description: "Invalid input parameters", | |||
| }, | |||
| 500: { | |||
| description: "Internal server error", | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| '/users/{id}': { | |||
| get: { | |||
| tags: ["User"], | |||
| description: "Get user by id", | |||
| parameters: [ | |||
| { | |||
| name: "id", | |||
| in: "path", | |||
| schema: { | |||
| $ref: "#/components/schemas/id", | |||
| }, | |||
| required: true, | |||
| description: "A single user id", | |||
| } | |||
| ], | |||
| responses: { | |||
| 200: { | |||
| description: "Success", | |||
| content: { | |||
| "application/json": { | |||
| schema: { | |||
| $ref: "#/components/schemas/User", | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| 400: { | |||
| description: "Bad request" | |||
| }, | |||
| 404: { | |||
| description: "User with specified id does not exist" | |||
| }, | |||
| 500: { | |||
| description: "Internal server error" | |||
| } | |||
| } | |||
| }, | |||
| put: { | |||
| tags: ["User"], | |||
| description: "Update user", | |||
| parameters: [ | |||
| { | |||
| name: "id", | |||
| in: "path", | |||
| schema: { | |||
| $ref: "#/components/schemas/id", // data model of the param | |||
| }, | |||
| required: true, | |||
| description: "A single user id", | |||
| } | |||
| ], | |||
| requestBody: { | |||
| content: { | |||
| "application/json": { | |||
| schema: { | |||
| type: "object", | |||
| properties: { | |||
| name: { | |||
| type: "string" | |||
| }, | |||
| email: { | |||
| type: "string" | |||
| }, | |||
| password: { | |||
| type: "string" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| responses: { | |||
| 200: { | |||
| description: "User updated successfully", | |||
| }, | |||
| 400: { | |||
| description: "Invalid input parameters", | |||
| }, | |||
| 500: { | |||
| description: "Server error", | |||
| } | |||
| } | |||
| }, | |||
| patch: { | |||
| tags: ["User"], | |||
| description: "Update user", | |||
| parameters: [ | |||
| { | |||
| name: "id", | |||
| in: "path", | |||
| // schema: { | |||
| // $ref: "#/components/schemas/id", // data model of the param | |||
| // }, | |||
| required: true, | |||
| description: "A single user id", | |||
| } | |||
| ], | |||
| requestBody: { | |||
| content: { | |||
| } | |||
| }, | |||
| responses: { | |||
| } | |||
| }, | |||
| delete: { | |||
| tags: ["User"], | |||
| description: "Delete user", | |||
| parameters: [ | |||
| { | |||
| name: "id", | |||
| in: "path", | |||
| schema: { | |||
| $ref: "#/components/schemas/id", | |||
| }, | |||
| required: true, | |||
| }, | |||
| ], | |||
| responses: { | |||
| 204: { | |||
| description: "User deleted successfully", | |||
| }, | |||
| 400: { | |||
| description: "You need to provide valid Id'", | |||
| }, | |||
| 404: { | |||
| description: "User not found", | |||
| }, | |||
| 500: { | |||
| description: "Internal server error", | |||
| }, | |||
| }, | |||
| } | |||
| }, | |||
| '/auth/token': { | |||
| post: { | |||
| tags: ["Token"], | |||
| description: "Log in user", | |||
| parameters: [], | |||
| requestBody: { | |||
| content: { | |||
| "application/json": { | |||
| schema: { | |||
| type: "object", | |||
| properties: { | |||
| email: { | |||
| type: "string" | |||
| }, | |||
| password: { | |||
| type: "string" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| responses: { | |||
| 201: { | |||
| description: "User logged in successfully!" | |||
| }, | |||
| 400: { | |||
| description: "Wrong credentials!" | |||
| }, | |||
| 500: { | |||
| description: "Internal server error" | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| '/auth/logout': { | |||
| post: { | |||
| tags: ["Token"], | |||
| description: "Log out user", | |||
| parameters: [], | |||
| requestBody: { | |||
| content: { | |||
| "application/json": { | |||
| schema: { | |||
| $ref: "#/components/schemas/Token" | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| responses: { | |||
| 201: { | |||
| description: "User logged out successfully" | |||
| }, | |||
| 404: { | |||
| description: "No user has the token provided!" | |||
| }, | |||
| 500: { | |||
| description: "Internal server error" | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| '/auth/refresh': { | |||
| post: { | |||
| tags: ["Token"], | |||
| description: "Log out user", | |||
| parameters: [], | |||
| requestBody: { | |||
| content: { | |||
| "application/json": { | |||
| schema: { | |||
| $ref: "#/components/schemas/Token" | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| responses: { | |||
| 201: { | |||
| description: "User refreshed successfully" | |||
| }, | |||
| 404: { | |||
| description: "Token not valid!" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| components: { | |||
| schemas: { | |||
| id: { | |||
| type: "string", | |||
| description: "An id of a user" | |||
| }, | |||
| User: { | |||
| type: "object", | |||
| properties: { | |||
| name: { | |||
| type: "string" | |||
| }, | |||
| email: { | |||
| type: "string" | |||
| }, | |||
| password: { | |||
| type: "string" | |||
| }, | |||
| tokens: { | |||
| type: "array", | |||
| items: { | |||
| type: "string" | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| Token: { | |||
| type: "object", | |||
| properties: { | |||
| token: { | |||
| type: "string" | |||
| }, | |||
| userId: { | |||
| type: "string" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| const Joi = require("joi"); | |||
| Joi.objectId = require('joi-objectid')(Joi) | |||
| const getIdValidator = Joi.objectId().required(); | |||
| const getUserValidator = Joi.object({ | |||
| name: Joi.string().min(2).required(), | |||
| password: Joi.string().min(8).regex(/[a-zA-Z0-9]{3,30}/).required(), | |||
| email: Joi.string().email().required(), | |||
| roles: Joi.string() | |||
| }) | |||
| const getUpdatedUserValidator = Joi.object({ | |||
| name: Joi.string().min(2).required(), | |||
| roles: Joi.string() | |||
| }) | |||
| module.exports = {getUserValidator, getIdValidator, getUpdatedUserValidator} | |||