/** * Tracker Service — Fastify server entry point. * * Modules: items, comments, votes. * Port: 4004 (configurable via PORT env var). * Product-agnostic: all data scoped by productId. */ import { randomUUID } from "node:crypto"; import Fastify from "fastify"; import cors from "@fastify/cors"; import swagger from "@fastify/swagger"; import metricsPlugin from "fastify-metrics"; import { ServiceError } from "./lib/errors.js"; import { itemRoutes } from "./modules/items/routes.js"; import { commentRoutes } from "./modules/comments/routes.js"; import { voteRoutes } from "./modules/votes/routes.js"; import { publicRoutes } from "./modules/public/routes.js"; import { config } from "./lib/config.js"; const PORT = config.PORT; const HOST = config.HOST; const app = Fastify({ logger: true }); // CORS — restrict to specific origins in production via CORS_ORIGIN (comma-separated) const corsOrigin = config.CORS_ORIGIN; await app.register(cors, { origin: corsOrigin ? corsOrigin.split(",").map((o) => o.trim()) : true, }); // OpenAPI spec auto-generation (GET /api/docs/json) await app.register(swagger, { openapi: { info: { title: "Tracker Service", version: "0.1.0", description: "Feature requests, bugs, tasks — product-agnostic" }, servers: [{ url: `http://localhost:${PORT}` }], }, }); // Prometheus metrics await app.register(metricsPlugin, { endpoint: "/metrics" }); // x-request-id: propagate incoming header or generate a new one app.addHook("onRequest", async (req, reply) => { const requestId = (req.headers["x-request-id"] as string) || randomUUID(); req.headers["x-request-id"] = requestId; reply.header("x-request-id", requestId); req.log = req.log.child({ requestId }); }); // Health check app.get("/health", async (req) => ({ status: "ok", service: "tracker-service", version: "0.1.0", timestamp: new Date().toISOString(), requestId: req.headers["x-request-id"], })); // Custom error handler app.setErrorHandler((error, _req, reply) => { if (error instanceof ServiceError) { reply.code(error.statusCode).send({ error: error.message }); return; } app.log.error(error); reply.code(500).send({ error: "Internal server error" }); }); // Register route modules await app.register(itemRoutes, { prefix: "/api" }); await app.register(commentRoutes, { prefix: "/api" }); await app.register(voteRoutes, { prefix: "/api" }); await app.register(publicRoutes, { prefix: "/api" }); // Start try { await app.listen({ port: PORT, host: HOST }); app.log.info(`Tracker Service listening on ${HOST}:${PORT}`); } catch (err) { app.log.error(err); process.exit(1); }