this repo has no description
at experiment/session-classification 144 lines 3.8 kB view raw
1import { getDays, getDayDetail, getStats } from '../core/db'; 2import { processCommand } from '../cli/process'; 3 4type ApiHandler = (req: Request, url: URL) => Promise<Response>; 5 6const routes: Record<string, ApiHandler> = { 7 'GET /api/days': handleGetDays, 8 'GET /api/days/:date': handleGetDayDetail, 9 'GET /api/days/:date/brag': handleGetDayBrag, 10 'GET /api/stats': handleGetStats, 11 'POST /api/refresh': handleRefresh, 12}; 13 14export async function handleApiRequest( 15 req: Request, 16 url: URL 17): Promise<Response> { 18 const method = req.method; 19 const path = url.pathname; 20 21 // Match routes 22 for (const [route, handler] of Object.entries(routes)) { 23 const [routeMethod, routePath] = route.split(' '); 24 if (method !== routeMethod) continue; 25 26 const params = matchPath(routePath, path); 27 if (params !== null) { 28 try { 29 // Attach params to URL for handler access 30 (url as any).params = params; 31 return await handler(req, url); 32 } catch (error) { 33 console.error('API error:', error); 34 return jsonResponse({ error: 'Internal server error' }, 500); 35 } 36 } 37 } 38 39 return jsonResponse({ error: 'Not found' }, 404); 40} 41 42function matchPath( 43 pattern: string, 44 path: string 45): Record<string, string> | null { 46 const patternParts = pattern.split('/'); 47 const pathParts = path.split('/'); 48 49 if (patternParts.length !== pathParts.length) return null; 50 51 const params: Record<string, string> = {}; 52 53 for (let i = 0; i < patternParts.length; i++) { 54 const patternPart = patternParts[i]; 55 const pathPart = pathParts[i]; 56 57 if (patternPart.startsWith(':')) { 58 params[patternPart.slice(1)] = pathPart; 59 } else if (patternPart !== pathPart) { 60 return null; 61 } 62 } 63 64 return params; 65} 66 67function jsonResponse(data: unknown, status = 200): Response { 68 return new Response(JSON.stringify(data), { 69 status, 70 headers: { 71 'Content-Type': 'application/json', 72 'Access-Control-Allow-Origin': '*', 73 }, 74 }); 75} 76 77// Handlers 78 79async function handleGetDays(req: Request, url: URL): Promise<Response> { 80 const limit = parseInt(url.searchParams.get('limit') || '30'); 81 const days = getDays(limit); 82 return jsonResponse(days); 83} 84 85async function handleGetDayDetail(req: Request, url: URL): Promise<Response> { 86 const params = (url as any).params as Record<string, string>; 87 const date = params.date; 88 89 const detail = getDayDetail(date); 90 if (!detail) { 91 return jsonResponse({ error: 'Day not found' }, 404); 92 } 93 94 return jsonResponse(detail); 95} 96 97async function handleGetDayBrag(req: Request, url: URL): Promise<Response> { 98 const params = (url as any).params as Record<string, string>; 99 const date = params.date; 100 101 const detail = getDayDetail(date); 102 if (!detail) { 103 return jsonResponse({ error: 'Day not found' }, 404); 104 } 105 106 return jsonResponse({ 107 date, 108 bragSummary: detail.bragSummary || 'No summary available', 109 projectCount: detail.projects.length, 110 sessionCount: detail.stats.totalSessions, 111 }); 112} 113 114async function handleGetStats(req: Request, url: URL): Promise<Response> { 115 const stats = getStats(); 116 return jsonResponse(stats); 117} 118 119async function handleRefresh(req: Request, url: URL): Promise<Response> { 120 // Run processing in background 121 const startTime = Date.now(); 122 123 try { 124 const result = await processCommand({ 125 force: false, 126 verbose: false, 127 }); 128 129 return jsonResponse({ 130 success: true, 131 sessionsProcessed: result.sessionsProcessed, 132 errors: result.errors, 133 durationMs: Date.now() - startTime, 134 }); 135 } catch (error) { 136 return jsonResponse( 137 { 138 success: false, 139 error: error instanceof Error ? error.message : 'Unknown error', 140 }, 141 500 142 ); 143 } 144}