feat: M4-02 — admin learning data views (sessions, AI analysis, AI usage logs)
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 44s

- 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 <noreply@anthropic.com>
This commit is contained in:
WangDL 2026-05-24 17:45:43 +08:00
parent 5816ddf488
commit 90e921366a
2 changed files with 102 additions and 1 deletions

View File

@ -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 };
}
}

View File

@ -1,10 +1,11 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { LearningSessionController } from './learning-session.controller'; import { LearningSessionController } from './learning-session.controller';
import { AdminLearningController } from './admin-learning.controller';
import { LearningSessionService } from './learning-session.service'; import { LearningSessionService } from './learning-session.service';
import { LearningSessionRepository } from './learning-session.repository'; import { LearningSessionRepository } from './learning-session.repository';
@Module({ @Module({
controllers: [LearningSessionController], controllers: [LearningSessionController, AdminLearningController],
providers: [LearningSessionService, LearningSessionRepository], providers: [LearningSessionService, LearningSessionRepository],
exports: [LearningSessionService], exports: [LearningSessionService],
}) })