feat(0.2.1): M2 服务端同步接入 —— /api/powersync token+upload 幂等上行, 项目/任务/拷贝链路接入 uuid 与编号分配 (identity 助手)

This commit is contained in:
jif2.zhang
2026-10-25 23:49:09 +08:00
parent 61d3028909
commit a4ab22588c
6 changed files with 547 additions and 45 deletions
+26 -23
View File
@@ -8,6 +8,7 @@ import {
import { resolveTaskWrite } from '../task-domain/resolveStatus.js'
import { filterVisible } from '../task-domain/retention.js'
import { copyTask } from '../task-domain/copyTask.js'
import { genUuid, withUuid, withTaskNumber } from '../task-domain/identity.js'
const router = Router()
router.use(authRequired)
@@ -101,28 +102,30 @@ router.post('/projects/:id/tasks', async (req, res, next) => {
}
const firstCol = columns[0]
const row = await prisma.task.create({
data: {
projectId: project.id,
columnId: firstCol.id,
parentId: pid,
title: cleanTitle,
description: String(description || ''),
kind: kind ? String(kind).trim().slice(0, 20) : null,
markDate: mark,
status: firstCol.boundStatus || 'todo',
assigneeId: finalAssignee,
creatorId: userId,
extras: [],
members: {
create: (Array.isArray(memberIds) ? memberIds : [])
.map(Number).filter((id) => memberSet.has(id))
.map((uid) => ({ userId: uid })),
},
tags: { create: (Array.isArray(tags) ? tags : []).filter(Boolean).map((n) => ({ name: String(n).slice(0, 20) })) },
const taskData = {
uuid: req.body?.uuid || genUuid(),
projectId: project.id,
columnId: firstCol.id,
parentId: pid,
title: cleanTitle,
description: String(description || ''),
kind: kind ? String(kind).trim().slice(0, 20) : null,
markDate: mark,
status: firstCol.boundStatus || 'todo',
assigneeId: finalAssignee,
creatorId: userId,
extras: [],
members: {
create: withUuid((Array.isArray(memberIds) ? memberIds : [])
.map(Number).filter((id) => memberSet.has(id))
.map((uid) => ({ userId: uid }))),
},
include: TASK_INCLUDE,
})
tags: { create: withUuid((Array.isArray(tags) ? tags : []).filter(Boolean).map((n) => ({ name: String(n).slice(0, 20) }))) },
}
// 项目内自增编号 (展示 `code-number`): 服务端分配, 并发冲突时重取 max+1 重试
const { value: row } = await withTaskNumber(project.id, (number) =>
prisma.task.create({ data: { ...taskData, number }, include: TASK_INCLUDE }),
)
res.json({ item: shape(row) })
} catch (e) { next(e) }
})
@@ -250,7 +253,7 @@ router.put('/tasks/:id', async (req, res, next) => {
await prisma.taskMember.deleteMany({ where: { taskId: task.id } })
if (ids.length) {
await prisma.taskMember.createMany({
data: ids.map((uid) => ({ taskId: task.id, userId: uid })),
data: withUuid(ids.map((uid) => ({ taskId: task.id, userId: uid }))),
})
}
}
@@ -258,7 +261,7 @@ router.put('/tasks/:id', async (req, res, next) => {
await prisma.taskTag.deleteMany({ where: { taskId: task.id } })
const names = body.tags.map((n) => String(n).trim()).filter(Boolean).slice(0, 10)
if (names.length) {
await prisma.taskTag.createMany({ data: names.map((name) => ({ taskId: task.id, name: name.slice(0, 20) })) })
await prisma.taskTag.createMany({ data: withUuid(names.map((name) => ({ taskId: task.id, name: name.slice(0, 20) }))) })
}
}