Full MVP: workspace layout, visual refresh, PWA, production deploy

Major changes since initial commit:

Schema: version branching (parentVersionId, branchLabel), share links,
guest comments, track status enum (sketch/in_progress/final/released),
track sections, cover art for projects and tracks.

API: 29+ endpoints — auth, projects, tracks, versions, comments, share
links (public + management), uploads (cover), activity feed, onboarding
demo seed. Email templates in German with brand styling.

Web: SvelteKit 5 workspace layout with persistent sidebar, breadcrumb
top-bar, collapsible right panel. SoundCloud-style waveform player with
round play button, avatar comment markers, keyboard shortcuts (Space/JKL/C).
Full German UI. Cover art with gradient fallback. Track status pills.
Activity feed dashboard. Welcome modal with demo-seed trigger. Landing
page with 7-section scroll layout. Login on /login. Public /listen/:token
page for guest feedback.

Visual: Inter Variable font, Magenta→Orange gradient accent, warm dark
neutrals, Lucide-style inline SVG icon set, spring animations on modals,
glass-effect toasts, responsive from 360px to 2560px+.

PWA: manifest, service worker, icons, iOS/Android installable.

Production: adapter-node, server-side API proxy hook, docker-compose with
Postgres + MinIO + auto-migration + health checks. Env example included.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Robin Choice
2026-04-10 11:47:48 +02:00
parent 4dc095463f
commit 8bf72c2482
78 changed files with 8216 additions and 1386 deletions

View File

@@ -0,0 +1 @@
ALTER TABLE "tracks" ADD COLUMN "cover_image_url" text;

View File

@@ -0,0 +1,3 @@
CREATE TYPE "public"."track_status" AS ENUM('sketch', 'in_progress', 'final', 'released');--> statement-breakpoint
ALTER TABLE "tracks" ADD COLUMN "status" "track_status" DEFAULT 'in_progress' NOT NULL;--> statement-breakpoint
ALTER TABLE "tracks" ADD COLUMN "section" varchar(100);

View File

