learning_ai_common_plat/services/tracker-service/src/server.ts
saravanakumardb1 2738124ab9 feat(services): add tracker-service (items, comments, votes, public roadmap)
- Copied as-is from learning_voice_ai_agent/services/tracker-service
- 45 tests passing (vitest)
- Fastify 5 + Cosmos DB + jose + Zod + @fastify/rate-limit
- Modules: items, comments, votes, public
- Port 4004
2026-02-12 11:39:17 -08:00

84 lines
2.6 KiB
TypeScript

/**
* 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);
}