this repo has no description
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}