@@ -0,0 +1,891 @@
{
"id": "3065b19e-d647-4c33-90fb-2a4ae29e9e44",
"prevId": "7e1d45fa-02c2-43ae-96f3-d5c8367c15ed",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_email_unique": {
"name": "users_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.magic_links": {
"name": "magic_links",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"token": {
"name": "token",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"used_at": {
"name": "used_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"magic_links_token_unique": {
"name": "magic_links_token_unique",
"nullsNotDistinct": false,
"columns": [
"token"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.sessions": {
"name": "sessions",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"token_hash": {
"name": "token_hash",
"type": "varchar(128)",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"sessions_user_id_users_id_fk": {
"name": "sessions_user_id_users_id_fk",
"tableFrom": "sessions",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"sessions_token_hash_unique": {
"name": "sessions_token_hash_unique",
"nullsNotDistinct": false,
"columns": [
"token_hash"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.project_members": {
"name": "project_members",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"project_id": {
"name": "project_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"role": {
"name": "role",
"type": "project_role",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
},
"can_upload": {
"name": "can_upload",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"can_comment": {
"name": "can_comment",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"can_approve": {
"name": "can_approve",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"invited_at": {
"name": "invited_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"project_members_project_id_projects_id_fk": {
"name": "project_members_project_id_projects_id_fk",
"tableFrom": "project_members",
"tableTo": "projects",
"columnsFrom": [
"project_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"project_members_user_id_users_id_fk": {
"name": "project_members_user_id_users_id_fk",
"tableFrom": "project_members",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"project_members_project_id_user_id_unique": {
"name": "project_members_project_id_user_id_unique",
"nullsNotDistinct": false,
"columns": [
"project_id",
"user_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.projects": {
"name": "projects",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"cover_image_url": {
"name": "cover_image_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_by_id": {
"name": "created_by_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"is_archived": {
"name": "is_archived",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"projects_created_by_id_users_id_fk": {
"name": "projects_created_by_id_users_id_fk",
"tableFrom": "projects",
"tableTo": "users",
"columnsFrom": [
"created_by_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.tracks": {
"name": "tracks",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"project_id": {
"name": "project_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"cover_image_url": {
"name": "cover_image_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"sort_order": {
"name": "sort_order",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
},
"created_by_id": {
"name": "created_by_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"tracks_project_id_projects_id_fk": {
"name": "tracks_project_id_projects_id_fk",
"tableFrom": "tracks",
"tableTo": "projects",
"columnsFrom": [
"project_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"tracks_created_by_id_users_id_fk": {
"name": "tracks_created_by_id_users_id_fk",
"tableFrom": "tracks",
"tableTo": "users",
"columnsFrom": [
"created_by_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.versions": {
"name": "versions",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"track_id": {
"name": "track_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"version_number": {
"name": "version_number",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"label": {
"name": "label",
"type": "varchar(100)",
"primaryKey": false,
"notNull": false
},
"notes": {
"name": "notes",
"type": "text",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "version_status",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'uploaded'"
},
"parent_version_id": {
"name": "parent_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"branch_label": {
"name": "branch_label",
"type": "varchar(100)",
"primaryKey": false,
"notNull": false
},
"original_file_name": {
"name": "original_file_name",
"type": "varchar(500)",
"primaryKey": false,
"notNull": true
},
"mime_type": {
"name": "mime_type",
"type": "varchar(100)",
"primaryKey": false,
"notNull": true
},
"file_size": {
"name": "file_size",
"type": "bigint",
"primaryKey": false,
"notNull": true
},
"duration": {
"name": "duration",
"type": "real",
"primaryKey": false,
"notNull": false
},
"sample_rate": {
"name": "sample_rate",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"bit_depth": {
"name": "bit_depth",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"original_file_key": {
"name": "original_file_key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"stream_file_key": {
"name": "stream_file_key",
"type": "text",
"primaryKey": false,
"notNull": false
},
"waveform_data_key": {
"name": "waveform_data_key",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_by_id": {
"name": "created_by_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"versions_track_id_tracks_id_fk": {
"name": "versions_track_id_tracks_id_fk",
"tableFrom": "versions",
"tableTo": "tracks",
"columnsFrom": [
"track_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"versions_parent_version_id_versions_id_fk": {
"name": "versions_parent_version_id_versions_id_fk",
"tableFrom": "versions",
"tableTo": "versions",
"columnsFrom": [
"parent_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"versions_created_by_id_users_id_fk": {
"name": "versions_created_by_id_users_id_fk",
"tableFrom": "versions",
"tableTo": "users",
"columnsFrom": [
"created_by_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.comments": {
"name": "comments",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"version_id": {
"name": "version_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"guest_name": {
"name": "guest_name",
"type": "varchar(100)",
"primaryKey": false,
"notNull": false
},
"body": {
"name": "body",
"type": "text",
"primaryKey": false,
"notNull": true
},
"timestamp_seconds": {
"name": "timestamp_seconds",
"type": "real",
"primaryKey": false,
"notNull": false
},
"parent_id": {
"name": "parent_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"resolved_at": {
"name": "resolved_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"comments_version_id_versions_id_fk": {
"name": "comments_version_id_versions_id_fk",
"tableFrom": "comments",
"tableTo": "versions",
"columnsFrom": [
"version_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"comments_user_id_users_id_fk": {
"name": "comments_user_id_users_id_fk",
"tableFrom": "comments",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.share_links": {
"name": "share_links",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"version_id": {
"name": "version_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"token": {
"name": "token",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"created_by_id": {
"name": "created_by_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"allow_comments": {
"name": "allow_comments",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"allow_download": {
"name": "allow_download",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"password_hash": {
"name": "password_hash",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"share_links_version_id_versions_id_fk": {
"name": "share_links_version_id_versions_id_fk",
"tableFrom": "share_links",
"tableTo": "versions",
"columnsFrom": [
"version_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"share_links_created_by_id_users_id_fk": {
"name": "share_links_created_by_id_users_id_fk",
"tableFrom": "share_links",
"tableTo": "users",
"columnsFrom": [
"created_by_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"share_links_token_unique": {
"name": "share_links_token_unique",
"nullsNotDistinct": false,
"columns": [
"token"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {
"public.project_role": {
"name": "project_role",
"schema": "public",
"values": [
"owner",
"recording_engineer",
"mixing_engineer",
"mastering_engineer",
"artist",
"label",
"management",
"viewer"
]
},
"public.version_status": {
"name": "version_status",
"schema": "public",
"values": [
"uploaded",
"processing",
"ready",
"approved",
"rejected"
]
}
},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,915 @@
{
"id": "c17d41fa-dca4-4e1a-96c0-e3d574ce67e3",
"prevId": "3065b19e-d647-4c33-90fb-2a4ae29e9e44",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_email_unique": {
"name": "users_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.magic_links": {
"name": "magic_links",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"token": {
"name": "token",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"used_at": {
"name": "used_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"magic_links_token_unique": {
"name": "magic_links_token_unique",
"nullsNotDistinct": false,
"columns": [
"token"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.sessions": {
"name": "sessions",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"token_hash": {
"name": "token_hash",
"type": "varchar(128)",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"sessions_user_id_users_id_fk": {
"name": "sessions_user_id_users_id_fk",
"tableFrom": "sessions",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"sessions_token_hash_unique": {
"name": "sessions_token_hash_unique",
"nullsNotDistinct": false,
"columns": [
"token_hash"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.project_members": {
"name": "project_members",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"project_id": {
"name": "project_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"role": {
"name": "role",
"type": "project_role",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
},
"can_upload": {
"name": "can_upload",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"can_comment": {
"name": "can_comment",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"can_approve": {
"name": "can_approve",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"invited_at": {
"name": "invited_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"project_members_project_id_projects_id_fk": {
"name": "project_members_project_id_projects_id_fk",
"tableFrom": "project_members",
"tableTo": "projects",
"columnsFrom": [
"project_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"project_members_user_id_users_id_fk": {
"name": "project_members_user_id_users_id_fk",
"tableFrom": "project_members",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"project_members_project_id_user_id_unique": {
"name": "project_members_project_id_user_id_unique",
"nullsNotDistinct": false,
"columns": [
"project_id",
"user_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.projects": {
"name": "projects",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"cover_image_url": {
"name": "cover_image_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_by_id": {
"name": "created_by_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"is_archived": {
"name": "is_archived",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"projects_created_by_id_users_id_fk": {
"name": "projects_created_by_id_users_id_fk",
"tableFrom": "projects",
"tableTo": "users",
"columnsFrom": [
"created_by_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.tracks": {
"name": "tracks",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"project_id": {
"name": "project_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"cover_image_url": {
"name": "cover_image_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "track_status",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'in_progress'"
},
"section": {
"name": "section",
"type": "varchar(100)",
"primaryKey": false,
"notNull": false
},
"sort_order": {
"name": "sort_order",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
},
"created_by_id": {
"name": "created_by_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"tracks_project_id_projects_id_fk": {
"name": "tracks_project_id_projects_id_fk",
"tableFrom": "tracks",
"tableTo": "projects",
"columnsFrom": [
"project_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"tracks_created_by_id_users_id_fk": {
"name": "tracks_created_by_id_users_id_fk",
"tableFrom": "tracks",
"tableTo": "users",
"columnsFrom": [
"created_by_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.versions": {
"name": "versions",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"track_id": {
"name": "track_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"version_number": {
"name": "version_number",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"label": {
"name": "label",
"type": "varchar(100)",
"primaryKey": false,
"notNull": false
},
"notes": {
"name": "notes",
"type": "text",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "version_status",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'uploaded'"
},
"parent_version_id": {
"name": "parent_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"branch_label": {
"name": "branch_label",
"type": "varchar(100)",
"primaryKey": false,
"notNull": false
},
"original_file_name": {
"name": "original_file_name",
"type": "varchar(500)",
"primaryKey": false,
"notNull": true
},
"mime_type": {
"name": "mime_type",
"type": "varchar(100)",
"primaryKey": false,
"notNull": true
},
"file_size": {
"name": "file_size",
"type": "bigint",
"primaryKey": false,
"notNull": true
},
"duration": {
"name": "duration",
"type": "real",
"primaryKey": false,
"notNull": false
},
"sample_rate": {
"name": "sample_rate",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"bit_depth": {
"name": "bit_depth",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"original_file_key": {
"name": "original_file_key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"stream_file_key": {
"name": "stream_file_key",
"type": "text",
"primaryKey": false,
"notNull": false
},
"waveform_data_key": {
"name": "waveform_data_key",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_by_id": {
"name": "created_by_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"versions_track_id_tracks_id_fk": {
"name": "versions_track_id_tracks_id_fk",
"tableFrom": "versions",
"tableTo": "tracks",
"columnsFrom": [
"track_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"versions_parent_version_id_versions_id_fk": {
"name": "versions_parent_version_id_versions_id_fk",
"tableFrom": "versions",
"tableTo": "versions",
"columnsFrom": [
"parent_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"versions_created_by_id_users_id_fk": {
"name": "versions_created_by_id_users_id_fk",
"tableFrom": "versions",
"tableTo": "users",
"columnsFrom": [
"created_by_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.comments": {
"name": "comments",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"version_id": {
"name": "version_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"guest_name": {
"name": "guest_name",
"type": "varchar(100)",
"primaryKey": false,
"notNull": false
},
"body": {
"name": "body",
"type": "text",
"primaryKey": false,
"notNull": true
},
"timestamp_seconds": {
"name": "timestamp_seconds",
"type": "real",
"primaryKey": false,
"notNull": false
},
"parent_id": {
"name": "parent_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"resolved_at": {
"name": "resolved_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"comments_version_id_versions_id_fk": {
"name": "comments_version_id_versions_id_fk",
"tableFrom": "comments",
"tableTo": "versions",
"columnsFrom": [
"version_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"comments_user_id_users_id_fk": {
"name": "comments_user_id_users_id_fk",
"tableFrom": "comments",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.share_links": {
"name": "share_links",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"version_id": {
"name": "version_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"token": {
"name": "token",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"created_by_id": {
"name": "created_by_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"allow_comments": {
"name": "allow_comments",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"allow_download": {
"name": "allow_download",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"password_hash": {
"name": "password_hash",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"share_links_version_id_versions_id_fk": {
"name": "share_links_version_id_versions_id_fk",
"tableFrom": "share_links",
"tableTo": "versions",
"columnsFrom": [
"version_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"share_links_created_by_id_users_id_fk": {
"name": "share_links_created_by_id_users_id_fk",
"tableFrom": "share_links",
"tableTo": "users",
"columnsFrom": [
"created_by_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"share_links_token_unique": {
"name": "share_links_token_unique",
"nullsNotDistinct": false,
"columns": [
"token"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {
"public.project_role": {
"name": "project_role",
"schema": "public",
"values": [
"owner",
"recording_engineer",
"mixing_engineer",
"mastering_engineer",
"artist",
"label",
"management",
"viewer"
]
},
"public.track_status": {
"name": "track_status",
"schema": "public",
"values": [
"sketch",
"in_progress",
"final",
"released"
]
},
"public.version_status": {
"name": "version_status",
"schema": "public",
"values": [
"uploaded",
"processing",
"ready",
"approved",
"rejected"
]
}
},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -15,6 +15,20 @@
"when": 1775571497577,
"tag": "0001_many_sir_ram",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1775655179619,
"tag": "0002_loud_masque",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1775723994470,
"tag": "0003_huge_mystique",
"breakpoints": true
}
]
}

View File

@@ -21,6 +21,13 @@ export const versionStatusEnum = pgEnum('version_status', [
'rejected',
]);
export const trackStatusEnum = pgEnum('track_status', [
'sketch',
'in_progress',
'final',
'released',
]);
export const tracks = pgTable('tracks', {
id: uuid('id').defaultRandom().primaryKey(),
projectId: uuid('project_id')
@@ -28,6 +35,9 @@ export const tracks = pgTable('tracks', {
.notNull(),
name: varchar('name', { length: 255 }).notNull(),
description: text('description'),
coverImageUrl: text('cover_image_url'),
status: trackStatusEnum('status').default('in_progress').notNull(),
section: varchar('section', { length: 100 }),
sortOrder: integer('sort_order').default(0).notNull(),
createdById: uuid('created_by_id')
.references(() => users.id)

View File

@@ -12,14 +12,14 @@ export const PROJECT_ROLES = [
export type ProjectRole = (typeof PROJECT_ROLES)[number];
export const ROLE_LABELS: Record<ProjectRole, string> = {
owner: 'Owner',
recording_engineer: 'Recording Engineer',
mixing_engineer: 'Mixing Engineer',
mastering_engineer: 'Mastering Engineer',
owner: 'Besitzer',
recording_engineer: 'Aufnahme',
mixing_engineer: 'Mixing',
mastering_engineer: 'Mastering',
artist: 'Artist',
label: 'Label',
management: 'Management',
viewer: 'Viewer',
viewer: 'Nur Zuhören',
};
export const ENGINEER_ROLES: ProjectRole[] = [

View File

@@ -9,6 +9,7 @@ export const createProjectSchema = z.object({
export const updateProjectSchema = z.object({
name: z.string().min(1).max(255).optional(),
description: z.string().max(2000).optional(),
coverImageUrl: z.string().nullable().optional(),
});
export const inviteMemberSchema = z.object({

View File

@@ -6,9 +6,28 @@ export const createTrackSchema = z.object({
description: z.string().max(2000).optional(),
});
export const TRACK_STATUSES = ['sketch', 'in_progress', 'final', 'released'] as const;
export type TrackStatus = (typeof TRACK_STATUSES)[number];
export const TRACK_STATUS_LABELS: Record<TrackStatus, string> = {
sketch: 'Skizze',
in_progress: 'In Arbeit',
final: 'Final',
released: 'Veröffentlicht',
};
export const updateTrackSchema = z.object({
name: z.string().min(1).max(255).optional(),
description: z.string().max(2000).optional(),
coverImageUrl: z.string().nullable().optional(),
status: z.enum(TRACK_STATUSES).optional(),
section: z.string().max(100).nullable().optional(),
});
export const coverUploadSchema = z.object({
fileName: z.string().min(1).max(200),
mimeType: z.enum(['image/jpeg', 'image/png', 'image/webp']),
fileSize: z.number().int().positive().max(2 * 1024 * 1024),
});
export const requestUploadUrlSchema = z.object({
@@ -28,6 +47,12 @@ export const createVersionSchema = z.object({
branchLabel: z.string().max(100).optional(),
});
export const updateVersionSchema = z.object({
label: z.string().max(100).nullable().optional(),
notes: z.string().max(2000).nullable().optional(),
branchLabel: z.string().max(100).nullable().optional(),
});
export const createShareLinkSchema = z.object({
expiresAt: z.string().datetime().optional(),
allowComments: z.boolean().optional(),
@@ -46,5 +71,7 @@ export type CreateTrackInput = z.infer<typeof createTrackSchema>;
export type UpdateTrackInput = z.infer<typeof updateTrackSchema>;
export type RequestUploadUrlInput = z.infer<typeof requestUploadUrlSchema>;
export type CreateVersionInput = z.infer<typeof createVersionSchema>;
export type UpdateVersionInput = z.infer<typeof updateVersionSchema>;
export type CreateShareLinkInput = z.infer<typeof createShareLinkSchema>;
export type CoverUploadInput = z.infer<typeof coverUploadSchema>;
export type GuestCommentInput = z.infer<typeof guestCommentSchema>;