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 }