Commit 62a4e29b authored by Vitoria Ebeling Hahn's avatar Vitoria Ebeling Hahn
Browse files

Merge branch 'dev' into feat/auth-guards

parents 14cd9e71 5becf787
Showing with 882 additions and 102 deletions
+882 -102
DB_TYPE='postgres'
DB_HOST='localhost'
DB_HOST='dpg-co32plo21fec738qvpp0-a.oregon-postgres.render.com'
DB_PORT=5432
DB_USER='admin'
DB_PSSWRD='admin123'
DB_USER='excedentes_admin'
DB_PSSWRD='MhkvGtNqe2WDRV0Q3fne7FFWWCjxkMkU'
DB_BASE='excedentes'
\ No newline at end of file
......@@ -13,6 +13,12 @@ export const dbdatasource: DataSourceOptions = {
database: process.env.DB_BASE,
// Synchronize database schema with entities
synchronize: true,
ssl: true,
extra: {
ssl: {
rejectUnauthorized: false,
},
},
// TypeORM Entity
entities: ['dist/src/api/**/*.entity.js'],
// Your Migration path
......
This diff is collapsed.
......@@ -14,7 +14,7 @@
"start": "nest start",
"start:dev": "cross-env NODE_ENV=dev nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "cross-env NODE_ENV=prod node dist/main",
"start:prod": "cross-env NODE_ENV=prod node dist/src/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
......@@ -31,6 +31,7 @@
"@nestjs/platform-express": "^10.0.0",
"@nestjs/swagger": "^7.3.0",
"@nestjs/typeorm": "^10.0.2",
"bcrypt": "^5.1.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"cross-env": "^7.0.3",
......@@ -54,6 +55,7 @@
"@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"bcrypt": "^5.1.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
......
import { Module } from '@nestjs/common';
import { ProductsModule } from 'src/api/products/products.module';
import { AuthModule } from './auth/auth.module';
import { ClientModule } from './client/client.module';
import { ContractorCompaniesModule } from './contractorCompanies/contractorCompanies.module';
@Module({
imports: [ClientModule, AuthModule, ContractorCompaniesModule],
imports: [
ClientModule,
ContractorCompaniesModule,
ProductsModule,
AuthModule,
],
providers: [],
})
export class ApiModule {}
......@@ -9,9 +9,10 @@ import { LoginDto } from './dto/auth.dto';
export class AuthController {
constructor(public authService: AuthService) {}
@ApiResponse({ status: 200, description: 'SignIn successfull.' })
@ApiResponse({ status: 201, description: 'SignIn successfull.' })
@ApiResponse({ status: 401, description: 'Unauthorized.' })
@UseGuards(AuthGuard('local'))
@ApiResponse({ status: 400, description: 'User not found.' })
@Post()
login(@Body() loginDto: LoginDto) {
return this.authService.signUser(loginDto);
......
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import {
BadRequestException,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ClientEntity } from 'src/api/client/client.entity';
import { ClientService } from 'src/api/client/domain/client.service';
import { ContractorCompaniesEntity } from 'src/api/contractorCompanies/contractorCompanies.entity';
import { ContractorCompaniesService } from 'src/api/contractorCompanies/domain/contractorCompanies.service';
import { LoginDto } from '../dto/auth.dto';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
......@@ -15,35 +20,40 @@ export class AuthService {
) {}
async login({ email, password }: LoginDto) {
try {
const client = await this.clientService.findByEmail(email);
if (client && client.password === password) {
const payload = { email, sub: client.id };
return {
access_token: this.jwtService.sign(payload, {
expiresIn: '7d',
}),
};
const client = await this.clientService.findByEmail(email);
if (client) {
const passwordMatch = await bcrypt.compare(password, client.password);
if (!passwordMatch) {
throw new UnauthorizedException(`Senha incorreta.`);
}
const payload = { email, sub: client.id };
return {
access_token: this.jwtService.sign(payload, {
expiresIn: '7d',
}),
userType: 'client',
};
}
const company = await this.companyService.findByEmail(email);
if (company && company.password === password) {
const payload = { email, sub: company.id };
return {
access_token: this.jwtService.sign(payload, {
expiresIn: '7d',
}),
};
const company = await this.companyService.findByEmail(email);
if (company) {
const passwordMatch = await bcrypt.compare(password, company.password);
if (!passwordMatch) {
throw new UnauthorizedException(`Senha incorreta.`);
}
} catch (error) {
throw new HttpException(
{
status: HttpStatus.BAD_REQUEST,
error: error.message,
},
HttpStatus.BAD_REQUEST,
);
const payload = { email, sub: company.id };
return {
access_token: this.jwtService.sign(payload, {
expiresIn: '7d',
}),
userType: 'company',
};
}
throw new BadRequestException(`Usuário não encontrado.`);
}
signUser(user: Partial<ClientEntity> | Partial<ContractorCompaniesEntity>) {
......
......@@ -11,10 +11,10 @@ export abstract class BaseEntity {
id: number;
@CreateDateColumn()
createdAt: string;
createdAt: Date;
@UpdateDateColumn()
updatedAt: string;
updatedAt: Date;
@DeleteDateColumn()
deletedAt?: Date;
......
import { Controller } from '@nestjs/common/decorators/core';
import {
Body,
Controller,
Delete,
Get,
HttpException,
HttpStatus,
Param,
Post,
Put,
Query,
} from '@nestjs/common';
import {
ApiOperation,
ApiParam,
ApiQuery,
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
import { ClientService } from './domain/client.service';
import { Get } from '@nestjs/common/decorators/http';
import { ApiTags, ApiResponse } from '@nestjs/swagger';
import { CreateClientDto } from './dto/createClient.dto';
@ApiTags('Cliente')
@Controller('client')
@ApiTags('Consumer')
@Controller('consumers')
export class ClientController {
constructor(public service: ClientService) {}
@ApiResponse({ status: 403, description: 'Forbidden.' })
@ApiResponse({ status: 200, description: 'Records successfully fetched.' })
@ApiOperation({ summary: 'Procura os consumidors cadastrados' })
@ApiQuery({
name: 'cpf_cnpj',
required: false,
description: 'CPF/CNPJ do Comsumidor',
})
@Get()
findAll() {
async findAll(@Query('cpf_cnpj') cpf_cnpj: string) {
if (cpf_cnpj) {
return await this.service.findOneBycpf_cnpj(cpf_cnpj);
}
return this.service.findAll();
}
@ApiOperation({ summary: 'Cadastra um consumidor' })
@ApiResponse({ status: 201, description: 'Cadastrado com sucesso.' })
@ApiResponse({ status: 400, description: 'Informações inválidas' })
@ApiResponse({ status: 409, description: 'Consumidor ja castrado' })
@Post()
async create(@Body() createdto: CreateClientDto) {
try {
const client = await this.service.create(createdto);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { password, ...res } = client;
return {
statuscode: HttpStatus.CREATED,
messagee: 'Consumidor cadastrado com sucesso',
data: res,
};
} catch (erro) {
throw new HttpException({ message: erro.message }, erro.status);
}
}
@ApiOperation({ summary: 'Atualiza um consumidor' })
@ApiResponse({ status: 200, description: 'Atualizado com sucesso.' })
@ApiResponse({ status: 404, description: 'Consumidor não cadastrado.' })
@ApiResponse({ status: 400, description: 'Informações inválidas.' })
@ApiResponse({ status: 403, description: 'Forbidden.' })
@Put(':cpfcnpj')
async update(
@Param('cpfcnpj') cpfcnpj: string,
@Body() updateCliente: CreateClientDto,
) {
return await this.service.update(cpfcnpj, updateCliente);
}
@ApiOperation({ summary: 'Deleta um consumidor' })
@ApiResponse({ status: 200, description: 'Consumidor deletado com sucesso.' })
@ApiResponse({ status: 404, description: 'Consumidor não cadastrado.' })
@ApiResponse({ status: 400, description: 'Informações inválidas.' })
@ApiResponse({ status: 403, description: 'Forbidden.' })
@ApiParam({
name: 'cpfcnpj',
type: String,
required: true,
description: 'CPF/CNPJ do cliente',
})
@Delete(':cpfcnpj')
async delete(@Param('cpfcnpj') cpfcnpj: string) {
return await this.service.delete(cpfcnpj);
}
}
......@@ -10,7 +10,7 @@ export class ClientEntity extends BaseEntity {
unique: true,
name: 'cpf_cnpj',
})
cpfcnpj: string;
cpf_cnpj: string;
@Column({
type: 'enum',
......@@ -42,3 +42,4 @@ export class ClientEntity extends BaseEntity {
})
password: string;
}
export { TipoCliente };
import { HttpException, HttpStatus, NotFoundException } from '@nestjs/common';
import { Injectable } from '@nestjs/common/decorators/core';
import { ClientEntity } from '../client.entity';
import { Repository } from 'typeorm';
import { DeepPartial, Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CreateClientDto } from '../dto/createClient.dto';
import * as bcrypt from 'bcrypt';
@Injectable()
export class ClientService {
......@@ -14,6 +17,65 @@ export class ClientService {
return this.clientsRepository.find();
}
async findOneBycpf_cnpj(cpf_cnpj: string): Promise<ClientEntity> {
const numbcpf = cpf_cnpj;
return await this.clientsRepository.findOne({
where: { cpf_cnpj: numbcpf },
});
}
async create(createdto: CreateClientDto): Promise<ClientEntity> {
const { email, cpf_cnpj, password } = createdto;
const existeEmail = await this.clientsRepository.findOne({
where: { email },
});
const existeCpf = await this.clientsRepository.findOne({
where: { cpf_cnpj },
});
const hashsenha = await bcrypt.hash(password, 10);
if (existeEmail) {
throw new HttpException(
'Este email já está sendo utilizado',
HttpStatus.CONFLICT,
);
}
if (existeCpf) {
throw new HttpException(
'Este CPF/CNPJ já está sendo utilizado',
HttpStatus.CONFLICT,
);
}
const entity = this.clientsRepository.create({
...createdto,
password: hashsenha,
} as DeepPartial<ClientEntity>);
return await this.clientsRepository.save(entity);
}
async update(
cpf_cnpj: string,
updatedto: CreateClientDto,
): Promise<ClientEntity> {
const criteria = { cpf_cnpj: cpf_cnpj };
const partialEntity = { ...criteria, ...updatedto };
await this.clientsRepository.update(criteria, partialEntity);
const entity = await this.clientsRepository.findOne({ where: criteria });
return entity!;
}
async delete(cpf_cnpj: string): Promise<void> {
const res = await this.clientsRepository.softDelete(cpf_cnpj);
if (res.affected == 0) {
throw new NotFoundException('Cliente com o CPF/CNPJ não encontrado');
}
}
async findByEmail(email: string): Promise<ClientEntity> {
return this.clientsRepository.findOne({ where: { email } });
}
......
import { IsNotEmpty, IsString, IsEmail } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { TipoCliente } from '../client.entity';
export class CreateClientDto {
@ApiProperty({
......@@ -10,7 +11,6 @@ export class CreateClientDto {
@IsString()
nome: string;
//TO-DO: add custom decorator por cpf/cnpj?
@ApiProperty({
example: '00100100101',
required: true,
......@@ -27,11 +27,19 @@ export class CreateClientDto {
@IsEmail()
email: string;
@ApiProperty({
example: 'Pessoa Juridica',
required: true,
})
@IsNotEmpty()
@IsString()
tipo: TipoCliente;
@ApiProperty({
example: 'Batata123',
required: true,
})
@IsNotEmpty()
@IsString()
senha: string;
password: string;
}
......@@ -65,18 +65,19 @@ export class ContractorCompaniesController {
}
@ApiOperation({ summary: 'Deleta uma empresa contratante' })
@ApiResponse({
status: 200,
description: 'Empresa Contratante deletada com sucesso.',
})
@ApiParam({
name: 'id',
type: String,
required: true,
description: 'ID da Empresa Contratante',
})
@ApiResponse({
status: 200,
description: 'Empresa Contratante deletada com sucesso.',
})
@Delete(':id')
async delete(@Param('id') id: string) {
return await this.service.delete(+id);
const message = await this.service.delete(+id);
return { message };
}
}
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
import { Entity, Column, OneToOne, JoinColumn } from 'typeorm';
import { BaseEntity } from '../base/entities/base.entity';
import { AddressEntity } from './address.entity';
@Entity('contractor-companies')
export class ContractorCompaniesEntity extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({
type: 'varchar',
length: 255,
......@@ -28,11 +26,9 @@ export class ContractorCompaniesEntity extends BaseEntity {
})
password: string;
@Column({
type: 'json',
nullable: false,
})
address: { formattedName: string; latitude: string; longitude: string };
@OneToOne(() => AddressEntity, { eager: true, cascade: true })
@JoinColumn()
address: AddressEntity;
@Column({
type: 'varchar',
......
......@@ -8,16 +8,21 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ContractorCompaniesEntity } from '../contractorCompanies.entity';
import { CreateContractorCompaniesDto } from '../dto/createContractorCompanies.dto';
import * as bcrypt from 'bcrypt';
import { AddressEntity } from '../address.entity';
@Injectable()
export class ContractorCompaniesService {
constructor(
@InjectRepository(ContractorCompaniesEntity)
private readonly contractorCompaniesRepository: Repository<ContractorCompaniesEntity>,
@InjectRepository(AddressEntity)
private readonly addressRepository: Repository<AddressEntity>,
) {}
async findAll(): Promise<ContractorCompaniesEntity[]> {
return await this.contractorCompaniesRepository.find();
return await this.contractorCompaniesRepository.find({
relations: ['address'],
});
}
async findByEmail(email: string): Promise<ContractorCompaniesEntity> {
......@@ -35,7 +40,7 @@ export class ContractorCompaniesService {
async create(
createDto: CreateContractorCompaniesDto,
): Promise<ContractorCompaniesEntity> {
const { email, cnpj } = createDto;
const { email, cnpj, password } = createDto;
const existingEmail = await this.contractorCompaniesRepository.findOne({
where: { email },
});
......@@ -51,7 +56,17 @@ export class ContractorCompaniesService {
throw new HttpException('CNPJ já em uso.', HttpStatus.CONFLICT);
}
const entity = this.contractorCompaniesRepository.create(createDto);
const hashedPassword = await bcrypt.hash(password, 10);
const address = this.addressRepository.create(createDto.address);
await this.addressRepository.save(address);
const entity = this.contractorCompaniesRepository.create({
...createDto,
password: hashedPassword,
address,
});
return await this.contractorCompaniesRepository.save(entity);
}
......@@ -64,19 +79,16 @@ export class ContractorCompaniesService {
...updateDto,
});
if (!entity) {
throw new NotFoundException(
`Contractor Companies with ID ${id} not found`,
);
throw new NotFoundException(`Empresa contratante não encontrada.`);
}
return await this.contractorCompaniesRepository.save(entity);
}
async delete(id: number): Promise<void> {
async delete(id: number): Promise<string> {
const result = await this.contractorCompaniesRepository.delete(id);
if (result.affected === 0) {
throw new NotFoundException(
`Contractor Companies with ID ${id} not found`,
);
throw new NotFoundException(`Empresa contratante não encontrada.`);
}
return `Empresa contratante deletada com sucesso.`;
}
}
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class AddressDTO {
@ApiProperty({
example: 'Rua Principal, 123, Cidade - Estado',
description: 'Endereço formatado',
required: true,
})
@IsNotEmpty({ message: 'Endereço é obrigatório.' })
@IsString()
formattedName: string;
@ApiProperty({
example: '-23.56168',
description: 'Latitude',
required: true,
})
@IsNotEmpty({ message: 'Latitude é obrigatória.' })
@IsString()
latitude: string;
@ApiProperty({
example: '-46.656139',
description: 'Longitude',
required: true,
})
@IsNotEmpty({ message: 'Longitude é obrigatória.' })
@IsString()
longitude: string;
}
......@@ -11,35 +11,7 @@ import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsCNPJ } from 'src/api/commons/validators/isCnpj';
import { IsEqualTo } from 'src/api/commons/validators/isEqualTo';
class AddressDto {
@ApiProperty({
example: 'Rua Principal, 123, Cidade - Estado',
description: 'Endereço formatado',
required: true,
})
@IsNotEmpty({ message: 'Endereço é obrigatório.' })
@IsString()
formattedName: string;
@ApiProperty({
example: '-23.56168',
description: 'Latitude',
required: true,
})
@IsNotEmpty({ message: 'Latitude é obrigatória.' })
@IsString()
latitude: string;
@ApiProperty({
example: '-46.656139',
description: 'Longitude',
required: true,
})
@IsNotEmpty({ message: 'Longitude é obrigatória.' })
@IsString()
longitude: string;
}
import { AddressDTO } from './adress.dto';
export class CreateContractorCompaniesDto {
@ApiProperty({
......@@ -74,9 +46,9 @@ export class CreateContractorCompaniesDto {
})
@IsNotEmpty({ message: 'Endereço é obrigatório.' })
@ValidateNested()
@Type(() => AddressDto)
@Type(() => AddressDTO)
@IsObject()
address: AddressDto;
address: AddressDTO;
@ApiProperty({
example: 'senhaSegura123',
......
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateProductDto } from '../dto/createProductDto';
import { ProductEntity } from '../product.entity';
@Injectable()
export class ProductsService {
constructor(
@InjectRepository(ProductEntity)
private readonly productRepository: Repository<ProductEntity>,
) {}
async findAll(): Promise<ProductEntity[]> {
return await this.productRepository.find();
}
// async findOne(id_alimento: number): Promise<ProductEntity> {
// const product = await this.productRepository.findOne({
// where: { id_alimento },
// });
// if (!product) {
// throw new NotFoundException(`Product with ID ${id_alimento} not found`);
// }
// return product;
// }
async create(product: CreateProductDto): Promise<ProductEntity> {
const newProduct = this.productRepository.create(product);
return await this.productRepository.save(newProduct);
}
// async update(
// id: number,
// updateDto: CreateProductDto,
// ): Promise<ProductEntity> {
// const entity = await this.productRepository.preload({
// id: id,
// ...updateDto,
// });
// if (!entity) {
// throw new NotFoundException(
// `Product with ID ${id} not found`,
// );
// }
// entity.updatedAt = new Date(Date.now());
// return await this.productRepository.save(entity);
// }
async delete(id: number): Promise<ProductEntity> {
const product = await this.productRepository.findOne({ where: { id } });
if (!product) {
throw new NotFoundException(
`Product with ID ${id} not found`,
);
}
product.deletedAt = new Date(Date.now());
return await this.productRepository.save(product);
}
}
\ No newline at end of file
import { ApiProperty } from '@nestjs/swagger';
import {
IsNotEmpty,
IsNumber,
IsOptional,
IsString,
IsUrl,
} from 'class-validator';
export class CreateProductDto {
@ApiProperty({
example: '1',
description: 'ID da empresa (temporario; será determinado pela empresa autenticada)',
required: true,
})
@IsNotEmpty({ message: 'Empresa nao pode ser vazia' })
@IsNumber()
company_id: number;
@ApiProperty({
example: 'Banana',
description: 'nome do produto',
required: true,
})
@IsNotEmpty({ message: 'Nome nao pode ser vazio ' })
@IsString()
name: string;
@ApiProperty({
example: 'descricao do produto',
description: 'descricao do produto',
required: false,
})
@IsOptional()
@IsString()
description: string;
@ApiProperty({
example: '1.00',
description: 'Preço do produto',
required: true,
})
@IsNotEmpty({ message: 'Este campo não pode estar vazio' })
@IsNumber()
price: number;
@ApiProperty({
example: '5',
description: 'Quantidade do produto em estoque',
required: true,
})
@IsNotEmpty({ message: 'Este campo não pode estar vazio' })
@IsNumber()
quantity: number;
@ApiProperty({
example: 'Tio Joao',
description: 'Marca do produto',
required: false,
})
@IsOptional()
@IsString()
brand: string;
@ApiProperty({
example: '2024-04-10',
description: 'Data de validade do produto',
required: true,
})
@IsNotEmpty({ message: 'Este campo não pode estar vazio' })
expiration_date: Date;
@ApiProperty({
example: 'Frutas',
description: 'Categoria do produto',
required: true,
})
@IsNotEmpty({ message: 'Este campo não pode estar vazio' })
@IsString()
category: string;
@ApiProperty({
example: 'https://www.google.com.br/image.png',
description: 'URL da imagem do produto',
required: false,
})
@IsOptional()
@IsUrl()
picture: string;
@ApiProperty({
example: '',
description: 'codigo de barras dos produtos',
required: false,
})
@IsOptional()
bar_code: string;
}
import { Column, Entity, JoinColumn, OneToMany } from 'typeorm';
import { BaseEntity } from '../base/entities/base.entity';
@Entity('alimentos')
export class ProductEntity extends BaseEntity {
@OneToMany(() => ProductEntity, (product) => product.company_id)
@JoinColumn()
company_id: number;
@Column({ type: 'varchar', length: 255, nullable: false })
name: string;
@Column({ type: 'varchar', length: 255, nullable: true })
description: string;
@Column({ type: 'numeric', precision: 10, scale: 2 })
price: number;
@Column({ type: 'int' })
quantity: number;
@Column({ length: 255, nullable: true })
brand: string;
@Column({ type: 'date' })
expiration_date: Date;
@Column({ length: 255 })
category: string;
@Column({ length: 255, nullable: true })
picture: string;
@Column({ length: 13, nullable: true })
bar_code: string;
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment