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") 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[] @@map("users") } model Device { id String @id @default(uuid()) @db.Uuid userId String @db.Uuid @map("user_id") platform DevicePlatform deviceName String @map("device_name") appVersion String @map("app_version") installTokenHash String @map("install_token_hash") 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[] 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 @db.Uuid @map("user_id") primaryAudioAssetId String? @unique @db.Uuid @map("primary_audio_asset_id") artworkAssetId String? @db.Uuid @map("artwork_asset_id") 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 @db.Uuid @map("user_id") trackId String? @db.Uuid @map("track_id") 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? @db.Uuid @map("source_device_id") 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 @db.Uuid @map("user_id") 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 @db.Uuid @map("user_id") deviceId String @db.Uuid @map("device_id") trackId String? @db.Uuid @map("track_id") audioAssetId String? @db.Uuid @map("audio_asset_id") 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 @db.Uuid @map("user_id") entityType EntityType @map("entity_type") entityId String @db.Uuid @map("entity_id") action EventAction 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) @@index([userId]) @@map("library_events") } model DeviceSyncCursor { deviceId String @id @db.Uuid @map("device_id") lastEventId BigInt @default(0) @map("last_event_id") lastFullSyncAt DateTime? @map("last_full_sync_at") updatedAt DateTime @updatedAt @map("updated_at") device Device @relation(fields: [deviceId], references: [id], onDelete: Cascade, onUpdate: Cascade) @@map("device_sync_cursors") } 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 }