Skip to content

Commit

Permalink
Merge pull request #57 from RoutinelyOrganization/develop
Browse files Browse the repository at this point in the history
Sync
  • Loading branch information
viniciuscosmome authored Nov 10, 2023
2 parents 6ef0ad9 + 94cc83c commit a8c6eae
Show file tree
Hide file tree
Showing 19 changed files with 287 additions and 110 deletions.
20 changes: 2 additions & 18 deletions src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,7 @@
import { Module } from '@nestjs/common';
import { PrismaModule } from './prisma/prisma.module';
import {
AccountModule,
SessionModule,
PasswordTokenModule,
MailingModule,
TaskModule,
GoalModule,
} from './modules';
import { AccountModule, TaskModule, GoalModule } from './modules';

@Module({
imports: [
PrismaModule,
AccountModule,
SessionModule,
PasswordTokenModule,
MailingModule,
TaskModule,
GoalModule,
],
imports: [AccountModule, TaskModule, GoalModule],
})
export class AppModule {}
84 changes: 84 additions & 0 deletions src/config/exceptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { HttpException } from '@nestjs/common';

type iCustomException = {
property?: string | undefined;
message?: string | undefined;
};

export class CustomException extends HttpException {
constructor(message: string, status = 500, property?: string) {
super(
{
errors: [
{
property,
message,
},
],
},
status
);
}
}

// 400
export class DataValidationError extends CustomException {
constructor(props: iCustomException) {
super(props.message || 'Erro em um dado', 400, props.property);
}
}

// 401
export class AuthorizationError extends CustomException {
constructor(props: iCustomException) {
super(props.message || 'Credenciais inválidas', 401, props.property);
}
}

// 403
export class PermissionError extends CustomException {
constructor(props: iCustomException) {
super(
props.message || 'Você não tem permissão para executar esta ação',
403,
props.property
);
}
}

// 404
export class NotFoundError extends CustomException {
constructor(props: iCustomException) {
super(props.message || 'Informação não encontrada', 404, props.property);
}
}

// 422
export class UnprocessableEntityError extends CustomException {
constructor(props: iCustomException) {
super(
props.message || 'Você não tem permissão alterar esta informação',
422,
props.property
);
}
}

// 429
export class RatelimitError extends CustomException {
constructor(props: iCustomException) {
super(
props.message ||
'Muitas requisições em um curto período, aguarde e tente novamente mais tarde',
429,
props.property
);
}
}

// 500
export class InternalServerError extends CustomException {
constructor(props: iCustomException) {
super(props.message || 'Erro desconhecido', 500);
}
}
44 changes: 44 additions & 0 deletions src/config/responses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { iValidationResponses } from './types';

export const responses: iValidationResponses = {
notEmpty: 'Não pode estar vazio',
boolean: 'Deve ser verdadeiro ou falso (true | false)',
string: 'Deve ser um texto',
arrayOfString: 'Deve ser uma lista de textos',
number: 'Deve ser um número',
integer: 'Deve ser um número inteiro',
enum(validationArguments) {
const enums = validationArguments.constraints[1];
return `Deve ser um texto entre essas opções: ${enums}`;
},
arrayMinSize(validationArguments) {
const min = validationArguments.constraints[0];
return `Deve ter no mínimo ${min} item(ns)`;
},
arrayMaxSize(validationArguments) {
const max = validationArguments.constraints[0];
return `Deve ter no máximo ${max} item(ns)`;
},
minLength(validationArguments) {
const min = validationArguments.constraints[0];
return `Deve ter no mínimo ${min} caracteres`;
},
maxLength(validationArguments) {
const max = validationArguments.constraints[0];
return `Deve ter no máximo ${max} caracteres`;
},
minValue(validationArguments) {
const min = validationArguments.constraints[0];
return `O valor mínimo deve ser ${min}`;
},
maxValue(validationArguments) {
const max = validationArguments.constraints[0];
return `O valor máximo deve ser ${max}`;
},
email: 'O e-mail deve ter um formato válido',
strongPassword:
'Deve conter no mínimo 6 caracteres com uma letra maiúscula, uma letra minúscula, um número e um símbolo',
fullname: 'Deve conter apenas letras e espaço em branco entre palavras',
datePattern: 'Deve ser um texto nesse padrão, YYYY-MM-DD',
dateRange: 'O mês e o dia devem ser válidos',
};
2 changes: 1 addition & 1 deletion src/config/swagger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ export const swaggerDocumentConfig = new DocumentBuilder()
)
.addBearerAuth({
type: 'http',
description: 'Get the `token` property after logging in',
description: 'Use o `token` adquirido ao acessar a conta',
})
.build();
24 changes: 24 additions & 0 deletions src/config/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ValidationOptions } from 'class-validator';

type iValidator = ValidationOptions['message'] | string;

export type iValidationResponses = {
notEmpty: iValidator;
boolean: iValidator;
string: iValidator;
arrayOfString: iValidator;
number: iValidator;
integer: iValidator;
enum: iValidator;
arrayMinSize: iValidator;
arrayMaxSize: iValidator;
minLength: iValidator;
maxLength: iValidator;
minValue: iValidator;
maxValue: iValidator;
email: iValidator;
strongPassword: iValidator;
fullname: iValidator;
datePattern: iValidator;
dateRange: iValidator;
};
18 changes: 18 additions & 0 deletions src/config/validation-pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {
BadRequestException,
ValidationPipe,
ValidationPipeOptions,
} from '@nestjs/common';

