Comprehensive admin + user dashboards (production-ready)

This commit is contained in:
Leon Serfaty
2026-06-07 17:54:30 -04:00
parent 155507f21a
commit f033f00379
122 changed files with 7878 additions and 805 deletions
+53 -2
View File
@@ -30,6 +30,7 @@ model User {
// app fields
stripeCustomerId String?
paypalPayerId String?
onboardedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -37,13 +38,15 @@ model User {
sessions Session[]
accounts Account[]
memberships Member[]
sentInvitations Invitation[] @relation("SentInvitations")
sentInvitations Invitation[] @relation("SentInvitations")
episodes Episode[]
series Series[]
apiKeys ApiKey[]
usageRecords UsageRecord[]
auditLogs AuditLog[] @relation("ActorLogs")
auditLogs AuditLog[] @relation("ActorLogs")
preferences UserPreferences?
@@index([createdAt])
@@map("user")
}
@@ -210,6 +213,8 @@ model Subscription {
@@index([referenceId])
@@index([stripeSubscriptionId])
@@index([paypalSubscriptionId])
@@index([status])
@@index([createdAt])
@@map("subscription")
}
@@ -290,6 +295,11 @@ model Episode {
stage String? // human-readable current step
errorMessage String?
// Public share: when shareId is set, the episode is reachable at /p/<shareId>
// without auth. Clearing shareId disables the public page.
shareId String? @unique
sharedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -305,6 +315,7 @@ model Episode {
@@index([organizationId])
@@index([seriesId])
@@index([status])
@@index([createdAt])
@@map("episode")
}
@@ -483,6 +494,8 @@ model ContentFlag {
episodeId String
episode Episode @relation(fields: [episodeId], references: [id], onDelete: Cascade)
reason String
source String @default("system") // "moderation" | "report" | "system"
severity String @default("medium") // "low" | "medium" | "high"
status String @default("open") // open | reviewed | removed
reviewedBy String?
createdAt DateTime @default(now())
@@ -490,3 +503,41 @@ model ContentFlag {
@@index([status])
@@map("content_flag")
}
/// Liveness of a long-running worker process (heartbeat). One row per worker name.
model WorkerHeartbeat {
name String @id
lastBeatAt DateTime
queued Int?
running Int?
meta Json?
@@map("worker_heartbeat")
}
/// Per-user editor defaults and notification preferences (settings page).
model UserPreferences {
userId String @id
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
defaultVoiceId String?
defaultLanguage String @default("en")
emailOnEpisodeReady Boolean @default(true)
productEmails Boolean @default(true)
updatedAt DateTime @updatedAt
@@map("user_preferences")
}
/// Billing webhook delivery log — also dedups replayed events on `eventId`.
model WebhookEvent {
id String @id @default(cuid())
provider String // "stripe" | "paypal"
eventId String @unique
type String
status String @default("processed") // processed | failed | skipped
error String?
createdAt DateTime @default(now())
@@index([provider, createdAt])
@@map("webhook_event")
}