email #7

Manually merged
CIDWA merged 2 commits from email into develop 2023-03-27 15:24:21 +00:00
16 changed files with 3049 additions and 83 deletions
+10 -10
View File
@@ -1,16 +1,16 @@
PORT=5089
DB_HOST=132.248.180.82
DB_USER=user_cedetec
DB_PASSWORD=c3t3d3c
DB_NAME=Betelgeuse_Cedetec_Prueba
DB_PORT=
# DB_HOST=132.248.180.82
# DB_USER=user_cedetec
# DB_PASSWORD=c3t3d3c
# DB_NAME=Betelgeuse_Cedetec_Prueba
# DB_PORT=
# DB_HOST=localhost
# DB_USER=root
# DB_PASSWORD=Dosmiluno2001
# DB_NAME=cedetec_proyectos
# DB_PORT=3306
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=Dosmiluno2001
DB_NAME=cedetec_proyectos
DB_PORT=3306
DB_HOST_DSC = 132.248.180.82
DB_USER_DSC = sites_user
+2737 -63
View File
File diff suppressed because it is too large Load Diff
+10 -1
View File
@@ -26,24 +26,32 @@
"@nestjs/jwt": "^10.0.2",
"@nestjs/passport": "^9.0.3",
"@nestjs/platform-express": "^9.0.0",
"@nestjs/serve-static": "^3.0.1",
"@nestjs/swagger": "^6.2.1",
"@nestjs/typeorm": "^9.0.1",
"@types/multer": "^1.4.7",
"bcrypt": "^5.1.0",
"blob-stream": "^0.1.3",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"exceljs": "^4.3.0",
"moment": "^2.29.4",
"multer": "^1.4.5-lts.1",
"mysql2": "^3.1.2",
"nodemailer": "^6.9.1",
"passport": "^0.6.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"pdf-lib": "^1.17.1",
"pdfkit": "^0.13.0",
"pg": "^8.9.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0",
"swagger-ui-express": "^4.6.1",
"sybase": "^1.2.3",
"typeorm": "^0.3.12"
"typeorm": "^0.3.12",
"uuid": "^9.0.0",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@jridgewell/sourcemap-codec": "^1.4.14",
@@ -61,6 +69,7 @@
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "29.3.1",
"pdfmake": "^0.2.7",
"prettier": "^2.3.2",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
View File
+14 -1
View File
@@ -45,9 +45,18 @@ import { EquipoMiembro } from './equipo-miembro/entity/equipo_miembro.entity';
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';
@Module({
imports: [
ServeStaticModule.forRoot({
rootPath: join(__dirname, '..', 'public'),
exclude: ['/api*'], // Excluir la ruta "/api" de servir archivos estáticos
}),
ConfigModule.forRoot({
envFilePath: '.env',
isGlobal: true,
@@ -106,13 +115,17 @@ import { EmailModule } from './email/email.module';
ProyectoActividadModule,
ActividadModule,
EmailModule,
MulterModule.register({
dest: './uploads', // directorio de destino para guardar los archivos cargados
}),
],
controllers: [
AppController,
ParticipanteController,
InscripcionStatusController,
CarouselController,
],
providers: [AppService, InscripcionStatusService],
providers: [AppService, InscripcionStatusService, CarouselService],
})
export class AppModule {
static port: number;
+18
View File
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CarouselController } from './carousel.controller';
describe('CarouselController', () => {
let controller: CarouselController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [CarouselController],
}).compile();
controller = module.get<CarouselController>(CarouselController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
+92
View File
@@ -0,0 +1,92 @@
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);
}
}
+18
View File
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CarouselService } from './carousel.service';
describe('CarouselService', () => {
let service: CarouselService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [CarouselService],
}).compile();
service = module.get<CarouselService>(CarouselService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
+9
View File
@@ -0,0 +1,9 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class CarouselService {
uploadImage(file){
/* console.log(file) */
}
}
+9
View File
@@ -0,0 +1,9 @@
interface Row {
Asistencia: number
Nombre: string
'Apellido Paterno': string
'Apellido Materno': string
Carrera: string
'Institución Procedencia': string
Email: string
}
+26 -1
View File
@@ -1,6 +1,21 @@
import { Body, Controller, Param, ParseIntPipe, Post } from '@nestjs/common';
import {
Body,
Controller,
HttpException,
HttpStatus,
Param,
ParseIntPipe,
Post,
Res,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { EmailDto } from './dto/emailDto.dto';
import { EmailService } from './email.service';
import { Response } from 'express';
import * as xlsx from 'xlsx';
@Controller('email')
export class EmailController {
@@ -13,4 +28,14 @@ export class EmailController {
): Promise<any> {
return this.emailService.sendEmail(id, emailDto);
}
@Post('constancias')
@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)
}
/* console.log(file) */
return this.emailService.sendConstancias(file, nombreEvento);
}
}
+68 -1
View File
@@ -1,4 +1,4 @@
import { Inject, Injectable } 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';
@@ -6,6 +6,13 @@ import { EventosService } from 'src/eventos/eventos.service';
import { Participante } from 'src/participante/participante.entity';
import { Repository } from 'typeorm';
import { EmailDto } from './dto/emailDto.dto';
import * as xlsx from 'xlsx';
import * as pdfMake from 'pdfmake/build/pdfmake';
import * as pdfFonts from 'pdfmake/build/vfs_fonts';
/* import * as PDFDocument from 'pdfkit'; */
import * as fs from 'fs';
import * as path from 'path';
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
@Injectable()
export class EmailService {
@@ -49,4 +56,64 @@ export class EmailService {
return participantes;
}
async sendConstancias(file, nombreEvento) {
/* console.log(file) */
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);
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);
page.drawText(text, {
x: (page.getWidth() - textWidth) / 2,
y: (page.getHeight() - textHeight) / 2,
size: fontSize,
font: font,
color: rgb(0/255, 0/255, 0/255),
});
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 pdfBytes = await pdfDoc.save();
const attachment = {
filename: `Constancia ${nombreEvento} - ${row.Nombre}_${row['Apellido Paterno']}.pdf`,
content: pdfBytes,
};
/* const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob); */
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.`,
attachments: [attachment],
});
}
}
}
@@ -46,12 +46,25 @@ export class EventoParticipanteService {
id_evento: number,
registrarParticipanteDto: RegistrarParticipanteDto,
) {
/* console.log(id_evento);
console.log(registrarParticipanteDto); */
return this.participanteExiste(registrarParticipanteDto.email).then(
(res) => {
if (res) {
this.registrarEnEvento(id_evento, res);
res.nombre = registrarParticipanteDto.nombre;
res.apellido_paterno = registrarParticipanteDto.apellido_paterno;
res.apellido_materno = registrarParticipanteDto.apellido_materno;
res.carrera = registrarParticipanteDto.carrera;
res.institucion_procedencia =
registrarParticipanteDto.institucion_procedencia;
//si el correo ya existe en la base de datos solo se le cambia el nombre, apellidos, carrera e institucion
return this.participanteRepository
.save(res)
.then((participanteActualizado) => {
this.registrarEnEvento(id_evento, participanteActualizado);
return participanteActualizado;
});
} else {
this.participanteRepository
.save(this.participanteRepository.create(registrarParticipanteDto))
+5
View File
@@ -58,4 +58,9 @@ export class EventosController {
return this.eventoService.getEventosDisponibles()
}
@Post('disponibles/pendientesDconstancia')
getEventosDisponiblesPendientes(){
return this.eventoService.geteventosDisponiblesMasDosDias()
}
}
+10 -2
View File
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { EventoParticipante } from 'src/evento-participante/evento-participante/eventoParticipante.entity';
import { Participante } from 'src/participante/participante.entity';
import { MoreThanOrEqual, Repository } from 'typeorm';
import { Between, MoreThanOrEqual, Repository } from 'typeorm';
import { actualizarEventoDto } from './dto/actualizarEvento.dto';
import { crearEventoDto } from './dto/crearEvento.dto';
import { Evento } from './evento.entity';
@@ -162,7 +162,7 @@ export class EventosService {
});
});
console.log(workbook.description);
/* console.log(workbook.description); */
// Generar el archivo Excel y guardarlo en el servidor
const fileName = `participantes-${idEvento}.xlsx`;
@@ -186,4 +186,12 @@ export class EventosService {
},
});
}
geteventosDisponiblesMasDosDias(){
const hoy = new Date();
return this.eventoRepository.createQueryBuilder("evento")
.where(`DATE_ADD(evento.fecha_fin, INTERVAL 3 DAY) >= :hoy`, { hoy })
.andWhere('fecha_fin <= :hoy', {hoy})
.getMany();
}
}
+7 -1
View File
@@ -1,9 +1,11 @@
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { join } from 'path';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const app = await NestFactory.create<NestExpressApplication>(AppModule);
const config = new DocumentBuilder()
.addBearerAuth()
@@ -14,6 +16,10 @@ async function bootstrap() {
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
app.useStaticAssets(join(__dirname, '..', 'public'), {
prefix: '/public/',
});
app.enableCors();
await app.listen(AppModule.port);
}