import { Injectable, Logger, Optional } from '@nestjs/common'; import type { UpdateProfileDto } from './dto/rbac.dto'; import { RbacSeedService } from './rbac-seed.service'; import { RbacUserService } from './rbac-user.service'; import { getChinaDateParts } from './rbac-presets'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, In } from 'typeorm'; import { Permission, Role, User, Class, ClassStudent, ClassTeacher, ClassSchedule, Student, AttendanceSession, } from '../entities'; @Injectable() export class RbacService { private readonly logger = new Logger(RbacService.name); constructor( @InjectRepository(Permission) private permRepo: Repository, @InjectRepository(Role) private roleRepo: Repository, @InjectRepository(User) private userRepo: Repository, @InjectRepository(Class) private classRepo: Repository, @InjectRepository(ClassStudent) private classStudentRepo: Repository, @InjectRepository(ClassTeacher) private classTeacherRepo: Repository, @InjectRepository(ClassSchedule) private classScheduleRepo: Repository, @InjectRepository(Student) private studentRepo: Repository, @InjectRepository(AttendanceSession) private attendanceSessionRepo: Repository, @Optional() private seedService?: RbacSeedService, @Optional() private userService?: RbacUserService, ) {} private get seedOps(): RbacSeedService { if (!this.seedService) { this.seedService = new RbacSeedService(this.permRepo, this.roleRepo, this.userRepo); } return this.seedService; } private get userOps(): RbacUserService { if (!this.userService) { this.userService = new RbacUserService( this.permRepo, this.roleRepo, this.userRepo, this.classRepo, this.classStudentRepo, this.classTeacherRepo, this.classScheduleRepo, this.studentRepo, this.attendanceSessionRepo, ); } return this.userService; } async findAllRoles(): Promise { return this.roleRepo.find({ relations: ['permissions'], order: { id: 'ASC' }, }); } async findRoleById(id: number): Promise { return this.roleRepo.findOneOrFail({ where: { id }, relations: ['permissions'] }); } async createRole(dto: { name: string; description?: string; permissionIds?: number[]; }): Promise { const role = this.roleRepo.create({ name: dto.name, description: dto.description }); if (dto.permissionIds && dto.permissionIds.length > 0) { role.permissions = await this.userOps.resolvePermissions(dto.permissionIds); } return this.roleRepo.save(role); } async updateRole( id: number, dto: { name?: string; description?: string; permissionIds?: number[] }, ): Promise { const role = await this.roleRepo.findOneOrFail({ where: { id }, relations: ['permissions'], }); if (dto.name !== undefined && !role.isSystem) role.name = dto.name; if (dto.description !== undefined) role.description = dto.description; if (dto.permissionIds !== undefined) { role.permissions = dto.permissionIds.length > 0 ? await this.userOps.resolvePermissions(dto.permissionIds) : []; } return this.roleRepo.save(role); } async deleteRole(id: number): Promise<{ message: string }> { const role = await this.roleRepo.findOneOrFail({ where: { id } }); if (role.isSystem) throw new Error('系统角色不可停用'); if (role.status === 0) return { message: '角色已停用' }; role.status = 0; await this.roleRepo.save(role); return { message: '角色已停用' }; } async findAllPermissions(): Promise { return this.permRepo.find({ order: { group: 'ASC', code: 'ASC' } }); } async getPermissionTree(): Promise<{ group: string; permissions: Permission[] }[]> { const all = await this.findAllPermissions(); const map = new Map(); for (const p of all) { if (!map.has(p.group)) map.set(p.group, []); map.get(p.group)!.push(p); } return Array.from(map.entries()).map(([group, permissions]) => ({ group, permissions })); } async getUserPermissions(userId: number): Promise { const user = await this.userRepo.findOne({ where: { id: userId }, relations: ['roles', 'roles.permissions'], }); if (!user || !user.roles) return []; const codes = new Set(); for (const role of user.roles) { if (role.status !== 1) continue; for (const perm of role.permissions) { codes.add(perm.code); } } return Array.from(codes); } async getTeacherWorkspace(userId: number) { // Find all classes where this user is a teacher const teacherAssignments = await this.classTeacherRepo.find({ where: { userId }, relations: ['class'], }); const classIds = [...new Set(teacherAssignments.map((t) => t.classId))]; if (classIds.length === 0) { return { assignedClasses: [], todaySchedules: [], myStudents: [] }; } const assignedClasses = teacherAssignments.map((t) => ({ classId: t.classId, className: t.class?.name || '', classCode: t.class?.code || '', roleType: t.roleType, subject: t.subject, })); // Get today's China business date and day of week (1=Monday, 7=Sunday) const { date: todayStr, weekDay: adjustedWeekDay } = getChinaDateParts(); const todaySchedules = await this.classScheduleRepo .createQueryBuilder('cs') .where('cs.classId IN (:...classIds)', { classIds }) .andWhere('cs.weekDay = :weekDay', { weekDay: adjustedWeekDay }) .andWhere('cs.startDate <= :today', { today: todayStr }) .andWhere('cs.endDate >= :today', { today: todayStr }) .andWhere('cs.status = :status', { status: 'active' }) .orderBy('cs.startTime', 'ASC') .getMany(); const classStudents = await this.classStudentRepo.find({ where: { classId: In(classIds), status: 'active' }, relations: ['student', 'class'], }); const myStudents = classStudents.map((cs) => ({ studentId: cs.studentId, studentName: cs.student?.name || '', studentNo: cs.student?.studentNo || '', className: cs.class?.name || '', classId: cs.classId, joinDate: cs.joinDate, })); return { assignedClasses, todaySchedules: todaySchedules.map((s) => ({ id: s.id, classId: s.classId, classroomId: s.classroomId, teacherId: s.teacherId, weekDay: s.weekDay, startTime: s.startTime, endTime: s.endTime, subject: s.subject, scheduleType: s.scheduleType, })), myStudents, }; } async seedData(): Promise { return this.seedOps.seedData(); } async findAllUsers(isArchived = false) { return this.userOps.findAllUsers(isArchived); } async createUser(dto: { username: string; password: string; name: string; roleIds?: number[] }) { return this.userOps.createUser(dto); } async updateUser(id: number, dto: { username?: string; name?: string; roleIds?: number[] }) { return this.userOps.updateUser(id, dto); } async resetPassword(id: number, newPassword: string) { return this.userOps.resetPassword(id, newPassword); } async archiveUser(id: number) { return this.userOps.archiveUser(id); } async restoreUser(id: number) { return this.userOps.restoreUser(id); } async purgeUser(id: number, currentUserId: number) { return this.userOps.purgeUser(id, currentUserId); } async markAsStaff(userId: number) { return this.userOps.markAsStaff(userId); } async markAsStudent(userId: number) { return this.userOps.markAsStudent(userId); } async getUserProfile(id: number) { return this.userOps.getUserProfile(id); } async updateUserProfile(id: number, dto: UpdateProfileDto) { return this.userOps.updateUserProfile(id, dto); } async getTeachers(query?: { search?: string; page?: number; pageSize?: number }) { return this.userOps.getTeachers(query); } async updateTeacherProfile(id: number, dto: UpdateProfileDto) { return this.userOps.updateTeacherProfile(id, dto); } }