fix: admin controller audit — merge duplicate controllers, add Post import, validate params
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 44s

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
wangdl 2026-06-09 21:46:37 +08:00
parent 4414f9cc55
commit 1442b9b69e

View File

@ -1,10 +1,12 @@
import { Controller, Get, Param, Query } from '@nestjs/common'; import { Controller, Get, Param, Post, Query, UseGuards } from '@nestjs/common';
import { PrismaService } from '../../infrastructure/database/prisma.service'; import { PrismaService } from '../../infrastructure/database/prisma.service';
@Controller('admin/learning') @Controller('admin/learning')
export class AdminReadingController { export class AdminReadingController {
constructor(private readonly prisma: PrismaService) {} constructor(private readonly prisma: PrismaService) {}
// ── Dashboard ──
@Get('dashboard') @Get('dashboard')
async getDashboard() { async getDashboard() {
const now = new Date(); const now = new Date();
@ -35,7 +37,6 @@ export class AdminReadingController {
materials: { totalRead, totalMarkedRead }, materials: { totalRead, totalMarkedRead },
}; };
} }
}
// ── ReadingEvent ── // ── ReadingEvent ──
@ -169,16 +170,12 @@ export class AdminReadingController {
async getRecord(@Param('id') id: string) { async getRecord(@Param('id') id: string) {
return this.prisma.learningRecord.findUnique({ where: { id } }); return this.prisma.learningRecord.findUnique({ where: { id } });
} }
}
// ── Diagnosis ── // ── Diagnosis ──
@Controller('admin/learning')
export class AdminDiagnosticController {
constructor(private readonly prisma: PrismaService) {}
@Get('user-timeline') @Get('user-timeline')
async userTimeline(@Query('userId') userId: string) { async userTimeline(@Query('userId') userId?: string) {
if (!userId) return { error: 'userId is required' };
const events = await this.prisma.readingEvent.findMany({ const events = await this.prisma.readingEvent.findMany({
where: { userId }, orderBy: { clientTimestampMs: 'asc' }, take: 200, where: { userId }, orderBy: { clientTimestampMs: 'asc' }, take: 200,
select: { eventType: true, materialId: true, clientTimestampMs: true, activeSecondsDelta: true }, select: { eventType: true, materialId: true, clientTimestampMs: true, activeSecondsDelta: true },
@ -187,7 +184,8 @@ export class AdminDiagnosticController {
} }
@Get('user-diagnose') @Get('user-diagnose')
async userDiagnose(@Query('userId') userId: string) { async userDiagnose(@Query('userId') userId?: string) {
if (!userId) return { error: 'userId is required' };
const [sessions, progressList, dailyActivities, records] = await Promise.all([ const [sessions, progressList, dailyActivities, records] = await Promise.all([
this.prisma.learningSession.findMany({ where: { userId }, orderBy: { startedAt: 'desc' }, take: 50 }), this.prisma.learningSession.findMany({ where: { userId }, orderBy: { startedAt: 'desc' }, take: 50 }),
this.prisma.materialReadingProgress.findMany({ where: { userId } }), this.prisma.materialReadingProgress.findMany({ where: { userId } }),
@ -198,7 +196,8 @@ export class AdminDiagnosticController {
} }
@Get('material-diagnose') @Get('material-diagnose')
async materialDiagnose(@Query('materialId') materialId: string) { async materialDiagnose(@Query('materialId') materialId?: string) {
if (!materialId) return { error: 'materialId is required' };
const [events, progress] = await Promise.all([ const [events, progress] = await Promise.all([
this.prisma.readingEvent.findMany({ where: { materialId }, orderBy: { clientTimestampMs: 'desc' }, take: 100 }), this.prisma.readingEvent.findMany({ where: { materialId }, orderBy: { clientTimestampMs: 'desc' }, take: 100 }),
this.prisma.materialReadingProgress.findMany({ where: { materialId } }), this.prisma.materialReadingProgress.findMany({ where: { materialId } }),
@ -230,13 +229,8 @@ export class AdminDiagnosticController {
]); ]);
return { items, total, page, limit }; return { items, total, page, limit };
} }
}
// ── Operations ── // ── Operations ──
@Controller('admin/learning')
export class AdminOperationsController {
constructor(private readonly prisma: PrismaService) {}
@Post('recalculate') @Post('recalculate')
async recalculate() { async recalculate() {
@ -249,14 +243,17 @@ export class AdminOperationsController {
if (startDate) where.createdAt = { gte: new Date(startDate) }; if (startDate) where.createdAt = { gte: new Date(startDate) };
if (endDate) where.createdAt = { ...where.createdAt, lte: new Date(endDate) }; if (endDate) where.createdAt = { ...where.createdAt, lte: new Date(endDate) };
let data: any[] = []; const take = 5000;
switch (type) { switch (type) {
case 'events': data = await this.prisma.readingEvent.findMany({ where, take: 10000 }); break; case 'events': {
case 'sessions': data = await this.prisma.learningSession.findMany({ where, take: 10000 }); break; const data = await this.prisma.readingEvent.findMany({ where, take, orderBy: { createdAt: 'desc' } });
default: return { error: 'Invalid export type. Use: events, sessions' };
}
return { type, count: data.length, data }; return { type, count: data.length, data };
} }
case 'sessions': {
const data = await this.prisma.learningSession.findMany({ where, take, orderBy: { startedAt: 'desc' } });
return { type, count: data.length, data };
}
default: return { error: 'Invalid export type. Use: events, sessions' };
}
}
} }
EOF
echo "Admin diagnostic + operations endpoints added"