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 --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
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
|
||||
EXPOSE 3000
|
||||
ENV PORT=3000
|
||||
|
||||
@@ -6,6 +6,46 @@ import { sessionCookieName } from '@/lib/auth/session-cookie';
|
||||
import { saveUserSettings, loadUserSettings, deleteUserSettings } from '@/lib/settings-sync';
|
||||
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 {
|
||||
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 code = (error as NodeJS.ErrnoException).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 code = (error as NodeJS.ErrnoException).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);
|
||||
return NextResponse.json({ ok: true });
|
||||
} catch (error) {
|
||||
logger.error('Settings delete error', { error: error instanceof Error ? error.message : 'Unknown error' });
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||
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) {
|
||||
syncLog('Settings fetch failed (status', res.status + ')');
|
||||
const body = await res.json().catch(() => ({}));
|
||||
syncLog('Settings fetch failed:', body.error || `status ${res.status}`);
|
||||
return false;
|
||||
}
|
||||
const { settings } = await res.json();
|
||||
@@ -647,11 +648,13 @@ if (typeof window !== 'undefined') {
|
||||
syncWarn('Settings sync endpoint returned 404, disabling sync');
|
||||
syncEnabled = false;
|
||||
} 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));
|
||||
return syncToServer(retries - 1);
|
||||
} 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 {
|
||||
syncLog('Settings synced to server successfully');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user