diff --git a/src/excel/excel.controller.ts b/src/excel/excel.controller.ts index 182d6b1..4d5691f 100644 --- a/src/excel/excel.controller.ts +++ b/src/excel/excel.controller.ts @@ -1,14 +1,20 @@ -import { Controller, Get } from '@nestjs/common'; +import { Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { ApiTags, ApiConsumes } from '@nestjs/swagger'; import { ExcelService } from './excel.service'; +import { ApiReadEmails } from './excel.decorators'; +@ApiTags('Excel') // Agrupa los endpoints en Swagger @Controller('excel') export class ExcelController { constructor(private readonly excelService: ExcelService) {} - @Get('read-emails') - async getEmails() { - const filePath = './src/excel_Usuarios/usuarios_fake.xlsx'; - const emails = this.excelService.readEmailsFromExcel(filePath); + @Post('read-emails') + @UseInterceptors(FileInterceptor('file')) // Permite subir archivos + @ApiConsumes('multipart/form-data') // Indica que se usa un formulario con archivos + @ApiReadEmails() + async getEmails(@UploadedFile() file: Express.Multer.File) { + const emails = await this.excelService.readEmailsFromExcel(file); return { emails }; } } diff --git a/src/excel/excel.decorators.ts b/src/excel/excel.decorators.ts new file mode 100644 index 0000000..825066c --- /dev/null +++ b/src/excel/excel.decorators.ts @@ -0,0 +1,45 @@ +import { applyDecorators } from '@nestjs/common'; +import { ApiBody, ApiConsumes, ApiOperation, ApiResponse } from '@nestjs/swagger'; + +export const ApiReadEmails = () => { + return applyDecorators( + ApiOperation({ + summary: 'Leer correos desde un archivo Excel', + description: `Este endpoint permite subir un archivo Excel y extraer los correos electrónicos de la primera columna. +El archivo debe ser un **.xlsx** válido con una estructura adecuada.`, + }), + ApiConsumes('multipart/form-data'), + ApiBody({ + description: 'Sube un archivo Excel con correos', + schema: { + type: 'object', + properties: { + file: { + type: 'string', + format: 'binary', + }, + }, + }, + }), + ApiResponse({ + status: 200, + description: 'Lista de correos extraídos exitosamente', + schema: { + example: { + emails: ['usuario1@example.com', 'usuario2@example.com', 'usuario3@example.com'], + }, + }, + }), + ApiResponse({ + status: 400, + description: 'Error en la lectura del archivo Excel', + schema: { + example: { + statusCode: 400, + message: 'Formato de archivo inválido o columna de correos no encontrada', + error: 'Bad Request', + }, + }, + }), + ); +}; diff --git a/src/excel/excel.service.ts b/src/excel/excel.service.ts index 404b8f6..9dde3ff 100644 --- a/src/excel/excel.service.ts +++ b/src/excel/excel.service.ts @@ -4,17 +4,17 @@ import { Injectable } from '@nestjs/common'; @Injectable() export class ExcelService { - async readEmailsFromExcel(filePath: string): Promise { + async readEmailsFromExcel(file: Express.Multer.File) { - if (!fs.existsSync(filePath)) { + if (!file) { throw new Error('Archivo Excel no encontrado'); } const workbook = new ExcelJS.Workbook(); - await workbook.xlsx.readFile(filePath); // 🔹 `await` para leer el archivo correctamente + await workbook.xlsx.load(file.buffer); // 🔹 `await` para leer el archivo correctamente const worksheet = workbook.getWorksheet(1); // Obtiene la primera hoja if (!worksheet) { diff --git a/src/mail/mail.controller.ts b/src/mail/mail.controller.ts index d70de2f..7fe899a 100644 --- a/src/mail/mail.controller.ts +++ b/src/mail/mail.controller.ts @@ -7,11 +7,9 @@ import { } from '@nestjs/common'; import { MailService } from './mail.service'; import { ExcelService } from 'src/excel/excel.service'; -import { FileInterceptor } from '@nestjs/platform-express'; -import * as fs from 'fs'; -import * as csv from 'csv-parser'; -import { diskStorage } from 'multer'; // Importar para almacenar en el sistema de archivos import { ApiTags } from '@nestjs/swagger'; +import { ApiSendMail, ApiSendMailExcel} from './mail.decorators'; + @ApiTags('Mail') // Grupo de endpoints @Controller('mail') @@ -22,7 +20,9 @@ export class MailController { ) {} // Endpoint para enviar correos a través de una solicitud POST + @Post('send') + @ApiSendMail() async sendMail(@Body() body: { to: string; subject: string; text: string; fecha_recibido: Date }) { const { to, subject, text, fecha_recibido } = body; @@ -31,9 +31,10 @@ export class MailController { } @Post('send-excel') - async sendMailExcel() { + @ApiSendMailExcel() + async sendMailExcel(@UploadedFile() file: Express.Multer.File) { - const emails=this.excelService.readEmailsFromExcel('./src/excel_Usuarios/usuarios_fake.xlsx'); + const emails=this.excelService.readEmailsFromExcel(file); let fechaActual: Date = new Date(); for (const email of await emails) { @@ -45,46 +46,4 @@ export class MailController { } } } - - - @Post('send-bulk') - //@UseInterceptors(FileInterceptor('file')) - @UseInterceptors( - FileInterceptor('file', { - storage: diskStorage({ - destination: './uploads', // Carpeta donde guardar los archivos - filename: (req, file, cb) => { - // Guardar el archivo con su nombre original - const filename = file.originalname; - cb(null, filename); - }, - }), - }), - ) - async sendBulkMails(@UploadedFile() file: Express.Multer.File) { - const emails: string[] = []; - const subject = 'Correo de prueba'; - const text = 'Este es un correo enviado de manera masiva.'; - - // Leer el archivo CSV - return new Promise((resolve, reject) => { - fs.createReadStream(file.path) - .pipe(csv()) - .on('data', (row) => { - emails.push(row.email); // Agregar cada correo a la lista - }) - .on('end', async () => { - // Enviar correos en bulk usando el servicio de correos - const result = await this.mailService.sendBulkMails( - emails, - subject, - text, - ); - resolve(result); - }) - .on('error', (error) => { - reject(error); - }); - }); - } } diff --git a/src/mail/mail.decorators.ts b/src/mail/mail.decorators.ts new file mode 100644 index 0000000..312635f --- /dev/null +++ b/src/mail/mail.decorators.ts @@ -0,0 +1,91 @@ +import { applyDecorators } from '@nestjs/common'; +import { ApiBody, ApiConsumes, ApiOperation, ApiResponse } from '@nestjs/swagger'; + +export const ApiSendMail = () => { + return applyDecorators( + ApiOperation({ + summary: 'Enviar un correo manualmente', + description: `Este endpoint permite enviar un correo individualmente. Se requiere proporcionar: +- **to**: Dirección de correo del destinatario. +- **subject**: Asunto del correo. +- **text**: Cuerpo del mensaje. +- **fecha_recibido**: Fecha en que se registró la solicitud.`, + }), + ApiBody({ + description: 'Datos del correo a enviar', + schema: { + type: 'object', + properties: { + to: { type: 'string', example: 'usuario@example.com' }, + subject: { type: 'string', example: 'Asunto del correo' }, + text: { type: 'string', example: 'Cuerpo del mensaje' }, + fecha_recibido: { type: 'string', format: 'date-time', example: '2025-03-10T12:00:00Z' } + }, + }, + }), + ApiResponse({ + status: 200, + description: 'Correo enviado con éxito', + schema: { + example: { + message: 'Correo enviado correctamente', + }, + }, + }), + ApiResponse({ + status: 400, + description: 'Error en el envío del correo', + schema: { + example: { + statusCode: 400, + message: 'Dirección de correo inválida', + error: 'Bad Request', + }, + }, + }), + ); +}; + +export const ApiSendMailExcel = () => { + return applyDecorators( + ApiOperation({ + summary: 'Enviar correos desde un archivo Excel', + description: `Este endpoint permite enviar correos a partir de una lista contenida en un archivo Excel. +El archivo debe contener una columna con direcciones de correo válidas.`, + }), + ApiConsumes('multipart/form-data'), + ApiBody({ + description: 'Sube un archivo Excel con correos', + schema: { + type: 'object', + properties: { + file: { + type: 'string', + format: 'binary', + }, + }, + }, + }), + ApiResponse({ + status: 200, + description: 'Correos enviados exitosamente', + schema: { + example: { + message: 'Correos enviados correctamente', + emails: ['usuario1@example.com', 'usuario2@example.com'], + }, + }, + }), + ApiResponse({ + status: 400, + description: 'Error al procesar el archivo', + schema: { + example: { + statusCode: 400, + message: 'El archivo no es un Excel válido', + error: 'Bad Request', + }, + }, + }), + ); +}; diff --git a/src/mail/mail.service.ts b/src/mail/mail.service.ts index ceee383..44aeb15 100644 --- a/src/mail/mail.service.ts +++ b/src/mail/mail.service.ts @@ -12,7 +12,7 @@ export class MailService { private transporter; constructor( - @InjectRepository(Correo) private readonly correoRepository: Repository, + @InjectRepository(Correo) private readonly correoRepository: Repository, @InjectRepository(Status) private readonly statusRepository: Repository, ) { diff --git a/src/main.ts b/src/main.ts index f76bc8d..e91e9cf 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,22 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; +import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; + async function bootstrap() { const app = await NestFactory.create(AppModule); + + const config = new DocumentBuilder() + .setTitle('Documentacion de la api de votaciones v2') + .setDescription( + 'El proyecto ha sido refactorizado a una version mejorada con nestjs', + ) + .setVersion('1.0') + .addTag('description') + .build(); + const document = SwaggerModule.createDocument(app, config); + SwaggerModule.setup('documentation', app, document); + await app.listen(process.env.PORT ?? 3000); } bootstrap();