fix(0.2.1): upload 契约对齐客户端 —— 外键列即父行 uuid (projectId/columnId/parentId/taskId); 附 Node 闭环验证脚本
This commit is contained in:
+16
-15
@@ -31,7 +31,8 @@ router.post('/token', authRequired, async (req, res, next) => {
|
||||
// ---------- 上传队列 ----------
|
||||
// 契约: POST { ops: [{ id, table, op: 'PUT'|'DELETE', uuid, data }] }
|
||||
// table ∈ Project | BoardColumn | ProjectMember | Task | TaskMember | TaskTag
|
||||
// data 为客户端本地全量行; 父引用走 *Uuid 字段 (projectUuid / columnUuid / parentUuid / taskUuid)
|
||||
// data 为客户端本地全量行 (列名与客户端 schema 一致); 外键列 (projectId/columnId/parentId/taskId)
|
||||
// 的值即父行的本地 id = uuid, 服务端据此解析为内部自增 id
|
||||
// 返回 { results: [{ id, ok, skipped?, error? }] }, 单条失败不影响同批其它 op
|
||||
const TABLES = ['Project', 'BoardColumn', 'ProjectMember', 'Task', 'TaskMember', 'TaskTag']
|
||||
const TABLE_RANK = Object.fromEntries(TABLES.map((t, i) => [t, i]))
|
||||
@@ -120,7 +121,7 @@ async function applyPut(op, ctx) {
|
||||
if (Object.keys(data).length) await prisma.boardColumn.update({ where: { uuid }, data })
|
||||
return {}
|
||||
}
|
||||
const project = await prisma.project.findUnique({ where: { uuid: d.projectUuid } })
|
||||
const project = await prisma.project.findUnique({ where: { uuid: d.projectId } })
|
||||
if (!project || !(await assertMember(project.id))) throw new Error('项目不存在或无权限')
|
||||
const name = String(d.name || '').trim()
|
||||
if (!name || name.length > 20) throw new Error('列名非法')
|
||||
@@ -143,7 +144,7 @@ async function applyPut(op, ctx) {
|
||||
// 成员关系只增不改: 已存在即视为完成 (幂等)
|
||||
return { skipped: true }
|
||||
}
|
||||
const project = await prisma.project.findUnique({ where: { uuid: d.projectUuid } })
|
||||
const project = await prisma.project.findUnique({ where: { uuid: d.projectId } })
|
||||
if (!project || !(await assertMember(project.id))) throw new Error('项目不存在或无权限')
|
||||
const target = Number(d.userId)
|
||||
if (!target) throw new Error('userId 非法')
|
||||
@@ -179,16 +180,16 @@ async function applyPut(op, ctx) {
|
||||
if (d.sortOrder !== undefined) data.sortOrder = Number(d.sortOrder) || 0
|
||||
if (d.assigneeId !== undefined) data.assigneeId = d.assigneeId ? Number(d.assigneeId) : null
|
||||
if (d.doneAt !== undefined) data.doneAt = isoTime(d.doneAt)
|
||||
if (d.columnUuid !== undefined) {
|
||||
const col = await prisma.boardColumn.findUnique({ where: { uuid: d.columnUuid } })
|
||||
if (d.columnId !== undefined) {
|
||||
const col = await prisma.boardColumn.findUnique({ where: { uuid: d.columnId } })
|
||||
if (!col || col.projectId !== existing.projectId) throw new Error('看板列不存在')
|
||||
data.columnId = col.id
|
||||
}
|
||||
if (d.parentUuid !== undefined) {
|
||||
if (!d.parentUuid) {
|
||||
if (d.parentId !== undefined) {
|
||||
if (!d.parentId) {
|
||||
data.parentId = null
|
||||
} else {
|
||||
const parent = await prisma.task.findUnique({ where: { uuid: d.parentUuid } })
|
||||
const parent = await prisma.task.findUnique({ where: { uuid: d.parentId } })
|
||||
if (!parent || parent.projectId !== existing.projectId) throw new Error('父任务不存在')
|
||||
if (parent.parentId) throw new Error('仅支持一级子任务')
|
||||
data.parentId = parent.id
|
||||
@@ -198,13 +199,13 @@ async function applyPut(op, ctx) {
|
||||
return {}
|
||||
}
|
||||
// 离线新建: 服务端解析父引用并分配项目内编号
|
||||
const project = await prisma.project.findUnique({ where: { uuid: d.projectUuid } })
|
||||
const project = await prisma.project.findUnique({ where: { uuid: d.projectId } })
|
||||
if (!project || !(await assertMember(project.id))) throw new Error('项目不存在或无权限')
|
||||
const title = String(d.title || '').trim()
|
||||
if (!title || title.length > 30) throw new Error('任务标题非法')
|
||||
let columnId = null
|
||||
if (d.columnUuid) {
|
||||
const col = await prisma.boardColumn.findUnique({ where: { uuid: d.columnUuid } })
|
||||
if (d.columnId) {
|
||||
const col = await prisma.boardColumn.findUnique({ where: { uuid: d.columnId } })
|
||||
if (!col || col.projectId !== project.id) throw new Error('看板列不存在')
|
||||
columnId = col.id
|
||||
} else {
|
||||
@@ -216,8 +217,8 @@ async function applyPut(op, ctx) {
|
||||
columnId = first.id
|
||||
}
|
||||
let parentId = null
|
||||
if (d.parentUuid) {
|
||||
const parent = await prisma.task.findUnique({ where: { uuid: d.parentUuid } })
|
||||
if (d.parentId) {
|
||||
const parent = await prisma.task.findUnique({ where: { uuid: d.parentId } })
|
||||
if (!parent || parent.projectId !== project.id) throw new Error('父任务不存在')
|
||||
if (parent.parentId) throw new Error('仅支持一级子任务')
|
||||
parentId = parent.id
|
||||
@@ -248,7 +249,7 @@ async function applyPut(op, ctx) {
|
||||
if (table === 'TaskMember') {
|
||||
const existing = await prisma.taskMember.findUnique({ where: { uuid } })
|
||||
if (existing) return { skipped: true }
|
||||
const task = await prisma.task.findUnique({ where: { uuid: d.taskUuid } })
|
||||
const task = await prisma.task.findUnique({ where: { uuid: d.taskId } })
|
||||
if (!task || !(await assertMember(task.projectId))) throw new Error('任务不存在或无权限')
|
||||
const uid = Number(d.userId)
|
||||
if (!uid) throw new Error('userId 非法')
|
||||
@@ -263,7 +264,7 @@ async function applyPut(op, ctx) {
|
||||
if (table === 'TaskTag') {
|
||||
const existing = await prisma.taskTag.findUnique({ where: { uuid } })
|
||||
if (existing) return { skipped: true }
|
||||
const task = await prisma.task.findUnique({ where: { uuid: d.taskUuid } })
|
||||
const task = await prisma.task.findUnique({ where: { uuid: d.taskId } })
|
||||
if (!task || !(await assertMember(task.projectId))) throw new Error('任务不存在或无权限')
|
||||
const name = String(d.name || '').trim().slice(0, 20)
|
||||
if (!name) throw new Error('标签名非法')
|
||||
|
||||
Reference in New Issue
Block a user