v1.3 - added an sqlite db and built data structure
This commit is contained in:
parent
b0fba52c0e
commit
5efdafbb97
37 changed files with 6414 additions and 1988 deletions
84
server/src/index.js
Normal file
84
server/src/index.js
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/* ═══════════════════════════════════════════════════════════════
|
||||
NGU API
|
||||
|
||||
Binds to localhost only. nginx is the only thing that talks to
|
||||
it, which is what lets the routes trust X-Forwarded-For and skip
|
||||
CORS entirely — in production the API and the site share an
|
||||
origin, and in development Vite proxies /api so they share one
|
||||
there too.
|
||||
═══════════════════════════════════════════════════════════════ */
|
||||
|
||||
import { serve } from "@hono/node-server";
|
||||
import { Hono } from "hono";
|
||||
import { logger } from "hono/logger";
|
||||
|
||||
import { openDatabase, migrate } from "./db.js";
|
||||
import { rateLimit } from "./rateLimit.js";
|
||||
import content from "./routes/content.js";
|
||||
import feedback from "./routes/feedback.js";
|
||||
|
||||
const HOST = process.env.HOST ?? "127.0.0.1";
|
||||
const PORT = Number(process.env.PORT ?? 3001);
|
||||
const DB_PATH = process.env.DB_PATH ?? "./ngu.db";
|
||||
|
||||
/* ── Boot ────────────────────────────────────────────────────── */
|
||||
|
||||
const db = await openDatabase(DB_PATH);
|
||||
const version = migrate(db);
|
||||
|
||||
console.log(`db ${DB_PATH} (${db.driverName}, schema v${version})`);
|
||||
|
||||
/* ── App ─────────────────────────────────────────────────────── */
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
app.use("*", logger());
|
||||
|
||||
app.use("*", async (c, next) => {
|
||||
c.set("db", db);
|
||||
await next();
|
||||
});
|
||||
|
||||
app.get("/api/health", (c) =>
|
||||
c.json({ ok: true, schema: version, driver: db.driverName }),
|
||||
);
|
||||
|
||||
app.route("/api", content);
|
||||
|
||||
// Tighter limit on the write path than anything else gets.
|
||||
app.use("/api/feedback", rateLimit({ windowMs: 60_000, max: 5 }));
|
||||
app.route("/api/feedback", feedback);
|
||||
|
||||
app.notFound((c) => c.json({ error: "Not found" }, 404));
|
||||
|
||||
app.onError((err, c) => {
|
||||
console.error(err);
|
||||
// Never leak internals to the browser.
|
||||
return c.json({ error: "Something went wrong." }, 500);
|
||||
});
|
||||
|
||||
/* ── Serve ───────────────────────────────────────────────────── */
|
||||
|
||||
const server = serve({ fetch: app.fetch, hostname: HOST, port: PORT }, (info) =>
|
||||
console.log(`listening http://${info.address}:${info.port}`),
|
||||
);
|
||||
|
||||
/* ── Shutdown ──────────────────────────────────────────────────
|
||||
systemd sends SIGTERM on stop and restart. Closing the handle
|
||||
flushes the WAL cleanly, which saves a recovery pass on the
|
||||
next boot.
|
||||
───────────────────────────────────────────────────────────── */
|
||||
|
||||
for (const signal of ["SIGTERM", "SIGINT"]) {
|
||||
process.on(signal, () => {
|
||||
console.log(`${signal} — shutting down`);
|
||||
server.close(() => {
|
||||
try {
|
||||
db.close();
|
||||
} catch {
|
||||
/* nothing useful to do here */
|
||||
}
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue