├── test ├── modules │ ├── company │ │ └── .gitkeep │ └── user │ │ └── services │ │ ├── find-all-users.service.spec.ts │ │ └── find-one-user.service.spec.ts ├── mocks │ └── user │ │ ├── user-update.mock.ts │ │ ├── create-user.mock.ts │ │ ├── user.mock.ts │ │ └── get-all-user.mock.ts ├── jest-e2e.json └── app.e2e-spec.ts ├── .dockerignore ├── .prettierrc ├── src ├── shared │ ├── interfaces │ │ └── interfaces.ts │ ├── pagination │ │ ├── order.dto.ts │ │ ├── pageMetaParameters.dto.ts │ │ ├── index.ts │ │ ├── page.dto.ts │ │ ├── pageMeta.dto.ts │ │ └── pageOptions.dto.ts │ ├── utils │ │ ├── userRole │ │ │ └── userRole.ts │ │ └── handle-error.util.ts │ ├── enums │ │ └── status.enum.ts │ ├── Swagger │ │ ├── not-found.swagger.ts │ │ ├── bad-request.swagger.ts │ │ ├── conflict.swagger.ts │ │ ├── unauthorized.swagger.ts │ │ ├── decorators │ │ │ ├── jobs │ │ │ │ ├── search-job.swagger.ts │ │ │ │ ├── get-one-job.swagger.ts │ │ │ │ ├── archive-job.swagger.ts │ │ │ │ ├── update-job.swagger.ts │ │ │ │ ├── get-all-jobs-of-logged-company.swagger.ts │ │ │ │ ├── get-all-jobs-of-logged-company.swagger copy.ts │ │ │ │ └── create-new-job.swagger.ts │ │ │ ├── alerts │ │ │ │ └── create-alert.swagger.ts │ │ │ ├── reports │ │ │ │ ├── create-report.swagger.ts │ │ │ │ ├── delete-report.swagger.ts │ │ │ │ ├── update-report.swagger.ts │ │ │ │ ├── get-all-reports.swagger.ts │ │ │ │ └── get-report-by-id.swagger.ts │ │ │ ├── comment │ │ │ │ ├── create-comment.swagger.ts │ │ │ │ ├── delete-commentary.swagger.ts │ │ │ │ ├── get-one-commentary.swagger.ts │ │ │ │ ├── update-commentary.swagger.ts │ │ │ │ └── get-all-commentaries.swagger.ts │ │ │ ├── app │ │ │ │ ├── classes │ │ │ │ │ └── health-check-response.swagger.ts │ │ │ │ └── health-check.swagger.decorator.ts │ │ │ ├── user │ │ │ │ ├── classes │ │ │ │ │ ├── recovery-password.swagger.ts │ │ │ │ │ ├── create-response.swagger.ts │ │ │ │ │ └── list-response.swagger.ts │ │ │ │ ├── create-user.swagger.decorator.ts │ │ │ │ ├── view-users.swagger.decorator.ts │ │ │ │ ├── update-pass-after-email-recovery.swagger.decorator.ts │ │ │ │ ├── update-password.swagger.decorator.ts │ │ │ │ ├── recover-by-email.swagger.decorator.ts │ │ │ │ ├── get-user.swagger.decorator.ts │ │ │ │ ├── delete-user.swagger.decorator.ts │ │ │ │ ├── get-user-adm.swagger.decorator.ts │ │ │ │ └── update-user.swagger.decorator.ts │ │ │ ├── curriculum │ │ │ │ └── upload-curriculum.swagger.ts │ │ │ ├── upload │ │ │ │ └── upload.swagger.ts │ │ │ ├── company │ │ │ │ ├── activate-company.swagger.ts │ │ │ │ ├── get-company-by-id.swagger.ts │ │ │ │ ├── get-all-companies.swagger.ts │ │ │ │ ├── create-company.swagger.ts │ │ │ │ ├── delete-company-by-id.swagger.ts │ │ │ │ ├── recovery-password-by-email.swagger.ts │ │ │ │ ├── update-password.swagger.ts │ │ │ │ ├── update-password-after-recovery-email.swagger.ts │ │ │ │ └── update-company-by-id.swagger.ts │ │ │ ├── auth │ │ │ │ ├── user-logged.swagger.ts │ │ │ │ └── login.swagger.ts │ │ │ ├── candidacy │ │ │ │ ├── create-candidacy.swagger.ts │ │ │ │ ├── update-candidacy.swagger.ts │ │ │ │ └── get-candidacies.swagger.ts │ │ │ └── savedjobs │ │ │ │ ├── view-savedjobs.swagger.decorator.ts │ │ │ │ └── create-savedjobs.swagger.decorator.ts │ │ └── unprocessable-entity.swagger.ts │ └── validators │ │ └── cnpj.validator.ts ├── modules │ ├── auth │ │ ├── enums │ │ │ └── login-type.enum.ts │ │ ├── decorator │ │ │ ├── logged-user.decorator.ts │ │ │ ├── logged-admin.decorator.ts │ │ │ └── logged-company.decorator.ts │ │ ├── dtos │ │ │ ├── user-login-response.dto.ts │ │ │ └── user-login.dto.ts │ │ ├── types │ │ │ ├── user-response-login.types.ts │ │ │ └── logged-user.types.ts │ │ ├── auth.module.ts │ │ ├── auth.controller.ts │ │ ├── jtw │ │ │ └── jwt.strategy.ts │ │ └── services │ │ │ └── auth-login.service.ts │ ├── reports │ │ ├── types │ │ │ └── find-by-params.type.ts │ │ ├── dtos │ │ │ ├── update-report.dto.ts │ │ │ ├── get-report-by-id.dto.ts │ │ │ └── create-report.dto.ts │ │ ├── services │ │ │ ├── index.ts │ │ │ ├── find-all-reports.service.ts │ │ │ ├── find-report-by-id.service.ts │ │ │ ├── delete-report.service.ts │ │ │ ├── update-report.service.ts │ │ │ └── create-report.service.ts │ │ ├── reports.module.ts │ │ ├── repository │ │ │ └── reports.repository.ts │ │ └── reports.controller.ts │ ├── jobs │ │ ├── enums │ │ │ ├── job-contract-type.enum.ts │ │ │ ├── job-modality.enum.ts │ │ │ ├── job-type.enum.ts │ │ │ └── job-affirmative-type.enum.ts │ │ ├── services │ │ │ ├── index.ts │ │ │ ├── get-one-job-by-id.service.ts │ │ │ ├── get-all-jobs.service.ts │ │ │ ├── update-job.service.ts │ │ │ ├── get-all-jobs-from-logged-company.service.ts │ │ │ ├── search-job.service.ts │ │ │ ├── delete-job.service.ts │ │ │ └── create-job.service.ts │ │ ├── dtos │ │ │ ├── update-job.dto.ts │ │ │ └── get-all-jobs.dto.ts │ │ ├── interfaces │ │ │ └── interfaces.ts │ │ └── jobs.module.ts │ ├── company │ │ ├── enum │ │ │ └── company-size.enum.ts │ │ ├── services │ │ │ ├── index.ts │ │ │ ├── find-all-company.service.ts │ │ │ ├── activate-company.service.ts │ │ │ ├── delete-company.service.ts │ │ │ ├── update-company.service.ts │ │ │ ├── update-password-by-email.service.ts │ │ │ ├── recovery-password-by-email.service.ts │ │ │ ├── update-password.service.ts │ │ │ └── create-company.service.ts │ │ ├── interfaces │ │ │ └── interfaces.ts │ │ ├── dtos │ │ │ ├── company-id.dto.ts │ │ │ ├── decorators │ │ │ │ └── match.decorator.ts │ │ │ └── create-company.dto.ts │ │ └── company.module.ts │ ├── upload │ │ ├── types │ │ │ └── response-s3.types.ts │ │ ├── upload.module.ts │ │ ├── upload.controller.ts │ │ └── upload.service.ts │ ├── savedjobs │ │ ├── dtos │ │ │ ├── delete-saved-job-dto.ts │ │ │ ├── create-savedJob-dto.ts │ │ │ └── get-all-savedjobs.dto.ts │ │ ├── services │ │ │ ├── savedjobs.service.spec.ts │ │ │ ├── find-all-savedjobs.service.ts │ │ │ ├── savedjobs-cleaner.service.ts │ │ │ └── delete-saved-jobs.service.ts │ │ ├── savedjobs.module.ts │ │ └── repository │ │ │ └── savedjobs.repository.ts │ ├── user │ │ ├── dtos │ │ │ ├── get-by-params.dto.ts │ │ │ ├── email-user.dto.ts │ │ │ ├── update-user.dto.ts │ │ │ ├── create-user.dto.ts │ │ │ └── update-my-password.dto.ts │ │ ├── services │ │ │ ├── index.ts │ │ │ ├── delete-user.service.ts │ │ │ ├── find-one-user.service.ts │ │ │ ├── find-all-users.service.ts │ │ │ ├── activate-user.service.ts │ │ │ ├── update-user.service.ts │ │ │ ├── update-password-by-email.service.ts │ │ │ ├── recovery-password-by-email.service.ts │ │ │ ├── create-user.service.ts │ │ │ └── update-password.service.ts │ │ ├── interfaces │ │ │ └── interfaces.ts │ │ ├── decorators │ │ │ └── match.decorator.ts │ │ └── user.module.ts │ ├── curriculum │ │ ├── types │ │ │ └── create-curriculum.type.ts │ │ ├── interfaces │ │ │ └── interfaces.ts │ │ ├── dtos │ │ │ └── delete-curriculum.dto.ts │ │ ├── curriculum.module.ts │ │ ├── repository │ │ │ └── curriculum-repository.ts │ │ ├── curriculum.controller.ts │ │ └── curriculum.service.ts │ ├── comment │ │ ├── services │ │ │ ├── index.ts │ │ │ ├── get-all-comments.service.ts │ │ │ ├── get-comment-by-id.service.ts │ │ │ ├── delete-comment.service.ts │ │ │ ├── update-comment.service.ts │ │ │ └── create-comment.service.ts │ │ ├── dtos │ │ │ ├── update-comment.dto.ts │ │ │ ├── comment-id.dto.ts │ │ │ └── create-comment.dto.ts │ │ ├── interfaces │ │ │ └── interfaces.ts │ │ ├── comment.module.ts │ │ └── repository │ │ │ └── comment.repository.ts │ ├── candidacy │ │ ├── dto │ │ │ ├── create-candidacy.dto.ts │ │ │ └── update-candidacy.dto.ts │ │ ├── service │ │ │ └── candidacy.service.spec.ts │ │ ├── candidacy.module.ts │ │ ├── controller │ │ │ └── candidacy.controller.ts │ │ └── repository │ │ │ └── candidacy.repository.ts │ ├── mails │ │ ├── templates │ │ │ ├── health.hbs │ │ │ ├── create.hbs │ │ │ └── passwordupdate.hbs │ │ └── mail.module.ts │ ├── applications │ │ ├── interfaces │ │ │ └── interfaces.ts │ │ ├── applications.module.ts │ │ ├── applications.service.ts │ │ ├── repository │ │ │ └── applications.repository.ts │ │ ├── applications.controller.ts │ │ └── dtos │ │ │ └── data-application.dto.ts │ └── alert │ │ ├── alerts.module.ts │ │ ├── controller │ │ └── alerts.controller.ts │ │ ├── dtos │ │ └── create-alert.dto.ts │ │ ├── repository │ │ └── alerts.repository.ts │ │ └── service │ │ └── alerts.service.ts ├── database │ ├── entities │ │ ├── candidancy-status.enum.ts │ │ ├── alert.entity.ts │ │ ├── reports.entity.ts │ │ ├── curriculum.entity.ts │ │ ├── comments.entity.ts │ │ ├── candidacy.entity.ts │ │ ├── applications.entity.ts │ │ ├── savedjobs.entity.ts │ │ ├── certifications.entity.ts │ │ ├── work-experiences.entity.ts │ │ ├── languages.entity.ts │ │ └── companies.entity.ts │ └── data-source.ts ├── app.controller.ts └── main.ts ├── tsconfig.build.json ├── nest-cli.json ├── .editorconfig ├── Dockerfile ├── tsconfig.json ├── run ├── .eslintrc.js ├── .env.example ├── docker-compose-vm.yml ├── .github └── workflows │ ├── deploy-prod.yml │ └── deploy-hmg.yml └── docker-compose.yml /test/modules/company/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .git 3 | .vscode -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /src/shared/interfaces/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface IGlobalResponse { 2 | status: number; 3 | } -------------------------------------------------------------------------------- /src/shared/pagination/order.dto.ts: -------------------------------------------------------------------------------- 1 | export enum Order { 2 | ASC = 'ASC', 3 | DESC = 'DESC', 4 | } 5 | -------------------------------------------------------------------------------- /src/shared/utils/userRole/userRole.ts: -------------------------------------------------------------------------------- 1 | export enum UserRole { 2 | ADMIN = 'ADMIN', 3 | USER = 'USER', 4 | } 5 | -------------------------------------------------------------------------------- /src/shared/enums/status.enum.ts: -------------------------------------------------------------------------------- 1 | export enum StatusEnum { 2 | ACTIVE = 'ACTIVE', 3 | ARCHIVED = 'ARCHIVED', 4 | } 5 | -------------------------------------------------------------------------------- /src/modules/auth/enums/login-type.enum.ts: -------------------------------------------------------------------------------- 1 | export enum LoginTypeEnum { 2 | USER = 'USER', 3 | COMPANY = 'COMPANY', 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /src/modules/reports/types/find-by-params.type.ts: -------------------------------------------------------------------------------- 1 | export type ReportParamsType = { 2 | user_id: string; 3 | job_id: string; 4 | }; 5 | -------------------------------------------------------------------------------- /src/modules/jobs/enums/job-contract-type.enum.ts: -------------------------------------------------------------------------------- 1 | export enum JobsTypeContractEnum { 2 | CLT = 'CLT', 3 | PJ = 'PJ', 4 | OTHER = 'OTHER', 5 | } 6 | -------------------------------------------------------------------------------- /src/modules/jobs/enums/job-modality.enum.ts: -------------------------------------------------------------------------------- 1 | export enum JobsModalityEnum { 2 | REMOTE = 'REMOTE', 3 | HYBRID = 'HYBRID', 4 | ON_SITE = 'ON_SITE', 5 | } 6 | -------------------------------------------------------------------------------- /src/modules/company/enum/company-size.enum.ts: -------------------------------------------------------------------------------- 1 | export enum CompanySizeEnum { 2 | BIG_SIZE = 'BIG SIZE', 3 | HALF_SIZE = 'HALF SIZE', 4 | SMALL_SIZE = 'SMALL SIZE', 5 | } 6 | -------------------------------------------------------------------------------- /src/shared/Swagger/not-found.swagger.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | export class NotFoundSwagger { 3 | @ApiProperty() 4 | message: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/shared/Swagger/bad-request.swagger.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | export class BadRequestSwagger { 3 | @ApiProperty() 4 | message: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/database/entities/candidancy-status.enum.ts: -------------------------------------------------------------------------------- 1 | export enum CandidacyStatus { 2 | InProgress = 'em andamento', 3 | Closed = 'encerrada', 4 | NoInterest = 'sem interesse', 5 | } 6 | -------------------------------------------------------------------------------- /src/modules/jobs/enums/job-type.enum.ts: -------------------------------------------------------------------------------- 1 | export enum JobsTypeEnum { 2 | TRAINEE = 'TRAINEE', 3 | JUNIOR = 'JUNIOR', 4 | ANALYST = 'ANALYST', 5 | INTERNSHIP = 'INTERNSHIP', 6 | } 7 | -------------------------------------------------------------------------------- /src/shared/Swagger/conflict.swagger.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | 3 | export class ConflictSwagger { 4 | @ApiProperty() 5 | message: string; 6 | } -------------------------------------------------------------------------------- /src/modules/upload/types/response-s3.types.ts: -------------------------------------------------------------------------------- 1 | export type ResponseS3 = { 2 | ETag: string; 3 | VersionId: string; 4 | Location: string; 5 | key: string; 6 | Bucket: string; 7 | }; 8 | -------------------------------------------------------------------------------- /src/modules/savedjobs/dtos/delete-saved-job-dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsUUID } from 'class-validator'; 2 | 3 | export class DeleteSavedJobDto { 4 | @IsUUID() 5 | @IsNotEmpty() 6 | id: string; 7 | } -------------------------------------------------------------------------------- /src/shared/pagination/pageMetaParameters.dto.ts: -------------------------------------------------------------------------------- 1 | import { PageOptionsDto } from '.'; 2 | 3 | export interface PageMetaParametersDto { 4 | pageOptionsDto: PageOptionsDto; 5 | itemCount: number; 6 | } 7 | -------------------------------------------------------------------------------- /src/modules/jobs/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-job.service'; 2 | export * from './get-all-jobs.service'; 3 | export * from './get-one-job-by-id.service'; 4 | export * from './update-job.service'; 5 | -------------------------------------------------------------------------------- /src/shared/pagination/index.ts: -------------------------------------------------------------------------------- 1 | export * from './order.dto'; 2 | export * from './page.dto'; 3 | export * from './pageMeta.dto'; 4 | export * from './pageMetaParameters.dto'; 5 | export * from './pageOptions.dto'; 6 | -------------------------------------------------------------------------------- /src/modules/jobs/dtos/update-job.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { CreateJobDto } from './create-job.dto'; 3 | 4 | export class UpdateJobDto extends PartialType(CreateJobDto) {} 5 | -------------------------------------------------------------------------------- /src/modules/reports/dtos/update-report.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { CreateReportDto } from './create-report.dto'; 3 | 4 | export class UpdateReportDto extends PartialType(CreateReportDto) {} 5 | -------------------------------------------------------------------------------- /test/mocks/user/user-update.mock.ts: -------------------------------------------------------------------------------- 1 | export const userUpdateMock = () => { 2 | return { 3 | name: 'Non-Admin for tests', 4 | email: 'user@teste.com', 5 | cpf: '12345678910', 6 | password: 'password', 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /src/modules/user/dtos/get-by-params.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | export class GetByParamsDto { 4 | @ApiProperty({ 5 | example: '30c75129-df8f-4b19-9331-239ec36ed923', 6 | }) 7 | id: string; 8 | } 9 | -------------------------------------------------------------------------------- /test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/curriculum/types/create-curriculum.type.ts: -------------------------------------------------------------------------------- 1 | import { UsersEntity } from '../../../database/entities/users.entity'; 2 | 3 | export type CreateCurriculumType = { 4 | file: string; 5 | fileKey: string; 6 | user: UsersEntity; 7 | }; 8 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "assets": ["**/*.hbs"], 7 | "watchAssets": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/comment/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-comment.service'; 2 | export * from './get-all-comments.service'; 3 | export * from './get-comment-by-id.service'; 4 | export * from './update-comment.service'; 5 | export * from './delete-comment.service'; 6 | -------------------------------------------------------------------------------- /src/modules/jobs/enums/job-affirmative-type.enum.ts: -------------------------------------------------------------------------------- 1 | export enum JobsAffirmativeTypeEnum { 2 | LGBTQIA = 'LGBTQIA+', 3 | SIXTY_PLUS = 'SIXTY_PLUS', 4 | BLACK_BROWN_PERSON = 'BLACK_BROWN_PERSON', 5 | CIS_TRANS_WOMEN = 'CIS_TRANS_WOMEN', 6 | PWD = 'PWD', 7 | } 8 | -------------------------------------------------------------------------------- /src/modules/reports/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-report.service'; 2 | export * from './find-all-reports.service'; 3 | export * from './find-report-by-id.service'; 4 | export * from './delete-report.service'; 5 | export * from './update-report.service'; 6 | -------------------------------------------------------------------------------- /src/shared/Swagger/unauthorized.swagger.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | export class UnauthorizedSwagger { 3 | @ApiProperty({ example: 401 }) 4 | statusCode: number; 5 | 6 | @ApiProperty({ example: 'Unauthorized' }) 7 | message: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/modules/savedjobs/dtos/create-savedJob-dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsUUID } from 'class-validator'; 2 | 3 | export class CreateSavedJobDto { 4 | @IsUUID() 5 | @IsNotEmpty() 6 | userId: string; 7 | 8 | @IsUUID() 9 | @IsNotEmpty() 10 | jobId: string; 11 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = false 12 | insert_final_newline = false -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/jobs/search-job.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation } from '@nestjs/swagger'; 3 | 4 | export function SearchJobSwagger() { 5 | return applyDecorators( 6 | ApiOperation({ 7 | summary: 'Buscar vaga', 8 | }), 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/comment/dtos/update-comment.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString, MaxLength } from 'class-validator'; 2 | 3 | export class UpdateCommentDto { 4 | @IsString({ message: 'O campo comment deve ser uma string' }) 5 | @IsNotEmpty({ message: 'O campo comment não pode estar vazio' }) 6 | @MaxLength(500) 7 | comment: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/alerts/create-alert.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation } from '@nestjs/swagger'; 3 | 4 | export function CreateAlertSwagger() { 5 | return applyDecorators( 6 | ApiOperation({ 7 | summary: 'Criar alerta de vaga', 8 | }), 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/jobs/get-one-job.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation } from '@nestjs/swagger'; 3 | 4 | export function GetOneJobSwagger() { 5 | return applyDecorators( 6 | ApiOperation({ 7 | summary: 'Buscar uma vaga pelo id.', 8 | }), 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/jobs/archive-job.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation } from '@nestjs/swagger'; 3 | 4 | export function ArchiveJobSwagger() { 5 | return applyDecorators( 6 | ApiOperation({ 7 | summary: 'Arquivar uma vaga pelo id.', 8 | }), 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/jobs/update-job.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation } from '@nestjs/swagger'; 3 | 4 | export function UpdateJobSwagger() { 5 | return applyDecorators( 6 | ApiOperation({ 7 | summary: 'Atualizar uma vaga pelo id.', 8 | }), 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/reports/create-report.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation } from '@nestjs/swagger'; 3 | 4 | export function CreateReportSwagger() { 5 | return applyDecorators( 6 | ApiOperation({ 7 | summary: 'Criar um relatório.', 8 | }), 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/comment/create-comment.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation } from '@nestjs/swagger'; 3 | 4 | export function CreateCommentSwagger() { 5 | return applyDecorators( 6 | ApiOperation({ 7 | summary: 'Cadastrar um comentário.', 8 | }), 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/reports/delete-report.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation } from '@nestjs/swagger'; 3 | 4 | export function DeleteReportSwagger() { 5 | return applyDecorators( 6 | ApiOperation({ 7 | summary: 'Excluir um relatório por id.', 8 | }), 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/reports/update-report.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation } from '@nestjs/swagger'; 3 | 4 | export function UpdateReportSwagger() { 5 | return applyDecorators( 6 | ApiOperation({ 7 | summary: 'Atualizar um relatório por id.', 8 | }), 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/comment/delete-commentary.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation } from '@nestjs/swagger'; 3 | 4 | export function DeleteCommentarySwagger() { 5 | return applyDecorators( 6 | ApiOperation({ 7 | summary: 'Excluir um comentário por id.', 8 | }), 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/reports/get-all-reports.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation } from '@nestjs/swagger'; 3 | 4 | export function GetAllReportsSwagger() { 5 | return applyDecorators( 6 | ApiOperation({ 7 | summary: 'Encontrar todos os relatórios.', 8 | }), 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/reports/get-report-by-id.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation } from '@nestjs/swagger'; 3 | 4 | export function GetReportByIdSwagger() { 5 | return applyDecorators( 6 | ApiOperation({ 7 | summary: 'Encontrar um relatório por id.', 8 | }), 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/comment/get-one-commentary.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation } from '@nestjs/swagger'; 3 | 4 | export function GetOneCommentaryByIdSwagger() { 5 | return applyDecorators( 6 | ApiOperation({ 7 | summary: 'Buscar um comentário por id.', 8 | }), 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/comment/update-commentary.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation } from '@nestjs/swagger'; 3 | 4 | export function UpdateCommentarySwagger() { 5 | return applyDecorators( 6 | ApiOperation({ 7 | summary: 'Atualizar um comentário por id.', 8 | }), 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/shared/pagination/page.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsArray } from 'class-validator'; 2 | import { PageMetaDto } from '.'; 3 | 4 | export class PageDto { 5 | @IsArray() 6 | readonly data: T[]; 7 | 8 | readonly meta: PageMetaDto; 9 | 10 | constructor(data: T[], meta: PageMetaDto) { 11 | this.data = data; 12 | this.meta = meta; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/comment/get-all-commentaries.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation } from '@nestjs/swagger'; 3 | 4 | export function GetAllCommentariesSwagger() { 5 | return applyDecorators( 6 | ApiOperation({ 7 | summary: 'Encontrar todos os comentários.', 8 | }), 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/company/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './activate-company.service'; 2 | export * from './create-company.service'; 3 | export * from './delete-company.service'; 4 | export * from './find-all-company.service'; 5 | export * from './recovery-password-by-email.service'; 6 | export * from './update-company.service'; 7 | export * from './update-password-by-email.service'; 8 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/app/classes/health-check-response.swagger.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | export class HealthCheckResponse { 4 | @ApiProperty({ 5 | example: 'DOWN', 6 | }) 7 | databaseStatus: string; 8 | 9 | @ApiProperty({ 10 | example: 'OK', 11 | }) 12 | mailerStatus: string; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/user/dtos/email-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class EmailDto { 5 | @IsString() 6 | @IsEmail() 7 | @IsNotEmpty() 8 | @ApiProperty({ 9 | description: 'E-mail.', 10 | example: 'johnsnow@outlook.com', 11 | }) 12 | email: string; 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/jobs/interfaces/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { JobsEntity } from "src/database/entities/jobs.entity"; 2 | import { IGlobalResponse } from "src/shared/interfaces/interfaces"; 3 | 4 | interface IJobsResponseContent { 5 | message: string, 6 | content?: JobsEntity | JobsEntity[] 7 | } 8 | 9 | export interface IJobsResponse extends IGlobalResponse { 10 | data: IJobsResponseContent 11 | } -------------------------------------------------------------------------------- /src/modules/user/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './activate-user.service'; 2 | export * from './create-user.service'; 3 | export * from './delete-user.service'; 4 | export * from './find-all-users.service'; 5 | export * from './find-one-user.service'; 6 | export * from './recovery-password-by-email.service'; 7 | export * from './update-password-by-email.service'; 8 | export * from './update-user.service'; 9 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/jobs/get-all-jobs-of-logged-company.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | 4 | export function GetAllJobsOfLoggedCompanySwagger() { 5 | return applyDecorators( 6 | ApiOperation({ 7 | summary: 'Buscar todas as vagas da empresa logada.', 8 | }), 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/shared/Swagger/unprocessable-entity.swagger.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | export class UnprocessableEntitySwagger { 3 | @ApiProperty({ example: 422 }) 4 | statusCode: number; 5 | 6 | @ApiProperty({ example: 'invalid input syntax for type uuid:' }) 7 | message: string; 8 | 9 | @ApiProperty({ example: 'Unprocessable Entity' }) 10 | error: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/user/interfaces/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { UsersEntity } from "src/database/entities/users.entity"; 2 | import { IGlobalResponse } from "src/shared/interfaces/interfaces"; 3 | 4 | interface IUsersResponseContent { 5 | message: string, 6 | content?: UsersEntity | UsersEntity[] 7 | } 8 | 9 | export interface IUsersResponse extends IGlobalResponse { 10 | data: IUsersResponseContent 11 | } -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/user/classes/recovery-password.swagger.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | export class RecoveryPasswordSwagger { 4 | @ApiProperty({ 5 | example: 6 | 'Caso esse e-mail esteja cadastrado no sistema, será encaminhado para ele uma mensagem de orientação sobre os próximos passos para a redefinição da senha.', 7 | }) 8 | message: string; 9 | } 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine AS builder 2 | 3 | WORKDIR /user/app 4 | 5 | COPY package.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | RUN npm run build 12 | 13 | FROM node:16-alpine 14 | 15 | COPY --from=builder /user/app/node_modules ./node_modules 16 | COPY --from=builder /user/app/package*.json ./ 17 | COPY --from=builder /user/app/dist ./dist 18 | 19 | EXPOSE 3000 20 | 21 | CMD ["npm","run","start:prod"] -------------------------------------------------------------------------------- /src/modules/comment/interfaces/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { CommentsEntity } from "src/database/entities/comments.entity"; 2 | import { IGlobalResponse } from "src/shared/interfaces/interfaces"; 3 | 4 | interface ICommentsResponseContent { 5 | message: string, 6 | content?: CommentsEntity | CommentsEntity[] 7 | } 8 | 9 | export interface ICommentsResponse extends IGlobalResponse { 10 | data: ICommentsResponseContent 11 | } -------------------------------------------------------------------------------- /src/modules/candidacy/dto/create-candidacy.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsUUID, IsNotEmpty } from 'class-validator'; 3 | 4 | export class CreateCandidacyDto { 5 | @IsUUID() 6 | @IsNotEmpty() 7 | @ApiProperty({ type: 'string', format: 'uuid' }) 8 | userId: string; 9 | 10 | @IsUUID() 11 | @IsNotEmpty() 12 | @ApiProperty({ type: 'string', format: 'uuid' }) 13 | jobId: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/company/interfaces/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { CompaniesEntity } from "src/database/entities/companies.entity"; 2 | import { IGlobalResponse } from "src/shared/interfaces/interfaces"; 3 | 4 | interface ICompaniesResponseContent { 5 | message: string, 6 | content?: CompaniesEntity | CompaniesEntity[] 7 | } 8 | 9 | export interface ICompaniesResponse extends IGlobalResponse { 10 | data: ICompaniesResponseContent 11 | } -------------------------------------------------------------------------------- /src/modules/mails/templates/health.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Teste de Email 7 | 8 | 9 |

Isso é um teste

10 |

Isso é um teste. O primeiro argumento de contexto foi: {{arg1}}

11 |

O segundo argumento foi: {{arg2}}.

12 | 13 | -------------------------------------------------------------------------------- /src/modules/curriculum/interfaces/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { CurriculumEntity } from "src/database/entities/curriculum.entity"; 2 | import { IGlobalResponse } from "src/shared/interfaces/interfaces"; 3 | 4 | interface ICurriculumsResponseContent { 5 | message: string, 6 | content?: CurriculumEntity | CurriculumEntity[] 7 | } 8 | 9 | export interface ICurriculumsResponse extends IGlobalResponse { 10 | data: ICurriculumsResponseContent 11 | } -------------------------------------------------------------------------------- /src/modules/applications/interfaces/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationEntity } from "src/database/entities/applications.entity"; 2 | import { IGlobalResponse } from "src/shared/interfaces/interfaces"; 3 | 4 | interface IApplicationsResponseContent { 5 | message: string, 6 | content?: ApplicationEntity | ApplicationEntity[] 7 | } 8 | 9 | export interface IApplicationsResponse extends IGlobalResponse { 10 | data: IApplicationsResponseContent 11 | } -------------------------------------------------------------------------------- /src/modules/mails/templates/create.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vaga Criada 7 | 8 | 9 |

Vaga Criada!

10 |

Olá {{name}}, a vaga foi criada com sucesso.

11 |

Você pode conferir mais detalhes sobre a vaga aqui.

12 | 13 | -------------------------------------------------------------------------------- /src/modules/company/dtos/company-id.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class CompanyIdDto { 5 | @ApiProperty({ 6 | description: 'ID da empresa', 7 | example: 'be02e7b0-238a-44c2-b9db-ccb339d63fc9', 8 | }) 9 | @IsString({ message: 'O campo id deve ser uma string' }) 10 | @IsNotEmpty({ message: 'O campo id não pode estar vazio' }) 11 | id: string; 12 | } 13 | -------------------------------------------------------------------------------- /src/modules/comment/dtos/comment-id.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class CommentIdDto { 5 | @ApiProperty({ 6 | description: 'ID do comentário', 7 | example: 'be02e7b0-238a-44c2-b9db-ccb339d63fc9', 8 | }) 9 | @IsString({ message: 'O campo id deve ser uma string' }) 10 | @IsNotEmpty({ message: 'O campo id não pode estar vazio' }) 11 | id: string; 12 | } 13 | -------------------------------------------------------------------------------- /src/modules/reports/dtos/get-report-by-id.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class ReportIdDto { 5 | @ApiProperty({ 6 | description: 'ID do relatório', 7 | example: 'be02e7b0-238a-44c2-b9db-ccb339d63fc9', 8 | }) 9 | @IsString({ message: 'O campo id deve ser uma string' }) 10 | @IsNotEmpty({ message: 'O campo id não pode estar vazio' }) 11 | id: string; 12 | } 13 | -------------------------------------------------------------------------------- /test/mocks/user/create-user.mock.ts: -------------------------------------------------------------------------------- 1 | import { CreateUserDto } from '../../../src/modules/user/dtos/create-user.dto'; 2 | import { UserRole } from '../../../src/shared/utils/userRole/userRole'; 3 | 4 | export const createUserMock = (): CreateUserDto => { 5 | return { 6 | name: 'Non-Admin for tests', 7 | email: 'user@teste.com', 8 | password: 'teste@12A', 9 | cpf: '12345678910', 10 | policies: true, 11 | type: UserRole.USER, 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /src/modules/user/services/delete-user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { UserRepository } from '../repository/user.repository'; 3 | 4 | @Injectable() 5 | export class DeleteUserService { 6 | constructor( 7 | private userRepository: UserRepository, 8 | ) {} 9 | 10 | async execute(id: string) { 11 | await this.userRepository.deleteUserById(id); 12 | 13 | return { message: 'User deleted successfully' }; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/modules/curriculum/dtos/delete-curriculum.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class DeleteCurriculumDto { 5 | @ApiProperty({ 6 | example: '1235689523635479', 7 | description: 'Chave do currículo a ser excluído', 8 | }) 9 | @IsString({ message: 'O campo key deve ser uma string' }) 10 | @IsNotEmpty({ message: 'O campo key não pode estar vazio' }) 11 | key: string; 12 | } 13 | -------------------------------------------------------------------------------- /src/modules/upload/upload.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { PassportModule } from '@nestjs/passport'; 3 | import { UploadController } from './upload.controller'; 4 | import { FileUploadService } from './upload.service'; 5 | 6 | @Module({ 7 | imports: [PassportModule.register({ defaultStrategy: 'jwt' })], 8 | controllers: [UploadController], 9 | providers: [FileUploadService], 10 | exports: [FileUploadService], 11 | }) 12 | export class UploadModule {} 13 | -------------------------------------------------------------------------------- /src/shared/utils/handle-error.util.ts: -------------------------------------------------------------------------------- 1 | import { UnprocessableEntityException } from '@nestjs/common'; 2 | 3 | export const handleError = (error: Error): undefined => { 4 | const errorLines = error.message?.split('\n'); 5 | const lastErrorLine = errorLines[errorLines.length - 1]?.trim(); 6 | 7 | if (!lastErrorLine) { 8 | console.error(error); 9 | } 10 | 11 | throw new UnprocessableEntityException( 12 | lastErrorLine || 'An error occurred while performing the operation.', 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/jobs/get-all-jobs-of-logged-company.swagger copy.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatus, applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { UnauthorizedSwagger } from '../../unauthorized.swagger'; 4 | import { BadRequestSwagger } from '../../bad-request.swagger'; 5 | 6 | export function GetAllJobsSwagger() { 7 | return applyDecorators( 8 | ApiOperation({ 9 | summary: 'Buscar todas as vagas.', 10 | }), 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/modules/candidacy/dto/update-candidacy.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsEnum, IsNotEmpty, IsUUID } from 'class-validator'; 3 | import { CandidacyStatus } from 'src/database/entities/candidancy-status.enum'; 4 | 5 | export class UpdateCandidacyDto { 6 | @ApiProperty({ type: 'string', format: 'uuid' }) 7 | @IsUUID() 8 | @IsNotEmpty() 9 | id: string; 10 | 11 | @ApiProperty({ enum: CandidacyStatus }) 12 | @IsNotEmpty() 13 | @IsEnum(CandidacyStatus) 14 | status: CandidacyStatus; 15 | } 16 | -------------------------------------------------------------------------------- /src/modules/auth/decorator/logged-user.decorator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createParamDecorator, 3 | ExecutionContext, 4 | UnauthorizedException, 5 | } from '@nestjs/common'; 6 | 7 | export const LoggedUser = createParamDecorator((_, ctx: ExecutionContext) => { 8 | const request = ctx.switchToHttp().getRequest(); 9 | const userObject = request.user; 10 | 11 | if (!userObject) { 12 | throw new UnauthorizedException( 13 | 'User does not have permission to access this route', 14 | ); 15 | } 16 | 17 | return userObject; 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/reports/services/find-all-reports.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { ReportRepository } from '../repository/reports.repository'; 3 | 4 | @Injectable() 5 | export class FindAllReportsService { 6 | constructor(private reportRepository: ReportRepository) {} 7 | 8 | async execute() { 9 | const reports = await this.reportRepository.findAllRepots(); 10 | 11 | if (reports.length <= 0) { 12 | return { message: 'Reports is empty' }; 13 | } 14 | 15 | return reports; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/modules/auth/decorator/logged-admin.decorator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createParamDecorator, 3 | ExecutionContext, 4 | UnauthorizedException, 5 | } from '@nestjs/common'; 6 | 7 | export const LoggedAdmin = createParamDecorator((_, ctx: ExecutionContext) => { 8 | const request = ctx.switchToHttp().getRequest(); 9 | const userObject = request.user; 10 | 11 | if (!userObject) { 12 | throw new UnauthorizedException( 13 | 'User does not have permission to access this route', 14 | ); 15 | } 16 | 17 | return userObject; 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/comment/services/get-all-comments.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CommentRepository } from '../repository/comment.repository'; 3 | 4 | @Injectable() 5 | export class GetAllCommentsService { 6 | constructor(private commentRepository: CommentRepository) {} 7 | 8 | async execute() { 9 | const comments = await this.commentRepository.getAllComments(); 10 | 11 | if (comments.length <= 0) { 12 | return { message: 'No comments' }; 13 | } 14 | 15 | return comments; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/modules/auth/decorator/logged-company.decorator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createParamDecorator, 3 | ExecutionContext, 4 | UnauthorizedException, 5 | } from '@nestjs/common'; 6 | 7 | export const LoggedCompany = createParamDecorator( 8 | (_, ctx: ExecutionContext) => { 9 | const request = ctx.switchToHttp().getRequest(); 10 | const userObject = request.user; 11 | 12 | if (!userObject) { 13 | throw new UnauthorizedException( 14 | 'Company does not have permission to access this route', 15 | ); 16 | } 17 | 18 | return userObject; 19 | }, 20 | ); 21 | -------------------------------------------------------------------------------- /src/modules/user/services/find-one-user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { UserRepository } from '../repository/user.repository'; 3 | 4 | @Injectable() 5 | export class FindOneUserService { 6 | constructor(public userRepository: UserRepository) {} 7 | 8 | async execute(id: string) { 9 | const userExists = await this.userRepository.findOneById(id); 10 | 11 | delete userExists.password; 12 | delete userExists.type; 13 | delete userExists.ip; 14 | delete userExists.recoverPasswordToken; 15 | 16 | return userExists; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/curriculum/upload-curriculum.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { ApiBody, ApiConsumes } from '@nestjs/swagger'; 3 | 4 | export function UploadCurriculumSwagger() { 5 | return applyDecorators( 6 | ApiConsumes('multipart/form-data'), 7 | ApiBody({ 8 | description: 'Upload images', 9 | schema: { 10 | type: 'object', 11 | properties: { 12 | file: { 13 | type: 'string', 14 | format: 'binary', 15 | }, 16 | }, 17 | }, 18 | }), 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/modules/candidacy/service/candidacy.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { CandidacyService } from '../candidacy.service'; 3 | 4 | describe('CandidacyService', () => { 5 | let service: CandidacyService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [CandidacyService], 10 | }).compile(); 11 | 12 | service = module.get(CandidacyService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/savedjobs/services/savedjobs.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { SavedJobsService } from './savedjobs.service'; 3 | 4 | describe('SavedjobsService', () => { 5 | let service: SavedJobsService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [SavedJobsService], 10 | }).compile(); 11 | 12 | service = module.get(SavedJobsService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/comment/services/get-comment-by-id.service.ts: -------------------------------------------------------------------------------- 1 | import { BadRequestException, Injectable } from '@nestjs/common'; 2 | import { CommentRepository } from '../repository/comment.repository'; 3 | 4 | @Injectable() 5 | export class GetCommentByIdService { 6 | constructor(private commentRepository: CommentRepository) {} 7 | 8 | async execute(id: string) { 9 | const commentExists = await this.commentRepository.getCommentById(id); 10 | 11 | if (!commentExists) { 12 | throw new BadRequestException(`Comment does not exist`); 13 | } 14 | 15 | return commentExists; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/upload/upload.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { ApiBody, ApiConsumes, ApiProperty } from '@nestjs/swagger'; 3 | 4 | export function UploadSwagger() { 5 | return applyDecorators( 6 | ApiProperty(), 7 | ApiConsumes('multipart/form-data'), 8 | ApiBody({ 9 | description: 'Upload images', 10 | schema: { 11 | type: 'object', 12 | properties: { 13 | file: { 14 | type: 'string', 15 | format: 'binary', 16 | }, 17 | }, 18 | }, 19 | }), 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/database/entities/alert.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | Entity, 4 | PrimaryGeneratedColumn, 5 | ManyToOne, 6 | JoinColumn, 7 | } from 'typeorm'; 8 | import { UsersEntity } from './users.entity'; 9 | 10 | @Entity('tb_alerts') 11 | export class AlertEntity { 12 | @PrimaryGeneratedColumn('uuid') 13 | id: string; 14 | 15 | @Column({ name: 'user_id', nullable: false }) 16 | userId: string; 17 | 18 | @Column() 19 | keyword: string; 20 | 21 | @Column() 22 | location: string; 23 | 24 | @ManyToOne(() => UsersEntity) 25 | @JoinColumn({ name: 'user_id' }) 26 | user: UsersEntity; 27 | } 28 | -------------------------------------------------------------------------------- /src/modules/auth/dtos/user-login-response.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { UserResponseLoginTypes } from '../types/user-response-login.types'; 3 | 4 | export class UserLoginResponseDto { 5 | @ApiProperty({ 6 | example: 7 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6IkVtYWlsUGFyYVRlc3RlQHRlc3RlLmNvbSIsImlhdCI6MTY3OTcwODczNSwiZXhwIjoxNjc5Nzk1MTM1fQ.G1r68O4MNDXx_uy7AbgltEln2cOd7UGxw6jNXbF5HZ0', 8 | description: 'Token de autenticação JWT', 9 | }) 10 | token: string; 11 | 12 | @ApiProperty({ example: UserResponseLoginTypes }) 13 | info: UserResponseLoginTypes; 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/comment/services/delete-comment.service.ts: -------------------------------------------------------------------------------- 1 | import { BadRequestException, Injectable } from '@nestjs/common'; 2 | import { CommentRepository } from './../repository/comment.repository'; 3 | 4 | @Injectable() 5 | export class DeleteCommentService { 6 | constructor(private commentRepository: CommentRepository) {} 7 | 8 | async execute(id: string) { 9 | const commentExists = await this.commentRepository.getCommentById(id); 10 | 11 | if (!commentExists) { 12 | throw new BadRequestException(`Comment does not exist`); 13 | } 14 | 15 | return this.commentRepository.deleteComment(id); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2020", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/modules/savedjobs/dtos/get-all-savedjobs.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsOptional, IsUUID } from 'class-validator'; 3 | 4 | export class GetAllSavedJobsDto { 5 | @IsOptional() 6 | @IsUUID() 7 | @ApiProperty({ 8 | required: false, 9 | description: 'ID do usuário que salvou a vaga', 10 | example: 'e2c1a2b7-8b4f-4c1b-bf94-2cb20de984d0', 11 | }) 12 | userId?: string; 13 | 14 | @IsOptional() 15 | @IsUUID() 16 | @ApiProperty({ 17 | required: false, 18 | description: 'ID da vaga salva', 19 | example: 'job_abc123', 20 | }) 21 | jobId?: string; 22 | } 23 | -------------------------------------------------------------------------------- /src/modules/user/services/find-all-users.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { UsersEntity } from '../../../database/entities/users.entity'; 3 | import { PageDto, PageOptionsDto } from '../../../shared/pagination'; 4 | 5 | import { UserRepository } from '../repository/user.repository'; 6 | 7 | @Injectable() 8 | export class FindAllUsersService { 9 | constructor(private userRepository: UserRepository) {} 10 | 11 | async execute(pageOptionsDto: PageOptionsDto): Promise> { 12 | const query = await this.userRepository.getAllUsers(pageOptionsDto); 13 | 14 | return query; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/app/health-check.swagger.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, HttpStatus } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { HealthCheckResponse } from './classes/health-check-response.swagger'; 4 | 5 | export function SwaggerHealthCheck() { 6 | return applyDecorators( 7 | ApiResponse({ 8 | status: HttpStatus.OK, 9 | description: 'Exemplo do retorno de sucesso da rota', 10 | type: HealthCheckResponse, 11 | }), 12 | ApiOperation({ 13 | summary: 'Retorna status dos serviços de email e banco de dados', 14 | }), 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/modules/jobs/services/get-one-job-by-id.service.ts: -------------------------------------------------------------------------------- 1 | import { BadRequestException, Injectable } from '@nestjs/common'; 2 | import { JobRepository } from '../repository/job.repository'; 3 | 4 | @Injectable() 5 | export class GetOneJobByIdService { 6 | constructor(private jobRepository: JobRepository) {} 7 | 8 | async execute(id: string) { 9 | if (!id) { 10 | throw new BadRequestException('Id not provided'); 11 | } 12 | 13 | const jobExists = await this.jobRepository.findOneById(id); 14 | 15 | if (!jobExists) { 16 | throw new BadRequestException('Job not found'); 17 | } 18 | 19 | return jobExists; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/modules/reports/services/find-report-by-id.service.ts: -------------------------------------------------------------------------------- 1 | import { BadRequestException, Injectable } from '@nestjs/common'; 2 | import { ReportIdDto } from '../dtos/get-report-by-id.dto'; 3 | import { ReportRepository } from '../repository/reports.repository'; 4 | 5 | @Injectable() 6 | export class FindReportByIdService { 7 | constructor(private reportRepository: ReportRepository) {} 8 | 9 | async execute({ id }: ReportIdDto) { 10 | const reportExists = await this.reportRepository.findReportById(id); 11 | 12 | if (!reportExists) { 13 | throw new BadRequestException('Report not found'); 14 | } 15 | 16 | return reportExists; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/candidacy/candidacy.module.ts: -------------------------------------------------------------------------------- 1 | import { TypeOrmModule } from '@nestjs/typeorm'; 2 | import { Module } from '@nestjs/common'; 3 | import { CandidacyEntity } from 'src/database/entities/candidacy.entity'; 4 | import { CandidacyRepository } from './repository/candidacy.repository'; 5 | import { CandidacyService } from './service/candidacy.service'; 6 | import { CandidacyController } from './controller/candidacy.controller'; 7 | 8 | @Module({ 9 | imports: [TypeOrmModule.forFeature([CandidacyEntity])], 10 | controllers: [CandidacyController], 11 | providers: [CandidacyService, CandidacyRepository], 12 | exports: [CandidacyService], 13 | }) 14 | export class CandidacyModule {} 15 | -------------------------------------------------------------------------------- /src/modules/reports/services/delete-report.service.ts: -------------------------------------------------------------------------------- 1 | import { BadRequestException, Injectable } from '@nestjs/common'; 2 | import { ReportIdDto } from '../dtos/get-report-by-id.dto'; 3 | import { ReportRepository } from '../repository/reports.repository'; 4 | 5 | @Injectable() 6 | export class DeleteReportService { 7 | constructor(private reportRepository: ReportRepository) {} 8 | 9 | async execute({ id }: ReportIdDto) { 10 | const reportExists = await this.reportRepository.findReportById(id); 11 | 12 | if (!reportExists) { 13 | throw new BadRequestException('Report not found'); 14 | } 15 | 16 | return this.reportRepository.deleteReportById(id); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/applications/applications.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { ApplicationsController } from './applications.controller'; 4 | import { ApplicationsService } from './applications.service'; 5 | import { ApplicationsRepository } from './repository/applications.repository'; 6 | import { ApplicationEntity } from 'src/database/entities/applications.entity'; 7 | 8 | @Module({ 9 | imports: [ 10 | TypeOrmModule.forFeature([ApplicationEntity]), 11 | ], 12 | controllers: [ApplicationsController], 13 | providers: [ApplicationsRepository, ApplicationsService], 14 | }) 15 | export class ApplicationsModule {} 16 | -------------------------------------------------------------------------------- /src/modules/company/services/find-all-company.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CompaniesEntity } from '../../../database/entities/companies.entity'; 3 | import { PageDto, PageOptionsDto } from '../../../shared/pagination'; 4 | 5 | import { CompanyRepository } from '../repository/company-repository'; 6 | 7 | @Injectable() 8 | export class FindAllCompanyService { 9 | constructor(private companyRepository: CompanyRepository) {} 10 | 11 | async execute( 12 | pageOptionsDto: PageOptionsDto, 13 | ): Promise> { 14 | const query = await this.companyRepository.findAllCompany(pageOptionsDto); 15 | 16 | return query; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/alert/alerts.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { AlertsController } from './controller/alerts.controller'; 4 | import { AlertEntity } from 'src/database/entities/alert.entity'; 5 | import { MailModule } from '../mails/mail.module'; 6 | import { AlertsRepository } from './repository/alerts.repository'; 7 | import { AlertsService } from './service/alerts.service'; 8 | 9 | @Module({ 10 | imports: [TypeOrmModule.forFeature([AlertEntity]), MailModule], 11 | controllers: [AlertsController], 12 | providers: [AlertsService, AlertsRepository], 13 | exports: [AlertsService], 14 | }) 15 | export class AlertsModule {} 16 | -------------------------------------------------------------------------------- /src/modules/company/services/activate-company.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { CompanyRepository } from '../repository/company-repository'; 3 | 4 | @Injectable() 5 | export class ActivateCompanyService { 6 | constructor(private companyRepository: CompanyRepository) {} 7 | 8 | async execute(id: string) { 9 | const companyExists = await this.companyRepository.findOneById(id); 10 | 11 | if (!companyExists) { 12 | throw new NotFoundException('company not found'); 13 | } 14 | 15 | await this.companyRepository.activateCompany(id); 16 | 17 | return { 18 | status: 204, 19 | data: 'Company Activated', 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/modules/comment/services/update-comment.service.ts: -------------------------------------------------------------------------------- 1 | import { CommentRepository } from './../repository/comment.repository'; 2 | import { BadRequestException, Injectable } from '@nestjs/common'; 3 | import { UpdateCommentDto } from '../dtos/update-comment.dto'; 4 | 5 | @Injectable() 6 | export class UpdateCommentService { 7 | constructor(private commentRepository: CommentRepository) {} 8 | 9 | async execute(id: string, data: UpdateCommentDto) { 10 | const commentExists = await this.commentRepository.getCommentById(id); 11 | 12 | if (!commentExists) { 13 | throw new BadRequestException(`Comment does not exist`); 14 | } 15 | 16 | return this.commentRepository.updateComment(id, data); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/applications/applications.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { UsersEntity } from '../../database/entities/users.entity'; 3 | import { ApplicationsRepository } from './repository/applications.repository'; 4 | 5 | @Injectable() 6 | export class ApplicationsService { 7 | constructor(private applicationsRepository: ApplicationsRepository) {} 8 | 9 | async saveApplication( 10 | user: UsersEntity, 11 | jobId: string, 12 | curriculumId: string, 13 | ) { 14 | const newApplication = { 15 | job_id: jobId, 16 | user_id: user.id, 17 | curriculum_id: curriculumId, 18 | }; 19 | 20 | return this.applicationsRepository.saveApplication(newApplication); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/modules/jobs/services/get-all-jobs.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { JobsEntity } from '../../../database/entities/jobs.entity'; 3 | import { PageDto, PageOptionsDto } from '../../../shared/pagination'; 4 | import { JobRepository } from '../repository/job.repository'; 5 | import { GetAllJobsDto } from '../dtos/get-all-jobs.dto'; 6 | 7 | @Injectable() 8 | export class GetAllJobsService { 9 | constructor(private jobRepository: JobRepository) {} 10 | 11 | async execute( 12 | pageOptionsDto: PageOptionsDto, 13 | params: GetAllJobsDto, 14 | ): Promise> { 15 | const query = await this.jobRepository.getAllJobs(pageOptionsDto, params); 16 | 17 | return query; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | COMMAND=${1:-"local"} 6 | 7 | DOCKER_REPOSITORY="public.ecr.aws/a8g4m8m1/vagas-backend" 8 | BUILD_DOCKER_TAG=$(git log -n 1 --pretty='format:%cd-%h' --date=format:'%Y%m%d%H%M') 9 | 10 | push() { 11 | docker buildx build --platform linux/arm64/v8,linux/amd64 -f Dockerfile \ 12 | --push -t $DOCKER_REPOSITORY:$BUILD_DOCKER_TAG . 13 | } 14 | 15 | compose_up() { 16 | [[ ! $COMMAND == "server" ]] || declare -a args=('-f' 'docker-compose-vm.yml') 17 | CONTAINER_IMAGE="$DOCKER_REPOSITORY:$BUILD_DOCKER_TAG" docker-compose ${args[*]} up -d 18 | } 19 | 20 | case $COMMAND in 21 | build-push) 22 | push;; 23 | local) 24 | compose_up;; 25 | server) 26 | compose_up;; 27 | esac 28 | -------------------------------------------------------------------------------- /src/modules/jobs/services/update-job.service.ts: -------------------------------------------------------------------------------- 1 | import { BadRequestException, Injectable } from '@nestjs/common'; 2 | import { UpdateJobDto } from '../dtos/update-job.dto'; 3 | import { JobRepository } from '../repository/job.repository'; 4 | 5 | @Injectable() 6 | export class UpdateJobService { 7 | constructor(private jobRepository: JobRepository) {} 8 | 9 | async execute(id: string, data: UpdateJobDto) { 10 | if (!id) { 11 | throw new BadRequestException('Id not provided'); 12 | } 13 | 14 | const jobExists = await this.jobRepository.findOneById(id); 15 | 16 | if (!jobExists) { 17 | throw new BadRequestException('Job not found'); 18 | } 19 | 20 | return this.jobRepository.updateJob(id, data); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir : __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/modules/reports/services/update-report.service.ts: -------------------------------------------------------------------------------- 1 | import { UpdateReportDto } from './../dtos/update-report.dto'; 2 | import { BadRequestException, Injectable } from '@nestjs/common'; 3 | import { ReportRepository } from '../repository/reports.repository'; 4 | import { ReportIdDto } from '../dtos/get-report-by-id.dto'; 5 | 6 | @Injectable() 7 | export class UpdateReportService { 8 | constructor(private reportRepository: ReportRepository) {} 9 | 10 | async execute({ id }: ReportIdDto, data: UpdateReportDto) { 11 | const reportExists = await this.reportRepository.findReportById(id); 12 | 13 | if (!reportExists) { 14 | throw new BadRequestException('Report not found'); 15 | } 16 | 17 | return this.reportRepository.updateReport(id, data); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/modules/auth/types/user-response-login.types.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { LoginTypeEnum } from '../enums/login-type.enum'; 3 | 4 | export class UserResponseLoginTypes { 5 | @ApiProperty({ example: '218926fe-a3fa-4381-af8e-77c4017ebdb8' }) 6 | id: string; 7 | 8 | @ApiProperty({ example: 'Any Name' }) 9 | name: string; 10 | 11 | @ApiProperty({ example: 'EmailParaTeste@teste.com' }) 12 | email: string; 13 | 14 | @ApiProperty({ example: LoginTypeEnum.USER }) 15 | type: LoginTypeEnum; 16 | 17 | @ApiProperty({ example: '2023-03-25T04:40:28.329Z' }) 18 | created_at: Date; 19 | 20 | @ApiProperty({ example: '2023-03-25T04:45:27.883Z' }) 21 | updated_at: Date; 22 | 23 | @ApiProperty({ example: true }) 24 | policies: boolean; 25 | } 26 | -------------------------------------------------------------------------------- /src/modules/applications/repository/applications.repository.ts: -------------------------------------------------------------------------------- 1 | import { Repository } from 'typeorm'; 2 | import { ApplicationEntity } from '../../../database/entities/applications.entity'; 3 | import { handleError } from '../../../shared/utils/handle-error.util'; 4 | import { InjectRepository } from '@nestjs/typeorm'; 5 | import { Injectable } from '@nestjs/common'; 6 | import { DataApplicationDto } from '../dtos/data-application.dto'; 7 | 8 | @Injectable() 9 | export class ApplicationsRepository { 10 | constructor(@InjectRepository(ApplicationEntity) private applicationsRepository: Repository) {} 11 | 12 | async saveApplication(data: DataApplicationDto): Promise { 13 | return this.applicationsRepository.save(data).catch(handleError); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/shared/pagination/pageMeta.dto.ts: -------------------------------------------------------------------------------- 1 | import { PageMetaParametersDto } from '.'; 2 | 3 | export class PageMetaDto { 4 | readonly page: number; 5 | readonly take: number; 6 | readonly itemCount: number; 7 | readonly pageCount: number; 8 | readonly hasPreviousPage: boolean; 9 | readonly hasNextPage: boolean; 10 | readonly orderByColumn: string; 11 | 12 | constructor({ pageOptionsDto, itemCount }: PageMetaParametersDto) { 13 | this.orderByColumn = pageOptionsDto.orderByColumn; 14 | this.page = pageOptionsDto.page; 15 | this.take = pageOptionsDto.take; 16 | this.itemCount = itemCount; 17 | this.pageCount = Math.ceil(this.itemCount / this.take); 18 | this.hasPreviousPage = this.page > 1; 19 | this.hasNextPage = this.page < this.pageCount; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/company/activate-company.swagger.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatus, applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { UnauthorizedSwagger } from '../../unauthorized.swagger'; 4 | import { BadRequestSwagger } from '../../bad-request.swagger'; 5 | 6 | export function ActivateCompanySwagger() { 7 | return applyDecorators( 8 | ApiResponse({ 9 | status: HttpStatus.UNAUTHORIZED, 10 | description: 'Modelo de erro', 11 | type: UnauthorizedSwagger, 12 | }), 13 | ApiResponse({ 14 | status: HttpStatus.BAD_REQUEST, 15 | description: 'Modelo de erro', 16 | type: BadRequestSwagger, 17 | }), 18 | ApiOperation({ 19 | summary: 'Ativar uma empresa pelo ID', 20 | }), 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/auth/user-logged.swagger.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatus, applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { LoggerUserType } from 'src/modules/auth/types/logged-user.types'; 4 | import { UnauthorizedSwagger } from '../../unauthorized.swagger'; 5 | 6 | export function UserLoggedSwagger() { 7 | return applyDecorators( 8 | ApiResponse({ 9 | status: HttpStatus.OK, 10 | description: 'Exemplo do retorno de sucesso da rota', 11 | type: LoggerUserType, 12 | }), 13 | ApiResponse({ 14 | status: HttpStatus.UNAUTHORIZED, 15 | description: 'Modelo de erro', 16 | type: UnauthorizedSwagger, 17 | }), 18 | ApiOperation({ 19 | summary: 'Retorna usuário logado', 20 | }), 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/modules/company/services/delete-company.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CompanyRepository } from '../repository/company-repository'; 3 | 4 | @Injectable() 5 | export class DeleteCompanyService { 6 | constructor(private companyRepository: CompanyRepository) {} 7 | 8 | async execute(id: string) { 9 | const companyExists = await this.companyRepository.findCompanyById(id); 10 | 11 | if (!companyExists) { 12 | return { 13 | status: 404, 14 | data: { 15 | message: 'Company not found', 16 | }, 17 | }; 18 | } 19 | 20 | await this.companyRepository.deleteCompanyById(id); 21 | 22 | return { 23 | status: 200, 24 | data: { 25 | message: 'Company deleted successfully', 26 | }, 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/auth/login.swagger.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatus, applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { UserLoginResponseDto } from 'src/modules/auth/dtos/user-login-response.dto'; 4 | import { BadRequestSwagger } from '../../bad-request.swagger'; 5 | 6 | export function LoginSwagger() { 7 | return applyDecorators( 8 | ApiResponse({ 9 | status: HttpStatus.OK, 10 | description: 'Exemplo do retorno de sucesso da rota', 11 | type: UserLoginResponseDto, 12 | }), 13 | ApiResponse({ 14 | status: HttpStatus.BAD_REQUEST, 15 | description: 'Modelo de erro', 16 | type: BadRequestSwagger, 17 | }), 18 | ApiOperation({ 19 | summary: 'Rota para fazer login na plataforma', 20 | }), 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/user/create-user.swagger.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, HttpStatus } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { BadRequestSwagger } from '../../bad-request.swagger'; 4 | import { NotFoundSwagger } from '../../not-found.swagger'; 5 | 6 | export function SwaggerCreateUser() { 7 | return applyDecorators( 8 | ApiResponse({ 9 | status: HttpStatus.CREATED, 10 | description: 'Exemplo do retorno de sucesso da rota', 11 | type: NotFoundSwagger, 12 | }), 13 | ApiResponse({ 14 | status: HttpStatus.BAD_REQUEST, 15 | description: 'Modelo de erro', 16 | type: BadRequestSwagger, 17 | }), 18 | ApiOperation({ 19 | summary: 'Rota para cadastrar usuário plataforma', 20 | }), 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/candidacy/create-candidacy.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, HttpStatus } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { BadRequestSwagger } from '../../bad-request.swagger'; 4 | import { CandidacyEntity } from 'src/database/entities/candidacy.entity'; 5 | 6 | export function CreateCandidacySwagger() { 7 | return applyDecorators( 8 | ApiResponse({ 9 | status: HttpStatus.CREATED, 10 | description: 'Exemplo do retorno de sucesso da rota', 11 | type: CandidacyEntity, 12 | }), 13 | ApiResponse({ 14 | status: HttpStatus.BAD_REQUEST, 15 | description: 'Modelo de erro', 16 | type: BadRequestSwagger, 17 | }), 18 | ApiOperation({ 19 | summary: 'Rota para registrar uma candidatura', 20 | }), 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/candidacy/update-candidacy.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, HttpStatus } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { BadRequestSwagger } from '../../bad-request.swagger'; 4 | import { CandidacyEntity } from 'src/database/entities/candidacy.entity'; 5 | 6 | export function UpdateCandidacySwagger() { 7 | return applyDecorators( 8 | ApiResponse({ 9 | status: HttpStatus.CREATED, 10 | description: 'Exemplo do retorno de sucesso da rota', 11 | type: CandidacyEntity, 12 | }), 13 | ApiResponse({ 14 | status: HttpStatus.BAD_REQUEST, 15 | description: 'Modelo de erro', 16 | type: BadRequestSwagger, 17 | }), 18 | ApiOperation({ 19 | summary: 'Rota para atualizar uma candidatura', 20 | }), 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/candidacy/get-candidacies.swagger.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, HttpStatus } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { BadRequestSwagger } from '../../bad-request.swagger'; 4 | import { CandidacyEntity } from 'src/database/entities/candidacy.entity'; 5 | 6 | export function GetCandidaciesSwagger() { 7 | return applyDecorators( 8 | ApiResponse({ 9 | status: HttpStatus.OK, 10 | description: 'Exemplo do retorno de sucesso da rota', 11 | type: [CandidacyEntity], 12 | }), 13 | ApiResponse({ 14 | status: HttpStatus.BAD_REQUEST, 15 | description: 'Modelo de erro', 16 | type: BadRequestSwagger, 17 | }), 18 | ApiOperation({ 19 | summary: 'Rota para obter todas as candidaturas de um usuário', 20 | }), 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/modules/user/services/activate-user.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BadRequestException, 3 | Injectable, 4 | NotFoundException, 5 | } from '@nestjs/common'; 6 | import { UserRepository } from '../repository/user.repository'; 7 | 8 | @Injectable() 9 | export class ActivateUserService { 10 | constructor(private userRepository: UserRepository) {} 11 | 12 | async execute(id: string) { 13 | if (!id) { 14 | throw new BadRequestException('Id not provided'); 15 | } 16 | 17 | const userExists = await this.userRepository.findOneById(id); 18 | 19 | if (!userExists) { 20 | throw new NotFoundException('User not found'); 21 | } 22 | 23 | const user = await this.userRepository.activateUser(id); 24 | 25 | delete user.password; 26 | delete user.recoverPasswordToken; 27 | delete user.ip; 28 | return user; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/modules/alert/controller/alerts.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Post, UseGuards } from '@nestjs/common'; 2 | import { AlertsService } from '../service/alerts.service'; 3 | import { CreateAlertDto } from '../dtos/create-alert.dto'; 4 | import { AuthGuard } from '@nestjs/passport'; 5 | import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; 6 | import { CreateAlertSwagger } from '../../../shared/Swagger/decorators/alerts/create-alert.swagger'; 7 | 8 | @ApiTags('Alerts') 9 | @Controller('alerts') 10 | @UseGuards(AuthGuard('jwt')) 11 | export class AlertsController { 12 | constructor(private readonly alertsService: AlertsService) {} 13 | 14 | @Post() 15 | @ApiBearerAuth() 16 | @CreateAlertSwagger() 17 | async createAlert(@Body() createAlertDto: CreateAlertDto) { 18 | return await this.alertsService.createAlert(createAlertDto); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/company/get-company-by-id.swagger.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatus, applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger'; 3 | import { UnauthorizedSwagger } from '../../unauthorized.swagger'; 4 | import { BadRequestSwagger } from '../../bad-request.swagger'; 5 | 6 | export function GetCompanyByIdSwagger() { 7 | return applyDecorators( 8 | ApiParam({ 9 | name: 'id', 10 | type: 'string', 11 | }), 12 | ApiResponse({ 13 | status: HttpStatus.UNAUTHORIZED, 14 | description: 'Modelo de erro', 15 | type: UnauthorizedSwagger, 16 | }), 17 | ApiResponse({ 18 | status: HttpStatus.BAD_REQUEST, 19 | description: 'Modelo de erro', 20 | type: BadRequestSwagger, 21 | }), 22 | ApiOperation({ 23 | summary: 'Buscar uma empresa por id.', 24 | }), 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/database/entities/reports.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | CreateDateColumn, 4 | Entity, 5 | JoinColumn, 6 | ManyToOne, 7 | PrimaryGeneratedColumn, 8 | UpdateDateColumn, 9 | } from 'typeorm'; 10 | import { JobsEntity } from './jobs.entity'; 11 | import { UsersEntity } from './users.entity'; 12 | 13 | @Entity('tb_reports') 14 | export class ReportsEntity { 15 | @PrimaryGeneratedColumn('uuid') 16 | id: string; 17 | 18 | @ManyToOne(() => JobsEntity) 19 | @JoinColumn({ name: 'job_id' }) 20 | job: JobsEntity; 21 | 22 | @Column() 23 | job_id: string; 24 | 25 | @ManyToOne(() => UsersEntity) 26 | @JoinColumn({ name: 'user_id' }) 27 | user: UsersEntity; 28 | 29 | @Column() 30 | user_id: string; 31 | 32 | @Column() 33 | description: string; 34 | 35 | @CreateDateColumn() 36 | created_at: Date; 37 | 38 | @UpdateDateColumn({ update: true }) 39 | updated_at: Date; 40 | } 41 | -------------------------------------------------------------------------------- /src/modules/alert/dtos/create-alert.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class CreateAlertDto { 5 | @ApiProperty({ 6 | name: 'userId', 7 | type: String, 8 | }) 9 | @IsString({ message: 'O campo userId deve ser uma string' }) 10 | @IsNotEmpty({ message: 'O campo userId não pode estar vazio' }) 11 | userId: string; 12 | 13 | @ApiProperty({ 14 | name: 'keyword', 15 | type: String, 16 | }) 17 | @IsString({ message: 'O campo keyword deve ser uma string' }) 18 | @IsNotEmpty({ message: 'O campo keyword não pode estar vazio' }) 19 | keyword: string; 20 | 21 | @ApiProperty({ 22 | name: 'location', 23 | type: String, 24 | }) 25 | @IsString({ message: 'O campo location deve ser uma string' }) 26 | @IsNotEmpty({ message: 'O campo location não pode estar vazio' }) 27 | location: string; 28 | } 29 | -------------------------------------------------------------------------------- /src/shared/validators/cnpj.validator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | registerDecorator, 3 | ValidationArguments, 4 | ValidationOptions, 5 | } from 'class-validator'; 6 | import { cnpj, cpf } from 'cpf-cnpj-validator'; 7 | 8 | export default function IsCPForCNPJ(validationOptions?: ValidationOptions) { 9 | return function (object: any, propertyName: string) { 10 | registerDecorator({ 11 | name: 'IsCPForCNPJ', 12 | target: object.constructor, 13 | propertyName: propertyName, 14 | options: validationOptions, 15 | validator: { 16 | validate(value: any) { 17 | if (!value) return true; 18 | 19 | return cpf.isValid(value) || cnpj.isValid(value); 20 | }, 21 | defaultMessage(validationArguments?: ValidationArguments): string { 22 | return `${validationArguments.property} must be a valid CPF or CNPJ document`; 23 | }, 24 | }, 25 | }); 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /src/modules/applications/applications.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; 4 | import { UsersEntity } from '../../database/entities/users.entity'; 5 | import { LoggedUser } from '../auth/decorator/logged-user.decorator'; 6 | import { ApplicationsService } from './applications.service'; 7 | 8 | @ApiTags('Applications') 9 | @ApiBearerAuth() 10 | @UseGuards(AuthGuard()) 11 | @Controller('applications') 12 | export class ApplicationsController { 13 | constructor(private applicationsService: ApplicationsService) {} 14 | 15 | @Get() 16 | async saveApplication( 17 | @Query() jobId: string, 18 | @Query() curriculumId: string, 19 | @LoggedUser() user: UsersEntity, 20 | ) { 21 | return this.applicationsService.saveApplication(user, jobId, curriculumId); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/modules/applications/dtos/data-application.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class DataApplicationDto { 5 | @ApiProperty({ 6 | example: '4b9864dc-79cf-4018-96a4-ad82137ba321', 7 | description: 'ID do usuário', 8 | }) 9 | @IsString() 10 | @IsNotEmpty({ message: 'O campo user_id não pode estar vazio' }) 11 | user_id: string; 12 | 13 | @ApiProperty({ 14 | example: '4b9864dc-79cf-4018-96a4-ad82137ba321', 15 | description: 'ID da vaga', 16 | }) 17 | @IsString() 18 | @IsNotEmpty({ message: 'O campo job_id não pode estar vazio' }) 19 | job_id: string; 20 | 21 | @ApiProperty({ 22 | example: 'fed50a27-3f51-4642-bfd6-57d5bd3c352a', 23 | description: 'ID do currículo', 24 | }) 25 | @IsString() 26 | @IsNotEmpty({ message: 'O campo curriculum_id não pode estar vazio' }) 27 | curriculum_id: string; 28 | } 29 | -------------------------------------------------------------------------------- /src/modules/user/decorators/match.decorator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | registerDecorator, 3 | ValidationArguments, 4 | ValidationOptions, 5 | ValidatorConstraint, 6 | ValidatorConstraintInterface, 7 | } from 'class-validator'; 8 | 9 | export function Match(property: string, validationOptions?: ValidationOptions) { 10 | return (object: any, propertyName: string) => { 11 | registerDecorator({ 12 | target: object.constructor, 13 | propertyName, 14 | options: validationOptions, 15 | constraints: [property], 16 | validator: MatchConstraint, 17 | }); 18 | }; 19 | } 20 | 21 | @ValidatorConstraint({ name: 'Match' }) 22 | export class MatchConstraint implements ValidatorConstraintInterface { 23 | validate(value: any, args: ValidationArguments) { 24 | const [relatedPropertyName] = args.constraints; 25 | const relatedValue = (args.object as any)[relatedPropertyName]; 26 | return value === relatedValue; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/modules/jobs/dtos/get-all-jobs.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsOptional, IsString } from 'class-validator'; 3 | import { JobsModalityEnum } from '../enums/job-modality.enum'; 4 | 5 | export class GetAllJobsDto { 6 | @IsOptional() 7 | @IsString() 8 | @ApiProperty({ 9 | required: false, 10 | description: 'Modalidade do trabalho', 11 | enum: [ 12 | JobsModalityEnum.HYBRID, 13 | JobsModalityEnum.ON_SITE, 14 | JobsModalityEnum.REMOTE, 15 | ], 16 | }) 17 | modality: JobsModalityEnum; 18 | 19 | @IsOptional() 20 | @IsString() 21 | @ApiProperty({ 22 | required: false, 23 | description: 'Filtro por unidade federal', 24 | example: 'SP', 25 | }) 26 | federalUnit: string; 27 | 28 | @IsOptional() 29 | @IsString() 30 | @ApiProperty({ 31 | required: false, 32 | description: 'Filtro por cidade', 33 | example: 'São Paulo', 34 | }) 35 | city: string; 36 | } 37 | -------------------------------------------------------------------------------- /src/modules/company/dtos/decorators/match.decorator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | registerDecorator, 3 | ValidationArguments, 4 | ValidationOptions, 5 | ValidatorConstraint, 6 | ValidatorConstraintInterface, 7 | } from 'class-validator'; 8 | 9 | export function Match(property: string, validationOptions?: ValidationOptions) { 10 | return (object: any, propertyName: string) => { 11 | registerDecorator({ 12 | target: object.constructor, 13 | propertyName, 14 | options: validationOptions, 15 | constraints: [property], 16 | validator: MatchConstraint, 17 | }); 18 | }; 19 | } 20 | 21 | @ValidatorConstraint({ name: 'Match' }) 22 | export class MatchConstraint implements ValidatorConstraintInterface { 23 | validate(value: any, args: ValidationArguments) { 24 | const [relatedPropertyName] = args.constraints; 25 | const relatedValue = (args.object as any)[relatedPropertyName]; 26 | return value === relatedValue; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/database/entities/curriculum.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | CreateDateColumn, 4 | DeleteDateColumn, 5 | Entity, 6 | JoinColumn, 7 | ManyToOne, 8 | OneToMany, 9 | PrimaryGeneratedColumn, 10 | } from 'typeorm'; 11 | import { ApplicationEntity } from './applications.entity'; 12 | import { UsersEntity } from './users.entity'; 13 | 14 | @Entity('tb_curriculum') 15 | export class CurriculumEntity { 16 | @PrimaryGeneratedColumn('uuid') 17 | id: string; 18 | 19 | @Column() 20 | user_id: string; 21 | 22 | @ManyToOne(() => UsersEntity, {onDelete: "CASCADE"}) 23 | @JoinColumn({ name: 'user_id' }) 24 | user: UsersEntity; 25 | 26 | @OneToMany(() => ApplicationEntity, (application) => application.curriculum) 27 | applications: ApplicationEntity[]; 28 | 29 | @Column() 30 | file: string; 31 | 32 | @Column() 33 | fileKey: string; 34 | 35 | @CreateDateColumn() 36 | created_at: Date; 37 | 38 | @DeleteDateColumn() 39 | desativated_at: Date; 40 | } 41 | -------------------------------------------------------------------------------- /src/modules/auth/types/logged-user.types.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { LoginTypeEnum } from '../enums/login-type.enum'; 3 | 4 | export class LoggerUserType { 5 | @ApiProperty({ example: '218926fe-a3fa-4381-af8e-77c4017ebdb8' }) 6 | id: string; 7 | @ApiProperty({ example: 'Any Name' }) 8 | name: string; 9 | 10 | @ApiProperty({ example: 'EmailParaTeste@teste.com' }) 11 | email: string; 12 | 13 | @ApiProperty({ example: LoginTypeEnum.USER }) 14 | type: LoginTypeEnum; 15 | 16 | @ApiProperty({ example: true }) 17 | mailConfirm: boolean; 18 | 19 | @ApiProperty({ example: null }) 20 | recoverPasswordToken: string | null; 21 | 22 | @ApiProperty({ example: '2023-03-25T04:40:28.329Z' }) 23 | created_at: Date; 24 | 25 | @ApiProperty({ example: '2023-03-25T04:40:28.329Z' }) 26 | updated_at: Date; 27 | 28 | @ApiProperty({ example: true }) 29 | policies: boolean; 30 | 31 | @ApiProperty({ example: '127.0.0.1' }) 32 | ip: string; 33 | } 34 | -------------------------------------------------------------------------------- /src/modules/savedjobs/services/find-all-savedjobs.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, InternalServerErrorException } from "@nestjs/common"; 2 | import { SavedJobsEntity } from "src/database/entities/savedjobs.entity"; 3 | import { PageDto, PageOptionsDto } from "src/shared/pagination"; 4 | import { SavedJobsRepository } from "../repository/savedjobs.repository"; 5 | import { GetAllSavedJobsDto } from "../dtos/get-all-savedjobs.dto"; 6 | 7 | @Injectable() 8 | export class FindAllSavedJobsService { 9 | constructor(private readonly savedJobsRepository: SavedJobsRepository) {} 10 | 11 | async getAllSavedJobs( 12 | pageOptionsDto: PageOptionsDto, 13 | filters: GetAllSavedJobsDto, 14 | ): Promise> { 15 | try { 16 | return await this.savedJobsRepository.getAllSavedJobs(pageOptionsDto, filters); 17 | } catch (error) { 18 | throw new InternalServerErrorException(`Falha ao salvar os trabalhos salvos: ${error.message}`); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/modules/savedjobs/services/savedjobs-cleaner.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger } from "@nestjs/common"; 2 | import { Cron, CronExpression } from "@nestjs/schedule"; 3 | import { InjectRepository } from "@nestjs/typeorm"; 4 | import { SavedJobsEntity } from "src/database/entities/savedjobs.entity"; 5 | import { LessThan, Repository } from "typeorm"; 6 | 7 | @Injectable() 8 | export class SavedJobsCleanerService { 9 | private readonly logger = new Logger(SavedJobsCleanerService.name); 10 | 11 | constructor( 12 | @InjectRepository(SavedJobsEntity) 13 | private readonly savedJobsRepository: Repository, 14 | ) {} 15 | 16 | @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT) 17 | async cleanExpiredJobs() { 18 | const now = new Date(); 19 | const result = await this.savedJobsRepository.delete({ 20 | expiresAt: LessThan(now), 21 | }); 22 | 23 | this.logger.log(`🧹 Removidos ${result.affected} registros expirados.`); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/database/data-source.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config' 2 | import { DataSource, DataSourceOptions } from 'typeorm'; 3 | import 'reflect-metadata'; 4 | 5 | const { 6 | NODE_ENV, 7 | TYPEORM_HOST, 8 | TYPEORM_PORT, 9 | TYPEORM_PASSWORD, 10 | TYPEORM_USERNAME, 11 | TYPEORM_DATABASE, 12 | CA_CERT, 13 | } = process.env; 14 | 15 | export const typeormConfig: DataSourceOptions = { 16 | type: 'postgres', 17 | host: TYPEORM_HOST, 18 | port: parseInt(TYPEORM_PORT), 19 | username: TYPEORM_USERNAME, 20 | password: TYPEORM_PASSWORD, 21 | database: TYPEORM_DATABASE, 22 | entities: ['dist/database/entities/*.entity.js'], 23 | migrations: [ 24 | 'dist/database/migrations/*.js', 25 | 'dist/database/migrations/seeds/*.js', 26 | ], 27 | ssl: 28 | NODE_ENV == 'production' 29 | ? { 30 | ca: CA_CERT, 31 | rejectUnauthorized: false, 32 | } 33 | : undefined, 34 | }; 35 | 36 | export const AppDataSource = new DataSource({ 37 | ...typeormConfig, 38 | }); 39 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Configuração do ambiente 2 | NODE_ENV="development" 3 | PORT= # Porta onde o projeto vai rodar na sua máquina 4 | 5 | # JWT 6 | SECRET_KEY="" # Uma string qualquer, chave para gerar o JWT 7 | 8 | # Conexão com o banco de dados 9 | TYPEORM_HOST="localhost" # Host name do seu banco (geralmente quando está na sua maquina fica localhost) 10 | TYPEORM_PORT=5432 # A porta geralmente é 5432, se no seu caso for outra porta basta alterar 11 | TYPEORM_USERNAME="docker" # Usuário do servidor Postgres via docker compose 12 | TYPEORM_PASSWORD="ignite" # Senha do servidor Postgres via docker compose 13 | TYPEORM_DATABASE="linkedin_backend" # Banco de dados a ser conectado via docker compose 14 | 15 | CA_CERT="" # Apenas para o db em produção 16 | 17 | # AWS S3 18 | AWS_ACCESS_KEY_ID="" 19 | AWS_SECRET_ACCESS_KEY="" 20 | AWS_S3_BUCKET_NAME="" 21 | 22 | # Website 23 | FRONTEND_URL="http://localhost:3000" 24 | 25 | # Web Scaper 26 | VACANCIES_URL="http://localhost:3333" 27 | -------------------------------------------------------------------------------- /src/modules/upload/upload.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Post, 4 | UploadedFile, 5 | UseGuards, 6 | UseInterceptors, 7 | } from '@nestjs/common'; 8 | import { AuthGuard } from '@nestjs/passport'; 9 | import { FileInterceptor } from '@nestjs/platform-express'; 10 | import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; 11 | import { UploadSwagger } from 'src/shared/Swagger/decorators/upload/upload.swagger'; 12 | import { FileUploadService } from './upload.service'; 13 | 14 | @ApiTags('Upload') 15 | @ApiBearerAuth() 16 | @UseGuards(AuthGuard()) 17 | @Controller('upload') 18 | export class UploadController { 19 | constructor(private fileUploadService: FileUploadService) {} 20 | 21 | @UploadSwagger() 22 | @Post() 23 | @UseInterceptors(FileInterceptor('file')) 24 | async upload(@UploadedFile() file) { 25 | const response = await this.fileUploadService.upload(file); 26 | 27 | if (response.Location) { 28 | return response.Location; 29 | } 30 | 31 | return response; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/modules/curriculum/curriculum.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { PassportModule } from '@nestjs/passport'; 3 | import { TypeOrmModule } from '@nestjs/typeorm'; 4 | import { FileUploadService } from '../upload/upload.service'; 5 | import { CurriculumController } from './curriculum.controller'; 6 | import { CurriculumService } from './curriculum.service'; 7 | import { CurriculumRepository } from './repository/curriculum-repository'; 8 | import { UserRepository } from '../user/repository/user.repository'; 9 | import { CurriculumEntity } from 'src/database/entities/curriculum.entity'; 10 | import { UsersEntity } from 'src/database/entities/users.entity'; 11 | 12 | @Module({ 13 | imports: [ 14 | TypeOrmModule.forFeature([CurriculumEntity, UsersEntity]), 15 | PassportModule.register({ defaultStrategy: 'jwt' }), 16 | ], 17 | controllers: [CurriculumController], 18 | providers: [CurriculumService, FileUploadService, CurriculumRepository, UserRepository], 19 | }) 20 | export class CurriculumModule {} 21 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/company/get-all-companies.swagger.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatus, applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { BadRequestSwagger } from '../../bad-request.swagger'; 4 | import { UnauthorizedSwagger } from '../../unauthorized.swagger'; 5 | import { PageOptionsDto } from 'src/shared/pagination'; 6 | 7 | export function GetAllCompaniesSwagger() { 8 | return applyDecorators( 9 | ApiResponse({ 10 | status: HttpStatus.OK, 11 | description: 'Exemplo do retorno de sucesso da rota', 12 | type: PageOptionsDto, 13 | }), 14 | ApiResponse({ 15 | status: HttpStatus.UNAUTHORIZED, 16 | description: 'Modelo de erro', 17 | type: UnauthorizedSwagger, 18 | }), 19 | ApiResponse({ 20 | status: HttpStatus.BAD_REQUEST, 21 | description: 'Modelo de erro', 22 | type: BadRequestSwagger, 23 | }), 24 | ApiOperation({ 25 | summary: 'Buscar todas as empresas.', 26 | }), 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/user/classes/create-response.swagger.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { UserRole } from 'src/shared/utils/userRole/userRole'; 3 | 4 | export class CreateResponseSwagger { 5 | @ApiProperty({ 6 | example: 'Fulano de Tal', 7 | }) 8 | name: string; 9 | 10 | @ApiProperty({ 11 | example: 'johnsnow+2356@outlook.com', 12 | }) 13 | email: string; 14 | 15 | @ApiProperty({ 16 | example: '941fb31b-5799-44bc-9870-d7c1d5d2ec2c', 17 | }) 18 | id: string; 19 | 20 | @ApiProperty({ 21 | example: true, 22 | }) 23 | policies: boolean; 24 | 25 | @ApiProperty({ 26 | enum: UserRole, 27 | example: UserRole.USER, 28 | }) 29 | type: UserRole; 30 | 31 | @ApiProperty({ 32 | example: '2023-04-06T01:48:41.314Z', 33 | }) 34 | created_at: Date; 35 | 36 | @ApiProperty({ 37 | example: '2023-04-06T01:48:41.314Z', 38 | }) 39 | updated_at: Date; 40 | 41 | @ApiProperty({ 42 | example: false, 43 | }) 44 | mailConfirm: boolean; 45 | } 46 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/company/create-company.swagger.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatus, applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { CreateCompanyDto } from 'src/modules/company/dtos/create-company.dto'; 4 | import { UnauthorizedSwagger } from '../../unauthorized.swagger'; 5 | import { BadRequestSwagger } from '../../bad-request.swagger'; 6 | 7 | export function CreateCompanySwagger() { 8 | return applyDecorators( 9 | ApiResponse({ 10 | status: HttpStatus.OK, 11 | description: 'Exemplo do retorno de sucesso da rota', 12 | type: CreateCompanyDto, 13 | }), 14 | ApiResponse({ 15 | status: HttpStatus.UNAUTHORIZED, 16 | description: 'Modelo de erro', 17 | type: UnauthorizedSwagger, 18 | }), 19 | ApiResponse({ 20 | status: HttpStatus.BAD_REQUEST, 21 | description: 'Modelo de erro', 22 | type: BadRequestSwagger, 23 | }), 24 | ApiOperation({ 25 | summary: 'Cadastrar uma empresa.', 26 | }), 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/user/view-users.swagger.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, HttpStatus } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { BadRequestSwagger } from '../../bad-request.swagger'; 4 | import { UnauthorizedSwagger } from '../../unauthorized.swagger'; 5 | import { ListResponseSwagger } from './classes/list-response.swagger'; 6 | 7 | export function SwaggerFindUsers() { 8 | return applyDecorators( 9 | ApiResponse({ 10 | status: HttpStatus.OK, 11 | description: 'Exemplo do retorno de sucesso da rota', 12 | type: ListResponseSwagger, 13 | }), 14 | ApiResponse({ 15 | status: HttpStatus.UNAUTHORIZED, 16 | description: 'Modelo de erro', 17 | type: UnauthorizedSwagger, 18 | }), 19 | ApiResponse({ 20 | status: HttpStatus.BAD_REQUEST, 21 | description: 'Modelo de erro', 22 | type: BadRequestSwagger, 23 | }), 24 | ApiOperation({ 25 | summary: 'Visualizar todos os usuários', 26 | }), 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/company/delete-company-by-id.swagger.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatus, applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { CompanyIdDto } from 'src/modules/company/dtos/company-id.dto'; 4 | import { UnauthorizedSwagger } from '../../unauthorized.swagger'; 5 | import { BadRequestSwagger } from '../../bad-request.swagger'; 6 | 7 | export function DeleteCompanyByIdSwagger() { 8 | return applyDecorators( 9 | ApiResponse({ 10 | status: HttpStatus.OK, 11 | description: 'Exemplo do retorno de sucesso da rota', 12 | type: CompanyIdDto, 13 | }), 14 | ApiResponse({ 15 | status: HttpStatus.UNAUTHORIZED, 16 | description: 'Modelo de erro', 17 | type: UnauthorizedSwagger, 18 | }), 19 | ApiResponse({ 20 | status: HttpStatus.BAD_REQUEST, 21 | description: 'Modelo de erro', 22 | type: BadRequestSwagger, 23 | }), 24 | ApiOperation({ 25 | summary: 'Excluir uma empresa por id.', 26 | }), 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/jobs/create-new-job.swagger.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatus, applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { UnauthorizedSwagger } from '../../unauthorized.swagger'; 4 | import { BadRequestSwagger } from '../../bad-request.swagger'; 5 | 6 | export function CreateNewJobSwagger() { 7 | return applyDecorators( 8 | ApiResponse({ 9 | status: HttpStatus.CREATED, 10 | description: 'Exemplo do retorno de sucesso da rota', 11 | type: 'Vaga publicada com sucesso', 12 | }), 13 | ApiResponse({ 14 | status: HttpStatus.UNAUTHORIZED, 15 | description: 'Modelo de erro', 16 | type: UnauthorizedSwagger, 17 | }), 18 | ApiResponse({ 19 | status: HttpStatus.BAD_REQUEST, 20 | description: 'Modelo de erro', 21 | type: BadRequestSwagger, 22 | }), 23 | ApiOperation({ 24 | summary: 'Criar uma vaga!', 25 | }), 26 | ApiOperation({ 27 | summary: 'Buscar todas as vagas.', 28 | }), 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/modules/alert/repository/alerts.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Repository } from 'typeorm'; 3 | import { AlertEntity } from '../../../database/entities/alert.entity'; 4 | import { InjectRepository } from '@nestjs/typeorm'; 5 | import { handleError } from '../../../shared/utils/handle-error.util'; 6 | 7 | @Injectable() 8 | export class AlertsRepository { 9 | constructor( 10 | @InjectRepository(AlertEntity) 11 | private alertsRepository: Repository, 12 | ) {} 13 | 14 | async createAlert(data: Partial): Promise { 15 | return this.alertsRepository.save(data).catch(handleError); 16 | } 17 | 18 | async findAlertById(alertId: string): Promise { 19 | return this.alertsRepository 20 | .findOne({ 21 | where: { id: alertId }, 22 | relations: ['user'], 23 | }) 24 | .catch(handleError); 25 | } 26 | 27 | async findAll(): Promise { 28 | return this.alertsRepository.find().catch(handleError); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/company/recovery-password-by-email.swagger.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatus, applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { EmailDto } from 'src/modules/user/dtos/email-user.dto'; 4 | import { UnauthorizedSwagger } from '../../unauthorized.swagger'; 5 | import { BadRequestSwagger } from '../../bad-request.swagger'; 6 | 7 | export function RecoverPasswordByEmailSwagger() { 8 | return applyDecorators( 9 | ApiResponse({ 10 | status: HttpStatus.OK, 11 | description: 'Exemplo do retorno de sucesso da rota', 12 | type: EmailDto, 13 | }), 14 | ApiResponse({ 15 | status: HttpStatus.UNAUTHORIZED, 16 | description: 'Modelo de erro', 17 | type: UnauthorizedSwagger, 18 | }), 19 | ApiResponse({ 20 | status: HttpStatus.BAD_REQUEST, 21 | description: 'Modelo de erro', 22 | type: BadRequestSwagger, 23 | }), 24 | ApiOperation({ 25 | summary: 'Send email to recovery password.', 26 | }), 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/user/update-pass-after-email-recovery.swagger.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, HttpStatus } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { BadRequestSwagger } from '../../bad-request.swagger'; 4 | import { NotFoundSwagger } from '../../not-found.swagger'; 5 | import { UnauthorizedSwagger } from '../../unauthorized.swagger'; 6 | 7 | export function SwaggerUpdatePassAfterRecovery() { 8 | return applyDecorators( 9 | ApiResponse({ 10 | status: HttpStatus.OK, 11 | description: 'Exemplo do retorno de sucesso da rota', 12 | type: NotFoundSwagger, 13 | }), 14 | ApiResponse({ 15 | status: HttpStatus.UNAUTHORIZED, 16 | description: 'Modelo de erro', 17 | type: UnauthorizedSwagger, 18 | }), 19 | ApiResponse({ 20 | status: HttpStatus.BAD_REQUEST, 21 | description: 'Modelo de erro', 22 | type: BadRequestSwagger, 23 | }), 24 | ApiOperation({ 25 | summary: 'User update password.', 26 | }), 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/user/update-password.swagger.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, HttpStatus } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { BadRequestSwagger } from '../../bad-request.swagger'; 4 | import { NotFoundSwagger } from '../../not-found.swagger'; 5 | import { UnauthorizedSwagger } from '../../unauthorized.swagger'; 6 | 7 | export function SwaggerUpdatePassword() { 8 | return applyDecorators( 9 | ApiResponse({ 10 | status: HttpStatus.OK, 11 | description: 'Exemplo do retorno de sucesso da rota', 12 | type: NotFoundSwagger, 13 | }), 14 | ApiResponse({ 15 | status: HttpStatus.UNAUTHORIZED, 16 | description: 'Modelo de erro', 17 | type: UnauthorizedSwagger, 18 | }), 19 | ApiResponse({ 20 | status: HttpStatus.BAD_REQUEST, 21 | description: 'Modelo de erro', 22 | type: BadRequestSwagger, 23 | }), 24 | ApiOperation({ 25 | summary: 'User update password without recovery e-mail.', 26 | }), 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /docker-compose-vm.yml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | image: ${CONTAINER_IMAGE} 4 | container_name: linkedIn_Backend 5 | restart: always 6 | networks: 7 | - traefik 8 | - database 9 | env_file: 10 | - .env 11 | environment: 12 | - TYPEORM_HOST=database_soujunior 13 | labels: 14 | - "traefik.enable=true" 15 | - "traefik.http.routers.vagas-backend.rule=Host(`vagas-backend.soujunior.tech`)" 16 | - "traefik.http.routers.vagas-backend.entrypoints=websecure" 17 | - "traefik.http.routers.vagas-backend.tls.certresolver=myresolver" 18 | - "traefik.http.services.vagas-backend.loadbalancer.server.port=3000" 19 | - "traefik.http.middlewares.traefik-headers.headers.sslredirect=true" 20 | - "traefik.http.middlewares.traefik-headers.headers.sslforcehost=true" 21 | - "traefik.http.middlewares.traefik-headers.headers.sslproxyheaders.X-Forwarded-Proto=https" 22 | - "traefik.docker.network=traefik" 23 | 24 | networks: 25 | database: 26 | external: true 27 | traefik: 28 | external: true 29 | -------------------------------------------------------------------------------- /src/modules/jobs/services/get-all-jobs-from-logged-company.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CompanyRepository } from 'src/modules/company/repository/company-repository'; 3 | import { IJobsResponse } from '../interfaces/interfaces'; 4 | import { JobRepository } from '../repository/job.repository'; 5 | 6 | @Injectable() 7 | export class GetAllJobsFromLoggedCompanyService { 8 | constructor( 9 | private companyRepository: CompanyRepository, 10 | private jobsRepository: JobRepository 11 | ) {} 12 | 13 | async execute(companyId: string 14 | ): Promise { 15 | const jobs = await this.jobsRepository.getAllJobsByCompanyId(companyId); 16 | 17 | if (!jobs) { 18 | return { 19 | status: 400, 20 | data:{ 21 | message: "This company has no jobs yet." 22 | } 23 | } 24 | } 25 | 26 | return { 27 | status: 200, 28 | data:{ 29 | message: "Logged company jobs listed successfully.", 30 | content: jobs 31 | } 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/user/recover-by-email.swagger.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, HttpStatus } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { BadRequestSwagger } from '../../bad-request.swagger'; 4 | import { UnauthorizedSwagger } from '../../unauthorized.swagger'; 5 | import { RecoveryPasswordSwagger } from './classes/recovery-password.swagger'; 6 | 7 | export function SwaggerRecoverEmail() { 8 | return applyDecorators( 9 | ApiResponse({ 10 | status: HttpStatus.OK, 11 | description: 'Exemplo do retorno de sucesso da rota', 12 | type: RecoveryPasswordSwagger, 13 | }), 14 | ApiResponse({ 15 | status: HttpStatus.UNAUTHORIZED, 16 | description: 'Modelo de erro', 17 | type: UnauthorizedSwagger, 18 | }), 19 | ApiResponse({ 20 | status: HttpStatus.BAD_REQUEST, 21 | description: 'Modelo de erro', 22 | type: BadRequestSwagger, 23 | }), 24 | ApiOperation({ 25 | summary: 'Send email to recovery password.', 26 | }), 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/database/entities/comments.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | CreateDateColumn, 4 | DeleteDateColumn, 5 | Entity, 6 | JoinColumn, 7 | ManyToOne, 8 | PrimaryGeneratedColumn, 9 | UpdateDateColumn, 10 | } from 'typeorm'; 11 | import { JobsEntity } from './jobs.entity'; 12 | import { UsersEntity } from './users.entity'; 13 | 14 | @Entity('tb_comments') 15 | export class CommentsEntity { 16 | @PrimaryGeneratedColumn('uuid') 17 | id: string; 18 | 19 | @Column({ length: 500 }) 20 | comment: string; 21 | 22 | @ManyToOne(() => UsersEntity, { onDelete: "CASCADE"}) 23 | @JoinColumn({ name: 'user_id' }) 24 | user: UsersEntity; 25 | 26 | @Column() 27 | user_id: string; 28 | 29 | @ManyToOne(() => JobsEntity, (job) => job.comments, { onDelete: "CASCADE"}) 30 | @JoinColumn({ name: 'job_id' }) 31 | job: JobsEntity; 32 | 33 | @Column() 34 | job_id: string; 35 | 36 | @CreateDateColumn() 37 | created_at: Date; 38 | 39 | @UpdateDateColumn({ update: true }) 40 | updated_at: Date; 41 | 42 | @DeleteDateColumn() 43 | desativated_at: Date; 44 | } 45 | -------------------------------------------------------------------------------- /src/modules/auth/dtos/user-login.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { 3 | IsEmail, 4 | IsEnum, 5 | IsNotEmpty, 6 | IsString, 7 | Length, 8 | Matches, 9 | } from 'class-validator'; 10 | import { LoginTypeEnum } from '../enums/login-type.enum'; 11 | 12 | export class UserLoginDto { 13 | @IsEmail() 14 | @IsNotEmpty() 15 | @IsString() 16 | @ApiProperty({ 17 | description: 'E-mail do usuário.', 18 | example: 'pipomills@pipomills.com', 19 | }) 20 | @IsNotEmpty() 21 | email: string; 22 | 23 | @IsString() 24 | @Length(8, 20) 25 | @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, { 26 | message: 'Senha muito fraca', 27 | }) 28 | @ApiProperty({ 29 | description: 'Senha de Login', 30 | example: 'Abcd@1234', 31 | }) 32 | @IsNotEmpty() 33 | password: string; 34 | 35 | @ApiProperty({ 36 | enum: LoginTypeEnum, 37 | example: LoginTypeEnum.COMPANY, 38 | }) 39 | @IsEnum(LoginTypeEnum, { message: 'Opções de type COMPANY e USER' }) 40 | @IsNotEmpty() 41 | type: LoginTypeEnum; 42 | } 43 | -------------------------------------------------------------------------------- /src/modules/comment/dtos/create-comment.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString, MaxLength } from 'class-validator'; 3 | 4 | export class CreateCommentDto { 5 | @ApiProperty({ 6 | description: 'Comentário (até 500 caracteres)', 7 | example: 'Boa!', 8 | }) 9 | @IsString({ message: 'O campo comment deve ser uma string' }) 10 | @IsNotEmpty({ message: 'O campo comment não pode estar vazio' }) 11 | @MaxLength(500) 12 | comment: string; 13 | 14 | @ApiProperty({ 15 | description: 'ID do trabalho', 16 | example: 'be02e7b0-238a-44c2-b9db-ccb339d63fc9', 17 | }) 18 | @IsString({ message: 'O campo job_id deve ser uma string' }) 19 | @IsNotEmpty({ message: 'O campo job_id não pode estar vazio' }) 20 | job_id: string; 21 | 22 | @ApiProperty({ 23 | description: 'ID do usuário', 24 | example: 'be02e7b0-238a-44c2-b9db-ccb339d63fc9', 25 | }) 26 | @IsString({ message: 'O campo user_id deve ser uma string' }) 27 | @IsNotEmpty({ message: 'O campo user_id não pode estar vazio' }) 28 | user_id: string; 29 | } 30 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/user/get-user.swagger.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, HttpStatus } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { BadRequestSwagger } from '../../bad-request.swagger'; 4 | import { UnprocessableEntitySwagger } from '../../unprocessable-entity.swagger'; 5 | import { CreateResponseSwagger } from './classes/create-response.swagger'; 6 | 7 | export function SwaggerGetUser() { 8 | return applyDecorators( 9 | ApiResponse({ 10 | status: HttpStatus.OK, 11 | description: 'Exemplo do retorno de sucesso da rota', 12 | type: CreateResponseSwagger, 13 | }), 14 | ApiResponse({ 15 | status: HttpStatus.UNPROCESSABLE_ENTITY, 16 | description: 'Modelo de erro', 17 | type: UnprocessableEntitySwagger, 18 | }), 19 | ApiResponse({ 20 | status: HttpStatus.BAD_REQUEST, 21 | description: 'Modelo de erro', 22 | type: BadRequestSwagger, 23 | }), 24 | ApiOperation({ 25 | summary: 'Ativar um usuário pelo ID', 26 | }), 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/company/update-password.swagger.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatus, applyDecorators } from '@nestjs/common'; 2 | import { CreatePasswordHashDto } from 'src/modules/company/dtos/update-my-password.dto'; 3 | import { UnauthorizedSwagger } from '../../unauthorized.swagger'; 4 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 5 | import { BadRequestSwagger } from '../../bad-request.swagger'; 6 | 7 | export function UpdatePasswordSwagger() { 8 | return applyDecorators( 9 | ApiResponse({ 10 | status: HttpStatus.OK, 11 | description: 'Exemplo do retorno de sucesso da rota', 12 | type: CreatePasswordHashDto, 13 | }), 14 | ApiResponse({ 15 | status: HttpStatus.UNAUTHORIZED, 16 | description: 'Modelo de erro', 17 | type: UnauthorizedSwagger, 18 | }), 19 | ApiResponse({ 20 | status: HttpStatus.BAD_REQUEST, 21 | description: 'Modelo de erro', 22 | type: BadRequestSwagger, 23 | }), 24 | ApiOperation({ 25 | summary: 'Company update password without recovery e-mail.', 26 | }), 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/shared/pagination/pageOptions.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { Type } from 'class-transformer'; 3 | import { IsEnum, IsInt, IsOptional, IsString, Min } from 'class-validator'; 4 | import { Order } from '.'; 5 | 6 | export class PageOptionsDto { 7 | @IsEnum(Order) 8 | @IsOptional() 9 | @ApiProperty({ 10 | description: 'Ordenação da lista', 11 | default: 'ASC', 12 | }) 13 | readonly order?: Order = Order.ASC; 14 | 15 | @Type(() => Number) 16 | @IsInt() 17 | @Min(1) 18 | @IsOptional() 19 | @ApiProperty({ 20 | description: 'Numero da pagina', 21 | default: 1, 22 | }) 23 | readonly page?: number = 1; 24 | 25 | @Type(() => Number) 26 | @IsInt() 27 | @Min(1) 28 | @IsOptional() 29 | @ApiProperty({ 30 | description: 'Itens por pagina', 31 | default: 10, 32 | }) 33 | readonly take?: number = 10; 34 | 35 | @IsOptional() 36 | @IsString() 37 | @ApiProperty({ 38 | description: 'Ordenação por uma coluna espeficica', 39 | default: 'id', 40 | }) 41 | readonly orderByColumn?: string = 'id'; 42 | } 43 | -------------------------------------------------------------------------------- /src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Req, Res } from '@nestjs/common'; 2 | import { ApiOperation, ApiTags } from '@nestjs/swagger'; 3 | import { AppService } from './app.service'; 4 | import { SwaggerHealthCheck } from './shared/Swagger/decorators/app/health-check.swagger.decorator'; 5 | 6 | import { Request, Response } from 'express'; 7 | 8 | @ApiTags('Status') 9 | @Controller() 10 | export class AppController { 11 | constructor(private readonly appService: AppService) {} 12 | 13 | @Get() 14 | @ApiOperation({ 15 | summary: 'Show status of operation', 16 | }) 17 | getAppStatus(@Req() req: Request) { 18 | const baseUrl = req.protocol + '://' + req.get('host'); 19 | return this.appService.getAppStatus(baseUrl); 20 | } 21 | 22 | @Get('/health-check') 23 | @SwaggerHealthCheck() 24 | @ApiOperation({ 25 | summary: 'Retorna status dos serviços de email e banco de dados', 26 | }) 27 | async getHealthCheck(@Res() res: Response){ 28 | const {status, data} = await this.appService.getHealthCheck(); 29 | return res.status(status).send(data); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/company/update-password-after-recovery-email.swagger.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatus, applyDecorators } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse } from '@nestjs/swagger'; 3 | import { CreatePasswordHashDto } from 'src/modules/company/dtos/update-my-password.dto'; 4 | import { UnauthorizedSwagger } from '../../unauthorized.swagger'; 5 | import { BadRequestSwagger } from '../../bad-request.swagger'; 6 | 7 | export function UpdatePasswordAfterRecoveryEmailSwagger() { 8 | return applyDecorators( 9 | ApiResponse({ 10 | status: HttpStatus.OK, 11 | description: 'Exemplo do retorno de sucesso da rota', 12 | type: CreatePasswordHashDto, 13 | }), 14 | ApiResponse({ 15 | status: HttpStatus.UNAUTHORIZED, 16 | description: 'Modelo de erro', 17 | type: UnauthorizedSwagger, 18 | }), 19 | ApiResponse({ 20 | status: HttpStatus.BAD_REQUEST, 21 | description: 'Modelo de erro', 22 | type: BadRequestSwagger, 23 | }), 24 | ApiOperation({ 25 | summary: 'Company update password.', 26 | }), 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/modules/jobs/services/search-job.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { JobsEntity } from '../../../database/entities/jobs.entity'; 4 | import { 5 | PageDto, 6 | PageMetaDto, 7 | PageOptionsDto, 8 | } from '../../../shared/pagination'; 9 | import { GetAllJobsDto } from '../dtos/get-all-jobs.dto'; 10 | import { JobRepository } from '../repository/job.repository'; 11 | 12 | @Injectable() 13 | export class SearchJobsService { 14 | constructor( 15 | @InjectRepository(JobRepository) 16 | private readonly jobRepository: JobRepository, 17 | ) {} 18 | 19 | async execute( 20 | searchQuery: string, 21 | pageOptionsDto: PageOptionsDto, 22 | params: GetAllJobsDto, 23 | ): Promise> { 24 | const { itemCount, entities } = await this.jobRepository.searchJobs( 25 | searchQuery, 26 | pageOptionsDto, 27 | params, 28 | ); 29 | 30 | const pageMetaDto = new PageMetaDto({ itemCount, pageOptionsDto }); 31 | 32 | return new PageDto(entities, pageMetaDto); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/modules/jobs/services/delete-job.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { JobsEntity } from '../../../database/entities/jobs.entity'; 3 | import { StatusEnum } from '../../../shared/enums/status.enum'; 4 | import { JobRepository } from '../repository/job.repository'; 5 | import { IJobsResponse } from '../interfaces/interfaces'; 6 | 7 | @Injectable() 8 | export class DeleteJobService { 9 | constructor(private jobRepository: JobRepository) {} 10 | 11 | async execute(jobId: string, content: string) : Promise { 12 | 13 | const jobExists = await this.jobRepository.findOneById(jobId) 14 | 15 | if (!jobExists) { 16 | return { 17 | status: 404, 18 | data: { 19 | message: "Job could not be found" 20 | } 21 | } 22 | } 23 | 24 | jobExists.status = StatusEnum.ARCHIVED; 25 | jobExists.content = content; 26 | 27 | await this.jobRepository.updateJob(jobId, jobExists); 28 | 29 | return { 30 | status: 200, 31 | data: { 32 | message: "Job archived successfully" 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/deploy-prod.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Production 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | deploy: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | 18 | - name: Configure AWS credentials for Production 19 | uses: aws-actions/configure-aws-credentials@v1 20 | with: 21 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 22 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 23 | aws-region: us-east-1 24 | 25 | - name: Login to Amazon ECR 26 | id: login-ecr 27 | uses: aws-actions/amazon-ecr-login@v1 28 | 29 | - name: Build, Tag, and Push the Image to Amazon ECR for Production 30 | env: 31 | ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} 32 | ECR_REPOSITORY: vagas 33 | IMAGE_TAG: latest 34 | run: | 35 | docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . 36 | docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -------------------------------------------------------------------------------- /src/modules/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ConfigModule } from '@nestjs/config'; 3 | import { JwtModule } from '@nestjs/jwt'; 4 | import { TypeOrmModule } from '@nestjs/typeorm'; 5 | import { CompanyRepository } from '../company/repository/company-repository'; 6 | import { UserRepository } from '../user/repository/user.repository'; 7 | import { AuthController } from './auth.controller'; 8 | import { JwtStrategy } from './jtw/jwt.strategy'; 9 | import { AuthLoginService } from './services/auth-login.service'; 10 | import { UsersEntity } from 'src/database/entities/users.entity'; 11 | import { CompaniesEntity } from 'src/database/entities/companies.entity'; 12 | 13 | @Module({ 14 | imports: [ 15 | ConfigModule.forRoot({ isGlobal: true }), 16 | TypeOrmModule.forFeature([UsersEntity, CompaniesEntity]), 17 | JwtModule.register({ 18 | secret: process.env.SECRET_KEY, 19 | signOptions: { expiresIn: '24h' }, 20 | }), 21 | ], 22 | controllers: [AuthController], 23 | providers: [UserRepository, CompanyRepository, AuthLoginService, JwtStrategy], 24 | }) 25 | export class AuthModule {} 26 | -------------------------------------------------------------------------------- /.github/workflows/deploy-hmg.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Homologation 2 | 3 | on: 4 | push: 5 | branches: 6 | - hmg 7 | pull_request: 8 | branches: 9 | - hmg 10 | 11 | jobs: 12 | deploy: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | 18 | - name: Configure AWS credentials for Homologation 19 | uses: aws-actions/configure-aws-credentials@v1 20 | with: 21 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 22 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 23 | aws-region: us-east-1 24 | 25 | - name: Login to Amazon ECR 26 | id: login-ecr 27 | uses: aws-actions/amazon-ecr-login@v1 28 | 29 | - name: Build, Tag, and Push the Image to Amazon ECR for Homologation 30 | env: 31 | ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} 32 | ECR_REPOSITORY: vagas-hmg 33 | IMAGE_TAG: latest 34 | run: | 35 | docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . 36 | docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -------------------------------------------------------------------------------- /src/database/entities/candidacy.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | Entity, 4 | PrimaryGeneratedColumn, 5 | ManyToOne, 6 | JoinColumn, 7 | } from 'typeorm'; 8 | import { UsersEntity } from './users.entity'; 9 | import { JobsEntity } from './jobs.entity'; 10 | import { CandidacyStatus } from './candidancy-status.enum'; 11 | 12 | @Entity('tb_candidacies') 13 | export class CandidacyEntity { 14 | @PrimaryGeneratedColumn('uuid') 15 | id: string; 16 | 17 | @Column('uuid', { name: 'job_id' }) 18 | jobId: string; 19 | 20 | @Column('uuid', { name: 'user_id' }) 21 | userId: string; 22 | 23 | @Column({ type: 'enum', enum: CandidacyStatus }) 24 | status: CandidacyStatus; 25 | 26 | @Column({ 27 | type: 'date', 28 | name: 'date_candidacy', 29 | default: () => 'CURRENT_TIMESTAMP', 30 | }) 31 | dateCandidacy: Date; 32 | 33 | @Column({ name: 'date_closing', type: 'timestamp', nullable: true }) 34 | dateClosing: Date; 35 | 36 | @ManyToOne(() => UsersEntity) 37 | @JoinColumn({ name: 'user_id' }) 38 | user: UsersEntity; 39 | 40 | @ManyToOne(() => JobsEntity) 41 | @JoinColumn({ name: 'job_id' }) 42 | job: JobsEntity; 43 | } 44 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | database_soujunior: 5 | image: postgres 6 | container_name: vagas-api-db 7 | restart: always 8 | ports: 9 | - '5432:5432' 10 | environment: 11 | - POSTGRES_USER=${TYPEORM_USERNAME} 12 | - POSTGRES_PASSWORD=${TYPEORM_PASSWORD} 13 | - POSTGRES_DB=${TYPEORM_DATABASE} 14 | volumes: 15 | - pgdata:/data/postgres 16 | healthcheck: 17 | test: ['CMD-SHELL', 'pg_isready -U ${TYPEORM_USERNAME}'] 18 | interval: 10s 19 | timeout: 5s 20 | retries: 5 21 | 22 | mailhog: 23 | image: mailhog/mailhog 24 | container_name: mailhog 25 | restart: always 26 | ports: 27 | - '1025:1025' 28 | - '8025:8025' 29 | 30 | app: 31 | build: . 32 | container_name: linkedin-backend 33 | restart: always 34 | ports: 35 | - '3000:3000' 36 | volumes: 37 | - .:/usr/app 38 | depends_on: 39 | database_soujunior: 40 | condition: service_healthy 41 | mailhog: 42 | condition: service_started 43 | env_file: 44 | - .env 45 | 46 | volumes: 47 | pgdata: 48 | driver: local 49 | -------------------------------------------------------------------------------- /src/modules/reports/dtos/create-report.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString, MinLength } from 'class-validator'; 3 | 4 | export class CreateReportDto { 5 | @ApiProperty({ 6 | description: 'ID do trabalho a ser relatado', 7 | example: 'be02e7b0-238a-44c2-b9db-ccb339d63fc9', 8 | }) 9 | @IsString({ message: 'O campo job_id deve ser uma string' }) 10 | @IsNotEmpty({ message: 'O campo job_id não pode estar vazio' }) 11 | job_id: string; 12 | 13 | @ApiProperty({ 14 | description: 'ID do usuário que está fazendo o relatório', 15 | example: 'be02e7b0-238a-44c2-b9db-ccb339d63fc9', 16 | }) 17 | @IsString({ message: 'O campo user_id deve ser uma string' }) 18 | @IsNotEmpty({ message: 'O campo user_id não pode estar vazio' }) 19 | user_id: string; 20 | 21 | @ApiProperty({ 22 | description: 'Descrição do relatório (mínimo de 10 caracteres)', 23 | example: 'Trabalho bem feito!', 24 | }) 25 | @IsString({ message: 'O campo description deve ser uma string' }) 26 | @IsNotEmpty({ message: 'O campo description não pode estar vazio' }) 27 | @MinLength(10) 28 | description: string; 29 | } 30 | -------------------------------------------------------------------------------- /src/database/entities/applications.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | CreateDateColumn, 4 | Entity, 5 | JoinColumn, 6 | ManyToOne, 7 | PrimaryGeneratedColumn, 8 | UpdateDateColumn, 9 | } from 'typeorm'; 10 | import { CurriculumEntity } from './curriculum.entity'; 11 | import { JobsEntity } from './jobs.entity'; 12 | import { UsersEntity } from './users.entity'; 13 | 14 | @Entity('tb_applications') 15 | export class ApplicationEntity { 16 | @PrimaryGeneratedColumn('uuid') 17 | id: string; 18 | 19 | @ManyToOne(() => JobsEntity, { onDelete: "CASCADE"}) 20 | @JoinColumn({ name: 'job_id' }) 21 | job: JobsEntity; 22 | 23 | @Column() 24 | job_id: string; 25 | 26 | @ManyToOne(() => UsersEntity, { onDelete: "CASCADE"}) 27 | @JoinColumn({ name: 'user_id' }) 28 | user: UsersEntity; 29 | 30 | @Column() 31 | user_id: string; 32 | 33 | @ManyToOne(() => CurriculumEntity, { onDelete: "CASCADE"}) 34 | @JoinColumn({ name: 'curriculum_id' }) 35 | curriculum: CurriculumEntity; 36 | 37 | @Column() 38 | curriculum_id: string; 39 | 40 | @CreateDateColumn() 41 | created_at: Date; 42 | 43 | @UpdateDateColumn({ update: true }) 44 | updated_at: Date; 45 | } 46 | -------------------------------------------------------------------------------- /src/database/entities/savedjobs.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | CreateDateColumn, 4 | Entity, 5 | Index, 6 | JoinColumn, 7 | ManyToOne, 8 | PrimaryGeneratedColumn, 9 | } from 'typeorm'; 10 | import { UsersEntity } from './users.entity'; 11 | import { JobsEntity } from './jobs.entity'; 12 | 13 | @Entity('tb_saved_jobs') 14 | export class SavedJobsEntity { 15 | @PrimaryGeneratedColumn('uuid') 16 | id: string; 17 | 18 | @Index() 19 | @ManyToOne(() => UsersEntity, (user) => user.savedJobs, { 20 | onDelete: 'CASCADE', 21 | nullable: false, 22 | }) 23 | @JoinColumn({ name: 'userId' }) 24 | user: UsersEntity; 25 | 26 | @Index() 27 | @ManyToOne(() => JobsEntity, (job) => job.savedJobs, { eager: false }) 28 | @JoinColumn({ name: 'jobId' }) 29 | job: JobsEntity; 30 | 31 | @CreateDateColumn() 32 | savedAt: Date; 33 | 34 | @Column({ type: 'timestamp' }) 35 | expiresAt: Date; 36 | 37 | constructor(savedJob?: Partial) { 38 | this.id = savedJob?.id; 39 | this.user = savedJob?.user; 40 | this.job = savedJob?.job; 41 | this.savedAt = savedJob?.savedAt; 42 | this.expiresAt = savedJob?.expiresAt; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/modules/comment/services/create-comment.service.ts: -------------------------------------------------------------------------------- 1 | import { BadRequestException, Injectable } from '@nestjs/common'; 2 | import { UserRepository } from '../../../modules/user/repository/user.repository'; 3 | import { JobRepository } from '../../jobs/repository/job.repository'; 4 | import { CreateCommentDto } from '../dtos/create-comment.dto'; 5 | import { CommentRepository } from '../repository/comment.repository'; 6 | 7 | @Injectable() 8 | export class CreateCommentService { 9 | constructor( 10 | private commentRepository: CommentRepository, 11 | private jobRepository: JobRepository, 12 | private userRepository: UserRepository, 13 | ) {} 14 | 15 | async execute(data: CreateCommentDto) { 16 | const { job_id, user_id } = data; 17 | 18 | const userExists = await this.userRepository.findOneById(user_id); 19 | 20 | if (!userExists) { 21 | throw new BadRequestException(`User does not exist`); 22 | } 23 | 24 | const jobExists = await this.jobRepository.findOneById(job_id); 25 | 26 | if (!jobExists) { 27 | throw new BadRequestException(`Job does not exist`); 28 | } 29 | 30 | return this.commentRepository.createComment(data); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/savedjobs/view-savedjobs.swagger.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, HttpStatus } from "@nestjs/common"; 2 | import { ApiOperation, ApiResponse } from "@nestjs/swagger"; 3 | import { ListResponseSwagger } from "../user/classes/list-response.swagger"; 4 | import { UnauthorizedSwagger } from "../../unauthorized.swagger"; 5 | import { BadRequestSwagger } from "../../bad-request.swagger"; 6 | 7 | export function SwaggerFindSavedJobs(){ 8 | return applyDecorators( 9 | ApiResponse({ 10 | status: HttpStatus.OK, 11 | description: 'Sucesso na rota. Exemplo de sucesso: ', 12 | type: ListResponseSwagger, 13 | }), 14 | ApiResponse({ 15 | status: HttpStatus.UNAUTHORIZED, 16 | description: 'Não autorizado.', 17 | type: UnauthorizedSwagger, 18 | }), 19 | ApiResponse({ 20 | status: HttpStatus.BAD_REQUEST, 21 | description: 'Erro no servidor.', 22 | type: BadRequestSwagger, 23 | }), 24 | ApiOperation({ 25 | summary: 'Visualizar todas as aplicações salvas.', 26 | }), 27 | 28 | ); 29 | } -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/user/delete-user.swagger.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, HttpStatus } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger'; 3 | import { GetByParamsDto } from 'src/modules/user/dtos/get-by-params.dto'; 4 | import { BadRequestSwagger } from '../../bad-request.swagger'; 5 | import { NotFoundSwagger } from '../../not-found.swagger'; 6 | import { UnauthorizedSwagger } from '../../unauthorized.swagger'; 7 | 8 | export function SwaggerDeleteUser() { 9 | return applyDecorators( 10 | ApiResponse({ 11 | status: HttpStatus.OK, 12 | description: 'Exemplo do retorno de sucesso da rota', 13 | type: NotFoundSwagger, 14 | }), 15 | ApiResponse({ 16 | status: HttpStatus.UNAUTHORIZED, 17 | description: 'Modelo de erro', 18 | type: UnauthorizedSwagger, 19 | }), 20 | ApiResponse({ 21 | status: HttpStatus.BAD_REQUEST, 22 | description: 'Modelo de erro', 23 | type: BadRequestSwagger, 24 | }), 25 | ApiOperation({ 26 | summary: 'Deletar um usuário pelo ID', 27 | }), 28 | ApiParam({ 29 | type: GetByParamsDto, 30 | name: '', 31 | }), 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/modules/savedjobs/services/delete-saved-jobs.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | NotFoundException, 4 | UnauthorizedException, 5 | } from '@nestjs/common'; 6 | import { DeleteSavedJobDto } from '../dtos/delete-saved-job-dto'; 7 | import { InjectRepository } from '@nestjs/typeorm'; 8 | import { Repository } from 'typeorm'; 9 | import { SavedJobsEntity } from '../../../database/entities/savedjobs.entity'; 10 | 11 | @Injectable() 12 | export class DeleteSavedJobsService { 13 | constructor( 14 | @InjectRepository(SavedJobsEntity) 15 | private readonly savedJobsRepository: Repository, 16 | ) {} 17 | 18 | async execute(deleteSavedJobDto: DeleteSavedJobDto, userId: string) { 19 | const { id } = deleteSavedJobDto; 20 | 21 | const savedJob = await this.savedJobsRepository.findOne({ 22 | where: { id }, 23 | relations: ['user'], 24 | }); 25 | 26 | if (!savedJob) { 27 | throw new NotFoundException(); 28 | } 29 | 30 | if (savedJob.user.id !== userId) { 31 | throw new UnauthorizedException(); 32 | } 33 | 34 | await this.savedJobsRepository.remove(savedJob); 35 | 36 | return { message: 'Job deleted successfully' }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/company/update-company-by-id.swagger.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatus, applyDecorators } from '@nestjs/common'; 2 | import { ApiBody, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger'; 3 | import { UpdateCompanyDto } from 'src/modules/company/dtos/update-company.dto'; 4 | import { BadRequestSwagger } from '../../bad-request.swagger'; 5 | import { UnauthorizedSwagger } from '../../unauthorized.swagger'; 6 | 7 | export function UpdateCompanyByIdSwagger() { 8 | return applyDecorators( 9 | ApiParam({ 10 | name: 'id', 11 | type: 'string', 12 | }), 13 | ApiBody({ 14 | type: UpdateCompanyDto, 15 | }), 16 | ApiResponse({ 17 | status: HttpStatus.OK, 18 | description: 'Exemplo do retorno de sucesso da rota', 19 | type: UpdateCompanyDto, 20 | }), 21 | ApiResponse({ 22 | status: HttpStatus.UNAUTHORIZED, 23 | description: 'Modelo de erro', 24 | type: UnauthorizedSwagger, 25 | }), 26 | ApiResponse({ 27 | status: HttpStatus.BAD_REQUEST, 28 | description: 'Modelo de erro', 29 | type: BadRequestSwagger, 30 | }), 31 | ApiOperation({ 32 | summary: 'Atualizar uma empresa por id.', 33 | }), 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/modules/reports/reports.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { JobRepository } from '../jobs/repository/job.repository'; 4 | import { UserRepository } from '../user/repository/user.repository'; 5 | import { ReportsController } from './reports.controller'; 6 | import { ReportRepository } from './repository/reports.repository'; 7 | import { 8 | CreateReportService, 9 | DeleteReportService, 10 | FindAllReportsService, 11 | FindReportByIdService, 12 | UpdateReportService, 13 | } from './services'; 14 | import { ReportsEntity } from 'src/database/entities/reports.entity'; 15 | import { UsersEntity } from 'src/database/entities/users.entity'; 16 | import { JobsEntity } from 'src/database/entities/jobs.entity'; 17 | 18 | @Module({ 19 | imports: [ 20 | TypeOrmModule.forFeature([ReportsEntity, UsersEntity, JobsEntity]), 21 | ], 22 | controllers: [ReportsController], 23 | providers: [ 24 | JobRepository, 25 | UserRepository, 26 | ReportRepository, 27 | CreateReportService, 28 | FindAllReportsService, 29 | FindReportByIdService, 30 | DeleteReportService, 31 | UpdateReportService, 32 | ], 33 | }) 34 | export class ReportsModule {} 35 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/user/get-user-adm.swagger.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, HttpStatus } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger'; 3 | import { GetByParamsDto } from 'src/modules/user/dtos/get-by-params.dto'; 4 | import { BadRequestSwagger } from '../../bad-request.swagger'; 5 | import { UnauthorizedSwagger } from '../../unauthorized.swagger'; 6 | import { CreateResponseSwagger } from './classes/create-response.swagger'; 7 | 8 | export function SwaggerGetUserAdm() { 9 | return applyDecorators( 10 | ApiResponse({ 11 | status: HttpStatus.OK, 12 | description: 'Exemplo do retorno de sucesso da rota', 13 | type: CreateResponseSwagger, 14 | }), 15 | ApiResponse({ 16 | status: HttpStatus.UNPROCESSABLE_ENTITY, 17 | description: 'Modelo de erro', 18 | type: UnauthorizedSwagger, 19 | }), 20 | ApiResponse({ 21 | status: HttpStatus.BAD_REQUEST, 22 | description: 'Modelo de erro', 23 | type: BadRequestSwagger, 24 | }), 25 | ApiOperation({ 26 | summary: 'Visualizar um usuário pelo ID (precisa ser adm)', 27 | }), 28 | ApiParam({ 29 | type: GetByParamsDto, 30 | name: '', 31 | }), 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/user/update-user.swagger.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, HttpStatus } from '@nestjs/common'; 2 | import { ApiOperation, ApiResponse, ApiBody } from '@nestjs/swagger'; 3 | import { BadRequestSwagger } from '../../bad-request.swagger'; 4 | import { UnauthorizedSwagger } from '../../unauthorized.swagger'; 5 | 6 | export function SwaggerUpdateUser() { 7 | return applyDecorators( 8 | ApiBody({ 9 | description: 'Upload images', 10 | schema: { 11 | type: 'object', 12 | properties: { 13 | file: { 14 | type: 'string', 15 | format: 'binary', 16 | }, 17 | }, 18 | }, 19 | }), 20 | ApiResponse({ 21 | status: HttpStatus.OK, 22 | description: 'Exemplo do retorno de sucesso da rota', 23 | type: BadRequestSwagger, 24 | }), 25 | ApiResponse({ 26 | status: HttpStatus.UNAUTHORIZED, 27 | description: 'Modelo de erro', 28 | type: UnauthorizedSwagger, 29 | }), 30 | ApiResponse({ 31 | status: HttpStatus.BAD_REQUEST, 32 | description: 'Modelo de erro', 33 | type: BadRequestSwagger, 34 | }), 35 | ApiOperation({ 36 | summary: 'Atualizar um usuário pelo ID', 37 | }), 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/modules/savedjobs/savedjobs.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | 4 | import { SavedJobsService } from './services/savedjobs.service'; 5 | import { FindAllSavedJobsService } from './services/find-all-savedjobs.service'; 6 | import { SavedJobsRepository } from './repository/savedjobs.repository'; 7 | import { SavedJobsCleanerService } from './services/savedjobs-cleaner.service'; 8 | import { DeleteSavedJobsService } from './services/delete-saved-jobs.service'; 9 | import { SavedJobsEntity } from 'src/database/entities/savedjobs.entity'; 10 | import { UsersEntity } from 'src/database/entities/users.entity'; 11 | import { JobsEntity } from 'src/database/entities/jobs.entity'; 12 | 13 | @Module({ 14 | imports: [ 15 | TypeOrmModule.forFeature([SavedJobsEntity, UsersEntity, JobsEntity]), 16 | ], 17 | providers: [ 18 | SavedJobsService, 19 | FindAllSavedJobsService, 20 | SavedJobsRepository, 21 | SavedJobsCleanerService, 22 | DeleteSavedJobsService, 23 | ], 24 | exports: [ 25 | SavedJobsService, 26 | FindAllSavedJobsService, 27 | SavedJobsRepository, 28 | SavedJobsCleanerService, 29 | DeleteSavedJobsService, 30 | ], 31 | }) 32 | export class SavedJobsModule {} 33 | -------------------------------------------------------------------------------- /test/mocks/user/user.mock.ts: -------------------------------------------------------------------------------- 1 | import { UserRole } from '../../../src/shared/utils/userRole/userRole'; 2 | 3 | export const userMock = () => { 4 | return { 5 | id: '729c7919-583c-40a5-b0ca-137e282345d4', 6 | name: 'Non-Admin for tests', 7 | email: 'user@teste.com', 8 | cpf: '12345678910', 9 | policies: true, 10 | created_at: '2023-02-21T00:25:07.000Z', 11 | updated_at: '2023-02-21T00:25:07.000Z', 12 | }; 13 | }; 14 | 15 | export const userUpdateRecoveryMock = () => { 16 | return { 17 | id: '729c7919-583c-40a5-b0ca-137e282345d4', 18 | name: 'Non-Admin for tests', 19 | email: 'user@teste.com', 20 | cpf: '12345678910', 21 | policies: true, 22 | created_at: '2023-02-21T00:25:07.000Z', 23 | updated_at: '2023-02-21T00:25:07.000Z', 24 | recoverPasswordToken: '729c7919-583c-40a5-b0ca-137e282345d4', 25 | }; 26 | }; 27 | 28 | export const userEntityMock = () => { 29 | return { 30 | id: '729c7919-583c-40a5-b0ca-137e282345d4', 31 | name: 'Non-Admin for tests', 32 | email: 'user@teste.com', 33 | cpf: '12345678910', 34 | personal_data: null, 35 | policies: true, 36 | password: '1234', 37 | type: UserRole.USER, 38 | created_at: new Date(), 39 | updated_at: new Date(), 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /src/modules/curriculum/repository/curriculum-repository.ts: -------------------------------------------------------------------------------- 1 | import { Repository } from 'typeorm'; 2 | import { CurriculumEntity } from '../../../database/entities/curriculum.entity'; 3 | import { handleError } from '../../../shared/utils/handle-error.util'; 4 | import { CreateCurriculumType } from '../types/create-curriculum.type'; 5 | import { Injectable } from '@nestjs/common'; 6 | import { InjectRepository } from '@nestjs/typeorm'; 7 | 8 | @Injectable() 9 | export class CurriculumRepository { 10 | constructor(@InjectRepository(CurriculumEntity) private curriculumRepository: Repository) {} 11 | 12 | async saveCurriculum(data: CreateCurriculumType): Promise { 13 | 14 | return this.curriculumRepository.save(data).catch(handleError); 15 | } 16 | 17 | async findOneByUserId(userId: string): Promise { 18 | return this.curriculumRepository.findOneBy({user_id: userId}).catch(handleError); 19 | } 20 | 21 | async findAllCurriculum(id: string): Promise { 22 | return this.curriculumRepository.find({ select: { user_id: true } }).catch(handleError); 23 | } 24 | 25 | async deleteByKey(key: string): Promise { 26 | await this.curriculumRepository.softDelete({ fileKey: key }).catch(handleError); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/database/entities/certifications.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | CreateDateColumn, 4 | Entity, 5 | JoinColumn, 6 | ManyToOne, 7 | PrimaryGeneratedColumn, 8 | UpdateDateColumn, 9 | } from 'typeorm'; 10 | import { PersonalDataEntity } from './personal-data.entity'; 11 | 12 | @Entity('tb_certifications') 13 | export class CertificationsEntity { 14 | @PrimaryGeneratedColumn('uuid') 15 | id: string; 16 | 17 | @Column() 18 | name: string; 19 | 20 | @Column() 21 | institution: string; 22 | 23 | @Column() 24 | description: string; 25 | 26 | @ManyToOne(() => PersonalDataEntity) 27 | @JoinColumn({ name: 'personal_data_id' }) 28 | personal_data: PersonalDataEntity; 29 | 30 | @Column() 31 | personal_data_id: string; 32 | 33 | @CreateDateColumn() 34 | created_at: Date; 35 | 36 | @UpdateDateColumn({ update: true }) 37 | updated_at: Date; 38 | 39 | constructor(certification?: Partial) { 40 | this.id = certification?.id; 41 | this.name = certification?.name; 42 | this.institution = certification?.institution; 43 | this.description = certification?.description; 44 | this.personal_data_id = certification?.personal_data_id; 45 | this.created_at = certification?.created_at; 46 | this.updated_at = certification?.updated_at; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/modules/jobs/services/create-job.service.ts: -------------------------------------------------------------------------------- 1 | import { BadRequestException, Injectable } from '@nestjs/common'; 2 | import { CompaniesEntity } from '../../../database/entities/companies.entity'; 3 | import { MailService } from '../../mails/mail.service'; 4 | import { CreateJobDto } from '../dtos/create-job.dto'; 5 | import { JobRepository } from '../repository/job.repository'; 6 | 7 | @Injectable() 8 | export class CreateJobService { 9 | constructor( 10 | private jobRepository: JobRepository, 11 | private mailService: MailService, 12 | ) {} 13 | 14 | async execute(data: CreateJobDto, company: CompaniesEntity) { 15 | const { salaryMin, salaryMax } = data; 16 | 17 | if (salaryMin > salaryMax) { 18 | throw new BadRequestException( 19 | 'Informe um valor final maior do que o valor inicial descrito', 20 | ); 21 | } 22 | 23 | data.company_id = company.id; 24 | await this.jobRepository.createNewJob(data); 25 | 26 | const options = { 27 | subject: 'Vaga criada com sucesso', 28 | template: './create', 29 | context: { 30 | name: company.companyName, 31 | url: '', 32 | }, 33 | email: company.email, 34 | }; 35 | 36 | await this.mailService.sendMail(options); 37 | 38 | return 'Vaga publicada com sucesso'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/modules/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, Post, Res, UseGuards } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; 4 | import { Response } from 'express'; 5 | import { LoginSwagger } from 'src/shared/Swagger/decorators/auth/login.swagger'; 6 | import { UserLoggedSwagger } from 'src/shared/Swagger/decorators/auth/user-logged.swagger'; 7 | import { UsersEntity } from '../../database/entities/users.entity'; 8 | import { LoggedUser } from './decorator/logged-user.decorator'; 9 | import { UserLoginDto } from './dtos/user-login.dto'; 10 | import { AuthLoginService } from './services/auth-login.service'; 11 | 12 | @Controller('auth') 13 | @ApiTags('Auth') 14 | export class AuthController { 15 | constructor(private authLoginService: AuthLoginService) {} 16 | 17 | @Post('/login') 18 | @LoginSwagger() 19 | async login(@Body() loginData: UserLoginDto, @Res() res: Response) { 20 | const { status, data } = await this.authLoginService.execute(loginData); 21 | 22 | return res.status(status).send(data); 23 | } 24 | 25 | @Get('/user-logged') 26 | @UseGuards(AuthGuard('jwt')) 27 | @UserLoggedSwagger() 28 | @ApiBearerAuth() 29 | async userLogged(@LoggedUser() user: UsersEntity) { 30 | return user; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/mocks/user/get-all-user.mock.ts: -------------------------------------------------------------------------------- 1 | export const getAllUserMock = () => { 2 | return { 3 | data: [ 4 | { 5 | id: '729c7919-583c-40a5-b0ca-137e282345d4', 6 | name: 'Non-Admin for tests2.0', 7 | email: 'user@teste.com', 8 | cpf: '12345678910', 9 | created_at: '2023-02-21T00:25:07.000Z', 10 | }, 11 | { 12 | id: '729c7919-583c-40a5-b0ca-137e282345d4', 13 | name: 'Non-Admin for tests2.0', 14 | email: 'user@teste.com', 15 | cpf: '12345678910', 16 | created_at: '2023-02-21T00:25:07.000Z', 17 | }, 18 | { 19 | id: '729c7919-583c-40a5-b0ca-137e282345d4', 20 | name: 'Non-Admin for tests2.0', 21 | email: 'user@teste.com', 22 | cpf: '12345678910', 23 | created_at: '2023-02-21T00:25:07.000Z', 24 | }, 25 | { 26 | id: '729c7919-583c-40a5-b0ca-137e282345d4', 27 | name: 'Non-Admin for tests2.0', 28 | email: 'user@teste.com', 29 | cpf: '12345678910', 30 | created_at: '2023-02-21T00:25:07.000Z', 31 | }, 32 | ], 33 | meta: { 34 | orderByColumn: 'name', 35 | page: '1', 36 | take: '5', 37 | itemCount: 1, 38 | pageCount: 1, 39 | hasPreviousPage: false, 40 | hasNextPage: false, 41 | }, 42 | }; 43 | }; 44 | -------------------------------------------------------------------------------- /src/modules/user/dtos/update-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { ApiProperty } from '@nestjs/swagger'; 3 | import { Transform } from 'class-transformer'; 4 | import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; 5 | import { CreateUserDto } from './create-user.dto'; 6 | 7 | export class UpdateUserDto extends PartialType(CreateUserDto) { 8 | @ApiProperty({ required: true, example: '11111111111' }) 9 | @Transform(({ value }) => value.replace(/\D/g, '')) 10 | @IsNotEmpty() 11 | @IsString() 12 | mainPhone: string; 13 | 14 | @ApiProperty({ required: false, example: '11111111111' }) 15 | @Transform(({ value }) => value.replace(/\D/g, '')) 16 | @IsOptional() 17 | @IsString() 18 | phone: string; 19 | 20 | @ApiProperty({ required: true, example: 'Rio Branco' }) 21 | @IsNotEmpty() 22 | @IsString() 23 | city: string; 24 | 25 | @ApiProperty({ required: true, example: 'Acre' }) 26 | @IsNotEmpty() 27 | @IsString() 28 | state: string; 29 | 30 | @IsOptional() 31 | @IsString() 32 | @ApiProperty({ 33 | description: 'Imagem do perfil', 34 | }) 35 | profile?: string; 36 | 37 | @IsOptional() 38 | @IsString() 39 | @ApiProperty({ 40 | description: 'Chave para remoção da imagem do perfil', 41 | }) 42 | profileKey?: string; 43 | 44 | @IsOptional() 45 | file: any; 46 | } 47 | -------------------------------------------------------------------------------- /src/modules/company/services/update-company.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CompaniesEntity } from '../../../database/entities/companies.entity'; 3 | import { FileUploadService } from '../../upload/upload.service'; 4 | import { UpdateCompanyDto } from '../dtos/update-company.dto'; 5 | import { CompanyRepository } from '../repository/company-repository'; 6 | 7 | @Injectable() 8 | export class UpdateCompanyService { 9 | constructor( 10 | private companyRepository: CompanyRepository, 11 | private fileUploadService: FileUploadService, 12 | ) {} 13 | 14 | async execute(company: CompaniesEntity, data: UpdateCompanyDto, file) { 15 | if (file && !data.profileKey) { 16 | return { 17 | status: 400, 18 | data: { 19 | message: 'profileKey is required when file is send', 20 | }, 21 | }; 22 | } 23 | 24 | if (file) { 25 | await this.fileUploadService.deleteFile(data.profileKey); 26 | const { Location, key } = await this.fileUploadService.upload(file); 27 | data.profile = Location; 28 | data.profileKey = key; 29 | } 30 | 31 | delete data.file; 32 | 33 | await this.companyRepository.UpdateCompanyById(company.id, data); 34 | 35 | return { 36 | status: 200, 37 | data: { 38 | message: 'Company updated successfully', 39 | }, 40 | }; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPipe } from '@nestjs/common'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import { NestExpressApplication } from '@nestjs/platform-express'; 4 | import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; 5 | import { AppModule } from './app.module'; 6 | 7 | async function bootstrap() { 8 | const app = await NestFactory.create(AppModule, { 9 | cors: true, 10 | }); 11 | 12 | app.useGlobalPipes( 13 | new ValidationPipe({ 14 | whitelist: true, 15 | transform: true, 16 | }), 17 | ); 18 | 19 | const config = new DocumentBuilder() 20 | .setTitle('Vagas-Backend') 21 | .setDescription('App for Vagas-Backend.') 22 | .setVersion('1.1.1') 23 | .addBearerAuth() 24 | .addTag('Upload') 25 | .addTag('Status') 26 | .addTag('Auth') 27 | .addTag('User') 28 | .addTag('Company') 29 | .build(); 30 | 31 | const document = SwaggerModule.createDocument(app, config); 32 | 33 | if (process.env.NODE_ENV == 'development') { 34 | SwaggerModule.setup('api', app, document); 35 | console.info( 36 | `Documentation running on http://localhost:${process.env.PORT || 3000}/api 🚀🚀`, 37 | ); 38 | } 39 | 40 | await app.listen(process.env.PORT || 3000, () => { 41 | console.info(`🚀🚀 App listening on port ${process.env.PORT || 3000} 🚀🚀`); 42 | }); 43 | } 44 | bootstrap(); 45 | -------------------------------------------------------------------------------- /src/modules/user/services/update-user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import * as bcrypt from 'bcrypt'; 3 | import { UsersEntity } from '../../../database/entities/users.entity'; 4 | import { FileUploadService } from '../../upload/upload.service'; 5 | import { UpdateUserDto } from '../dtos/update-user.dto'; 6 | import { UserRepository } from '../repository/user.repository'; 7 | 8 | @Injectable() 9 | export class UpdateUserService { 10 | constructor( 11 | private userRepository: UserRepository, 12 | private fileUploadService: FileUploadService, 13 | ) {} 14 | 15 | async execute(user: UsersEntity, data: UpdateUserDto, file) { 16 | if (file && !data.profileKey) { 17 | return { 18 | status: 400, 19 | data: { 20 | message: 'profileKey is required when file is send', 21 | }, 22 | }; 23 | } 24 | 25 | if (file) { 26 | await this.fileUploadService.deleteFile(data?.profileKey); 27 | const { Location, key } = await this.fileUploadService.upload(file); 28 | data.profile = Location; 29 | data.profileKey = key; 30 | } 31 | 32 | delete data?.file; 33 | 34 | if (data?.password) { 35 | data.password = await bcrypt.hash(data?.password, 10); 36 | } 37 | 38 | await this.userRepository.updateUser(user, data); 39 | 40 | return { message: 'User updated successfully' }; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/modules/mails/mail.module.ts: -------------------------------------------------------------------------------- 1 | import { MailerModule } from '@nestjs-modules/mailer'; 2 | import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter'; 3 | import { Global, Module } from '@nestjs/common'; 4 | import { ConfigService } from '@nestjs/config'; 5 | import { join } from 'path'; 6 | import { MailService } from './mail.service'; 7 | 8 | @Global() 9 | @Module({ 10 | imports: [ 11 | MailerModule.forRootAsync({ 12 | useFactory: async (config: ConfigService) => ({ 13 | transport: { 14 | host: config.get('MAIL_HOST'), 15 | port: config.get('MAIL_PORT'), 16 | // secure: true, 17 | // secure: false, 18 | auth: { 19 | user: config.get('MAIL_USER'), 20 | pass: config.get('MAIL_PASSWORD'), 21 | }, 22 | // tls: { 23 | // rejectUnauthorized: false, 24 | // }, 25 | }, 26 | defaults: { 27 | from: `no-reply `, 28 | }, 29 | template: { 30 | dir: join(__dirname, 'templates'), 31 | adapter: new HandlebarsAdapter(), 32 | options: { 33 | strict: true, 34 | }, 35 | }, 36 | }), 37 | inject: [ConfigService], 38 | }), 39 | ], 40 | providers: [MailService], 41 | exports: [MailService], 42 | }) 43 | export class MailModule {} 44 | -------------------------------------------------------------------------------- /src/modules/auth/jtw/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { ExtractJwt, Strategy } from 'passport-jwt'; 4 | import { UserRepository } from 'src/modules/user/repository/user.repository'; 5 | import { handleError } from '../../../shared/utils/handle-error.util'; 6 | import { CompanyRepository } from './../../company/repository/company-repository'; 7 | 8 | @Injectable() 9 | export class JwtStrategy extends PassportStrategy(Strategy) { 10 | constructor( 11 | private readonly userRepository: UserRepository, 12 | private readonly companyRepository: CompanyRepository, 13 | ) { 14 | super({ 15 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 16 | ignoreExpiration: false, 17 | secretOrKey: process.env.SECRET_KEY, 18 | }); 19 | } 20 | 21 | async validate(payload: { email: string }) { 22 | let user: any = {}; 23 | 24 | user = await this.userRepository 25 | .findOneByEmail(payload.email) 26 | .catch(handleError); 27 | 28 | if (!user) { 29 | user = await this.companyRepository 30 | .findOneByEmail(payload.email) 31 | .catch(handleError); 32 | } 33 | 34 | if (!user) { 35 | throw new UnauthorizedException('User not found or not authorized!'); 36 | } 37 | 38 | if (user) { 39 | delete user.password; 40 | return user; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/modules/user/services/update-password-by-email.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import * as bcrypt from 'bcrypt'; 3 | import { MailService } from 'src/modules/mails/mail.service'; 4 | import { CreatePasswordHashDto } from '../dtos/update-my-password.dto'; 5 | import { UserRepository } from '../repository/user.repository'; 6 | 7 | @Injectable() 8 | export class UpdatePasswordByEmailService { 9 | constructor( 10 | private userRepository: UserRepository, 11 | private mailService: MailService, 12 | ) {} 13 | 14 | async execute({ 15 | recoverPasswordToken, 16 | password, 17 | confirmPassword, 18 | }: CreatePasswordHashDto) { 19 | const user = await this.userRepository.findByToken(recoverPasswordToken); 20 | 21 | if (!user) { 22 | return { 23 | status: 400, 24 | data: { message: 'Usuário não encontrado!' }, 25 | }; 26 | } 27 | 28 | if (password != confirmPassword) { 29 | return { 30 | status: 400, 31 | data: { message: 'As senhas não conferem!' }, 32 | }; 33 | } 34 | const passwordHash = await bcrypt.hash(password, 10); 35 | 36 | const userUpdated = await this.userRepository.updatePassword( 37 | user.id, 38 | passwordHash, 39 | ); 40 | 41 | await this.mailService.sendUserConfirmation(userUpdated); 42 | 43 | return { 44 | status: 200, 45 | data: { message: 'Senha redefinida com sucesso!' }, 46 | }; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/modules/user/services/recovery-password-by-email.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import * as crypto from 'crypto'; 3 | import { MailService } from '../../mails/mail.service'; 4 | import { UserRepository } from '../repository/user.repository'; 5 | 6 | @Injectable() 7 | export class RecoveryPasswordByEmail { 8 | constructor( 9 | private userRepository: UserRepository, 10 | private mailService: MailService, 11 | ) {} 12 | 13 | async execute(email: string) { 14 | const userExists = await this.userRepository.findOneByEmail(email); 15 | if (!userExists) { 16 | return { 17 | status: 200, 18 | data: { 19 | message: 20 | 'Caso esse e-mail esteja cadastrado no sistema, será encaminhado para ele uma mensagem de orientação sobre os próximos passos para a redefinição da senha.', 21 | }, 22 | }; 23 | } 24 | 25 | const recoverPasswordToken = crypto.randomBytes(32).toString('hex'); 26 | 27 | const userUpdated = await this.userRepository.updateRecoveryPassword( 28 | userExists.id, 29 | recoverPasswordToken, 30 | ); 31 | 32 | await this.mailService.sendUserConfirmation(userUpdated); 33 | 34 | return { 35 | status: 200, 36 | data: { 37 | message: 38 | 'Caso esse e-mail esteja cadastrado no sistema, será encaminhado para ele uma mensagem de orientação sobre os próximos passos para a redefinição da senha.', 39 | }, 40 | }; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/modules/reports/services/create-report.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, BadRequestException } from '@nestjs/common'; 2 | import { JobRepository } from '../../jobs/repository/job.repository'; 3 | import { UserRepository } from '../../../modules/user/repository/user.repository'; 4 | import { CreateReportDto } from '../dtos/create-report.dto'; 5 | import { ReportRepository } from '../repository/reports.repository'; 6 | 7 | @Injectable() 8 | export class CreateReportService { 9 | constructor( 10 | private reportRepository: ReportRepository, 11 | private userRepository: UserRepository, 12 | private jobRepository: JobRepository, 13 | ) {} 14 | 15 | async execute({ user_id, job_id, description }: CreateReportDto) { 16 | const userExists = await this.userRepository.findOneById(user_id); 17 | 18 | if (!userExists) { 19 | throw new BadRequestException('User not found'); 20 | } 21 | 22 | const jobExists = await this.jobRepository.findOneById(job_id); 23 | 24 | if (!jobExists) { 25 | throw new BadRequestException('Job not found'); 26 | } 27 | 28 | const reportExists = await this.reportRepository.findByParams({ 29 | user_id, 30 | job_id, 31 | }); 32 | 33 | if (reportExists) { 34 | throw new BadRequestException('You have already reported this job'); 35 | } 36 | 37 | const response = await this.reportRepository.createReport({ 38 | user_id, 39 | job_id, 40 | description, 41 | }); 42 | 43 | return response; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/user/classes/list-response.swagger.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | export class CreateUsersSwagger { 4 | @ApiProperty({ 5 | example: '941fb31b-5799-44bc-9870-d7c1d5d2ec2c', 6 | }) 7 | id: string; 8 | 9 | @ApiProperty({ 10 | example: 'Fulano de Tal', 11 | }) 12 | name: string; 13 | 14 | @ApiProperty({ 15 | example: 'johnsnow+2356@outlook.com', 16 | }) 17 | email: string; 18 | 19 | @ApiProperty({ 20 | example: '2023-04-06T01:48:41.314Z', 21 | }) 22 | created_at: Date; 23 | } 24 | 25 | export class MetaPaginationSwagger { 26 | @ApiProperty({ 27 | example: 'id', 28 | }) 29 | orderByColumn: string; 30 | 31 | @ApiProperty({ 32 | example: '10', 33 | }) 34 | page: string; 35 | 36 | @ApiProperty({ 37 | example: '10', 38 | }) 39 | take: string; 40 | 41 | @ApiProperty({ 42 | example: 22, 43 | }) 44 | itemCount: number; 45 | 46 | @ApiProperty({ 47 | example: 3, 48 | }) 49 | pageCount: number; 50 | 51 | @ApiProperty({ 52 | example: true, 53 | }) 54 | hasPreviousPage: boolean; 55 | 56 | @ApiProperty({ 57 | example: true, 58 | }) 59 | hasNextPage: boolean; 60 | } 61 | 62 | export class ListResponseSwagger { 63 | @ApiProperty({ 64 | isArray: true, 65 | type: CreateUsersSwagger, 66 | }) 67 | data: CreateUsersSwagger[]; 68 | 69 | @ApiProperty({ 70 | isArray: false, 71 | type: MetaPaginationSwagger, 72 | }) 73 | meta: MetaPaginationSwagger; 74 | } 75 | -------------------------------------------------------------------------------- /src/modules/company/services/update-password-by-email.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import * as bcrypt from 'bcrypt'; 3 | import { MailService } from 'src/modules/mails/mail.service'; 4 | import { CreatePasswordHashDto } from '../dtos/update-my-password.dto'; 5 | import { CompanyRepository } from '../repository/company-repository'; 6 | 7 | @Injectable() 8 | export class UpdatePasswordByEmailService { 9 | constructor( 10 | private companyRepository: CompanyRepository, 11 | private mailService: MailService, 12 | ) {} 13 | 14 | async execute({ 15 | recoverPasswordToken, 16 | password, 17 | confirmPassword, 18 | }: CreatePasswordHashDto) { 19 | const company = await this.companyRepository.findByToken( 20 | recoverPasswordToken, 21 | ); 22 | 23 | if (!company) { 24 | return { 25 | status: 400, 26 | data: { message: 'Empresa não encontrada!' }, 27 | }; 28 | } 29 | 30 | if (password != confirmPassword) { 31 | return { 32 | status: 400, 33 | data: { message: 'As senhas não conferem!' }, 34 | }; 35 | } 36 | const passwordHash = await bcrypt.hash(password, 10); 37 | 38 | const companyUpdated = await this.companyRepository.updatePassword( 39 | company.id, 40 | passwordHash, 41 | ); 42 | 43 | await this.mailService.sendCompanyConfirmation(companyUpdated); 44 | 45 | return { 46 | status: 200, 47 | data: { message: 'Senha redefinida com sucesso!' }, 48 | }; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/modules/company/services/recovery-password-by-email.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import * as crypto from 'crypto'; 3 | import { MailService } from '../../mails/mail.service'; 4 | import { CompanyRepository } from '../repository/company-repository'; 5 | 6 | @Injectable() 7 | export class RecoveryCompanyPasswordByEmail { 8 | constructor( 9 | private companyRepository: CompanyRepository, 10 | private mailService: MailService, 11 | ) {} 12 | 13 | async execute(email: string) { 14 | const companyExists = await this.companyRepository.findOneByEmail(email); 15 | if (!companyExists) { 16 | return { 17 | status: 200, 18 | data: { 19 | message: 20 | 'Caso esse e-mail esteja cadastrado no sistema, será encaminhado para ele uma mensagem de orientação sobre os próximos passos para a redefinição da senha.', 21 | }, 22 | }; 23 | } 24 | 25 | const recoverPasswordToken = crypto.randomBytes(32).toString('hex'); 26 | 27 | const { id } = companyExists; 28 | 29 | const companyUpdated = await this.companyRepository.updateRecoveryPassword( 30 | id, 31 | recoverPasswordToken, 32 | ); 33 | 34 | await this.mailService.sendCompanyConfirmation(companyUpdated); 35 | 36 | return { 37 | status: 200, 38 | data: { 39 | message: 40 | 'Caso esse e-mail esteja cadastrado no sistema, será encaminhado para ele uma mensagem de orientação sobre os próximos passos para a redefinição da senha.', 41 | }, 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/shared/Swagger/decorators/savedjobs/create-savedjobs.swagger.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, HttpStatus } from "@nestjs/common"; 2 | import { ApiBearerAuth, ApiOperation, ApiResponse } from "@nestjs/swagger"; 3 | import { NotFoundSwagger } from "../../not-found.swagger"; 4 | import { BadRequestSwagger } from "../../bad-request.swagger"; 5 | import { SavedJobsEntity } from "src/database/entities/savedjobs.entity"; 6 | import { ConflictSwagger } from "../../conflict.swagger"; 7 | 8 | export function SwaggerCreateSavedJobs() { 9 | return applyDecorators( 10 | ApiBearerAuth(), 11 | ApiOperation({ 12 | summary: 'Salvar uma vaga', 13 | description: 'Cria um novo registro de vaga salva para o usuário autenticado. Requer dados obrigatórios no corpo da requisição.', 14 | }), 15 | ApiResponse({ 16 | status: HttpStatus.CREATED, 17 | description: 'Vaga salva com sucesso. Retorna os dados do registro criado.', 18 | type: SavedJobsEntity, 19 | }), 20 | ApiResponse({ 21 | status: HttpStatus.BAD_REQUEST, 22 | description: 'Requisição inválida. Verifique os campos enviados no corpo da requisição.', 23 | type: BadRequestSwagger, 24 | }), 25 | ApiResponse({ 26 | status: HttpStatus.CONFLICT, 27 | description: 'Esta vaga já foi salva anteriormente pelo usuário.', 28 | type: ConflictSwagger, 29 | }), 30 | ApiResponse({ 31 | status: HttpStatus.NOT_FOUND, 32 | description: 'Usuário ou vaga não encontrados.', 33 | type: NotFoundSwagger, 34 | }), 35 | ); 36 | } -------------------------------------------------------------------------------- /src/modules/company/company.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { UploadModule } from '../upload/upload.module'; 4 | import { UserRepository } from '../user/repository/user.repository'; 5 | import { CompanyController } from './company.controller'; 6 | import { CompanyRepository } from './repository/company-repository'; 7 | import { 8 | CreateCompanyService, 9 | DeleteCompanyService, 10 | FindAllCompanyService, 11 | UpdateCompanyService, 12 | } from './services'; 13 | import { ActivateCompanyService } from './services/activate-company.service'; 14 | import { RecoveryCompanyPasswordByEmail } from './services/recovery-password-by-email.service'; 15 | import { UpdatePasswordByEmailService } from './services/update-password-by-email.service'; 16 | import { UpdateCompanyPassword } from './services/update-password.service'; 17 | import { CompaniesEntity } from 'src/database/entities/companies.entity'; 18 | import { UsersEntity } from 'src/database/entities/users.entity'; 19 | 20 | @Module({ 21 | imports: [ 22 | UploadModule, 23 | TypeOrmModule.forFeature([CompaniesEntity, UsersEntity]), 24 | ], 25 | controllers: [CompanyController], 26 | providers: [ 27 | CompanyRepository, 28 | UserRepository, 29 | CreateCompanyService, 30 | FindAllCompanyService, 31 | UpdateCompanyService, 32 | DeleteCompanyService, 33 | RecoveryCompanyPasswordByEmail, 34 | UpdatePasswordByEmailService, 35 | UpdateCompanyPassword, 36 | ActivateCompanyService, 37 | ], 38 | }) 39 | export class CompanyModule {} 40 | -------------------------------------------------------------------------------- /src/modules/jobs/jobs.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { PassportModule } from '@nestjs/passport'; 3 | import { TypeOrmModule } from '@nestjs/typeorm'; 4 | import { CompanyModule } from '../company/company.module'; 5 | import { CompanyRepository } from '../company/repository/company-repository'; 6 | import { MailModule } from '../mails/mail.module'; 7 | import { JobsController } from './jobs.controller'; 8 | import { JobRepository } from './repository/job.repository'; 9 | import { 10 | CreateJobService, 11 | GetAllJobsService, 12 | GetOneJobByIdService, 13 | UpdateJobService, 14 | } from './services'; 15 | import { SearchJobsService } from './services/search-job.service'; 16 | import { GetAllJobsFromLoggedCompanyService } from './services/get-all-jobs-from-logged-company.service'; 17 | import { JobsEntity } from 'src/database/entities/jobs.entity'; 18 | import { CompaniesEntity } from 'src/database/entities/companies.entity'; 19 | import { DeleteJobService } from './services/delete-job.service'; 20 | 21 | @Module({ 22 | imports: [ 23 | MailModule, 24 | CompanyModule, 25 | TypeOrmModule.forFeature([JobRepository, JobsEntity, CompaniesEntity]), 26 | PassportModule.register({ defaultStrategy: 'jwt' }), 27 | ], 28 | controllers: [JobsController], 29 | providers: [ 30 | CreateJobService, 31 | GetAllJobsService, 32 | GetAllJobsFromLoggedCompanyService, 33 | GetOneJobByIdService, 34 | UpdateJobService, 35 | DeleteJobService, 36 | SearchJobsService, 37 | JobRepository, 38 | CompanyRepository 39 | ], 40 | }) 41 | export class JobsModule {} 42 | -------------------------------------------------------------------------------- /src/modules/user/dtos/create-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { 3 | IsEmail, 4 | IsEnum, 5 | IsNotEmpty, 6 | IsOptional, 7 | IsString, 8 | Length, 9 | Matches, 10 | MaxLength, 11 | Validate, 12 | } from 'class-validator'; 13 | import { UserRole } from '../../../shared/utils/userRole/userRole'; 14 | import { Match } from '../decorators/match.decorator'; 15 | 16 | export class CreateUserDto { 17 | @IsNotEmpty() 18 | @MaxLength(50) 19 | @IsString() 20 | @ApiProperty({ 21 | description: 'Nome do usuário.', 22 | example: 'Amaro Francisco', 23 | }) 24 | name: string; 25 | 26 | @IsNotEmpty() 27 | @IsEmail() 28 | @IsString() 29 | @ApiProperty({ 30 | description: 'E-mail do usuário.', 31 | example: 'johnsnow@outlook.com', 32 | }) 33 | email: string; 34 | 35 | @IsNotEmpty() 36 | @IsString() 37 | @Matches( 38 | /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*\W)[a-zA-Z\d\W]{8,}$/, 39 | { 40 | message: 41 | 'Senha inválida. Deve conter pelo menos 8 caracteres, uma letra maiúscula, uma letra minúscula, um número e um caractere especial.', 42 | }, 43 | ) 44 | @ApiProperty({ 45 | description: 'Senha de Login', 46 | example: 'Abcd@1234', 47 | }) 48 | password: string; 49 | 50 | @IsNotEmpty() 51 | @IsString() 52 | @ApiProperty({ 53 | description: 'Confirmação de senha', 54 | example: 'Abcd@1234', 55 | }) 56 | @Match('password', { 57 | message: 'The password does not match with the password confirmation', 58 | }) 59 | confirmPassword: string; 60 | 61 | @IsOptional() 62 | @IsEnum(UserRole) 63 | type: UserRole; 64 | } 65 | -------------------------------------------------------------------------------- /src/modules/comment/comment.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { APP_GUARD } from '@nestjs/core'; 3 | import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler'; 4 | import { TypeOrmModule } from '@nestjs/typeorm'; 5 | import { JobRepository } from '../jobs/repository/job.repository'; 6 | import { UserRepository } from '../user/repository/user.repository'; 7 | import { CommentController } from './comment.controller'; 8 | import { CommentRepository } from './repository/comment.repository'; 9 | import { 10 | CreateCommentService, 11 | DeleteCommentService, 12 | GetAllCommentsService, 13 | GetCommentByIdService, 14 | UpdateCommentService, 15 | } from './services'; 16 | import { CommentsEntity } from 'src/database/entities/comments.entity'; 17 | import { JobsEntity } from 'src/database/entities/jobs.entity'; 18 | import { UsersEntity } from 'src/database/entities/users.entity'; 19 | 20 | @Module({ 21 | imports: [ 22 | TypeOrmModule.forFeature([ 23 | CommentsEntity, 24 | JobsEntity, 25 | UsersEntity, 26 | ]), 27 | ThrottlerModule.forRoot({ 28 | throttlers: [ 29 | { 30 | ttl: 10000, 31 | limit: 4 32 | } 33 | ] 34 | }), 35 | ], 36 | controllers: [CommentController], 37 | providers: [ 38 | CommentRepository, 39 | JobRepository, 40 | UserRepository, 41 | CreateCommentService, 42 | GetAllCommentsService, 43 | GetCommentByIdService, 44 | UpdateCommentService, 45 | DeleteCommentService, 46 | { 47 | provide: APP_GUARD, 48 | useClass: ThrottlerGuard, 49 | }, 50 | ], 51 | }) 52 | export class CommentModule {} 53 | -------------------------------------------------------------------------------- /src/modules/company/services/update-password.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import * as bcrypt from 'bcrypt'; 3 | import { MailService } from 'src/modules/mails/mail.service'; 4 | import { UpdateMyPasswordDto } from '../dtos/update-my-password.dto'; 5 | import { CompanyRepository } from '../repository/company-repository'; 6 | import { CompaniesEntity } from 'src/database/entities/companies.entity'; 7 | 8 | @Injectable() 9 | export class UpdateCompanyPassword { 10 | constructor( 11 | private companyRepository: CompanyRepository, 12 | private mailService: MailService, 13 | ) {} 14 | 15 | async execute( 16 | company: CompaniesEntity, 17 | { oldPassword, password, confirmNewPassword }: UpdateMyPasswordDto, 18 | ) { 19 | const user = await this.companyRepository.findCompanyById(company.id); 20 | 21 | const isOldPassCorrect = await bcrypt.compare(oldPassword, user.password); 22 | 23 | if (!isOldPassCorrect) { 24 | return { 25 | status: 400, 26 | data: { message: 'A senha atual está incorreta.' }, 27 | }; 28 | } 29 | 30 | if (password != confirmNewPassword) { 31 | return { 32 | status: 400, 33 | data: { message: 'As senhas não conferem!' }, 34 | }; 35 | } 36 | const passwordHash = await bcrypt.hash(password, 10); 37 | 38 | const companyUpdated = await this.companyRepository.updatePassword( 39 | company.id, 40 | passwordHash, 41 | ); 42 | 43 | await this.mailService.sendCompanyConfirmation(companyUpdated); 44 | 45 | return { 46 | status: 200, 47 | data: { message: 'Senha redefinida com sucesso!' }, 48 | }; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/database/entities/work-experiences.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | CreateDateColumn, 4 | Entity, 5 | JoinColumn, 6 | ManyToOne, 7 | PrimaryGeneratedColumn, 8 | Timestamp, 9 | UpdateDateColumn, 10 | } from 'typeorm'; 11 | import { PersonalDataEntity } from './personal-data.entity'; 12 | 13 | @Entity('tb_work_experiences') 14 | export class WorkExperiencesEntity { 15 | @PrimaryGeneratedColumn('uuid') 16 | id: string; 17 | 18 | @Column() 19 | name: string; 20 | 21 | @Column() 22 | institution: string; 23 | 24 | @Column({ default: false }) 25 | actual_job: boolean; 26 | 27 | @Column() 28 | start_date: Date; 29 | 30 | @Column({ nullable: true }) 31 | end_date: Date; 32 | 33 | @Column({ nullable: true }) 34 | description: string; 35 | 36 | @ManyToOne(() => PersonalDataEntity) 37 | @JoinColumn({ name: 'personal_data_id' }) 38 | personal_data: PersonalDataEntity; 39 | 40 | @Column() 41 | personal_data_id: string; 42 | 43 | @CreateDateColumn() 44 | created_at: Date; 45 | 46 | @UpdateDateColumn({ update: true }) 47 | updated_at: Date; 48 | 49 | constructor(workExperience?: Partial) { 50 | this.id = workExperience?.id; 51 | this.name = workExperience?.name; 52 | this.institution = workExperience?.institution; 53 | this.actual_job = workExperience?.actual_job; 54 | this.start_date = workExperience?.start_date; 55 | this.end_date = workExperience?.end_date; 56 | this.description = workExperience?.description; 57 | this.personal_data_id = workExperience?.personal_data_id; 58 | this.created_at = workExperience?.created_at; 59 | this.updated_at = workExperience?.updated_at; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/modules/comment/repository/comment.repository.ts: -------------------------------------------------------------------------------- 1 | import { Repository } from 'typeorm'; 2 | import { CommentsEntity } from '../../../database/entities/comments.entity'; 3 | import { handleError } from '../../../shared/utils/handle-error.util'; 4 | import { UpdateCommentDto } from '../dtos/update-comment.dto'; 5 | import { CreateCommentDto } from './../dtos/create-comment.dto'; 6 | import { InjectRepository } from '@nestjs/typeorm'; 7 | import { Injectable } from '@nestjs/common'; 8 | 9 | @Injectable() 10 | export class CommentRepository { 11 | constructor(@InjectRepository(CommentsEntity) private commentsRepository: Repository) {} 12 | 13 | async createComment(data: CreateCommentDto): Promise { 14 | return this.commentsRepository.save(data).catch(handleError); 15 | } 16 | 17 | async getAllComments(): Promise { 18 | return this.commentsRepository.find({ where: { desativated_at: null}}).catch(handleError); 19 | } 20 | 21 | async getCommentById(id: string): Promise { 22 | return this.commentsRepository.findOneBy({id, desativated_at: null}).catch( 23 | handleError, 24 | ); 25 | } 26 | 27 | async updateComment(id: string, data: UpdateCommentDto) { 28 | const comment = await this.commentsRepository.findOneBy({id}).catch(handleError); 29 | 30 | return this.commentsRepository.save({ 31 | ...comment, 32 | ...data, 33 | }).catch(handleError); 34 | } 35 | 36 | async deleteComment(id: string): Promise { 37 | await this.commentsRepository.update(id, { desativated_at: new Date() }).catch(handleError); 38 | 39 | return { message: 'Comment deleted successfully' }; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/modules/auth/services/auth-login.service.ts: -------------------------------------------------------------------------------- 1 | import * as bcrypt from 'bcrypt'; 2 | import { CompanyRepository } from './../../company/repository/company-repository'; 3 | 4 | import { Injectable } from '@nestjs/common'; 5 | import { JwtService } from '@nestjs/jwt'; 6 | import { UserRepository } from '../../../modules/user/repository/user.repository'; 7 | import { UserLoginDto } from '../dtos/user-login.dto'; 8 | import { LoginTypeEnum } from '../enums/login-type.enum'; 9 | 10 | @Injectable() 11 | export class AuthLoginService { 12 | constructor( 13 | private userRepository: UserRepository, 14 | private companyRepository: CompanyRepository, 15 | private jwt: JwtService, 16 | ) {} 17 | 18 | async execute({ email, password, type }: UserLoginDto) { 19 | let info: any; 20 | 21 | if (type == LoginTypeEnum.COMPANY) { 22 | info = await this.companyRepository.findOneByEmail(email); 23 | } else { 24 | info = await this.userRepository.findOneByEmail(email); 25 | } 26 | 27 | if (!info?.mailConfirm || !info) { 28 | return { 29 | status: 400, 30 | data: { message: 'Email not validated' }, 31 | }; 32 | } 33 | 34 | const passwordIsValid = await bcrypt.compare(password, info.password); 35 | 36 | if (!passwordIsValid) { 37 | return { 38 | status: 400, 39 | data: { message: 'E-mail ou Senha não conferem' }, 40 | }; 41 | } 42 | 43 | delete info.password; 44 | delete info.recoverPasswordToken; 45 | delete info.mailconfirm; 46 | delete info?.ip; 47 | 48 | return { 49 | status: 200, 50 | data: { 51 | token: this.jwt.sign({ email }), 52 | info, 53 | }, 54 | }; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/modules/user/services/create-user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import * as bcrypt from 'bcrypt'; 3 | import { Request } from 'express'; 4 | 5 | import { MailService } from '../../../modules/mails/mail.service'; 6 | import { CreateUserDto } from '../dtos/create-user.dto'; 7 | import { UserRepository } from '../repository/user.repository'; 8 | import { CompanyRepository } from 'src/modules/company/repository/company-repository'; 9 | 10 | @Injectable() 11 | export class CreateUserService { 12 | constructor( 13 | private userRepository: UserRepository, 14 | private companyRepository: CompanyRepository, 15 | private mailService: MailService, 16 | ) {} 17 | 18 | async execute(data: CreateUserDto, req: Request) { 19 | const { email, password } = data; 20 | 21 | data['ip'] = req.ip; 22 | 23 | const emailAlreadyInUseCompany = await this.companyRepository.findOneByEmail( 24 | email, 25 | ); 26 | 27 | const emailAlreadyInUseUser = await this.userRepository.findOneByEmail( 28 | email, 29 | ); 30 | 31 | if (emailAlreadyInUseCompany || emailAlreadyInUseUser) { 32 | return { 33 | status: 404, 34 | data: { 35 | message: 'E-mail já cadastrado', 36 | }, 37 | }; 38 | } 39 | 40 | data.password = await bcrypt.hash(password, 10); 41 | 42 | delete data.confirmPassword; 43 | 44 | const response = await this.userRepository.createUser(data); 45 | 46 | delete response.password; 47 | delete response.recoverPasswordToken; 48 | delete response.ip; 49 | 50 | await this.mailService.sendUserCreationConfirmation(response); 51 | 52 | return { 53 | status: 201, 54 | data: response, 55 | }; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/modules/user/services/update-password.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import * as bcrypt from 'bcrypt'; 3 | import { MailService } from 'src/modules/mails/mail.service'; 4 | import { UpdateMyPasswordDto } from '../dtos/update-my-password.dto'; 5 | import { UserRepository } from '../repository/user.repository'; 6 | import { UsersEntity } from 'src/database/entities/users.entity'; 7 | 8 | @Injectable() 9 | export class UpdatePasswordService { 10 | constructor( 11 | private userRepository: UserRepository, 12 | private mailService: MailService, 13 | ) {} 14 | 15 | async execute( 16 | user: UsersEntity, 17 | { oldPassword, password, confirmNewPassword }: UpdateMyPasswordDto, 18 | ) { 19 | const userExists = await this.userRepository.findOneById(user.id); 20 | 21 | if (!userExists) { 22 | return { 23 | status: 400, 24 | data: { message: 'usuário não encontrado ou não autenticado.' }, 25 | }; 26 | } 27 | 28 | const isOldPassCorrect = await bcrypt.compare( 29 | oldPassword, 30 | userExists.password, 31 | ); 32 | 33 | if (!isOldPassCorrect) { 34 | return { 35 | status: 400, 36 | data: { message: 'A senha atual está incorreta.' }, 37 | }; 38 | } 39 | 40 | if (password != confirmNewPassword) { 41 | return { 42 | status: 400, 43 | data: { message: 'As senhas não conferem!' }, 44 | }; 45 | } 46 | const passwordHash = await bcrypt.hash(password, 10); 47 | 48 | const userUpdated = await this.userRepository.updatePassword( 49 | user.id, 50 | passwordHash, 51 | ); 52 | 53 | await this.mailService.sendUserConfirmation(userUpdated); 54 | 55 | return { 56 | status: 200, 57 | data: { message: 'Senha redefinida com sucesso!' }, 58 | }; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/modules/upload/upload.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | InternalServerErrorException, 4 | Logger, 5 | } from '@nestjs/common'; 6 | import { S3 } from 'aws-sdk'; 7 | import { ResponseS3 } from './types/response-s3.types'; 8 | 9 | @Injectable() 10 | export class FileUploadService { 11 | private s3 = new S3({ 12 | accessKeyId: process.env.AWS_ACCESS_KEY_ID, 13 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, 14 | }); 15 | 16 | async upload( 17 | file: { 18 | buffer?: any; 19 | originalname?: any; 20 | }, 21 | contentType?: string, 22 | ): Promise { 23 | const { originalname } = file; 24 | const bucketS3 = process.env.AWS_S3_BUCKET_NAME; 25 | const result = await this.uploadS3( 26 | file.buffer, 27 | bucketS3, 28 | originalname, 29 | contentType, 30 | ); 31 | 32 | return result as ResponseS3; 33 | } 34 | 35 | async deleteFile(name: string) { 36 | const params = { 37 | Bucket: process.env.AWS_S3_BUCKET_NAME, 38 | Key: name, 39 | }; 40 | 41 | try { 42 | await this.s3.deleteObject(params).promise(); 43 | } catch (error) { 44 | throw new InternalServerErrorException(error.message); 45 | } 46 | } 47 | 48 | async uploadS3( 49 | file: any, 50 | bucket: string, 51 | name: number, 52 | contentType?: string, 53 | ) { 54 | const newName = Date.now() + name; 55 | 56 | const params = { 57 | Bucket: bucket, 58 | Key: String(newName), 59 | Body: file, 60 | ContentType: contentType, 61 | }; 62 | 63 | return new Promise((resolve, reject) => { 64 | this.s3.upload(params, (err: { message: any }, data: unknown) => { 65 | if (err) { 66 | Logger.error(err); 67 | reject(err.message); 68 | } 69 | resolve(data); 70 | }); 71 | }); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/modules/reports/repository/reports.repository.ts: -------------------------------------------------------------------------------- 1 | import { ReportsEntity } from '../../../database/entities/reports.entity'; 2 | import { Repository } from 'typeorm'; 3 | import { CreateReportDto } from '../dtos/create-report.dto'; 4 | import { UpdateReportDto } from '../dtos/update-report.dto'; 5 | import { ReportParamsType } from '../types/find-by-params.type'; 6 | import { handleError } from '../../../shared/utils/handle-error.util'; 7 | import { InjectRepository } from '@nestjs/typeorm'; 8 | import { Injectable } from '@nestjs/common'; 9 | 10 | @Injectable() 11 | export class ReportRepository { 12 | constructor(@InjectRepository(ReportsEntity) private reportsRepository: Repository) {} 13 | 14 | async createReport(data: CreateReportDto): Promise { 15 | return this.reportsRepository.save(data).catch(handleError); 16 | } 17 | 18 | async findByParams(params: ReportParamsType): Promise { 19 | const { job_id, user_id } = params 20 | 21 | return this.reportsRepository.findOneBy({ job_id, user_id}).catch(handleError); 22 | } 23 | 24 | async findAllRepots(): Promise { 25 | return this.reportsRepository.find().catch(handleError); 26 | } 27 | 28 | async findReportById(id: string): Promise { 29 | return this.reportsRepository.findOneBy({id}).catch(handleError); 30 | } 31 | 32 | async updateReport(id: string, data: UpdateReportDto) { 33 | const report = await this.reportsRepository.findOneBy({id}).catch(handleError); 34 | 35 | return this.reportsRepository.save({ 36 | ...report, 37 | ...data, 38 | }).catch(handleError); 39 | } 40 | 41 | async deleteReportById(id: string): Promise { 42 | await this.reportsRepository.delete(id).catch(handleError); 43 | 44 | return { 45 | message: 'Deleted Report successfully', 46 | }; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/modules/savedjobs/repository/savedjobs.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, InternalServerErrorException } from "@nestjs/common"; 2 | import { InjectRepository } from "@nestjs/typeorm"; 3 | import { SavedJobsEntity } from "src/database/entities/savedjobs.entity"; 4 | import { PageDto, PageMetaDto, PageOptionsDto } from "src/shared/pagination"; 5 | import { Repository } from "typeorm"; 6 | import { GetAllSavedJobsDto } from "../dtos/get-all-savedjobs.dto"; 7 | 8 | @Injectable() 9 | export class SavedJobsRepository { 10 | constructor( 11 | @InjectRepository(SavedJobsEntity) 12 | private readonly savedJobsRepository: Repository, 13 | ) {} 14 | 15 | async getAllSavedJobs( 16 | pageOptionsDto: PageOptionsDto, 17 | filters: GetAllSavedJobsDto, 18 | ): Promise> { 19 | try { 20 | const queryBuilder = this.savedJobsRepository 21 | .createQueryBuilder('savedJob') 22 | .leftJoinAndSelect('savedJob.user', 'user') 23 | .leftJoinAndSelect('savedJob.job', 'job') 24 | .orderBy('savedJob.savedAt', pageOptionsDto.order) 25 | .skip((pageOptionsDto.page - 1) * pageOptionsDto.take) 26 | .take(pageOptionsDto.take); 27 | 28 | if (filters.userId) { 29 | queryBuilder.andWhere('user.id = :userId', { userId: filters.userId }); 30 | } 31 | 32 | if (filters.jobId) { 33 | queryBuilder.andWhere('job.id = :jobId', { jobId: filters.jobId }); 34 | } 35 | 36 | const [savedJobs, itemCount] = await queryBuilder.getManyAndCount(); 37 | 38 | const pageMetaDto = new PageMetaDto({ itemCount, pageOptionsDto }); 39 | return new PageDto(savedJobs, pageMetaDto); 40 | } catch (error) { 41 | throw new InternalServerErrorException( 42 | `Erro ao buscar vagas salvas: ${error.message}`, 43 | ); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/modules/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { PassportModule } from '@nestjs/passport'; 3 | import { TypeOrmModule } from '@nestjs/typeorm'; 4 | import { MailModule } from '../mails/mail.module'; 5 | import { UserRepository } from './repository/user.repository'; 6 | import { 7 | CreateUserService, 8 | DeleteUserService, 9 | FindAllUsersService, 10 | FindOneUserService, 11 | RecoveryPasswordByEmail, 12 | UpdatePasswordByEmailService, 13 | UpdateUserService, 14 | } from './services'; 15 | import { ActivateUserService } from './services/activate-user.service'; 16 | import { UserController } from './user.controller'; 17 | import { UploadModule } from '../upload/upload.module'; 18 | import { UpdatePasswordService } from './services/update-password.service'; 19 | import { CompanyRepository } from '../company/repository/company-repository'; 20 | import { CurriculumRepository } from '../curriculum/repository/curriculum-repository'; 21 | import { UsersEntity } from 'src/database/entities/users.entity'; 22 | import { CompaniesEntity } from 'src/database/entities/companies.entity'; 23 | import { CurriculumEntity } from 'src/database/entities/curriculum.entity'; 24 | 25 | @Module({ 26 | imports: [ 27 | TypeOrmModule.forFeature([UsersEntity, CompaniesEntity, CurriculumEntity]), 28 | PassportModule.register({ defaultStrategy: 'jwt' }), 29 | MailModule, 30 | UploadModule, 31 | ], 32 | controllers: [UserController], 33 | providers: [ 34 | CreateUserService, 35 | FindOneUserService, 36 | FindAllUsersService, 37 | UpdateUserService, 38 | DeleteUserService, 39 | RecoveryPasswordByEmail, 40 | UpdatePasswordByEmailService, 41 | UpdatePasswordService, 42 | ActivateUserService, 43 | UserRepository, 44 | CompanyRepository, 45 | CurriculumRepository 46 | ], 47 | exports: [RecoveryPasswordByEmail], 48 | }) 49 | export class UserModule {} 50 | -------------------------------------------------------------------------------- /src/modules/curriculum/curriculum.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Delete, 4 | Get, 5 | Param, 6 | Post, 7 | Res, 8 | UploadedFile, 9 | UseGuards, 10 | UseInterceptors, 11 | } from '@nestjs/common'; 12 | import { AuthGuard } from '@nestjs/passport'; 13 | import { FileInterceptor } from '@nestjs/platform-express'; 14 | import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; 15 | import { Response } from 'express'; 16 | import { UploadCurriculumSwagger } from 'src/shared/Swagger/decorators/curriculum/upload-curriculum.swagger'; 17 | import { UsersEntity } from '../../database/entities/users.entity'; 18 | import { LoggedUser } from '../auth/decorator/logged-user.decorator'; 19 | import { CurriculumService } from './curriculum.service'; 20 | import { DeleteCurriculumDto } from './dtos/delete-curriculum.dto'; 21 | 22 | @ApiTags('Curriculum') 23 | @ApiBearerAuth() 24 | @UseGuards(AuthGuard()) 25 | @Controller('curriculum') 26 | export class CurriculumController { 27 | constructor(private curriculumService: CurriculumService) {} 28 | 29 | @Get() 30 | async getAllCurriculum( 31 | @LoggedUser() user: UsersEntity, 32 | @Res() res: Response, 33 | ) { 34 | const { data, status } = await this.curriculumService.getALlCurriculum( 35 | user, 36 | ); 37 | 38 | return res.status(status).send(data); 39 | } 40 | 41 | @UploadCurriculumSwagger() 42 | @Post('upload') 43 | @UseInterceptors(FileInterceptor('file')) 44 | async uploadCurriculum( 45 | @LoggedUser() user: UsersEntity, 46 | @UploadedFile() file, 47 | @Res() res: Response, 48 | ) { 49 | const { data, status } = await this.curriculumService.uploadCurriculum( 50 | file, 51 | user, 52 | ); 53 | 54 | return res.status(status).send(data); 55 | } 56 | 57 | @Delete(':key') 58 | async deleteCurriculum(@Param() { key }: DeleteCurriculumDto) { 59 | return this.curriculumService.deleteCurriculum(key); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/modules/user/services/find-all-users.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UserRepository } from '../../../../src/modules/user/repository/user.repository'; 3 | import { FindAllUsersService } from '../../../../src/modules/user/services/find-all-users.service'; 4 | import { Order, PageOptionsDto } from '../../../../src/shared/pagination'; 5 | import { getAllUserMock } from '../../../mocks/user/get-all-user.mock'; 6 | 7 | class UserRepositoryMock { 8 | getAllUsers = jest.fn(); 9 | } 10 | 11 | describe('FindAllUsersService', () => { 12 | let service: FindAllUsersService; 13 | let userRepository: UserRepositoryMock; 14 | 15 | beforeEach(async () => { 16 | const module: TestingModule = await Test.createTestingModule({ 17 | controllers: [FindAllUsersService], 18 | providers: [ 19 | { 20 | provide: UserRepository, 21 | useClass: UserRepositoryMock, 22 | }, 23 | ], 24 | }).compile(); 25 | 26 | service = module.get(FindAllUsersService); 27 | userRepository = module.get(UserRepository); 28 | }); 29 | 30 | it('should be defined', () => { 31 | expect(service).toBeDefined(); 32 | }); 33 | 34 | describe('execute', () => { 35 | it('should be able to return an array of user and pagination', async () => { 36 | userRepository.getAllUsers = jest 37 | .fn() 38 | .mockResolvedValue(getAllUserMock()); 39 | const getAllUsersSpy = jest.spyOn(userRepository, 'getAllUsers'); 40 | const pageOptionsDto: PageOptionsDto = { 41 | orderByColumn: 'name', 42 | page: 1, 43 | take: 5, 44 | order: Order.ASC, 45 | skip: 0, 46 | }; 47 | const response = await service.execute(pageOptionsDto); 48 | expect(response).toEqual(getAllUserMock()); 49 | expect(getAllUsersSpy).toBeCalled(); 50 | expect(getAllUsersSpy).toBeCalledTimes(1); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/database/entities/languages.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | CreateDateColumn, 4 | Entity, 5 | JoinColumn, 6 | ManyToOne, 7 | PrimaryGeneratedColumn, 8 | Timestamp, 9 | UpdateDateColumn, 10 | } from 'typeorm'; 11 | import { PersonalDataEntity } from './personal-data.entity'; 12 | 13 | enum ProficenceEnum { 14 | BASIC = 'BASIC', 15 | INTERMEDITE = 'INTERMEDITE', 16 | ADVANCED = 'ADVANCED', 17 | FLUENT = 'FLUENT', 18 | } 19 | 20 | @Entity('tb_languages') 21 | export class LanguagesEntity { 22 | @PrimaryGeneratedColumn('uuid') 23 | id: string; 24 | 25 | @Column() 26 | language: string; 27 | 28 | @Column({ 29 | type: 'enum', 30 | enum: ProficenceEnum, 31 | default: ProficenceEnum.BASIC, 32 | }) 33 | writing: string; 34 | 35 | @Column({ 36 | type: 'enum', 37 | enum: ProficenceEnum, 38 | default: ProficenceEnum.BASIC, 39 | }) 40 | reading: string; 41 | 42 | @Column({ 43 | type: 'enum', 44 | enum: ProficenceEnum, 45 | default: ProficenceEnum.BASIC, 46 | }) 47 | listening: string; 48 | 49 | @Column({ 50 | type: 'enum', 51 | enum: ProficenceEnum, 52 | default: ProficenceEnum.BASIC, 53 | }) 54 | speaking: string; 55 | 56 | @ManyToOne(() => PersonalDataEntity) 57 | @JoinColumn({ name: 'personal_data_id' }) 58 | personal_data: PersonalDataEntity; 59 | 60 | @Column() 61 | personal_data_id: string; 62 | 63 | @CreateDateColumn() 64 | created_at: Date; 65 | 66 | @UpdateDateColumn({ update: true }) 67 | updated_at: Date; 68 | 69 | constructor(language?: Partial) { 70 | this.id = language?.id; 71 | this.language = language?.language; 72 | this.writing = language?.writing; 73 | this.reading = language?.reading; 74 | this.listening = language?.listening; 75 | this.speaking = language?.speaking; 76 | this.personal_data_id = language?.personal_data_id; 77 | this.created_at = language?.created_at; 78 | this.updated_at = language?.updated_at; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/modules/curriculum/curriculum.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { UsersEntity } from '../../database/entities/users.entity'; 3 | import { FileUploadService } from '../upload/upload.service'; 4 | import { CurriculumRepository } from './repository/curriculum-repository'; 5 | import { CreateCurriculumType } from './types/create-curriculum.type'; 6 | 7 | @Injectable() 8 | export class CurriculumService { 9 | constructor( 10 | private fileUploadService: FileUploadService, 11 | private curriculumRepository: CurriculumRepository, 12 | ) {} 13 | 14 | async getALlCurriculum( 15 | user: UsersEntity, 16 | ): Promise<{ status: number; data: any }> { 17 | const curriculuns = await this.curriculumRepository.findAllCurriculum( 18 | user.id, 19 | ); 20 | 21 | return { 22 | status: 200, 23 | data: curriculuns, 24 | }; 25 | } 26 | 27 | async uploadCurriculum( 28 | file, 29 | user: UsersEntity, 30 | ): Promise<{ status: number; data: any }> { 31 | if (!file) { 32 | return { 33 | status: 400, 34 | data: { message: 'File is required' }, 35 | }; 36 | } 37 | 38 | const { Location, key } = await this.fileUploadService.upload( 39 | file, 40 | 'application/pdf', 41 | ); 42 | 43 | if (!Location || !key) { 44 | return { 45 | status: 400, 46 | data: { message: 'Upload file' }, 47 | }; 48 | } 49 | 50 | const saveNewCurriculum: CreateCurriculumType = { 51 | file: Location, 52 | fileKey: key, 53 | user, 54 | }; 55 | 56 | const curriculuns = await this.curriculumRepository.saveCurriculum( 57 | saveNewCurriculum, 58 | ); 59 | 60 | return { 61 | status: 200, 62 | data: curriculuns, 63 | }; 64 | } 65 | 66 | async deleteCurriculum(key: string) { 67 | await this.fileUploadService.deleteFile(key); 68 | 69 | await this.curriculumRepository.deleteByKey(key); 70 | 71 | return { message: 'Deleted successfully' }; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/modules/company/services/create-company.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import * as bcrypt from 'bcrypt'; 3 | import { MailService } from 'src/modules/mails/mail.service'; 4 | import { CreateCompanyDto } from '../dtos/create-company.dto'; 5 | import { CompanyRepository } from '../repository/company-repository'; 6 | import { UserRepository } from 'src/modules/user/repository/user.repository'; 7 | 8 | @Injectable() 9 | export class CreateCompanyService { 10 | constructor( 11 | private companyRepository: CompanyRepository, 12 | private userRepository: UserRepository, 13 | private mailService: MailService, 14 | ) {} 15 | 16 | async execute(data: CreateCompanyDto) { 17 | const { email, password, passwordConfirmation, cnpj } = data; 18 | const emailAlreadyInUseCompany = await this.companyRepository.findOneByEmail( 19 | email, 20 | ); 21 | 22 | const emailAlreadyInUseUser = await this.userRepository.findOneByEmail( 23 | email, 24 | ); 25 | 26 | if (emailAlreadyInUseCompany || emailAlreadyInUseUser) { 27 | return { 28 | status: 404, 29 | data: { 30 | message: 'E-mail já cadastrado', 31 | }, 32 | }; 33 | } 34 | 35 | if (password != passwordConfirmation) { 36 | return { 37 | status: 404, 38 | data: { 39 | message: 'As senhas precisam ser idênticas', 40 | }, 41 | }; 42 | } 43 | 44 | const cnpjAlreadyInUse = await this.companyRepository.findOneByCnpj(cnpj); 45 | 46 | if (cnpjAlreadyInUse) { 47 | return { 48 | status: 404, 49 | data: { 50 | message: `This CNPJ is already in use`, 51 | }, 52 | }; 53 | } 54 | 55 | data.password = await bcrypt.hash(password, 10); 56 | 57 | const response = await this.companyRepository.createCompany(data); 58 | 59 | delete response.password; 60 | delete response.recoverPasswordToken; 61 | 62 | await this.mailService.sendCompanyCreationConfirmation(response); 63 | 64 | return { 65 | status: 201, 66 | data: response, 67 | }; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/modules/candidacy/controller/candidacy.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BadRequestException, 3 | Body, 4 | Controller, 5 | Get, 6 | Patch, 7 | Post, 8 | UseGuards, 9 | } from '@nestjs/common'; 10 | import { CandidacyService } from '../service/candidacy.service'; 11 | import { CreateCandidacyDto } from '../dto/create-candidacy.dto'; 12 | import { LoggedUser } from 'src/modules/auth/decorator/logged-user.decorator'; 13 | import { UsersEntity } from 'src/database/entities/users.entity'; 14 | import { AuthGuard } from '@nestjs/passport'; 15 | import { UpdateCandidacyDto } from '../dto/update-candidacy.dto'; 16 | import { CandidacyStatus } from 'src/database/entities/candidancy-status.enum'; 17 | import { ApiTags } from '@nestjs/swagger'; 18 | import { CreateCandidacySwagger } from 'src/shared/Swagger/decorators/candidacy/create-candidacy.swagger'; 19 | import { GetCandidaciesSwagger } from 'src/shared/Swagger/decorators/candidacy/get-candidacies.swagger'; 20 | import { UpdateCandidacySwagger } from 'src/shared/Swagger/decorators/candidacy/update-candidacy.swagger'; 21 | 22 | @ApiTags('Candidacy') 23 | @Controller('candidacy') 24 | @UseGuards(AuthGuard('jwt')) 25 | export class CandidacyController { 26 | constructor(private readonly candidacyService: CandidacyService) {} 27 | 28 | @Post() 29 | @CreateCandidacySwagger() 30 | async createCandidacy(@Body() createCandidacyDTO: CreateCandidacyDto) { 31 | return await this.candidacyService.create(createCandidacyDTO); 32 | } 33 | 34 | @Get() 35 | @GetCandidaciesSwagger() 36 | async getCandidacies(@LoggedUser() user: UsersEntity) { 37 | return await this.candidacyService.getCandidacyByUserId(user.id); 38 | } 39 | 40 | @Patch() 41 | @UpdateCandidacySwagger() 42 | async updateCandidacy(@Body() updateCandidacyDto: UpdateCandidacyDto) { 43 | if (updateCandidacyDto.status === CandidacyStatus.InProgress) { 44 | throw new BadRequestException( 45 | 'Não é possível atualizar para o status "em andamento"', 46 | ); 47 | } 48 | return await this.candidacyService.closeCandidacy( 49 | updateCandidacyDto.id, 50 | updateCandidacyDto.status, 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/modules/mails/templates/passwordupdate.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | Logo 15 |
16 |
17 |
18 |
19 | sucesso 20 |
21 |
22 |

Senha redefinida

23 |
24 |

Olá, {{name}}!

25 |

Você redefiniu a sua senha.

26 |

Obrigado por utilizar o nosso site.

27 |
28 |
29 | -Equipe SouJunior 30 |
31 |
32 |
33 |
34 |
35 | 36 |
37 |

Volte para a página de login:

38 | 39 | www.portaldevagas.com.br 40 | 41 |
42 |
43 |

Esta é uma mensagem automática. Por favor não responda este e-mail.

44 |
45 |
46 |
47 |
48 | 49 | -------------------------------------------------------------------------------- /src/database/entities/companies.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | CreateDateColumn, 4 | Entity, 5 | OneToMany, 6 | PrimaryGeneratedColumn, 7 | UpdateDateColumn, 8 | } from 'typeorm'; 9 | import { CompanySizeEnum } from '../../modules/company/enum/company-size.enum'; 10 | import { JobsEntity } from './jobs.entity'; 11 | 12 | @Entity('tb_companies') 13 | export class CompaniesEntity { 14 | @PrimaryGeneratedColumn('uuid') 15 | id: string; 16 | 17 | @Column() 18 | companyName: string; 19 | 20 | @Column() 21 | email: string; 22 | 23 | @Column() 24 | password: string; 25 | 26 | @Column() 27 | cnpj: string; 28 | 29 | @OneToMany(() => JobsEntity, (jobs) => jobs.company) 30 | jobs: JobsEntity[]; 31 | 32 | @CreateDateColumn() 33 | created_at: Date; 34 | 35 | @UpdateDateColumn() 36 | updated_at: Date; 37 | 38 | @Column({ default: false }) 39 | mailConfirm: boolean; 40 | 41 | @Column({ nullable: true }) 42 | recoverPasswordToken: string; 43 | 44 | @Column({ nullable: true }) 45 | companyType: string; 46 | 47 | @Column({ 48 | nullable: true, 49 | type: 'enum', 50 | enum: [ 51 | CompanySizeEnum.BIG_SIZE, 52 | CompanySizeEnum.HALF_SIZE, 53 | CompanySizeEnum.SMALL_SIZE, 54 | ], 55 | }) 56 | companySize: string; 57 | 58 | @Column({ nullable: true }) 59 | uf: string; 60 | 61 | @Column({ nullable: true }) 62 | companySite: string; 63 | 64 | @Column({ type: 'json', nullable: true }) 65 | otherSite: { 66 | instagran: string; 67 | linkedin: string; 68 | twitter: string; 69 | }; 70 | 71 | @Column({ nullable: true }) 72 | description: string; 73 | 74 | @Column({ nullable: true }) 75 | profile: string; 76 | 77 | @Column({ nullable: true }) 78 | profileKey: string; 79 | 80 | constructor(company?: Partial) { 81 | this.id = company?.id; 82 | this.companyName = company?.companyName; 83 | this.email = company?.email; 84 | this.password = company?.password; 85 | this.cnpj = company?.cnpj; 86 | this.mailConfirm = company?.mailConfirm; 87 | this.recoverPasswordToken = company?.recoverPasswordToken; 88 | this.created_at = company?.created_at; 89 | this.updated_at = company?.updated_at; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/modules/candidacy/repository/candidacy.repository.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BadRequestException, 3 | Injectable, 4 | InternalServerErrorException, 5 | NotFoundException, 6 | } from '@nestjs/common'; 7 | import { Repository } from 'typeorm'; 8 | import { CandidacyEntity } from '../../../database/entities/candidacy.entity'; 9 | import { InjectRepository } from '@nestjs/typeorm'; 10 | import { CandidacyStatus } from 'src/database/entities/candidancy-status.enum'; 11 | 12 | @Injectable() 13 | export class CandidacyRepository { 14 | constructor( 15 | @InjectRepository(CandidacyEntity) 16 | private candidacyRepository: Repository, 17 | ) {} 18 | 19 | async createCandidacy(candidacy: CandidacyEntity): Promise { 20 | return this.candidacyRepository.save(candidacy); 21 | } 22 | 23 | async findAllByUserId(userId: string): Promise { 24 | if (!userId) { 25 | throw new BadRequestException('userId é obrigatório'); 26 | } 27 | try { 28 | const candidacy = await this.candidacyRepository.find({ 29 | where: { userId: userId }, 30 | }); 31 | if (!candidacy.length) { 32 | throw new NotFoundException( 33 | 'Nenhuma candidatura encontrada para este usuário', 34 | ); 35 | } 36 | return candidacy; 37 | } catch (error) { 38 | throw new BadRequestException( 39 | 'Erro ao buscar candidaturas: ' + error.message, 40 | ); 41 | } 42 | } 43 | 44 | async updateStatus( 45 | id: string, 46 | status: CandidacyStatus, 47 | ): Promise { 48 | try { 49 | const candidacy = await this.candidacyRepository.findOne({ 50 | where: { id }, 51 | }); 52 | if (!candidacy) { 53 | throw new NotFoundException('Candidatura não encontrada'); 54 | } 55 | candidacy.status = status; 56 | await this.candidacyRepository.save(candidacy); 57 | 58 | return candidacy; 59 | } catch (error) { 60 | if (error instanceof NotFoundException) { 61 | throw error; 62 | } else { 63 | throw new InternalServerErrorException( 64 | 'Erro ao atualizar o status da candidatura: ' + error.message, 65 | ); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/modules/user/dtos/update-my-password.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString, Length, Matches } from 'class-validator'; 3 | 4 | export class UpdateMyPasswordDto { 5 | @IsString({ message: "O campo 'oldPassword' não pode ficar vazio" }) 6 | @IsNotEmpty() 7 | @ApiProperty({ 8 | description: 'Senha antiga', 9 | example: 'Abcd@1234', 10 | }) 11 | oldPassword: string; 12 | 13 | @IsString() 14 | @IsNotEmpty({ message: "O campo 'password' não pode ficar vazio" }) 15 | @Length(8, 50) 16 | @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, { 17 | message: 18 | 'A senha precisa ter no mínimo 8 caracteres, máximo de 50, uma letra maiúscula, um número e um símbolo.', 19 | }) 20 | @ApiProperty({ 21 | description: 'Senha de Login', 22 | example: 'Abcd@1234', 23 | }) 24 | password: string; 25 | 26 | @IsString() 27 | @IsNotEmpty({ message: "O campo 'confirmPassword' não pode ficar vazio" }) 28 | @Length(8, 50) 29 | @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, { 30 | message: 31 | 'A senha precisa ter no mínimo 8 caracteres, máximo de 50, uma letra maiúscula, um número e um símbolo.', 32 | }) 33 | @ApiProperty({ 34 | description: 'Senha de Login', 35 | example: 'Abcd@1234', 36 | }) 37 | confirmNewPassword: string; 38 | } 39 | 40 | export class CreatePasswordHashDto { 41 | @IsString() 42 | @Length(8, 50) 43 | @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, { 44 | message: 'Senha muito fraca', 45 | }) 46 | @ApiProperty({ 47 | description: 48 | 'A senha precisa ter no mínimo 8 caracteres, máximo de 50, uma letra maiúscula, um número e um símbolo.', 49 | example: 'Abcd@1234', 50 | }) 51 | password: string; 52 | 53 | @IsString() 54 | @Length(8, 50) 55 | @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, { 56 | message: 57 | 'A senha precisa ter no mínimo 8 caracteres, máximo de 50, uma letra maiúscula, um número e um símbolo.', 58 | }) 59 | @ApiProperty({ 60 | description: 'Confirmação de senha de Login', 61 | example: 'Abcd@1234', 62 | }) 63 | confirmPassword: string; 64 | 65 | @IsString() 66 | @ApiProperty({ 67 | description: 'token de recuperação de senha.', 68 | example: 'algo uuid bem rândomico', 69 | }) 70 | recoverPasswordToken?: string; 71 | } 72 | -------------------------------------------------------------------------------- /test/modules/user/services/find-one-user.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UserRepository } from '../../../../src/modules/user/repository/user.repository'; 3 | import { FindOneUserService } from '../../../../src/modules/user/services/find-one-user.service'; 4 | import { userMock } from '../../../mocks/user/user.mock'; 5 | 6 | class UserRepositoryMock { 7 | findOneById = jest.fn(); 8 | } 9 | 10 | describe('FindOneUserService', () => { 11 | let service: FindOneUserService; 12 | let userRepository: UserRepositoryMock; 13 | 14 | beforeEach(async () => { 15 | const module: TestingModule = await Test.createTestingModule({ 16 | controllers: [FindOneUserService], 17 | providers: [ 18 | { 19 | provide: UserRepository, 20 | useClass: UserRepositoryMock, 21 | }, 22 | ], 23 | }).compile(); 24 | 25 | service = module.get(FindOneUserService); 26 | userRepository = module.get(UserRepository); 27 | }); 28 | 29 | it('should be defined', () => { 30 | expect(service).toBeDefined(); 31 | }); 32 | 33 | describe('execute', () => { 34 | it('should be able to return an error when id not provide', () => { 35 | const findOneByIdSpy = jest.spyOn(userRepository, 'findOneById'); 36 | expect(async () => { 37 | await service.execute(''); 38 | }).rejects.toThrow('Id not provider'); 39 | expect(findOneByIdSpy).not.toHaveBeenCalled(); 40 | }); 41 | 42 | it('should be able to return an error when user not found', () => { 43 | userRepository.findOneById = jest.fn().mockResolvedValue(''); 44 | const findOneByIdSpy = jest.spyOn(userRepository, 'findOneById'); 45 | expect(async () => { 46 | await service.execute('123'); 47 | }).rejects.toThrow('User not found'); 48 | expect(findOneByIdSpy).toHaveBeenCalled(); 49 | expect(findOneByIdSpy).toBeCalledTimes(1); 50 | }); 51 | 52 | it('should be able to return an user', async () => { 53 | userRepository.findOneById = jest.fn().mockResolvedValue(userMock()); 54 | const findOneByIdSpy = jest.spyOn(userRepository, 'findOneById'); 55 | const response = await service.execute('123'); 56 | expect(response).toEqual(userMock()); 57 | expect(findOneByIdSpy).toHaveBeenCalled(); 58 | expect(findOneByIdSpy).toBeCalledTimes(1); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /src/modules/company/dtos/create-company.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { 3 | IsEmail, 4 | IsNotEmpty, 5 | IsString, 6 | Length, 7 | Matches, 8 | MaxLength, 9 | MinLength, 10 | } from 'class-validator'; 11 | 12 | export class CreateCompanyDto { 13 | @ApiProperty({ 14 | description: 'Nome da empresa (até 30 caracteres)', 15 | example: 'Pipomills', 16 | }) 17 | @IsString({ message: 'O campo companyName deve ser uma string' }) 18 | @IsNotEmpty({ message: 'O campo companyName não pode estar vazio' }) 19 | @MaxLength(30) 20 | companyName: string; 21 | 22 | @ApiProperty({ 23 | description: 'Email da empresa', 24 | example: 'pipomills@pipomills.com', 25 | }) 26 | @IsString({ message: 'O campo email deve ser uma string' }) 27 | @IsNotEmpty({ message: 'O campo email não pode estar vazio' }) 28 | @IsEmail() 29 | email: string; 30 | 31 | @ApiProperty({ 32 | description: 'CNPJ (formato: xx.xxx.xxx/xxxx-xx)', 33 | example: '67.979.311/0001-15', 34 | }) 35 | @IsString({ message: 'O campo cnpj deve ser uma string' }) 36 | @IsNotEmpty({ message: 'O campo cnpj não pode estar vazio' }) 37 | @MaxLength(14) 38 | @MinLength(14) 39 | cnpj: string; 40 | 41 | @ApiProperty({ 42 | description: 43 | 'Senha de Login (8 a 20 caracteres, deve conter letras maiúsculas, minúsculas, números e caracteres especiais)', 44 | example: 'Abcd@1234', 45 | }) 46 | @IsString({ message: 'O campo password deve ser uma string' }) 47 | @IsNotEmpty({ message: 'O campo password não pode estar vazio' }) 48 | @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, { 49 | message: 50 | 'A senha deve conter letras maiúsculas, minúsculas, números e caracteres especiais', 51 | }) 52 | @Length(8, 20) 53 | password: string; 54 | 55 | @ApiProperty({ 56 | description: 57 | 'Confirmação de Senha de Login (8 a 20 caracteres, deve ser idêntica à senha)', 58 | example: 'Abcd@1234', 59 | }) 60 | @IsString({ message: 'O campo passwordConfirmation deve ser uma string' }) 61 | @IsNotEmpty({ message: 'O campo passwordConfirmation não pode estar vazio' }) 62 | @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, { 63 | message: 64 | 'A confirmação de senha deve conter letras maiúsculas, minúsculas, números e caracteres especiais', 65 | }) 66 | @Length(8, 20) 67 | passwordConfirmation: string; 68 | } 69 | -------------------------------------------------------------------------------- /src/modules/alert/service/alerts.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { AlertsRepository } from '../repository/alerts.repository'; 3 | import { AlertEntity } from '../../../database/entities/alert.entity'; 4 | import { JobsEntity } from 'src/database/entities/jobs.entity'; 5 | import axios from 'axios'; 6 | import { MailService } from '../../mails/mail.service'; 7 | import * as Cron from 'node-cron'; 8 | 9 | @Injectable() 10 | export class AlertsService { 11 | constructor( 12 | private readonly alertsRepository: AlertsRepository, 13 | private readonly mailerService: MailService, 14 | ) { 15 | this.configureCronJob(); 16 | } 17 | 18 | private configureCronJob() { 19 | Cron.schedule( 20 | '49 15 * * *', 21 | () => { 22 | this.sendEmailsDaily(); 23 | }, 24 | { 25 | scheduled: true, 26 | timezone: 'America/Sao_Paulo', 27 | }, 28 | ); 29 | } 30 | 31 | async getUserEmailByAlertId(alertId: string): Promise { 32 | const alert = await this.alertsRepository.findAlertById(alertId); 33 | if (!alert) throw new NotFoundException('Alert not found'); 34 | 35 | return alert.user.email; 36 | } 37 | 38 | async createAlert(data: Partial): Promise { 39 | return this.alertsRepository.createAlert(data); 40 | } 41 | 42 | async sendEmailsDaily() { 43 | const alerts = await this.alertsRepository.findAll(); 44 | 45 | await Promise.all( 46 | alerts.map(async (alert) => { 47 | const jobs = await this.findJobs(alert.keyword, alert.location); 48 | const userEmail = await this.getUserEmailByAlertId(alert.id); 49 | 50 | await this.sendEmail(userEmail, jobs); 51 | }), 52 | ); 53 | } 54 | 55 | private async findJobs( 56 | keyword: string, 57 | location: string, 58 | ): Promise { 59 | try { 60 | const response = await axios.get( 61 | `${process.env.VACANCIES_URL}/job?keyword=${keyword}&location=${location}`, 62 | ); 63 | return response.data; 64 | } catch (error) { 65 | console.error( 66 | `Error fetching jobs for keyword: ${keyword}, location: ${location}`, 67 | error, 68 | ); 69 | return []; 70 | } 71 | } 72 | 73 | private async sendEmail(email: string, jobs: JobsEntity[]): Promise { 74 | await this.mailerService.sendJobAlerts(email, jobs); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/modules/reports/reports.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Get, 6 | Param, 7 | Post, 8 | Put, 9 | } from '@nestjs/common'; 10 | import { ApiExcludeController, ApiOperation, ApiTags } from '@nestjs/swagger'; 11 | import { CreateReportDto } from './dtos/create-report.dto'; 12 | import { ReportIdDto } from './dtos/get-report-by-id.dto'; 13 | import { UpdateReportDto } from './dtos/update-report.dto'; 14 | import { 15 | CreateReportService, 16 | DeleteReportService, 17 | FindAllReportsService, 18 | FindReportByIdService, 19 | UpdateReportService, 20 | } from './services'; 21 | import { CreateReportSwagger } from 'src/shared/Swagger/decorators/reports/create-report.swagger'; 22 | import { GetAllReportsSwagger } from 'src/shared/Swagger/decorators/reports/get-all-reports.swagger'; 23 | import { GetReportByIdSwagger } from 'src/shared/Swagger/decorators/reports/get-report-by-id.swagger'; 24 | import { UpdateReportSwagger } from 'src/shared/Swagger/decorators/reports/update-report.swagger'; 25 | import { DeleteReportSwagger } from 'src/shared/Swagger/decorators/reports/delete-report.swagger'; 26 | 27 | @ApiExcludeController() 28 | @ApiTags('Report') 29 | @Controller('report') 30 | export class ReportsController { 31 | constructor( 32 | private createReportService: CreateReportService, 33 | private findAllReportsService: FindAllReportsService, 34 | private findReportByIdService: FindReportByIdService, 35 | private deleteReportService: DeleteReportService, 36 | private updateReportService: UpdateReportService, 37 | ) {} 38 | 39 | @Post('') 40 | @CreateReportSwagger() 41 | async create(@Body() data: CreateReportDto) { 42 | return this.createReportService.execute(data); 43 | } 44 | 45 | @Get() 46 | @GetAllReportsSwagger() 47 | async getAllReports() { 48 | return this.findAllReportsService.execute(); 49 | } 50 | 51 | @Get(':id') 52 | @GetReportByIdSwagger() 53 | async getReportById(@Param() data: ReportIdDto) { 54 | return this.findReportByIdService.execute(data); 55 | } 56 | 57 | @Put(':id') 58 | @UpdateReportSwagger() 59 | async updateReport( 60 | @Param() reportId: ReportIdDto, 61 | @Body() data: UpdateReportDto, 62 | ) { 63 | return this.updateReportService.execute(reportId, data); 64 | } 65 | 66 | @Delete(':id') 67 | @DeleteReportSwagger() 68 | async deleteReport(@Param() data: ReportIdDto) { 69 | return this.deleteReportService.execute(data); 70 | } 71 | } 72 | --------------------------------------------------------------------------------