fix(api): add try-catch error handling to route handlers

- Add try-catch to cliproxy-auth-routes GET /
- Add try-catch to health-routes GET / and POST /fix/:checkId
- Add try-catch to config-routes GET /format and POST /migrate
- Improve error messages with specific context
- All handlers now return 500 with error message on failure
This commit is contained in:
kaitranntt
2025-12-21 18:03:37 -05:00
parent 557926ffe3
commit 85b0f17110
3 changed files with 121 additions and 77 deletions
+86 -58
View File
@@ -32,57 +32,61 @@ const validProviders: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'qwen', 'i
* GET /api/cliproxy/auth - Get auth status for built-in CLIProxy profiles * GET /api/cliproxy/auth - Get auth status for built-in CLIProxy profiles
* Also fetches CLIProxyAPI stats to update lastUsedAt for active providers * Also fetches CLIProxyAPI stats to update lastUsedAt for active providers
*/ */
router.get('/', async (_req: Request, res: Response) => { router.get('/', async (_req: Request, res: Response): Promise<void> => {
// Initialize accounts from existing tokens on first request try {
initializeAccounts(); // Initialize accounts from existing tokens on first request
initializeAccounts();
// Fetch CLIProxyAPI usage stats to determine active providers // Fetch CLIProxyAPI usage stats to determine active providers
const stats = await fetchCliproxyStats(); const stats = await fetchCliproxyStats();
// Map CLIProxyAPI provider names to our internal provider names // Map CLIProxyAPI provider names to our internal provider names
const statsProviderMap: Record<string, CLIProxyProvider> = { const statsProviderMap: Record<string, CLIProxyProvider> = {
gemini: 'gemini', gemini: 'gemini',
antigravity: 'agy', antigravity: 'agy',
codex: 'codex', codex: 'codex',
qwen: 'qwen', qwen: 'qwen',
iflow: 'iflow', iflow: 'iflow',
}; };
// Update lastUsedAt for providers with recent activity // Update lastUsedAt for providers with recent activity
if (stats?.requestsByProvider) { if (stats?.requestsByProvider) {
for (const [statsProvider, requestCount] of Object.entries(stats.requestsByProvider)) { for (const [statsProvider, requestCount] of Object.entries(stats.requestsByProvider)) {
if (requestCount > 0) { if (requestCount > 0) {
const provider = statsProviderMap[statsProvider.toLowerCase()]; const provider = statsProviderMap[statsProvider.toLowerCase()];
if (provider) { if (provider) {
// Touch the default account for this provider (or all accounts) // Touch the default account for this provider (or all accounts)
const accounts = getProviderAccounts(provider); const accounts = getProviderAccounts(provider);
for (const account of accounts) { for (const account of accounts) {
// Only touch if this is the default account (most likely being used) // Only touch if this is the default account (most likely being used)
if (account.isDefault) { if (account.isDefault) {
touchAccount(provider, account.id); touchAccount(provider, account.id);
}
} }
} }
} }
} }
} }
const statuses = getAllAuthStatus();
const authStatus = statuses.map((status) => {
const oauthConfig = getOAuthConfig(status.provider);
return {
provider: status.provider,
displayName: oauthConfig.displayName,
authenticated: status.authenticated,
lastAuth: status.lastAuth?.toISOString() || null,
tokenFiles: status.tokenFiles.length,
accounts: status.accounts,
defaultAccount: status.defaultAccount,
};
});
res.json({ authStatus });
} catch (error) {
res.status(500).json({ error: (error as Error).message });
} }
const statuses = getAllAuthStatus();
const authStatus = statuses.map((status) => {
const oauthConfig = getOAuthConfig(status.provider);
return {
provider: status.provider,
displayName: oauthConfig.displayName,
authenticated: status.authenticated,
lastAuth: status.lastAuth?.toISOString() || null,
tokenFiles: status.tokenFiles.length,
accounts: status.accounts,
defaultAccount: status.defaultAccount,
};
});
res.json({ authStatus });
}); });
// ==================== Account Management ==================== // ==================== Account Management ====================
@@ -91,11 +95,16 @@ router.get('/', async (_req: Request, res: Response) => {
* GET /api/cliproxy/accounts - Get all accounts across all providers * GET /api/cliproxy/accounts - Get all accounts across all providers
*/ */
router.get('/accounts', (_req: Request, res: Response) => { router.get('/accounts', (_req: Request, res: Response) => {
// Initialize accounts from existing tokens try {
initializeAccounts(); // Initialize accounts from existing tokens
initializeAccounts();
const accounts = getAllAccountsSummary(); const accounts = getAllAccountsSummary();
res.json({ accounts }); res.json({ accounts });
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to list accounts';
res.status(500).json({ error: message });
}
}); });
/** /**
@@ -110,8 +119,13 @@ router.get('/accounts/:provider', (req: Request, res: Response): void => {
return; return;
} }
const accounts = getProviderAccounts(provider as CLIProxyProvider); try {
res.json({ provider, accounts }); const accounts = getProviderAccounts(provider as CLIProxyProvider);
res.json({ provider, accounts });
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get provider accounts';
res.status(500).json({ error: message });
}
}); });
/** /**
@@ -132,12 +146,19 @@ router.post('/accounts/:provider/default', (req: Request, res: Response): void =
return; return;
} }
const success = setDefaultAccountFn(provider as CLIProxyProvider, accountId); try {
const success = setDefaultAccountFn(provider as CLIProxyProvider, accountId);
if (success) { if (success) {
res.json({ provider, defaultAccount: accountId }); res.json({ provider, defaultAccount: accountId });
} else { } else {
res.status(404).json({ error: 'Account not found' }); res
.status(404)
.json({ error: `Account '${accountId}' not found for provider '${provider}'` });
}
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to set default account';
res.status(500).json({ error: message });
} }
}); });
@@ -153,12 +174,19 @@ router.delete('/accounts/:provider/:accountId', (req: Request, res: Response): v
return; return;
} }
const success = removeAccountFn(provider as CLIProxyProvider, accountId); try {
const success = removeAccountFn(provider as CLIProxyProvider, accountId);
if (success) { if (success) {
res.json({ provider, accountId, deleted: true }); res.json({ provider, accountId, deleted: true });
} else { } else {
res.status(404).json({ error: 'Account not found' }); res
.status(404)
.json({ error: `Account '${accountId}' not found for provider '${provider}'` });
}
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to remove account';
res.status(500).json({ error: message });
} }
}); });
+18 -10
View File
@@ -24,12 +24,16 @@ const router = Router();
/** /**
* GET /api/config/format - Return current config format and migration status * GET /api/config/format - Return current config format and migration status
*/ */
router.get('/format', (_req: Request, res: Response) => { router.get('/format', (_req: Request, res: Response): void => {
res.json({ try {
format: getConfigFormat(), res.json({
migrationNeeded: needsMigration(), format: getConfigFormat(),
backups: getBackupDirectories(), migrationNeeded: needsMigration(),
}); backups: getBackupDirectories(),
});
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
}); });
/** /**
@@ -89,10 +93,14 @@ router.put('/', (req: Request, res: Response): void => {
/** /**
* POST /api/config/migrate - Trigger migration from JSON to YAML * POST /api/config/migrate - Trigger migration from JSON to YAML
*/ */
router.post('/migrate', async (req: Request, res: Response) => { router.post('/migrate', async (req: Request, res: Response): Promise<void> => {
const dryRun = req.query.dryRun === 'true'; try {
const result = await migrate(dryRun); const dryRun = req.query.dryRun === 'true';
res.json(result); const result = await migrate(dryRun);
res.json(result);
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
}); });
/** /**
+17 -9
View File
@@ -10,22 +10,30 @@ const router = Router();
/** /**
* GET /api/health - Run health checks * GET /api/health - Run health checks
*/ */
router.get('/', async (_req: Request, res: Response) => { router.get('/', async (_req: Request, res: Response): Promise<void> => {
const report = await runHealthChecks(); try {
res.json(report); const report = await runHealthChecks();
res.json(report);
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
}); });
/** /**
* POST /api/health/fix/:checkId - Fix a health issue * POST /api/health/fix/:checkId - Fix a health issue
*/ */
router.post('/fix/:checkId', (req: Request, res: Response): void => { router.post('/fix/:checkId', (req: Request, res: Response): void => {
const { checkId } = req.params; try {
const result = fixHealthIssue(checkId); const { checkId } = req.params;
const result = fixHealthIssue(checkId);
if (result.success) { if (result.success) {
res.json({ success: true, message: result.message }); res.json({ success: true, message: result.message });
} else { } else {
res.status(400).json({ success: false, message: result.message }); res.status(400).json({ success: false, message: result.message });
}
} catch (error) {
res.status(500).json({ error: (error as Error).message });
} }
}); });