feat(settings): enhance error handling for settings operations and improve logging
This commit is contained in:
@@ -26,6 +26,7 @@ RUN apk upgrade --no-cache && \
|
|||||||
COPY --from=builder /app/public ./public
|
COPY --from=builder /app/public ./public
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
RUN mkdir -p /app/data/settings /app/data/admin && chown -R nextjs:nodejs /app/data
|
||||||
USER nextjs
|
USER nextjs
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
ENV PORT=3000
|
ENV PORT=3000
|
||||||
|
|||||||
@@ -6,6 +6,46 @@ import { sessionCookieName } from '@/lib/auth/session-cookie';
|
|||||||
import { saveUserSettings, loadUserSettings, deleteUserSettings } from '@/lib/settings-sync';
|
import { saveUserSettings, loadUserSettings, deleteUserSettings } from '@/lib/settings-sync';
|
||||||
import { configManager } from '@/lib/admin/config-manager';
|
import { configManager } from '@/lib/admin/config-manager';
|
||||||
|
|
||||||
|
function classifyError(error: unknown): { message: string; status: number } {
|
||||||
|
const code = (error as NodeJS.ErrnoException).code;
|
||||||
|
const msg = error instanceof Error ? error.message : 'Unknown error';
|
||||||
|
|
||||||
|
switch (code) {
|
||||||
|
case 'EACCES':
|
||||||
|
case 'EPERM':
|
||||||
|
return {
|
||||||
|
message: 'Write permission denied on settings data directory. Check filesystem permissions for the SETTINGS_DATA_DIR (or data/settings/).',
|
||||||
|
status: 500,
|
||||||
|
};
|
||||||
|
case 'EROFS':
|
||||||
|
return {
|
||||||
|
message: 'Filesystem is read-only. Settings cannot be saved. Ensure the data directory is on a writable volume.',
|
||||||
|
status: 500,
|
||||||
|
};
|
||||||
|
case 'ENOSPC':
|
||||||
|
return {
|
||||||
|
message: 'No disk space available to save settings.',
|
||||||
|
status: 507,
|
||||||
|
};
|
||||||
|
case 'ENOENT':
|
||||||
|
return {
|
||||||
|
message: 'Settings data directory does not exist and could not be created. Check SETTINGS_DATA_DIR configuration.',
|
||||||
|
status: 500,
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
if (msg.includes('SESSION_SECRET')) {
|
||||||
|
return {
|
||||||
|
message: 'Server configuration error: SESSION_SECRET is not set.',
|
||||||
|
status: 500,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
message: `Internal server error: ${msg}`,
|
||||||
|
status: 500,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function isEnabled(): boolean {
|
function isEnabled(): boolean {
|
||||||
return process.env.SETTINGS_SYNC_ENABLED === 'true' && !!process.env.SESSION_SECRET;
|
return process.env.SETTINGS_SYNC_ENABLED === 'true' && !!process.env.SESSION_SECRET;
|
||||||
}
|
}
|
||||||
@@ -59,7 +99,8 @@ export async function GET(request: NextRequest) {
|
|||||||
const message = error instanceof Error ? error.message : 'Unknown error';
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||||
const code = (error as NodeJS.ErrnoException).code;
|
const code = (error as NodeJS.ErrnoException).code;
|
||||||
logger.error('Settings load error', { error: message, code });
|
logger.error('Settings load error', { error: message, code });
|
||||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
const classified = classifyError(error);
|
||||||
|
return NextResponse.json({ error: classified.message }, { status: classified.status });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +140,8 @@ export async function POST(request: NextRequest) {
|
|||||||
const message = error instanceof Error ? error.message : 'Unknown error';
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||||
const code = (error as NodeJS.ErrnoException).code;
|
const code = (error as NodeJS.ErrnoException).code;
|
||||||
logger.error('Settings save error', { error: message, code });
|
logger.error('Settings save error', { error: message, code });
|
||||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
const classified = classifyError(error);
|
||||||
|
return NextResponse.json({ error: classified.message }, { status: classified.status });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +163,10 @@ export async function DELETE(request: NextRequest) {
|
|||||||
await deleteUserSettings(username, serverUrl);
|
await deleteUserSettings(username, serverUrl);
|
||||||
return NextResponse.json({ ok: true });
|
return NextResponse.json({ ok: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Settings delete error', { error: error instanceof Error ? error.message : 'Unknown error' });
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
const code = (error as NodeJS.ErrnoException).code;
|
||||||
|
logger.error('Settings delete error', { error: message, code });
|
||||||
|
const classified = classifyError(error);
|
||||||
|
return NextResponse.json({ error: classified.message }, { status: classified.status });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -510,7 +510,8 @@ export const useSettingsStore = create<SettingsState>()(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
syncLog('Settings fetch failed (status', res.status + ')');
|
const body = await res.json().catch(() => ({}));
|
||||||
|
syncLog('Settings fetch failed:', body.error || `status ${res.status}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const { settings } = await res.json();
|
const { settings } = await res.json();
|
||||||
@@ -647,11 +648,13 @@ if (typeof window !== 'undefined') {
|
|||||||
syncWarn('Settings sync endpoint returned 404, disabling sync');
|
syncWarn('Settings sync endpoint returned 404, disabling sync');
|
||||||
syncEnabled = false;
|
syncEnabled = false;
|
||||||
} else if (res.status >= 500 && retries > 0) {
|
} else if (res.status >= 500 && retries > 0) {
|
||||||
syncWarn('Settings sync got server error, retrying...');
|
const body = await res.json().catch(() => ({}));
|
||||||
|
syncWarn('Settings sync got server error:', body.error || `status ${res.status}`, '- retrying...');
|
||||||
await new Promise((r) => setTimeout(r, 2000));
|
await new Promise((r) => setTimeout(r, 2000));
|
||||||
return syncToServer(retries - 1);
|
return syncToServer(retries - 1);
|
||||||
} else if (!res.ok) {
|
} else if (!res.ok) {
|
||||||
syncError('Settings sync failed with status', res.status);
|
const body = await res.json().catch(() => ({}));
|
||||||
|
syncError('Settings sync failed:', body.error || `status ${res.status}`);
|
||||||
} else {
|
} else {
|
||||||
syncLog('Settings synced to server successfully');
|
syncLog('Settings synced to server successfully');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user