import { defineStore } from 'pinia' import { openDB, type IDBPDatabase } from 'idb' type Track = { name: string duration?: number } type DBTrack = { name: string blob: Blob type?: string duration?: number } let dbPromise: Promise | null = null async function getDB() { if (!dbPromise) { dbPromise = openDB('repodructor-music', 1, { upgrade(db) { if (!db.objectStoreNames.contains('tracks')) { const store = db.createObjectStore('tracks', { keyPath: 'name' }) store.createIndex('name', 'name', { unique: true }) } } }) } return dbPromise } export const useMusicStore = defineStore('music', { state: () => ({ tracks: [] as Track[], cachedNames: new Set(), loading: false, error: null as string | null }), getters: { isCached: (state) => (name: string) => state.cachedNames.has(name) }, actions: { async fetchTracks() { this.loading = true this.error = null try { const response = await $fetch<{ tracks: Track[] }>('/api/music') this.tracks = response.tracks || [] } catch (e: any) { const errorMsg = e?.message || 'Failed to load tracks' this.error = errorMsg // Check if it's an auth error if (e?.statusCode === 401 || e?.statusCode === 403 || errorMsg.includes('401') || errorMsg.includes('403') || errorMsg.includes('Unauthorized')) { console.warn('[Music Store] Authentication error detected') // The useAuth composable will be notified via watch in components } } finally { this.loading = false } }, async loadCachedNames() { try { const db = await getDB() const tx = db.transaction('tracks', 'readonly') const store = tx.objectStore('tracks') const all = await store.getAllKeys() this.cachedNames = new Set((all as string[]) || []) } catch (e) { // ignore } }, async loadCachedTracks(): Promise { try { const db = await getDB() const tx = db.transaction('tracks', 'readonly') const store = tx.objectStore('tracks') const all = (await store.getAll()) as DBTrack[] const names = all.map(t => t.name) this.cachedNames = new Set(names) const list: Track[] = all.map(t => ({ name: t.name, duration: t.duration })) return list } catch (e) { return [] } }, async getCachedBlob(name: string): Promise { try { const db = await getDB() const entry = (await db.get('tracks', name)) as DBTrack | undefined if (entry && entry.blob) { this.cachedNames.add(name) return entry.blob } } catch (e) { // ignore } return null }, async saveTrackBlob(name: string, blob: Blob, duration?: number) { try { const db = await getDB() const payload: DBTrack = { name, blob, type: blob.type, duration } await db.put('tracks', payload) this.cachedNames.add(name) } catch (e) { // ignore } }, async deleteCachedTrack(name: string) { try { const db = await getDB() await db.delete('tracks', name) this.cachedNames.delete(name) } catch (e) { // ignore } }, // Fetch from API and store in IndexedDB async cacheByName(name: string, duration?: number): Promise { try { const encodedName = encodeURIComponent(name) const response = await fetch(`/api/music/${encodedName}`) if (!response.ok) { const errorMsg = `HTTP ${response.status}` // Detect authentication errors if (response.status === 401 || response.status === 403) { console.warn('[Music Store] Cache failed - authentication error:', response.status) this.error = `${errorMsg}: Unauthorized - Please log in` } throw new Error(errorMsg) } const blob = await response.blob() await this.saveTrackBlob(name, blob, duration) return true } catch (e: any) { console.error('[Music Store] Cache failed:', e) // Propagate auth errors if (e?.message?.includes('401') || e?.message?.includes('403')) { this.error = e.message } return false } } } })