/** * Property On — die abgelegten Dokumente liegen in IndexedDB (Runde 10, §2.6). * * Keine Mockup-Variante und kein zweiter Backend-Dienst: Die Anwendung läuft * im Browser, und der Browser hat eine Ablage, die auch Dateien verträgt. Ein * Server nur für die Dateihaltung wäre Infrastruktur für einen einzigen Zweck. * * Aufgeteilt in zwei Speicher: die Metadaten samt extrahiertem Text in `meta`, * die Originaldatei in `blobs`. So kann die Liste aufgebaut werden, ohne dass * zwanzig Megabyte PDF durch den Speicher wandern — gebraucht wird die Datei * erst, wenn jemand sie herunterlädt. */ import type { IResearchDocumentProvider } from './IResearchDocumentProvider' import type { ResearchDocument } from '../domain/researchDocument' const DB_NAME = 'property-on' const DB_VERSION = 1 const STORE_META = 'research-document-meta' const STORE_BLOBS = 'research-document-blobs' function openDb(): Promise { return new Promise((resolve, reject) => { const req = indexedDB.open(DB_NAME, DB_VERSION) req.onupgradeneeded = () => { const db = req.result if (!db.objectStoreNames.contains(STORE_META)) db.createObjectStore(STORE_META, { keyPath: 'id' }) if (!db.objectStoreNames.contains(STORE_BLOBS)) db.createObjectStore(STORE_BLOBS) } req.onsuccess = () => resolve(req.result) req.onerror = () => reject(req.error ?? new Error('IndexedDB liess sich nicht öffnen.')) }) } /** Eine Transaktion als Versprechen — IndexedDB spricht sonst nur in Ereignissen. */ function alsVersprechen( req: IDBRequest, tx: IDBTransaction, ): Promise { return new Promise((resolve, reject) => { req.onsuccess = () => resolve(req.result) req.onerror = () => reject(req.error ?? new Error('Zugriff auf die Dokumentenablage fehlgeschlagen.')) tx.onabort = () => reject(tx.error ?? new Error('Die Dokumentenablage brach die Transaktion ab.')) }) } export const IndexedDbResearchDocumentProvider: IResearchDocumentProvider = { async getAll() { const db = await openDb() const tx = db.transaction(STORE_META, 'readonly') const alle = await alsVersprechen(tx.objectStore(STORE_META).getAll() as IDBRequest, tx) db.close() // Neuestes zuerst — wer eben etwas abgelegt hat, sucht es oben. return alle.sort((a, b) => b.uploadedAt.localeCompare(a.uploadedAt)) }, async add(doc, blob) { const db = await openDb() const tx = db.transaction([STORE_META, STORE_BLOBS], 'readwrite') tx.objectStore(STORE_META).put(doc) await alsVersprechen(tx.objectStore(STORE_BLOBS).put(blob, doc.id), tx) db.close() }, async remove(id) { const db = await openDb() const tx = db.transaction([STORE_META, STORE_BLOBS], 'readwrite') tx.objectStore(STORE_META).delete(id) await alsVersprechen(tx.objectStore(STORE_BLOBS).delete(id), tx) db.close() }, async getBlob(id) { const db = await openDb() const tx = db.transaction(STORE_BLOBS, 'readonly') const blob = await alsVersprechen(tx.objectStore(STORE_BLOBS).get(id) as IDBRequest, tx) db.close() return blob ?? null }, }