Skip to content

Commit 639df69

Browse files
authored
Merge pull request #52 from Carouan/codex/implement-user-storage-migration-for-settings
Migrate settings and local user profile persistence to IndexedDB
2 parents ef8961e + f789bc4 commit 639df69

3 files changed

Lines changed: 117 additions & 24 deletions

File tree

src/repositories/storageRepository.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,18 @@ export async function savePersistedProjects(projects) {
1515
await saveProjects(projects);
1616
}
1717

18-
export function loadPersistedSettings() {
18+
export async function loadPersistedSettings() {
1919
return loadSettings();
2020
}
2121

22-
export function savePersistedSettings(settings) {
23-
saveSettings(settings);
22+
export async function savePersistedSettings(settings) {
23+
await saveSettings(settings);
2424
}
2525

26-
export function loadPersistedUserProfile() {
26+
export async function loadPersistedUserProfile() {
2727
return loadUserProfile();
2828
}
2929

30-
export function savePersistedUserProfile(profile) {
31-
saveUserProfile(profile);
30+
export async function savePersistedUserProfile(profile) {
31+
await saveUserProfile(profile);
3232
}

src/services/storage.js

Lines changed: 101 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ const SETTINGS_STORAGE_KEY = "ide-projet-personnel.settings";
33
const USER_PROFILE_STORAGE_KEY = "ide-projet-personnel.user-profile";
44

55
const PROJECTS_DB_NAME = "ide-projet-personnel";
6-
const PROJECTS_DB_VERSION = 1;
6+
const PROJECTS_DB_VERSION = 2;
77
const PROJECTS_STORE_NAME = "project_storage";
88
const PROJECTS_RECORD_KEY = "projects";
9+
const APP_STORE_NAME = "app_storage";
10+
const SETTINGS_RECORD_KEY = "settings";
11+
const USER_PROFILE_RECORD_KEY = "user-profile";
912

1013
let projectsDbPromise = null;
1114

@@ -35,6 +38,9 @@ function openProjectsDb() {
3538
if (!db.objectStoreNames.contains(PROJECTS_STORE_NAME)) {
3639
db.createObjectStore(PROJECTS_STORE_NAME);
3740
}
41+
if (!db.objectStoreNames.contains(APP_STORE_NAME)) {
42+
db.createObjectStore(APP_STORE_NAME);
43+
}
3844
};
3945

4046
request.onsuccess = () => resolve(request.result);
@@ -89,6 +95,51 @@ async function writeProjectsToIndexedDb(projects) {
8995
});
9096
}
9197

98+
function runAppStore(mode, action) {
99+
return openProjectsDb().then(
100+
(db) =>
101+
new Promise((resolve, reject) => {
102+
const tx = db.transaction(APP_STORE_NAME, mode);
103+
const store = tx.objectStore(APP_STORE_NAME);
104+
105+
let settled = false;
106+
const resolveOnce = (value) => {
107+
if (settled) return;
108+
settled = true;
109+
resolve(value);
110+
};
111+
112+
const rejectOnce = (error) => {
113+
if (settled) return;
114+
settled = true;
115+
reject(error);
116+
};
117+
118+
tx.oncomplete = () => resolveOnce();
119+
tx.onerror = () => rejectOnce(tx.error || new Error("IndexedDB transaction failed"));
120+
tx.onabort = () => rejectOnce(tx.error || new Error("IndexedDB transaction aborted"));
121+
122+
action(store, resolveOnce, rejectOnce);
123+
})
124+
);
125+
}
126+
127+
async function readAppValueFromIndexedDb(recordKey) {
128+
return runAppStore("readonly", (store, resolve, reject) => {
129+
const request = store.get(recordKey);
130+
request.onsuccess = () => resolve(request.result);
131+
request.onerror = () => reject(request.error || new Error("Failed to read app record"));
132+
});
133+
}
134+
135+
async function writeAppValueToIndexedDb(recordKey, value) {
136+
await runAppStore("readwrite", (store, resolve, reject) => {
137+
const request = store.put(value, recordKey);
138+
request.onsuccess = () => resolve();
139+
request.onerror = () => reject(request.error || new Error("Failed to write app record"));
140+
});
141+
}
142+
92143
export async function loadProjects() {
93144
try {
94145
const indexedDbProjects = await readProjectsFromIndexedDb();
@@ -118,8 +169,7 @@ export async function saveProjects(projects) {
118169
}
119170
}
120171

121-
export function loadSettings() {
122-
const raw = localStorage.getItem(SETTINGS_STORAGE_KEY);
172+
function parseJsonObject(raw) {
123173
if (!raw) return null;
124174

125175
try {
@@ -129,21 +179,60 @@ export function loadSettings() {
129179
}
130180
}
131181

132-
export function saveSettings(settings) {
133-
localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(settings));
182+
export async function loadSettings() {
183+
try {
184+
const indexedDbSettings = await readAppValueFromIndexedDb(SETTINGS_RECORD_KEY);
185+
186+
if (indexedDbSettings && typeof indexedDbSettings === "object") {
187+
return indexedDbSettings;
188+
}
189+
190+
const rawLegacySettings = localStorage.getItem(SETTINGS_STORAGE_KEY);
191+
const legacySettings = parseJsonObject(rawLegacySettings);
192+
193+
if (rawLegacySettings !== null && legacySettings) {
194+
await writeAppValueToIndexedDb(SETTINGS_RECORD_KEY, legacySettings);
195+
}
196+
197+
return legacySettings;
198+
} catch {
199+
return parseJsonObject(localStorage.getItem(SETTINGS_STORAGE_KEY));
200+
}
134201
}
135202

136-
export function loadUserProfile() {
137-
const raw = localStorage.getItem(USER_PROFILE_STORAGE_KEY);
138-
if (!raw) return null;
203+
export async function saveSettings(settings) {
204+
try {
205+
await writeAppValueToIndexedDb(SETTINGS_RECORD_KEY, settings);
206+
} catch {
207+
localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(settings));
208+
}
209+
}
139210

211+
export async function loadUserProfile() {
140212
try {
141-
return JSON.parse(raw);
213+
const indexedDbUserProfile = await readAppValueFromIndexedDb(USER_PROFILE_RECORD_KEY);
214+
215+
if (indexedDbUserProfile && typeof indexedDbUserProfile === "object") {
216+
return indexedDbUserProfile;
217+
}
218+
219+
const rawLegacyUserProfile = localStorage.getItem(USER_PROFILE_STORAGE_KEY);
220+
const legacyUserProfile = parseJsonObject(rawLegacyUserProfile);
221+
222+
if (rawLegacyUserProfile !== null && legacyUserProfile) {
223+
await writeAppValueToIndexedDb(USER_PROFILE_RECORD_KEY, legacyUserProfile);
224+
}
225+
226+
return legacyUserProfile;
142227
} catch {
143-
return null;
228+
return parseJsonObject(localStorage.getItem(USER_PROFILE_STORAGE_KEY));
144229
}
145230
}
146231

147-
export function saveUserProfile(profile) {
148-
localStorage.setItem(USER_PROFILE_STORAGE_KEY, JSON.stringify(profile));
232+
export async function saveUserProfile(profile) {
233+
try {
234+
await writeAppValueToIndexedDb(USER_PROFILE_RECORD_KEY, profile);
235+
} catch {
236+
localStorage.setItem(USER_PROFILE_STORAGE_KEY, JSON.stringify(profile));
237+
}
149238
}

src/store/useAppStore.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ export function useAppStore() {
110110
async function hydrateStore() {
111111
try {
112112
const storedProjects = await loadPersistedProjects();
113-
const storedSettings = loadPersistedSettings();
114-
const storedUserProfile = loadPersistedUserProfile();
113+
const storedSettings = await loadPersistedSettings();
114+
const storedUserProfile = await loadPersistedUserProfile();
115115

116116
const initialUserProfile = normalizeUserProfile(storedUserProfile);
117117

@@ -140,12 +140,12 @@ if (loaded.length > 0) {
140140
console.error("Failed to hydrate store", error);
141141

142142
const fallbackUserProfile = normalizeUserProfile(
143-
loadPersistedUserProfile()
143+
await loadPersistedUserProfile()
144144
);
145145

146146
const fallbackSettings = {
147147
...DEFAULT_SETTINGS,
148-
...(loadPersistedSettings() || {}),
148+
...((await loadPersistedSettings()) || {}),
149149
};
150150

151151
if (isCancelled) return;
@@ -174,12 +174,16 @@ if (loaded.length > 0) {
174174

175175
useEffect(() => {
176176
if (!isHydrated) return;
177-
savePersistedSettings(settings);
177+
savePersistedSettings(settings).catch((error) => {
178+
console.error("Failed to persist settings", error);
179+
});
178180
}, [settings, isHydrated]);
179181

180182
useEffect(() => {
181183
if (!isHydrated || !userProfile) return;
182-
savePersistedUserProfile(userProfile);
184+
savePersistedUserProfile(userProfile).catch((error) => {
185+
console.error("Failed to persist user profile", error);
186+
});
183187
}, [userProfile, isHydrated]);
184188

185189
function createProject() {

0 commit comments

Comments
 (0)