Skip to content

Corelab Challenge - Backend NodeJS + Nest + Prisma + Redis + Swagger#82

Open
odavibatista wants to merge 23 commits into
corelabbr:mainfrom
odavibatista:corelab-challenge-solved
Open

Corelab Challenge - Backend NodeJS + Nest + Prisma + Redis + Swagger#82
odavibatista wants to merge 23 commits into
corelabbr:mainfrom
odavibatista:corelab-challenge-solved

Conversation

@odavibatista

@odavibatista odavibatista commented Sep 2, 2025

Copy link
Copy Markdown

Desafio Corelab - Solução

Esta PR contém a solução do desafio para a vaga de Dev Full-stack da Corelab, seguindo os requisitos solicitados para o desenvolvimento de uma aplicação full-stack de anotações.

Features

  • Criptografia: Os dados sensíveis do usuário e o conteúdo das notas é inteiramente criptografado através de criptografia AES-256 utilizando a lib crypto do Node.js. Sempre que um dado é solicitado, ele é descriptografado em tempo real, garantindo a segurança das informações, e é sempre criptografado ao ser salvo no banco de dados;
  • Autenticação JWT + Middleware: A API conta com autenticação utilizando a lib JWT, que gera tokens baseados em dados do próprio usuário. Os UseCases de cada implementação garantem que somente o usuário dono daquele item possa acessá-lo, e respostas negativas da API são dadas em caso de tentativa de trazer dados indevidos.
  • Documentação no Swagger: A API conta com uma documentação utilizando a lib Swagger, que utiliza as tipagens montadas com as libs zod e nestjs-zod para documentar cada tipo de informação esperada em cada endpoint, e o objeto retornado nos casos bem e mau-sucedidos;
  • Implementação de ID aleatório no banco de dados: a API gera IDs únicos e aleatórios para cada entidade, não sendo gerado um ID numérico crescente, de modo que os dados sejam mais protegidos e que não sejam facilmente buscados por qualquer pessoa, sendo necessário saber o ID randômico para buscar um item pelo ID;
  • Testes Unitários: A aplicação possua uma cobertura de aproximadamente 50% de suas implementações utilizando as libs Jest e Supertest (esta última para testar os métodos dos controllers da aplicação);
  • Clean Architecture: A API está desenvolvida seguindo princípios de Clean Architecture, utilizando uma arquitetura modular de estilo cebola, de modo a evitar que itens de diferentes entidades sistêmicas estejam misturados entre si em pastas;
  • Caching: A API utiliza caching através das libs cache-manager e @nestjs/cache-manager, utilizando um banco de dados Redis em ambiente local ou de produção para armazenar alguns dados de forma a evitar stress na aplicação por muitas requisições;

Tecnologias Utilizadas

Back-end:

  • ORM: Prisma + Typescript;
  • Banco de dados: MySQL;
  • Framework: NestJS + Express;
  • Caching: Redis + KeyV + Cache-Manager + Docker (ver abaixo);
  • Documentação da API: Swagger;
  • Testes: Jest + Supertest;
  • Autenticação: JWT

Front-end:

  • Framework: NextJS + Typescript;
  • Estilização: SASS;
  • Tipagens: Zod + TypeScript;
  • Requisições/Integração: Axios;

Rodando a aplicação

Rodando - clonagem + configuração das variáveis de ambiente

Efetue a clonagem dos dois repositórios, front-end e back-end em uma única pasta, de modo a manter a estrutura mais limpa.

Na pasta do back-end, crie um arquivo .env com as variáveis de ambiente necessárias (veja o arquivo .env.example para referência).

Para o app rodar corretamente, é necessário gerar duas chaves de criptografia para o AES-256 e inseri-las como as variáveis CRYPTO_SECRET e CRYPTO_IV. Neste link é possível gerá-las de modo randômico. Após gerá-las, coloque a "Basic 16" na variável CRYPTO_IV e a "Encryption key 256" na variável CRYPTO_SECRET.

Para rodar o Cache com o Redis, é preciso gerar um container via Docker ou utilizar um banco da própria Redis Cloud. Caso deseje utilizar via Docker Desktop, copie o comando abaixo para a criação de um novo container:

docker run --hostname=ff67fd77cb8d --env=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin --env=REDISBLOOM_ARGS= --env=REDISEARCH_ARGS= --env=REDISJSON_ARGS= --env=REDISTIMESERIES_ARGS= --env=REDIS_ARGS= --network=bridge -p 6379:6379 -p 8001:8001 --restart=no --label='desktop.docker.io/wsl-distro=Ubuntu' --label='org.opencontainers.image.ref.name=ubuntu' --label='org.opencontainers.image.version=22.04' --runtime=runc -d redis/redis-stack:latest

Por padrão, o banco ficará como o host localhost e a porta 6379, e somente isso é necessário para poder rodar ele com o caching local. Caso deseje utilizar um banco de dados Redis em produção, será necessário alterar as variáveis de ambiente REDIS_PRODUCTION_HOST, REDIS_PRODUCTION_PORT, REDIS_PRODUCTION_USER e REDIS_PRODUCTION_PASSWORD no arquivo .env com os dados fornecidos pela Redis Cloud ou outro provedor do banco.

O Container do Redis vem junto com o Redis-Insight, que permite visualizar os dados armazenados no cache da aplicação. Para acessá-lo, basta abrir o navegador e ir até a URL http://localhost:8001.

A aplicação depende de pelo menos um banco de dados MySQL para ser rodada. Para isso, basta utilizar o Shell do MySQL ou um cliente de banco de dados para criar o banco de dados corenotes_db, e inserir os dados de autenticação. Após criar o banco, copie a sua string de conexão na variável DATABASE_URL.
Caso queira rodar os testes unitários, crie um segundo banco de dados corenotes_db_test e copie a sua string de conexão na variável SHADOW_DATABASE_URL para o Prisma poder reconhecê-lo na hora de rodar os testes dos endpoints da API.

Por fim, é necessário preencher algumas variáveis de ambiente para o Nest e os providers utilizarem. Utilize o valor "development" ou "local" para a chave NODE_ENV, o valor "mysql" para a chave DATABASE_PROVIDER, uma porta de quatro dígitos para a chave PORT, a url da API e do front-end nas chaves API_URL e FRONTEND_URL e a chave para a criação de JWTs na variável APP_SECRET. Por segurança, pode-se usar uma das chaves geradas no link acima.

Ao fim da configuração, o arquivo .env deve ficar parecido com o abaixo:

# Application Environment Variables
NODE_ENV=development
PORT=5000
API_URL="http://localhost:5000/api"
FRONTEND_URL="http://localhost:3000"
DATABASE_URL="mysql://user:password@localhost:3306/corenotes_db"
DATABASE_PROVIDER="mysql"
APP_SECRET=mycrypto

# Tests
SHADOW_DATABASE_URL="mysql://user:password@localhost:3306/corenotes_db_test"

# Cryptography Settings
CRYPTO_SECRET=obXApy1XJIL8eR5fNI40ApGYcnnZjmvO
CRYPTO_IV=wDYth95sawqDQYss

# Redis Cache Settings
REDIS_HOST=localhost
REDIS_PORT=6379

# Redis Cache Production Settings - [Parte opcional]
REDIS_PRODUCTION_HOST="redis-example.redis-cloud.com"
REDIS_PRODUCTION_PORT=13503
REDIS_PRODUCTION_USER="default"
REDIS_PRODUCTION_PASSWORD="your-password-here"

Por fim, no repositório do front-end, copie o arquivo .env.example, altere seu nome para .env e mude o valor da variável NEXT_PUBLIC_API para a URL da API montada via configuração manual ou via Docker (próximo passo).

Rodando com Docker

Ambos os repositórios já vêm pré-configurados com as informações do Docker que serão utilizadas para criar os containers. Para isso, basta rodar o comando npm run docker:up ou docker-compose up na root de cada um dos projetos, e os containers serão iniciados automaticamente.

(OBS: Lembre-se de que é necessário ter o Docker instalado e em execução na sua máquina.)
(OBS 2: Caso esteja utilizando Windows, o Docker pode confundir o localhost da url do banco criado em container com o localhost do seu sistema. Fechar o processo do mysql no Gerenciador de Tarefas pode ajudar a resolver esse problema.)

Por padrão, a API será aberta na porta 3311. Basta utilizar a URL http://localhost:3311/swagger para acessar a documentação.

Rodando localmente

Após a configuração, rode o comando npm install para instalar as dependências do projeto. Após isso, rode o comando npx prisma migrate dev, e insira o nome da migration para que o banco seja sincronizado com o modelo das entidades do arquivo schema.prisma. Após isso, rode o comando npx prisma db seed para rodar o seed de um usuário e três notas padrão (vê-los na pasta ./src\shared\infra\db\prisma\seeders).
Por fim, rode o comando npm run start:dev para iniciar a aplicação em modo de desenvolvimento, e assim que a aplicação iniciar, é possível acessar sua documentação através da URL http://localhost:5000/swagger.

No repositório do front-end, rode o comando npm install, e em seguida npm run dev para iniciar a aplicação front-end.

Vídeo

Para assistir o vídeo mostrando a execução do projeto integrado, clique aqui!

Summary by CodeRabbit

  • New Features

    • Introduces Corenotes API with endpoints to browse, search, view, create, edit, star, change color, and delete notes.
    • JWT-based authentication and improved error handling.
    • Redis-backed caching for faster responses.
    • Interactive API docs available at /swagger.
    • Health check endpoint.
  • Refactor

    • Migrated from AdonisJS to NestJS; removed legacy framework files.
  • Chores

    • Added Dockerfile and docker-compose (app, MySQL, Redis).
    • Configured ESLint/Prettier and Nest CLI.
    • Updated environment template and ignore rules.
  • Documentation

    • Rewrote README with setup and usage instructions; added PR template.

…er for API endpoint's docs, added redis configuration and prisma's first schemas
@coderabbitai

coderabbitai Bot commented Sep 2, 2025

Copy link
Copy Markdown

Walkthrough

This PR replaces an AdonisJS setup with a NestJS-based API. It removes Adonis files and configurations, adds Nest app bootstrap, modules for users and notes (controllers, use cases, repositories, DTOs), shared configs (env, redis, swagger), middleware for JWT auth, caching via Redis, Prisma-based persistence, tests, Docker/Compose, and tooling configs.

Changes