const config: ValidationPipeOptions = {
exceptionFactory(errors) {
const formated = errors.map((error) => ({
property: error.property,
message: error.constraints[Object.keys(error.constraints)[0]],
}));

return new BadRequestException({ errors: formated });
},
};

export default new ValidationPipe(config);
10 changes: 6 additions & 4 deletions src/guards/roles.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ export class RolesGuard implements CanActivate {
const firstRequiredRole = requiredRoles?.length && requiredRoles[0];

if (!firstRequiredRole) {
throw new InternalServerErrorException('unspecified permissions');
throw new InternalServerErrorException(
'As permissões necessárias não foram definidas'
);
}

const isResetPasswordRequest = firstRequiredRole === Permissions['001'];
Expand All @@ -70,13 +72,13 @@ export class RolesGuard implements CanActivate {
}

if (token && !this.isHexString(token)) {
throw new BadRequestException('Access token has a type error');
throw new BadRequestException('O formato do token não está correto');
}

const isRefreshTokenRequest = firstRequiredRole === Permissions['000'];

if (isRefreshTokenRequest && !token) {
throw new BadRequestException('Original access token is required');
throw new BadRequestException('O token de acesso é necessário');
}

if (isRefreshTokenRequest) {
Expand All @@ -97,7 +99,7 @@ export class RolesGuard implements CanActivate {

if (isInvalid) {
throw new ForbiddenException(
'You do not have permission to perform this action.'
'Você não tem permissão para realizar esta ação.'
);
}

Expand Down
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { SwaggerModule } from '@nestjs/swagger';
import helmet from 'helmet';

import ValidationPipe from './config/validation-pipe';
import { corsOptionsConfig } from './config/cors';
import { swaggerDocumentConfig } from './config/swagger';
import { AppModule } from './app.module';
Expand All @@ -12,7 +12,7 @@ async function bootstrap() {

app.use(helmet());
app.enableCors(corsOptionsConfig);
app.useGlobalPipes(new ValidationPipe());
app.useGlobalPipes(ValidationPipe);

const document = SwaggerModule.createDocument(app, swaggerDocumentConfig);
SwaggerModule.setup('', app, document);
Expand Down
34 changes: 22 additions & 12 deletions src/modules/Account/account.dtos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import {
IsStrongPassword,
IsBoolean,
IsHexadecimal,
IsString,
} from 'class-validator';
import { IsEqualTo } from 'src/utils/decorators/isEqualTo';
import { responses } from 'src/config/responses';

class AccountBaseDto {
id: string;
Expand All @@ -20,26 +22,29 @@ class AccountBaseDto {
updatedAt: Date;
}

// Create
const nameRegex = /^[a-zA-ZÀ-ÿ ]+$/;

export class CreateAccountControllerInput {
@IsNotEmpty()
@Matches(/^[a-zA-ZÀ-ÿ ]+$/)
@ApiProperty()
@ApiProperty({ example: 'John Doe' })
@IsNotEmpty({ message: responses.notEmpty })
@IsString({ message: responses.string })
@Matches(nameRegex, { message: responses.fullname })
name: string;

@IsNotEmpty()
@IsEmail()
@ApiProperty()
@ApiProperty({ example: 'example@string.com' })
@IsNotEmpty({ message: responses.notEmpty })
@IsString({ message: responses.string })
@IsEmail({}, { message: responses.email })
email: string;

@IsNotEmpty()
@IsStrongPassword()
@ApiProperty()
@ApiProperty({ example: 'strW#3' })
@IsNotEmpty({ message: responses.notEmpty })
@IsStrongPassword({ minLength: 6 }, { message: responses.strongPassword })
password: string;

@IsNotEmpty()
@IsBoolean()
@ApiProperty()
@IsNotEmpty({ message: responses.notEmpty })
@IsBoolean({ message: responses.boolean })
acceptedTerms?: boolean;
}

Expand Down Expand Up @@ -98,6 +103,7 @@ export class RefreshSessionControllerInput {

// password
export class ResetPasswordInput {
@ApiProperty()
@IsNotEmpty()
@IsEmail()
email: string;
Expand All @@ -108,18 +114,22 @@ export class ResetPasswordOutput {
}

export class ChangePasswordInput {
@ApiProperty()
@IsNotEmpty()
@IsStrongPassword()
password: string;

@ApiProperty()
@IsNotEmpty()
@IsStrongPassword()
@IsEqualTo('password')
repeatPassword: string;

@ApiProperty()
@IsNotEmpty()
code: string;

@ApiProperty()
@IsNotEmpty()
accountId: string;
}
Expand Down
4 changes: 2 additions & 2 deletions src/modules/Account/account.errors.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
export class AccountNotFoundError extends Error {
constructor() {
super('Account does not exists');
super('A conta não existe');
this.name = 'AccountNotFoundError';
}
}

export class InvalidCodeError extends Error {
constructor() {
super('Invalid code');
super('Código inválido');
this.name = 'InvalidCodeError';
}
}
Loading

0 comments on commit a8c6eae

Please sign in to comment.