Merge branch 'dev-0.2.1' into dev-0.2

# Conflicts:
#	generated/prisma/internal/class.ts
#	generated/prisma/internal/prismaNamespace.ts
#	generated/prisma/internal/prismaNamespaceBrowser.ts
#	generated/prisma/models/BoardColumn.ts
#	generated/prisma/models/Project.ts
#	generated/prisma/models/ProjectMember.ts
#	generated/prisma/models/Task.ts
#	generated/prisma/models/TaskMember.ts
#	generated/prisma/models/TaskTag.ts
#	generated/prisma/models/User.ts
#	server/routes/projects.js
#	server/routes/tasks.js
#	src/api/task.js
This commit is contained in:
jif2.zhang
2026-10-26 20:46:25 +08:00
95 changed files with 8694 additions and 1609 deletions
+44 -5
View File
@@ -3,6 +3,7 @@ import { Router } from 'express'
import { prisma } from '../db.js'
import { authRequired } from '../middleware.js'
import { loadProjectForUser, loadColumns, loadMemberIds } from '../task-domain/access.js'
import { genUuid, withUuid, normProjectCode, allocProjectCode } from '../task-domain/identity.js'
const router = Router()
router.use(authRequired)
@@ -21,7 +22,7 @@ const MAX_NAME = 20
/** 建 4 个默认列 */
async function createDefaultColumns(projectId) {
await prisma.boardColumn.createMany({
data: DEFAULT_COLUMNS.map((c) => ({ ...c, projectId, retention: 'forever' })),
data: withUuid(DEFAULT_COLUMNS.map((c) => ({ ...c, projectId, retention: 'forever' }))),
})
}
@@ -32,7 +33,14 @@ export async function ensurePersonalProject(userId) {
const user = await prisma.user.findUnique({ where: { id: userId }, select: { name: true } })
const name = `${user?.name || '我'}的任务`
const project = await prisma.project.create({
data: { name, ownerId: userId, isPersonal: true, members: { create: { userId } } },
data: {
name,
ownerId: userId,
isPersonal: true,
uuid: genUuid(),
code: await allocProjectCode(name),
members: { create: { uuid: genUuid(), userId } },
},
})
await createDefaultColumns(project.id)
return project
@@ -58,6 +66,8 @@ export function normTaskKinds(v) {
function projectSummary(p) {
return {
id: p.id,
uuid: p.uuid,
code: p.code,
name: p.name,
ownerId: p.ownerId,
isPersonal: p.isPersonal,
@@ -96,8 +106,27 @@ router.post('/', async (req, res, next) => {
if (!name) return res.status(400).json({ error: '项目名称不能为空' })
if (name.length > MAX_NAME) return res.status(400).json({ error: `项目名称不能超过 ${MAX_NAME} 字` })
// 项目短编号: 可由客户端自拟 (2~8 位字母数字, 全局唯一), 未提供时按名称自动分配
let code
const rawCode = req.body?.code
if (rawCode !== undefined && rawCode !== null && String(rawCode).trim() !== '') {
code = normProjectCode(rawCode)
if (!code) return res.status(400).json({ error: '项目编号须为 2~8 位字母数字' })
const dup = await prisma.project.findUnique({ where: { code } })
if (dup) return res.status(409).json({ error: `项目编号 ${code} 已被占用` })
} else {
code = await allocProjectCode(name)
}
const project = await prisma.project.create({
data: { name, ownerId: userId, isPersonal: false, members: { create: { userId } } },
data: {
name,
ownerId: userId,
isPersonal: false,
uuid: genUuid(),
code,
members: { create: { uuid: genUuid(), userId } },
},
})
await createDefaultColumns(project.id)
const full = await prisma.project.findUnique({
@@ -139,7 +168,7 @@ router.put('/:id', async (req, res, next) => {
const userId = req.user.sub
const project = await loadProjectForUser(req.params.id, userId)
if (!project) return res.status(404).json({ error: '项目不存在或无权限' })
const { name, archived, notes, taskKinds, icon, autoArchive } = req.body || {}
const { name, archived, notes, taskKinds, icon, autoArchive, code } = req.body || {}
const data = {}
if (name !== undefined) {
const t = String(name).trim()
@@ -147,6 +176,16 @@ router.put('/:id', async (req, res, next) => {
if (t.length > MAX_NAME) return res.status(400).json({ error: `项目名称不能超过 ${MAX_NAME} 字` })
data.name = t
}
// 项目短编号: 可修改 (2~8 位字母数字, 全局唯一)
if (code !== undefined) {
const c = normProjectCode(code)
if (!c) return res.status(400).json({ error: '项目编号须为 2~8 位字母数字' })
if (c !== project.code) {
const dup = await prisma.project.findUnique({ where: { code: c } })
if (dup) return res.status(409).json({ error: `项目编号 ${c} 已被占用` })
data.code = c
}
}
// 任务类型标签: 整体替换; 空数组回落默认
if (taskKinds !== undefined) {
if (!Array.isArray(taskKinds)) return res.status(400).json({ error: '任务类型格式非法' })
@@ -215,7 +254,7 @@ router.post('/:id/members', async (req, res, next) => {
})
if (exist) return res.status(409).json({ error: '该用户已是项目成员' })
await prisma.projectMember.create({ data: { projectId: project.id, userId: target.id } })
await prisma.projectMember.create({ data: { uuid: genUuid(), projectId: project.id, userId: target.id } })
res.json({ ok: true, member: { userId: target.id, name: target.name } })
} catch (e) { next(e) }
})