Files
ShieldAI/packages/api/src/server.ts
Michael Freno 9d4865306c ShieldAI waitlist landing page and analytics infrastructure FRE-5274
Build waitlist landing page with Solid.js (hero, features, tier comparison,
waitlist signup form, blog preview, footer). Create waitlist signup and blog
API endpoints in Fastify. Add WaitlistEntry and BlogPost models to Prisma
schema. Create analytics hooks for GA4 and Mixpanel tracking. Fix pre-existing
Prisma schema issue (AnalysisJob relation missing User field).

- Landing page: responsive Solid.js app with hero, 6 feature cards, 3-tier
  pricing comparison table, blog preview, and full waitlist signup form with
  interest tier selection
- API: POST /api/waitlist/signup, GET /api/waitlist/count, GET /api/blog,
  GET /api/blog/:slug, CRUD /api/admin/blog
- DB models: WaitlistEntry (with UTM params, conversion tracking, source),
  BlogPost (with tags, view count, publish scheduling)
- Analytics: useAnalytics hook with initAnalytics(), trackEvent(),
  trackWaitlistSignup(), trackPageView() — GA4 and Mixpanel dual-tracking
- Blog: listing, detail, and admin CRUD routes; seed.ts with 3 starter articles
- Fix: AnalysisJob.analysisJobId missing @unique constraint, missing
  analysisJobs[] on User model

Delegated to CMO: FRE-5280 (GA4 config), FRE-5281 (Mixpanel config),
FRE-5282 (email marketing platform)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-13 23:47:25 -04:00

76 lines
2.9 KiB
TypeScript

// dd-trace must be initialized before any other module is loaded for auto-instrumentation
import '@shieldai/monitoring/datadog-init';
import Fastify from "fastify";
import cors from "@fastify/cors";
import helmet from "@fastify/helmet";
import sensible from "@fastify/sensible";
import { extractOrGenerateRequestId } from "@shieldai/types";
import { authMiddleware } from "./middleware/auth.middleware";
import { errorHandlingMiddleware } from "./middleware/error-handling.middleware";
import { loggingMiddleware } from "./middleware/logging.middleware";
import { monitoringMiddleware } from "./middleware/monitoring.middleware";
import { darkwatchRoutes } from "./routes/darkwatch.routes";
import { voiceprintRoutes } from "./routes/voiceprint.routes";
import { correlationRoutes } from "./routes/correlation.routes";
import { extensionRoutes } from "./routes/extension.routes";
import { waitlistRoutes } from "./routes/waitlist.routes";
import { blogRoutes } from "./routes/blog.routes";
import { blogAdminRoutes } from "./routes/blog-admin.routes";
import { captureSentryError } from "@shieldai/monitoring";
import { getCorsOrigins } from "./config/api.config";
const app = Fastify({
logger: {
level: process.env.LOG_LEVEL || "info",
},
});
async function bootstrap() {
const corsOrigins = getCorsOrigins();
await app.register(cors, { origin: corsOrigins });
await app.register(helmet);
await app.register(sensible);
// Register auth middleware to populate request.user
await app.register(authMiddleware);
// Register logging middleware (request/response logging)
await app.register(loggingMiddleware);
// Register monitoring middleware (CloudWatch metrics)
await app.register(monitoringMiddleware);
// Register error handling middleware (Sentry integration)
await app.register(errorHandlingMiddleware);
app.addHook("onRequest", async (request, _reply) => {
const requestId = extractOrGenerateRequestId(request.headers);
request.id = requestId;
const pinoLog = request.log as typeof request.log & { bindings?: Record<string, string>; bindActive?: () => void };
pinoLog.bindings = { requestId };
pinoLog.bindActive?.();
request.headers["x-request-id"] = requestId;
});
await app.register(darkwatchRoutes);
await app.register(voiceprintRoutes);
await app.register(correlationRoutes);
await app.register(extensionRoutes, { prefix: '/extension' });
await app.register(waitlistRoutes);
await app.register(blogRoutes, { prefix: '/blog' });
await app.register(blogAdminRoutes);
app.get("/health", async () => ({ status: "ok", timestamp: new Date().toISOString() }));
try {
await app.listen({ port: parseInt(process.env.PORT || "3000", 10), host: "0.0.0.0" });
app.log.info(`Server listening on port ${process.env.PORT || 3000}`);
} catch (err) {
app.log.error(err);
captureSentryError(err as Error, { context: "server_startup" });
process.exit(1);
}
}
bootstrap();