Cohort / File(s) Summary
Framework migration: Remove AdonisJS
server.ts, ace, ace-manifest.json, .adonisrc.json, app/*, config/*, contracts/*, commands/index.ts, env.ts, providers/AppProvider.ts
Removed AdonisJS bootstrap, CLI, configs (http, bodyparser, cors, drive, hash), contracts, handlers, controllers, and env validation.
Documentation and templates
README.md, PULL_REQUEST.md, .env.example
Replaced README with Nest/Prisma API docs; added detailed PR write-up; restructured env example with sections and placeholders.
Containerization
Dockerfile, docker-compose.yml
Added Node 20 Alpine image with wait script; compose services for MySQL, Redis, and API with Prisma generate/migrate/seed and dev startup.
Tooling configs
eslint.config.mjs, .prettierrc, .prettierignore, .editorconfig (deleted), nest-cli.json
Added ESLint (TS, Prettier) and Prettier config; removed EditorConfig; configured Nest CLI; adjusted Prettier ignore.
Git ignore updates
.gitignore, .gitignore copy
Consolidated root-scoped ignores; added Prisma paths and pnpm-debug; cleared duplicated copy file.
Package overhaul
package.json
Renamed package, replaced Adonis deps with NestJS/Prisma/Redis stack, added Jest config, Prisma paths, Docker scripts, and Nest scripts.
App bootstrap
src/app/app.module.ts, src/app/main.ts, src/@types/express.d.ts
Added Nest AppModule with Redis cache and global ValidationPipe; bootstrap with CORS and Swagger; extended Express.Request typing.
Shared configuration
src/shared/config/app.config.ts, src/shared/config/redis.config.ts, src/shared/config/swagger.config.ts
Zod-validated env loader; multi-store cache config (memory + Redis via Keyv); shared Swagger config.
Shared errors & DTOs
src/shared/domain/errors/*, src/shared/domain/dtos/*
Added global exception filter, environment and token errors; DTOs for health and error shape; provider DTO types.
User module: Config
src/modules/user/config/auth.config.ts, src/modules/user/config/jwt.config.ts
Zod-validated auth and JWT configs sourced from env/defaults.
User module: Domain DTOs & providers
src/modules/user/domain/dtos/**/*, src/modules/user/domain/providers/*
Added interfaces for controller and repository contracts; request/response DTOs; errors; hash/JWT provider interfaces.
User module: Infra
src/modules/user/infra/modules/user.module.ts, src/modules/user/http/controllers/user.controller.ts, src/modules/user/http/middlewares/Auth.middleware.ts, src/modules/user/infra/db/repositories/user.repository.ts, src/modules/user/infra/providers/*
Implemented User module with controller, auth middleware, Prisma repository with encryption, bcrypt-based hash provider, JWT provider.
User module: Tests
src/modules/user/http/controllers/user.controller.spec.ts, src/modules/user/infra/db/repositories/User.repository.spec.ts, src/modules/user/infra/providers/*.spec.ts, src/modules/user/infra/usecases/*.spec.ts
Added integration/unit tests for controller, repository, providers, and use cases (create user, login, home data).
Note module: Domain DTOs
src/modules/note/domain/dtos/**/*
Added controller interface; request/response DTOs for create/edit/find/browse/star/color; note-specific errors; repository interface.
Note module: Infra
src/modules/note/infra/modules/note.module.ts, src/modules/note/http/controllers/note.controller.ts, src/modules/note/infra/db/repositories/note.repository.ts
Implemented Note module with controller, Prisma repository (encrypt/decrypt fields), DI wiring, and auth middleware routes.
Note module: Use cases
src/modules/note/infra/usecases/*.ts
Implemented note use cases: browse, search, find by id, create, star, change color, edit, delete with validations and ownership checks.
Note module: Tests
src/modules/note/http/controllers/note.controller.spec.ts, src/modules/note/infra/db/repositories/note.repository.spec.ts, src/modules/note/infra/usecases/*.spec.ts
Added tests for controller, repository skeleton, and use cases covering success and error paths.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant A as API (Nest)
  participant M as Auth Middleware
  participant UC as Usecase
  participant R as Repository (Prisma)
  participant Cache as Cache (Keyv/Redis)

  rect rgba(200,220,255,0.2)
    note over C,A: Authenticated request
    C->>A: HTTP request with Authorization: Bearer <token>
    A->>M: Run AuthenticationMiddleware
    M->>M: Verify JWT (APP_SECRET)
    M-->>A: Attach req.user, next()
  end

  alt Read endpoint (cached)
    A->>Cache: Get notes-{userId} / note-{id}
    Cache-->>A: Hit
    A-->>C: 200 OK (cached payload)
  else Read endpoint (miss)
    A->>UC: execute(...)
    UC->>R: Query Prisma
    R-->>UC: Data
    UC-->>A: DTO
    A->>Cache: Set cache key
    A-->>C: 200 OK
  end

  rect rgba(220,255,220,0.2)
    note over A,UC: Write endpoint
    C->>A: Create/Star/Edit/Delete
    A->>UC: execute(...)
    UC->>R: Mutate via Prisma
    R-->>UC: Result
    UC-->>A: DTO/boolean
    A->>Cache: Invalidate/Refresh per-note and per-user keys
    A-->>C: 200/201/204
  end
Loading
sequenceDiagram
  autonumber
  participant C as Client
  participant A as API (Nest)
  participant UUC as User Usecases
  participant UR as UserRepository
  participant H as HashProvider
  participant JWT as JWTProvider

  rect rgba(255,240,200,0.25)
    note over C,A: Registration
    C->>A: POST /user/register
    A->>UUC: CreateUserUseCase.execute(data)
    UUC->>UR: findByEmail
    UR-->>UUC: null or user
    alt Email free
      UUC->>H: hash(password)
      H-->>UUC: hash
      UUC->>UR: create(user)
      UR-->>UUC: created user
      UUC->>JWT: generate({ user })
      JWT-->>UUC: token
      UUC-->>A: { token, id }
      A-->>C: 201 Created
    else Email exists
      UUC-->>A: EmailAlreadyRegisteredException
      A-->>C: 403 with message
    end
  end

  rect rgba(255,220,220,0.25)
    note over C,A: Login
    C->>A: POST /user/login
    A->>UUC: UserLoginUsecase.execute(data, ip)
    UUC->>UR: findByEmail
    UR-->>UUC: user
    UUC->>UR: comparePassword
    UR-->>UUC: true/false
    alt Valid
      UUC->>JWT: generate({ user })
      JWT-->>UUC: token
      UUC-->>A: { token, user }
      A-->>C: 200 OK
    else Invalid
      UUC-->>A: InvalidCredentialsException
      A-->>C: 400 with message
    end
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

In burrows of code I hop and compose,
Adonis to Nest, a new garden grows.
Notes in the cache, secrets well kept,
Prisma sows rows where data once slept.
With Redis I whisk, with JWT I glide—
Ship with Docker, ears to the tide. 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@odavibatista odavibatista changed the title Corelab challenge solved Corelab Challenge - Backend NodeJS + Prisma + Redis Sep 2, 2025
@odavibatista odavibatista changed the title Corelab Challenge - Backend NodeJS + Prisma + Redis Corelab Challenge - Backend NodeJS + Prisma + Redis + Swagger Sep 2, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 80

🧹 Nitpick comments (115)
src/shared/domain/dtos/repositories/Configuration.repository.dto.ts (3)

1-3: Type the config key (avoid magic strings) and make it readonly

Passing arbitrary strings invites runtime errors and weakens refactors. Consider a dedicated key type and readonly field.

-export interface GetConfigurationValueParamsDTO {
-  name: string;
-}
+export type ConfigurationKey = string; // tighten to a union of known keys when available
+export interface GetConfigurationValueParamsDTO {
+  readonly name: ConfigurationKey;
+}

If you already have a Zod env schema, we can derive this type (or define a union in the domain to avoid cross-layer coupling). Want me to wire that up?


5-7: Generalize the value type; many config entries aren’t strings

Some values (PORT, flags) are numeric/boolean. A generic with a safe default keeps callers typed without breaking anything.

-export interface GetConfigurationValueResponseDTO {
-  value: string;
-}
+export type ConfigurationPrimitive = string | number | boolean;
+export interface GetConfigurationValueResponseDTO<T extends ConfigurationPrimitive = string> {
+  value: T;
+}

1-7: Guard against leaking secrets

If this DTO ever bubbles up to controllers, ensure secret-like keys (e.g., APP_SECRET, CRYPTO_SECRET/IV) are never serialized to API responses or logs. Confirm usage is constrained to repository/service layers.

I can add a small masking utility and an allowlist to enforce this at boundaries if you’d like.

.gitignore (2)

31-37: Consider dropping VSCode exceptions or audit them for secrets.

Keeping these files tracked is fine for shared debug configs, but ensure they never contain tokens/paths specific to individuals. Otherwise, ignore the entire .vscode/.


59-59: Ensure src/shared/infra/db/generated artifacts are regenerated on install/build: ignoring this directory will break fresh clones if those files aren’t produced. Add a generation step (e.g. prisma generate in postinstall) or switch users to a published client like @prisma/client.

.prettierrc (1)

1-4: Add trailing newline and consider pinning end-of-line to avoid cross-OS diffs.

Prettier prefers files ending with a newline; also consider endOfLine: "lf" if teammates use Windows.

 {
   "singleQuote": true,
-  "trailingComma": "all"
-}
+  "trailingComma": "all"
+}
+
nest-cli.json (1)

5-8: entryFile path valid; consider adding asset configuration
src/app/main.ts exists; no compilerOptions.assets configured—add asset patterns if you serve Swagger static assets or other non-TS files.

src/modules/user/domain/dtos/errors/UserNotFound.exception.ts (2)

1-6: Prefer Nest built-ins over raw HttpException.

Use NotFoundException for semantics and consistency; drop the numeric status.

-import { HttpException } from '@nestjs/common';
+import { NotFoundException } from '@nestjs/common';

-export class UserNotFoundException extends HttpException {
+export class UserNotFoundException extends NotFoundException {
   constructor() {
-    super('Usuário não encontrado.', 404);
+    super('Usuário não encontrado.');
   }
 }

3-7: Unify error filename conventions.

File is UserNotFound.exception.ts while class is UserNotFoundException. Consider user-not-found.exception.ts for consistency with other Nest projects.

src/modules/note/domain/dtos/errors/ColorAlreadySetException.exception.ts (2)

1-6: Use BadRequestException instead of base HttpException.

Encodes the 400 semantics without magic numbers.

-import { HttpException } from '@nestjs/common';
+import { BadRequestException } from '@nestjs/common';

-export class ColorAlreadySetException extends HttpException {
+export class ColorAlreadySetException extends BadRequestException {
   constructor() {
-    super('A nota já está com esta cor inserida.', 400);
+    super('A nota já está com esta cor inserida.');
   }
 }

3-7: Fix duplicated “Exception” in filename.

ColorAlreadySetException.exception.ts repeats the suffix. Suggest color-already-set.exception.ts.

src/modules/note/domain/dtos/errors/NoteNotFoundException.exception.ts (2)

1-6: Prefer Nest’s NotFoundException (or HttpStatus) for clarity and consistency

Use the framework’s specific exception or the HttpStatus enum instead of a numeric literal.

-import { HttpException } from '@nestjs/common';
+import { NotFoundException } from '@nestjs/common';

-export class NoteNotFoundException extends HttpException {
+export class NoteNotFoundException extends NotFoundException {
   constructor() {
-    super('Anotação não encontrada.', 404);
+    super('Anotação não encontrada.');
   }
 }

3-7: Consider relocating exceptions out of “dtos”

Placing exceptions under a “dtos” path is misleading. Suggest moving to domain/errors or shared/errors to align with intent.

src/shared/config/swagger.config.ts (1)

3-16: Set default security requirement so all operations require Bearer by default

This avoids repeating @ApiBearerAuth on every controller and ensures the Swagger UI sends the token automatically.

 export const sharedSwaggerConfig = new DocumentBuilder()
   .setTitle('CoreNotes API')
   .setDescription('API for a note-taking application.')
   .setVersion('1.0.0')
   .addBearerAuth(
     {
       type: 'http',
       scheme: 'bearer',
       bearerFormat: 'JWT',
       description: 'Insert a JWT token in order to authenticate.',
     },
     'access-token',
   )
+  .addSecurityRequirements('access-token')
   .build();
src/@types/express.d.ts (1)

1-9: Harden Express.Request module augmentation
Add an empty export and wrap the namespace in a global‐augmentation block so this file is always treated as a module and your augmentation merges reliably.

File: src/@types/express.d.ts

export {};

declare global {
  namespace Express {
    interface Request {
      userIp: string | string[];
      user?: {
        id_user: string;
        name: string;
      };
    }
  }
}
src/modules/note/domain/dtos/requests/StarNote.request.dto.ts (1)

4-9: Tighten validation for note_id and reject unknown fields

If IDs are UUIDs, validate as such. At minimum ensure non-empty and strip/forbid extraneous properties to harden the DTO.

-export const StarNoteBodySchema = z.object({
-  note_id: z.string(),
-});
+export const StarNoteBodySchema = z
+  .object({
+    // If your IDs are UUIDs, swap .min(1) for .uuid()
+    note_id: z.string().min(1, 'note_id is required'),
+  })
+  .strict();
src/shared/domain/dtos/requests/HealthCheck.request.dto.ts (1)

4-8: Tighten validation (non-empty strings, ISO datetime).

Add basic guards and ensure timestamp is ISO-8601. Keeps Swagger accurate and prevents junk values.

 export const HealthCheckSchema = z.object({
-  message: z.string().describe('Mensagem de verificação do HealthCheck'),
-  status: z.string().describe('Status do HealthCheck'),
-  timestamp: z.string().describe('Timestamp da verificação do HealthCheck'),
+  message: z.string().min(1).describe('Mensagem de verificação do HealthCheck'),
+  status: z.string().min(1).describe('Status do HealthCheck'),
+  timestamp: z.string().datetime().describe('Timestamp da verificação do HealthCheck'),
 });
src/modules/user/domain/providers/Hash.provider.ts (1)

1-4: Make bcrypt cost configurable
Replace the hardcoded 10 in HashProvider (src/modules/user/infra/providers/hash.provider.ts:8) with a value injected via environment (e.g. process.env.BCRYPT_ROUNDS) or passed through the provider’s config, with a sensible default fallback.

PULL_REQUEST.md (2)

46-50: Add languages to fenced code blocks (markdownlint MD040).

Use bash for commands and env for .env snippets to improve rendering and tooling.

Examples:

Also applies to: 61-87, 93-95, 100-106


102-103: Use forward slashes in paths for cross‑platform docs.

Prefer ./src/shared/infra/db/prisma/seeders over backslashes.

-('./src\shared\infra\db\prisma\seeders')
+('./src/shared/infra/db/prisma/seeders')
src/shared/domain/dtos/providers/Date.provider.dto.ts (2)

21-25: Clarify interval semantics (inclusive/exclusive).

Add flags or document boundary behavior to avoid off‑by‑one errors.

 export interface IsDateInIntervalDTO {
   date: Date;
   startDate: Date;
   endDate: Date;
+  inclusiveStart?: boolean; // default: true
+  inclusiveEnd?: boolean;   // default: false
 }

16-19: Naming nit: simplify DateIsDateGreaterThanDTO.

IsDateGreaterThanDTO is clearer.

src/modules/user/domain/dtos/requests/UserLogin.request.dto.ts (1)

11-19: Make response schemas strict and non-empty token.

  • Add .strict() to both the top-level and nested user object.
  • Ensure token is non-empty.
-export const UserLoginResponseSchema = z.object({
-  token: z.string().describe('Token de autenticação do usuário'),
-  user: z
-    .object({
-      id_user: z.string().describe('ID do usuário autenticado'),
-      name: z.string().describe('Nome do usuário autenticado'),
-    })
-    .describe('Dados do usuário autenticado'),
-});
+export const UserLoginResponseSchema = z
+  .object({
+    token: z.string().min(1).describe('Token de autenticação do usuário'),
+    user: z
+      .object({
+        id_user: z.string().describe('ID do usuário autenticado'),
+        name: z.string().describe('Nome do usuário autenticado'),
+      })
+      .strict()
+      .describe('Dados do usuário autenticado'),
+  })
+  .strict();

If IDs are UUIDs/CUIDs, consider z.string().uuid() or z.string().cuid2() for id_user.

src/shared/domain/dtos/controllers/health.controller.dto.ts (2)

1-9: Decouple controllers from Express Response across the codebase.

Script (rg -nP --type=ts -C2 '\bResponse\b' src) revealed Express Response injections in:

  • src/shared/http/controllers/shared.controller.ts
  • src/modules/user/domain/dtos/controllers/user.controller.dto.ts
  • src/modules/note/domain/dtos/controllers/note.controller.dto.ts
    (plus other controller DTOs and HTTP controllers)

• Update HealthControllerInterface.getStatus to drop @Res()/Response and return a typed DTO (e.g. HealthStatusResponseDto).
• Apply the same refactor to all controller interfaces and implementations—return plain objects or DTO classes and let Nest handle HTTP responses.
• Optionally introduce dedicated response DTOs (via Zod or class-transformer) to enforce schema and keep Swagger docs consistent.


2-2: Configure and use TS path aliases

tsconfig.json currently lacks a paths mapping under compilerOptions. Add something like:

"compilerOptions": {
  "baseUrl": ".",
  "paths": {
    "@shared/*": ["src/shared/*"]
  }
}

Then replace the relative import with an alias import:

import { AllExceptionsFilterDTO } from '@shared/domain/dtos/errors/AllException.filter.dto';
src/modules/note/domain/dtos/requests/ChangeNoteColor.request.dto.ts (1)

4-9: Tighten schema (strict) and fix apostrophe.

  • Make the request body strict.
  • Fix description punctuation.
-export const ChangeNoteColorBodySchema = z.object({
-  note_id: z.string(),
-  note_color: z
-    .enum(['red', 'green', 'blue', 'yellow'])
-    .describe('Note`s color'),
-});
+export const ChangeNoteColorBodySchema = z
+  .object({
+    note_id: z.string(),
+    note_color: z
+      .enum(['red', 'green', 'blue', 'yellow'])
+      .describe("Note's color"),
+  })
+  .strict();

If IDs are UUIDs/CUIDs, consider z.string().uuid() or z.string().cuid2(). Also consider centralizing the color enum in a shared type used by both FE and BE to avoid drift.

eslint.config.mjs (2)

9-10: Ignore common build/artifact folders.

Add dist/** and coverage/** to reduce lint noise.

-    ignores: ['eslint.config.mjs'],
+    ignores: ['eslint.config.mjs', 'dist/**', 'coverage/**'],

20-20: Remove explicit sourceType: defer to tsconfig
tsconfig.json already specifies compilerOptions.module = "commonjs", so ESLint will infer the same—dropping this override preserves current behavior and avoids ESM conflicts.

src/modules/note/domain/dtos/requests/EditNote.request.dto.ts (1)

15-16: Unify color typing via a shared enum.

Keep a single source of truth and reuse across DTOs (this file and FindNote.*).

Example:

// src/modules/note/domain/schemas/note-color.schema.ts
export const NoteColorSchema = z.enum(['red', 'green', 'blue', 'yellow']);
export type NoteColor = z.infer<typeof NoteColorSchema>;

Then in this file:

-  note_color: z
-    .enum(['red', 'green', 'blue', 'yellow'])
+  note_color: NoteColorSchema
src/modules/note/domain/dtos/requests/FindNote.request.dto.ts (2)

15-23: Class-name casing: standardize DTO suffix.

Other files use “DTO”; these are “Dto”. Consider renaming for consistency (will require updating imports).

-export class FindNoteByIdResponseDto extends createZodDto(
+export class FindNoteByIdResponseDTO extends createZodDto(
   FindNoteByIdResponseSchema,
 ) {}
 
 export const BrowseNotesResponseSchema = z.array(FindNoteByIdResponseSchema);
 
-export class BrowseNotesResponseDto extends createZodDto(
+export class BrowseNotesResponseDTO extends createZodDto(
   BrowseNotesResponseSchema,
 ) {}

This is a breaking rename; confirm and update all imports/usages.


4-13: Doc language consistency.

Descriptions here are PT‑BR; others are EN. Pick one language for Swagger to avoid mixed docs.

Also applies to: 19-19

Dockerfile (1)

7-9: Pin and verify the wait script download.

Remote ADD without checksum is brittle. Prefer curl + sha256 verification.

# example
# RUN apk add --no-cache curl ca-certificates && \
#     curl -fsSL -o /wait ".../wait" && \
#     echo "<sha256>  /wait" | sha256sum -c - && chmod +x /wait
src/modules/user/domain/dtos/errors/InvalidCredentials.exception.ts (1)

1-7: Use UnauthorizedException for invalid credentials
Extending UnauthorizedException and removing the explicit 400 status aligns with HTTP semantics and NestJS conventions; existing tests rely on new InvalidCredentialsException().getStatus() and will adapt automatically.

src/app/main.ts (1)

22-25: Hide Swagger in production (or guard it)

Prevent exposing API docs publicly in prod.

-  const sharedDocument = SwaggerModule.createDocument(app, sharedSwaggerConfig);
-  SwaggerModule.setup('swagger', app, sharedDocument);
+  if (process.env.NODE_ENV !== 'production') {
+    const sharedDocument = SwaggerModule.createDocument(app, sharedSwaggerConfig);
+    SwaggerModule.setup('swagger', app, sharedDocument);
+  }
src/modules/user/infra/providers/jwt.provider.spec.ts (1)

15-28: Drop unnecessary async from this test

No awaits used; keep it synchronous.

-it('should generate a JWT given the proper data', async () => {
+it('should generate a JWT given the proper data', () => {
src/modules/user/domain/dtos/errors/EmailAlreadyRegistered.exception.ts (1)

1-7: Use 409 Conflict (ConflictException) instead of 403 for duplicate email

Matches resource-conflict semantics and common REST patterns.

-import { HttpException } from '@nestjs/common';
+import { ConflictException } from '@nestjs/common';

-export class EmailAlreadyRegisteredException extends HttpException {
+export class EmailAlreadyRegisteredException extends ConflictException {
   constructor() {
-    super('Este e-mail já está associado a uma conta.', 403);
+    super('Este e-mail já está associado a uma conta.');
   }
 }
src/shared/domain/errors/Environment.exception.ts (1)

5-9: Improve message formatting and name the error

Prefix the message with the path only when present and set a specific name to ease filtering/logging.

-    const errorMessage = error.issues
-      .map((issue) => `${issue.path.join('.')} ${issue.message}`)
-      .join('\n');
-    super(errorMessage);
+    const errorMessage = error.issues
+      .map(({ path, message }) => `${path.length ? `${path.join('.')}: ` : ''}${message}`)
+      .join('\n');
+    super(errorMessage);
+    this.name = 'EnvironmentException';
src/app/app.module.ts (1)

19-24: Provide ValidationPipe options

Enable common safeguards (strip unknown fields, transform primitives).

   providers: [
     {
       provide: APP_PIPE,
-      useClass: ValidationPipe,
+      useFactory: () =>
+        new ValidationPipe({
+          whitelist: true,
+          transform: true,
+          forbidNonWhitelisted: true,
+        }),
     },
   ],
src/modules/note/infra/db/repositories/note.repository.spec.ts (3)

5-5: Rename suite to match subject

-describe('Gym Repository Test Suites', () => {
+describe('Note Repository Test Suites', () => {

9-16: Merge duplicate beforeEach hooks (Biome warning)

-beforeEach(() => {
-  jest.useFakeTimers({ doNotFake: ['nextTick'] });
-});
-
-beforeEach(() => {
-  encrypterProvider = new EncrypterProvider();
-  repository = new NoteRepository(encrypterProvider);
-});
+beforeEach(() => {
+  jest.useFakeTimers({ doNotFake: ['nextTick'] });
+  encrypterProvider = new EncrypterProvider();
+  repository = new NoteRepository(encrypterProvider);
+});

1-25: Add meaningful tests for encryption behavior

No assertions yet. Add at least one test asserting the repository encrypts sensitive fields and can round-trip decrypt.

I can draft tests once you confirm the repository API (e.g., create, findById) signatures.

src/modules/user/infra/providers/hash.provider.spec.ts (3)

7-13: Compile the TestingModule once with beforeAll to speed up tests.
Provider is stateless; setting up the module per test is unnecessary overhead.

Apply this diff:

-  beforeEach(async () => {
+  beforeAll(async () => {
     const module: TestingModule = await Test.createTestingModule({
       providers: [HashProvider],
     }).compile();

     hashProvider = module.get<HashProvider>(HashProvider);
-  });
+  });

15-20: Avoid brittle assertion on '$' in hash string.
The presence of '$' is bcrypt‑specific; the test should remain algorithm‑agnostic.

Apply this diff:

   const hashedValue = await hashProvider.hash('password');

   expect(hashedValue).toEqual(expect.any(String));
-  expect(hashedValue).toContain('$');
+  expect(hashedValue).not.toBe('password');
+  expect(hashedValue.length).toBeGreaterThan(20);

28-33: Add a test to ensure salting (same input ≠ same hash).
Prevents regressions if salting is accidentally disabled.

Add this test:

it('should produce different hashes for the same password (salted)', async () => {
  const a = await hashProvider.hash('password');
  const b = await hashProvider.hash('password');
  expect(a).not.toBe(b);
});
src/modules/user/domain/providers/JWT.provider.ts (1)

1-7: Decouple domain from jsonwebtoken types; return a domain payload instead.
Importing JwtPayload in the domain layer couples it to an infra lib and leaks implementation details.

Apply this diff to remove the dependency and return a generic decoded shape:

-import { JwtPayload } from 'jsonwebtoken';
 import { JWTProviderDTO, JWTValidateDTO } from '../../dto/JWTProvider.dto';

 export interface JWTProviderInterface {
   generate(data: JWTProviderDTO): string;
-  validate(data: JWTValidateDTO): JwtPayload | string;
+  validate(data: JWTValidateDTO): Record<string, unknown>;
 }

Optionally, define and return a project‑level JWTPayload type (e.g., { user: { id_user: string; name?: string } } & standard claims).

src/shared/domain/errors/AllException.filter.ts (3)

12-12: Remove unnecessary async from catch; nothing is awaited.

Apply this diff:

-  async catch(exception: unknown, host: ArgumentsHost) {
+  catch(exception: unknown, host: ArgumentsHost) {

22-24: Preserve HttpException response message(s) when available.
Surface validation/user errors consistently; fall back safely for unknown errors.

Apply this diff:

-    const message =
-      exception instanceof Error ? exception.message : 'Internal Server Error';
+    const message =
+      exception instanceof HttpException
+        ? (() => {
+            const res = exception.getResponse();
+            if (typeof res === 'string') return res;
+            const msg = (res as any)?.message;
+            return Array.isArray(msg) ? msg.join(', ') : msg ?? exception.message;
+          })()
+        : 'Internal Server Error';

25-33: Avoid leaking internal messages in production; add minimal guard.
For 500s in production, prefer a generic message.

Suggested snippet (outside diff scope):

const isProd = process.env.NODE_ENV === 'production';
const safeMessage =
  isProd && status === HttpStatus.INTERNAL_SERVER_ERROR ? 'Internal Server Error' : message;
response.status(status).json({ ...responseJSON, message: safeMessage });

Also consider logging the original error via Nest Logger.

src/modules/user/dto/JWTProvider.dto.ts (1)

1-10: Make secret a config concern (not per-call DTO) or require it explicitly.
Per-call optional secret risks signing/verifying with an empty/undefined secret.

If the provider already falls back to auth.config, remove secret from the DTO:

 export type JWTProviderDTO = {
   payload: {
     user?: {
       id_user: string;
       name?: string;
     };
   };
-  secret?: string;
   expiresIn?: number | string;
 };

Otherwise, make it required and validate upstream.
To keep naming consistent, consider userId over id_user in tokens.

src/modules/note/infra/usecases/browse-notes.usecase.spec.ts (3)

20-24: Avoid constructing real crypto/hash providers in unit tests

Instantiate minimal mocks/stubs to avoid env-coupling and speed up tests. Also, EncrypterProvider uses a fixed IV for AES‑CBC (security risk); mocking here avoids accidental crypto usage in unit tests.

Example:

-    encrypterProvider = new EncrypterProvider();
-    mockRepository = new NoteRepository(encrypterProvider);
-    hashProvider = new HashProvider();
-    userRepository = new UserRepository(hashProvider, encrypterProvider);
+    mockRepository = { findByUser: jest.fn() } as unknown as NoteRepository;
+    userRepository = { findById: jest.fn() } as unknown as UserRepository;

(Architecture note: use a random per‑message IV and store it alongside ciphertext in EncrypterProvider.)


35-41: Use a color-like value for note_color to match domain intent

faker.lorem.paragraph() isn’t a color. Use faker.color.rgb() (or hex) to better reflect the schema.

-    note_color: faker.lorem.paragraph(),
+    note_color: faker.color.rgb(), // or faker.color.rgb({ prefix: '#' })

44-52: Assert repository calls, not just results

Validate that the correct dependencies are invoked with the right arguments.

   const result = await usecase.execute(mockNote.user_id);
 
   expect(result).toEqual([]);
+  expect(userRepository.findById).toHaveBeenCalledWith(mockNote.user_id);
+  expect(mockRepository.findByUser).toHaveBeenCalledWith(mockNote.user_id);
src/modules/user/infra/usecases/user-login.usecase.spec.ts (1)

64-69: Verify JWT payload shape

Assert generator is called with the expected payload to lock contract.

   jest.spyOn(jwtProvider, 'generate').mockReturnValue('valid-token');
 
   const result = await usecase.execute(data, ip);
 
   expect(result).toEqual({
@@
   });
+  expect(jwtProvider.generate).toHaveBeenCalledWith({
+    payload: { user: { id_user: mockUser.id_user, name: mockUser.name } },
+  });

Also applies to: 70-79

src/modules/note/infra/usecases/change-note-color.usecase.spec.ts (2)

39-45: Rename misleading variable and prefer a domain‑valid color

invalidData is used in successful paths; rename to payload and use a color-like string.

-  const invalidData: ChangeNoteColorRequestDto = {
+  const payload: ChangeNoteColorRequestDto = {
     note_id: faker.string.uuid(),
-    note_color: faker.color.rgb() as any,
+    note_color: faker.color.rgb(),
   };

Also update subsequent references from invalidData → payload.


81-92: Fix test description and assert repo call arguments

The test is for color change, not starring. Also ensure changeColor is called correctly.

-    it('should successfully star a note', async () => {
+    it('should successfully change note color', async () => {
@@
-      await expect(useCase.execute(userId, invalidData)).resolves.not.toThrow();
+      await expect(useCase.execute(userId, payload)).resolves.not.toThrow();
+      expect(mockNoteRepository.changeColor).toHaveBeenCalledWith(
+        payload.note_id,
+        payload.note_color,
+      );
src/modules/note/infra/usecases/change-note-color.usecase.ts (3)

21-27: Tighten return type to reflect thrown exceptions.

This use case throws on errors; it does not return exception instances. Prefer Promise<boolean>.

-  ): Promise<
-    | boolean
-    | ColorAlreadySetException
-    | UserNotFoundException
-    | UnauthorizedException
-    | NoteNotFoundException
-  > {
+  ): Promise<boolean> {

1-1: Use Forbidden (403) for ownership violations, not Unauthorized (401).

Ownership check should return 403. Keep 401 for unauthenticated requests.

-import { Inject, UnauthorizedException } from '@nestjs/common';
+import { Inject, ForbiddenException, UnauthorizedException } from '@nestjs/common';
...
-    if (note.user_id !== user.id_user) throw new UnauthorizedException();
+    if (note.user_id !== user.id_user) throw new ForbiddenException();

Also applies to: 36-36


46-46: Minor: Prefer explicit Boolean conversion.

Boolean(updated) is clearer than !!updated.

-    return !!updated;
+    return Boolean(updated);
src/modules/user/infra/providers/jwt.provider.ts (1)

15-18: Constrain verification algorithm and narrow payload type.

  • Restrict algorithms to prevent alg confusion.
  • Consider a custom payload type including user.
-  public validate({ token, secret }: JWTValidateDTO): JwtPayload | string {
-    const decoded = verify(token, secret);
+  public validate({ token, secret }: JWTValidateDTO): JwtPayload | string {
+    const decoded = verify(token, secret, { algorithms: ['HS256'] });
     return decoded;
   }

Optionally define:

type AppJwtPayload = JwtPayload & { user?: { id_user: string; name?: string } };

and propagate through the interface.

src/modules/note/infra/usecases/edit-note.usecase.spec.ts (2)

37-40: Avoid shared mutable test data; make each test self-contained.

Mutating invalidData/data introduces inter-test coupling. Create local payloads per test.

Example pattern:

const payload: EditNoteBodyDTO = { note_title: faker.string.alpha(10), note_text: faker.string.alpha(10) };
await expect(useCase.execute(noteId, userId, payload)).rejects.toThrow(...);

Also applies to: 49-54, 58-66, 68-76, 78-88, 92-105


94-105: Strengthen success case: return value assertions and realistic mocks.

Mock edit with a full entity and assert the mapped DTO.

-      jest.spyOn(mockNoteRepository, 'edit').mockResolvedValueOnce({} as any);
-      await expect(
-        useCase.execute(noteId, userId, invalidData),
-      ).resolves.not.toThrow();
+      const edited = {
+        id_note: faker.string.uuid(),
+        note_title: faker.string.alpha(10),
+        note_text: faker.string.alpha(20),
+        note_color: 'yellow',
+        user_id: userId,
+        starred: false,
+        createdAt: new Date(),
+        updatedAt: new Date(),
+      };
+      jest.spyOn(mockNoteRepository, 'edit').mockResolvedValueOnce(edited as any);
+      await expect(useCase.execute(noteId, userId, {
+        note_title: edited.note_title,
+        note_text: edited.note_text,
+      } as any)).resolves.toEqual({
+        id_note: edited.id_note,
+        note_title: edited.note_title,
+        note_text: edited.note_text,
+        note_color: edited.note_color,
+        user_id: edited.user_id,
+        starred: edited.starred,
+        created_at: edited.createdAt,
+        updated_at: edited.updatedAt,
+      });

Also applies to: 100-101

src/modules/user/infra/usecases/create-user.usecase.spec.ts (1)

43-52: Use per-test input objects; assert hashing is used.

  • Avoid mutating shared data/mockdata across tests.
  • In the success case, stub hash to return a different value (e.g., 'hashed') and assert create receives it.

Example additions:

jest.spyOn(hashProvider, 'hash').mockResolvedValueOnce('hashed');
...
expect(mockUserRepository.create).toHaveBeenCalledWith(
  expect.objectContaining({ password: 'hashed' })
);

Also applies to: 64-72, 74-82, 84-92, 93-101, 105-129

src/shared/config/app.config.ts (1)

20-36: Optional: Use Zod coercion for numeric envs and add stronger validation.

  • Replace manual Number(...) with z.coerce.number() for PORT/Redis ports.
  • Consider CRYPTO_IV length constraints (e.g., 16 bytes for AES-256-CBC).
  • Optionally validate FRONTEND_URL with .url().

Example schema tweaks:

-  PORT: z.number(),
+  PORT: z.coerce.number(),
...
-  REDIS_PRODUCTION_PORT: z.number().optional(),
+  REDIS_PRODUCTION_PORT: z.coerce.number().optional(),
...
-  REDIS_PORT: z.number().optional(),
+  REDIS_PORT: z.coerce.number().optional(),
+  CRYPTO_IV: z.string().length(16),
+  FRONTEND_URL: z.string().url(),

And pass raw strings into parse (no pre-Number casting).

Also applies to: 48-49, 61-62, 71-73

src/modules/note/infra/modules/note.module.ts (3)

31-31: Remove duplicate provider entry for UserRepository.

It's listed twice; keep a single entry.

     BrowseNotesUsecase,
-    UserRepository,
     ChangeNoteColorUsecase,

Also applies to: 38-38


57-78: Unify route param naming (:noteId vs :cuid).

Use one param name across find/edit/delete for consistency and clearer Swagger.

Would you like me to align controller @Param names and middleware paths to :noteId?


24-26: Consider importing shared/user modules instead of re-declaring shared providers.

Import/export Encrypter/Date/JWT/User providers from SharedModule/UserModule to avoid multiple instances and ease maintenance.

src/modules/user/http/controllers/user.controller.spec.ts (2)

21-26: Remove unused variables (controller, jwtToken) or use them.

Avoid dead code in tests.

-  let controller: UserController;
-
   let app: INestApplication;
   let prisma: PrismaProvider;
-  let jwtToken: string;

51-53: No-op global filters call.

useGlobalFilters() without args is a no-op; remove or pass filters.

-    app.useGlobalFilters();
src/modules/user/infra/providers/hash.provider.ts (1)

1-16: Make bcrypt cost configurable and avoid name shadowing.

  • Externalize salt rounds to config (e.g., ENV or app config) for security/perf tuning.
  • Alias imports to avoid confusion with param name “hash”.
-import { compare, hash } from 'bcryptjs';
+import { compare as bcryptCompare, hash as bcryptHash } from 'bcryptjs';
@@
   public async hash(payload: string): Promise<string> {
-    const hashedValue = await hash(payload, 10);
+    const rounds = Number(process.env.BCRYPT_ROUNDS ?? 10);
+    const hashedValue = await bcryptHash(payload, rounds);
@@
   public async compare(payload: string, hash: string): Promise<boolean> {
-    const isHashEqual = await compare(payload, hash);
+    const isHashEqual = await bcryptCompare(payload, hash);
src/modules/note/infra/usecases/find-note-by-id.usecase.spec.ts (1)

25-27: Merge duplicate beforeEach hooks.

Keep a single beforeEach to set timers and initialize test state.

-  beforeEach(() => {
-    jest.useFakeTimers({ doNotFake: ['nextTick'] });
-  });
-
-  beforeEach(async () => {
+  beforeEach(async () => {
+    jest.useFakeTimers({ doNotFake: ['nextTick'] });

Also applies to: 29-34

src/modules/note/infra/usecases/create-note.usecase.spec.ts (2)

22-29: Merge duplicate beforeEach hooks.

One hook is enough; initialize timers and test doubles there.

-  beforeEach(() => {
-    jest.useFakeTimers({ doNotFake: ['nextTick'] });
-  });
-
-  beforeEach(async () => {
+  beforeEach(async () => {
+    jest.useFakeTimers({ doNotFake: ['nextTick'] });

1-10: Remove unused imports.

UnauthorizedException and UnprocessableDataException aren’t used here.

-import { UnauthorizedException } from '@nestjs/common';
@@
-import { UnprocessableDataException } from '../../../../shared/domain/errors/UnprocessableData.exception';
src/modules/user/domain/dtos/requests/CreateUser.request.dto.ts (1)

18-21: Constrain id format if applicable.

If user IDs are CUIDs/UUIDs, assert it in the schema to prevent leaking internal formats.

Example (CUID):

-  id: z.string().describe('ID do usuário criado'),
+  id: z.string().cuid().describe('ID do usuário criado'),

If UUID, use .uuid() instead. Confirm the intended format before changing.

src/modules/user/http/middlewares/Auth.middleware.ts (2)

5-5: Fix typo in exception name (Eception → Exception) across codebase.

Consistent naming improves readability and discoverability.

If you rename the class to BadTokenException, update imports here and all throw sites.


24-24: Avoid reading env directly in request path; inject config.

Use Nest ConfigService (or a dedicated settings provider) to access APP_SECRET and fail fast on misconfig at boot.

src/modules/note/infra/usecases/browse-notes.usecase.ts (2)

16-24: Align typing with control flow: remove exception types from return union.

This use case throws on error; it never returns exceptions.

-  async execute(
-    cuid: string,
-  ): Promise<BrowseNotesResponseDto | UserNotFoundException> {
+  async execute(cuid: string): Promise<BrowseNotesResponseDto> {

17-18: Clarify parameter name.

cuid here is a user id; rename to user_id for readability unless it is strictly a CUID type everywhere.

src/modules/note/infra/usecases/find-note-by-id.usecase.ts (1)

16-18: Narrow the return type to the success DTO only.

Exceptions are thrown, not returned.

-  ): Promise<
-    FindNoteByIdResponseDto | NoteNotFoundException | UnauthorizedException
-  > {
+  ): Promise<FindNoteByIdResponseDto> {

(Update the imported exception type above per the prior comment.)

src/modules/note/infra/usecases/star-note.usecase.ts (2)

10-15: Mark injected deps as readonly

Minor immutability/readability improvement.

-    private noteRepository: NoteRepository,
+    private readonly noteRepository: NoteRepository,
@@
-    private userRepository: UserRepository,
+    private readonly userRepository: UserRepository,

17-25: Narrow return type; exceptions are thrown, not returned

The method never resolves to an exception instance. Declare Promise.

-  ): Promise<
-    | boolean
-    | NoteNotFoundException
-    | UserNotFoundException
-    | UnauthorizedException
-  > {
+  ): Promise<boolean> {
src/modules/note/infra/usecases/search-notes.usecase.spec.ts (1)

15-27: Consolidate duplicate beforeEach and move clearAllMocks to afterEach

Removes lint warning (noDuplicateTestHooks) and keeps hooks tidy.

-beforeEach(() => {
-  jest.useFakeTimers({ doNotFake: ['nextTick'] });
-});
-
-beforeEach(async () => {
+beforeEach(async () => {
+  jest.useFakeTimers({ doNotFake: ['nextTick'] });
   encrypterProvider = new EncrypterProvider();
   mockRepository = new NoteRepository(encrypterProvider);
   hashProvider = new HashProvider();
   userRepository = new UserRepository(hashProvider, encrypterProvider);
-
   usecase = new SearchNotesUsecase(mockRepository, userRepository);
-  jest.clearAllMocks();
 });

Add afterEach to clean mocks (outside this hunk):

afterEach(() => {
  jest.clearAllMocks();
  jest.restoreAllMocks();
});
src/modules/user/domain/dtos/repositories/User.repository.ts (1)

4-14: Interface shape: consider returning concrete User where possible and make encryptedFields readonly

Returning Partial from reads makes downstream code defensive and led to inconsistent ownership checks elsewhere. Prefer Promise<User | null> and readonly encryptedFields to stabilize contracts. If changing now is too invasive, add a typed pick that matches actual projections (e.g., Pick<User, 'id_user' | 'email' | ...>).

src/modules/note/infra/usecases/delete-note.usecase.spec.ts (2)

19-29: Consolidate duplicate beforeEach; move timers into the remaining one

Fixes lint warning and reduces hook churn.

-beforeEach(() => {
-  jest.useFakeTimers({ doNotFake: ['nextTick'] });
-});
-
-beforeEach(async () => {
+beforeEach(async () => {
+  jest.useFakeTimers({ doNotFake: ['nextTick'] });
   encrypterProvider = new EncrypterProvider();
   hashProvider = new HashProvider();
   mockUserRepository = new UserRepository(hashProvider, encrypterProvider);
   mockNoteRepository = new NoteRepository(encrypterProvider);
   useCase = new DeleteNoteUsecase(mockUserRepository, mockNoteRepository);
-  jest.clearAllMocks();
 });

Add afterEach to clean mocks (outside this hunk):

afterEach(() => {
  jest.clearAllMocks();
  jest.restoreAllMocks();
});

87-101: Set mockNote.user_id before wiring spies

Avoid relying on object aliasing timing; makes the test intention clearer.

-    it('should return true when a note is successfully deleted', async () => {
-      jest
-        .spyOn(mockNoteRepository, 'findById')
-        .mockResolvedValueOnce(mockNote);
-      jest
-        .spyOn(mockUserRepository, 'findById')
-        .mockResolvedValueOnce({} as any);
-      jest.spyOn(mockNoteRepository, 'delete').mockResolvedValueOnce(true);
-
-      mockNote.user_id = user_id;
+    it('should return true when a note is successfully deleted', async () => {
+      mockNote.user_id = user_id;
+      jest
+        .spyOn(mockNoteRepository, 'findById')
+        .mockResolvedValueOnce(mockNote);
+      jest
+        .spyOn(mockUserRepository, 'findById')
+        .mockResolvedValueOnce({} as any);
+      jest.spyOn(mockNoteRepository, 'delete').mockResolvedValueOnce(true);
src/modules/user/infra/db/repositories/User.repository.spec.ts (1)

10-19: Consolidate duplicate beforeEach

Removes lint warning and keeps setup simple.

-beforeEach(() => {
-  jest.useFakeTimers({ doNotFake: ['nextTick'] });
-});
-
-beforeEach(() => {
+beforeEach(() => {
+  jest.useFakeTimers({ doNotFake: ['nextTick'] });
   repository = new UserRepository(
     new HashProvider(),
     new EncrypterProvider(),
   );
});
src/modules/note/infra/usecases/star-note.usecase.spec.ts (2)

32-34: Remove unnecessary fake timers.

Timers aren’t used; drop this to reduce global test side effects.

-afterAll(() => {
-  jest.useRealTimers();
-});
+// No fake timers used; no teardown needed.

24-29: Avoid env‑dependent providers in unit tests.

Instantiating real EncrypterProvider/HashProvider and concrete repositories couples tests to env (CRYPTO_SECRET/IV). Prefer pure mocks for NoteRepository/UserRepository.

I can replace these with typed jest mocks (minimal interface stubs) if you want.

src/modules/user/domain/dtos/controllers/user.controller.dto.ts (1)

6-21: Controller contract mixes Express Response and DTOs; align with Nest patterns.

Returning Response couples to Express and complicates testing. Prefer returning DTOs and letting Nest handle the response; also keep param order consistent.

Would you like me to generate a DTO-first interface and update the controller signatures accordingly?

src/modules/note/infra/usecases/create-note.usecase.ts (1)

30-46: Defensive default for starred.

If DTO marks “starred” optional, default to false when creating notes.

-      starred: data.starred,
+      starred: data.starred ?? false,
README.md (4)

56-61: Clarify database engine and provide correct DSN examples.

PR uses MySQL; either lock to MySQL or show both DSNs explicitly.

-Since we're using Prisma, you only need to set the DB's connection string in the `DATABASE_URL` variable. The application supports both PostgreSQL and MySQL, so you can choose any of them. The connection string should be set in the following way:
+Since we're using Prisma, set the DB connection string in `DATABASE_URL`.
+Examples:
@@
-DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public"
+# MySQL
+DATABASE_URL="mysql://USER:PASSWORD@HOST:3306/DATABASE"
+# PostgreSQL
+# DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/DATABASE?schema=public"

30-38: Tighten Redis wording.

“Redis CLI” isn’t a database; clarify local server via Docker/redis-server and credentials behavior.

-If you're going for Redis CLI or a Redis Image being run on Docker, the application won't require user and password to authenticate you in the database. It's enough to only set the following variables:
+If you're running a local Redis server (e.g., Docker image or redis-server), authentication is typically disabled by default. In that case, set only:

87-87: Link to Node’s built‑in crypto docs (not the npm “crypto” stub).

-- [Crypto](https://www.npmjs.com/package/crypto)
+- [Node.js Crypto](https://nodejs.org/api/crypto.html)

94-94: Use the correct Faker package.

The repo uses @faker-js/faker; update the link/name.

-- [Faker](https://www.npmjs.com/package/faker)
+- [@faker-js/faker](https://www.npmjs.com/package/@faker-js/faker)
src/modules/note/http/controllers/note.controller.spec.ts (3)

12-12: Remove unused import.

NoteController is not referenced.

-import { NoteController } from './note.controller';

23-23: Remove unused variable.

controller is declared but never used.

-  let controller: NoteController;

47-47: Drop no-op global filters registration.

app.useGlobalFilters() with no filters is a no-op; remove for clarity or pass actual filters.

-    app.useGlobalFilters();
+    // app.useGlobalFilters(new AllExceptionsFilter()); // if/when needed
src/modules/note/domain/dtos/controllers/note.controller.dto.ts (1)

8-49: Prefer Nest-style controller contracts (return DTOs, avoid Response).

Returning Express Response and threading req/res through the interface tightly couples to Express and complicates testing. Consider returning DTOs and letting Nest handle response mapping; keep errors as thrown exceptions, not part of the return union.

src/modules/note/infra/usecases/edit-note.usecase.ts (4)

42-42: Use ForbiddenException (403) for ownership violations.

The user is authenticated; lack of ownership is authorization, not authentication.

-    if (noteExists.user_id !== user_id) throw new UnauthorizedException();
+    if (noteExists.user_id !== user_id) throw new ForbiddenException();

1-1: Align imports with the change above.

-import { Inject, UnauthorizedException } from '@nestjs/common';
+import { ForbiddenException } from '@nestjs/common';

15-19: Remove redundant @Inject with type tokens.

Constructor injection by type doesn’t need @Inject here.

-  constructor(
-    @Inject()
-    private readonly userRepository: UserRepository,
-    @Inject()
-    private readonly noteRepository: NoteRepository,
-  ) {}
+  constructor(
+    private readonly userRepository: UserRepository,
+    private readonly noteRepository: NoteRepository,
+  ) {}

25-29: Document thrown NoteNotFoundException in the signature (for consistency).

You already throw it below; add it to the union for clarity (even though TS doesn’t enforce thrown types).

   ): Promise<
     | EditNoteResponseDTO
     | UnauthorizedException
     | UserNotFoundException
     | UnprocessableDataException
+    | NoteNotFoundException
   > {
src/modules/note/domain/dtos/repositories/Note.repository.ts (2)

14-14: Parameter name typo: this should be id_note.

Prevents confusion with user id.

-  findById(id_user: string): Promise<FindNoteByIdResponseDto | null>;
+  findById(id_note: string): Promise<FindNoteByIdResponseDto | null>;

11-27: Repository return-type consistency.

Some methods return DTOs while others return entities (Note). Consider standardizing on entities at repository layer and mapping to DTOs in use cases/controllers.

src/modules/user/infra/usecases/create-user.usecase.ts (3)

42-47: Avoid mixing await with .then; simplify for readability.

-    await this.userRepository
-      .findByEmail(data.email)
-      .then((emailAlreadyRegisteredByUser) => {
-        if (emailAlreadyRegisteredByUser)
-          throw new EmailAlreadyRegisteredException();
-      });
+    const emailAlreadyRegisteredByUser =
+      await this.userRepository.findByEmail(data.email);
+    if (emailAlreadyRegisteredByUser)
+      throw new EmailAlreadyRegisteredException();

22-24: Consistent DI style: drop @Inject() when injecting by type.

-    @Inject()
-    private userRepository: UserRepository,
+    private userRepository: UserRepository,

1-1: Remove unused Inject import (after the change above).

-import { Inject, UnprocessableEntityException } from '@nestjs/common';
+import { UnprocessableEntityException } from '@nestjs/common';
src/modules/user/infra/usecases/user-login.usecase.ts (1)

30-31: Redundant checks for id_user and name

The checks for !user.id_user and !user.name are redundant if the database schema enforces these as required fields. The !user check should be sufficient.

-    if (!user || !user.id_user || !user.name)
+    if (!user)
       throw new InvalidCredentialsException();
src/modules/user/infra/db/repositories/user.repository.ts (2)

50-60: Potential performance issue with encrypted email lookups

Finding users by encrypted email requires exact string matching of the encrypted value. This prevents database indexing from working efficiently and could cause performance issues at scale.

Consider using a deterministic encryption scheme for the email field or maintaining a separate hashed email column for lookups. This would allow for efficient indexing while maintaining security.


112-112: Unnecessary object spread operation

The spread operation { ...userData } creates an unnecessary copy since you're not modifying the original object structure.

-    const { ...userData } = data;
+    const userData = data;
docker-compose.yml (2)

28-30: Deprecated links directive

The links directive is deprecated in Docker Compose. Services can communicate using service names directly through the default network.

-    links:
-      - database
-      - redis
    depends_on:

40-40: Missing newline at end of file

Add a newline character at the end of the file to follow YAML best practices.

      - PORT=3333
+
src/modules/user/http/controllers/user.controller.ts (1)

75-77: Consider using middleware for authentication checks.

The authentication check if (req.user) is repeated across methods. Consider extracting this into a middleware or guard for better separation of concerns and consistency.

src/modules/note/infra/db/repositories/note.repository.ts (1)

28-35: Search functionality has potential performance issues.

The search method loads all user notes into memory and filters them client-side. For users with many notes, this could be inefficient.

Consider implementing database-level search or adding pagination:

// Alternative approach using database search
async search(id_user: string, content: string): Promise<BrowseNotesResponseDto> {
  const notes = await prisma.note.findMany({
    where: { 
      user_id: id_user, 
      deletedAt: null,
      OR: [
        { note_title: { contains: content } },
        { note_text: { contains: content } }
      ]
    },
  });
  
  // Note: This would require storing search keywords unencrypted
  // or implementing searchable encryption
}
src/modules/note/http/controllers/note.controller.ts (2)

164-164: Return type should be more specific.

The return type any | Response | AllExceptionsFilterDTO is too broad. Consider using a union of specific response types.

Apply this diff for better type safety:

-  ): Promise<any | Response | AllExceptionsFilterDTO> {
+  ): Promise<Response | AllExceptionsFilterDTO> {

Also applies to: 209-209


90-90: Authentication checks are consistent but repetitive.

All endpoints properly check for authentication, but this pattern is repeated throughout. Consider using a guard or middleware for cleaner code.

Also applies to: 121-121, 152-152, 165-165, 210-210, 249-249, 296-296, 344-344, 382-382

Comment thread .env.example
Comment on lines +1 to +8
# Application Environment Variables
NODE_ENV=
PORT=
API_URL=""
FRONTEND_URL=""
DATABASE_URL=""
APP_SECRET=

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Sync with docs, remove quotes, add missing DATABASE_PROVIDER, and end with newline.

Aligns with PULL_REQUEST.md and quiets dotenv-linter. Quotes aren’t needed here.

 # Application Environment Variables
-NODE_ENV=
-PORT=
-API_URL=""
-FRONTEND_URL=""
-DATABASE_URL=""
-APP_SECRET=
+NODE_ENV=
+PORT=
+API_URL=
+FRONTEND_URL=
+DATABASE_URL=
+DATABASE_PROVIDER=
+APP_SECRET=
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Application Environment Variables
NODE_ENV=
PORT=
API_URL=""
FRONTEND_URL=""
DATABASE_URL=""
APP_SECRET=
# Application Environment Variables
NODE_ENV=
PORT=
API_URL=
FRONTEND_URL=
DATABASE_URL=
DATABASE_PROVIDER=
APP_SECRET=
🧰 Tools
🪛 dotenv-linter (3.3.0)

[warning] 4-4: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)


[warning] 4-4: [UnorderedKey] The API_URL key should go before the NODE_ENV key

(UnorderedKey)


[warning] 5-5: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)


[warning] 5-5: [UnorderedKey] The FRONTEND_URL key should go before the NODE_ENV key

(UnorderedKey)


[warning] 6-6: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)


[warning] 6-6: [UnorderedKey] The DATABASE_URL key should go before the FRONTEND_URL key

(UnorderedKey)


[warning] 7-7: [UnorderedKey] The APP_SECRET key should go before the DATABASE_URL key

(UnorderedKey)

🤖 Prompt for AI Agents
In .env.example lines 1-8, the file needs to be synced with PULL_REQUEST.md and
dotenv-linter expectations: remove the unnecessary double quotes around API_URL
and FRONTEND_URL, add a new DATABASE_PROVIDER= line (matching docs), ensure all
env entries are present (NODE_ENV, PORT, API_URL, FRONTEND_URL,
DATABASE_PROVIDER, DATABASE_URL, APP_SECRET) and finish the file with a trailing
newline; update the lines accordingly so values are blank without quotes and
append the newline at EOF.

Comment thread .env.example
API_URL=""
FRONTEND_URL=""
DATABASE_URL=""
APP_SECRET=

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Require high-entropy JWT secret.

Document that APP_SECRET must be at least 256-bit random. Example generation:

  • macOS/Linux:
    • openssl rand -hex 32
    • node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
🧰 Tools
🪛 dotenv-linter (3.3.0)

[warning] 7-7: [UnorderedKey] The APP_SECRET key should go before the DATABASE_URL key

(UnorderedKey)

🤖 Prompt for AI Agents
In .env.example around line 7, APP_SECRET currently empty; update the file to
require a high-entropy JWT secret (at least 256 bits / 32 bytes) and document
generation examples. Replace the blank value with a comment explaining the
requirement and add example CLI commands to generate a 32-byte hex secret
(openssl rand -hex 32 and node -e
"console.log(require('crypto').randomBytes(32).toString('hex'))") so developers
know to use a 64-character hex string for APP_SECRET.

Comment thread .env.example
Comment on lines +12 to +15
# Cryptography Settings
CRYPTO_SECRET=
CRYPTO_IV=

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Do not use a static IV in AES. Generate a random IV per encryption and store it alongside the ciphertext.

A fixed CRYPTO_IV enables IV reuse, breaking confidentiality (CBC) or authenticity (GCM). Keep CRYPTO_SECRET in env, but derive a fresh IV with crypto.randomBytes() for each operation and persist it with the payload.


🏁 Script executed:

#!/bin/bash
# Verify whether a static IV from env is used.
rg -nP -C3 'CRYPTO_IV' src
rg -nP -C3 'createCipheriv\(|createDecipheriv\(' src
rg -nP -C3 'aes-256-' src
rg -nP -C3 'getAuthTag\(|setAuthTag\(' src

Length of output: 3181


Use a random IV per encryption instead of the static CRYPTO_IV
In src/shared/infra/providers/Encrypter.provider.ts you’re currently doing

this.iv = Buffer.from(appConfigurations.CRYPTO_IV);

createCipheriv(this.algorithm, key, this.iv)
createDecipheriv(this.algorithm, key, this.iv)

Replace that with, in encrypt(),

const iv = crypto.randomBytes(16);
const cipher = createCipheriv(this.algorithm, key, iv);
// prepend or include iv in the returned ciphertext

and in decrypt(), extract the IV from the payload before calling createDecipheriv.

🧰 Tools
🪛 dotenv-linter (3.3.0)

[warning] 14-14: [UnorderedKey] The CRYPTO_IV key should go before the CRYPTO_SECRET key

(UnorderedKey)

🤖 Prompt for AI Agents
.env.example lines 12-15: the current pattern of using a static CRYPTO_IV is
insecure; update Encrypter.provider.ts to generate a random IV per encryption
and include it with the ciphertext. In encrypt(), generate a random 16-byte IV,
use it in createCipheriv, and prepend or otherwise include the IV (e.g., prefix)
in the returned encrypted payload; in decrypt(), extract the IV from the
incoming payload before calling createDecipheriv and use the extracted IV to
decrypt. Ensure key derivation/formatting stays the same and update any
consumers/tests to handle the combined IV+ciphertext format.

Comment thread Dockerfile
Comment on lines +2 to +11
WORKDIR /usr/src/corenotes-api
COPY package.json ./
COPY package-lock.json ./
COPY . .
EXPOSE 3333
ENV WAIT_VERSION 2.9.0
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/$WAIT_VERSION/wait /wait
RUN chmod +x /wait

USER node No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Non-root runtime will lack write perms; chown workdir.

You switch to USER node but never chown the app dir; npm install or writing cache/logs will fail.

 COPY . .
 EXPOSE 3333
 ENV WAIT_VERSION 2.9.0
 ADD https://github.com/ufoscout/docker-compose-wait/releases/download/$WAIT_VERSION/wait /wait
 RUN chmod +x /wait
+RUN chown -R node:node /usr/src/corenotes-api
 
 USER node
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
WORKDIR /usr/src/corenotes-api
COPY package.json ./
COPY package-lock.json ./
COPY . .
EXPOSE 3333
ENV WAIT_VERSION 2.9.0
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/$WAIT_VERSION/wait /wait
RUN chmod +x /wait
USER node
WORKDIR /usr/src/corenotes-api
COPY package.json ./
COPY package-lock.json ./
COPY . .
EXPOSE 3333
ENV WAIT_VERSION 2.9.0
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/$WAIT_VERSION/wait /wait
RUN chmod +x /wait
RUN chown -R node:node /usr/src/corenotes-api
USER node
🤖 Prompt for AI Agents
In Dockerfile around lines 2-11, you switch to USER node but never chown the
application directory, so the non-root user will lack write permissions; fix by
changing ownership of /usr/src/corenotes-api to the node user (chown -R
node:node /usr/src/corenotes-api) after copying files and before switching to
USER node (or alternatively run npm install as root then chown the directory),
ensuring any subsequent writes (npm install, cache, logs) succeed under the node
user.

Comment thread package.json
Comment on lines +46 to +49
"crypto": "^1.0.1",
"date-fns": "^4.1.0",
"dot-env": "^0.0.1",
"dotenv": "^17.2.1",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove unsafe/typosquat packages: do not depend on npm ‘crypto’ or ‘dot-env’.

Node’s crypto is built-in; the npm ‘crypto’ and ‘dot-env’ packages are unnecessary and risky.

-    "crypto": "^1.0.1",
-    "dot-env": "^0.0.1",

Also ensure imports use import crypto from 'node:crypto' or import * as crypto from 'crypto', and only dotenv for env loading.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"crypto": "^1.0.1",
"date-fns": "^4.1.0",
"dot-env": "^0.0.1",
"dotenv": "^17.2.1",
"date-fns": "^4.1.0",
"dotenv": "^17.2.1",
🤖 Prompt for AI Agents
In package.json around lines 46-49, remove the unsafe/typosquat dependencies
"crypto" and "dot-env" from dependencies and update the lockfile (run npm/yarn
uninstall and regen lockfile). Search the repo for imports of 'crypto' and
replace them with the built-in form (either import crypto from 'node:crypto' or
import * as crypto from 'crypto') and remove any imports of 'dot-env', ensuring
only 'dotenv' is used for env loading (and that dotenv is loaded correctly at
app entry). After changes, run tests/lint and rebuild to confirm no remaining
references to the removed packages.

Comment on lines +4 to +11
export const AllExceptionFilterSchema = z
.object({
message: z.string().describe('Descrição do erro'),
statusCode: z.number().describe('Código do erro'),
timestamp: z.string().describe('Timestamp do erro'),
})
.required()
.describe('Resposta de erro genérico');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Align error DTO with actual filter output; tighten types.

  • Add path (commonly returned by error filters).
  • Use .int() for statusCode.
  • Use .datetime() for ISO timestamps.
  • Make schema strict; .required() is redundant here.
 export const AllExceptionFilterSchema = z
   .object({
-    message: z.string().describe('Descrição do erro'),
-    statusCode: z.number().describe('Código do erro'),
-    timestamp: z.string().describe('Timestamp do erro'),
+    message: z.string().describe('Descrição do erro'),
+    statusCode: z.number().int().describe('Código do erro'),
+    timestamp: z.string().datetime().describe('Timestamp do erro (ISO-8601)'),
+    path: z.string().describe('Path da requisição'),
   })
-  .required()
+  .strict()
   .describe('Resposta de erro genérico');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const AllExceptionFilterSchema = z
.object({
message: z.string().describe('Descrição do erro'),
statusCode: z.number().describe('Código do erro'),
timestamp: z.string().describe('Timestamp do erro'),
})
.required()
.describe('Resposta de erro genérico');
export const AllExceptionFilterSchema = z
.object({
message: z.string().describe('Descrição do erro'),
statusCode: z.number().int().describe('Código do erro'),
timestamp: z.string().datetime().describe('Timestamp do erro (ISO-8601)'),
path: z.string().describe('Path da requisição'),
})
.strict()
.describe('Resposta de erro genérico');
🤖 Prompt for AI Agents
In src/shared/domain/dtos/errors/AllException.filter.dto.ts around lines 4 to
11, the DTO does not match the actual filter output and uses loose types: add a
path field (z.string()) to reflect returned error paths, change statusCode to
z.number().int(), change timestamp to z.string().datetime() (ISO), remove the
redundant .required(), and make the object strict (use .strict()) so no extra
properties are allowed; keep descriptions for each field.

Comment on lines +1 to +9
export interface EncrypterEncryptDTO {
content: string;
secret?: string;
}

export interface EncrypterDecryptDTO {
content: string;
secret?: string;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Static IV in env is insecure; include IV (and auth tag) in DTOs and use per-message random IV.

PR setup references CRYPTO_IV in .env, which implies a fixed IV. For AES modes (CBC/GCM/CTR), a per-encryption random IV/nonce is mandatory; reusing IVs breaks confidentiality/integrity. Propagate IV through DTOs and store it alongside ciphertext. Prefer AES-256-GCM and persist iv + authTag.

-export interface EncrypterEncryptDTO {
-  content: string;
-  secret?: string;
-}
+export interface EncrypterEncryptDTO {
+  readonly content: string;
+  readonly secret?: string; // KMS/env-managed key OK, but IV must be per-message
+  readonly iv?: string;     // optional override; if omitted, provider MUST generate cryptographically random IV
+  readonly aad?: string;    // optional: for AES-GCM Additional Authenticated Data
+}
 
-export interface EncrypterDecryptDTO {
-  content: string;
-  secret?: string;
-}
+export interface EncrypterDecryptDTO {
+  readonly content: string; // ciphertext (e.g., base64)
+  readonly secret?: string;
+  readonly iv: string;      // required to decrypt
+  readonly authTag?: string; // required if using AES-GCM
+  readonly aad?: string;
+}

Additionally, define result types returned by the provider:

export interface EncrypterEncryptResultDTO {
  ciphertext: string;
  iv: string;
  authTag?: string; // for GCM
}

Verification checklist:

  • Provider generates a random IV per encryption (e.g., crypto.randomBytes(12) for GCM).
  • IV and authTag are stored with the ciphertext (DB columns or serialized blob).
  • Decrypt path uses the stored IV/authTag; env only supplies the key, not the IV.

🏁 Script executed:

#!/bin/bash
set -e

echo "Searching for CRYPTO_IV references..."
rg -n 'CRYPTO_IV' -C3 .

echo "Searching for randomBytes usage..."
rg -n 'randomBytes' -C3 .

echo "Searching for create(Cipher|Decipher)iv usage..."
rg -n -P 'create(Cipher|Decipher)iv' -C3 .

echo "Locating Encrypter provider implementation..."
if file=$(fd --ignore-case 'Encrypter.provider.ts' 2>/dev/null); then
  echo "Found: $file"
  sed -n '1,200p' "$file"
else
  echo "Encrypter.provider.ts not found via fd; trying find..."
  file=$(find . -type f -path '*Encrypter*provider.ts' | head -n1)
  if [ -n "$file" ]; then
    echo "Found via find: $file"
    sed -n '1,200p' "$file"
  else
    echo "Encrypter provider file not found"
  fi
fi

Length of output: 3176


Replace the static IV with a per‐encryption random IV and include the IV/authTag in your DTOs
The current provider (src/shared/infra/providers/Encrypter.provider.ts:16) reads a fixed IV from CRYPTO_IV, which breaks confidentiality when reused.

  • In the provider’s encrypt method, generate a cryptographically secure random IV (e.g., crypto.randomBytes(16) or 12 bytes for GCM) for each call and, if using AES-GCM, collect the authTag.
  • Update EncrypterEncryptDTO/Result to carry the IV (and authTag for GCM) alongside the ciphertext.
  • Update EncrypterDecryptDTO to require the IV (and authTag) so decryption uses the stored values, with the env only supplying the secret key.

timestamp: z.string().describe('Timestamp da verificação do HealthCheck'),
});

export class HealthCheckDto extends createZodDto(HealthCheckSchema) {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Confirm intent: request vs response DTO.

Health check typically returns this shape; it’s unusual as a request body. Validate actual usage and consider moving to responses/* if it’s a response DTO.


🏁 Script executed:

#!/bin/bash
# Locate usages to confirm role (request/response).
rg -nP -C2 '\bHealthCheckDto\b' --type=ts

Length of output: 1205


🏁 Script executed:

sed -n '1,200p' src/shared/http/controllers/shared.controller.ts

Length of output: 1555


🏁 Script executed:

#!/bin/bash
sed -n '1,200p' src/shared/domain/dtos/requests/HealthCheck.request.dto.ts

Length of output: 479


Move HealthCheckDto to the response DTOs folder. HealthCheckDto is exclusively used for the GET /health-check response schema, not as a request body—relocate src/shared/domain/dtos/requests/HealthCheck.request.dto.ts to src/shared/domain/dtos/responses/HealthCheck.response.dto.ts.

🤖 Prompt for AI Agents
In src/shared/domain/dtos/requests/HealthCheck.request.dto.ts around line 10,
the DTO is placed in the requests folder but is only used as a response; move it
to src/shared/domain/dtos/responses/HealthCheck.response.dto.ts. Create the new
file with the same class definition (rename file but keep class name or adjust
export if you have naming conventions), update all imports across the codebase
from the old requests path to the new responses path (including barrel/index
files), and delete the original requests file to avoid duplicate exports. Ensure
CI/import checks pass after the rename.

Comment on lines +9 to +10
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify global registration of the filter.
Ensure it’s applied (via app.useGlobalFilters or APP_FILTER).

Run:


🏁 Script executed:

#!/bin/bash
rg -nC2 'AllExceptionsFilter' src
rg -nC2 'useGlobalFilters' src/**/main.ts
rg -nC2 'APP_FILTER' src

