fix(server): 路由 id 使用 ParseIntPipe,限制分页与批量数组上限

This commit is contained in:
2026-08-07 16:38:03 +08:00
parent 899d2dde5b
commit 5b5ffb5b9e
14 changed files with 96 additions and 58 deletions

View File

@@ -131,8 +131,8 @@ export class AttendanceQueryService {
}, },
accessibleClassIds?: number[], accessibleClassIds?: number[],
) { ) {
const page = query.page || 1; const page = Math.max(1, Math.floor(Number(query.page) || 1));
const pageSize = query.pageSize || 20; const pageSize = Math.min(200, Math.max(1, Math.floor(Number(query.pageSize) || 20)));
const qb = this.attendanceRepo.createQueryBuilder('ar'); const qb = this.attendanceRepo.createQueryBuilder('ar');
@@ -233,8 +233,8 @@ export class AttendanceQueryService {
// ── DingAttendance raw records ── // ── DingAttendance raw records ──
async getDingRaw(query: QueryDingRawDto, accessibleClassIds?: number[]) { async getDingRaw(query: QueryDingRawDto, accessibleClassIds?: number[]) {
const page = query.page || 1; const page = Math.max(1, Math.floor(Number(query.page) || 1));
const pageSize = query.pageSize || 20; const pageSize = Math.min(200, Math.max(1, Math.floor(Number(query.pageSize) || 20)));
const qb = this.dingRawRepo.createQueryBuilder('ar'); const qb = this.dingRawRepo.createQueryBuilder('ar');
qb.leftJoinAndSelect('ar.matchedStudent', 'matchedStudent'); qb.leftJoinAndSelect('ar.matchedStudent', 'matchedStudent');

View File

@@ -6,6 +6,7 @@ import {
Delete, Delete,
Body, Body,
Param, Param,
ParseIntPipe,
Query, Query,
UseGuards, UseGuards,
UsePipes, UsePipes,
@@ -85,7 +86,7 @@ export class ClassesController {
@Get(':id') @Get(':id')
@RequirePermission('class:view') @RequirePermission('class:view')
async findOne(@Param('id') id: string, @Request() req: AuthenticatedRequest) { async findOne(@Param('id', ParseIntPipe) id: number, @Request() req: AuthenticatedRequest) {
await this.assertReadAccess(req, +id); await this.assertReadAccess(req, +id);
return this.service.findOne(+id); return this.service.findOne(+id);
} }
@@ -93,7 +94,7 @@ export class ClassesController {
@Get(':id/schedule') @Get(':id/schedule')
@RequirePermission('class:view') @RequirePermission('class:view')
async getSchedule( async getSchedule(
@Param('id') id: string, @Param('id', ParseIntPipe) id: number,
@Query() query: QueryClassScheduleDto, @Query() query: QueryClassScheduleDto,
@Request() req: AuthenticatedRequest, @Request() req: AuthenticatedRequest,
) { ) {
@@ -104,7 +105,7 @@ export class ClassesController {
@Get(':id/attendance-summary') @Get(':id/attendance-summary')
@RequirePermission('class:view') @RequirePermission('class:view')
async getAttendanceSummary( async getAttendanceSummary(
@Param('id') id: string, @Param('id', ParseIntPipe) id: number,
@Query() query: QueryClassAttendanceSummaryDto, @Query() query: QueryClassAttendanceSummaryDto,
@Request() req: AuthenticatedRequest, @Request() req: AuthenticatedRequest,
) { ) {
@@ -125,27 +126,27 @@ export class ClassesController {
/** 批量导入学生到班级通过钉钉用户ID */ /** 批量导入学生到班级通过钉钉用户ID */
@Post(':id/students/import') @Post(':id/students/import')
@RequirePermission('class:edit') @RequirePermission('class:edit')
async batchImportStudents(@Param('id') id: string, @Body() dto: BatchImportStudentsDto) { async batchImportStudents(@Param('id', ParseIntPipe) id: number, @Body() dto: BatchImportStudentsDto) {
return this.service.batchImportStudents(+id, dto.users); return this.service.batchImportStudents(+id, dto.users);
} }
/** 归档班级 */ /** 归档班级 */
@Put(':id/archive') @Put(':id/archive')
@RequirePermission('class:edit') @RequirePermission('class:edit')
async archive(@Param('id') id: string) { async archive(@Param('id', ParseIntPipe) id: number) {
return this.service.archive(+id); return this.service.archive(+id);
} }
/** 取消归档 */ /** 取消归档 */
@Put(':id/restore') @Put(':id/restore')
@RequirePermission('class:edit') @RequirePermission('class:edit')
async restore(@Param('id') id: string) { async restore(@Param('id', ParseIntPipe) id: number) {
return this.service.restore(+id); return this.service.restore(+id);
} }
@Put(':id') @Put(':id')
@RequirePermission('class:edit') @RequirePermission('class:edit')
async update(@Param('id') id: string, @Body() dto: UpdateClassDto, @Request() req: any) { async update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateClassDto, @Request() req: any) {
const result = await this.service.update(+id, dto); const result = await this.service.update(+id, dto);
await logAudit(this.logService, req, { await logAudit(this.logService, req, {
module: '班级管理', action: '编辑班级', targetId: +id, targetType: 'class', detail: JSON.stringify(dto), module: '班级管理', action: '编辑班级', targetId: +id, targetType: 'class', detail: JSON.stringify(dto),
@@ -155,7 +156,7 @@ export class ClassesController {
@Delete(':id') @Delete(':id')
@RequirePermission('class:delete') @RequirePermission('class:delete')
async remove(@Param('id') id: string, @Request() req: any) { async remove(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
const result = await this.service.remove(+id); const result = await this.service.remove(+id);
await logAudit(this.logService, req, { await logAudit(this.logService, req, {
module: '班级管理', action: '归档班级', targetId: +id, targetType: 'class', module: '班级管理', action: '归档班级', targetId: +id, targetType: 'class',
@@ -165,7 +166,7 @@ export class ClassesController {
@Delete(':id/permanent') @Delete(':id/permanent')
@RequirePermission('class:purge') @RequirePermission('class:purge')
async purge(@Param('id') id: string, @Request() req: any) { async purge(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
const result = await this.service.purge(+id); const result = await this.service.purge(+id);
await logAudit(this.logService, req, { await logAudit(this.logService, req, {
module: '班级管理', action: '永久删除班级', targetId: +id, targetType: 'class', detail: '物理删除,不可恢复', module: '班级管理', action: '永久删除班级', targetId: +id, targetType: 'class', detail: '物理删除,不可恢复',
@@ -176,7 +177,7 @@ export class ClassesController {
@Get(':id/roster/export') @Get(':id/roster/export')
@RequirePermission('class:view') @RequirePermission('class:view')
async exportRoster( async exportRoster(
@Param('id') id: string, @Param('id', ParseIntPipe) id: number,
@Res() res: Response, @Res() res: Response,
@Request() req: AuthenticatedRequest, @Request() req: AuthenticatedRequest,
) { ) {
@@ -218,14 +219,14 @@ export class ClassesController {
@Get(':id/students') @Get(':id/students')
@RequirePermission('class:view') @RequirePermission('class:view')
async getStudents(@Param('id') id: string, @Request() req: AuthenticatedRequest) { async getStudents(@Param('id', ParseIntPipe) id: number, @Request() req: AuthenticatedRequest) {
await this.assertReadAccess(req, +id); await this.assertReadAccess(req, +id);
return this.service.getStudents(+id); return this.service.getStudents(+id);
} }
@Post(':id/students') @Post(':id/students')
@RequirePermission('class:edit') @RequirePermission('class:edit')
async addStudents(@Param('id') id: string, @Body() dto: AddStudentsDto, @Request() req: any) { async addStudents(@Param('id', ParseIntPipe) id: number, @Body() dto: AddStudentsDto, @Request() req: any) {
const result = await this.service.addStudents(+id, dto.studentIds); const result = await this.service.addStudents(+id, dto.studentIds);
await logAudit(this.logService, req, { await logAudit(this.logService, req, {
module: '班级管理', action: '添加学生', targetId: +id, targetType: 'class', detail: `新增${result.added}名学生`, module: '班级管理', action: '添加学生', targetId: +id, targetType: 'class', detail: `新增${result.added}名学生`,
@@ -249,8 +250,8 @@ export class ClassesController {
@Delete(':id/students/:studentId') @Delete(':id/students/:studentId')
@RequirePermission('class:edit') @RequirePermission('class:edit')
async removeStudent( async removeStudent(
@Param('id') id: string, @Param('id', ParseIntPipe) id: number,
@Param('studentId') studentId: string, @Param('studentId', ParseIntPipe) studentId: number,
@Request() req: any, @Request() req: any,
) { ) {
const result = await this.service.removeStudent(+id, +studentId); const result = await this.service.removeStudent(+id, +studentId);
@@ -262,14 +263,14 @@ export class ClassesController {
@Get(':id/teachers') @Get(':id/teachers')
@RequirePermission('class:view') @RequirePermission('class:view')
async getTeachers(@Param('id') id: string, @Request() req: AuthenticatedRequest) { async getTeachers(@Param('id', ParseIntPipe) id: number, @Request() req: AuthenticatedRequest) {
await this.assertReadAccess(req, +id); await this.assertReadAccess(req, +id);
return this.service.getTeachers(+id); return this.service.getTeachers(+id);
} }
@Post(':id/teachers') @Post(':id/teachers')
@RequirePermission('class:edit') @RequirePermission('class:edit')
async addTeacher(@Param('id') id: string, @Body() dto: AddTeacherDto, @Request() req: any) { async addTeacher(@Param('id', ParseIntPipe) id: number, @Body() dto: AddTeacherDto, @Request() req: any) {
const result = await this.service.addTeacher(+id, dto); const result = await this.service.addTeacher(+id, dto);
await logAudit(this.logService, req, { await logAudit(this.logService, req, {
module: '班级管理', action: '添加教师', targetId: +id, targetType: 'class', detail: `教师${dto.userId} 角色${dto.roleType}`, module: '班级管理', action: '添加教师', targetId: +id, targetType: 'class', detail: `教师${dto.userId} 角色${dto.roleType}`,
@@ -290,8 +291,8 @@ export class ClassesController {
@Delete(':id/teacher-assignments/:assignmentId') @Delete(':id/teacher-assignments/:assignmentId')
@RequirePermission('class:edit') @RequirePermission('class:edit')
async removeTeacherAssignment( async removeTeacherAssignment(
@Param('id') id: string, @Param('id', ParseIntPipe) id: number,
@Param('assignmentId') assignmentId: string, @Param('assignmentId', ParseIntPipe) assignmentId: number,
@Request() req: any, @Request() req: any,
) { ) {
const result = await this.service.removeTeacherAssignment(+id, +assignmentId); const result = await this.service.removeTeacherAssignment(+id, +assignmentId);
@@ -304,8 +305,8 @@ export class ClassesController {
@Delete(':id/teachers/:userId') @Delete(':id/teachers/:userId')
@RequirePermission('class:edit') @RequirePermission('class:edit')
async removeTeacher( async removeTeacher(
@Param('id') id: string, @Param('id', ParseIntPipe) id: number,
@Param('userId') userId: string, @Param('userId', ParseIntPipe) userId: number,
@Request() req: any, @Request() req: any,
) { ) {
const result = await this.service.removeTeacher(+id, +userId); const result = await this.service.removeTeacher(+id, +userId);

View File

@@ -6,6 +6,7 @@ import {
Delete, Delete,
Body, Body,
Param, Param,
ParseIntPipe,
Query, Query,
UseGuards, UseGuards,
Request, Request,
@@ -95,7 +96,7 @@ export class ClassroomRentalsController {
@Get(':id') @Get(':id')
@RequirePermission('rental:view') @RequirePermission('rental:view')
findOne(@Param('id') id: string) { findOne(@Param('id', ParseIntPipe) id: number) {
return this.service.findOne(+id); return this.service.findOne(+id);
} }
@@ -111,7 +112,7 @@ export class ClassroomRentalsController {
@Put(':id') @Put(':id')
@RequirePermission('rental:edit') @RequirePermission('rental:edit')
async update(@Param('id') id: string, @Body() dto: UpdateRentalDto, @Request() req: any) { async update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateRentalDto, @Request() req: any) {
const result = await this.service.update(+id, dto); const result = await this.service.update(+id, dto);
await logAudit(this.logService, req, { await logAudit(this.logService, req, {
module: '教室租赁', action: '编辑租赁', targetId: +id, targetType: 'classroom-rental', detail: JSON.stringify(dto), module: '教室租赁', action: '编辑租赁', targetId: +id, targetType: 'classroom-rental', detail: JSON.stringify(dto),
@@ -121,7 +122,7 @@ export class ClassroomRentalsController {
@Put(':id/cancel') @Put(':id/cancel')
@RequirePermission('rental:edit') @RequirePermission('rental:edit')
async cancel(@Param('id') id: string, @Request() req: any) { async cancel(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
const result = await this.service.cancel(+id); const result = await this.service.cancel(+id);
await logAudit(this.logService, req, { await logAudit(this.logService, req, {
module: '教室租赁', action: '取消租赁', targetId: +id, targetType: 'classroom-rental', module: '教室租赁', action: '取消租赁', targetId: +id, targetType: 'classroom-rental',
@@ -131,7 +132,7 @@ export class ClassroomRentalsController {
@Put(':id/end') @Put(':id/end')
@RequirePermission('rental:edit') @RequirePermission('rental:edit')
async end(@Param('id') id: string, @Request() req: any) { async end(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
const result = await this.service.end(+id); const result = await this.service.end(+id);
await logAudit(this.logService, req, { await logAudit(this.logService, req, {
module: '教室租赁', action: '结束租赁', targetId: +id, targetType: 'classroom-rental', module: '教室租赁', action: '结束租赁', targetId: +id, targetType: 'classroom-rental',
@@ -141,7 +142,7 @@ export class ClassroomRentalsController {
@Delete(':id') @Delete(':id')
@RequirePermission('rental:delete') @RequirePermission('rental:delete')
async remove(@Param('id') id: string, @Request() req: any) { async remove(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
const result = await this.service.remove(+id); const result = await this.service.remove(+id);
await logAudit(this.logService, req, { await logAudit(this.logService, req, {
module: '教室租赁', action: '归档租赁', targetId: +id, targetType: 'classroom-rental', module: '教室租赁', action: '归档租赁', targetId: +id, targetType: 'classroom-rental',
@@ -151,7 +152,7 @@ export class ClassroomRentalsController {
@Delete(':id/permanent') @Delete(':id/permanent')
@RequirePermission('rental:purge') @RequirePermission('rental:purge')
async purge(@Param('id') id: string, @Request() req: any) { async purge(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
const result = await this.service.purge(+id); const result = await this.service.purge(+id);
await logAudit(this.logService, req, { await logAudit(this.logService, req, {
module: '教室租赁', action: '永久删除租赁订单', targetId: +id, targetType: 'classroom-rental', detail: '物理删除,不可恢复', module: '教室租赁', action: '永久删除租赁订单', targetId: +id, targetType: 'classroom-rental', detail: '物理删除,不可恢复',
@@ -174,7 +175,7 @@ export class ClassroomRentalsController {
}), }),
) )
async uploadContract( async uploadContract(
@Param('id') id: string, @Param('id', ParseIntPipe) id: number,
@UploadedFile() file: Express.Multer.File, @UploadedFile() file: Express.Multer.File,
@Request() req: any, @Request() req: any,
) { ) {
@@ -188,7 +189,7 @@ export class ClassroomRentalsController {
@Get(':id/contract') @Get(':id/contract')
@RequirePermission('rental:view') @RequirePermission('rental:view')
async downloadContract(@Param('id') id: string, @Res() res: Response) { async downloadContract(@Param('id', ParseIntPipe) id: number, @Res() res: Response) {
const { fullPath, originalName } = await this.service.getContractPath(+id); const { fullPath, originalName } = await this.service.getContractPath(+id);
res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Type', 'application/pdf');
res.setHeader( res.setHeader(
@@ -201,7 +202,7 @@ export class ClassroomRentalsController {
@Delete(':id/contract') @Delete(':id/contract')
@RequirePermission('rental:edit') @RequirePermission('rental:edit')
async deleteContract(@Param('id') id: string, @Request() req: any) { async deleteContract(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
const result = await this.service.removeContract(+id); const result = await this.service.removeContract(+id);
await logAudit(this.logService, req, { await logAudit(this.logService, req, {
module: '教室租赁', action: '移除合同', targetId: +id, targetType: 'classroom-rental', module: '教室租赁', action: '移除合同', targetId: +id, targetType: 'classroom-rental',

View File

@@ -17,4 +17,11 @@ describe('BatchIdsDto', () => {
const dto = Object.assign(new BatchIdsDto(), { ids: [1, 1, 2] }); const dto = Object.assign(new BatchIdsDto(), { ids: [1, 1, 2] });
await expect(validate(dto)).resolves.toHaveLength(0); await expect(validate(dto)).resolves.toHaveLength(0);
}); });
it('rejects batches with more than 500 ids', async () => {
const dto = Object.assign(new BatchIdsDto(), {
ids: Array.from({ length: 501 }, (_, i) => i + 1),
});
await expect(validate(dto)).resolves.not.toHaveLength(0);
});
}); });

View File

@@ -1,8 +1,9 @@
import { ArrayNotEmpty, IsArray, IsInt, Min } from 'class-validator'; import { ArrayMaxSize, ArrayNotEmpty, IsArray, IsInt, Min } from 'class-validator';
export class BatchIdsDto { export class BatchIdsDto {
@IsArray() @IsArray()
@ArrayNotEmpty() @ArrayNotEmpty()
@ArrayMaxSize(500)
@IsInt({ each: true }) @IsInt({ each: true })
@Min(1, { each: true }) @Min(1, { each: true })
ids: number[]; ids: number[];

View File

@@ -1,4 +1,4 @@
import { Controller, Get, Post, Put, Delete, Body, Param, UseGuards, Request } from '@nestjs/common'; import { Controller, Get, Post, Put, Delete, Body, Param, ParseIntPipe, UseGuards, Request } from '@nestjs/common';
import { ExpenseTypesService } from './expense-types.service'; import { ExpenseTypesService } from './expense-types.service';
import { CreateExpenseTypeDto, UpdateExpenseTypeDto } from './dto/expense-type.dto'; import { CreateExpenseTypeDto, UpdateExpenseTypeDto } from './dto/expense-type.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
@@ -47,7 +47,7 @@ export class ExpenseTypesController {
@Put(':id') @Put(':id')
@RequirePermission('expense:edit') @RequirePermission('expense:edit')
async update(@Param('id') id: string, @Body() dto: UpdateExpenseTypeDto, @Request() req: any) { async update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateExpenseTypeDto, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req); const { ipAddress, userAgent } = extractRequestInfo(req);
const result = await this.service.update(+id, dto); const result = await this.service.update(+id, dto);
await this.logService.log({ await this.logService.log({
@@ -66,7 +66,7 @@ export class ExpenseTypesController {
@Delete(':id') @Delete(':id')
@RequirePermission('expense:delete') @RequirePermission('expense:delete')
async remove(@Param('id') id: string, @Request() req: any) { async remove(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req); const { ipAddress, userAgent } = extractRequestInfo(req);
await this.service.remove(+id); await this.service.remove(+id);
await this.logService.log({ await this.logService.log({

View File

@@ -28,4 +28,17 @@ describe('BatchRoomExpenseDto boundaries', () => {
}); });
await expect(validate(dto)).resolves.toHaveLength(0); await expect(validate(dto)).resolves.toHaveLength(0);
}); });
it('rejects a batch with more than 500 items', async () => {
const dto = plainToInstance(BatchRoomExpenseDto, {
periodStart: '2026-07-01',
periodEnd: '2026-07-31',
expenses: Array.from({ length: 501 }, () => ({
roomId: 1,
expenseType: 'water',
amount: 10,
})),
});
await expect(validate(dto)).resolves.not.toHaveLength(0);
});
}); });

View File

@@ -1,4 +1,4 @@
import { ArrayNotEmpty, IsArray, IsDateString, IsIn, IsInt, IsISO8601, IsString, IsNumber, IsOptional, Matches, Min, ValidateNested } from 'class-validator'; import { ArrayMaxSize, ArrayNotEmpty, IsArray, IsDateString, IsIn, IsInt, IsISO8601, IsString, IsNumber, IsOptional, Matches, Min, ValidateNested } from 'class-validator';
import { PartialType } from '@nestjs/mapped-types'; import { PartialType } from '@nestjs/mapped-types';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
@@ -117,6 +117,7 @@ export class BatchRoomExpenseDto {
@IsArray() @IsArray()
@ArrayNotEmpty() @ArrayNotEmpty()
@ArrayMaxSize(500)
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@Type(() => BatchRoomExpenseItemDto) @Type(() => BatchRoomExpenseItemDto)
expenses: BatchRoomExpenseItemDto[]; expenses: BatchRoomExpenseItemDto[];

View File

@@ -32,4 +32,13 @@ describe('notification DTO boundaries', () => {
expect(await validate(dto)).not.toEqual([]); expect(await validate(dto)).not.toEqual([]);
} }
}); });
it('rejects more than 500 recipients', async () => {
const dto = plainToInstance(CreateNotificationDto, {
recipientIds: Array.from({ length: 501 }, (_, i) => i + 1),
type: 'test',
title: '标题',
});
await expect(validate(dto)).resolves.not.toEqual([]);
});
}); });

View File

@@ -1,5 +1,6 @@
import { import {
ArrayNotEmpty, ArrayNotEmpty,
ArrayMaxSize,
IsString, IsString,
IsNotEmpty, IsNotEmpty,
IsOptional, IsOptional,
@@ -13,6 +14,7 @@ import { Type } from 'class-transformer';
export class CreateNotificationDto { export class CreateNotificationDto {
@IsArray() @IsArray()
@ArrayNotEmpty() @ArrayNotEmpty()
@ArrayMaxSize(500)
@IsInt({ each: true }) @IsInt({ each: true })
recipientIds: number[]; recipientIds: number[];

View File

@@ -3,6 +3,7 @@ import {
Get, Get,
Put, Put,
Param, Param,
ParseIntPipe,
Query, Query,
Req, Req,
Sse, Sse,
@@ -74,7 +75,7 @@ export class NotificationsController {
@Put(':id/read') @Put(':id/read')
async markRead( async markRead(
@Param('id') id: string, @Param('id', ParseIntPipe) id: number,
@Req() req: AuthenticatedRequest, @Req() req: AuthenticatedRequest,
) { ) {
await this.service.markRead(+id, req.user.id); await this.service.markRead(+id, req.user.id);

View File

@@ -53,8 +53,8 @@ export class OperationLogsService {
if (query?.endDate) if (query?.endDate)
qb.andWhere('log.createdAt <= :endDate', { endDate: query.endDate + ' 23:59:59' }); qb.andWhere('log.createdAt <= :endDate', { endDate: query.endDate + ' 23:59:59' });
const page = query?.page || 1; const page = Math.max(1, Math.floor(Number(query?.page) || 1));
const pageSize = query?.pageSize || 50; const pageSize = Math.min(200, Math.max(1, Math.floor(Number(query?.pageSize) || 50)));
const [data, total] = await qb const [data, total] = await qb
.skip((page - 1) * pageSize) .skip((page - 1) * pageSize)
.take(pageSize) .take(pageSize)

View File

@@ -6,6 +6,7 @@ import {
Delete, Delete,
Body, Body,
Param, Param,
ParseIntPipe,
Query, Query,
UseGuards, UseGuards,
Request, Request,
@@ -42,7 +43,7 @@ export class OrganizationsController {
@Get(':id') @Get(':id')
@RequirePermission('organization:view') @RequirePermission('organization:view')
findOne(@Param('id') id: string) { findOne(@Param('id', ParseIntPipe) id: number) {
return this.service.findOne(+id); return this.service.findOne(+id);
} }
@@ -67,7 +68,7 @@ export class OrganizationsController {
@Put(':id') @Put(':id')
@RequirePermission('organization:edit') @RequirePermission('organization:edit')
async update(@Param('id') id: string, @Body() dto: UpdateOrganizationDto, @Request() req: any) { async update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateOrganizationDto, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req); const { ipAddress, userAgent } = extractRequestInfo(req);
const result = await this.service.update(+id, dto); const result = await this.service.update(+id, dto);
await this.logService.log({ await this.logService.log({
@@ -86,7 +87,7 @@ export class OrganizationsController {
@Delete(':id') @Delete(':id')
@RequirePermission('organization:delete') @RequirePermission('organization:delete')
async remove(@Param('id') id: string, @Request() req: any) { async remove(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req); const { ipAddress, userAgent } = extractRequestInfo(req);
const result = await this.service.remove(+id); const result = await this.service.remove(+id);
await this.logService.log({ await this.logService.log({
@@ -104,7 +105,7 @@ export class OrganizationsController {
@Delete(':id/permanent') @Delete(':id/permanent')
@RequirePermission('organization:purge') @RequirePermission('organization:purge')
async purge(@Param('id') id: string, @Request() req: any) { async purge(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req); const { ipAddress, userAgent } = extractRequestInfo(req);
const result = await this.service.purge(+id); const result = await this.service.purge(+id);
await this.logService.log({ await this.logService.log({

View File

@@ -10,6 +10,7 @@ import {
UseGuards, UseGuards,
Request, Request,
BadRequestException, BadRequestException,
ParseIntPipe,
} from '@nestjs/common'; } from '@nestjs/common';
import { RbacService } from './rbac.service'; import { RbacService } from './rbac.service';
import { import {
@@ -41,7 +42,7 @@ export class RbacController {
@Get('roles/:id') @Get('roles/:id')
@RequirePermission('role:view') @RequirePermission('role:view')
findRoleById(@Param('id') id: string) { findRoleById(@Param('id', ParseIntPipe) id: number) {
return this.rbacService.findRoleById(+id); return this.rbacService.findRoleById(+id);
} }
@@ -57,7 +58,7 @@ export class RbacController {
@Put('roles/:id') @Put('roles/:id')
@RequirePermission('role:edit') @RequirePermission('role:edit')
async updateRole(@Param('id') id: string, @Body() dto: UpdateRoleDto, @Request() req: any) { async updateRole(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateRoleDto, @Request() req: any) {
try { try {
const result = await this.rbacService.updateRole(+id, dto); const result = await this.rbacService.updateRole(+id, dto);
await logAudit(this.logService, req, { await logAudit(this.logService, req, {
@@ -71,7 +72,7 @@ export class RbacController {
@Delete('roles/:id') @Delete('roles/:id')
@RequirePermission('role:delete') @RequirePermission('role:delete')
async deleteRole(@Param('id') id: string, @Request() req: any) { async deleteRole(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
try { try {
const result = await this.rbacService.deleteRole(+id); const result = await this.rbacService.deleteRole(+id);
await logAudit(this.logService, req, { await logAudit(this.logService, req, {
@@ -118,7 +119,7 @@ export class RbacController {
@Put('users/:id') @Put('users/:id')
@RequirePermission('user:edit') @RequirePermission('user:edit')
async updateUser(@Param('id') id: string, @Body() dto: UpdateUserDto, @Request() req: any) { async updateUser(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateUserDto, @Request() req: any) {
try { try {
const result = await this.rbacService.updateUser(+id, dto); const result = await this.rbacService.updateUser(+id, dto);
await logAudit(this.logService, req, { await logAudit(this.logService, req, {
@@ -132,7 +133,7 @@ export class RbacController {
@Put('users/:id/password') @Put('users/:id/password')
@RequirePermission('user:reset-password') @RequirePermission('user:reset-password')
async resetPassword(@Param('id') id: string, @Body() dto: ResetPasswordDto, @Request() req: any) { async resetPassword(@Param('id', ParseIntPipe) id: number, @Body() dto: ResetPasswordDto, @Request() req: any) {
try { try {
const result = await this.rbacService.resetPassword(+id, dto.password); const result = await this.rbacService.resetPassword(+id, dto.password);
await logAudit(this.logService, req, { await logAudit(this.logService, req, {
@@ -146,7 +147,7 @@ export class RbacController {
@Put('users/:id/archive') @Put('users/:id/archive')
@RequirePermission('user:edit') @RequirePermission('user:edit')
async archiveUser(@Param('id') id: string) { async archiveUser(@Param('id', ParseIntPipe) id: number) {
try { try {
return await this.rbacService.archiveUser(+id); return await this.rbacService.archiveUser(+id);
} catch (e: unknown) { } catch (e: unknown) {
@@ -157,7 +158,7 @@ export class RbacController {
@Put('users/:id/restore') @Put('users/:id/restore')
@RequirePermission('user:edit') @RequirePermission('user:edit')
async restoreUser(@Param('id') id: string) { async restoreUser(@Param('id', ParseIntPipe) id: number) {
try { try {
return await this.rbacService.restoreUser(+id); return await this.rbacService.restoreUser(+id);
} catch (e: unknown) { } catch (e: unknown) {
@@ -168,7 +169,7 @@ export class RbacController {
@Delete('users/:id/permanent') @Delete('users/:id/permanent')
@RequirePermission('user:purge') @RequirePermission('user:purge')
async purgeUser(@Param('id') id: string, @Request() req: any) { async purgeUser(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
try { try {
const result = await this.rbacService.purgeUser(+id, req.user?.id); const result = await this.rbacService.purgeUser(+id, req.user?.id);
await logAudit(this.logService, req, { await logAudit(this.logService, req, {
@@ -183,7 +184,7 @@ export class RbacController {
@Put('users/:id/mark-staff') @Put('users/:id/mark-staff')
@RequirePermission('user:edit') @RequirePermission('user:edit')
async markAsStaff(@Param('id') id: string) { async markAsStaff(@Param('id', ParseIntPipe) id: number) {
try { try {
return await this.rbacService.markAsStaff(+id); return await this.rbacService.markAsStaff(+id);
} catch (e: unknown) { } catch (e: unknown) {
@@ -194,7 +195,7 @@ export class RbacController {
@Put('users/:id/mark-student') @Put('users/:id/mark-student')
@RequirePermission('user:edit') @RequirePermission('user:edit')
async markAsStudent(@Param('id') id: string) { async markAsStudent(@Param('id', ParseIntPipe) id: number) {
try { try {
return await this.rbacService.markAsStudent(+id); return await this.rbacService.markAsStudent(+id);
} catch (e: unknown) { } catch (e: unknown) {
@@ -205,14 +206,14 @@ export class RbacController {
@Get('users/:id/profile') @Get('users/:id/profile')
@RequirePermission('user:view') @RequirePermission('user:view')
getUserProfile(@Param('id') id: string) { getUserProfile(@Param('id', ParseIntPipe) id: number) {
return this.rbacService.getUserProfile(+id); return this.rbacService.getUserProfile(+id);
} }
@Put('users/:id/profile') @Put('users/:id/profile')
@RequirePermission('user:edit') @RequirePermission('user:edit')
async updateUserProfile( async updateUserProfile(
@Param('id') id: string, @Param('id', ParseIntPipe) id: number,
@Body() dto: UpdateProfileDto, @Body() dto: UpdateProfileDto,
@Request() req: any, @Request() req: any,
) { ) {
@@ -250,7 +251,7 @@ export class RbacController {
@Put('teachers/:id/profile') @Put('teachers/:id/profile')
@RequirePermission('teacher:edit') @RequirePermission('teacher:edit')
async updateTeacherProfile( async updateTeacherProfile(
@Param('id') id: string, @Param('id', ParseIntPipe) id: number,
@Body() profile: UpdateProfileDto, @Body() profile: UpdateProfileDto,
@Request() req: { user?: { id: number; username: string } }, @Request() req: { user?: { id: number; username: string } },
) { ) {