diff --git a/app/api/settings/route.ts b/app/api/settings/route.ts index 7210b410..ccb3eb26 100644 --- a/app/api/settings/route.ts +++ b/app/api/settings/route.ts @@ -47,7 +47,9 @@ export async function GET(request: NextRequest) { } return NextResponse.json({ settings }); } catch (error) { - logger.error('Settings load error', { error: error instanceof Error ? error.message : 'Unknown error' }); + const message = error instanceof Error ? error.message : 'Unknown error'; + const code = (error as NodeJS.ErrnoException).code; + logger.error('Settings load error', { error: message, code }); return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } @@ -74,7 +76,9 @@ export async function POST(request: NextRequest) { await saveUserSettings(username, serverUrl, settings); return NextResponse.json({ ok: true }); } catch (error) { - logger.error('Settings save error', { error: error instanceof Error ? error.message : 'Unknown error' }); + const message = error instanceof Error ? error.message : 'Unknown error'; + const code = (error as NodeJS.ErrnoException).code; + logger.error('Settings save error', { error: message, code }); return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } diff --git a/lib/settings-sync.ts b/lib/settings-sync.ts index b4ac52c2..47404e8f 100644 --- a/lib/settings-sync.ts +++ b/lib/settings-sync.ts @@ -1,5 +1,5 @@ import { createHash, createCipheriv, createDecipheriv, randomBytes } from 'node:crypto'; -import { readFile, writeFile, unlink, mkdir } from 'node:fs/promises'; +import { readFile, writeFile, unlink, mkdir, rename } from 'node:fs/promises'; import { existsSync } from 'node:fs'; import path from 'node:path'; import { logger } from '@/lib/logger'; @@ -45,7 +45,10 @@ export async function saveUserSettings(username: string, serverUrl: string, sett const tag = cipher.getAuthTag(); const data = Buffer.concat([iv, tag, encrypted]); - await writeFile(getSettingsPath(username, serverUrl), data); + const targetPath = getSettingsPath(username, serverUrl); + const tmpPath = targetPath + '.tmp'; + await writeFile(tmpPath, data); + await rename(tmpPath, targetPath); } export async function loadUserSettings(username: string, serverUrl: string): Promise | null> { diff --git a/stores/settings-store.ts b/stores/settings-store.ts index 95c3952f..5a0399ec 100644 --- a/stores/settings-store.ts +++ b/stores/settings-store.ts @@ -562,26 +562,34 @@ if (typeof window !== 'undefined') { applyAnimations(store.animationsEnabled); // Shared sync function used by all store subscribers + const syncToServer = async (retries = 1): Promise => { + const settings = JSON.parse(useSettingsStore.getState().exportSettings()); + syncLog('Syncing settings to server...'); + const res = await fetch('/api/settings', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username: syncUsername, serverUrl: syncServerUrl, settings }), + }); + if (res.status === 404) { + syncWarn('Settings sync endpoint returned 404, disabling sync'); + syncEnabled = false; + } else if (res.status >= 500 && retries > 0) { + syncWarn('Settings sync got server error, retrying...'); + await new Promise((r) => setTimeout(r, 2000)); + return syncToServer(retries - 1); + } else if (!res.ok) { + syncError('Settings sync failed with status', res.status); + } else { + syncLog('Settings synced to server successfully'); + } + }; + const triggerSync = () => { if (!syncEnabled || !syncUsername || !syncServerUrl || isLoadingFromServer) return; if (syncTimeout) clearTimeout(syncTimeout); syncTimeout = setTimeout(async () => { try { - const settings = JSON.parse(useSettingsStore.getState().exportSettings()); - syncLog('Syncing settings to server...'); - const res = await fetch('/api/settings', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username: syncUsername, serverUrl: syncServerUrl, settings }), - }); - if (res.status === 404) { - syncWarn('Settings sync endpoint returned 404, disabling sync'); - syncEnabled = false; - } else if (!res.ok) { - syncError('Settings sync failed with status', res.status); - } else { - syncLog('Settings synced to server successfully'); - } + await syncToServer(); } catch (error) { syncError('Settings sync error:', error); }