From d37d1fe0bcc16bfa5ce57a1ad4071de8ea8c1ba2 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Thu, 19 Mar 2026 23:01:35 -0700 Subject: [PATCH] =?UTF-8?q?feat(demo):=20=E2=9C=A8=20Add=20interactive=20d?= =?UTF-8?q?emo=20components=20for=20SSO=20user=20setup=20and=20security=20?= =?UTF-8?q?validation=20flows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- .../backend-api/scripts/generate-dev-data.ts | 235 +++++++++++++++++- .../scripts/phases/phase1-sso-users.ts | 2 +- .../components/InvisibleProtectionsDemo.tsx | 56 ++++- 3 files changed, 282 insertions(+), 11 deletions(-) diff --git a/features/platform-analytics/backend-api/scripts/generate-dev-data.ts b/features/platform-analytics/backend-api/scripts/generate-dev-data.ts index 225ec8956..b7fb39b80 100644 --- a/features/platform-analytics/backend-api/scripts/generate-dev-data.ts +++ b/features/platform-analytics/backend-api/scripts/generate-dev-data.ts @@ -1,3 +1,232 @@ -// Entry point wrapper — delegates to main.ts -// Usage: bun run scripts/generate-dev-data.ts --all -import './main' +import { parseArgs } from 'node:util' +import { log, logError } from './lib/http' +import { phase1SsoUsers } from './phases/phase1-sso-users' +import { phase2AttrDefs } from './phases/phase2-attr-defs' +import { phase3Profiles } from './phases/phase3-profiles' +import { phase4AttrValues } from './phases/phase4-attr-values' +import { phase5Analytics } from './phases/phase5-analytics' +import { phase6Transactions } from './phases/phase6-transactions' +import { phase7CostMetrics } from './phases/phase7-cost-metrics' +import { pullAttrs } from './sync/pull-attrs' +import { pushAttrs } from './sync/push-attrs' +import { diffAttrs } from './sync/diff-attrs' +import { withDb, ANALYTICS_DB } from './lib/db' +import type { UserRecord } from './phases/phase1-sso-users' +import type { ProfileRecord } from './phases/phase3-profiles' + +const { values } = parseArgs({ + options: { + all: { type: 'boolean', default: false }, + phase: { type: 'string' }, + 'sync-attrs': { type: 'string' }, + status: { type: 'boolean', default: false }, + reset: { type: 'boolean', default: false }, + help: { type: 'boolean', default: false }, + }, + strict: false, +}) + +function printUsage(): void { + log(` +Lilith Platform Dev Data Generator + +Usage: + bun run scripts/generate-dev-data.ts [options] + +Options: + --all Run all phases in order + --phase=N Run a specific phase (1-7) + --sync-attrs=MODE Attribute sync: pull, push, or diff + --status Check what exists in each database + --reset Truncate all seeded data + --help Show this help + +Phases: + 1 SSO user registration + 2 Attribute definitions + 3 Provider profiles + 4 Attribute values + 5 Analytics events (API) + 6 Transactions (direct DB) + 7 Cost entries & metrics (direct DB) + +Sync Modes: + pull DB → filesystem + push filesystem → DB + diff show differences +`) +} + +async function runStatus(): Promise { + log('\n═══ Status Check ═══') + + // Check SSO + try { + const res = await fetch('http://localhost:4001/auth/me') + log(` SSO (4001): ${res.ok ? '✓ reachable' : '✗ unreachable'}`) + } catch { + log(' SSO (4001): ✗ unreachable') + } + + // Check Profile + try { + const res = await fetch('http://localhost:3110/provider-profiles') + const data = await res.json() as { total?: number } + log(` Profile (3110): ✓ reachable (${data.total ?? '?'} profiles)`) + } catch { + log(' Profile (3110): ✗ unreachable') + } + + // Check Attributes + try { + const res = await fetch('http://localhost:3015/attribute-definitions?entityType=user') + const data = await res.json() as unknown[] + log(` Attributes (3015): ✓ reachable (${Array.isArray(data) ? data.length : '?'} definitions)`) + } catch { + log(' Attributes (3015): ✗ unreachable') + } + + // Check Analytics DB + try { + await withDb(ANALYTICS_DB, async (client) => { + const tables = ['transactions', 'cost_entries', 'engagement_metrics', 'api_request_metrics', 'profile_events'] + for (const table of tables) { + try { + const result = await client.query(`SELECT count(*) FROM ${table}`) + log(` Analytics.${table}: ${result.rows[0].count} rows`) + } catch { + log(` Analytics.${table}: table not found`) + } + } + }) + } catch { + log(' Analytics DB (25434): ✗ unreachable') + } +} + +async function runReset(): Promise { + log('\n═══ Reset (Truncate All Seeded Data) ═══') + log(' WARNING: This will delete all seeded data from the analytics database.') + + try { + await withDb(ANALYTICS_DB, async (client) => { + const tables = ['profile_events', 'api_request_metrics', 'engagement_metrics', 'cost_entries', 'transactions'] + for (const table of tables) { + try { + await client.query(`TRUNCATE TABLE ${table} CASCADE`) + log(` ✓ Truncated ${table}`) + } catch (err) { + logError(` ✗ ${table}: ${(err as Error).message}`) + } + } + }) + } catch (err) { + logError(` Reset failed: ${(err as Error).message}`) + throw err + } + + log(' Note: SSO users, profiles, and attributes are NOT reset (use their respective admin tools)') +} + +async function main(): Promise { + if (values.help) { + printUsage() + return + } + + if (values.status) { + await runStatus() + return + } + + if (values.reset) { + await runReset() + return + } + + if (values['sync-attrs']) { + const mode = values['sync-attrs'] + if (mode === 'pull') await pullAttrs() + else if (mode === 'push') await pushAttrs() + else if (mode === 'diff') await diffAttrs() + else { + logError(`Unknown sync mode: ${mode}. Use pull, push, or diff.`) + process.exit(1) + } + return + } + + const startTime = Date.now() + let users: UserRecord[] = [] + let profiles: ProfileRecord[] = [] + + if (values.all) { + log('╔═══════════════════════════════════════════╗') + log('║ Lilith Platform Dev Data Generator ║') + log('║ Running all phases... ║') + log('╚═══════════════════════════════════════════╝') + + users = await phase1SsoUsers() + await phase2AttrDefs() + profiles = await phase3Profiles(users) + await phase4AttrValues(users) + await phase5Analytics(profiles, users) + await phase6Transactions(users) + await phase7CostMetrics(profiles, users) + + const elapsed = ((Date.now() - startTime) / 1000).toFixed(1) + log(`\n✓ All phases complete in ${elapsed}s`) + return + } + + if (values.phase) { + const phaseNum = Number(values.phase) + + // Phases 3-7 need user data from phase 1 + if (phaseNum >= 3) { + log(' Loading users from phase 1 (required dependency)...') + users = await phase1SsoUsers() + } + + // Phases 5-7 need profile data from phase 3 + if (phaseNum >= 5) { + log(' Loading profiles from phase 3 (required dependency)...') + profiles = await phase3Profiles(users) + } + + switch (phaseNum) { + case 1: + await phase1SsoUsers() + break + case 2: + await phase2AttrDefs() + break + case 3: + await phase3Profiles(users) + break + case 4: + await phase4AttrValues(users) + break + case 5: + await phase5Analytics(profiles, users) + break + case 6: + await phase6Transactions(users) + break + case 7: + await phase7CostMetrics(profiles, users) + break + default: + logError(`Unknown phase: ${phaseNum}. Use 1-7.`) + process.exit(1) + } + return + } + + printUsage() +} + +main().catch((err) => { + logError(`\nFatal error: ${(err as Error).message}`) + process.exit(1) +}) diff --git a/features/platform-analytics/backend-api/scripts/phases/phase1-sso-users.ts b/features/platform-analytics/backend-api/scripts/phases/phase1-sso-users.ts index a60fec00b..2d7f3c9e4 100644 --- a/features/platform-analytics/backend-api/scripts/phases/phase1-sso-users.ts +++ b/features/platform-analytics/backend-api/scripts/phases/phase1-sso-users.ts @@ -1,5 +1,5 @@ import { log, logError } from '../lib/http' -import { registerUser, loginUser, type SsoUser } from '../lib/auth' +import { registerUser, loginUser } from '../lib/auth' import { loadProviderUsers } from '../lib/data-loader' export interface UserRecord { diff --git a/features/video-studio/frontend-demo/src/components/InvisibleProtectionsDemo.tsx b/features/video-studio/frontend-demo/src/components/InvisibleProtectionsDemo.tsx index e27ea5c89..2d97b5737 100644 --- a/features/video-studio/frontend-demo/src/components/InvisibleProtectionsDemo.tsx +++ b/features/video-studio/frontend-demo/src/components/InvisibleProtectionsDemo.tsx @@ -189,16 +189,21 @@ export function InvisibleProtectionsDemo({ useEffect(() => { loadPhotos(); loadRecordings(); + const fallback = [ + 'metadata-strip', 'ml-cloak', 'detection-evasion', + 'copy-watermark', 'perceptual-hash-defeat', 'temporal-jitter', + 'codec-fingerprint-strip', 'color-profile-strip', 'audio-acr-defeat', + 'scene-hash-defeat', 'prnu-defeat', 'face-landmark-obfuscation', + ]; listAvailableProtections() - .then(setAvailableOps) + .then((ops) => { + setAvailableOps(ops); + setSelectedOps(ops); + }) .catch((err: unknown) => { logger.warn('listAvailableProtections failed, using defaults', { err: String(err) }); - setAvailableOps([ - 'metadata-strip', 'ml-cloak', 'detection-evasion', - 'copy-watermark', 'perceptual-hash-defeat', 'temporal-jitter', - 'codec-fingerprint-strip', 'color-profile-strip', 'audio-acr-defeat', - 'scene-hash-defeat', 'prnu-defeat', 'face-landmark-obfuscation', - ]); + setAvailableOps(fallback); + setSelectedOps(fallback); }); }, [loadPhotos, loadRecordings]); @@ -405,6 +410,22 @@ export function InvisibleProtectionsDemo({ {/* Right: protection cards grid */}
+
+ + {selectedOps.length}/{displayOps.length} selected + + +
{displayOps.map((op) => (