feat: Day 8-9 - Project Management API completed

Backend:
- Create project routes (GET, POST, PUT, DELETE)
- Implement projectController with CRUD operations
- Create projectService for database operations
- Add validation middleware for request validation
- Update Prisma schema (add background, researchType, deletedAt fields)
- Implement soft delete for projects

Frontend:
- Create projectApi service module
- Update useProjectStore with fetchProjects and loading state
- Connect ProjectSelector to real API with loading indicator
- Connect CreateProjectDialog to real API with error handling
- Connect EditProjectDialog to real API with loading state
- Add comprehensive error handling and user feedback

Build: Both frontend and backend build successfully
This commit is contained in:
AI Clinical Dev Team
2025-10-10 19:38:18 +08:00
parent e9e19064e2
commit b72167f73e
11 changed files with 613 additions and 42 deletions

View File

@@ -0,0 +1,110 @@
import { FastifyRequest, FastifyReply } from 'fastify';
interface CreateProjectBody {
name: string;
background?: string;
researchType: 'observational' | 'interventional';
}
interface UpdateProjectBody {
name?: string;
background?: string;
researchType?: 'observational' | 'interventional';
}
// 验证创建项目请求
export async function validateProjectCreate(request: FastifyRequest, reply: FastifyReply) {
const body = request.body as CreateProjectBody;
// 验证必填字段
if (!body.name || typeof body.name !== 'string') {
return reply.code(400).send({
success: false,
message: '项目名称为必填项',
});
}
if (body.name.trim().length === 0) {
return reply.code(400).send({
success: false,
message: '项目名称不能为空',
});
}
if (body.name.length > 100) {
return reply.code(400).send({
success: false,
message: '项目名称不能超过100个字符',
});
}
// 验证研究类型
if (!body.researchType) {
return reply.code(400).send({
success: false,
message: '研究类型为必填项',
});
}
if (!['observational', 'interventional'].includes(body.researchType)) {
return reply.code(400).send({
success: false,
message: '研究类型必须是observational或interventional',
});
}
// 验证项目背景(可选,但有长度限制)
if (body.background && body.background.length > 2000) {
return reply.code(400).send({
success: false,
message: '项目背景不能超过2000个字符',
});
}
}
// 验证更新项目请求
export async function validateProjectUpdate(request: FastifyRequest, reply: FastifyReply) {
const body = request.body as UpdateProjectBody;
// 至少需要更新一个字段
if (!body.name && !body.background && !body.researchType) {
return reply.code(400).send({
success: false,
message: '至少需要提供一个要更新的字段',
});
}
// 验证项目名称
if (body.name !== undefined) {
if (typeof body.name !== 'string' || body.name.trim().length === 0) {
return reply.code(400).send({
success: false,
message: '项目名称不能为空',
});
}
if (body.name.length > 100) {
return reply.code(400).send({
success: false,
message: '项目名称不能超过100个字符',
});
}
}
// 验证研究类型
if (body.researchType && !['observational', 'interventional'].includes(body.researchType)) {
return reply.code(400).send({
success: false,
message: '研究类型必须是observational或interventional',
});
}
// 验证项目背景
if (body.background && body.background.length > 2000) {
return reply.code(400).send({
success: false,
message: '项目背景不能超过2000个字符',
});
}
}