feat: implement robust offline-first sync with Dexie.js
- Migrated to Dexie.js for IndexedDB management - Added optimistic UI for tasting notes with 'Wartet auf Sync' badge - Implemented background caching for tags and buddies - Unified scanning and tasting sync in a global UploadQueue
This commit is contained in:
57
src/lib/db.ts
Normal file
57
src/lib/db.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import Dexie, { type Table } from 'dexie';
|
||||
|
||||
export interface PendingScan {
|
||||
id?: number;
|
||||
imageBase64: string;
|
||||
timestamp: number;
|
||||
provider?: 'gemini' | 'nebius';
|
||||
}
|
||||
|
||||
export interface PendingTasting {
|
||||
id?: number;
|
||||
bottle_id: string;
|
||||
data: {
|
||||
session_id?: string;
|
||||
rating: number;
|
||||
nose_notes?: string;
|
||||
palate_notes?: string;
|
||||
finish_notes?: string;
|
||||
is_sample?: boolean;
|
||||
buddy_ids?: string[];
|
||||
tag_ids?: string[];
|
||||
};
|
||||
photo?: string; // Optional photo if taken during tasting
|
||||
tasted_at: string;
|
||||
}
|
||||
|
||||
export interface CachedTag {
|
||||
id: string;
|
||||
name: string;
|
||||
category: string;
|
||||
is_system_default: boolean;
|
||||
popularity_score: number;
|
||||
}
|
||||
|
||||
export interface CachedBuddy {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class WhiskyDexie extends Dexie {
|
||||
pending_scans!: Table<PendingScan>;
|
||||
pending_tastings!: Table<PendingTasting>;
|
||||
cache_tags!: Table<CachedTag>;
|
||||
cache_buddies!: Table<CachedBuddy>;
|
||||
|
||||
constructor() {
|
||||
super('WhiskyVault');
|
||||
this.version(1).stores({
|
||||
pending_scans: '++id, timestamp',
|
||||
pending_tastings: '++id, bottle_id, tasted_at',
|
||||
cache_tags: 'id, category, name',
|
||||
cache_buddies: 'id, name'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const db = new WhiskyDexie();
|
||||
@@ -1,66 +0,0 @@
|
||||
export interface PendingBottle {
|
||||
id: string;
|
||||
imageBase64: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
const DB_NAME = 'WhiskyVaultOffline';
|
||||
const STORE_NAME = 'pendingCaptures';
|
||||
const DB_VERSION = 1;
|
||||
|
||||
export const openDB = (): Promise<IDBDatabase> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = (event.target as IDBOpenDBRequest).result;
|
||||
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
||||
db.createObjectStore(STORE_NAME, { keyPath: 'id' });
|
||||
}
|
||||
};
|
||||
|
||||
request.onsuccess = (event) => {
|
||||
resolve((event.target as IDBOpenDBRequest).result);
|
||||
};
|
||||
|
||||
request.onerror = (event) => {
|
||||
reject((event.target as IDBOpenDBRequest).error);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const savePendingBottle = async (bottle: PendingBottle): Promise<void> => {
|
||||
const db = await openDB();
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = db.transaction(STORE_NAME, 'readwrite');
|
||||
const store = transaction.objectStore(STORE_NAME);
|
||||
const request = store.put(bottle);
|
||||
|
||||
request.onsuccess = () => resolve();
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
};
|
||||
|
||||
export const getAllPendingBottles = async (): Promise<PendingBottle[]> => {
|
||||
const db = await openDB();
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = db.transaction(STORE_NAME, 'readonly');
|
||||
const store = transaction.objectStore(STORE_NAME);
|
||||
const request = store.getAll();
|
||||
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
};
|
||||
|
||||
export const deletePendingBottle = async (id: string): Promise<void> => {
|
||||
const db = await openDB();
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = db.transaction(STORE_NAME, 'readwrite');
|
||||
const store = transaction.objectStore(STORE_NAME);
|
||||
const request = store.delete(id);
|
||||
|
||||
request.onsuccess = () => resolve();
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user