Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 43b9387a9f | |||
| 0658c1f80b | |||
| 1d8f959eb4 | |||
| f9c72ced6d | |||
| 098c01884e | |||
| 08aade7dcd | |||
| f04f01a39c | |||
| 82550eefcc | |||
| c9f883de3c | |||
| a23ba6da18 |
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Directorio API Dev Container",
|
||||
"dockerFile": "../Dockerfile",
|
||||
"appPort": ["3000:3000"],
|
||||
"postCreateCommand": "npm install",
|
||||
"settings": {
|
||||
"terminal.integrated.defaultProfile.linux": "bash",
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"extensions": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"ms-azuretools.vscode-docker",
|
||||
"streetsidesoftware.code-spell-checker"
|
||||
],
|
||||
"remoteUser": "node"
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
# Usa una imagen de Node.js 22 basada en Alpine
|
||||
FROM node:22.10.0-alpine
|
||||
|
||||
# Establece el directorio de trabajo
|
||||
WORKDIR /app
|
||||
|
||||
# Copia los archivos de dependencias y los instala
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm install
|
||||
|
||||
# Copia el resto del código fuente
|
||||
COPY . .
|
||||
|
||||
# Expone el puerto 3000
|
||||
EXPOSE 3000
|
||||
|
||||
# Comando por defecto para iniciar la aplicación en desarrollo
|
||||
CMD ["npm", "run", "start:dev"]
|
||||
Generated
+2405
-1536
File diff suppressed because it is too large
Load Diff
+3
-1
@@ -28,6 +28,7 @@
|
||||
"@nestjs/passport": "^10.0.3",
|
||||
"@nestjs/platform-express": "^10.3.9",
|
||||
"@nestjs/serve-static": "^4.0.2",
|
||||
"@nestjs/swagger": "^7.4.2",
|
||||
"@nestjs/typeorm": "^10.0.2",
|
||||
"bcrypt": "^5.1.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
@@ -40,7 +41,8 @@
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"sharp": "^0.33.4",
|
||||
"typeorm": "^0.3.20"
|
||||
"typeorm": "^0.3.20",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.0.0",
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
import { Type } from "class-transformer";
|
||||
import { IsBoolean, IsInt, IsNotEmpty, IsNumber, IsString, IsOptional, Length, ValidateNested} from "class-validator";
|
||||
|
||||
export class profesorDto{
|
||||
|
||||
@IsInt()
|
||||
id_profesor: number;
|
||||
|
||||
@IsInt()
|
||||
@IsNotEmpty()
|
||||
id_usuario: number;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Length(0,10)
|
||||
num_trabajador: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Length(0,80)
|
||||
nombre: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Length(0,10)
|
||||
rfc: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Length(0,3)
|
||||
homoclave: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Length(0,13)
|
||||
tel_oficina: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Length(0,10)
|
||||
extension: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Length(0,13)
|
||||
tel_personal: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Length(0,65)
|
||||
correo_pcp: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Length(0,65)
|
||||
correo_per: string;
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
id_adscripcion: number;
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
id_categoria: number;
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
id_edificio: number;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Length(0,256)
|
||||
ubicacion: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Length(0,150)
|
||||
fotografia: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
activo: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
mostrar: boolean;
|
||||
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ProyectosAcademicos)
|
||||
proyectosAcademicos: ProyectosAcademicos[];
|
||||
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => DatosAcademicos)
|
||||
datosAcademicos: DatosAcademicos[];
|
||||
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => LineasInvestigacion)
|
||||
lineasInvestigacion: LineasInvestigacion[];
|
||||
}
|
||||
|
||||
export class ProyectosAcademicos {
|
||||
|
||||
@IsOptional()
|
||||
@Length(0, 500)
|
||||
proyecto_html: string;
|
||||
}
|
||||
|
||||
export class DatosAcademicos{
|
||||
|
||||
@IsOptional()
|
||||
@Length(0,30)
|
||||
grado_maximo: string;
|
||||
|
||||
@IsOptional()
|
||||
@Length(0,600)
|
||||
grados_obtenidos: string;
|
||||
}
|
||||
|
||||
export class LineasInvestigacion{
|
||||
|
||||
@IsOptional()
|
||||
@Length(0,300)
|
||||
lineas_inv_html: string;
|
||||
}
|
||||
|
||||
export class UpdateProfesorImageDto {
|
||||
@IsString()
|
||||
fotografia: string;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ProfesorController } from './profesor.controller';
|
||||
|
||||
describe('ProfesorController', () => {
|
||||
let controller: ProfesorController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [ProfesorController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<ProfesorController>(ProfesorController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,96 +0,0 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpException,
|
||||
Logger,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
UseGuards
|
||||
} from "@nestjs/common";
|
||||
import { ProfesorService } from "./profesor.service";
|
||||
import { profesorDto, UpdateProfesorImageDto } from "./dto/profesorDto.dto";
|
||||
import { Roles } from "../permissions/roles.decorator";
|
||||
import { RolesGuard } from "../permissions/roles.guard";
|
||||
import { Role } from "../permissions/role.enum";
|
||||
import { Profesor } from "./entities/profesor.entity";
|
||||
|
||||
@Controller("profesor")
|
||||
@UseGuards(RolesGuard)
|
||||
export class ProfesorController {
|
||||
constructor(private profesorService: ProfesorService) {
|
||||
}
|
||||
|
||||
@Post()
|
||||
@Roles(Role.Responsable, Role.Admin)
|
||||
async register(@Body() data: profesorDto) {
|
||||
Logger.debug("register profesor");
|
||||
return this.profesorService.register(data);
|
||||
}
|
||||
|
||||
@Put(':id_profesor/image')
|
||||
async updateImage(
|
||||
@Param('id_profesor') id_profesor: number,
|
||||
@Body() updateProfesorImageDto: UpdateProfesorImageDto,
|
||||
): Promise<Profesor> {
|
||||
const { fotografia } = updateProfesorImageDto;
|
||||
|
||||
const profesor = await this.profesorService.findOne(id_profesor);
|
||||
|
||||
if (!profesor) {
|
||||
throw new HttpException('Profesor not found', 404);
|
||||
}
|
||||
|
||||
profesor.fotografia = fotografia;
|
||||
|
||||
return this.profesorService.update(profesor);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
Logger.debug("findAllProfesors");
|
||||
return this.profesorService.findAll();
|
||||
}
|
||||
|
||||
@Get("page")
|
||||
findAllPaginated(@Query("page") page: number = 1, @Query("limit") limit: number = 10, @Query() filters: any) {
|
||||
try {
|
||||
Logger.debug("Page Profesors");
|
||||
page = page < 1 ? 1 : page;
|
||||
limit = limit > 10 || limit < 1 ? 10 : limit;
|
||||
return this.profesorService.findAllPaginated(page, limit, filters);
|
||||
} catch (err) {
|
||||
Logger.error("profesor controller ", err.error);
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Put(":id_profesor")
|
||||
@Roles(Role.Responsable, Role.Admin)
|
||||
async modify(@Param("id_profesor", ParseIntPipe) id_profesor: number, @Body() data: profesorDto) {
|
||||
Logger.debug("put Profesors by id");
|
||||
|
||||
return this.profesorService.modify(id_profesor, data);
|
||||
}
|
||||
|
||||
@Delete(":id_profesor")
|
||||
@Roles(Role.Responsable, Role.Admin)
|
||||
async remove(@Param("id_profesor", ParseIntPipe) id_profesor: number) {
|
||||
Logger.debug("delete Profesors by id");
|
||||
|
||||
return this.profesorService.remove(id_profesor);
|
||||
}
|
||||
|
||||
@Get(":id_profesor")
|
||||
async profile(@Param("id_profesor", ParseIntPipe) id_profesor: number) {
|
||||
Logger.debug("get Profesors by id");
|
||||
|
||||
return this.profesorService.profile(id_profesor);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ProfesorService } from './profesor.service';
|
||||
import { ProfesorController } from './profesor.controller';
|
||||
import { Profesor } from './entities/profesor.entity';
|
||||
import { DatosAcademicosModule } from '../datos_academicos/datos_academicos.module';
|
||||
import { LineasInvestigacionModule } from '../lineas_investigacion/lineas_investigacion.module';
|
||||
import { ProyectosAcademicosModule } from '../proyectos_academicos/proyectos_academicos.module';
|
||||
import { ProyectosAcademicos } from 'src/proyectos_academicos/entities/proyectos_academicos.entity';
|
||||
import { DatosAcademicos } from 'src/datos_academicos/entities/datos_academicos.entity'
|
||||
import { LineasInvestigacion } from 'src/lineas_investigacion/entities/lineas_investigacion.entity'
|
||||
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
Profesor,
|
||||
ProyectosAcademicos,
|
||||
DatosAcademicos,
|
||||
LineasInvestigacion
|
||||
]),
|
||||
DatosAcademicosModule,
|
||||
LineasInvestigacionModule,
|
||||
ProyectosAcademicosModule,
|
||||
JwtModule,
|
||||
],
|
||||
providers: [ProfesorService],
|
||||
controllers: [ProfesorController],
|
||||
})
|
||||
export class ProfesorModule {}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ProfesorService } from './profesor.service';
|
||||
|
||||
describe('ProfesorService', () => {
|
||||
let service: ProfesorService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [ProfesorService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<ProfesorService>(ProfesorService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,255 +0,0 @@
|
||||
import { HttpException, Injectable, Logger } from "@nestjs/common";
|
||||
import { Profesor } from "./entities/profesor.entity";
|
||||
import { DatosAcademicosService } from "../datos_academicos/datos_academicos.service";
|
||||
import { LineasInvestigacionService } from "../lineas_investigacion/lineas_investigacion.service";
|
||||
import { ProyectosAcademicosService } from "../proyectos_academicos/proyectos_academicos.service";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { profesorDto } from "./dto/profesorDto.dto";
|
||||
import { ProyectosAcademicos } from "../proyectos_academicos/entities/proyectos_academicos.entity";
|
||||
import { DatosAcademicos } from "../datos_academicos/entities/datos_academicos.entity";
|
||||
import { LineasInvestigacion } from "../lineas_investigacion/entities/lineas_investigacion.entity";
|
||||
|
||||
@Injectable()
|
||||
export class ProfesorService {
|
||||
constructor(
|
||||
@InjectRepository(Profesor)
|
||||
private readonly profesorRepository: Repository<Profesor>,
|
||||
private readonly datosAcademicosService: DatosAcademicosService,
|
||||
private readonly lineasInvestigacionService: LineasInvestigacionService,
|
||||
private readonly proyectosAcademicosService: ProyectosAcademicosService,
|
||||
@InjectRepository(DatosAcademicos)
|
||||
private readonly datosAcademicosRepository: Repository<DatosAcademicos>,
|
||||
@InjectRepository(LineasInvestigacion)
|
||||
private readonly lineasInvestigacionRepository: Repository<LineasInvestigacion>,
|
||||
@InjectRepository(ProyectosAcademicos)
|
||||
private readonly proyectosAcademicosRepository: Repository<ProyectosAcademicos>
|
||||
) {
|
||||
}
|
||||
|
||||
async findOne(id_profesor: number): Promise<Profesor | undefined> {
|
||||
return this.profesorRepository.findOne({ where: { id_profesor } });
|
||||
}
|
||||
|
||||
async update(profesor: Profesor): Promise<Profesor> {
|
||||
return this.profesorRepository.save(profesor);
|
||||
}
|
||||
|
||||
private async findAvailableId(): Promise<number> {
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
//register teacher
|
||||
async register(data: profesorDto): Promise<Profesor | undefined> {
|
||||
const { num_trabajador, rfc, proyectosAcademicos, datosAcademicos, lineasInvestigacion } = data;
|
||||
|
||||
if (await this.profesorRepository.findOne({ where: { rfc } })) {
|
||||
throw new HttpException("rfc already exist", 403);
|
||||
}
|
||||
|
||||
if (await this.profesorRepository.findOne({ where: { num_trabajador } })) {
|
||||
throw new HttpException("number of worker already exist", 403);
|
||||
}
|
||||
|
||||
const availableId = await this.findAvailableId();
|
||||
data.id_profesor = availableId;
|
||||
|
||||
const savedProfesor = await this.profesorRepository.save(data);
|
||||
|
||||
if (proyectosAcademicos && proyectosAcademicos.length > 0) {
|
||||
const proyectos = proyectosAcademicos.map(proyecto => {
|
||||
const newProyecto = this.proyectosAcademicosRepository.create({
|
||||
...proyecto,
|
||||
profesor: savedProfesor,
|
||||
id_profesor: savedProfesor.id_profesor
|
||||
});
|
||||
return newProyecto;
|
||||
});
|
||||
await this.proyectosAcademicosRepository.save(proyectos);
|
||||
}
|
||||
|
||||
if (lineasInvestigacion && lineasInvestigacion.length > 0) {
|
||||
const lineas = lineasInvestigacion.map(linea => {
|
||||
const newLinea = this.lineasInvestigacionRepository.create({
|
||||
...linea,
|
||||
profesor: savedProfesor,
|
||||
id_profesor: savedProfesor.id_profesor
|
||||
});
|
||||
return newLinea;
|
||||
});
|
||||
await this.lineasInvestigacionRepository.save(lineas);
|
||||
}
|
||||
|
||||
if (datosAcademicos && datosAcademicos.length > 0) {
|
||||
const datos = datosAcademicos.map(datosAcademicos => {
|
||||
const newDato = this.datosAcademicosRepository.create({
|
||||
...datosAcademicos,
|
||||
profesor: savedProfesor,
|
||||
id_profesor: savedProfesor.id_profesor
|
||||
});
|
||||
return newDato;
|
||||
});
|
||||
await this.datosAcademicosRepository.save(datos);
|
||||
}
|
||||
|
||||
return savedProfesor;
|
||||
}
|
||||
|
||||
//brings all teachers
|
||||
async findAll(): Promise<Profesor[]> {
|
||||
return this.profesorRepository.find({
|
||||
order: {
|
||||
nombre: "ASC"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//modify teacher
|
||||
async modify(id_profesor: number, data: profesorDto) {
|
||||
const profesor = await this.profesorRepository.findOne({ where: { id_profesor } });
|
||||
if (!profesor) {
|
||||
throw new HttpException("Profesor not found", 404);
|
||||
}
|
||||
|
||||
const { proyectosAcademicos, datosAcademicos, lineasInvestigacion, ...profesorData } = data;
|
||||
Object.assign(profesor, profesorData);
|
||||
|
||||
const savedProfesor = await this.profesorRepository.save(profesor);
|
||||
|
||||
if (proyectosAcademicos) {
|
||||
await this.proyectosAcademicosRepository.delete({ profesor: { id_profesor } });
|
||||
const proyectos = proyectosAcademicos.map(proyecto => {
|
||||
const newProyecto = this.proyectosAcademicosRepository.create({
|
||||
...proyecto,
|
||||
profesor: savedProfesor,
|
||||
id_profesor: savedProfesor.id_profesor
|
||||
});
|
||||
return newProyecto;
|
||||
});
|
||||
await this.proyectosAcademicosRepository.save(proyectos);
|
||||
}
|
||||
|
||||
if (lineasInvestigacion) {
|
||||
await this.lineasInvestigacionRepository.delete({ profesor: { id_profesor } });
|
||||
const lineas = lineasInvestigacion.map(linea => {
|
||||
const newLinea = this.lineasInvestigacionRepository.create({
|
||||
...linea,
|
||||
profesor: savedProfesor,
|
||||
id_profesor: savedProfesor.id_profesor
|
||||
});
|
||||
return newLinea;
|
||||
});
|
||||
await this.lineasInvestigacionRepository.save(lineas);
|
||||
}
|
||||
|
||||
if (datosAcademicos) {
|
||||
await this.datosAcademicosRepository.delete({ profesor: { id_profesor } });
|
||||
const datos = datosAcademicos.map(datosAcademicos => {
|
||||
const newDato = this.datosAcademicosRepository.create({
|
||||
...datosAcademicos,
|
||||
profesor: savedProfesor,
|
||||
id_profesor: savedProfesor.id_profesor
|
||||
});
|
||||
return newDato;
|
||||
});
|
||||
await this.datosAcademicosRepository.save(datos);
|
||||
}
|
||||
|
||||
return savedProfesor;
|
||||
}
|
||||
|
||||
//remove teacher
|
||||
async remove(id_profesor: number) {
|
||||
const profesor = await this.profesorRepository.findOne({ where: { id_profesor }, relations: ["datosAcademicos"] });
|
||||
if (!profesor) {
|
||||
throw new HttpException("Profesor not found", 404);
|
||||
}
|
||||
|
||||
await this.datosAcademicosService.removeByProfesorId(id_profesor);
|
||||
await this.lineasInvestigacionService.removeByProfesorId(id_profesor);
|
||||
await this.proyectosAcademicosService.removeByProfesorId(id_profesor);
|
||||
|
||||
await this.profesorRepository.remove(profesor);
|
||||
return { message: "Profesor removed successfully" };
|
||||
}
|
||||
|
||||
//brings teachers by id
|
||||
async profile(id_profesor: number) {
|
||||
const profesor = await this.profesorRepository.findOne({
|
||||
where: { id_profesor },
|
||||
relations: ["proyectosAcademicos", "datosAcademicos", "lineasInvestigacion"]
|
||||
});
|
||||
|
||||
if (!profesor) {
|
||||
throw new HttpException("Profesor not found", 404);
|
||||
}
|
||||
|
||||
return profesor;
|
||||
}
|
||||
|
||||
async findAllPaginated(
|
||||
page: number,
|
||||
limit: number,
|
||||
filters: any
|
||||
): Promise<{ profesores: Profesor[], total: number, totalPages: number }> {
|
||||
const query = this.profesorRepository.createQueryBuilder("profesor");
|
||||
|
||||
if (filters.nombre) {
|
||||
Logger.debug('fiter by name');
|
||||
|
||||
query.andWhere("profesor.nombre LIKE :nombre", { nombre: `%${filters.nombre}%` });
|
||||
}
|
||||
|
||||
if (filters.adscripcion) {
|
||||
Logger.debug("fiter by adscription");
|
||||
|
||||
query.andWhere("profesor.adscripcion = :adscripcion", { adscripcion: filters.adscripcion });
|
||||
}
|
||||
|
||||
if (filters.categoria) {
|
||||
Logger.debug("fiter by category");
|
||||
|
||||
query.andWhere("profesor.categoria = :categoria", { categoria: filters.categoria });
|
||||
}
|
||||
|
||||
if (filters.edificio) {
|
||||
Logger.debug("fiter by building");
|
||||
|
||||
query.andWhere("profesor.edificio = :edificio", { edificio: filters.edificio });
|
||||
}
|
||||
|
||||
if (typeof filters.activo !== "undefined") {
|
||||
Logger.debug("fiter by is active");
|
||||
|
||||
query.andWhere("profesor.activo = :activo", { activo: filters.activo });
|
||||
} else {
|
||||
query.orderBy("profesor.activo", "DESC");
|
||||
}
|
||||
|
||||
query.addOrderBy("profesor.nombre", "ASC");
|
||||
|
||||
const [profesores, total] = await query
|
||||
.skip((page - 1) * limit)
|
||||
.take(limit)
|
||||
.getManyAndCount();
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
return { profesores, total, totalPages };
|
||||
}
|
||||
|
||||
|
||||
async updateImageURL(id_profesor: number, imageUrl: string): Promise<void> {
|
||||
await this.profesorRepository.update(id_profesor, { fotografia: imageUrl });
|
||||
}
|
||||
}
|
||||
@@ -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>(AdscripcionController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,6 @@ export class AdscripcionController {
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
Logger.debug('find all adscripcion');
|
||||
return this.adscripcionService.findAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>(AdscripcionService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -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>(AppController);
|
||||
});
|
||||
|
||||
describe('root', () => {
|
||||
it('should return "Hello World!"', () => {
|
||||
expect(appController.getHello()).toBe('Hello World!');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -10,7 +10,6 @@ export class AppController {
|
||||
|
||||
@Get()
|
||||
getHello(): string {
|
||||
console.log("Todo bien", this.configservice.get("DATABASE_NAME"))
|
||||
return this.appService.getHello();
|
||||
}
|
||||
}
|
||||
|
||||
+23
-18
@@ -6,35 +6,35 @@ import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { UsersModule } from './users/users.module';
|
||||
import { User } from './users/entities/user.entity';
|
||||
import { UsuarioModule } from './usuario/usuario.module';
|
||||
import { Usuario } from './usuario/entities/usuario.entity';
|
||||
import { Adscripcion } from './adscripcion/entities/adscripcion.entity';
|
||||
import { Categoria } from './categoria/entities/categoria.entity';
|
||||
import { DatosAcademicos } from './datos_academicos/entities/datos_academicos.entity';
|
||||
import { DatosAcademicos } from './datos-academicos/entities/datos_academicos.entity';
|
||||
import { Edificio } from './edificio/entities/edificio.entity';
|
||||
import { LineasInvestigacion } from './lineas_investigacion/entities/lineas_investigacion.entity';
|
||||
import { ProyectosAcademicos } from './proyectos_academicos/entities/proyectos_academicos.entity';
|
||||
import { LineasInvestigacion } from './lineas-investigacion/entities/lineas-investigacion.entity';
|
||||
import { ProyectosAcademicos } from './proyectos-academicos/entities/proyectos-academicos.entity';
|
||||
import { TipoUsuario } from './tipo_usuario/entities/tipo_usuario.entity';
|
||||
import { ProfesorModule } from './Profesor/profesor.module';
|
||||
import { Profesor } from './Profesor/entities/profesor.entity';
|
||||
import { ProfesorModule } from './profesor/profesor.module';
|
||||
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 { DatosAcademicosModule } from './datos_academicos/datos_academicos.module';
|
||||
import { LineasInvestigacionModule } from './lineas_investigacion/lineas_investigacion.module';
|
||||
import { ProyectosAcademicosModule } from './proyectos_academicos/proyectos_academicos.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: [
|
||||
AuthModule,
|
||||
UsersModule,
|
||||
UsuarioModule,
|
||||
ConfigModule.forRoot({
|
||||
envFilePath: '.env',
|
||||
isGlobal: true,
|
||||
}),
|
||||
TypeOrmModule.forRootAsync({
|
||||
imports: [ConfigModule, AuthModule, UsersModule],
|
||||
imports: [ConfigModule, AuthModule, UsuarioModule],
|
||||
inject: [ConfigService],
|
||||
useFactory: async (configService: ConfigService) => ({
|
||||
type: 'mysql',
|
||||
@@ -45,7 +45,7 @@ import { ImagenModule } from './images/images.module';
|
||||
database: configService.get('DB_DATABASE'),
|
||||
entities: [
|
||||
Profesor,
|
||||
User,
|
||||
Usuario,
|
||||
Adscripcion,
|
||||
Categoria,
|
||||
Edificio,
|
||||
@@ -54,18 +54,23 @@ import { ImagenModule } from './images/images.module';
|
||||
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,
|
||||
CategoriaModule,
|
||||
DatabaseModule,
|
||||
LineasInvestigacionModule,
|
||||
ProyectosAcademicosModule,
|
||||
UsuarioModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
|
||||
@@ -3,8 +3,6 @@ import { Injectable } from '@nestjs/common';
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
console.log("it's ok IO")
|
||||
|
||||
return "hei verden";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AuthController } from './auth.controller';
|
||||
|
||||
describe('AuthController', () => {
|
||||
let controller: AuthController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AuthController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<AuthController>(AuthController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
+19
-65
@@ -15,86 +15,40 @@ import {
|
||||
Logger,
|
||||
Req,
|
||||
} from '@nestjs/common';
|
||||
import { AuthGuard } from './auth.guard';
|
||||
import { AuthService } from './auth.service';
|
||||
import { registerDto } from './dto/registerDto.dto';
|
||||
import { registerDto, SignInDto } from './dto/sign-in.dto';
|
||||
import { Roles } from '../permissions/roles.decorator';
|
||||
import { RolesGuard } from '../permissions/roles.guard';
|
||||
import { Role } from '../permissions/role.enum';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { SignUpDto } from './dto/sign-up.dto';
|
||||
import { JwtAuthGuard } from 'src/permissions/jwt.guard';
|
||||
|
||||
@ApiTags('Auth')
|
||||
@Controller('auth')
|
||||
@UseGuards(RolesGuard)
|
||||
export class AuthController {
|
||||
constructor(private authService: AuthService) {}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('login')
|
||||
async signIn(@Body() data: registerDto) {
|
||||
Logger.debug('signIn');
|
||||
const { rememberMe } = data;
|
||||
return this.authService.signIn(data, rememberMe);
|
||||
@ApiOperation({
|
||||
summary: 'Iniciar sesión',
|
||||
description: 'Autentica a un usuario y devuelve un token de acceso.',
|
||||
})
|
||||
async signIn(@Body() data: SignInDto) {
|
||||
return this.authService.signIn(data);
|
||||
}
|
||||
|
||||
//@UseGuards()
|
||||
@Post('register')
|
||||
@ApiOperation({
|
||||
summary: 'Registro de usuario',
|
||||
description: 'Registro ded administradores o responsables',
|
||||
})
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(RolesGuard)
|
||||
@Roles(Role.Admin)
|
||||
async register(@Body() data: registerDto) {
|
||||
async register(@Body() data: SignUpDto) {
|
||||
Logger.debug('register user');
|
||||
return this.authService.register(data);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
Logger.debug('find all users');
|
||||
return this.authService.findAll();
|
||||
}
|
||||
|
||||
@Get('page')
|
||||
findAllPaginated(
|
||||
@Query('page') page: number = 1,
|
||||
@Query('limit') limit: number = 10,
|
||||
@Query() filters: any,
|
||||
) {
|
||||
Logger.debug('find all users max 10');
|
||||
page = page < 1 ? 1 : page;
|
||||
limit = limit > 10 || limit < 1 ? 10 : limit;
|
||||
|
||||
return this.authService.findAllPaginated(page, limit, filters);
|
||||
}
|
||||
|
||||
@Get(':id_usuario')
|
||||
async profile(@Param('id_usuario', ParseIntPipe) id_usuario: number) {
|
||||
Logger.debug('find one user');
|
||||
return this.authService.profile(id_usuario);
|
||||
}
|
||||
|
||||
//@UseGuards(AuthGuard)
|
||||
@Put(':id_usuario')
|
||||
@Roles(Role.Admin)
|
||||
async update(
|
||||
@Param('id_usuario') id_usuario: number,
|
||||
@Body() data: registerDto,
|
||||
@Req() req: any
|
||||
) {
|
||||
const authHeader = req.headers['authorization'];
|
||||
const [, token] = authHeader.split(' ');
|
||||
|
||||
return this.authService.update(id_usuario, data, token);
|
||||
}
|
||||
|
||||
//@UseGuards(AuthGuard)
|
||||
@Delete(':id_usuario')
|
||||
@Roles(Role.Admin)
|
||||
async remove(@Param('id_usuario') id_usuario: number) {
|
||||
Logger.debug('delete user');
|
||||
await this.authService.remove(id_usuario);
|
||||
return { message: 'User successfully deleted' };
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('validate')
|
||||
async validateToken(@Body('token') token: string) {
|
||||
Logger.debug('validate user');
|
||||
return this.authService.validateToken(token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { Request } from 'express';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
@Injectable()
|
||||
export class AuthGuard implements CanActivate {
|
||||
constructor(
|
||||
private jwtService: JwtService,
|
||||
private configService: ConfigService
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const token = this.extractTokenFromHeader(request);
|
||||
if (!token) {
|
||||
throw new UnauthorizedException('token faltante');
|
||||
}
|
||||
try {
|
||||
const payload = await this.jwtService.verifyAsync(
|
||||
token,
|
||||
{
|
||||
secret: this.configService.get('JWT_SECRET'),
|
||||
}
|
||||
);
|
||||
|
||||
request['user'] = payload;
|
||||
} catch {
|
||||
throw new UnauthorizedException('algo fallo');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private extractTokenFromHeader(request: Request): string | undefined {
|
||||
const [type, token] = request.headers.authorization?.split(' ') ?? [];
|
||||
return type === 'Bearer' ? token : undefined;
|
||||
}
|
||||
}
|
||||
+14
-11
@@ -1,28 +1,31 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { UsersModule } from '../users/users.module';
|
||||
import { UsuarioModule } from '../usuario/usuario.module';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { User } from 'src/users/entities/user.entity';
|
||||
import { Usuario } from 'src/usuario/entities/usuario.entity';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { JwtStrategy } from 'src/permissions/jwt.strategy';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
UsersModule,
|
||||
UsuarioModule,
|
||||
PassportModule.register({ defaultStrategy: 'jwt' }),
|
||||
JwtModule.registerAsync({
|
||||
inject:[ConfigService],
|
||||
useFactory:async(configService:ConfigService)=>{
|
||||
return{
|
||||
inject: [ConfigService],
|
||||
useFactory: async (configService: ConfigService) => {
|
||||
return {
|
||||
global: true,
|
||||
secret: configService.get('JWT_SECRET'),
|
||||
signOptions: { expiresIn: "7h"},
|
||||
}
|
||||
}
|
||||
signOptions: { expiresIn: configService.get('JWT_SECRET_EXPIRES') },
|
||||
};
|
||||
},
|
||||
}),
|
||||
TypeOrmModule.forFeature([User])
|
||||
TypeOrmModule.forFeature([Usuario]),
|
||||
],
|
||||
providers: [AuthService],
|
||||
providers: [AuthService, JwtStrategy],
|
||||
controllers: [AuthController],
|
||||
exports: [AuthService],
|
||||
})
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
describe('AuthService', () => {
|
||||
let service: AuthService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [AuthService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<AuthService>(AuthService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
+26
-103
@@ -5,50 +5,56 @@ import {
|
||||
Logger,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { UsersService } from '../users/users.service';
|
||||
import { UsuarioService } from '../usuario/usuario.service';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { registerDto } from './dto/registerDto.dto';
|
||||
import { User } from 'src/users/entities/user.entity';
|
||||
import { registerDto, SignInDto } from './dto/sign-in.dto';
|
||||
import { Usuario } from 'src/usuario/entities/usuario.entity';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { hash } from 'bcrypt';
|
||||
import { compare } from 'bcryptjs';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { SignUpDto } from './dto/sign-up.dto';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private usersService: UsersService,
|
||||
private usersService: UsuarioService,
|
||||
private jwtService: JwtService,
|
||||
private configService: ConfigService,
|
||||
@InjectRepository(User) private userRepository: Repository<User>,
|
||||
@InjectRepository(Usuario) private userRepository: Repository<Usuario>,
|
||||
) {}
|
||||
|
||||
//register users
|
||||
async register(data: registerDto) {
|
||||
const { usuario, contraseña } = data;
|
||||
async register(data: SignUpDto) {
|
||||
const { usuario, password } = data;
|
||||
|
||||
if (await this.usersService.findOne(usuario)) {
|
||||
throw new HttpException('User already exist', 403);
|
||||
const searchedUser = await this.usersService.findOneByName(usuario);
|
||||
|
||||
if (searchedUser) {
|
||||
throw new HttpException('El usuario ya existe', 409);
|
||||
}
|
||||
|
||||
const hashedpassword = await hash(contraseña, 10);
|
||||
const hashedpassword = await hash(password, 10);
|
||||
data = { ...data, password: hashedpassword };
|
||||
|
||||
data = { ...data, contraseña: hashedpassword };
|
||||
return this.userRepository.save(this.userRepository.create(data));
|
||||
const createdUsuario = await this.userRepository.create(data);
|
||||
const savedUsuario = await this.userRepository.save(createdUsuario);
|
||||
|
||||
return savedUsuario;
|
||||
}
|
||||
|
||||
//singin
|
||||
async signIn(data: registerDto, rememberMe: boolean) {
|
||||
const { usuario, contraseña } = data;
|
||||
const user = await this.usersService.findOne(usuario);
|
||||
async signIn(data: SignInDto) {
|
||||
const { usuario, password, rememberMe } = data;
|
||||
const user = await this.usersService.findOneByName(usuario);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException('Invalid name');
|
||||
throw new UnauthorizedException('Credenciales inválidas.');
|
||||
}
|
||||
|
||||
const checkPassword = await compare(contraseña, user.contraseña);
|
||||
const checkPassword = await compare(password, user.password);
|
||||
if (!checkPassword) {
|
||||
throw new UnauthorizedException('Invalid password');
|
||||
throw new UnauthorizedException('Credenciales inválidas.');
|
||||
}
|
||||
|
||||
const payload = {
|
||||
@@ -73,49 +79,15 @@ export class AuthService {
|
||||
return { token };
|
||||
}
|
||||
|
||||
//update user
|
||||
async update(
|
||||
id_usuario: number,
|
||||
data: registerDto,
|
||||
token: string,
|
||||
): Promise<any> {
|
||||
const { contraseña, id_tipo_usuario } = data;
|
||||
|
||||
const tokenUserId = this.getTokenUserId(token);
|
||||
|
||||
if (tokenUserId == id_usuario && id_tipo_usuario !== undefined) {
|
||||
Logger.debug('Cannot modify id_tipo_usuario')
|
||||
delete data.id_tipo_usuario;
|
||||
}
|
||||
|
||||
const user = await this.userRepository.findOne({ where: { id_usuario } });
|
||||
if (!user) {
|
||||
throw new HttpException('User not found', 404);
|
||||
}
|
||||
|
||||
if (contraseña) {
|
||||
const hashedpassword = await hash(contraseña, 10);
|
||||
data = { ...data, contraseña: hashedpassword };
|
||||
} else {
|
||||
data = { ...data, contraseña: user.contraseña };
|
||||
}
|
||||
|
||||
return await this.userRepository.update(id_usuario, data);
|
||||
}
|
||||
|
||||
async remove(id_usuario: number): Promise<void> {
|
||||
await this.userRepository.delete(id_usuario);
|
||||
}
|
||||
|
||||
//validate tojen of users
|
||||
async validateToken(token: string): Promise<User> {
|
||||
async validateToken(token: string): Promise<Usuario> {
|
||||
try {
|
||||
const decoded = this.jwtService.verify(token, {
|
||||
secret: this.configService.get<string>('JWT_SECRET'),
|
||||
});
|
||||
|
||||
//verify JWT's username with DB's username
|
||||
const user = await this.usersService.findOne(decoded.username);
|
||||
const user = await this.usersService.findOneByName(decoded.username);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException('Invalid token');
|
||||
}
|
||||
@@ -132,55 +104,6 @@ export class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
async findAll(): Promise<User[]> {
|
||||
return this.userRepository.find();
|
||||
}
|
||||
|
||||
async findAllPaginated(
|
||||
page: number,
|
||||
limit: number,
|
||||
filters: any,
|
||||
): Promise<{ usuarios: User[]; total: number; totalPages: number }> {
|
||||
const query = this.userRepository.createQueryBuilder('usuario');
|
||||
|
||||
if (filters.nombre) {
|
||||
Logger.debug('fiter by name user');
|
||||
query.andWhere('usuario.nombre LIKE :nombre', {
|
||||
nombre: `%${filters.nombre}%`,
|
||||
});
|
||||
}
|
||||
|
||||
if (filters.id_tipo_usuario) {
|
||||
Logger.debug('fiter by id user');
|
||||
query.andWhere('usuario.id_tipo_usuario = :id_tipo_usuario', {
|
||||
id_tipo_usuario: filters.id_tipo_usuario,
|
||||
});
|
||||
}
|
||||
|
||||
const [usuarios, total] = await query
|
||||
.skip((page - 1) * limit)
|
||||
.take(limit)
|
||||
.orderBy('usuario.nombre', 'ASC')
|
||||
.getManyAndCount();
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
return { usuarios, total, totalPages };
|
||||
}
|
||||
|
||||
async profile(id_usuario: number) {
|
||||
const user = await this.userRepository.findOne({
|
||||
where: { id_usuario },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
Logger.debug('user not found');
|
||||
throw new HttpException('user not found', 404);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
getTokenUserId(token: string): number {
|
||||
try {
|
||||
const decoded = this.jwtService.verify(token);
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNumber, IsString } from 'class-validator';
|
||||
|
||||
export class JwtPayloadDto {
|
||||
@ApiProperty({ example: 1, description: 'ID único del usuario' })
|
||||
@IsNumber()
|
||||
id_usuario: number;
|
||||
|
||||
@ApiProperty({ example: 'juan.perez', description: 'Nombre de usuario' })
|
||||
@IsString()
|
||||
usuario: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 2,
|
||||
description: 'Tipo de usuario (ejemplo: Admin, Estudiante, etc.)',
|
||||
})
|
||||
@IsNumber()
|
||||
id_tipo_usuario: number;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import {IsString, IsNotEmpty,IsNumber, IsBoolean, IsOptional } from "class-validator";
|
||||
|
||||
export class registerDto{
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
usuario: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
nombre: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
contraseña: string;
|
||||
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
id_tipo_usuario:number;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
correo: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
rememberMe: boolean;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import {
|
||||
IsString,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsBoolean,
|
||||
IsOptional,
|
||||
} from 'class-validator';
|
||||
|
||||
export class SignInDto {
|
||||
@ApiProperty({
|
||||
description: 'Nombre de usuario o correo electrónico para iniciar sesión',
|
||||
example: 'admin',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
usuario: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Contraseña del usuario',
|
||||
example: 'Test2024DSC',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
password: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Indica si se debe recordar la sesión (opcional)',
|
||||
example: true,
|
||||
required: false,
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
rememberMe?: boolean;
|
||||
}
|
||||
|
||||
export class registerDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
usuario: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
nombre: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
contraseña: string;
|
||||
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
id_tipo_usuario: number;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
correo: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
rememberMe: boolean;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
IsEmail,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MinLength,
|
||||
} from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class SignUpDto {
|
||||
@ApiProperty({ description: 'ID del tipo de usuario', example: 1 })
|
||||
@IsInt()
|
||||
@IsNotEmpty()
|
||||
id_tipo_usuario: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Nombre completo del usuario',
|
||||
example: 'Juan Pérez',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
nombre: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Correo electrónico',
|
||||
example: 'juan.perez@example.com',
|
||||
})
|
||||
@IsEmail()
|
||||
@IsNotEmpty()
|
||||
correo: string;
|
||||
|
||||
@ApiProperty({ description: 'Nombre de usuario', example: 'juanperez' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
usuario: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Contraseña del usuario',
|
||||
example: 'password',
|
||||
})
|
||||
@IsString()
|
||||
@MinLength(2)
|
||||
@IsNotEmpty()
|
||||
password: string;
|
||||
}
|
||||
@@ -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>(CategoriaController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,6 @@ export class CategoriaController {
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
Logger.debug('find all categoria');
|
||||
return this.categoriaService.findAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>(CategoriaService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Controller, Post } from '@nestjs/common';
|
||||
import { DatabaseService } from './database.service';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
@ApiTags('Carga de Datos')
|
||||
@Controller('database')
|
||||
export class DatabaseController {
|
||||
constructor(private readonly databaseService: DatabaseService) {}
|
||||
|
||||
@Post('cargar-catalogos')
|
||||
async uploadFile() {
|
||||
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' };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DatabaseService } from './database.service';
|
||||
import { DatabaseController } from './database.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Adscripcion } from 'src/adscripcion/entities/adscripcion.entity';
|
||||
import { Edificio } from 'src/edificio/entities/edificio.entity';
|
||||
import { Categoria } from 'src/categoria/entities/categoria.entity';
|
||||
import { TipoUsuario } from 'src/tipo_usuario/entities/tipo_usuario.entity';
|
||||
import { Usuario } from 'src/usuario/entities/usuario.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
Adscripcion,
|
||||
Edificio,
|
||||
Categoria,
|
||||
TipoUsuario,
|
||||
Usuario,
|
||||
]),
|
||||
],
|
||||
providers: [DatabaseService],
|
||||
controllers: [DatabaseController],
|
||||
})
|
||||
export class DatabaseModule {}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Adscripcion } from 'src/adscripcion/entities/adscripcion.entity';
|
||||
import { Repository } from 'typeorm';
|
||||
import * as XLSX from 'xlsx';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { Edificio } from 'src/edificio/entities/edificio.entity';
|
||||
import { Categoria } from 'src/categoria/entities/categoria.entity';
|
||||
import { TipoUsuario } from 'src/tipo_usuario/entities/tipo_usuario.entity';
|
||||
import { Usuario } from 'src/usuario/entities/usuario.entity';
|
||||
|
||||
@Injectable()
|
||||
export class DatabaseService {
|
||||
constructor(
|
||||
@InjectRepository(Adscripcion)
|
||||
private readonly adscripcionRepository: Repository<Adscripcion>,
|
||||
@InjectRepository(Edificio)
|
||||
private readonly edificioRepository: Repository<Edificio>,
|
||||
@InjectRepository(Categoria)
|
||||
private readonly categoriaRepository: Repository<Categoria>,
|
||||
@InjectRepository(TipoUsuario)
|
||||
private readonly tipoUsuarioRepository: Repository<TipoUsuario>,
|
||||
@InjectRepository(Usuario)
|
||||
private readonly usuarioRepository: Repository<Usuario>,
|
||||
) {}
|
||||
|
||||
async cargarDatosDesdeExcel(): Promise<void> {
|
||||
const folderPath = path.join(__dirname, '../../utils');
|
||||
|
||||
if (!fs.existsSync(folderPath)) {
|
||||
console.error('La carpeta utils no existe:', folderPath);
|
||||
return;
|
||||
}
|
||||
|
||||
const files = fs
|
||||
.readdirSync(folderPath)
|
||||
.filter((file) => file.endsWith('.xlsx'));
|
||||
|
||||
const repos = {
|
||||
'adscripciones_data.xlsx': this.adscripcionRepository,
|
||||
'edificios_data.xlsx': this.edificioRepository,
|
||||
'categorias_data.xlsx': this.categoriaRepository,
|
||||
'tipo_usuario.xlsx': this.tipoUsuarioRepository,
|
||||
'usuarios.xlsx': this.usuarioRepository,
|
||||
};
|
||||
|
||||
for (const file of files) {
|
||||
const repo = repos[file];
|
||||
if (!repo) continue; // Si el archivo no está en el mapeo, lo omitimos
|
||||
|
||||
const filePath = path.join(folderPath, file);
|
||||
console.log(`Procesando: ${file}`);
|
||||
|
||||
const workbook = XLSX.readFile(filePath);
|
||||
const sheetName = workbook.SheetNames[0];
|
||||
const data = XLSX.utils.sheet_to_json(workbook.Sheets[sheetName]);
|
||||
|
||||
// Guardar datos en paralelo para mejorar el rendimiento
|
||||
await Promise.all(data.map((row) => repo.save(row)));
|
||||
}
|
||||
}
|
||||
|
||||
async migrateDB(): Promise<any> {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Patch,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
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')
|
||||
export class DatosAcademicosController {
|
||||
constructor(private datosAcademicosService: DatosAcademicosService) {}
|
||||
|
||||
@Patch(':id_profesor')
|
||||
@ApiOperation({
|
||||
summary: 'Actualizar datos academicos de un profesor',
|
||||
description:
|
||||
'Este endpoint permite actualizar las líneas de investigación en formato Markdown/HTML para un profesor específico.',
|
||||
})
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id_datos: {
|
||||
type: 'number',
|
||||
description: 'ID de los datos que se van a actualizar',
|
||||
example: 1,
|
||||
},
|
||||
grados_obtenidos: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Nuevo contenido de líneas de investigación en formato Markdown o HTML',
|
||||
example: '# Investigación en IA\n- Tema 1\n- Tema 2\n- Tema 3',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
async updateDatosAcademicos(
|
||||
@Param('id_profesor') id_profesor: number,
|
||||
@Body('id_datos') id_datos: number,
|
||||
@Body('grados_obtenidos') grados_obtenidos: string,
|
||||
) {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { forwardRef, Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { DatosAcademicosService } from './datos-academicos.service';
|
||||
import { DatosAcademicosController } from './datos-academicos.controller';
|
||||
import { DatosAcademicos } from './entities/datos_academicos.entity';
|
||||
import { ProfesorModule } from 'src/profesor/profesor.module';
|
||||
import { UsuarioModule } from 'src/usuario/usuario.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([DatosAcademicos]),
|
||||
forwardRef(() => ProfesorModule),
|
||||
UsuarioModule,
|
||||
],
|
||||
providers: [DatosAcademicosService],
|
||||
controllers: [DatosAcademicosController],
|
||||
exports: [DatosAcademicosService],
|
||||
})
|
||||
export class DatosAcademicosModule {}
|
||||
@@ -0,0 +1,88 @@
|
||||
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<DatosAcademicos>,
|
||||
|
||||
@Inject(forwardRef(() => ProfesorService))
|
||||
private readonly profesorService: ProfesorService,
|
||||
) {}
|
||||
|
||||
async create(profesor: Profesor, grado_maximo: string) {
|
||||
const datosAcademicos = this.datosAcademicosRepository.create({
|
||||
profesor: profesor,
|
||||
grados_obtenidos: '',
|
||||
grado_maximo: grado_maximo || 'Sin Grado Maximo',
|
||||
});
|
||||
|
||||
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<void> {
|
||||
await this.datosAcademicosRepository.delete({ profesor: { id_profesor } });
|
||||
}
|
||||
|
||||
async findAll(): Promise<DatosAcademicos[]> {
|
||||
return this.datosAcademicosRepository.find();
|
||||
}
|
||||
}
|
||||
+10
-4
@@ -1,5 +1,11 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { Profesor } from '../../Profesor/entities/profesor.entity';
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { Profesor } from '../../profesor/entities/profesor.entity';
|
||||
|
||||
@Entity({ name: 'datos_academicos' })
|
||||
export class DatosAcademicos {
|
||||
@@ -15,7 +21,7 @@ export class DatosAcademicos {
|
||||
@Column({ type: 'varchar', length: 600, nullable: true })
|
||||
grados_obtenidos: string;
|
||||
|
||||
@ManyToOne(() => Profesor, (profesor) => profesor.datosAcademicos)
|
||||
@JoinColumn({ name: 'id_profesor', referencedColumnName: 'id_profesor', foreignKeyConstraintName: 'FK_datos_academicos' })
|
||||
@ManyToOne(() => Profesor, (profesor) => profesor.lineasInvestigacion)
|
||||
@JoinColumn({ name: 'id_profesor' })
|
||||
profesor: Profesor;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { DatosAcademicosController } from './datos_academicos.controller';
|
||||
|
||||
describe('DatosAcademicosController', () => {
|
||||
let controller: DatosAcademicosController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [DatosAcademicosController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<DatosAcademicosController>(DatosAcademicosController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import { Controller, Get, Logger } from '@nestjs/common';
|
||||
import { DatosAcademicosService } from './datos_academicos.service';
|
||||
|
||||
@Controller('datos-academicos')
|
||||
export class DatosAcademicosController {
|
||||
constructor(
|
||||
private readonly datosAcademicosService: DatosAcademicosService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
Logger.debug('find all datos academicos');
|
||||
return this.datosAcademicosService.findAll();
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { DatosAcademicosService } from './datos_academicos.service';
|
||||
import { DatosAcademicosController } from './datos_academicos.controller';
|
||||
import { DatosAcademicos } from './entities/datos_academicos.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([DatosAcademicos])],
|
||||
providers: [DatosAcademicosService],
|
||||
controllers: [DatosAcademicosController],
|
||||
exports: [DatosAcademicosService],
|
||||
})
|
||||
export class DatosAcademicosModule {}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { DatosAcademicosService } from './datos_academicos.service';
|
||||
|
||||
describe('DatosAcademicosService', () => {
|
||||
let service: DatosAcademicosService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [DatosAcademicosService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<DatosAcademicosService>(DatosAcademicosService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { DatosAcademicos } from './entities/datos_academicos.entity';
|
||||
|
||||
@Injectable()
|
||||
export class DatosAcademicosService {
|
||||
constructor(
|
||||
@InjectRepository(DatosAcademicos)
|
||||
private readonly datosAcademicosRepository: Repository<DatosAcademicos>,
|
||||
) {}
|
||||
|
||||
async removeByProfesorId(id_profesor: number): Promise<void> {
|
||||
await this.datosAcademicosRepository.delete({ profesor: { id_profesor } });
|
||||
}
|
||||
|
||||
async findAll(): Promise<DatosAcademicos[]> {
|
||||
return this.datosAcademicosRepository.find();
|
||||
}
|
||||
}
|
||||
@@ -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>(EdificioController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,6 @@ export class EdificioController {
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
Logger.debug('find all edificio');
|
||||
return this.edificioService.findAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>(EdificioService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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<string> {
|
||||
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<void> {
|
||||
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<string> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { Profesor } from '../../profesor/entities/profesor.entity';
|
||||
|
||||
@Entity({ name: 'lineas_investigacion' })
|
||||
export class LineasInvestigacion {
|
||||
@PrimaryGeneratedColumn()
|
||||
id_linea_inv: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 600, nullable: true })
|
||||
lineas_inv_html: string;
|
||||
|
||||
@ManyToOne(() => Profesor, (profesor) => profesor.lineasInvestigacion)
|
||||
@JoinColumn({ name: 'id_profesor' })
|
||||
profesor: Profesor;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Patch,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { LineasInvestigacionService } from './lineas-investigacion.service';
|
||||
import { ApiBearerAuth, ApiBody, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { JwtAuthGuard } from 'src/permissions/jwt.guard';
|
||||
import { RolesGuard } from 'src/permissions/roles.guard';
|
||||
|
||||
@ApiBearerAuth()
|
||||
@ApiTags('lineas-investigacion')
|
||||
@Controller('lineas-investigacion')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
export class LineasInvestigacionController {
|
||||
constructor(private lineasInvestigacionService: LineasInvestigacionService) {}
|
||||
|
||||
@Patch(':id_profesor')
|
||||
@ApiOperation({
|
||||
summary: 'Actualizar líneas de investigación de un profesor',
|
||||
description:
|
||||
'Este endpoint permite actualizar las líneas de investigación en formato Markdown/HTML para un profesor específico.',
|
||||
})
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id_linea_inv: {
|
||||
type: 'number',
|
||||
description:
|
||||
'ID de las lineas de investigacion que se deben actualizar',
|
||||
example: 1,
|
||||
},
|
||||
lineas_inv_html: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Nuevo contenido de líneas de investigación en formato Markdown o HTML',
|
||||
example: '# Investigación en IA\n- Tema 1\n- Tema 2\n- Tema 3',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
async updateLineasInvestigacion(
|
||||
@Param('id_profesor') id_profesor: number,
|
||||
@Body('id_linea_inv') id_linea_inv: number,
|
||||
@Body('lineas_inv_html') lineasInvHtml: string,
|
||||
) {
|
||||
if (!lineasInvHtml || lineasInvHtml.trim() === '') {
|
||||
throw new HttpException(
|
||||
'El contenido no puede estar vacío.',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
return await this.lineasInvestigacionService.updateLineasInvestigacion(
|
||||
id_linea_inv,
|
||||
id_profesor,
|
||||
lineasInvHtml,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { forwardRef, Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { LineasInvestigacion } from './entities/lineas-investigacion.entity';
|
||||
import { LineasInvestigacionService } from './lineas-investigacion.service';
|
||||
import { ProfesorModule } from 'src/profesor/profesor.module';
|
||||
import { LineasInvestigacionController } from './lineas-investigacion.controller';
|
||||
import { UsuarioModule } from 'src/usuario/usuario.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([LineasInvestigacion]),
|
||||
forwardRef(() => ProfesorModule),
|
||||
UsuarioModule,
|
||||
],
|
||||
controllers: [LineasInvestigacionController],
|
||||
providers: [LineasInvestigacionService],
|
||||
exports: [LineasInvestigacionService],
|
||||
})
|
||||
export class LineasInvestigacionModule {}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { LineasInvestigacion } from './entities/lineas-investigacion.entity';
|
||||
import { Profesor } from 'src/profesor/entities/profesor.entity';
|
||||
import { ProfesorService } from 'src/profesor/profesor.service';
|
||||
|
||||
@Injectable()
|
||||
export class LineasInvestigacionService {
|
||||
constructor(
|
||||
@InjectRepository(LineasInvestigacion)
|
||||
private readonly lineasInvestigacionRepository: Repository<LineasInvestigacion>,
|
||||
|
||||
@Inject(forwardRef(() => ProfesorService))
|
||||
private readonly profesorService: ProfesorService,
|
||||
) {}
|
||||
|
||||
async create(profesor: Profesor) {
|
||||
const lineasInvestigacion = this.lineasInvestigacionRepository.create({
|
||||
profesor: profesor,
|
||||
lineas_inv_html: '',
|
||||
});
|
||||
|
||||
return await this.lineasInvestigacionRepository.save(lineasInvestigacion);
|
||||
}
|
||||
|
||||
async updateLineasInvestigacion(
|
||||
id_linea_inv: number,
|
||||
id_profesor: number,
|
||||
nuevoContenido: string,
|
||||
) {
|
||||
const profesor = await this.profesorService.findOne(id_profesor);
|
||||
|
||||
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. Se procederá a crear una nueva.`,
|
||||
);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
return lineasInvestigacion; // Devolver la línea de investigación actualizada o recién creada
|
||||
}
|
||||
|
||||
async removeByProfesorId(id_profesor: number): Promise<void> {
|
||||
await this.lineasInvestigacionRepository.delete({
|
||||
profesor: { id_profesor },
|
||||
});
|
||||
}
|
||||
|
||||
async findAll(): Promise<LineasInvestigacion[]> {
|
||||
return this.lineasInvestigacionRepository.find();
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { Profesor } from "../../Profesor/entities/profesor.entity";
|
||||
|
||||
@Entity({ name: 'lineas_investigacion' })
|
||||
export class LineasInvestigacion {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id_linea_inv: number;
|
||||
|
||||
@Column({ type: 'int', nullable: false })
|
||||
id_profesor: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 350, nullable: true })
|
||||
lineas_inv_html: string;
|
||||
|
||||
@ManyToOne(() => Profesor, profesor => profesor.lineasInvestigacion)
|
||||
@JoinColumn({ name: 'id_profesor', referencedColumnName: 'id_profesor', foreignKeyConstraintName: 'FK_lineas_investigacion' })
|
||||
profesor: Profesor;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { LineasInvestigacionController } from './lineas_investigacion.controller';
|
||||
|
||||
describe('LineasInvestigacionController', () => {
|
||||
let controller: LineasInvestigacionController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [LineasInvestigacionController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<LineasInvestigacionController>(LineasInvestigacionController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Controller, Get, Logger } from '@nestjs/common';
|
||||
import { LineasInvestigacionService } from './lineas_investigacion.service';
|
||||
|
||||
@Controller('lineas-investigacion')
|
||||
export class LineasInvestigacionController {
|
||||
constructor(private readonly lineasInvestigacionService: LineasInvestigacionService) {}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
Logger.debug('find all lineas de investigacion');
|
||||
return this.lineasInvestigacionService.findAll();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { LineasInvestigacion } from './entities/lineas_investigacion.entity';
|
||||
import { LineasInvestigacionService } from './lineas_investigacion.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([LineasInvestigacion])],
|
||||
providers: [LineasInvestigacionService],
|
||||
exports: [LineasInvestigacionService],
|
||||
})
|
||||
export class LineasInvestigacionModule {}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { LineasInvestigacionService } from './lineas_investigacion.service';
|
||||
|
||||
describe('LineasInvestigacionService', () => {
|
||||
let service: LineasInvestigacionService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [LineasInvestigacionService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<LineasInvestigacionService>(LineasInvestigacionService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { LineasInvestigacion } from './entities/lineas_investigacion.entity';
|
||||
|
||||
@Injectable()
|
||||
export class LineasInvestigacionService {
|
||||
constructor(
|
||||
@InjectRepository(LineasInvestigacion)
|
||||
private readonly lineasInvestigacionRepository: Repository<LineasInvestigacion>,
|
||||
) {}
|
||||
|
||||
async removeByProfesorId(id_profesor: number): Promise<void> {
|
||||
await this.lineasInvestigacionRepository.delete({ profesor: { id_profesor } });
|
||||
}
|
||||
|
||||
async findAll(): Promise<LineasInvestigacion[]> {
|
||||
return this.lineasInvestigacionRepository.find();
|
||||
}
|
||||
}
|
||||
+32
-2
@@ -3,16 +3,46 @@ import { AppModule } from './app.module';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import * as path from 'path';
|
||||
import * as bodyParser from 'body-parser';
|
||||
import { Logger, ValidationPipe } from '@nestjs/common';
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
||||
|
||||
// 💡 Agregar validaciones globales con class-validator
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true, // Elimina propiedades desconocidas
|
||||
forbidNonWhitelisted: true, // Lanza error si hay propiedades no permitidas
|
||||
transform: true, // Convierte tipos automáticamente
|
||||
}),
|
||||
);
|
||||
|
||||
if (process.env.PROD === 'false') {
|
||||
Logger.log('--- Cargando Documentacion ---');
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle('Directorio API')
|
||||
.setVersion('1.0')
|
||||
.addTag('Carga de Datos')
|
||||
.addTag('Auth')
|
||||
.addTag('Profesor')
|
||||
.addTag('Usuario')
|
||||
.addTag('lineas-investigacion')
|
||||
.addTag('proyectos-academicos')
|
||||
.addTag('datos-academicos')
|
||||
.addBearerAuth()
|
||||
.build();
|
||||
const documentFactory = () => SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('docs', app, documentFactory);
|
||||
}
|
||||
|
||||
app.enableCors();
|
||||
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import {
|
||||
Injectable,
|
||||
ExecutionContext,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { UsuarioService } from 'src/usuario/usuario.service';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||
constructor(
|
||||
private usuarioService: UsuarioService,
|
||||
private readonly reflector: Reflector,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
|
||||
// ✅ Primero, ejecutar la validación del padre
|
||||
const isValid = await super.canActivate(context);
|
||||
if (!isValid) {
|
||||
throw new UnauthorizedException('Token inválido o expirado');
|
||||
}
|
||||
|
||||
const authHeader = request.headers.authorization;
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
throw new UnauthorizedException('Token no proporcionado');
|
||||
}
|
||||
|
||||
const token = authHeader.split(' ')[1];
|
||||
const payload = request.user; // Extraído desde Passport
|
||||
|
||||
if (!payload || !payload.id_usuario) {
|
||||
throw new UnauthorizedException('Token inválido');
|
||||
}
|
||||
|
||||
// Obtener el usuario desde la base de datos
|
||||
const usuario = await this.usuarioService.findOneById(payload.id_usuario);
|
||||
if (!usuario) {
|
||||
throw new UnauthorizedException('Usuario no encontrado');
|
||||
}
|
||||
|
||||
// Validar que el token en la DB coincida con el enviado
|
||||
if (usuario.jwt !== token) {
|
||||
throw new UnauthorizedException('Token no autorizado');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtPayloadDto } from 'src/auth/dto/payload.dto';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
constructor(configService: ConfigService) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: configService.get<string>('JWT_SECRET'),
|
||||
});
|
||||
}
|
||||
|
||||
async validate(payload: JwtPayloadDto) {
|
||||
return payload
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
export enum Role {
|
||||
Admin = 1,
|
||||
Responsable = 2,
|
||||
}
|
||||
|
||||
Admin = 1,
|
||||
Responsable = 2,
|
||||
}
|
||||
|
||||
@@ -2,62 +2,43 @@ import {
|
||||
Injectable,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
UnauthorizedException,
|
||||
Logger,
|
||||
ForbiddenException,
|
||||
} from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { Role } from './role.enum';
|
||||
import { ROLES_KEY } from './roles.decorator';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
@Injectable()
|
||||
export class RolesGuard implements CanActivate {
|
||||
constructor(
|
||||
private reflector: Reflector,
|
||||
private jwtService: JwtService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
constructor(private reflector: Reflector) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
Logger.debug('verify roles');
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
// ✅ Obtener los roles requeridos en el endpoint
|
||||
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
|
||||
if (!requiredRoles) {
|
||||
return true;
|
||||
return true; // Si el endpoint no tiene restricción de roles, permitir acceso
|
||||
}
|
||||
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const authHeader = request.headers['authorization'];
|
||||
const user = request.user; // Se obtiene de `JwtAuthGuard`
|
||||
|
||||
if (!authHeader) {
|
||||
throw new UnauthorizedException('No authorization header provided');
|
||||
if (!user || !user.id_tipo_usuario) {
|
||||
throw new ForbiddenException(
|
||||
'No tienes permisos para acceder a este recurso',
|
||||
);
|
||||
}
|
||||
|
||||
const [, token] = authHeader.split(' ');
|
||||
if (!token) {
|
||||
throw new UnauthorizedException('No token provided');
|
||||
// Verificar si el usuario tiene el rol requerido
|
||||
if (!requiredRoles.includes(user.id_tipo_usuario)) {
|
||||
throw new ForbiddenException(
|
||||
'No tienes permisos para acceder a este recurso',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const userPayload = await this.jwtService.verifyAsync(token, {
|
||||
secret: this.configService.get<string>('JWT_SECRET'),
|
||||
});
|
||||
|
||||
const userRole: Role = userPayload.id_tipo_usuario;
|
||||
const userId: number = userPayload.id_usuario;
|
||||
|
||||
const paramId = parseInt(request.params.id_usuario, 10);
|
||||
if (requiredRoles.includes(userRole) || userId === paramId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
throw new UnauthorizedException('Invalid token');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
import {
|
||||
IsBoolean,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsString,
|
||||
IsOptional,
|
||||
Length,
|
||||
} from 'class-validator';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
|
||||
export class CreateProfesorDto {
|
||||
@ApiProperty({
|
||||
example: 'Juan Pérez',
|
||||
description: 'Nombre completo del profesor',
|
||||
})
|
||||
@IsString({ message: 'El nombre debe ser una cadena de texto' })
|
||||
@IsNotEmpty({ message: 'El nombre es obligatorio' })
|
||||
@Length(1, 80, { message: 'El nombre debe tener entre 1 y 80 caracteres' })
|
||||
nombre: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: '123456',
|
||||
description: 'Número de trabajador único',
|
||||
})
|
||||
@IsString({ message: 'El número de trabajador debe ser una cadena de texto' })
|
||||
@IsOptional()
|
||||
@Length(1, 10, {
|
||||
message: 'El número de trabajador debe ser maximo de 10 caracteres',
|
||||
})
|
||||
num_trabajador?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 'ABC1234567',
|
||||
description: 'RFC del profesor',
|
||||
})
|
||||
@IsString({ message: 'El RFC debe ser una cadena de texto' })
|
||||
@IsOptional()
|
||||
@Length(1, 10, { message: 'El RFC debe tener maximo 10 caracteres' })
|
||||
rfc?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'XYZ', description: 'Homoclave del RFC' })
|
||||
@IsString({ message: 'La homoclave debe ser una cadena de texto' })
|
||||
@IsOptional()
|
||||
@Length(1, 3, { message: 'La homoclave debe tener maximo 3 caracteres' })
|
||||
homoclave?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: '5551234567',
|
||||
description: 'Teléfono de oficina del profesor',
|
||||
})
|
||||
@IsString({ message: 'El teléfono debe ser una cadena de texto' })
|
||||
@IsOptional()
|
||||
@Length(1, 14, { message: 'El teléfono debe tener entre 1 y 14 caracteres' })
|
||||
tel_oficina?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: '1234',
|
||||
description: 'Extensión del teléfono de oficina',
|
||||
})
|
||||
@IsString({ message: 'La extensión debe ser una cadena de texto' })
|
||||
@IsOptional()
|
||||
@Length(1, 10, { message: 'La extensión debe tener entre 1 y 10 caracteres' })
|
||||
extension?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 'correo@escuela.edu.mx',
|
||||
description: 'Correo institucional',
|
||||
})
|
||||
@IsString({ message: 'El correo debe ser una cadena de texto' })
|
||||
@IsOptional()
|
||||
@Length(1, 65, { message: 'El correo debe tener entre 1 y 65 caracteres' })
|
||||
correo_pcp?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 1,
|
||||
description: 'ID de la adscripción a la que pertenece el profesor',
|
||||
})
|
||||
@IsInt({ message: 'El ID de adscripción debe ser un número entero' })
|
||||
@IsOptional()
|
||||
id_adscripcion?: number;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 2,
|
||||
description: 'ID de la categoría del profesor',
|
||||
})
|
||||
@IsInt({ message: 'El ID de categoría debe ser un número entero' })
|
||||
@IsOptional()
|
||||
id_categoria?: number;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 3,
|
||||
description: 'ID del edificio donde se ubica el profesor',
|
||||
})
|
||||
@IsInt({ message: 'El ID de edificio debe ser un número entero' })
|
||||
@IsOptional()
|
||||
id_edificio?: number;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 'Aula 205',
|
||||
description: 'Ubicación física del profesor',
|
||||
})
|
||||
@IsString({ message: 'La ubicación debe ser una cadena de texto' })
|
||||
@IsOptional()
|
||||
ubicacion?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 'https://mi-url.com/foto.jpg',
|
||||
description:
|
||||
'URL de la fotografía del profesor. \n\n Antes de crear el registro del profesor, la imagen debe ser subida al endpoint correspondiente para almacenar imágenes. \n\n Este endpoint devolverá la URL de la imagen almacenada, la cual debe enviarse en este campo al momento de registrar al profesor.',
|
||||
})
|
||||
@IsString({
|
||||
message: 'La URL de la fotografía debe ser una cadena de texto válida',
|
||||
})
|
||||
@IsOptional()
|
||||
fotografia?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: true,
|
||||
description: 'Indica si el profesor está activo',
|
||||
})
|
||||
@IsBoolean({
|
||||
message: 'El estado activo debe ser un valor booleano (true o false)',
|
||||
})
|
||||
@IsOptional()
|
||||
activo?: boolean;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: false,
|
||||
description: 'Indica si el profesor debe mostrarse en la lista pública',
|
||||
})
|
||||
@IsBoolean({
|
||||
message:
|
||||
'El estado de visibilidad debe ser un valor booleano (true o false)',
|
||||
})
|
||||
@IsOptional()
|
||||
mostrar?: boolean;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 'Maestro en Ciencias',
|
||||
description: 'Grado máximo de estudios del profesor',
|
||||
})
|
||||
@IsString({ message: 'El grado máximo debe ser una cadena de texto' })
|
||||
@IsOptional()
|
||||
grado_maximo?: string;
|
||||
}
|
||||
|
||||
export class UpdateProfesorDto extends PartialType(CreateProfesorDto) {}
|
||||
|
||||
/*
|
||||
Se retiro informacion personal
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Length(0, 13)
|
||||
tel_personal: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Length(0, 65)
|
||||
correo_per: string;
|
||||
*/
|
||||
+56
-42
@@ -1,63 +1,45 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryColumn,
|
||||
Column,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
JoinColumn,
|
||||
PrimaryGeneratedColumn,
|
||||
} from 'typeorm';
|
||||
import { Edificio } from '../../edificio/entities/edificio.entity';
|
||||
import { Adscripcion } from '../../adscripcion/entities/adscripcion.entity';
|
||||
import { Categoria } from '../../categoria/entities/categoria.entity';
|
||||
import { DatosAcademicos } from '../../datos_academicos/entities/datos_academicos.entity';
|
||||
import { LineasInvestigacion } from '../../lineas_investigacion/entities/lineas_investigacion.entity';
|
||||
import { ProyectosAcademicos } from '../../proyectos_academicos/entities/proyectos_academicos.entity';
|
||||
import { User } from 'src/users/entities/user.entity';
|
||||
import { DatosAcademicos } from '../../datos-academicos/entities/datos_academicos.entity';
|
||||
import { LineasInvestigacion } from '../../lineas-investigacion/entities/lineas-investigacion.entity';
|
||||
import { ProyectosAcademicos } from '../../proyectos-academicos/entities/proyectos-academicos.entity';
|
||||
import { Usuario } from 'src/usuario/entities/usuario.entity';
|
||||
|
||||
@Entity({ name: 'profesor' })
|
||||
export class Profesor {
|
||||
@PrimaryColumn({ type: 'int', nullable: false })
|
||||
@PrimaryGeneratedColumn({ type: 'int' })
|
||||
id_profesor: number;
|
||||
|
||||
@Column({ type: 'int', nullable: false })
|
||||
id_usuario: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 10, nullable: true })
|
||||
num_trabajador: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 80, nullable: false })
|
||||
nombre: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 10, nullable: true })
|
||||
num_trabajador: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 10, nullable: true })
|
||||
rfc: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 3, nullable: true })
|
||||
homoclave: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 13, nullable: true })
|
||||
@Column({ type: 'varchar', length: 14, nullable: true })
|
||||
tel_oficina: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 10, nullable: true })
|
||||
extension: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 13, nullable: true })
|
||||
tel_personal: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 65, nullable: true })
|
||||
correo_pcp: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 65, nullable: true })
|
||||
correo_per: string;
|
||||
|
||||
@Column({ type: 'int', nullable: true })
|
||||
id_adscripcion: number;
|
||||
|
||||
@Column({ type: 'int', nullable: true })
|
||||
id_categoria: number;
|
||||
|
||||
@Column({ type: 'int', nullable: true })
|
||||
id_edificio: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 256, nullable: true })
|
||||
ubicacion: string;
|
||||
|
||||
@@ -72,8 +54,8 @@ export class Profesor {
|
||||
|
||||
@Column({
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
nullable: false,
|
||||
})
|
||||
fecha_alta: Date;
|
||||
|
||||
@@ -85,29 +67,61 @@ export class Profesor {
|
||||
})
|
||||
fecha_actualizacion: Date;
|
||||
|
||||
@ManyToOne(() => Edificio, edificio => edificio.profesores)
|
||||
@JoinColumn({ name: 'id_edificio', referencedColumnName: 'id_edificio', foreignKeyConstraintName: 'FK_edificio_fk' })
|
||||
/* RELACIONES */
|
||||
|
||||
@Column({ type: 'int', nullable: true })
|
||||
id_adscripcion: number;
|
||||
|
||||
@Column({ type: 'int', nullable: true })
|
||||
id_categoria: number;
|
||||
|
||||
@Column({ type: 'int', nullable: true })
|
||||
id_edificio: number;
|
||||
|
||||
@Column({ type: 'int', nullable: true })
|
||||
id_usuario: number;
|
||||
|
||||
@ManyToOne(() => Edificio, (edificio) => edificio.profesores)
|
||||
@JoinColumn({
|
||||
name: 'id_edificio',
|
||||
})
|
||||
edificio: Edificio;
|
||||
|
||||
@ManyToOne(() => Adscripcion, adscripcion => adscripcion.profesores)
|
||||
@JoinColumn({ name: 'id_adscripcion', referencedColumnName: 'id_adscripcion', foreignKeyConstraintName: 'FK_adscripcion_fk' })
|
||||
@ManyToOne(() => Adscripcion, (adscripcion) => adscripcion.profesores)
|
||||
@JoinColumn({
|
||||
name: 'id_adscripcion',
|
||||
})
|
||||
adscripcion: Adscripcion;
|
||||
|
||||
@ManyToOne(() => Categoria, categoria => categoria.profesores)
|
||||
@JoinColumn({ name: 'id_categoria', referencedColumnName: 'id_categoria', foreignKeyConstraintName: 'FK_categoria_fk' })
|
||||
@ManyToOne(() => Categoria, (categoria) => categoria.profesores)
|
||||
@JoinColumn({
|
||||
name: 'id_categoria',
|
||||
})
|
||||
categoria: Categoria;
|
||||
|
||||
@ManyToOne(() => User, user => user.profesores)
|
||||
@JoinColumn({ name: 'id_usuario', referencedColumnName: 'id_usuario', foreignKeyConstraintName: 'FK_usuario_fk' })
|
||||
user: User;
|
||||
@ManyToOne(() => Usuario, (usuario) => usuario.profesores)
|
||||
@JoinColumn({
|
||||
name: 'id_usuario',
|
||||
})
|
||||
usuario: Usuario;
|
||||
|
||||
@OneToMany(() => DatosAcademicos, datosAcademicos => datosAcademicos.profesor,{ cascade: true })
|
||||
@OneToMany(
|
||||
() => DatosAcademicos,
|
||||
(datosAcademicos) => datosAcademicos.profesor,
|
||||
)
|
||||
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; */
|
||||
@@ -0,0 +1,169 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpException,
|
||||
Logger,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
Patch,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
Req,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ProfesorService } from './profesor.service';
|
||||
import {
|
||||
CreateProfesorDto,
|
||||
UpdateProfesorDto,
|
||||
} from './dto/create-profesor.dto';
|
||||
import { Roles } from '../permissions/roles.decorator';
|
||||
import { RolesGuard } from '../permissions/roles.guard';
|
||||
import { Role } from '../permissions/role.enum';
|
||||
import { Profesor } from './entities/profesor.entity';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiBody,
|
||||
ApiOperation,
|
||||
ApiQuery,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import { JwtAuthGuard } from 'src/permissions/jwt.guard';
|
||||
import { JwtPayloadDto } from 'src/auth/dto/payload.dto';
|
||||
|
||||
interface RequestWithUser extends Request {
|
||||
user?: JwtPayloadDto;
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@ApiTags('Profesor')
|
||||
@Controller('profesor')
|
||||
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);
|
||||
}
|
||||
|
||||
@Patch(':id_profesor')
|
||||
@ApiOperation({
|
||||
summary: 'Actualizar parcialmente la informacion de un profesor',
|
||||
})
|
||||
@ApiBody({
|
||||
description: 'Datos parciales del profesor a actualizar',
|
||||
required: true,
|
||||
schema: {
|
||||
example: {
|
||||
nombre: 'Dr. Juan Pérez',
|
||||
tel_oficina: '5559876543',
|
||||
correo_pcp: 'juan.perez@escuela.edu.mx',
|
||||
activo: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
async updateProfesor(
|
||||
@Param('id_profesor') id_profesor: number,
|
||||
@Body() body: UpdateProfesorDto,
|
||||
): Promise<Profesor> {
|
||||
return this.profesorService.update(id_profesor, body);
|
||||
}
|
||||
|
||||
@Get('filter')
|
||||
@ApiOperation({
|
||||
summary: 'Obtener lista paginada de profesores con filtros opcionales',
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'page',
|
||||
required: false,
|
||||
example: 1,
|
||||
description: 'Número de página (por defecto 1)',
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'limit',
|
||||
required: false,
|
||||
example: 12,
|
||||
description: 'Cantidad de registros por página (máximo 12)',
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'nombre',
|
||||
required: false,
|
||||
example: 'Juan',
|
||||
description: 'Filtrar por nombre del profesor',
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'adscripcion',
|
||||
required: false,
|
||||
example: 2,
|
||||
description: 'Filtrar por ID de adscripción',
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'edificio',
|
||||
required: false,
|
||||
example: 3,
|
||||
description: 'Filtrar por ID de edificio',
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'categoria',
|
||||
required: false,
|
||||
example: 1,
|
||||
description: 'Filtrar por ID de categoría',
|
||||
})
|
||||
async findAllPaginated(
|
||||
@Query('page') page: number = 1,
|
||||
@Query('limit') limit: number = 12,
|
||||
@Query('nombre') nombre?: string,
|
||||
@Query('adscripcion') adscripcion?: number,
|
||||
@Query('edificio') edificio?: number,
|
||||
@Query('categoria') categoria?: number,
|
||||
) {
|
||||
// Validaciones básicas
|
||||
page = page < 1 ? 1 : page;
|
||||
limit = limit > 12 || limit < 1 ? 12 : limit;
|
||||
|
||||
// Filtros dinámicos
|
||||
const filters: any = {};
|
||||
if (nombre) filters.nombre = nombre;
|
||||
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);
|
||||
}
|
||||
|
||||
@Get(':id_profesor')
|
||||
async profile(@Param('id_profesor', ParseIntPipe) id_profesor: number) {
|
||||
return this.profesorService.profile(id_profesor);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Obtiene todos los profesores' })
|
||||
findAll() {
|
||||
return this.profesorService.findAll();
|
||||
}
|
||||
|
||||
/* @Put(':id_profesor')
|
||||
@Roles(Role.Responsable, Role.Admin)
|
||||
async modify(
|
||||
@Param('id_profesor', ParseIntPipe) id_profesor: number,
|
||||
@Body() data: CreateProfesorDto,
|
||||
) {
|
||||
return this.profesorService.modify(id_profesor, data);
|
||||
} */
|
||||
|
||||
@Delete(':id_profesor')
|
||||
@ApiOperation({
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { forwardRef, Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ProfesorService } from './profesor.service';
|
||||
import { ProfesorController } from './profesor.controller';
|
||||
import { Profesor } from './entities/profesor.entity';
|
||||
import { DatosAcademicosModule } from '../datos-academicos/datos-academicos.module';
|
||||
import { LineasInvestigacionModule } from '../lineas-investigacion/lineas-investigacion.module';
|
||||
import { ProyectosAcademicosModule } from '../proyectos-academicos/proyectos-academicos.module';
|
||||
import { UsuarioModule } from 'src/usuario/usuario.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Profesor]),
|
||||
forwardRef(() => LineasInvestigacionModule),
|
||||
DatosAcademicosModule,
|
||||
ProyectosAcademicosModule,
|
||||
UsuarioModule,
|
||||
],
|
||||
providers: [ProfesorService],
|
||||
controllers: [ProfesorController],
|
||||
exports: [ProfesorService],
|
||||
})
|
||||
export class ProfesorModule {}
|
||||
@@ -0,0 +1,242 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
forwardRef,
|
||||
HttpException,
|
||||
Inject,
|
||||
Injectable,
|
||||
Logger,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { Profesor } from './entities/profesor.entity';
|
||||
import { DatosAcademicosService } from '../datos-academicos/datos-academicos.service';
|
||||
import { LineasInvestigacionService } from '../lineas-investigacion/lineas-investigacion.service';
|
||||
import { ProyectosAcademicosService } from '../proyectos-academicos/proyectos-academicos.service';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import {
|
||||
CreateProfesorDto,
|
||||
UpdateProfesorDto,
|
||||
} from './dto/create-profesor.dto';
|
||||
import { ProyectosAcademicos } from '../proyectos-academicos/entities/proyectos-academicos.entity';
|
||||
import { DatosAcademicos } from '../datos-academicos/entities/datos_academicos.entity';
|
||||
import { LineasInvestigacion } from '../lineas-investigacion/entities/lineas-investigacion.entity';
|
||||
import { JwtPayloadDto } from 'src/auth/dto/payload.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ProfesorService {
|
||||
constructor(
|
||||
@InjectRepository(Profesor)
|
||||
private readonly profesorRepository: Repository<Profesor>,
|
||||
private readonly datosAcademicosService: DatosAcademicosService,
|
||||
private readonly proyectosAcademicosService: ProyectosAcademicosService,
|
||||
|
||||
@Inject(forwardRef(() => LineasInvestigacionService))
|
||||
private readonly lineasInvestigacionService: LineasInvestigacionService,
|
||||
) {}
|
||||
|
||||
async findOne(id_profesor: number): Promise<Profesor> {
|
||||
const profesor = await this.profesorRepository.findOne({
|
||||
where: { id_profesor },
|
||||
});
|
||||
if (!profesor) {
|
||||
throw new NotFoundException(
|
||||
`El profesor con ID ${id_profesor} no fue encontrado`,
|
||||
);
|
||||
}
|
||||
return profesor;
|
||||
}
|
||||
|
||||
async update(
|
||||
id_profesor: number,
|
||||
data: UpdateProfesorDto,
|
||||
): Promise<Profesor> {
|
||||
const { grado_maximo } = data;
|
||||
const profesor = await this.findOne(id_profesor);
|
||||
Object.assign(profesor, data);
|
||||
|
||||
if (grado_maximo) {
|
||||
await this.datosAcademicosService.updateGradoMaximo(
|
||||
profesor.id_profesor,
|
||||
grado_maximo,
|
||||
);
|
||||
}
|
||||
|
||||
return await this.profesorRepository.save(profesor);
|
||||
}
|
||||
|
||||
//register teacher
|
||||
async register(
|
||||
data: CreateProfesorDto,
|
||||
payload: JwtPayloadDto,
|
||||
): Promise<Profesor> {
|
||||
const { num_trabajador, rfc, homoclave, grado_maximo, ...res } = data;
|
||||
|
||||
// 1.- Buscar si ya existe un profesor con ese número de trabajador
|
||||
const profesorByNumTrabajador = await this.profesorRepository.findOne({
|
||||
where: { num_trabajador },
|
||||
});
|
||||
|
||||
if (profesorByNumTrabajador) {
|
||||
Logger.error(
|
||||
`Ya existe un profesor con el número de trabajador ${num_trabajador}.`,
|
||||
);
|
||||
throw new HttpException(
|
||||
`Ya existe un profesor con el número de trabajador ${num_trabajador}.`,
|
||||
409,
|
||||
);
|
||||
}
|
||||
|
||||
// 2.- Buscar si ya existe un profesor con ese RFC
|
||||
const profesorByRFC = await this.profesorRepository.findOne({
|
||||
where: { rfc },
|
||||
});
|
||||
|
||||
if (profesorByRFC) {
|
||||
Logger.error(`Ya existe un profesor con el RFC ${rfc}.`);
|
||||
throw new HttpException(
|
||||
`Ya existe un profesor con el RFC ${rfc}.`,
|
||||
409, // Código 409 (Conflict)
|
||||
);
|
||||
}
|
||||
|
||||
// 4.- Buscar si ya existe un profesor con ese RFC
|
||||
const profesorByHomoclave = await this.profesorRepository.findOne({
|
||||
where: { homoclave },
|
||||
});
|
||||
|
||||
if (profesorByHomoclave) {
|
||||
Logger.error(`Ya existe un profesor con esa homoclave ${homoclave}.`);
|
||||
throw new HttpException(
|
||||
`Ya existe un profesor con esa homoclave ${homoclave}.`,
|
||||
409, // Código 409 (Conflict)
|
||||
);
|
||||
}
|
||||
|
||||
// 5.- Guardar al profesor
|
||||
const createdProfesor = this.profesorRepository.create({
|
||||
id_usuario: payload.id_usuario,
|
||||
...res,
|
||||
num_trabajador,
|
||||
rfc,
|
||||
});
|
||||
|
||||
const savedProfesor = await this.profesorRepository.save(createdProfesor);
|
||||
|
||||
await this.lineasInvestigacionService.create(savedProfesor);
|
||||
await this.proyectosAcademicosService.create(savedProfesor);
|
||||
await this.datosAcademicosService.create(savedProfesor, grado_maximo);
|
||||
|
||||
return savedProfesor;
|
||||
}
|
||||
|
||||
//brings all teachers
|
||||
async findAll(): Promise<Profesor[]> {
|
||||
return this.profesorRepository.find({
|
||||
order: {
|
||||
nombre: 'ASC',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
//remove teacher
|
||||
async remove(id_profesor: number) {
|
||||
const profesor = await this.profesorRepository.findOne({
|
||||
where: { id_profesor },
|
||||
relations: ['datosAcademicos'],
|
||||
});
|
||||
|
||||
if (!profesor) {
|
||||
throw new HttpException('No se encontro profesor', 404);
|
||||
}
|
||||
|
||||
await this.datosAcademicosService.removeByProfesorId(id_profesor);
|
||||
await this.lineasInvestigacionService.removeByProfesorId(id_profesor);
|
||||
await this.proyectosAcademicosService.removeByProfesorId(id_profesor);
|
||||
|
||||
await this.profesorRepository.remove(profesor);
|
||||
return { message: 'Profesor eliminado con exito' };
|
||||
}
|
||||
|
||||
//brings teachers by id
|
||||
async profile(id_profesor: number) {
|
||||
const profesor = await this.profesorRepository.findOne({
|
||||
where: { id_profesor },
|
||||
relations: [
|
||||
'proyectosAcademicos',
|
||||
'datosAcademicos',
|
||||
'lineasInvestigacion',
|
||||
],
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return profesor;
|
||||
}
|
||||
|
||||
async findAllPaginated(
|
||||
page: number,
|
||||
limit: number,
|
||||
filters: any,
|
||||
): Promise<{ profesores: Profesor[]; total: number; totalPages: number }> {
|
||||
const query = this.profesorRepository.createQueryBuilder('profesor');
|
||||
|
||||
console.log(filters);
|
||||
|
||||
if (filters.nombre) {
|
||||
Logger.debug('Filter by name');
|
||||
query.andWhere('LOWER(profesor.nombre) LIKE LOWER(:nombre)', {
|
||||
nombre: `%${filters.nombre}%`,
|
||||
});
|
||||
}
|
||||
|
||||
if (filters.adscripcion && !isNaN(filters.adscripcion)) {
|
||||
Logger.debug('Filter by adscription');
|
||||
query.andWhere('profesor.id_adscripcion = :adscripcion', {
|
||||
adscripcion: Number(filters.adscripcion),
|
||||
});
|
||||
}
|
||||
|
||||
if (filters.categoria && !isNaN(filters.categoria)) {
|
||||
Logger.debug('Filter by category');
|
||||
query.andWhere('profesor.categoria = :categoria', {
|
||||
categoria: Number(filters.categoria),
|
||||
});
|
||||
}
|
||||
|
||||
if (filters.edificio && !isNaN(filters.edificio)) {
|
||||
Logger.debug('Filter by building');
|
||||
query.andWhere('profesor.id_edificio = :edificio', {
|
||||
edificio: Number(filters.edificio),
|
||||
});
|
||||
}
|
||||
|
||||
query.addOrderBy('profesor.nombre', 'ASC');
|
||||
|
||||
const [profesores, total] = await query
|
||||
.skip((page - 1) * limit)
|
||||
.take(limit)
|
||||
.getManyAndCount();
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
return { profesores, total, totalPages };
|
||||
}
|
||||
|
||||
async updateImageURL(id_profesor: number, imageUrl: string): Promise<void> {
|
||||
await this.profesorRepository.update(id_profesor, { fotografia: imageUrl });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { Profesor } from '../../profesor/entities/profesor.entity';
|
||||
|
||||
@Entity({ name: 'proyectos_academicos' })
|
||||
export class ProyectosAcademicos {
|
||||
@PrimaryGeneratedColumn()
|
||||
id_proyecto: number;
|
||||
|
||||
@Column({ type: 'int', nullable: false })
|
||||
id_profesor: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 700, nullable: true })
|
||||
proyectos_html: string;
|
||||
|
||||
@ManyToOne(() => Profesor, (profesor) => profesor.proyectosAcademicos)
|
||||
@JoinColumn({
|
||||
name: 'id_profesor',
|
||||
})
|
||||
profesor: Profesor;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Logger,
|
||||
Param,
|
||||
Patch,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ProyectosAcademicosService } from './proyectos-academicos.service';
|
||||
import { ApiBearerAuth, ApiBody, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { RolesGuard } from 'src/permissions/roles.guard';
|
||||
import { JwtAuthGuard } from 'src/permissions/jwt.guard';
|
||||
|
||||
@ApiBearerAuth()
|
||||
@ApiTags('proyectos-academicos')
|
||||
@Controller('proyectos-academicos')
|
||||
export class ProyectosAcademicosController {
|
||||
constructor(private proyectosAcademicosService: ProyectosAcademicosService) {}
|
||||
|
||||
@Patch(':id_profesor')
|
||||
@ApiOperation({
|
||||
summary: 'Actualizar los proyectos academicos de un profesor',
|
||||
description:
|
||||
'Este endpoint permite actualizar las líneas de investigación en formato Markdown/HTML para un profesor específico.',
|
||||
})
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id_profesor: {
|
||||
type: 'number',
|
||||
description:
|
||||
'ID del profesor al que se debe actualizar sus proyectos academicos',
|
||||
example: 1,
|
||||
},
|
||||
proyectos_html: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Nuevo contenido de líneas de investigación en formato Markdown',
|
||||
example: '# Proyectos Academicos \n- Proyecto 1\n- Proyecto 2',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
async updateProyectosAcademicos(
|
||||
@Param('id_profesor') id_profesor: number,
|
||||
@Body('proyectos_html') proyectos_html: string,
|
||||
@Body('id_proyecto') id_proyecto: number,
|
||||
) {
|
||||
if (!proyectos_html || proyectos_html.trim() === '') {
|
||||
throw new HttpException(
|
||||
'El contenido no puede estar vacío.',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
return await this.proyectosAcademicosService.updateProyectosAcademicos(
|
||||
id_proyecto,
|
||||
id_profesor,
|
||||
proyectos_html,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { forwardRef, Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ProyectosAcademicos } from './entities/proyectos-academicos.entity';
|
||||
import { ProyectosAcademicosService } from './proyectos-academicos.service';
|
||||
import { ProfesorModule } from 'src/profesor/profesor.module';
|
||||
import { UsuarioModule } from 'src/usuario/usuario.module';
|
||||
import { ProyectosAcademicosController } from './proyectos-academicos.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([ProyectosAcademicos]),
|
||||
forwardRef(() => ProfesorModule),
|
||||
UsuarioModule,
|
||||
],
|
||||
controllers: [ProyectosAcademicosController],
|
||||
providers: [ProyectosAcademicosService],
|
||||
exports: [ProyectosAcademicosService],
|
||||
})
|
||||
export class ProyectosAcademicosModule {}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { ProyectosAcademicos } from './entities/proyectos-academicos.entity';
|
||||
import { Profesor } from 'src/profesor/entities/profesor.entity';
|
||||
import { ProfesorService } from 'src/profesor/profesor.service';
|
||||
|
||||
@Injectable()
|
||||
export class ProyectosAcademicosService {
|
||||
constructor(
|
||||
@InjectRepository(ProyectosAcademicos)
|
||||
private readonly proyectosAcademicosRepository: Repository<ProyectosAcademicos>,
|
||||
|
||||
@Inject(forwardRef(() => ProfesorService))
|
||||
private readonly profesorService: ProfesorService,
|
||||
) {}
|
||||
|
||||
async create(profesor: Profesor) {
|
||||
const proyectosAcademicos = this.proyectosAcademicosRepository.create({
|
||||
profesor: profesor,
|
||||
proyectos_html: '',
|
||||
});
|
||||
|
||||
return await this.proyectosAcademicosRepository.save(proyectosAcademicos);
|
||||
}
|
||||
|
||||
async updateProyectosAcademicos(
|
||||
id_proyecto: number,
|
||||
id_profesor: number,
|
||||
nuevoContenido: string,
|
||||
) {
|
||||
const profesor = await this.profesorService.findOne(id_profesor);
|
||||
|
||||
let proyecto = await this.proyectosAcademicosRepository.findOne({
|
||||
where: { profesor: profesor, id_proyecto },
|
||||
});
|
||||
|
||||
if (!proyecto) {
|
||||
Logger.error(
|
||||
`No se encontraron proyectos académicos del profesor con ID ${id_profesor} al intentar actualizar. Se procederá a crear uno nuevo.`,
|
||||
);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
return proyecto; // Devolver el proyecto actualizado o recién creado
|
||||
}
|
||||
|
||||
async removeByProfesorId(id_profesor: number): Promise<void> {
|
||||
await this.proyectosAcademicosRepository.delete({
|
||||
profesor: { id_profesor },
|
||||
});
|
||||
}
|
||||
|
||||
async findAll(): Promise<ProyectosAcademicos[]> {
|
||||
return this.proyectosAcademicosRepository.find();
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { Profesor } from "../../Profesor/entities/profesor.entity";
|
||||
|
||||
@Entity({ name: 'proyectos_academicos' })
|
||||
export class ProyectosAcademicos {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id_proyecto: number;
|
||||
|
||||
@Column({ type: 'int', nullable: false })
|
||||
id_profesor: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 700, nullable: true })
|
||||
proyecto_html: string;
|
||||
|
||||
@ManyToOne(() => Profesor, profesor => profesor.proyectosAcademicos)
|
||||
@JoinColumn({ name: 'id_profesor', referencedColumnName: 'id_profesor', foreignKeyConstraintName: 'FK_proyectos_academicos' })
|
||||
profesor: Profesor;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ProyectosAcademicosController } from './proyectos_academicos.controller';
|
||||
|
||||
describe('ProyectosAcademicosController', () => {
|
||||
let controller: ProyectosAcademicosController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [ProyectosAcademicosController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<ProyectosAcademicosController>(ProyectosAcademicosController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Controller,Get, Logger } from '@nestjs/common';
|
||||
import { ProyectosAcademicosService } from './proyectos_academicos.service';
|
||||
|
||||
@Controller('proyectos-academicos')
|
||||
export class ProyectosAcademicosController {
|
||||
constructor(private readonly proyectosAcademicosService: ProyectosAcademicosService) {}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
Logger.debug('find all proyectos academicos');
|
||||
return this.proyectosAcademicosService.findAll();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ProyectosAcademicos } from './entities/proyectos_academicos.entity';
|
||||
import { ProyectosAcademicosService } from './proyectos_academicos.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([ProyectosAcademicos])],
|
||||
providers: [ProyectosAcademicosService],
|
||||
exports: [ProyectosAcademicosService],
|
||||
})
|
||||
export class ProyectosAcademicosModule {}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ProyectosAcademicosService } from './proyectos_academicos.service';
|
||||
|
||||
describe('ProyectosAcademicosService', () => {
|
||||
let service: ProyectosAcademicosService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [ProyectosAcademicosService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<ProyectosAcademicosService>(ProyectosAcademicosService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { ProyectosAcademicos } from './entities/proyectos_academicos.entity';
|
||||
|
||||
@Injectable()
|
||||
export class ProyectosAcademicosService {
|
||||
constructor(
|
||||
@InjectRepository(ProyectosAcademicos)
|
||||
private readonly proyectosAcademicosRepository: Repository<ProyectosAcademicos>,
|
||||
) {}
|
||||
|
||||
async removeByProfesorId(id_profesor: number): Promise<void> {
|
||||
await this.proyectosAcademicosRepository.delete({ profesor: { id_profesor } });
|
||||
}
|
||||
|
||||
async findAll(): Promise<ProyectosAcademicos[]> {
|
||||
return this.proyectosAcademicosRepository.find();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
|
||||
import { User } from '../../users/entities/user.entity';
|
||||
import { Usuario } from '../../usuario/entities/usuario.entity';
|
||||
|
||||
@Entity({ name: 'tipo_usuario' })
|
||||
export class TipoUsuario {
|
||||
@@ -9,6 +9,6 @@ export class TipoUsuario {
|
||||
@Column({ type: 'varchar', length: 30, nullable: false })
|
||||
tipo_usuario: string;
|
||||
|
||||
@OneToMany(() => User, (user) => user.tipoUsuario)
|
||||
users: User[];
|
||||
@OneToMany(() => Usuario, (usuario) => usuario.tipoUsuario)
|
||||
usuarios: Usuario[];
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, OneToMany } from 'typeorm';
|
||||
import { TipoUsuario } from "../../tipo_usuario/entities/tipo_usuario.entity";
|
||||
import { Profesor } from 'src/Profesor/entities/profesor.entity';
|
||||
|
||||
@Entity({ name: 'usuario' })
|
||||
export class User {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id_usuario: number;
|
||||
|
||||
@Column({ type: 'int', nullable: false })
|
||||
id_tipo_usuario: number;
|
||||
|
||||
@Column({type: 'varchar', length: 40, nullable: false })
|
||||
nombre: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 60, nullable: false })
|
||||
correo: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 60, nullable: false })
|
||||
usuario: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 60, nullable: false })
|
||||
contraseña: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
jwt: string;
|
||||
|
||||
@Column({ type: 'datetime', nullable: true })
|
||||
expirationjwt: Date;
|
||||
|
||||
@Column({ type: 'datetime', nullable: true })
|
||||
lastlog: Date;
|
||||
|
||||
@ManyToOne(() => TipoUsuario, tipoUsuario => tipoUsuario.users)
|
||||
@JoinColumn({ name: 'id_tipo_usuario', referencedColumnName: 'id_tipo_usuario', foreignKeyConstraintName: 'FK_tipo_usuario' })
|
||||
tipoUsuario: TipoUsuario;
|
||||
|
||||
@OneToMany(() => Profesor, profesor => profesor.user)
|
||||
profesores: Profesor[];
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { UsersService } from './users.service';
|
||||
import { User } from './entities/user.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([User])],
|
||||
providers: [UsersService],
|
||||
exports: [UsersService],
|
||||
})
|
||||
export class UsersModule {}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { UsersService } from './users.service';
|
||||
|
||||
describe('UsersService', () => {
|
||||
let service: UsersService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [UsersService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<UsersService>(UsersService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,31 +0,0 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { User } from './entities/user.entity';
|
||||
import { UserDto } from './dto/userDto.dto';
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
constructor(
|
||||
@InjectRepository(User)
|
||||
private users: Repository<User>,
|
||||
) {}
|
||||
|
||||
async findOne(usuario: string): Promise<User | undefined> {
|
||||
return this.users.findOne({ where: { usuario } });
|
||||
}
|
||||
|
||||
async updateTokenAndDates(
|
||||
id: number,
|
||||
token: string,
|
||||
lastLogin: Date,
|
||||
jwtExpiry: Date,
|
||||
): Promise<void> {
|
||||
Logger.debug('update token and date user');
|
||||
await this.users.update(id, {
|
||||
jwt: token,
|
||||
lastlog: lastLogin,
|
||||
expirationjwt: jwtExpiry,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { Expose } from 'class-transformer';
|
||||
import { SignUpDto } from 'src/auth/dto/sign-up.dto';
|
||||
|
||||
export class UsuarioResponseDto {
|
||||
@Expose()
|
||||
id_usuario: number;
|
||||
|
||||
@Expose()
|
||||
usuario: string;
|
||||
|
||||
@Expose()
|
||||
nombre: string;
|
||||
|
||||
@Expose()
|
||||
id_tipo_usuario: string;
|
||||
|
||||
@Expose()
|
||||
correo: string;
|
||||
}
|
||||
|
||||
export class UpdateUsuarioDto extends PartialType(SignUpDto) {}
|
||||
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
OneToMany,
|
||||
} from 'typeorm';
|
||||
import { TipoUsuario } from '../../tipo_usuario/entities/tipo_usuario.entity';
|
||||
import { Profesor } from 'src/profesor/entities/profesor.entity';
|
||||
|
||||
@Entity({ name: 'usuario' })
|
||||
export class Usuario {
|
||||
@PrimaryGeneratedColumn()
|
||||
id_usuario: number;
|
||||
|
||||
@Column({ type: 'int', nullable: false })
|
||||
id_tipo_usuario: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 40, nullable: false })
|
||||
nombre: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 60, nullable: false })
|
||||
correo: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 60, nullable: false })
|
||||
usuario: string;
|
||||
|
||||
@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 })
|
||||
jwt: string;
|
||||
|
||||
@Column({ type: 'datetime', nullable: true })
|
||||
expirationjwt: Date;
|
||||
|
||||
@Column({ type: 'datetime', nullable: true })
|
||||
lastlog: Date;
|
||||
|
||||
@ManyToOne(() => TipoUsuario, (tipoUsuario) => tipoUsuario.usuarios)
|
||||
@JoinColumn({
|
||||
name: 'id_tipo_usuario',
|
||||
})
|
||||
tipoUsuario: TipoUsuario;
|
||||
|
||||
@OneToMany(() => Profesor, (profesor) => profesor.usuario)
|
||||
profesores: Profesor[];
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Patch,
|
||||
Body,
|
||||
Param,
|
||||
UseGuards,
|
||||
Delete,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiOperation,
|
||||
ApiParam,
|
||||
ApiResponse,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import { RolesGuard } from 'src/permissions/roles.guard';
|
||||
import { UsuarioService } from './usuario.service';
|
||||
import { JwtAuthGuard } from 'src/permissions/jwt.guard';
|
||||
import { UpdateUsuarioDto } from './dto/usuario-response.dto';
|
||||
import { DeleteResult } from 'typeorm';
|
||||
|
||||
@ApiBearerAuth()
|
||||
@ApiTags('Usuario')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Controller('usuario')
|
||||
export class UsuarioController {
|
||||
constructor(private readonly usuarioService: UsuarioService) {}
|
||||
|
||||
@Get(':id_usuario')
|
||||
@ApiOperation({
|
||||
summary: 'Obtener un usuario por su id',
|
||||
})
|
||||
async getUsuario(@Param('id_usuario') id_usuario: number) {
|
||||
return this.usuarioService.findOneById(id_usuario);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({
|
||||
summary: 'Obtener todos los usuarios registrados',
|
||||
})
|
||||
async getUsuarios() {
|
||||
return this.usuarioService.findAll();
|
||||
}
|
||||
|
||||
@Patch(':id_usuario')
|
||||
@ApiOperation({
|
||||
summary: 'Actualizar un usuario por su ID',
|
||||
})
|
||||
async updateUsuario(
|
||||
@Param('id_usuario') id_usuario: number,
|
||||
@Body() updateUsuarioDto: UpdateUsuarioDto,
|
||||
) {
|
||||
return this.usuarioService.update(id_usuario, updateUsuarioDto);
|
||||
}
|
||||
|
||||
@Delete(':id_usuario')
|
||||
@ApiOperation({
|
||||
summary: 'Eliminar un usuario',
|
||||
description:
|
||||
'Elimina un usuario y desvincula a los profesores relacionados.',
|
||||
})
|
||||
async deleteUsuario(@Param('id_usuario') id_usuario: number) {
|
||||
return this.usuarioService.remove(id_usuario);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { UsuarioService } from './usuario.service';
|
||||
import { Usuario } from './entities/usuario.entity';
|
||||
import { UsuarioController } from './usuario.controller';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Usuario])],
|
||||
providers: [UsuarioService],
|
||||
exports: [UsuarioService],
|
||||
controllers: [UsuarioController],
|
||||
})
|
||||
export class UsuarioModule {}
|
||||
@@ -0,0 +1,109 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
Logger,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { DeleteResult, Not, Repository } from 'typeorm';
|
||||
import { Usuario } from './entities/usuario.entity';
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import {
|
||||
UpdateUsuarioDto,
|
||||
UsuarioResponseDto,
|
||||
} from './dto/usuario-response.dto';
|
||||
import { Profesor } from 'src/profesor/entities/profesor.entity';
|
||||
|
||||
@Injectable()
|
||||
export class UsuarioService {
|
||||
constructor(
|
||||
@InjectRepository(Usuario)
|
||||
private usuarioRepository: Repository<Usuario>,
|
||||
) {}
|
||||
|
||||
async findOneByName(usuario: string): Promise<Usuario> {
|
||||
return this.usuarioRepository.findOne({ where: { usuario } });
|
||||
}
|
||||
|
||||
async findOneById(id_usuario: number): Promise<Usuario> {
|
||||
return this.usuarioRepository.findOne({ where: { id_usuario } });
|
||||
}
|
||||
|
||||
async findAll(): Promise<UsuarioResponseDto[]> {
|
||||
const usuarios = await this.usuarioRepository.find();
|
||||
return plainToInstance(UsuarioResponseDto, usuarios, {
|
||||
excludeExtraneousValues: true,
|
||||
});
|
||||
}
|
||||
|
||||
async update(
|
||||
id_usuario: number,
|
||||
updateUsuarioDto: UpdateUsuarioDto,
|
||||
): Promise<Usuario> {
|
||||
const usuario = await this.usuarioRepository.findOne({
|
||||
where: { id_usuario },
|
||||
});
|
||||
|
||||
if (!usuario) {
|
||||
throw new NotFoundException(`Usuario con ID ${id_usuario} no encontrado`);
|
||||
}
|
||||
|
||||
if (updateUsuarioDto.nombre) {
|
||||
const usuarioExistente = await this.usuarioRepository.findOne({
|
||||
where: {
|
||||
usuario: updateUsuarioDto.usuario,
|
||||
id_usuario: Not(id_usuario),
|
||||
}, // Excluye al usuario actual
|
||||
});
|
||||
|
||||
if (usuarioExistente) {
|
||||
throw new BadRequestException(
|
||||
`El nombre de usuario "${updateUsuarioDto.nombre}" ya está en uso`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Si el usuario envía una nueva contraseña, la encriptamos antes de guardarla
|
||||
if (updateUsuarioDto.password) {
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
updateUsuarioDto.password = await bcrypt.hash(
|
||||
updateUsuarioDto.password,
|
||||
salt,
|
||||
);
|
||||
}
|
||||
|
||||
Object.assign(usuario, updateUsuarioDto);
|
||||
return this.usuarioRepository.save(usuario);
|
||||
}
|
||||
|
||||
async remove(id_usuario: number): Promise<DeleteResult> {
|
||||
return await this.usuarioRepository.manager.transaction(
|
||||
async (transactionalEntityManager) => {
|
||||
// Paso 1: Desvincular el usuario de la tabla profesor
|
||||
await transactionalEntityManager.update(
|
||||
Profesor,
|
||||
{ id_usuario },
|
||||
{ id_usuario: null },
|
||||
);
|
||||
|
||||
// Paso 2: Intentar eliminar el usuario
|
||||
return await transactionalEntityManager.delete(Usuario, { id_usuario });
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async updateTokenAndDates(
|
||||
id: number,
|
||||
token: string,
|
||||
lastLogin: Date,
|
||||
jwtExpiry: Date,
|
||||
): Promise<void> {
|
||||
Logger.debug('update token and date user');
|
||||
await this.usuarioRepository.update(id, {
|
||||
jwt: token,
|
||||
lastlog: lastLogin,
|
||||
expirationjwt: jwtExpiry,
|
||||
});
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user