Devlog-Suche (FTS5) + öffentlicher RSS-Feed
- FTS5-Volltextindex mit Trigger-Sync und einmaligem Backfill bestehender Devlogs - /api/devlogs?q= — Präfix-Suche, injection-sicher (Phrasen-Quoting) - Suchfeld mit Entprellung und Treffer-Zähler auf der Devlog-Seite - /feed.xml — RSS 2.0 der letzten 20 Devlogs (escaped, deutsche Titel) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,30 @@ if (!devlogCols.some((c) => c.name === 'images')) {
|
||||
db.exec(`ALTER TABLE devlogs ADD COLUMN images TEXT NOT NULL DEFAULT '[]'`);
|
||||
}
|
||||
|
||||
// Volltextsuche (FTS5) über Devlogs — Trigger halten den Index synchron
|
||||
db.exec(`
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS devlogs_fts USING fts5(content, message_id UNINDEXED);
|
||||
CREATE TRIGGER IF NOT EXISTS devlogs_fts_ai AFTER INSERT ON devlogs BEGIN
|
||||
INSERT INTO devlogs_fts (rowid, content, message_id)
|
||||
VALUES (new.rowid, new.content, new.message_id);
|
||||
END;
|
||||
CREATE TRIGGER IF NOT EXISTS devlogs_fts_ad AFTER DELETE ON devlogs BEGIN
|
||||
DELETE FROM devlogs_fts WHERE rowid = old.rowid;
|
||||
END;
|
||||
`);
|
||||
// Bestehende Zeilen nachindexieren (einmalig nach der Migration)
|
||||
{
|
||||
const ftsCount = db.prepare('SELECT count(*) AS n FROM devlogs_fts').get().n;
|
||||
const devlogCount = db.prepare('SELECT count(*) AS n FROM devlogs').get().n;
|
||||
if (ftsCount !== devlogCount) {
|
||||
db.exec(`
|
||||
DELETE FROM devlogs_fts;
|
||||
INSERT INTO devlogs_fts (rowid, content, message_id)
|
||||
SELECT rowid, content, message_id FROM devlogs;
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
// Laufzeit-Einstellungen (Settings-Seite im Webinterface) — überschreiben Env-Defaults
|
||||
db.exec('CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT NOT NULL)');
|
||||
const getSettingStmt = db.prepare('SELECT value FROM settings WHERE key = ?');
|
||||
@@ -110,6 +134,32 @@ export function listDevlogs(limit, offset) {
|
||||
return { items: selectDevlogs.all(limit, offset), total: countDevlogsStmt.get().n };
|
||||
}
|
||||
|
||||
const searchDevlogsStmt = db.prepare(`
|
||||
SELECT d.message_id, d.content, d.author_name, d.posted_at, d.images
|
||||
FROM devlogs_fts f
|
||||
JOIN devlogs d ON d.rowid = f.rowid
|
||||
WHERE devlogs_fts MATCH ?
|
||||
ORDER BY d.posted_at DESC LIMIT ? OFFSET ?
|
||||
`);
|
||||
const searchDevlogsCountStmt = db.prepare(
|
||||
'SELECT count(*) AS n FROM devlogs_fts WHERE devlogs_fts MATCH ?'
|
||||
);
|
||||
|
||||
/** Suchbegriff in eine sichere FTS5-Query übersetzen: jede Eingabe als Präfix-Phrase */
|
||||
function ftsQuery(input) {
|
||||
const terms = input.split(/\s+/).filter(Boolean).slice(0, 8);
|
||||
return terms.map((t) => `"${t.replaceAll('"', '')}"*`).join(' ');
|
||||
}
|
||||
|
||||
export function searchDevlogs(input, limit, offset) {
|
||||
const query = ftsQuery(input);
|
||||
if (!query) return { items: [], total: 0 };
|
||||
return {
|
||||
items: searchDevlogsStmt.all(query, limit, offset),
|
||||
total: searchDevlogsCountStmt.get(query).n,
|
||||
};
|
||||
}
|
||||
|
||||
const selectCommits = db.prepare(`
|
||||
SELECT sha, repo, branch, message, author_name, url, committed_at
|
||||
FROM commits ORDER BY committed_at DESC LIMIT ? OFFSET ?
|
||||
|
||||
Reference in New Issue
Block a user