fix: align permission navigation and page access

This commit is contained in:
2026-07-11 14:55:49 +08:00
parent 1e1c476bc3
commit b6fca99390
27 changed files with 434 additions and 49 deletions

View File

@@ -56,6 +56,16 @@ export class SchedulesController {
);
}
@Get('lookups')
@RequirePermission('schedule:view')
async getLookups(@Request() req: { user: RequestUser }) {
const classIds = await this.service.getAccessibleClassIds(
req.user.id,
this.canManageAllSchedules(req),
);
return this.service.getLookups(classIds);
}
@Get()
@RequirePermission('schedule:view')
async findAll(@Query() query: QueryScheduleDto, @Request() req: { user: RequestUser }) {

View File

@@ -0,0 +1,31 @@
import { SchedulesService } from './schedules.service';
describe('SchedulesService permission-scoped lookups', () => {
it('returns scoped classes and minimal classrooms for schedule viewers', async () => {
const classRepo = {
find: jest.fn().mockResolvedValue([{ id: 3, name: '三班', code: 'C3' }]),
};
const scheduleRepo = {
createQueryBuilder: jest.fn().mockReturnValue({
select: jest.fn().mockReturnThis(),
addSelect: jest.fn().mockReturnThis(),
innerJoin: jest.fn().mockReturnThis(),
distinct: jest.fn().mockReturnThis(),
orderBy: jest.fn().mockReturnThis(),
addOrderBy: jest.fn().mockReturnThis(),
getRawMany: jest.fn().mockResolvedValue([{ classroomId: 5, classroomName: '教室5', classroomBuilding: 'A' }]),
}),
};
const service = new SchedulesService(
scheduleRepo as never,
classRepo as never,
{} as never,
{} as never,
);
await expect(service.getLookups([3])).resolves.toEqual({
classes: [{ id: 3, name: '三班', code: 'C3' }],
classrooms: [{ id: 5, name: '教室5', building: 'A' }],
});
});
});

View File

@@ -5,7 +5,7 @@ import {
BadRequestException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { In, Repository } from 'typeorm';
import { ClassSchedule, Class, ClassroomRental, ClassTeacher } from '../entities';
import {
@@ -33,6 +33,41 @@ export class SchedulesService {
return [...new Set(assignments.map((assignment) => assignment.classId))];
}
async getLookups(accessibleClassIds?: number[]) {
const classes = accessibleClassIds
? accessibleClassIds.length > 0
? await this.classRepo.find({
where: { id: In(accessibleClassIds) },
select: ['id', 'name', 'code'],
order: { name: 'ASC' },
})
: []
: await this.classRepo.find({
select: ['id', 'name', 'code'],
order: { name: 'ASC' },
});
const classroomRows = await this.scheduleRepo
.createQueryBuilder('schedule')
.select('classroom.id', 'classroomId')
.addSelect('classroom.name', 'classroomName')
.addSelect('classroom.building', 'classroomBuilding')
.innerJoin('schedule.classroom', 'classroom')
.distinct(true)
.orderBy('classroom.building', 'ASC')
.addOrderBy('classroom.name', 'ASC')
.getRawMany();
return {
classes,
classrooms: classroomRows.map((row) => ({
id: Number(row.classroomId),
name: String(row.classroomName ?? ''),
building: String(row.classroomBuilding ?? ''),
})),
};
}
async findAll(query: QueryScheduleDto, accessibleClassIds?: number[]) {
const qb = this.scheduleRepo.createQueryBuilder('cs');