import { Database } from "@db/sqlite"; import type { Note } from "./note.ts"; export const db = new Database(Deno.env.get("SQLITE_PATH") ?? "notes.db"); try { db.exec("PRAGMA journal_mode=WAL"); db.exec("PRAGMA busy_timeout=5000"); const [row] = db.prepare("PRAGMA journal_mode").all<{ journal_mode: string }>(); console.log(`[db] journal_mode=${row.journal_mode}, busy_timeout=5000`); } catch (e) { console.error("[db] failed to set PRAGMAs:", e); } type NoteRow = { did: string; rkey: string; title: string; publishedAt: string; createdAt: string; }; export const getNotes = (cursor?: string, limit = 20) => { const notes = cursor ? db.prepare( "SELECT did, rkey, title, publishedAt, createdAt, language FROM note WHERE discoverable = 1 AND rkey < ? ORDER BY rkey DESC LIMIT ?", ).all(cursor, limit) : db.prepare( "SELECT did, rkey, title, publishedAt, createdAt, language FROM note WHERE discoverable = 1 ORDER BY rkey DESC LIMIT ?", ).all(limit); return { notes, cursor: notes.length === limit ? notes[notes.length - 1].rkey : undefined, }; }; export const getNotesByDid = (did: string, cursor?: string, limit = 20) => { const notes = cursor ? db.prepare( "SELECT did, rkey, title, publishedAt, createdAt, language FROM note WHERE discoverable = 1 AND did = ? AND rkey < ? ORDER BY rkey DESC LIMIT ?", ).all(did, cursor, limit) : db.prepare( "SELECT did, rkey, title, publishedAt, createdAt, language FROM note WHERE discoverable = 1 AND did = ? ORDER BY rkey DESC LIMIT ?", ).all(did, limit); return { notes, cursor: notes.length === limit ? notes[notes.length - 1].rkey : undefined, }; }; export const getNotesByDids = (dids: string[], cursor?: string, limit = 20) => { if (dids.length === 0) return { notes: [] }; const placeholders = dids.map(() => "?").join(", "); const notes = cursor ? db.prepare( `SELECT did, rkey, title, publishedAt, createdAt, language FROM note WHERE discoverable = 1 AND did IN (${placeholders}) AND rkey < ? ORDER BY rkey DESC LIMIT ?`, ).all(...dids, cursor, limit) : db.prepare( `SELECT did, rkey, title, publishedAt, createdAt, language FROM note WHERE discoverable = 1 AND did IN (${placeholders}) ORDER BY rkey DESC LIMIT ?`, ).all(...dids, limit); return { notes, cursor: notes.length === limit ? notes[notes.length - 1].rkey : undefined, }; }; export const deleteNote = ({ did, rkey }: { did: string; rkey: string }) => { db.exec("DELETE FROM note WHERE did = ? AND rkey = ?", did, rkey); }; export const getCursor = (): string | undefined => { const row = db.prepare( "SELECT value FROM state WHERE key = 'cursor'", ).get<{ value: string }>(); return row?.value; }; export const saveCursor = (cursor: number) => { db.exec( "INSERT OR REPLACE INTO state (key, value) VALUES ('cursor', ?)", String(cursor), ); }; type WebhookSubscriptionRow = { id: number; did: string; method: string; url: string; }; export const addWebhookSubscription = ( { did, method, url }: Omit, ): WebhookSubscriptionRow => { db.exec( "INSERT INTO webhook_subscription (did, method, url) VALUES (?, ?, ?)", did, method, url, ); return db.prepare( "SELECT id, did, method, url FROM webhook_subscription WHERE id = last_insert_rowid()", ).get()!; }; export const deleteWebhooksByDid = (did: string): void => { db.exec("DELETE FROM webhook_subscription WHERE did = ?", did); }; export const getWebhooksByDid = (did: string): WebhookSubscriptionRow[] => { return db.prepare( "SELECT id, did, method, url FROM webhook_subscription WHERE did = ? ORDER BY id DESC LIMIT 10", ).all(did); }; export const upsertNote = (note: Note) => { const now = new Date().toISOString(); db.exec( ` INSERT INTO note ( title, publishedAt, createdAt, did, rkey, discoverable, language ) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(did, rkey) DO UPDATE SET title = excluded.title, publishedAt = excluded.publishedAt, discoverable = excluded.discoverable, language = excluded.language `, note.title, note.publishedAt ? new Date(note.publishedAt).toISOString() : now, note.createdAt ? new Date(note.createdAt).toISOString() : now, note.did, note.rkey, note.discoverable !== false ? 1 : 0, note.language, ); };