Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 648e980fa8 | |||
| cbdcd0ca1e | |||
| 7110104e90 | |||
| d0da4e81cf | |||
| 58997d6d8c | |||
| 8f39fb86b3 | |||
| c4fd905317 | |||
| 4a24eafa0d | |||
| a220f2933d | |||
| 904db0549f | |||
| ccf476bd6e | |||
| 8949c57bdb | |||
| 336bf545b7 | |||
| 66c81120aa | |||
| 9e8d339e11 | |||
| 50ac4081a9 |
Generated
+5832
-3737
File diff suppressed because it is too large
Load Diff
+30
-24
@@ -20,52 +20,58 @@
|
|||||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@nestjs/bull": "^11.0.2",
|
||||||
"@nestjs/common": "^11.0.1",
|
"@nestjs/common": "^11.0.1",
|
||||||
"@nestjs/config": "^4.0.1",
|
"@nestjs/config": "^4.0.1",
|
||||||
"@nestjs/core": "^11.0.1",
|
"@nestjs/core": "^11.0.1",
|
||||||
|
"@nestjs/jwt": "^11.0.2",
|
||||||
|
"@nestjs/passport": "^11.0.5",
|
||||||
"@nestjs/platform-express": "^11.0.1",
|
"@nestjs/platform-express": "^11.0.1",
|
||||||
"@nestjs/swagger": "^11.0.5",
|
"@nestjs/swagger": "^11.4.5",
|
||||||
"@nestjs/typeorm": "^11.0.0",
|
"@nestjs/typeorm": "^11.0.3",
|
||||||
"class-validator": "^0.14.1",
|
"bull": "^4.16.5",
|
||||||
"csv-parser": "^3.2.0",
|
"class-transformer": "^0.5.1",
|
||||||
"dotenv": "^16.4.7",
|
"class-validator": "^0.14.4",
|
||||||
|
"csv-parser": "^3.2.1",
|
||||||
|
"dotenv": "^17.4.2",
|
||||||
"exceljs": "^4.4.0",
|
"exceljs": "^4.4.0",
|
||||||
"fs": "^0.0.1-security",
|
"multer": "^1.4.5-lts.2",
|
||||||
"multer": "^1.4.5-lts.1",
|
"mysql2": "^3.23.1",
|
||||||
"mysql2": "^3.13.0",
|
"nodemailer": "^9.0.3",
|
||||||
"nodemailer": "^6.10.0",
|
"passport": "^0.7.0",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"typeorm": "^0.3.21"
|
"typeorm": "^1.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.2.0",
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
"@eslint/js": "^9.18.0",
|
"@eslint/js": "^9.39.5",
|
||||||
"@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.1.28",
|
||||||
"@swc/cli": "^0.6.0",
|
"@swc/cli": "^0.8.1",
|
||||||
"@swc/core": "^1.10.7",
|
"@swc/core": "^1.15.46",
|
||||||
"@types/express": "^5.0.0",
|
"@types/bull": "^3.15.9",
|
||||||
|
"@types/express": "^5.0.6",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/multer": "^1.4.12",
|
"@types/multer": "^1.4.13",
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.20.1",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
"@types/swagger-ui-express": "^4.1.8",
|
"@types/swagger-ui-express": "^4.1.8",
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^9.18.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-prettier": "^5.2.2",
|
"eslint-plugin-prettier": "^5.5.6",
|
||||||
"globals": "^15.14.0",
|
"globals": "^15.14.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.9.6",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"supertest": "^7.0.0",
|
"supertest": "^7.2.2",
|
||||||
"ts-jest": "^29.2.5",
|
"ts-jest": "^29.4.12",
|
||||||
"ts-loader": "^9.5.2",
|
"ts-loader": "^9.6.2",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"typescript-eslint": "^8.20.0"
|
"typescript-eslint": "^8.65.0"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleFileExtensions": [
|
"moduleFileExtensions": [
|
||||||
|
|||||||
BIN
Binary file not shown.
+16
-7
@@ -2,31 +2,40 @@ import { Module } from '@nestjs/common';
|
|||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
import { MailModule } from './mail/mail.module';
|
import { MailModule } from './mail/mail.module';
|
||||||
import {ExcelModule} from './excel/excel.module'
|
import { ExcelModule } from './excel/excel.module'
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { SistemaModule } from './sistema/sistema.module';
|
import { SistemaModule } from './sistema/sistema.module';
|
||||||
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot(),
|
ConfigModule.forRoot(
|
||||||
|
{ isGlobal: true }
|
||||||
|
),
|
||||||
|
|
||||||
|
JwtModule.register({
|
||||||
|
secret: process.env.JWT_SECRET,
|
||||||
|
|
||||||
|
}),
|
||||||
|
|
||||||
TypeOrmModule.forRoot({
|
TypeOrmModule.forRoot({
|
||||||
type: 'mysql',
|
type: 'mysql',
|
||||||
host: process.env.db_host,
|
host: process.env.db_host,
|
||||||
username: process.env.db_username,
|
username: process.env.db_username,
|
||||||
database: process.env.db_database,
|
database: process.env.db_database,
|
||||||
password: process.env.db_password,
|
password: process.env.db_password,
|
||||||
port: Number(process.env.db_port),
|
port: Number(process.env.db_port),
|
||||||
synchronize: true,
|
synchronize: true,
|
||||||
dropSchema: true, // elimina la base de datos
|
dropSchema: false, // elimina la base de datos
|
||||||
// logging: true, // Habilita los logs para depuración
|
// logging: true, // Habilita los logs para depuración
|
||||||
autoLoadEntities: true, // Carga automáticamente las entidades
|
autoLoadEntities: true, // Carga automáticamente las entidades
|
||||||
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
MailModule,
|
MailModule,
|
||||||
ExcelModule,
|
ExcelModule,
|
||||||
SistemaModule,
|
SistemaModule,
|
||||||
@@ -36,4 +45,4 @@ import { SistemaModule } from './sistema/sistema.module';
|
|||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule { }
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// auth.module.ts
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ConfigModule, // Ya es global, pero lo puedes dejar
|
||||||
|
JwtModule.register({
|
||||||
|
secret: process.env.JWT_SECRET
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
],
|
||||||
|
providers: [AuthService],
|
||||||
|
exports: [AuthService, JwtModule],
|
||||||
|
})
|
||||||
|
export class AuthModule { }
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthService {
|
||||||
|
constructor(private jwtService: JwtService) {}
|
||||||
|
|
||||||
|
generarToken(payload: any) {
|
||||||
|
console.log('JWT_SECRET:', process.env.JWT_SECRET);
|
||||||
|
return this.jwtService.sign(payload, {
|
||||||
|
secret: process.env.JWT_SECRET,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async verificarToken(token: string) {
|
||||||
|
try {
|
||||||
|
const result = await this.jwtService.verify(token, {
|
||||||
|
secret: process.env.JWT_SECRET,
|
||||||
|
});
|
||||||
|
console.log('Resultado de verificación:', result);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Token inválido o expirado');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import * as crypto from 'crypto';
|
||||||
|
|
||||||
|
export function encrypt(text: string) {
|
||||||
|
const secret = process.env.KEY_SECRET;
|
||||||
|
if (!secret) throw new Error('Falta la variable de entorno KEY_SECRET');
|
||||||
|
|
||||||
|
const key = crypto.scryptSync(secret, 'salt', 32);
|
||||||
|
const iv = crypto.randomBytes(16);
|
||||||
|
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
|
||||||
|
|
||||||
|
let encrypted = cipher.update(text, 'utf8', 'hex');
|
||||||
|
encrypted += cipher.final('hex');
|
||||||
|
|
||||||
|
return {
|
||||||
|
iv: iv.toString('hex'),
|
||||||
|
encryptedData: encrypted,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decrypt(encryptedData: string, ivHex: string) {
|
||||||
|
const secret = process.env.KEY_SECRET;
|
||||||
|
if (!process.env.KEY_SECRET) {
|
||||||
|
throw new Error('Falta la variable de entorno KEY_SECRET');
|
||||||
|
}
|
||||||
|
if (!secret) throw new Error('Falta la variable de entorno KEY_SECRET');
|
||||||
|
|
||||||
|
const key = crypto.scryptSync(secret, 'salt', 32);
|
||||||
|
const iv = Buffer.from(ivHex, 'hex');
|
||||||
|
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
|
||||||
|
|
||||||
|
let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
|
||||||
|
decrypted += decipher.final('utf8');
|
||||||
|
|
||||||
|
return decrypted;
|
||||||
|
}
|
||||||
@@ -7,14 +7,14 @@ import { ApiReadEmails } from './excel.decorators';
|
|||||||
@ApiTags('Excel') // Agrupa los endpoints en Swagger
|
@ApiTags('Excel') // Agrupa los endpoints en Swagger
|
||||||
@Controller('excel')
|
@Controller('excel')
|
||||||
export class ExcelController {
|
export class ExcelController {
|
||||||
constructor(private readonly excelService: ExcelService) {}
|
constructor(private readonly excelService: ExcelService) { }
|
||||||
|
|
||||||
@Post('read-emails')
|
// @Post('read-emails')
|
||||||
@UseInterceptors(FileInterceptor('file')) // Permite subir archivos
|
// @UseInterceptors(FileInterceptor('file')) // Permite subir archivos
|
||||||
@ApiConsumes('multipart/form-data') // Indica que se usa un formulario con archivos
|
// @ApiConsumes('multipart/form-data') // Indica que se usa un formulario con archivos
|
||||||
@ApiReadEmails()
|
// @ApiReadEmails()
|
||||||
async getEmails(@UploadedFile() file: Express.Multer.File) {
|
// async getEmails(@UploadedFile() file: Express.Multer.File) {
|
||||||
const emails = await this.excelService.readEmailsFromExcel(file);
|
// const emails = await this.excelService.readEmailsFromExcel(file);
|
||||||
return { emails };
|
// return { emails };
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-27
@@ -4,42 +4,42 @@ import { Injectable } from '@nestjs/common';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExcelService {
|
export class ExcelService {
|
||||||
async readEmailsFromExcel(file: Express.Multer.File) {
|
// async readEmailsFromExcel(file: Express.Multer.File) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const workbook = new ExcelJS.Workbook();
|
|
||||||
await workbook.xlsx.load(file.buffer); // 🔹 `await` para leer el archivo correctamente
|
|
||||||
|
|
||||||
const worksheet = workbook.getWorksheet(1); // Obtiene la primera hoja
|
|
||||||
if (!worksheet) {
|
|
||||||
throw new Error(' No se encontró ninguna hoja en el archivo.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const jsonData: any[] = [];
|
|
||||||
|
|
||||||
// 🔹 Iterar sobre las filas y extraer datos
|
// const workbook = new ExcelJS.Workbook();
|
||||||
worksheet.eachRow((row, rowNumber) => {
|
// await workbook.xlsx.load(file.buffer); // 🔹 `await` para leer el archivo correctamente
|
||||||
if (rowNumber === 1) return; // Ignorar encabezado
|
|
||||||
|
|
||||||
const rowData = {
|
// const worksheet = workbook.getWorksheet(1); // Obtiene la primera hoja
|
||||||
Correo: row.getCell(1).value, // Ajusta el índice según tu archivo
|
// if (!worksheet) {
|
||||||
};
|
// throw new Error(' No se encontró ninguna hoja en el archivo.');
|
||||||
|
// }
|
||||||
|
|
||||||
jsonData.push(rowData);
|
// const jsonData: any[] = [];
|
||||||
});
|
|
||||||
|
|
||||||
console.log(` Datos extraídos:`, jsonData);
|
// // 🔹 Iterar sobre las filas y extraer datos
|
||||||
|
// worksheet.eachRow((row, rowNumber) => {
|
||||||
|
// if (rowNumber === 1) return; // Ignorar encabezado
|
||||||
|
|
||||||
// 🔹 Extraer correos de la columna "Correo"
|
// const rowData = {
|
||||||
const emails = jsonData
|
// Correo: row.getCell(1).value, // Ajusta el índice según tu archivo
|
||||||
.map(row => row.Correo)
|
// };
|
||||||
.filter(email => typeof email === 'string');
|
|
||||||
|
|
||||||
console.log(` Correos extraídos:`, emails);
|
// jsonData.push(rowData);
|
||||||
|
// });
|
||||||
|
|
||||||
return emails;
|
// console.log(` Datos extraídos:`, jsonData);
|
||||||
}
|
|
||||||
|
// // 🔹 Extraer correos de la columna "Correo"
|
||||||
|
// const emails = jsonData
|
||||||
|
// .map(row => row.Correo)
|
||||||
|
// .filter(email => typeof email === 'string');
|
||||||
|
|
||||||
|
// console.log(` Correos extraídos:`, emails);
|
||||||
|
|
||||||
|
// return emails;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { IsString, IsEmail, IsDate, IsOptional } from 'class-validator';
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
|
|
||||||
|
export class SendCorreoDto {
|
||||||
|
@IsEmail()
|
||||||
|
to: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
subject: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
text: string;
|
||||||
|
|
||||||
|
@IsDate()
|
||||||
|
@Type(() => Date)
|
||||||
|
fecha_recibido: Date;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
html: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
adjuntos: any;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+62
-59
@@ -2,98 +2,101 @@ import {
|
|||||||
Controller,
|
Controller,
|
||||||
Post,
|
Post,
|
||||||
Body,
|
Body,
|
||||||
UploadedFile,
|
|
||||||
Headers,
|
Headers,
|
||||||
UnauthorizedException,
|
UnauthorizedException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { MailService } from './mail.service';
|
import { MailService } from './mail.service';
|
||||||
import { ExcelService } from 'src/excel/excel.service';
|
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
import { ApiSendMail, ApiSendMailExcel} from './mail.decorators';
|
import { ApiSendMail } from './mail.decorators';
|
||||||
import { Repository } from 'typeorm';
|
import { SistemaService } from 'src/sistema/sistema.service';
|
||||||
import { Sistema } from 'src/typeorm/votacionesPrueba.entity';
|
import { SendCorreoDto } from 'src/mail/dto/send-email.dto';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { AuthService } from 'src/auth/auth.service';
|
||||||
|
|
||||||
|
|
||||||
@ApiTags('Mail') // Grupo de endpoints
|
@ApiTags('Mail') // Grupo de endpoints
|
||||||
@Controller('mail')
|
@Controller('mail')
|
||||||
export class MailController {
|
export class MailController {
|
||||||
constructor(
|
constructor(
|
||||||
|
private readonly sistemaService: SistemaService,
|
||||||
private readonly mailService: MailService,
|
private readonly mailService: MailService,
|
||||||
private readonly excelService: ExcelService,
|
// private readonly excelService: ExcelService,
|
||||||
@InjectRepository(Sistema) private readonly sistemaRepository: Repository<Sistema>,
|
private readonly authService: AuthService,
|
||||||
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
// Endpoint para enviar correos a través de una solicitud POST
|
// Endpoint para enviar correos a través de una solicitud POST
|
||||||
|
|
||||||
@Post('send')
|
@Post('send')
|
||||||
@ApiSendMail()
|
@ApiSendMail()
|
||||||
async sendMail(
|
async sendMail(
|
||||||
@Headers() headers:{token:string},
|
@Headers() headers: { token: string },
|
||||||
@Body() body: { to: string; subject: string; text: string; fecha_recibido: Date, html:string, adjuntos:any }) {
|
@Body() body: SendCorreoDto,
|
||||||
|
) {
|
||||||
|
if (!headers.token) {
|
||||||
|
throw new UnauthorizedException('Header "token" es requerido');
|
||||||
|
}
|
||||||
|
|
||||||
// esta parte hay que rehacerla, se tiene que obtener la contraseña del sistema en base a la clave del sistema.
|
const result = await this.mailService
|
||||||
// debe de existir un modulo para crear contraseñas para las apis y administrar esas contraseñas
|
.sendMail(body, headers.token)
|
||||||
|
.then((value) => {
|
||||||
|
console.log('ya acabe el correo', value);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// sistem
|
||||||
/* if (!headers.token) {
|
|
||||||
throw new UnauthorizedException('Header "Password" es requerido');
|
|
||||||
} */
|
|
||||||
|
|
||||||
/* const sistem = await this.sistemaRepository.findOne({ where: { token: headers.token } });
|
|
||||||
|
|
||||||
if (!sistem) {
|
|
||||||
throw new UnauthorizedException('Password incorrecto');
|
|
||||||
} */
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
console.log("estos son los archivos adjuntos en el controller",body);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const { to, subject, text, html, fecha_recibido, adjuntos } = body;
|
|
||||||
|
|
||||||
const result = await this.mailService.sendMail(to, subject, text,html, fecha_recibido,1,adjuntos); // sistem
|
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('masivo')
|
||||||
|
async enviarMasivo(
|
||||||
|
@Body() data: SendCorreoDto[],
|
||||||
|
@Headers() headers: { token: string },
|
||||||
|
) {
|
||||||
|
return await this.mailService.sendBulkMail(data, headers.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
@Post('send-excel')
|
@Post('send-excel')
|
||||||
|
@UseInterceptors(FileInterceptor('file'))
|
||||||
@ApiSendMailExcel()
|
@ApiSendMailExcel()
|
||||||
async sendMailExcel(@Headers() headers:{password:string},@Body() file: Express.Multer.File) {
|
async sendMailExcel(
|
||||||
|
@Headers() headers: { token: string },
|
||||||
if (!headers.password) {
|
@UploadedFile() file: Express.Multer.File
|
||||||
throw new UnauthorizedException('Header "Password" es requerido');
|
) {
|
||||||
|
if (!headers.token) {
|
||||||
|
throw new UnauthorizedException('Header "token" es requerido');
|
||||||
}
|
}
|
||||||
|
|
||||||
const sistem = await this.sistemaRepository.findOne({ where: { token: headers.password } });
|
let payload;
|
||||||
|
try {
|
||||||
|
payload = await this.jwtService.verify(headers.token);
|
||||||
|
} catch {
|
||||||
|
throw new UnauthorizedException('Token inválido');
|
||||||
|
}
|
||||||
|
|
||||||
|
const sistem = await this.sistemaService.findByNombre(payload.nombre);
|
||||||
if (!sistem) {
|
if (!sistem) {
|
||||||
throw new UnauthorizedException('Password incorrecto');
|
throw new UnauthorizedException('Sistema no encontrado');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
|
|
||||||
throw new Error('Archivo Excel no encontrado');
|
throw new Error('Archivo Excel no encontrado');
|
||||||
}
|
}
|
||||||
|
|
||||||
const emails=this.excelService.readEmailsFromExcel(file);
|
const emails = await this.excelService.readEmailsFromExcel(file);
|
||||||
let fechaActual: Date = new Date();
|
const fechaActual: Date = new Date();
|
||||||
|
|
||||||
for (const email of await emails) {
|
for (const email of emails) {
|
||||||
try {
|
try {
|
||||||
await this.mailService.sendMail(email, "subject", "text","html", fechaActual,sistem);
|
await this.mailService.sendMail(email, 'subject', 'text', 'html', fechaActual, sistem);
|
||||||
console.log(`📧 Enviado a: ${email}`);
|
console.log(`Enviado a: ${email}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`❌ Error enviando a ${email}:`, error.message);
|
console.error(`Error enviando a ${email}:`, error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { message: 'Correos enviados (si no hubo errores)' };
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-70
@@ -1,88 +1,63 @@
|
|||||||
import { applyDecorators } from '@nestjs/common';
|
import { applyDecorators } from '@nestjs/common';
|
||||||
import { ApiBody, ApiConsumes, ApiHeader, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
import { ApiOperation, ApiBody, ApiHeader } from '@nestjs/swagger';
|
||||||
|
|
||||||
export const ApiSendMail = () => {
|
export const ApiSendMail = () => {
|
||||||
return applyDecorators(
|
return applyDecorators(
|
||||||
ApiOperation({
|
ApiOperation({
|
||||||
summary: 'Enviar un correo manualmente',
|
summary: 'Enviar un correo electrónico desde el sistema autenticado',
|
||||||
description: `Este endpoint permite enviar un correo individualmente. Se requiere proporcionar:
|
description: `Este endpoint permite a un sistema autenticado enviar un correo electrónico utilizando su configuración registrada.
|
||||||
- **to**: Dirección de correo del destinatario.
|
Debe incluirse un token JWT válido en los encabezados (headers).
|
||||||
- **subject**: Asunto del correo.
|
El sistema debe proporcionar el destinatario, asunto, contenido del mensaje y credenciales de su correo.`,
|
||||||
- **text**: Cuerpo del mensaje.
|
}),
|
||||||
- **fecha_recibido**: Fecha en que se registró la solicitud.
|
|
||||||
- **sistema**: contraseña del sistema.
|
|
||||||
- **html**: se puede mandar html
|
|
||||||
- **adjuntos**:mandar qr
|
|
||||||
(Se requiere una contraseña de aplicación
|
|
||||||
No caducan automáticamente.
|
|
||||||
Pero se invalidan si:
|
|
||||||
Cambias la contraseña de tu cuenta.
|
|
||||||
Desactivas la verificación en dos pasos.
|
|
||||||
Revocas el acceso desde https://myaccount.google.com/apppasswords.
|
|
||||||
Google detecta actividad sospechosa o problemas de seguridad.))`,
|
|
||||||
|
|
||||||
}),
|
|
||||||
ApiHeader({
|
ApiHeader({
|
||||||
name: 'password',
|
name: 'token',
|
||||||
description: 'Contraseña del sistema para autenticación',
|
description: 'Token JWT del sistema para autenticación',
|
||||||
required: true,
|
required: true,
|
||||||
example: 'mi-password-segura'
|
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
||||||
}),
|
}),
|
||||||
ApiBody({
|
ApiBody({
|
||||||
description: 'Datos del correo a enviar',
|
description: 'Datos del correo a enviar',
|
||||||
schema: {
|
schema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
to: { type: 'string', example: 'usuario@example.com' },
|
to: {
|
||||||
subject: { type: 'string', example: 'Asunto del correo' },
|
type: 'string',
|
||||||
text: { type: 'string', example: 'Cuerpo del mensaje' },
|
format: 'email',
|
||||||
fecha_recibido: { type: 'string', format: 'date-time', example: '2025-03-10T12:00:00Z' }
|
example: 'destinatario@example.com',
|
||||||
|
},
|
||||||
|
subject: {
|
||||||
|
type: 'string',
|
||||||
|
example: 'Informe Semanal',
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
type: 'string',
|
||||||
|
example: 'Aquí está el informe solicitado...',
|
||||||
|
},
|
||||||
|
html: {
|
||||||
|
type: 'string',
|
||||||
|
example: '<p>Este es un <b>mensaje</b> en HTML</p>',
|
||||||
|
},
|
||||||
|
fecha_recibido: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'date-time',
|
||||||
|
example: '2025-05-01T15:00:00Z',
|
||||||
|
},
|
||||||
|
adjuntos: {
|
||||||
|
type: 'array',
|
||||||
|
description: 'Archivos adjuntos en base64',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
filename: { type: 'string', example: 'reporte.pdf' },
|
||||||
|
content: { type: 'string', example: 'base64string...' },
|
||||||
|
encoding: { type: 'string', example: 'base64' },
|
||||||
|
cid: { type: 'string', example: 'file123' }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
required: ['to', 'fecha_recibido'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ import {
|
|||||||
Status,
|
Status,
|
||||||
Correo,
|
Correo,
|
||||||
Sistema,
|
Sistema,
|
||||||
}from 'src/typeorm/votacionesPrueba.entity'
|
}from 'src/typeorm/entidades'
|
||||||
import { SistemaModule } from 'src/sistema/sistema.module';
|
import { SistemaModule } from 'src/sistema/sistema.module';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import { AuthService } from 'src/auth/auth.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [MailService],
|
providers: [MailService,JwtService,AuthService],
|
||||||
imports: [
|
imports: [
|
||||||
ExcelModule,
|
ExcelModule,
|
||||||
SistemaModule,
|
SistemaModule,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
|
|||||||
import { MailService } from './mail.service';
|
import { MailService } from './mail.service';
|
||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { Correo, Sistema, Status } from '../typeorm/votacionesPrueba.entity'; // Ajusta si es necesario
|
import { Correo, Sistema, Status } from '../typeorm/entidades'; // Ajusta si es necesario
|
||||||
|
|
||||||
describe('MailService', () => {
|
describe('MailService', () => {
|
||||||
let service: MailService;
|
let service: MailService;
|
||||||
|
|||||||
+268
-118
@@ -1,68 +1,51 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
import * as nodemailer from 'nodemailer';
|
import * as nodemailer from 'nodemailer';
|
||||||
import * as dotenv from 'dotenv';
|
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { Correo, Status } from '../typeorm/votacionesPrueba.entity';
|
import { Correo, Sistema, Status } from '../typeorm/entidades';
|
||||||
|
import { SistemaService } from 'src/sistema/sistema.service';
|
||||||
dotenv.config();
|
import { SendCorreoDto } from 'src/mail/dto/send-email.dto';
|
||||||
|
import { AuthService } from 'src/auth/auth.service';
|
||||||
|
import { decrypt } from '../crypto/crypto.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MailService {
|
export class MailService {
|
||||||
private transporter;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Correo) private readonly correoRepository: Repository<Correo>,
|
private readonly sistemaService: SistemaService,
|
||||||
@InjectRepository(Status) private readonly statusRepository: Repository<Status>,
|
private readonly authService: AuthService,
|
||||||
|
@InjectRepository(Correo)
|
||||||
) {
|
private readonly correoRepository: Repository<Correo>,
|
||||||
|
@InjectRepository(Status)
|
||||||
|
private readonly statusRepository: Repository<Status>,
|
||||||
|
@InjectRepository(Sistema)
|
||||||
/* this.transporter = nodemailer.createTransport({
|
private readonly sistemaRepositosistemaRepositoryry: Repository<Sistema>,
|
||||||
host: 'smtp-relay.gmail.com',
|
) {
|
||||||
port: 587,
|
/* this.transporter = nodemailer.createTransport({
|
||||||
secure: false,
|
host: 'smtp-relay.gmail.com',
|
||||||
requireTLS: true,
|
port: 587,
|
||||||
auth: {
|
secure: false,
|
||||||
user: process.env.USER_GMAIL,
|
requireTLS: true,
|
||||||
pass: process.env.PASS_GMAIL,
|
auth: {
|
||||||
},
|
user: process.env.USER_GMAIL,
|
||||||
}); */
|
pass: process.env.PASS_GMAIL,
|
||||||
|
},
|
||||||
this.transporter = nodemailer.createTransport({
|
}); */
|
||||||
host: 'smtp.gmail.com', // o smtp‑relay.gmail.com (ver punto 2)
|
/*
|
||||||
port: 587,
|
this.transporter = nodemailer.createTransport({
|
||||||
secure: false,
|
host: 'smtp.gmail.com', // o smtp‑relay.gmail.com (ver punto 2)
|
||||||
requireTLS: true, // TLS STARTTLS
|
port: 587,
|
||||||
auth: { user: process.env.USER_GMAIL, pass: process.env.PASS_GMAIL },
|
secure: false,
|
||||||
pool: true, // <‑‑ activa reuse
|
requireTLS: true, // TLS STARTTLS
|
||||||
maxConnections: 1, // una sola conexión viva
|
auth: { user: process.env.USER_GMAIL, pass: process.env.PASS_GMAIL },
|
||||||
maxMessages: 100, // reabrir después de 100 envíos
|
pool: true, // <‑‑ activa reuse
|
||||||
rateDelta: 2000, // ventana 1 s
|
maxConnections: 1, // una sola conexión viva
|
||||||
rateLimit: 1 // máx. 5 mensajes/seg
|
maxMessages: 100, // reabrir después de 100 envíos
|
||||||
});
|
rateDelta: 2000, // ventana 1 s
|
||||||
|
rateLimit: 1 // máx. 5 mensajes/seg
|
||||||
|
});
|
||||||
|
|
||||||
/* this.transporter = nodemailer.createTransport({
|
|
||||||
host: 'smtp-relay.gmail.com', // o smtp‑relay.gmail.com (ver punto 2)
|
|
||||||
port: 587,
|
|
||||||
secure: false,
|
|
||||||
requireTLS: true, // TLS STARTTLS
|
|
||||||
//auth: { user: process.env.USER_GMAIL, pass: process.env.PASS_GMAIL },
|
|
||||||
name:"acatlan.unam.mx",
|
|
||||||
|
|
||||||
pool: true, // <‑‑ activa reuse
|
|
||||||
maxConnections: 1, // una sola conexión viva
|
|
||||||
maxMessages: 100, // reabrir después de 100 envíos
|
|
||||||
rateDelta: 2000, // ventana 1 s
|
|
||||||
rateLimit: 1 // máx. 5 mensajes/seg
|
|
||||||
}); */
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
this.transporter = nodemailer.createTransport({
|
this.transporter = nodemailer.createTransport({
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
@@ -72,85 +55,252 @@ export class MailService {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// Función para generar el reporte de correos enviados y fallidos
|
async findAllBySistema(TOKEN: string, nombreSistema: string) {
|
||||||
|
if (!TOKEN) {
|
||||||
|
throw new UnauthorizedException('Header "Password" es requerido');
|
||||||
|
}
|
||||||
|
|
||||||
|
const sistem = await this.sistemaService.acceso(TOKEN);
|
||||||
|
if (!sistem) {
|
||||||
|
throw new UnauthorizedException('Password incorrecto');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.correoRepository.find({
|
||||||
|
where: {
|
||||||
|
id_sistema: { nombre_sistema: nombreSistema },
|
||||||
|
},
|
||||||
|
relations: {
|
||||||
|
id_sistema: true,
|
||||||
|
id_status: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendMail(sendEmail: SendCorreoDto, token: string) {
|
||||||
|
let payload;
|
||||||
|
|
||||||
|
payload = await this.authService.verificarToken(token);
|
||||||
|
|
||||||
|
if (!payload || !payload.nombre) {
|
||||||
|
throw new UnauthorizedException('Token inválido o incompleto');
|
||||||
|
}
|
||||||
|
|
||||||
|
const sistem = await this.sistemaService.findByNombre(payload.nombre);
|
||||||
|
|
||||||
|
if (!sistem) {
|
||||||
|
throw new UnauthorizedException('sistema No encontrado');
|
||||||
|
}
|
||||||
|
|
||||||
|
const decryptedPassword = decrypt(
|
||||||
|
sistem.email_password_encrypted,
|
||||||
|
sistem.email_password_iv,
|
||||||
|
);
|
||||||
|
|
||||||
|
const decryptedToken = decrypt(
|
||||||
|
sistem.refreshToken_encrypted,
|
||||||
|
sistem.refreshToken_iv,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('sistem', {
|
||||||
|
type: 'OAuth2',
|
||||||
|
user: sistem.email,
|
||||||
|
clientId: sistem.clientId,
|
||||||
|
clientSecret: decryptedPassword,
|
||||||
|
refreshToken: decryptedToken,
|
||||||
|
//accessToken: sistem.accessToken, // opcional
|
||||||
|
});
|
||||||
|
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
service: 'gmail',
|
||||||
|
auth: {
|
||||||
|
type: 'OAuth2',
|
||||||
|
user: sistem.email,
|
||||||
|
clientId: sistem.clientId,
|
||||||
|
clientSecret: decryptedPassword,
|
||||||
|
refreshToken: decryptedToken,
|
||||||
|
//accessToken: sistem.accessToken, // opcional
|
||||||
|
},
|
||||||
|
pool: true,
|
||||||
|
maxConnections: 1,
|
||||||
|
maxMessages: 100,
|
||||||
|
rateDelta: 2000,
|
||||||
|
rateLimit: 1,
|
||||||
|
});
|
||||||
|
|
||||||
async sendMail(to: string, subject: string, text: string, html: string, fecha_recibido: Date, sistema, adjuntos?: any[] ) {
|
|
||||||
|
|
||||||
console.log("estos son los archivos adjuntos en el service",adjuntos);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const mailOptions = {
|
const mailOptions = {
|
||||||
from: process.env.USER_GMAIL,
|
from: sistem.email,
|
||||||
to,
|
to: sendEmail.to,
|
||||||
subject,
|
subject: sendEmail.subject,
|
||||||
text,
|
text: sendEmail.text,
|
||||||
html,
|
html: sendEmail.html,
|
||||||
|
|
||||||
// OJO: nodemailer usa 'attachments', no 'adjuntos'
|
|
||||||
attachments: (adjuntos || []).map((adj) => {
|
|
||||||
// Asegúrate de no incluir 'data:image/png;base64,' en 'content'
|
|
||||||
let base64Clean = adj.content;
|
|
||||||
// Si viene con prefijo "data:image...", lo quitamos
|
|
||||||
if (base64Clean.startsWith('data:image/')) {
|
|
||||||
base64Clean = base64Clean.split('base64,')[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
filename: adj.filename || 'qr.png',
|
|
||||||
content: Buffer.from(base64Clean, adj.encoding || 'base64'),
|
|
||||||
cid: adj.cid || 'qrCode', // el mismo que uses en <img src="cid:..."/>
|
|
||||||
contentType: 'image/png' // recomendable para que sepa que es PNG
|
|
||||||
};
|
|
||||||
})
|
|
||||||
|
|
||||||
|
// OJO: nodemailer usa 'attachments', no 'adjuntos'
|
||||||
|
attachments: (sendEmail.adjuntos || []).map((adj) => {
|
||||||
|
// Asegúrate de no incluir 'data:image/png;base64,' en 'content'
|
||||||
|
let base64Clean = adj.content;
|
||||||
|
// Si viene con prefijo "data:image...", lo quitamos
|
||||||
|
if (base64Clean.startsWith('data:image/')) {
|
||||||
|
base64Clean = base64Clean.split('base64,')[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
filename: adj.filename || 'qr.png',
|
||||||
|
content: Buffer.from(base64Clean, adj.encoding || 'base64'),
|
||||||
|
cid: adj.cid || 'qrCode', // el mismo que uses en <img src="cid:..."/>
|
||||||
|
contentType: 'image/png', // recomendable para que sepa que es PNG
|
||||||
|
};
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let resMail = await transporter.sendMail(mailOptions);
|
||||||
|
if (!resMail) throw new Error('');
|
||||||
|
|
||||||
console.log(mailOptions);
|
const statusTexto = resMail.accepted.length > 0 ? 'Enviado' : 'Fallido';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let resMail = await this.transporter.sendMail(mailOptions);
|
|
||||||
|
|
||||||
console.log("este es el resultado del envio",resMail);
|
|
||||||
//console.log("este es el resultado del envio",resMail.accepted.length);
|
|
||||||
|
|
||||||
const statusTexto = resMail.accepted.length > 0 ? "Enviado" : "Fallido";
|
|
||||||
|
|
||||||
/*
|
|
||||||
let status = await this.statusRepository.findOne({ where: { status: statusTexto } });
|
|
||||||
|
|
||||||
|
let status = await this.statusRepository.findOne({
|
||||||
|
where: { status: statusTexto },
|
||||||
|
});
|
||||||
|
|
||||||
if (!status) {
|
if (!status) {
|
||||||
status = this.statusRepository.create({ status: statusTexto });
|
status = this.statusRepository.create({ status: statusTexto });
|
||||||
await this.statusRepository.save(status);
|
await this.statusRepository.save(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
const info= this.correoRepository.create({
|
const info = this.correoRepository.create({
|
||||||
id_status: status,
|
id_status: status,
|
||||||
id_sistema: sistema,
|
id_sistema: sistem,
|
||||||
fecha_recibido: fecha_recibido,
|
fecha_recibido: sendEmail.fecha_recibido,
|
||||||
fecha_enviado: new Date(),
|
fecha_enviado: new Date(),
|
||||||
destinatario: to,
|
destinatario: sendEmail.to,
|
||||||
remitente: process.env.USER_GMAIL,
|
remitente: sistem.email,
|
||||||
|
});
|
||||||
|
|
||||||
}); */
|
if (!resMail) {
|
||||||
|
throw new Error('fallo');
|
||||||
|
|
||||||
if(!resMail){
|
|
||||||
throw new Error("fallo")
|
|
||||||
}
|
}
|
||||||
//this.correoRepository.save(info);
|
this.correoRepository.save(info);
|
||||||
|
|
||||||
|
|
||||||
|
return statusTexto;
|
||||||
return (statusTexto);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async saveEmailStatus(
|
||||||
|
dto: SendCorreoDto,
|
||||||
|
sistem: any,
|
||||||
|
statusText: string,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// 1. Buscar el estado en la base de datos (Enviado o Fallido)
|
||||||
|
let status = await this.statusRepository.findOne({
|
||||||
|
where: { status: statusText },
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Si el estado no existe en el catálogo, lo creamos para evitar errores de FK
|
||||||
|
if (!status) {
|
||||||
|
status = this.statusRepository.create({ status: statusText });
|
||||||
|
await this.statusRepository.save(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Crear el registro histórico del correo
|
||||||
|
const info = this.correoRepository.create({
|
||||||
|
id_status: status,
|
||||||
|
id_sistema: sistem,
|
||||||
|
fecha_recibido: dto.fecha_recibido || new Date(), // Fecha en que se solicitó
|
||||||
|
fecha_enviado: new Date(), // Fecha real de salida
|
||||||
|
destinatario: dto.to,
|
||||||
|
remitente: sistem.email,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. Guardar el registro de forma asíncrona
|
||||||
|
return await this.correoRepository.save(info);
|
||||||
|
} catch (error) {
|
||||||
|
// Solo hacemos log del error para no detener el proceso masivo si falla el log en DB
|
||||||
|
console.error(
|
||||||
|
`Error crítico al guardar status en DB para ${dto.to}:`,
|
||||||
|
error.message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendBulkMail(emailsData: SendCorreoDto[], token: string) {
|
||||||
|
// 1. Validación de acceso y obtención de credenciales (una sola vez)
|
||||||
|
const payload = await this.authService.verificarToken(token);
|
||||||
|
if (!payload || !payload.nombre)
|
||||||
|
throw new UnauthorizedException('Token inválido');
|
||||||
|
|
||||||
|
const sistem = await this.sistemaService.findByNombre(payload.nombre);
|
||||||
|
if (!sistem) throw new UnauthorizedException('Sistema no encontrado');
|
||||||
|
|
||||||
|
const decryptedPassword = decrypt(
|
||||||
|
sistem.email_password_encrypted,
|
||||||
|
sistem.email_password_iv,
|
||||||
|
);
|
||||||
|
const decryptedToken = decrypt(
|
||||||
|
sistem.refreshToken_encrypted,
|
||||||
|
sistem.refreshToken_iv,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. Crear el transporter único con Pool
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
service: 'gmail',
|
||||||
|
pool: true,
|
||||||
|
maxConnections: 5, // Procesará hasta 5 conexiones SMTP simultáneas
|
||||||
|
auth: {
|
||||||
|
type: 'OAuth2',
|
||||||
|
user: sistem.email,
|
||||||
|
clientId: sistem.clientId,
|
||||||
|
clientSecret: decryptedPassword,
|
||||||
|
refreshToken: decryptedToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const CHUNK_SIZE = 50; // Tamaño de cada lote
|
||||||
|
const results = { successful: 0, failed: 0 };
|
||||||
|
|
||||||
|
// 3. Proceso por Chunks (Lotes)
|
||||||
|
for (let i = 0; i < emailsData.length; i += CHUNK_SIZE) {
|
||||||
|
const chunk = emailsData.slice(i, i + CHUNK_SIZE);
|
||||||
|
|
||||||
|
// Procesamos el lote actual en paralelo para ganar velocidad
|
||||||
|
await Promise.all(
|
||||||
|
chunk.map(async (emailDto) => {
|
||||||
|
try {
|
||||||
|
const mailOptions = {
|
||||||
|
from: sistem.email,
|
||||||
|
to: emailDto.to,
|
||||||
|
subject: emailDto.subject,
|
||||||
|
html: emailDto.html,
|
||||||
|
attachments: (emailDto.adjuntos || []).map((adj) => ({
|
||||||
|
filename: adj.filename || 'adjunto.png',
|
||||||
|
content: Buffer.from(
|
||||||
|
adj.content.split('base64,')[1] || adj.content,
|
||||||
|
'base64',
|
||||||
|
),
|
||||||
|
cid: adj.cid,
|
||||||
|
contentType: adj.contentType || 'image/png',
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
await transporter.sendMail(mailOptions);
|
||||||
|
await this.saveEmailStatus(emailDto, sistem, 'Enviado');
|
||||||
|
results.successful++;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`Error en envío masivo a ${emailDto.to}:`,
|
||||||
|
error.message,
|
||||||
|
);
|
||||||
|
await this.saveEmailStatus(emailDto, sistem, 'Fallido');
|
||||||
|
results.failed++;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Lote completado: ${i + chunk.length} de ${emailsData.length}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
transporter.close();
|
||||||
|
return results;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -1,11 +1,12 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||||
|
import { json } from 'express';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
|
||||||
|
app.use(json({ limit: '50mb' }));
|
||||||
|
|
||||||
app.enableCors();
|
app.enableCors();
|
||||||
|
|
||||||
@@ -19,9 +20,11 @@ async function bootstrap() {
|
|||||||
.build();
|
.build();
|
||||||
const document = SwaggerModule.createDocument(app, config);
|
const document = SwaggerModule.createDocument(app, config);
|
||||||
SwaggerModule.setup('documentation', app, document);
|
SwaggerModule.setup('documentation', app, document);
|
||||||
|
|
||||||
await app.listen(process.env.PORT ?? 3000);
|
await app.listen(process.env.PORT ?? 3000);
|
||||||
|
|
||||||
console.log(`Aplicación corriendo en: http://localhost:${process.env.PORT ?? 3000}`);
|
console.log(
|
||||||
|
`Aplicación corriendo en: http://localhost:${process.env.PORT ?? 3000}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// comprobar-sistema.dto.ts
|
||||||
|
export class ComprobarSistemaDto {
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IsString, IsOptional, IsBoolean, IsNotEmpty } from 'class-validator';
|
import { IsString, IsNotEmpty } from 'class-validator';
|
||||||
|
|
||||||
export class CreateSistemaDto {
|
export class CreateSistemaDto {
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@@ -9,8 +9,29 @@ export class CreateSistemaDto {
|
|||||||
@IsString()
|
@IsString()
|
||||||
ip:string;
|
ip:string;
|
||||||
|
|
||||||
@IsNotEmpty()
|
//Password del sistema
|
||||||
@IsString()
|
@IsNotEmpty()
|
||||||
password: string;
|
@IsString()
|
||||||
|
password: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
//clientSecret
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
email_password:string
|
||||||
|
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
clientId:string
|
||||||
|
|
||||||
|
//TU_REFRESH_TOKEN
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
refreshToken:string;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class UpdateSistemaDto {
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
Nombre?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
ip?: string;
|
||||||
|
|
||||||
|
//Password del sistema
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
token: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
email?: string;
|
||||||
|
|
||||||
|
//clientSecret
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
email_password?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
clientId?: string;
|
||||||
|
|
||||||
|
//TU_REFRESH_TOKEN
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
refreshToken?: string;
|
||||||
|
}
|
||||||
@@ -1,22 +1,58 @@
|
|||||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
Patch,
|
||||||
|
Param,
|
||||||
|
Query,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { SistemaService } from './sistema.service';
|
import { SistemaService } from './sistema.service';
|
||||||
import { CreateSistemaDto } from './dto/create-sistema.dto';
|
import { CreateSistemaDto } from './dto/create-sistema.dto';
|
||||||
import { ApiCreateSistema, ApiGenerarToken } from './sistema.decorators';
|
import { ComprobarSistemaDto } from './dto/comprobar-sistema';
|
||||||
|
import {
|
||||||
|
ApiAccesoSistema,
|
||||||
|
ApiCreateSistema,
|
||||||
|
ApiGenerarToken,
|
||||||
|
ApiUpdateSistema,
|
||||||
|
} from './sistema.decorators';
|
||||||
|
import { UpdateSistemaDto } from './dto/sistema-update';
|
||||||
|
|
||||||
@Controller('sistema')
|
@Controller('sistema')
|
||||||
export class SistemaController {
|
export class SistemaController {
|
||||||
constructor(private readonly sistemaService: SistemaService) {}
|
constructor(private readonly sistemaService: SistemaService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
findAll() {
|
||||||
|
return this.sistemaService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@ApiCreateSistema()
|
@ApiCreateSistema()
|
||||||
create(@Body() createSistemaDto: CreateSistemaDto, password:string) {
|
create(@Body() createSistemaDto: CreateSistemaDto) {
|
||||||
return this.sistemaService.create(createSistemaDto);
|
return this.sistemaService.create(createSistemaDto);
|
||||||
}
|
}
|
||||||
|
@Patch('config')
|
||||||
|
@ApiUpdateSistema()
|
||||||
|
update2(@Body() updateSistemaDto: UpdateSistemaDto) {
|
||||||
|
return this.sistemaService.update(updateSistemaDto);
|
||||||
|
}
|
||||||
|
|
||||||
@Patch(':nombre_sistema')
|
@Patch(':nombre_sistema')
|
||||||
@ApiGenerarToken()
|
@ApiGenerarToken()
|
||||||
update(@Param('nombre_sistema') nombre_sistema: string, @Body() password: string) {
|
update(
|
||||||
return this.sistemaService.Generar_Token(nombre_sistema, password);
|
@Param('nombre_sistema') nombre_sistema: string,
|
||||||
|
@Body() comprobarSistemaDto: ComprobarSistemaDto,
|
||||||
|
) {
|
||||||
|
return this.sistemaService.Generar_Token(
|
||||||
|
nombre_sistema,
|
||||||
|
comprobarSistemaDto.password,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiAccesoSistema()
|
||||||
|
@Get('acceso')
|
||||||
|
async acceso(@Query('token') token: string) {
|
||||||
|
return this.sistemaService.acceso(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,115 @@
|
|||||||
import { applyDecorators } from '@nestjs/common';
|
import { applyDecorators } from '@nestjs/common';
|
||||||
import { ApiBody, ApiOperation, ApiParam } from '@nestjs/swagger';
|
import { ApiBody, ApiOperation, ApiParam, ApiQuery } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export const ApiUpdateSistema = () => {
|
||||||
|
return applyDecorators(
|
||||||
|
ApiOperation({
|
||||||
|
summary: 'Actualizar configuración del sistema',
|
||||||
|
description: `
|
||||||
|
Este endpoint permite **actualizar parcialmente** la configuración de un sistema existente.
|
||||||
|
|
||||||
|
🔐 **Requiere token del sistema** para autorizar la operación.
|
||||||
|
|
||||||
|
Se pueden actualizar uno o más de los siguientes campos:
|
||||||
|
- Nombre del sistema
|
||||||
|
- IP
|
||||||
|
- Credenciales de correo
|
||||||
|
- Credenciales OAuth (clientId, refreshToken)
|
||||||
|
|
||||||
|
⚠️ Solo se actualizarán los campos enviados en el cuerpo.
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
ApiBody({
|
||||||
|
description: 'Datos a actualizar del sistema',
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
Nombre: {
|
||||||
|
type: 'string',
|
||||||
|
example: 'Sistema de Notificaciones',
|
||||||
|
description: 'Nuevo nombre del sistema',
|
||||||
|
},
|
||||||
|
ip: {
|
||||||
|
type: 'string',
|
||||||
|
example: '192.168.0.20',
|
||||||
|
description: 'Nueva dirección IP autorizada',
|
||||||
|
},
|
||||||
|
token: {
|
||||||
|
type: 'string',
|
||||||
|
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
||||||
|
description: 'Token JWT del sistema (obligatorio)',
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'email',
|
||||||
|
example: 'sistema@gmail.com',
|
||||||
|
description: 'Correo del sistema para envío de emails',
|
||||||
|
},
|
||||||
|
email_password: {
|
||||||
|
type: 'string',
|
||||||
|
example: 'app-password-123',
|
||||||
|
description: 'Contraseña o secreto del correo',
|
||||||
|
},
|
||||||
|
clientId: {
|
||||||
|
type: 'string',
|
||||||
|
example: '1234567890-abc.apps.googleusercontent.com',
|
||||||
|
description: 'Client ID de Google OAuth',
|
||||||
|
},
|
||||||
|
refreshToken: {
|
||||||
|
type: 'string',
|
||||||
|
example: '1//0gxxxxxxxxxxxxxxxx',
|
||||||
|
description: 'Refresh Token OAuth del correo',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['token'],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const ApiCreateSistema = () => {
|
export const ApiCreateSistema = () => {
|
||||||
return applyDecorators(
|
return applyDecorators(
|
||||||
ApiOperation({
|
ApiOperation({
|
||||||
summary: 'Registrar un nuevo sistema',
|
summary: 'Registrar un nuevo sistema',
|
||||||
description: `Este endpoint permite dar de alta un sistema para poder utilizar funcionalidades autenticadas mediante contraseña.
|
description: `Este endpoint permite registrar un sistema que podrá autenticarse y utilizar servicios protegidos.
|
||||||
Se debe proporcionar:
|
Debe incluirse la contraseña maestra del administrador para autorizar la creación.
|
||||||
- **Nombre**: Nombre único del sistema.
|
Además, se deben proporcionar las credenciales del correo que el sistema usará para enviar emails.`,
|
||||||
- **ip**: Dirección IP desde la cual se harán peticiones.
|
|
||||||
- **password**: Contraseña segura que el sistema usará para autenticarse.`,
|
|
||||||
}),
|
}),
|
||||||
ApiBody({
|
ApiBody({
|
||||||
description: 'Datos del sistema a registrar',
|
description: 'Datos del sistema a registrar',
|
||||||
schema: {
|
schema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
Nombre: { type: 'string', example: 'Sistema de Reportes' },
|
Nombre: {
|
||||||
ip: { type: 'string', example: '192.168.1.100' },
|
type: 'string',
|
||||||
password: { type: 'string', example: 'clave-segura-123' },
|
example: 'Sistema de Notificaciones',
|
||||||
|
description: 'Nombre único del sistema a registrar',
|
||||||
|
},
|
||||||
|
ip: {
|
||||||
|
type: 'string',
|
||||||
|
example: '192.168.0.10',
|
||||||
|
description: 'Dirección IP desde donde se harán las peticiones',
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'email',
|
||||||
|
example: 'sistema@gmail.com',
|
||||||
|
description: 'Correo electrónico del sistema para enviar emails',
|
||||||
|
},
|
||||||
|
email_password: {
|
||||||
|
type: 'string',
|
||||||
|
example: 'contraseña-de-aplicacion',
|
||||||
|
description: 'Contraseña de aplicación del correo proporcionado',
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
type: 'string',
|
||||||
|
example: 'clave-admin-secreta',
|
||||||
|
description: 'Contraseña maestra para autorizar el registro',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
required: ['Nombre', 'ip', 'password'],
|
required: ['Nombre', 'ip', 'email', 'email_password', 'password'],
|
||||||
},
|
},
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -30,21 +117,43 @@ export const ApiGenerarToken = () => {
|
|||||||
return applyDecorators(
|
return applyDecorators(
|
||||||
ApiOperation({
|
ApiOperation({
|
||||||
summary: 'Generar token JWT para un sistema',
|
summary: 'Generar token JWT para un sistema',
|
||||||
description: `Este endpoint permite generar un token de acceso válido para el sistema registrado.
|
description: `Este endpoint permite generar un token de autenticación JWT para un sistema previamente registrado.
|
||||||
Se debe enviar el nombre del sistema como parámetro de ruta y la contraseña como cuerpo.`,
|
Se debe enviar el nombre del sistema como parámetro de ruta y su contraseña de registro en el cuerpo.`,
|
||||||
}),
|
}),
|
||||||
ApiParam({
|
ApiParam({
|
||||||
name: 'nombre_sistema',
|
name: 'nombre_sistema',
|
||||||
required: true,
|
required: true,
|
||||||
description: 'Nombre exacto del sistema registrado',
|
description: 'Nombre exacto del sistema registrado',
|
||||||
example: 'Sistema de Reportes',
|
example: 'Sistema de Notificaciones',
|
||||||
}),
|
}),
|
||||||
ApiBody({
|
ApiBody({
|
||||||
description: 'Contraseña del sistema',
|
description: 'Contraseña del sistema para verificar identidad',
|
||||||
schema: {
|
schema: {
|
||||||
type: 'string',
|
type: 'object',
|
||||||
example: 'clave-segura-123',
|
properties: {
|
||||||
|
password: {
|
||||||
|
type: 'string',
|
||||||
|
example: 'clave-admin-secreta',
|
||||||
|
description: 'Contraseña de autenticación del sistema',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['password'],
|
||||||
},
|
},
|
||||||
})
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ApiAccesoSistema = () => {
|
||||||
|
return applyDecorators(
|
||||||
|
ApiOperation({
|
||||||
|
summary: 'Verificar acceso del sistema mediante token',
|
||||||
|
description: `Este endpoint permite verificar si un token JWT enviado por un sistema es válido y tiene acceso.`,
|
||||||
|
}),
|
||||||
|
ApiQuery({
|
||||||
|
name: 'token',
|
||||||
|
required: true,
|
||||||
|
description: 'Token JWT del sistema',
|
||||||
|
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,15 +2,17 @@ import { Module } from '@nestjs/common';
|
|||||||
import { SistemaService } from './sistema.service';
|
import { SistemaService } from './sistema.service';
|
||||||
import { SistemaController } from './sistema.controller';
|
import { SistemaController } from './sistema.controller';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { Sistema } from 'src/typeorm/votacionesPrueba.entity';
|
import { Sistema } from 'src/typeorm/entidades';
|
||||||
|
import { AuthModule } from 'src/auth/auth.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [SistemaController],
|
controllers: [SistemaController],
|
||||||
providers: [SistemaService],
|
providers: [SistemaService],
|
||||||
imports:[
|
imports:[
|
||||||
TypeOrmModule.forFeature([
|
TypeOrmModule.forFeature([
|
||||||
Sistema])
|
Sistema]),
|
||||||
|
AuthModule
|
||||||
],
|
],
|
||||||
exports:[SistemaModule]
|
exports:[SistemaModule,SistemaService]
|
||||||
})
|
})
|
||||||
export class SistemaModule {}
|
export class SistemaModule {}
|
||||||
|
|||||||
+143
-32
@@ -1,53 +1,164 @@
|
|||||||
import { Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common';
|
import {
|
||||||
|
Injectable,
|
||||||
|
NotFoundException,
|
||||||
|
UnauthorizedException,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { CreateSistemaDto } from './dto/create-sistema.dto';
|
import { CreateSistemaDto } from './dto/create-sistema.dto';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Sistema } from 'src/typeorm/votacionesPrueba.entity';
|
import { Sistema } from 'src/typeorm/entidades';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
import { AuthService } from 'src/auth/auth.service';
|
||||||
import { randomBytes } from 'crypto';
|
import { encrypt } from 'src/crypto/crypto.util';
|
||||||
|
import { UpdateSistemaDto } from './dto/sistema-update';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SistemaService {
|
export class SistemaService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Sistema) private readonly sistemaRepository: Repository<Sistema>
|
@InjectRepository(Sistema)
|
||||||
|
private readonly sistemaRepository: Repository<Sistema>,
|
||||||
|
private readonly authService: AuthService,
|
||||||
|
) {}
|
||||||
|
|
||||||
){}
|
async findAll() {
|
||||||
|
const sistemas = await this.sistemaRepository.find();
|
||||||
generarPassword(longitud = 12): string {
|
if (!sistemas || sistemas.length === 0) {
|
||||||
return randomBytes(longitud)
|
throw new NotFoundException('No hay sistemas registrados');
|
||||||
.toString('base64')
|
}
|
||||||
.slice(0, longitud)
|
return sistemas;
|
||||||
.replace(/[+/=]/g, () => String.fromCharCode(33 + Math.floor(Math.random() * 94))); // reemplaza caracteres raros
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async Generar_Token(nombreSistema:string,adminPasword:string) {
|
async Generar_Token(nombreSistema: string, adminPassword: string) {
|
||||||
if(adminPasword!==process.env.ADMIN_PASSWORD){
|
if (adminPassword !== process.env.ADMIN_PASSWORD) {
|
||||||
throw new UnauthorizedException('Password erroneo');
|
throw new UnauthorizedException('Password erróneo');
|
||||||
}
|
}
|
||||||
const sistema = await this.sistemaRepository.findOneBy({nombre_sistema:nombreSistema})
|
|
||||||
if(!sistema){
|
|
||||||
throw new NotFoundException('Sistema no encontrado')
|
|
||||||
}
|
|
||||||
sistema.token=this.generarPassword()
|
|
||||||
|
|
||||||
this.sistemaRepository.save(sistema)
|
const sistema = await this.sistemaRepository.findOneBy({
|
||||||
return sistema.token;
|
nombre_sistema: nombreSistema,
|
||||||
|
});
|
||||||
|
if (!sistema) {
|
||||||
|
throw new NotFoundException('Sistema no encontrado');
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = this.authService.generarToken({
|
||||||
|
sistemaId: sistema.id_sistema,
|
||||||
|
nombre: sistema.nombre_sistema,
|
||||||
|
ip: sistema.ip,
|
||||||
|
});
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
async acceso(token: string) {
|
||||||
|
const decoded = await this.authService.verificarToken(token);
|
||||||
|
if (!decoded) {
|
||||||
|
throw new UnauthorizedException('Token inválido');
|
||||||
|
}
|
||||||
|
|
||||||
|
const sistema = await this.sistemaRepository.findOneBy({
|
||||||
|
id_sistema: decoded.sistemaId,
|
||||||
|
});
|
||||||
|
if (!sistema) {
|
||||||
|
throw new UnauthorizedException('Sistema no encontrado');
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(createSistemaDto: CreateSistemaDto) {
|
async create(createSistemaDto: CreateSistemaDto) {
|
||||||
if(createSistemaDto.password!==process.env.ADMIN_PASSWORD){
|
if (createSistemaDto.password !== process.env.ADMIN_PASSWORD) {
|
||||||
throw new UnauthorizedException('Password erroneo');
|
throw new UnauthorizedException('Password erróneo');
|
||||||
}
|
}
|
||||||
|
|
||||||
const sistem = this.sistemaRepository.create({
|
const { encryptedData: encryptedData_token, iv: encryptedData_iv } =
|
||||||
nombre_sistema:createSistemaDto.Nombre,
|
encrypt(createSistemaDto.refreshToken);
|
||||||
ip:createSistemaDto.ip,
|
|
||||||
});
|
|
||||||
await this.sistemaRepository.save(sistem)
|
|
||||||
|
|
||||||
return this.Generar_Token(createSistemaDto.Nombre,createSistemaDto.password);
|
const { encryptedData: encrypted_password, iv: iv_password } = encrypt(
|
||||||
|
createSistemaDto.email_password,
|
||||||
|
);
|
||||||
|
|
||||||
|
const sistema = this.sistemaRepository.create({
|
||||||
|
nombre_sistema: createSistemaDto.Nombre,
|
||||||
|
ip: createSistemaDto.ip,
|
||||||
|
email: createSistemaDto.email,
|
||||||
|
email_password_encrypted: encrypted_password,
|
||||||
|
email_password_iv: iv_password,
|
||||||
|
clientId: createSistemaDto.clientId,
|
||||||
|
refreshToken_encrypted: encryptedData_token,
|
||||||
|
refreshToken_iv: encryptedData_iv,
|
||||||
|
});
|
||||||
|
await this.sistemaRepository.save(sistema);
|
||||||
|
|
||||||
|
return this.Generar_Token(
|
||||||
|
createSistemaDto.Nombre,
|
||||||
|
createSistemaDto.password,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async update(updateSistemaDto: UpdateSistemaDto) {
|
||||||
|
const decoded = await this.authService.verificarToken(
|
||||||
|
updateSistemaDto.token,
|
||||||
|
);
|
||||||
|
console.log('aqui');
|
||||||
|
if (!decoded) {
|
||||||
|
throw new UnauthorizedException('Token inválido');
|
||||||
|
}
|
||||||
|
|
||||||
|
let sistem = await this.sistemaRepository.findOneBy({
|
||||||
|
id_sistema: decoded.sistemaId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!sistem) {
|
||||||
|
throw new UnauthorizedException('Sistema no encontrado');
|
||||||
|
}
|
||||||
|
let encryptedData_token: string | undefined;
|
||||||
|
let encryptedData_iv: string | undefined;
|
||||||
|
|
||||||
|
let encrypted_password: string | undefined;
|
||||||
|
let iv_password: string | undefined;
|
||||||
|
|
||||||
|
if (updateSistemaDto.refreshToken) {
|
||||||
|
({ encryptedData: encryptedData_token, iv: encryptedData_iv } = encrypt(
|
||||||
|
updateSistemaDto.refreshToken,
|
||||||
|
));
|
||||||
|
sistem.refreshToken_encrypted = encryptedData_token;
|
||||||
|
sistem.refreshToken_iv = encryptedData_iv;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateSistemaDto.email_password) {
|
||||||
|
({ encryptedData: encrypted_password, iv: iv_password } = encrypt(
|
||||||
|
updateSistemaDto.email_password,
|
||||||
|
));
|
||||||
|
sistem.email_password_encrypted = encrypted_password;
|
||||||
|
sistem.email_password_iv = iv_password;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateSistemaDto.Nombre) {
|
||||||
|
sistem.nombre_sistema = updateSistemaDto.Nombre;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateSistemaDto.ip) {
|
||||||
|
sistem.ip = updateSistemaDto.ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateSistemaDto.email) {
|
||||||
|
sistem.email = updateSistemaDto.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateSistemaDto.clientId) {
|
||||||
|
sistem.clientId = updateSistemaDto.clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.sistemaRepository.save(sistem);
|
||||||
|
|
||||||
|
return 'se guardo correctamente';
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByNombre(sistema: string) {
|
||||||
|
const sistem = this.sistemaRepository.findOne({
|
||||||
|
where: { nombre_sistema: sistema },
|
||||||
|
});
|
||||||
|
if (!sistem) {
|
||||||
|
throw new UnauthorizedException('Password erróneo');
|
||||||
|
}
|
||||||
|
return sistem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,33 @@ export class Sistema {
|
|||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id_sistema: number;
|
id_sistema: number;
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
@Column({ type: 'varchar', length: 50, nullable: false })
|
||||||
token: string;
|
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 50 })
|
|
||||||
nombre_sistema: string;
|
nombre_sistema: string;
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 50 })
|
@Column({ type: 'varchar', length: 50,nullable: false })
|
||||||
ip:string
|
ip:string;
|
||||||
|
|
||||||
|
@Column({nullable: false})
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
//clientSecret encriptada
|
||||||
|
@Column({nullable: false})
|
||||||
|
email_password_encrypted: string;
|
||||||
|
|
||||||
|
|
||||||
|
@Column({nullable: false})
|
||||||
|
email_password_iv: string;
|
||||||
|
|
||||||
|
@Column({nullable: false})
|
||||||
|
clientId:string
|
||||||
|
|
||||||
|
//TU_REFRESH_TOKEN
|
||||||
|
@Column({nullable: false})
|
||||||
|
refreshToken_encrypted:string;
|
||||||
|
|
||||||
|
@Column({nullable: false})
|
||||||
|
refreshToken_iv: string;
|
||||||
|
|
||||||
|
|
||||||
@OneToMany(() => Correo, (correo) => correo.id_sistema)
|
@OneToMany(() => Correo, (correo) => correo.id_sistema)
|
||||||
correos: Correo[];
|
correos: Correo[];
|
||||||
Reference in New Issue
Block a user