Initial commit — Performance West telecom compliance platform
Includes: API (Express/TypeScript), Astro site, Python workers, document generators, FCC compliance tools, Canada CRTC formation, Ansible infrastructure, and deployment scripts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
f8cd37ac8c
1823 changed files with 145167 additions and 0 deletions
184
api/src/index.ts
Normal file
184
api/src/index.ts
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
import express from "express";
|
||||
import cookieParser from "cookie-parser";
|
||||
import { config } from "./config.js";
|
||||
import { pool, pgHealthy } from "./db.js";
|
||||
import { securityHeaders, extractClientIp } from "./middleware/security.js";
|
||||
import { corsMiddleware } from "./middleware/cors.js";
|
||||
import { globalLimiter } from "./middleware/rate-limit.js";
|
||||
import { errorHandler } from "./middleware/error-handler.js";
|
||||
import { accessLog } from "./middleware/access-log.js";
|
||||
|
||||
import healthRouter from "./routes/health.js";
|
||||
import subscribeRouter from "./routes/subscribe.js";
|
||||
import ticketsRouter from "./routes/tickets.js";
|
||||
import quotesRouter from "./routes/quotes.js";
|
||||
import formationsRouter from "./routes/formations.js";
|
||||
import discountsRouter from "./routes/discounts.js";
|
||||
import adminRouter from "./routes/admin.js";
|
||||
import webhooksRouter from "./routes/webhooks.js";
|
||||
import identityRouter from "./routes/identity.js";
|
||||
import refundsRouter from "./routes/refunds.js";
|
||||
import agentsRouter from "./routes/agents.js";
|
||||
import bundlesRouter from "./routes/bundles.js";
|
||||
import entitiesRouter from "./routes/entities.js";
|
||||
import idUploadRouter from "./routes/id-upload.js";
|
||||
import canadaCrtcRouter from "./routes/canada-crtc.js";
|
||||
import checkoutRouter from "./routes/checkout.js";
|
||||
import ambLocationsRouter from "./routes/amb-locations.js";
|
||||
import paymentMethodsRouter from "./routes/payment-methods.js";
|
||||
import paypalRouter from "./routes/paypal.js";
|
||||
import portalAuthRouter from "./routes/portal-auth.js";
|
||||
import portalRouter from "./routes/portal.js";
|
||||
import portalSetupRouter from "./routes/portal-setup.js";
|
||||
import portalEsignRouter from "./routes/portal-esign.js";
|
||||
import fccLookupRouter from "./routes/fcc-lookup.js";
|
||||
import telecomEntitiesRouter from "./routes/telecom-entities.js";
|
||||
import complianceOrdersRouter from "./routes/compliance-orders.js";
|
||||
import cdrRouter from "./routes/cdr.js";
|
||||
import iccRouter from "./routes/icc.js";
|
||||
import resellerCertsRouter from "./routes/reseller-certs.js";
|
||||
import lnpaRegionsRouter from "./routes/lnpa-regions.js";
|
||||
import fccFilingsRouter from "./routes/fcc-filings.js";
|
||||
import adminCryptoRouter from "./routes/admin-crypto.js";
|
||||
import foreignQualRouter from "./routes/foreign-qualification.js";
|
||||
import corpStatusRouter from "./routes/corp-status.js";
|
||||
import portalRmdReviewRouter from "./routes/portal-rmd-review.js";
|
||||
import pucRouter from "./routes/puc.js";
|
||||
|
||||
const app = express();
|
||||
|
||||
// Trust first proxy (nginx) in production
|
||||
if (config.nodeEnv === "production") {
|
||||
app.set("trust proxy", 1);
|
||||
}
|
||||
|
||||
// --- Middleware stack (order matters) ---
|
||||
app.use(securityHeaders);
|
||||
app.use(extractClientIp);
|
||||
app.use(accessLog);
|
||||
app.use(corsMiddleware);
|
||||
app.use(cookieParser());
|
||||
app.use(globalLimiter);
|
||||
|
||||
// Stripe webhook — raw body MUST be preserved for signature verification.
|
||||
// Mount BEFORE express.json() so the Buffer is not parsed away.
|
||||
app.use("/api/v1/webhooks/stripe", express.raw({ type: "application/json" }));
|
||||
|
||||
app.use(identityRouter); // identity webhook uses raw() internally on its specific route
|
||||
|
||||
app.use(express.json({ limit: "512kb" })); // 512kb for eSign signature PNG base64
|
||||
|
||||
// Reject non-JSON content types on POST/PUT/PATCH
|
||||
app.use((req, res, next) => {
|
||||
if (["POST", "PUT", "PATCH"].includes(req.method) && !req.is("json")) {
|
||||
res.status(415).json({ error: "Content-Type must be application/json" });
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
// --- Routes ---
|
||||
app.use(healthRouter);
|
||||
app.use(subscribeRouter);
|
||||
app.use(ticketsRouter);
|
||||
app.use(quotesRouter);
|
||||
app.use(formationsRouter);
|
||||
app.use(discountsRouter);
|
||||
app.use(adminRouter);
|
||||
app.use(webhooksRouter);
|
||||
app.use(refundsRouter);
|
||||
app.use(agentsRouter);
|
||||
app.use(bundlesRouter);
|
||||
app.use(entitiesRouter);
|
||||
app.use(idUploadRouter);
|
||||
app.use(canadaCrtcRouter);
|
||||
app.use(checkoutRouter);
|
||||
app.use(ambLocationsRouter);
|
||||
app.use(paymentMethodsRouter);
|
||||
app.use(paypalRouter);
|
||||
app.use("/api/v1/auth", portalAuthRouter);
|
||||
app.use(portalSetupRouter);
|
||||
app.use(portalEsignRouter);
|
||||
app.use(portalRmdReviewRouter);
|
||||
app.use("/api/v1/portal", portalRouter); // Must be AFTER specific portal routes (uses catch-all customer-auth)
|
||||
app.use(fccLookupRouter);
|
||||
app.use(corpStatusRouter);
|
||||
app.use(telecomEntitiesRouter);
|
||||
app.use(complianceOrdersRouter);
|
||||
app.use(cdrRouter);
|
||||
app.use(iccRouter);
|
||||
app.use(resellerCertsRouter);
|
||||
app.use(lnpaRegionsRouter);
|
||||
app.use(fccFilingsRouter);
|
||||
app.use(foreignQualRouter);
|
||||
app.use(pucRouter);
|
||||
app.use(adminCryptoRouter);
|
||||
// Note: identityRouter mounted above express.json() for webhook route,
|
||||
// but also handles non-webhook routes (create-session, poll) which work fine with json()
|
||||
|
||||
// --- Error handler (must be last) ---
|
||||
app.use(errorHandler);
|
||||
|
||||
// --- Start ---
|
||||
async function start() {
|
||||
// Verify database connection
|
||||
const dbOk = await pgHealthy();
|
||||
if (dbOk) {
|
||||
console.log(`[db] PostgreSQL connected`);
|
||||
} else {
|
||||
console.warn(`[db] PostgreSQL unreachable — API will start but DB-dependent routes will fail`);
|
||||
}
|
||||
|
||||
// Pre-warm FX rate cache
|
||||
import("./fx.js").then(fx => fx.warmFxCache()).catch(err => console.warn("[fx] warmup failed:", err));
|
||||
|
||||
// Log ERPNext/Listmonk configuration status (non-blocking)
|
||||
if (config.erpnext.apiKey) {
|
||||
console.log(`[erpnext] Configured at ${config.erpnext.url}`);
|
||||
try {
|
||||
const r = await fetch(`${config.erpnext.url.replace(/\/$/, "")}/api/method/ping`, {
|
||||
headers: {
|
||||
Authorization: `token ${config.erpnext.apiKey}:${config.erpnext.apiSecret}`,
|
||||
"X-Frappe-Site-Name": config.erpnext.siteName,
|
||||
},
|
||||
});
|
||||
if (r.ok) {
|
||||
console.log("[erpnext] Connectivity check passed");
|
||||
} else {
|
||||
console.warn(`[erpnext] Connectivity check failed (HTTP ${r.status})`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("[erpnext] Connectivity check failed (network error):", err);
|
||||
}
|
||||
} else {
|
||||
console.warn(`[erpnext] No API key configured — ERPNext integration disabled`);
|
||||
}
|
||||
if (config.listmonk.password) {
|
||||
console.log(`[listmonk] Configured at ${config.listmonk.url}`);
|
||||
} else {
|
||||
console.warn(`[listmonk] No credentials configured — Listmonk integration disabled`);
|
||||
}
|
||||
|
||||
const host = "0.0.0.0"; // bind all interfaces — nginx on host proxies to Docker container port
|
||||
app.listen(config.port, host, () => {
|
||||
console.log(`[api] Performance West API listening on ${host}:${config.port} (${config.nodeEnv})`);
|
||||
});
|
||||
}
|
||||
|
||||
start().catch((err) => {
|
||||
console.error("[api] Fatal startup error:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
process.on("SIGTERM", async () => {
|
||||
console.log("[api] SIGTERM received, shutting down...");
|
||||
await pool.end();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on("SIGINT", async () => {
|
||||
console.log("[api] SIGINT received, shutting down...");
|
||||
await pool.end();
|
||||
process.exit(0);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue