Skip to content

finalizando implementacao e testes da criacao de rascunhos em vaga#203

Open
lucaserib wants to merge 1 commit intoSouJunior:mainfrom
lucaserib:feature/job-draft-system
Open

finalizando implementacao e testes da criacao de rascunhos em vaga#203
lucaserib wants to merge 1 commit intoSouJunior:mainfrom
lucaserib:feature/job-draft-system

Conversation

@lucaserib
Copy link
Contributor

@lucaserib lucaserib commented Dec 11, 2025

Implementação completa do sistema de gerenciamento de ciclo de vida de vagas, permitindo que empresas criem rascunhos, publiquem, cancelem e excluam vagas de acordo com regras de negócio específicas.

🎯 Objetivo
Permitir que empresas criem vagas em etapas, salvando primeiro um rascunho com informações básicas e posteriormente completando os dados para publicação, melhorando a experiência do usuário e reduzindo erros no cadastro de vagas.

Summary by CodeRabbit

Notas de Lançamento

  • Novas Funcionalidades
    • Sistema de rascunhos para vagas: criação, publicação e cancelamento de anúncios de trabalho.
    • Rastreamento de status de vagas com os estados: rascunho, publicado, arquivado e cancelado.
    • Funcionalidade para deletar rascunhos de vagas.
    • Integração com serviço de e-mail aprimorada.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 11, 2025

Walkthrough

Este pull request implementa um sistema de rascunho e publicação de vagas com workflow de estados. Adiciona novos campos à entidade de vagas, cria uma tabela para vagas salvas, implementa um guard de autorização para proprietários de vagas, adiciona novos DTOs e serviços para gerenciar o ciclo de vida das vagas, e atualiza a configuração Docker com o serviço Mailhog e ajustes de nomenclatura.

Changes

Cohort / Cohesão Alterações
Configuração Docker
docker-compose.yml
Renomeia serviço de banco de dados (database_soujuniorvagas-api-db), atualiza credenciais para variáveis de template TypeORM, adiciona healthcheck com validação de usuário, inclui novo serviço Mailhog (SMTP + Web UI), renomeia container app (linkedIn_Backendlinkedin-backend), muda porta do app (3000 → 3001), corrige caminho de volume (.:/user/app.:/usr/app), adiciona dependência do Mailhog
Entidade de Vagas
src/database/entities/jobs.entity.ts
Adiciona campos jobStatus (enum com padrão DRAFT), publishedAt, canceledAt (ambos nullable), torna description, prerequisites, type, typeContract nullable, remove defaults de type e typeContract, torna contractText obrigatório, adiciona openEndedContract e affirmative como nullable
Migração de Banco de Dados
src/database/migrations/1765483583757-AddJobStatusFields.ts
Cria tabela tb_saved_jobs com índices, define enum tb_jobs_jobstatus_enum, adiciona coluna jobStatus com default DRAFT à tb_jobs, ajusta constraints em description, prerequisites, type, typeContract, altera date_candidacy, configura múltiplas foreign keys, inclui rollback completo
DTOs para Workflow de Vagas
src/modules/jobs/dtos/complete-job.dto.ts, src/modules/jobs/dtos/create-job-draft.dto.ts
CompleteJobDto: campos obrigatórios para publicação (descrição, requisitos, benefícios, tipo de contrato, jornada, processo seletivo, info adicional); CreateJobDraftDto: campos iniciais para criar rascunho (título, área interesse, modalidade, cidade condicional, faixa salarial, opção remota) com validadores e metadata Swagger
Enum de Status de Vagas
src/modules/jobs/enums/job-status.enum.ts
Define enum JobStatus com valores DRAFT, PUBLISHED, ARCHIVED, CANCELED
Autenticação e Autorização
src/modules/jobs/guards/job-owner.guard.ts
Implementa guard NestJS que verifica autenticação do usuário, carrega vaga por ID, retorna NotFound se não existe, Forbidden se usuário não é proprietário (validando job.company_id === user.id)
Serviços de Gerenciamento de Vagas
src/modules/jobs/services/create-job-draft.service.ts, src/modules/jobs/services/publish-job.service.ts, src/modules/jobs/services/cancel-job.service.ts, src/modules/jobs/services/delete-job-draft.service.ts
Serviços para: criar rascunho (valida faixa salarial), publicar rascunho (valida status DRAFT), cancelar vaga publicada (valida status PUBLISHED), deletar rascunho (valida status DRAFT); todos com tratamento de erro específico e respostas estruturadas
Controller e Módulo
src/modules/jobs/jobs.controller.ts, src/modules/jobs/jobs.module.ts
Controller: adiciona endpoints POST /job/draft, PATCH /job/:id/publish, PATCH /job/:id/cancel, DELETE /job/:id com guards e decoradores Swagger; Módulo: registra novos serviços e guard como providers
Repository
src/modules/jobs/repository/job.repository.ts
Adiciona métodos createJobDraft(), publishJob(), cancelJob(), deleteJobDraft(); filtra listagens por status PUBLISHED em getAllJobs() e searchJobs()
Decoradores Swagger
src/shared/Swagger/decorators/jobs/create-job-draft.swagger.ts, src/shared/Swagger/decorators/jobs/publish-job.swagger.ts, src/shared/Swagger/decorators/jobs/cancel-job.swagger.ts, src/shared/Swagger/decorators/jobs/delete-job-draft.swagger.ts
Define composições de decoradores Swagger para documentação de endpoints, incluindo respostas HTTP (201/200/400/401/403) com tipos e mensagens localizadas em português
Configuração de Email
src/modules/mails/mail.module.ts
Comenta bloco de autenticação na configuração de transporte de email; mantém host e porta inalterados
Testes Unitários
test/modules/jobs/services/create-job-draft.service.spec.ts, test/modules/jobs/services/publish-job.service.spec.ts, test/modules/jobs/services/job-owner.guard.spec.ts
Suítes de testes para validar criação de rascunho (validação salarial), publicação de rascunho (validação de status), guard de proprietário (autenticação, existência, proprietário) e cenários de erro

Sequence Diagram(s)

sequenceDiagram
    actor User as Usuário (Empresa)
    participant Ctrl as Controller
    participant Guard as JobOwnerGuard
    participant Service as Job Service
    participant Repo as Repository
    participant DB as Database

    Note over User,DB: Workflow de Vaga: Criar → Publicar → Cancelar

    User->>Ctrl: POST /job/draft (CreateJobDraftDto)
    activate Ctrl
    Ctrl->>Service: CreateJobDraftService.execute()
    activate Service
    Service->>Repo: createJobDraft(data, company_id)
    activate Repo
    Repo->>DB: INSERT vaga (jobStatus=DRAFT)
    DB-->>Repo: JobsEntity {id, status=DRAFT}
    Repo-->>Service: JobsEntity
    deactivate Repo
    Service-->>Ctrl: {id, status, message}
    deactivate Service
    Ctrl-->>User: 201 Created
    deactivate Ctrl

    User->>Ctrl: PATCH /job/:id/publish (CompleteJobDto)
    activate Ctrl
    Ctrl->>Guard: canActivate(jobId, userId)
    activate Guard
    Guard->>Repo: findOneById(jobId)
    activate Repo
    Repo->>DB: SELECT * FROM tb_jobs WHERE id=:id
    DB-->>Repo: JobsEntity {jobStatus=DRAFT, company_id}
    Repo-->>Guard: JobsEntity
    deactivate Repo
    Guard->>Guard: Validar: userId === job.company_id
    Guard-->>Ctrl: true
    deactivate Guard
    Ctrl->>Service: PublishJobService.execute(jobId, completeData)
    activate Service
    Service->>Repo: publishJob(jobId, completeData)
    activate Repo
    Repo->>DB: UPDATE tb_jobs SET jobStatus=PUBLISHED, <br/>description, requirements, ...
    DB-->>Repo: JobsEntity {jobStatus=PUBLISHED, publishedAt}
    Repo-->>Service: JobsEntity
    deactivate Repo
    Service-->>Ctrl: {id, status=PUBLISHED, publishedAt, message}
    deactivate Service
    Ctrl-->>User: 200 OK
    deactivate Ctrl

    User->>Ctrl: PATCH /job/:id/cancel
    activate Ctrl
    Ctrl->>Guard: canActivate(jobId, userId)
    activate Guard
    Guard->>Repo: findOneById(jobId)
    activate Repo
    Repo->>DB: SELECT * FROM tb_jobs WHERE id=:id
    DB-->>Repo: JobsEntity {jobStatus=PUBLISHED}
    Repo-->>Guard: JobsEntity
    deactivate Repo
    Guard-->>Ctrl: true
    deactivate Guard
    Ctrl->>Service: CancelJobService.execute(jobId)
    activate Service
    Service->>Repo: cancelJob(jobId)
    activate Repo
    Repo->>DB: UPDATE tb_jobs SET jobStatus=CANCELED, canceledAt=NOW()
    DB-->>Repo: JobsEntity {jobStatus=CANCELED}
    Repo-->>Service: JobsEntity
    deactivate Repo
    Service-->>Ctrl: {id, status=CANCELED, canceledAt, message}
    deactivate Service
    Ctrl-->>User: 200 OK
    deactivate Ctrl

    Note over Guard,DB: Guard bloqueia acesso se:<br/>- Usuário não autenticado<br/>- Vaga não encontrada<br/>- Usuário não é proprietário
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutos

Áreas de atenção especial:

  • Migração de banco de dados (AddJobStatusFields.ts): Alterações complexas em schema com múltiplas constraints de chave estrangeira, requer validação cuidadosa do rollback e consistência de dados
  • Workflow de estados (JobStatus): Lógica de transição de estados (DRAFT → PUBLISHED → CANCELED) em múltiplos services, requer validação de regras de negócio em PublishJobService, CancelJobService, DeleteJobDraftService
  • Guard de autorização (JobOwnerGuard): Validação crítica de proprietário da vaga, requer verificação de lógica de permissão e tratamento de exceções
  • Campos nullable em JobsEntity: Análise do impacto de campos anteriormente obrigatórios agora opcionais em queries e relacionamentos existentes
  • Relacionamentos com tb_saved_jobs: Nova tabela com foreign keys para usuários e vagas, requer verificação de integridade referencial

Possibly related PRs

Suggested reviewers

  • MikaelMelo1

Poem

🐰 Um coelho salta ao ver este PR,
Rascunhos de vagas prontas para brotar,
DRAFT, PUBLISH, CANCEL — qual flor escolher?
Com guards e migrações, tudo vai florescer,
De Docker com Mailhog até o final,
Que este merge seja sempre especial! 🌸

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed O título é diretamente relacionado à mudança principal: implementação e testes do sistema de criação de rascunhos de vagas. Descreve com precisão o que foi desenvolvido.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • 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

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
7.4% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/modules/jobs/repository/job.repository.ts (1)

164-168: Vulnerabilidade de SQL Injection.

A interpolação direta de searchQuery na consulta SQL cria uma vulnerabilidade crítica de SQL injection. Utilize parâmetros nomeados para sanitizar a entrada.

     queryBuilder
       .leftJoin('job.company', 'company')
       .select(['job', 'company.id', 'company.companyName', 'company.profile'])
-      .andWhere(`job.title ILIKE '%${searchQuery}%'`)
+      .andWhere(`job.title ILIKE :searchQuery`, { searchQuery: `%${searchQuery}%` })
       .andWhere(`job.jobStatus = :status`, { status: JobStatus.PUBLISHED })
🧹 Nitpick comments (16)
src/modules/mails/mail.module.ts (1)

13-21: Porta do SMTP como número e clarificação da auth

Ajuste para usar Mailhog sem autenticação faz sentido. Só sugiro converter explicitamente a porta para número para evitar qualquer surpresa de tipo entre ambientes.

-          host: config.get('MAIL_HOST'),
-          port: config.get('MAIL_PORT'),
+          host: config.get('MAIL_HOST'),
+          port: Number(config.get('MAIL_PORT')),
src/shared/Swagger/decorators/jobs/publish-job.swagger.ts (1)

1-30: Swagger de publicação bem alinhado com erros esperados

O decorator documenta bem o fluxo de publicação (OK + 400/401/403) e reaproveita os modelos de erro existentes.

Se a resposta 403 tiver um corpo padronizado na API (similar ao UnauthorizedSwagger), pode valer a pena no futuro extrair um ForbiddenSwagger para manter a consistência dos contratos.

test/modules/jobs/services/job-owner.guard.spec.ts (1)

1-101: Testes do JobOwnerGuard bem completos, com pequeno ajuste possível

A suíte cobre bem todos os cenários relevantes do guard (não autenticado, job inexistente, usuário não dono e dono).

Em vários testes você chama guard.canActivate(context) duas vezes apenas para validar tipo e depois mensagem da exceção. Se o método ganhar efeitos colaterais no futuro, isso pode virar fonte de flakiness. Opcionalmente, dá para capturar o erro uma única vez (via try/catch ou await guard.canActivate(context).catch(...)) e fazer múltiplos expect sobre o mesmo objeto.

test/modules/jobs/services/publish-job.service.spec.ts (1)

1-102: Cobertura de PublishJobService está boa; dá para enxugar chamadas duplicadas

Os testes cobrem bem os casos de erro (job inexistente / não-draft) e o fluxo feliz, incluindo o payload retornado e a chamada correta a publishJob.

Nos casos de erro, você chama service.execute duas vezes no mesmo teste para checar tipo e mensagem da BadRequestException. Assim como no guard, é mais robusto capturar a exceção uma única vez e fazer múltiplos asserts sobre ela, evitando executar a lógica de serviço duas vezes sem necessidade.

src/shared/Swagger/decorators/jobs/delete-job-draft.swagger.ts (1)

1-30: Swagger de exclusão de rascunho consistente com o restante da API

As respostas documentadas (OK, 400, 401, 403) e o summary descrevem bem o comportamento esperado do endpoint, mantendo o padrão dos outros decorators de vagas.

Assim como no caso de publicação, se a resposta 403 tiver um corpo padronizado, pode ser interessante extrair um modelo (ex.: ForbiddenSwagger) para manter os contratos de erro homogêneos.

test/modules/jobs/services/create-job-draft.service.spec.ts (2)

49-62: Considere consolidar as asserções de exceção.

Chamar service.execute() duas vezes para verificar a exceção é redundante e pode afetar a performance dos testes. Utilize rejects.toThrow() com um matcher mais específico.

-      await expect(service.execute(invalidData, mockCompany)).rejects.toThrow(
-        BadRequestException,
-      );
-      await expect(service.execute(invalidData, mockCompany)).rejects.toThrow(
-        'Salary minimum cannot be greater than salary maximum',
-      );
+      await expect(service.execute(invalidData, mockCompany)).rejects.toThrow(
+        new BadRequestException('Salary minimum cannot be greater than salary maximum'),
+      );

