import { localStorageAPI, STORAGE_KEYS } from "./localStorage"; // Migration utility to copy localStorage data to backend export async function migrateLocalStorageToBackend(customBase) { // Determine API base dynamically from saved settings unless overridden const apiSettings = (() => { try { return JSON.parse(localStorage.getItem("api_settings") || "{}"); } catch { return {}; } })(); const API_BASE = (() => { if (customBase) return customBase; if (apiSettings.protocol && apiSettings.hostname) { const p = apiSettings.port || ""; const portPart = p ? `:${p}` : ""; return `${apiSettings.protocol}://${apiSettings.hostname}${portPart}/api`; } return `https://houseofprayer.ddns.net:8080/api`; })(); console.log("[Migration] Upload API Base:", API_BASE); try { // Preload existing backend data to minimize duplicates let existingSongs = []; let existingProfiles = []; let existingPlans = []; try { console.log("[Migration] Fetching existing backend data..."); const [songsRes, profilesRes, plansRes] = await Promise.all([ fetch(`${API_BASE}/songs`).then((r) => (r.ok ? r.json() : [])), fetch(`${API_BASE}/profiles`).then((r) => (r.ok ? r.json() : [])), fetch(`${API_BASE}/plans`).then((r) => (r.ok ? r.json() : [])), ]); existingSongs = songsRes; existingProfiles = profilesRes; existingPlans = plansRes; } catch (e) {} const existingSongTitles = new Set( existingSongs.map((s) => (s.title || "").toLowerCase()) ); const existingProfileIds = new Set(existingProfiles.map((p) => p.id)); const existingPlanDates = new Set(existingPlans.map((pl) => pl.date)); // 1. Migrate Songs const songs = await localStorageAPI.getSongs(""); for (const song of songs) { if (existingSongTitles.has((song.title || "").toLowerCase())) { continue; } try { const res = await fetch(`${API_BASE}/songs`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(song), }); if (res.ok) { } else { } } catch (err) {} } // 2. Migrate Profiles const profiles = await localStorageAPI.getProfiles(); for (const profile of profiles) { if (existingProfileIds.has(profile.id)) { continue; } try { const res = await fetch(`${API_BASE}/profiles`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(profile), }); if (res.ok) { } else { } } catch (err) {} } // 3. Migrate Plans const plans = await localStorageAPI.getPlans(); for (const plan of plans) { if (existingPlanDates.has(plan.date)) { continue; } try { const planSongs = await localStorageAPI.getPlanSongs(plan.id); const songsData = []; for (const ps of planSongs) { const song = await localStorageAPI.getSong(ps.song_id); if (song) songsData.push({ ...song, order: ps.order_index }); } const res = await fetch(`${API_BASE}/plans`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...plan, songs: songsData }), }); if (res.ok) { } else { } } catch (err) {} } // 4. Migrate Profile Songs const allProfileSongs = JSON.parse( localStorage.getItem(STORAGE_KEYS.PROFILE_SONGS) || "[]" ); for (const ps of allProfileSongs) { try { const res = await fetch(`${API_BASE}/profiles/${ps.profile_id}/songs`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ song_id: ps.song_id }), }); if (res.ok) { } else { } } catch (err) { console.error( `❌ Failed to migrate profile-song (profile ${ps.profile_id} song ${ps.song_id}):`, err ); } } // 5. Migrate Profile Song Keys (array form in localStorage) const profileSongKeysArr = JSON.parse( localStorage.getItem(STORAGE_KEYS.PROFILE_SONG_KEYS) || "[]" ); for (const entry of profileSongKeysArr) { if (!entry.profile_id || !entry.song_id || !entry.key) continue; try { const res = await fetch( `${API_BASE}/profiles/${entry.profile_id}/songs/${entry.song_id}/key`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ key: entry.key }), } ); if (res.ok) { console.log( `✅ Migrated key for profile ${entry.profile_id} song ${entry.song_id}` ); } else { console.warn( `⚠️ Failed to migrate key for profile ${entry.profile_id} song ${entry.song_id}` ); } } catch (err) { console.error( `❌ Failed to migrate key (profile ${entry.profile_id} song ${entry.song_id}):`, err ); } } return { success: true, migrated: { songs: songs.length, profiles: profiles.length, plans: plans.length, profileSongs: allProfileSongs.length, keys: profileSongKeysArr.length, }, }; } catch (error) { return { success: false, error: error.message }; } } // Sync FROM backend TO localStorage (pull down all data) export async function syncFromBackendToLocalStorage(customBase) { const apiSettings = (() => { try { return JSON.parse(localStorage.getItem("api_settings") || "{}"); } catch { return {}; } })(); const API_BASE = (() => { if (customBase) return customBase; if (apiSettings.protocol && apiSettings.hostname) { const p = apiSettings.port || ""; const portPart = p ? `:${p}` : ""; return `${apiSettings.protocol}://${apiSettings.hostname}${portPart}/api`; } return `https://houseofprayer.ddns.net:8080/api`; })(); console.log("[Migration] Download API Base:", API_BASE); try { // 1. Sync Songs const songsRes = await fetch(`${API_BASE}/songs`); if (!songsRes.ok) throw new Error(`Failed to fetch songs: ${songsRes.status}`); const backendSongs = await songsRes.json(); const localSongs = await localStorageAPI.getSongs(""); const localSongIds = new Set(localSongs.map((s) => s.id)); let songsAdded = 0; let songsUpdated = 0; for (const song of backendSongs) { // Fetch full song details try { const detailRes = await fetch(`${API_BASE}/songs/${song.id}`); if (!detailRes.ok) { continue; } const fullSong = await detailRes.json(); if (!fullSong.lyrics && !fullSong.chords) { } if (localSongIds.has(fullSong.id)) { await localStorageAPI.updateSong(fullSong.id, fullSong); songsUpdated++; } else { await localStorageAPI.createSong(fullSong); songsAdded++; } } catch (err) {} } // 2. Sync Profiles const profilesRes = await fetch(`${API_BASE}/profiles`); if (!profilesRes.ok) throw new Error(`Failed to fetch profiles: ${profilesRes.status}`); const backendProfiles = await profilesRes.json(); const localProfiles = await localStorageAPI.getProfiles(); const localProfileIds = new Set(localProfiles.map((p) => p.id)); let profilesAdded = 0; let profilesUpdated = 0; for (const profile of backendProfiles) { if (localProfileIds.has(profile.id)) { await localStorageAPI.updateProfile(profile.id, profile); profilesUpdated++; } else { await localStorageAPI.createProfile(profile); profilesAdded++; } } // 3. Sync Plans const plansRes = await fetch(`${API_BASE}/plans`); if (!plansRes.ok) throw new Error(`Failed to fetch plans: ${plansRes.status}`); const backendPlans = await plansRes.json(); const localPlans = await localStorageAPI.getPlans(); const localPlanIds = new Set(localPlans.map((p) => p.id)); let plansAdded = 0; let plansUpdated = 0; for (const plan of backendPlans) { if (localPlanIds.has(plan.id)) { await localStorageAPI.updatePlan(plan.id, plan); plansUpdated++; } else { await localStorageAPI.createPlan(plan); plansAdded++; } } return { success: true, synced: { songs: { added: songsAdded, updated: songsUpdated }, profiles: { added: profilesAdded, updated: profilesUpdated }, plans: { added: plansAdded, updated: plansUpdated }, }, }; } catch (error) { return { success: false, error: error.message }; } } // Full bidirectional sync: merge localStorage and backend data export async function fullSync(customBase) { // First, push local changes to backend const uploadResult = await migrateLocalStorageToBackend(customBase); // Then, pull backend changes to local const downloadResult = await syncFromBackendToLocalStorage(customBase); // Trigger page refresh to show updated data without full reload window.dispatchEvent(new CustomEvent("dataFullySynced")); // Also trigger events that the app already listens for window.dispatchEvent(new Event("songsChanged")); window.dispatchEvent(new Event("plansChanged")); return { success: uploadResult.success && downloadResult.success, upload: uploadResult, download: downloadResult, }; } // Export functions to call from browser console window.migrateData = migrateLocalStorageToBackend; window.syncFromBackend = syncFromBackendToLocalStorage; window.fullSync = fullSync;