import localForage from 'localforage'; import type { ImageType, ImageMetaType, ImageSyncStatus } from './image.type'; const imageStore = localForage.createInstance({ name: 'opennotas', storeName: 'images', }); class ImageRepository { async getAllImagesMeta(): Promise { const metas = JSON.parse(await localForage.getItem('imagesMeta') || '[]'); return metas as ImageMetaType[]; } async getImagesByNoteId(noteId: string): Promise { const metas = await this.getAllImagesMeta(); return metas.filter((meta) => meta.noteId === noteId); } async getImage(imageId: string): Promise { const meta = await this.getImageMeta(imageId); if (!meta) return null; const blob = await imageStore.getItem(`image-blob-${imageId}`); if (!blob) return null; return { ...meta, blob }; } async getImageMeta(imageId: string): Promise { const metas = await this.getAllImagesMeta(); return metas.find((meta) => meta.id === imageId) || null; } async saveImage(image: ImageType): Promise { await imageStore.setItem(`image-blob-${image.id}`, image.blob); const metas = await this.getAllImagesMeta(); const existingIndex = metas.findIndex((m) => m.id === image.id); const meta: ImageMetaType = { id: image.id, noteId: image.noteId, mimeType: image.mimeType, size: image.size, syncStatus: image.syncStatus, remoteUrl: image.remoteUrl, createdAt: image.createdAt, updatedAt: image.updatedAt, }; if (existingIndex >= 0) { metas[existingIndex] = meta; } else { metas.push(meta); } await localForage.setItem('imagesMeta', JSON.stringify(metas)); return image; } async updateSyncStatus( imageId: string, syncStatus: ImageSyncStatus, remoteUrl?: string ): Promise { const metas = await this.getAllImagesMeta(); const index = metas.findIndex((m) => m.id === imageId); if (index < 0) return null; metas[index] = { ...metas[index], syncStatus, remoteUrl: remoteUrl || metas[index].remoteUrl, updatedAt: Date.now(), }; await localForage.setItem('imagesMeta', JSON.stringify(metas)); return metas[index]; } async getPendingImages(): Promise { const metas = await this.getAllImagesMeta(); const pendingMetas = metas.filter((m) => m.syncStatus === 'pending'); const images: ImageType[] = []; for (const meta of pendingMetas) { const blob = await imageStore.getItem(`image-blob-${meta.id}`); if (blob) { images.push({ ...meta, blob }); } } return images; } async deleteImage(imageId: string): Promise { await imageStore.removeItem(`image-blob-${imageId}`); const metas = await this.getAllImagesMeta(); const newMetas = metas.filter((m) => m.id !== imageId); await localForage.setItem('imagesMeta', JSON.stringify(newMetas)); return true; } async deleteImagesByNoteId(noteId: string): Promise { const metas = await this.getAllImagesMeta(); const toDelete = metas.filter((m) => m.noteId === noteId); for (const meta of toDelete) { await imageStore.removeItem(`image-blob-${meta.id}`); } const newMetas = metas.filter((m) => m.noteId !== noteId); await localForage.setItem('imagesMeta', JSON.stringify(newMetas)); return true; } async cleanupOrphanedBlobs(): Promise { const metas = await this.getAllImagesMeta(); const validIds = new Set(metas.map((m) => m.id)); const keys = await imageStore.keys(); for (const key of keys) { if (key.startsWith('image-blob-')) { const id = key.replace('image-blob-', ''); if (!validIds.has(id)) { await imageStore.removeItem(key); } } } } async saveImageFromRemote( imageId: string, blob: Blob, mimeType: string, remoteUrl: string ): Promise { await imageStore.setItem(`image-blob-${imageId}`, blob); const now = Date.now(); const meta: ImageMetaType = { id: imageId, noteId: '', // We don't know the noteId when fetching from remote mimeType, size: blob.size, syncStatus: 'synced', remoteUrl, createdAt: now, updatedAt: now, }; const metas = await this.getAllImagesMeta(); const existingIndex = metas.findIndex((m) => m.id === imageId); if (existingIndex >= 0) { metas[existingIndex] = meta; } else { metas.push(meta); } await localForage.setItem('imagesMeta', JSON.stringify(metas)); return meta; } } export default ImageRepository;