fix: clarify client amounts and log date filters

This commit is contained in:
hectorzhao
2026-08-27 19:50:12 +08:00
parent ec721bf95a
commit 33fa3709d9
18 changed files with 136 additions and 82 deletions
@@ -431,10 +431,12 @@ export class AdminSystemLogsController {
@Query('status') status?: string,
@Query('keyword') keyword?: string,
@Query('range') range?: string,
@Query('createdAtFrom') createdAtFrom?: string,
@Query('createdAtTo') createdAtTo?: string,
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
) {
return this.protocolLogs.list({ protocol, direction, eventType, status, keyword, range, page: Number(page), pageSize: Number(pageSize) });
return this.protocolLogs.list({ protocol, direction, eventType, status, keyword, range, createdAtFrom, createdAtTo, page: Number(page), pageSize: Number(pageSize) });
}
@Get()
@@ -445,14 +447,16 @@ export class AdminSystemLogsController {
@Query('level') level?: string,
@Query('module') module?: string,
@Query('range') range?: string,
@Query('createdAtFrom') createdAtFrom?: string,
@Query('createdAtTo') createdAtTo?: string,
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
) {
return this.operations.systemLogs({ tenantId, userId, keyword, level, module, range, page: Number(page), pageSize: Number(pageSize) });
return this.operations.systemLogs({ tenantId, userId, keyword, level, module, range, createdAtFrom, createdAtTo, page: Number(page), pageSize: Number(pageSize) });
}
@Post('exports')
export(@Body() body: { tenantId?: string; userId?: string; keyword?: string; level?: string; module?: string; range?: string }) {
export(@Body() body: { tenantId?: string; userId?: string; keyword?: string; level?: string; module?: string; range?: string; createdAtFrom?: string; createdAtTo?: string }) {
return this.operations.exportSystemLogs(body);
}
}
@@ -67,16 +67,18 @@ export class ClientOperationsController {
@Query('level') level?: string,
@Query('module') module?: string,
@Query('range') range?: string,
@Query('createdAtFrom') createdAtFrom?: string,
@Query('createdAtTo') createdAtTo?: string,
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
) {
return this.operations.systemLogs({ tenantId, keyword, level, module, range, 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')
exportSystemLogs(
@CurrentSessionUserId() userId: string | undefined,
@Body() body: { keyword?: string; level?: string; module?: string; range?: string },
@Body() body: { keyword?: string; level?: string; module?: string; range?: string; createdAtFrom?: string; createdAtTo?: string },
) {
return this.operations.exportSystemLogs(body, userId);
}
@@ -29,6 +29,8 @@ export interface OperationLogQuery {
level?: string;
module?: string;
range?: string;
createdAtFrom?: string;
createdAtTo?: string;
page?: number;
pageSize?: number;
}
@@ -890,6 +890,22 @@ describe('OperationsService', () => {
});
});
it('applies an explicit Beijing date interval to operation logs', async () => {
const prisma = createPrismaMock();
const service = new OperationsService(prisma as never);
await service.systemLogs({ createdAtFrom: '2026-08-21', createdAtTo: '2026-08-27' });
expect(prisma.operationLog.findMany).toHaveBeenCalledWith(expect.objectContaining({
where: expect.objectContaining({
createdAt: {
gte: new Date('2026-08-20T16:00:00.000Z'),
lte: new Date('2026-08-27T15:59:59.999Z'),
},
}),
}));
});
it('exports filtered operation logs with a traceable operation id', async () => {
const prisma = createPrismaMock();
const service = new OperationsService(prisma as never);
+8 -3
View File
@@ -31,7 +31,7 @@ async systemLogs(query: OperationLogQuery) {
const where: Prisma.OperationLogWhereInput = {
tenantId: query.tenantId,
userId: query.userId,
createdAt: createdAtRange(query.range),
createdAt: this.operationLogDateRange(query),
resource: query.module && query.module !== 'all' ? query.module : undefined,
AND: query.level && query.level !== 'all' ? operationLogLevelWhere(query.level) : undefined,
OR: query.keyword ? [
@@ -73,7 +73,7 @@ async exportSystemLogs(query: OperationLogQuery, clientUserId?: string) {
const where: Prisma.OperationLogWhereInput = {
tenantId: effectiveQuery.tenantId,
userId: effectiveQuery.userId,
createdAt: createdAtRange(effectiveQuery.range),
createdAt: this.operationLogDateRange(effectiveQuery),
resource: effectiveQuery.module && effectiveQuery.module !== 'all' ? effectiveQuery.module : undefined,
AND: effectiveQuery.level && effectiveQuery.level !== 'all' ? operationLogLevelWhere(effectiveQuery.level) : undefined,
OR: effectiveQuery.keyword ? [
@@ -107,9 +107,14 @@ async exportSystemLogs(query: OperationLogQuery, clientUserId?: string) {
recordCount: exportedRows.length,
truncated,
content: [headers, ...values].map((row) => row.map((cell) => escapeCsvCell(String(cell ?? ''))).join(',')).join('\n'),
filters: { keyword: effectiveQuery.keyword, level: effectiveQuery.level, module: effectiveQuery.module, range: effectiveQuery.range },
filters: { keyword: effectiveQuery.keyword, level: effectiveQuery.level, module: effectiveQuery.module, range: effectiveQuery.range, createdAtFrom: effectiveQuery.createdAtFrom, createdAtTo: effectiveQuery.createdAtTo },
};
}
private operationLogDateRange(query: OperationLogQuery) {
const createdAtFrom = parseDateBoundary(query.createdAtFrom, false);
const createdAtTo = parseDateBoundary(query.createdAtTo, true);
return createdAtFrom || createdAtTo ? { gte: createdAtFrom, lte: createdAtTo } : createdAtRange(query.range);
}
private async resolveClientTenantId(userId: string) {
const user = await this.prisma.user.findFirst({
where: { id: userId, status: 'active', deletedAt: null, tenantId: { not: null } },
@@ -76,4 +76,18 @@ describe('ProtocolLogsService', () => {
}),
}));
});
it('applies an explicit Beijing date interval to protocol logs', async () => {
const service = new ProtocolLogsService(prisma as never);
await service.list({ createdAtFrom: '2026-08-21', createdAtTo: '2026-08-27' });
expect(prisma.protocolInteractionLog.findMany).toHaveBeenCalledWith(expect.objectContaining({
where: expect.objectContaining({
createdAt: {
gte: new Date('2026-08-20T16:00:00.000Z'),
lte: new Date('2026-08-27T15:59:59.999Z'),
},
}),
}));
});
});
+10 -1
View File
@@ -1,6 +1,7 @@
import { Injectable, Logger, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { parseDateBoundary } from '../operations/operations.helpers';
export type ProtocolLogInput = {
protocol: 'cmpp' | 'http';
@@ -34,6 +35,8 @@ export type ProtocolLogQuery = {
status?: string;
keyword?: string;
range?: string;
createdAtFrom?: string;
createdAtTo?: string;
page?: number;
pageSize?: number;
};
@@ -115,7 +118,7 @@ export class ProtocolLogsService implements OnModuleInit, OnModuleDestroy {
direction: selected(query.direction),
eventType: selected(query.eventType),
status: selected(query.status),
createdAt: rangeWhere(query.range),
createdAt: protocolLogDateRange(query),
OR: query.keyword ? [
{ messageId: { contains: query.keyword } },
{ gatewayMessageId: { contains: query.keyword } },
@@ -197,6 +200,12 @@ function rangeWhere(range?: string): Prisma.DateTimeFilter | undefined {
return undefined;
}
function protocolLogDateRange(query: ProtocolLogQuery): Prisma.DateTimeFilter | undefined {
const createdAtFrom = parseDateBoundary(query.createdAtFrom, false);
const createdAtTo = parseDateBoundary(query.createdAtTo, true);
return createdAtFrom || createdAtTo ? { gte: createdAtFrom, lte: createdAtTo } : rangeWhere(query.range);
}
function sanitizeDetail(detail?: Record<string, unknown> | null): Prisma.InputJsonValue | undefined {
if (!detail) return undefined;
const blocked = /password|secret|token|signature|authorization|content|raw|body/i;