├── docs └── macro.jpg ├── server.js ├── .env.example ├── database └── prismaClient.js ├── prisma ├── migrations │ ├── migration_lock.toml │ ├── 20221019030538_add_default_boolean_checked │ │ └── migration.sql │ ├── 20220721041352_create_users_table │ │ └── migration.sql │ ├── 20220818033245_create_shopping_list_products_table │ │ └── migration.sql │ ├── 20220810030048_create_shopping_list_table │ │ └── migration.sql │ ├── 20220921160145_fix_purchase_id_column_name │ │ └── migration.sql │ ├── 20220818143654_create_table_shopping_list_products_correct_name_table │ │ └── migration.sql │ ├── 20220921042318_create_purchases_table │ │ └── migration.sql │ └── 20221019013931_added_checked_product_list_shopping │ │ └── migration.sql └── schema.prisma ├── app.js ├── src ├── auth │ ├── utils │ │ ├── decrypt.js │ │ └── generateToken.js │ ├── validators │ │ └── loginValidator.js │ └── controllers │ │ └── authController.js ├── shared │ ├── config │ │ └── config.js │ ├── auth │ │ └── validateToken.js │ └── middlewares │ │ ├── payloadValidatorMiddleware.js │ │ └── authMiddleware.js ├── users │ ├── utils │ │ └── encrypt.js │ ├── repository │ │ └── findUserByIdRepository.js │ ├── controllers │ │ ├── MeController.js │ │ └── CreateUserController.js │ └── validators │ │ └── createUserValidator.js ├── shoppingList │ ├── controllers │ │ ├── FindShoppingListByUserController.js │ │ ├── CreateShoppingListController.js │ │ ├── DeleteShoppingListController.js │ │ └── FindShoppingListByIdController.js │ ├── validators │ │ └── createShoppingListValidator.js │ └── repository │ │ └── shoppingListRepository.js ├── shoppingListProducts │ ├── validators │ │ ├── checkedProductsValidator.js │ │ └── createProductsValidator.js │ ├── repository │ │ └── productsRepository.js │ └── controllers │ │ └── ShoppingListProductsController.js ├── smartList │ ├── controllers │ │ └── SmartListController.js │ └── services │ │ └── createSmartListService.js ├── purchases │ ├── validators │ │ └── createPurchaseValidator.js │ ├── controllers │ │ └── PurchaseController.js │ └── repository │ │ └── purchaseRepository.js └── purchasedProducts │ ├── validators │ └── createPurchasedProductsValidator.js │ ├── controllers │ └── PurchasedProductsController.js │ └── repository │ └── purchasedProductsRepository.js ├── docker-compose.yml ├── package.json ├── LICENSE ├── README.md ├── .gitignore └── routes.js /docs/macro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicolasPereira/nodejs-api-tcc/HEAD/docs/macro.jpg -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | import { app } from "./app.js"; 2 | import { config } from "./src/shared/config/config.js"; 3 | app.listen(config.PORT); 4 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL="postgresql://root:password@0.0.0.0:5431/tcc_database?schema=public" 2 | APP_NAME = "TCC" 3 | SALT = 10 4 | SECRET = mysecret 5 | -------------------------------------------------------------------------------- /database/prismaClient.js: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client'; 2 | 3 | const prismaClient = new PrismaClient(); 4 | 5 | export { prismaClient }; -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /prisma/migrations/20221019030538_add_default_boolean_checked/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "shopping_lists_products" ALTER COLUMN "checked" SET DEFAULT false; 3 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { routes } from './routes.js'; 3 | 4 | const app = express(); 5 | 6 | app.use(express.json()); 7 | app.use(routes); 8 | 9 | export { app }; -------------------------------------------------------------------------------- /src/auth/utils/decrypt.js: -------------------------------------------------------------------------------- 1 | import bcrypt from 'bcrypt' 2 | export async function comparePassword(passwordRequest, passwordHash){ 3 | return await bcrypt.compare(passwordRequest, passwordHash); 4 | } 5 | -------------------------------------------------------------------------------- /src/shared/config/config.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | APP_NAME: process.env.APP_NAME || "TCC", 3 | SALT: process.env.SALT || 10, 4 | PORT: process.env.PORT || 5000, 5 | SECRET: process.env.SECRET || "mysecret", 6 | }; 7 | -------------------------------------------------------------------------------- /src/users/utils/encrypt.js: -------------------------------------------------------------------------------- 1 | import bcrypt from 'bcrypt' 2 | import { config } from '../../shared/config/config.js'; 3 | 4 | export async function encryptPassword(password){ 5 | const salt = await bcrypt.genSalt(Number(config.SALT)); 6 | return await bcrypt.hash(password, salt); 7 | } -------------------------------------------------------------------------------- /src/auth/utils/generateToken.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | import { config } from '../../shared/config/config.js'; 3 | 4 | export async function generateToken(user){ 5 | return await jwt.sign({ 6 | exp: Math.floor(Date.now() / 1000) + (60 * 60), 7 | user: user 8 | }, config.SECRET); 9 | } 10 | -------------------------------------------------------------------------------- /src/users/repository/findUserByIdRepository.js: -------------------------------------------------------------------------------- 1 | import { prismaClient } from "../../../database/prismaClient.js"; 2 | 3 | export async function findUserById(id) { 4 | return await prismaClient.user.findUnique({ 5 | where: { 6 | id, 7 | }, 8 | select: { 9 | id: true, 10 | email: true, 11 | name: true, 12 | }, 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | db: 5 | image: postgres:latest 6 | container_name: db 7 | restart: always 8 | volumes: 9 | - db-app:/var/lib/postgresql/data 10 | environment: 11 | POSTGRES_PASSWORD: password 12 | POSTGRES_USER: root 13 | POSTGRES_DB: tcc_database 14 | ports: 15 | - "5432:5432" 16 | 17 | volumes: 18 | db-app: -------------------------------------------------------------------------------- /src/shared/auth/validateToken.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | import { config } from '../../shared/config/config.js'; 3 | 4 | export function validateToken(token) { 5 | return jwt.verify(token,config.SECRET, function(err, decoded) { 6 | const errorMessage = { messageError: 'Token de autenticação não é válido' }; 7 | return (err) ? errorMessage : decoded; 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /prisma/migrations/20220721041352_create_users_table/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "users" ( 3 | "id" TEXT NOT NULL, 4 | "name" TEXT NOT NULL, 5 | "email" TEXT NOT NULL, 6 | "password" TEXT NOT NULL, 7 | "active_account" BOOLEAN NOT NULL DEFAULT true, 8 | 9 | CONSTRAINT "users_pkey" PRIMARY KEY ("id") 10 | ); 11 | 12 | -- CreateIndex 13 | CREATE UNIQUE INDEX "users_email_key" ON "users"("email"); 14 | -------------------------------------------------------------------------------- /src/shoppingList/controllers/FindShoppingListByUserController.js: -------------------------------------------------------------------------------- 1 | import { findShoppingListByUserId } from "../repository/shoppingListRepository.js"; 2 | import { StatusCodes } from "http-status-codes"; 3 | 4 | export class FindShoppingListByUserController { 5 | async handle(req, res) { 6 | const userId = req.userId; 7 | 8 | const shoppingList = await findShoppingListByUserId(userId); 9 | 10 | return res.status(StatusCodes.OK).json(shoppingList); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/shared/middlewares/payloadValidatorMiddleware.js: -------------------------------------------------------------------------------- 1 | import { validationResult } from "express-validator"; 2 | import { StatusCodes } from "http-status-codes"; 3 | const validatePayloadMiddleware = (req, res, next) => { 4 | const errors = validationResult(req); 5 | if (!errors.isEmpty()) { 6 | return res 7 | .status(StatusCodes.UNPROCESSABLE_ENTITY) 8 | .json({ errors: errors.array() }); 9 | } 10 | return next(); 11 | }; 12 | export { validatePayloadMiddleware }; 13 | -------------------------------------------------------------------------------- /src/shoppingListProducts/validators/checkedProductsValidator.js: -------------------------------------------------------------------------------- 1 | import { body } from "express-validator"; 2 | import { validatePayloadMiddleware } from "../../shared/middlewares/payloadValidatorMiddleware.js"; 3 | 4 | const checkedProductsValidator = [ 5 | body("checked").notEmpty().withMessage("O campo checked é obrigatório"), 6 | body("checked").isBoolean().withMessage("O campo checked deve ser booleano"), 7 | validatePayloadMiddleware, 8 | ]; 9 | 10 | export { checkedProductsValidator }; 11 | -------------------------------------------------------------------------------- /src/smartList/controllers/SmartListController.js: -------------------------------------------------------------------------------- 1 | import { createSmartList } from "../services/createSmartListService.js"; 2 | import { StatusCodes } from "http-status-codes"; 3 | 4 | export class SmartListControllers { 5 | async create(req, res) { 6 | const userId = req.userId; 7 | const { products } = req.body; 8 | 9 | const smartList = await createSmartList(userId, products); 10 | 11 | return res.status(StatusCodes.CREATED).json({ 12 | smartList, 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@prisma/client": "^4.1.0", 5 | "bcrypt": "^5.0.1", 6 | "dotenv": "^16.0.1", 7 | "express": "^4.18.1", 8 | "express-validator": "^6.14.2", 9 | "http-status-codes": "^2.2.0", 10 | "jsonwebtoken": "^8.5.1", 11 | "nodemon": "^2.0.19" 12 | }, 13 | "scripts": { 14 | "start": "node server.js", 15 | "dev": "nodemon server.js" 16 | }, 17 | "devDependencies": { 18 | "prisma": "^4.0.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/purchases/validators/createPurchaseValidator.js: -------------------------------------------------------------------------------- 1 | import { body } from "express-validator"; 2 | import { validatePayloadMiddleware } from "../../shared/middlewares/payloadValidatorMiddleware.js"; 3 | 4 | const createPurchaseValidator = [ 5 | body("title").notEmpty().withMessage("O campo título é obrigatório"), 6 | body("title") 7 | .isLength({ max: 255 }) 8 | .withMessage("O título deve ter no máximo 255 caracteres"), 9 | validatePayloadMiddleware, 10 | ]; 11 | 12 | export { createPurchaseValidator }; 13 | -------------------------------------------------------------------------------- /src/purchasedProducts/validators/createPurchasedProductsValidator.js: -------------------------------------------------------------------------------- 1 | import { body } from "express-validator"; 2 | import { validatePayloadMiddleware } from "../../shared/middlewares/payloadValidatorMiddleware.js"; 3 | 4 | const createPurchasedProductsValidator = [ 5 | body("products") 6 | .isArray({ min: 1 }) 7 | .withMessage( 8 | "É necessário passar uma lista de produtos com no minimo 1 produto" 9 | ), 10 | validatePayloadMiddleware, 11 | ]; 12 | 13 | export { createPurchasedProductsValidator }; 14 | -------------------------------------------------------------------------------- /src/shoppingListProducts/validators/createProductsValidator.js: -------------------------------------------------------------------------------- 1 | import { body } from "express-validator"; 2 | import { validatePayloadMiddleware } from "../../shared/middlewares/payloadValidatorMiddleware.js"; 3 | 4 | const createShoppingListProductsValidator = [ 5 | body("products") 6 | .isArray({ min: 1 }) 7 | .withMessage( 8 | "É necessário passar uma lista de produtos com no minimo 1 produto" 9 | ), 10 | validatePayloadMiddleware, 11 | ]; 12 | 13 | export { createShoppingListProductsValidator }; 14 | -------------------------------------------------------------------------------- /src/shoppingList/validators/createShoppingListValidator.js: -------------------------------------------------------------------------------- 1 | import { body } from "express-validator"; 2 | import { validatePayloadMiddleware } from "../../shared/middlewares/payloadValidatorMiddleware.js"; 3 | 4 | const createShoppingListValidator = [ 5 | body("title").notEmpty().withMessage("O campo título é obrigatório"), 6 | body("title") 7 | .isLength({ max: 255 }) 8 | .withMessage("O título deve ter no máximo 255 caracteres"), 9 | validatePayloadMiddleware, 10 | ]; 11 | 12 | export { createShoppingListValidator }; 13 | -------------------------------------------------------------------------------- /prisma/migrations/20220818033245_create_shopping_list_products_table/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "ShoppingListProduct" ( 3 | "id" SERIAL NOT NULL, 4 | "name" VARCHAR(50) NOT NULL, 5 | "shoppingListId" INTEGER NOT NULL, 6 | 7 | CONSTRAINT "ShoppingListProduct_pkey" PRIMARY KEY ("id") 8 | ); 9 | 10 | -- AddForeignKey 11 | ALTER TABLE "ShoppingListProduct" ADD CONSTRAINT "ShoppingListProduct_shoppingListId_fkey" FOREIGN KEY ("shoppingListId") REFERENCES "shopping_lists"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 12 | -------------------------------------------------------------------------------- /src/shoppingList/controllers/CreateShoppingListController.js: -------------------------------------------------------------------------------- 1 | import { createShoppingList } from "../repository/shoppingListRepository.js"; 2 | import { StatusCodes } from "http-status-codes"; 3 | 4 | export class CreateShoppingListController { 5 | async handle(req, res) { 6 | const userId = req.userId; 7 | const { title } = req.body; 8 | 9 | const shoppingList = await createShoppingList(title, userId); 10 | 11 | return res.status(StatusCodes.CREATED).json({ 12 | id: shoppingList.id, 13 | title: shoppingList.title, 14 | }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /prisma/migrations/20220810030048_create_shopping_list_table/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "shopping_lists" ( 3 | "id" SERIAL NOT NULL, 4 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 5 | "title" VARCHAR(255) NOT NULL, 6 | "finished" BOOLEAN NOT NULL DEFAULT false, 7 | "userId" TEXT NOT NULL, 8 | 9 | CONSTRAINT "shopping_lists_pkey" PRIMARY KEY ("id") 10 | ); 11 | 12 | -- AddForeignKey 13 | ALTER TABLE "shopping_lists" ADD CONSTRAINT "shopping_lists_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 14 | -------------------------------------------------------------------------------- /src/shoppingList/controllers/DeleteShoppingListController.js: -------------------------------------------------------------------------------- 1 | import { deleteShoppingList } from "../repository/shoppingListRepository.js"; 2 | import { StatusCodes } from "http-status-codes"; 3 | 4 | export class DeleteShoppingListController { 5 | async handle(req, res) { 6 | const idShoppingList = parseInt(req.params.idShoppingList); 7 | const result = await deleteShoppingList(idShoppingList); 8 | 9 | if ("errorCode" in result) { 10 | return res.status(StatusCodes.BAD_REQUEST).json(result); 11 | } 12 | 13 | return res.status(StatusCodes.NO_CONTENT).json({}); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/auth/validators/loginValidator.js: -------------------------------------------------------------------------------- 1 | import {body} from 'express-validator' 2 | import { validatePayloadMiddleware } from '../../shared/middlewares/payloadValidatorMiddleware.js' 3 | 4 | const loginValidator = 5 | [ 6 | body('email').notEmpty().withMessage('O campo email é obrigatório'), 7 | body('email').isEmail().withMessage('O campo email esta incorreto'), 8 | body('password').notEmpty().withMessage('O campo senha é obrigatório'), 9 | body('password').isLength({min: 5}).withMessage('A senha deve ter no minimo 5 caracteres'), 10 | validatePayloadMiddleware 11 | ]; 12 | 13 | export {loginValidator} 14 | -------------------------------------------------------------------------------- /src/users/controllers/MeController.js: -------------------------------------------------------------------------------- 1 | import { findUserById } from "../repository/findUserByIdRepository.js"; 2 | import { StatusCodes } from "http-status-codes"; 3 | 4 | export class MeController { 5 | async handle(req, res) { 6 | const user = await findUserById(req.userId); 7 | 8 | if (!user) { 9 | return res 10 | .status(StatusCodes.NOT_FOUND) 11 | .json({ 12 | error: true, 13 | message: "Usuário não encontrado", 14 | }); 15 | } 16 | 17 | return res 18 | .status(StatusCodes.OK) 19 | .json({ 20 | id: user.id, 21 | name: user.name, 22 | email: user.email, 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/purchasedProducts/controllers/PurchasedProductsController.js: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { insertProductsInPurchased } from "../repository/purchasedProductsRepository.js"; 3 | import { findPurchaseById } from "../../purchases/repository/purchaseRepository.js"; 4 | 5 | export class PurchasedProductsController { 6 | async create(req, res) { 7 | const idPurchase = parseInt(req.params.idPurchase); 8 | const { products } = req.body; 9 | await insertProductsInPurchased(products, idPurchase); 10 | 11 | const purchase = await findPurchaseById(idPurchase); 12 | 13 | return res.status(StatusCodes.CREATED).json({ 14 | purchase, 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/purchasedProducts/repository/purchasedProductsRepository.js: -------------------------------------------------------------------------------- 1 | import { prismaClient } from "../../../database/prismaClient.js"; 2 | 3 | async function insertProductsInPurchased(products, purchaseId) { 4 | await prismaClient.purchasedProducts.createMany({ 5 | data: products.map((product) => ({ 6 | name: product.name, 7 | purchaseId, 8 | })), 9 | }); 10 | } 11 | 12 | async function findProductsByPurchaseId(id) { 13 | const products = await prismaClient.purchasedProducts.findMany({ 14 | where: { 15 | purchaseId: id, 16 | }, 17 | select: { 18 | name: true, 19 | }, 20 | }); 21 | return products; 22 | } 23 | 24 | export { insertProductsInPurchased, findProductsByPurchaseId }; 25 | -------------------------------------------------------------------------------- /src/users/validators/createUserValidator.js: -------------------------------------------------------------------------------- 1 | import {body} from 'express-validator' 2 | import { validatePayloadMiddleware } from '../../shared/middlewares/payloadValidatorMiddleware.js' 3 | 4 | const createUserValidator = 5 | [ 6 | body('name').notEmpty().withMessage('O campo nome é obrigatório'), 7 | body('email').notEmpty().withMessage('O campo email é obrigatório'), 8 | body('email').isEmail().withMessage('O campo email esta incorreto'), 9 | body('password').notEmpty().withMessage('O campo senha é obrigatório'), 10 | body('password').isLength({min: 5}).withMessage('A senha deve ter no minimo 5 caracteres'), 11 | validatePayloadMiddleware 12 | ]; 13 | 14 | export {createUserValidator} 15 | -------------------------------------------------------------------------------- /src/shared/middlewares/authMiddleware.js: -------------------------------------------------------------------------------- 1 | import { validateToken } from "../auth/validateToken.js"; 2 | import { StatusCodes } from "http-status-codes"; 3 | 4 | const authMiddleware = (req, res, next) => { 5 | const token = req.headers["authorization"]; 6 | if (!token) { 7 | return res 8 | .status(StatusCodes.UNAUTHORIZED) 9 | .json({ error: true, message: "Token de autenticação não fornecido" }); 10 | } 11 | 12 | 13 | const result = validateToken(token); 14 | 15 | if (result.messageError) { 16 | return res 17 | .status(StatusCodes.UNAUTHORIZED) 18 | .json({ error: true, message: result.messageError }); 19 | } 20 | 21 | req.userId = result.user.id; 22 | req.userName = result.user.name; 23 | 24 | next(); 25 | }; 26 | 27 | export { authMiddleware }; 28 | -------------------------------------------------------------------------------- /prisma/migrations/20220921160145_fix_purchase_id_column_name/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `purchasesId` on the `purchased_products` table. All the data in the column will be lost. 5 | - Added the required column `purchaseId` to the `purchased_products` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- DropForeignKey 9 | ALTER TABLE "purchased_products" DROP CONSTRAINT "purchased_products_purchasesId_fkey"; 10 | 11 | -- AlterTable 12 | ALTER TABLE "purchased_products" DROP COLUMN "purchasesId", 13 | ADD COLUMN "purchaseId" INTEGER NOT NULL; 14 | 15 | -- AddForeignKey 16 | ALTER TABLE "purchased_products" ADD CONSTRAINT "purchased_products_purchaseId_fkey" FOREIGN KEY ("purchaseId") REFERENCES "purchases"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 17 | -------------------------------------------------------------------------------- /src/shoppingList/controllers/FindShoppingListByIdController.js: -------------------------------------------------------------------------------- 1 | import { findShoppingListById } from "../repository/shoppingListRepository.js"; 2 | import { StatusCodes } from "http-status-codes"; 3 | 4 | export class FindShoppingListByIdController { 5 | async handle(req, res) { 6 | const idShoppingList = parseInt(req.params.idShoppingList); 7 | 8 | if (isNaN(idShoppingList)) { 9 | return res.status(StatusCodes.NOT_FOUND).json({ 10 | error: true, 11 | message: "A lista procura não existe", 12 | }); 13 | } 14 | 15 | const shoppingList = await findShoppingListById(idShoppingList); 16 | 17 | if (shoppingList == null) { 18 | return res.status(StatusCodes.NOT_FOUND).json({ 19 | error: true, 20 | message: "A lista procura não existe", 21 | }); 22 | } 23 | 24 | return res.status(StatusCodes.OK).json({ 25 | shoppingList, 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /prisma/migrations/20220818143654_create_table_shopping_list_products_correct_name_table/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the `ShoppingListProduct` table. If the table is not empty, all the data it contains will be lost. 5 | 6 | */ 7 | -- DropForeignKey 8 | ALTER TABLE "ShoppingListProduct" DROP CONSTRAINT "ShoppingListProduct_shoppingListId_fkey"; 9 | 10 | -- DropTable 11 | DROP TABLE "ShoppingListProduct"; 12 | 13 | -- CreateTable 14 | CREATE TABLE "shopping_lists_products" ( 15 | "id" SERIAL NOT NULL, 16 | "name" VARCHAR(50) NOT NULL, 17 | "shoppingListId" INTEGER NOT NULL, 18 | 19 | CONSTRAINT "shopping_lists_products_pkey" PRIMARY KEY ("id") 20 | ); 21 | 22 | -- AddForeignKey 23 | ALTER TABLE "shopping_lists_products" ADD CONSTRAINT "shopping_lists_products_shoppingListId_fkey" FOREIGN KEY ("shoppingListId") REFERENCES "shopping_lists"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 24 | -------------------------------------------------------------------------------- /prisma/migrations/20220921042318_create_purchases_table/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "purchases" ( 3 | "id" SERIAL NOT NULL, 4 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 5 | "title" VARCHAR(255) NOT NULL, 6 | "userId" TEXT NOT NULL, 7 | 8 | CONSTRAINT "purchases_pkey" PRIMARY KEY ("id") 9 | ); 10 | 11 | -- CreateTable 12 | CREATE TABLE "purchased_products" ( 13 | "id" SERIAL NOT NULL, 14 | "name" VARCHAR(50) NOT NULL, 15 | "purchasesId" INTEGER NOT NULL, 16 | 17 | CONSTRAINT "purchased_products_pkey" PRIMARY KEY ("id") 18 | ); 19 | 20 | -- AddForeignKey 21 | ALTER TABLE "purchases" ADD CONSTRAINT "purchases_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 22 | 23 | -- AddForeignKey 24 | ALTER TABLE "purchased_products" ADD CONSTRAINT "purchased_products_purchasesId_fkey" FOREIGN KEY ("purchasesId") REFERENCES "purchases"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 25 | -------------------------------------------------------------------------------- /prisma/migrations/20221019013931_added_checked_product_list_shopping/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `checked` to the `shopping_lists_products` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- DropForeignKey 8 | ALTER TABLE "purchased_products" DROP CONSTRAINT "purchased_products_purchaseId_fkey"; 9 | 10 | -- DropForeignKey 11 | ALTER TABLE "shopping_lists_products" DROP CONSTRAINT "shopping_lists_products_shoppingListId_fkey"; 12 | 13 | -- AlterTable 14 | ALTER TABLE "shopping_lists_products" ADD COLUMN "checked" BOOLEAN NOT NULL; 15 | 16 | -- AddForeignKey 17 | ALTER TABLE "shopping_lists_products" ADD CONSTRAINT "shopping_lists_products_shoppingListId_fkey" FOREIGN KEY ("shoppingListId") REFERENCES "shopping_lists"("id") ON DELETE CASCADE ON UPDATE CASCADE; 18 | 19 | -- AddForeignKey 20 | ALTER TABLE "purchased_products" ADD CONSTRAINT "purchased_products_purchaseId_fkey" FOREIGN KEY ("purchaseId") REFERENCES "purchases"("id") ON DELETE CASCADE ON UPDATE CASCADE; 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Nicolas Pereira 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/users/controllers/CreateUserController.js: -------------------------------------------------------------------------------- 1 | import { Prisma } from "@prisma/client"; 2 | import { prismaClient } from "../../../database/prismaClient.js"; 3 | import { encryptPassword } from "../utils/encrypt.js"; 4 | import { StatusCodes } from "http-status-codes"; 5 | 6 | export class CreateUserController { 7 | async handle(request, response) { 8 | const { name, email, password } = request.body; 9 | const hashPassword = await encryptPassword(password); 10 | try { 11 | const user = await prismaClient.user.create({ 12 | data: { 13 | name, 14 | email, 15 | password: hashPassword, 16 | }, 17 | }); 18 | 19 | return response 20 | .status(StatusCodes.CREATED) 21 | .json({ 22 | id: user.id, 23 | name: user.name, 24 | }); 25 | } catch (e) { 26 | console.log(e); 27 | if (e instanceof Prisma.PrismaClientKnownRequestError) { 28 | return response.status(e.errorCode).json({ 29 | errorCode: e.errorCode, 30 | message: e.message, 31 | }); 32 | } 33 | return response.json({ e }); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/shoppingListProducts/repository/productsRepository.js: -------------------------------------------------------------------------------- 1 | import { prismaClient } from "../../../database/prismaClient.js"; 2 | import { Prisma } from "@prisma/client"; 3 | 4 | async function insertProductsInShoppingList(products, shoppingListId) { 5 | await prismaClient.shoppingListProducts.createMany({ 6 | data: products.map((product) => ({ 7 | name: product.name, 8 | shoppingListId, 9 | })), 10 | }); 11 | } 12 | 13 | async function deleteProductsInShoppingList(productId) { 14 | await prismaClient.shoppingListProducts.delete({ 15 | where: { 16 | id: productId, 17 | }, 18 | }); 19 | } 20 | 21 | async function updateProductsStatus(productId, status) { 22 | try { 23 | const product = await prismaClient.shoppingListProducts.update({ 24 | where: { 25 | id: productId, 26 | }, 27 | data: { 28 | checked: status, 29 | }, 30 | }); 31 | return product; 32 | } catch (e) { 33 | if (e instanceof Prisma.PrismaClientValidationError) { 34 | return { 35 | errorCode: e.errorCode, 36 | message: e.message, 37 | }; 38 | } 39 | } 40 | } 41 | 42 | export { 43 | insertProductsInShoppingList, 44 | deleteProductsInShoppingList, 45 | updateProductsStatus, 46 | }; 47 | -------------------------------------------------------------------------------- /src/auth/controllers/authController.js: -------------------------------------------------------------------------------- 1 | import { prismaClient } from "../../../database/prismaClient.js"; 2 | import { comparePassword } from "../utils/decrypt.js"; 3 | import { generateToken } from "../utils/generateToken.js"; 4 | import { StatusCodes } from "http-status-codes"; 5 | 6 | export class AuthController { 7 | async authenticate(request, response) { 8 | const { email, password } = request.body; 9 | const user = await prismaClient.user.findUnique({ 10 | where: { 11 | email: email, 12 | }, 13 | }); 14 | 15 | if (!user) { 16 | return response 17 | .status(StatusCodes.UNAUTHORIZED) 18 | .json({ message: "Usuário ou senha incorreta!" }); 19 | } 20 | 21 | if (!user.active_account) { 22 | return response 23 | .status(StatusCodes.FORBIDDEN) 24 | .json({ message: "Usuário desativado" }); 25 | } 26 | 27 | const validatePassword = await comparePassword(password, user.password); 28 | 29 | if (!validatePassword) { 30 | return response 31 | .status(StatusCodes.UNAUTHORIZED) 32 | .json({ message: "Usuário ou senha incorreta!" }); 33 | } 34 | 35 | delete user.password; 36 | 37 | const token = await generateToken(user); 38 | 39 | return response.status(StatusCodes.OK).json({ 40 | user, 41 | token, 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NodeJS API 2 | API feita em NodeJS para cadastrar e gerenciar lista de compras no fluxo de um trabalho de conclusão de curso! 3 | 4 | Arquitetura Macro do Projeto: 5 | 6 | 7 | 8 | O Projeto de Conclusão de Curso tem como ideia facilitar a criação de lista de compras através do reconhecimento de imagem, dessa forma o usuário scanneia sua compra e armazenamos todos os itens de sua compra, na sua próxima compra o usuário deve apenas scannear os produtos que restaram, deste modo a aplicação deve gerar a lista de compras baseada nos itens da última compra e nos itens que o usuário já possui. 9 | 10 | 11 | Está API tem a responsabilidade de se comunicar com a aplicação mobile, realizar a validação de regra de negócio e persistir os dados no banco de dados. 12 | 13 | 14 | 15 | ## Tecnologias 16 | 17 | 1 - NodeJS 18 | 2 - Express 19 | 3 - Prisma (ORM) 20 | 4 - PostgreeSQL 21 | 5 - Docker 22 | 23 | ## Como Rodar o projeto 24 | 25 | 1 - Clone este repositório 26 | ```shell 27 | git clone git@github.com:NicolasPereira/nodejs-api-tcc.git 28 | ``` 29 | 30 | 2 - Build a imagem 31 | ```shell 32 | docker-compose build image -d 33 | ``` 34 | 35 | 3 - O serviço estara rodando na porta `3000` 36 | 37 | 4 - Para parar a aplicação 38 | 39 | ```shell 40 | docker-compose down 41 | ``` 42 | 43 | 5 - Para iniciar a aplicação após a imagem já estar buildada 44 | ```shell 45 | docker-compose up 46 | ``` -------------------------------------------------------------------------------- /src/shoppingListProducts/controllers/ShoppingListProductsController.js: -------------------------------------------------------------------------------- 1 | import { 2 | insertProductsInShoppingList, 3 | deleteProductsInShoppingList, 4 | updateProductsStatus, 5 | } from "../repository/productsRepository.js"; 6 | import { findShoppingListById } from "../../shoppingList/repository/shoppingListRepository.js"; 7 | import { StatusCodes } from "http-status-codes"; 8 | 9 | export class ShoppingListProductsController { 10 | async create(req, res) { 11 | const idShoppingList = parseInt(req.params.idShoppingList); 12 | const { products } = req.body; 13 | 14 | await insertProductsInShoppingList(products, idShoppingList); 15 | 16 | const shoppingList = await findShoppingListById(idShoppingList); 17 | 18 | return res.status(StatusCodes.CREATED).json({ 19 | shoppingList, 20 | }); 21 | } 22 | 23 | async deleteProduct(req, res) { 24 | const idShoppingList = parseInt(req.params.idShoppingList); 25 | const idProduct = parseInt(req.params.idProduct); 26 | await deleteProductsInShoppingList(idProduct); 27 | 28 | const shoppingList = await findShoppingListById(idShoppingList); 29 | 30 | return res.status(StatusCodes.OK).json({ 31 | shoppingList, 32 | }); 33 | } 34 | 35 | async updateCheckedProduct(req, res) { 36 | const idProduct = parseInt(req.params.idProduct); 37 | const { checked } = req.body; 38 | const result = await updateProductsStatus(idProduct, checked); 39 | if ("errorCode" in result) { 40 | return res.status(StatusCodes.BAD_REQUEST).json(result); 41 | } 42 | 43 | return res.status(StatusCodes.OK).json({ 44 | message: `${result.name} foi atualizado`, 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/shoppingList/repository/shoppingListRepository.js: -------------------------------------------------------------------------------- 1 | import { prismaClient } from "../../../database/prismaClient.js"; 2 | import { Prisma } from "@prisma/client"; 3 | async function createShoppingList(title, userId) { 4 | return prismaClient.shoppingList.create({ 5 | data: { 6 | title, 7 | userId, 8 | }, 9 | }); 10 | } 11 | 12 | async function findShoppingListById(id) { 13 | return prismaClient.shoppingList.findUnique({ 14 | where: { 15 | id: id, 16 | }, 17 | select: { 18 | id: true, 19 | title: true, 20 | ShoppingListProducts: { 21 | select: { 22 | id: true, 23 | name: true, 24 | checked: true, 25 | }, 26 | orderBy: { 27 | id: "asc", 28 | }, 29 | }, 30 | }, 31 | }); 32 | } 33 | 34 | async function findShoppingListByUserId(userId) { 35 | return prismaClient.shoppingList.findMany({ 36 | orderBy: [ 37 | { 38 | id: "desc", 39 | }, 40 | ], 41 | where: { 42 | userId: userId, 43 | }, 44 | select: { 45 | id: true, 46 | title: true, 47 | createdAt: true, 48 | }, 49 | }); 50 | } 51 | 52 | async function deleteShoppingList(id) { 53 | try { 54 | const deletedList = await prismaClient.shoppingList.delete({ 55 | where: { 56 | id: id, 57 | }, 58 | }); 59 | return deletedList; 60 | } catch (e) { 61 | if (e instanceof Prisma.PrismaClientKnownRequestError) { 62 | return { 63 | errorCode: e.errorCode, 64 | message: e.message, 65 | }; 66 | } 67 | } 68 | } 69 | 70 | export { 71 | createShoppingList, 72 | findShoppingListById, 73 | findShoppingListByUserId, 74 | deleteShoppingList, 75 | }; 76 | -------------------------------------------------------------------------------- /src/purchases/controllers/PurchaseController.js: -------------------------------------------------------------------------------- 1 | import { 2 | createPurchase, 3 | findPurchasesByUserId, 4 | findPurchaseById, 5 | deletePurchase, 6 | } from "../repository/purchaseRepository.js"; 7 | import { StatusCodes } from "http-status-codes"; 8 | 9 | export class PurchaseController { 10 | async create(req, res) { 11 | const userId = req.userId; 12 | const { title } = req.body; 13 | 14 | const purchase = await createPurchase(title, userId); 15 | 16 | return res.status(StatusCodes.CREATED).json({ 17 | id: purchase.id, 18 | title: purchase.title, 19 | }); 20 | } 21 | 22 | async findUserPurchases(req, res) { 23 | const userId = req.userId; 24 | 25 | const purchases = await findPurchasesByUserId(userId); 26 | 27 | return res.status(StatusCodes.OK).json(purchases); 28 | } 29 | 30 | async findPurchaseById(req, res) { 31 | const idPurchase = parseInt(req.params.idPurchase); 32 | 33 | if (isNaN(idPurchase)) { 34 | return res.status(StatusCodes.NOT_FOUND).json({ 35 | error: true, 36 | message: "A compra procurada não existe", 37 | }); 38 | } 39 | 40 | const purchase = await findPurchaseById(idPurchase); 41 | 42 | if (purchase == null) { 43 | return res.status(StatusCodes.NOT_FOUND).json({ 44 | error: true, 45 | message: "A compra procurada não existe", 46 | }); 47 | } 48 | 49 | return res.status(StatusCodes.OK).json(purchase); 50 | } 51 | 52 | async deletePurchase(req, res) { 53 | const idPurchase = parseInt(req.params.idPurchase); 54 | const result = await deletePurchase(idPurchase); 55 | 56 | if ("errorCode" in result) { 57 | return res.status(StatusCodes.BAD_REQUEST).json(result); 58 | } 59 | 60 | return res.status(StatusCodes.NO_CONTENT).json({}); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/smartList/services/createSmartListService.js: -------------------------------------------------------------------------------- 1 | import { 2 | createShoppingList, 3 | findShoppingListById, 4 | } from "../../shoppingList/repository/shoppingListRepository.js"; 5 | import { findLastPurchaseByUser } from "../../purchases/repository/purchaseRepository.js"; 6 | import { findProductsByPurchaseId } from "../../purchasedProducts/repository/purchasedProductsRepository.js"; 7 | import { insertProductsInShoppingList } from "../../shoppingListProducts/repository/productsRepository.js"; 8 | 9 | async function createSmartList(userId, products) { 10 | const title = "Nova Lista Inteligente"; 11 | const shoppingList = await createShoppingList(title, userId); 12 | const purchasesProducts = await findLastPurchasesProducts(userId); 13 | const productsSmartListGenerated = await generateSmartShoppingListProducts( 14 | products, 15 | purchasesProducts 16 | ); 17 | const productsForSmartList = await insertProductsInShoppingList( 18 | productsSmartListGenerated, 19 | shoppingList.id 20 | ); 21 | 22 | const shoppingListUpdated = await findShoppingListById(shoppingList.id); 23 | return shoppingListUpdated; 24 | } 25 | 26 | async function findLastPurchasesProducts(userId) { 27 | const lastPurchase = await findLastPurchaseByUser(userId); 28 | const productsLastPuchase = await findProductsByPurchaseId(lastPurchase.id); 29 | return productsLastPuchase; 30 | } 31 | 32 | async function generateSmartShoppingListProducts(products, purchasesProducts) { 33 | let smartProducts = []; 34 | 35 | for (const product of products) { 36 | for (const productPurchase of purchasesProducts) { 37 | if (product.name != productPurchase.name) { 38 | let productSmart = { name: productPurchase.name }; 39 | smartProducts.push(productSmart); 40 | } 41 | } 42 | } 43 | return smartProducts; 44 | } 45 | 46 | export { createSmartList }; 47 | -------------------------------------------------------------------------------- /src/purchases/repository/purchaseRepository.js: -------------------------------------------------------------------------------- 1 | import { prismaClient } from "../../../database/prismaClient.js"; 2 | import { Prisma } from "@prisma/client"; 3 | async function createPurchase(title, userId) { 4 | return await prismaClient.purchases.create({ 5 | data: { 6 | title, 7 | userId, 8 | }, 9 | }); 10 | } 11 | 12 | async function findPurchasesByUserId(userId) { 13 | return prismaClient.purchases.findMany({ 14 | orderBy: [ 15 | { 16 | id: "desc", 17 | }, 18 | ], 19 | where: { 20 | userId: userId, 21 | }, 22 | select: { 23 | id: true, 24 | title: true, 25 | createdAt: true, 26 | }, 27 | }); 28 | } 29 | 30 | async function findPurchaseById(id) { 31 | return prismaClient.purchases.findUnique({ 32 | where: { 33 | id: id, 34 | }, 35 | select: { 36 | id: true, 37 | title: true, 38 | createdAt: true, 39 | PurchasedProducts: { 40 | select: { 41 | id: true, 42 | name: true, 43 | }, 44 | }, 45 | }, 46 | }); 47 | } 48 | 49 | async function findLastPurchaseByUser(userId) { 50 | return prismaClient.purchases.findFirst({ 51 | where: { 52 | userId: userId 53 | }, 54 | orderBy: { 55 | id: 'asc', 56 | }, 57 | take: -1, 58 | }) 59 | } 60 | 61 | async function deletePurchase(id) { 62 | try { 63 | const deletedList = await prismaClient.purchases.delete({ 64 | where: { 65 | id: id, 66 | }, 67 | }); 68 | return deletedList; 69 | } catch (e) { 70 | if (e instanceof Prisma.PrismaClientKnownRequestError) { 71 | return { 72 | errorCode: e.errorCode, 73 | message: e.message, 74 | }; 75 | } 76 | } 77 | } 78 | export { 79 | createPurchase, 80 | findPurchasesByUserId, 81 | findPurchaseById, 82 | deletePurchase, 83 | findLastPurchaseByUser 84 | }; 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | binaryTargets = ["native", "linux-musl"] 4 | } 5 | 6 | datasource db { 7 | provider = "postgresql" 8 | url = env("DATABASE_URL") 9 | } 10 | 11 | model User { 12 | id String @id @default(uuid()) 13 | name String 14 | email String @unique 15 | password String 16 | active_account Boolean @default(true) 17 | shoppingLists ShoppingList[] 18 | purchases Purchases[] 19 | 20 | @@map("users") 21 | } 22 | 23 | model ShoppingList { 24 | id Int @id @default(autoincrement()) 25 | createdAt DateTime @default(now()) 26 | title String @db.VarChar(255) 27 | finished Boolean @default(false) 28 | user User @relation(fields: [userId], references: [id]) 29 | userId String 30 | ShoppingListProducts ShoppingListProducts[] 31 | 32 | @@map("shopping_lists") 33 | } 34 | 35 | model ShoppingListProducts { 36 | id Int @id @default(autoincrement()) 37 | name String @db.VarChar(50) 38 | shoppingList ShoppingList @relation(fields: [shoppingListId], references: [id], onDelete: Cascade) 39 | shoppingListId Int 40 | checked Boolean @default(false) 41 | 42 | @@map("shopping_lists_products") 43 | } 44 | 45 | model Purchases { 46 | id Int @id @default(autoincrement()) 47 | createdAt DateTime @default(now()) 48 | title String @db.VarChar(255) 49 | user User @relation(fields: [userId], references: [id]) 50 | userId String 51 | PurchasedProducts PurchasedProducts[] 52 | 53 | @@map("purchases") 54 | } 55 | 56 | model PurchasedProducts { 57 | id Int @id @default(autoincrement()) 58 | name String @db.VarChar(50) 59 | Purchases Purchases @relation(fields: [purchaseId], references: [id], onDelete: Cascade) 60 | purchaseId Int 61 | 62 | @@map("purchased_products") 63 | } 64 | -------------------------------------------------------------------------------- /routes.js: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | import { prismaClient } from "./database/prismaClient.js"; 3 | import express from "express"; 4 | import { AuthController } from "./src/auth/controllers/authController.js"; 5 | import { CreateUserController } from "./src/users/controllers/CreateUserController.js"; 6 | import { MeController } from "./src/users/controllers/MeController.js"; 7 | import { CreateShoppingListController } from "./src/shoppingList/controllers/CreateShoppingListController.js"; 8 | import { createUserValidator } from "./src/users/validators/createUserValidator.js"; 9 | import { createShoppingListValidator } from "./src/shoppingList/validators/createShoppingListValidator.js"; 10 | import { loginValidator } from "./src/auth/validators/loginValidator.js"; 11 | import { authMiddleware } from "./src/shared/middlewares/authMiddleware.js"; 12 | import { createShoppingListProductsValidator } from "./src/shoppingListProducts/validators/createProductsValidator.js"; 13 | import { config } from "./src/shared/config/config.js"; 14 | import { ShoppingListProductsController } from "./src/shoppingListProducts/controllers/ShoppingListProductsController.js"; 15 | import { FindShoppingListByUserController } from "./src/shoppingList/controllers/FindShoppingListByUserController.js"; 16 | import { FindShoppingListByIdController } from "./src/shoppingList/controllers/FindShoppingListByIdController.js"; 17 | import { PurchaseController } from "./src/purchases/controllers/PurchaseController.js"; 18 | import { createPurchaseValidator } from "./src/purchases/validators/createPurchaseValidator.js"; 19 | import { PurchasedProductsController } from "./src/purchasedProducts/controllers/PurchasedProductsController.js"; 20 | import { createPurchasedProductsValidator } from "./src/purchasedProducts/validators/createPurchasedProductsValidator.js"; 21 | import { DeleteShoppingListController } from "./src/shoppingList/controllers/DeleteShoppingListController.js"; 22 | import { checkedProductsValidator } from "./src/shoppingListProducts/validators/checkedProductsValidator.js"; 23 | import { SmartListControllers } from "./src/smartList/controllers/SmartListController.js"; 24 | 25 | const routes = express.Router(); 26 | 27 | const createUser = new CreateUserController(); 28 | const authUserController = new AuthController(); 29 | const meController = new MeController(); 30 | const createShoppingList = new CreateShoppingListController(); 31 | const shoppingListProductsController = new ShoppingListProductsController(); 32 | const findShoppingListByUser = new FindShoppingListByUserController(); 33 | const findShoppingListById = new FindShoppingListByIdController(); 34 | const purchaseController = new PurchaseController(); 35 | const purchasedProductsController = new PurchasedProductsController(); 36 | const deleteShoppingListController = new DeleteShoppingListController(); 37 | const smartListController = new SmartListControllers(); 38 | 39 | routes.post("/", authMiddleware, (req, res) => { 40 | const applicationName = config.APP_NAME; 41 | 42 | res.json({ 43 | message: `Hi Node and Docker!!! ${applicationName} - ${req.userName}`, 44 | }); 45 | }); 46 | 47 | routes.post("/users", createUserValidator, createUser.handle); 48 | 49 | routes.get("/users/me", authMiddleware, meController.handle); 50 | 51 | routes.get( 52 | "/users/me/shopping-lists", 53 | authMiddleware, 54 | findShoppingListByUser.handle 55 | ); 56 | 57 | routes.post("/login", loginValidator, authUserController.authenticate); 58 | 59 | routes.post( 60 | "/shopping-lists", 61 | createShoppingListValidator, 62 | authMiddleware, 63 | createShoppingList.handle 64 | ); 65 | 66 | routes.get( 67 | "/shopping-lists/:idShoppingList", 68 | authMiddleware, 69 | findShoppingListById.handle 70 | ); 71 | 72 | routes.post( 73 | "/shopping-lists/:idShoppingList/products", 74 | authMiddleware, 75 | createShoppingListProductsValidator, 76 | shoppingListProductsController.create 77 | ); 78 | 79 | routes.patch( 80 | "/shopping-lists/:idShoppingList/products/:idProduct", 81 | authMiddleware, 82 | checkedProductsValidator, 83 | shoppingListProductsController.updateCheckedProduct 84 | ); 85 | 86 | routes.delete( 87 | "/shopping-lists/:idShoppingList/products/:idProduct", 88 | authMiddleware, 89 | shoppingListProductsController.deleteProduct 90 | ); 91 | 92 | routes.delete( 93 | "/shopping-lists/:idShoppingList", 94 | authMiddleware, 95 | deleteShoppingListController.handle 96 | ); 97 | 98 | routes.post( 99 | "/purchases", 100 | createPurchaseValidator, 101 | authMiddleware, 102 | purchaseController.create 103 | ); 104 | 105 | routes.get( 106 | "/users/me/purchases", 107 | authMiddleware, 108 | purchaseController.findUserPurchases 109 | ); 110 | 111 | routes.get( 112 | "/purchases/:idPurchase", 113 | authMiddleware, 114 | purchaseController.findPurchaseById 115 | ); 116 | 117 | routes.delete( 118 | "/purchases/:idPurchase/", 119 | authMiddleware, 120 | purchaseController.deletePurchase 121 | ); 122 | 123 | routes.post( 124 | "/purchases/:idPurchase/products", 125 | authMiddleware, 126 | createPurchasedProductsValidator, 127 | purchasedProductsController.create 128 | ); 129 | 130 | routes.post( 131 | "/smart-list", 132 | authMiddleware, 133 | createShoppingListProductsValidator, 134 | smartListController.create 135 | ); 136 | 137 | routes.get("/health", (req, res) => { 138 | const dbStatus = prismaClient.$queryRaw`SELECT 1`; 139 | 140 | res.status(200).json({ 141 | app: "ok", 142 | db: dbStatus, 143 | }); 144 | }); 145 | 146 | export { routes }; 147 | --------------------------------------------------------------------------------