forked from fr/deguapp
		
	Added: tests, signin api, not working docker
This commit is contained in:
		
							
								
								
									
										23
									
								
								api/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								api/Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | FROM node:18-alpine | ||||||
|  |  | ||||||
|  | WORKDIR /nork | ||||||
|  |  | ||||||
|  | COPY ../../nork . | ||||||
|  |  | ||||||
|  | RUN npm install | ||||||
|  |  | ||||||
|  | RUN npm run build | ||||||
|  |  | ||||||
|  | RUN npm link | ||||||
|  |  | ||||||
|  | WORKDIR /app | ||||||
|  |  | ||||||
|  | COPY package*.json ./ | ||||||
|  |  | ||||||
|  | RUN npm install | ||||||
|  |  | ||||||
|  | COPY . . | ||||||
|  |  | ||||||
|  | EXPOSE 3000 | ||||||
|  |  | ||||||
|  | CMD ["npm", "start"] | ||||||
							
								
								
									
										19
									
								
								api/docker-compose.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								api/docker-compose.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | services: | ||||||
|  |   mongo: | ||||||
|  |     image: mongo | ||||||
|  |     restart: always | ||||||
|  |     environment: | ||||||
|  |       MONGO_INITDB_ROOT_USERNAME: root | ||||||
|  |       MONGO_INITDB_ROOT_PASSWORD: root | ||||||
|  |     ports: | ||||||
|  |       - 27017:27017 | ||||||
|  |   mongo-express: | ||||||
|  |     image: mongo-express | ||||||
|  |     restart: always | ||||||
|  |     ports: | ||||||
|  |       - 8081:8081 | ||||||
|  |     environment: | ||||||
|  |       ME_CONFIG_MONGODB_ADMINUSERNAME: root | ||||||
|  |       ME_CONFIG_MONGODB_ADMINPASSWORD: root | ||||||
|  |       ME_CONFIG_MONGODB_URL: mongodb://root:root@mongo:27017/ | ||||||
|  |       ME_CONFIG_BASICAUTH: false | ||||||
| @@ -19,22 +19,29 @@ | |||||||
| 	"author": "Filip Rojek", | 	"author": "Filip Rojek", | ||||||
| 	"license": "MIT", | 	"license": "MIT", | ||||||
| 	"dependencies": { | 	"dependencies": { | ||||||
|  | 		"bcrypt": "^5.1.1", | ||||||
| 		"colors": "1.4.0", | 		"colors": "1.4.0", | ||||||
| 		"dotenv": "^16.4.5", | 		"dotenv": "^16.4.5", | ||||||
| 		"express": "^4.19.2", | 		"express": "^4.19.2", | ||||||
| 		"fs-extra": "^10.0.0", | 		"fs-extra": "^10.0.0", | ||||||
| 		"inquirer": "^8.1.2", | 		"inquirer": "^8.1.2", | ||||||
|  | 		"jsonwebtoken": "^9.0.2", | ||||||
|  | 		"mongoose": "^8.3.3", | ||||||
| 		"morgan": "^1.10.0", | 		"morgan": "^1.10.0", | ||||||
| 		"pad": "^3.2.0", | 		"pad": "^3.2.0", | ||||||
| 		"path": "^0.12.7" | 		"path": "^0.12.7", | ||||||
|  | 		"yup": "^1.4.0", | ||||||
|  | 		"yup-password": "^0.4.0" | ||||||
| 	}, | 	}, | ||||||
| 	"devDependencies": { | 	"devDependencies": { | ||||||
| 		"@biomejs/biome": "1.7.1", | 		"@biomejs/biome": "1.7.1", | ||||||
|  | 		"@types/bcrypt": "^5.0.2", | ||||||
| 		"@types/chai": "^4.2.22", | 		"@types/chai": "^4.2.22", | ||||||
| 		"@types/express": "^4.17.21", | 		"@types/express": "^4.17.21", | ||||||
| 		"@types/fs-extra": "^9.0.13", | 		"@types/fs-extra": "^9.0.13", | ||||||
| 		"@types/inquirer": "^8.1.3", | 		"@types/inquirer": "^8.1.3", | ||||||
| 		"@types/jest": "^29.5.12", | 		"@types/jest": "^29.5.12", | ||||||
|  | 		"@types/jsonwebtoken": "^9.0.6", | ||||||
| 		"@types/mocha": "^9.0.0", | 		"@types/mocha": "^9.0.0", | ||||||
| 		"@types/morgan": "^1.9.9", | 		"@types/morgan": "^1.9.9", | ||||||
| 		"@types/shelljs": "^0.8.11", | 		"@types/shelljs": "^0.8.11", | ||||||
| @@ -46,6 +53,7 @@ | |||||||
| 		"http": "^0.0.1-security", | 		"http": "^0.0.1-security", | ||||||
| 		"jest": "^29.7.0", | 		"jest": "^29.7.0", | ||||||
| 		"mocha": "^9.1.3", | 		"mocha": "^9.1.3", | ||||||
|  | 		"mongodb-memory-server": "^9.2.0", | ||||||
| 		"npm-run-all": "^4.1.5", | 		"npm-run-all": "^4.1.5", | ||||||
| 		"prettier": "^2.7.1", | 		"prettier": "^2.7.1", | ||||||
| 		"rimraf": "^3.0.2", | 		"rimraf": "^3.0.2", | ||||||
|   | |||||||
| @@ -1,5 +1,81 @@ | |||||||
| import { Request, Response } from "express"; | import { Request, Response } from 'express'; | ||||||
|  | import bcrypt from 'bcrypt'; | ||||||
|  | import jwt from 'jsonwebtoken'; | ||||||
|  | import env from '../config/environment'; | ||||||
|  | import Docs from '../services/docsService'; | ||||||
|  | import User from '../models/User'; | ||||||
|  | import {Log} from 'nork' | ||||||
|  | import { IUser, signupExam, ISignin, signinExam } from '../validators/authValidator'; | ||||||
|  |  | ||||||
| export function signup_post(req: Request, res: Response) { | new Docs('user', 'signup', '/api/v1/auth/signup', 'POST', 'user signup api', undefined, signupExam, 'status object'); | ||||||
|   res.send("logged in"); | export async function signup_post(req: Request, res: Response) { | ||||||
|  | 	try { | ||||||
|  | 		const payload: IUser = req.body; | ||||||
|  |  | ||||||
|  | 		payload.password = await bcrypt.hash(payload.password, 12); | ||||||
|  | 		const user = new User(payload); | ||||||
|  | 		await user.save(); | ||||||
|  |  | ||||||
|  | 		res.status(201).json(Log.info(201, 'user was successfully signed up')); | ||||||
|  | 	} catch (err: any) { | ||||||
|  | 		if (err.code == 11000) { | ||||||
|  | 			res.status(400).json(Log.error(400, 'this user already exists')); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		Log.error(500, err); | ||||||
|  | 		res.status(500).json(Log.error(500, 'something went wrong')); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | new Docs('user', 'signin', '/api/v1/auth/signin', 'POST', 'user signin api', undefined, signinExam, 'status object'); | ||||||
|  | export async function signin_post(req: Request, res: Response) { | ||||||
|  | 	try { | ||||||
|  | 		const payload: ISignin = req.body; | ||||||
|  |  | ||||||
|  | 		const user = await User.findOne({ email: payload.email }); | ||||||
|  | 		if (!user) { | ||||||
|  | 			res.cookie('jwt', '', { httpOnly: true, maxAge: 0 }); | ||||||
|  | 			res.cookie('auth', false, { httpOnly: false, maxAge: 0 }); | ||||||
|  | 			res.status(401).json(Log.error(401, 'email or password is wrong')); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (await bcrypt.compare(payload.password, user.password)) { | ||||||
|  | 			const maxAge = 3 * 24 * 60 * 60; // 3 days in seconds | ||||||
|  | 			const createToken = (id: any) => { | ||||||
|  | 				return jwt.sign({ id }, env.JWT_SECRET, { | ||||||
|  | 					expiresIn: maxAge | ||||||
|  | 				}); | ||||||
|  | 			}; | ||||||
|  |  | ||||||
|  | 			const token = createToken(user._id); | ||||||
|  | 			res.cookie('jwt', token, { httpOnly: true, maxAge: maxAge * 1000 }); | ||||||
|  | 			res.cookie('auth', true, { httpOnly: false, maxAge: maxAge * 1000 }); | ||||||
|  |  | ||||||
|  | 			res.json(Log.info(200, 'user is logged in')); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		res.cookie('jwt', '', { httpOnly: true, maxAge: 0 }); | ||||||
|  | 		res.cookie('auth', false, { httpOnly: false, maxAge: 0 }); | ||||||
|  | 		res.status(401).json(Log.error(401, 'email or password is wrong')); | ||||||
|  | 	} catch (err: any) { | ||||||
|  | 		Log.error(500, err); | ||||||
|  | 		res.status(500).json(Log.error(500, 'something went wrong')); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | new Docs('user', 'logout', '/api/v1/auth/logout', 'POST', 'user logout api', undefined, {}, 'status object'); | ||||||
|  | export function logout_post(req: Request, res: Response) { | ||||||
|  | 	res.cookie('jwt', '', { httpOnly: true, maxAge: 0 }); | ||||||
|  | 	res.cookie('auth', false, { httpOnly: false, maxAge: 0 }); | ||||||
|  | 	res.json(Log.info(200, 'user was logged out')); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | new Docs('user', 'status', '/api/v1/auth/status', 'GET', 'user login status api', undefined, undefined, 'status code | user object'); | ||||||
|  | export function status_get(req: Request, res: Response) { | ||||||
|  | 	let userObject = res.locals.user; | ||||||
|  | 	userObject.password = undefined; | ||||||
|  | 	userObject.__v = undefined; | ||||||
|  | 	res.status(200).json(Log.info(200, 'user is logged in', userObject)); | ||||||
| } | } | ||||||
							
								
								
									
										15
									
								
								api/src/middlewares/validateRequest.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								api/src/middlewares/validateRequest.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | import { Request, Response, NextFunction } from 'express'; | ||||||
|  | import { object, AnySchema } from 'yup'; | ||||||
|  | import { Log } from 'nork'; | ||||||
|  |  | ||||||
|  | const validate = (schema: AnySchema) => async (req: Request, res: Response, next: NextFunction) => { | ||||||
|  | 	try { | ||||||
|  | 		await schema.validate(req.body); | ||||||
|  |  | ||||||
|  | 		next(); | ||||||
|  | 	} catch (err: any) { | ||||||
|  | 		return res.status(400).json(Log.error(400, 'validation error', err)); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default validate; | ||||||
							
								
								
									
										26
									
								
								api/src/models/User.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								api/src/models/User.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | import path from 'path'; | ||||||
|  | import { Schema, model } from 'mongoose'; | ||||||
|  | import { IUser } from '../validators/authValidator'; | ||||||
|  |  | ||||||
|  | const schema = new Schema<IUser>( | ||||||
|  | 	{ | ||||||
|  | 		username: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: true | ||||||
|  | 		}, | ||||||
|  | 		email: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: true, | ||||||
|  | 			unique: true | ||||||
|  | 		}, | ||||||
|  | 		password: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: true | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		timestamps: true | ||||||
|  | 	} | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | export default model(path.basename(__filename).split('.')[0], schema); | ||||||
| @@ -1,6 +1,7 @@ | |||||||
| import { Router } from "express"; | import { Router } from "express"; | ||||||
| import * as authController from "../controllers/authController"; | import * as authController from "../controllers/authController"; | ||||||
| //import authValidator from "../validators/authValidator";
 | import validate from '../middlewares/validateRequest' | ||||||
|  | import * as AuthVal from '../validators/authValidator' | ||||||
| //import handleValidation from "../middlewares/handleValidation";
 | //import handleValidation from "../middlewares/handleValidation";
 | ||||||
| //import { requireAuth } from "../middlewares/authMiddleware";
 | //import { requireAuth } from "../middlewares/authMiddleware";
 | ||||||
| 
 | 
 | ||||||
| @@ -8,7 +9,7 @@ const router = Router(); | |||||||
| 
 | 
 | ||||||
| //const mws = [requireAuth, handleValidation.handleValidationError];
 | //const mws = [requireAuth, handleValidation.handleValidationError];
 | ||||||
| 
 | 
 | ||||||
| router.post("/signup", authController.signup_post); | router.post("/auth/signup",validate(AuthVal.signup) , authController.signup_post); | ||||||
| 
 | 
 | ||||||
| //router.post(
 | //router.post(
 | ||||||
| //  "/login",
 | //  "/login",
 | ||||||
| @@ -1,9 +1,9 @@ | |||||||
| import { Request, Response, Router } from "express"; | import { Request, Response, Router } from "express"; | ||||||
| import path from "path"; | import path from "path"; | ||||||
| import authRoutes from "./authRoutes"; | import api_v1 from "./api_v1"; | ||||||
| export const router = Router(); | export const router = Router(); | ||||||
|  |  | ||||||
| router.use("/api/auth", authRoutes); | router.use("/api/v1", api_v1); | ||||||
|  |  | ||||||
| //router.get("*", (req: Request, res: Response) => { | //router.get("*", (req: Request, res: Response) => { | ||||||
| //  res.sendFile(path.join(__dirname, "../views/index.html")); | //  res.sendFile(path.join(__dirname, "../views/index.html")); | ||||||
|   | |||||||
| @@ -1,26 +1,40 @@ | |||||||
| import http from "http"; | import http from "http"; | ||||||
| import { app } from "./app"; | import { app } from "./app"; | ||||||
| import env from "./config/environment"; | import env from "./config/environment"; | ||||||
| //const env = { | import mongoose from 'mongoose' // TODO: dopsat nork module pro db | ||||||
| //  APP_PORT: 8080, |  | ||||||
| //  APP_HOSTNAME: "127.0.0.1", |  | ||||||
| //}; |  | ||||||
| import { Log } from "nork"; | import { Log } from "nork"; | ||||||
| //import database from './config/database' |  | ||||||
| const port: number = env.APP_PORT || 8080; | const port: number = env.APP_PORT || 8080; | ||||||
| const hostname: string = env.APP_HOSTNAME || "localhost"; | const hostname: string = env.APP_HOSTNAME || "localhost"; | ||||||
|  |  | ||||||
| export const server = http.createServer(app); | export const server = http.createServer(app); | ||||||
|  |  | ||||||
| // Server | // Server | ||||||
| export function runServer(): void { | //export function runServer(): void { | ||||||
|   server.listen(port, hostname, () => { | //  server.listen(port, hostname, () => { | ||||||
|     Log.info(200, `Server is listening on http://${hostname}:${port}`); | //    Log.info(200, `Server is listening on http://${hostname}:${port}`); | ||||||
|   }); | //  }); | ||||||
| } | //} | ||||||
|  | // | ||||||
| //if (!env.NORK.database) { | ////if (!env.NORK.database) { | ||||||
| runServer(); | //runServer(); | ||||||
| //} else { | //} else { | ||||||
| //	const db_connection = database() | //	const db_connection = database() | ||||||
| //	runServer() | //	runServer() | ||||||
| //} | //} | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | (async () => { | ||||||
|  | 	if (!process.env.DOCS_GEN) { | ||||||
|  | 		try { | ||||||
|  | 			await mongoose.connect(env.DB_URI); | ||||||
|  | 			Log.info(200, 'connected to db'); | ||||||
|  | 			server.listen(port, () => { | ||||||
|  | 				Log.info(200, `Server is listening on http://localhost:${port}`); | ||||||
|  | 			}); | ||||||
|  | 		} catch (err: any) { | ||||||
|  | 			Log.error(500, err); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | })(); | ||||||
|   | |||||||
							
								
								
									
										62
									
								
								api/src/services/docsService.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								api/src/services/docsService.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | |||||||
|  | import fs from 'fs'; | ||||||
|  | import path from 'path'; | ||||||
|  |  | ||||||
|  | export type HttpMethods = 'POST' | 'GET' | 'PUT' | 'DELETE'; | ||||||
|  | export type ApiResponse = 'status object' | Object | string; | ||||||
|  |  | ||||||
|  | export class Docs { | ||||||
|  | 	private name: string; | ||||||
|  | 	private operation: string; | ||||||
|  | 	private route: string; | ||||||
|  | 	private method: HttpMethods; | ||||||
|  | 	private parameters?: Object[]; | ||||||
|  | 	private description?: string; | ||||||
|  | 	private body?: Object; | ||||||
|  | 	private response?: ApiResponse; | ||||||
|  |  | ||||||
|  | 	public constructor(name: string, operation: string, route: string, method: HttpMethods, description?: string, parameters?: Object[], body?: Object, response?: ApiResponse) { | ||||||
|  | 		this.name = name; | ||||||
|  | 		this.operation = operation; | ||||||
|  | 		this.route = route; | ||||||
|  | 		this.method = method; | ||||||
|  | 		description ? (this.description = description) : null; | ||||||
|  | 		parameters ? (this.parameters = parameters) : null; | ||||||
|  | 		body ? (this.body = body) : null; | ||||||
|  | 		response ? (this.response = response) : null; | ||||||
|  |  | ||||||
|  | 		this.generate(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	private generate(): boolean { | ||||||
|  | 		if (!process.env.DOCS_GEN) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		const jsonPath = path.join(__dirname, '../public/api.json'); | ||||||
|  | 		const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json')).toString()); | ||||||
|  |  | ||||||
|  | 		const jsonAPI = () => JSON.parse(fs.readFileSync(jsonPath).toString()); | ||||||
|  | 		const genJsonAPI = () => fs.writeFileSync(jsonPath, JSON.stringify({ version: pkg.version, endpoints: {} })); | ||||||
|  |  | ||||||
|  | 		if (!fs.existsSync(path.join(__dirname, '../public'))) { | ||||||
|  | 			fs.mkdirSync(path.join(__dirname, '../public')); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (!fs.existsSync(jsonPath)) { | ||||||
|  | 			genJsonAPI(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (jsonAPI().version != pkg.version) { | ||||||
|  | 			genJsonAPI(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		const data = jsonAPI(); | ||||||
|  | 		data.endpoints[this.name] ? undefined : (data.endpoints[this.name] = {}); | ||||||
|  | 		data.endpoints[this.name][this.operation] = this; | ||||||
|  | 		fs.writeFileSync(jsonPath, JSON.stringify(data)); | ||||||
|  |  | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default Docs; | ||||||
| @@ -1,48 +0,0 @@ | |||||||
| import { Sequelize } from 'sequelize'; |  | ||||||
|  |  | ||||||
| let sequelize: Sequelize | null = null; |  | ||||||
|  |  | ||||||
| const connectDB = async () => { |  | ||||||
|   sequelize = new Sequelize({ |  | ||||||
|     dialect: 'mariadb', |  | ||||||
|     host: 'localhost', |  | ||||||
|     username: 'your_username', |  | ||||||
|     password: 'your_password', |  | ||||||
|     database: 'your_database', |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   try { |  | ||||||
|     await sequelize.authenticate(); |  | ||||||
|     console.log('Connection to the database has been established successfully.'); |  | ||||||
|   } catch (error) { |  | ||||||
|     console.error('Unable to connect to the database:', error); |  | ||||||
|   } |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const dropDB = async () => { |  | ||||||
|   if (sequelize) { |  | ||||||
|     try { |  | ||||||
|       await sequelize.drop(); |  | ||||||
|       console.log('Database dropped successfully.'); |  | ||||||
|     } catch (error) { |  | ||||||
|       console.error('Error dropping database:', error); |  | ||||||
|     } finally { |  | ||||||
|       await sequelize.close(); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const dropTables = async () => { |  | ||||||
|   if (sequelize) { |  | ||||||
|     try { |  | ||||||
|       await sequelize.sync({ force: true }); |  | ||||||
|       console.log('All tables dropped successfully.'); |  | ||||||
|     } catch (error) { |  | ||||||
|       console.error('Error dropping tables:', error); |  | ||||||
|     } finally { |  | ||||||
|       await sequelize.close(); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export { connectDB, dropDB, dropTables }; |  | ||||||
							
								
								
									
										30
									
								
								api/src/utils/test_mongodb.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								api/src/utils/test_mongodb.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | const mongoose = require('mongoose'); | ||||||
|  | const { MongoMemoryServer } = require('mongodb-memory-server'); | ||||||
|  |  | ||||||
|  | let mongo: any = null; | ||||||
|  |  | ||||||
|  | const connectDB = async () => { | ||||||
|  | 	mongo = await MongoMemoryServer.create({ binary: { os: { os: 'linux', dist: 'ubuntu', release: '18.04' } } }); // TODO: check that host OS is Void Linux, else remove the argument  | ||||||
|  | 	const uri = mongo.getUri(); | ||||||
|  |  | ||||||
|  | 	await mongoose.connect(uri); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const dropDB = async () => { | ||||||
|  | 	if (mongo) { | ||||||
|  | 		await mongoose.connection.dropDatabase(); | ||||||
|  | 		await mongoose.connection.close(); | ||||||
|  | 		await mongo.stop(); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const dropCollections = async () => { | ||||||
|  | 	if (mongo) { | ||||||
|  | 		const collections = await mongoose.connection.db.collections(); | ||||||
|  | 		for (let collection of collections) { | ||||||
|  | 			await collection.remove(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export { connectDB, dropDB, dropCollections }; | ||||||
							
								
								
									
										34
									
								
								api/src/validators/authValidator.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								api/src/validators/authValidator.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | import * as yup from 'yup'; | ||||||
|  | import YupPassword from 'yup-password'; | ||||||
|  | YupPassword(yup); | ||||||
|  | import { Schema } from 'mongoose'; | ||||||
|  |  | ||||||
|  | interface mongooseAddition { | ||||||
|  | 	_id?: Schema.Types.ObjectId; | ||||||
|  | 	createdAt?: Schema.Types.Date; | ||||||
|  | 	updatedAt?: Schema.Types.Date; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SignUp | ||||||
|  | export const signup = yup.object({ | ||||||
|  | 	username: yup.string().required(), | ||||||
|  | 	email: yup.string().email().required(), | ||||||
|  | 	password: yup.string().min(8).minLowercase(1).minUppercase(1).minNumbers(1).required() | ||||||
|  | }); | ||||||
|  | export interface IUser extends yup.InferType<typeof signup>, mongooseAddition {} | ||||||
|  | export const signupExam: IUser = { | ||||||
|  | 	username: 'testuser', | ||||||
|  | 	email: 'text@example.com', | ||||||
|  | 	password: 'Test1234' | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // SignIn | ||||||
|  | export const signin = yup.object({ | ||||||
|  | 	email: yup.string().email().required(), | ||||||
|  | 	password: yup.string().min(8).minLowercase(1).minUppercase(1).minNumbers(1).required() | ||||||
|  | }); | ||||||
|  | export interface ISignin extends yup.InferType<typeof signin>, mongooseAddition {} | ||||||
|  | export const signinExam: ISignin = { | ||||||
|  | 	email: 'text@example.com', | ||||||
|  | 	password: 'Test1234' | ||||||
|  | }; | ||||||
| @@ -1,17 +1,227 @@ | |||||||
| import request from "supertest" | import supertest from 'supertest'; | ||||||
| import {server as app} from "../server" | import { app } from '../src/app'; | ||||||
|  | import { connectDB, dropDB, dropCollections } from '../src/utils/test_mongodb'; | ||||||
|  |  | ||||||
| describe('Auth API Endpoints', () => { | const request = supertest(app); | ||||||
|     afterAll((done) => { |  | ||||||
|         app.close(done); |  | ||||||
|     }) |  | ||||||
|  |  | ||||||
|     it('should signup new user', async () => { | export const getJWT = async () => { | ||||||
|         const res = await request(app) | 	try { | ||||||
|             .post('/api/auth/signup') | 		const resReg: any = await request.post('/api/v1/auth/signup').send({ | ||||||
|             .send({}); | 			email: 'test@example.local', | ||||||
|  | 			password: 'admin1234', | ||||||
|  | 			username: 'Test Test' | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|         expect(res.statusCode).toEqual(200) | 		const resLog: any = await request.post('/api/auth/login').send({ | ||||||
|  | 			email: 'test@example.local', | ||||||
|  | 			password: 'admin1234' | ||||||
|  | 		}); | ||||||
|  | 		if (resLog.statusCode != 200) throw 'error while logging in'; | ||||||
|  |  | ||||||
|     }) | 		const body = JSON.parse(resLog.text); | ||||||
| }) | 		return Promise.resolve(body.data.jwt); | ||||||
|  | 	} catch (err: any) { | ||||||
|  | 		console.log(err); | ||||||
|  | 		return err; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * | ||||||
|  |  * @returns JWT cookie | ||||||
|  |  */ | ||||||
|  | export async function login(): Promise<string> { | ||||||
|  | 	const res = await request.post('/api/v1/auth/signin').send({ | ||||||
|  | 		email: 'thisistest@host.local', | ||||||
|  | 		password: 'Admin1234' | ||||||
|  | 	}); | ||||||
|  | 	return res.headers['set-cookie']; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export async function signup(): Promise<boolean> { | ||||||
|  | 	const res = await request.post('/api/v1/auth/signup').send({ | ||||||
|  | 		email: 'thisistest@host.local', | ||||||
|  | 		password: 'Admin1234', | ||||||
|  | 		username: 'Test Test' | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	if (res.statusCode == 201) return true; | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | describe('POST /api/v1/auth/signup', () => { | ||||||
|  | 	describe('should drop validation error', () => { | ||||||
|  | 		it('should drop 400 (empty request))', async () => { | ||||||
|  | 			const res: any = await request.post('/api/v1/auth/signup').send({}); | ||||||
|  | 			expect(res.statusCode).toBe(400); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		it('should drop 400 (email))', async () => { | ||||||
|  | 			const res: any = await request.post('/api/v1/auth/signup').send({ | ||||||
|  | 				email: '', | ||||||
|  | 				username: 'User Admin', | ||||||
|  | 				password: 'Admin1234' | ||||||
|  | 			}); | ||||||
|  |             console.log(res) | ||||||
|  | 			const body = JSON.parse(res.text); | ||||||
|  | 			expect(res.statusCode).toBe(400); | ||||||
|  | 			expect(body.data.path).toBe('email'); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		it('should drop 400 (username))', async () => { | ||||||
|  | 			const res: any = await request.post('/api/v1/auth/signup').send({ | ||||||
|  | 				email: 'admin@localhost.local', | ||||||
|  | 				username: '', | ||||||
|  | 				password: 'Admin1234' | ||||||
|  | 			}); | ||||||
|  | 			const body = JSON.parse(res.text); | ||||||
|  |  | ||||||
|  | 			expect(res.statusCode).toBe(400); | ||||||
|  | 			expect(body.data.path).toBe('username'); | ||||||
|  | 		}); | ||||||
|  | 		it('should drop 400 (password))', async () => { | ||||||
|  | 			const res: any = await request.post('/api/v1/auth/signup').send({ | ||||||
|  | 				email: 'admin@localhost.local', | ||||||
|  | 				username: 'User Admin', | ||||||
|  | 				password: '' | ||||||
|  | 			}); | ||||||
|  | 			const body = JSON.parse(res.text); | ||||||
|  |  | ||||||
|  | 			expect(res.statusCode).toBe(400); | ||||||
|  | 			expect(body.data.path).toBe('password'); | ||||||
|  | 		}); | ||||||
|  | 		it('should drop 400 (password - min 8 chars', async () => { | ||||||
|  | 			const res = await request.post('/api/v1/auth/signup').send({ | ||||||
|  | 				email: 'admin@localhost.local', | ||||||
|  | 				username: 'User Admin', | ||||||
|  | 				password: 'Admin12' | ||||||
|  | 			}); | ||||||
|  | 			const body = JSON.parse(res.text); | ||||||
|  |  | ||||||
|  | 			expect(res.statusCode).toBe(400); | ||||||
|  | 			expect(body.data.path).toBe('password'); | ||||||
|  | 		}); | ||||||
|  | 		it('should drop 400 (password - min 1 number', async () => { | ||||||
|  | 			const res = await request.post('/api/v1/auth/signup').send({ | ||||||
|  | 				email: 'admin@localhost.local', | ||||||
|  | 				username: 'User Admin', | ||||||
|  | 				password: 'Adminadmin' | ||||||
|  | 			}); | ||||||
|  | 			const body = JSON.parse(res.text); | ||||||
|  |  | ||||||
|  | 			expect(res.statusCode).toBe(400); | ||||||
|  | 			expect(body.data.path).toBe('password'); | ||||||
|  | 		}); | ||||||
|  | 		it('should drop 400 (password - min 1 uppercase', async () => { | ||||||
|  | 			const res = await request.post('/api/v1/auth/signup').send({ | ||||||
|  | 				email: 'admin@localhost.local', | ||||||
|  | 				username: 'User Admin', | ||||||
|  | 				password: 'admin1234' | ||||||
|  | 			}); | ||||||
|  | 			const body = JSON.parse(res.text); | ||||||
|  |  | ||||||
|  | 			expect(res.statusCode).toBe(400); | ||||||
|  | 			expect(body.data.path).toBe('password'); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	test('should register an user', async () => { | ||||||
|  | 		const res: any = await request.post('/api/v1/auth/signup').send({ | ||||||
|  | 			email: 'thisistest@host.local', | ||||||
|  | 			password: 'Admin1234', | ||||||
|  | 			username: 'Test Test' | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		expect(res.statusCode).toBe(201); | ||||||
|  | 	}); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | describe('POST /api/v1/auth/signin', () => { | ||||||
|  | 	const url = '/api/v1/auth/signin'; | ||||||
|  |  | ||||||
|  | 	describe('should drop an validation error', () => { | ||||||
|  | 		it('should drop 400 (empty)', async () => { | ||||||
|  | 			const res = await request.post(url).send(); | ||||||
|  |  | ||||||
|  | 			expect(res.statusCode).toBe(400); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		it('should drop 400 (email)', async () => { | ||||||
|  | 			const res = await request.post(url).send({ | ||||||
|  | 				password: 'Admin1234' | ||||||
|  | 			}); | ||||||
|  | 			const body = JSON.parse(res.text); | ||||||
|  |  | ||||||
|  | 			expect(res.statusCode).toBe(400); | ||||||
|  | 			expect(body.data.path).toBe('email'); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		it('should drop 400 (password)', async () => { | ||||||
|  | 			const res = await request.post(url).send({ | ||||||
|  | 				email: 'thisistest@host.local' | ||||||
|  | 			}); | ||||||
|  | 			const body = JSON.parse(res.text); | ||||||
|  |  | ||||||
|  | 			expect(res.statusCode).toBe(400); | ||||||
|  | 			expect(body.data.path).toBe('password'); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	test('should drop 401', async () => { | ||||||
|  | 		const res = await request.post(url).send({ | ||||||
|  | 			email: 'thisistest@host.local', | ||||||
|  | 			password: 'Test12365465132' | ||||||
|  | 		}); | ||||||
|  | 		expect(res.statusCode).toBe(401); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	test('should login an user', async () => { | ||||||
|  | 		const res: any = await request.post(url).send({ | ||||||
|  | 			email: 'thisistest@host.local', | ||||||
|  | 			password: 'Admin1234' | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		expect(res.statusCode).toBe(200); | ||||||
|  | 	}); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Throws errors idk | ||||||
|  |    | ||||||
|  | describe('POST /api/v1/auth/logout', () => { | ||||||
|  | 	const url = '/api/v1/auth/logout'; | ||||||
|  | 	test('should drop 401 error', async () => { | ||||||
|  | 		const res = await request.post(url).send({}); | ||||||
|  | 		expect(res.statusCode).toBe(401); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	test('should logout an user', async () => { | ||||||
|  | 		const jwt = await login(); | ||||||
|  | 		const res = await request.post(url).set('Cookie', jwt).send({}); | ||||||
|  |  | ||||||
|  | 		res.headers['set-cookie'].forEach((el: any) => { | ||||||
|  | 			if (el.split('=')[0] == 'jwt') { | ||||||
|  | 				expect(Number(el.split('=')[2][0])).toBe(0); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		expect(res.statusCode).toBe(200); | ||||||
|  | 	}); | ||||||
|  | }); | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | describe('GET /api/v1/auth/status', () => { | ||||||
|  | 	const url = '/api/v1/auth/status'; | ||||||
|  | 	test('should return login status 401', async () => { | ||||||
|  | 		const res = await request.get(url).send(); | ||||||
|  | 		expect(res.statusCode).toBe(401); | ||||||
|  | 	}); | ||||||
|  | 	test('should return login status 200', async () => { | ||||||
|  | 		const jwt = await login(); | ||||||
|  | 		const res = await request.get(url).set('Cookie', jwt).send(); | ||||||
|  | 		expect(res.statusCode).toBe(200); | ||||||
|  | 	}); | ||||||
|  | }); | ||||||
|  | */ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user