version 1.0.0
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
|
||||
CREATE TYPE "DevicePlatform" AS ENUM ('MACOS', 'IPHONE');
|
||||
CREATE TYPE "TrackStatus" AS ENUM ('ACTIVE', 'DELETED');
|
||||
CREATE TYPE "UploadSessionStatus" AS ENUM ('PENDING', 'READY_TO_UPLOAD', 'COMPLETED', 'FAILED');
|
||||
CREATE TYPE "EntityType" AS ENUM ('TRACK', 'AUDIO_ASSET', 'ARTWORK_ASSET');
|
||||
CREATE TYPE "EventAction" AS ENUM ('CREATED', 'UPDATED', 'DELETED');
|
||||
|
||||
CREATE TABLE "devices" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"platform" "DevicePlatform" NOT NULL,
|
||||
"device_name" TEXT NOT NULL,
|
||||
"app_version" TEXT NOT NULL,
|
||||
"install_token_hash" TEXT NOT NULL,
|
||||
"last_seen_at" TIMESTAMP(3) NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "devices_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "tracks" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"primary_audio_asset_id" UUID,
|
||||
"artwork_asset_id" UUID,
|
||||
"title" TEXT NOT NULL,
|
||||
"artist" TEXT NOT NULL,
|
||||
"album" TEXT,
|
||||
"album_artist" TEXT,
|
||||
"genre" TEXT,
|
||||
"disc_number" INTEGER,
|
||||
"track_number" INTEGER,
|
||||
"year" INTEGER,
|
||||
"duration_ms" INTEGER,
|
||||
"status" "TrackStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted_at" TIMESTAMP(3),
|
||||
CONSTRAINT "tracks_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "audio_assets" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"track_id" UUID,
|
||||
"sha256" TEXT NOT NULL,
|
||||
"storage_key" TEXT NOT NULL,
|
||||
"original_filename" TEXT NOT NULL,
|
||||
"mime_type" TEXT NOT NULL,
|
||||
"file_extension" TEXT NOT NULL,
|
||||
"file_size_bytes" BIGINT NOT NULL,
|
||||
"bit_rate_kbps" INTEGER,
|
||||
"sample_rate_hz" INTEGER,
|
||||
"channels" INTEGER,
|
||||
"duration_ms" INTEGER,
|
||||
"source_device_id" UUID,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "audio_assets_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "artwork_assets" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"sha256" TEXT NOT NULL,
|
||||
"storage_key" TEXT NOT NULL,
|
||||
"mime_type" TEXT NOT NULL,
|
||||
"width" INTEGER,
|
||||
"height" INTEGER,
|
||||
"file_size_bytes" BIGINT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "artwork_assets_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "upload_sessions" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"device_id" UUID NOT NULL,
|
||||
"expected_sha256" TEXT NOT NULL,
|
||||
"expected_size_bytes" BIGINT NOT NULL,
|
||||
"received_bytes" BIGINT NOT NULL DEFAULT 0,
|
||||
"temp_storage_path" TEXT NOT NULL,
|
||||
"status" "UploadSessionStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"expires_at" TIMESTAMP(3) NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "upload_sessions_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "library_events" (
|
||||
"id" BIGSERIAL NOT NULL,
|
||||
"entity_type" "EntityType" NOT NULL,
|
||||
"entity_id" UUID NOT NULL,
|
||||
"action" "EventAction" NOT NULL,
|
||||
"payload_version" INTEGER NOT NULL DEFAULT 1,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "library_events_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "device_sync_cursors" (
|
||||
"device_id" UUID NOT NULL,
|
||||
"last_event_id" BIGINT NOT NULL DEFAULT 0,
|
||||
"last_full_sync_at" TIMESTAMP(3),
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "device_sync_cursors_pkey" PRIMARY KEY ("device_id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "tracks_primary_audio_asset_id_key" ON "tracks"("primary_audio_asset_id");
|
||||
CREATE UNIQUE INDEX "tracks_artwork_asset_id_key" ON "tracks"("artwork_asset_id");
|
||||
CREATE UNIQUE INDEX "audio_assets_sha256_key" ON "audio_assets"("sha256");
|
||||
CREATE UNIQUE INDEX "audio_assets_storage_key" ON "audio_assets"("storage_key");
|
||||
CREATE UNIQUE INDEX "artwork_assets_sha256_key" ON "artwork_assets"("sha256");
|
||||
CREATE UNIQUE INDEX "artwork_assets_storage_key" ON "artwork_assets"("storage_key");
|
||||
|
||||
ALTER TABLE "audio_assets"
|
||||
ADD CONSTRAINT "audio_assets_track_id_fkey"
|
||||
FOREIGN KEY ("track_id") REFERENCES "tracks"("id")
|
||||
ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "audio_assets"
|
||||
ADD CONSTRAINT "audio_assets_source_device_id_fkey"
|
||||
FOREIGN KEY ("source_device_id") REFERENCES "devices"("id")
|
||||
ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "tracks"
|
||||
ADD CONSTRAINT "tracks_primary_audio_asset_id_fkey"
|
||||
FOREIGN KEY ("primary_audio_asset_id") REFERENCES "audio_assets"("id")
|
||||
ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "tracks"
|
||||
ADD CONSTRAINT "tracks_artwork_asset_id_fkey"
|
||||
FOREIGN KEY ("artwork_asset_id") REFERENCES "artwork_assets"("id")
|
||||
ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "upload_sessions"
|
||||
ADD CONSTRAINT "upload_sessions_device_id_fkey"
|
||||
FOREIGN KEY ("device_id") REFERENCES "devices"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "device_sync_cursors"
|
||||
ADD CONSTRAINT "device_sync_cursors_device_id_fkey"
|
||||
FOREIGN KEY ("device_id") REFERENCES "devices"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,2 @@
|
||||
# Please do not edit this file manually
|
||||
provider = "postgresql"
|
||||
@@ -0,0 +1,150 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Device {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
platform DevicePlatform
|
||||
deviceName String @map("device_name")
|
||||
appVersion String @map("app_version")
|
||||
installTokenHash String @map("install_token_hash")
|
||||
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[]
|
||||
|
||||
@@map("devices")
|
||||
}
|
||||
|
||||
model Track {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
primaryAudioAssetId String? @unique @db.Uuid @map("primary_audio_asset_id")
|
||||
artworkAssetId String? @unique @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")
|
||||
|
||||
@@map("tracks")
|
||||
}
|
||||
|
||||
model AudioAsset {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
trackId String? @db.Uuid @map("track_id")
|
||||
sha256 String @unique
|
||||
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)
|
||||
|
||||
@@map("audio_assets")
|
||||
}
|
||||
|
||||
model ArtworkAsset {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
sha256 String @unique
|
||||
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")
|
||||
track Track? @relation("TrackArtwork")
|
||||
|
||||
@@map("artwork_assets")
|
||||
}
|
||||
|
||||
model UploadSession {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
deviceId String @db.Uuid @map("device_id")
|
||||
expectedSha256 String @map("expected_sha256")
|
||||
expectedSizeBytes BigInt @map("expected_size_bytes")
|
||||
receivedBytes BigInt @default(0) @map("received_bytes")
|
||||
tempStoragePath String @map("temp_storage_path")
|
||||
status UploadSessionStatus @default(PENDING)
|
||||
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)
|
||||
|
||||
@@map("upload_sessions")
|
||||
}
|
||||
|
||||
model LibraryEvent {
|
||||
id BigInt @id @default(autoincrement())
|
||||
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")
|
||||
|
||||
@@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
|
||||
}
|
||||
Reference in New Issue
Block a user