mirror of
https://github.com/YouXam/claude-code-usage-dashboard.git
synced 2026-03-22 12:33:47 +08:00
init
This commit is contained in:
168
server/index.ts
Normal file
168
server/index.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import { apiClient } from './api-client';
|
||||
import { billingCalculator } from './billing-calculator';
|
||||
import indexHtml from '../client/index.html';
|
||||
|
||||
// Middleware to validate API key and get user ID
|
||||
const validateApiKey = async (request: Request): Promise<{valid: boolean, userId?: string, error?: string}> => {
|
||||
const apiKey = request.headers.get('x-api-key');
|
||||
|
||||
if (!apiKey) {
|
||||
return { valid: false, error: 'API key required' };
|
||||
}
|
||||
|
||||
try {
|
||||
const userId = await apiClient.getKeyId(apiKey);
|
||||
return { valid: true, userId };
|
||||
} catch (error) {
|
||||
return { valid: false, error: 'Invalid API key' };
|
||||
}
|
||||
};
|
||||
|
||||
const port = parseInt(process.env.PORT || '3000');
|
||||
|
||||
Bun.serve({
|
||||
port,
|
||||
routes: {
|
||||
'/': indexHtml,
|
||||
|
||||
'/api/periods': {
|
||||
async GET(req) {
|
||||
const validation = await validateApiKey(req);
|
||||
if (!validation.valid) {
|
||||
return new Response(JSON.stringify({ error: validation.error }), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const periods = await billingCalculator.getPeriods();
|
||||
return new Response(JSON.stringify({ periods }), {
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error getting periods:', error);
|
||||
return new Response(JSON.stringify({ error: 'Failed to get periods' }), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'/api/periods/:index/summary': {
|
||||
async GET(req: Request) {
|
||||
const validation = await validateApiKey(req);
|
||||
if (!validation.valid) {
|
||||
return new Response(JSON.stringify({ error: validation.error }), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(req.url);
|
||||
const periodIndex = parseInt(url.pathname.split('/')[3] || '0');
|
||||
|
||||
if (isNaN(periodIndex)) {
|
||||
return new Response(JSON.stringify({ error: 'Invalid period index' }), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
const summary = await billingCalculator.getPeriodSummary(periodIndex, validation.userId);
|
||||
return new Response(JSON.stringify(summary), {
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error getting period summary:', error);
|
||||
if (error instanceof Error && error.message.includes('not found')) {
|
||||
return new Response(JSON.stringify({ error: 'Period not found' }), {
|
||||
status: 404,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
return new Response(JSON.stringify({ error: 'Failed to get period summary' }), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'/api/periods/:index/me': {
|
||||
async GET(req) {
|
||||
const validation = await validateApiKey(req);
|
||||
if (!validation.valid) {
|
||||
return new Response(JSON.stringify({ error: validation.error }), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(req.url);
|
||||
const periodIndex = parseInt(url.pathname.split('/')[3] || '0');
|
||||
|
||||
if (isNaN(periodIndex)) {
|
||||
return new Response(JSON.stringify({ error: 'Invalid period index' }), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
const userDetail = await billingCalculator.getUserDetail(periodIndex, validation.userId!);
|
||||
return new Response(JSON.stringify(userDetail), {
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error getting user detail:', error);
|
||||
if (error instanceof Error && error.message.includes('not found')) {
|
||||
return new Response(JSON.stringify({ error: 'User not found in this period (possibly deleted)' }), {
|
||||
status: 404,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
return new Response(JSON.stringify({ error: 'Failed to get user details' }), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'/api/auth/test': {
|
||||
async POST(req: Request) {
|
||||
try {
|
||||
const body = await req.json() as { apiKey?: string };
|
||||
const { apiKey } = body;
|
||||
|
||||
if (!apiKey) {
|
||||
return new Response(JSON.stringify({ error: 'API key required' }), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
const userId = await apiClient.getKeyId(apiKey);
|
||||
return new Response(JSON.stringify({ success: true, userId }), {
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error testing API key:', error);
|
||||
return new Response(JSON.stringify({ error: 'Invalid API key' }), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// development: {
|
||||
// hmr: true,
|
||||
// console: true,
|
||||
// }
|
||||
});
|
||||
|
||||
console.log(`Server running on http://localhost:${port}`);
|
||||
Reference in New Issue
Block a user