diff --git a/.gitignore b/.gitignore index 1f69350..6f9159a 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,4 @@ pids # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json -imagenes +fotografias diff --git a/src/adscripcion/adscripcion.controller.spec.ts b/src/adscripcion/adscripcion.controller.spec.ts deleted file mode 100644 index 0d73f84..0000000 --- a/src/adscripcion/adscripcion.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AdscripcionController } from './adscripcion.controller'; // Asumiendo que el controlador se llama EdificioController - -describe('AdscripcionController', () => { - let controller: AdscripcionController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [AdscripcionController], - }).compile(); - - controller = module.get(AdscripcionController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/adscripcion/adscripcion.controller.ts b/src/adscripcion/adscripcion.controller.ts index 6625136..aadf602 100644 --- a/src/adscripcion/adscripcion.controller.ts +++ b/src/adscripcion/adscripcion.controller.ts @@ -7,7 +7,6 @@ export class AdscripcionController { @Get() findAll() { - Logger.debug('find all adscripcion'); return this.adscripcionService.findAll(); } } diff --git a/src/adscripcion/adscripcion.service.spec.ts b/src/adscripcion/adscripcion.service.spec.ts deleted file mode 100644 index b74f297..0000000 --- a/src/adscripcion/adscripcion.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AdscripcionService } from './adscripcion.service'; - -describe('AdscripcionService', () => { - let service: AdscripcionService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [AdscripcionService], - }).compile(); - - service = module.get(AdscripcionService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/app.controller.spec.ts b/src/app.controller.spec.ts deleted file mode 100644 index d22f389..0000000 --- a/src/app.controller.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AppController } from './app.controller'; -import { AppService } from './app.service'; - -describe('AppController', () => { - let appController: AppController; - - beforeEach(async () => { - const app: TestingModule = await Test.createTestingModule({ - controllers: [AppController], - providers: [AppService], - }).compile(); - - appController = app.get(AppController); - }); - - describe('root', () => { - it('should return "Hello World!"', () => { - expect(appController.getHello()).toBe('Hello World!'); - }); - }); -}); diff --git a/src/app.module.ts b/src/app.module.ts index 8f545da..6a9c6d1 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -20,10 +20,10 @@ import { Profesor } from './profesor/entities/profesor.entity'; import { AdscripcionModule } from './adscripcion/adscripcion.module'; import { EdificioModule } from './edificio/edificio.module'; import { CategoriaModule } from './categoria/categoria.module'; -import { ImagenModule } from './images/images.module'; import { DatabaseModule } from './database/database.module'; import { LineasInvestigacionModule } from './lineas-investigacion/lineas-investigacion.module'; import { ProyectosAcademicosModule } from './proyectos-academicos/proyectos-academicos.module'; +import { FotografiaModule } from './fotografia/fotografia.module'; @Module({ imports: [ @@ -43,7 +43,6 @@ import { ProyectosAcademicosModule } from './proyectos-academicos/proyectos-acad username: configService.get('DB_USER'), password: configService.get('DB_PASSWORD'), database: configService.get('DB_DATABASE'), - //dropSchema: true, entities: [ Profesor, Usuario, @@ -55,14 +54,15 @@ import { ProyectosAcademicosModule } from './proyectos-academicos/proyectos-acad ProyectosAcademicos, TipoUsuario, ], - synchronize: true, + //dropSchema: true, + //synchronize: true, JAMA USAR ESTO EN PRODUCCION BORRA DATOS DE LA DB }), }), ServeStaticModule.forRoot({ - rootPath: join(__dirname, '..', 'imagenes'), - serveRoot: '/imagenes', + rootPath: join(__dirname, '..', 'fotografias'), + serveRoot: '/fotografias', }), - ImagenModule, + FotografiaModule, ProfesorModule, AdscripcionModule, EdificioModule, diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 355d35a..4841da1 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -24,10 +24,8 @@ import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { SignUpDto } from './dto/sign-up.dto'; import { JwtAuthGuard } from 'src/permissions/jwt.guard'; -@ApiBearerAuth() @ApiTags('Auth') @Controller('auth') -@UseGuards(RolesGuard) export class AuthController { constructor(private authService: AuthService) {} @@ -45,7 +43,9 @@ export class AuthController { summary: 'Registro de usuario', description: 'Registro ded administradores o responsables', }) + @ApiBearerAuth() @UseGuards(JwtAuthGuard) + @UseGuards(RolesGuard) @Roles(Role.Admin) async register(@Body() data: SignUpDto) { Logger.debug('register user'); diff --git a/src/categoria/categiria.controller.spec.ts b/src/categoria/categiria.controller.spec.ts deleted file mode 100644 index 1455267..0000000 --- a/src/categoria/categiria.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { CategoriaController } from './categoria.controller'; - -describe('CategoriaController', () => { - let controller: CategoriaController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [CategoriaController], - }).compile(); - - controller = module.get(CategoriaController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/categoria/categoria.controller.ts b/src/categoria/categoria.controller.ts index 72293cc..1f26041 100644 --- a/src/categoria/categoria.controller.ts +++ b/src/categoria/categoria.controller.ts @@ -7,7 +7,6 @@ export class CategoriaController { @Get() findAll() { - Logger.debug('find all categoria'); return this.categoriaService.findAll(); } } diff --git a/src/categoria/categoria.service.spec.ts b/src/categoria/categoria.service.spec.ts deleted file mode 100644 index 9c5f1b4..0000000 --- a/src/categoria/categoria.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { CategoriaService } from './categoria.service'; - -describe('CategoriaService', () => { - let service: CategoriaService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [CategoriaService], - }).compile(); - - service = module.get(CategoriaService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/database/database.controller.spec.ts b/src/database/database.controller.spec.ts deleted file mode 100644 index d453bdf..0000000 --- a/src/database/database.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { DatabaseController } from './database.controller'; - -describe('DatabaseController', () => { - let controller: DatabaseController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [DatabaseController], - }).compile(); - - controller = module.get(DatabaseController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/database/database.controller.ts b/src/database/database.controller.ts index 86690f7..aaad580 100644 --- a/src/database/database.controller.ts +++ b/src/database/database.controller.ts @@ -1,6 +1,6 @@ import { Controller, Post } from '@nestjs/common'; import { DatabaseService } from './database.service'; -import { ApiTags } from '@nestjs/swagger'; +import { ApiOperation, ApiTags } from '@nestjs/swagger'; @ApiTags('Carga de Datos') @Controller('database') @@ -12,4 +12,15 @@ export class DatabaseController { await this.databaseService.cargarDatosDesdeExcel(); return { message: 'Datos cargados exitosamente' }; } + + @Post('migrar-datos') + @ApiOperation({ + summary: 'Migrar datos a la nueva base de datos', + description: + 'Este endpoint ejecuta la migración desde la base de datos antigua a la nueva.', + }) + async migrateDatabase() { + await this.databaseService.migrateDB(); + return { message: 'Migración completada exitosamente' }; + } } diff --git a/src/database/database.service.spec.ts b/src/database/database.service.spec.ts deleted file mode 100644 index b806f31..0000000 --- a/src/database/database.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { DatabaseService } from './database.service'; - -describe('DatabaseService', () => { - let service: DatabaseService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [DatabaseService], - }).compile(); - - service = module.get(DatabaseService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/database/database.service.ts b/src/database/database.service.ts index 790754b..c449008 100644 --- a/src/database/database.service.ts +++ b/src/database/database.service.ts @@ -60,4 +60,8 @@ export class DatabaseService { await Promise.all(data.map((row) => repo.save(row))); } } + + async migrateDB(): Promise { + + } } diff --git a/src/datos-academicos/datos-academicos.controller.ts b/src/datos-academicos/datos-academicos.controller.ts index 56b3c13..e5a2fde 100644 --- a/src/datos-academicos/datos-academicos.controller.ts +++ b/src/datos-academicos/datos-academicos.controller.ts @@ -11,15 +11,16 @@ import { DatosAcademicosService } from './datos-academicos.service'; import { ApiBearerAuth, ApiBody, ApiOperation, ApiTags } from '@nestjs/swagger'; import { JwtAuthGuard } from 'src/permissions/jwt.guard'; import { RolesGuard } from 'src/permissions/roles.guard'; +import { Roles } from 'src/permissions/roles.decorator'; +import { Role } from 'src/permissions/role.enum'; @ApiBearerAuth() @ApiTags('datos-academicos') @Controller('datos-academicos') -@UseGuards(JwtAuthGuard, RolesGuard) export class DatosAcademicosController { constructor(private datosAcademicosService: DatosAcademicosService) {} - @Patch(':id_datos') + @Patch(':id_profesor') @ApiOperation({ summary: 'Actualizar datos academicos de un profesor', description: @@ -29,13 +30,12 @@ export class DatosAcademicosController { schema: { type: 'object', properties: { - id_profesor: { + id_datos: { type: 'number', - description: - 'ID del profesor al que se debe actualizar sus lineas de investigacion', + description: 'ID de los datos que se van a actualizar', example: 1, }, - lineas_inv_html: { + grados_obtenidos: { type: 'string', description: 'Nuevo contenido de líneas de investigación en formato Markdown o HTML', @@ -45,14 +45,21 @@ export class DatosAcademicosController { }, }) async updateDatosAcademicos( - @Param('id_datos') id_datos: number, + @Param('id_profesor') id_profesor: number, + @Body('id_datos') id_datos: number, @Body('grados_obtenidos') grados_obtenidos: string, - @Body('id_profesor') id_profesor: number, - @Body('grado_maximo') grado_maximo: number, ) { - console.log(grados_obtenidos); - console.log(grado_maximo); - console.log(id_profesor); - console.log(id_datos); + if (!grados_obtenidos || grados_obtenidos.trim() === '') { + throw new HttpException( + 'El contenido no puede estar vacío.', + HttpStatus.BAD_REQUEST, + ); + } + + return this.datosAcademicosService.updateDatosAcademicos( + id_datos, + id_profesor, + grados_obtenidos, + ); } } diff --git a/src/datos-academicos/datos-academicos.service.ts b/src/datos-academicos/datos-academicos.service.ts index 0345cad..a7e7ee6 100644 --- a/src/datos-academicos/datos-academicos.service.ts +++ b/src/datos-academicos/datos-academicos.service.ts @@ -1,14 +1,18 @@ -import { Injectable } from '@nestjs/common'; +import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { DatosAcademicos } from './entities/datos_academicos.entity'; import { Profesor } from 'src/profesor/entities/profesor.entity'; +import { ProfesorService } from 'src/profesor/profesor.service'; @Injectable() export class DatosAcademicosService { constructor( @InjectRepository(DatosAcademicos) private readonly datosAcademicosRepository: Repository, + + @Inject(forwardRef(() => ProfesorService)) + private readonly profesorService: ProfesorService, ) {} async create(profesor: Profesor, grado_maximo: string) { @@ -21,6 +25,59 @@ export class DatosAcademicosService { return await this.datosAcademicosRepository.save(datosAcademicos); } + async updateDatosAcademicos( + id_datos: number, + id_profesor: number, + nuevoContenido: string, + ) { + const profesor = await this.profesorService.findOne(id_profesor); + + let datosAcademicos = await this.datosAcademicosRepository.findOne({ + where: { profesor: profesor, id_datos }, + }); + + if (!datosAcademicos) { + Logger.error( + `No se encontraron datos académicos del profesor con ID ${id_profesor} al intentar actualizar. Se procederá a crear una nueva.`, + ); + + // Crear nuevos datos académicos con el contenido + datosAcademicos = this.datosAcademicosRepository.create({ + profesor: profesor, + grados_obtenidos: nuevoContenido, // Se asigna el nuevo contenido directamente + grado_maximo: 'Sin Grado Maximo', // Valor predeterminado + }); + + // Guardar los nuevos datos académicos + await this.datosAcademicosRepository.save(datosAcademicos); + } else { + // Si ya existen, actualizar su contenido + datosAcademicos.grados_obtenidos = nuevoContenido; + await this.datosAcademicosRepository.save(datosAcademicos); + } + + return datosAcademicos; // Devolver los datos académicos actualizados o recién creados + } + + async updateGradoMaximo(id_profesor: number, nuevoGradoMaximo: string) { + const profesor = await this.profesorService.findOne(id_profesor); + + const datosAcademicos = await this.datosAcademicosRepository.findOne({ + where: { profesor: profesor }, + }); + + if (!datosAcademicos) { + Logger.error( + `No se encontraron datos académicos del profesor con ID ${id_profesor} al intentar actualizar el grado máximo \n Se procede a crear una nueva`, + ); + return await this.create(profesor, nuevoGradoMaximo); + } + + datosAcademicos.grado_maximo = nuevoGradoMaximo; + + return await this.datosAcademicosRepository.save(datosAcademicos); + } + async removeByProfesorId(id_profesor: number): Promise { await this.datosAcademicosRepository.delete({ profesor: { id_profesor } }); } diff --git a/src/datos-academicos/entities/datos_academicos.entity.ts b/src/datos-academicos/entities/datos_academicos.entity.ts index 06e6141..141ba38 100644 --- a/src/datos-academicos/entities/datos_academicos.entity.ts +++ b/src/datos-academicos/entities/datos_academicos.entity.ts @@ -21,9 +21,7 @@ export class DatosAcademicos { @Column({ type: 'varchar', length: 600, nullable: true }) grados_obtenidos: string; - @ManyToOne(() => Profesor, (profesor) => profesor.lineasInvestigacion, { - onDelete: 'CASCADE', - }) + @ManyToOne(() => Profesor, (profesor) => profesor.lineasInvestigacion) @JoinColumn({ name: 'id_profesor' }) profesor: Profesor; } diff --git a/src/edificio/edificio.controller.spec.ts b/src/edificio/edificio.controller.spec.ts deleted file mode 100644 index a30c9c4..0000000 --- a/src/edificio/edificio.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { EdificioController } from './edificio.controller'; - -describe('EdificioController', () => { - let controller: EdificioController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [EdificioController], - }).compile(); - - controller = module.get(EdificioController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/edificio/edificio.controller.ts b/src/edificio/edificio.controller.ts index fbe06cc..95b8e38 100644 --- a/src/edificio/edificio.controller.ts +++ b/src/edificio/edificio.controller.ts @@ -7,7 +7,6 @@ export class EdificioController { @Get() findAll() { - Logger.debug('find all edificio'); return this.edificioService.findAll(); } } diff --git a/src/edificio/edificio.service.spec.ts b/src/edificio/edificio.service.spec.ts deleted file mode 100644 index 9c7704c..0000000 --- a/src/edificio/edificio.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { EdificioService } from './edificio.service'; // Asegúrate de importar el servicio correcto - -describe('EdificioService', () => { - let service: EdificioService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [EdificioService], // Asegúrate de incluir el servicio correcto aquí - }).compile(); - - service = module.get(EdificioService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/fotografia/fotografia.controller.ts b/src/fotografia/fotografia.controller.ts new file mode 100644 index 0000000..0fe549a --- /dev/null +++ b/src/fotografia/fotografia.controller.ts @@ -0,0 +1,85 @@ +import { + Controller, + Post, + Patch, + UseInterceptors, + UploadedFile, + Body, +} from '@nestjs/common'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { FotografiaService } from './fotografia.service'; +import { + ApiBody, + ApiConsumes, + ApiOperation, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { Express } from 'express'; + +@ApiTags('fotografia') +@Controller('fotografia') +export class FotografiaController { + constructor(private readonly fotografiaService: FotografiaService) {} + + @Post('upload') + @UseInterceptors(FileInterceptor('file')) + @ApiOperation({ summary: 'Subir una imagen con un nombre personalizado' }) + @ApiConsumes('multipart/form-data') + @ApiBody({ + schema: { + type: 'object', + properties: { + customName: { + type: 'string', + description: 'Nombre personalizado de la imagen', + }, + file: { + type: 'string', + format: 'binary', + }, + }, + }, + }) + @ApiResponse({ status: 201, description: 'Imagen subida exitosamente' }) + uploadFile( + @UploadedFile() file: Express.Multer.File, + @Body('customName') customName: string, + ) { + return { message: 'Imagen subida exitosamente', fileName: file.filename }; + } + + @Post('update') + @UseInterceptors(FileInterceptor('file')) + @ApiOperation({ + summary: 'Actualizar una imagen existente con un nombre personalizado', + }) + @ApiConsumes('multipart/form-data') + @ApiBody({ + schema: { + type: 'object', + properties: { + oldFileName: { + type: 'string', + description: 'Nombre de la imagen antigua', + }, + customName: { + type: 'string', + description: 'Nuevo nombre personalizado de la imagen', + }, + file: { + type: 'string', + format: 'binary', + }, + }, + }, + }) + @ApiResponse({ status: 200, description: 'Imagen actualizada correctamente' }) + updateFile( + @UploadedFile() file: Express.Multer.File, + @Body('oldFileName') oldFileName: string, + @Body('customName') customName: string, + ) { + return this.fotografiaService.updateFile(oldFileName, file, customName); + } +} diff --git a/src/fotografia/fotografia.module.ts b/src/fotografia/fotografia.module.ts new file mode 100644 index 0000000..03ef27a --- /dev/null +++ b/src/fotografia/fotografia.module.ts @@ -0,0 +1,37 @@ +import { Module } from '@nestjs/common'; +import { MulterModule } from '@nestjs/platform-express'; +import { FotografiaController } from './fotografia.controller'; +import { FotografiaService } from './fotografia.service'; +import { diskStorage } from 'multer'; +import { join } from 'path'; +import { existsSync, mkdirSync } from 'fs'; + +@Module({ + imports: [ + MulterModule.register({ + storage: diskStorage({ + destination: (req, file, cb) => { + const uploadPath = join(__dirname, '..', '..', 'fotografias'); + if (!existsSync(uploadPath)) { + mkdirSync(uploadPath, { recursive: true }); + } + cb(null, uploadPath); + }, + filename: (req, file, cb) => { + const uniqueSuffix = + Date.now() + '-' + Math.round(Math.random() * 1e9); + const customName = req.body.customName + ? req.body.customName.toLowerCase().replace(/\s+/g, '-') + : 'image'; + cb( + null, + `${customName}-${uniqueSuffix}${file.originalname.substring(file.originalname.lastIndexOf('.'))}`, + ); + }, + }), + }), + ], + controllers: [FotografiaController], + providers: [FotografiaService], +}) +export class FotografiaModule {} diff --git a/src/fotografia/fotografia.service.ts b/src/fotografia/fotografia.service.ts new file mode 100644 index 0000000..7ebc35d --- /dev/null +++ b/src/fotografia/fotografia.service.ts @@ -0,0 +1,58 @@ +import { Injectable, BadRequestException } from '@nestjs/common'; +import { renameSync, existsSync, unlinkSync, mkdirSync } from 'fs'; +import { join } from 'path'; + +@Injectable() +export class FotografiaService { + updateFile( + oldFileName: string, + newFile: Express.Multer.File, + customName?: string, + ) { + try { + const uploadPath = join(__dirname, '..', '..', 'fotografias'); + const oldFilePath = join(uploadPath, oldFileName); + const tempFilePath = newFile.path; // Ruta temporal del archivo subido + + // 🛠️ 1. Asegurar que la carpeta 'fotografias/' exista + if (!existsSync(uploadPath)) { + mkdirSync(uploadPath, { recursive: true }); + } + + // 🛠️ 2. Verificar si el archivo temporal realmente existe antes de renombrar + if (!existsSync(tempFilePath)) { + throw new BadRequestException('El archivo temporal no fue encontrado.'); + } + + // 🛠️ 3. Eliminar la imagen anterior si existe + if (oldFileName && existsSync(oldFilePath)) { + try { + unlinkSync(oldFilePath); + console.log(`✅ Imagen anterior eliminada: ${oldFileName}`); + } catch (error) { + console.error(`❌ Error al eliminar la imagen anterior: ${error.message}`); + } + } else { + console.log('No se encontró imagen anterior para eliminar.', oldFileName); + } + + // 🛠️ 4. Generar nuevo nombre de archivo con customName y timestamp + const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9); + const fileExtension = newFile.originalname.substring(newFile.originalname.lastIndexOf('.')); + const newFileName = `${customName ? customName.toLowerCase().replace(/\s+/g, '-') : 'image'}-${uniqueSuffix}${fileExtension}`; + const newFilePath = join(uploadPath, newFileName); + + // 🛠️ 5. Mover el archivo de la carpeta temporal a 'fotografias/' con su nuevo nombre + renameSync(tempFilePath, newFilePath); + console.log(`Imagen subida con éxito: ${newFileName}`); + + console.log('-------------------') + return { + message: 'Imagen actualizada correctamente', + fileName: newFileName, + }; + } catch (error) { + throw new BadRequestException(`Error en la actualización de la imagen: ${error.message}`); + } + } +} diff --git a/src/images/images.controller.ts b/src/images/images.controller.ts deleted file mode 100644 index ca9a90d..0000000 --- a/src/images/images.controller.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Controller, Post, Body, Put, Logger } from '@nestjs/common'; -import { ImagenService } from './images.service'; - -@Controller('imagen') -export class ImagenController { - constructor(private readonly imagenService: ImagenService) {} - - @Post() - async upload(@Body() body) { - Logger.debug('upload image'); - const { fotografia, nombre } = body; - - const imageUrl = await this.imagenService.saveImage(fotografia, nombre); - - return { message: 'Image uploaded successfully', imageUrl }; - } - - @Put() - async modify(@Body() body) { - Logger.debug('modify image'); - const { fotografia, nombre } = body; - - const imageUrl = await this.imagenService.modify(fotografia, nombre); - - return { message: 'Image uploaded successfully', imageUrl }; - } -} diff --git a/src/images/images.module.ts b/src/images/images.module.ts deleted file mode 100644 index 55a1035..0000000 --- a/src/images/images.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from '@nestjs/common'; -import { ImagenController } from './images.controller'; -import { ImagenService } from './images.service'; - -@Module({ - controllers: [ImagenController], - providers: [ImagenService], -}) -export class ImagenModule {} - diff --git a/src/images/images.service.ts b/src/images/images.service.ts deleted file mode 100644 index 43a468d..0000000 --- a/src/images/images.service.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Injectable, BadRequestException, Logger } from '@nestjs/common'; -import * as fs from 'fs'; -import * as path from 'path'; - -@Injectable() -export class ImagenService { - async saveImage(fotografia: string, id: string): Promise { - if (!fotografia) { - throw new BadRequestException('No image provided'); - } - - // Extract the image extension - const matches = fotografia.match(/^data:image\/([a-zA-Z]+);base64,/); - if (!matches) { - throw new BadRequestException('Invalid image format'); - } - const ext = matches[1]; - - // Decode base64 string and save the image - const buffer = Buffer.from(fotografia.split(',')[1], 'base64'); - const imageName = `${id}.${ext}`; - - // Save image to the 'imagenes' directory in the root of the project - const imagePath = path.join(process.cwd(), 'imagenes', imageName); - - // Ensure the directory exists - const dirPath = path.join(process.cwd(), 'imagenes'); - if (!fs.existsSync(dirPath)) { - fs.mkdirSync(dirPath, { recursive: true }); - } - - fs.writeFileSync(imagePath, buffer); - - return imageName; - } - - async deleteImage(id: string): Promise { - Logger.debug('delete image'); - const imageDir = path.join(process.cwd(), 'imagenes'); - const imagePath = path.join(imageDir, id); - - const extensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'avif']; - - let fileFound = false; - for (const ext of extensions) { - const fullImagePath = `${imagePath}.${ext}`; - if (fs.existsSync(fullImagePath)) { - fs.unlinkSync(fullImagePath); - fileFound = true; - break; - } - } - - if (!fileFound) { - console.log('Image not found, but continuing to save the new image.'); - } - } - - async modify(fotografia: string, id: string): Promise { - if (!fotografia) { - throw new BadRequestException('No image provided'); - } - - await this.deleteImage(id); - - // Extract the image extension - const matches = fotografia.match(/^data:image\/([a-zA-Z]+);base64,/); - if (!matches) { - throw new BadRequestException('Invalid image format'); - } - const ext = matches[1]; - - // Decode base64 string and save the image - const buffer = Buffer.from(fotografia.split(',')[1], 'base64'); - const imageName = `${id}.${ext}`; - - // Save image to the 'imagenes' directory in the root of the project - const imagePath = path.join(process.cwd(), 'imagenes', imageName); - - // Ensure the directory exists - const dirPath = path.join(process.cwd(), 'imagenes'); - if (!fs.existsSync(dirPath)) { - fs.mkdirSync(dirPath, { recursive: true }); - } - - fs.writeFileSync(imagePath, buffer); - - return imageName; - } -} diff --git a/src/lineas-investigacion/entities/lineas-investigacion.entity.ts b/src/lineas-investigacion/entities/lineas-investigacion.entity.ts index 38b2d87..01d7551 100644 --- a/src/lineas-investigacion/entities/lineas-investigacion.entity.ts +++ b/src/lineas-investigacion/entities/lineas-investigacion.entity.ts @@ -12,15 +12,10 @@ export class LineasInvestigacion { @PrimaryGeneratedColumn() id_linea_inv: number; - @Column({ type: 'int', nullable: false, unique: true }) - id_profesor: number; - - @Column({ type: 'longtext', nullable: true }) + @Column({ type: 'varchar', length: 600, nullable: true }) lineas_inv_html: string; - @ManyToOne(() => Profesor, (profesor) => profesor.lineasInvestigacion, { - onDelete: 'CASCADE', - }) + @ManyToOne(() => Profesor, (profesor) => profesor.lineasInvestigacion) @JoinColumn({ name: 'id_profesor' }) profesor: Profesor; } diff --git a/src/lineas-investigacion/lineas-investigacion.controller.ts b/src/lineas-investigacion/lineas-investigacion.controller.ts index 3dcff54..3476072 100644 --- a/src/lineas-investigacion/lineas-investigacion.controller.ts +++ b/src/lineas-investigacion/lineas-investigacion.controller.ts @@ -19,7 +19,7 @@ import { RolesGuard } from 'src/permissions/roles.guard'; export class LineasInvestigacionController { constructor(private lineasInvestigacionService: LineasInvestigacionService) {} - @Patch(':id_linea_inv') + @Patch(':id_profesor') @ApiOperation({ summary: 'Actualizar líneas de investigación de un profesor', description: @@ -29,10 +29,10 @@ export class LineasInvestigacionController { schema: { type: 'object', properties: { - id_profesor: { + id_linea_inv: { type: 'number', description: - 'ID del profesor al que se debe actualizar sus lineas de investigacion', + 'ID de las lineas de investigacion que se deben actualizar', example: 1, }, lineas_inv_html: { @@ -45,9 +45,9 @@ export class LineasInvestigacionController { }, }) async updateLineasInvestigacion( - @Param('id_linea_inv') id_linea_inv: number, + @Param('id_profesor') id_profesor: number, + @Body('id_linea_inv') id_linea_inv: number, @Body('lineas_inv_html') lineasInvHtml: string, - @Body('id_profesor') id_profesor: number, ) { if (!lineasInvHtml || lineasInvHtml.trim() === '') { throw new HttpException( diff --git a/src/lineas-investigacion/lineas-investigacion.service.ts b/src/lineas-investigacion/lineas-investigacion.service.ts index ca95aca..987b00e 100644 --- a/src/lineas-investigacion/lineas-investigacion.service.ts +++ b/src/lineas-investigacion/lineas-investigacion.service.ts @@ -31,24 +31,30 @@ export class LineasInvestigacionService { ) { const profesor = await this.profesorService.findOne(id_profesor); - console.log(profesor); - console.log(nuevoContenido); - - const lineasInvestigacion = - await this.lineasInvestigacionRepository.findOne({ - where: { profesor: profesor, id_linea_inv }, - }); + let lineasInvestigacion = await this.lineasInvestigacionRepository.findOne({ + where: { profesor: profesor, id_linea_inv }, + }); if (!lineasInvestigacion) { Logger.error( - `No se encontró la línea de investigación del profesor con ID ${id_profesor} al intentar actualizarla \n Se procede a crear una nueva`, + `No se encontró la línea de investigación del profesor con ID ${id_profesor} al intentar actualizarla. Se procederá a crear una nueva.`, ); - return await this.create(profesor); + + // Crear una nueva línea de investigación con el contenido + lineasInvestigacion = this.lineasInvestigacionRepository.create({ + profesor: profesor, + lineas_inv_html: nuevoContenido, // Se asigna el nuevo contenido directamente + }); + + // Guardar la nueva línea de investigación + await this.lineasInvestigacionRepository.save(lineasInvestigacion); + } else { + // Si ya existe, actualizar su contenido + lineasInvestigacion.lineas_inv_html = nuevoContenido; + await this.lineasInvestigacionRepository.save(lineasInvestigacion); } - lineasInvestigacion.lineas_inv_html = nuevoContenido; - - return await this.lineasInvestigacionRepository.save(lineasInvestigacion); + return lineasInvestigacion; // Devolver la línea de investigación actualizada o recién creada } async removeByProfesorId(id_profesor: number): Promise { diff --git a/src/main.ts b/src/main.ts index 4fb1556..1ec139a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -29,6 +29,7 @@ async function bootstrap() { .addTag('Usuario') .addTag('lineas-investigacion') .addTag('proyectos-academicos') + .addTag('datos-academicos') .addBearerAuth() .build(); const documentFactory = () => SwaggerModule.createDocument(app, config); @@ -40,8 +41,8 @@ async function bootstrap() { app.use(bodyParser.json({ limit: '1mb' })); app.use(bodyParser.urlencoded({ limit: '1mb', extended: true })); - app.useStaticAssets(path.join(__dirname, '..', 'imagenes'), { - prefix: '/imagenes/', + app.useStaticAssets(path.join(__dirname, '..', 'fotografias'), { + prefix: '/fotografias/', }); await app.listen(3000); diff --git a/src/profesor/entities/profesor.entity.ts b/src/profesor/entities/profesor.entity.ts index f777695..9a1a3e9 100644 --- a/src/profesor/entities/profesor.entity.ts +++ b/src/profesor/entities/profesor.entity.ts @@ -67,14 +67,6 @@ export class Profesor { }) fecha_actualizacion: Date; - /* Datos Personales Eliminados */ - - /* @Column({ type: 'varchar', length: 13, nullable: true }) - tel_personal: string; */ - - /* @Column({ type: 'varchar', length: 65, nullable: true }) - correo_per: string; */ - /* RELACIONES */ @Column({ type: 'int', nullable: true }) @@ -92,49 +84,44 @@ export class Profesor { @ManyToOne(() => Edificio, (edificio) => edificio.profesores) @JoinColumn({ name: 'id_edificio', - referencedColumnName: 'id_edificio', - foreignKeyConstraintName: 'FK_edificio_fk', }) edificio: Edificio; @ManyToOne(() => Adscripcion, (adscripcion) => adscripcion.profesores) @JoinColumn({ name: 'id_adscripcion', - referencedColumnName: 'id_adscripcion', - foreignKeyConstraintName: 'FK_adscripcion_fk', }) adscripcion: Adscripcion; @ManyToOne(() => Categoria, (categoria) => categoria.profesores) @JoinColumn({ name: 'id_categoria', - referencedColumnName: 'id_categoria', - foreignKeyConstraintName: 'FK_categoria_fk', }) categoria: Categoria; @ManyToOne(() => Usuario, (usuario) => usuario.profesores) @JoinColumn({ name: 'id_usuario', - referencedColumnName: 'id_usuario', - foreignKeyConstraintName: 'FK_usuario_fk', }) usuario: Usuario; @OneToMany( () => DatosAcademicos, (datosAcademicos) => datosAcademicos.profesor, - { cascade: true }, ) datosAcademicos: DatosAcademicos[]; - @OneToMany(() => LineasInvestigacion, (linea) => linea.profesor, { - cascade: true, - }) + @OneToMany(() => LineasInvestigacion, (linea) => linea.profesor) lineasInvestigacion: LineasInvestigacion[]; - @OneToMany(() => ProyectosAcademicos, (proyecto) => proyecto.profesor, { - cascade: true, - }) + @OneToMany(() => ProyectosAcademicos, (proyecto) => proyecto.profesor) proyectosAcademicos: ProyectosAcademicos[]; } + +/* Datos Personales Eliminados */ + +/* @Column({ type: 'varchar', length: 13, nullable: true }) + tel_personal: string; */ + +/* @Column({ type: 'varchar', length: 65, nullable: true }) + correo_per: string; */ diff --git a/src/profesor/profesor.controller.ts b/src/profesor/profesor.controller.ts index bab9f81..52ccdbd 100644 --- a/src/profesor/profesor.controller.ts +++ b/src/profesor/profesor.controller.ts @@ -40,12 +40,12 @@ interface RequestWithUser extends Request { @ApiBearerAuth() @ApiTags('Profesor') @Controller('profesor') -@UseGuards(JwtAuthGuard, RolesGuard) export class ProfesorController { constructor(private profesorService: ProfesorService) {} @Post() @ApiOperation({ summary: 'Crear un profesor' }) + @UseGuards(JwtAuthGuard, RolesGuard) @Roles(Role.Responsable, Role.Admin) async register(@Body() data: CreateProfesorDto, @Req() req: RequestWithUser) { return this.profesorService.register(data, req.user); @@ -97,19 +97,19 @@ export class ProfesorController { description: 'Filtrar por nombre del profesor', }) @ApiQuery({ - name: 'id_adscripcion', + name: 'adscripcion', required: false, example: 2, description: 'Filtrar por ID de adscripción', }) @ApiQuery({ - name: 'id_edificio', + name: 'edificio', required: false, example: 3, description: 'Filtrar por ID de edificio', }) @ApiQuery({ - name: 'id_categoria', + name: 'categoria', required: false, example: 1, description: 'Filtrar por ID de categoría', @@ -118,9 +118,9 @@ export class ProfesorController { @Query('page') page: number = 1, @Query('limit') limit: number = 12, @Query('nombre') nombre?: string, - @Query('id_adscripcion') id_adscripcion?: number, - @Query('id_edificio') id_edificio?: number, - @Query('id_categoria') id_categoria?: number, + @Query('adscripcion') adscripcion?: number, + @Query('edificio') edificio?: number, + @Query('categoria') categoria?: number, ) { // Validaciones básicas page = page < 1 ? 1 : page; @@ -129,9 +129,9 @@ export class ProfesorController { // Filtros dinámicos const filters: any = {}; if (nombre) filters.nombre = nombre; - if (id_adscripcion) filters.id_adscripcion = Number(id_adscripcion); - if (id_edificio) filters.id_edificio = Number(id_edificio); - if (id_categoria) filters.id_categoria = Number(id_categoria); + if (adscripcion) filters.adscripcion = Number(adscripcion); + if (edificio) filters.edificio = Number(edificio); + if (categoria) filters.categoria = Number(categoria); return await this.profesorService.findAllPaginated(page, limit, filters); } @@ -161,6 +161,7 @@ export class ProfesorController { summary: 'Elimina un profesor con sus proyectos y lineas de investigacion por su id_profesor', }) + @UseGuards(JwtAuthGuard, RolesGuard) @Roles(Role.Responsable, Role.Admin) async remove(@Param('id_profesor', ParseIntPipe) id_profesor: number) { return this.profesorService.remove(id_profesor); diff --git a/src/profesor/profesor.service.ts b/src/profesor/profesor.service.ts index 81d502b..0440ca7 100644 --- a/src/profesor/profesor.service.ts +++ b/src/profesor/profesor.service.ts @@ -50,25 +50,18 @@ export class ProfesorService { id_profesor: number, data: UpdateProfesorDto, ): Promise { + const { grado_maximo } = data; const profesor = await this.findOne(id_profesor); Object.assign(profesor, data); - return await this.profesorRepository.save(profesor); - } - private async findAvailableId(): Promise { - let id = 1; - let exists = true; - - while (exists) { - if ( - !(await this.profesorRepository.findOne({ where: { id_profesor: id } })) - ) { - Logger.debug(`id_profesor = ${id}`); - exists = false; - return id; - } - id++; + if (grado_maximo) { + await this.datosAcademicosService.updateGradoMaximo( + profesor.id_profesor, + grado_maximo, + ); } + + return await this.profesorRepository.save(profesor); } //register teacher @@ -175,6 +168,18 @@ export class ProfesorService { ], }); + if (profesor.lineasInvestigacion.length === 0) { + await this.lineasInvestigacionService.create(profesor); + } + + if (profesor.proyectosAcademicos.length === 0) { + await this.proyectosAcademicosService.create(profesor); + } + + if (profesor.datosAcademicos.length === 0) { + await this.datosAcademicosService.create(profesor, ''); + } + if (!profesor) { throw new HttpException('Profesor not found', 404); } @@ -189,7 +194,8 @@ export class ProfesorService { ): Promise<{ profesores: Profesor[]; total: number; totalPages: number }> { const query = this.profesorRepository.createQueryBuilder('profesor'); - // 🔹 Filtro por Nombre (LIKE %nombre%) + console.log(filters); + if (filters.nombre) { Logger.debug('Filter by name'); query.andWhere('LOWER(profesor.nombre) LIKE LOWER(:nombre)', { @@ -197,15 +203,13 @@ export class ProfesorService { }); } - // 🔹 Filtro por Adscripción if (filters.adscripcion && !isNaN(filters.adscripcion)) { Logger.debug('Filter by adscription'); - query.andWhere('profesor.adscripcion = :adscripcion', { + query.andWhere('profesor.id_adscripcion = :adscripcion', { adscripcion: Number(filters.adscripcion), }); } - // 🔹 Filtro por Categoría if (filters.categoria && !isNaN(filters.categoria)) { Logger.debug('Filter by category'); query.andWhere('profesor.categoria = :categoria', { @@ -213,26 +217,15 @@ export class ProfesorService { }); } - // 🔹 Filtro por Edificio if (filters.edificio && !isNaN(filters.edificio)) { Logger.debug('Filter by building'); - query.andWhere('profesor.edificio = :edificio', { + query.andWhere('profesor.id_edificio = :edificio', { edificio: Number(filters.edificio), }); } - // 🔹 Filtro por Activo - if (typeof filters.activo !== 'undefined') { - const isActive = filters.activo === 'true' || filters.activo === true; - Logger.debug('Filter by active status'); - query.andWhere('profesor.activo = :activo', { activo: isActive }); - } else { - query.orderBy('profesor.activo', 'DESC'); // Si no se especifica, prioriza activos - } - query.addOrderBy('profesor.nombre', 'ASC'); - // 🔹 Ejecutar consulta con paginación const [profesores, total] = await query .skip((page - 1) * limit) .take(limit) diff --git a/src/proyectos-academicos/entities/proyectos-academicos.entity.ts b/src/proyectos-academicos/entities/proyectos-academicos.entity.ts index 444ec87..c782f1b 100644 --- a/src/proyectos-academicos/entities/proyectos-academicos.entity.ts +++ b/src/proyectos-academicos/entities/proyectos-academicos.entity.ts @@ -18,9 +18,7 @@ export class ProyectosAcademicos { @Column({ type: 'varchar', length: 700, nullable: true }) proyectos_html: string; - @ManyToOne(() => Profesor, (profesor) => profesor.proyectosAcademicos, { - onDelete: 'CASCADE', - }) + @ManyToOne(() => Profesor, (profesor) => profesor.proyectosAcademicos) @JoinColumn({ name: 'id_profesor', }) diff --git a/src/proyectos-academicos/proyectos-academicos.controller.ts b/src/proyectos-academicos/proyectos-academicos.controller.ts index ac6a1ca..3674d3c 100644 --- a/src/proyectos-academicos/proyectos-academicos.controller.ts +++ b/src/proyectos-academicos/proyectos-academicos.controller.ts @@ -17,11 +17,10 @@ import { JwtAuthGuard } from 'src/permissions/jwt.guard'; @ApiBearerAuth() @ApiTags('proyectos-academicos') @Controller('proyectos-academicos') -@UseGuards(JwtAuthGuard, RolesGuard) export class ProyectosAcademicosController { constructor(private proyectosAcademicosService: ProyectosAcademicosService) {} - @Patch(':id_proyecto') + @Patch(':id_profesor') @ApiOperation({ summary: 'Actualizar los proyectos academicos de un profesor', description: @@ -47,9 +46,9 @@ export class ProyectosAcademicosController { }, }) async updateProyectosAcademicos( - @Param('id_proyecto') id_proyecto: number, + @Param('id_profesor') id_profesor: number, @Body('proyectos_html') proyectos_html: string, - @Body('id_profesor') id_profesor: number, + @Body('id_proyecto') id_proyecto: number, ) { if (!proyectos_html || proyectos_html.trim() === '') { throw new HttpException( diff --git a/src/proyectos-academicos/proyectos-academicos.service.ts b/src/proyectos-academicos/proyectos-academicos.service.ts index a4d5c48..2de4059 100644 --- a/src/proyectos-academicos/proyectos-academicos.service.ts +++ b/src/proyectos-academicos/proyectos-academicos.service.ts @@ -31,20 +31,30 @@ export class ProyectosAcademicosService { ) { const profesor = await this.profesorService.findOne(id_profesor); - const proyectos = await this.proyectosAcademicosRepository.findOne({ + let proyecto = await this.proyectosAcademicosRepository.findOne({ where: { profesor: profesor, id_proyecto }, }); - if (!proyectos) { + if (!proyecto) { Logger.error( - `No se encontrarón proyectos academicos del profesor con ID ${id_profesor} al intentar actualizarla \n Se procede a crear uno nuevo`, + `No se encontraron proyectos académicos del profesor con ID ${id_profesor} al intentar actualizar. Se procederá a crear uno nuevo.`, ); - return await this.create(profesor); + + // Crear nuevo proyecto con el contenido + proyecto = this.proyectosAcademicosRepository.create({ + profesor: profesor, + proyectos_html: nuevoContenido, // Se asigna el nuevo contenido directamente + }); + + // Guardar el nuevo proyecto + await this.proyectosAcademicosRepository.save(proyecto); + } else { + // Si ya existe, actualizar su contenido + proyecto.proyectos_html = nuevoContenido; + await this.proyectosAcademicosRepository.save(proyecto); } - proyectos.proyectos_html = nuevoContenido; - - return await this.proyectosAcademicosRepository.save(proyectos); + return proyecto; // Devolver el proyecto actualizado o recién creado } async removeByProfesorId(id_profesor: number): Promise { diff --git a/src/usuario/entities/usuario.entity.ts b/src/usuario/entities/usuario.entity.ts index 54a4758..3b05e4d 100644 --- a/src/usuario/entities/usuario.entity.ts +++ b/src/usuario/entities/usuario.entity.ts @@ -26,7 +26,7 @@ export class Usuario { @Column({ type: 'varchar', length: 60, nullable: false }) usuario: string; - @Column({ type: 'varchar', length: 60, nullable: false }) + @Column({ type: 'varchar', length: 60, nullable: false }) // ALTER TABLE usuario CHANGE COLUMN contraseña password VARCHAR(60); password: string; @Column({ type: 'varchar', length: 255, nullable: true }) @@ -41,8 +41,6 @@ export class Usuario { @ManyToOne(() => TipoUsuario, (tipoUsuario) => tipoUsuario.usuarios) @JoinColumn({ name: 'id_tipo_usuario', - referencedColumnName: 'id_tipo_usuario', - foreignKeyConstraintName: 'FK_tipo_usuario', }) tipoUsuario: TipoUsuario;