/** * IIT 项目管理控制器 */ import { FastifyRequest, FastifyReply } from 'fastify'; import { getIitProjectService, CreateProjectInput, UpdateProjectInput } from './iitProjectService.js'; import { prisma } from '../../../config/database.js'; import { logger } from '../../../common/logging/index.js'; // ==================== 类型定义 ==================== interface ProjectIdParams { id: string; } interface ListProjectsQuery { status?: string; search?: string; } interface TestConnectionBody { redcapUrl: string; redcapApiToken: string; } interface LinkKbBody { knowledgeBaseId: string; } // ==================== 控制器函数 ==================== /** * 获取项目列表 */ export async function listProjects( request: FastifyRequest<{ Querystring: ListProjectsQuery }>, reply: FastifyReply ) { try { const { status, search } = request.query; const service = getIitProjectService(prisma); const projects = await service.listProjects({ status, search }); return reply.send({ success: true, data: projects, }); } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error('获取 IIT 项目列表失败', { error: message }); return reply.status(500).send({ success: false, error: message, }); } } /** * 获取项目详情 */ export async function getProject( request: FastifyRequest<{ Params: ProjectIdParams }>, reply: FastifyReply ) { try { const { id } = request.params; const service = getIitProjectService(prisma); const project = await service.getProject(id); if (!project) { return reply.status(404).send({ success: false, error: '项目不存在', }); } return reply.send({ success: true, data: project, }); } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error('获取 IIT 项目详情失败', { error: message }); return reply.status(500).send({ success: false, error: message, }); } } /** * 创建项目 */ export async function createProject( request: FastifyRequest<{ Body: CreateProjectInput }>, reply: FastifyReply ) { try { const input = request.body; // 验证必填字段 if (!input.name) { return reply.status(400).send({ success: false, error: '项目名称为必填项', }); } if (!input.redcapUrl || !input.redcapProjectId || !input.redcapApiToken) { return reply.status(400).send({ success: false, error: 'REDCap 配置信息为必填项(URL、项目ID、API Token)', }); } const service = getIitProjectService(prisma); const project = await service.createProject(input); return reply.status(201).send({ success: true, data: project, }); } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error('创建 IIT 项目失败', { error: message }); if (message.includes('连接测试失败')) { return reply.status(400).send({ success: false, error: message, }); } return reply.status(500).send({ success: false, error: message, }); } } /** * 更新项目 */ export async function updateProject( request: FastifyRequest<{ Params: ProjectIdParams; Body: UpdateProjectInput }>, reply: FastifyReply ) { try { const { id } = request.params; const input = request.body; const service = getIitProjectService(prisma); const project = await service.updateProject(id, input); return reply.send({ success: true, data: project, }); } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error('更新 IIT 项目失败', { error: message }); if (message.includes('连接测试失败')) { return reply.status(400).send({ success: false, error: message, }); } return reply.status(500).send({ success: false, error: message, }); } } /** * 删除项目 */ export async function deleteProject( request: FastifyRequest<{ Params: ProjectIdParams }>, reply: FastifyReply ) { try { const { id } = request.params; const service = getIitProjectService(prisma); await service.deleteProject(id); return reply.send({ success: true, message: '删除成功', }); } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error('删除 IIT 项目失败', { error: message }); return reply.status(500).send({ success: false, error: message, }); } } /** * 测试 REDCap 连接(新配置) */ export async function testConnection( request: FastifyRequest<{ Body: TestConnectionBody }>, reply: FastifyReply ) { try { const { redcapUrl, redcapApiToken } = request.body; if (!redcapUrl || !redcapApiToken) { return reply.status(400).send({ success: false, error: '请提供 REDCap URL 和 API Token', }); } const service = getIitProjectService(prisma); const result = await service.testConnection(redcapUrl, redcapApiToken); return reply.send({ success: result.success, data: result, }); } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error('测试 REDCap 连接失败', { error: message }); return reply.status(500).send({ success: false, error: message, }); } } /** * 测试项目的 REDCap 连接 */ export async function testProjectConnection( request: FastifyRequest<{ Params: ProjectIdParams }>, reply: FastifyReply ) { try { const { id } = request.params; const service = getIitProjectService(prisma); const result = await service.testProjectConnection(id); return reply.send({ success: result.success, data: result, }); } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error('测试项目连接失败', { error: message }); return reply.status(500).send({ success: false, error: message, }); } } /** * 同步 REDCap 元数据 */ export async function syncMetadata( request: FastifyRequest<{ Params: ProjectIdParams }>, reply: FastifyReply ) { try { const { id } = request.params; const service = getIitProjectService(prisma); const result = await service.syncMetadata(id); return reply.send({ success: true, data: result, }); } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error('同步元数据失败', { error: message }); return reply.status(500).send({ success: false, error: message, }); } } /** * 获取字段元数据列表 */ export async function listFieldMetadata( request: FastifyRequest<{ Params: ProjectIdParams; Querystring: { formName?: string; search?: string }; }>, reply: FastifyReply ) { try { const { id } = request.params; const { formName, search } = request.query as { formName?: string; search?: string }; const where: any = { projectId: id }; if (formName) { where.formName = formName; } if (search) { where.OR = [ { fieldName: { contains: search, mode: 'insensitive' } }, { fieldLabel: { contains: search, mode: 'insensitive' } }, ]; } const [fields, total] = await Promise.all([ prisma.iitFieldMetadata.findMany({ where, orderBy: [{ formName: 'asc' }, { fieldName: 'asc' }], }), prisma.iitFieldMetadata.count({ where }), ]); const forms = await prisma.iitFieldMetadata.findMany({ where: { projectId: id }, select: { formName: true }, distinct: ['formName'], orderBy: { formName: 'asc' }, }); return reply.send({ success: true, data: { fields, total, forms: forms.map(f => f.formName), }, }); } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error('获取字段元数据失败', { error: message }); return reply.status(500).send({ success: false, error: message, }); } } /** * 关联知识库 */ export async function linkKnowledgeBase( request: FastifyRequest<{ Params: ProjectIdParams; Body: LinkKbBody }>, reply: FastifyReply ) { try { const { id } = request.params; const { knowledgeBaseId } = request.body; if (!knowledgeBaseId) { return reply.status(400).send({ success: false, error: '请提供知识库 ID', }); } const service = getIitProjectService(prisma); await service.linkKnowledgeBase(id, knowledgeBaseId); return reply.send({ success: true, message: '关联成功', }); } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error('关联知识库失败', { error: message }); return reply.status(500).send({ success: false, error: message, }); } } /** * 解除知识库关联 */ export async function unlinkKnowledgeBase( request: FastifyRequest<{ Params: ProjectIdParams }>, reply: FastifyReply ) { try { const { id } = request.params; const service = getIitProjectService(prisma); await service.unlinkKnowledgeBase(id); return reply.send({ success: true, message: '解除关联成功', }); } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error('解除知识库关联失败', { error: message }); return reply.status(500).send({ success: false, error: message, }); } }