2.1 KB79 lines
Blame
1import Fastify from "fastify";
2import cors from "@fastify/cors";
3import jwt from "@fastify/jwt";
4import { initDatabase } from "./services/database.js";
5import { authRoutes } from "./routes/auth.js";
6import { instanceRoutes } from "./routes/instances.js";
7import { orgRoutes } from "./routes/orgs.js";
8
9const app = Fastify({
10 logger: {
11 level: process.env.LOG_LEVEL ?? "info",
12 transport:
13 process.env.NODE_ENV !== "production"
14 ? { target: "pino-pretty" }
15 : undefined,
16 },
17});
18
19await app.register(cors, {
20 origin: (process.env.CORS_ORIGIN ?? "https://grove.host")
21 .split(",")
22 .map((origin) => origin.trim())
23 .filter(Boolean),
24});
25
26await app.register(jwt, {
27 secret: process.env.JWT_SECRET ?? "grove-dev-secret",
28 sign: { expiresIn: "7d" },
29});
30
31// Auth decorator
32app.decorate("authenticate", async function (request: any, reply: any) {
33 try {
34 await request.jwtVerify();
35 } catch (err) {
36 reply.code(401).send({ error: "Unauthorized" });
37 }
38});
39
40// Initialize database
41const db = initDatabase(process.env.DATABASE_PATH ?? "./data/hub.db");
42app.decorate("db", db);
43
44// In-memory challenge store
45const challenges = new Map<
46 string,
47 { username?: string; displayName?: string; userId?: number; expiresAt: number }
48>();
49app.decorate("challenges", challenges);
50
51// Cleanup expired challenges every 5 minutes
52setInterval(() => {
53 const now = Date.now();
54 for (const [key, val] of challenges) {
55 if (val.expiresAt < now) challenges.delete(key);
56 }
57}, 5 * 60 * 1000);
58
59// Health check
60app.get("/health", async () => ({ status: "ok", service: "grove-hub-api" }));
61app.get("/api/health", async () => ({ status: "ok", service: "grove-hub-api" }));
62
63// Routes
64await app.register(authRoutes, { prefix: "/api/auth" });
65await app.register(instanceRoutes, { prefix: "/api/instances" });
66await app.register(orgRoutes, { prefix: "/api/orgs" });
67
68// Start
69const port = parseInt(process.env.PORT ?? "4000", 10);
70const host = process.env.HOST ?? "0.0.0.0";
71
72try {
73 await app.listen({ port, host });
74 app.log.info(`Grove Hub API running at http://${host}:${port}`);
75} catch (err) {
76 app.log.error(err);
77 process.exit(1);
78}
79