// AeThex LIVE - Prisma Schema // Phase 1: Foundation (Authentication, Streaming, Real-time Chat) // Learn more: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ===== USERS & AUTHENTICATION ===== model User { id String @id @default(cuid()) supabaseId String @unique // Supabase Auth user ID (UUID) username String @unique email String @unique displayName String? bio String? avatarUrl String? bannerUrl String? isCreator Boolean @default(false) verified Boolean @default(false) twoFactorEnabled Boolean @default(false) // Only show online status to followers isOnline Boolean @default(false) lastSeen DateTime? // Relations channels Channel[] followers Follower[] // Channels this user follows subscriptions Subscription[] // subscriptions TO channels chatMessages ChatMessage[] donations Donation[] clips Clip[] pollVotes PollVote[] clipLikes ClipLike[] watchHistory WatchHistory[] notifications Notification[] userPreferences UserPreferences? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([username]) @@index([email]) @@index([supabaseId]) @@index([isCreator]) } model UserPreferences { id String @id @default(cuid()) userId String @unique user User @relation(fields: [userId], references: [id], onDelete: Cascade) emailNotifications Boolean @default(true) pushNotifications Boolean @default(true) showOnlineStatus Boolean @default(true) allowDirectMessages Boolean @default(true) theme String @default("dark") // dark, light language String @default("en") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // ===== CHANNELS & STREAMING ===== model Channel { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) name String @unique slug String @unique description String? category String? // gaming, music, education, creative, etc. tags String[] @default([]) language String @default("en") // Images thumbnailUrl String? bannerUrl String? // Stats isLive Boolean @default(false) totalViews BigInt @default(0) totalFollowers Int @default(0) totalSubscribers Int @default(0) // Moderation isSuspended Boolean @default(false) suspensionReason String? // Relations streams Stream[] followers Follower[] stats ChannelStats? subscriptions Subscription[] donations Donation[] clips Clip[] notifications Notification[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([userId]) @@index([slug]) @@index([category]) } model ChannelStats { id String @id @default(cuid()) channelId String @unique channel Channel @relation(fields: [channelId], references: [id], onDelete: Cascade) // Peak metrics peakViewers Int @default(0) peakViewerDate DateTime? // Monetization totalRevenue BigInt @default(0) totalDonations BigInt @default(0) totalSubscribersRevenue BigInt @default(0) // Engagement avgChatMessagesPerStream Int @default(0) avgViewDuration Int @default(0) // in seconds bounceRate Float @default(0.0) // percentage // Content totalStreamHours BigInt @default(0) totalClips Int @default(0) lastUpdated DateTime @default(now()) @updatedAt @@index([channelId]) } model Stream { id String @id @default(cuid()) channelId String channel Channel @relation(fields: [channelId], references: [id], onDelete: Cascade) title String description String? status String @default("scheduled") // scheduled, live, ended // Streaming streamKey String @unique hlsUrl String? rtmpIngestUrl String? thumbnailUrl String? // Viewer info viewerCount Int @default(0) peakViewers Int @default(0) uniqueViewers BigInt @default(0) // Timing scheduledAt DateTime? startedAt DateTime? endedAt DateTime? durationSeconds Int? // Content category String? tags String[] @default([]) language String? // Archive isArchived Boolean @default(false) archiveUrl String? processingStatus String @default("pending") // pending, processing, ready, failed // Moderation isMature Boolean @default(false) // Relations chatMessages ChatMessage[] vod VOD? polls Poll[] watchHistory WatchHistory[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([channelId]) @@index([status]) @@unique([channelId, scheduledAt]) // Prevent duplicate scheduled streams } // ===== SOCIAL FEATURES ===== model Follower { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) channelId String channel Channel @relation(fields: [channelId], references: [id], onDelete: Cascade) notificationEnabled Boolean @default(true) followDate DateTime @default(now()) @@unique([userId, channelId]) @@index([channelId]) @@index([followDate]) } // ===== NOTIFICATIONS ===== model Notification { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) channelId String? channel Channel? @relation(fields: [channelId], references: [id], onDelete: SetNull) type String // stream_started, new_follower, new_subscriber, donation title String message String? data String? // JSON for context read Boolean @default(false) readAt DateTime? createdAt DateTime @default(now()) @@index([userId]) @@index([read]) @@index([createdAt]) } // ===== CHAT & MODERATION ===== model ChatMessage { id String @id @default(cuid()) streamId String stream Stream @relation(fields: [streamId], references: [id], onDelete: Cascade) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) message String badges String[] @default([]) // moderator, subscriber, founder isDeleted Boolean @default(false) deletedReason String? createdAt DateTime @default(now()) @@index([streamId]) @@index([createdAt]) } // ===== MONETIZATION ===== model SubscriptionTier { id String @id @default(cuid()) channelId String name String // Basic, Premium, VIP description String? priceCents Int // $4.99 = 499 cents features String[] // Ad-free, Custom emotes, etc. order Int // For sorting createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([channelId, name]) @@index([channelId]) } model Subscription { id String @id @default(cuid()) subscriberId String subscriber User @relation(fields: [subscriberId], references: [id], onDelete: Cascade) channelId String channel Channel @relation(fields: [channelId], references: [id], onDelete: Cascade) tier String // basic, premium, vip priceCents Int stripeSubscriptionId String? status String @default("active") // active, cancelled, expired autoRenew Boolean @default(true) startedAt DateTime @default(now()) renewsAt DateTime? endedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([subscriberId, channelId]) @@index([channelId]) @@index([status]) } model Donation { id String @id @default(cuid()) donorId String donor User @relation(fields: [donorId], references: [id], onDelete: Cascade) channelId String channel Channel @relation(fields: [channelId], references: [id], onDelete: Cascade) streamId String? // Optional: during a specific stream amountCents Int currency String @default("USD") message String? @db.Text stripeChargeId String? status String @default("completed") // pending, completed, failed createdAt DateTime @default(now()) @@index([channelId]) @@index([status]) @@index([createdAt]) } // ===== CONTENT (VOD & CLIPS) ===== model VOD { id String @id @default(cuid()) streamId String @unique stream Stream @relation(fields: [streamId], references: [id], onDelete: Cascade) title String? description String? @db.Text thumbnailUrl String? videoUrl String? durationSeconds Int? views Int @default(0) isPublic Boolean @default(true) processingStatus String @default("processing") // processing, ready, failed clips Clip[] watchHistory WatchHistory[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([processingStatus]) } model Clip { id String @id @default(cuid()) vodId String? vod VOD? @relation(fields: [vodId], references: [id], onDelete: SetNull) channelId String channel Channel @relation(fields: [channelId], references: [id], onDelete: Cascade) creatorId String creator User @relation(fields: [creatorId], references: [id], onDelete: Cascade) title String slug String @unique description String? thumbnailUrl String? videoUrl String @unique startSeconds Int? durationSeconds Int? views Int @default(0) likes Int @default(0) isPublic Boolean @default(true) clipLikes ClipLike[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([channelId]) @@index([creatorId]) @@index([slug]) } model ClipLike { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) clipId String clip Clip @relation(fields: [clipId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) @@unique([userId, clipId]) @@index([clipId]) } // ===== VIEWING & WATCH HISTORY ===== model WatchHistory { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) streamId String? stream Stream? @relation(fields: [streamId], references: [id], onDelete: SetNull) vodId String? vod VOD? @relation(fields: [vodId], references: [id], onDelete: SetNull) watchedSeconds Int? totalSeconds Int? startedAt DateTime @default(now()) lastWatchedAt DateTime @updatedAt @@unique([userId, streamId]) @@unique([userId, vodId]) @@index([userId]) } // ===== INTERACTIONS & ENGAGEMENT ===== model Poll { id String @id @default(cuid()) streamId String stream Stream @relation(fields: [streamId], references: [id], onDelete: Cascade) question String durationSeconds Int? status String @default("active") // active, ended options PollOption[] createdAt DateTime @default(now()) endedAt DateTime? @@index([streamId]) } model PollOption { id String @id @default(cuid()) pollId String poll Poll @relation(fields: [pollId], references: [id], onDelete: Cascade) option String voteCount Int @default(0) order Int @default(0) votes PollVote[] @@index([pollId]) } model PollVote { id String @id @default(cuid()) optionId String option PollOption @relation(fields: [optionId], references: [id], onDelete: Cascade) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) @@unique([userId, optionId]) @@index([optionId]) }