portal-auth P1(#24 #25):KBDB library filter 地基(不 merge,待總管審) (#50)
This commit was merged in pull request #50.
This commit is contained in:
@@ -58,6 +58,8 @@ export interface ListEntriesFilter {
|
||||
parent_id?: string;
|
||||
page_name?: string; // exact-match lookup (e.g. skill-/example- idempotency key)
|
||||
source?: string; // filter by metadata_json.$.source (ingest envelope source.uri). issue #5.1
|
||||
library?: string[]; // filter by metadata_json.$.library(多值 OR;portal-auth P1,#24/#25)。
|
||||
// 未帶=不過濾(向後相容硬驗收);未標記的舊資料視同 'general'(design §3.2)。
|
||||
q?: string; // keyword filter on content (LIKE). Arcrun#3 發現①:list 端點原本完全不吃
|
||||
// search/q,caller 帶了也被靜默丟棄(不是 458K 筆搜不到,是這個 filter 沒接)。
|
||||
limit?: number;
|
||||
@@ -81,6 +83,7 @@ export async function listEntries(db: D1Database, f: ListEntriesFilter = {}): Pr
|
||||
// source is queryable via SQLite json_extract on the existing metadata_json TEXT column —
|
||||
// no new column / no migration (表不變鐵律). Per issue #5.1 (頂層化 source 成可查 filter).
|
||||
if (f.source) { conds.push("json_extract(metadata_json, '$.source') = ?"); params.push(f.source); }
|
||||
if (f.library && f.library.length > 0) { conds.push(libraryPredicate(f.library)); params.push(...f.library); }
|
||||
if (f.q) { conds.push('content LIKE ?'); params.push(`%${f.q}%`); }
|
||||
const where = conds.length ? `WHERE ${conds.join(' AND ')}` : '';
|
||||
const limit = Math.min(f.limit ?? 100, 1000);
|
||||
@@ -123,19 +126,30 @@ export async function deleteEntry(db: D1Database, id: string): Promise<void> {
|
||||
await db.prepare('DELETE FROM entries WHERE id = ?').bind(id).run();
|
||||
}
|
||||
|
||||
// 「庫」filter 的 SQL 謂詞(portal-auth P1,design §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'),但單組佔位符、不用重複綁參數。
|
||||
function libraryPredicate(libraries: string[]): string {
|
||||
const placeholders = libraries.map(() => '?').join(',');
|
||||
return `COALESCE(json_extract(metadata_json, '$.library'), 'general') IN (${placeholders})`;
|
||||
}
|
||||
|
||||
// D1 LIKE keyword search (base; semantic search is the optional embed module).
|
||||
// entry_type: optional base filter (generic — caller passes any type, base stays type-agnostic).
|
||||
// library: optional 多值庫 filter(portal-auth P1);未帶=行為與舊版一字不變(向後相容)。
|
||||
export async function searchEntries(
|
||||
db: D1Database,
|
||||
q: string,
|
||||
owner_id?: string,
|
||||
entry_type?: string,
|
||||
limit = 50,
|
||||
library?: string[],
|
||||
): Promise<Entry[]> {
|
||||
const conds = ['content LIKE ?'];
|
||||
const params: unknown[] = [`%${q}%`];
|
||||
if (owner_id) { conds.push('owner_id = ?'); params.push(owner_id); }
|
||||
if (entry_type) { conds.push('entry_type = ?'); params.push(entry_type); }
|
||||
if (library && library.length > 0) { conds.push(libraryPredicate(library)); params.push(...library); }
|
||||
const res = await db
|
||||
.prepare(`SELECT * FROM entries WHERE ${conds.join(' AND ')} ORDER BY updated_at DESC LIMIT ?`)
|
||||
.bind(...params, Math.min(limit, 200))
|
||||
|
||||
Reference in New Issue
Block a user