Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bfd9d7da0d | |||
| 8e5d2c000d | |||
| d86565c2af | |||
| 56f3eec287 | |||
| cc7473ebd2 | |||
| 8eace2204b | |||
| 9ab9d05122 |
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
@@ -54,5 +54,3 @@ pids
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
fotografias
|
||||
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
# 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
+1350
-2805
File diff suppressed because it is too large
Load Diff
+3
-8
@@ -20,15 +20,13 @@
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^10.3.9",
|
||||
"@nestjs/common": "^10.0.0",
|
||||
"@nestjs/config": "^3.2.2",
|
||||
"@nestjs/core": "^10.0.0",
|
||||
"@nestjs/jwt": "^10.2.0",
|
||||
"@nestjs/mapped-types": "*",
|
||||
"@nestjs/passport": "^10.0.3",
|
||||
"@nestjs/platform-express": "^10.3.9",
|
||||
"@nestjs/serve-static": "^4.0.2",
|
||||
"@nestjs/swagger": "^7.4.2",
|
||||
"@nestjs/platform-express": "^10.0.0",
|
||||
"@nestjs/typeorm": "^10.0.2",
|
||||
"bcrypt": "^5.1.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
@@ -40,9 +38,7 @@
|
||||
"passport-jwt": "^4.0.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"sharp": "^0.33.4",
|
||||
"typeorm": "^0.3.20",
|
||||
"xlsx": "^0.18.5"
|
||||
"typeorm": "^0.3.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.0.0",
|
||||
@@ -51,7 +47,6 @@
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@types/multer": "^1.4.11",
|
||||
"@types/node": "^20.14.2",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"@types/supertest": "^6.0.0",
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import { IsBoolean, IsInt, IsNotEmpty, IsNumber, IsString, IsOptional, Length} from "class-validator";
|
||||
|
||||
export class profesorDto{
|
||||
|
||||
@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;
|
||||
|
||||
proyectosAcademicos: ProyectoDto[];
|
||||
datosAcademicos: DatosAcademicos[];
|
||||
lineasInvestigacion: LineasInvestigacion[];
|
||||
}
|
||||
|
||||
export class ProyectoDto {
|
||||
|
||||
@IsOptional()
|
||||
@Length(0, 500)
|
||||
proyecto: 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: string;
|
||||
}
|
||||
+42
-56
@@ -1,45 +1,63 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
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 { Usuario } from 'src/usuario/entities/usuario.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';
|
||||
|
||||
@Entity({ name: 'profesor' })
|
||||
@Entity()
|
||||
export class Profesor {
|
||||
@PrimaryGeneratedColumn({ type: 'int' })
|
||||
@PrimaryGeneratedColumn()
|
||||
id_profesor: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 80, nullable: false })
|
||||
nombre: string;
|
||||
@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 })
|
||||
rfc: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 3, nullable: true })
|
||||
homoclave: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 14, nullable: true })
|
||||
@Column({ type: 'varchar', length: 13, 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;
|
||||
|
||||
@@ -54,8 +72,8 @@ export class Profesor {
|
||||
|
||||
@Column({
|
||||
type: 'timestamp',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
fecha_alta: Date;
|
||||
|
||||
@@ -67,61 +85,29 @@ export class Profesor {
|
||||
})
|
||||
fecha_actualizacion: Date;
|
||||
|
||||
/* 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',
|
||||
})
|
||||
@ManyToOne(() => Edificio, edificio => edificio.profesores)
|
||||
@JoinColumn({ name: 'id_edificio' })
|
||||
edificio: Edificio;
|
||||
|
||||
@ManyToOne(() => Adscripcion, (adscripcion) => adscripcion.profesores)
|
||||
@JoinColumn({
|
||||
name: 'id_adscripcion',
|
||||
})
|
||||
@ManyToOne(() => Adscripcion, adscripcion => adscripcion.profesores)
|
||||
@JoinColumn({ name: 'id_adscripcion' })
|
||||
adscripcion: Adscripcion;
|
||||
|
||||
@ManyToOne(() => Categoria, (categoria) => categoria.profesores)
|
||||
@JoinColumn({
|
||||
name: 'id_categoria',
|
||||
})
|
||||
@ManyToOne(() => Categoria, categoria => categoria.profesores)
|
||||
@JoinColumn({ name: 'id_categoria' })
|
||||
categoria: Categoria;
|
||||
|
||||
@ManyToOne(() => Usuario, (usuario) => usuario.profesores)
|
||||
@JoinColumn({
|
||||
name: 'id_usuario',
|
||||
})
|
||||
usuario: Usuario;
|
||||
@ManyToOne(() => User, user => user.profesores)
|
||||
@JoinColumn({ name: 'id_usuario' })
|
||||
user: User;
|
||||
|
||||
@OneToMany(
|
||||
() => DatosAcademicos,
|
||||
(datosAcademicos) => datosAcademicos.profesor,
|
||||
)
|
||||
@OneToMany(() => DatosAcademicos, datosAcademicos => datosAcademicos.profesor)
|
||||
datosAcademicos: DatosAcademicos[];
|
||||
|
||||
@OneToMany(() => LineasInvestigacion, (linea) => linea.profesor)
|
||||
@OneToMany(() => LineasInvestigacion, linea => linea.profesor)
|
||||
lineasInvestigacion: LineasInvestigacion[];
|
||||
|
||||
@OneToMany(() => ProyectosAcademicos, (proyecto) => proyecto.profesor)
|
||||
@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,18 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, Query,UseGuards } from '@nestjs/common';
|
||||
import { ProfesorService } from './profesor.service';
|
||||
import { profesorDto } from './dto/profesorDto.dto';
|
||||
import {Roles} from '../permissions/roles.decorator'
|
||||
import {RolesGuard} from '../permissions/roles.guard'
|
||||
import {Role} from '../permissions/role.enum'
|
||||
|
||||
@Controller('profesor')
|
||||
@UseGuards(RolesGuard)
|
||||
export class ProfesorController {
|
||||
constructor(private profesorService: ProfesorService) {}
|
||||
|
||||
@Post()
|
||||
@Roles(Role.Responsable,Role.Admin)
|
||||
async register(@Body() data: profesorDto) {
|
||||
return this.profesorService.register(data);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.profesorService.findAll();
|
||||
}
|
||||
|
||||
@Get('page')
|
||||
findAllPaginated(@Query('page') page: number = 1, @Query('limit') limit: number = 10, @Query() filters: any) {
|
||||
page = page < 1 ? 1 : page;
|
||||
limit = limit > 10 || limit < 1 ? 10 : limit;
|
||||
|
||||
return this.profesorService.findAllPaginated(page, limit, filters);
|
||||
}
|
||||
|
||||
@Put(':id_profesor')
|
||||
@Roles(Role.Responsable,Role.Admin)
|
||||
async modify(@Param('id_profesor', ParseIntPipe) id_profesor: number, @Body() data: profesorDto) {
|
||||
return this.profesorService.modify(id_profesor, data);
|
||||
}
|
||||
|
||||
@Delete(':id_profesor')
|
||||
@Roles(Role.Responsable,Role.Admin)
|
||||
async remove(@Param('id_profesor', ParseIntPipe) id_profesor: number) {
|
||||
return this.profesorService.remove(id_profesor);
|
||||
}
|
||||
|
||||
@Get(':id_profesor')
|
||||
async profile(@Param('id_profesor', ParseIntPipe) id_profesor: number) {
|
||||
return this.profesorService.profile(id_profesor);
|
||||
}
|
||||
|
||||
@Post('filter')
|
||||
async filter(@Body() data: profesorDto) {
|
||||
return this.profesorService.filter(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
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, LineasInvestigacion } from './dto/profesorDto.dto';
|
||||
import { ProyectosAcademicosService } from 'src/proyectos_academicos/proyectos_academicos.service';
|
||||
import { DatosAcademicosService } from 'src/datos_academicos/datos_academicos.service';
|
||||
import { LineasInvestigacionService } from 'src/lineas_investigacion/lineas_investigacion.service';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Profesor,
|
||||
ProyectosAcademicos,
|
||||
DatosAcademicos,
|
||||
LineasInvestigacion]),
|
||||
DatosAcademicosModule,
|
||||
LineasInvestigacionModule,
|
||||
ProyectosAcademicosModule,
|
||||
JwtModule.register({
|
||||
secret: process.env.JWT_SECRET,
|
||||
signOptions: { expiresIn: '60m' },
|
||||
}),
|
||||
ConfigModule,
|
||||
],
|
||||
providers: [ProfesorService,
|
||||
ProyectosAcademicosService,
|
||||
DatosAcademicosService,
|
||||
LineasInvestigacionService,],
|
||||
controllers: [ProfesorController],
|
||||
})
|
||||
export class ProfesorModule {}
|
||||
@@ -0,0 +1,18 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,241 @@
|
||||
import { HttpException, Injectable } 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 profesorRepository: Repository<Profesor>,
|
||||
private datosAcademicosService: DatosAcademicosService,
|
||||
private lineasInvestigacionService: LineasInvestigacionService,
|
||||
private proyectosAcademicosService: ProyectosAcademicosService,
|
||||
@InjectRepository(ProyectosAcademicos)
|
||||
private readonly proyectosAcademicosRepository: Repository<ProyectosAcademicos>,
|
||||
@InjectRepository(DatosAcademicos)
|
||||
private readonly datosAcademicosRepository: Repository<DatosAcademicos>,
|
||||
@InjectRepository(LineasInvestigacion)
|
||||
private readonly lineasInvestigacionRepository: Repository<LineasInvestigacion>,
|
||||
) {}
|
||||
|
||||
//register teacher
|
||||
async register(data: profesorDto) {
|
||||
const {num_trabajador,rfc,proyectosAcademicos, datosAcademicos, lineasInvestigacion } = data;
|
||||
|
||||
const existrfc = await this.profesorRepository.findOne({where:{rfc}});
|
||||
if(existrfc){
|
||||
throw new HttpException('rfc already exist', 403);
|
||||
}
|
||||
|
||||
const existnumber = await this.profesorRepository.findOne({where:{num_trabajador}});
|
||||
if(existnumber){
|
||||
throw new HttpException('number of worker already exist', 403);
|
||||
}
|
||||
|
||||
const newProfesor = this.profesorRepository.create(data);
|
||||
const savedProfesor = await this.profesorRepository.save(newProfesor);
|
||||
|
||||
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 (datosAcademicos && datosAcademicos.length > 0) {
|
||||
const datos = datosAcademicos.map(dato => {
|
||||
const newDato = this.datosAcademicosRepository.create({
|
||||
...dato,
|
||||
profesor: savedProfesor,
|
||||
id_profesor: savedProfesor.id_profesor,
|
||||
});
|
||||
return newDato;
|
||||
});
|
||||
await this.datosAcademicosRepository.save(datos);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 (datosAcademicos) {
|
||||
await this.datosAcademicosRepository.delete({ profesor: { id_profesor } });
|
||||
const datos = datosAcademicos.map(dato => {
|
||||
const newDato = this.datosAcademicosRepository.create({
|
||||
...dato,
|
||||
profesor: savedProfesor,
|
||||
id_profesor: savedProfesor.id_profesor,
|
||||
});
|
||||
return newDato;
|
||||
});
|
||||
await this.datosAcademicosRepository.save(datos);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Delete related records in datos_academicos
|
||||
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;
|
||||
}
|
||||
|
||||
//brings teachers by filter
|
||||
async filter(filterDto: profesorDto): Promise<Profesor[]> {
|
||||
const query = this.profesorRepository.createQueryBuilder('profesor');
|
||||
|
||||
if (filterDto.nombre) {
|
||||
query.andWhere('profesor.nombre like :nombre', { nombre: `%${filterDto.nombre}%` });
|
||||
}
|
||||
|
||||
if (filterDto.id_categoria !== undefined) {
|
||||
query.andWhere('profesor.id_categoria = :id_categoria', { id_categoria: filterDto.id_categoria });
|
||||
}
|
||||
|
||||
if (filterDto.extension) {
|
||||
query.andWhere('profesor.extension like :extension', { extension: `%${filterDto.extension}%` });
|
||||
}
|
||||
|
||||
if (filterDto.id_adscripcion !== undefined) {
|
||||
query.andWhere('profesor.id_adscripcion = :id_adscripcion', { id_adscripcion: filterDto.id_adscripcion });
|
||||
}
|
||||
|
||||
if (filterDto.id_edificio !== undefined) {
|
||||
query.andWhere('profesor.id_edificio = :id_edificio', { id_edificio: filterDto.id_edificio });
|
||||
}
|
||||
|
||||
if (filterDto.activo !== undefined) {
|
||||
query.andWhere('profesor.activo = :activo', { activo: filterDto.activo });
|
||||
}
|
||||
|
||||
return query.getMany();
|
||||
}
|
||||
|
||||
async findAllPaginated(
|
||||
page: number,
|
||||
limit: number,
|
||||
filters: any
|
||||
): Promise<{ profesores: Profesor[], total: number, totalPages: number }> {
|
||||
const query = this.profesorRepository.createQueryBuilder('profesor')
|
||||
|
||||
if (filters.nombre) {
|
||||
query.andWhere('profesor.nombre LIKE :nombre', { nombre: `%${filters.nombre}%` });
|
||||
}
|
||||
|
||||
if (filters.adscripcion) {
|
||||
query.andWhere('profesor.adscripcion = :adscripcion', { adscripcion: filters.adscripcion });
|
||||
}
|
||||
|
||||
if (filters.categoria) {
|
||||
query.andWhere('profesor.categoria = :categoria', { categoria: filters.categoria });
|
||||
}
|
||||
|
||||
if (filters.edificio) {
|
||||
query.andWhere('profesor.edificio = :edificio', { edificio: filters.edificio });
|
||||
}
|
||||
|
||||
const [profesores, total] = await query
|
||||
.skip((page - 1) * limit)
|
||||
.take(limit)
|
||||
.orderBy('profesor.nombre', 'ASC')
|
||||
.getManyAndCount();
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
return { profesores, total, totalPages };
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Controller, Get, Logger } from '@nestjs/common';
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { AdscripcionService } from './adscripcion.service';
|
||||
|
||||
@Controller('adscripcion')
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
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,6 +1,6 @@
|
||||
import { Entity, PrimaryColumn, Column, OneToMany } from 'typeorm';
|
||||
|
||||
@Entity({ name: 'adscripcion' })
|
||||
@Entity()
|
||||
export class Adscripcion {
|
||||
@PrimaryColumn({ type: 'int', nullable: false })
|
||||
id_adscripcion: number;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
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,6 +10,7 @@ export class AppController {
|
||||
|
||||
@Get()
|
||||
getHello(): string {
|
||||
console.log("Todo bien", this.configservice.get("DATABASE_NAME"))
|
||||
return this.appService.getHello();
|
||||
}
|
||||
}
|
||||
|
||||
+20
-36
@@ -1,43 +1,40 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||
import { join } from 'path';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { UsuarioModule } from './usuario/usuario.module';
|
||||
import { Usuario } from './usuario/entities/usuario.entity';
|
||||
import { UsersModule } from './users/users.module';
|
||||
import { User } from './users/entities/user.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 { AdscripcionModule } from './adscripcion/adscripcion.module';
|
||||
import { EdificioModule } from './edificio/edificio.module';
|
||||
import { CategoriaModule } from './categoria/categoria.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';
|
||||
import { ProfesorModule } from './Profesor/profesor.module';
|
||||
import { Profesor } from './Profesor/entities/profesor.entity';
|
||||
|
||||
import { AdscripcionController } from "./adscripcion/adscripcion.controller";
|
||||
import { AdscripcionService } from "./adscripcion/adscripcion.service";
|
||||
import { AdscripcionModule } from "./adscripcion/adscripcion.module";
|
||||
import { EdificioModule } from "./edificio/edificio.module";
|
||||
import { CategoriaModule } from "./categoria/categoria.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
AuthModule,
|
||||
UsuarioModule,
|
||||
UsersModule,
|
||||
ConfigModule.forRoot({
|
||||
envFilePath: '.env',
|
||||
isGlobal: true,
|
||||
}),
|
||||
TypeOrmModule.forRootAsync({
|
||||
imports: [ConfigModule, AuthModule, UsuarioModule],
|
||||
imports: [ConfigModule, AuthModule, UsersModule],
|
||||
inject: [ConfigService],
|
||||
useFactory: async (configService: ConfigService) => ({
|
||||
type: 'mysql',
|
||||
type: 'mariadb',
|
||||
host: configService.get('DB_HOST'),
|
||||
port: configService.get('DB_PORT'),
|
||||
username: configService.get('DB_USER'),
|
||||
@@ -45,32 +42,19 @@ import { FotografiaModule } from './fotografia/fotografia.module';
|
||||
database: configService.get('DB_DATABASE'),
|
||||
entities: [
|
||||
Profesor,
|
||||
Usuario,
|
||||
User,
|
||||
Adscripcion,
|
||||
Categoria,
|
||||
Edificio,
|
||||
DatosAcademicos,
|
||||
Edificio,
|
||||
LineasInvestigacion,
|
||||
ProyectosAcademicos,
|
||||
TipoUsuario,
|
||||
],
|
||||
//dropSchema: true,
|
||||
//synchronize: true, JAMA USAR ESTO EN PRODUCCION BORRA DATOS DE LA DB
|
||||
synchronize: true,
|
||||
}),
|
||||
}),
|
||||
ServeStaticModule.forRoot({
|
||||
rootPath: join(__dirname, '..', 'fotografias'),
|
||||
serveRoot: '/fotografias',
|
||||
}),
|
||||
FotografiaModule,
|
||||
ProfesorModule,
|
||||
AdscripcionModule,
|
||||
EdificioModule,
|
||||
CategoriaModule,
|
||||
DatabaseModule,
|
||||
LineasInvestigacionModule,
|
||||
ProyectosAcademicosModule,
|
||||
UsuarioModule,
|
||||
ProfesorModule, AdscripcionModule, EdificioModule, CategoriaModule
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Injectable } from '@nestjs/common';
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
console.log("it's ok ")
|
||||
|
||||
return "hei verden";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
+68
-29
@@ -11,44 +11,83 @@ import {
|
||||
UseGuards,
|
||||
Delete,
|
||||
ParseIntPipe,
|
||||
Query,
|
||||
Logger,
|
||||
Req,
|
||||
Query
|
||||
} from '@nestjs/common';
|
||||
import { AuthGuard } from './auth.guard';
|
||||
import { AuthService } from './auth.service';
|
||||
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';
|
||||
import { registerDto } from './dto/registerDto.dto';
|
||||
import {Roles} from '../permissions/roles.decorator'
|
||||
import {RolesGuard} from '../permissions/roles.guard'
|
||||
import {Role} from '../permissions/role.enum'
|
||||
|
||||
|
||||
@ApiTags('Auth')
|
||||
@Controller('auth')
|
||||
//@UseGuards(RolesGuard)
|
||||
export class AuthController {
|
||||
constructor(private authService: AuthService) {}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('login')
|
||||
@ApiOperation({
|
||||
summary: 'Iniciar sesión',
|
||||
description: 'Autentica a un usuario y devuelve un token de acceso.',
|
||||
})
|
||||
async signIn(@Body() data: SignInDto) {
|
||||
async signIn(@Body() data: registerDto) {
|
||||
return this.authService.signIn(data);
|
||||
}
|
||||
|
||||
@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: SignUpDto) {
|
||||
Logger.debug('register user');
|
||||
return this.authService.register(data);
|
||||
//@UseGuards()
|
||||
@Post("register")
|
||||
//@Roles(Role.Admin)
|
||||
async register(@Body() data: registerDto){
|
||||
return this.authService.register(data)
|
||||
}
|
||||
}
|
||||
|
||||
//@UseGuards(AuthGuard)
|
||||
@Get('profile')
|
||||
async getProfile(@Request() req) {
|
||||
return req.user;
|
||||
}
|
||||
|
||||
//@UseGuards(AuthGuard)
|
||||
@Put('Modificacion/:id')
|
||||
//@Roles(Role.Admin)
|
||||
async update(@Param('id') userId: number, @Body() data: registerDto) {
|
||||
return this.authService.update(userId, data);
|
||||
}
|
||||
|
||||
//@UseGuards(AuthGuard)
|
||||
@Delete('Borrado/:id')
|
||||
//@Roles(Role.Admin)
|
||||
async remove(@Param('id') id: number) {
|
||||
await this.authService.remove(id);
|
||||
return { message: 'User successfully deleted' };
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('validate')
|
||||
async validateToken(@Body('token') token: string) {
|
||||
return this.authService.validateToken(token);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.authService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id_user')
|
||||
async profile(@Param('id_user', ParseIntPipe) id_user: number) {
|
||||
return this.authService.profile(id_user);
|
||||
}
|
||||
|
||||
@Get('page')
|
||||
findAllPaginated(
|
||||
@Query('page') page: number = 1,
|
||||
@Query('limit') limit: number = 10,
|
||||
@Query('nombreUsuario') nombreUsuario?: string,
|
||||
@Query('tipoUsuario') tipoUsuario?: string
|
||||
) {
|
||||
page = page < 1 ? 1 : page;
|
||||
limit = limit > 10 || limit < 1 ? 10 : limit;
|
||||
|
||||
return this.authService.findAllPaginated(page, limit, nombreUsuario, tipoUsuario);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
+12
-14
@@ -1,31 +1,29 @@
|
||||
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { UsuarioModule } from '../usuario/usuario.module';
|
||||
import { UsersModule } from '../users/users.module';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Usuario } from 'src/usuario/entities/usuario.entity';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { JwtStrategy } from 'src/permissions/jwt.strategy';
|
||||
import { User } from 'src/users/entities/user.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
UsuarioModule,
|
||||
PassportModule.register({ defaultStrategy: 'jwt' }),
|
||||
UsersModule,
|
||||
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: configService.get('JWT_SECRET_EXPIRES') },
|
||||
};
|
||||
},
|
||||
signOptions: { expiresIn: "1h"},
|
||||
}
|
||||
}
|
||||
}),
|
||||
TypeOrmModule.forFeature([Usuario]),
|
||||
TypeOrmModule.forFeature([User])
|
||||
],
|
||||
providers: [AuthService, JwtStrategy],
|
||||
providers: [AuthService],
|
||||
controllers: [AuthController],
|
||||
exports: [AuthService],
|
||||
})
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
+83
-43
@@ -2,59 +2,52 @@ import {
|
||||
HttpException,
|
||||
Inject,
|
||||
Injectable,
|
||||
Logger,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { UsuarioService } from '../usuario/usuario.service';
|
||||
import { UsersService } from '../users/users.service';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { registerDto, SignInDto } from './dto/sign-in.dto';
|
||||
import { Usuario } from 'src/usuario/entities/usuario.entity';
|
||||
import { registerDto } from './dto/registerDto.dto';
|
||||
import { User } from 'src/users/entities/user.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: UsuarioService,
|
||||
private usersService: UsersService,
|
||||
private jwtService: JwtService,
|
||||
private configService: ConfigService,
|
||||
@InjectRepository(Usuario) private userRepository: Repository<Usuario>,
|
||||
@InjectRepository(User) private userRepository: Repository<User>,
|
||||
) {}
|
||||
|
||||
//register users
|
||||
async register(data: SignUpDto) {
|
||||
const { usuario, password } = data;
|
||||
async register(data: registerDto) {
|
||||
const { usuario, contraseña} = data;
|
||||
|
||||
const searchedUser = await this.usersService.findOneByName(usuario);
|
||||
|
||||
if (searchedUser) {
|
||||
throw new HttpException('El usuario ya existe', 409);
|
||||
if (await this.usersService.findOne(usuario)) {
|
||||
throw new HttpException('User already exist', 403);
|
||||
}
|
||||
|
||||
const hashedpassword = await hash(password, 10);
|
||||
data = { ...data, password: hashedpassword };
|
||||
const hashedpassword = await hash(contraseña, 10);
|
||||
|
||||
const createdUsuario = await this.userRepository.create(data);
|
||||
const savedUsuario = await this.userRepository.save(createdUsuario);
|
||||
|
||||
return savedUsuario;
|
||||
data = { ...data, contraseña: hashedpassword };
|
||||
return this.userRepository.save(this.userRepository.create(data));
|
||||
}
|
||||
|
||||
//singin
|
||||
async signIn(data: SignInDto) {
|
||||
const { usuario, password, rememberMe } = data;
|
||||
const user = await this.usersService.findOneByName(usuario);
|
||||
async signIn(data: registerDto) {
|
||||
const { usuario, contraseña } = data;
|
||||
const user = await this.usersService.findOne(usuario);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException('Credenciales inválidas.');
|
||||
throw new UnauthorizedException('Invalid name');
|
||||
}
|
||||
|
||||
const checkPassword = await compare(password, user.password);
|
||||
const checkPassword = await compare(contraseña, user.contraseña);
|
||||
if (!checkPassword) {
|
||||
throw new UnauthorizedException('Credenciales inválidas.');
|
||||
throw new UnauthorizedException('Invalid password');
|
||||
}
|
||||
|
||||
const payload = {
|
||||
@@ -62,32 +55,44 @@ export class AuthService {
|
||||
usuario: user.usuario,
|
||||
id_tipo_usuario: user.id_tipo_usuario,
|
||||
};
|
||||
|
||||
const token = this.jwtService.sign(payload, {
|
||||
expiresIn: rememberMe ? '31d' : '7h',
|
||||
});
|
||||
|
||||
const expirationTime = rememberMe ? 31 * 24 * 3600 * 1000 : 7 * 3600 * 1000;
|
||||
const token = this.jwtService.sign(payload);
|
||||
|
||||
await this.usersService.updateTokenAndDates(
|
||||
user.id_usuario,
|
||||
token,
|
||||
new Date(),
|
||||
new Date(Date.now() + expirationTime),
|
||||
new Date(Date.now() + 3600 * 1000),
|
||||
);
|
||||
|
||||
return { token };
|
||||
return { token: token };
|
||||
}
|
||||
|
||||
//update user
|
||||
async update(userId, data:registerDto) {
|
||||
|
||||
const{contraseña}=data;
|
||||
|
||||
if(contraseña){
|
||||
const hashedpassword = await hash(contraseña, 10);
|
||||
data = {...data,contraseña:hashedpassword};
|
||||
}
|
||||
|
||||
return await this.userRepository.update(userId, data);
|
||||
}
|
||||
|
||||
async remove(userId: number): Promise<void> {
|
||||
await this.userRepository.delete(userId);
|
||||
}
|
||||
|
||||
//validate tojen of users
|
||||
async validateToken(token: string): Promise<Usuario> {
|
||||
async validateToken(token: string): Promise<User> {
|
||||
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.findOneByName(decoded.username);
|
||||
const user = await this.usersService.findOne(decoded.username);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException('Invalid token');
|
||||
}
|
||||
@@ -98,18 +103,53 @@ export class AuthService {
|
||||
throw new UnauthorizedException('Token has expired');
|
||||
}
|
||||
|
||||
return;
|
||||
return user;
|
||||
} catch (error) {
|
||||
throw new UnauthorizedException('validation token failed');
|
||||
}
|
||||
}
|
||||
|
||||
getTokenUserId(token: string): number {
|
||||
try {
|
||||
const decoded = this.jwtService.verify(token);
|
||||
return decoded['id_usuario'];
|
||||
} catch (error) {
|
||||
throw new UnauthorizedException('Invalid token');
|
||||
}
|
||||
async findAll(): Promise<User[]> {
|
||||
return this.userRepository.find();
|
||||
}
|
||||
|
||||
async profile(id_usuario: number) {
|
||||
const user = await this.userRepository.findOne({
|
||||
where: { id_usuario },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new HttpException('user not found', 404);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async findAllPaginated(
|
||||
page: number,
|
||||
limit: number,
|
||||
nombreUsuario?: string,
|
||||
tipoUsuario?: string
|
||||
): Promise<{ usuarios: User[], total: number, totalPages: number }> {
|
||||
const query = this.userRepository.createQueryBuilder('usuario');
|
||||
|
||||
if (nombreUsuario) {
|
||||
query.andWhere('usuario.nombre LIKE :nombreUsuario', { nombreUsuario: `%${nombreUsuario}%` });
|
||||
}
|
||||
|
||||
if (tipoUsuario) {
|
||||
query.andWhere('usuario.id_tipo_usuario = :tipoUsuario', { tipoUsuario });
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {IsString, IsNotEmpty,IsNumber } 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;
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Controller, Get, Logger } from '@nestjs/common';
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { CategoriaService } from './categoria.service';
|
||||
|
||||
@Controller('categoria')
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Entity, PrimaryColumn, Column, OneToMany } from 'typeorm';
|
||||
|
||||
@Entity({ name: 'categoria' })
|
||||
@Entity()
|
||||
export class Categoria {
|
||||
@PrimaryColumn({ type: 'int', nullable: false })
|
||||
id_categoria: number;
|
||||
@@ -10,4 +10,4 @@ export class Categoria {
|
||||
|
||||
@OneToMany(() => Categoria, (categoria) => categoria.profesores)
|
||||
profesores: Categoria[];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
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' };
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
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 {}
|
||||
@@ -1,67 +0,0 @@
|
||||
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> {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
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 {}
|
||||
@@ -1,88 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { DatosAcademicosService } from './datos_academicos.service';
|
||||
|
||||
@Controller('datos-academicos')
|
||||
export class DatosAcademicosController {
|
||||
constructor(private readonly datosAcademicosService: DatosAcademicosService) {}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.datosAcademicosService.findAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { DatosAcademicos } from './entities/datos_academicos.entity';
|
||||
import { DatosAcademicosService } from './datos_academicos.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([DatosAcademicos])],
|
||||
providers: [DatosAcademicosService],
|
||||
exports: [DatosAcademicosService],
|
||||
})
|
||||
export class DatosAcademicosModule {}
|
||||
@@ -0,0 +1,21 @@
|
||||
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>,
|
||||
) {}
|
||||
|
||||
// Add methods to manage DatosAcademicos entities as needed
|
||||
async removeByProfesorId(id_profesor: number): Promise<void> {
|
||||
await this.datosAcademicosRepository.delete({ profesor: { id_profesor } });
|
||||
}
|
||||
|
||||
async findAll(): Promise<DatosAcademicos[]> {
|
||||
return this.datosAcademicosRepository.find();
|
||||
}
|
||||
}
|
||||
+3
-9
@@ -1,11 +1,5 @@
|
||||
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 {
|
||||
@@ -21,7 +15,7 @@ export class DatosAcademicos {
|
||||
@Column({ type: 'varchar', length: 600, nullable: true })
|
||||
grados_obtenidos: string;
|
||||
|
||||
@ManyToOne(() => Profesor, (profesor) => profesor.lineasInvestigacion)
|
||||
@ManyToOne(() => Profesor, (profesor) => profesor.datosAcademicos)
|
||||
@JoinColumn({ name: 'id_profesor' })
|
||||
profesor: Profesor;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Controller, Get, Logger } from '@nestjs/common';
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { EdificioService } from './edificio.service';
|
||||
|
||||
@Controller('edificio')
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Entity, PrimaryColumn, Column, OneToMany} from 'typeorm';
|
||||
import { Entity, PrimaryColumn, Column, OneToMany } from 'typeorm';
|
||||
|
||||
@Entity({ name: 'edificio' })
|
||||
@Entity()
|
||||
export class Edificio {
|
||||
@PrimaryColumn({ type: 'int', nullable: false })
|
||||
id_edificio: number;
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
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 {}
|
||||
@@ -1,58 +0,0 @@
|
||||
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,21 +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: 'varchar', length: 600, nullable: true })
|
||||
lineas_inv_html: string;
|
||||
|
||||
@ManyToOne(() => Profesor, (profesor) => profesor.lineasInvestigacion)
|
||||
@JoinColumn({ name: 'id_profesor' })
|
||||
profesor: Profesor;
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
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 {}
|
||||
@@ -1,69 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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: 300, nullable: true })
|
||||
lineas_inv: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 350, nullable: true })
|
||||
lineas_inv_html: string;
|
||||
|
||||
@ManyToOne(() => Profesor, profesor => profesor.lineasInvestigacion)
|
||||
@JoinColumn({ name: 'id_profesor' })
|
||||
profesor: Profesor;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { LineasInvestigacionService } from './lineas_investigacion.service';
|
||||
|
||||
@Controller('lineas-investigacion')
|
||||
export class LineasInvestigacionController {
|
||||
constructor(private readonly lineasInvestigacionService: LineasInvestigacionService) {}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.lineasInvestigacionService.findAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
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 {}
|
||||
@@ -0,0 +1,21 @@
|
||||
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>,
|
||||
) {}
|
||||
|
||||
// Add methods to manage LineasInvestigacion entities as needed
|
||||
async removeByProfesorId(id_profesor: number): Promise<void> {
|
||||
await this.lineasInvestigacionRepository.delete({ profesor: { id_profesor } });
|
||||
}
|
||||
|
||||
async findAll(): Promise<LineasInvestigacion[]> {
|
||||
return this.lineasInvestigacionRepository.find();
|
||||
}
|
||||
}
|
||||
+1
-42
@@ -1,50 +1,9 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
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);
|
||||
}
|
||||
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.enableCors();
|
||||
|
||||
app.use(bodyParser.json({ limit: '1mb' }));
|
||||
app.use(bodyParser.urlencoded({ limit: '1mb', extended: true }));
|
||||
|
||||
app.useStaticAssets(path.join(__dirname, '..', 'fotografias'), {
|
||||
prefix: '/fotografias/',
|
||||
});
|
||||
|
||||
await app.listen(3000);
|
||||
}
|
||||
bootstrap();
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
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,4 +1,5 @@
|
||||
export enum Role {
|
||||
Admin = 1,
|
||||
Responsable = 2,
|
||||
}
|
||||
Admin = 1,
|
||||
Responsable = 2,
|
||||
}
|
||||
|
||||
@@ -1,44 +1,56 @@
|
||||
import {
|
||||
Injectable,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
ForbiddenException,
|
||||
} from '@nestjs/common';
|
||||
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } 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) {}
|
||||
constructor(
|
||||
private reflector: Reflector,
|
||||
private jwtService: JwtService,
|
||||
private configService: ConfigService
|
||||
) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
// ✅ Obtener los roles requeridos en el endpoint
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
|
||||
if (!requiredRoles) {
|
||||
return true; // Si el endpoint no tiene restricción de roles, permitir acceso
|
||||
return true; // Permitir acceso si no se especifican roles requeridos
|
||||
}
|
||||
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const user = request.user; // Se obtiene de `JwtAuthGuard`
|
||||
const authHeader = request.headers['authorization'];
|
||||
|
||||
if (!user || !user.id_tipo_usuario) {
|
||||
throw new ForbiddenException(
|
||||
'No tienes permisos para acceder a este recurso',
|
||||
);
|
||||
if (!authHeader) {
|
||||
throw new UnauthorizedException('No authorization header 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',
|
||||
);
|
||||
const [, token] = authHeader.split(' ');
|
||||
if (!token) {
|
||||
throw new UnauthorizedException('No token provided');
|
||||
}
|
||||
|
||||
return true;
|
||||
try {
|
||||
const userPayload = await this.jwtService.verifyAsync(
|
||||
token,
|
||||
{
|
||||
secret: this.configService.get<string>('JWT_SECRET'),
|
||||
}
|
||||
);
|
||||
|
||||
const userRole: Role = userPayload.id_tipo_usuario;
|
||||
if (userRole === undefined) {
|
||||
return false; // Si el token no contiene roles, se niega el acceso
|
||||
}
|
||||
|
||||
return requiredRoles.includes(userRole);
|
||||
} catch (error) {
|
||||
throw new UnauthorizedException('Invalid token');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
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;
|
||||
*/
|
||||
@@ -1,169 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
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 {}
|
||||
@@ -1,242 +0,0 @@
|
||||
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 });
|
||||
}
|
||||
}
|
||||
@@ -1,26 +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 })
|
||||
proyectos_html: string;
|
||||
|
||||
@ManyToOne(() => Profesor, (profesor) => profesor.proyectosAcademicos)
|
||||
@JoinColumn({
|
||||
name: 'id_profesor',
|
||||
})
|
||||
profesor: Profesor;
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
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 {}
|
||||
@@ -1,69 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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: 500, nullable: true })
|
||||
proyecto: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 700, nullable: true })
|
||||
proyecto_html: string;
|
||||
|
||||
@ManyToOne(() => Profesor, profesor => profesor.proyectosAcademicos)
|
||||
@JoinColumn({ name: 'id_profesor' })
|
||||
profesor: Profesor;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Controller,Get } from '@nestjs/common';
|
||||
import { ProyectosAcademicosService } from './proyectos_academicos.service';
|
||||
|
||||
@Controller('proyectos-academicos')
|
||||
export class ProyectosAcademicosController {
|
||||
constructor(private readonly proyectosAcademicosService: ProyectosAcademicosService) {}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.proyectosAcademicosService.findAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
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 {}
|
||||
@@ -0,0 +1,21 @@
|
||||
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>,
|
||||
) {}
|
||||
|
||||
// Add methods to manage ProyectosAcademicos entities as needed
|
||||
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 { Usuario } from '../../usuario/entities/usuario.entity';
|
||||
import { User } from '../../users/entities/user.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(() => Usuario, (usuario) => usuario.tipoUsuario)
|
||||
usuarios: Usuario[];
|
||||
@OneToMany(() => User, (user) => user.tipoUsuario)
|
||||
users: User[];
|
||||
}
|
||||
|
||||
@@ -4,21 +4,17 @@ export class UserDto{
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
usuario: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
nombre: string;
|
||||
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
contraseña: string;
|
||||
|
||||
@IsString()
|
||||
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
id_tipo_usuario:string;
|
||||
id_tipo_usuario:number;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
correo: string;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
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' })
|
||||
tipoUsuario: TipoUsuario;
|
||||
|
||||
@OneToMany(() => Profesor, profesor => profesor.user)
|
||||
profesores: Profesor[];
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
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 {}
|
||||
@@ -0,0 +1,18 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Injectable } 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> {
|
||||
await this.users.update(id, {
|
||||
jwt: token,
|
||||
lastlog: lastLogin,
|
||||
expirationjwt: jwtExpiry,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
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) {}
|
||||
@@ -1,49 +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 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[];
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
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 {}
|
||||
@@ -1,109 +0,0 @@
|
||||
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