feat(V0.2): 任务模块 —— 项目 / 列表与看板 / 列-状态绑定 / 一级子任务
Docker Build and Push / build-image (push) Failing after 3m51s
Docker Build and Push / build-image (push) Failing after 3m51s
需求 142 条 (T-*), 设计见 docs/V0.2/任务模块/ 数据层: - 新增 6 张表: Project / ProjectMember / BoardColumn / Task / TaskMember / TaskTag - 看板列是实体(可增删改排序), Task.columnId 外键 RESTRICT 防误删列丢任务 - Task.status(todo/doing/done) 与 columnId 双落库, 二者正交 领域层 server/task-domain/ (唯一口径, 前后端不重复实现): - resolveStatus: 列决定状态(只看目标列, 无拖出回退); 状态与列不匹配时按列序取第一个绑定该状态的列自动归位, 无匹配列则不移动; 子任务完全豁免 - retention: 逐列 6 档保留窗口(永不显示/当天/3天/本周/本月/永久显示), 以 doneAt 判定 - copyTask: 跨项目深拷贝, 人员按目标项目成员过滤并回报丢弃名单 接口: /api/projects, /api/columns, /api/tasks (均校验项目成员) 前端: - 底部 Tab 插入「任务」, 位于「打卡」之后; 默认首页仍为打卡 - 列表视图(子任务缩进+折叠) / 看板视图(长按拖拽换列、拖到卡片降为子任务) - 列设置(列管理+状态绑定+保留选项) / 任务详情(扩展信息卡片默认收起) / 成员管理 - 视图记忆与上次项目存设备端 已验证: 95 文件语法 0 错误、相对导入 0 缺失、领域逻辑用例全部通过
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
// 任务模块: 项目成员校验 (T-PM-01, B-13)
|
||||
// 所有 /api/projects/:id/* 与任务写操作都必须确认请求者是项目成员.
|
||||
import { prisma } from '../db.js'
|
||||
|
||||
/** 取出当前用户可访问的项目 (不存在或非成员 -> null) */
|
||||
export async function loadProjectForUser(projectId, userId) {
|
||||
const id = Number(projectId)
|
||||
if (!Number.isInteger(id) || id <= 0) return null
|
||||
const project = await prisma.project.findFirst({
|
||||
where: { id, members: { some: { userId } } },
|
||||
})
|
||||
return project || null
|
||||
}
|
||||
|
||||
/** 任务 -> 校验其项目可访问, 返回 { task, project } */
|
||||
export async function loadTaskForUser(taskId, userId) {
|
||||
const id = Number(taskId)
|
||||
if (!Number.isInteger(id) || id <= 0) return null
|
||||
const task = await prisma.task.findFirst({
|
||||
where: { id, project: { members: { some: { userId } } } },
|
||||
include: {
|
||||
column: true,
|
||||
assignee: { select: { id: true, name: true } },
|
||||
creator: { select: { id: true, name: true } },
|
||||
members: { include: { user: { select: { id: true, name: true } } } },
|
||||
tags: true,
|
||||
children: {
|
||||
include: {
|
||||
column: true,
|
||||
assignee: { select: { id: true, name: true } },
|
||||
members: { include: { user: { select: { id: true, name: true } } } },
|
||||
tags: true,
|
||||
},
|
||||
orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }],
|
||||
},
|
||||
},
|
||||
})
|
||||
if (!task) return null
|
||||
const project = await prisma.project.findUnique({ where: { id: task.projectId } })
|
||||
return { task, project }
|
||||
}
|
||||
|
||||
/** 项目全部列 (按显示顺序) */
|
||||
export async function loadColumns(projectId) {
|
||||
return prisma.boardColumn.findMany({
|
||||
where: { projectId: Number(projectId) },
|
||||
orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }],
|
||||
})
|
||||
}
|
||||
|
||||
/** 项目成员 userId 集合 */
|
||||
export async function loadMemberIds(projectId) {
|
||||
const rows = await prisma.projectMember.findMany({
|
||||
where: { projectId: Number(projectId) },
|
||||
select: { userId: true },
|
||||
})
|
||||
return rows.map((r) => r.userId)
|
||||
}
|
||||
Reference in New Issue
Block a user