87-104: Teste parcialmente redundante com o anterior.

Este teste valida a associação do company_id, mas essa verificação já é coberta implicitamente no teste anterior (linhas 81-84). Considere manter apenas um teste mais abrangente ou diferenciar os cenários testados.

src/modules/jobs/services/create-job-draft.service.ts (1)

19-19: Evite mutar o parâmetro de entrada diretamente.

Atribuir company_id diretamente ao objeto data viola o princípio de imutabilidade e pode causar efeitos colaterais inesperados. Prefira criar um novo objeto para passar ao repositório.

-    data.company_id = company.id;
-
-    const draft = await this.jobRepository.createJobDraft(data);
+    const draftData = {
+      ...data,
+      company_id: company.id,
+    };
+
+    const draft = await this.jobRepository.createJobDraft(draftData);
src/modules/jobs/guards/job-owner.guard.ts (1)

23-27: Guard realiza busca no banco que pode ser duplicada no serviço.

O guard busca a vaga para verificar ownership, mas os serviços (DeleteJobDraftService, CancelJobService, PublishJobService) também buscam a mesma vaga novamente. Considere armazenar a vaga no request para evitar duplicação de queries.

     const job = await this.jobRepository.findOneById(jobId);

     if (!job) {
       throw new NotFoundException('Job not found');
     }

