Skip to content

Commit

Permalink
Merge pull request #21 from loryblu/develop
Browse files Browse the repository at this point in the history
0.3.0 - api responses
  • Loading branch information
viniciuscosmome committed Sep 27, 2023
2 parents 115f14e + d1a0d5e commit 6cdd42e
Show file tree
Hide file tree
Showing 15 changed files with 293 additions and 131 deletions.
7 changes: 3 additions & 4 deletions src/globals/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ export const appLicense = process.env.npm_package_license;

export const fullnameRegExp = /^[a-zÀ-ÿ ]+$/i;
export const recoveryTokenRegExp = /^[a-zA-Z0-9_-]+$/;
export const childrenBirthDateExample = '2009-02-28';

export const isDevelopmentEnv = () => process.env.NODE_ENV === 'development';
export const isHomologationEnv = () => process.env.NODE_ENV === 'homologation';
export const isProductionEnv = () => process.env.NODE_ENV === 'production';
export const isDevelopmentEnv = process.env.NODE_ENV === 'development';
export const isHomologationEnv = process.env.NODE_ENV === 'homologation';
export const isProductionEnv = process.env.NODE_ENV === 'production';
6 changes: 2 additions & 4 deletions src/globals/cors.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface';

const whitelist = [];

export const corsOptionsConfig: CorsOptions = {
origin: whitelist,
origin: '*',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type, Authorization'],
allowedHeaders: ['Content-Type', 'Authorization'],
};
18 changes: 0 additions & 18 deletions src/globals/entity.d.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,3 @@
import type { ValidationOptions } from 'class-validator';

type Message = ValidationOptions['message'];

export type ValidationErrorMessagesProps = {
emptyField: Message;
booleanField: Message;
stringField: Message;
minLength: Message;
maxLength: Message;
fullnameField: Message;
emailPattern: Message;
passwordPattern: Message;
birthDatePattern: Message;
genderPattern: Message;
pattern: Message;
};

