velody/backend/prisma/schema.prisma
2026-06-28 14:32:30 +02:00

403 lines
16 KiB
Plaintext

generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid()) @db.Uuid
slug String @unique
displayName String @map("display_name")
isDefault Boolean @default(false) @map("is_default")
accountKind UserAccountKind @default(LEGACY_DEFAULT) @map("account_kind")
accountStatus UserAccountStatus @default(ACTIVE) @map("account_status")
libraryNamespace String @unique @default(uuid()) @map("library_namespace") @db.Uuid
libraryCursor BigInt @default(0) @map("library_cursor")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
devices Device[]
tracks Track[]
audioAssets AudioAsset[]
artworkAssets ArtworkAsset[]
uploadSessions UploadSession[]
libraryEvents LibraryEvent[]
syncCursors DeviceSyncCursor[]
passwordCredential UserPasswordCredential?
oauthIdentities UserOAuthIdentity[]
emailVerificationTokens EmailVerificationToken[]
passwordResetTokens PasswordResetToken[]
deviceLinkHistory DeviceLinkHistory[]
outgoingTransfers OwnershipTransfer[] @relation("OwnershipTransferFromUser")
incomingTransfers OwnershipTransfer[] @relation("OwnershipTransferToUser")
initiatedTransfers OwnershipTransfer[] @relation("OwnershipTransferRequestedByUser")
accountSessions AccountSession[]
subscription UserSubscription?
entitlements UserEntitlement[]
@@map("users")
}
model Device {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
platform DevicePlatform
deviceName String @map("device_name")
appVersion String @map("app_version")
installTokenHash String @map("install_token_hash")
linkedAt DateTime? @map("linked_at")
relinkedAt DateTime? @map("relinked_at")
tokenHash String? @unique @map("token_hash")
tokenCreatedAt DateTime? @map("token_created_at")
tokenLastUsedAt DateTime? @map("token_last_used_at")
tokenRevokedAt DateTime? @map("token_revoked_at")
lastSeenAt DateTime @map("last_seen_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
uploadSessions UploadSession[]
syncCursor DeviceSyncCursor?
audioAssets AudioAsset[]
linkHistory DeviceLinkHistory[] @relation("DeviceLinkHistoryDevice")
linkedByHistory DeviceLinkHistory[] @relation("DeviceLinkHistoryLinkedByDevice")
requestedTransfers OwnershipTransfer[] @relation("OwnershipTransferRequestedByDevice")
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
@@index([userId])
@@map("devices")
}
model Track {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
primaryAudioAssetId String? @unique @map("primary_audio_asset_id") @db.Uuid
artworkAssetId String? @map("artwork_asset_id") @db.Uuid
title String
artist String
album String?
albumArtist String? @map("album_artist")
genre String?
discNumber Int? @map("disc_number")
trackNumber Int? @map("track_number")
year Int?
durationMs Int? @map("duration_ms")
status TrackStatus @default(ACTIVE)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
primaryAudioAsset AudioAsset? @relation("PrimaryAudioAsset", fields: [primaryAudioAssetId], references: [id], onDelete: SetNull, onUpdate: Cascade)
artworkAsset ArtworkAsset? @relation("TrackArtwork", fields: [artworkAssetId], references: [id], onDelete: SetNull, onUpdate: Cascade)
audioAssets AudioAsset[] @relation("TrackAudioAssets")
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
@@index([userId])
@@map("tracks")
}
model AudioAsset {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
trackId String? @map("track_id") @db.Uuid
sha256 String
storageKey String @unique @map("storage_key")
originalFilename String @map("original_filename")
mimeType String @map("mime_type")
fileExtension String @map("file_extension")
fileSizeBytes BigInt @map("file_size_bytes")
bitRateKbps Int? @map("bit_rate_kbps")
sampleRateHz Int? @map("sample_rate_hz")
channels Int?
durationMs Int? @map("duration_ms")
sourceDeviceId String? @map("source_device_id") @db.Uuid
createdAt DateTime @default(now()) @map("created_at")
track Track? @relation("TrackAudioAssets", fields: [trackId], references: [id], onDelete: SetNull, onUpdate: Cascade)
primaryForTrack Track? @relation("PrimaryAudioAsset")
sourceDevice Device? @relation(fields: [sourceDeviceId], references: [id], onDelete: SetNull, onUpdate: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
@@unique([userId, sha256])
@@index([userId])
@@map("audio_assets")
}
model ArtworkAsset {
userId String @map("user_id") @db.Uuid
id String @id @default(uuid()) @db.Uuid
sha256 String
storageKey String @unique @map("storage_key")
mimeType String @map("mime_type")
width Int?
height Int?
fileSizeBytes BigInt @map("file_size_bytes")
createdAt DateTime @default(now()) @map("created_at")
tracks Track[] @relation("TrackArtwork")
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
@@unique([userId, sha256])
@@index([userId])
@@map("artwork_assets")
}
model UploadSession {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
deviceId String @map("device_id") @db.Uuid
trackId String? @map("track_id") @db.Uuid
audioAssetId String? @map("audio_asset_id") @db.Uuid
expectedSha256 String @map("expected_sha256")
originalFilename String @map("original_filename")
expectedSizeBytes BigInt @map("expected_size_bytes")
receivedBytes BigInt @default(0) @map("received_bytes")
tempStoragePath String @map("temp_storage_path")
status UploadSessionStatus @default(PENDING)
completedAt DateTime? @map("completed_at")
finalizedAt DateTime? @map("finalized_at")
expiresAt DateTime @map("expires_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
device Device @relation(fields: [deviceId], references: [id], onDelete: Cascade, onUpdate: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
@@index([userId])
@@map("upload_sessions")
}
model LibraryEvent {
id BigInt @id @default(autoincrement())
userId String @map("user_id") @db.Uuid
cursor BigInt
entityType EntityType @map("entity_type")
entityId String @map("entity_id") @db.Uuid
action EventAction
payload Json
payloadVersion Int @default(1) @map("payload_version")
createdAt DateTime @default(now()) @map("created_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
@@unique([userId, cursor])
@@index([userId])
@@index([userId, cursor])
@@map("library_events")
}
model DeviceSyncCursor {
deviceId String @id @map("device_id") @db.Uuid
userId String @map("user_id") @db.Uuid
cursor BigInt @default(0)
updatedAt DateTime @updatedAt @map("updated_at")
device Device @relation(fields: [deviceId], references: [id], onDelete: Cascade, onUpdate: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
@@index([userId])
@@map("device_sync_cursors")
}
model UserPasswordCredential {
id String @id @default(uuid()) @db.Uuid
userId String @unique @map("user_id") @db.Uuid
passwordHash String @map("password_hash")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
@@map("user_password_credentials")
}
model UserOAuthIdentity {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
provider String
providerSubject String @map("provider_subject")
email String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
@@unique([provider, providerSubject])
@@index([userId])
@@map("user_oauth_identities")
}
model EmailVerificationToken {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
email String
tokenHash String @unique @map("token_hash")
expiresAt DateTime @map("expires_at")
consumedAt DateTime? @map("consumed_at")
createdAt DateTime @default(now()) @map("created_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
@@index([userId])
@@map("email_verification_tokens")
}
model PasswordResetToken {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
tokenHash String @unique @map("token_hash")
expiresAt DateTime @map("expires_at")
consumedAt DateTime? @map("consumed_at")
createdAt DateTime @default(now()) @map("created_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
@@index([userId])
@@map("password_reset_tokens")
}
model DeviceLinkHistory {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
deviceId String? @map("device_id") @db.Uuid
linkedByDeviceId String? @map("linked_by_device_id") @db.Uuid
linkMethod String @map("link_method")
createdAt DateTime @default(now()) @map("created_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
device Device? @relation("DeviceLinkHistoryDevice", fields: [deviceId], references: [id], onDelete: SetNull, onUpdate: Cascade)
linkedByDevice Device? @relation("DeviceLinkHistoryLinkedByDevice", fields: [linkedByDeviceId], references: [id], onDelete: SetNull, onUpdate: Cascade)
@@index([userId])
@@index([deviceId])
@@map("device_link_history")
}
model OwnershipTransfer {
id String @id @default(uuid()) @db.Uuid
fromUserId String @map("from_user_id") @db.Uuid
toUserId String? @map("to_user_id") @db.Uuid
requestedByUserId String? @map("requested_by_user_id") @db.Uuid
requestedByDeviceId String? @map("requested_by_device_id") @db.Uuid
mode String @default("COPY")
status String
summary Json? @map("summary")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
completedAt DateTime? @map("completed_at")
fromUser User @relation("OwnershipTransferFromUser", fields: [fromUserId], references: [id], onDelete: Cascade, onUpdate: Cascade)
toUser User? @relation("OwnershipTransferToUser", fields: [toUserId], references: [id], onDelete: SetNull, onUpdate: Cascade)
requestedByUser User? @relation("OwnershipTransferRequestedByUser", fields: [requestedByUserId], references: [id], onDelete: SetNull, onUpdate: Cascade)
requestedByDevice Device? @relation("OwnershipTransferRequestedByDevice", fields: [requestedByDeviceId], references: [id], onDelete: SetNull, onUpdate: Cascade)
@@index([fromUserId])
@@index([toUserId])
@@index([requestedByUserId])
@@map("ownership_transfers")
}
model AccountSession {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
refreshTokenHash String? @unique @map("refresh_token_hash")
status AccountSessionStatus @default(ACTIVE)
accessTokenVersion Int @default(1) @map("access_token_version")
expiresAt DateTime @map("expires_at")
lastRefreshedAt DateTime? @map("last_refreshed_at")
lastSeenAt DateTime? @map("last_seen_at")
revokedAt DateTime? @map("revoked_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
@@index([userId])
@@index([status, expiresAt])
@@map("account_sessions")
}
model UserSubscription {
id String @id @default(uuid()) @db.Uuid
userId String @unique @map("user_id") @db.Uuid
provider BillingProvider
providerCustomerId String? @unique @map("provider_customer_id")
providerSubscriptionId String? @unique @map("provider_subscription_id")
plan SubscriptionPlan
status SubscriptionStatus
currentPeriodStart DateTime @map("current_period_start")
currentPeriodEnd DateTime @map("current_period_end")
cancelAtPeriodEnd Boolean @default(false) @map("cancel_at_period_end")
cancelledAt DateTime? @map("cancelled_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
@@map("user_subscriptions")
}
model UserEntitlement {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
entitlementKey String @map("entitlement_key")
granted Boolean @default(false)
source String
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
@@unique([userId, entitlementKey])
@@map("user_entitlements")
}
enum SubscriptionStatus {
TRIAL
ACTIVE
PAST_DUE
CANCELLED
EXPIRED
}
enum SubscriptionPlan {
FREE
PRO
}
enum BillingProvider {
PADDLE
}
enum UserAccountKind {
LEGACY_DEFAULT
GUEST
REGISTERED
}
enum UserAccountStatus {
ACTIVE
LOCKED
DELETED
}
enum AccountSessionStatus {
ACTIVE
REVOKED
EXPIRED
}
enum DevicePlatform {
MACOS
IPHONE
}
enum TrackStatus {
ACTIVE
DELETED
}
enum UploadSessionStatus {
PENDING
READY_TO_UPLOAD
COMPLETED
FAILED
}
enum EntityType {
TRACK
AUDIO_ASSET
ARTWORK_ASSET
}
enum EventAction {
CREATED
UPDATED
DELETED
}