+    // Anexa a vaga ao request para reutilização nos serviços
+    request.job = job;
+
     if (job.company_id !== user.id) {
src/modules/jobs/services/delete-job-draft.service.ts (1)

9-14: Usar NotFoundException para recurso não encontrado.

O uso de BadRequestException para "Job not found" é semanticamente incorreto. O código HTTP 400 indica erro do cliente na requisição, enquanto 404 indica recurso não encontrado. Além disso, considere adicionar a anotação de tipo de retorno no método.

+import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
-import { BadRequestException, Injectable } from '@nestjs/common';
-  async execute(jobId: string) {
+  async execute(jobId: string): Promise<{ message: string }> {
     const job = await this.jobRepository.findOneById(jobId);
 
     if (!job) {
-      throw new BadRequestException('Job not found');
+      throw new NotFoundException('Job not found');
     }
src/modules/jobs/services/cancel-job.service.ts (1)

9-14: Consistência: usar NotFoundException para recurso não encontrado.

Mesmo padrão identificado em DeleteJobDraftService. Utilize NotFoundException em vez de BadRequestException quando o job não for encontrado, mantendo consistência semântica dos códigos HTTP.

-import { BadRequestException, Injectable } from '@nestjs/common';
+import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
     if (!job) {
-      throw new BadRequestException('Job not found');
+      throw new NotFoundException('Job not found');
     }
src/modules/jobs/dtos/create-job-draft.dto.ts (2)

50-57: Validação incompleta para city quando modalidade não é remota.

O @ValidateIf corretamente condiciona a validação, mas falta @IsNotEmpty() para garantir que city seja obrigatório quando a modalidade não for REMOTE. Atualmente, uma string vazia seria aceita.

   @ValidateIf((o) => o.modality !== JobsModalityEnum.REMOTE)
+  @IsNotEmpty()
   @IsString()
   @ApiProperty({
     required: false,
     description: 'Cidade',
     example: 'São Paulo',
   })
   city?: string;

90-91: Adicionar validação de tipo para company_id.

Embora company_id seja definido pelo serviço, adicionar @IsString() previne erros caso o campo seja inadvertidamente passado no payload com tipo incorreto.

   @IsOptional()
+  @IsString()
   company_id?: string;
src/modules/jobs/dtos/complete-job.dto.ts (1)

23-31: Considerar validação de tamanho mínimo para arrays obrigatórios.

Os campos requirements e selectionProcess são obrigatórios, mas arrays vazios passariam na validação atual. Considere adicionar @ArrayMinSize(1) para garantir pelo menos um elemento.

+import { ArrayMinSize } from 'class-validator';

   @IsNotEmpty()
   @IsArray()
+  @ArrayMinSize(1)
   @IsString({ each: true })
   @ApiProperty({
     required: true,
     description: 'Requisitos da vaga',
     example: ['Node.js', 'TypeScript', 'NestJS'],
   })
   requirements: string[];
src/modules/jobs/jobs.controller.ts (2)

98-104: Considere tornar a rota mais explícita.

O nome do método deleteJobDraft sugere que é específico para rascunhos, mas a rota @Delete(':id') é genérica e conflita com possíveis outros endpoints DELETE. Se o serviço valida internamente que apenas rascunhos podem ser excluídos, considere adicionar essa validação no Swagger ou renomear o método para refletir melhor o comportamento real.


135-140: Evite usar @Res() a menos que seja estritamente necessário.

O uso do decorador @Res() desabilita o tratamento automático de respostas do NestJS, exigindo gerenciamento manual. A menos que você precise de acesso de baixo nível ao objeto de resposta (por exemplo, para streaming ou manipulação de headers específicos), é melhor deixar o framework lidar com a resposta automaticamente retornando o objeto diretamente.

Considere esta refatoração:

-  @Res() res: Response,
 ) {
-  const { status, data } = await this.getAllJobsFromLoggedCompany.execute(
+  const result = await this.getAllJobsFromLoggedCompany.execute(
     company.id,
   );
-  return res.status(status).json(data);
+  return result;
 }

Se o status HTTP precisa ser customizado, use @HttpCode() ou lance exceções apropriadas.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dcee15d and 35e3a79.

📒 Files selected for processing (22)
  • docker-compose.yml (1 hunks)
  • src/database/entities/jobs.entity.ts (6 hunks)
  • src/database/migrations/1765483583757-AddJobStatusFields.ts (1 hunks)
  • src/modules/jobs/dtos/complete-job.dto.ts (1 hunks)
  • src/modules/jobs/dtos/create-job-draft.dto.ts (1 hunks)
  • src/modules/jobs/enums/job-status.enum.ts (1 hunks)
  • src/modules/jobs/guards/job-owner.guard.ts (1 hunks)
  • src/modules/jobs/jobs.controller.ts (5 hunks)
  • src/modules/jobs/jobs.module.ts (2 hunks)
  • src/modules/jobs/repository/job.repository.ts (5 hunks)
  • src/modules/jobs/services/cancel-job.service.ts (1 hunks)
  • src/modules/jobs/services/create-job-draft.service.ts (1 hunks)
  • src/modules/jobs/services/delete-job-draft.service.ts (1 hunks)
  • src/modules/jobs/services/publish-job.service.ts (1 hunks)
  • src/modules/mails/mail.module.ts (1 hunks)
  • src/shared/Swagger/decorators/jobs/cancel-job.swagger.ts (1 hunks)
  • src/shared/Swagger/decorators/jobs/create-job-draft.swagger.ts (1 hunks)
  • src/shared/Swagger/decorators/jobs/delete-job-draft.swagger.ts (1 hunks)
  • src/shared/Swagger/decorators/jobs/publish-job.swagger.ts (1 hunks)
  • test/modules/jobs/services/create-job-draft.service.spec.ts (1 hunks)
  • test/modules/jobs/services/job-owner.guard.spec.ts (1 hunks)
  • test/modules/jobs/services/publish-job.service.spec.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (9)
src/shared/Swagger/decorators/jobs/delete-job-draft.swagger.ts (2)
src/shared/Swagger/bad-request.swagger.ts (1)
  • BadRequestSwagger (2-5)
src/shared/Swagger/unauthorized.swagger.ts (1)
  • UnauthorizedSwagger (2-8)
src/modules/jobs/services/create-job-draft.service.ts (1)
src/modules/jobs/dtos/create-job-draft.dto.ts (1)
  • CreateJobDraftDto (15-92)
src/modules/jobs/services/cancel-job.service.ts (5)
src/modules/jobs/guards/job-owner.guard.ts (1)
  • Injectable (10-37)
src/modules/jobs/repository/job.repository.ts (1)
  • Injectable (18-196)
src/modules/jobs/services/create-job-draft.service.ts (1)
  • Injectable (6-29)
src/modules/jobs/services/delete-job-draft.service.ts (1)
  • Injectable (5-26)
src/modules/jobs/services/publish-job.service.ts (1)
  • Injectable (6-33)
src/shared/Swagger/decorators/jobs/publish-job.swagger.ts (2)
src/shared/Swagger/bad-request.swagger.ts (1)
  • BadRequestSwagger (2-5)
src/shared/Swagger/unauthorized.swagger.ts (1)
  • UnauthorizedSwagger (2-8)
src/shared/Swagger/decorators/jobs/cancel-job.swagger.ts (2)
src/shared/Swagger/bad-request.swagger.ts (1)
  • BadRequestSwagger (2-5)
src/shared/Swagger/unauthorized.swagger.ts (1)
  • UnauthorizedSwagger (2-8)
src/shared/Swagger/decorators/jobs/create-job-draft.swagger.ts (2)
src/shared/Swagger/bad-request.swagger.ts (1)
  • BadRequestSwagger (2-5)
src/shared/Swagger/unauthorized.swagger.ts (1)
  • UnauthorizedSwagger (2-8)
src/modules/jobs/guards/job-owner.guard.ts (5)
src/modules/jobs/repository/job.repository.ts (1)
  • Injectable (18-196)
src/modules/jobs/services/cancel-job.service.ts (1)
  • Injectable (5-29)
src/modules/jobs/services/create-job-draft.service.ts (1)
  • Injectable (6-29)
src/modules/jobs/services/delete-job-draft.service.ts (1)
  • Injectable (5-26)
src/modules/jobs/services/publish-job.service.ts (1)
  • Injectable (6-33)
src/modules/jobs/repository/job.repository.ts (8)
src/modules/jobs/services/cancel-job.service.ts (1)
  • Injectable (5-29)
src/modules/jobs/services/create-job-draft.service.ts (1)
  • Injectable (6-29)
src/modules/jobs/services/delete-job.service.ts (1)
  • Injectable (7-36)
src/modules/jobs/services/get-all-jobs.service.ts (1)
  • Injectable (7-19)
src/modules/jobs/services/update-job.service.ts (1)
  • Injectable (5-22)
src/shared/utils/handle-error.util.ts (1)
  • handleError (3-14)
src/modules/jobs/dtos/create-job-draft.dto.ts (1)
  • CreateJobDraftDto (15-92)
src/modules/jobs/dtos/complete-job.dto.ts (1)
  • CompleteJobDto (12-85)
src/modules/jobs/jobs.controller.ts (8)
src/shared/Swagger/decorators/jobs/create-job-draft.swagger.ts (1)
  • CreateJobDraftSwagger (6-26)
src/modules/jobs/dtos/create-job-draft.dto.ts (1)
  • CreateJobDraftDto (15-92)
src/modules/auth/decorator/logged-company.decorator.ts (1)
  • LoggedCompany (7-20)
src/shared/Swagger/decorators/jobs/publish-job.swagger.ts (1)
  • PublishJobSwagger (6-30)
src/modules/jobs/dtos/complete-job.dto.ts (1)
  • CompleteJobDto (12-85)
src/shared/Swagger/decorators/jobs/cancel-job.swagger.ts (1)
  • CancelJobSwagger (6-30)
src/shared/Swagger/decorators/jobs/delete-job-draft.swagger.ts (1)
  • DeleteJobDraftSwagger (6-30)
src/shared/Swagger/decorators/jobs/get-all-jobs-of-logged-company.swagger.ts (1)
  • GetAllJobsOfLoggedCompanySwagger (4-10)
🔇 Additional comments (25)
src/shared/Swagger/decorators/jobs/create-job-draft.swagger.ts (1)

1-26: Swagger de criação de rascunho bem padronizado

Decorator está consistente com o padrão do projeto (status 201/400/401 + summary claro) e reaproveita os modelos de erro compartilhados. Nada a ajustar aqui.

src/modules/jobs/enums/job-status.enum.ts (1)

1-6: Enum JobStatus bem definido para o ciclo de vida das vagas

Os valores cobrem bem o fluxo de rascunho/publicação/arquivamento/cancelamento e o uso de strings facilita integração com banco e Swagger. Tudo ok aqui.

test/modules/jobs/services/create-job-draft.service.spec.ts (2)

8-10: LGTM!

A classe mock é simples e suficiente para os testes. Boa prática ao manter o mock focado apenas no método necessário.


64-85: LGTM!

O teste verifica corretamente a estrutura de retorno e a chamada ao repositório com os parâmetros esperados.

src/modules/jobs/guards/job-owner.guard.ts (1)

14-21: LGTM!

Boa prática verificar autenticação antes de fazer chamadas ao banco de dados. A ordem das validações está correta.

src/modules/jobs/services/publish-job.service.ts (1)

21-31: LGTM!

A lógica de publicação está correta. O retorno inclui informações relevantes como id, status, publishedAt e uma mensagem de sucesso.

src/modules/jobs/services/create-job-draft.service.ts (1)

10-17: A validação de undefined/null já é garantida pelo DTO.

Os campos salaryMin e salaryMax em CreateJobDraftDto possuem os decoradores @IsNotEmpty() e @IsNumber() (linhas 59-68 e 70-79), que garantem que ambos os valores são sempre números válidos quando chegam ao serviço. A comparação salaryMin > salaryMax é segura e não há risco de valores undefined ou null.

src/modules/jobs/jobs.module.ts (1)

20-48: LGTM!

Os novos serviços e o guard foram corretamente importados e registrados como providers no módulo. A estrutura segue as convenções do NestJS.

src/modules/jobs/services/cancel-job.service.ts (1)

16-27: LGTM!

A lógica de validação do status e a estrutura do retorno estão corretas. A verificação de que apenas jobs publicados podem ser cancelados está adequada.

src/modules/jobs/repository/job.repository.ts (2)

30-37: LGTM!

O método createJobDraft está bem implementado, criando o draft com status DRAFT e mapeando interestArea para prerequisites conforme a lógica de negócio.


60-73: LGTM!

Os métodos cancelJob e deleteJobDraft estão implementados corretamente, atualizando o status e registrando a data de cancelamento quando aplicável.

src/modules/jobs/dtos/complete-job.dto.ts (1)

43-64: LGTM!

As validações para contractType e journey estão corretas, com uso adequado de enum e decoradores do Swagger.

src/database/migrations/1765483583757-AddJobStatusFields.ts (3)

7-10: LGTM!

A tabela tb_saved_jobs está bem estruturada com índices apropriados para userId e jobId, otimizando consultas de jobs salvos por usuário ou por vaga.


11-23: LGTM!

A adição do enum jobStatus e dos campos temporais (publishedAt, canceledAt) está correta. A alteração das colunas para nullable suporta adequadamente o fluxo de draft onde esses campos são preenchidos posteriormente.


43-78: Verificar consistência do down para FK de tb_alerts.

O método up dropa a FK de tb_alerts (linha 7) e recria com ON DELETE NO ACTION (linha 40), mas o down recria com ON DELETE CASCADE (linha 77). Isso pode resultar em comportamento diferente após rollback. Verifique qual era o comportamento original pretendido.

src/modules/jobs/jobs.controller.ts (5)

39-49: LGTM! Importações organizadas corretamente.

As novas importações para DTOs, serviços, guards e decoradores Swagger estão bem estruturadas e são necessárias para a funcionalidade de rascunhos.


62-65: LGTM! Injeção de dependências correta.

Os novos serviços estão sendo injetados adequadamente no construtor do controller.


68-77: LGTM! Endpoint de criação de rascunho bem implementado.

O endpoint está protegido com autenticação, usa os DTOs corretos e delega a lógica para o serviço apropriado.


79-88: LGTM! Endpoint de publicação bem implementado.

O endpoint utiliza corretamente o JobOwnerGuard para garantir que apenas o proprietário possa publicar a vaga.


90-96: LGTM! Endpoint de cancelamento bem implementado.

A proteção com JobOwnerGuard garante que apenas o proprietário possa cancelar a vaga.

src/database/entities/jobs.entity.ts (5)

15-15: LGTM! Importação necessária adicionada.

O enum JobStatus é necessário para o novo campo de controle de estado das vagas.


28-32: LGTM! Campos opcionais para rascunhos.

Tornar description e prerequisites anuláveis é consistente com o sistema de rascunhos, onde esses campos podem ser preenchidos posteriormente.


45-58: LGTM! Campos de tipo agora opcionais para rascunhos.

A anulabilidade de type e typeContract permite criar rascunhos sem especificar esses campos inicialmente.


83-93: LGTM! Campos de contrato flexibilizados.

Tornar openEndedContract e affirmative anuláveis fornece mais flexibilidade no preenchimento de rascunhos.


108-110: LGTM! Formatação do relacionamento atualizada.

A mudança de aspas duplas para aspas simples é uma melhoria de consistência de estilo.

Comment on lines 4 to +42
database_soujunior:
image: postgres
container_name: database_soujunior
container_name: vagas-api-db
restart: always
ports:
- 5432:5432
- '5432:5432'
environment:
- POSTGRES_USER=docker
- POSTGRES_PASSWORD=ignite
- POSTGRES_DB=linkedin_backend
- POSTGRES_USER=${TYPEORM_USERNAME}
- POSTGRES_PASSWORD=${TYPEORM_PASSWORD}
- POSTGRES_DB=${TYPEORM_DATABASE}
volumes:
- pgdata:/data/postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
test: ['CMD-SHELL', 'pg_isready -U ${TYPEORM_USERNAME}']
interval: 10s
timeout: 5s
retries: 5

mailhog:
image: mailhog/mailhog
container_name: mailhog
restart: always
ports:
- '1025:1025'
- '8025:8025'

app:
build: .
container_name: linkedIn_Backend
container_name: linkedin-backend
restart: always
ports:
- 3000:3000
- '3001:3001'
volumes:
- .:/user/app
- .:/usr/app
depends_on:
database_soujunior:
condition: service_healthy
mailhog:
condition: service_started
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Procurar por configurações de porta em arquivos JavaScript/TypeScript
rg -i "listen|port" --type ts --type js -n | head -50

Repository: SouJunior/vagas-api

Length of output: 6107


🏁 Script executed:

# Verificar o Dockerfile
find . -name "Dockerfile" -type f | head -5

Repository: SouJunior/vagas-api

Length of output: 75


🏁 Script executed:

# Procurar por ambiente PORT ou 3001/3000
rg "3001|3000|PORT" --type ts --type js --type yml -n | head -50

Repository: SouJunior/vagas-api

Length of output: 90


🏁 Script executed:

# Read the Dockerfile
cat -n ./Dockerfile

Repository: SouJunior/vagas-api

Length of output: 541


🏁 Script executed:

# Look for main.ts where the app starts
find . -name "main.ts" -type f

Repository: SouJunior/vagas-api

Length of output: 76


🏁 Script executed:

# Search for port configuration more broadly
rg "3001|3000|\.listen" -n | head -30

Repository: SouJunior/vagas-api

Length of output: 34420


🏁 Script executed:

# Check package.json for startup command
cat -n package.json

Repository: SouJunior/vagas-api

Length of output: 4221


Corrigir porta da aplicação e WORKDIR no Dockerfile com urgência

Existem dois problemas críticos que impedem o container de funcionar corretamente:

  1. Mismatch de porta: O docker-compose mapeia 3001:3001, mas a aplicação NestJS escuta na porta 3000 por padrão (src/main.ts linha 40). Sem definir a variável PORT no environment do docker-compose, o mapeamento fica inoperante. Adicione - PORT=3001 ao serviço app ou ajuste o mapeamento para 3000:3000.

  2. WORKDIR incorreto no Dockerfile: Linha 3 define WORKDIR /user/app (deve ser /usr/app). Esse typo causa falhas no build e no volume bind. Corrija para WORKDIR /usr/app em todas as ocorrências (linhas 3, 15-17).

🤖 Prompt for AI Agents
In docker-compose.yml around lines 4 to 42: fix two issues—(1) the app service
port mismatch: either add the environment entry "- PORT=3001" under the app
service or change the ports mapping to "3000:3000" so the mapped host port
matches the NestJS app listening port; (2) correct the WORKDIR typo in the
project's Dockerfile (currently "WORKDIR /user/app") to "WORKDIR /usr/app" in
every occurrence (mentioned at lines ~3 and ~15-17 of the Dockerfile) so builds
and volume binds use the correct path.


@Column({ nullable: true })
contractText?: string;
contractText: string;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Inconsistência entre tipo TypeScript e schema do banco de dados.

O campo contractText foi alterado de opcional (contractText?: string) para obrigatório (contractText: string) no TypeScript, mas a coluna permanece nullable: true no banco. Isso cria uma discrepância onde o TypeScript exige o campo, mas o banco permite valores nulos.

Escolha uma das soluções:

Opção 1: Se o campo deve ser opcional (recomendado para rascunhos):

-contractText: string;
+contractText?: string;

Opção 2: Se o campo deve ser obrigatório:

-@Column({ nullable: true })
+@Column({ nullable: false })
 contractText: string;
📝 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
contractText: string;
contractText?: string;
🤖 Prompt for AI Agents
In src/database/entities/jobs.entity.ts around line 90, the TypeScript property
was changed from optional to required (contractText: string) while the DB column
is still nullable, causing a type/schema mismatch; either make the TS property
optional again (contractText?: string) to match nullable: true for drafts, or
make the DB column non-nullable (remove nullable: true / set nullable: false and
add a migration to set existing nulls to '' or a default) so the schema enforces
the required field—pick the correct option and update both the entity and
database migration accordingly.

Comment on lines 115 to +129
@Column({ nullable: false, default: 'ACTIVE' })
status: string;

@Column({
type: 'enum',
enum: JobStatus,
default: JobStatus.DRAFT,
})
jobStatus: JobStatus;

@Column({ nullable: true })
publishedAt: Date;

@Column({ nullable: true })
canceledAt: Date;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Dois campos de status podem causar confusão e bugs.

A entidade agora possui dois campos para controlar o estado:

  • status (linha 115-116): string com valor padrão 'ACTIVE'
  • jobStatus (linhas 118-123): enum JobStatus com valor padrão DRAFT

Ter dois campos de status pode levar a estados inconsistentes e dificultar a manutenção. Por exemplo, uma vaga pode estar com status: 'ACTIVE' mas jobStatus: DRAFT, criando ambiguidade.

Recomendações:

  1. Se status é usado para um propósito diferente (ex: soft delete com ACTIVE/ARCHIVED), considere renomeá-lo para deixar mais claro (ex: archivalStatus ou recordStatus)
  2. Documente claramente a diferença entre os dois campos
  3. Considere consolidar em um único campo de status se possível
  4. Adicione validação para evitar estados inconsistentes

Você gostaria que eu gerasse um script para verificar como status é usado no restante do código?

🤖 Prompt for AI Agents
In src/database/entities/jobs.entity.ts lines 115-129 there are two overlapping
status fields (status: string default 'ACTIVE' and jobStatus: JobStatus enum
default DRAFT) which can produce inconsistent state; decide on a single source
of truth: if the string field represents record lifecycle (soft-delete/archival)
rename it to a clearer name (e.g., archivalStatus or recordStatus) and update
the DB column and DTOs, otherwise consolidate both into the JobStatus enum;
update all usages across the codebase (search and replace) and database
migrations accordingly; add runtime validation (entity hooks or class-validator)
to prevent contradictory values and add a brief comment/JSDoc on the chosen
field explaining its purpose to avoid future confusion.

Comment on lines +118 to +129
@Column({
type: 'enum',
enum: JobStatus,
default: JobStatus.DRAFT,
})
jobStatus: JobStatus;

@Column({ nullable: true })
publishedAt: Date;

@Column({ nullable: true })
canceledAt: Date;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "*.ts" | grep -i "publish" | grep -i "service" | head -20

Repository: SouJunior/vagas-api

Length of output: 170


🏁 Script executed:

cat -n ./src/modules/jobs/services/publish-job.service.ts

Repository: SouJunior/vagas-api

Length of output: 1249


🏁 Script executed:

# Find and read the CompleteJobDto
find . -type f -name "*complete-job.dto*" | head -5

Repository: SouJunior/vagas-api

Length of output: 106


🏁 Script executed:

cat -n ./src/modules/jobs/dtos/complete-job.dto.ts

Repository: SouJunior/vagas-api

Length of output: 2471


🏁 Script executed:

# Search for controllers that use PublishJobService
rg -B 5 -A 10 "PublishJobService" --type=ts -g '!*.spec.ts' | grep -A 10 "@Controller\|@Post\|@Put\|execute"

Repository: SouJunior/vagas-api

Length of output: 1445


🏁 Script executed:

# Read the jobs controller to find the publish endpoint
cat -n ./src/modules/jobs/jobs.controller.ts | grep -A 20 -B 2 "publish"

Repository: SouJunior/vagas-api

Length of output: 3064


🏁 Script executed:

# Check for ValidationPipe setup
find . -name "main.ts" -o -name "app.module.ts" | head -5

Repository: SouJunior/vagas-api

Length of output: 96


🏁 Script executed:

cat -n ./src/main.ts

Repository: SouJunior/vagas-api

Length of output: 1588


🏁 Script executed:

# Find and read the job repository
find . -type f -name "*job.repository*" | grep -v spec

Repository: SouJunior/vagas-api

Length of output: 110


🏁 Script executed:

cat -n ./src/modules/jobs/repository/job.repository.ts

Repository: SouJunior/vagas-api

Length of output: 7671


🏁 Script executed:

cat -n ./src/database/entities/jobs.entity.ts

Repository: SouJunior/vagas-api

Length of output: 4346


🏁 Script executed:

# Check CreateJobDraftDto
cat -n ./src/modules/jobs/dtos/create-job-draft.dto.ts

Repository: SouJunior/vagas-api

Length of output: 2475


Validação de campos obrigatórios na publicação está parcialmente implementada, mas sem lógica explícita no serviço.

A validação ocorre no nível do DTO (CompleteJobDto) via decoradores @IsNotEmpty e o ValidationPipe global, garantindo que description, requirements, contractType, journey e selectionProcess sejam fornecidos. No entanto, PublishJobService.execute() não contém lógica explícita de validação — depende integralmente do pipeline de validação do NestJS. Para maior robustez e clareza, considere adicionar validações explícitas no PublishJobService, especialmente para cenários onde o repositório possa ser chamado diretamente, ou garantir que toda publicação passe obrigatoriamente pela validação do DTO.

🤖 Prompt for AI Agents
In src/database/entities/jobs.entity.ts around lines 118-129, the entity marks
publishedAt and canceledAt nullable and DTO-level validation exists but
PublishJobService.execute() lacks explicit checks; update
PublishJobService.execute to explicitly validate required publication fields
(description, requirements, contractType, journey, selectionProcess) before
persisting: fetch the job, verify each required field is present and non-empty,
throw a proper HTTP/Domain error (e.g., BadRequest) when any are missing, set
jobStatus to PUBLISHED and publishedAt to now, then save via the repository;
this ensures robustness if the service is called bypassing the global
ValidationPipe.

Comment on lines +36 to +37
await queryRunner.query(`ALTER TABLE "tb_saved_jobs" ADD CONSTRAINT "FK_3845b06bfda63ccc1da359b378a" FOREIGN KEY ("userId") REFERENCES "tb_users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "tb_saved_jobs" ADD CONSTRAINT "FK_9bbd9a1f3bb4942f0471816b111" FOREIGN KEY ("jobId") REFERENCES "tb_jobs"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Revisar comportamento ON DELETE para constraint de jobId em tb_saved_jobs (linha 37).

A FK para jobId usa ON DELETE NO ACTION, o que impedirá a exclusão de um job enquanto existirem referências em tb_saved_jobs. Isso é inconsistente com a FK para userId na mesma tabela (linha 36), que usa ON DELETE CASCADE. Considere usar ON DELETE CASCADE para permitir limpeza automática de saved_jobs ao deletar um job, ou ON DELETE SET NULL se preferir manter o histórico com jobId nulo.

🤖 Prompt for AI Agents
In src/database/migrations/1765483583757-AddJobStatusFields.ts around lines
36–37, the foreign key constraint for tb_saved_jobs.jobId is created with ON
DELETE NO ACTION which prevents deleting a job while saved_job rows exist;
change the constraint to ON DELETE CASCADE to match the userId behavior (or, if
the design requires preserving saved job records, change to ON DELETE SET NULL
and make the jobId column nullable) and update the migration SQL accordingly so
saved_job rows are cleaned up or preserved as intended.

Comment on lines +29 to +33
if (job.company_id !== user.id) {
throw new ForbiddenException(
'You do not have permission to modify this job',
);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, examine the job-owner.guard.ts file to understand the context
cat -n src/modules/jobs/guards/job-owner.guard.ts

Repository: SouJunior/vagas-api

Length of output: 1230


🏁 Script executed:

# Search for request.user patterns to understand user structure
rg -n "request\.user" --type ts -A 2 -B 2

Repository: SouJunior/vagas-api

Length of output: 1854


🏁 Script executed:

# Search for authentication strategy and user setup
rg -n "strategy|AuthGuard|auth" --type ts src/modules -A 2 | head -100

Repository: SouJunior/vagas-api

Length of output: 6362


🏁 Script executed:

# Check for company_id property usage on user object
rg -n "user\.company_id|user\.id" --type ts -B 1 -A 1

Repository: SouJunior/vagas-api

Length of output: 2577


🏁 Script executed:

# Search for user entity/interface definitions
rg -n "interface User|class User|type User" --type ts -A 5

Repository: SouJunior/vagas-api

Length of output: 6548


🏁 Script executed:

# Check LoggedCompany decorator to understand its structure
cat -n src/modules/auth/decorator/logged-company.decorator.ts

Repository: SouJunior/vagas-api

Length of output: 656


🏁 Script executed:

# Check LoggedUser decorator for comparison
cat -n src/modules/auth/decorator/logged-user.decorator.ts

Repository: SouJunior/vagas-api

Length of output: 613


🏁 Script executed:

# Check JWT strategy to see what gets attached to request.user
cat -n src/modules/auth/jtw/jwt.strategy.ts

Repository: SouJunior/vagas-api

Length of output: 1625


🏁 Script executed:

# Check Job entity structure to understand company_id
fd job.entity.ts -type f -exec cat -n {} \;

Repository: SouJunior/vagas-api

Length of output: 233


🏁 Script executed:

# Check how jobs controller uses these decorators
rg -n "@LoggedCompany|@LoggedUser" src/modules/jobs -B 2 -A 5

Repository: SouJunior/vagas-api

Length of output: 1645


🏁 Script executed:

# Find and check CompaniesEntity structure
find . -name "*compan*entity*" -type f | head -20

Repository: SouJunior/vagas-api

Length of output: 106


🏁 Script executed:

# Search for CompaniesEntity definition
rg -n "class CompaniesEntity|export class CompaniesEntity" --type ts -A 10

Repository: SouJunior/vagas-api

Length of output: 715


🏁 Script executed:

# Check if there are any middleware or guards that ensure only companies access job endpoints
rg -n "publishJob|deleteJobDraft|cancelJob" src/modules/jobs/jobs.controller.ts -B 5 -A 2

Repository: SouJunior/vagas-api

Length of output: 1312


🏁 Script executed:

# Final verification: Check if job endpoints should only be accessible by companies
# by looking at how jobs are created to understand the expected user type
cat -n src/modules/jobs/jobs.controller.ts | grep -A 10 "createJobDraft\|createNewJob"

Repository: SouJunior/vagas-api

Length of output: 1279


🏁 Script executed:

# Check if there's any role validation or company check elsewhere
rg -n "isCompany|role|company" src/modules/auth/decorator --type ts -A 3

Repository: SouJunior/vagas-api

Length of output: 45


A comparação job.company_id !== user.id possui um flaw semântico crítico.

O guarda assume que request.user.id representa uma empresa, mas a estratégia JWT (jwt.strategy.ts) pode retornar tanto UserEntity quanto CompaniesEntity. Como os endpoints de modificação de job (publishJob, cancelJob, deleteJobDraft) usam apenas @UseGuards(AuthGuard()) sem a validação explícita @LoggedCompany() presente nos endpoints de criação, um usuário comum autenticado poderia chegar até o guarda, causando falha na comparação ou comportamento de segurança incorreto.

Solução necessária: Adicione @LoggedCompany() aos endpoints de modificação de job (linhas 83, 94, 102) para garantir que apenas empresas acessem estes endpoints, alinhando com o padrão usado em createJobDraft e createNewJob.

🤖 Prompt for AI Agents
In src/modules/jobs/guards/job-owner.guard.ts around lines 29 to 33 the guard
compares job.company_id !== user.id but this is unsafe because request.user may
be a UserEntity or CompaniesEntity; update the endpoints that modify jobs to
ensure request.user is always a CompaniesEntity by adding the @LoggedCompany()
decorator to the publishJob, cancelJob, and deleteJobDraft controller methods
(around lines 83, 94, 102) so only companies can reach this guard and the id
comparison is semantically correct.

jobId: string,
@Body('content') content: string,
) {
async archivedJob(@Param() jobId: string, @Body('content') content: string) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Corrigir bug no decorador de parâmetro.

O decorador @Param() sem argumentos retorna o objeto inteiro de parâmetros, não apenas o id. Isso provavelmente causará um erro no serviço que espera uma string.

Aplique esta correção:

-async archivedJob(@Param() jobId: string, @Body('content') content: string) {
+async archivedJob(@Param('id') jobId: string, @Body('content') content: string) {
   return this.deleteJobService.execute(jobId, content);
 }
📝 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
async archivedJob(@Param() jobId: string, @Body('content') content: string) {
async archivedJob(@Param('id') jobId: string, @Body('content') content: string) {
return this.deleteJobService.execute(jobId, content);
}
🤖 Prompt for AI Agents
src/modules/jobs/jobs.controller.ts around line 159: o decorador @Param() está
retornando o objeto completo de parâmetros em vez de apenas o id, causando
type/valor inesperado; substitua-o por @Param('jobId') para extrair a string do
parâmetro de rota (ou use o nome exato do parâmetro de rota se for diferente),
mantendo o resto da assinatura igual.

Comment on lines +43 to +51
await this.jobsRepository
.update(jobId, {
description: completeData.description,
prerequisites: completeData.requirements.join(', '),
benefits: completeData.benefits?.join(', '),
typeContract: completeData.contractType,
contractType: completeData.journey,
contractText: completeData.selectionProcess.join(' -> '),
content: completeData.additionalInfo,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "*job*.entity.ts" -o -name "*job*.model.ts" | head -20

Repository: SouJunior/vagas-api

Length of output: 101


🏁 Script executed:

rg "class JobsEntity|interface.*Job" --type ts -A 30

Repository: SouJunior/vagas-api

Length of output: 2249


🏁 Script executed:

rg "contractType|typeContract" --type ts -B 2 -A 2

Repository: SouJunior/vagas-api

Length of output: 6238


🏁 Script executed:

rg "duration|journey" src/modules/jobs/dtos/complete-job.dto.ts -B 2 -A 2

Repository: SouJunior/vagas-api

Length of output: 137


🏁 Script executed:

cat -n src/database/entities/jobs.entity.ts | sed -n '1,100p'

Repository: SouJunior/vagas-api

Length of output: 3015


🏁 Script executed:

cat -n src/modules/jobs/dtos/complete-job.dto.ts

Repository: SouJunior/vagas-api

Length of output: 2471


Corrigir atribuição de campos em publishJob (linhas 48-49).

A atribuição está incorreta:

  • contractType recebe completeData.journey (jornada de trabalho) quando deveria receber dados de duração/tipo de contrato
  • typeContract recebe corretamente completeData.contractType (enum CLT/PJ/OTHER)

O campo contractType está sendo preenchido com dados de jornada de trabalho (ex: "40 horas semanais"), causando perda de informação. Verifique qual dado deveria ser atribuído a contractType ou se este campo deve ser removido da atualização.

🤖 Prompt for AI Agents
In src/modules/jobs/repository/job.repository.ts around lines 43 to 51, the
update assigns contract fields incorrectly: currently typeContract is set to
completeData.contractType (correct) but contractType is incorrectly set to
completeData.journey (work schedule). Change the update so that typeContract
remains completeData.contractType and contractType receives the appropriate
contract duration/type field (e.g., completeData.contractDuration or the correct
property name from CompleteData); if there is no separate duration field, remove
contractType from the update to avoid overwriting it with journey — verify the
actual CompleteData property and use that instead.

Comment on lines +13 to +15
if (!job) {
throw new BadRequestException('Job not found');
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistência no tipo de exceção para "Job not found".

O JobOwnerGuard usa NotFoundException para vaga não encontrada, mas este serviço usa BadRequestException. Se o guard for aplicado antes do serviço, esta verificação será redundante. Caso contrário, considere usar NotFoundException para consistência semântica.

     if (!job) {
-      throw new BadRequestException('Job not found');
+      throw new NotFoundException('Job not found');
     }

Não esqueça de adicionar o import:

-import { BadRequestException, Injectable } from '@nestjs/common';
+import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
📝 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
if (!job) {
throw new BadRequestException('Job not found');
}
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
if (!job) {
throw new NotFoundException('Job not found');
}
🤖 Prompt for AI Agents
In src/modules/jobs/services/publish-job.service.ts around lines 13 to 15,
replace the thrown BadRequestException('Job not found') with
NotFoundException('Job not found') to match the semantic used by JobOwnerGuard
and maintain consistency; also add the corresponding import for
NotFoundException from @nestjs/common at the top of the file (and remove the
unused BadRequestException import if no longer used).

Comment on lines +6 to +29
export function CancelJobSwagger() {
return applyDecorators(
ApiResponse({
status: HttpStatus.OK,
description: 'Vaga cancelada com sucesso',
}),
ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: 'Modelo de erro',
type: BadRequestSwagger,
}),
ApiResponse({
status: HttpStatus.UNAUTHORIZED,
description: 'Não autorizado',
type: UnauthorizedSwagger,
}),
ApiResponse({
status: HttpStatus.FORBIDDEN,
description: 'Sem permissão para modificar esta vaga',
}),
ApiOperation({
summary: 'Cancelar vaga publicada',
}),
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Falta documentar a resposta 404 (Not Found).

O JobOwnerGuard lança NotFoundException quando a vaga não é encontrada. A documentação Swagger deve incluir essa resposta para refletir corretamente o comportamento da API.

     ApiResponse({
       status: HttpStatus.FORBIDDEN,
       description: 'Sem permissão para modificar esta vaga',
     }),
+    ApiResponse({
+      status: HttpStatus.NOT_FOUND,
+      description: 'Vaga não encontrada',
+    }),
     ApiOperation({
       summary: 'Cancelar vaga publicada',
     }),
📝 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 function CancelJobSwagger() {
return applyDecorators(
ApiResponse({
status: HttpStatus.OK,
description: 'Vaga cancelada com sucesso',
}),
ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: 'Modelo de erro',
type: BadRequestSwagger,
}),
ApiResponse({
status: HttpStatus.UNAUTHORIZED,
description: 'Não autorizado',
type: UnauthorizedSwagger,
}),
ApiResponse({
status: HttpStatus.FORBIDDEN,
description: 'Sem permissão para modificar esta vaga',
}),
ApiOperation({
summary: 'Cancelar vaga publicada',
}),
);
export function CancelJobSwagger() {
return applyDecorators(
ApiResponse({
status: HttpStatus.OK,
description: 'Vaga cancelada com sucesso',
}),
ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: 'Modelo de erro',
type: BadRequestSwagger,
}),
ApiResponse({
status: HttpStatus.UNAUTHORIZED,
description: 'Não autorizado',
type: UnauthorizedSwagger,
}),
ApiResponse({
status: HttpStatus.FORBIDDEN,
description: 'Sem permissão para modificar esta vaga',
}),
ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Vaga não encontrada',
}),
ApiOperation({
summary: 'Cancelar vaga publicada',
}),
);
}
🤖 Prompt for AI Agents
In src/shared/Swagger/decorators/jobs/cancel-job.swagger.ts around lines 6 to
29, the Swagger responses miss the 404 Not Found case thrown by JobOwnerGuard;
add an ApiResponse for status HttpStatus.NOT_FOUND with a descriptive message
(e.g., 'Vaga não encontrada') and the appropriate error DTO/type (e.g.,
NotFoundSwagger), and if that type doesn't exist, import or create it and use it
in the ApiResponse so Swagger reflects the NotFoundException behavior.

Copy link
Contributor

@MikaelMelo1 MikaelMelo1 left a comment

Choose a reason for hiding this comment

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

Alguns pontos precisam de ajustes.

Copy link
Contributor

Choose a reason for hiding this comment

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

  • Dá uma olhada na parte de enum no @column. os campos estão como string, o ideal é tipar direto com o enum e declarar no decorator.

  • Veja a questão de typeContract e contractType. Os dois estão representando a mesma coisa, então seria bom padronizar em um só campo

  • Revisar o campo openEndedContract, que está com default e nullable juntos.

  • Campos de texto (description, prerequisites, contractText) podem ter o tipo/tamanho mais definido

Copy link
Contributor

Choose a reason for hiding this comment

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

O Enum no contractType: dá uma olhada no alinhamento do DTO (usa enum) e a entity, pra garantir que os valores salvos sejam compatíveis.

typeContract e contractType: bom seria padronizar um

Campos array no DTO (requirements, benefits, selectionProcess): o banco ta preparado pra receber o array?

Tamanho dos textos (description, additionalInfo): o limite do DTO bate com o banco?

Copy link
Contributor

Choose a reason for hiding this comment

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

city + modality: a validação está ok, bom é confirmar se no fluxo da aplicação o campo city é realmente tratado como obrigatório quando não for remoto.

salaryMin e salaryMax: seria bom garantir no serviço que o salário mínimo não seja maior que o máximo, pra evitar inconsistência de dados.

@IsNumber: só conferir se o ValidationPipe está com transform: true, senão esses campos podem chegar como string.

company_id: o campo não tem validação nem ApiProperty. avaliar se isso é intencional ou se precisa de um mínimo de padronização.

Copy link
Contributor

Choose a reason for hiding this comment

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

jobId vindo dos params: vale confirmar se o id sempre existe na rota. Se não, vai resultar no repo undefined.

Comparação job.company_id !== user.id: conferir se os dois valores têm o mesmo tipo (ex: ambos string ou number) assim vamos evitar falha na comparação

Mensagens de erro: estão claras , só avaliar se esse nível de detalhe é o esperado para um ForbiddenException (nada de urgente)

Responsabilidade do Guard: o guard está buscando no banco, o que funciona, mas vale só validar se isso está alinhado com o restante do projeto

Copy link
Contributor

Choose a reason for hiding this comment

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

publishJob: da uma atenção notypeContract e contractType

Conversão de arrays para string (join): ta certo o formato esperado?

orderByColumn vindo do request: está sendo validado?

searchJobs: o ILIKE esta sendo montado direto na string.

findOneById: esta retornando como any, consegue tipar?

Copy link
Contributor

Choose a reason for hiding this comment

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

as DELETE e PATCH ':id estão com responsabilidade parecidas, da uma olhada.

Guards: JobOwnerGuard está aplicado nas rotas, verifique isso pra mim

archivedJob: @param() está sem chave (jobId: string), o valor ta chegando?

Search (/search/:keyword): mistura param, query e body., está certo?

Swagger imports duplicados/nome estranho (get-all-jobs-of-logged-company.swagger copy): da uma organizada

Copy link
Contributor

Choose a reason for hiding this comment

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

O bom seria não colocar no PR junto.

Copy link
Contributor

Choose a reason for hiding this comment

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

Da uma conferida se é isso mesmo.

Copy link
Contributor

Choose a reason for hiding this comment

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

Da uma conferida no que o Bot falou, o resto está ok.

Copy link
Contributor

Choose a reason for hiding this comment

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

FORBIDDEN: está alinhado com o comportamento do guard?

BadRequestSwagger cobre todos os cenários de erro?

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.

2 participants