Files
gongxue-base/apps/server/src/rbac/rbac.service.ts

264 lines
8.2 KiB
TypeScript

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<Permission>,
@InjectRepository(Role) private roleRepo: Repository<Role>,
@InjectRepository(User) private userRepo: Repository<User>,
@InjectRepository(Class) private classRepo: Repository<Class>,
@InjectRepository(ClassStudent) private classStudentRepo: Repository<ClassStudent>,
@InjectRepository(ClassTeacher) private classTeacherRepo: Repository<ClassTeacher>,
@InjectRepository(ClassSchedule) private classScheduleRepo: Repository<ClassSchedule>,
@InjectRepository(Student) private studentRepo: Repository<Student>,
@InjectRepository(AttendanceSession)
private attendanceSessionRepo: Repository<AttendanceSession>,
@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<Role[]> {
return this.roleRepo.find({
relations: ['permissions'],
order: { id: 'ASC' },
});
}
async findRoleById(id: number): Promise<Role> {
return this.roleRepo.findOneOrFail({ where: { id }, relations: ['permissions'] });
}
async createRole(dto: {
name: string;
description?: string;
permissionIds?: number[];
}): Promise<Role> {
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<Role> {
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<Permission[]> {
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<string, Permission[]>();
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<string[]> {
const user = await this.userRepo.findOne({
where: { id: userId },
relations: ['roles', 'roles.permissions'],
});
if (!user || !user.roles) return [];
const codes = new Set<string>();
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<void> {
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);
}
}