Se agregó la contraseña de acceso
This commit is contained in:
@@ -7,10 +7,7 @@ export class ExcelService {
|
||||
async readEmailsFromExcel(file: Express.Multer.File) {
|
||||
|
||||
|
||||
if (!file) {
|
||||
|
||||
throw new Error('Archivo Excel no encontrado');
|
||||
}
|
||||
|
||||
|
||||
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
|
||||
@@ -3,12 +3,16 @@ import {
|
||||
Post,
|
||||
Body,
|
||||
UploadedFile,
|
||||
UseInterceptors,
|
||||
Headers,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { MailService } from './mail.service';
|
||||
import { ExcelService } from 'src/excel/excel.service';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { ApiSendMail, ApiSendMailExcel} from './mail.decorators';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Sistema } from 'src/typeorm/votacionesPrueba.entity';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
|
||||
@ApiTags('Mail') // Grupo de endpoints
|
||||
@@ -16,30 +20,66 @@ import { ApiSendMail, ApiSendMailExcel} from './mail.decorators';
|
||||
export class MailController {
|
||||
constructor(
|
||||
private readonly mailService: MailService,
|
||||
private readonly excelService: ExcelService
|
||||
private readonly excelService: ExcelService,
|
||||
@InjectRepository(Sistema) private readonly sistemaRepository: Repository<Sistema>,
|
||||
|
||||
) {}
|
||||
|
||||
// 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;
|
||||
async sendMail(
|
||||
@Headers() headers:{password:string},
|
||||
@Body() body: { to: string; subject: string; text: string; fecha_recibido: Date }) {
|
||||
|
||||
const result = await this.mailService.sendMail(to, subject, text, fecha_recibido);
|
||||
|
||||
if (!headers.password) {
|
||||
throw new UnauthorizedException('Header "Password" es requerido');
|
||||
}
|
||||
|
||||
const sistem = await this.sistemaRepository.findOne({ where: { password: headers.password } });
|
||||
|
||||
if (!sistem) {
|
||||
throw new UnauthorizedException('Password incorrecto');
|
||||
}
|
||||
|
||||
|
||||
const { to, subject, text, fecha_recibido } = body;
|
||||
|
||||
const result = await this.mailService.sendMail(to, subject, text, fecha_recibido,sistem);
|
||||
return result;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Post('send-excel')
|
||||
@ApiSendMailExcel()
|
||||
async sendMailExcel(@UploadedFile() file: Express.Multer.File) {
|
||||
async sendMailExcel(@Headers() headers:{password:string},@Body() file: Express.Multer.File) {
|
||||
|
||||
if (!headers.password) {
|
||||
throw new UnauthorizedException('Header "Password" es requerido');
|
||||
}
|
||||
|
||||
const sistem = await this.sistemaRepository.findOne({ where: { password: headers.password } });
|
||||
|
||||
if (!sistem) {
|
||||
throw new UnauthorizedException('Password incorrecto');
|
||||
}
|
||||
|
||||
if (!file) {
|
||||
|
||||
throw new Error('Archivo Excel no encontrado');
|
||||
}
|
||||
|
||||
const emails=this.excelService.readEmailsFromExcel(file);
|
||||
let fechaActual: Date = new Date();
|
||||
|
||||
for (const email of await emails) {
|
||||
try {
|
||||
await this.mailService.sendMail(email, "subject", "text", fechaActual);
|
||||
await this.mailService.sendMail(email, "subject", "text", fechaActual,sistem);
|
||||
console.log(`📧 Enviado a: ${email}`);
|
||||
} catch (error) {
|
||||
console.error(`❌ Error enviando a ${email}:`, error.message);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import {
|
||||
Status,
|
||||
Correo,
|
||||
Sistema,
|
||||
}from 'src/typeorm/votacionesPrueba.entity'
|
||||
|
||||
@Module({
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
TypeOrmModule.forFeature([
|
||||
Status,
|
||||
Correo,
|
||||
Sistema,
|
||||
]),
|
||||
],
|
||||
controllers: [MailController],
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { MailService } from './mail.service';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Correo, Sistema, Status } from '../typeorm/votacionesPrueba.entity'; // Ajusta si es necesario
|
||||
|
||||
describe('MailService', () => {
|
||||
let service: MailService;
|
||||
let correoRepository: Repository<Correo>;
|
||||
let statusRepository: Repository<Status>;
|
||||
let sistemaRepository: Repository<Sistema>;
|
||||
|
||||
const mockCorreoRepository = {
|
||||
create: jest.fn(),
|
||||
save: jest.fn(),
|
||||
};
|
||||
|
||||
const mockStatusRepository = {
|
||||
findOne: jest.fn(),
|
||||
create: jest.fn(),
|
||||
save: jest.fn(),
|
||||
};
|
||||
|
||||
const mockSistemaRepository = {
|
||||
findOne: jest.fn(),
|
||||
create: jest.fn(),
|
||||
save: jest.fn(),
|
||||
};
|
||||
|
||||
const mockTransporter = {
|
||||
sendMail: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
MailService,
|
||||
{ provide: getRepositoryToken(Correo), useValue: mockCorreoRepository },
|
||||
{ provide: getRepositoryToken(Status), useValue: mockStatusRepository },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<MailService>(MailService);
|
||||
correoRepository = module.get<Repository<Correo>>(getRepositoryToken(Correo));
|
||||
statusRepository = module.get<Repository<Status>>(getRepositoryToken(Status));
|
||||
sistemaRepository = module.get<Repository<Sistema>>(getRepositoryToken(Sistema));
|
||||
|
||||
|
||||
service['transporter'] = mockTransporter;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('debe enviar un correo exitosamente y guardar en la base de datos', async () => {
|
||||
const to = 'test@example.com';
|
||||
const subject = 'Test Email';
|
||||
const text = 'Este es un correo de prueba';
|
||||
const fecha_recibido = new Date();
|
||||
const sistema = mockSistemaRepository.findOne.mockResolvedValue({id:1});
|
||||
|
||||
mockTransporter.sendMail.mockResolvedValue({ accepted: [to] });
|
||||
|
||||
mockStatusRepository.findOne.mockResolvedValue({ id: 1, status: 'Enviado' });
|
||||
|
||||
mockCorreoRepository.create.mockReturnValue({ id: 1 });
|
||||
|
||||
const result = await service.sendMail(to, subject, text, fecha_recibido,sistema);
|
||||
|
||||
expect(result).toBe('Enviado');
|
||||
|
||||
expect(mockTransporter.sendMail).toHaveBeenCalledWith({
|
||||
from: process.env.USER_GMAIL,
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
});
|
||||
|
||||
|
||||
expect(mockStatusRepository.findOne).toHaveBeenCalledWith({
|
||||
where: { status: 'Enviado' },
|
||||
});
|
||||
|
||||
expect(mockCorreoRepository.create).toHaveBeenCalled();
|
||||
expect(mockCorreoRepository.save).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
+9
-120
@@ -3,7 +3,7 @@ import * as nodemailer from 'nodemailer';
|
||||
import * as dotenv from 'dotenv';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Correo, Status } from 'src/typeorm/votacionesPrueba.entity';
|
||||
import { Correo, Status } from '../typeorm/votacionesPrueba.entity';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
@@ -22,8 +22,8 @@ export class MailService {
|
||||
this.transporter = nodemailer.createTransport({
|
||||
service: 'gmail',
|
||||
auth: {
|
||||
user: process.env.USER_GMAIL, // tu dirección de correo de Gmail
|
||||
pass: process.env.PASS_GMAIL, // contraseña de aplicación generada en Google
|
||||
user: process.env.USER_GMAIL,
|
||||
pass: process.env.PASS_GMAIL,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -40,33 +40,9 @@ export class MailService {
|
||||
}
|
||||
|
||||
// Función para generar el reporte de correos enviados y fallidos
|
||||
private generateReport(
|
||||
sentEmails: string[],
|
||||
failedEmails: { email: string; error: any }[],
|
||||
totalTime: number,
|
||||
) {
|
||||
console.log('--- Reporte de envío de correos ---');
|
||||
console.log(`Tiempo total de envío: ${totalTime} segundos`);
|
||||
|
||||
|
||||
console.log('\nCorreos enviados con éxito:');
|
||||
sentEmails.forEach((email) => console.log(email));
|
||||
|
||||
console.log('\nCorreos fallidos:');
|
||||
failedEmails.forEach((failed) =>
|
||||
console.log(`Correo: ${failed.email} - Error: ${failed.error.message}`),
|
||||
);
|
||||
|
||||
console.log(`\nTotal de correos enviados: ${sentEmails.length}`);
|
||||
console.log(`Total de correos fallidos: ${failedEmails.length}`);
|
||||
|
||||
if (failedEmails.length > 0) {
|
||||
console.log(
|
||||
'\nAlgunos correos fallaron. Inténtalos de nuevo de manera manual.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async sendMail(to: string, subject: string, text: string, fecha_recibido: Date) {
|
||||
async sendMail(to: string, subject: string, text: string, fecha_recibido: Date, sistema) {
|
||||
const mailOptions = {
|
||||
from: process.env.USER_GMAIL,
|
||||
to,
|
||||
@@ -88,7 +64,7 @@ export class MailService {
|
||||
|
||||
const info= this.correoRepository.create({
|
||||
id_status: status,
|
||||
|
||||
id_sistema: sistema,
|
||||
fecha_recibido: fecha_recibido,
|
||||
fecha_enviado: new Date(),
|
||||
destinatario: to,
|
||||
@@ -102,96 +78,9 @@ export class MailService {
|
||||
|
||||
|
||||
|
||||
return
|
||||
return (statusTexto);
|
||||
}
|
||||
|
||||
async sendBulkMailsOld(emails: string[], subject: string, text: string) {
|
||||
const batchSize = 100; // Número de correos por lote
|
||||
for (let i = 0; i < emails.length; i += batchSize) {
|
||||
const batch = emails.slice(i, i + batchSize);
|
||||
const promises = batch.map((email) =>
|
||||
this.transporter.sendMail({
|
||||
from: process.env.USER_GMAIL,
|
||||
to: email,
|
||||
subject,
|
||||
text,
|
||||
}),
|
||||
);
|
||||
|
||||
// Esperar que se envíen todos los correos del lote
|
||||
await Promise.all(promises);
|
||||
|
||||
// Delay entre lotes para no sobrecargar el servidor de Gmail
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000)); // 2 segundos de pausa
|
||||
}
|
||||
}
|
||||
|
||||
async sendBulkMails(emails: string[], subject: string, text: string) {
|
||||
const batchSize = 100;
|
||||
const sentEmails: string[] = [];
|
||||
const failedEmails: { email: string; error: any }[] = [];
|
||||
const startTime = Date.now(); // Inicia el temporizador
|
||||
|
||||
for (let i = 0; i < emails.length; i += batchSize) {
|
||||
const batch = emails.slice(i, i + batchSize);
|
||||
const promises = batch.map(async (email) => {
|
||||
try {
|
||||
await this.transporter.sendMail({
|
||||
from: process.env.USER_GMAIL,
|
||||
to: email,
|
||||
subject,
|
||||
text,
|
||||
});
|
||||
sentEmails.push(email); // Agregar a lista de enviados exitosamente
|
||||
} catch (error) {
|
||||
|
||||
failedEmails.push({ email, error }); // Agregar a lista de fallos
|
||||
}
|
||||
});
|
||||
|
||||
// Espera a que todos los correos del lote se envíen
|
||||
await Promise.all(promises);
|
||||
|
||||
// Delay para evitar saturar el servidor de correo
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
}
|
||||
|
||||
const totalTime = (Date.now() - startTime) / 1000; // Tiempo total en segundos
|
||||
|
||||
// Generar el reporte final
|
||||
this.generateReport(sentEmails, failedEmails, totalTime);
|
||||
}
|
||||
|
||||
async retryFailedEmails(
|
||||
failedEmails: { email: string; error: any }[],
|
||||
subject: string,
|
||||
text: string,
|
||||
) {
|
||||
const retrySuccess: string[] = [];
|
||||
const retryFailed: { email: string; error: any }[] = [];
|
||||
|
||||
for (const failed of failedEmails) {
|
||||
try {
|
||||
await this.transporter.sendMail({
|
||||
from: process.env.USER_GMAIL,
|
||||
to: failed.email,
|
||||
subject,
|
||||
text,
|
||||
});
|
||||
retrySuccess.push(failed.email); // Reintento exitoso
|
||||
} catch (error) {
|
||||
|
||||
retryFailed.push({ email: failed.email, error }); // Reintento fallido
|
||||
}
|
||||
}
|
||||
|
||||
console.log('--- Reintento de correos fallidos ---');
|
||||
console.log('\nCorreos reenviados con éxito:');
|
||||
retrySuccess.forEach((email) => console.log(email));
|
||||
|
||||
console.log('\nCorreos que fallaron nuevamente:');
|
||||
retryFailed.forEach((failed) =>
|
||||
console.log(`Correo: ${failed.email} - Error: ${failed.error.message}`),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle('Documentacion de la api de votaciones v2')
|
||||
.setTitle('Documentacion de la api de votaciones_mails v2')
|
||||
.setDescription(
|
||||
'El proyecto ha sido refactorizado a una version mejorada con nestjs',
|
||||
)
|
||||
|
||||
@@ -1,15 +1,4 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
export class Status {
|
||||
@PrimaryGeneratedColumn()
|
||||
id_status: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, unique:true})
|
||||
status: string;
|
||||
|
||||
correos: Correo[];
|
||||
}
|
||||
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToOne, JoinColumn } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
export class Sistema {
|
||||
@@ -17,31 +6,46 @@ export class Sistema {
|
||||
id_sistema: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 50 })
|
||||
refresh_token: string;
|
||||
password: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50})
|
||||
clave: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50})
|
||||
@Column({ type: 'varchar', length: 50 })
|
||||
nombre_sistema: string;
|
||||
|
||||
@OneToMany(() => Correo, (correo) => correo.id_sistema)
|
||||
correos: Correo[];
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class Status {
|
||||
@PrimaryGeneratedColumn()
|
||||
id_status: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, unique: true })
|
||||
status: string;
|
||||
|
||||
@OneToMany(() => Correo, (correo) => correo.id_status)
|
||||
correos: Correo[];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Entity()
|
||||
export class Correo {
|
||||
@PrimaryGeneratedColumn()
|
||||
id_correo: number;
|
||||
|
||||
@ManyToOne(() => Status, (status) => status.correos)
|
||||
@JoinColumn({ name: 'id_status' })
|
||||
id_status: Status;
|
||||
|
||||
|
||||
@ManyToOne(() => Sistema, (sistema) => sistema.correos)
|
||||
@JoinColumn({ name: 'id_sistema' })
|
||||
id_sistema: Sistema;
|
||||
|
||||
@Column({ type: 'datetime'})
|
||||
@Column({ type: 'datetime' })
|
||||
fecha_recibido: Date;
|
||||
|
||||
@Column({ type: 'datetime'})
|
||||
@Column({ type: 'datetime' })
|
||||
fecha_enviado: Date;
|
||||
|
||||
@Column({ type: 'varchar', length: 50 })
|
||||
|
||||
Reference in New Issue
Block a user