Files
AIclinicalresearch/backend/src/common/prompt/prompt.controller.ts
HaHafeng d595037316 feat(admin): Complete tenant management and module access control system
Major Features:
- Tenant management CRUD (list, create, edit, delete, module configuration)
- Dynamic module management system (modules table with 8 modules)
- Multi-tenant module permission merging (ModuleService)
- Module access control middleware (requireModule)
- User module permission API (GET /api/v1/auth/me/modules)
- Frontend module permission filtering (HomePage + TopNavigation)

Module Integration:
- RVW module integrated with PromptService (editorial + methodology)
- All modules (RVW/PKB/ASL/DC) added authenticate + requireModule middleware
- Fixed ReviewTask foreign key constraint (cross-schema issue)
- Removed all MOCK_USER_ID, unified to request.user?.userId

Prompt Management Enhancements:
- Module names displayed in Chinese (RVW -> 智能审稿)
- Enhanced version history with view content and rollback features
- List page shows both activeVersion and draftVersion columns

Database Changes:
- Added platform_schema.modules table
- Modified tenant_modules table (added index and UUID)
- Removed ReviewTask foreign key to public.users (cross-schema fix)
- Seeded 8 modules: RVW, PKB, ASL, DC, IIT, AIA, SSA, ST

Documentation Updates:
- Updated ADMIN module development status
- Updated TODO checklist (89% progress)
- Updated Prompt management plan (Phase 3.5.5 completed)
- Added module authentication specification

Files Changed: 80+
Status: All features tested and verified locally
Next: User management module development
2026-01-13 07:34:30 +08:00

442 lines
11 KiB
TypeScript

