hub-api/src/server.tsblame
View source
135dfe51import Fastify from "fastify";
135dfe52import cors from "@fastify/cors";
135dfe53import jwt from "@fastify/jwt";
135dfe54import { initDatabase } from "./services/database.js";
135dfe55import { authRoutes } from "./routes/auth.js";
135dfe56import { instanceRoutes } from "./routes/instances.js";
79efd417import { orgRoutes } from "./routes/orgs.js";
135dfe58
135dfe59const app = Fastify({
135dfe510 logger: {
135dfe511 level: process.env.LOG_LEVEL ?? "info",
135dfe512 transport:
135dfe513 process.env.NODE_ENV !== "production"
135dfe514 ? { target: "pino-pretty" }
135dfe515 : undefined,
135dfe516 },
135dfe517});
135dfe518
135dfe519await app.register(cors, {
a33b2b620 origin: (process.env.CORS_ORIGIN ?? "https://grove.host")
a33b2b621 .split(",")
a33b2b622 .map((origin) => origin.trim())
a33b2b623 .filter(Boolean),
135dfe524});
135dfe525
135dfe526await app.register(jwt, {
3c994d327 secret: process.env.JWT_SECRET ?? "grove-dev-secret",
3c994d328 sign: { expiresIn: "7d" },
135dfe529});
135dfe530
135dfe531// Auth decorator
135dfe532app.decorate("authenticate", async function (request: any, reply: any) {
135dfe533 try {
135dfe534 await request.jwtVerify();
135dfe535 } catch (err) {
135dfe536 reply.code(401).send({ error: "Unauthorized" });
135dfe537 }
135dfe538});
135dfe539
135dfe540// Initialize database
135dfe541const db = initDatabase(process.env.DATABASE_PATH ?? "./data/hub.db");
135dfe542app.decorate("db", db);
135dfe543
135dfe544// In-memory challenge store
135dfe545const challenges = new Map<
135dfe546 string,
135dfe547 { username?: string; displayName?: string; userId?: number; expiresAt: number }
135dfe548>();
135dfe549app.decorate("challenges", challenges);
135dfe550
135dfe551// Cleanup expired challenges every 5 minutes
135dfe552setInterval(() => {
135dfe553 const now = Date.now();
135dfe554 for (const [key, val] of challenges) {
135dfe555 if (val.expiresAt < now) challenges.delete(key);
135dfe556 }
135dfe557}, 5 * 60 * 1000);
135dfe558
135dfe559// Health check
135dfe560app.get("/health", async () => ({ status: "ok", service: "grove-hub-api" }));
8e98aa161app.get("/api/health", async () => ({ status: "ok", service: "grove-hub-api" }));
135dfe562
135dfe563// Routes
135dfe564await app.register(authRoutes, { prefix: "/api/auth" });
135dfe565await app.register(instanceRoutes, { prefix: "/api/instances" });
79efd4166await app.register(orgRoutes, { prefix: "/api/orgs" });
135dfe567
135dfe568// Start
135dfe569const port = parseInt(process.env.PORT ?? "4000", 10);
135dfe570const host = process.env.HOST ?? "0.0.0.0";
135dfe571
135dfe572try {
135dfe573 await app.listen({ port, host });
135dfe574 app.log.info(`Grove Hub API running at http://${host}:${port}`);
135dfe575} catch (err) {
135dfe576 app.log.error(err);
135dfe577 process.exit(1);
135dfe578}