From 5b5ffb5b9ebcc44bde6f367ca21483dbbf651f14 Mon Sep 17 00:00:00 2001 From: wangziqi Date: Fri, 7 Aug 2026 16:38:03 +0800 Subject: [PATCH] =?UTF-8?q?fix(server):=20=E8=B7=AF=E7=94=B1=20id=20?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=20ParseIntPipe=EF=BC=8C=E9=99=90=E5=88=B6?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E4=B8=8E=E6=89=B9=E9=87=8F=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E4=B8=8A=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../attendance/attendance-query.service.ts | 8 ++-- apps/server/src/classes/classes.controller.ts | 41 ++++++++++--------- .../classroom-rentals.controller.ts | 19 +++++---- apps/server/src/common/batch-ids.dto.spec.ts | 7 ++++ apps/server/src/common/batch-ids.dto.ts | 3 +- .../expense-types/expense-types.controller.ts | 6 +-- .../src/expenses/dto/expense.dto.spec.ts | 13 ++++++ apps/server/src/expenses/dto/expense.dto.ts | 3 +- .../dto/notification.dto.spec.ts | 9 ++++ .../src/notifications/dto/notification.dto.ts | 2 + .../notifications/notifications.controller.ts | 3 +- .../operation-logs/operation-logs.service.ts | 4 +- .../organizations/organizations.controller.ts | 9 ++-- apps/server/src/rbac/rbac.controller.ts | 27 ++++++------ 14 files changed, 96 insertions(+), 58 deletions(-) diff --git a/apps/server/src/attendance/attendance-query.service.ts b/apps/server/src/attendance/attendance-query.service.ts index c22fbfd..7974eb7 100644 --- a/apps/server/src/attendance/attendance-query.service.ts +++ b/apps/server/src/attendance/attendance-query.service.ts @@ -131,8 +131,8 @@ export class AttendanceQueryService { }, accessibleClassIds?: number[], ) { - const page = query.page || 1; - const pageSize = query.pageSize || 20; + const page = Math.max(1, Math.floor(Number(query.page) || 1)); + const pageSize = Math.min(200, Math.max(1, Math.floor(Number(query.pageSize) || 20))); const qb = this.attendanceRepo.createQueryBuilder('ar'); @@ -233,8 +233,8 @@ export class AttendanceQueryService { // ── DingAttendance raw records ── async getDingRaw(query: QueryDingRawDto, accessibleClassIds?: number[]) { - const page = query.page || 1; - const pageSize = query.pageSize || 20; + const page = Math.max(1, Math.floor(Number(query.page) || 1)); + const pageSize = Math.min(200, Math.max(1, Math.floor(Number(query.pageSize) || 20))); const qb = this.dingRawRepo.createQueryBuilder('ar'); qb.leftJoinAndSelect('ar.matchedStudent', 'matchedStudent'); diff --git a/apps/server/src/classes/classes.controller.ts b/apps/server/src/classes/classes.controller.ts index 2723aca..139cbb5 100644 --- a/apps/server/src/classes/classes.controller.ts +++ b/apps/server/src/classes/classes.controller.ts @@ -6,6 +6,7 @@ import { Delete, Body, Param, + ParseIntPipe, Query, UseGuards, UsePipes, @@ -85,7 +86,7 @@ export class ClassesController { @Get(':id') @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); return this.service.findOne(+id); } @@ -93,7 +94,7 @@ export class ClassesController { @Get(':id/schedule') @RequirePermission('class:view') async getSchedule( - @Param('id') id: string, + @Param('id', ParseIntPipe) id: number, @Query() query: QueryClassScheduleDto, @Request() req: AuthenticatedRequest, ) { @@ -104,7 +105,7 @@ export class ClassesController { @Get(':id/attendance-summary') @RequirePermission('class:view') async getAttendanceSummary( - @Param('id') id: string, + @Param('id', ParseIntPipe) id: number, @Query() query: QueryClassAttendanceSummaryDto, @Request() req: AuthenticatedRequest, ) { @@ -125,27 +126,27 @@ export class ClassesController { /** 批量导入学生到班级(通过钉钉用户ID) */ @Post(':id/students/import') @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); } /** 归档班级 */ @Put(':id/archive') @RequirePermission('class:edit') - async archive(@Param('id') id: string) { + async archive(@Param('id', ParseIntPipe) id: number) { return this.service.archive(+id); } /** 取消归档 */ @Put(':id/restore') @RequirePermission('class:edit') - async restore(@Param('id') id: string) { + async restore(@Param('id', ParseIntPipe) id: number) { return this.service.restore(+id); } @Put(':id') @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); await logAudit(this.logService, req, { module: '班级管理', action: '编辑班级', targetId: +id, targetType: 'class', detail: JSON.stringify(dto), @@ -155,7 +156,7 @@ export class ClassesController { @Delete(':id') @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); await logAudit(this.logService, req, { module: '班级管理', action: '归档班级', targetId: +id, targetType: 'class', @@ -165,7 +166,7 @@ export class ClassesController { @Delete(':id/permanent') @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); await logAudit(this.logService, req, { module: '班级管理', action: '永久删除班级', targetId: +id, targetType: 'class', detail: '物理删除,不可恢复', @@ -176,7 +177,7 @@ export class ClassesController { @Get(':id/roster/export') @RequirePermission('class:view') async exportRoster( - @Param('id') id: string, + @Param('id', ParseIntPipe) id: number, @Res() res: Response, @Request() req: AuthenticatedRequest, ) { @@ -218,14 +219,14 @@ export class ClassesController { @Get(':id/students') @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); return this.service.getStudents(+id); } @Post(':id/students') @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); await logAudit(this.logService, req, { module: '班级管理', action: '添加学生', targetId: +id, targetType: 'class', detail: `新增${result.added}名学生`, @@ -249,8 +250,8 @@ export class ClassesController { @Delete(':id/students/:studentId') @RequirePermission('class:edit') async removeStudent( - @Param('id') id: string, - @Param('studentId') studentId: string, + @Param('id', ParseIntPipe) id: number, + @Param('studentId', ParseIntPipe) studentId: number, @Request() req: any, ) { const result = await this.service.removeStudent(+id, +studentId); @@ -262,14 +263,14 @@ export class ClassesController { @Get(':id/teachers') @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); return this.service.getTeachers(+id); } @Post(':id/teachers') @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); await logAudit(this.logService, req, { module: '班级管理', action: '添加教师', targetId: +id, targetType: 'class', detail: `教师${dto.userId} 角色${dto.roleType}`, @@ -290,8 +291,8 @@ export class ClassesController { @Delete(':id/teacher-assignments/:assignmentId') @RequirePermission('class:edit') async removeTeacherAssignment( - @Param('id') id: string, - @Param('assignmentId') assignmentId: string, + @Param('id', ParseIntPipe) id: number, + @Param('assignmentId', ParseIntPipe) assignmentId: number, @Request() req: any, ) { const result = await this.service.removeTeacherAssignment(+id, +assignmentId); @@ -304,8 +305,8 @@ export class ClassesController { @Delete(':id/teachers/:userId') @RequirePermission('class:edit') async removeTeacher( - @Param('id') id: string, - @Param('userId') userId: string, + @Param('id', ParseIntPipe) id: number, + @Param('userId', ParseIntPipe) userId: number, @Request() req: any, ) { const result = await this.service.removeTeacher(+id, +userId); diff --git a/apps/server/src/classroom-rentals/classroom-rentals.controller.ts b/apps/server/src/classroom-rentals/classroom-rentals.controller.ts index dd9e970..d64be00 100644 --- a/apps/server/src/classroom-rentals/classroom-rentals.controller.ts +++ b/apps/server/src/classroom-rentals/classroom-rentals.controller.ts @@ -6,6 +6,7 @@ import { Delete, Body, Param, + ParseIntPipe, Query, UseGuards, Request, @@ -95,7 +96,7 @@ export class ClassroomRentalsController { @Get(':id') @RequirePermission('rental:view') - findOne(@Param('id') id: string) { + findOne(@Param('id', ParseIntPipe) id: number) { return this.service.findOne(+id); } @@ -111,7 +112,7 @@ export class ClassroomRentalsController { @Put(':id') @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); await logAudit(this.logService, req, { module: '教室租赁', action: '编辑租赁', targetId: +id, targetType: 'classroom-rental', detail: JSON.stringify(dto), @@ -121,7 +122,7 @@ export class ClassroomRentalsController { @Put(':id/cancel') @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); await logAudit(this.logService, req, { module: '教室租赁', action: '取消租赁', targetId: +id, targetType: 'classroom-rental', @@ -131,7 +132,7 @@ export class ClassroomRentalsController { @Put(':id/end') @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); await logAudit(this.logService, req, { module: '教室租赁', action: '结束租赁', targetId: +id, targetType: 'classroom-rental', @@ -141,7 +142,7 @@ export class ClassroomRentalsController { @Delete(':id') @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); await logAudit(this.logService, req, { module: '教室租赁', action: '归档租赁', targetId: +id, targetType: 'classroom-rental', @@ -151,7 +152,7 @@ export class ClassroomRentalsController { @Delete(':id/permanent') @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); await logAudit(this.logService, req, { module: '教室租赁', action: '永久删除租赁订单', targetId: +id, targetType: 'classroom-rental', detail: '物理删除,不可恢复', @@ -174,7 +175,7 @@ export class ClassroomRentalsController { }), ) async uploadContract( - @Param('id') id: string, + @Param('id', ParseIntPipe) id: number, @UploadedFile() file: Express.Multer.File, @Request() req: any, ) { @@ -188,7 +189,7 @@ export class ClassroomRentalsController { @Get(':id/contract') @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); res.setHeader('Content-Type', 'application/pdf'); res.setHeader( @@ -201,7 +202,7 @@ export class ClassroomRentalsController { @Delete(':id/contract') @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); await logAudit(this.logService, req, { module: '教室租赁', action: '移除合同', targetId: +id, targetType: 'classroom-rental', diff --git a/apps/server/src/common/batch-ids.dto.spec.ts b/apps/server/src/common/batch-ids.dto.spec.ts index 6377f99..64f9977 100644 --- a/apps/server/src/common/batch-ids.dto.spec.ts +++ b/apps/server/src/common/batch-ids.dto.spec.ts @@ -17,4 +17,11 @@ describe('BatchIdsDto', () => { const dto = Object.assign(new BatchIdsDto(), { ids: [1, 1, 2] }); 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); + }); }); diff --git a/apps/server/src/common/batch-ids.dto.ts b/apps/server/src/common/batch-ids.dto.ts index d778abb..5e8ce70 100644 --- a/apps/server/src/common/batch-ids.dto.ts +++ b/apps/server/src/common/batch-ids.dto.ts @@ -1,8 +1,9 @@ -import { ArrayNotEmpty, IsArray, IsInt, Min } from 'class-validator'; +import { ArrayMaxSize, ArrayNotEmpty, IsArray, IsInt, Min } from 'class-validator'; export class BatchIdsDto { @IsArray() @ArrayNotEmpty() + @ArrayMaxSize(500) @IsInt({ each: true }) @Min(1, { each: true }) ids: number[]; diff --git a/apps/server/src/expense-types/expense-types.controller.ts b/apps/server/src/expense-types/expense-types.controller.ts index d7db659..3813d15 100644 --- a/apps/server/src/expense-types/expense-types.controller.ts +++ b/apps/server/src/expense-types/expense-types.controller.ts @@ -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 { CreateExpenseTypeDto, UpdateExpenseTypeDto } from './dto/expense-type.dto'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; @@ -47,7 +47,7 @@ export class ExpenseTypesController { @Put(':id') @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 result = await this.service.update(+id, dto); await this.logService.log({ @@ -66,7 +66,7 @@ export class ExpenseTypesController { @Delete(':id') @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); await this.service.remove(+id); await this.logService.log({ diff --git a/apps/server/src/expenses/dto/expense.dto.spec.ts b/apps/server/src/expenses/dto/expense.dto.spec.ts index 2db7053..9a03a7c 100644 --- a/apps/server/src/expenses/dto/expense.dto.spec.ts +++ b/apps/server/src/expenses/dto/expense.dto.spec.ts @@ -28,4 +28,17 @@ describe('BatchRoomExpenseDto boundaries', () => { }); 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); + }); }); diff --git a/apps/server/src/expenses/dto/expense.dto.ts b/apps/server/src/expenses/dto/expense.dto.ts index f6aee27..dd00816 100644 --- a/apps/server/src/expenses/dto/expense.dto.ts +++ b/apps/server/src/expenses/dto/expense.dto.ts @@ -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 { Type } from 'class-transformer'; @@ -117,6 +117,7 @@ export class BatchRoomExpenseDto { @IsArray() @ArrayNotEmpty() + @ArrayMaxSize(500) @ValidateNested({ each: true }) @Type(() => BatchRoomExpenseItemDto) expenses: BatchRoomExpenseItemDto[]; diff --git a/apps/server/src/notifications/dto/notification.dto.spec.ts b/apps/server/src/notifications/dto/notification.dto.spec.ts index 235aa75..2bacea3 100644 --- a/apps/server/src/notifications/dto/notification.dto.spec.ts +++ b/apps/server/src/notifications/dto/notification.dto.spec.ts @@ -32,4 +32,13 @@ describe('notification DTO boundaries', () => { 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([]); + }); }); diff --git a/apps/server/src/notifications/dto/notification.dto.ts b/apps/server/src/notifications/dto/notification.dto.ts index fc524ad..1f7a3c3 100644 --- a/apps/server/src/notifications/dto/notification.dto.ts +++ b/apps/server/src/notifications/dto/notification.dto.ts @@ -1,5 +1,6 @@ import { ArrayNotEmpty, + ArrayMaxSize, IsString, IsNotEmpty, IsOptional, @@ -13,6 +14,7 @@ import { Type } from 'class-transformer'; export class CreateNotificationDto { @IsArray() @ArrayNotEmpty() + @ArrayMaxSize(500) @IsInt({ each: true }) recipientIds: number[]; diff --git a/apps/server/src/notifications/notifications.controller.ts b/apps/server/src/notifications/notifications.controller.ts index d285482..d3fdc7b 100644 --- a/apps/server/src/notifications/notifications.controller.ts +++ b/apps/server/src/notifications/notifications.controller.ts @@ -3,6 +3,7 @@ import { Get, Put, Param, + ParseIntPipe, Query, Req, Sse, @@ -74,7 +75,7 @@ export class NotificationsController { @Put(':id/read') async markRead( - @Param('id') id: string, + @Param('id', ParseIntPipe) id: number, @Req() req: AuthenticatedRequest, ) { await this.service.markRead(+id, req.user.id); diff --git a/apps/server/src/operation-logs/operation-logs.service.ts b/apps/server/src/operation-logs/operation-logs.service.ts index 644e554..4cc8db0 100644 --- a/apps/server/src/operation-logs/operation-logs.service.ts +++ b/apps/server/src/operation-logs/operation-logs.service.ts @@ -53,8 +53,8 @@ export class OperationLogsService { if (query?.endDate) qb.andWhere('log.createdAt <= :endDate', { endDate: query.endDate + ' 23:59:59' }); - const page = query?.page || 1; - const pageSize = query?.pageSize || 50; + const page = Math.max(1, Math.floor(Number(query?.page) || 1)); + const pageSize = Math.min(200, Math.max(1, Math.floor(Number(query?.pageSize) || 50))); const [data, total] = await qb .skip((page - 1) * pageSize) .take(pageSize) diff --git a/apps/server/src/organizations/organizations.controller.ts b/apps/server/src/organizations/organizations.controller.ts index 9548256..f260c48 100644 --- a/apps/server/src/organizations/organizations.controller.ts +++ b/apps/server/src/organizations/organizations.controller.ts @@ -6,6 +6,7 @@ import { Delete, Body, Param, + ParseIntPipe, Query, UseGuards, Request, @@ -42,7 +43,7 @@ export class OrganizationsController { @Get(':id') @RequirePermission('organization:view') - findOne(@Param('id') id: string) { + findOne(@Param('id', ParseIntPipe) id: number) { return this.service.findOne(+id); } @@ -67,7 +68,7 @@ export class OrganizationsController { @Put(':id') @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 result = await this.service.update(+id, dto); await this.logService.log({ @@ -86,7 +87,7 @@ export class OrganizationsController { @Delete(':id') @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 result = await this.service.remove(+id); await this.logService.log({ @@ -104,7 +105,7 @@ export class OrganizationsController { @Delete(':id/permanent') @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 result = await this.service.purge(+id); await this.logService.log({ diff --git a/apps/server/src/rbac/rbac.controller.ts b/apps/server/src/rbac/rbac.controller.ts index c4dee8f..68d1c31 100644 --- a/apps/server/src/rbac/rbac.controller.ts +++ b/apps/server/src/rbac/rbac.controller.ts @@ -10,6 +10,7 @@ import { UseGuards, Request, BadRequestException, + ParseIntPipe, } from '@nestjs/common'; import { RbacService } from './rbac.service'; import { @@ -41,7 +42,7 @@ export class RbacController { @Get('roles/:id') @RequirePermission('role:view') - findRoleById(@Param('id') id: string) { + findRoleById(@Param('id', ParseIntPipe) id: number) { return this.rbacService.findRoleById(+id); } @@ -57,7 +58,7 @@ export class RbacController { @Put('roles/:id') @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 { const result = await this.rbacService.updateRole(+id, dto); await logAudit(this.logService, req, { @@ -71,7 +72,7 @@ export class RbacController { @Delete('roles/:id') @RequirePermission('role:delete') - async deleteRole(@Param('id') id: string, @Request() req: any) { + async deleteRole(@Param('id', ParseIntPipe) id: number, @Request() req: any) { try { const result = await this.rbacService.deleteRole(+id); await logAudit(this.logService, req, { @@ -118,7 +119,7 @@ export class RbacController { @Put('users/:id') @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 { const result = await this.rbacService.updateUser(+id, dto); await logAudit(this.logService, req, { @@ -132,7 +133,7 @@ export class RbacController { @Put('users/:id/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 { const result = await this.rbacService.resetPassword(+id, dto.password); await logAudit(this.logService, req, { @@ -146,7 +147,7 @@ export class RbacController { @Put('users/:id/archive') @RequirePermission('user:edit') - async archiveUser(@Param('id') id: string) { + async archiveUser(@Param('id', ParseIntPipe) id: number) { try { return await this.rbacService.archiveUser(+id); } catch (e: unknown) { @@ -157,7 +158,7 @@ export class RbacController { @Put('users/:id/restore') @RequirePermission('user:edit') - async restoreUser(@Param('id') id: string) { + async restoreUser(@Param('id', ParseIntPipe) id: number) { try { return await this.rbacService.restoreUser(+id); } catch (e: unknown) { @@ -168,7 +169,7 @@ export class RbacController { @Delete('users/:id/permanent') @RequirePermission('user:purge') - async purgeUser(@Param('id') id: string, @Request() req: any) { + async purgeUser(@Param('id', ParseIntPipe) id: number, @Request() req: any) { try { const result = await this.rbacService.purgeUser(+id, req.user?.id); await logAudit(this.logService, req, { @@ -183,7 +184,7 @@ export class RbacController { @Put('users/:id/mark-staff') @RequirePermission('user:edit') - async markAsStaff(@Param('id') id: string) { + async markAsStaff(@Param('id', ParseIntPipe) id: number) { try { return await this.rbacService.markAsStaff(+id); } catch (e: unknown) { @@ -194,7 +195,7 @@ export class RbacController { @Put('users/:id/mark-student') @RequirePermission('user:edit') - async markAsStudent(@Param('id') id: string) { + async markAsStudent(@Param('id', ParseIntPipe) id: number) { try { return await this.rbacService.markAsStudent(+id); } catch (e: unknown) { @@ -205,14 +206,14 @@ export class RbacController { @Get('users/:id/profile') @RequirePermission('user:view') - getUserProfile(@Param('id') id: string) { + getUserProfile(@Param('id', ParseIntPipe) id: number) { return this.rbacService.getUserProfile(+id); } @Put('users/:id/profile') @RequirePermission('user:edit') async updateUserProfile( - @Param('id') id: string, + @Param('id', ParseIntPipe) id: number, @Body() dto: UpdateProfileDto, @Request() req: any, ) { @@ -250,7 +251,7 @@ export class RbacController { @Put('teachers/:id/profile') @RequirePermission('teacher:edit') async updateTeacherProfile( - @Param('id') id: string, + @Param('id', ParseIntPipe) id: number, @Body() profile: UpdateProfileDto, @Request() req: { user?: { id: number; username: string } }, ) {