feat(admin): site-import API 与模型纯函数

Task 8(TDD):草稿包→表单/保存序列/warnings 分级纯函数 + POST/GET
API 薄封装。事实源 app/platform/routes/site_import_routes.py(GET 五键
无 failed_phase——retry 对 failed 一律展示后端 409 兜底)与 generator.py
build() 的 spec §4 草稿包形状。保存序列沉淀 spec §5 顺序语义(brand 的
GET 在头像上传之后)与 deflections_contact 池形态分派注记,Task 9 装配
消费。前端 vitest 188 → 201。

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-10-20 21:56:05 +08:00
parent 17a5c9ec1c
commit 323588c54d
3 changed files with 525 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
/**
* site-import API(POST 启动 / GET 轮询)——Task 8;消费方
* site-import 视图(Task 9)。
*
* 端点事实源:app/platform/routes/site_import_routes.py
* (/api/platform/tenants/{slug}/site-import[/{task_id}],
* _tenant_guard 守租户——超管与租户管理员两角色可用)。
*
* 契约要点(Task 7 评审备忘):
* - POST 200(非 201)→ {task_id};body 单形态发送——新建 {url} /
* 复活 {retry: 'generate', task_id}(retry 形态后端忽略 url 字段);
* - 429(每租户 15 分钟 3 次频控)/ 409(并发上限·非 generate-failed
* 态复活)的 detail 已是白话文案——调用方原样展示,不二次翻译;
* - GET 五键 status/phase/pages/draft/error——**无 failed_phase**:前端
* retry 按钮对 failed 一律展示,能否复活由后端 409 兜底判定;
* - GET 404 = 任务不存在/已过期(终态语义——视图层 isTerminal404 消费)。
*
* 草稿包类型 SiteImportDraft 是 spec §4 的单一 TS 镜像,定义在视图
* model(type-only 反向引用,无运行时耦合——api 层不 import 视图代码)。
*/
import { http, type RequestOptions } from './client'
import type { SiteImportDraft } from '@/views/site-import/model'
/** 任务状态(app/platform/site_import/task.py TaskState 镜像) */
export type SiteImportStatus = 'pending' | 'crawling' | 'generating' | 'done' | 'failed'
/** GET 轮询响应五键(spec §4;后端恒返五键——非 done 时 draft 为 null) */
export interface SiteImportTaskResponse {
status: SiteImportStatus
/** 运行中阶段('crawling' | 'generating';非运行态 null) */
phase: string | null
/** 已抓页数(运行中递增;BFS 总数不可知——展示「第 N 页」不展示 M) */
pages: number
/** 草稿包(仅 status=done 非 null) */
draft: SiteImportDraft | null
/** 白话失败原因(仅 status=failed 非 null) */
error: string | null
}
/** POST body 单形态:新建 {url} / 复活 {retry, task_id}(后端对 retry
* 形态忽略 url——前端按用途二选一发送,不带冗余键) */
export type StartSiteImportBody = { url: string } | { retry: 'generate'; task_id: string }
/** POST 启动响应 */
export interface StartSiteImportResponse {
task_id: string
}
/** 启动/复活导入任务(POST /api/platform/tenants/{slug}/site-import)。
* 新建传 {url}(http(s) 公网地址——内网/非法地址 400);复活传
* {retry: 'generate', task_id}(同 task_id 只重跑生成步,复用已抓文本)。
* 429/409/404 经既有 ApiError 抛(detail 为白话文案)。 */
export function startSiteImport(
slug: string,
body: StartSiteImportBody,
config?: RequestOptions
): Promise<StartSiteImportResponse> {
return http.post<StartSiteImportResponse>(
`/api/platform/tenants/${slug}/site-import`,
body,
config
)
}
/** 轮询任务状态(GET /api/platform/tenants/{slug}/site-import/{task_id})。
* 3s 间隔;7.5 分钟窗口外自动停(> 服务端 7 分钟总超时)。404 = 过期
* 终态(isTerminal404)——跨租户持他人 task_id 同为 404。 */
export function getSiteImportTask(
slug: string,
taskId: string,
config?: RequestOptions
): Promise<SiteImportTaskResponse> {
return http.get<SiteImportTaskResponse>(
`/api/platform/tenants/${slug}/site-import/${taskId}`,
config
)
}