aethex.live/prisma/schema.prisma

477 lines
12 KiB
Text

// 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])
}