355 lines
9.5 KiB
Plaintext
355 lines
9.5 KiB
Plaintext
// 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")
|
|
}
|