Skip to content

Commit

Permalink
feat: add auth and policies User/Admin
Browse files Browse the repository at this point in the history
  • Loading branch information
fabiobrasileiroo committed Sep 15, 2024
1 parent 62a1b6e commit a5560fa
Show file tree
Hide file tree
Showing 23 changed files with 1,149 additions and 94 deletions.
726 changes: 722 additions & 4 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,23 @@
"lint": "npx @biomejs/biome lint --write ./",
"lint:check": "npx @biomejs/biome check --write ./"
},
"keywords": [
"api",
"api gateway"
],
"keywords": ["api", "api gateway"],
"author": "fabio",
"license": "MIT",
"dependencies": {
"@biomejs/biome": "1.9.0",
"@prisma/client": "^5.19.1",
"bcrypt": "5.1.1",
"dotenv": "^16.4.5",
"express": "^4.21.0",
"jsonwebtoken": "9.0.2",
"mercadopago": "^2.0.15",
"zod": "3.23.8"
},
"devDependencies": {
"@types/bcrypt": "5.0.2",
"@types/express": "4.17.21",
"@types/jsonwebtoken": "9.0.6",
"@types/node": "22.4.0",
"prisma": "^5.19.1",
"tsup": "8.2.4",
Expand Down
43 changes: 43 additions & 0 deletions prisma/migrations/20240915175636_admin_users/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
Warnings:
- Added the required column `companyId` to the `Product` table without a default value. This is not possible if the table is not empty.
*/
-- CreateEnum
CREATE TYPE "UserRole" AS ENUM ('USER', 'ADMIN');

-- AlterTable
ALTER TABLE "Product" ADD COLUMN "companyId" INTEGER NOT NULL;

-- CreateTable
CREATE TABLE "Company" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,

CONSTRAINT "Company_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "Unit" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"companyId" INTEGER NOT NULL,

CONSTRAINT "Unit_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "User" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"role" "UserRole" NOT NULL,

CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "Product" ADD CONSTRAINT "Product_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Unit" ADD CONSTRAINT "Unit_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
14 changes: 14 additions & 0 deletions prisma/migrations/20240915181843_add_unique_email/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
Warnings:
- A unique constraint covering the columns `[email]` on the table `User` will be added. If there are existing duplicate values, this will fail.
- Added the required column `email` to the `User` table without a default value. This is not possible if the table is not empty.
- Added the required column `password` to the `User` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "User" ADD COLUMN "email" TEXT NOT NULL,
ADD COLUMN "password" TEXT NOT NULL;

-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
31 changes: 30 additions & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,37 @@ generator client {
}

model Product {
id Int @id @default(autoincrement())
id Int @id @default(autoincrement())
name String
price Float
description String?
companyId Int
company Company @relation(fields: [companyId], references: [id])
}

model Company {
id Int @id @default(autoincrement())
name String
units Unit[]
products Product[]
}

model Unit {
id Int @id @default(autoincrement())
name String
company Company @relation(fields: [companyId], references: [id])
companyId Int
}

model User {
id Int @id @default(autoincrement())
email String @unique
name String
password String
role UserRole
}

enum UserRole {
USER
ADMIN
}
6 changes: 3 additions & 3 deletions src/@types/PaymentData.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export interface PaymentData {
// Defina a estrutura esperada para o paymentData aqui
amount: number;
description: string;
amount: number
description: string
// Adicione outros campos conforme necessário
}
}
13 changes: 13 additions & 0 deletions src/@types/express.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// src/types/express.d.ts
import type { UserRole } from '@prisma/client'

declare global {
namespace Express {
interface Request {
user?: {
role: UserRole
// Adicione outras propriedades do usuário, se necessário
}
}
}
}
8 changes: 4 additions & 4 deletions src/@types/productTypes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export interface Product {
id?: number;
name: string;
price: number;
id?: number
name: string
price: number
// Adicione outros campos conforme necessário
}
}
5 changes: 3 additions & 2 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import express from 'express'
import productRoutes from './routes/productRoutes.js'
import authRoutes from './routes/authRoutes.js'
import paymentRoutes from './routes/paymentRoutes.js'
import productRoutes from './routes/productRoutes.js'

const app = express()

Expand All @@ -9,6 +10,6 @@ app.use(express.json())
// Defina as rotas para produtos e pagamentos
app.use('/api/products', productRoutes)
app.use('/api/payments', paymentRoutes)
app.use('/api', authRoutes) // Adicione '/api' como prefixo para as rotas de autenticação

export default app

7 changes: 4 additions & 3 deletions src/config/mercadoPagoConfig.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { MercadoPagoConfig } from 'mercadopago';
import 'dotenv/config'
import { MercadoPagoConfig } from 'mercadopago'

const mercadoPagoConfig = new MercadoPagoConfig({
accessToken: process.env.accessToken || '',
options: {
timeout: 5000,
},
});
})

