From be3d39569d4c7c8c7fd90818c90a8165ad2c0433 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Wed, 19 Nov 2025 13:49:01 -0300 Subject: [PATCH 1/5] chore: alter port from postgres also alter name, password and database name --- docker-compose.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 0247ec6..046c977 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,13 @@ services: postgres: image: bitnami/postgresql:latest - container_name: pizza-stars + container_name: tcc-back ports: - - '5432:5432' + - '5433:5432' environment: - - POSTGRESQL_USERNAME=lucas - - POSTGRESQL_PASSWORD=lucas123 - - POSTGRESQL_DATABASE=pizza-stars + - POSTGRESQL_USERNAME=tcc + - POSTGRESQL_PASSWORD=tcc123 + - POSTGRESQL_DATABASE=tcc-back volumes: - postgres_data:/bitnami/postgresql From 39400b4b5198b06c282f5c8e2176cb9660adbb60 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Fri, 21 Nov 2025 11:14:18 -0300 Subject: [PATCH 2/5] refact: replace Google by Resend on send email --- package-lock.json | 111 +++++++++++++++++++++++++------- package.json | 3 +- src/env/index.ts | 3 +- src/lib/nodemailer.ts | 4 +- src/lib/resend.ts | 4 ++ src/services/users/send-code.ts | 10 +-- 6 files changed, 101 insertions(+), 34 deletions(-) create mode 100644 src/lib/resend.ts diff --git a/package-lock.json b/package-lock.json index 516f485..1bab8d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "dotenv": "^16.5.0", "fastify": "^5.3.2", "nanoid": "^5.1.5", - "nodemailer": "^7.0.5", + "resend": "^6.5.2", "zod": "^3.24.2" }, "devDependencies": { @@ -28,7 +28,6 @@ "@commitlint/types": "^19.8.0", "@faker-js/faker": "^9.8.0", "@types/node": "^22.13.9", - "@types/nodemailer": "^6.4.17", "conventional-changelog-atom": "^5.0.0", "prisma": "^6.4.1", "tsup": "^8.4.0", @@ -1409,6 +1408,12 @@ "win32" ] }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, "node_modules/@types/conventional-commits-parser": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz", @@ -1430,22 +1435,11 @@ "version": "22.13.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.20.0" } }, - "node_modules/@types/nodemailer": { - "version": "6.4.17", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz", - "integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/abstract-logging": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", @@ -2044,6 +2038,12 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", @@ -2182,6 +2182,12 @@ "node": ">=6" } }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense" + }, "node_modules/fast-uri": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", @@ -3033,15 +3039,6 @@ "node": "^18 || >=20" } }, - "node_modules/nodemailer": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.5.tgz", - "integrity": "sha512-nsrh2lO3j4GkLLXoeEksAMgAOqxOv6QumNRVQTJwKH4nuiww6iC2y7GyANs9kRAxCexg3+lTWM3PZ91iLlVjfg==", - "license": "MIT-0", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3404,6 +3401,12 @@ "node": ">=6" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, "node_modules/quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", @@ -3457,6 +3460,32 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/resend": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/resend/-/resend-6.5.2.tgz", + "integrity": "sha512-Yl83UvS8sYsjgmF8dVbNPzlfpmb3DkLUk3VwsAbkaEFo9UMswpNuPGryHBXGk+Ta4uYMv5HmjVk3j9jmNkcEDg==", + "license": "MIT", + "dependencies": { + "svix": "1.76.1" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@react-email/render": "*" + }, + "peerDependenciesMeta": { + "@react-email/render": { + "optional": true + } + } + }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -3900,6 +3929,20 @@ "node": ">=8" } }, + "node_modules/svix": { + "version": "1.76.1", + "resolved": "https://registry.npmjs.org/svix/-/svix-1.76.1.tgz", + "integrity": "sha512-CRuDWBTgYfDnBLRaZdKp9VuoPcNUq9An14c/k+4YJ15Qc5Grvf66vp0jvTltd4t7OIRj+8lM1DAgvSgvf7hdLw==", + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "@types/node": "^22.7.5", + "es6-promise": "^4.2.8", + "fast-sha256": "^1.3.0", + "url-parse": "^1.5.10", + "uuid": "^10.0.0" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -4115,9 +4158,18 @@ "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, "license": "MIT" }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4125,6 +4177,19 @@ "dev": true, "license": "MIT" }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", diff --git a/package.json b/package.json index dd1ccf8..9ffeffc 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "@commitlint/types": "^19.8.0", "@faker-js/faker": "^9.8.0", "@types/node": "^22.13.9", - "@types/nodemailer": "^6.4.17", "conventional-changelog-atom": "^5.0.0", "prisma": "^6.4.1", "tsup": "^8.4.0", @@ -47,7 +46,7 @@ "dotenv": "^16.5.0", "fastify": "^5.3.2", "nanoid": "^5.1.5", - "nodemailer": "^7.0.5", + "resend": "^6.5.2", "zod": "^3.24.2" } } diff --git a/src/env/index.ts b/src/env/index.ts index ce824cb..01a19a9 100644 --- a/src/env/index.ts +++ b/src/env/index.ts @@ -6,8 +6,7 @@ const envSchema = z.object({ PORT: z.coerce.number().min(1000).max(9999).default(3333), NODE_ENV: z.enum(['dev', 'test', 'prod']).default('dev'), JWT_SECRET: z.string(), - GMAIL_USER: z.string().email(), - GMAIL_PASS: z.string(), + RESEND_API_KEY: z.string(), }) const _env = envSchema.safeParse(process.env) diff --git a/src/lib/nodemailer.ts b/src/lib/nodemailer.ts index 935addb..ce62f56 100644 --- a/src/lib/nodemailer.ts +++ b/src/lib/nodemailer.ts @@ -1,4 +1,4 @@ -import { env } from '@/env' +/* import { env } from '@/env' import { type SendMailOptions, createTransport } from 'nodemailer' const transporter = createTransport({ @@ -25,4 +25,4 @@ export async function sendMail(options: SendMailOptions) { const email = await transporter.sendMail(mailOptions) return email -} +} */ diff --git a/src/lib/resend.ts b/src/lib/resend.ts new file mode 100644 index 0000000..c414a44 --- /dev/null +++ b/src/lib/resend.ts @@ -0,0 +1,4 @@ +import { env } from '@/env' +import { Resend } from 'resend' + +export const resend = new Resend(env.RESEND_API_KEY) diff --git a/src/services/users/send-code.ts b/src/services/users/send-code.ts index 106d82e..ced6560 100644 --- a/src/services/users/send-code.ts +++ b/src/services/users/send-code.ts @@ -4,7 +4,7 @@ import { CodeGeneratedRecentlyError } from '../errors/code-generated-recently-er import { UserNotExistsError } from '../errors/user-not-exists-error' import { generateAuthCode } from '@/lib/nanoid' -import { sendMail } from '@/lib/nodemailer' +import { resend } from '@/lib/resend' import dayjs from 'dayjs' import { UnableToSendEmailError } from '../errors/unable-to-send-email-error' @@ -52,7 +52,8 @@ export class SendCodeService { }, }) - const emailSent = await sendMail({ + const emailSent = await resend.emails.send({ + from: 'Pizza Stars ', to: email, subject: `Seu código Pizza Stars é ${code}`, text: `Aqui está seu código: ${code}`, @@ -145,13 +146,12 @@ export class SendCodeService { `, - priority: 'high', }) - if (!emailSent.messageId) { + if (emailSent.error) { throw new UnableToSendEmailError() } - return { messageId: emailSent.messageId } + return { messageId: emailSent.data.id } } } From a08433b3e86d83d6f0543ee203e35487923f7100 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Fri, 21 Nov 2025 11:14:52 -0300 Subject: [PATCH 3/5] chore: add warp.md file --- WARP.md | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 WARP.md diff --git a/WARP.md b/WARP.md new file mode 100644 index 0000000..0fe6c84 --- /dev/null +++ b/WARP.md @@ -0,0 +1,156 @@ +# WARP.md + +This file provides guidance to WARP (warp.dev) when working with code in this repository. + +## Project Overview + +**Pizza Stars API Backend** - A TypeScript/Node.js backend for a pizza restaurant management system (TCC project) built with Fastify, Prisma ORM, and PostgreSQL. Features JWT authentication, role-based access control, and comprehensive CRUD operations for users, addresses, and future product/order management. + +## Development Commands + +### Core Development +```bash +# Development server with hot reload +npm run dev + +# Build for production +npm run build + +# Start production server +npm start +``` + +### Database Operations +```bash +# Start PostgreSQL container +docker-compose up -d + +# Run database migrations +npx prisma migrate dev + +# Seed database with sample data +npx prisma db seed + +# Open Prisma Studio (database GUI) +npx prisma studio + +# Reset database (drops all data) +npx prisma migrate reset +``` + +### Code Quality +```bash +# Run Biome linter +npm run lint + +# Fix linting issues automatically +npx biome check --write + +# Structured commit (uses commitlint) +npm run commit +``` + +## Architecture Overview + +### High-Level Structure + +The application follows a **layered architecture** with clear separation of concerns: + +**Controllers** → **Services** → **Repositories** → **Database** + +- **Controllers** (`src/http/controllers/`): Handle HTTP requests/responses, validation, error handling +- **Services** (`src/services/`): Business logic, coordinating between repositories +- **Repositories** (`src/repositories/`): Data access layer, abstracted database operations +- **Factories** (`src/services/factories/`): Dependency injection pattern for service instantiation + +### Key Architectural Patterns + +1. **Repository Pattern**: Abstract database operations through interfaces (`UsersRepository`, `AddressRepository`) +2. **Factory Pattern**: Service instantiation with proper dependency injection +3. **Service Layer**: Encapsulate business logic separate from HTTP concerns +4. **Middleware Chain**: JWT verification and role-based access control + +### Authentication Flow + +The app uses **JWT-based authentication** with refresh tokens: + +- Access tokens expire in 10 minutes +- Refresh tokens stored in HTTP-only cookies +- Role-based access control (CUSTOMER, EMPLOYEE, ADMIN) +- Custom middlewares: `verifyJWT`, `verifyUserRole` + +### Database Schema Architecture + +**Core Entities**: +- **Users**: Authentication, profile data, roles +- **Addresses**: User delivery addresses (1:1 with User) +- **Products**: Pizzas, Drinks, Desserts with enum-based categorization +- **Orders/Cart**: Many-to-many relationships with products + +**Key Relationships**: +- User ↔ Address (1:1) +- User ↔ Cart (1:1) +- User ↔ Orders (1:N) +- Orders/Cart ↔ Products (N:N via implicit join tables) + +## Development Notes + +### Environment Setup + +Required environment variables in `.env`: +```env +DATABASE_URL="postgresql://lucas:lucas123@localhost:5432/pizza-stars" +PORT=3333 +NODE_ENV="dev" +JWT_SECRET="your-secure-secret-key" +GMAIL_USER="your-email@gmail.com" +GMAIL_PASS="your-app-password" +``` + +### Code Style Guidelines + +- **Biome** enforces code style (no default exports, semicolon preferences, etc.) +- **TypeScript strict mode** enabled +- **Path aliases**: Use `@/*` for `src/*` imports +- **Conventional commits** required via commitlint + +### Error Handling Strategy + +- **Custom Error Classes**: `UserAlreadyExistsError`, `InvalidCredentialsError`, etc. +- **Global Error Handler**: Catches and formats errors appropriately +- **Zod Validation**: Automatic request validation with detailed error responses +- **Database Error Detection**: Handles connection issues gracefully + +### Testing Considerations + +- Services are designed for easy unit testing with dependency injection +- Repository interfaces allow for easy mocking +- Factory pattern enables swapping implementations for testing + +### Deployment Configuration + +- **Docker**: PostgreSQL container via docker-compose +- **Build Tool**: tsup for fast TypeScript compilation +- **Module System**: ESM modules (`"type": "module"`) +- **Node.js Version**: Locked to Node.js 20 via Volta + +## API Structure + +Base URL: `http://localhost:3333/api` + +### Authentication Endpoints +- `POST /auth/login` - User authentication +- `PATCH /auth/refresh` - Token refresh +- `DELETE /auth/logout` - User logout (requires JWT) + +### User Management +- `POST /users` - User registration +- `GET /user` - Get current user profile (requires JWT) +- `PATCH /user` - Update user profile (requires JWT) +- `DELETE /user` - Delete user account (requires JWT) +- `GET /users` - List all users (requires ADMIN role) + +### Address Management +- Full CRUD operations for user addresses (all require JWT) + +*Note: Product and order management APIs are planned but not yet implemented.* From ca662288beaedadab2aaa90256e1d07a62d3b345 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Fri, 21 Nov 2025 12:46:31 -0300 Subject: [PATCH 4/5] chore: alter email from admin on seed --- prisma/seed.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prisma/seed.ts b/prisma/seed.ts index 6e8de3d..426a613 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -15,7 +15,7 @@ async function seed() { const admin = await prisma.user.create({ data: { - email: 'admin@gmail.com', + email: 'pizzastars33@gmail.com', name: 'Admin', password: await hash('dnx42697', 10), role: 'ADMIN', From 13f8d534d56576b6ccc262bc2e8906a41f3033c2 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Fri, 21 Nov 2025 13:07:47 -0300 Subject: [PATCH 5/5] chore: add urls to cors --- src/app.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app.ts b/src/app.ts index aff71b5..3e309ad 100644 --- a/src/app.ts +++ b/src/app.ts @@ -11,7 +11,12 @@ export const app = fastify() app.register(fastifyCors, { origin: (origin, cb) => { - const allowedOrigins = ['http://localhost:5500', 'https://mlkp1.github.io'] + const allowedOrigins = [ + 'http://localhost:5500', + 'http://localhost:3003', + 'https://mlkp1.github.io', + 'https://pizzastars.shop', + ] if (!origin || allowedOrigins.includes(origin)) { cb(null, true) return