interface Note { id: string x: number y: number content: string } interface Space { id: string notes: Note[] } type StorageMode = 'indexeddb' | 'database' interface StorageAdapter { init(): Promise getSpace(id: string): Promise saveSpace(space: Space): Promise createSpace(id: string): Promise addNoteToSpace(spaceId: string, note: Note): Promise updateNoteInSpace(spaceId: string, noteId: string, updates: Partial): Promise deleteNoteFromSpace(spaceId: string, noteId: string): Promise getAllSpaceIds(): Promise deleteSpace(id: string): Promise } class IndexedDBAdapter implements StorageAdapter { private db: IDBDatabase | null = null async init() { return new Promise((resolve, reject) => { const request = indexedDB.open('palace-db', 1) request.onerror = () => reject(request.error) request.onsuccess = () => { this.db = request.result resolve() } request.onupgradeneeded = (event) => { const db = (event.target as IDBOpenDBRequest).result if (!db.objectStoreNames.contains('spaces')) { db.createObjectStore('spaces', { keyPath: 'id' }) } } }) } async getSpace(id: string): Promise { if (!this.db) { await this.init() } return new Promise((resolve, reject) => { const transaction = this.db!.transaction(['spaces'], 'readonly') const store = transaction.objectStore('spaces') const request = store.get(id) request.onerror = () => reject(request.error) request.onsuccess = () => resolve(request.result) }) } async saveSpace(space: Space): Promise { if (!this.db) { await this.init() } return new Promise((resolve, reject) => { const transaction = this.db!.transaction(['spaces'], 'readwrite') const store = transaction.objectStore('spaces') const request = store.put(space) request.onerror = () => reject(request.error) request.onsuccess = () => resolve() }) } async createSpace(id: string): Promise { const space: Space = { id, notes: [] } await this.saveSpace(space) return space } async addNoteToSpace(spaceId: string, note: Note): Promise { const space = await this.getSpace(spaceId) if (!space) { throw new Error(`Space ${spaceId} not found`) } space.notes.push(note) await this.saveSpace(space) } async updateNoteInSpace(spaceId: string, noteId: string, updates: Partial): Promise { const space = await this.getSpace(spaceId) if (!space) { throw new Error(`Space ${spaceId} not found`) } const noteIndex = space.notes.findIndex(n => n.id === noteId) if (noteIndex === -1) { throw new Error(`Note ${noteId} not found in space ${spaceId}`) } space.notes[noteIndex] = { ...space.notes[noteIndex]!, ...updates } as Note await this.saveSpace(space) } async deleteNoteFromSpace(spaceId: string, noteId: string): Promise { const space = await this.getSpace(spaceId) if (!space) { throw new Error(`Space ${spaceId} not found`) } space.notes = space.notes.filter(n => n.id !== noteId) await this.saveSpace(space) } async getAllSpaceIds(): Promise { if (!this.db) { await this.init() } return new Promise((resolve, reject) => { const transaction = this.db!.transaction(['spaces'], 'readonly') const store = transaction.objectStore('spaces') const request = store.getAllKeys() request.onerror = () => reject(request.error) request.onsuccess = () => resolve(request.result as string[]) }) } async deleteSpace(id: string): Promise { if (!this.db) { await this.init() } return new Promise((resolve, reject) => { const transaction = this.db!.transaction(['spaces'], 'readwrite') const store = transaction.objectStore('spaces') const request = store.delete(id) request.onerror = () => reject(request.error) request.onsuccess = () => resolve() }) } } class DatabaseAdapter implements StorageAdapter { private readonly apiUrl: string constructor(apiUrl?: string) { this.apiUrl = apiUrl || this.getApiUrl() } private getApiUrl(): string { if (typeof window !== 'undefined' && window.location.hostname === 'palace.raphaelforment.fr') { return 'https://palace.raphaelforment.fr:3001/api' } return '/api' } async init(): Promise { // Database connection is handled per request } async getSpace(id: string): Promise { const response = await fetch(`${this.apiUrl}/spaces/${id}`) if (response.status === 404) { return undefined } if (!response.ok) { throw new Error(`Failed to get space: ${response.statusText}`) } return await response.json() } async saveSpace(space: Space): Promise { const response = await fetch(`${this.apiUrl}/spaces/${space.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(space) }) if (!response.ok) { throw new Error(`Failed to save space: ${response.statusText}`) } } async createSpace(id: string): Promise { const space: Space = { id, notes: [] } await this.saveSpace(space) return space } async addNoteToSpace(spaceId: string, note: Note): Promise { const response = await fetch(`${this.apiUrl}/spaces/${spaceId}/notes`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(note) }) if (!response.ok) { throw new Error(`Failed to add note: ${response.statusText}`) } } async updateNoteInSpace(spaceId: string, noteId: string, updates: Partial): Promise { const response = await fetch(`${this.apiUrl}/spaces/${spaceId}/notes/${noteId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updates) }) if (!response.ok) { throw new Error(`Failed to update note: ${response.statusText}`) } } async deleteNoteFromSpace(spaceId: string, noteId: string): Promise { const response = await fetch(`${this.apiUrl}/spaces/${spaceId}/notes/${noteId}`, { method: 'DELETE' }) if (!response.ok) { throw new Error(`Failed to delete note: ${response.statusText}`) } } async getAllSpaceIds(): Promise { const response = await fetch(`${this.apiUrl}/spaces`) if (!response.ok) { throw new Error(`Failed to get spaces: ${response.statusText}`) } const spaces = await response.json() return spaces.map((space: Space) => space.id) } async deleteSpace(id: string): Promise { const response = await fetch(`${this.apiUrl}/spaces/${id}`, { method: 'DELETE' }) if (!response.ok) { throw new Error(`Failed to delete space: ${response.statusText}`) } } } function getStorageMode(): StorageMode { return (localStorage.getItem('palace-storage-mode') as StorageMode) || 'indexeddb' } function setStorageMode(mode: StorageMode): void { localStorage.setItem('palace-storage-mode', mode) } function createStorageAdapter(): StorageAdapter { const mode = getStorageMode() switch (mode) { case 'database': return new DatabaseAdapter() case 'indexeddb': default: return new IndexedDBAdapter() } } export const db = createStorageAdapter() export { type Note, type Space, type StorageMode, getStorageMode, setStorageMode }