-
-
Notifications
You must be signed in to change notification settings - Fork 40
finalizando implementacao e testes da criacao de rascunhos em vaga #203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -12,6 +12,7 @@ import { JobsAffirmativeTypeEnum } from '../../modules/jobs/enums/job-affirmativ | |||||
| import { JobsTypeContractEnum } from '../../modules/jobs/enums/job-contract-type.enum'; | ||||||
| import { JobsModalityEnum } from '../../modules/jobs/enums/job-modality.enum'; | ||||||
| import { JobsTypeEnum } from '../../modules/jobs/enums/job-type.enum'; | ||||||
| import { JobStatus } from '../../modules/jobs/enums/job-status.enum'; | ||||||
| import { ApplicationEntity } from './applications.entity'; | ||||||
| import { CommentsEntity } from './comments.entity'; | ||||||
| import { CompaniesEntity } from './companies.entity'; | ||||||
|
|
@@ -27,10 +28,10 @@ export class JobsEntity { | |||||
| @Column() | ||||||
| title: string; | ||||||
|
|
||||||
| @Column() | ||||||
| @Column({ nullable: true }) | ||||||
| description: string; | ||||||
|
|
||||||
| @Column() | ||||||
| @Column({ nullable: true }) | ||||||
| prerequisites: string; | ||||||
|
|
||||||
| @Column({ nullable: true }) | ||||||
|
|
@@ -44,7 +45,7 @@ export class JobsEntity { | |||||
| JobsTypeEnum.TRAINEE, | ||||||
| JobsTypeEnum.INTERNSHIP, | ||||||
| ], | ||||||
| default: JobsTypeEnum.JUNIOR, | ||||||
| nullable: true, | ||||||
| }) | ||||||
| type: string; | ||||||
|
|
||||||
|
|
@@ -55,7 +56,6 @@ export class JobsEntity { | |||||
| JobsTypeContractEnum.PJ, | ||||||
| JobsTypeContractEnum.OTHER, | ||||||
| ], | ||||||
| default: JobsTypeContractEnum.CLT, | ||||||
| nullable: true, | ||||||
| }) | ||||||
| typeContract: string; | ||||||
|
|
@@ -83,18 +83,16 @@ export class JobsEntity { | |||||
| @Column({ nullable: true }) | ||||||
| city: string; | ||||||
|
|
||||||
| @Column({ | ||||||
| default: true, | ||||||
| }) | ||||||
| @Column({ default: true, nullable: true }) | ||||||
| openEndedContract: boolean; | ||||||
|
|
||||||
| @Column({ nullable: true }) | ||||||
| contractType: string; | ||||||
|
|
||||||
| @Column({ nullable: true }) | ||||||
| contractText?: string; | ||||||
| contractText: string; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inconsistência entre tipo TypeScript e schema do banco de dados. O campo 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
Suggested change
🤖 Prompt for AI Agents |
||||||
|
|
||||||
| @Column({ default: true }) | ||||||
| @Column({ default: true, nullable: true }) | ||||||
| affirmative: boolean; | ||||||
|
|
||||||
| @Column({ | ||||||
|
|
@@ -121,6 +119,19 @@ export class JobsEntity { | |||||
| @Column({ nullable: false, default: StatusEnum.ACTIVE }) | ||||||
| status: StatusEnum; | ||||||
|
|
||||||
| @Column({ | ||||||
| type: 'enum', | ||||||
| enum: JobStatus, | ||||||
| default: JobStatus.DRAFT, | ||||||
| }) | ||||||
| jobStatus: JobStatus; | ||||||
|
|
||||||
| @Column({ nullable: true }) | ||||||
| publishedAt: Date; | ||||||
|
|
||||||
| @Column({ nullable: true }) | ||||||
| canceledAt: Date; | ||||||
|
Comment on lines
+122
to
+133
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -type f -name "*.ts" | grep -i "publish" | grep -i "service" | head -20Repository: SouJunior/vagas-api Length of output: 170 🏁 Script executed: cat -n ./src/modules/jobs/services/publish-job.service.tsRepository: SouJunior/vagas-api Length of output: 1249 🏁 Script executed: # Find and read the CompleteJobDto
find . -type f -name "*complete-job.dto*" | head -5Repository: SouJunior/vagas-api Length of output: 106 🏁 Script executed: cat -n ./src/modules/jobs/dtos/complete-job.dto.tsRepository: 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 -5Repository: SouJunior/vagas-api Length of output: 96 🏁 Script executed: cat -n ./src/main.tsRepository: SouJunior/vagas-api Length of output: 1588 🏁 Script executed: # Find and read the job repository
find . -type f -name "*job.repository*" | grep -v specRepository: SouJunior/vagas-api Length of output: 110 🏁 Script executed: cat -n ./src/modules/jobs/repository/job.repository.tsRepository: SouJunior/vagas-api Length of output: 7671 🏁 Script executed: cat -n ./src/database/entities/jobs.entity.tsRepository: SouJunior/vagas-api Length of output: 4346 🏁 Script executed: # Check CreateJobDraftDto
cat -n ./src/modules/jobs/dtos/create-job-draft.dto.tsRepository: 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 ( 🤖 Prompt for AI Agents |
||||||
|
|
||||||
| @OneToMany(() => CommentsEntity, (comment) => comment.job, { | ||||||
| cascade: true, | ||||||
| }) | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| import { MigrationInterface, QueryRunner } from "typeorm"; | ||
|
|
||
| export class AddJobStatusFields1765483583757 implements MigrationInterface { | ||
| name = 'AddJobStatusFields1765483583757' | ||
|
|
||
| public async up(queryRunner: QueryRunner): Promise<void> { | ||
| await queryRunner.query(`ALTER TABLE "tb_alerts" DROP CONSTRAINT "FK_a60dd357707a39803ce4cfbd90b"`); | ||
| await queryRunner.query(`CREATE TABLE "tb_saved_jobs" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "savedAt" TIMESTAMP NOT NULL DEFAULT now(), "expiresAt" TIMESTAMP NOT NULL, "userId" uuid NOT NULL, "jobId" uuid, CONSTRAINT "PK_b69821433d55c7266698911ead6" PRIMARY KEY ("id"))`); | ||
| await queryRunner.query(`CREATE INDEX "IDX_3845b06bfda63ccc1da359b378" ON "tb_saved_jobs" ("userId") `); | ||
| await queryRunner.query(`CREATE INDEX "IDX_9bbd9a1f3bb4942f0471816b11" ON "tb_saved_jobs" ("jobId") `); | ||
| await queryRunner.query(`CREATE TYPE "public"."tb_jobs_jobstatus_enum" AS ENUM('DRAFT', 'PUBLISHED', 'ARCHIVED', 'CANCELED')`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" ADD "jobStatus" "public"."tb_jobs_jobstatus_enum" NOT NULL DEFAULT 'DRAFT'`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" ADD "publishedAt" TIMESTAMP`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" ADD "canceledAt" TIMESTAMP`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" ALTER COLUMN "description" DROP NOT NULL`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" ALTER COLUMN "prerequisites" DROP NOT NULL`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" ALTER COLUMN "type" DROP NOT NULL`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" ALTER COLUMN "type" DROP DEFAULT`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" ALTER COLUMN "typeContract" DROP DEFAULT`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" ALTER COLUMN "openEndedContract" DROP NOT NULL`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" ALTER COLUMN "affirmative" DROP NOT NULL`); | ||
| await queryRunner.query(`ALTER TABLE "tb_candidacies" DROP COLUMN "date_candidacy"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_candidacies" ADD "date_candidacy" date NOT NULL DEFAULT now()`); | ||
| await queryRunner.query(`ALTER TABLE "tb_certifications" ADD CONSTRAINT "FK_75f15015611ef7f444ae3fade23" FOREIGN KEY ("personal_data_id") REFERENCES "tb_personal_data"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); | ||
| await queryRunner.query(`ALTER TABLE "tb_courses" ADD CONSTRAINT "FK_beb3c749f9d5000af64c4442989" FOREIGN KEY ("personal_data_id") REFERENCES "tb_personal_data"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); | ||
| await queryRunner.query(`ALTER TABLE "tb_languages" ADD CONSTRAINT "FK_ecd946bdf16c31509a966a5002f" FOREIGN KEY ("personal_data_id") REFERENCES "tb_personal_data"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); | ||
| await queryRunner.query(`ALTER TABLE "tb_work_experiences" ADD CONSTRAINT "FK_e668e3b05cc240131e27a41e5b5" FOREIGN KEY ("personal_data_id") REFERENCES "tb_personal_data"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); | ||
| await queryRunner.query(`ALTER TABLE "tb_curriculum" ADD CONSTRAINT "FK_2c53580e4d1f616f6ffee74ba51" FOREIGN KEY ("user_id") REFERENCES "tb_users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); | ||
| await queryRunner.query(`ALTER TABLE "tb_comments" ADD CONSTRAINT "FK_b7d6e83dbfa5e98148529803894" FOREIGN KEY ("user_id") REFERENCES "tb_users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); | ||
| await queryRunner.query(`ALTER TABLE "tb_comments" ADD CONSTRAINT "FK_d4e57c6e48ba93300dc02734659" FOREIGN KEY ("job_id") REFERENCES "tb_jobs"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" ADD CONSTRAINT "FK_a64a855331c54d698baddb03b6f" FOREIGN KEY ("company_id") REFERENCES "tb_companies"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); | ||
| await queryRunner.query(`ALTER TABLE "tb_applications" ADD CONSTRAINT "FK_1408b5e5220c7d0fe25573cb3b9" FOREIGN KEY ("job_id") REFERENCES "tb_jobs"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); | ||
| await queryRunner.query(`ALTER TABLE "tb_applications" ADD CONSTRAINT "FK_08c82d5bde7b75b17f54460adda" FOREIGN KEY ("user_id") REFERENCES "tb_users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); | ||
| await queryRunner.query(`ALTER TABLE "tb_applications" ADD CONSTRAINT "FK_f3502a850c1b2b75a5dbe2c04ee" FOREIGN KEY ("curriculum_id") REFERENCES "tb_curriculum"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); | ||
| await queryRunner.query(`ALTER TABLE "tb_users" ADD CONSTRAINT "FK_0afe3b230cbd95a08c72f9df3f0" FOREIGN KEY ("personalDataId") REFERENCES "tb_personal_data"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); | ||
| 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`); | ||
|
Comment on lines
+36
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Revisar comportamento ON DELETE para constraint de A FK para 🤖 Prompt for AI Agents |
||
| await queryRunner.query(`ALTER TABLE "tb_reports" ADD CONSTRAINT "FK_c95aa2c975cb6369772d5f15c7d" FOREIGN KEY ("job_id") REFERENCES "tb_jobs"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); | ||
| await queryRunner.query(`ALTER TABLE "tb_reports" ADD CONSTRAINT "FK_86d265112a095a1daae2e34d669" FOREIGN KEY ("user_id") REFERENCES "tb_users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); | ||
| await queryRunner.query(`ALTER TABLE "tb_alerts" ADD CONSTRAINT "FK_a60dd357707a39803ce4cfbd90b" FOREIGN KEY ("user_id") REFERENCES "tb_users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); | ||
| } | ||
|
|
||
| public async down(queryRunner: QueryRunner): Promise<void> { | ||
| await queryRunner.query(`ALTER TABLE "tb_alerts" DROP CONSTRAINT "FK_a60dd357707a39803ce4cfbd90b"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_reports" DROP CONSTRAINT "FK_86d265112a095a1daae2e34d669"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_reports" DROP CONSTRAINT "FK_c95aa2c975cb6369772d5f15c7d"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_saved_jobs" DROP CONSTRAINT "FK_9bbd9a1f3bb4942f0471816b111"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_saved_jobs" DROP CONSTRAINT "FK_3845b06bfda63ccc1da359b378a"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_users" DROP CONSTRAINT "FK_0afe3b230cbd95a08c72f9df3f0"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_applications" DROP CONSTRAINT "FK_f3502a850c1b2b75a5dbe2c04ee"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_applications" DROP CONSTRAINT "FK_08c82d5bde7b75b17f54460adda"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_applications" DROP CONSTRAINT "FK_1408b5e5220c7d0fe25573cb3b9"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" DROP CONSTRAINT "FK_a64a855331c54d698baddb03b6f"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_comments" DROP CONSTRAINT "FK_d4e57c6e48ba93300dc02734659"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_comments" DROP CONSTRAINT "FK_b7d6e83dbfa5e98148529803894"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_curriculum" DROP CONSTRAINT "FK_2c53580e4d1f616f6ffee74ba51"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_work_experiences" DROP CONSTRAINT "FK_e668e3b05cc240131e27a41e5b5"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_languages" DROP CONSTRAINT "FK_ecd946bdf16c31509a966a5002f"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_courses" DROP CONSTRAINT "FK_beb3c749f9d5000af64c4442989"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_certifications" DROP CONSTRAINT "FK_75f15015611ef7f444ae3fade23"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_candidacies" DROP COLUMN "date_candidacy"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_candidacies" ADD "date_candidacy" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" ALTER COLUMN "affirmative" SET NOT NULL`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" ALTER COLUMN "openEndedContract" SET NOT NULL`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" ALTER COLUMN "typeContract" SET DEFAULT 'CLT'`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" ALTER COLUMN "type" SET DEFAULT 'JUNIOR'`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" ALTER COLUMN "type" SET NOT NULL`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" ALTER COLUMN "prerequisites" SET NOT NULL`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" ALTER COLUMN "description" SET NOT NULL`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" DROP COLUMN "canceledAt"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" DROP COLUMN "publishedAt"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_jobs" DROP COLUMN "jobStatus"`); | ||
| await queryRunner.query(`DROP TYPE "public"."tb_jobs_jobstatus_enum"`); | ||
| await queryRunner.query(`DROP INDEX "public"."IDX_9bbd9a1f3bb4942f0471816b11"`); | ||
| await queryRunner.query(`DROP INDEX "public"."IDX_3845b06bfda63ccc1da359b378"`); | ||
| await queryRunner.query(`DROP TABLE "tb_saved_jobs"`); | ||
| await queryRunner.query(`ALTER TABLE "tb_alerts" ADD CONSTRAINT "FK_a60dd357707a39803ce4cfbd90b" FOREIGN KEY ("user_id") REFERENCES "tb_users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); | ||
| } | ||
|
|
||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| import { ApiProperty } from '@nestjs/swagger'; | ||
| import { | ||
| IsArray, | ||
| IsEnum, | ||
| IsNotEmpty, | ||
| IsOptional, | ||
| IsString, | ||
| MaxLength, | ||
| } from 'class-validator'; | ||
| import { JobsTypeContractEnum } from '../enums/job-contract-type.enum'; | ||
|
|
||
| export class CompleteJobDto { | ||
| @IsNotEmpty() | ||
| @IsString() | ||
| @MaxLength(5000) | ||
| @ApiProperty({ | ||
| required: true, | ||
| description: 'Descrição da vaga', | ||
| example: 'Buscamos um desenvolvedor backend...', | ||
| }) | ||
| description: string; | ||
|
|
||
| @IsNotEmpty() | ||
| @IsArray() | ||
| @IsString({ each: true }) | ||
| @ApiProperty({ | ||
| required: true, | ||
| description: 'Requisitos da vaga', | ||
| example: ['Node.js', 'TypeScript', 'NestJS'], | ||
| }) | ||
| requirements: string[]; | ||
|
|
||
| @IsOptional() | ||
| @IsArray() | ||
| @IsString({ each: true }) | ||
| @ApiProperty({ | ||
| required: false, | ||
| description: 'Benefícios', | ||
| example: ['Vale alimentação', 'Plano de saúde'], | ||
| }) | ||
| benefits?: string[]; | ||
|
|
||
| @IsNotEmpty() | ||
| @IsEnum(JobsTypeContractEnum) | ||
| @ApiProperty({ | ||
| required: true, | ||
| description: 'Tipo de contrato', | ||
| example: JobsTypeContractEnum.CLT, | ||
| enum: [ | ||
| JobsTypeContractEnum.CLT, | ||
| JobsTypeContractEnum.PJ, | ||
| JobsTypeContractEnum.OTHER, | ||
| ], | ||
| }) | ||
| contractType: JobsTypeContractEnum; | ||
|
|
||
| @IsNotEmpty() | ||
| @IsString() | ||
| @ApiProperty({ | ||
| required: true, | ||
| description: 'Jornada de trabalho', | ||
| example: '40 horas semanais', | ||
| }) | ||
| journey: string; | ||
|
|
||
| @IsNotEmpty() | ||
| @IsArray() | ||
| @IsString({ each: true }) | ||
| @ApiProperty({ | ||
| required: true, | ||
| description: 'Processo seletivo', | ||
| example: ['Entrevista com RH', 'Teste técnico', 'Entrevista técnica'], | ||
| }) | ||
| selectionProcess: string[]; | ||
|
|
||
| @IsOptional() | ||
| @IsString() | ||
| @MaxLength(2000) | ||
| @ApiProperty({ | ||
| required: false, | ||
| description: 'Informações adicionais', | ||
| example: 'Início imediato', | ||
| }) | ||
| additionalInfo?: string; | ||
| } |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| import { ApiProperty } from '@nestjs/swagger'; | ||
| import { | ||
| IsEnum, | ||
| IsNotEmpty, | ||
| IsNumber, | ||
| IsOptional, | ||
| IsString, | ||
| Max, | ||
| MaxLength, | ||
| Min, | ||
| ValidateIf, | ||
| } from 'class-validator'; | ||
| import { JobsModalityEnum } from '../enums/job-modality.enum'; | ||
|
|
||
| export class CreateJobDraftDto { | ||
| @IsNotEmpty() | ||
| @IsString() | ||
| @MaxLength(100) | ||
| @ApiProperty({ | ||
| required: true, | ||
| description: 'Título da vaga', | ||
| example: 'Desenvolvedor Backend Junior', | ||
| }) | ||
| title: string; | ||
|
|
||
| @IsNotEmpty() | ||
| @IsString() | ||
| @MaxLength(100) | ||
| @ApiProperty({ | ||
| required: true, | ||
| description: 'Área de interesse', | ||
| example: 'Tecnologia', | ||
| }) | ||
| interestArea: string; | ||
|
|
||
| @IsNotEmpty() | ||
| @IsEnum(JobsModalityEnum) | ||
| @ApiProperty({ | ||
| required: true, | ||
| description: 'Modalidade do trabalho', | ||
| example: JobsModalityEnum.REMOTE, | ||
| enum: [ | ||
| JobsModalityEnum.HYBRID, | ||
| JobsModalityEnum.ON_SITE, | ||
| JobsModalityEnum.REMOTE, | ||
| ], | ||
| }) | ||
| modality: JobsModalityEnum; | ||
|
|
||
| @ValidateIf((o) => o.modality !== JobsModalityEnum.REMOTE) | ||
| @IsString() | ||
| @ApiProperty({ | ||
| required: false, | ||
| description: 'Cidade', | ||
| example: 'São Paulo', | ||
| }) | ||
| city?: string; | ||
|
|
||
| @IsNotEmpty() | ||
| @IsNumber({ maxDecimalPlaces: 2 }) | ||
| @Min(0) | ||
| @Max(999999) | ||
| @ApiProperty({ | ||
| required: true, | ||
| description: 'Salário mínimo', | ||
| example: 3000, | ||
| }) | ||
| salaryMin: number; | ||
|
|
||
| @IsNotEmpty() | ||
| @IsNumber({ maxDecimalPlaces: 2 }) | ||
| @Min(0) | ||
| @Max(999999) | ||
| @ApiProperty({ | ||
| required: true, | ||
| description: 'Salário máximo', | ||
| example: 5000, | ||
| }) | ||
| salaryMax: number; | ||
|
|
||
| @IsOptional() | ||
| @IsString() | ||
| @ApiProperty({ | ||
| required: false, | ||
| description: 'Opção de trabalho remoto', | ||
| example: 'Híbrido 2x por semana', | ||
| }) | ||
| remoteWorkOption?: string; | ||
|
|
||
| @IsOptional() | ||
| company_id?: string; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| export enum JobStatus { | ||
| DRAFT = 'DRAFT', | ||
| PUBLISHED = 'PUBLISHED', | ||
| ARCHIVED = 'ARCHIVED', | ||
| CANCELED = 'CANCELED', | ||
| } |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import { | ||
| Injectable, | ||
| CanActivate, | ||
| ExecutionContext, | ||
| ForbiddenException, | ||
| NotFoundException, | ||
| } from '@nestjs/common'; | ||
| import { JobRepository } from '../repository/job.repository'; | ||
|
|
||
| @Injectable() | ||
| export class JobOwnerGuard implements CanActivate { | ||
| constructor(private jobRepository: JobRepository) {} | ||
|
|
||
| async canActivate(context: ExecutionContext): Promise<boolean> { | ||
| const request = context.switchToHttp().getRequest(); | ||
| const user = request.user; | ||
| const jobId = request.params.id; | ||
|
|
||
| if (!user || !user.id) { | ||
| throw new ForbiddenException('User not authenticated'); | ||
| } | ||
|
|
||
| const job = await this.jobRepository.findOneById(jobId); | ||
|
|
||
| if (!job) { | ||
| throw new NotFoundException('Job not found'); | ||
| } | ||
|
|
||
| if (job.company_id !== user.id) { | ||
| throw new ForbiddenException( | ||
| 'You do not have permission to modify this job', | ||
| ); | ||
| } | ||
|
Comment on lines
+29
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 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.tsRepository: 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 2Repository: 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 -100Repository: 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 1Repository: 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 5Repository: 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.tsRepository: SouJunior/vagas-api Length of output: 656 🏁 Script executed: # Check LoggedUser decorator for comparison
cat -n src/modules/auth/decorator/logged-user.decorator.tsRepository: 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.tsRepository: 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 5Repository: SouJunior/vagas-api Length of output: 1645 🏁 Script executed: # Find and check CompaniesEntity structure
find . -name "*compan*entity*" -type f | head -20Repository: SouJunior/vagas-api Length of output: 106 🏁 Script executed: # Search for CompaniesEntity definition
rg -n "class CompaniesEntity|export class CompaniesEntity" --type ts -A 10Repository: 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 2Repository: 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 3Repository: SouJunior/vagas-api Length of output: 45 A comparação O guarda assume que Solução necessária: Adicione 🤖 Prompt for AI Agents |
||
|
|
||
| return true; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
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