feat: v0.3.8–v0.3.14 工作区汇总(依赖升级、整理来源、手动入库、原地入库等)
生产 v0.3.8~v0.3.13 均由 8013196 加工作区修改构建部署,此前未提交;本提交补齐源码,
并含未部署的 v0.3.14 原地入库。中间各版本源码状态未保存,无法逐版拆分,详见
docs/plan/17-full-upgrade.md 与 docs/plan/18-organize-release.md。
- v0.3.8 全面升级:Deno workspace 依赖、Router 8 / Vite 8、camofox 1.18.0 / Node 24、
PostgreSQL 18.6、备份客户端 18.6、演示 qB 5.2.4
- v0.3.9 媒体整理:分类规则、整理来源与目录快照(0059)、文件管理整理入口
- v0.3.10 手动下载与入库:独立手动下载、详情页资源下载、TMDB / Local 元数据兜底
(metadata.resolve_organize)、正常多版本 / 一次性洗版(0060)
- v0.3.11 搜索详情与桌面黑边修复
- v0.3.12 列表分页、搜索历史、首页概况
- v0.3.13 统一整理弹窗与队列
- v0.3.14 原地入库(0061,未部署)
同批审计修复:
- 刮削入队:确认原记录存在后才删重放产生的新记录;写回 run 关联失败时保持 queued,
由处理器补关联(对齐 MP 先登记、只在入队失败时记失败)
- 识别升级链只在结果更确定时采用,不再用 no_match 顶替原有歧义候选
- bluray_dir 资产以原盘目录本身为刮削根(MP 同做法)
- metadata.resolve_organize 登记到入库排障 Skill 与能力地图
- db/src/types.ts 按迁移重新生成;contracts 整理动作枚举顺序与数据库对齐
- 测试:原地入库用例改为直接查记录表;domain 测试模板补 version
- 前端:通知铃铛弹层与用户菜单统一样式;站点卡片共用卡头、底栏贴底、分页间距
依赖升级证据只提交摘要文件,registry-snapshot.json 移至 .local/(SHA-256 记于文档)。
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
8013196e3f
commit
ceb66f8c77
265 files changed
+17751
-2483
No files matched your search
@@ -2,11 +2,15 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type { ApiKey, ApiKeyCreate, ApiKeyCreated, ApiKeyUpdate, CursorPage } from "@umt/contracts";
|
||||
import { apiData } from "~/lib/api.ts";
|
||||
import { qk } from "~/lib/query-keys.ts";
|
||||
import { fetchAllCursorPages } from "./parse.ts";
|
||||
|
||||
export function useApiKeys() {
|
||||
return useQuery({
|
||||
queryKey: qk.list("api-keys"),
|
||||
queryFn: ({ signal }) => apiData<CursorPage<ApiKey>>("/api-keys", { params: { limit: 200 }, signal }),
|
||||
queryFn: ({ signal }) =>
|
||||
fetchAllCursorPages((cursor) =>
|
||||
apiData<CursorPage<ApiKey>>("/api-keys", { params: { limit: 200, cursor }, signal }),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query";
|
||||
import { cursorPage, type Dashboard, SiteListItem } from "@umt/contracts";
|
||||
import { apiData } from "~/lib/api.ts";
|
||||
import { qk } from "~/lib/query-keys.ts";
|
||||
import { apiParsed } from "./parse.ts";
|
||||
import { apiParsed, fetchAllCursorPages } from "./parse.ts";
|
||||
|
||||
/** 首页聚合;SSE 事件触发 invalidate,另有 60 秒兜底轮询 */
|
||||
export function useDashboard() {
|
||||
@@ -20,7 +20,9 @@ export function useSitesWithAccounts() {
|
||||
return useQuery({
|
||||
queryKey: [...qk.dashboard, "sites-accounts"],
|
||||
queryFn: ({ signal }) =>
|
||||
apiParsed(SitePage, "/api/v1/sites", { params: { limit: 200, include: "accounts" }, signal }),
|
||||
fetchAllCursorPages((cursor) =>
|
||||
apiParsed(SitePage, "/api/v1/sites", { params: { limit: 200, cursor, include: "accounts" }, signal }),
|
||||
),
|
||||
refetchInterval: 60_000,
|
||||
});
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
} from "@umt/contracts";
|
||||
import { z } from "zod";
|
||||
import { TTL_SECOND } from "~/lib/api.ts";
|
||||
import { apiMutate, apiParsed, cleanParams } from "./parse.ts";
|
||||
import { apiMutate, apiParsed, cleanParams, fetchAllCursorPages } from "./parse.ts";
|
||||
|
||||
// 下载任务、计划(界面统称"下载")、缓存卷(W2-C:/download-tasks、/acquisition-plans、/download-cache/cleanup、/dashboard)
|
||||
|
||||
@@ -181,7 +181,12 @@ export function useDownloaderNames() {
|
||||
queryKey: ["downloads", "instances"],
|
||||
staleTime: 5 * 60 * TTL_SECOND,
|
||||
queryFn: ({ signal }) =>
|
||||
apiParsed(cursorPage(DownloaderInstance), "/api/v1/downloader-instances", { params: { limit: 200 }, signal }),
|
||||
fetchAllCursorPages((cursor) =>
|
||||
apiParsed(cursorPage(DownloaderInstance), "/api/v1/downloader-instances", {
|
||||
params: { limit: 200, cursor },
|
||||
signal,
|
||||
}),
|
||||
),
|
||||
select: (d) => new Map(d.items.map((i) => [i.id, i.name])),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export type EngineRunFilters = {
|
||||
status?: string;
|
||||
/** 触发来源,单值或逗号分隔多值(user,api_key) */
|
||||
triggeredBy?: string;
|
||||
/** 单值或逗号分隔多个工作流名称 */
|
||||
workflowName?: string;
|
||||
businessKind?: EngineBusinessKind;
|
||||
businessRefId?: string;
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from "@umt/contracts";
|
||||
import { z } from "zod";
|
||||
import { TTL_SECOND } from "~/lib/api.ts";
|
||||
import { apiMutate, apiParsed, cleanParams } from "./parse.ts";
|
||||
import { apiMutate, apiParsed, cleanParams, fetchAllCursorPages } from "./parse.ts";
|
||||
|
||||
// Feed 来源(W2-B:/feed-sources)
|
||||
|
||||
@@ -25,7 +25,9 @@ export function useFeeds(f: { siteId?: string; health?: FeedHealth } = {}) {
|
||||
return useQuery({
|
||||
queryKey: feedKeys.list(f),
|
||||
queryFn: ({ signal }) =>
|
||||
apiParsed(FeedPage, "/api/v1/feed-sources", { params: cleanParams({ ...f, limit: 200 }), signal }),
|
||||
fetchAllCursorPages((cursor) =>
|
||||
apiParsed(FeedPage, "/api/v1/feed-sources", { params: cleanParams({ ...f, limit: 200, cursor }), signal }),
|
||||
),
|
||||
refetchInterval: 30 * TTL_SECOND,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,14 +24,16 @@ import type {
|
||||
} from "@umt/contracts";
|
||||
import { apiData } from "~/lib/api.ts";
|
||||
import { type QueryArea, qk } from "~/lib/query-keys.ts";
|
||||
import { fetchAllCursorPages } from "./parse.ts";
|
||||
|
||||
/** 实例类资源的标准 CRUD 钩子(列表取前 200 条,首版实例数量很少) */
|
||||
/** 实例类资源的标准 CRUD 钩子(列表按游标取齐配置) */
|
||||
function crudHooks<Row extends { id: string }, Create, Update>(area: QueryArea, base: string) {
|
||||
return {
|
||||
useList: () =>
|
||||
useQuery({
|
||||
queryKey: qk.list(area),
|
||||
queryFn: ({ signal }) => apiData<CursorPage<Row>>(base, { params: { limit: 200 }, signal }),
|
||||
queryFn: ({ signal }) =>
|
||||
fetchAllCursorPages((cursor) => apiData<CursorPage<Row>>(base, { params: { limit: 200, cursor }, signal })),
|
||||
}),
|
||||
useCreate: () => {
|
||||
const qc = useQueryClient();
|
||||
@@ -100,14 +102,18 @@ export const pathMappings = {
|
||||
export function useSiteOptions() {
|
||||
return useQuery({
|
||||
queryKey: qk.list("sites", { limit: 200 }),
|
||||
queryFn: ({ signal }) => apiData<CursorPage<Site>>("/sites", { params: { limit: 200 }, signal }),
|
||||
queryFn: ({ signal }) =>
|
||||
fetchAllCursorPages((cursor) => apiData<CursorPage<Site>>("/sites", { params: { limit: 200, cursor }, signal })),
|
||||
});
|
||||
}
|
||||
|
||||
export function useSiteAccounts() {
|
||||
return useQuery({
|
||||
queryKey: qk.list("site-accounts"),
|
||||
queryFn: ({ signal }) => apiData<CursorPage<SiteAccount>>("/site-accounts", { params: { limit: 200 }, signal }),
|
||||
queryFn: ({ signal }) =>
|
||||
fetchAllCursorPages((cursor) =>
|
||||
apiData<CursorPage<SiteAccount>>("/site-accounts", { params: { limit: 200, cursor }, signal }),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from "@umt/contracts";
|
||||
import type { z } from "zod";
|
||||
import { ApiRequestError, api, type QueryParams } from "~/lib/api.ts";
|
||||
import { fetchAllCursorPages } from "./parse.ts";
|
||||
|
||||
// 媒体库:列表 / 详情 / 作品详情(内容单元、别名、字段来源)与作品级操作。
|
||||
// 所有响应都按 contracts 的 zod schema 校验,结构不符按错误处理(不静默渲染半截数据)。
|
||||
@@ -106,7 +107,8 @@ const RootPage = cursorPage(LibraryRoot);
|
||||
export function useLibraryRoots() {
|
||||
return useQuery({
|
||||
queryKey: libraryKeys.roots,
|
||||
queryFn: ({ signal }) => getParsed(RootPage, "/library-roots", { limit: 200 }, signal),
|
||||
queryFn: ({ signal }) =>
|
||||
fetchAllCursorPages((cursor) => getParsed(RootPage, "/library-roots", { limit: 200, cursor }, signal)),
|
||||
staleTime: 5 * 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
ClassificationConfigView,
|
||||
type OrganizeSourceInput,
|
||||
OrganizeSourceList,
|
||||
OrganizeSourceView,
|
||||
OrganizeStockResult,
|
||||
} from "@umt/contracts";
|
||||
import { z } from "zod";
|
||||
import { apiParsed } from "./parse.ts";
|
||||
|
||||
export function useOrganizeSources() {
|
||||
return useQuery({
|
||||
queryKey: ["organize-sources"],
|
||||
queryFn: ({ signal }) => apiParsed(OrganizeSourceList, "/organize-sources", { signal }),
|
||||
refetchInterval: 10_000,
|
||||
});
|
||||
}
|
||||
export function useSaveOrganizeSource() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, body }: { id: string; body: OrganizeSourceInput }) =>
|
||||
apiParsed(OrganizeSourceView, `/organize-sources/${id}`, { method: "PUT", body }),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ["organize-sources"] }),
|
||||
});
|
||||
}
|
||||
export function useRemoveOrganizeSource() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) =>
|
||||
apiParsed(z.object({ deleted: z.boolean() }), `/organize-sources/${id}`, { method: "DELETE" }),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ["organize-sources"] }),
|
||||
});
|
||||
}
|
||||
export function useOrganizeStock() {
|
||||
return useMutation({
|
||||
mutationFn: ({ id, path }: { id: string; path?: string }) =>
|
||||
apiParsed(OrganizeStockResult, `/organize-sources/${id}/stock`, { method: "POST", body: { path } }),
|
||||
});
|
||||
}
|
||||
export function useClassification() {
|
||||
return useQuery({
|
||||
queryKey: ["classification"],
|
||||
queryFn: ({ signal }) => apiParsed(ClassificationConfigView, "/classification", { signal }),
|
||||
});
|
||||
}
|
||||
export function useSaveClassification() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (yaml: string) =>
|
||||
apiParsed(ClassificationConfigView, "/classification", { method: "PUT", body: { yaml } }),
|
||||
onSuccess: (data) => qc.setQueryData(["classification"], data),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { fetchAllCursorPages } from "./parse.ts";
|
||||
|
||||
describe("fetchAllCursorPages", () => {
|
||||
it("keeps configuration rows beyond the first cursor page", async () => {
|
||||
const fetchPage = vi.fn(async (cursor?: string) =>
|
||||
cursor
|
||||
? { items: [201, 202], nextCursor: null, total: 202 }
|
||||
: { items: Array.from({ length: 200 }, (_, i) => i + 1), nextCursor: "page-2", total: 202 },
|
||||
);
|
||||
const result = await fetchAllCursorPages(fetchPage);
|
||||
expect(result.items).toHaveLength(202);
|
||||
expect(result.items.slice(-2)).toEqual([201, 202]);
|
||||
expect(result.total).toBe(202);
|
||||
expect(result.nextCursor).toBeNull();
|
||||
expect(fetchPage.mock.calls).toEqual([[], ["page-2"]]);
|
||||
});
|
||||
|
||||
it("rejects a repeated cursor instead of looping indefinitely", async () => {
|
||||
const fetchPage = vi.fn(async () => ({ items: [1], nextCursor: "same" }));
|
||||
await expect(fetchAllCursorPages(fetchPage)).rejects.toThrow("分页游标重复");
|
||||
expect(fetchPage).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("propagates a later page failure instead of returning a partial configuration", async () => {
|
||||
const failure = new Error("aborted");
|
||||
await expect(
|
||||
fetchAllCursorPages(async (cursor) => {
|
||||
if (cursor) throw failure;
|
||||
return { items: [1], nextCursor: "next" };
|
||||
}),
|
||||
).rejects.toBe(failure);
|
||||
});
|
||||
});
|
||||
@@ -37,3 +37,21 @@ export function cleanParams(p: Record<string, string | number | boolean | undefi
|
||||
for (const [k, v] of Object.entries(p)) if (v !== undefined && v !== null && v !== "") out[k] = v;
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 小规模配置集合:跟随游标取齐,保留列表返回形状供选择器与本地分页共用。 */
|
||||
export async function fetchAllCursorPages<P extends { items: unknown[]; nextCursor?: string | null }>(
|
||||
fetchPage: (cursor?: string) => Promise<P>,
|
||||
): Promise<P> {
|
||||
const first = await fetchPage();
|
||||
const items = [...first.items];
|
||||
const seen = new Set<string>();
|
||||
let cursor = first.nextCursor;
|
||||
while (cursor) {
|
||||
if (seen.has(cursor)) throw new ApiRequestError("internal", "分页游标重复,请重试");
|
||||
seen.add(cursor);
|
||||
const page = await fetchPage(cursor);
|
||||
items.push(...page.items);
|
||||
cursor = page.nextCursor;
|
||||
}
|
||||
return { ...first, items, nextCursor: null };
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from "@umt/contracts";
|
||||
import { api } from "~/lib/api.ts";
|
||||
import { getParsed, parseOrThrow } from "./library.ts";
|
||||
import { fetchAllCursorPages } from "./parse.ts";
|
||||
|
||||
// 策略版本:草案 → 预览 → 授权发布 → 版本化 / 回滚(REQ §6.4)
|
||||
|
||||
@@ -24,7 +25,8 @@ const Page = cursorPage(PolicyRevision);
|
||||
export function usePolicyRevisions(filters: PolicyFilters = {}) {
|
||||
return useQuery({
|
||||
queryKey: policyKeys.list(filters),
|
||||
queryFn: ({ signal }) => getParsed(Page, "/policy-revisions", { ...filters, limit: 200 }, signal),
|
||||
queryFn: ({ signal }) =>
|
||||
fetchAllCursorPages((cursor) => getParsed(Page, "/policy-revisions", { ...filters, limit: 200, cursor }, signal)),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "@umt/contracts";
|
||||
import { api } from "~/lib/api.ts";
|
||||
import { getParsed, parseOrThrow } from "./library.ts";
|
||||
import { fetchAllCursorPages } from "./parse.ts";
|
||||
|
||||
// 偏好标签与抑制记录(REQ §6.7):停用/删除/语义变更都会留抑制记录,Agent 不得据旧证据自动加回。
|
||||
|
||||
@@ -29,7 +30,10 @@ const SupPage = cursorPage(PreferenceSuppression);
|
||||
export function usePreferenceTags(filters: TagFilters = {}) {
|
||||
return useQuery({
|
||||
queryKey: preferenceKeys.tags(filters),
|
||||
queryFn: ({ signal }) => getParsed(TagPage, "/preference-tags", { ...filters, limit: 200 }, signal),
|
||||
queryFn: ({ signal }) =>
|
||||
fetchAllCursorPages((cursor) =>
|
||||
getParsed(TagPage, "/preference-tags", { ...filters, limit: 200, cursor }, signal),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,11 +41,13 @@ export function useSuppressions(active?: boolean) {
|
||||
return useQuery({
|
||||
queryKey: preferenceKeys.suppressions(active),
|
||||
queryFn: ({ signal }) =>
|
||||
getParsed(
|
||||
SupPage,
|
||||
"/preference-suppressions",
|
||||
{ active: active === undefined ? undefined : String(active), limit: 200 },
|
||||
signal,
|
||||
fetchAllCursorPages((cursor) =>
|
||||
getParsed(
|
||||
SupPage,
|
||||
"/preference-suppressions",
|
||||
{ active: active === undefined ? undefined : String(active), limit: 200, cursor },
|
||||
signal,
|
||||
),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
PublicOrganizationRecord,
|
||||
} from "@umt/contracts";
|
||||
import { TTL_SECOND } from "~/lib/api.ts";
|
||||
import { apiMutate, apiParsed, cleanParams } from "./parse.ts";
|
||||
import { apiMutate, apiParsed, cleanParams, fetchAllCursorPages } from "./parse.ts";
|
||||
|
||||
// 整理记录(W2-C:/organization-records;读 organize.read,发起 organize.execute)
|
||||
|
||||
@@ -23,6 +23,7 @@ const RecordPage = cursorPage(PublicOrganizationRecord);
|
||||
|
||||
export type RecordFilters = {
|
||||
status?: OrganizationStatus;
|
||||
phase?: "active" | "history";
|
||||
trigger?: OrganizationTrigger;
|
||||
mediaEntityId?: string;
|
||||
q?: string;
|
||||
@@ -145,6 +146,7 @@ export const TRIGGER_LABEL: Record<OrganizationTrigger, string> = { auto: "自
|
||||
/** 整理方式 / 文件操作(记录的 action) */
|
||||
export const ACTION_LABEL = {
|
||||
copy: "复制",
|
||||
in_place: "原地入库",
|
||||
move: "移动",
|
||||
rename: "重命名",
|
||||
relocate: "目录调整",
|
||||
@@ -174,7 +176,9 @@ export function useLibraryRoots() {
|
||||
queryKey: ["records", "library-roots"],
|
||||
staleTime: 5 * 60 * TTL_SECOND,
|
||||
queryFn: ({ signal }) =>
|
||||
apiParsed(cursorPage(LibraryRoot), "/api/v1/library-roots", { params: { limit: 200 }, signal }),
|
||||
fetchAllCursorPages((cursor) =>
|
||||
apiParsed(cursorPage(LibraryRoot), "/api/v1/library-roots", { params: { limit: 200, cursor }, signal }),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { keepPreviousData, useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
type ManualAcquisitionRequest,
|
||||
ManualAcquisitionResult,
|
||||
type ResolutionStatus,
|
||||
WorkbenchReleaseDetail,
|
||||
@@ -81,7 +82,7 @@ export function useRefreshReleaseDetail() {
|
||||
export function useSubmitRelease() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (body: { sourceReleaseId: string; slotId?: string; entityId?: string; note?: string }) =>
|
||||
mutationFn: (body: ManualAcquisitionRequest) =>
|
||||
apiMutate("/api/v1/acquisition-plans", { body }).then((d) => ManualAcquisitionResult.parse(d)),
|
||||
onSuccess: (r) => {
|
||||
if (r.submitted) void qc.invalidateQueries({ queryKey: ["downloads"] });
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from "@umt/contracts";
|
||||
import { api } from "~/lib/api.ts";
|
||||
import { getParsed, parseOrThrow } from "./library.ts";
|
||||
import { fetchAllCursorPages } from "./parse.ts";
|
||||
|
||||
// 规则(W9-D,docs/runbook/rules.md):规则组 = 收录过滤条件 + 偏好标签合一;自定义条件;三套默认链;抑制记录。
|
||||
// 写入给 ruleString(优先)或 layers;PATCH 带 expectedRevision,不匹配 409。
|
||||
@@ -53,7 +54,8 @@ const SupPage = cursorPage(PreferenceSuppression);
|
||||
export function useRuleGroups(filters: RuleGroupFilters = {}) {
|
||||
return useQuery({
|
||||
queryKey: ruleKeys.groups(filters),
|
||||
queryFn: ({ signal }) => getParsed(GroupPage, "/rules", { ...filters, limit: 200 }, signal),
|
||||
queryFn: ({ signal }) =>
|
||||
fetchAllCursorPages((cursor) => getParsed(GroupPage, "/rules", { ...filters, limit: 200, cursor }, signal)),
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
}
|
||||
@@ -78,7 +80,8 @@ export function useRuleKeywords() {
|
||||
export function useRuleAtoms() {
|
||||
return useQuery({
|
||||
queryKey: ruleKeys.atoms,
|
||||
queryFn: ({ signal }) => getParsed(AtomPage, "/rules/atoms", { limit: 200 }, signal),
|
||||
queryFn: ({ signal }) =>
|
||||
fetchAllCursorPages((cursor) => getParsed(AtomPage, "/rules/atoms", { limit: 200, cursor }, signal)),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -92,7 +95,8 @@ export function useRuleDefaults() {
|
||||
export function useRuleSuppressions() {
|
||||
return useQuery({
|
||||
queryKey: ruleKeys.suppressions,
|
||||
queryFn: ({ signal }) => getParsed(SupPage, "/rules/suppressions", { limit: 200 }, signal),
|
||||
queryFn: ({ signal }) =>
|
||||
fetchAllCursorPages((cursor) => getParsed(SupPage, "/rules/suppressions", { limit: 200, cursor }, signal)),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from "@umt/contracts";
|
||||
import { z } from "zod";
|
||||
import { TTL_SECOND } from "~/lib/api.ts";
|
||||
import { apiMutate, apiParsed } from "./parse.ts";
|
||||
import { apiMutate, apiParsed, fetchAllCursorPages } from "./parse.ts";
|
||||
|
||||
// 站点、站点账号、站点快照(W2-B:/sites、/site-accounts、/sites/{id}/snapshot(s))
|
||||
// 站点中心(W9-A):include=accounts、凭证 PUT、健康检测、刷新登录、用户数据、删除
|
||||
@@ -41,12 +41,14 @@ export const siteKeys = {
|
||||
loginHistory: (accountId: string) => ["sites", "login-history", accountId] as const,
|
||||
};
|
||||
|
||||
/** 站点列表(附账号与整体账号状态,站点页一次请求) */
|
||||
/** 站点列表(附账号与整体账号状态,站点页按游标取齐配置) */
|
||||
export function useSites() {
|
||||
return useQuery({
|
||||
queryKey: siteKeys.list(),
|
||||
queryFn: ({ signal }) =>
|
||||
apiParsed(SitePage, "/api/v1/sites", { params: { limit: 200, include: "accounts" }, signal }),
|
||||
fetchAllCursorPages((cursor) =>
|
||||
apiParsed(SitePage, "/api/v1/sites", { params: { limit: 200, include: "accounts", cursor }, signal }),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -62,7 +64,9 @@ export function useSiteAccounts(siteId?: string) {
|
||||
return useQuery({
|
||||
queryKey: siteKeys.accounts(siteId),
|
||||
queryFn: ({ signal }) =>
|
||||
apiParsed(AccountPage, "/api/v1/site-accounts", { params: { limit: 200, siteId }, signal }),
|
||||
fetchAllCursorPages((cursor) =>
|
||||
apiParsed(AccountPage, "/api/v1/site-accounts", { params: { limit: 200, siteId, cursor }, signal }),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "@umt/contracts";
|
||||
import { api } from "~/lib/api.ts";
|
||||
import { getParsed, parseOrThrow } from "./library.ts";
|
||||
import { fetchAllCursorPages } from "./parse.ts";
|
||||
|
||||
// 收录目标 Slot:硬条件 / 软偏好 / 回退 / 升级阈值 / 完成条件(REQ §6.1)
|
||||
|
||||
@@ -24,7 +25,10 @@ export function useSlots(filters: SlotFilters, enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: slotKeys.list(filters),
|
||||
enabled,
|
||||
queryFn: ({ signal }) => getParsed(Page, "/acquisition-slots", { ...filters, limit: 200 }, signal),
|
||||
queryFn: ({ signal }) =>
|
||||
fetchAllCursorPages((cursor) =>
|
||||
getParsed(Page, "/acquisition-slots", { ...filters, limit: 200, cursor }, signal),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user