refactor: strengthen client boundaries and quality gates

This commit is contained in:
hectorzhao
2026-08-28 14:26:58 +08:00
parent 3af145abe5
commit ad27acad7e
51 changed files with 7703 additions and 697 deletions
@@ -1,8 +1,10 @@
import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common';
import { Body, Controller, Get, Param, Post, Query, UsePipes } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { CurrentSessionUserId } from '../auth/current-session-user.decorator';
import { CurrentTenantId } from '../auth/current-tenant-id.decorator';
import { OperationsService } from './operations.service';
import { strictValidationPipe } from '../common/strict-validation.pipe';
import { ClientSystemLogExportDto } from './client-operations.dto';
@ApiTags('client-operations')
@Controller('client/operations')
@@ -15,7 +17,11 @@ export class ClientOperationsController {
}
@Get('batch-tasks/:id/messages')
listTaskMessages(@CurrentTenantId() tenantId: string, @Param('id') taskId: string, @Query('phoneNumber') phoneNumber?: string) {
listTaskMessages(
@CurrentTenantId() tenantId: string,
@Param('id') taskId: string,
@Query('phoneNumber') phoneNumber?: string,
) {
return this.operations.listClientMessages({ tenantId, taskId, phoneNumber });
}
@@ -49,9 +55,31 @@ export class ClientOperationsController {
}
@Get('uplink-messages')
listUplinkMessages(@CurrentTenantId() tenantId: string, @Query('channelId') channelId?: string, @Query('applicationId') applicationId?: string, @Query('phoneNumber') phoneNumber?: string, @Query('keyword') keyword?: string, @Query('startTime') startTime?: string, @Query('endTime') endTime?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string) {
listUplinkMessages(
@CurrentTenantId() tenantId: string,
@Query('channelId') channelId?: string,
@Query('applicationId') applicationId?: string,
@Query('phoneNumber') phoneNumber?: string,
@Query('keyword') keyword?: string,
@Query('startTime') startTime?: string,
@Query('endTime') endTime?: string,
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
) {
return page || pageSize
? this.operations.listUplinkMessagesPage({ tenantId, applicationId, phoneNumber, keyword, startTime, endTime, page: Number(page), pageSize: Number(pageSize) }, true)
? this.operations.listUplinkMessagesPage(
{
tenantId,
applicationId,
phoneNumber,
keyword,
startTime,
endTime,
page: Number(page),
pageSize: Number(pageSize),
},
true,
)
: this.operations.listClientUplinkMessages({ tenantId, applicationId, phoneNumber, keyword, startTime, endTime });
}
@@ -72,14 +100,25 @@ export class ClientOperationsController {
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
) {
return this.operations.systemLogs({ tenantId, keyword, level, module, range, createdAtFrom, createdAtTo, page: Number(page), pageSize: Number(pageSize) });
return this.operations.systemLogs({
tenantId,
keyword,
level,
module,
range,
createdAtFrom,
createdAtTo,
page: Number(page),
pageSize: Number(pageSize),
});
}
@Post('system-logs/exports')
@UsePipes(strictValidationPipe)
exportSystemLogs(
@CurrentSessionUserId() userId: string | undefined,
@CurrentTenantId() tenantId: string,
@Body() body: { keyword?: string; level?: string; module?: string; range?: string; createdAtFrom?: string; createdAtTo?: string },
@Body() body: ClientSystemLogExportDto,
) {
return this.operations.exportSystemLogs({ ...body, tenantId }, userId);
}
@@ -0,0 +1,28 @@
import { BadRequestException } from '@nestjs/common';
import { strictValidationPipe } from '../common/strict-validation.pipe';
import { ClientSystemLogExportDto } from './client-operations.dto';
describe('ClientSystemLogExportDto', () => {
it('accepts the supported date range and rejects extra or malformed fields', async () => {
await expect(
strictValidationPipe.transform(
{ createdAtFrom: '2026-08-21', createdAtTo: '2026-08-28', level: 'error' },
{
type: 'body',
metatype: ClientSystemLogExportDto,
data: undefined,
},
),
).resolves.toEqual(expect.objectContaining({ level: 'error' }));
await expect(
strictValidationPipe.transform(
{ createdAtFrom: 'last-week', tenantId: 'spoofed' },
{
type: 'body',
metatype: ClientSystemLogExportDto,
data: undefined,
},
),
).rejects.toBeInstanceOf(BadRequestException);
});
});
@@ -0,0 +1,10 @@
import { IsDateString, IsIn, IsOptional, IsString, MaxLength } from 'class-validator';
export class ClientSystemLogExportDto {
@IsOptional() @IsString() @MaxLength(200) keyword?: string;
@IsOptional() @IsIn(['all', 'debug', 'info', 'warning', 'error']) level?: string;
@IsOptional() @IsString() @MaxLength(100) module?: string;
@IsOptional() @IsIn(['7d', '30d']) range?: string;
@IsOptional() @IsDateString({ strict: true }) createdAtFrom?: string;
@IsOptional() @IsDateString({ strict: true }) createdAtTo?: string;
}