t142: 雲端子庫顯示同步幾張卡+幾個三元組(政府專案驗收需求)

leo 07-29:「要在雲端子庫顯示同步了幾個 wiki,既然這樣也同步顯示有幾個三元組,
這是為了政府專案驗收。」

kbdb 兩支統計端點:
- entries.ts: COUNT(DISTINCT page_name) AS card_count
  ⚠️ 必須 distinct——算 block 數會膨脹 3-5 倍,政府驗收看到假數字比沒數字更糟
- records.ts: COUNT(*) AS triplet_count(三元組本來就算全部)
portal.ts 聚合兩者掛進庫目錄;前端 index.html 顯示。

驗:卡數確為 COUNT(DISTINCT page_name)/前端內嵌 JS node --check 全通過
(07-29 白畫面事故教訓)/portal-admin 測試 34 passed(改動前 31 passed,
唯一的 1 failed 是既有債:GET /portal 回 404,改動前後相同,非本次造成)。

註:此為子 CC 完成後未 commit 的懸置工作,總管收工檢查時發現並補收。
This commit is contained in:
2026-07-29 19:55:03 +08:00
parent cdca296044
commit e28e19069f
6 changed files with 211 additions and 4 deletions
+30 -4
View File
@@ -1081,11 +1081,35 @@ portalRouter.get('/portal/admin/libraries', (c) =>
return { ...lib, ...(watching !== undefined ? { daemon_watching: watching } : {}) };
});
const known = new Set(out.map((l) => l.name));
// 資料面實際出現的庫(來自 ingest 蓋章的 metadata.library
// t142資料面實際出現的庫+統計數字(卡數、三元組數)並行撈取,避免 N+1。
// 任一端點失敗不擋登記簿列表(誠實降級:stats 保持 0,不炸主流程)。
try {
const res = await kbdbFetch(c.env, `/entries/libraries?owner_id=${encodeURIComponent(portalTenant(c.env))}`);
if (res.ok) {
const body = (await res.json()) as { libraries?: string[] };
const tenant = portalTenant(c.env);
const ownerParam = `owner_id=${encodeURIComponent(tenant)}`;
const [autoRes, cardRes, tripletRes] = await Promise.all([
kbdbFetch(c.env, `/entries/libraries?${ownerParam}`).catch(() => null),
kbdbFetch(c.env, `/entries/library-stats?${ownerParam}`).catch(() => null),
kbdbFetch(c.env, `/records/triplet-stats?${ownerParam}`).catch(() => null),
]);
// 解析統計,建成 Map 供 O(1) 查找
const cardMap = new Map<string, number>();
if (cardRes?.ok) {
const body = (await cardRes.json()) as { stats?: { library: string; card_count: number }[] };
for (const s of body.stats ?? []) cardMap.set(s.library, s.card_count);
}
const tripletMap = new Map<string, number>();
if (tripletRes?.ok) {
const body = (await tripletRes.json()) as { stats?: { library: string; triplet_count: number }[] };
for (const s of body.stats ?? []) tripletMap.set(s.library, s.triplet_count);
}
// 已登記庫補入統計
for (const lib of out) {
(lib as Record<string, unknown>).card_count = cardMap.get(lib.name) ?? 0;
(lib as Record<string, unknown>).triplet_count = tripletMap.get(lib.name) ?? 0;
}
// 資料面自動出現的庫(蓋章即現身)
if (autoRes?.ok) {
const body = (await autoRes.json()) as { libraries?: string[] };
for (const name of body.libraries ?? []) {
const n = String(name ?? '').trim();
// general 是系統內部「未標庫」桶(未標記 entry 的 fallback),不在用戶目錄露臉
@@ -1096,6 +1120,8 @@ portalRouter.get('/portal/admin/libraries', (c) =>
record_id: '', name: n, display_name: n,
description: '資料同步時自動出現(可在此補顯示名)',
status: 'active', graph_source: false, auto: true,
card_count: cardMap.get(n) ?? 0,
triplet_count: tripletMap.get(n) ?? 0,
...(watching !== undefined ? { daemon_watching: watching } : {}),
});
}