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
442 lines
11 KiB
TypeScript
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',
|
|
});
|
|
}
|
|
}
|
|
|
|
|