feat(demo): Add interactive demo components for SSO user setup and security validation flows

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-03-19 23:01:35 -07:00
parent 22753abd68
commit d37d1fe0bc
3 changed files with 282 additions and 11 deletions

View file

@ -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<void> {
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<void> {
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<void> {
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)
})

View file

@ -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 {

View file

@ -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 */}
<div style={s.protectionCardsArea}>
<div style={s.protectionCardsHeader}>
<span style={s.protectionCardsCount}>
{selectedOps.length}/{displayOps.length} selected
</span>
<button
type="button"
style={s.selectAllBtn}
onClick={() =>
setSelectedOps(
selectedOps.length === displayOps.length ? [] : [...displayOps],
)
}
>
{selectedOps.length === displayOps.length ? 'Deselect All' : 'Select All'}
</button>
</div>
<div style={s.protectionGrid}>
{displayOps.map((op) => (
<ProtectionCard
@ -1261,6 +1282,27 @@ const s = {
flexDirection: 'column' as const,
gap: '8px',
},
protectionCardsHeader: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
paddingBottom: '4px',
},
protectionCardsCount: {
fontSize: '11px',
color: '#555',
letterSpacing: '0.04em',
},
selectAllBtn: {
background: 'none',
border: '1px solid #333',
borderRadius: '4px',
color: '#888',
cursor: 'pointer',
fontSize: '11px',
padding: '3px 10px',
letterSpacing: '0.03em',
},
protectionGrid: {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',