/**
* Prompt 管理 API 控制器
*/
import type { FastifyRequest, FastifyReply } from 'fastify';
import { getPromptService } from './prompt.service.js';
import type { ModelConfig } from './prompt.types.js';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// 请求类型定义
interface ListPromptsQuery {
module?: string;
}
interface GetPromptParams {
code: string;
}
interface SaveDraftBody {
content: string;
modelConfig?: ModelConfig;
changelog?: string;
}
interface PublishBody {
// 可扩展
}
interface RollbackBody {
version: number;
}
interface SetDebugModeBody {
modules: string[];
enabled: boolean;
}
interface TestRenderBody {
content: string;
variables: Record<string, any>;
}
/**
* 获取 Prompt 列表
* GET /api/admin/prompts?module=RVW
*/
export async function listPrompts(
request: FastifyRequest<{ Querystring: ListPromptsQuery }>,
reply: FastifyReply
) {
try {
const { module } = request.query;
const promptService = getPromptService(prisma);
const templates = await promptService.listTemplates(module);
// 转换为 API 响应格式,分别返回 ACTIVE 和 DRAFT 版本
const result = templates.map(t => {
const activeVersion = t.versions.find(v => v.status === 'ACTIVE');
const draftVersion = t.versions.find(v => v.status === 'DRAFT');
// 调试日志
console.log(`[PromptController] ${t.code} 版本数量: ${t.versions.length}`);
console.log(` - ACTIVE: ${activeVersion ? 'v' + activeVersion.version : '无'}`);
console.log(` - DRAFT: ${draftVersion ? 'v' + draftVersion.version : '无'}`);
return {
id: t.id,
code: t.code,
name: t.name,
module: t.module,
description: t.description,
variables: t.variables,
activeVersion: activeVersion ? {
version: activeVersion.version,
status: activeVersion.status,
createdAt: activeVersion.created_at,
} : null,
draftVersion: draftVersion ? {
version: draftVersion.version,
status: draftVersion.status,
createdAt: draftVersion.created_at,
} : null,
latestVersion: t.versions[0] ? {
version: t.versions[0].version,
status: t.versions[0].status,
createdAt: t.versions[0].created_at,
} : null,
updatedAt: t.updated_at,
};
});
console.log('[PromptController] 返回数据示例:', JSON.stringify(result[0], null, 2));
return reply.send({
success: true,
data: result,
total: result.length,
});
} catch (error) {
console.error('[PromptController] listPrompts error:', error);
return reply.status(500).send({
success: false,
error: 'Failed to list prompts',
});
}
}
/**
* 获取 Prompt 详情(含版本历史)
* GET /api/admin/prompts/:code
*/
export async function getPromptDetail(
request: FastifyRequest<{ Params: GetPromptParams }>,
reply: FastifyReply
) {
try {
const { code } = request.params;
const promptService = getPromptService(prisma);
const template = await promptService.getTemplateDetail(code);
if (!template) {
return reply.status(404).send({
success: false,
error: 'Prompt not found',
});
}
// 转换版本历史
const versions = template.versions.map(v => ({
id: v.id,
version: v.version,
status: v.status,
content: v.content,
modelConfig: v.model_config,
changelog: v.changelog,
createdBy: v.created_by,
createdAt: v.created_at,
}));
return reply.send({
success: true,
data: {
id: template.id,
code: template.code,
name: template.name,
module: template.module,
description: template.description,
variables: template.variables,
versions,
createdAt: template.created_at,
updatedAt: template.updated_at,
},
});
} catch (error) {
console.error('[PromptController] getPromptDetail error:', error);
return reply.status(500).send({
success: false,
error: 'Failed to get prompt detail',
});
}
}
/**
* 保存草稿
* POST /api/admin/prompts/:code/draft
*
* 需要权限: prompt:edit
*/
export async function saveDraft(
request: FastifyRequest<{ Params: GetPromptParams; Body: SaveDraftBody }>,
reply: FastifyReply
) {
try {
const { code } = request.params;
const { content, modelConfig, changelog } = request.body;
const userId = (request as any).user?.userId;
const promptService = getPromptService(prisma);
// 保存草稿
const draft = await promptService.saveDraft(
code,
content,
modelConfig,
changelog,
userId
);
// 提取变量信息
const variables = promptService.extractVariables(content);
return reply.send({
success: true,
data: {
id: draft.id,
version: draft.version,
status: draft.status,
variables,
message: 'Draft saved successfully',
},
});
} catch (error: any) {
console.error('[PromptController] saveDraft error:', error);
return reply.status(400).send({
success: false,
error: error.message || 'Failed to save draft',
});
}
}
/**
* 发布 Prompt
* POST /api/admin/prompts/:code/publish
*
* 需要权限: prompt:publish
*/
export async function publishPrompt(
request: FastifyRequest<{ Params: GetPromptParams; Body: PublishBody }>,
reply: FastifyReply
) {
try {
const { code } = request.params;
const userId = (request as any).user?.userId;
const promptService = getPromptService(prisma);
const published = await promptService.publish(code, userId);
return reply.send({
success: true,
data: {
version: published.version,
status: 'ACTIVE',
message: `Prompt ${code} published successfully (v${published.version})`,
},
});
} catch (error: any) {
console.error('[PromptController] publishPrompt error:', error);
return reply.status(400).send({
success: false,
error: error.message || 'Failed to publish prompt',
});
}
}
/**
* 回滚到指定版本
* POST /api/admin/prompts/:code/rollback
*
* 需要权限: prompt:publish
*/
export async function rollbackPrompt(
request: FastifyRequest<{ Params: GetPromptParams; Body: RollbackBody }>,
reply: FastifyReply
) {
try {
const { code } = request.params;
const { version } = request.body;
const userId = (request as any).user?.userId;
const promptService = getPromptService(prisma);
const rolled = await promptService.rollback(code, version, userId);
return reply.send({
success: true,
data: {
version: rolled.version,
status: 'ACTIVE',
message: `Prompt ${code} rolled back to v${version}`,
},
});
} catch (error: any) {
console.error('[PromptController] rollbackPrompt error:', error);
return reply.status(400).send({
success: false,
error: error.message || 'Failed to rollback prompt',
});
}
}
/**
* 设置调试模式
* POST /api/admin/prompts/debug
*
* 需要权限: prompt:debug
*/
export async function setDebugMode(
request: FastifyRequest<{ Body: SetDebugModeBody }>,
reply: FastifyReply
) {
try {
const { modules, enabled } = request.body;
const userId = (request as any).user?.userId;
if (!userId) {
return reply.status(401).send({
success: false,
error: 'User not authenticated',
});
}
const promptService = getPromptService(prisma);
promptService.setDebugMode(userId, modules, enabled);
const state = promptService.getDebugState(userId);
return reply.send({
success: true,
data: {
userId,
modules: state ? Array.from(state.modules) : [],
enabled,
message: enabled
? `Debug mode enabled for modules: [${modules.join(', ')}]`
: 'Debug mode disabled',
},
});
} catch (error: any) {
console.error('[PromptController] setDebugMode error:', error);
return reply.status(500).send({
success: false,
error: error.message || 'Failed to set debug mode',
});
}
}
/**
* 获取当前用户的调试状态
* GET /api/admin/prompts/debug
*/
export async function getDebugStatus(
request: FastifyRequest,
reply: FastifyReply
) {
try {
const userId = (request as any).user?.userId;
if (!userId) {
return reply.status(401).send({
success: false,
error: 'User not authenticated',
});
}
const promptService = getPromptService(prisma);
const state = promptService.getDebugState(userId);
return reply.send({
success: true,
data: {
userId,
isDebugging: !!state,
modules: state ? Array.from(state.modules) : [],
enabledAt: state?.enabledAt,
},
});
} catch (error: any) {
console.error('[PromptController] getDebugStatus error:', error);
return reply.status(500).send({
success: false,
error: error.message || 'Failed to get debug status',
});
}
}
/**
* 测试渲染 Prompt
* POST /api/admin/prompts/test-render
*/
export async function testRender(
request: FastifyRequest<{ Body: TestRenderBody }>,
reply: FastifyReply
) {
try {
const { content, variables } = request.body;
const promptService = getPromptService(prisma);
// 提取变量
const extractedVars = promptService.extractVariables(content);
// 校验变量
const validation = promptService.validateVariables(content, variables);
// 渲染
const rendered = promptService.render(content, variables);
return reply.send({
success: true,
data: {
rendered,
extractedVariables: extractedVars,
validation,
},
});
} catch (error: any) {
console.error('[PromptController] testRender error:', error);
return reply.status(400).send({
success: false,
error: error.message || 'Failed to render prompt',
});
}
}
/**
* 清除缓存
* POST /api/admin/prompts/:code/invalidate-cache
*/
export async function invalidateCache(
request: FastifyRequest<{ Params: GetPromptParams }>,
reply: FastifyReply
) {
try {
const { code } = request.params;
const promptService = getPromptService(prisma);
promptService.invalidateCache(code);
return reply.send({
success: true,
data: {
code,
message: `Cache invalidated for ${code}`,
},
});
} catch (error: any) {
console.error('[PromptController] invalidateCache error:', error);
return reply.status(500).send({
success: false,
error: error.message || 'Failed to invalidate cache',
});
}
}