feat(t52): 資料夾=庫端到端——kbdb 加 /entries/libraries(資料面 distinct 庫);portal 庫目錄合併「登記簿+蓋章自動出現」(auto);auto 庫改安全渲染(無 record_id 不放死按鈕,改給『登記到目錄』)+daemon/libraries 自動登記端點。leo 07-26:地端 2 個資料夾雲端就要 2 個庫
This commit is contained in:
Symlink
+1
@@ -0,0 +1 @@
|
||||
/Users/youlinhsieh/Documents/tech_projects/InkStoneCo/matrix/arcrun/cypher-executor/node_modules
|
||||
@@ -699,6 +699,62 @@ function toPublicLibrary(rec: PortalRecord) {
|
||||
};
|
||||
}
|
||||
|
||||
// POST /portal/daemon/libraries — body {email, password, libraries:[{name, display_name?}]}。
|
||||
// t52(leo 2026-07-26:「用戶可以看到我有 2 個庫,地端雲端都是 2 個,如果只有一個一定被罵」):
|
||||
// 小幫手回報它看守的資料夾各自對應的庫,雲端**自動登記**——庫目錄與地端資料夾一比一。
|
||||
// 認證=同 /portal/daemon/config(用戶帳密)。已存在的庫略過(冪等),不覆寫顯示名。
|
||||
portalRouter.post('/portal/daemon/libraries', (c) =>
|
||||
run(c, async () => {
|
||||
const body = (await c.req.json().catch(() => null)) as
|
||||
| { email?: string; password?: string; libraries?: { name?: string; display_name?: string }[] }
|
||||
| null;
|
||||
const email = String(body?.email ?? '').trim().toLowerCase();
|
||||
const password = String(body?.password ?? '');
|
||||
if (!email || !password) return c.json({ error: 'email 與 password 必填' }, 400);
|
||||
if (await isLocked(c.env, email)) return c.json({ error: '登入失敗次數過多,請稍後再試' }, 429);
|
||||
const recordId = await findUserRecordId(c.env, email);
|
||||
const rec = recordId ? await getRecordById(c.env, recordId) : null;
|
||||
if (!rec || (rec.values.status ?? '') !== 'active'
|
||||
|| !(await verifyPassword(password, rec.values.password_hash ?? ''))) {
|
||||
await recordLoginFail(c.env, email);
|
||||
return c.json({ error: 'email 或密碼錯誤' }, 401);
|
||||
}
|
||||
await clearLoginFail(c.env, email);
|
||||
|
||||
const wanted = Array.isArray(body?.libraries) ? body!.libraries! : [];
|
||||
const seeded = await ensurePortalTemplates(c.env);
|
||||
if (seeded.errors.length > 0) {
|
||||
return c.json({ error: `portal templates seed 失敗:${seeded.errors.join('; ')}` }, 502);
|
||||
}
|
||||
const existing = await listRecordsByTemplate(c.env, LIBRARY_TEMPLATE);
|
||||
const have = new Set(existing.map((l) => String(l.values.name ?? '')));
|
||||
const ns = portalNamespace(c.env);
|
||||
const created: string[] = [];
|
||||
for (const item of wanted) {
|
||||
const name = String(item?.name ?? '').trim();
|
||||
if (!isValidLibraryName(name) || name === '*' || have.has(name)) continue;
|
||||
const res = await kbdbFetch(c.env, '/records', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
template: LIBRARY_TEMPLATE,
|
||||
owner_id: ns,
|
||||
values: {
|
||||
name,
|
||||
display_name: String(item?.display_name ?? '').trim() || name,
|
||||
description: '同步小幫手看守的資料夾',
|
||||
status: 'active',
|
||||
},
|
||||
}),
|
||||
});
|
||||
if (!res.ok) throw new KbdbError(`POST /records(portal_library)→ ${res.status}`);
|
||||
have.add(name);
|
||||
created.push(name);
|
||||
}
|
||||
const after = await listRecordsByTemplate(c.env, LIBRARY_TEMPLATE);
|
||||
return c.json({ success: true, created, libraries: after.map(toPublicLibrary) });
|
||||
}),
|
||||
);
|
||||
|
||||
// POST /portal/daemon/config — body {email, password}。同步小幫手憑「用戶剛設的帳密」
|
||||
// 直接換到自己的設定(t54,leo 07-25:「最好的就是把它的帳密直接輸入」)——
|
||||
// 用戶不必再下載 config.json 丟隱藏資料夾,托盤第一次開啟輸入網址+帳密就上工。
|
||||
@@ -782,12 +838,32 @@ portalRouter.post('/portal/admin/chat-key', (c) =>
|
||||
);
|
||||
|
||||
// GET /portal/admin/libraries — 庫目錄列表。
|
||||
// t52(leo 2026-07-26:「地端 2 個資料夾、雲端就要 2 個庫,只有一個一定被罵」):
|
||||
// 除了登記簿裡的庫,**也把資料裡實際蓋過章的庫一併列出**(標 auto:true)——
|
||||
// 蓋章即現身,用戶不必先去登記;登記簿只負責顯示名/圖譜來源這些額外設定。
|
||||
portalRouter.get('/portal/admin/libraries', (c) =>
|
||||
run(c, async () => {
|
||||
const auth = await requirePortalAdmin(c);
|
||||
if (!auth.ok) return auth.res;
|
||||
const libs = await listRecordsByTemplate(c.env, LIBRARY_TEMPLATE);
|
||||
return c.json({ success: true, libraries: libs.map(toPublicLibrary), count: libs.length });
|
||||
const out = libs.map(toPublicLibrary);
|
||||
const known = new Set(out.map((l) => l.name));
|
||||
// 資料面實際出現的庫(來自 ingest 蓋章的 metadata.library)
|
||||
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[] };
|
||||
for (const name of body.libraries ?? []) {
|
||||
const n = String(name ?? '').trim();
|
||||
if (!n || known.has(n)) continue;
|
||||
known.add(n);
|
||||
out.push({ record_id: '', name: n, display_name: n, description: '資料同步時自動出現(可在此補顯示名)', status: 'active', graph_source: false, auto: true });
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// 資料面查不到不擋登記簿(誠實降級:至少顯示已登記的庫)
|
||||
}
|
||||
return c.json({ success: true, libraries: out, count: out.length });
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user