feat(t135): 庫目錄可自主移除+標示還在不在同步

leo:「需要加移除按鈕。因為別人裝錯我沒辦法幫他弄,需要可以自主」
+「你應該要顯示這個庫沒有本地對應的 folder,那就不容易刪錯」
(兩態非三態——leo 二修:「分兩種沒意義」,那是內部狀態不是用戶分類)。
- DELETE /portal/admin/libraries/:id(登記簿)與 by-name/:name(auto 庫需輸入庫名確認)
- kbdb 加 deprecate-by-library(auto 庫移除=標 deprecated,資料保留可還原)
- daemon/libraries 存 active 清單 → 卡片標 🟢同步中/灰目前沒有在同步
- 不自動刪(daemon 可能沒開機);daemon 從未回報時整列不標
vitest 24 passed(1 紅=console HTML 搬遷陳舊測試,非本案)。
(實作=子 CC;驗證+commit=總管。含 t116/t117 先前未 commit 的 graph-executor/wasi-shim 修正)
This commit is contained in:
uncle6me-web
2026-07-29 14:45:44 +08:00
parent 6d4980d3d7
commit 0860e84d22
11 changed files with 362 additions and 5 deletions
+21
View File
@@ -126,6 +126,27 @@ export async function deleteEntry(db: D1Database, id: string): Promise<void> {
await db.prepare('DELETE FROM entries WHERE id = ?').bind(id).run();
}
/**
* 把某 owner 下某庫的所有 entries 標 deprecatedt135 by-name 移除語意)。
* 沿用既有 deprecated 機制:metadata_json.status='deprecated' → 搜尋端過濾、庫列表排除。
* 回 deprecated 的筆數(0 = 庫名不存在或早已全部 deprecated)。
*/
export async function deprecateEntriesByLibrary(db: D1Database, ownerId: string, library: string): Promise<number> {
const result = await db
.prepare(
`UPDATE entries
SET metadata_json = json_set(COALESCE(metadata_json, '{}'), '$.status', 'deprecated'),
updated_at = unixepoch()
WHERE owner_id = ?
AND COALESCE(json_extract(metadata_json, '$.library'), 'general') = ?
AND (json_extract(metadata_json, '$.status') IS NULL
OR json_extract(metadata_json, '$.status') != 'deprecated')`,
)
.bind(ownerId, library)
.run();
return (result.meta?.changes as number | undefined) ?? 0;
}
// 「庫」filter 的 SQL 謂詞(portal-auth P1design §3.2/§3.3;零建表,同 #5.1 source 的 json_extract 先例)。
// COALESCE(x,'general') IN (…) ≡ SDD §3.3 寫的 (x IN (…) OR (x IS NULL AND 'general' IN (…)))——
// 語意完全相同(未標記/無 metadata_json 的舊資料歸 'general'),但單組佔位符、不用重複綁參數。
+15
View File
@@ -209,3 +209,18 @@ export async function searchByTemplate(db: D1Database, template: string, owner_i
}
return ids.map((id) => byId.get(id)).filter((r): r is RecordResult => !!r);
}
/** 刪除一筆 record:先刪 entry_valuesFK),再刪底層 entries。回 false 表示 record 不存在。 */
export async function deleteRecord(db: D1Database, recordId: string): Promise<boolean> {
const evRes = await db
.prepare('SELECT entry_id FROM entry_values WHERE record_id = ?')
.bind(recordId)
.all<{ entry_id: string }>();
const rows = evRes.results ?? [];
if (rows.length === 0) return false;
await db.prepare('DELETE FROM entry_values WHERE record_id = ?').bind(recordId).run();
for (const { entry_id } of rows) {
await db.prepare('DELETE FROM entries WHERE id = ?').bind(entry_id).run();
}
return true;
}
+13
View File
@@ -3,6 +3,7 @@ import { Hono } from 'hono';
import type { Bindings } from '../types';
import {
createEntry,
deprecateEntriesByLibrary,
getEntry,
listEntries,
updateEntry,
@@ -139,6 +140,18 @@ entryRoutes.get('/:id', async (c) => {
return c.json({ success: true, entry });
});
// PATCH /entries/deprecate-by-library — body {owner_id, library}。
// t135:把某租戶某庫的所有 entries 標 deprecated,讓庫從 auto 清單消失。
// 此路由必須在 '/:id' 之前,否則 'deprecate-by-library' 會被當成 id 參數。
entryRoutes.patch('/deprecate-by-library', async (c) => {
const body = (await c.req.json().catch(() => null)) as { owner_id?: string; library?: string } | null;
const ownerId = String(body?.owner_id ?? '').trim();
const library = String(body?.library ?? '').trim();
if (!ownerId || !library) return c.json({ success: false, error: 'owner_id 與 library 必填' }, 400);
const count = await deprecateEntriesByLibrary(c.env.DB, ownerId, library);
return c.json({ success: true, deprecated_count: count });
});
// PATCH /entries/:id
entryRoutes.patch('/:id', async (c) => {
const body = await c.req.json().catch(() => ({}));
+8 -1
View File
@@ -1,7 +1,7 @@
// Records route — structured records (entry_values composed by a template).
import { Hono } from 'hono';
import type { Bindings } from '../types';
import { createRecord, getRecord, searchByTemplate, updateRecord } from '../actions/record-crud';
import { createRecord, deleteRecord, getRecord, searchByTemplate, updateRecord } from '../actions/record-crud';
export const recordRoutes = new Hono<{ Bindings: Bindings }>();
@@ -47,3 +47,10 @@ recordRoutes.patch('/:recordId', async (c) => {
return c.json({ success: false, error: e instanceof Error ? e.message : String(e) }, 400);
}
});
// DELETE /records/:recordId — 刪除一筆 record 及其底層 entries。
recordRoutes.delete('/:recordId', async (c) => {
const found = await deleteRecord(c.env.DB, c.req.param('recordId'));
if (!found) return c.json({ success: false, error: 'not found' }, 404);
return c.json({ success: true });
});