-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.tsx
More file actions
80 lines (66 loc) · 3.14 KB
/
main.tsx
File metadata and controls
80 lines (66 loc) · 3.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/** @jsxImportSource hono/jsx */
/**
* mtchat — Hono server entry point.
*
* Routes:
* / → redirect to /chat
* /chat → chat page (Phase 2 mounts the React UI here)
* /admin → admin page (Phase 1 mounts the MonkeyTable here)
* /api/qa → CRUD for question/answer entries
* /api/chat → query the Q&A system
* /static/* → built client bundles + static assets (Phase 1+)
*/
import { Hono } from 'hono';
import { InMemoryQAStore } from './lib/store.ts';
import { seedEntries } from './lib/seed.ts';
import { createQARoutes } from './routes/api/qa.ts';
import { createChatRoutes } from './routes/api/chat.ts';
import { AdminShell } from './views/AdminShell.tsx';
import { ChatShell } from './views/ChatShell.tsx';
// ─── Store ──────────────────────────────────────────────────────────────────
const store = new InMemoryQAStore();
await store.replaceAll(seedEntries);
// ─── App ────────────────────────────────────────────────────────────────────
const app = new Hono();
// API
app.route('/api/qa', createQARoutes(store));
app.route('/api/chat', createChatRoutes(store));
// Pages
app.get('/', (c) => c.redirect('/chat'));
app.get('/chat', (c) => c.html(<ChatShell />));
app.get('/admin', async (c) => {
const entries = await store.list();
return c.html(<AdminShell entryCount={entries.length} />);
});
// Static — serves the esbuild output in ./static. Filenames only (no
// subdirectories), and a small extension → content-type table covers
// everything the bundler emits today (.js, .css, .map).
const STATIC_TYPES: Record<string, string> = {
js: 'application/javascript; charset=utf-8',
css: 'text/css; charset=utf-8',
map: 'application/json; charset=utf-8',
svg: 'image/svg+xml',
};
app.get('/static/:file', async (c) => {
const file = c.req.param('file');
// Reject anything that tries to escape the static dir.
if (file.includes('/') || file.includes('..')) {
return c.notFound();
}
try {
const content = await Deno.readFile(`./static/${file}`);
const ext = file.split('.').pop() ?? '';
const type = STATIC_TYPES[ext] ?? 'application/octet-stream';
return new Response(content, { headers: { 'content-type': type } });
} catch {
return c.notFound();
}
});
// 404
app.notFound((c) => c.json({ error: 'not_found', path: c.req.path }, 404));
// ─── Serve ──────────────────────────────────────────────────────────────────
// 8519 is mtchat's reserved local port — picked to not collide with the
// usual suspects (3000, 5173, 8000, etc.). Override with PORT env var.
const port = Number(Deno.env.get('PORT') ?? '8519');
console.log(`mtchat listening on http://localhost:${port}`);
Deno.serve({ port }, app.fetch);