From a97b730a89cd748d4fd7e49d73be1fc153ec1c74 Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Sat, 28 Feb 2026 19:33:22 -0800 Subject: [PATCH] feat(flags): seed default feature flags for all products on startup --- .../src/modules/flags/seed.ts | 189 ++++++++++++++++++ services/platform-service/src/server.ts | 14 +- 2 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 services/platform-service/src/modules/flags/seed.ts diff --git a/services/platform-service/src/modules/flags/seed.ts b/services/platform-service/src/modules/flags/seed.ts new file mode 100644 index 00000000..60392077 --- /dev/null +++ b/services/platform-service/src/modules/flags/seed.ts @@ -0,0 +1,189 @@ +/** + * Feature flag seeding — creates default flags per product on startup. + * + * Idempotent: skips flags that already exist (matched by key + productId). + * Called from server startup after Cosmos init. + */ + +import * as repo from './repository.js'; +import type { FeatureFlagDoc } from './types.js'; + +interface FlagSeedDef { + key: string; + enabled: boolean; + description: string; + platforms: string[]; + percentage: number; +} + +// ─── Default flags per product ─────────────────────────────────────────────── + +const COMMON_FLAGS: FlagSeedDef[] = [ + { + key: 'maintenance_mode', + enabled: false, + description: 'Global maintenance mode — disables non-essential features', + platforms: [], + percentage: 100, + }, + { + key: 'telemetry_enabled', + enabled: true, + description: 'Client telemetry collection enabled', + platforms: [], + percentage: 100, + }, +]; + +const PRODUCT_FLAGS: Record = { + chronomind: [ + { + key: 'routines_enabled', + enabled: true, + description: 'Routines feature visible in navigation', + platforms: ['web', 'ios', 'android'], + percentage: 100, + }, + { + key: 'focus_mode_enabled', + enabled: true, + description: 'Pomodoro / focus mode available', + platforms: ['web', 'ios', 'android'], + percentage: 100, + }, + { + key: 'calendar_import_enabled', + enabled: true, + description: 'Calendar .ics import feature', + platforms: ['web'], + percentage: 100, + }, + { + key: 'cloud_sync_enabled', + enabled: false, + description: 'Cloud sync for timers and routines', + platforms: [], + percentage: 0, + }, + ], + nomgap: [ + { + key: 'ai_coach_enabled', + enabled: false, + description: 'AI coaching card on fasting screen (Pro feature)', + platforms: ['ios', 'android'], + percentage: 0, + }, + { + key: 'social_fasting_enabled', + enabled: false, + description: 'Social fasting / group fasts', + platforms: ['ios', 'android'], + percentage: 0, + }, + { + key: 'meal_logging_enabled', + enabled: true, + description: 'Meal logging before/after fasts', + platforms: ['ios', 'android'], + percentage: 100, + }, + { + key: 'anatomical_mode_enabled', + enabled: false, + description: 'Anatomical body visualization (Pro)', + platforms: ['ios', 'android'], + percentage: 0, + }, + ], + mindlyst: [ + { + key: 'voice_capture_enabled', + enabled: true, + description: 'Voice capture / transcription', + platforms: ['ios', 'android'], + percentage: 100, + }, + { + key: 'ai_triage_enabled', + enabled: true, + description: 'AI auto-triage into brains', + platforms: ['ios', 'android', 'web'], + percentage: 100, + }, + { + key: 'share_cards_enabled', + enabled: false, + description: 'Shareable memory cards', + platforms: [], + percentage: 0, + }, + ], + lysnrai: [ + { + key: 'keyboard_dictation_enabled', + enabled: true, + description: 'Keyboard extension dictation', + platforms: ['ios'], + percentage: 100, + }, + { + key: 'smart_punctuation_enabled', + enabled: true, + description: 'Smart punctuation in dictation', + platforms: ['ios', 'desktop'], + percentage: 100, + }, + { + key: 'speak_to_translate_enabled', + enabled: false, + description: 'Speak-to-translate mode (requires Azure Translator)', + platforms: ['ios'], + percentage: 0, + }, + ], +}; + +// ─── Seed function ─────────────────────────────────────────────────────────── + +export async function seedDefaultFlags(logger?: { info: (msg: string) => void }): Promise { + const log = logger ?? { info: () => {} }; + let created = 0; + + const products = Object.keys(PRODUCT_FLAGS); + + for (const productId of products) { + const flags = [...COMMON_FLAGS, ...PRODUCT_FLAGS[productId]]; + + for (const def of flags) { + const existing = await repo.getByKey(def.key, productId); + if (existing) continue; + + const now = new Date().toISOString(); + const doc: FeatureFlagDoc = { + id: `flag_${productId}_${def.key}`, + productId, + key: def.key, + enabled: def.enabled, + description: def.description, + platforms: def.platforms, + regions: [], + osVersions: [], + segments: [], + percentage: def.percentage, + createdAt: now, + updatedAt: now, + }; + + await repo.create(doc); + created++; + log.info(`Seeded flag: ${productId}/${def.key} (enabled=${def.enabled})`); + } + } + + if (created > 0) { + log.info(`Feature flag seeding complete: ${created} flags created`); + } + + return created; +} diff --git a/services/platform-service/src/server.ts b/services/platform-service/src/server.ts index 0a9efa09..ca080e8f 100644 --- a/services/platform-service/src/server.ts +++ b/services/platform-service/src/server.ts @@ -42,6 +42,10 @@ import { itemRoutes } from './modules/items/routes.js'; import { commentRoutes } from './modules/comments/routes.js'; import { voteRoutes } from './modules/votes/routes.js'; import { memoryRoutes } from './modules/memory/routes.js'; +import { brainRoutes } from './modules/brains/routes.js'; +import { streakRoutes } from './modules/streaks/routes.js'; +import { reflectionRoutes } from './modules/reflections/routes.js'; +import { dailyBriefRoutes } from './modules/daily-briefs/routes.js'; import { publicRoutes } from './modules/public/routes.js'; import { tokenRoutes } from './modules/tokens/routes.js'; import { themeRoutes } from './modules/themes/routes.js'; @@ -72,10 +76,14 @@ import { changelogRoutes } from './modules/changelog/routes.js'; import { pushTriggerRoutes } from './modules/push-triggers/routes.js'; import { initCosmosIfNeeded } from './lib/cosmos-init.js'; import { config } from './lib/config.js'; +import { seedDefaultFlags } from './modules/flags/seed.js'; await initCosmosIfNeeded(); await loadProductCache(); +// Seed default feature flags (idempotent, best-effort) +seedDefaultFlags({ info: (msg: string) => console.log(`[flags-seed] ${msg}`) }).catch(() => {}); + const app = await createServiceApp({ name: 'platform-service', version: '0.1.0', @@ -132,8 +140,12 @@ await app.register(settingsRoutes, { prefix: '/api' }); await app.register(itemRoutes, { prefix: '/api' }); await app.register(commentRoutes, { prefix: '/api' }); await app.register(voteRoutes, { prefix: '/api' }); -// Mobile capture modules +// MindLyst modules (brains, memory, streaks, reflections, daily briefs) +await app.register(brainRoutes, { prefix: '/api' }); await app.register(memoryRoutes, { prefix: '/api' }); +await app.register(streakRoutes, { prefix: '/api' }); +await app.register(reflectionRoutes, { prefix: '/api' }); +await app.register(dailyBriefRoutes, { prefix: '/api' }); // API tokens module await app.register(tokenRoutes, { prefix: '/api' }); // Themes module