Add modulos de usuario
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
DATABASE_URL="postgresql://username:password@ip_server:port/database?schema=public"
|
DATABASE_URL="postgresql://username:password@ip_server:port/database?schema=public"
|
||||||
REDIS_URL="redis://username:password@ip_server:port"
|
REDIS_URL="redis://username:password@ip_server:port"
|
||||||
|
SESSION_TTL_SECONDS=3600
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
"@nestjs/core": "11.0.1",
|
"@nestjs/core": "11.0.1",
|
||||||
"@nestjs/platform-express": "11.0.1",
|
"@nestjs/platform-express": "11.0.1",
|
||||||
"@prisma/client": "6.14.0",
|
"@prisma/client": "6.14.0",
|
||||||
|
"bcrypt": "6.0.0",
|
||||||
"class-transformer": "0.5.1",
|
"class-transformer": "0.5.1",
|
||||||
"class-validator": "0.14.2",
|
"class-validator": "0.14.2",
|
||||||
"dayjs": "1.11.13",
|
"dayjs": "1.11.13",
|
||||||
@@ -36,6 +37,7 @@
|
|||||||
"@nestjs/cli": "11.0.0",
|
"@nestjs/cli": "11.0.0",
|
||||||
"@nestjs/schematics": "11.0.0",
|
"@nestjs/schematics": "11.0.0",
|
||||||
"@nestjs/testing": "11.0.1",
|
"@nestjs/testing": "11.0.1",
|
||||||
|
"@types/bcrypt": "6.0.0",
|
||||||
"@types/express": "5.0.0",
|
"@types/express": "5.0.0",
|
||||||
"@types/jest": "30.0.0",
|
"@types/jest": "30.0.0",
|
||||||
"@types/node": "22.10.7",
|
"@types/node": "22.10.7",
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ enum status_usuario {
|
|||||||
EXCLUIDO
|
EXCLUIDO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum eboolean {
|
||||||
|
V
|
||||||
|
F
|
||||||
|
}
|
||||||
|
|
||||||
model videos {
|
model videos {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
uuid String @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
uuid String @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
@@ -54,7 +59,7 @@ model Usuario {
|
|||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
uuid String @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
uuid String @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
email String @unique @db.VarChar(255)
|
email String @unique @db.VarChar(255)
|
||||||
email_verificado Boolean @default(false)
|
email_verificado eboolean @default(F)
|
||||||
password String @db.VarChar(255)
|
password String @db.VarChar(255)
|
||||||
nome String? @db.VarChar(100)
|
nome String? @db.VarChar(100)
|
||||||
sobrenome String? @db.VarChar(100)
|
sobrenome String? @db.VarChar(100)
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { PrismaModule } from './prisma/prisma.module';
|
import { PrismaModule } from './prisma/prisma.module';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
import { VideosModule } from './videos/videos.module';
|
import { VideosModule } from './videos/videos.module';
|
||||||
|
import { UsuariosModule } from './usuarios/usuarios.module';
|
||||||
|
import { RedisModule } from './redis/redis.module';
|
||||||
|
import { AuthModule } from './auth/auth.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [PrismaModule, VideosModule],
|
imports: [
|
||||||
|
ConfigModule.forRoot({ isGlobal: true }),
|
||||||
|
PrismaModule,
|
||||||
|
RedisModule,
|
||||||
|
AuthModule,
|
||||||
|
VideosModule,
|
||||||
|
UsuariosModule,
|
||||||
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
})
|
})
|
||||||
|
|||||||
0
src/redis/redis.module.ts
Normal file
0
src/redis/redis.module.ts
Normal file
19
src/usuarios/dto/create-usuario-dto.ts
Normal file
19
src/usuarios/dto/create-usuario-dto.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { IsString, MaxLength } from 'class-validator';
|
||||||
|
|
||||||
|
export class CreateUsuarioDto {
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(244)
|
||||||
|
nome!: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(244)
|
||||||
|
sobrenome!: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(244)
|
||||||
|
email!: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(244)
|
||||||
|
password!: string;
|
||||||
|
}
|
||||||
53
src/usuarios/dto/usuarios.response.ts
Normal file
53
src/usuarios/dto/usuarios.response.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { Expose, Transform, TransformFnParams } from 'class-transformer';
|
||||||
|
import { papel_usuario, status_usuario, eboolean } from 'generated/prisma';
|
||||||
|
|
||||||
|
export class UsuariosResponseDto {
|
||||||
|
@Expose()
|
||||||
|
id!: number;
|
||||||
|
|
||||||
|
@Expose({ name: 'nome' })
|
||||||
|
nome!: string | null;
|
||||||
|
|
||||||
|
@Expose()
|
||||||
|
sobrenome!: string | null;
|
||||||
|
|
||||||
|
@Expose()
|
||||||
|
uuid!: string | null;
|
||||||
|
|
||||||
|
@Expose()
|
||||||
|
email!: string | null;
|
||||||
|
|
||||||
|
@Expose({ name: 'email_verificado' })
|
||||||
|
email_verificado!: eboolean;
|
||||||
|
|
||||||
|
@Expose()
|
||||||
|
papel!: papel_usuario;
|
||||||
|
|
||||||
|
@Expose()
|
||||||
|
status!: status_usuario;
|
||||||
|
|
||||||
|
@Expose({ name: 'criado_em' })
|
||||||
|
@Transform(
|
||||||
|
({ value }: TransformFnParams) =>
|
||||||
|
value ? dayjs(value as Date | string).format('DD/MM/YYYY HH:mm:ss') : '',
|
||||||
|
{ toPlainOnly: true },
|
||||||
|
)
|
||||||
|
criado_em!: string;
|
||||||
|
|
||||||
|
@Expose({ name: 'atualizado_em' })
|
||||||
|
@Transform(
|
||||||
|
({ value }: TransformFnParams) =>
|
||||||
|
value ? dayjs(value as Date | string).format('DD/MM/YYYY HH:mm:ss') : '',
|
||||||
|
{ toPlainOnly: true },
|
||||||
|
)
|
||||||
|
atualizado_em!: string;
|
||||||
|
|
||||||
|
@Expose({ name: 'bloqueado_ate' })
|
||||||
|
@Transform(
|
||||||
|
({ value }: TransformFnParams) =>
|
||||||
|
value ? dayjs(value as Date | string).format('DD/MM/YYYY HH:mm:ss') : '',
|
||||||
|
{ toPlainOnly: true },
|
||||||
|
)
|
||||||
|
bloqueado_ate!: string;
|
||||||
|
}
|
||||||
47
src/usuarios/usuarios.controller.ts
Normal file
47
src/usuarios/usuarios.controller.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
UsePipes,
|
||||||
|
ValidationPipe,
|
||||||
|
Patch,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { UsuariosService } from './usuarios.service';
|
||||||
|
import { UsuariosResponseDto } from './dto/usuarios.response';
|
||||||
|
import { CreateUsuarioDto } from './dto/create-usuario-dto';
|
||||||
|
|
||||||
|
@Controller('usuarios')
|
||||||
|
export class UsuariosController {
|
||||||
|
constructor(private readonly usuariosService: UsuariosService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async list(): Promise<UsuariosResponseDto[]> {
|
||||||
|
return this.usuariosService.list();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':uuid')
|
||||||
|
async get(@Param('uuid') uuid: string): Promise<UsuariosResponseDto> {
|
||||||
|
return this.usuariosService.get(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@UsePipes(
|
||||||
|
new ValidationPipe({
|
||||||
|
whitelist: true,
|
||||||
|
forbidNonWhitelisted: true,
|
||||||
|
transform: true,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
async create(@Body() body: CreateUsuarioDto): Promise<UsuariosResponseDto> {
|
||||||
|
return this.usuariosService.create(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':uuid/email-verificado')
|
||||||
|
async emailVerificado(
|
||||||
|
@Param('uuid') uuid: string,
|
||||||
|
): Promise<UsuariosResponseDto> {
|
||||||
|
return this.usuariosService.emailVerificado(uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/usuarios/usuarios.module.ts
Normal file
11
src/usuarios/usuarios.module.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { UsuariosService } from './usuarios.service';
|
||||||
|
import { UsuariosController } from './usuarios.controller';
|
||||||
|
import { PrismaModule } from '../prisma/prisma.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule],
|
||||||
|
providers: [UsuariosService],
|
||||||
|
controllers: [UsuariosController],
|
||||||
|
})
|
||||||
|
export class UsuariosModule {}
|
||||||
141
src/usuarios/usuarios.service.ts
Normal file
141
src/usuarios/usuarios.service.ts
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { plainToInstance } from 'class-transformer';
|
||||||
|
|
||||||
|
import { Prisma, Usuario } from 'generated/prisma';
|
||||||
|
|
||||||
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
import { PaginatedResponse } from '../shared/dto/paginated';
|
||||||
|
import { UsuariosResponseDto } from './dto/usuarios.response';
|
||||||
|
import { CreateUsuarioDto } from './dto/create-usuario-dto';
|
||||||
|
|
||||||
|
const SELECT = {
|
||||||
|
id: true,
|
||||||
|
uuid: true,
|
||||||
|
email: true,
|
||||||
|
email_verificado: true,
|
||||||
|
nome: true,
|
||||||
|
sobrenome: true,
|
||||||
|
papel: true,
|
||||||
|
status: true,
|
||||||
|
ultimo_login_em: true,
|
||||||
|
ultimo_login_ip: true,
|
||||||
|
tentativas_login_falhadas: true,
|
||||||
|
bloqueado_ate: true,
|
||||||
|
criado_em: true,
|
||||||
|
atualizado_em: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UsuariosService {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async list(): Promise<UsuariosResponseDto[]> {
|
||||||
|
const data = await this.prisma.usuario.findMany({
|
||||||
|
where: { email: { not: 'admin@clipperia.com' } },
|
||||||
|
orderBy: { id: 'desc' },
|
||||||
|
select: SELECT,
|
||||||
|
});
|
||||||
|
|
||||||
|
return plainToInstance(UsuariosResponseDto, data, {
|
||||||
|
excludeExtraneousValues: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async listPaginated(
|
||||||
|
page: number,
|
||||||
|
perPage: number,
|
||||||
|
direction: 'asc' | 'desc' = 'desc',
|
||||||
|
): Promise<PaginatedResponse<UsuariosResponseDto>> {
|
||||||
|
const skip = page >= 1 ? page * perPage : 0;
|
||||||
|
|
||||||
|
const [rows, total] = await Promise.all([
|
||||||
|
this.prisma.usuario.findMany({
|
||||||
|
orderBy: { id: direction },
|
||||||
|
skip,
|
||||||
|
take: perPage ?? 1,
|
||||||
|
select: SELECT,
|
||||||
|
}),
|
||||||
|
this.prisma.usuario.count(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const content: UsuariosResponseDto[] = plainToInstance(
|
||||||
|
UsuariosResponseDto,
|
||||||
|
rows,
|
||||||
|
{ excludeExtraneousValues: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const totalPages = Math.max(1, Math.ceil(total / perPage));
|
||||||
|
|
||||||
|
return {
|
||||||
|
content,
|
||||||
|
pagination: {
|
||||||
|
page,
|
||||||
|
direction,
|
||||||
|
perPage,
|
||||||
|
total,
|
||||||
|
totalPages,
|
||||||
|
hasNext: page < totalPages,
|
||||||
|
hasPrev: page > 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(uuid: string): Promise<UsuariosResponseDto> {
|
||||||
|
const row = await this.prisma.usuario.findUnique({
|
||||||
|
where: { uuid },
|
||||||
|
select: SELECT,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!row) {
|
||||||
|
throw new NotFoundException('Usuário não encontrado');
|
||||||
|
}
|
||||||
|
|
||||||
|
return plainToInstance(UsuariosResponseDto, row, {
|
||||||
|
excludeExtraneousValues: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: number, data: Prisma.UsuarioUpdateInput): Promise<Usuario> {
|
||||||
|
return this.prisma.usuario.update({
|
||||||
|
where: { id },
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(dto: CreateUsuarioDto): Promise<UsuariosResponseDto> {
|
||||||
|
const { email, password, nome, sobrenome } = dto;
|
||||||
|
|
||||||
|
const senhaCriptografada = await bcrypt.hash(password, 10);
|
||||||
|
|
||||||
|
const usuario = await this.prisma.usuario.create({
|
||||||
|
data: {
|
||||||
|
email,
|
||||||
|
password: senhaCriptografada,
|
||||||
|
nome,
|
||||||
|
sobrenome,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return plainToInstance(UsuariosResponseDto, usuario, {
|
||||||
|
excludeExtraneousValues: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(id: number): Promise<Usuario> {
|
||||||
|
return this.prisma.usuario.delete({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async emailVerificado(uuid: string): Promise<UsuariosResponseDto> {
|
||||||
|
const usuario = await this.prisma.usuario.update({
|
||||||
|
where: { uuid },
|
||||||
|
data: { email_verificado: 'V' },
|
||||||
|
});
|
||||||
|
|
||||||
|
return plainToInstance(UsuariosResponseDto, usuario, {
|
||||||
|
excludeExtraneousValues: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,8 +11,8 @@ import { videos, Prisma, video_situation } from 'generated/prisma';
|
|||||||
|
|
||||||
import { VideosService } from './videos.service';
|
import { VideosService } from './videos.service';
|
||||||
import { VideoResponseDto } from './dto/video-response.dto';
|
import { VideoResponseDto } from './dto/video-response.dto';
|
||||||
import { PaginatedQueryDto, PaginatedResponse } from './dto/paginated.dto';
|
import { PaginatedQueryDto, PaginatedResponse } from '../shared/dto/paginated';
|
||||||
import { EBooleanPipe } from './videos.pipe';
|
import { EBooleanPipe } from '../shared/pipe';
|
||||||
|
|
||||||
@Controller('videos')
|
@Controller('videos')
|
||||||
export class VideosController {
|
export class VideosController {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Prisma, videos, video_situation } from 'generated/prisma';
|
|||||||
|
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
import { VideoResponseDto } from './dto/video-response.dto';
|
import { VideoResponseDto } from './dto/video-response.dto';
|
||||||
import { PaginatedResponse } from './dto/paginated.dto';
|
import { PaginatedResponse } from '../shared/dto/paginated';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class VideosService {
|
export class VideosService {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { INestApplication } from '@nestjs/common';
|
import { INestApplication } from '@nestjs/common';
|
||||||
import * as request from 'supertest';
|
import request from 'supertest';
|
||||||
import { App } from 'supertest/types';
|
import { App } from 'supertest/types';
|
||||||
import { AppModule } from './../src/app.module';
|
import { AppModule } from './../src/app.module';
|
||||||
|
|
||||||
@@ -16,10 +16,6 @@ describe('AppController (e2e)', () => {
|
|||||||
await app.init();
|
await app.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('/ (GET)', () => {
|
it('/ (GET)', () =>
|
||||||
return request(app.getHttpServer())
|
request(app.getHttpServer()).get('/').expect(200).expect('Hello World!'));
|
||||||
.get('/')
|
|
||||||
.expect(200)
|
|
||||||
.expect('Hello World!');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user