Length of output: 24672


🏁 Script executed:

#!/bin/bash
# Broad search for global filter registration and bootstrap
echo "Searching for useGlobalFilters across entire repo"
rg -nC2 "useGlobalFilters" .
echo "Searching for APP_FILTER across entire repo"
rg -nC2 "APP_FILTER" .
echo "Searching for provider registration of APP_FILTER"
rg -nC3 "provide\s*:\s*APP_FILTER" .
echo "Locating NestFactory.create calls (bootstrapping entrypoints)"
rg -nC2 "NestFactory.create" -g "*.ts" .

Length of output: 2161


Register the global exception filter
In src/app/main.ts, after const app = await NestFactory.create…, import AllExceptionsFilter and add:

app.useGlobalFilters(new AllExceptionsFilter());

—or— in AppModule’s providers:

{
  provide: APP_FILTER,
  useClass: AllExceptionsFilter,
}
🤖 Prompt for AI Agents
In src/shared/domain/errors/AllException.filter.ts around lines 9-10, the
AllExceptionsFilter is implemented but not registered globally; register it
either in src/app/main.ts by importing AllExceptionsFilter and calling
app.useGlobalFilters(new AllExceptionsFilter()) after creating the Nest app, or
add it to your AppModule providers by importing APP_FILTER and providing {
provide: APP_FILTER, useClass: AllExceptionsFilter } so the filter is applied
application-wide.

Comment on lines +3 to +7
export class BadTokenEception extends HttpException {
constructor() {
super('Token inválido ou expirado.', 401);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Typo: BadTokenEception → BadTokenException; also prefer HttpStatus enum

The current name breaks imports/usages; fix the class name and use HttpStatus for clarity.

-import { HttpException } from '@nestjs/common';
+import { HttpException, HttpStatus } from '@nestjs/common';

-export class BadTokenEception extends HttpException {
+export class BadTokenException extends HttpException {
   constructor() {
-    super('Token inválido ou expirado.', 401);
+    super('Token inválido ou expirado.', HttpStatus.UNAUTHORIZED);
   }
 }

🏁 Script executed:

#!/bin/bash
# Find and update all references to the misspelled class
rg -n 'BadTokenEception|BadTokenException'

Length of output: 382


Rename BadTokenEception to BadTokenException and use HttpStatus.UNAUTHORIZED

  • In src/shared/domain/errors/BadToken.exception.ts:
    • Change class name from BadTokenEceptionBadTokenException
    • Add import of HttpStatus and replace literal 401 with HttpStatus.UNAUTHORIZED
  • In src/modules/user/http/middlewares/Auth.middleware.ts (lines 5 & 38):
    • Update import to BadTokenException and replace all its usages
--- a/src/shared/domain/errors/BadToken.exception.ts
+++ b/src/shared/domain/errors/BadToken.exception.ts
@@
-import { HttpException } from '@nestjs/common';
+import { HttpException, HttpStatus } from '@nestjs/common';
 
-export class BadTokenEception extends HttpException {
+export class BadTokenException extends HttpException {
   constructor() {
-    super('Token inválido ou expirado.', 401);
+    super('Token inválido ou expirado.', HttpStatus.UNAUTHORIZED);
   }
 }
 
--- a/src/modules/user/http/middlewares/Auth.middleware.ts
@@
-import { BadTokenEception } from '../../../../shared/domain/errors/BadToken.exception';
+import { BadTokenException } from '../../../../shared/domain/errors/BadToken.exception';-      throw new BadTokenEception();
+      throw new BadTokenException();
🤖 Prompt for AI Agents
In src/shared/domain/errors/BadToken.exception.ts (lines 3–7) the class is
misspelled and uses a magic status code; rename the class from BadTokenEception
to BadTokenException, import HttpStatus from @nestjs/common, and replace the
literal 401 with HttpStatus.UNAUTHORIZED in the super call. Also update
src/modules/user/http/middlewares/Auth.middleware.ts (lines ~5 and ~38) to
import the renamed BadTokenException and replace any references to the old name.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review continued from previous batch...

Comment thread docker-compose.yml
Comment on lines +8 to +9
volumes:
- .:/usr/src/corenotes-db

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Incorrect volume mount for MySQL container

The MySQL container volume mounts the entire project directory to /usr/src/corenotes-db, which is unnecessary and could cause issues. MySQL data should be persisted using a named volume.

    volumes:
-      - .:/usr/src/corenotes-db
+      - mysql_data:/var/lib/mysql
+
+volumes:
+  mysql_data:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
volumes:
- .:/usr/src/corenotes-db
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
🤖 Prompt for AI Agents
In docker-compose.yml around lines 8 to 9, the MySQL service is mounting the
entire project directory (.-:/usr/src/corenotes-db) which is incorrect; replace
that bind mount with a named volume for MySQL data (e.g., use a named volume
mapped to MySQL’s data directory such as /var/lib/mysql) and add the named
volume under the top-level volumes section so the database files are persisted
correctly and the project directory is not exposed to the container.

Comment thread docker-compose.yml
Comment on lines +11 to +14
- MYSQL_USER=docker
- MYSQL_PASSWORD=docker
- MYSQL_DATABASE=corenotes_db
- MYSQL_ROOT_PASSWORD=docker

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

⚠️ Hardcoded database credentials in docker-compose

Using hardcoded credentials ('docker'/'docker') in the docker-compose file is a security risk. These should be externalized using environment variables or Docker secrets.

    environment:
-      - MYSQL_USER=docker
-      - MYSQL_PASSWORD=docker
+      - MYSQL_USER=${MYSQL_USER:-docker}
+      - MYSQL_PASSWORD=${MYSQL_PASSWORD:-docker}
      - MYSQL_DATABASE=corenotes_db
-      - MYSQL_ROOT_PASSWORD=docker
+      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-docker}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- MYSQL_USER=docker
- MYSQL_PASSWORD=docker
- MYSQL_DATABASE=corenotes_db
- MYSQL_ROOT_PASSWORD=docker
environment:
- MYSQL_USER=${MYSQL_USER:-docker}
- MYSQL_PASSWORD=${MYSQL_PASSWORD:-docker}
- MYSQL_DATABASE=corenotes_db
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-docker}
🤖 Prompt for AI Agents
In docker-compose.yml around lines 11 to 14, hardcoded DB credentials
(MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, MYSQL_ROOT_PASSWORD) must be
removed and replaced with environment variables or Docker secrets; update the
file to reference variables (e.g., MYSQL_USER: ${MYSQL_USER}) and add a .env
file (gitignored) to store defaults or configure Docker secrets and mount them
into the service, ensuring sensitive values are not committed to the repo and
documented for local/dev deployment.

Comment thread docker-compose.yml
depends_on:
- database
- redis
command: sh -c "/wait; npm i; npx prisma generate; npx prisma migrate dev; npx prisma db seed; npm run start:dev"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Production-unsafe command in docker-compose

Running migrations, seeds, and installing dependencies in the container startup command is inappropriate for production. This should only be used for development.

Consider creating separate docker-compose files for development and production:

  • docker-compose.dev.yml - with the current setup
  • docker-compose.yml - for production with a simpler command like npm start
-    command: sh -c "/wait; npm i; npx prisma generate; npx prisma migrate dev; npx prisma db seed; npm run start:dev"
+    command: sh -c "/wait; npm start"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
command: sh -c "/wait; npm i; npx prisma generate; npx prisma migrate dev; npx prisma db seed; npm run start:dev"
command: sh -c "/wait; npm start"
🤖 Prompt for AI Agents
In docker-compose.yml around line 34, the service command runs development-only
steps (installing deps, generating prisma client, running migrations/seeds, and
start:dev) which is unsafe for production; split this into two files by moving
the current long sh -c command into docker-compose.dev.yml for local/dev usage
and replace the command in docker-compose.yml with a production-safe start such
as npm start (or the built artifact start command), ensure environment-specific
overrides (env files or compose -f docker-compose.yml -f docker-compose.dev.yml)
are documented, and remove any runtime migration/seed/install logic from the
production compose so builds are deterministic and migrations/seeds are applied
via CI/ops pipelines.

Comment thread README.md
Comment on lines +40 to +46
Since the application uses AES-256 cryptography, it's mandatory to set it's two keys. You can generate them [here](https://acte.ltd/utils/randomkeygen), then set them in the `./.env` file the following way:

```
# Cryptography Settings
CRYPTO_SECRET=(use the 'Basic 8' generated key)
CRYPTO_IV=(use the 'Basic 16' generated key)
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

AES‑256 key size guidance is wrong; will cause runtime “Invalid key length.”

AES‑256‑CBC requires a 32‑byte key and a 16‑byte IV. “Basic 8” is invalid. Provide secure generation instructions.

-Since the application uses AES-256 cryptography, it's mandatory to set it's two keys. You can generate them [here](https://acte.ltd/utils/randomkeygen), then set them in the `./.env` file the following way:
+Since the application uses AES-256-CBC, you must set a 32-byte key (CRYPTO_SECRET) and a 16-byte IV (CRYPTO_IV). Generate them securely and paste into `./.env`:
@@
-CRYPTO_SECRET=(use the 'Basic 8' generated key)
-CRYPTO_IV=(use the 'Basic 16' generated key)
+# Example (hex values recommended)
+# macOS/Linux:
+#   openssl rand -hex 32  # -> CRYPTO_SECRET
+#   openssl rand -hex 16  # -> CRYPTO_IV
+CRYPTO_SECRET=<64-hex-chars, 32 bytes>
+CRYPTO_IV=<32-hex-chars, 16 bytes>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Since the application uses AES-256 cryptography, it's mandatory to set it's two keys. You can generate them [here](https://acte.ltd/utils/randomkeygen), then set them in the `./.env` file the following way:
```
# Cryptography Settings
CRYPTO_SECRET=(use the 'Basic 8' generated key)
CRYPTO_IV=(use the 'Basic 16' generated key)
```
Since the application uses AES-256-CBC, you must set a 32-byte key (CRYPTO_SECRET) and a 16-byte IV (CRYPTO_IV). Generate them securely and paste into `./.env`:
# Example (hex values recommended)
# macOS/Linux:
# openssl rand -hex 32 # -> CRYPTO_SECRET
# openssl rand -hex 16 # -> CRYPTO_IV
CRYPTO_SECRET=<64-hex-chars, 32 bytes>
CRYPTO_IV=<32-hex-chars, 16 bytes>
🤖 Prompt for AI Agents
In README.md around lines 40 to 46, the AES-256 key guidance is incorrect (it
suggests a “Basic 8” key) which will cause “Invalid key length.” Update the
instructions to require a 32‑byte key and a 16‑byte IV for AES-256-CBC and show
how to securely generate them (e.g., using a cryptographic random generator such
as openssl or a secure online generator), specifying acceptable encodings (hex
or base64) and the exact expected lengths (e.g., 32 raw bytes — 64 hex chars —
for CRYPTO_SECRET; 16 raw bytes — 32 hex chars — for CRYPTO_IV), and instruct
users to place those generated values into the .env CRYPTO_SECRET and CRYPTO_IV
entries.

Comment thread README.md
Comment on lines +71 to +76
npx prisma generate

npx prisma migrate <migration-name>

npm run start:dev
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix Prisma migrate command and include seeding.

The correct dev command is migrate dev with a name; optionally seed after.

-npx prisma generate
-
-npx prisma migrate <migration-name>
-
-npm run start:dev
+npx prisma generate
+npx prisma migrate dev --name init
+npx prisma db seed
+npm run start:dev
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
npx prisma generate
npx prisma migrate <migration-name>
npm run start:dev
```
npx prisma generate
npx prisma migrate dev --name init
npx prisma db seed
npm run start:dev
🤖 Prompt for AI Agents
In README.md around lines 71–76, the migrate instruction is incorrect and
missing seeding; replace the generic "prisma migrate <migration-name>" step with
the dev migration flow using "prisma migrate dev" with an explicit migration
name, and add a follow-up seed step so users run the project seed after the
migration (e.g., run the project's configured prisma db seed or npm script that
performs seeding). Ensure the README shows the migration command with a required
name and a separate step to run the seed.

Comment on lines +12 to +15
validateEmail,
validateName,
validatePassword,
} from '../../../../shared/infra/utils/functions/validators';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify validators barrel import exists.

Ensure shared/infra/utils/functions/validators/index.ts re-exports validateName/Email/Password; otherwise this import will fail.


🏁 Script executed:

#!/bin/bash
# Check that a validators barrel exists and re-exports the functions
fd -a validators src/shared/infra/utils/functions
rg -nC1 'export\s+\{\s*validate(Name|Email|Password)' src/shared/infra/utils/functions/validators 2>/dev/null || true
rg -n 'export\s+const\s+validate(Name|Email|Password)' src/shared/infra/utils/functions -g '!**/node_modules/**'

Length of output: 709


Ensure validators barrel file re-exports functions
Add or update src/shared/infra/utils/functions/validators/index.ts to export validateEmail, validateName, and validatePassword; without these re-exports the import in create-user.usecase.ts will fail.

🤖 Prompt for AI Agents
In src/modules/user/infra/usecases/create-user.usecase.ts around lines 12 to 15,
the import expects validateEmail, validateName, and validatePassword to be
re-exported from src/shared/infra/utils/functions/validators; update or create
src/shared/infra/utils/functions/validators/index.ts to export these three
functions (re-export the named exports from their original module files using
the correct relative paths), ensure the functions are exported as named exports
(not default), and run a TypeScript build or linter to confirm the imports
resolve.

Comment on lines +15 to +19
): Promise<HomeDataResponseDTO | UserNotFoundException> {
const user = await this.userRepository.findById(user_id);

if (!user || !user.id_user || !user.name)
return new UserNotFoundException();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Don’t return exception objects; throw them and narrow the return type.

Returning new UserNotFoundException() can lead to 200 responses with an error payload.

-  async execute(
-    user_id: string,
-  ): Promise<HomeDataResponseDTO | UserNotFoundException> {
+  async execute(user_id: string): Promise<HomeDataResponseDTO> {
@@
-    if (!user || !user.id_user || !user.name)
-      return new UserNotFoundException();
+    if (!user || !user.id_user || !user.name) {
+      throw new UserNotFoundException();
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
): Promise<HomeDataResponseDTO | UserNotFoundException> {
const user = await this.userRepository.findById(user_id);
if (!user || !user.id_user || !user.name)
return new UserNotFoundException();
async execute(user_id: string): Promise<HomeDataResponseDTO> {
const user = await this.userRepository.findById(user_id);
if (!user || !user.id_user || !user.name) {
throw new UserNotFoundException();
}
🤖 Prompt for AI Agents
In src/modules/user/infra/usecases/home-data.usecase.ts around lines 15 to 19,
the code currently returns new UserNotFoundException() when the user is missing;
change this to throw new UserNotFoundException() so an error response is
produced instead of a 200 with an error payload, and update the method signature
to remove UserNotFoundException from the Promise union (e.g.,
Promise<HomeDataResponseDTO>), allowing the exception to propagate (or be caught
by upper layers) and adjust any callers/tests to expect the thrown exception
rather than an error object being returned.


async execute(
data: UserLoginRequestDTO,
ip: string,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Unused ip parameter in login method

The ip parameter is accepted but never used in the implementation. If IP tracking is planned for audit logging or rate limiting, implement it now. Otherwise, remove the parameter to avoid confusion.

  async execute(
    data: UserLoginRequestDTO,
-   ip: string,
  ): Promise<
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ip: string,
async execute(
data: UserLoginRequestDTO,
): Promise<
🤖 Prompt for AI Agents
In src/modules/user/infra/usecases/user-login.usecase.ts around line 21, the
login method declares an ip: string parameter that is never used; either remove
the ip parameter from the method signature and all call sites (and update
related types/interfaces) or implement its use (e.g., pass it into
audit/logging, attach to the created session/token, or feed it into
rate-limiting/auth-logging code) so it is consumed; update tests and types
accordingly to keep signatures consistent.

Comment on lines +40 to +47
const token = this.jwtProvider.generate({
payload: {
user: {
id_user: user.id_user,
name: user.name,
},
},
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding expiration time to JWT tokens

The JWT generation doesn't specify an expiration time, which could lead to tokens that never expire. This is a security concern as compromised tokens would remain valid indefinitely.

    const token = this.jwtProvider.generate({
      payload: {
        user: {
          id_user: user.id_user,
          name: user.name,
        },
      },
+     expiresIn: '24h', // or configure via environment variable
    });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const token = this.jwtProvider.generate({
payload: {
user: {
id_user: user.id_user,
name: user.name,
},
},
});
const token = this.jwtProvider.generate({
payload: {
user: {
id_user: user.id_user,
name: user.name,
},
},
expiresIn: '24h', // or configure via environment variable
});
🤖 Prompt for AI Agents
In src/modules/user/infra/usecases/user-login.usecase.ts around lines 40 to 47,
the JWT is generated without an expiration time; modify the call to
this.jwtProvider.generate to include an expiration (use a configurable value,
e.g., process.env.JWT_EXPIRES_IN or a constant like '1h'), either by adding an
expiresIn option to the generate call or by including an exp claim set to
Date.now()/Math.floor + TTL, depending on the jwtProvider API; ensure the value
is configurable via env and update any types/signatures if needed.

Comment on lines +57 to +69
REDIS_PRODUCTION_HOST: process.env.REDIS_PRODUCTION_HOST
? process.env.REDIS_PRODUCTION_HOST
: '',
REDIS_PRODUCTION_PORT: process.env.REDIS_PRODUCTION_PORT
? Number(process.env.REDIS_PRODUCTION_PORT)
: 0,
REDIS_PRODUCTION_USER: process.env.REDIS_PRODUCTION_USER
? process.env.REDIS_PRODUCTION_USER
: '',
REDIS_PRODUCTION_PASSWORD: process.env.REDIS_PRODUCTION_PASSWORD
? process.env.REDIS_PRODUCTION_PASSWORD
: '',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Don’t coerce absent optional Redis vars to ''/0 (can trigger bad connections).

Pass undefined when envs are unset so optional fields remain absent.

-    REDIS_PRODUCTION_HOST: process.env.REDIS_PRODUCTION_HOST
-      ? process.env.REDIS_PRODUCTION_HOST
-      : '',
-    REDIS_PRODUCTION_PORT: process.env.REDIS_PRODUCTION_PORT
-      ? Number(process.env.REDIS_PRODUCTION_PORT)
-      : 0,
-    REDIS_PRODUCTION_USER: process.env.REDIS_PRODUCTION_USER
-      ? process.env.REDIS_PRODUCTION_USER
-      : '',
-    REDIS_PRODUCTION_PASSWORD: process.env.REDIS_PRODUCTION_PASSWORD
-      ? process.env.REDIS_PRODUCTION_PASSWORD
-      : '',
+    REDIS_PRODUCTION_HOST: process.env.REDIS_PRODUCTION_HOST || undefined,
+    REDIS_PRODUCTION_PORT: process.env.REDIS_PRODUCTION_PORT
+      ? Number(process.env.REDIS_PRODUCTION_PORT)
+      : undefined,
+    REDIS_PRODUCTION_USER: process.env.REDIS_PRODUCTION_USER || undefined,
+    REDIS_PRODUCTION_PASSWORD: process.env.REDIS_PRODUCTION_PASSWORD || undefined,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
REDIS_PRODUCTION_HOST: process.env.REDIS_PRODUCTION_HOST
? process.env.REDIS_PRODUCTION_HOST
: '',
REDIS_PRODUCTION_PORT: process.env.REDIS_PRODUCTION_PORT
? Number(process.env.REDIS_PRODUCTION_PORT)
: 0,
REDIS_PRODUCTION_USER: process.env.REDIS_PRODUCTION_USER
? process.env.REDIS_PRODUCTION_USER
: '',
REDIS_PRODUCTION_PASSWORD: process.env.REDIS_PRODUCTION_PASSWORD
? process.env.REDIS_PRODUCTION_PASSWORD
: '',
REDIS_PRODUCTION_HOST: process.env.REDIS_PRODUCTION_HOST || undefined,
REDIS_PRODUCTION_PORT: process.env.REDIS_PRODUCTION_PORT
? Number(process.env.REDIS_PRODUCTION_PORT)
: undefined,
REDIS_PRODUCTION_USER: process.env.REDIS_PRODUCTION_USER || undefined,
REDIS_PRODUCTION_PASSWORD: process.env.REDIS_PRODUCTION_PASSWORD || undefined,
🤖 Prompt for AI Agents
In src/shared/config/app.config.ts around lines 57 to 69, optional Redis env
vars are being coerced to '' or 0 when unset; change them to return undefined
instead so optional fields remain absent. For host, user, and password, assign
process.env.VAR directly or use process.env.VAR ?? undefined; for port, parse
Number(process.env.REDIS_PRODUCTION_PORT) only when the env var is set,
otherwise return undefined (avoid coercing to 0). Ensure the config types remain
compatible with possibly undefined values.

@odavibatista odavibatista changed the title Corelab Challenge - Backend NodeJS + Prisma + Redis + Swagger Corelab Challenge - Backend NodeJS + Nest + Prisma + Redis + Swagger Sep 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant