Add MP3 upload pipeline foundation

This commit is contained in:
diyaa
2026-05-28 17:43:00 +02:00
parent 799c07b068
commit 98edfa0faf
24 changed files with 2469 additions and 93 deletions
@@ -0,0 +1,135 @@
CREATE TABLE "users" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"slug" TEXT NOT NULL,
"display_name" TEXT NOT NULL,
"is_default" BOOLEAN NOT NULL DEFAULT false,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "users_slug_key" ON "users"("slug");
INSERT INTO "users" (
"id",
"slug",
"display_name",
"is_default",
"created_at",
"updated_at"
)
VALUES (
gen_random_uuid(),
'default-owner',
'Default Owner',
true,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
)
ON CONFLICT ("slug") DO NOTHING;
ALTER TABLE "artwork_assets"
ALTER COLUMN "id" SET DEFAULT gen_random_uuid();
ALTER TABLE "audio_assets"
ALTER COLUMN "id" SET DEFAULT gen_random_uuid(),
ADD COLUMN "user_id" UUID;
ALTER TABLE "device_sync_cursors"
ALTER COLUMN "updated_at" SET DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE "devices"
ALTER COLUMN "id" SET DEFAULT gen_random_uuid(),
ALTER COLUMN "updated_at" SET DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "user_id" UUID;
ALTER TABLE "library_events"
ADD COLUMN "user_id" UUID;
ALTER TABLE "tracks"
ALTER COLUMN "id" SET DEFAULT gen_random_uuid(),
ALTER COLUMN "updated_at" SET DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "user_id" UUID;
ALTER TABLE "upload_sessions"
ALTER COLUMN "id" SET DEFAULT gen_random_uuid(),
ALTER COLUMN "updated_at" SET DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "user_id" UUID,
ADD COLUMN "track_id" UUID,
ADD COLUMN "audio_asset_id" UUID,
ADD COLUMN "original_filename" TEXT,
ADD COLUMN "completed_at" TIMESTAMP(3),
ADD COLUMN "finalized_at" TIMESTAMP(3);
UPDATE "devices"
SET "user_id" = (SELECT "id" FROM "users" WHERE "slug" = 'default-owner')
WHERE "user_id" IS NULL;
UPDATE "tracks"
SET "user_id" = (SELECT "id" FROM "users" WHERE "slug" = 'default-owner')
WHERE "user_id" IS NULL;
UPDATE "audio_assets"
SET "user_id" = (SELECT "id" FROM "users" WHERE "slug" = 'default-owner')
WHERE "user_id" IS NULL;
UPDATE "upload_sessions"
SET
"user_id" = (SELECT "id" FROM "users" WHERE "slug" = 'default-owner'),
"original_filename" = COALESCE("original_filename", "expected_sha256" || '.mp3')
WHERE "user_id" IS NULL
OR "original_filename" IS NULL;
UPDATE "library_events"
SET "user_id" = (SELECT "id" FROM "users" WHERE "slug" = 'default-owner')
WHERE "user_id" IS NULL;
ALTER TABLE "audio_assets"
ALTER COLUMN "user_id" SET NOT NULL;
ALTER TABLE "devices"
ALTER COLUMN "user_id" SET NOT NULL;
ALTER TABLE "library_events"
ALTER COLUMN "user_id" SET NOT NULL;
ALTER TABLE "tracks"
ALTER COLUMN "user_id" SET NOT NULL;
ALTER TABLE "upload_sessions"
ALTER COLUMN "user_id" SET NOT NULL,
ALTER COLUMN "original_filename" SET NOT NULL;
DROP INDEX "audio_assets_sha256_key";
CREATE UNIQUE INDEX "audio_assets_user_id_sha256_key" ON "audio_assets"("user_id", "sha256");
CREATE INDEX "audio_assets_user_id_idx" ON "audio_assets"("user_id");
CREATE INDEX "devices_user_id_idx" ON "devices"("user_id");
CREATE INDEX "library_events_user_id_idx" ON "library_events"("user_id");
CREATE INDEX "tracks_user_id_idx" ON "tracks"("user_id");
CREATE INDEX "upload_sessions_user_id_idx" ON "upload_sessions"("user_id");
ALTER TABLE "audio_assets"
ADD CONSTRAINT "audio_assets_user_id_fkey"
FOREIGN KEY ("user_id") REFERENCES "users"("id")
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "devices"
ADD CONSTRAINT "devices_user_id_fkey"
FOREIGN KEY ("user_id") REFERENCES "users"("id")
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "library_events"
ADD CONSTRAINT "library_events_user_id_fkey"
FOREIGN KEY ("user_id") REFERENCES "users"("id")
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "tracks"
ADD CONSTRAINT "tracks_user_id_fkey"
FOREIGN KEY ("user_id") REFERENCES "users"("id")
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "upload_sessions"
ADD CONSTRAINT "upload_sessions_user_id_fkey"
FOREIGN KEY ("user_id") REFERENCES "users"("id")
ON DELETE CASCADE ON UPDATE CASCADE;
+38 -1
View File
@@ -7,8 +7,25 @@ datasource db {
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[]
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")
@@ -19,12 +36,15 @@ model Device {
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? @unique @db.Uuid @map("artwork_asset_id")
title String
@@ -43,14 +63,17 @@ model Track {
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 @unique
sha256 String
storageKey String @unique @map("storage_key")
originalFilename String @map("original_filename")
mimeType String @map("mime_type")
@@ -65,7 +88,10 @@ model AudioAsset {
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")
}
@@ -85,28 +111,39 @@ model ArtworkAsset {
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")
}