export type HashDataAsyncProps = {
unhashedData: string;
salt: string;
Expand Down
53 changes: 6 additions & 47 deletions src/globals/errors.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,6 @@
import {
BadRequestException,
InternalServerErrorException,
} from '@nestjs/common';
import { Prisma } from '@prisma/client';
import type { ValidationErrorMessagesProps } from './entity';
import { isDevelopmentEnv } from './constants';

export const validationErrorMessages: ValidationErrorMessagesProps = {
emptyField: (args) => {
return `O ${args.property} não pode estar vazio.`;
},
booleanField: (args) => {
return `O ${args.property} deve ser booleano.`;
},
stringField: (args) => {
return `O ${args.property} deve ser do tipo texto.`;
},
minLength: (args) => {
return `O ${args.property} deve conter no mínimo ${args.constraints[0]} caracteres.`;
},
maxLength: (args) => {
return `O ${args.property} deve conter no máximo ${args.constraints[1]} caracteres.`;
},
fullnameField: (args) => {
return `O ${args.property} deve conter apenas letras e espaço em branco.`;
},
emailPattern: () => {
return 'O e-mail informado deve ter um formato válido. ex: [email protected]';
},
passwordPattern: () => {
return 'A senha deve conter no mínimo uma letra maiúscula, uma letra minúscula, um número, um símbolo e no mínimo 8 caracteres.';
},
birthDatePattern: () => {
return `A data de nascimento informada deve ser do tipo texto e ser neste formato: YYYY-MM-DD`;
},
genderPattern: (args) => {
return `O sexo deve ser [${args.constraints[1]}].`;
},
pattern: (args) => {
return `O ${args.property} está em um formato inválido.`;
},
};
import { isProductionEnv } from './constants';
import { UnknownErrorException, P2002Exception } from './responses/exceptions';

export function prismaKnownRequestErrors(
error: Prisma.PrismaClientKnownRequestError,
Expand All @@ -49,18 +9,17 @@ export function prismaKnownRequestErrors(

switch (error.code) {
case 'P2002':
throw new BadRequestException(
`O ${target} informado já está em uso, tente outro.`,
);
throw new P2002Exception(target[0]);
}
}

export function unknownError(error: unknown) {
if (isDevelopmentEnv()) {
// ! remover quando adicionar um logger
if (!isProductionEnv) {
console.info('unknownError', error);
}

throw new InternalServerErrorException('Erro ao tentar realizar a ação.');
throw new UnknownErrorException();
}

export function hendleErrors(error: unknown) {
Expand Down
87 changes: 87 additions & 0 deletions src/globals/responses/docs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { ApiResponses } from './entity';

export const apiResponses: ApiResponses = {
ok: {
status: 200,
description: 'Ocorreu tudo bem.',
schema: {
example: {
message: 'description',
},
},
},
created: {
status: 201,
description: 'O dado enviado criou um novo registro na base de dados.',
schema: {
example: {
message: 'description',
},
},
},
accepted: {
status: 202,
description: 'Ocorreu tudo bem, mas pode demorar para terminar.',
schema: {
example: {
message: 'description',
},
},
},
badRequest: {
status: 400,
description: 'Algum dado fornecido é, de alguma forma, inválido.',
schema: {
example: {
statusCode: 400,
error: 'Bad Request',
message: 'description',
},
},
},
unauthorized: {
status: 401,
description: 'Requisição não foi autorizado.',
schema: {
example: {
statusCode: 401,
error: 'Unauthorized',
message: 'description',
},
},
},
forbidden: {
status: 403,
description: 'Você não tem permissão para executar essa ação.',
schema: {
example: {
statusCode: 403,
error: 'Forbidden',
message: 'description',
},
},
},
unprocessable: {
status: 422,
description:
'A informação foi entendida, não há erro na sintaxe mas não pode ser processada.',
schema: {
example: {
statusCode: 422,
error: 'Unprocessable',
message: 'description',
},
},
},
internalError: {
status: 500,
description: 'Ocorreu um erro desconhecido no servidor.',
schema: {
example: {
statusCode: 500,
error: 'Internal Server Error',
message: 'description',
},
},
},
};
28 changes: 28 additions & 0 deletions src/globals/responses/entity.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ApiResponseOptions } from '@nestjs/swagger';
import { ValidationOptions } from 'class-validator';

export type ApiResponses = {
ok: ApiResponseOptions;
created: ApiResponseOptions;
accepted: ApiResponseOptions;
badRequest: ApiResponseOptions;
unauthorized: ApiResponseOptions;
forbidden: ApiResponseOptions;
unprocessable: ApiResponseOptions;
internalError: ApiResponseOptions;
};

type Validator = ValidationOptions['message'];

export type Messages = {
notEmpty: Validator;
boolean: Validator;
string: Validator;
minLength: Validator;
email: Validator;
strongPassword: Validator;
fullnamePattern: Validator;
birthDatePattern: Validator;
genderPattern: Validator;
recoveryTokenPattern: Validator;
};
7 changes: 7 additions & 0 deletions src/globals/responses/exceptions/database.exceptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { UnprocessableEntityException } from '@nestjs/common';

export class P2002Exception extends UnprocessableEntityException {
constructor(target: string) {
super(`O ${target} informado já está em uso.`);
}
}
58 changes: 58 additions & 0 deletions src/globals/responses/exceptions/general.exceptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {
BadRequestException,
InternalServerErrorException,
UnauthorizedException,
} from '@nestjs/common';

/*
* 400 - BadRequestException
*/
export class PoliciesException extends BadRequestException {
constructor() {
super(
'Por favor, para ter uma conta você deve aceitar nossos termos de uso e políticas de privacidade.',
);
}
}

/*
* 401 - UnauthorizedException
*/
export class ExpiredRecoveryTokenException extends UnauthorizedException {
constructor() {
super('Token expirado, ou inválido.');
}
}

/*
* 500 -
*/
export class UnknownErrorException extends InternalServerErrorException {
constructor() {
super('Erro não conhecido ao tentar executar ação.');
}
}

export class EmailLoaderException extends InternalServerErrorException {
constructor() {
super('Erro durante a configuração do e-mail.');
}
}

export class SendEmailException extends InternalServerErrorException {
constructor() {
super('Erro ao tentar enviar um e-mail.');
}
}

export class TryingHashException extends InternalServerErrorException {
constructor() {
super('Erro ao tentar criar o hash de uma informação.');
}
}

export class TryingEncryptException extends InternalServerErrorException {
constructor() {
super('Erro ao tentar criptografar uma informação.');
}
}
2 changes: 2 additions & 0 deletions src/globals/responses/exceptions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './general.exceptions';
export * from './database.exceptions';
36 changes: 36 additions & 0 deletions src/globals/responses/validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Messages } from './entity';

export const messages: Messages = {
notEmpty: ({ property }) => {
return `${property}: Não pode estar vazio.`;
},
boolean: ({ property }) => {
return `${property}: Deve ser 'boolean'.`;
},
string: ({ property }) => {
return `${property}: Deve ser uma 'string'.`;
},
minLength: ({ property, constraints }) => {
const minLength = constraints[0];
return `${property}: Deve ter no mínimo ${minLength}.`;
},
email: ({ property }) => {
return `${property}: Deve conter um formato de e-mail válido.`;
},
strongPassword: ({ property }) => {
return `${property}: Deve conter uma letra maiúscula, minúscula, número, símbolo e no mínimo 8 caracteres.`;
},
fullnamePattern: ({ property }) => {
return `${property}: Deve conter apenas letras e espaço em branco entre palavras.`;
},
birthDatePattern: ({ property }) => {
return `${property}: Deve ser um texto nesse padrão, YYYY-MM-DD.`;
},
genderPattern: ({ property, constraints }) => {
const genders = constraints[1];
return `${property}: Deve ser um texto. [${genders}]`;
},
recoveryTokenPattern: ({ property }) => {
return `${property}: Deve ser um texto base64url`;
},
};
2 changes: 1 addition & 1 deletion src/globals/swagger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const swaggerDocumentConfig = new DocumentBuilder()
.setTitle(appName)
.setVersion(appVersion)
.setLicense(
appLicense,
`${appLicense} License`,
'https://github.com/loryblu/loryblu-api/blob/main/LICENSE',
)
.build();
Loading

0 comments on commit 6cdd42e

Please sign in to comment.