// ============================================================ // LiveServer-M1 — Database Schema // Educational video conferencing platform on LiveKit // ============================================================ generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" } // ======================== ENUMS ============================= enum UserRole { ADMIN HOST GUEST } enum RoomStatus { WAITING // Создана, но лекция не началась ACTIVE // Лекция идёт ENDED // Лекция завершена } enum LobbyStatus { PENDING APPROVED REJECTED } // ======================== AUTH ============================== // Таблицы совместимы с better-auth (Prisma adapter) // Docs: https://www.better-auth.com/docs/adapters/prisma model User { id String @id @default(cuid()) email String @unique name String emailVerified Boolean @default(false) image String? role UserRole @default(HOST) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // better-auth relations sessions Session[] accounts Account[] // App relations hostedRooms Room[] @relation("RoomHost") participantHistories ParticipantHistory[] bannedByEntries BannedEntry[] @relation("BannedBy") @@map("users") } model Session { id String @id @default(cuid()) userId String token String @unique expiresAt DateTime ipAddress String? userAgent String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@map("sessions") } model Account { id String @id @default(cuid()) userId String accountId String providerId String accessToken String? refreshToken String? accessTokenExpiresAt DateTime? refreshTokenExpiresAt DateTime? scope String? idToken String? password String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@map("accounts") } model Verification { id String @id @default(cuid()) identifier String value String expiresAt DateTime createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("verifications") } // ======================== ROOMS ============================= model Room { id String @id @default(cuid()) name String code String @unique @default(cuid()) // Короткий код для инвайт-ссылки hostId String status RoomStatus @default(WAITING) // Security settings lobbyEnabled Boolean @default(true) pinHash String? // bcrypt hash, null = без PIN webinarMode Boolean @default(false) isLocked Boolean @default(false) // Хост может заблокировать вход полностью // LiveKit livekitRoomName String? @unique // Имя комнаты в LiveKit (создаётся при старте) // Timestamps createdAt DateTime @default(now()) startedAt DateTime? // Когда хост начал лекцию endedAt DateTime? // Когда завершилась // Relations host User @relation("RoomHost", fields: [hostId], references: [id]) lobbyEntries LobbyEntry[] participantHistories ParticipantHistory[] chatMessages ChatMessage[] sharedFiles SharedFile[] bannedEntries BannedEntry[] lectureArtifact LectureArtifact? @@index([hostId]) @@index([status]) @@index([code]) @@map("rooms") } // ======================== LOBBY ============================= model LobbyEntry { id String @id @default(cuid()) roomId String displayName String sessionId String // UUID, генерируется на клиенте sessionFingerprint String? // Browser fingerprint hash ipAddress String? status LobbyStatus @default(PENDING) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt room Room @relation(fields: [roomId], references: [id], onDelete: Cascade) @@index([roomId, status]) @@index([sessionId]) @@map("lobby_entries") } // ======================== PARTICIPANTS ====================== model ParticipantHistory { id String @id @default(cuid()) roomId String userId String? // null для гостей без аккаунта sessionId String // UUID, совпадает с LobbyEntry.sessionId displayName String role UserRole joinedAt DateTime @default(now()) leftAt DateTime? room Room @relation(fields: [roomId], references: [id], onDelete: Cascade) user User? @relation(fields: [userId], references: [id], onDelete: SetNull) @@index([roomId]) @@index([userId]) @@index([sessionId]) @@map("participant_histories") } // ======================== CHAT ============================== model ChatMessage { id String @id @default(cuid()) roomId String sessionId String // Кто отправил (связь через sessionId, не userId — гости тоже пишут) senderName String content String createdAt DateTime @default(now()) room Room @relation(fields: [roomId], references: [id], onDelete: Cascade) @@index([roomId, createdAt]) @@map("chat_messages") } // ======================== FILES ============================= model SharedFile { id String @id @default(cuid()) roomId String sessionId String // Кто загрузил fileName String fileKey String // S3 object key fileSize Int mimeType String createdAt DateTime @default(now()) room Room @relation(fields: [roomId], references: [id], onDelete: Cascade) @@index([roomId]) @@map("shared_files") } // ======================== BANS ============================== model BannedEntry { id String @id @default(cuid()) roomId String sessionFingerprint String // Основной идентификатор для бана ipAddress String? // Вторичный сигнал displayName String? // Для UI — кого забанили reason String? bannedById String // Хост, который забанил createdAt DateTime @default(now()) room Room @relation(fields: [roomId], references: [id], onDelete: Cascade) bannedBy User @relation("BannedBy", fields: [bannedById], references: [id]) @@unique([roomId, sessionFingerprint]) @@index([roomId]) @@map("banned_entries") } // ======================== LECTURE ARTIFACTS ================== model LectureArtifact { id String @id @default(cuid()) roomId String @unique // 1:1 с Room transcriptText String? @db.Text // Полный текст транскрипта (для поиска) transcriptUrl String? // S3 key для raw-файла транскрипта summary String? @db.Text // AI-сгенерированная суммаризация (Markdown/JSON) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt room Room @relation(fields: [roomId], references: [id], onDelete: Cascade) @@map("lecture_artifacts") }