Muchos cambios

This commit is contained in:
JorgeMike
2023-06-26 08:33:18 -06:00
parent 0a70bf2af2
commit e2afc63e28
13 changed files with 184 additions and 170 deletions
+42 -6
View File
@@ -23,6 +23,7 @@
"blob-stream": "^0.1.3",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"date-fns": "^2.30.0",
"exceljs": "^4.3.0",
"moment": "^2.29.4",
"multer": "^1.4.5-lts.1",
@@ -745,6 +746,17 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz",
"integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==",
"dependencies": {
"regenerator-runtime": "^0.13.11"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz",
@@ -4212,9 +4224,12 @@
"dev": true
},
"node_modules/date-fns": {
"version": "2.29.3",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
"integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==",
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
"dependencies": {
"@babel/runtime": "^7.21.0"
},
"engines": {
"node": ">=0.11"
},
@@ -8919,6 +8934,11 @@
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
"integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg=="
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"node_modules/regexp.prototype.flags": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
@@ -11784,6 +11804,14 @@
"@babel/helper-plugin-utils": "^7.19.0"
}
},
"@babel/runtime": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz",
"integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==",
"requires": {
"regenerator-runtime": "^0.13.11"
}
},
"@babel/template": {
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz",
@@ -14464,9 +14492,12 @@
"dev": true
},
"date-fns": {
"version": "2.29.3",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
"integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA=="
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
"requires": {
"@babel/runtime": "^7.21.0"
}
},
"dayjs": {
"version": "1.11.7",
@@ -18066,6 +18097,11 @@
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
"integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg=="
},
"regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"regexp.prototype.flags": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
+1
View File
@@ -34,6 +34,7 @@
"blob-stream": "^0.1.3",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"date-fns": "^2.30.0",
"exceljs": "^4.3.0",
"moment": "^2.29.4",
"multer": "^1.4.5-lts.1",

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 185 KiB

+1 -4
View File
@@ -46,8 +46,6 @@ import { EmailService } from './email/email.service';
import { EmailController } from './email/email.controller';
import { EmailModule } from './email/email.module';
import { MulterModule } from '@nestjs/platform-express';
import { CarouselController } from './carousel/carousel.controller';
import { CarouselService } from './carousel/carousel.service';
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';
import { CarruselModule } from './carrusel/carrusel.module';
@@ -128,9 +126,8 @@ import { Carrusel } from './entities/carrusel.entity';
AppController,
ParticipanteController,
InscripcionStatusController,
CarouselController,
],
providers: [AppService, InscripcionStatusService, CarouselService],
providers: [AppService, InscripcionStatusService],
})
export class AppModule {
static port: number;
+5 -3
View File
@@ -54,9 +54,11 @@ export class AuthService {
/* console.log(token); */
const data = {
usuario: usuario.nombre,
id: usuario.id_miembro,
email: usuario.email,
user: {
usuario: usuario.nombre,
id: usuario.id_miembro,
email: usuario.email,
},
token,
};
-92
View File
@@ -1,92 +0,0 @@
import {
BadRequestException,
Controller,
Delete,
Get,
HttpException,
HttpStatus,
Param,
Post,
Res,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { Response } from 'express';
import * as fs from 'fs';
import { diskStorage } from 'multer';
import * as path from 'path';
@Controller('carousel')
export class CarouselController {
private images: string[] = [];
@Post('/imagenes')
@UseInterceptors(
FileInterceptor('file', {
storage: diskStorage({
destination: './public',
filename: function (req, file, cb) {
const directory = './public';
const filePath = `${directory}/${file.originalname}`;
if (fs.existsSync(filePath)) {
cb(
new BadRequestException(
`Ya existe un archivo con el nombre: '${file.originalname}'. Si está seguro de que no es el mismo, cambie el nombre del archivo.`,
),
null,
);
} else {
cb(null, file.originalname);
}
},
}),
fileFilter: function (req, file, cb) {
const filetypes = /jpeg|jpg|webp|png/;
const extname = filetypes.test(
path.extname(file.originalname).toLowerCase(),
);
const mimetype = filetypes.test(file.mimetype);
if (mimetype && extname) {
return cb(null, true);
}
cb(
new BadRequestException(
'Lo sentimos, para mejorar la calidad de las imagenes en el carousel solo se aceptan imagenes con terminación: .jpeg .jpg .png .webp',
),
false,
);
},
}),
)
async addImage(@UploadedFile() file) {
return { msg: 'Imagen guardada con exito :)' };
}
@Delete('/imagenes/:nombreArchivo')
async deleteImage(@Param('nombreArchivo') nombreArchivo: string) {
try {
await fs.promises.unlink(`./public/${nombreArchivo}`);
return { message: 'Imagen eliminada exitosamente' };
} catch (err) {
throw new Error('Error al eliminar la imagen');
}
}
@Get('/imagenes')
async getImages(@Res() res: Response) {
// Leer el directorio 'public' para obtener el nombre de todos los archivos
const fileNames = await fs.promises.readdir('./public');
// Generar la URL para cada archivo y agregarla a un arreglo
// Filtrar solo las imágenes con las terminaciones "jpg", "webp" y "png"
const imageNames = fileNames.filter((fileName) =>
['.jpg', '.webp', '.png'].includes(path.extname(fileName)),
);
const imageUrls = imageNames.map((imageName) => `public/${imageName}`);
// Devolver el arreglo de URLs como respuesta
return res.json(imageUrls);
}
}
-9
View File
@@ -1,9 +0,0 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class CarouselService {
uploadImage(file){
/* console.log(file) */
}
}
Binary file not shown.
+18 -8
View File
@@ -8,6 +8,7 @@ import {
Post,
Res,
UploadedFile,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
@@ -15,27 +16,36 @@ import { EmailDto } from './dto/emailDto.dto';
import { EmailService } from './email.service';
import { Response } from 'express';
import * as xlsx from 'xlsx';
import { AuthGuard } from '@nestjs/passport';
@Controller('email')
export class EmailController {
constructor(private readonly emailService: EmailService) {}
@UseGuards(AuthGuard('jwt'))
@Post('/evento/:id')
async sendEmail(
@Param('id', ParseIntPipe) id: number,
@Body() emailDto: EmailDto,
@Res() res,
): Promise<any> {
return this.emailService.sendEmail(id, emailDto);
return this.emailService.sendEmail(id, emailDto, res);
}
@Post('constancias')
@UseGuards(AuthGuard('jwt'))
@Post('constancias/:id')
@UseInterceptors(FileInterceptor('file'))
async sendConstancias(@UploadedFile() file, @Body('nombreEvento') nombreEvento: string,) {
if(!file) {
throw new HttpException("No se ha cargado ningun archivo", HttpStatus.BAD_REQUEST)
async sendConstancias(
@UploadedFile() file,
@Param('id', ParseIntPipe) id: number,
@Res() res,
) {
if (!file) {
throw new HttpException(
'No se ha cargado ningun archivo',
HttpStatus.BAD_REQUEST,
);
}
/* console.log(file) */
return this.emailService.sendConstancias(file, nombreEvento);
return this.emailService.sendConstancias(file, id, res);
}
}
+5 -2
View File
@@ -4,10 +4,13 @@ import { EventosModule } from 'src/eventos/eventos.module';
import { Participante } from 'src/participante/participante.entity';
import { EmailController } from './email.controller';
import { EmailService } from './email.service';
import { EventosService } from 'src/eventos/eventos.service';
import { EventoParticipante } from 'src/evento-participante/evento-participante/eventoParticipante.entity';
import { Evento } from 'src/eventos/evento.entity';
@Module({
imports:[TypeOrmModule.forFeature([Participante])],
imports:[TypeOrmModule.forFeature([Participante, EventoParticipante, Evento])],
controllers:[EmailController],
providers:[EmailService]
providers:[EmailService, EventosService]
})
export class EmailModule {}
+103 -43
View File
@@ -1,4 +1,11 @@
import { HttpException, HttpStatus, Inject, Injectable, Res, UploadedFile } from '@nestjs/common';
import {
HttpException,
HttpStatus,
Inject,
Injectable,
Res,
UploadedFile,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { InjectRepository } from '@nestjs/typeorm';
import * as nodemailer from 'nodemailer';
@@ -13,6 +20,8 @@ import * as pdfFonts from 'pdfmake/build/vfs_fonts';
import * as fs from 'fs';
import * as path from 'path';
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
import { Response } from 'express';
import { format } from 'date-fns';
@Injectable()
export class EmailService {
@@ -22,6 +31,7 @@ export class EmailService {
@InjectRepository(Participante)
private participanteRepository: Repository<Participante>,
private configService: ConfigService,
private readonly eventoService: EventosService,
) {
this.transporter = nodemailer.createTransport({
service: this.configService.get('NODEMAILER_SERVICE'),
@@ -32,7 +42,7 @@ export class EmailService {
});
}
async sendEmail(id: number, message: EmailDto) {
async sendEmail(id: number, message: EmailDto, res: Response) {
const participantes = await this.getEmailDeParticipantes(id);
participantes.map((res) => {
const emailDestinatario = res.email;
@@ -41,8 +51,8 @@ export class EmailService {
subject: message.subject,
text: message.text,
});
/*console.log("Email enviado a: ", res)*/
});
return res.status(201).json({ message: 'Correos enviados con exito' });
}
async getEmailDeParticipantes(idEvento: number) {
@@ -57,63 +67,113 @@ export class EmailService {
return participantes;
}
async sendConstancias(file, nombreEvento) {
/* console.log(file) */
async sendConstancias(file, id_evento, res: Response) {
//traemos la info del evento
const evento = await this.eventoService.getEventosPorId(id_evento);
const workbook = xlsx.read(file.buffer);
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const data = xlsx.utils.sheet_to_json(worksheet);
for (const row of data as Row[]) {
const pdfDoc = await PDFDocument.create();
const page = pdfDoc.addPage();
page.setSize(792, 612);
// Carga el PDF pre-diseñado
const existingPdfBytes = fs.readFileSync('src/email/DisenoConstancia.pdf');
const font = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
const fontSize = 30;
const text = 'Constancia de asistencia';
const textWidth = font.widthOfTextAtSize(text, fontSize);
const textHeight = font.heightAtSize(fontSize);
for (let i = 0; i < data.length; i++) {
const row = data[i] as Row;
page.drawText(text, {
x: (page.getWidth() - textWidth) / 2,
y: (page.getHeight() - textHeight) / 2,
if(row.Email == undefined){
return res.status(400).json({ message: `El correo del renglon ${ i + 2} puede estar vacio o ser undefined`, solucion: `Seleccione el renglon donde ocurre el problema y borre todo lo de este renglon, Aunque parezca que el renglon esta vacio puede que haya un espacio en algun campo y esto genera que el sistema haga la lectura de este renglon`});
}
// Carga el PDF pre-diseñado en un documento PDF editable
const pdfDoc = await PDFDocument.load(existingPdfBytes);
const pagina = pdfDoc.getPages();
const primeraPagina = pagina[0];
// Obtén el ancho y alto de la página
const { width, height } = primeraPagina.getSize();
// Obtén la fuente del texto
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
// Configura el tamaño del texto
const fontSize = 26;
const nombreParticipante = `${row['Apellido Paterno']} ${row['Apellido Materno']} ${row.Nombre}`;
// Calcula la anchura del texto del nombre del participante
const textWidth = font.widthOfTextAtSize(nombreParticipante, fontSize);
const centerX = (width - textWidth) / 2;
const centerY = (height - fontSize) / 2;
// Dibuja el texto centrado en la página
primeraPagina.drawText(nombreParticipante, {
x: centerX,
y: centerY + 70,
size: fontSize,
font: font,
color: rgb(0/255, 0/255, 0/255),
color: rgb(0, 0, 0),
});
const { width, height } = page.getSize();
page.drawText(
`Se le otorga la constancia por su asistencia al evento: ${nombreEvento}`,
{
x: 50,
y: height - 50,
size: 12,
font: await pdfDoc.embedFont('Helvetica'),
},
);
page.drawText(
`A ${row['Apellido Paterno']} ${row['Apellido Materno']} ${row.Nombre}`,
{
x: 50,
y: height - 80,
size: 12,
font: await pdfDoc.embedFont('Helvetica'),
},
);
const nombreCurso = `${evento.nombre}`;
// Calcula la anchura del texto del nombre del evento
const nombreCursoWidth = font.widthOfTextAtSize(nombreCurso, fontSize);
const centerXNombreCurso = (width - nombreCursoWidth) / 2;
const centerYNombreCurso = (height - fontSize) / 2;
primeraPagina.drawText(nombreCurso, {
x: centerXNombreCurso,
y: centerYNombreCurso - 17,
size: fontSize,
color: rgb(0, 0, 0),
});
const fechaCurso = `del ${this.formatoFecha(
evento.fecha_inicio,
)} al ${this.formatoFecha(evento.fecha_fin)}`;
// Calcula la anchura del texto del nombre del participante
const fechaCursoWidth = font.widthOfTextAtSize(fechaCurso, 20);
const centerXFechaCurso = (width - fechaCursoWidth) / 2;
const centerYFechaCurso = (height - 20) / 2;
primeraPagina.drawText(fechaCurso, {
x: centerXFechaCurso,
y: centerYFechaCurso - 55,
size: 20,
color: rgb(0, 0, 0),
});
// Guarda el PDF modificado en forma de bytes
const pdfBytes = await pdfDoc.save();
// Crea el objeto de adjunto con el nombre y contenido del PDF
const attachment = {
filename: `Constancia ${nombreEvento} - ${row.Nombre}_${row['Apellido Paterno']}.pdf`,
filename: `Constancia ${evento.nombre} - ${row.Nombre}_${row['Apellido Paterno']}.pdf`,
content: pdfBytes,
};
/* const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob); */
// Crea el objeto Blob y la URL para el PDF
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
// Envía el correo electrónico con la constancia adjunta
this.transporter.sendMail({
to: row.Email,
subject: `Constancia de asistencia a "${nombreEvento}"`,
text: `¡Un saludo ${row.Nombre}! Aquí está tu constancia de asistencia por el evento "${nombreEvento}". Agradecemos tu asistencia y constancia, esperamos seguir viéndote en eventos futuros.`,
subject: `Constancia de asistencia a "${evento.nombre}"`,
text: `¡Un saludo ${row.Nombre}! Aquí está tu constancia de asistencia por el evento "${evento.nombre}". Agradecemos tu asistencia y constancia, esperamos seguir viéndote en eventos futuros.`,
attachments: [attachment],
});
}
return res.status(200).json({ message: 'Constancias enviadas con éxito' });
}
formatoFecha(fecha: Date) {
const fechaLimpia = format(fecha, 'dd/MM/yyyy');
return fechaLimpia;
}
}
+8 -2
View File
@@ -1,4 +1,4 @@
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Res } from '@nestjs/common';
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Res, UseGuards } from '@nestjs/common';
import { Response } from 'express';
import { InjectRepository } from '@nestjs/typeorm';
import { EventoParticipante } from 'src/evento-participante/evento-participante/eventoParticipante.entity';
@@ -7,6 +7,7 @@ import { actualizarEventoDto } from './dto/actualizarEvento.dto';
import { crearEventoDto } from './dto/crearEvento.dto';
import { Evento } from './evento.entity';
import { EventosService } from './eventos.service';
import { AuthGuard } from '@nestjs/passport';
@Controller('eventos')
export class EventosController {
@@ -23,16 +24,19 @@ export class EventosController {
return this.eventoService.getEventosPorId(id)
}
@UseGuards(AuthGuard('jwt'))
@Post()
postEvento(@Body() nuevoEvento: crearEventoDto){
return this.eventoService.postEvento(nuevoEvento)
}
@UseGuards(AuthGuard('jwt'))
@Patch(':id')
patchEvento(@Param('id', ParseIntPipe)id:number, @Body() actualizacion: actualizarEventoDto){
return this.eventoService.updateEvento(id,actualizacion)
}
@UseGuards(AuthGuard('jwt'))
@Delete(':id')
deleteEvento(@Param('id', ParseIntPipe)id: number){
return this.eventoService.deleteEventoParticipante(id)
@@ -43,11 +47,13 @@ export class EventosController {
return this.eventoService.getCupos(id)
}
@UseGuards(AuthGuard('jwt'))
@Get(':id/participante')
async getParticipnates(@Param('id', ParseIntPipe)id:number){
return this.eventoService.getParticipantes(id)
}
@UseGuards(AuthGuard('jwt'))
@Get(':id/exportExcel')
async exportarExcel (@Param('id', ParseIntPipe)id:number ,@Res() res: Response){
this.eventoService.exportarParticipantes(id, res)
+1 -1
View File
@@ -23,7 +23,7 @@ export class EventosService {
return this.eventoRepository.find();
}
getEventosPorId(id: number) {
public getEventosPorId(id: number) {
return this.eventoRepository.findOne({
where: {
id_evento: id,