// Records route — structured records (entry_values composed by a template). import { Hono } from 'hono'; import type { Bindings } from '../types'; import { createRecord, deleteRecord, getRecord, searchByTemplate, updateRecord } from '../actions/record-crud'; export const recordRoutes = new Hono<{ Bindings: Bindings }>(); // POST /records — { template, values:{slot:content}, owner_id? } recordRoutes.post('/', async (c) => { const body = await c.req.json().catch(() => null); if (!body || !body.template || !body.values) { return c.json({ success: false, error: 'template and values required' }, 400); } try { const rec = await createRecord(c.env.DB, body); return c.json({ success: true, record: rec }); } catch (e) { return c.json({ success: false, error: e instanceof Error ? e.message : String(e) }, 400); } }); // GET /records/triplet-stats?owner_id=... — 每個庫的三元組(關聯)數。 // t142(2026-07-29):政府驗收——顯示每個庫整理出幾條知識關聯。 // 計法:依 triplet 型 record 的 'library' slot 值分組計數。無 library slot 的舊三元組歸 general。 // 使用子查詢先取 distinct triplet record IDs(針對 owner),再 LEFT JOIN library slot, // 避免 N+1(全部一次 SQL 完成,不逐筆 getRecord)。 recordRoutes.get('/triplet-stats', async (c) => { const owner = c.req.query('owner_id') || ''; // 子查詢:找到屬於這個 owner 的所有 triplet records;LEFT JOIN library slot 取庫名 const rows = await c.env.DB.prepare( `SELECT COALESCE(NULLIF(lib_e.content, ''), 'general') AS library, COUNT(*) AS triplet_count FROM ( SELECT DISTINCT ev.record_id FROM entry_values ev JOIN templates t ON ev.template_id = t.id JOIN entries e ON ev.entry_id = e.id WHERE t.name = 'triplet' AND (?1 = '' OR e.owner_id = ?1) ) AS tr LEFT JOIN entry_values lev ON lev.record_id = tr.record_id AND lev.slot_name = 'library' LEFT JOIN entries lib_e ON lib_e.id = lev.entry_id GROUP BY COALESCE(NULLIF(lib_e.content, ''), 'general') ORDER BY library`, ) .bind(owner) .all<{ library: string; triplet_count: number }>(); const stats = (rows.results ?? []).map((r) => ({ library: r.library, triplet_count: r.triplet_count })); return c.json({ success: true, stats }); }); // GET /records/by-template/:template — list records of a template recordRoutes.get('/by-template/:template', async (c) => { const records = await searchByTemplate(c.env.DB, c.req.param('template'), c.req.query('owner_id') || undefined); return c.json({ success: true, records, count: records.length }); }); // GET /records/:recordId recordRoutes.get('/:recordId', async (c) => { const rec = await getRecord(c.env.DB, c.req.param('recordId')); if (!rec) return c.json({ success: false, error: 'not found' }, 404); return c.json({ success: true, record: rec }); }); // PATCH /records/:recordId — { values:{slot:content} } update existing record slot values // (mira-dissolve T2.1 / issue #6; deprecate = flip a slot value, append-only tables untouched). recordRoutes.patch('/:recordId', async (c) => { const body = await c.req.json().catch(() => null); if (!body || !body.values || typeof body.values !== 'object') { return c.json({ success: false, error: 'values required' }, 400); } try { const rec = await updateRecord(c.env.DB, c.req.param('recordId'), body.values); if (!rec) return c.json({ success: false, error: 'not found' }, 404); return c.json({ success: true, record: rec }); } catch (e) { 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 }); });