// Prisma schema for Sonic Cloud - Music Platform // Models: User, Artist, Label, Genre, Album, Song, SongGenre, Playlist, PlaylistSong generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" url = env("DATABASE_URL") } // User model - base authentication and profile model User { id String @id @default(uuid()) email String @unique passwordHash String username String @unique displayName String? avatarUrl String? bio String? role String @default("user") // user, artist, label, admin resetToken String? resetExpires DateTime? emailVerified Boolean @default(false) emailToken String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations artist Artist? label Label? playlists Playlist[] shares Share[] refreshTokens RefreshToken[] sessions Session[] playHistory PlayHistory[] queue Queue? uploadSessions UploadSession[] @@map("users") } // Artist model - for musicians who upload music model Artist { id String @id @default(uuid()) userId String @unique name String slug String @unique bio String? avatarUrl String? bannerUrl String? website String? twitter String? instagram String? spotify String? verified Boolean @default(false) labelId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations user User @relation(fields: [userId], references: [id], onDelete: Cascade) label Label? @relation(fields: [labelId], references: [id]) songs Song[] albums Album[] invitations LabelInvitation[] @@map("artists") } // Label model - record labels that manage artists model Label { id String @id @default(uuid()) userId String @unique name String slug String @unique description String? logoUrl String? website String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations user User @relation(fields: [userId], references: [id], onDelete: Cascade) artists Artist[] invitations LabelInvitation[] @@map("labels") } // Genre model - music genres for categorization model Genre { id String @id @default(uuid()) name String @unique slug String @unique description String? color String? // hex color for UI createdAt DateTime @default(now()) // Relations songs SongGenre[] @@map("genres") } // Album model - collection of songs model Album { id String @id @default(uuid()) artistId String title String slug String description String? coverUrl String? releaseDate DateTime? albumType String @default("album") // album, single, ep createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations artist Artist @relation(fields: [artistId], references: [id], onDelete: Cascade) songs Song[] @@unique([artistId, slug]) @@map("albums") } // Song model - individual music tracks model Song { id String @id @default(uuid()) artistId String albumId String? title String slug String description String? audioUrl String coverUrl String? duration Int // duration in seconds waveformUrl String? // waveform visualization data playCount Int @default(0) isPublic Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations artist Artist @relation(fields: [artistId], references: [id], onDelete: Cascade) album Album? @relation(fields: [albumId], references: [id], onDelete: SetNull) genres SongGenre[] playlists PlaylistSong[] playHistory PlayHistory[] @@unique([artistId, slug]) @@map("songs") } // SongGenre model - many-to-many relation between songs and genres model SongGenre { songId String genreId String song Song @relation(fields: [songId], references: [id], onDelete: Cascade) genre Genre @relation(fields: [genreId], references: [id], onDelete: Cascade) @@id([songId, genreId]) @@map("song_genres") } // Playlist model - user-created playlists model Playlist { id String @id @default(uuid()) userId String title String slug String description String? coverUrl String? isPublic Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations user User @relation(fields: [userId], references: [id], onDelete: Cascade) songs PlaylistSong[] @@unique([userId, slug]) @@map("playlists") } // PlaylistSong model - many-to-many relation with ordering model PlaylistSong { playlistId String songId String position Int addedAt DateTime @default(now()) playlist Playlist @relation(fields: [playlistId], references: [id], onDelete: Cascade) song Song @relation(fields: [songId], references: [id], onDelete: Cascade) @@id([playlistId, songId]) @@map("playlist_songs") } // Label invitation model - invitations from labels to artists model LabelInvitation { id String @id @default(uuid()) labelId String artistId String status String @default("pending") // pending, accepted, declined, expired message String? expiresAt DateTime createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations label Label @relation(fields: [labelId], references: [id], onDelete: Cascade) artist Artist @relation(fields: [artistId], references: [id], onDelete: Cascade) @@unique([labelId, artistId]) @@index([artistId, status]) @@map("label_invitations") } // Share model - tracks shared content links model Share { id String @id @default(cuid()) type String // SONG, PLAYLIST, or ALBUM targetId String token String @unique userId String? platform String? clickCount Int @default(0) createdAt DateTime @default(now()) // Relations user User? @relation(fields: [userId], references: [id], onDelete: SetNull) @@index([token]) @@index([targetId, type]) @@map("shares") } // RefreshToken model - for JWT refresh token rotation model RefreshToken { id String @id @default(uuid()) token String @unique userId String expiresAt DateTime isRevoked Boolean @default(false) createdAt DateTime @default(now()) // Relations user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([token]) @@index([userId]) @@map("refresh_tokens") } // Session model - for tracking active user sessions model Session { id String @id @default(uuid()) userId String token String @unique deviceInfo String? // JSON string ipAddress String? userAgent String? lastActivity DateTime? createdAt DateTime @default(now()) // Relations user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([token]) @@index([userId]) @@map("sessions") } // PlayHistory model - for tracking user playback history model PlayHistory { id String @id @default(uuid()) userId String songId String playedAt DateTime @default(now()) playedDuration Int? // seconds actually played completed Boolean @default(false) source String? // where playback was initiated // Relations user User @relation(fields: [userId], references: [id], onDelete: Cascade) song Song @relation(fields: [songId], references: [id], onDelete: Cascade) @@index([userId]) @@index([songId]) @@index([playedAt]) @@map("play_history") } // Queue model - for user playback queues model Queue { id String @id @default(uuid()) userId String @unique songIds String // JSON array of song IDs in order currentIndex Int @default(0) isShuffled Boolean @default(false) repeatMode String @default("none") // none, one, all createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@map("queues") } // UploadSession model - for chunked file uploads model UploadSession { id String @id @default(uuid()) userId String fileName String fileSize Int mimeType String chunkSize Int totalChunks Int uploadedChunks String? // JSON array of uploaded chunk numbers status String @default("pending") // pending, uploading, completed, failed, expired fileId String? metadata String? // JSON string for additional metadata createdAt DateTime @default(now()) expiresAt DateTime // Relations user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([status]) @@index([expiresAt]) @@map("upload_sessions") } // SearchIndex model - for full-text search indexing model SearchIndex { id String @id @default(uuid()) entityType String // song, album, artist, playlist entityId String title String content String? // full text content metadata String? // JSON string for additional searchable metadata createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([entityType, entityId]) @@index([title]) @@unique([entityType, entityId]) @@map("search_index") }