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