From 90e921366a34154d6a0af2a3791335212a0126d6 Mon Sep 17 00:00:00 2001 From: WangDL Date: Sun, 24 May 2026 17:45:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20M4-02=20=E2=80=94=20admin=20learning=20?= =?UTF-8?q?data=20views=20(sessions,=20AI=20analysis,=20AI=20usage=20logs)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add AdminLearningController with 3 endpoints: GET /admin-api/learning/sessions — learning sessions list GET /admin-api/learning/analysis — AI analysis results GET /admin-api/learning/ai-usage — AI usage logs Co-Authored-By: Claude Opus 4.7 --- .../admin-learning.controller.ts | 100 ++++++++++++++++++ .../learning-session.module.ts | 3 +- 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 src/modules/learning-session/admin-learning.controller.ts diff --git a/src/modules/learning-session/admin-learning.controller.ts b/src/modules/learning-session/admin-learning.controller.ts new file mode 100644 index 0000000..6f9cd58 --- /dev/null +++ b/src/modules/learning-session/admin-learning.controller.ts @@ -0,0 +1,100 @@ +import { Controller, Get, Query, UseGuards } from '@nestjs/common'; +import { ApiTags, ApiBearerAuth, ApiOperation, ApiQuery } from '@nestjs/swagger'; +import { PrismaService } from '../../infrastructure/database/prisma.service'; +import { AdminAuthGuard } from '../../common/guards/admin-auth.guard'; +import { AdminRolesGuard } from '../../common/guards/admin-roles.guard'; + +@ApiTags('admin-learning') +@ApiBearerAuth() +@Controller('admin-api/learning') +@UseGuards(AdminAuthGuard, AdminRolesGuard) +export class AdminLearningController { + constructor(private readonly prisma: PrismaService) {} + + @Get('sessions') + @ApiOperation({ summary: '学习会话列表' }) + @ApiQuery({ name: 'userId', required: false }) + @ApiQuery({ name: 'page', required: false }) + @ApiQuery({ name: 'limit', required: false }) + async listSessions( + @Query('userId') userId?: string, + @Query('page') page?: string, + @Query('limit') limit?: string, + ) { + const take = Math.min(Number(limit) || 20, 100); + const skip = (Math.max(Number(page) || 1, 1) - 1) * take; + const where: any = {}; + if (userId) where.userId = userId; + + const [items, total] = await Promise.all([ + this.prisma.learningSession.findMany({ + where, + orderBy: { startedAt: 'desc' }, + take, + skip, + select: { id: true, userId: true, knowledgeBaseId: true, knowledgeItemId: true, mode: true, status: true, startedAt: true, endedAt: true, durationSeconds: true, focusMinutes: true }, + }), + this.prisma.learningSession.count({ where }), + ]); + return { items, total }; + } + + @Get('analysis') + @ApiOperation({ summary: 'AI 分析结果列表' }) + @ApiQuery({ name: 'userId', required: false }) + @ApiQuery({ name: 'page', required: false }) + @ApiQuery({ name: 'limit', required: false }) + async listAnalysis( + @Query('userId') userId?: string, + @Query('page') page?: string, + @Query('limit') limit?: string, + ) { + const take = Math.min(Number(limit) || 20, 100); + const skip = (Math.max(Number(page) || 1, 1) - 1) * take; + const where: any = {}; + if (userId) where.userId = userId; + + const [items, total] = await Promise.all([ + this.prisma.aiAnalysisResult.findMany({ + where, + orderBy: { createdAt: 'desc' }, + take, + skip, + select: { id: true, userId: true, jobId: true, summary: true, masteryScore: true, weaknesses: true, strengths: true, createdAt: true }, + }), + this.prisma.aiAnalysisResult.count({ where }), + ]); + return { items, total }; + } + + @Get('ai-usage') + @ApiOperation({ summary: 'AI 调用日志' }) + @ApiQuery({ name: 'userId', required: false }) + @ApiQuery({ name: 'model', required: false }) + @ApiQuery({ name: 'page', required: false }) + @ApiQuery({ name: 'limit', required: false }) + async listAiUsage( + @Query('userId') userId?: string, + @Query('model') model?: string, + @Query('page') page?: string, + @Query('limit') limit?: string, + ) { + const take = Math.min(Number(limit) || 20, 100); + const skip = (Math.max(Number(page) || 1, 1) - 1) * take; + const where: any = {}; + if (userId) where.userId = userId; + if (model) where.model = { contains: model }; + + const [items, total] = await Promise.all([ + this.prisma.aiUsageLog.findMany({ + where, + orderBy: { createdAt: 'desc' }, + take, + skip, + select: { id: true, userId: true, model: true, provider: true, inputTokens: true, outputTokens: true, estimatedCost: true, success: true, createdAt: true }, + }), + this.prisma.aiUsageLog.count({ where }), + ]); + return { items, total }; + } +} diff --git a/src/modules/learning-session/learning-session.module.ts b/src/modules/learning-session/learning-session.module.ts index 3f82fe2..c68c1e7 100644 --- a/src/modules/learning-session/learning-session.module.ts +++ b/src/modules/learning-session/learning-session.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common'; import { LearningSessionController } from './learning-session.controller'; +import { AdminLearningController } from './admin-learning.controller'; import { LearningSessionService } from './learning-session.service'; import { LearningSessionRepository } from './learning-session.repository'; @Module({ - controllers: [LearningSessionController], + controllers: [LearningSessionController, AdminLearningController], providers: [LearningSessionService, LearningSessionRepository], exports: [LearningSessionService], })