From 0f709f6c04f4dd128bf05b56b9314e81b2340bcd Mon Sep 17 00:00:00 2001 From: IRUMVAEmmanuel1 Date: Fri, 19 Apr 2024 14:37:35 +0200 Subject: [PATCH 1/4] ESLint Configuration PreCommit format and Test --- .husky/pre-commit | 2 + .prettierrc.json | 5 + README.md | 2 + __test__/home.test.ts | 22 +- __test__/user.test.ts | 138 ++++++------ eslint.config.cjs | 51 +++++ hound.yml | 9 + index.ts | 18 +- jest.config.js | 16 +- package.json | 150 +++++++------ src/config/dbConnection.ts | 24 ++- src/controllers/userControllers.ts | 139 ++++++------ src/docs/swagger.ts | 111 +++++----- src/docs/users.ts | 192 ++++++++--------- src/helpers/comparePassword.ts | 11 +- src/middleware/validator.ts | 61 +++--- src/routes/homeRoutes.ts | 20 +- src/routes/index.ts | 4 +- src/routes/userRoutes.ts | 31 ++- src/schemas/signUpSchema.ts | 21 +- src/sequelize/config/config.js | 37 ++-- .../migrations/20240416115110-create-user.js | 83 ++++---- .../migrations/20240418083442-UserTests.js | 83 ++++---- src/sequelize/models/index.js | 50 ++--- src/sequelize/models/users.ts | 111 +++++----- src/sequelize/models/usersTests.ts | 112 +++++----- .../seeders/20240412144111-demo-user.js | 47 ++-- src/services/user.service.ts | 88 ++++---- src/types.ts | 16 +- src/utils/env.ts | 8 +- src/utils/hashPassword.ts | 10 +- src/utils/jsonwebtoken.ts | 20 +- tsconfig.json | 201 +++++++++--------- 33 files changed, 1009 insertions(+), 884 deletions(-) create mode 100644 .husky/pre-commit create mode 100644 .prettierrc.json create mode 100644 eslint.config.cjs create mode 100644 hound.yml diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..186e9aa --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +npm run pre-commit +npm run test diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..15319ae --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,5 @@ +{ + "semi": true, + "tabWidth": 2, + "useTabs": true +} diff --git a/README.md b/README.md index 7e689e7..43d209b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # eagles-ec-be
+ Codecov GitHub Actions Workflow Status +Static Badge
diff --git a/__test__/home.test.ts b/__test__/home.test.ts index a2ddf99..0ccd376 100644 --- a/__test__/home.test.ts +++ b/__test__/home.test.ts @@ -4,17 +4,17 @@ import app from "../src/utils/server"; import sequelize, { connect } from "../src/config/dbConnection"; describe("Testing Home route", () => { - beforeAll(async () => { - try { - await connect(); - } catch (error) { - sequelize.close(); - } - }, 20000); + beforeAll(async () => { + try { + await connect(); + } catch (error) { + sequelize.close(); + } + }, 20000); - test("servr should return status code of 200 --> given'/'", async () => { - const response = await request(app).get("/"); + test("servr should return status code of 200 --> given'/'", async () => { + const response = await request(app).get("/"); - expect(response.status).toBe(200); - }, 20000); + expect(response.status).toBe(200); + }, 20000); }); diff --git a/__test__/user.test.ts b/__test__/user.test.ts index 32e9a70..e4de66d 100644 --- a/__test__/user.test.ts +++ b/__test__/user.test.ts @@ -5,74 +5,82 @@ import User from "../src/sequelize/models/users"; import * as userServices from "../src/services/user.service"; import sequelize, { connect } from "../src/config/dbConnection"; -const userData:any = { - name: 'yvanna', - username: 'testuser', - email: 'test1@gmail.com', - password:'test1234', - }; +const userData: any = { + name: "yvanna", + username: "testuser", + email: "test1@gmail.com", + password: "test1234", +}; - const loginData:any = { - email:'test1@gmail.com', - password:"test1234" - } +const loginData: any = { + email: "test1@gmail.com", + password: "test1234", +}; describe("Testing user Routes", () => { - beforeAll(async () => { - try { - await connect(); - await User.destroy({truncate:true}) - } catch (error) { - sequelize.close(); - } - }, 20000); + beforeAll(async () => { + try { + await connect(); + await User.destroy({ truncate: true }); + } catch (error) { + sequelize.close(); + } + }, 20000); - afterAll(async () => { - await User.destroy({ truncate: true }); - await sequelize.close(); -}); -describe("Testing user authentication", () => { -test('should return 201 and create a new user when registering successfully', async () => { -const response = await request(app) -.post('/api/v1/users/register') -.send(userData); -expect(response.status).toBe(201); }, 20000); + afterAll(async () => { + await User.destroy({ truncate: true }); + await sequelize.close(); + }); + describe("Testing user authentication", () => { + test("should return 201 and create a new user when registering successfully", async () => { + const response = await request(app) + .post("/api/v1/users/register") + .send(userData); + expect(response.status).toBe(201); + }, 20000); -test('should return 409 when registering with an existing email', async () => { - User.create(userData) - const response = await request(app) - .post('/api/v1/users/register') - .send(userData); - expect(response.status).toBe(409); }, 20000); + test("should return 409 when registering with an existing email", async () => { + User.create(userData); + const response = await request(app) + .post("/api/v1/users/register") + .send(userData); + expect(response.status).toBe(409); + }, 20000); -test('should return 400 when registering with an invalid credential', async () => { - const userData = { - email: 'test@mail.com', name: "", username: 'existinguser', }; - const response = await request(app) - .post('/api/v1/users/register') - .send(userData); - - expect(response.status).toBe(400); }, 20000); }); + test("should return 400 when registering with an invalid credential", async () => { + const userData = { + email: "test@mail.com", + name: "", + username: "existinguser", + }; + const response = await request(app) + .post("/api/v1/users/register") + .send(userData); - test("should return all users in db --> given '/api/v1/users'", async () => { - const spy = jest.spyOn(User, "findAll"); - const spy2 = jest.spyOn(userServices, "getAllUsers"); - const response = await request(app).get("/api/v1/users"); - expect(spy).toHaveBeenCalled(); - expect(spy2).toHaveBeenCalled(); - }, 20000); - test("Should return status 401 to indicate Unauthorized user",async() =>{ - const loggedInUser ={ - email:userData.email, - password:"test", - }; - const spyonOne = jest.spyOn(User,"findOne").mockResolvedValueOnce({ - //@ts-ignore - email:userData.email, - password:loginData.password, - }); - const response = await request(app).post("/api/v1/users/login") - .send(loggedInUser) - expect(response.body.status).toBe(401); - spyonOne.mockRestore(); - }); -}) + expect(response.status).toBe(400); + }, 20000); + }); +}); + +test("should return all users in db --> given '/api/v1/users'", async () => { + const spy = jest.spyOn(User, "findAll"); + const spy2 = jest.spyOn(userServices, "getAllUsers"); + const response = await request(app).get("/api/v1/users"); + expect(spy).toHaveBeenCalled(); + expect(spy2).toHaveBeenCalled(); +}, 20000); +test("Should return status 401 to indicate Unauthorized user", async () => { + const loggedInUser = { + email: userData.email, + password: "test", + }; + const spyonOne = jest.spyOn(User, "findOne").mockResolvedValueOnce({ + //@ts-ignore + email: userData.email, + password: loginData.password, + }); + const response = await request(app) + .post("/api/v1/users/login") + .send(loggedInUser); + expect(response.body.status).toBe(401); + spyonOne.mockRestore(); +}); diff --git a/eslint.config.cjs b/eslint.config.cjs new file mode 100644 index 0000000..49f3638 --- /dev/null +++ b/eslint.config.cjs @@ -0,0 +1,51 @@ +const eslint = require("@eslint/js"); +const tseslint = require("typescript-eslint"); + +module.exports = tseslint.config( + { + ignores: ["**/__test__", "**/*.json"], + }, + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + languageOptions: { + parserOptions: { + project: true, + ecmaVersion: 2020, + }, + }, + }, + { + files: ["*.ts", "*.js"], + ...tseslint.configs.disableTypeChecked, + }, + { + files: ["*.test *.js"], + rules: { + "@typescript-eslint/no-unused-vars": 0, + "@typescript-eslint/no-unsafe-call": 0, + languageOptions: { + globals: { + it: "readonly", + describe: "readonly", + }, + }, + }, + }, + { + rules: { + semi: "error", + "@typescript-eslint/no-unused-vars": 2, + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/no-var-requires": 0, + "no-shadow": [2, { allow: ["req", "res", "err"] }], + "new-cap": 0, + "one-var-declaration-per-line": 0, + "consistent-return": 0, + "no-param-reassign": 0, + "comma-dangle": 0, + "no-undef": 0, + curly: ["error", "multi-line"], + }, + }, +); diff --git a/hound.yml b/hound.yml new file mode 100644 index 0000000..4e976af --- /dev/null +++ b/hound.yml @@ -0,0 +1,9 @@ +eslint: + enabled: true + file_patterns: + - "*.js" + - "*.ts" + enabled_plugins: + - eslint-plugin-import + - eslint-plugin-prettier + - eslint-plugin:@typescript-eslint diff --git a/index.ts b/index.ts index aabd3d2..328b27d 100644 --- a/index.ts +++ b/index.ts @@ -3,13 +3,13 @@ import { env } from "./src/utils/env"; import app from "./src/utils/server"; app.listen(env.port, async () => { - await connect(); - await sequelize - .sync() - .then(() => { - console.log(` db synced and server is running on port ${env.port}`); - }) - .catch((error: any) => { - console.log(error.message); - }); + await connect(); + await sequelize + .sync() + .then(() => { + console.log(" db synced and server is running"); + }) + .catch((error: any) => { + console.log(error.message); + }); }); diff --git a/jest.config.js b/jest.config.js index 36e4760..124cea1 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,11 +1,11 @@ /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ module.exports = { - preset: "ts-jest", - testEnvironment: "node", - testMatch: ["**/**/*.test.ts"], - verbose: true, - forceExit: true, - clearMocks: true, - resetMocks: true, - restoreMocks: true, + preset: "ts-jest", + testEnvironment: "node", + testMatch: ["**/**/*.test.ts"], + verbose: true, + forceExit: true, + clearMocks: true, + resetMocks: true, + restoreMocks: true, }; diff --git a/package.json b/package.json index e693874..0b8461e 100644 --- a/package.json +++ b/package.json @@ -1,65 +1,89 @@ { - "name": "eagles-ec-be", - "version": "1.0.0", - "description": "A platform providing a marketplace for buyers and sellers to meet", - "main": "index.ts", - "scripts": { - "start": "", - "dev": "nodemon index.ts", - "build": "tsc", - "migrate": "npx sequelize-cli db:migrate", - "seed": "npx sequelize-cli db:seed:all", - "lint": "npx eslint .", - "lint:fix": "npx eslint --fix .", - "test": "cross-env NODE_ENV=test jest --detectOpenHandles --coverage" - }, - "author": "atlp", - "license": "MIT", - "devDependencies": { - "@eslint/js": "^9.0.0", - "@types/bcryptjs": "^2.4.6", - "@types/cors": "^2.8.17", - "@types/dotenv": "^8.2.0", - "@types/express": "^4.17.21", - "@types/jest": "^29.5.12", - "@types/jsonwebtoken": "^9.0.6", - "@types/node": "^20.12.7", - "@types/supertest": "^6.0.2", - "@types/swagger-ui-express": "^4.1.6", - "@typescript-eslint/eslint-plugin": "^7.6.0", - "@typescript-eslint/parser": "^7.6.0", - "eslint": "^8.57.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jest": "^28.2.0", - "globals": "^15.0.0", - "jest": "^29.7.0", - "nodemon": "^3.1.0", - "prettier": "^3.2.5", - "sequelize-cli": "^6.6.2", - "supertest": "^6.3.4", - "ts-jest": "^29.1.2", - "ts-node": "^10.9.2", - "typescript": "^5.4.5", - "typescript-eslint": "^7.7.0" - }, - "dependencies": { - "@types/bcrypt": "^5.0.2", - "bcrypt": "^5.1.1", - "bcryptjs": "^2.4.3", - "cors": "^2.8.5", - "cross-env": "^7.0.3", - "cryptr": "^6.3.0", - "dotenv": "^16.4.5", - "email-validator": "^2.0.4", - "express": "^4.19.2", - "joi": "^17.12.3", - "jsonwebtoken": "^9.0.2", - "path": "^0.12.7", - "pg": "^8.11.5", - "pg-hstore": "^2.3.4", - "sequelize": "^6.37.2", - "swagger-ui-express": "^5.0.0" - } + "name": "eagles-ec-be", + "version": "1.0.0", + "description": "A platform providing a marketplace for buyers and sellers to meet", + "main": "index.ts", + "scripts": { + "start": "", + "pre-commit": "prettier . --write && eslint .", + "lint": "eslint .", + "dev": "nodemon index.ts", + "build": "tsc", + "migrate": "npx sequelize-cli db:migrate", + "seed": "npx sequelize-cli db:seed:all", + "lint:fix": "npx eslint --fix .", + "test": "cross-env NODE_ENV=test jest --detectOpenHandles --coverage", + "prepare": "husky", + "prettier": "prettier . --write" + }, + "lint-staged": { + ".ts": [ + "npm run lint:fix" + ], + "**/*": "prettier --write" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest", + "prettier" + ] + }, + "husky": { + "hooks": { + "pre-commit": "npm run lint-staged && npm run test" + } + }, + "author": "atlp", + "license": "MIT", + "devDependencies": { + "@eslint/js": "^9.0.0", + "@types/bcryptjs": "^2.4.6", + "@types/cors": "^2.8.17", + "@types/dotenv": "^8.2.0", + "@types/express": "^4.17.21", + "@types/jest": "^29.5.12", + "@types/jsonwebtoken": "^9.0.6", + "@types/node": "^20.12.7", + "@types/supertest": "^6.0.2", + "@types/swagger-ui-express": "^4.1.6", + "@typescript-eslint/eslint-plugin": "^7.6.0", + "@typescript-eslint/parser": "^7.6.0", + "eslint": "^8.57.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-jest": "^28.2.0", + "globals": "^15.0.0", + "jest": "^29.7.0", + "nodemon": "^3.1.0", + "pre-commit": "^1.2.2", + "prettier": "^3.2.5", + "sequelize-cli": "^6.6.2", + "supertest": "^6.3.4", + "ts-jest": "^29.1.2", + "ts-node": "^10.9.2", + "typescript": "^5.4.5", + "typescript-eslint": "^7.7.0" + }, + "dependencies": { + "@types/bcrypt": "^5.0.2", + "bcrypt": "^5.1.1", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "cross-env": "^7.0.3", + "cryptr": "^6.3.0", + "dotenv": "^16.4.5", + "email-validator": "^2.0.4", + "express": "^4.19.2", + "husky": "^9.0.11", + "joi": "^17.13.0", + "jsonwebtoken": "^9.0.2", + "lint-staged": "^15.2.2", + "path": "^0.12.7", + "pg": "^8.11.5", + "pg-hstore": "^2.3.4", + "sequelize": "^6.37.2", + "swagger-ui-express": "^5.0.0" + } } diff --git a/src/config/dbConnection.ts b/src/config/dbConnection.ts index 8500c3d..5ebaef7 100644 --- a/src/config/dbConnection.ts +++ b/src/config/dbConnection.ts @@ -3,19 +3,21 @@ import { env } from "../utils/env"; const envT = process.env.NODE_ENV; -const sequelize = new Sequelize(envT === "test" ? env.test_db_url : env.db_url,{ - dialect: 'postgres', -}) - +const sequelize = new Sequelize( + envT === "test" ? env.test_db_url : env.db_url, + { + dialect: "postgres", + }, +); export const connect = async () => { - try { - await sequelize.authenticate(); - console.log("successfuly connected's to db"); - } catch (error: any) { - console.log("error: ", error.message); - return; - } + try { + await sequelize.authenticate(); + console.log("successfuly connected's to db"); + } catch (error: any) { + console.log("error: ", error.message); + return; + } }; export default sequelize; diff --git a/src/controllers/userControllers.ts b/src/controllers/userControllers.ts index f3fed45..a1a276a 100644 --- a/src/controllers/userControllers.ts +++ b/src/controllers/userControllers.ts @@ -2,80 +2,83 @@ import { Request, Response } from "express"; import * as userService from "../services/user.service"; import { generateToken } from "../utils/jsonwebtoken"; import { comparePasswords } from "../helpers/comparePassword"; -import { loggedInUser} from "../services/user.service"; -import { createUserService, getUserByEmail } from "../services/user.service"; +import { loggedInUser } from "../services/user.service"; +import { createUserService } from "../services/user.service"; export const fetchAllUsers = async (req: Request, res: Response) => { - try { - const users = await userService.getAllUsers(); + try { + // const users = await userService.getAllUsers(); - if (users.length <= 0) { - return res.status(404).json({ - message: "No users found", - }); - } else { - res.status(200).json({ - message: "Users fetched successfully", - count: users.length, - users: users, - }); - } - } catch (error: any) { - res.status(500).json({ - message: "Internal server error", - error: error.message, - }); - } -}; + const users = await userService.getAllUsers(); -export const userLogin = async(req:Request,res:Response) =>{ - const {email, password} = req.body; - const user = await loggedInUser(email); - const accessToken = await generateToken(user); - if(!user){ - res.status(404).json({ - status:404, - message:'User Not Found ! Please Register new ancount' - }); - }else{ - const match = await comparePasswords(password,user.password); - if(!match){ - res.status(401).json({ - status:401, - message:' User email or password is incorrect!' - }); - }else{ - res.status(200).json({ - status:200, - message:"Logged in", - token:accessToken - }); - }; - }; + if (users.length <= 0) { + return res.status(404).json({ + message: "No users found", + }); + } else { + res.status(200).json({ + message: "Users fetched successfully", + count: users.length, + users: users, + }); + } + } catch (error: any) { + res.status(500).json({ + message: "Internal server error", + error: error.message, + }); + } }; +export const userLogin = async (req: Request, res: Response) => { + const { email, password } = req.body; + const user = await loggedInUser(email); + const accessToken = await generateToken(user); + if (!user) { + res.status(404).json({ + status: 404, + message: "User Not Found ! Please Register new ancount", + }); + } else { + const match = await comparePasswords(password, user.password); + if (!match) { + res.status(401).json({ + status: 401, + message: " User email or password is incorrect!", + }); + } else { + res.status(200).json({ + status: 200, + message: "Logged in", + token: accessToken, + }); + } + } +}; export const createUserController = async (req: Request, res: Response) => { - try { - const { name, email, username, password } = req.body; - const user = await createUserService(name, email, username, password); - - if (!user) { - return res.status(409).json({ - status: 409, - message: 'Username or email already exists' - }); - } - - res.status(201).json({ - status: 201, - message: "User successfully created." - }); - - } catch (err: any) { - if (err.name === 'UnauthorizedError' && err.message === 'User already exists') { - return res.status(409).json({ error: 'User already exists' }); - } - res.status(500).json({ error: err }); - } + try { + const { name, email, username, password } = req.body; + const user = await createUserService(name, email, username, password); + + if (!user) { + return res.status(409).json({ + status: 409, + message: "Username or email already exists", + }); + } + + res.status(201).json({ + status: 201, + message: "User successfully created.", + }); + } catch (err: any) { + if ( + err.name === "UnauthorizedError" && + err.message === "User already exists" + ) { + return res.status(409).json({ error: "User already exists" }); + } + res.status(500).json({ error: err }); + } }; diff --git a/src/docs/swagger.ts b/src/docs/swagger.ts index e7f82ec..46caa6c 100644 --- a/src/docs/swagger.ts +++ b/src/docs/swagger.ts @@ -1,68 +1,73 @@ import express from "express"; import { serve, setup } from "swagger-ui-express"; import { env } from "../utils/env"; -import { - createUsers, - getUsers, - loginAsUser, - userSchema, - loginSchema - } from "./users"; +import { + createUsers, + getUsers, + loginAsUser, + userSchema, + loginSchema, +} from "./users"; const docRouter = express.Router(); const options = { - openapi: "3.0.1", - info: { - title: "Eagles E-commerce API", - version: "1.0.0", - description: "Documentation for Eagles E-commerce Backend", - }, + openapi: "3.0.1", + info: { + title: "Eagles E-commerce API", + version: "1.0.0", + description: "Documentation for Eagles E-commerce Backend", + }, - servers: [{ - url: `http://localhost:${env.port}`, - description: 'Development server', - }, { - url: 'https://eagles-ec-be-development.onrender.com/', - description: 'Production server', - }], + servers: [ + { + url: `http://localhost:${env.port}`, + description: "Development server", + }, + { + url: "https://eagles-ec-be-development.onrender.com/", + description: "Production server", + }, + ], - basePath: "/", + basePath: "/", - tags: [ - { name: "Users", description: "Endpoints related to users" } - ], + tags: [ + { + name: "Users", + description: "Endpoints related to users", + }, + ], - paths: { - "/api/v1/users": { - get: getUsers - }, - "/api/v1/users/register": { - post: createUsers - }, - "/api/v1/users/login": { - post: loginAsUser - }, - }, + paths: { + "/api/v1/users": { + get: getUsers, + }, + "/api/v1/users/register": { + post: createUsers, + }, + "/api/v1/users/login": { + post: loginAsUser, + }, + }, - components: { - schemas: { - User: userSchema, - Login:loginSchema, - }, - securitySchemes: { - bearerAuth: { - type: "http", - scheme: "bearer", - bearerFormat: "JWT", - in: "header", - name: "Authorization", - }, - }, - } - -} + components: { + schemas: { + User: userSchema, + Login: loginSchema, + }, + securitySchemes: { + bearerAuth: { + type: "http", + scheme: "bearer", + bearerFormat: "JWT", + in: "header", + name: "Authorization", + }, + }, + }, +}; docRouter.use("/", serve, setup(options)); -export default docRouter \ No newline at end of file +export default docRouter; diff --git a/src/docs/users.ts b/src/docs/users.ts index ca2f301..a120523 100644 --- a/src/docs/users.ts +++ b/src/docs/users.ts @@ -1,104 +1,100 @@ -import { response } from "express" - export const userSchema = { - type: "object", - properties: { - name: { - type: "string", - }, - username: { - type: "string" - }, - email: { - type: "string", - format: "email", - }, - password: { - type: "string", - }, - }, -} + type: "object", + properties: { + name: { + type: "string", + }, + username: { + type: "string", + }, + email: { + type: "string", + format: "email", + }, + password: { + type: "string", + }, + }, +}; -export const loginSchema ={ - properties :{ - email: { - type: "string", - format: "email", - }, - password: { - type: "string", - }, - } -} +export const loginSchema = { + properties: { + email: { + type: "string", + format: "email", + }, + password: { + type: "string", + }, + }, +}; export const getUsers = { - tags: ["Users"], - summary: "Get all users", - responses: { - 200: { - description: "OK", - content: { - "application/json": { - schema: { - type: "array", - items: { - $ref: "#/components/schemas/User", - }, - }, - }, - }, - }, - }, - } + tags: ["Users"], + summary: "Get all users", + responses: { + 200: { + description: "OK", + content: { + "application/json": { + schema: { + type: "array", + items: { + $ref: "#/components/schemas/User", + }, + }, + }, + }, + }, + }, +}; - export const createUsers = { - - tags: ["Users"], - summary: "Register a new user", - requestBody: { - required: true, - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/User", - }, - }, - }, - }, - responses: { - 201: { - description: "Created", - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/User", - }, - }, - }, - }, - 400: { - description: "Bad request", - }, - }, - } +export const createUsers = { + tags: ["Users"], + summary: "Register a new user", + requestBody: { + required: true, + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/User", + }, + }, + }, + }, + responses: { + 201: { + description: "Created", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/User", + }, + }, + }, + }, + 400: { + description: "Bad request", + }, + }, +}; - export const loginAsUser ={ - tags: ["Users"], - summary: "Login as user", - requestBody: { - required: true, - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/Login" - } - } - } - }, - responses: { - 200: { - description: "OK", - } - } - }; - \ No newline at end of file +export const loginAsUser = { + tags: ["Users"], + summary: "Login as user", + requestBody: { + required: true, + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/Login", + }, + }, + }, + }, + responses: { + 200: { + description: "OK", + }, + }, +}; diff --git a/src/helpers/comparePassword.ts b/src/helpers/comparePassword.ts index 79654ba..27434ee 100644 --- a/src/helpers/comparePassword.ts +++ b/src/helpers/comparePassword.ts @@ -1,4 +1,7 @@ -import bcrypt from 'bcrypt' -export const comparePasswords = async(plainPassword: string, hashedPassword: string): Promise => { - return await bcrypt.compare(plainPassword, hashedPassword); - } \ No newline at end of file +import bcrypt from "bcrypt"; +export const comparePasswords = async ( + plainPassword: string, + hashedPassword: string, +): Promise => { + return await bcrypt.compare(plainPassword, hashedPassword); +}; diff --git a/src/middleware/validator.ts b/src/middleware/validator.ts index 93cc2b8..660fdbb 100644 --- a/src/middleware/validator.ts +++ b/src/middleware/validator.ts @@ -1,35 +1,38 @@ -import { Request, Response, NextFunction } from 'express'; -import validator from 'email-validator'; -import { Schema } from 'joi'; +import { Request, Response, NextFunction } from "express"; +import validator from "email-validator"; +import { Schema } from "joi"; -export const emailValidation = (req: Request, res: Response, next: NextFunction) => { - const { email } = req.body; +export const emailValidation = ( + req: Request, + res: Response, + next: NextFunction, +) => { + const { email } = req.body; - if (!email) { - return res.status(400).json({ - status: 400, - message: "Email is required" - }); - } + if (!email) { + return res.status(400).json({ + status: 400, + message: "Email is required", + }); + } - const isValid = validator.validate(email); - if (isValid) { - next(); - } else { - return res.status(400).json({ - status: 400, - message: "Email is not valid." - }); - } + const isValid = validator.validate(email); + if (isValid) { + next(); + } else { + return res.status(400).json({ + status: 400, + message: "Email is not valid.", + }); + } }; - export const validateSchema = (schema: Schema) => { - return (req: Request, res: Response, next: NextFunction) => { - const { error } = schema.validate(req.body); - if (error) { - return res.status(400).json({ message: error.details[0].message }); - } - next(); - }; -}; \ No newline at end of file + return (req: Request, res: Response, next: NextFunction) => { + const { error } = schema.validate(req.body); + if (error) { + return res.status(400).json({ message: error.details[0].message }); + } + next(); + }; +}; diff --git a/src/routes/homeRoutes.ts b/src/routes/homeRoutes.ts index b26f96b..884800e 100644 --- a/src/routes/homeRoutes.ts +++ b/src/routes/homeRoutes.ts @@ -3,16 +3,16 @@ import { Request, Response, Router } from "express"; const homeRoute = Router(); homeRoute.get("/", (req: Request, res: Response) => { - try { - res.status(200).json({ - message: "Api is working 😎", - }); - } catch (error: any) { - res.status(500).json({ - message: "Internal server error", - error: error.message, - }); - } + try { + res.status(200).json({ + message: "Api is working 😎", + }); + } catch (error: any) { + res.status(500).json({ + message: "Internal server error", + error: error.message, + }); + } }); export default homeRoute; diff --git a/src/routes/index.ts b/src/routes/index.ts index e7ddc3f..f65e84a 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,10 +1,8 @@ -import { Request, Response, Router } from "express"; +import { Router } from "express"; import userRoutes from "./userRoutes"; - const appROutes = Router(); appROutes.use("/users", userRoutes); - export default appROutes; diff --git a/src/routes/userRoutes.ts b/src/routes/userRoutes.ts index 23d9f47..c62efad 100644 --- a/src/routes/userRoutes.ts +++ b/src/routes/userRoutes.ts @@ -1,24 +1,23 @@ import { Router } from "express"; -import { - fetchAllUsers, - createUserController, - userLogin } -from "../controllers/userControllers"; -import { - emailValidation, - validateSchema, - } from "../middleware/validator"; +import { + fetchAllUsers, + createUserController, + userLogin, +} from "../controllers/userControllers"; +import { emailValidation, validateSchema } from "../middleware/validator"; import signUpSchema from "../schemas/signUpSchema"; const userRoutes = Router(); userRoutes.get("/", fetchAllUsers); -userRoutes.post('/login',userLogin); -userRoutes.post("/register", - emailValidation, - validateSchema(signUpSchema), - createUserController -) +userRoutes.post("/login", userLogin); +userRoutes.post( + "/register", + emailValidation, + validateSchema(signUpSchema), + createUserController, +); +userRoutes.post("/register", createUserController); -export default userRoutes; +export default userRoutes; diff --git a/src/schemas/signUpSchema.ts b/src/schemas/signUpSchema.ts index 030fcd3..88d3ff0 100644 --- a/src/schemas/signUpSchema.ts +++ b/src/schemas/signUpSchema.ts @@ -1,21 +1,10 @@ import Joi from "joi"; export const signUpSchema = Joi.object({ - name: Joi.string() - .min(5) - .max(40) - .required(), - username: Joi.string() - .min(4) - .required(), - email: Joi.string() - .min(6) - .required() - .email(), - password: Joi.string() - .min(6) - .max(20) - .required() + name: Joi.string().min(5).max(40).required(), + username: Joi.string().min(4).required(), + email: Joi.string().min(6).required().email(), + password: Joi.string().min(6).max(20).required(), }).options({ allowUnknown: false }); -export default signUpSchema \ No newline at end of file +export default signUpSchema; diff --git a/src/sequelize/config/config.js b/src/sequelize/config/config.js index 93850c4..a730b16 100644 --- a/src/sequelize/config/config.js +++ b/src/sequelize/config/config.js @@ -2,23 +2,22 @@ const dotenv = require("dotenv"); dotenv.config(); module.exports = { - development: { - url: process.env.DB_CONNECTION, - dialect: "postgres", - }, - test: { - url: process.env.TEST_DB, - dialect: "postgres", - }, - production: { - url: process.env.DB_CONNECTION, - dialect: "postgres", - dialectOptions: { - ssl: { - require: true, - rejectUnauthorized: false, - }, - }, - }, - + development: { + url: process.env.DB_CONNECTION, + dialect: "postgres", + }, + test: { + url: process.env.TEST_DB, + dialect: "postgres", + }, + production: { + url: process.env.DB_CONNECTION, + dialect: "postgres", + dialectOptions: { + ssl: { + require: true, + rejectUnauthorized: false, + }, + }, + }, }; diff --git a/src/sequelize/migrations/20240416115110-create-user.js b/src/sequelize/migrations/20240416115110-create-user.js index ba6c2a3..80aecab 100644 --- a/src/sequelize/migrations/20240416115110-create-user.js +++ b/src/sequelize/migrations/20240416115110-create-user.js @@ -1,44 +1,45 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ /** @type {import('sequelize-cli').Migration} */ module.exports = { - async up(queryInterface, Sequelize) { - await queryInterface.createTable("users", { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - name: { - type: Sequelize.STRING, - allowNull: false, - }, - username: { - type: Sequelize.STRING, - allowNull: false, - }, - email: { - type: Sequelize.STRING, - allowNull: false, - validate: { - isEmail: true, - }, - }, - password: { - type: Sequelize.STRING, - allowNull: false, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }); - }, + async up(queryInterface, Sequelize) { + await queryInterface.createTable("users", { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER, + }, + name: { + type: Sequelize.STRING, + allowNull: false, + }, + username: { + type: Sequelize.STRING, + allowNull: false, + }, + email: { + type: Sequelize.STRING, + allowNull: false, + validate: { + isEmail: true, + }, + }, + password: { + type: Sequelize.STRING, + allowNull: false, + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + }); + }, - async down(queryInterface, Sequelize) { - await queryInterface.describeTable("users"); - }, -}; \ No newline at end of file + async down(queryInterface, Sequelize) { + await queryInterface.describeTable("users"); + }, +}; diff --git a/src/sequelize/migrations/20240418083442-UserTests.js b/src/sequelize/migrations/20240418083442-UserTests.js index 1fac10a..871d9c4 100644 --- a/src/sequelize/migrations/20240418083442-UserTests.js +++ b/src/sequelize/migrations/20240418083442-UserTests.js @@ -1,44 +1,45 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ /** @type {import('sequelize-cli').Migration} */ module.exports = { - async up(queryInterface, Sequelize) { - await queryInterface.createTable("usersTests", { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - name: { - type: Sequelize.STRING, - allowNull: false, - }, - username: { - type: Sequelize.STRING, - allowNull: false, - }, - email: { - type: Sequelize.STRING, - allowNull: false, - validate: { - isEmail: true, - }, - }, - password: { - type: Sequelize.STRING, - allowNull: false, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }); - }, + async up(queryInterface, Sequelize) { + await queryInterface.createTable("usersTests", { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER, + }, + name: { + type: Sequelize.STRING, + allowNull: false, + }, + username: { + type: Sequelize.STRING, + allowNull: false, + }, + email: { + type: Sequelize.STRING, + allowNull: false, + validate: { + isEmail: true, + }, + }, + password: { + type: Sequelize.STRING, + allowNull: false, + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + }); + }, - async down(queryInterface, Sequelize) { - await queryInterface.describeTable("usersTests"); - }, -}; \ No newline at end of file + async down(queryInterface, Sequelize) { + await queryInterface.describeTable("usersTests"); + }, +}; diff --git a/src/sequelize/models/index.js b/src/sequelize/models/index.js index 7549577..bfd6219 100644 --- a/src/sequelize/models/index.js +++ b/src/sequelize/models/index.js @@ -11,37 +11,37 @@ const db = {}; let sequelize; if (config.use_env_variable) { - sequelize = new Sequelize(process.env[config.use_env_variable], config); + sequelize = new Sequelize(process.env[config.use_env_variable], config); } else { - sequelize = new Sequelize( - config.database, - config.username, - config.password, - config, - ); + sequelize = new Sequelize( + config.database, + config.username, + config.password, + config, + ); } fs.readdirSync(__dirname) - .filter((file) => { - return ( - file.indexOf(".") !== 0 && - file !== basename && - file.slice(-3) === ".js" && - file.indexOf(".test.js") === -1 - ); - }) - .forEach((file) => { - const model = require(path.join(__dirname, file))( - sequelize, - Sequelize.DataTypes, - ); - db[model.name] = model; - }); + .filter((file) => { + return ( + file.indexOf(".") !== 0 && + file !== basename && + file.slice(-3) === ".js" && + file.indexOf(".test.js") === -1 + ); + }) + .forEach((file) => { + const model = require(path.join(__dirname, file))( + sequelize, + Sequelize.DataTypes, + ); + db[model.name] = model; + }); Object.keys(db).forEach((modelName) => { - if (db[modelName].associate) { - db[modelName].associate(db); - } + if (db[modelName].associate) { + db[modelName].associate(db); + } }); db.sequelize = sequelize; diff --git a/src/sequelize/models/users.ts b/src/sequelize/models/users.ts index 2bacede..fdc585f 100644 --- a/src/sequelize/models/users.ts +++ b/src/sequelize/models/users.ts @@ -1,60 +1,63 @@ -import {Model,DataTypes} from 'sequelize'; -import sequelize from '../../config/dbConnection'; +import { Model, DataTypes } from "sequelize"; +import sequelize from "../../config/dbConnection"; -export interface UserAttributes{ - id?:number, - name: string, - username: string, - email:string, - password:string, - createdAt?:Date, - updatedAt?:Date +export interface UserAttributes { + id?: number; + name: string; + username: string; + email: string; + password: string; + createdAt?: Date; + updatedAt?: Date; } -class User extends Model implements UserAttributes{ - id!: number | undefined; - name!: string; - username!: string; - email!: string; - password!: string; - createdAt!: Date | undefined; - updatedAt1: Date | undefined; +class User extends Model implements UserAttributes { + id!: number | undefined; + name!: string; + username!: string; + email!: string; + password!: string; + createdAt!: Date | undefined; + updatedAt1: Date | undefined; } - User.init({ - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: DataTypes.NUMBER, - }, - name: { - type: DataTypes.STRING, - allowNull: false, - }, - username: { - type: DataTypes.STRING, - allowNull: false, - }, - email:{ - allowNull:false, - type:DataTypes.STRING - }, - password: { - allowNull:false, - type:DataTypes.STRING - }, - createdAt: { - allowNull: false, - type: DataTypes.DATE, - }, - updatedAt: { - allowNull: false, - type: DataTypes.DATE, - }, - }, { - sequelize:sequelize, - modelName: 'users', - }); +User.init( + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.NUMBER, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + }, + username: { + type: DataTypes.STRING, + allowNull: false, + }, + email: { + allowNull: false, + type: DataTypes.STRING, + }, + password: { + allowNull: false, + type: DataTypes.STRING, + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE, + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE, + }, + }, + { + sequelize: sequelize, + modelName: "users", + }, +); - export default User; \ No newline at end of file +export default User; diff --git a/src/sequelize/models/usersTests.ts b/src/sequelize/models/usersTests.ts index 62cf9d1..fcfe567 100644 --- a/src/sequelize/models/usersTests.ts +++ b/src/sequelize/models/usersTests.ts @@ -1,61 +1,63 @@ -import {Model,DataTypes} from 'sequelize'; -import sequelize from '../../config/dbConnection'; +import { Model, DataTypes } from "sequelize"; +import sequelize from "../../config/dbConnection"; - -export interface UserAttributes{ - id?:number, - name: string, - username: string, - email:string, - password:string, - createdAt?:Date, - updatedAt?:Date +export interface UserAttributes { + id?: number; + name: string; + username: string; + email: string; + password: string; + createdAt?: Date; + updatedAt?: Date; } -class UserTest extends Model implements UserAttributes{ - id!: number | undefined; - name!: string; - username!: string; - email!: string; - password!: string; - createdAt!: Date | undefined; - updatedAt1: Date | undefined; +class UserTest extends Model implements UserAttributes { + id!: number | undefined; + name!: string; + username!: string; + email!: string; + password!: string; + createdAt!: Date | undefined; + updatedAt!: Date | undefined; // Corrected typo here } - UserTest.init({ - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: DataTypes.NUMBER, - }, - name: { - type: DataTypes.STRING, - allowNull: false, - }, - username: { - type: DataTypes.STRING, - allowNull: false, - }, - email:{ - allowNull:false, - type:DataTypes.STRING - }, - password: { - allowNull:false, - type:DataTypes.STRING - }, - createdAt: { - allowNull: false, - type: DataTypes.DATE, - }, - updatedAt: { - allowNull: false, - type: DataTypes.DATE, - }, - }, { - sequelize:sequelize, - modelName: 'usersTests', - }); +UserTest.init( + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.NUMBER, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + }, + username: { + type: DataTypes.STRING, + allowNull: false, + }, + email: { + allowNull: false, + type: DataTypes.STRING, + }, + password: { + allowNull: false, + type: DataTypes.STRING, + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE, + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE, + }, + }, + { + sequelize: sequelize, + modelName: "usersTests", + }, +); - export default UserTest; \ No newline at end of file +export default UserTest; diff --git a/src/sequelize/seeders/20240412144111-demo-user.js b/src/sequelize/seeders/20240412144111-demo-user.js index e460e81..2fdad7e 100644 --- a/src/sequelize/seeders/20240412144111-demo-user.js +++ b/src/sequelize/seeders/20240412144111-demo-user.js @@ -1,26 +1,27 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ /** @type {import('sequelize-cli').Migration} */ module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.bulkInsert("Users", [ - { - name: "Rukundo Soleil", - username: "soleil00", - email: "soleil@soleil00.com", - password: "soleil00", - createdAt: new Date(), - updatedAt: new Date(), - }, - { - name: "test user", - username: "yes", - email: "soleil@soleil0w.com", - password: "soleil0w0", - createdAt: new Date(), - updatedAt: new Date(), - }, - ]); - }, - down: (queryInterface, Sequelize) => { - return queryInterface.bulkDelete("Users", null, {}); - }, + up: (queryInterface, Sequelize) => { + return queryInterface.bulkInsert("Users", [ + { + name: "Rukundo Soleil", + username: "soleil00", + email: "soleil@soleil00.com", + password: "soleil00", + createdAt: new Date(), + updatedAt: new Date(), + }, + { + name: "test user", + username: "yes", + email: "soleil@soleil0w.com", + password: "soleil0w0", + createdAt: new Date(), + updatedAt: new Date(), + }, + ]); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.bulkDelete("Users", null, {}); + }, }; diff --git a/src/services/user.service.ts b/src/services/user.service.ts index dbdeae8..f957347 100644 --- a/src/services/user.service.ts +++ b/src/services/user.service.ts @@ -1,50 +1,62 @@ -import { errors } from "undici-types"; import User from "../sequelize/models/users"; import { hashedPassword } from "../utils/hashPassword"; import { Op } from "sequelize"; export const getAllUsers = async () => { - try { - const users = await User.findAll(); - if (users.length === 0) { - console.log("no user"); - } - return users; - } catch (error: any) { - console.log(error.message); - throw new Error(error.message); - } + try { + const users = await User.findAll(); + if (users.length === 0) { + console.log("no user"); + } + return users; + } catch (error: any) { + console.log(error.message); + throw new Error(error.message); + } }; -export const loggedInUser = async(email:string) => { - try{ - const user:any = await User.findOne({ - where: { email: email } - }); - if(!user){ - return false; - }else{ - return user; - } -}catch(err:any){ - throw new Error(err.message); +export const loggedInUser = async (email: string) => { + try { + const user: any = await User.findOne({ + where: { email: email }, + }); + if (!user) { + return false; + } else { + return user; + } + } catch (err: any) { + throw new Error(err.message); + } }; -}; -export const createUserService = async (name: string, email: string, username: string, password: string): Promise => { - const existingUser = await User.findOne({ - where: { - [Op.or]: [{ email }, { username }] - } - }); - if (existingUser) { - return null; - } - const hashPassword = await hashedPassword(password); - const user = await User.create({ name, email, username, password: hashPassword }); - return user; + +export const createUserService = async ( + name: string, + email: string, + username: string, + password: string, +): Promise => { + const existingUser = await User.findOne({ + where: { + [Op.or]: [{ email }, { username }], + }, + }); + if (existingUser) { + return null; + } + const hashPassword = await hashedPassword(password); + const user = await User.create({ + name, + email, + username, + password: hashPassword, + }); + return user; }; export const getUserByEmail = async (email: string): Promise => { - const user = await User.findOne({ where: { email } }); - return user; + const user = await User.findOne({ + where: { email }, + }); + return user; }; diff --git a/src/types.ts b/src/types.ts index 3fdbbd9..ee1753d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,10 +1,10 @@ export interface IUser { - id?: number; - name: string; - username: string; - email: string; - password: string; - isMerchant?: boolean; - createdAt: Date; - updatedAt: Date; + id?: number; + name: string; + username: string; + email: string; + password: string; + isMerchant?: boolean; + createdAt: Date; + updatedAt: Date; } diff --git a/src/utils/env.ts b/src/utils/env.ts index 5d15773..d066db0 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -2,8 +2,8 @@ import dotenv from "dotenv"; dotenv.config(); export const env = { - port: process.env.PORT || 3000, - db_url: process.env.DB_CONNECTION as string, - test_db_url: process.env.TEST_DB as string, - jwt_secret:process.env.JWT_SECRET, + port: process.env.PORT || 3000, + db_url: process.env.DB_CONNECTION as string, + test_db_url: process.env.TEST_DB as string, + jwt_secret: process.env.JWT_SECRET, }; diff --git a/src/utils/hashPassword.ts b/src/utils/hashPassword.ts index 8da8382..18729fd 100644 --- a/src/utils/hashPassword.ts +++ b/src/utils/hashPassword.ts @@ -1,6 +1,6 @@ -import bcrypt from 'bcryptjs' +import bcrypt from "bcryptjs"; -export const hashedPassword = async(password: string) => { - const hashpass = await bcrypt.hash(password, 10) - return hashpass; -} \ No newline at end of file +export const hashedPassword = async (password: string) => { + const hashpass = await bcrypt.hash(password, 10); + return hashpass; +}; diff --git a/src/utils/jsonwebtoken.ts b/src/utils/jsonwebtoken.ts index 53e9af0..9c0178e 100644 --- a/src/utils/jsonwebtoken.ts +++ b/src/utils/jsonwebtoken.ts @@ -1,11 +1,15 @@ import { IUser } from "../types"; import { env } from "../utils/env"; -import { sign,verify } from "jsonwebtoken"; - -export const generateToken = async(user:IUser) =>{ - const accessToken = sign({email:user.email,password:user.password}, - `${env.jwt_secret}`,{expiresIn:'72h'} - ); - return accessToken; -} +import { sign } from "jsonwebtoken"; +export const generateToken = async (user: IUser) => { + const accessToken = sign( + { + email: user.email, + password: user.password, + }, + `${env.jwt_secret}`, + { expiresIn: "72h" }, + ); + return accessToken; +}; diff --git a/tsconfig.json b/tsconfig.json index b003173..70e032a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,109 +1,112 @@ { - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ - "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + /* Language and Environment */ + "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Modules */ - "module": "commonjs" /* Specify what module code is generated. */, - // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - "typeRoots": ["node_modules/@types", "./typings"], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + /* Modules */ + "module": "commonjs" /* Specify what module code is generated. */, + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + "typeRoots": [ + "node_modules/@types", + "./typings" + ] /* Specify multiple folders that act like './node_modules/@types'. */, + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "./dist" /* Specify an output folder for all emitted files. */, - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist" /* Specify an output folder for all emitted files. */, + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, - /* Type Checking */ - "strict": true /* Enable all strict type-checking options. */, - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } } From f5d89ccb88741af513048cb6ffefa35598b22c87 Mon Sep 17 00:00:00 2001 From: IRUMVAEmmanuel1 Date: Thu, 25 Apr 2024 21:10:16 +0200 Subject: [PATCH 2/4] ESLint Configuration format --- __test__/home.test.ts | 4 ++-- __test__/user.test.ts | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/__test__/home.test.ts b/__test__/home.test.ts index 0ccd376..0f1e54d 100644 --- a/__test__/home.test.ts +++ b/__test__/home.test.ts @@ -10,11 +10,11 @@ describe("Testing Home route", () => { } catch (error) { sequelize.close(); } - }, 20000); + }, 40000); test("servr should return status code of 200 --> given'/'", async () => { const response = await request(app).get("/"); expect(response.status).toBe(200); - }, 20000); + }, 40000); }); diff --git a/__test__/user.test.ts b/__test__/user.test.ts index e4de66d..28557ed 100644 --- a/__test__/user.test.ts +++ b/__test__/user.test.ts @@ -16,7 +16,7 @@ const loginData: any = { email: "test1@gmail.com", password: "test1234", }; -describe("Testing user Routes", () => { +describe("Testing a user Routes", () => { beforeAll(async () => { try { await connect(); @@ -24,7 +24,7 @@ describe("Testing user Routes", () => { } catch (error) { sequelize.close(); } - }, 20000); + }, 40000); afterAll(async () => { await User.destroy({ truncate: true }); @@ -36,7 +36,7 @@ describe("Testing user Routes", () => { .post("/api/v1/users/register") .send(userData); expect(response.status).toBe(201); - }, 20000); + }, 40000); test("should return 409 when registering with an existing email", async () => { User.create(userData); @@ -44,7 +44,7 @@ describe("Testing user Routes", () => { .post("/api/v1/users/register") .send(userData); expect(response.status).toBe(409); - }, 20000); + }, 40000); test("should return 400 when registering with an invalid credential", async () => { const userData = { @@ -57,7 +57,7 @@ describe("Testing user Routes", () => { .send(userData); expect(response.status).toBe(400); - }, 20000); + }, 40000); }); }); @@ -67,7 +67,7 @@ test("should return all users in db --> given '/api/v1/users'", async () => { const response = await request(app).get("/api/v1/users"); expect(spy).toHaveBeenCalled(); expect(spy2).toHaveBeenCalled(); -}, 20000); +}, 40000); test("Should return status 401 to indicate Unauthorized user", async () => { const loggedInUser = { email: userData.email, From 4d728d1821c61c1a992861c248d13d7f1154e9dc Mon Sep 17 00:00:00 2001 From: IRUMVAEmmanuel1 Date: Thu, 25 Apr 2024 21:49:59 +0200 Subject: [PATCH 3/4] ESLint Configuration format --- .editorconfig | 14 ++ .husky/pre-commit | 1 - .prettierrc.json | 11 +- __test__/home.test.ts | 22 +- __test__/user.test.ts | 118 +++++----- eslint.config.cjs | 93 ++++---- index.ts | 18 +- jest.config.js | 16 +- package.json | 174 +++++++-------- src/config/dbConnection.ts | 23 +- src/controllers/userControllers.ts | 127 ++++++----- src/docs/swagger.ts | 106 +++++---- src/docs/users.ts | 172 +++++++-------- src/helpers/comparePassword.ts | 7 +- src/middleware/validator.ts | 52 +++-- src/routes/homeRoutes.ts | 20 +- src/routes/userRoutes.ts | 13 +- src/schemas/signUpSchema.ts | 8 +- src/sequelize/config/config.js | 36 ++-- .../migrations/20240416115110-create-user.js | 80 +++---- .../migrations/20240418083442-UserTests.js | 80 +++---- src/sequelize/models/index.js | 37 ++-- src/sequelize/models/users.ts | 100 ++++----- src/sequelize/models/usersTests.ts | 100 ++++----- .../seeders/20240412144111-demo-user.js | 46 ++-- src/services/user.service.ts | 91 ++++---- src/types.ts | 16 +- src/utils/env.ts | 8 +- src/utils/hashPassword.ts | 4 +- src/utils/jsonwebtoken.ts | 18 +- tsconfig.json | 201 +++++++++--------- 31 files changed, 887 insertions(+), 925 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a94d12a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true +max_line_length = 160 + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit index 186e9aa..e37998f 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,2 +1 @@ -npm run pre-commit npm run test diff --git a/.prettierrc.json b/.prettierrc.json index 15319ae..3609f16 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,5 +1,10 @@ { - "semi": true, - "tabWidth": 2, - "useTabs": true + "tabWidth": 2, + "useTabs": false, + "singleQuote": false, + "semi": true, + "bracketSpacing": true, + "arrowParens": "always", + "bracketSameLine": true, + "endOfLine": "auto" } diff --git a/__test__/home.test.ts b/__test__/home.test.ts index 0f1e54d..03c4e86 100644 --- a/__test__/home.test.ts +++ b/__test__/home.test.ts @@ -4,17 +4,17 @@ import app from "../src/utils/server"; import sequelize, { connect } from "../src/config/dbConnection"; describe("Testing Home route", () => { - beforeAll(async () => { - try { - await connect(); - } catch (error) { - sequelize.close(); - } - }, 40000); + beforeAll(async () => { + try { + await connect(); + } catch (error) { + sequelize.close(); + } + }, 40000); - test("servr should return status code of 200 --> given'/'", async () => { - const response = await request(app).get("/"); + test("servr should return status code of 200 --> given'/'", async () => { + const response = await request(app).get("/"); - expect(response.status).toBe(200); - }, 40000); + expect(response.status).toBe(200); + }, 40000); }); diff --git a/__test__/user.test.ts b/__test__/user.test.ts index 28557ed..b6ab8e4 100644 --- a/__test__/user.test.ts +++ b/__test__/user.test.ts @@ -6,81 +6,73 @@ import * as userServices from "../src/services/user.service"; import sequelize, { connect } from "../src/config/dbConnection"; const userData: any = { - name: "yvanna", - username: "testuser", - email: "test1@gmail.com", - password: "test1234", + name: "yvanna", + username: "testuser", + email: "test1@gmail.com", + password: "test1234", }; const loginData: any = { - email: "test1@gmail.com", - password: "test1234", + email: "test1@gmail.com", + password: "test1234", }; describe("Testing a user Routes", () => { - beforeAll(async () => { - try { - await connect(); - await User.destroy({ truncate: true }); - } catch (error) { - sequelize.close(); - } - }, 40000); + beforeAll(async () => { + try { + await connect(); + await User.destroy({ truncate: true }); + } catch (error) { + sequelize.close(); + } + }, 40000); - afterAll(async () => { - await User.destroy({ truncate: true }); - await sequelize.close(); - }); - describe("Testing user authentication", () => { - test("should return 201 and create a new user when registering successfully", async () => { - const response = await request(app) - .post("/api/v1/users/register") - .send(userData); - expect(response.status).toBe(201); - }, 40000); + afterAll(async () => { + await User.destroy({ truncate: true }); + await sequelize.close(); + }); + describe("Testing user authentication", () => { + test("should return 201 and create a new user when registering successfully", async () => { + const response = await request(app).post("/api/v1/users/register").send(userData); + expect(response.status).toBe(201); + }, 40000); - test("should return 409 when registering with an existing email", async () => { - User.create(userData); - const response = await request(app) - .post("/api/v1/users/register") - .send(userData); - expect(response.status).toBe(409); - }, 40000); + test("should return 409 when registering with an existing email", async () => { + User.create(userData); + const response = await request(app).post("/api/v1/users/register").send(userData); + expect(response.status).toBe(409); + }, 40000); - test("should return 400 when registering with an invalid credential", async () => { - const userData = { - email: "test@mail.com", - name: "", - username: "existinguser", - }; - const response = await request(app) - .post("/api/v1/users/register") - .send(userData); + test("should return 400 when registering with an invalid credential", async () => { + const userData = { + email: "test@mail.com", + name: "", + username: "existinguser", + }; + const response = await request(app).post("/api/v1/users/register").send(userData); - expect(response.status).toBe(400); - }, 40000); - }); + expect(response.status).toBe(400); + }, 40000); + }); }); test("should return all users in db --> given '/api/v1/users'", async () => { - const spy = jest.spyOn(User, "findAll"); - const spy2 = jest.spyOn(userServices, "getAllUsers"); - const response = await request(app).get("/api/v1/users"); - expect(spy).toHaveBeenCalled(); - expect(spy2).toHaveBeenCalled(); + const spy = jest.spyOn(User, "findAll"); + const spy2 = jest.spyOn(userServices, "getAllUsers"); + const response = await request(app).get("/api/v1/users"); + expect(spy).toHaveBeenCalled(); + expect(spy2).toHaveBeenCalled(); }, 40000); test("Should return status 401 to indicate Unauthorized user", async () => { - const loggedInUser = { - email: userData.email, - password: "test", - }; - const spyonOne = jest.spyOn(User, "findOne").mockResolvedValueOnce({ - //@ts-ignore - email: userData.email, - password: loginData.password, - }); - const response = await request(app) - .post("/api/v1/users/login") - .send(loggedInUser); - expect(response.body.status).toBe(401); - spyonOne.mockRestore(); + const loggedInUser = { + email: userData.email, + password: "test", + }; + const spyonOne = jest.spyOn(User, "findOne").mockResolvedValueOnce({ + //@ts-ignore + email: userData.email, + password: loginData.password, + }); + const response = await request(app).post("/api/v1/users/login").send(loggedInUser); + expect(response.body.status).toBe(401); + spyonOne.mockRestore(); }); diff --git a/eslint.config.cjs b/eslint.config.cjs index 49f3638..938e455 100644 --- a/eslint.config.cjs +++ b/eslint.config.cjs @@ -2,50 +2,51 @@ const eslint = require("@eslint/js"); const tseslint = require("typescript-eslint"); module.exports = tseslint.config( - { - ignores: ["**/__test__", "**/*.json"], - }, - eslint.configs.recommended, - ...tseslint.configs.recommended, - { - languageOptions: { - parserOptions: { - project: true, - ecmaVersion: 2020, - }, - }, - }, - { - files: ["*.ts", "*.js"], - ...tseslint.configs.disableTypeChecked, - }, - { - files: ["*.test *.js"], - rules: { - "@typescript-eslint/no-unused-vars": 0, - "@typescript-eslint/no-unsafe-call": 0, - languageOptions: { - globals: { - it: "readonly", - describe: "readonly", - }, - }, - }, - }, - { - rules: { - semi: "error", - "@typescript-eslint/no-unused-vars": 2, - "@typescript-eslint/no-explicit-any": 0, - "@typescript-eslint/no-var-requires": 0, - "no-shadow": [2, { allow: ["req", "res", "err"] }], - "new-cap": 0, - "one-var-declaration-per-line": 0, - "consistent-return": 0, - "no-param-reassign": 0, - "comma-dangle": 0, - "no-undef": 0, - curly: ["error", "multi-line"], - }, - }, + { + ignores: ["**/__test__", "**/*.json"], + }, + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + languageOptions: { + parserOptions: { + project: true, + ecmaVersion: 2020, + }, + }, + }, + { + files: ["*.ts", "*.js"], + extends: ["eslint:recommended", "plugin:prettier/recommended"], + ...tseslint.configs.disableTypeChecked, + }, + { + files: ["*.test *.js"], + rules: { + "@typescript-eslint/no-unused-vars": 0, + "@typescript-eslint/no-unsafe-call": 0, + languageOptions: { + globals: { + it: "readonly", + describe: "readonly", + }, + }, + }, + }, + { + rules: { + semi: "error", + "@typescript-eslint/no-unused-vars": 2, + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/no-var-requires": 0, + "no-shadow": [2, { allow: ["req", "res", "err"] }], + "new-cap": 0, + "one-var-declaration-per-line": 0, + "consistent-return": 0, + "no-param-reassign": 0, + "comma-dangle": 0, + "no-undef": 0, + curly: ["error", "multi-line"], + }, + }, ); diff --git a/index.ts b/index.ts index 328b27d..76a39d4 100644 --- a/index.ts +++ b/index.ts @@ -3,13 +3,13 @@ import { env } from "./src/utils/env"; import app from "./src/utils/server"; app.listen(env.port, async () => { - await connect(); - await sequelize - .sync() - .then(() => { - console.log(" db synced and server is running"); - }) - .catch((error: any) => { - console.log(error.message); - }); + await connect(); + await sequelize + .sync() + .then(() => { + console.log(" db synced and server is running"); + }) + .catch((error: any) => { + console.log(error.message); + }); }); diff --git a/jest.config.js b/jest.config.js index 124cea1..36e4760 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,11 +1,11 @@ /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ module.exports = { - preset: "ts-jest", - testEnvironment: "node", - testMatch: ["**/**/*.test.ts"], - verbose: true, - forceExit: true, - clearMocks: true, - resetMocks: true, - restoreMocks: true, + preset: "ts-jest", + testEnvironment: "node", + testMatch: ["**/**/*.test.ts"], + verbose: true, + forceExit: true, + clearMocks: true, + resetMocks: true, + restoreMocks: true, }; diff --git a/package.json b/package.json index 0b8461e..a2a12ee 100644 --- a/package.json +++ b/package.json @@ -1,89 +1,89 @@ { - "name": "eagles-ec-be", - "version": "1.0.0", - "description": "A platform providing a marketplace for buyers and sellers to meet", - "main": "index.ts", - "scripts": { - "start": "", - "pre-commit": "prettier . --write && eslint .", - "lint": "eslint .", - "dev": "nodemon index.ts", - "build": "tsc", - "migrate": "npx sequelize-cli db:migrate", - "seed": "npx sequelize-cli db:seed:all", - "lint:fix": "npx eslint --fix .", - "test": "cross-env NODE_ENV=test jest --detectOpenHandles --coverage", - "prepare": "husky", - "prettier": "prettier . --write" - }, - "lint-staged": { - ".ts": [ - "npm run lint:fix" - ], - "**/*": "prettier --write" - }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest", - "prettier" - ] - }, - "husky": { - "hooks": { - "pre-commit": "npm run lint-staged && npm run test" - } - }, - "author": "atlp", - "license": "MIT", - "devDependencies": { - "@eslint/js": "^9.0.0", - "@types/bcryptjs": "^2.4.6", - "@types/cors": "^2.8.17", - "@types/dotenv": "^8.2.0", - "@types/express": "^4.17.21", - "@types/jest": "^29.5.12", - "@types/jsonwebtoken": "^9.0.6", - "@types/node": "^20.12.7", - "@types/supertest": "^6.0.2", - "@types/swagger-ui-express": "^4.1.6", - "@typescript-eslint/eslint-plugin": "^7.6.0", - "@typescript-eslint/parser": "^7.6.0", - "eslint": "^8.57.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jest": "^28.2.0", - "globals": "^15.0.0", - "jest": "^29.7.0", - "nodemon": "^3.1.0", - "pre-commit": "^1.2.2", - "prettier": "^3.2.5", - "sequelize-cli": "^6.6.2", - "supertest": "^6.3.4", - "ts-jest": "^29.1.2", - "ts-node": "^10.9.2", - "typescript": "^5.4.5", - "typescript-eslint": "^7.7.0" - }, - "dependencies": { - "@types/bcrypt": "^5.0.2", - "bcrypt": "^5.1.1", - "bcryptjs": "^2.4.3", - "cors": "^2.8.5", - "cross-env": "^7.0.3", - "cryptr": "^6.3.0", - "dotenv": "^16.4.5", - "email-validator": "^2.0.4", - "express": "^4.19.2", - "husky": "^9.0.11", - "joi": "^17.13.0", - "jsonwebtoken": "^9.0.2", - "lint-staged": "^15.2.2", - "path": "^0.12.7", - "pg": "^8.11.5", - "pg-hstore": "^2.3.4", - "sequelize": "^6.37.2", - "swagger-ui-express": "^5.0.0" - } + "name": "eagles-ec-be", + "version": "1.0.0", + "description": "A platform providing a marketplace for buyers and sellers to meet", + "main": "index.ts", + "scripts": { + "start": "", + "pre-commit": "prettier . --write && eslint .", + "lint": "eslint .", + "dev": "nodemon index.ts", + "build": "tsc", + "migrate": "npx sequelize-cli db:migrate", + "seed": "npx sequelize-cli db:seed:all", + "lint:fix": "npx eslint --fix .", + "test": "cross-env NODE_ENV=test jest --detectOpenHandles --coverage", + "prepare": "husky", + "prettier": "prettier . --write" + }, + "lint-staged": { + ".ts": [ + "npm run lint:fix" + ], + "**/*": "prettier --write" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest", + "prettier" + ] + }, + "husky": { + "hooks": { + "pre-commit": "npm run lint-staged && npm run test" + } + }, + "author": "atlp", + "license": "MIT", + "devDependencies": { + "@eslint/js": "^9.0.0", + "@types/bcryptjs": "^2.4.6", + "@types/cors": "^2.8.17", + "@types/dotenv": "^8.2.0", + "@types/express": "^4.17.21", + "@types/jest": "^29.5.12", + "@types/jsonwebtoken": "^9.0.6", + "@types/node": "^20.12.7", + "@types/supertest": "^6.0.2", + "@types/swagger-ui-express": "^4.1.6", + "@typescript-eslint/eslint-plugin": "^7.6.0", + "@typescript-eslint/parser": "^7.6.0", + "eslint": "^8.57.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-jest": "^28.2.0", + "globals": "^15.0.0", + "jest": "^29.7.0", + "nodemon": "^3.1.0", + "pre-commit": "^1.2.2", + "prettier": "^3.2.5", + "sequelize-cli": "^6.6.2", + "supertest": "^6.3.4", + "ts-jest": "^29.1.2", + "ts-node": "^10.9.2", + "typescript": "^5.4.5", + "typescript-eslint": "^7.7.0" + }, + "dependencies": { + "@types/bcrypt": "^5.0.2", + "bcrypt": "^5.1.1", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "cross-env": "^7.0.3", + "cryptr": "^6.3.0", + "dotenv": "^16.4.5", + "email-validator": "^2.0.4", + "express": "^4.19.2", + "husky": "^9.0.11", + "joi": "^17.13.0", + "jsonwebtoken": "^9.0.2", + "lint-staged": "^15.2.2", + "path": "^0.12.7", + "pg": "^8.11.5", + "pg-hstore": "^2.3.4", + "sequelize": "^6.37.2", + "swagger-ui-express": "^5.0.0" + } } diff --git a/src/config/dbConnection.ts b/src/config/dbConnection.ts index 5ebaef7..c892b31 100644 --- a/src/config/dbConnection.ts +++ b/src/config/dbConnection.ts @@ -3,21 +3,18 @@ import { env } from "../utils/env"; const envT = process.env.NODE_ENV; -const sequelize = new Sequelize( - envT === "test" ? env.test_db_url : env.db_url, - { - dialect: "postgres", - }, -); +const sequelize = new Sequelize(envT === "test" ? env.test_db_url : env.db_url, { + dialect: "postgres", +}); export const connect = async () => { - try { - await sequelize.authenticate(); - console.log("successfuly connected's to db"); - } catch (error: any) { - console.log("error: ", error.message); - return; - } + try { + await sequelize.authenticate(); + console.log("successfuly connected's to db"); + } catch (error: any) { + console.log("error: ", error.message); + return; + } }; export default sequelize; diff --git a/src/controllers/userControllers.ts b/src/controllers/userControllers.ts index a1a276a..6152856 100644 --- a/src/controllers/userControllers.ts +++ b/src/controllers/userControllers.ts @@ -6,79 +6,76 @@ import { loggedInUser } from "../services/user.service"; import { createUserService } from "../services/user.service"; export const fetchAllUsers = async (req: Request, res: Response) => { - try { - // const users = await userService.getAllUsers(); + try { + // const users = await userService.getAllUsers(); - const users = await userService.getAllUsers(); + const users = await userService.getAllUsers(); - if (users.length <= 0) { - return res.status(404).json({ - message: "No users found", - }); - } else { - res.status(200).json({ - message: "Users fetched successfully", - count: users.length, - users: users, - }); - } - } catch (error: any) { - res.status(500).json({ - message: "Internal server error", - error: error.message, - }); - } + if (users.length <= 0) { + return res.status(404).json({ + message: "No users found", + }); + } else { + res.status(200).json({ + message: "Users fetched successfully", + count: users.length, + users: users, + }); + } + } catch (error: any) { + res.status(500).json({ + message: "Internal server error", + error: error.message, + }); + } }; export const userLogin = async (req: Request, res: Response) => { - const { email, password } = req.body; - const user = await loggedInUser(email); - const accessToken = await generateToken(user); - if (!user) { - res.status(404).json({ - status: 404, - message: "User Not Found ! Please Register new ancount", - }); - } else { - const match = await comparePasswords(password, user.password); - if (!match) { - res.status(401).json({ - status: 401, - message: " User email or password is incorrect!", - }); - } else { - res.status(200).json({ - status: 200, - message: "Logged in", - token: accessToken, - }); - } - } + const { email, password } = req.body; + const user = await loggedInUser(email); + const accessToken = await generateToken(user); + if (!user) { + res.status(404).json({ + status: 404, + message: "User Not Found ! Please Register new ancount", + }); + } else { + const match = await comparePasswords(password, user.password); + if (!match) { + res.status(401).json({ + status: 401, + message: " User email or password is incorrect!", + }); + } else { + res.status(200).json({ + status: 200, + message: "Logged in", + token: accessToken, + }); + } + } }; export const createUserController = async (req: Request, res: Response) => { - try { - const { name, email, username, password } = req.body; - const user = await createUserService(name, email, username, password); + try { + const { name, email, username, password } = req.body; + const user = await createUserService(name, email, username, password); - if (!user) { - return res.status(409).json({ - status: 409, - message: "Username or email already exists", - }); - } + if (!user) { + return res.status(409).json({ + status: 409, + message: "Username or email already exists", + }); + } - res.status(201).json({ - status: 201, - message: "User successfully created.", - }); - } catch (err: any) { - if ( - err.name === "UnauthorizedError" && - err.message === "User already exists" - ) { - return res.status(409).json({ error: "User already exists" }); - } - res.status(500).json({ error: err }); - } + res.status(201).json({ + status: 201, + message: "User successfully created.", + }); + } catch (err: any) { + if (err.name === "UnauthorizedError" && err.message === "User already exists") { + return res.status(409).json({ error: "User already exists" }); + } + res.status(500).json({ error: err }); + } }; diff --git a/src/docs/swagger.ts b/src/docs/swagger.ts index 46caa6c..f5fc119 100644 --- a/src/docs/swagger.ts +++ b/src/docs/swagger.ts @@ -1,71 +1,65 @@ import express from "express"; import { serve, setup } from "swagger-ui-express"; import { env } from "../utils/env"; -import { - createUsers, - getUsers, - loginAsUser, - userSchema, - loginSchema, -} from "./users"; +import { createUsers, getUsers, loginAsUser, userSchema, loginSchema } from "./users"; const docRouter = express.Router(); const options = { - openapi: "3.0.1", - info: { - title: "Eagles E-commerce API", - version: "1.0.0", - description: "Documentation for Eagles E-commerce Backend", - }, + openapi: "3.0.1", + info: { + title: "Eagles E-commerce API", + version: "1.0.0", + description: "Documentation for Eagles E-commerce Backend", + }, - servers: [ - { - url: `http://localhost:${env.port}`, - description: "Development server", - }, - { - url: "https://eagles-ec-be-development.onrender.com/", - description: "Production server", - }, - ], + servers: [ + { + url: `http://localhost:${env.port}`, + description: "Development server", + }, + { + url: "https://eagles-ec-be-development.onrender.com/", + description: "Production server", + }, + ], - basePath: "/", + basePath: "/", - tags: [ - { - name: "Users", - description: "Endpoints related to users", - }, - ], + tags: [ + { + name: "Users", + description: "Endpoints related to users", + }, + ], - paths: { - "/api/v1/users": { - get: getUsers, - }, - "/api/v1/users/register": { - post: createUsers, - }, - "/api/v1/users/login": { - post: loginAsUser, - }, - }, + paths: { + "/api/v1/users": { + get: getUsers, + }, + "/api/v1/users/register": { + post: createUsers, + }, + "/api/v1/users/login": { + post: loginAsUser, + }, + }, - components: { - schemas: { - User: userSchema, - Login: loginSchema, - }, - securitySchemes: { - bearerAuth: { - type: "http", - scheme: "bearer", - bearerFormat: "JWT", - in: "header", - name: "Authorization", - }, - }, - }, + components: { + schemas: { + User: userSchema, + Login: loginSchema, + }, + securitySchemes: { + bearerAuth: { + type: "http", + scheme: "bearer", + bearerFormat: "JWT", + in: "header", + name: "Authorization", + }, + }, + }, }; docRouter.use("/", serve, setup(options)); diff --git a/src/docs/users.ts b/src/docs/users.ts index a120523..ae2c33c 100644 --- a/src/docs/users.ts +++ b/src/docs/users.ts @@ -1,100 +1,100 @@ export const userSchema = { - type: "object", - properties: { - name: { - type: "string", - }, - username: { - type: "string", - }, - email: { - type: "string", - format: "email", - }, - password: { - type: "string", - }, - }, + type: "object", + properties: { + name: { + type: "string", + }, + username: { + type: "string", + }, + email: { + type: "string", + format: "email", + }, + password: { + type: "string", + }, + }, }; export const loginSchema = { - properties: { - email: { - type: "string", - format: "email", - }, - password: { - type: "string", - }, - }, + properties: { + email: { + type: "string", + format: "email", + }, + password: { + type: "string", + }, + }, }; export const getUsers = { - tags: ["Users"], - summary: "Get all users", - responses: { - 200: { - description: "OK", - content: { - "application/json": { - schema: { - type: "array", - items: { - $ref: "#/components/schemas/User", - }, - }, - }, - }, - }, - }, + tags: ["Users"], + summary: "Get all users", + responses: { + 200: { + description: "OK", + content: { + "application/json": { + schema: { + type: "array", + items: { + $ref: "#/components/schemas/User", + }, + }, + }, + }, + }, + }, }; export const createUsers = { - tags: ["Users"], - summary: "Register a new user", - requestBody: { - required: true, - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/User", - }, - }, - }, - }, - responses: { - 201: { - description: "Created", - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/User", - }, - }, - }, - }, - 400: { - description: "Bad request", - }, - }, + tags: ["Users"], + summary: "Register a new user", + requestBody: { + required: true, + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/User", + }, + }, + }, + }, + responses: { + 201: { + description: "Created", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/User", + }, + }, + }, + }, + 400: { + description: "Bad request", + }, + }, }; export const loginAsUser = { - tags: ["Users"], - summary: "Login as user", - requestBody: { - required: true, - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/Login", - }, - }, - }, - }, - responses: { - 200: { - description: "OK", - }, - }, + tags: ["Users"], + summary: "Login as user", + requestBody: { + required: true, + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/Login", + }, + }, + }, + }, + responses: { + 200: { + description: "OK", + }, + }, }; diff --git a/src/helpers/comparePassword.ts b/src/helpers/comparePassword.ts index 27434ee..1552dec 100644 --- a/src/helpers/comparePassword.ts +++ b/src/helpers/comparePassword.ts @@ -1,7 +1,4 @@ import bcrypt from "bcrypt"; -export const comparePasswords = async ( - plainPassword: string, - hashedPassword: string, -): Promise => { - return await bcrypt.compare(plainPassword, hashedPassword); +export const comparePasswords = async (plainPassword: string, hashedPassword: string): Promise => { + return await bcrypt.compare(plainPassword, hashedPassword); }; diff --git a/src/middleware/validator.ts b/src/middleware/validator.ts index 660fdbb..d52295c 100644 --- a/src/middleware/validator.ts +++ b/src/middleware/validator.ts @@ -2,37 +2,33 @@ import { Request, Response, NextFunction } from "express"; import validator from "email-validator"; import { Schema } from "joi"; -export const emailValidation = ( - req: Request, - res: Response, - next: NextFunction, -) => { - const { email } = req.body; +export const emailValidation = (req: Request, res: Response, next: NextFunction) => { + const { email } = req.body; - if (!email) { - return res.status(400).json({ - status: 400, - message: "Email is required", - }); - } + if (!email) { + return res.status(400).json({ + status: 400, + message: "Email is required", + }); + } - const isValid = validator.validate(email); - if (isValid) { - next(); - } else { - return res.status(400).json({ - status: 400, - message: "Email is not valid.", - }); - } + const isValid = validator.validate(email); + if (isValid) { + next(); + } else { + return res.status(400).json({ + status: 400, + message: "Email is not valid.", + }); + } }; export const validateSchema = (schema: Schema) => { - return (req: Request, res: Response, next: NextFunction) => { - const { error } = schema.validate(req.body); - if (error) { - return res.status(400).json({ message: error.details[0].message }); - } - next(); - }; + return (req: Request, res: Response, next: NextFunction) => { + const { error } = schema.validate(req.body); + if (error) { + return res.status(400).json({ message: error.details[0].message }); + } + next(); + }; }; diff --git a/src/routes/homeRoutes.ts b/src/routes/homeRoutes.ts index 884800e..b26f96b 100644 --- a/src/routes/homeRoutes.ts +++ b/src/routes/homeRoutes.ts @@ -3,16 +3,16 @@ import { Request, Response, Router } from "express"; const homeRoute = Router(); homeRoute.get("/", (req: Request, res: Response) => { - try { - res.status(200).json({ - message: "Api is working 😎", - }); - } catch (error: any) { - res.status(500).json({ - message: "Internal server error", - error: error.message, - }); - } + try { + res.status(200).json({ + message: "Api is working 😎", + }); + } catch (error: any) { + res.status(500).json({ + message: "Internal server error", + error: error.message, + }); + } }); export default homeRoute; diff --git a/src/routes/userRoutes.ts b/src/routes/userRoutes.ts index c62efad..5b4e507 100644 --- a/src/routes/userRoutes.ts +++ b/src/routes/userRoutes.ts @@ -1,9 +1,5 @@ import { Router } from "express"; -import { - fetchAllUsers, - createUserController, - userLogin, -} from "../controllers/userControllers"; +import { fetchAllUsers, createUserController, userLogin } from "../controllers/userControllers"; import { emailValidation, validateSchema } from "../middleware/validator"; import signUpSchema from "../schemas/signUpSchema"; @@ -11,12 +7,7 @@ const userRoutes = Router(); userRoutes.get("/", fetchAllUsers); userRoutes.post("/login", userLogin); -userRoutes.post( - "/register", - emailValidation, - validateSchema(signUpSchema), - createUserController, -); +userRoutes.post("/register", emailValidation, validateSchema(signUpSchema), createUserController); userRoutes.post("/register", createUserController); diff --git a/src/schemas/signUpSchema.ts b/src/schemas/signUpSchema.ts index 88d3ff0..7d331d6 100644 --- a/src/schemas/signUpSchema.ts +++ b/src/schemas/signUpSchema.ts @@ -1,10 +1,10 @@ import Joi from "joi"; export const signUpSchema = Joi.object({ - name: Joi.string().min(5).max(40).required(), - username: Joi.string().min(4).required(), - email: Joi.string().min(6).required().email(), - password: Joi.string().min(6).max(20).required(), + name: Joi.string().min(5).max(40).required(), + username: Joi.string().min(4).required(), + email: Joi.string().min(6).required().email(), + password: Joi.string().min(6).max(20).required(), }).options({ allowUnknown: false }); export default signUpSchema; diff --git a/src/sequelize/config/config.js b/src/sequelize/config/config.js index a730b16..c28f20f 100644 --- a/src/sequelize/config/config.js +++ b/src/sequelize/config/config.js @@ -2,22 +2,22 @@ const dotenv = require("dotenv"); dotenv.config(); module.exports = { - development: { - url: process.env.DB_CONNECTION, - dialect: "postgres", - }, - test: { - url: process.env.TEST_DB, - dialect: "postgres", - }, - production: { - url: process.env.DB_CONNECTION, - dialect: "postgres", - dialectOptions: { - ssl: { - require: true, - rejectUnauthorized: false, - }, - }, - }, + development: { + url: process.env.DB_CONNECTION, + dialect: "postgres", + }, + test: { + url: process.env.TEST_DB, + dialect: "postgres", + }, + production: { + url: process.env.DB_CONNECTION, + dialect: "postgres", + dialectOptions: { + ssl: { + require: true, + rejectUnauthorized: false, + }, + }, + }, }; diff --git a/src/sequelize/migrations/20240416115110-create-user.js b/src/sequelize/migrations/20240416115110-create-user.js index 80aecab..5a452f2 100644 --- a/src/sequelize/migrations/20240416115110-create-user.js +++ b/src/sequelize/migrations/20240416115110-create-user.js @@ -1,45 +1,45 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ /** @type {import('sequelize-cli').Migration} */ module.exports = { - async up(queryInterface, Sequelize) { - await queryInterface.createTable("users", { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - name: { - type: Sequelize.STRING, - allowNull: false, - }, - username: { - type: Sequelize.STRING, - allowNull: false, - }, - email: { - type: Sequelize.STRING, - allowNull: false, - validate: { - isEmail: true, - }, - }, - password: { - type: Sequelize.STRING, - allowNull: false, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }); - }, + async up(queryInterface, Sequelize) { + await queryInterface.createTable("users", { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER, + }, + name: { + type: Sequelize.STRING, + allowNull: false, + }, + username: { + type: Sequelize.STRING, + allowNull: false, + }, + email: { + type: Sequelize.STRING, + allowNull: false, + validate: { + isEmail: true, + }, + }, + password: { + type: Sequelize.STRING, + allowNull: false, + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + }); + }, - async down(queryInterface, Sequelize) { - await queryInterface.describeTable("users"); - }, + async down(queryInterface, Sequelize) { + await queryInterface.describeTable("users"); + }, }; diff --git a/src/sequelize/migrations/20240418083442-UserTests.js b/src/sequelize/migrations/20240418083442-UserTests.js index 871d9c4..a71e1c5 100644 --- a/src/sequelize/migrations/20240418083442-UserTests.js +++ b/src/sequelize/migrations/20240418083442-UserTests.js @@ -1,45 +1,45 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ /** @type {import('sequelize-cli').Migration} */ module.exports = { - async up(queryInterface, Sequelize) { - await queryInterface.createTable("usersTests", { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - name: { - type: Sequelize.STRING, - allowNull: false, - }, - username: { - type: Sequelize.STRING, - allowNull: false, - }, - email: { - type: Sequelize.STRING, - allowNull: false, - validate: { - isEmail: true, - }, - }, - password: { - type: Sequelize.STRING, - allowNull: false, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }); - }, + async up(queryInterface, Sequelize) { + await queryInterface.createTable("usersTests", { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER, + }, + name: { + type: Sequelize.STRING, + allowNull: false, + }, + username: { + type: Sequelize.STRING, + allowNull: false, + }, + email: { + type: Sequelize.STRING, + allowNull: false, + validate: { + isEmail: true, + }, + }, + password: { + type: Sequelize.STRING, + allowNull: false, + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + }); + }, - async down(queryInterface, Sequelize) { - await queryInterface.describeTable("usersTests"); - }, + async down(queryInterface, Sequelize) { + await queryInterface.describeTable("usersTests"); + }, }; diff --git a/src/sequelize/models/index.js b/src/sequelize/models/index.js index bfd6219..916a184 100644 --- a/src/sequelize/models/index.js +++ b/src/sequelize/models/index.js @@ -11,37 +11,24 @@ const db = {}; let sequelize; if (config.use_env_variable) { - sequelize = new Sequelize(process.env[config.use_env_variable], config); + sequelize = new Sequelize(process.env[config.use_env_variable], config); } else { - sequelize = new Sequelize( - config.database, - config.username, - config.password, - config, - ); + sequelize = new Sequelize(config.database, config.username, config.password, config); } fs.readdirSync(__dirname) - .filter((file) => { - return ( - file.indexOf(".") !== 0 && - file !== basename && - file.slice(-3) === ".js" && - file.indexOf(".test.js") === -1 - ); - }) - .forEach((file) => { - const model = require(path.join(__dirname, file))( - sequelize, - Sequelize.DataTypes, - ); - db[model.name] = model; - }); + .filter((file) => { + return file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".js" && file.indexOf(".test.js") === -1; + }) + .forEach((file) => { + const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes); + db[model.name] = model; + }); Object.keys(db).forEach((modelName) => { - if (db[modelName].associate) { - db[modelName].associate(db); - } + if (db[modelName].associate) { + db[modelName].associate(db); + } }); db.sequelize = sequelize; diff --git a/src/sequelize/models/users.ts b/src/sequelize/models/users.ts index fdc585f..b928163 100644 --- a/src/sequelize/models/users.ts +++ b/src/sequelize/models/users.ts @@ -2,62 +2,62 @@ import { Model, DataTypes } from "sequelize"; import sequelize from "../../config/dbConnection"; export interface UserAttributes { - id?: number; - name: string; - username: string; - email: string; - password: string; - createdAt?: Date; - updatedAt?: Date; + id?: number; + name: string; + username: string; + email: string; + password: string; + createdAt?: Date; + updatedAt?: Date; } class User extends Model implements UserAttributes { - id!: number | undefined; - name!: string; - username!: string; - email!: string; - password!: string; - createdAt!: Date | undefined; - updatedAt1: Date | undefined; + id!: number | undefined; + name!: string; + username!: string; + email!: string; + password!: string; + createdAt!: Date | undefined; + updatedAt1: Date | undefined; } User.init( - { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: DataTypes.NUMBER, - }, - name: { - type: DataTypes.STRING, - allowNull: false, - }, - username: { - type: DataTypes.STRING, - allowNull: false, - }, - email: { - allowNull: false, - type: DataTypes.STRING, - }, - password: { - allowNull: false, - type: DataTypes.STRING, - }, - createdAt: { - allowNull: false, - type: DataTypes.DATE, - }, - updatedAt: { - allowNull: false, - type: DataTypes.DATE, - }, - }, - { - sequelize: sequelize, - modelName: "users", - }, + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.NUMBER, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + }, + username: { + type: DataTypes.STRING, + allowNull: false, + }, + email: { + allowNull: false, + type: DataTypes.STRING, + }, + password: { + allowNull: false, + type: DataTypes.STRING, + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE, + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE, + }, + }, + { + sequelize: sequelize, + modelName: "users", + }, ); export default User; diff --git a/src/sequelize/models/usersTests.ts b/src/sequelize/models/usersTests.ts index fcfe567..a036b1c 100644 --- a/src/sequelize/models/usersTests.ts +++ b/src/sequelize/models/usersTests.ts @@ -2,62 +2,62 @@ import { Model, DataTypes } from "sequelize"; import sequelize from "../../config/dbConnection"; export interface UserAttributes { - id?: number; - name: string; - username: string; - email: string; - password: string; - createdAt?: Date; - updatedAt?: Date; + id?: number; + name: string; + username: string; + email: string; + password: string; + createdAt?: Date; + updatedAt?: Date; } class UserTest extends Model implements UserAttributes { - id!: number | undefined; - name!: string; - username!: string; - email!: string; - password!: string; - createdAt!: Date | undefined; - updatedAt!: Date | undefined; // Corrected typo here + id!: number | undefined; + name!: string; + username!: string; + email!: string; + password!: string; + createdAt!: Date | undefined; + updatedAt!: Date | undefined; // Corrected typo here } UserTest.init( - { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: DataTypes.NUMBER, - }, - name: { - type: DataTypes.STRING, - allowNull: false, - }, - username: { - type: DataTypes.STRING, - allowNull: false, - }, - email: { - allowNull: false, - type: DataTypes.STRING, - }, - password: { - allowNull: false, - type: DataTypes.STRING, - }, - createdAt: { - allowNull: false, - type: DataTypes.DATE, - }, - updatedAt: { - allowNull: false, - type: DataTypes.DATE, - }, - }, - { - sequelize: sequelize, - modelName: "usersTests", - }, + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.NUMBER, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + }, + username: { + type: DataTypes.STRING, + allowNull: false, + }, + email: { + allowNull: false, + type: DataTypes.STRING, + }, + password: { + allowNull: false, + type: DataTypes.STRING, + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE, + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE, + }, + }, + { + sequelize: sequelize, + modelName: "usersTests", + }, ); export default UserTest; diff --git a/src/sequelize/seeders/20240412144111-demo-user.js b/src/sequelize/seeders/20240412144111-demo-user.js index 2fdad7e..4043e7f 100644 --- a/src/sequelize/seeders/20240412144111-demo-user.js +++ b/src/sequelize/seeders/20240412144111-demo-user.js @@ -1,27 +1,27 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ /** @type {import('sequelize-cli').Migration} */ module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.bulkInsert("Users", [ - { - name: "Rukundo Soleil", - username: "soleil00", - email: "soleil@soleil00.com", - password: "soleil00", - createdAt: new Date(), - updatedAt: new Date(), - }, - { - name: "test user", - username: "yes", - email: "soleil@soleil0w.com", - password: "soleil0w0", - createdAt: new Date(), - updatedAt: new Date(), - }, - ]); - }, - down: (queryInterface, Sequelize) => { - return queryInterface.bulkDelete("Users", null, {}); - }, + up: (queryInterface, Sequelize) => { + return queryInterface.bulkInsert("Users", [ + { + name: "Rukundo Soleil", + username: "soleil00", + email: "soleil@soleil00.com", + password: "soleil00", + createdAt: new Date(), + updatedAt: new Date(), + }, + { + name: "test user", + username: "yes", + email: "soleil@soleil0w.com", + password: "soleil0w0", + createdAt: new Date(), + updatedAt: new Date(), + }, + ]); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.bulkDelete("Users", null, {}); + }, }; diff --git a/src/services/user.service.ts b/src/services/user.service.ts index f957347..59c9fed 100644 --- a/src/services/user.service.ts +++ b/src/services/user.service.ts @@ -3,60 +3,55 @@ import { hashedPassword } from "../utils/hashPassword"; import { Op } from "sequelize"; export const getAllUsers = async () => { - try { - const users = await User.findAll(); - if (users.length === 0) { - console.log("no user"); - } - return users; - } catch (error: any) { - console.log(error.message); - throw new Error(error.message); - } + try { + const users = await User.findAll(); + if (users.length === 0) { + console.log("no user"); + } + return users; + } catch (error: any) { + console.log(error.message); + throw new Error(error.message); + } }; export const loggedInUser = async (email: string) => { - try { - const user: any = await User.findOne({ - where: { email: email }, - }); - if (!user) { - return false; - } else { - return user; - } - } catch (err: any) { - throw new Error(err.message); - } + try { + const user: any = await User.findOne({ + where: { email: email }, + }); + if (!user) { + return false; + } else { + return user; + } + } catch (err: any) { + throw new Error(err.message); + } }; -export const createUserService = async ( - name: string, - email: string, - username: string, - password: string, -): Promise => { - const existingUser = await User.findOne({ - where: { - [Op.or]: [{ email }, { username }], - }, - }); - if (existingUser) { - return null; - } - const hashPassword = await hashedPassword(password); - const user = await User.create({ - name, - email, - username, - password: hashPassword, - }); - return user; +export const createUserService = async (name: string, email: string, username: string, password: string): Promise => { + const existingUser = await User.findOne({ + where: { + [Op.or]: [{ email }, { username }], + }, + }); + if (existingUser) { + return null; + } + const hashPassword = await hashedPassword(password); + const user = await User.create({ + name, + email, + username, + password: hashPassword, + }); + return user; }; export const getUserByEmail = async (email: string): Promise => { - const user = await User.findOne({ - where: { email }, - }); - return user; + const user = await User.findOne({ + where: { email }, + }); + return user; }; diff --git a/src/types.ts b/src/types.ts index ee1753d..3fdbbd9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,10 +1,10 @@ export interface IUser { - id?: number; - name: string; - username: string; - email: string; - password: string; - isMerchant?: boolean; - createdAt: Date; - updatedAt: Date; + id?: number; + name: string; + username: string; + email: string; + password: string; + isMerchant?: boolean; + createdAt: Date; + updatedAt: Date; } diff --git a/src/utils/env.ts b/src/utils/env.ts index d066db0..32b85e6 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -2,8 +2,8 @@ import dotenv from "dotenv"; dotenv.config(); export const env = { - port: process.env.PORT || 3000, - db_url: process.env.DB_CONNECTION as string, - test_db_url: process.env.TEST_DB as string, - jwt_secret: process.env.JWT_SECRET, + port: process.env.PORT || 3000, + db_url: process.env.DB_CONNECTION as string, + test_db_url: process.env.TEST_DB as string, + jwt_secret: process.env.JWT_SECRET, }; diff --git a/src/utils/hashPassword.ts b/src/utils/hashPassword.ts index 18729fd..f94d63a 100644 --- a/src/utils/hashPassword.ts +++ b/src/utils/hashPassword.ts @@ -1,6 +1,6 @@ import bcrypt from "bcryptjs"; export const hashedPassword = async (password: string) => { - const hashpass = await bcrypt.hash(password, 10); - return hashpass; + const hashpass = await bcrypt.hash(password, 10); + return hashpass; }; diff --git a/src/utils/jsonwebtoken.ts b/src/utils/jsonwebtoken.ts index 9c0178e..6bea145 100644 --- a/src/utils/jsonwebtoken.ts +++ b/src/utils/jsonwebtoken.ts @@ -3,13 +3,13 @@ import { env } from "../utils/env"; import { sign } from "jsonwebtoken"; export const generateToken = async (user: IUser) => { - const accessToken = sign( - { - email: user.email, - password: user.password, - }, - `${env.jwt_secret}`, - { expiresIn: "72h" }, - ); - return accessToken; + const accessToken = sign( + { + email: user.email, + password: user.password, + }, + `${env.jwt_secret}`, + { expiresIn: "72h" }, + ); + return accessToken; }; diff --git a/tsconfig.json b/tsconfig.json index 70e032a..1a28634 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,112 +1,109 @@ { - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ - "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + /* Language and Environment */ + "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Modules */ - "module": "commonjs" /* Specify what module code is generated. */, - // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - "typeRoots": [ - "node_modules/@types", - "./typings" - ] /* Specify multiple folders that act like './node_modules/@types'. */, - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + /* Modules */ + "module": "commonjs" /* Specify what module code is generated. */, + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + "typeRoots": ["node_modules/@types", "./typings"] /* Specify multiple folders that act like './node_modules/@types'. */, + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "./dist" /* Specify an output folder for all emitted files. */, - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist" /* Specify an output folder for all emitted files. */, + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, - /* Type Checking */ - "strict": true /* Enable all strict type-checking options. */, - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } } From f47f2843b58afdef0080a731d9ff37de6321b25f Mon Sep 17 00:00:00 2001 From: IRUMVAEmmanuel1 Date: Thu, 25 Apr 2024 23:43:16 +0200 Subject: [PATCH 4/4] ESLint Configuration format --- eslint.config.cjs | 52 ----------------------------------------------- eslint.config.js | 51 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 ++-- 3 files changed, 53 insertions(+), 54 deletions(-) delete mode 100644 eslint.config.cjs create mode 100644 eslint.config.js diff --git a/eslint.config.cjs b/eslint.config.cjs deleted file mode 100644 index 938e455..0000000 --- a/eslint.config.cjs +++ /dev/null @@ -1,52 +0,0 @@ -const eslint = require("@eslint/js"); -const tseslint = require("typescript-eslint"); - -module.exports = tseslint.config( - { - ignores: ["**/__test__", "**/*.json"], - }, - eslint.configs.recommended, - ...tseslint.configs.recommended, - { - languageOptions: { - parserOptions: { - project: true, - ecmaVersion: 2020, - }, - }, - }, - { - files: ["*.ts", "*.js"], - extends: ["eslint:recommended", "plugin:prettier/recommended"], - ...tseslint.configs.disableTypeChecked, - }, - { - files: ["*.test *.js"], - rules: { - "@typescript-eslint/no-unused-vars": 0, - "@typescript-eslint/no-unsafe-call": 0, - languageOptions: { - globals: { - it: "readonly", - describe: "readonly", - }, - }, - }, - }, - { - rules: { - semi: "error", - "@typescript-eslint/no-unused-vars": 2, - "@typescript-eslint/no-explicit-any": 0, - "@typescript-eslint/no-var-requires": 0, - "no-shadow": [2, { allow: ["req", "res", "err"] }], - "new-cap": 0, - "one-var-declaration-per-line": 0, - "consistent-return": 0, - "no-param-reassign": 0, - "comma-dangle": 0, - "no-undef": 0, - curly: ["error", "multi-line"], - }, - }, -); diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..1a7d6f8 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,51 @@ +const eslint = require("@eslint/js"); +const tseslint = require("typescript-eslint"); + +module.exports = tseslint.config( + { + ignores: ["**/__test__", "**/*.json"], + }, + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + languageOptions: { + parserOptions: { + project: true, + ecmaVersion: 2020, + }, + }, + }, + { + files: ["*.ts", "*.js"], + ...tseslint.configs.disableTypeChecked, + }, + { + files: ["*.test *.js"], + rules: { + "@typescript-eslint/no-unused-vars": 0, + "@typescript-eslint/no-unsafe-call": 0, + languageOptions: { + globals: { + it: "readonly", + describe: "readonly", + }, + }, + }, + }, + { + rules: { + semi: "error", + "@typescript-eslint/no-unused-vars": 2, + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/no-var-requires": 0, + "no-shadow": [2, { allow: ["req", "res", "err"] }], + "new-cap": 0, + "one-var-declaration-per-line": 0, + "consistent-return": 0, + "no-param-reassign": 0, + "comma-dangle": 0, + "no-undef": 0, + curly: ["error", "multi-line"], + }, + }, +); \ No newline at end of file diff --git a/package.json b/package.json index a2a12ee..adba14e 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "author": "atlp", "license": "MIT", "devDependencies": { - "@eslint/js": "^9.0.0", + "@eslint/js": "^9.1.1", "@types/bcryptjs": "^2.4.6", "@types/cors": "^2.8.17", "@types/dotenv": "^8.2.0", @@ -47,7 +47,7 @@ "@types/node": "^20.12.7", "@types/supertest": "^6.0.2", "@types/swagger-ui-express": "^4.1.6", - "@typescript-eslint/eslint-plugin": "^7.6.0", + "@typescript-eslint/eslint-plugin": "^7.7.1", "@typescript-eslint/parser": "^7.6.0", "eslint": "^8.57.0", "eslint-config-airbnb-base": "^15.0.0",