learning_ai_clock/backend/src/server.ts
saravanakumardb1 8c90d863a8 feat(backend): admin-panel encryption toggle via initEncryption()
- FIELD_ENCRYPT_ENABLED env var (default: true, fallback only)
- initEncryption(productId) polls encryption_enabled from platform-service
- Admin panel toggle takes precedence, 3s timeout graceful fallback
2026-03-21 15:24:58 -07:00

81 lines
3.2 KiB
TypeScript

/**
* ChronoMind Backend — Fastify server entry point.
*
* Product-specific service for timers, routines, households, shared timers.
* Common platform features (auth, billing, flags, etc.) come from platform-service.
* Port: 4011 (configurable via PORT env var).
*/
import { createServiceApp, registerOptionalJwtContext, startService } from '@bytelyst/fastify-core';
import { timerRoutes } from './modules/timers/routes.js';
import { routineRoutes } from './modules/routines/routes.js';
import { householdRoutes } from './modules/households/routes.js';
import { sharedTimerRoutes } from './modules/shared-timers/routes.js';
import { webhookRoutes } from './modules/webhooks/routes.js';
import { initCosmosIfNeeded } from './lib/cosmos-init.js';
import { initDatastore } from './lib/datastore.js';
import { initEncryption } from './lib/field-encrypt.js';
import { config } from './lib/config.js';
import { getAllFlags } from './lib/feature-flags.js';
import { getBufferedEvents, flushEvents } from './lib/telemetry.js';
import { PRODUCT_ID, productConfig } from './lib/product-config.js';
import { jwtVerify } from 'jose';
import type { JwtPayload } from './lib/request-context.js';
const jwtSecret = new TextEncoder().encode(config.JWT_SECRET);
await initCosmosIfNeeded();
initDatastore();
const app = await createServiceApp({
name: 'chronomind-backend',
version: '0.1.0',
description: 'ChronoMind product-specific backend — timers, routines, households, shared timers, webhooks',
corsOrigin: config.CORS_ORIGIN,
swagger: {
title: 'ChronoMind Backend',
description: 'Timers, routines, households, shared timers, webhooks',
port: config.PORT,
},
metrics: true,
});
await registerOptionalJwtContext(app, {
verifyToken: async (token: string) => {
const { payload } = await jwtVerify(token, jwtSecret, { issuer: 'bytelyst-platform' });
return payload as unknown as JwtPayload;
},
});
await app.register(timerRoutes, { prefix: '/api' });
await app.register(routineRoutes, { prefix: '/api' });
await app.register(householdRoutes, { prefix: '/api' });
await app.register(sharedTimerRoutes, { prefix: '/api' });
await app.register(webhookRoutes, { prefix: '/api' });
// ── Bootstrap (no auth) ──────────────────────────────────────────
app.get('/api/bootstrap', async () => ({
productId: productConfig.productId,
displayName: productConfig.displayName,
backendPort: config.PORT,
}));
// ── Diagnostics routes (no auth) ────────────────────────────────
app.get('/api/diagnostics/flags', async () => getAllFlags());
app.get('/api/diagnostics/telemetry', async () => ({ events: getBufferedEvents() }));
app.post('/api/diagnostics/telemetry/flush', async () => ({ flushed: flushEvents().length }));
app.get('/api/diagnostics/config', async () => ({
productId: PRODUCT_ID,
serviceName: config.SERVICE_NAME,
port: config.PORT,
nodeEnv: config.NODE_ENV,
dbProvider: config.DB_PROVIDER,
telemetryEnabled: config.TELEMETRY_ENABLED,
featureFlagsEnabled: config.FEATURE_FLAGS_ENABLED,
}));
await initEncryption(PRODUCT_ID, app.log);
await startService(app, { port: config.PORT, host: config.HOST });