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:
parent
22753abd68
commit
d37d1fe0bc
3 changed files with 282 additions and 11 deletions
|
|
@ -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)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue