feat: 添加题目批量发布、归档和删除接口
- 新增 batchUpdateStatus 通用方法,带状态流转校验和 BatchResult 报告 - 改造 batchPublish 使用新方法,返回成功/失败详情 - 新增 batchArchive 和 batch-delete 端点(软删除) - 使用 inArray 批量查询和更新,优化数据库往返 - 更新 API 文档,补充三个批量接口说明
This commit is contained in:
parent
6a5490dea4
commit
1b142f2866
@ -1264,7 +1264,7 @@
|
||||
|
||||
#### POST /admin/questions/batch-publish
|
||||
|
||||
批量发布题目。
|
||||
批量发布题目(带状态流转校验,仅 reviewing 状态可发布)。
|
||||
|
||||
**认证**: Admin Token
|
||||
|
||||
@ -1275,15 +1275,77 @@
|
||||
}
|
||||
```
|
||||
|
||||
**参数说明**:
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| ids | string[] | 是 | 题目 ID 数组,1-200 个,每个为合法 UUID |
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": null,
|
||||
"data": {
|
||||
"total": 3,
|
||||
"succeeded": 2,
|
||||
"failed": [
|
||||
{ "id": "uuid3", "reason": "不允许从 draft 变更为 published" }
|
||||
]
|
||||
},
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
**data 字段说明**:
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| total | number | 提交的 ID 总数 |
|
||||
| succeeded | number | 成功更新的数量 |
|
||||
| failed | array | 失败记录列表(包含 id 和 reason) |
|
||||
|
||||
---
|
||||
|
||||
#### POST /admin/questions/batch-archive
|
||||
|
||||
批量归档题目(带状态流转校验,draft/reviewing/published 状态可归档)。
|
||||
|
||||
**认证**: Admin Token
|
||||
|
||||
**请求体**:
|
||||
```json
|
||||
{
|
||||
"ids": ["uuid1", "uuid2"]
|
||||
}
|
||||
```
|
||||
|
||||
**参数说明**:
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| ids | string[] | 是 | 题目 ID 数组,1-200 个,每个为合法 UUID |
|
||||
|
||||
**响应**: 与 batch-publish 相同的 `BatchResult` 格式。
|
||||
|
||||
---
|
||||
|
||||
#### POST /admin/questions/batch-delete
|
||||
|
||||
批量删除题目(软删除,等同于批量归档)。
|
||||
|
||||
**认证**: Admin Token
|
||||
|
||||
**请求体**:
|
||||
```json
|
||||
{
|
||||
"ids": ["uuid1", "uuid2"]
|
||||
}
|
||||
```
|
||||
|
||||
**参数说明**:
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| ids | string[] | 是 | 题目 ID 数组,1-200 个,每个为合法 UUID |
|
||||
|
||||
**响应**: 与 batch-publish 相同的 `BatchResult` 格式。
|
||||
|
||||
---
|
||||
|
||||
### 分类管理
|
||||
|
||||
@ -26,7 +26,7 @@ const updateQuestionSchema = z.object({
|
||||
status: z.enum(['draft', 'reviewing', 'published', 'archived']).optional(),
|
||||
});
|
||||
|
||||
const batchPublishSchema = z.object({ ids: z.array(z.string().uuid()) });
|
||||
const batchIdsSchema = z.object({ ids: z.array(z.string().uuid()).min(1).max(200) });
|
||||
|
||||
export async function adminQuestionsRoutes(app: FastifyInstance): Promise<void> {
|
||||
app.get('/', async (request) => {
|
||||
@ -96,11 +96,29 @@ export async function adminQuestionsRoutes(app: FastifyInstance): Promise<void>
|
||||
});
|
||||
|
||||
app.post('/batch-publish', async (request) => {
|
||||
const parsed = batchPublishSchema.safeParse(request.body);
|
||||
const parsed = batchIdsSchema.safeParse(request.body);
|
||||
if (!parsed.success) {
|
||||
return { success: false, data: null, error: { code: 'VALIDATION_ERROR', message: parsed.error.issues[0]?.message } };
|
||||
}
|
||||
await questionService.batchPublish(parsed.data.ids);
|
||||
return { success: true, data: null, error: null };
|
||||
const data = await questionService.batchPublish(parsed.data.ids);
|
||||
return { success: true, data, error: null };
|
||||
});
|
||||
|
||||
app.post('/batch-archive', async (request) => {
|
||||
const parsed = batchIdsSchema.safeParse(request.body);
|
||||
if (!parsed.success) {
|
||||
return { success: false, data: null, error: { code: 'VALIDATION_ERROR', message: parsed.error.issues[0]?.message } };
|
||||
}
|
||||
const data = await questionService.batchArchive(parsed.data.ids);
|
||||
return { success: true, data, error: null };
|
||||
});
|
||||
|
||||
app.post('/batch-delete', async (request) => {
|
||||
const parsed = batchIdsSchema.safeParse(request.body);
|
||||
if (!parsed.success) {
|
||||
return { success: false, data: null, error: { code: 'VALIDATION_ERROR', message: parsed.error.issues[0]?.message } };
|
||||
}
|
||||
const data = await questionService.batchArchive(parsed.data.ids);
|
||||
return { success: true, data, error: null };
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { db } from '../../db/client.js';
|
||||
import { questions, knowledgeCards } from '../../db/schema.js';
|
||||
import { eq, and, sql } from 'drizzle-orm';
|
||||
import { eq, and, sql, inArray } from 'drizzle-orm';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
interface ListOptions {
|
||||
@ -125,8 +125,49 @@ export async function updateQuestionStatus(id: string, newStatus: QuestionStatus
|
||||
return getQuestionById(id);
|
||||
}
|
||||
|
||||
export async function batchPublish(ids: string[]) {
|
||||
for (const id of ids) {
|
||||
await db.update(questions).set({ status: 'published' }).where(eq(questions.id, id));
|
||||
export interface BatchResult {
|
||||
total: number;
|
||||
succeeded: number;
|
||||
failed: Array<{ id: string; reason: string }>;
|
||||
}
|
||||
|
||||
export async function batchUpdateStatus(ids: string[], targetStatus: QuestionStatus): Promise<BatchResult> {
|
||||
// 去重,避免重复计数
|
||||
const uniqueIds = [...new Set(ids)];
|
||||
const result: BatchResult = { total: uniqueIds.length, succeeded: 0, failed: [] };
|
||||
|
||||
const existing = await db.select({ id: questions.id, status: questions.status })
|
||||
.from(questions)
|
||||
.where(inArray(questions.id, uniqueIds));
|
||||
const existingMap = new Map<string, QuestionStatus>();
|
||||
for (const row of existing) existingMap.set(row.id, row.status as QuestionStatus);
|
||||
|
||||
// 分类:合法 ID 和失败 ID
|
||||
const validIds: string[] = [];
|
||||
for (const id of uniqueIds) {
|
||||
const currentStatus = existingMap.get(id);
|
||||
if (!currentStatus) {
|
||||
result.failed.push({ id, reason: '题目不存在' });
|
||||
} else if (!ALLOWED_TRANSITIONS[currentStatus]?.includes(targetStatus)) {
|
||||
result.failed.push({ id, reason: `不允许从 ${currentStatus} 变更为 ${targetStatus}` });
|
||||
} else {
|
||||
validIds.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
// 单次批量 UPDATE
|
||||
if (validIds.length > 0) {
|
||||
await db.update(questions).set({ status: targetStatus }).where(inArray(questions.id, validIds));
|
||||
result.succeeded = validIds.length;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function batchPublish(ids: string[]): Promise<BatchResult> {
|
||||
return batchUpdateStatus(ids, 'published');
|
||||
}
|
||||
|
||||
export async function batchArchive(ids: string[]): Promise<BatchResult> {
|
||||
return batchUpdateStatus(ids, 'archived');
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user