profesores
This commit is contained in:
+1
-1
@@ -44,7 +44,7 @@ import { DatabaseModule } from './database/database.module';
|
||||
username: configService.get('DB_USER'),
|
||||
password: configService.get('DB_PASSWORD'),
|
||||
database: configService.get('DB_DATABASE'),
|
||||
dropSchema: true,
|
||||
//dropSchema: true,
|
||||
entities: [
|
||||
Profesor,
|
||||
User,
|
||||
|
||||
@@ -21,9 +21,10 @@ 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 { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { SignUpDto } from './dto/sign-up.dto';
|
||||
|
||||
@ApiBearerAuth()
|
||||
@ApiTags('Auth')
|
||||
@Controller('auth')
|
||||
@UseGuards(RolesGuard)
|
||||
@@ -39,14 +40,14 @@ export class AuthController {
|
||||
return this.authService.signIn(data);
|
||||
}
|
||||
|
||||
//@UseGuards()
|
||||
@Post('register')
|
||||
@ApiOperation({
|
||||
summary: 'Registro de usuario',
|
||||
description: 'Registro ded administradores o responsables',
|
||||
})
|
||||
@UseGuards()
|
||||
@Roles(Role.Admin)
|
||||
async register(@Body() data: registerDto) {
|
||||
async register(@Body() data: SignUpDto) {
|
||||
Logger.debug('register user');
|
||||
return this.authService.register(data);
|
||||
}
|
||||
|
||||
@@ -6,10 +6,13 @@ import { AuthController } from './auth.controller';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { User } from 'src/users/entities/user.entity';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { JwtStrategy } from 'src/permissions/jwt.strategy';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
UsersModule,
|
||||
PassportModule.register({ defaultStrategy: 'jwt' }),
|
||||
JwtModule.registerAsync({
|
||||
inject: [ConfigService],
|
||||
useFactory: async (configService: ConfigService) => {
|
||||
@@ -22,7 +25,7 @@ import { User } from 'src/users/entities/user.entity';
|
||||
}),
|
||||
TypeOrmModule.forFeature([User]),
|
||||
],
|
||||
providers: [AuthService],
|
||||
providers: [AuthService, JwtStrategy],
|
||||
controllers: [AuthController],
|
||||
exports: [AuthService],
|
||||
})
|
||||
|
||||
+17
-12
@@ -14,6 +14,7 @@ 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 {
|
||||
@@ -25,28 +26,33 @@ export class AuthService {
|
||||
) {}
|
||||
|
||||
//register users
|
||||
async register(data: registerDto) {
|
||||
const { usuario, contraseña } = data;
|
||||
async register(data: SignUpDto) {
|
||||
const { usuario, password } = data;
|
||||
|
||||
if (await this.usersService.findOne(usuario)) {
|
||||
throw new HttpException('User already exist', 403);
|
||||
const searchedUser = await this.usersService.findOne(usuario);
|
||||
|
||||
if (searchedUser) {
|
||||
throw new HttpException('El usuario ya existe', 409);
|
||||
}
|
||||
|
||||
const hashedpassword = await hash(contraseña, 10);
|
||||
|
||||
data = { ...data, contraseña: hashedpassword };
|
||||
return this.userRepository.save(this.userRepository.create(data));
|
||||
const hashedpassword = await hash(password, 10);
|
||||
data = { ...data, password: hashedpassword };
|
||||
|
||||
const createdUsuario = await this.userRepository.create(data);
|
||||
const savedUsuario = await this.userRepository.save(createdUsuario);
|
||||
|
||||
return savedUsuario;
|
||||
}
|
||||
|
||||
//singin
|
||||
async signIn(data: SignInDto) {
|
||||
const { usuario, contraseña, rememberMe } = data;
|
||||
const { usuario, password, rememberMe } = data;
|
||||
const user = await this.usersService.findOne(usuario);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException('Credenciales inválidas.');
|
||||
}
|
||||
|
||||
const checkPassword = await compare(contraseña, user.contraseña);
|
||||
const checkPassword = await compare(password, user.password);
|
||||
if (!checkPassword) {
|
||||
throw new UnauthorizedException('Credenciales inválidas.');
|
||||
}
|
||||
@@ -56,7 +62,6 @@ export class AuthService {
|
||||
usuario: user.usuario,
|
||||
id_tipo_usuario: user.id_tipo_usuario,
|
||||
};
|
||||
console.table(payload);
|
||||
|
||||
const token = this.jwtService.sign(payload, {
|
||||
expiresIn: rememberMe ? '31d' : '7h',
|
||||
@@ -98,7 +103,7 @@ export class AuthService {
|
||||
const hashedpassword = await hash(contraseña, 10);
|
||||
data = { ...data, contraseña: hashedpassword };
|
||||
} else {
|
||||
data = { ...data, contraseña: user.contraseña };
|
||||
data = { ...data, contraseña: user.password };
|
||||
}
|
||||
|
||||
return await this.userRepository.update(id_usuario, data);
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNumber, IsString } from 'class-validator';
|
||||
|
||||
export class JwtPayloadDto {
|
||||
@ApiProperty({ example: 1, description: 'ID único del usuario' })
|
||||
@IsNumber()
|
||||
id_usuario: number;
|
||||
|
||||
@ApiProperty({ example: 'juan.perez', description: 'Nombre de usuario' })
|
||||
@IsString()
|
||||
usuario: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 2,
|
||||
description: 'Tipo de usuario (ejemplo: Admin, Estudiante, etc.)',
|
||||
})
|
||||
@IsNumber()
|
||||
id_tipo_usuario: number;
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export class SignInDto {
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
contraseña: string;
|
||||
password: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Indica si se debe recordar la sesión (opcional)',
|
||||
|
||||
@@ -37,10 +37,10 @@ export class SignUpDto {
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Contraseña del usuario',
|
||||
example: 'password123',
|
||||
example: 'password',
|
||||
})
|
||||
@IsString()
|
||||
@MinLength(6)
|
||||
@MinLength(2)
|
||||
@IsNotEmpty()
|
||||
contraseña: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
+13
-2
@@ -3,19 +3,30 @@ import { AppModule } from './app.module';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import * as path from 'path';
|
||||
import * as bodyParser from 'body-parser';
|
||||
import { Logger } from '@nestjs/common';
|
||||
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 ---')
|
||||
Logger.log('--- Cargando Documentacion ---');
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle('Directorio API')
|
||||
.setVersion('1.0')
|
||||
.addTag('Carga de Datos')
|
||||
.addTag('Auth')
|
||||
.addTag('Profesor')
|
||||
.addBearerAuth()
|
||||
.build();
|
||||
const documentFactory = () => SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('docs', app, documentFactory);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||
canActivate(context: ExecutionContext) {
|
||||
return super.canActivate(context);
|
||||
}
|
||||
|
||||
handleRequest(err, user, info) {
|
||||
if (err || !user) {
|
||||
throw new UnauthorizedException('Token inválido o no autorizado');
|
||||
}
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtPayloadDto } from 'src/auth/dto/payload.dto';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
constructor(configService: ConfigService) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: configService.get<string>('JWT_SECRET'),
|
||||
});
|
||||
}
|
||||
|
||||
async validate(payload: JwtPayloadDto) {
|
||||
return payload
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
ExecutionContext,
|
||||
UnauthorizedException,
|
||||
Logger,
|
||||
ForbiddenException,
|
||||
} from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
@@ -20,12 +21,13 @@ export class RolesGuard implements CanActivate {
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
Logger.debug('verify roles');
|
||||
// Obtener los roles requeridos para el endpoint
|
||||
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
|
||||
// Si no se requieren roles, permitir acceso
|
||||
if (!requiredRoles) {
|
||||
return true;
|
||||
}
|
||||
@@ -34,30 +36,51 @@ export class RolesGuard implements CanActivate {
|
||||
const authHeader = request.headers['authorization'];
|
||||
|
||||
if (!authHeader) {
|
||||
throw new UnauthorizedException('No authorization header provided');
|
||||
throw new UnauthorizedException(
|
||||
'No se proporcionó un token de autorización',
|
||||
);
|
||||
}
|
||||
|
||||
// Extraer el token del header
|
||||
const [, token] = authHeader.split(' ');
|
||||
if (!token) {
|
||||
throw new UnauthorizedException('No token provided');
|
||||
throw new UnauthorizedException(
|
||||
'Token de autorización inválido o ausente',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// Verificar el token con la clave secreta
|
||||
const userPayload = await this.jwtService.verifyAsync(token, {
|
||||
secret: this.configService.get<string>('JWT_SECRET'),
|
||||
});
|
||||
|
||||
// Extraer información del usuario autenticado
|
||||
const userRole: Role = userPayload.id_tipo_usuario;
|
||||
const userId: number = userPayload.id_usuario;
|
||||
|
||||
const paramId = parseInt(request.params.id_usuario, 10);
|
||||
if (requiredRoles.includes(userRole) || userId === paramId) {
|
||||
if (!userRole || !userId) {
|
||||
throw new UnauthorizedException(
|
||||
'El token no contiene información válida del usuario',
|
||||
);
|
||||
}
|
||||
|
||||
// Extraer id_usuario de los parámetros de la ruta
|
||||
const paramId = request.params.id_usuario
|
||||
? parseInt(request.params.id_usuario, 10)
|
||||
: null;
|
||||
|
||||
// Comprobar si el usuario tiene permisos por rol o si es dueño del recurso
|
||||
if (requiredRoles.includes(userRole) || (paramId && userId === paramId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
// Si el usuario no cumple los criterios, lanzar error de acceso denegado
|
||||
throw new ForbiddenException(
|
||||
'No tienes permisos para acceder a este recurso',
|
||||
);
|
||||
} catch (error) {
|
||||
throw new UnauthorizedException('Invalid token');
|
||||
throw new UnauthorizedException('Token inválido o expirado');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,113 +9,132 @@ import {
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
|
||||
export class CreateProfesorDto {
|
||||
@ApiProperty({ example: 'Juan Pérez', description: 'Nombre del profesor' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Length(1, 80) // Se asegura de que no sea una cadena vacía
|
||||
@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',
|
||||
description: 'Número de trabajador único',
|
||||
})
|
||||
@IsString()
|
||||
@IsString({ message: 'El número de trabajador debe ser una cadena de texto' })
|
||||
@IsOptional()
|
||||
@Length(1, 10) // Se asegura de que no sea una cadena vacía si se envía
|
||||
@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()
|
||||
@IsString({ message: 'El RFC debe ser una cadena de texto' })
|
||||
@IsOptional()
|
||||
@Length(1, 10)
|
||||
@Length(1, 10, { message: 'El RFC debe tener maximo 10 caracteres' })
|
||||
rfc?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'XYZ', description: 'Homoclave del RFC' })
|
||||
@IsString()
|
||||
@IsString({ message: 'La homoclave debe ser una cadena de texto' })
|
||||
@IsOptional()
|
||||
@Length(1, 3)
|
||||
@Length(1, 3, { message: 'La homoclave debe tener maximo 3 caracteres' })
|
||||
homoclave?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: '5551234567',
|
||||
description: 'Teléfono de oficina',
|
||||
description: 'Teléfono de oficina del profesor',
|
||||
})
|
||||
@IsString()
|
||||
@IsString({ message: 'El teléfono debe ser una cadena de texto' })
|
||||
@IsOptional()
|
||||
@Length(1, 14) // Se corrige de 13 a 14
|
||||
@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',
|
||||
description: 'Extensión del teléfono de oficina',
|
||||
})
|
||||
@IsString()
|
||||
@IsString({ message: 'La extensión debe ser una cadena de texto' })
|
||||
@IsOptional()
|
||||
@Length(1, 10)
|
||||
@Length(1, 10, { message: 'La extensión debe tener entre 1 y 10 caracteres' })
|
||||
extension?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 'correo@escuela.edu.mx',
|
||||
description: 'Correo principal del profesor',
|
||||
description: 'Correo institucional',
|
||||
})
|
||||
@IsString()
|
||||
@IsString({ message: 'El correo debe ser una cadena de texto' })
|
||||
@IsOptional()
|
||||
@Length(1, 65)
|
||||
@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' })
|
||||
@IsInt()
|
||||
@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' })
|
||||
@IsInt()
|
||||
@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' })
|
||||
@IsInt()
|
||||
@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 del profesor',
|
||||
description: 'Ubicación física del profesor',
|
||||
})
|
||||
@IsString()
|
||||
@IsString({ message: 'La ubicación debe ser una cadena de texto' })
|
||||
@IsOptional()
|
||||
@Length(1, 256)
|
||||
ubicacion?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 'https://mi-url.com/foto.jpg',
|
||||
description: 'URL de la fotografía',
|
||||
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',
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Length(1, 150)
|
||||
fotografia?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: true,
|
||||
description: 'Si el profesor está activo o no',
|
||||
description: 'Indica si el profesor está activo',
|
||||
})
|
||||
@IsBoolean({
|
||||
message: 'El estado activo debe ser un valor booleano (true o false)',
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
activo?: boolean;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: false,
|
||||
description: 'Si el profesor debe mostrarse en la lista pública',
|
||||
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)',
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
mostrar?: boolean;
|
||||
}
|
||||
|
||||
/*
|
||||
Se retiro informacion personal
|
||||
|
||||
|
||||
@@ -55,8 +55,8 @@ export class Profesor {
|
||||
|
||||
@Column({
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
nullable: false,
|
||||
})
|
||||
fecha_alta: Date;
|
||||
|
||||
@@ -76,7 +76,7 @@ export class Profesor {
|
||||
/* @Column({ type: 'varchar', length: 65, nullable: true })
|
||||
correo_per: string; */
|
||||
|
||||
/* Relaciones */
|
||||
/* RELACIONES */
|
||||
|
||||
@Column({ type: 'int', nullable: true })
|
||||
id_adscripcion: number;
|
||||
|
||||
@@ -7,9 +7,11 @@ import {
|
||||
Logger,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
Patch,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
Req,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ProfesorService } from './profesor.service';
|
||||
@@ -21,22 +23,36 @@ 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 { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
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')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(RolesGuard)
|
||||
export class ProfesorController {
|
||||
constructor(private profesorService: ProfesorService) {}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Crear un profesor' })
|
||||
//@Roles(Role.Responsable, Role.Admin)
|
||||
async register(@Body() data: CreateProfesorDto) {
|
||||
return this.profesorService.register(data);
|
||||
@Roles(Role.Responsable, Role.Admin)
|
||||
async register(@Body() data: CreateProfesorDto, @Req() req: RequestWithUser) {
|
||||
return this.profesorService.register(data, req.user);
|
||||
}
|
||||
|
||||
@Put(':id_profesor/image')
|
||||
@Patch('/fotografia/:id_profesor')
|
||||
@ApiOperation({ summary: 'Actualizar la fotografía de un profesor' })
|
||||
async updateImage(
|
||||
@Param('id_profesor') id_profesor: number,
|
||||
@Body() updateProfesorImageDto: UpdateProfesorImageDto,
|
||||
@@ -54,27 +70,66 @@ export class ProfesorController {
|
||||
return this.profesorService.update(profesor);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
Logger.debug('findAllProfesors');
|
||||
return this.profesorService.findAll();
|
||||
}
|
||||
|
||||
@Get('page')
|
||||
findAllPaginated(
|
||||
@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: 'id_adscripcion',
|
||||
required: false,
|
||||
example: 2,
|
||||
description: 'Filtrar por ID de adscripción',
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'id_edificio',
|
||||
required: false,
|
||||
example: 3,
|
||||
description: 'Filtrar por ID de edificio',
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'id_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() filters: any,
|
||||
@Query('nombre') nombre?: string,
|
||||
@Query('id_adscripcion') id_adscripcion?: number,
|
||||
@Query('id_edificio') id_edificio?: number,
|
||||
@Query('id_categoria') id_categoria?: number,
|
||||
) {
|
||||
try {
|
||||
Logger.debug('Page Profesors');
|
||||
page = page < 1 ? 1 : page;
|
||||
limit = limit > 12 || limit < 1 ? 12 : limit;
|
||||
return this.profesorService.findAllPaginated(page, limit, filters);
|
||||
} catch (err) {
|
||||
Logger.error('profesor controller ', err.error);
|
||||
return [];
|
||||
}
|
||||
// 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 (id_adscripcion) filters.id_adscripcion = Number(id_adscripcion);
|
||||
if (id_edificio) filters.id_edificio = Number(id_edificio);
|
||||
if (id_categoria) filters.id_categoria = Number(id_categoria);
|
||||
|
||||
return await this.profesorService.findAllPaginated(page, limit, filters);
|
||||
}
|
||||
|
||||
@Put(':id_profesor')
|
||||
@@ -83,23 +138,23 @@ export class ProfesorController {
|
||||
@Param('id_profesor', ParseIntPipe) id_profesor: number,
|
||||
@Body() data: CreateProfesorDto,
|
||||
) {
|
||||
Logger.debug('put Profesors by id');
|
||||
|
||||
return this.profesorService.modify(id_profesor, data);
|
||||
}
|
||||
|
||||
@Delete(':id_profesor')
|
||||
@Roles(Role.Responsable, Role.Admin)
|
||||
async remove(@Param('id_profesor', ParseIntPipe) id_profesor: number) {
|
||||
Logger.debug('delete Profesors by id');
|
||||
|
||||
return this.profesorService.remove(id_profesor);
|
||||
}
|
||||
|
||||
@Get(':id_profesor')
|
||||
async profile(@Param('id_profesor', ParseIntPipe) id_profesor: number) {
|
||||
Logger.debug('get Profesors by id');
|
||||
|
||||
return this.profesorService.profile(id_profesor);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Obtiene todos los profesores' })
|
||||
findAll() {
|
||||
return this.profesorService.findAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { CreateProfesorDto } 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 {
|
||||
@@ -51,79 +52,64 @@ export class ProfesorService {
|
||||
}
|
||||
|
||||
//register teacher
|
||||
async register(data: CreateProfesorDto): Promise<any | undefined> {
|
||||
const { num_trabajador, rfc, ...res } = data;
|
||||
async register(
|
||||
data: CreateProfesorDto,
|
||||
payload: JwtPayloadDto,
|
||||
): Promise<any | undefined> {
|
||||
const { num_trabajador, rfc, homoclave, ...res } = data;
|
||||
|
||||
// 1.- Validar si ya existe este trabajador por su numero de trabajador o rfc, ya que son numeros unicos
|
||||
const profesorSearched = await this.profesorRepository.findOne({
|
||||
where: [{ num_trabajador }, { rfc }],
|
||||
// 1.- Buscar si ya existe un profesor con ese número de trabajador
|
||||
const profesorByNumTrabajador = await this.profesorRepository.findOne({
|
||||
where: { num_trabajador },
|
||||
});
|
||||
|
||||
if (profesorSearched) {
|
||||
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} o RFC ${rfc}.`,
|
||||
404,
|
||||
`Ya existe un profesor con el número de trabajador ${num_trabajador}.`,
|
||||
409,
|
||||
);
|
||||
}
|
||||
|
||||
// 2.- Guardar al profesor
|
||||
// 2.- Buscar si ya existe un profesor con ese RFC
|
||||
const profesorByRFC = await this.profesorRepository.findOne({
|
||||
where: { rfc },
|
||||
});
|
||||
|
||||
const profesorSaved = await this.profesorRepository.save(data)
|
||||
|
||||
console.table(profesorSaved)
|
||||
|
||||
/* const { num_trabajador, rfc, proyectosAcademicos, datosAcademicos, lineasInvestigacion } = data;
|
||||
|
||||
if (await this.profesorRepository.findOne({ where: { rfc } })) {
|
||||
throw new HttpException("rfc already exist", 403);
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
if (await this.profesorRepository.findOne({ where: { num_trabajador } })) {
|
||||
throw new HttpException("number of worker already exist", 403);
|
||||
// 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)
|
||||
);
|
||||
}
|
||||
|
||||
const availableId = await this.findAvailableId();
|
||||
data.id_profesor = availableId;
|
||||
// 5.- Guardar al profesor
|
||||
const createdProfesor = this.profesorRepository.create({
|
||||
id_usuario: payload.id_usuario,
|
||||
...res,
|
||||
num_trabajador,
|
||||
rfc,
|
||||
});
|
||||
|
||||
const savedProfesor = await this.profesorRepository.save(data);
|
||||
const savedProfesor = await this.profesorRepository.save(createdProfesor);
|
||||
|
||||
if (proyectosAcademicos && proyectosAcademicos.length > 0) {
|
||||
const proyectos = proyectosAcademicos.map(proyecto => {
|
||||
const newProyecto = this.proyectosAcademicosRepository.create({
|
||||
...proyecto,
|
||||
profesor: savedProfesor,
|
||||
id_profesor: savedProfesor.id_profesor
|
||||
});
|
||||
return newProyecto;
|
||||
});
|
||||
await this.proyectosAcademicosRepository.save(proyectos);
|
||||
}
|
||||
|
||||
if (lineasInvestigacion && lineasInvestigacion.length > 0) {
|
||||
const lineas = lineasInvestigacion.map(linea => {
|
||||
const newLinea = this.lineasInvestigacionRepository.create({
|
||||
...linea,
|
||||
profesor: savedProfesor,
|
||||
id_profesor: savedProfesor.id_profesor
|
||||
});
|
||||
return newLinea;
|
||||
});
|
||||
await this.lineasInvestigacionRepository.save(lineas);
|
||||
}
|
||||
|
||||
if (datosAcademicos && datosAcademicos.length > 0) {
|
||||
const datos = datosAcademicos.map(datosAcademicos => {
|
||||
const newDato = this.datosAcademicosRepository.create({
|
||||
...datosAcademicos,
|
||||
profesor: savedProfesor,
|
||||
id_profesor: savedProfesor.id_profesor
|
||||
});
|
||||
return newDato;
|
||||
});
|
||||
await this.datosAcademicosRepository.save(datos);
|
||||
}
|
||||
|
||||
return savedProfesor; */
|
||||
return savedProfesor;
|
||||
}
|
||||
|
||||
//brings all teachers
|
||||
@@ -232,48 +218,50 @@ export class ProfesorService {
|
||||
): Promise<{ profesores: Profesor[]; total: number; totalPages: number }> {
|
||||
const query = this.profesorRepository.createQueryBuilder('profesor');
|
||||
|
||||
// 🔹 Filtro por Nombre (LIKE %nombre%)
|
||||
if (filters.nombre) {
|
||||
Logger.debug('fiter by name');
|
||||
|
||||
query.andWhere('profesor.nombre LIKE :nombre', {
|
||||
Logger.debug('Filter by name');
|
||||
query.andWhere('LOWER(profesor.nombre) LIKE LOWER(:nombre)', {
|
||||
nombre: `%${filters.nombre}%`,
|
||||
});
|
||||
}
|
||||
|
||||
if (filters.adscripcion) {
|
||||
Logger.debug('fiter by adscription');
|
||||
|
||||
// 🔹 Filtro por Adscripción
|
||||
if (filters.adscripcion && !isNaN(filters.adscripcion)) {
|
||||
Logger.debug('Filter by adscription');
|
||||
query.andWhere('profesor.adscripcion = :adscripcion', {
|
||||
adscripcion: filters.adscripcion,
|
||||
adscripcion: Number(filters.adscripcion),
|
||||
});
|
||||
}
|
||||
|
||||
if (filters.categoria) {
|
||||
Logger.debug('fiter by category');
|
||||
|
||||
// 🔹 Filtro por Categoría
|
||||
if (filters.categoria && !isNaN(filters.categoria)) {
|
||||
Logger.debug('Filter by category');
|
||||
query.andWhere('profesor.categoria = :categoria', {
|
||||
categoria: filters.categoria,
|
||||
categoria: Number(filters.categoria),
|
||||
});
|
||||
}
|
||||
|
||||
if (filters.edificio) {
|
||||
Logger.debug('fiter by building');
|
||||
|
||||
// 🔹 Filtro por Edificio
|
||||
if (filters.edificio && !isNaN(filters.edificio)) {
|
||||
Logger.debug('Filter by building');
|
||||
query.andWhere('profesor.edificio = :edificio', {
|
||||
edificio: filters.edificio,
|
||||
edificio: Number(filters.edificio),
|
||||
});
|
||||
}
|
||||
|
||||
// 🔹 Filtro por Activo
|
||||
if (typeof filters.activo !== 'undefined') {
|
||||
Logger.debug('fiter by is active');
|
||||
|
||||
query.andWhere('profesor.activo = :activo', { activo: filters.activo });
|
||||
const isActive = filters.activo === 'true' || filters.activo === true;
|
||||
Logger.debug('Filter by active status');
|
||||
query.andWhere('profesor.activo = :activo', { activo: isActive });
|
||||
} else {
|
||||
query.orderBy('profesor.activo', 'DESC');
|
||||
query.orderBy('profesor.activo', 'DESC'); // Si no se especifica, prioriza activos
|
||||
}
|
||||
|
||||
query.addOrderBy('profesor.nombre', 'ASC');
|
||||
|
||||
// 🔹 Ejecutar consulta con paginación
|
||||
const [profesores, total] = await query
|
||||
.skip((page - 1) * limit)
|
||||
.take(limit)
|
||||
|
||||
@@ -21,7 +21,7 @@ export class User {
|
||||
usuario: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 60, nullable: false })
|
||||
contraseña: string;
|
||||
password: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
jwt: string;
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user