- broadcasts/types.ts: Broadcast, BroadcastTarget, BroadcastMetrics, InAppMessage - broadcasts/repository.ts: CRUD + delivery tracking + read receipts - broadcasts/targeting.ts: evaluateTarget(), semver, FNV-1a hash - broadcasts/routes.ts: Admin CRUD + public endpoints (14 routes) - surveys/types.ts: Survey, Question, SurveyResponse, conditional logic - surveys/repository.ts: CRUD + analytics + CSV export - surveys/routes.ts: Admin CRUD + public endpoints (13 routes) - cosmos-init.ts: 7 new containers with TTL policies - server.ts: Register broadcastRoutes + surveyRoutes Implements Phase 1 of platform_BROADCAST_SURVEY_ROADMAP.md
185 lines
8.7 KiB
TypeScript
185 lines
8.7 KiB
TypeScript
/**
|
|
* Platform Service — Fastify server entry point.
|
|
*
|
|
* Modules: auth, audit, notifications, feature flags, blob,
|
|
* invitations, referrals, promos (merged from growth-service),
|
|
* subscriptions, usage, plans, licenses, stripe (merged from billing-service),
|
|
* items, comments, votes, public (merged from tracker-service).
|
|
* Port: 4003 (configurable via PORT env var).
|
|
*/
|
|
|
|
// Resolve secrets from configured provider BEFORE config parsing
|
|
import { resolveSecrets, LYSNR_SECRETS } from '@bytelyst/config';
|
|
await resolveSecrets([
|
|
LYSNR_SECRETS.COSMOS_KEY,
|
|
LYSNR_SECRETS.COSMOS_ENDPOINT,
|
|
LYSNR_SECRETS.JWT_SECRET,
|
|
LYSNR_SECRETS.STRIPE_SECRET_KEY,
|
|
LYSNR_SECRETS.STRIPE_WEBHOOK_SECRET,
|
|
LYSNR_SECRETS.AZURE_BLOB_CONNECTION_STRING,
|
|
LYSNR_SECRETS.AZURE_BLOB_ACCOUNT_KEY,
|
|
]);
|
|
|
|
import { createServiceApp, startService } from '@bytelyst/fastify-core';
|
|
import { productRoutes } from './modules/products/routes.js';
|
|
import { loadProductCache } from './modules/products/cache.js';
|
|
import { authRoutes } from './modules/auth/routes.js';
|
|
import { auditRoutes } from './modules/audit/routes.js';
|
|
import { notificationRoutes } from './modules/notifications/routes.js';
|
|
import { flagRoutes } from './modules/flags/routes.js';
|
|
import { rateLimitRoutes } from './modules/ratelimit/routes.js';
|
|
import { blobRoutes } from './modules/blob/routes.js';
|
|
import { invitationRoutes } from './modules/invitations/routes.js';
|
|
import { referralRoutes } from './modules/referrals/routes.js';
|
|
import { referralMigrationAdminRoutes } from './modules/referrals/migration-admin-routes.js';
|
|
import { promoRoutes } from './modules/promos/routes.js';
|
|
import { subscriptionRoutes } from './modules/subscriptions/routes.js';
|
|
import { usageRoutes } from './modules/usage/routes.js';
|
|
import { planRoutes } from './modules/plans/routes.js';
|
|
import { licenseRoutes } from './modules/licenses/routes.js';
|
|
import { stripeRoutes } from './modules/stripe/routes.js';
|
|
import { settingsRoutes } from './modules/settings/routes.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 { tokenRoutes } from './modules/tokens/routes.js';
|
|
import { themeRoutes } from './modules/themes/routes.js';
|
|
import { waitlistRoutes } from './modules/waitlist/routes.js';
|
|
import { telemetryRoutes } from './modules/telemetry/routes.js';
|
|
import { diagnosticsRoutes } from './modules/diagnostics/routes.js';
|
|
import { broadcastRoutes } from './modules/broadcasts/routes.js';
|
|
import { surveyRoutes } from './modules/surveys/routes.js';
|
|
import { jobRoutes } from './modules/jobs/routes.js';
|
|
import { statusRoutes } from './modules/status/routes.js';
|
|
import { deliveryRoutes } from './modules/delivery/routes.js';
|
|
import { sessionRoutes } from './modules/sessions/routes.js';
|
|
import { maintenanceRoutes } from './modules/maintenance/routes.js';
|
|
import { exportRoutes } from './modules/exports/routes.js';
|
|
import { ipRuleRoutes } from './modules/ip-rules/routes.js';
|
|
import { experimentRoutes } from './modules/experiments/routes.js';
|
|
import { analyticsRoutes } from './modules/analytics/routes.js';
|
|
import { feedbackRoutes } from './modules/feedback/routes.js';
|
|
import { impersonationRoutes } from './modules/impersonation/routes.js';
|
|
import { changelogRoutes } from './modules/changelog/routes.js';
|
|
import { webhookRoutes } from './modules/webhooks/routes.js';
|
|
import { marketplaceRoutes } from './modules/marketplace/routes.js';
|
|
import { initCosmosIfNeeded } from './lib/cosmos-init.js';
|
|
import { config } from './lib/config.js';
|
|
import { seedDefaultFlags } from './modules/flags/seed.js';
|
|
import { runPendingMigrations } from './migrations/runner.js';
|
|
import { registerDiagnosticsSubscribers } from './modules/diagnostics/subscribers.js';
|
|
|
|
await initCosmosIfNeeded();
|
|
await loadProductCache();
|
|
|
|
// Run pending database migrations (idempotent, best-effort — failures don't block startup)
|
|
runPendingMigrations().catch(() => {});
|
|
|
|
// Seed default feature flags (idempotent, best-effort)
|
|
seedDefaultFlags({ info: (msg: string) => process.stdout.write(`[flags-seed] ${msg}\n`) }).catch(
|
|
() => {}
|
|
);
|
|
|
|
const app = await createServiceApp({
|
|
name: 'platform-service',
|
|
version: '0.1.0',
|
|
description:
|
|
'Auth, audit, notifications, feature flags, rate limiting, invitations, referrals, promos, subscriptions, usage, plans, licenses, stripe',
|
|
corsOrigin: config.CORS_ORIGIN,
|
|
swagger: {
|
|
title: 'Platform Service',
|
|
description:
|
|
'Auth, audit, notifications, feature flags, rate limiting, invitations, referrals, promos, subscriptions, usage, plans, licenses, stripe',
|
|
port: config.PORT,
|
|
},
|
|
metrics: true,
|
|
});
|
|
|
|
// Parse JWT on every request (best-effort — doesn't block unauthenticated routes)
|
|
import { verifyToken } from './modules/auth/jwt.js';
|
|
import type { JwtPayload } from './lib/request-context.js';
|
|
|
|
app.addHook('onRequest', async req => {
|
|
const auth = req.headers.authorization;
|
|
if (!auth?.startsWith('Bearer ')) return;
|
|
try {
|
|
const payload = await verifyToken(auth.slice(7));
|
|
req.jwtPayload = payload as JwtPayload;
|
|
} catch {
|
|
// Token invalid/expired — leave jwtPayload undefined.
|
|
// Auth-required routes will handle this in their own validation.
|
|
}
|
|
});
|
|
|
|
// Register route modules
|
|
await app.register(productRoutes, { prefix: '/api' });
|
|
await app.register(authRoutes, { prefix: '/api' });
|
|
await app.register(auditRoutes, { prefix: '/api' });
|
|
await app.register(notificationRoutes, { prefix: '/api' });
|
|
await app.register(flagRoutes, { prefix: '/api' });
|
|
await app.register(rateLimitRoutes, { prefix: '/api' });
|
|
await app.register(blobRoutes, { prefix: '/api' });
|
|
// Growth modules (merged from growth-service)
|
|
await app.register(invitationRoutes, { prefix: '/api' });
|
|
await app.register(referralRoutes, { prefix: '/api' });
|
|
await app.register(referralMigrationAdminRoutes, { prefix: '/api/admin' });
|
|
await app.register(promoRoutes, { prefix: '/api' });
|
|
// Billing modules (merged from billing-service)
|
|
await app.register(subscriptionRoutes, { prefix: '/api' });
|
|
await app.register(usageRoutes, { prefix: '/api' });
|
|
await app.register(planRoutes, { prefix: '/api' });
|
|
await app.register(licenseRoutes, { prefix: '/api' });
|
|
// Stripe routes outside billing scope (webhook has its own signature verification)
|
|
await app.register(stripeRoutes, { prefix: '/api' });
|
|
// Settings module (user-level global settings + per-device overrides)
|
|
await app.register(settingsRoutes, { prefix: '/api' });
|
|
// Tracker modules (merged from tracker-service)
|
|
await app.register(itemRoutes, { prefix: '/api' });
|
|
await app.register(commentRoutes, { prefix: '/api' });
|
|
await app.register(voteRoutes, { prefix: '/api' });
|
|
// API tokens module
|
|
await app.register(tokenRoutes, { prefix: '/api' });
|
|
// Themes module
|
|
await app.register(themeRoutes, { prefix: '/api' });
|
|
// Waitlist module (pre-launch signups — public + admin routes)
|
|
await app.register(waitlistRoutes, { prefix: '/api' });
|
|
// Telemetry module (client ingest + admin query + policies)
|
|
await app.register(telemetryRoutes, { prefix: '/api' });
|
|
// Diagnostics module (remote debug sessions — see docs/devops/REMOTE_DIAGNOSTICS_ROADMAP.md)
|
|
await app.register(diagnosticsRoutes, { prefix: '/api' });
|
|
// Public routes — no auth, registered at top level
|
|
await app.register(publicRoutes, { prefix: '/api' });
|
|
// Scheduled jobs module (admin: list, trigger, view runs)
|
|
await app.register(jobRoutes, { prefix: '/api' });
|
|
// Public status page + incident management
|
|
await app.register(statusRoutes, { prefix: '/api' });
|
|
// Transactional email delivery
|
|
await app.register(deliveryRoutes, { prefix: '/api' });
|
|
// Session management
|
|
await app.register(sessionRoutes, { prefix: '/api' });
|
|
// Maintenance mode
|
|
await app.register(maintenanceRoutes, { prefix: '/api' });
|
|
// Data exports
|
|
await app.register(exportRoutes, { prefix: '/api' });
|
|
// IP allow/deny rules
|
|
await app.register(ipRuleRoutes, { prefix: '/api' });
|
|
// P2 — Product Intelligence
|
|
await app.register(experimentRoutes, { prefix: '/api' });
|
|
await app.register(analyticsRoutes, { prefix: '/api' });
|
|
await app.register(feedbackRoutes, { prefix: '/api' });
|
|
await app.register(impersonationRoutes, { prefix: '/api' });
|
|
await app.register(changelogRoutes, { prefix: '/api' });
|
|
// Webhook subscriptions (replaces lib/webhooks.ts fire-and-forget)
|
|
await app.register(webhookRoutes, { prefix: '/api' });
|
|
// Generic Marketplace module
|
|
await app.register(marketplaceRoutes, { prefix: '/api' });
|
|
// Broadcast Messaging & Surveys (see docs/roadmaps/not-started/platform_BROADCAST_SURVEY_ROADMAP.md)
|
|
await app.register(broadcastRoutes, { prefix: '/api' });
|
|
await app.register(surveyRoutes, { prefix: '/api' });
|
|
|
|
// Register event bus subscribers
|
|
registerDiagnosticsSubscribers(app.log);
|
|
|
|
await startService(app, { port: config.PORT, host: config.HOST });
|