t159 修 portal 庫目錄空:補上 daemon 一直在打、但從未存在的登記端點
診斷更正(重要):kbdb 裡 50+ 筆 entry_type=value(content=資料夾名)**不是孤兒**——
那是 91 筆 triplet records 的 library slot 正常構造(records 全部組裝完整,資料同步
一切正常,無失敗無重試)。真病灶=**POST /portal/daemon/libraries 這個 route
從來不存在**:daemon registerLibraries(arcrun-tray main.go:505,t52「用戶可以看到
我有 2 個庫」)連線精靈時打它 → 404 → 被 daemon「失敗不擋連線」設計靜默吞掉
→ portal_library 登記簿永遠 0 筆 → portal「庫目錄管理」空。
- 新 route 契約照 daemon 既有呼叫:{email, password, libraries:[{name, display_name}]}
- 帳密驗證沿用 /portal/session 同一套(isLocked/findUserRecordId/verifyPassword/
recordLoginFail;daemon 只在精靈那刻拿帳密不存)
- 冪等:listRecordsByTemplate 比 name,已登記跳過(重跑精靈不堆重複)
- 庫名走 isValidLibraryName 同閘(拒 "*" 與非法字元)
驗:tsc 全綠;youlin 實例資料層已手動補登記(探針 record PATCH 成
youlinhsieh-test1+新建 test2,portal_library count=2);route 部署驗證
隨下批 staging bundle(假帳密應回 401 而非 404)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -746,6 +746,64 @@ portalRouter.post('/portal/admin/libraries', (c) =>
|
||||
}),
|
||||
);
|
||||
|
||||
// POST /portal/daemon/libraries — 小幫手(daemon)連線精靈時把看守資料夾的庫報上來自動登記。
|
||||
// t159(2026-07-31 leo prod 實走揪出):daemon registerLibraries(arcrun-tray main.go:505,t52)
|
||||
// 一直在打這個端點,但 cypher 從來沒有它 ⇒ 404 被 daemon「失敗不擋連線」靜默吞掉
|
||||
// ⇒ portal_library 登記簿永遠空 ⇒ portal「庫目錄管理」空(資料同步倒是全正常——
|
||||
// triplet records 的 library slot 都在,病只在登記簿沒人寫)。
|
||||
// 契約照 daemon 既有呼叫:body {email, password, libraries:[{name, display_name}]}。
|
||||
// 帳密驗證=與 /portal/session 同一套(daemon 只在精靈那一刻拿到帳密,不存)。
|
||||
// 冪等:已登記(同 name)跳過——重跑精靈不堆重複。
|
||||
portalRouter.post('/portal/daemon/libraries', (c) =>
|
||||
run(c, async () => {
|
||||
const body = await c.req.json().catch(() => null);
|
||||
const email = String(body?.email ?? '').trim().toLowerCase();
|
||||
const password = String(body?.password ?? '');
|
||||
if (!email || !password) return c.json({ error: 'email 與 password 必填' }, 400);
|
||||
const items = Array.isArray(body?.libraries) ? body.libraries : [];
|
||||
if (items.length === 0) return c.json({ success: true, registered: [], skipped: [] });
|
||||
|
||||
// 帳密驗證(沿用 /portal/session 的鎖定與驗證機制)
|
||||
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 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) => l.values.name ?? ''));
|
||||
const ns = portalNamespace(c.env);
|
||||
const registered: string[] = [];
|
||||
const skipped: string[] = [];
|
||||
for (const it of items) {
|
||||
const name = String(it?.name ?? '').trim();
|
||||
const displayName = String(it?.display_name ?? '').trim() || name;
|
||||
if (!isValidLibraryName(name) || name === '*') { skipped.push(name || '(空)'); continue; }
|
||||
if (have.has(name)) { skipped.push(name); continue; }
|
||||
const res = await kbdbFetch(c.env, '/records', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
template: LIBRARY_TEMPLATE,
|
||||
owner_id: ns,
|
||||
values: { name, display_name: displayName, description: '', status: 'active' },
|
||||
}),
|
||||
});
|
||||
if (!res.ok) throw new KbdbError(`POST /records(portal_library,daemon 登記)→ ${res.status}`);
|
||||
have.add(name);
|
||||
registered.push(name);
|
||||
}
|
||||
return c.json({ success: true, registered, skipped });
|
||||
}),
|
||||
);
|
||||
|
||||
// PATCH /portal/admin/libraries/:id — 改 display_name/description/status(停用庫=翻 status slot)。
|
||||
portalRouter.patch('/portal/admin/libraries/:id', (c) =>
|
||||
run(c, async () => {
|
||||
|
||||
Reference in New Issue
Block a user