this repo has no description
at main 105 lines 2.7 kB view raw
1import { serve } from 'bun'; 2import { existsSync } from 'fs'; 3import { join } from 'path'; 4 5import { handleApiRequest } from './api'; 6 7const PORT = parseInt(process.env.PORT ?? '3456'); 8const STATIC_DIR = join(import.meta.dir, '../../dist'); 9 10export function startServer() { 11 console.log(`\n🚀 Starting worklog server on http://localhost:${String(PORT)}\n`); 12 13 serve({ 14 port: PORT, 15 async fetch(req) { 16 const url = new URL(req.url); 17 18 // API routes 19 if (url.pathname.startsWith('/api/')) { 20 return handleApiRequest(req, url); 21 } 22 23 // Static files 24 return serveStatic(url.pathname); 25 }, 26 error(error) { 27 console.error('Server error:', error); 28 return new Response('Internal Server Error', { status: 500 }); 29 }, 30 }); 31 32 console.log('Server running. Press Ctrl+C to stop.\n'); 33} 34 35function serveStatic(pathname: string): Response { 36 // Map pathname to file 37 let filePath = pathname === '/' ? '/index.html' : pathname; 38 filePath = join(STATIC_DIR, filePath); 39 40 // Check if file exists 41 if (existsSync(filePath)) { 42 const file = Bun.file(filePath); 43 return new Response(file, { 44 headers: { 45 'Content-Type': getContentType(filePath), 46 }, 47 }); 48 } 49 50 // SPA fallback - serve index.html for all routes 51 const indexPath = join(STATIC_DIR, 'index.html'); 52 if (existsSync(indexPath)) { 53 const file = Bun.file(indexPath); 54 return new Response(file, { 55 headers: { 56 'Content-Type': 'text/html', 57 }, 58 }); 59 } 60 61 // No static files yet - serve a placeholder 62 return new Response( 63 `<!DOCTYPE html> 64<html> 65<head> 66 <title>Worklog</title> 67 <style> 68 body { font-family: system-ui; max-width: 600px; margin: 100px auto; padding: 20px; } 69 code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; } 70 </style> 71</head> 72<body> 73 <h1>Worklog</h1> 74 <p>Frontend not built yet. Run:</p> 75 <pre><code>bun run build</code></pre> 76 <p>Or for development:</p> 77 <pre><code>bun run dev</code></pre> 78 <hr> 79 <p>API is available at <a href="/api/stats">/api/stats</a></p> 80</body> 81</html>`, 82 { 83 headers: { 'Content-Type': 'text/html' }, 84 }, 85 ); 86} 87 88function getContentType(filePath: string): string { 89 const ext = filePath.split('.').pop()?.toLowerCase(); 90 const types: Record<string, string> = { 91 html: 'text/html', 92 css: 'text/css', 93 js: 'application/javascript', 94 json: 'application/json', 95 png: 'image/png', 96 jpg: 'image/jpeg', 97 jpeg: 'image/jpeg', 98 gif: 'image/gif', 99 svg: 'image/svg+xml', 100 ico: 'image/x-icon', 101 woff: 'font/woff', 102 woff2: 'font/woff2', 103 }; 104 return types[ext ?? ''] ?? 'application/octet-stream'; 105}