477 lines
12 KiB
Text
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])
|
|
}
|