feat(edit): P2 条目就地编辑 + P3 备份/审计/改密
- 迁移 v17:entry.updated_at(乐观锁)+ audit 表
- GET/PATCH /api/items/{key}:字段编辑、标签替换、409 冲突、400 校验
- 详情页 [编辑] → Drawer 表单(标题/简介/标签/评价/正文/封面/跳转),保存后局部刷新
- GET /api/backup:VACUUM INTO 一致性快照,附件下载
- /admin 系统 Tab:修改密码(清空会话)、下载备份
- 审计:item.update / settings.update
- 测试 46 passed(entries 4 例)
- 文档:ADMIN-PLAN P2/P3 记录、README
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 条目编辑的前端接口。
|
||||
*
|
||||
* 读取是公开的(编辑表单要能预览),写入必须登录,并用 `updatedAt` 做乐观锁。
|
||||
*/
|
||||
export type EntryRecord = {
|
||||
key: string
|
||||
name: string
|
||||
description: string
|
||||
icon: string
|
||||
href: string
|
||||
cover: string
|
||||
banner: string
|
||||
badge: string
|
||||
comment: string
|
||||
summary: string
|
||||
body: string
|
||||
tags: string[]
|
||||
updated_at: number
|
||||
}
|
||||
|
||||
export type EntryInput = {
|
||||
name: string
|
||||
description: string
|
||||
tags: string[]
|
||||
badge: string
|
||||
comment: string
|
||||
summary: string
|
||||
body: string
|
||||
cover: string
|
||||
banner: string
|
||||
href: string
|
||||
}
|
||||
|
||||
export type ItemResult =
|
||||
| { ok: true; entry: EntryRecord }
|
||||
| { ok: false; status: number; error: string }
|
||||
|
||||
export async function fetchItem(key: string): Promise<ItemResult> {
|
||||
try {
|
||||
const response = await fetch(`/api/items/${encodeURIComponent(key)}`, {
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
if (response.ok) {
|
||||
return { ok: true, entry: (await response.json()) as EntryRecord }
|
||||
}
|
||||
const payload = (await response.json().catch(() => null)) as { error?: string } | null
|
||||
return { ok: false, status: response.status, error: payload?.error ?? '读取失败' }
|
||||
} catch (error) {
|
||||
return { ok: false, status: 0, error: error instanceof Error ? error.message : '网络错误' }
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveItem(
|
||||
key: string,
|
||||
updatedAt: number,
|
||||
item: EntryInput,
|
||||
): Promise<ItemResult> {
|
||||
try {
|
||||
const response = await fetch(`/api/items/${encodeURIComponent(key)}`, {
|
||||
method: 'PATCH',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ updatedAt, item }),
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
return { ok: true, entry: (await response.json()) as EntryRecord }
|
||||
}
|
||||
|
||||
const payload = (await response.json().catch(() => null)) as { error?: string } | null
|
||||
return {
|
||||
ok: false,
|
||||
status: response.status,
|
||||
error: payload?.error ?? `保存失败(${response.status})`,
|
||||
}
|
||||
} catch (error) {
|
||||
return { ok: false, status: 0, error: error instanceof Error ? error.message : '网络错误' }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user