From 6fe658f668000cfae4b2eacb4b952ea46a205eac Mon Sep 17 00:00:00 2001 From: jorgemike Date: Tue, 13 May 2025 19:42:36 -0600 Subject: [PATCH] Refactor authentication and carousel services; enhance error handling and validation --- src/auth/auth.controller.ts | 9 ++-- src/auth/auth.docs.ts | 78 +++++++++++++++++++++++++++++ src/carrusel/carrusel.controller.ts | 12 +---- src/carrusel/carrusel.service.ts | 60 ++++++++++++++-------- src/entities/carrusel.entity.ts | 2 +- src/eventos/eventos.controller.ts | 2 +- src/main.ts | 5 ++ 7 files changed, 131 insertions(+), 37 deletions(-) create mode 100644 src/auth/auth.docs.ts diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 158331e..8f9ffe8 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -1,18 +1,19 @@ -import { Body, Controller, Post, Request, UseGuards } from '@nestjs/common'; -import { AuthGuard } from '@nestjs/passport'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { Body, Controller, Post } from '@nestjs/common'; import { AuthService } from './auth.service'; import { LoginMiembroDto } from './dto/loginMiembro.dto'; import { RegistrarMiembroDto } from './dto/registrarMiembro.dto'; +import { ApiTags } from '@nestjs/swagger'; +import { ApiPostRegistro } from './auth.docs'; @Controller('auth') +@ApiTags('auth') export class AuthController { constructor( private authService: AuthService, ) /* @InjectRepository(Usuario) private usuarioRepository: Repository */ {} @Post('registro') + @ApiPostRegistro() registrarUsuario(@Body() registrarMiembro: RegistrarMiembroDto) { return this.authService.registrar(registrarMiembro); } diff --git a/src/auth/auth.docs.ts b/src/auth/auth.docs.ts new file mode 100644 index 0000000..cd0cdec --- /dev/null +++ b/src/auth/auth.docs.ts @@ -0,0 +1,78 @@ +import { applyDecorators } from '@nestjs/common'; +import { + ApiOperation, + ApiResponse, + ApiTags, + ApiBody, +} from '@nestjs/swagger'; +import { RegistrarMiembroDto } from './dto/registrarMiembro.dto'; + +export function ApiPostRegistro() { + return applyDecorators( + ApiTags('Auth'), + ApiOperation({ + summary: 'Registrar nuevo miembro', + description: + 'Registra un nuevo usuario en la plataforma. No se permite duplicidad de correo electrónico.', + }), + ApiBody({ + type: RegistrarMiembroDto, + examples: { + requeridos: { + summary: 'Solo datos obligatorios', + description: 'Ejemplo básico con los campos mínimos requeridos para registrar un usuario.', + value: { + nombre: 'Luis', + apellido_paterno: 'Martínez', + apellido_materno: 'Lopez', + id_tipo_miembro: 1, + password: 'contraseñaSegura123', + cuenta: 421010101, + email: 'luis@example.com', + }, + }, + completo: { + summary: 'Todos los campos llenos', + description: 'Ejemplo de un usuario con carrera, semestre y descripción.', + value: { + nombre: 'Ana', + apellido_paterno: 'Torres', + apellido_materno: 'Hernández', + id_tipo_miembro: 2, + password: 'AnaSegura456', + descripcion: 'Estudiante interesada en programación web', + id_carrera: 3, + semestre: '4°', + cuenta: 421020202, + email: 'ana.torres@example.com', + }, + }, + invalido: { + summary: 'Ejemplo con datos inválidos', + description: 'Muestra un correo mal formado y una contraseña demasiado corta.', + value: { + nombre: 'Mario', + apellido_paterno: 'Gómez', + apellido_materno: 'Ramírez', + id_tipo_miembro: 1, + password: '123', + cuenta: 421000000, + email: 'mario-sin-arroba.com', + }, + }, + }, + }), + ApiResponse({ + status: 201, + description: 'Usuario registrado exitosamente.', + }), + ApiResponse({ + status: 403, + description: 'El usuario ya existe.', + }), + ApiResponse({ + status: 400, + description: 'Datos inválidos o faltantes.', + }), + ); +} diff --git a/src/carrusel/carrusel.controller.ts b/src/carrusel/carrusel.controller.ts index 16a0acd..8ad3d09 100644 --- a/src/carrusel/carrusel.controller.ts +++ b/src/carrusel/carrusel.controller.ts @@ -44,16 +44,8 @@ export class CarruselController { }, }), ) - addImagenToCarrusel( - @Body() carruselDto: carruselDto, - @UploadedFile() file, - @Res() res, - ) { - return this.carruselService.addImagenToCarrusel( - carruselDto, - file.filename, - res, - ); + addImagenToCarrusel(@Body() carruselDto: carruselDto, @UploadedFile() file) { + return this.carruselService.addImagenToCarrusel(carruselDto, file.filename); } @Delete('delete/:id') diff --git a/src/carrusel/carrusel.service.ts b/src/carrusel/carrusel.service.ts index 2e51d15..4a6c602 100644 --- a/src/carrusel/carrusel.service.ts +++ b/src/carrusel/carrusel.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable, InternalServerErrorException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Carrusel } from 'src/entities/carrusel.entity'; import { Repository } from 'typeorm'; @@ -13,31 +13,49 @@ export class CarruselService { private carruselRepository: Repository, ) {} - addImagenToCarrusel( + async addImagenToCarrusel( carruselDto: carruselDto, - filename, - res: Response, - ): Promise { + filename: string, + ): Promise<{ message: string; carrusel: Carrusel }> { + if (!filename) { + throw new BadRequestException({ + message: 'No se ha subido la imagen', + }); + } + + if (!carruselDto.link) { + throw new BadRequestException({ + message: 'El campo "link" es obligatorio', + }); + } + + if (carruselDto.link.length > 100) { + throw new BadRequestException({ + message: 'El campo "link" no puede tener más de 100 caracteres', + }); + } carruselDto.ruta = `/public/carrusel/${filename}`; const carruselCreate = this.carruselRepository.create(carruselDto); - return this.carruselRepository - .save(carruselCreate) - .then((carrusel) => { - res - .status(201) - .json({ message: 'Imagen agregada correctamente', carrusel }); - return carrusel; - }) - .catch((error) => { - res.status(500).json({ - message: 'Error al agregar la imagen', - error, - }); - throw error; + try { + const carrusel = await this.carruselRepository.save(carruselCreate); + return { + message: 'Imagen agregada correctamente', + carrusel, + }; + } catch (error) { + console.error('❌ Error al guardar carrusel en base de datos'); + console.table({ + code: error.code, + message: error.message, }); - } + throw new InternalServerErrorException({ + message: 'Ocurrió un error inesperado al guardar la imagen', + error: error.message, + }); + } + } async deleteImagenByRuta(id: number) { try { const res = await this.carruselRepository.findOne({ @@ -54,6 +72,6 @@ export class CarruselService { } getAllImages(): Promise { - return this.carruselRepository.find() + return this.carruselRepository.find(); } } diff --git a/src/entities/carrusel.entity.ts b/src/entities/carrusel.entity.ts index 7d092e0..4d8788a 100644 --- a/src/entities/carrusel.entity.ts +++ b/src/entities/carrusel.entity.ts @@ -9,7 +9,7 @@ export class Carrusel { ruta: string /* "/public/carrusel/carrusel1.jpg" */ - @Column({length: 60}) + @Column({length: 100}) link: string /* "https://www.acatlan.unam.mx/" */ } \ No newline at end of file diff --git a/src/eventos/eventos.controller.ts b/src/eventos/eventos.controller.ts index a484583..40db400 100644 --- a/src/eventos/eventos.controller.ts +++ b/src/eventos/eventos.controller.ts @@ -59,7 +59,7 @@ export class EventosController { this.eventoService.exportarParticipantes(id, res) } - @Post('disponibles') + @Get('disponibles') getEventosDisponibles (){ return this.eventoService.getEventosDisponibles() } diff --git a/src/main.ts b/src/main.ts index 708d899..d25772c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -20,7 +20,12 @@ async function bootstrap() { prefix: '/public/', }); + app.enableCors(); await app.listen(AppModule.port); + + const port = AppModule.port; + console.log(`Documentacion disponible en: http://localhost:${port}/api`); + console.log(`Servidor desplegado en: http://localhost:${port}`); } bootstrap();