Files
LiveServer-M1/prisma/schema.prisma
joylessorchid ca3c786a6b fix: Prisma 7 adapter-based connection (no url in schema)
- Use @prisma/adapter-pg for PrismaClient (Prisma 7 removed datasourceUrl/datasources)
- prisma.config.ts: migrate.url for CLI commands
- schema.prisma: no url in datasource (Prisma 7 requirement)
2026-03-24 06:43:47 +03:00

256 lines
7.5 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================
// 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")
}