export default mercadoPagoConfig;
export default mercadoPagoConfig
77 changes: 77 additions & 0 deletions src/controllers/authController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { PrismaClient, User } from '@prisma/client'
import bcrypt from 'bcrypt'
import type { Request, Response } from 'express'
import jwt from 'jsonwebtoken'

const prisma = new PrismaClient()
const saltRounds = 10

export const register = async (req: Request, res: Response) => {
const { email, password, name, role } = req.body

try {
// Verificar se o usuário já existe
const existingUser = await prisma.user.findUnique({
where: { email },
})

if (existingUser) {
return res.status(400).json({ message: 'User already exists' })
}

// Criptografar a senha
const hashedPassword = await bcrypt.hash(password, saltRounds)

// Criar um novo usuário
const newUser = await prisma.user.create({
data: {
email,
password: hashedPassword,
name,
role, // Supondo que role é uma propriedade opcional
},
})

res
.status(201)
.json({ message: 'User created successfully', user: newUser })
} catch (error) {
console.error(error)
res.status(500).json({ message: 'Server error' })
}
}

export const login = async (req: Request, res: Response) => {
const { email, password } = req.body

try {
// Encontrar o usuário pelo email
const user = await prisma.user.findUnique({
where: { email },
})

if (!user) {
return res.status(401).json({ message: 'Invalid credentials' })
}

// Verificar a senha
const isPasswordValid = await bcrypt.compare(password, user.password)

if (!isPasswordValid) {
return res.status(401).json({ message: 'Invalid credentials' })
}

const horaParaDias: string = `${24 * 356}h`
// Gerar token JWT
const token = jwt.sign(
{ id: user.id, email: user.email, role: user.role },
process.env.JWT_SECRET || 'your_jwt_secret',
{ expiresIn: horaParaDias } // 8544h == 356 dias
)

res.status(200).json({ token })
} catch (error) {
console.error(error)
res.status(500).json({ message: 'Server error' })
}
}
17 changes: 9 additions & 8 deletions src/controllers/paymentController.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import * as paymentService from '../services/paymentService';
import type { Request, Response } from 'express';
import type { PaymentData } from '../@types/PaymentData';
import type { PaymentData } from '@/@types/PaymentData'
import type { Request, Response } from 'express'
import * as paymentService from '../services/paymentService'

export const createPayment = async (req: Request, res: Response) => {
try {
const paymentData: PaymentData = req.body;
const paymentResponse = await paymentService.createPayment(paymentData);
res.status(201).json(paymentResponse);
const paymentData: PaymentData = req.body
console.log('🚀 ~ createPayment ~ paymentData:', paymentData)
const paymentResponse = await paymentService.createPayment(paymentData)
res.status(201).json(paymentResponse)
} catch (error) {
res.status(500).json({ message: (error as Error).message });
res.status(400).json({ message: (error as Error).message })
}
};
}
59 changes: 47 additions & 12 deletions src/controllers/productController.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,57 @@
import * as productService from '../services/productService';
import type { Request, Response } from 'express';
import type { Product } from '../@types/productTypes';
import type { Product } from '@/@types/productTypes'
import type { Request, Response } from 'express'
import * as productService from '../services/productService'

export const createProduct = async (req: Request, res: Response) => {
try {
const product: Product = req.body;
const createdProduct = await productService.createProduct(product);
res.status(201).json(createdProduct);
const product: Product | any = req.body
const createdProduct = await productService.createProduct(product)
res.status(201).json(createdProduct)
} catch (error) {
res.status(500).json({ message: (error as Error).message });
res.status(500).json({ message: (error as Error).message })
}
};
}

export const getProducts = async (req: Request, res: Response) => {
try {
const products = await productService.getProducts();
res.status(200).json(products);
const products = await productService.getProducts()
res.status(200).json(products)
} catch (error) {
res.status(500).json({ message: (error as Error).message });
res.status(500).json({ message: (error as Error).message })
}
};
}

export const getProductById = async (req: Request, res: Response) => {
try {
const id = Number.parseInt(req.params.id, 10)
const product = await productService.getProductById(id)
if (product) {
res.status(200).json(product)
} else {
res.status(404).json({ message: 'Product not found' })
}
} catch (error) {
res.status(500).json({ message: (error as Error).message })
}
}

export const updateProduct = async (req: Request, res: Response) => {
try {
const id = Number.parseInt(req.params.id, 10)
const productData: Partial<Product> = req.body
const updatedProduct = await productService.updateProduct(id, productData)
res.status(200).json(updatedProduct)
} catch (error) {
res.status(500).json({ message: (error as Error).message })
}
}

export const deleteProduct = async (req: Request, res: Response) => {
try {
const id = Number.parseInt(req.params.id, 10)
await productService.deleteProduct(id)
res.status(204).end()
} catch (error) {
res.status(500).json({ message: (error as Error).message })
}
}
Loading

0 comments on commit a5560fa

Please sign in to comment.