Phase 1: version branching + public share links

Add parentVersionId/branchLabel to versions, enabling git-style branching.
New /tree and /promote endpoints; VersionGraph (SVG) component as toggle
next to the existing list view. Upload dropzone accepts a parent for branch
uploads.

Add public share links: new share_links table, /api/v1/share router with
authenticated CRUD and a public /public/:token endpoint serving signed
stream/waveform URLs. Comments now allow guests (nullable userId, guestName)
so artists can leave timestamped feedback without an account. New
/listen/:token standalone page with password gate, optional download, and
guest comment form.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Robin Choice
2026-04-07 16:31:52 +02:00
parent e420ed198b
commit 4dc095463f
19 changed files with 2136 additions and 50 deletions

View File

@@ -0,0 +1,21 @@
CREATE TABLE "share_links" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"version_id" uuid NOT NULL,
"token" varchar(64) NOT NULL,
"created_by_id" uuid NOT NULL,
"expires_at" timestamp,
"allow_comments" boolean DEFAULT true NOT NULL,
"allow_download" boolean DEFAULT false NOT NULL,
"password_hash" varchar(255),
"created_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "share_links_token_unique" UNIQUE("token")
);
--> statement-breakpoint
ALTER TABLE "versions" DROP CONSTRAINT "versions_track_id_version_number_unique";--> statement-breakpoint
ALTER TABLE "comments" ALTER COLUMN "user_id" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "versions" ADD COLUMN "parent_version_id" uuid;--> statement-breakpoint
ALTER TABLE "versions" ADD COLUMN "branch_label" varchar(100);--> statement-breakpoint
ALTER TABLE "comments" ADD COLUMN "guest_name" varchar(100);--> statement-breakpoint
ALTER TABLE "share_links" ADD CONSTRAINT "share_links_version_id_versions_id_fk" FOREIGN KEY ("version_id") REFERENCES "public"."versions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "share_links" ADD CONSTRAINT "share_links_created_by_id_users_id_fk" FOREIGN KEY ("created_by_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "versions" ADD CONSTRAINT "versions_parent_version_id_versions_id_fk" FOREIGN KEY ("parent_version_id") REFERENCES "public"."versions"("id") ON DELETE set null ON UPDATE no action;

View File

@@ -0,0 +1,885 @@
{
"id": "7e1d45fa-02c2-43ae-96f3-d5c8367c15ed",
"prevId": "4e5be5fd-2fae-43d2-8273-5a372d714cc5",
"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
},
"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

@@ -8,6 +8,13 @@
"when": 1775122377765,
"tag": "0000_magenta_apocalypse",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1775571497577,
"tag": "0001_many_sir_ram",
"breakpoints": true
}
]
}

View File

@@ -1,4 +1,4 @@
import { pgTable, uuid, text, real, timestamp } from 'drizzle-orm/pg-core';
import { pgTable, uuid, varchar, text, real, timestamp } from 'drizzle-orm/pg-core';
import { users } from './users.js';
import { versions } from './tracks.js';
@@ -7,9 +7,8 @@ export const comments = pgTable('comments', {
versionId: uuid('version_id')
.references(() => versions.id, { onDelete: 'cascade' })
.notNull(),
userId: uuid('user_id')
.references(() => users.id)
.notNull(),
userId: uuid('user_id').references(() => users.id),
guestName: varchar('guest_name', { length: 100 }),
body: text('body').notNull(),
timestampSeconds: real('timestamp_seconds'),
parentId: uuid('parent_id'),

View File

@@ -3,3 +3,4 @@ export * from './auth.js';
export * from './projects.js';
export * from './tracks.js';
export * from './comments.js';
export * from './shareLinks.js';

View File

@@ -0,0 +1,19 @@
import { pgTable, uuid, varchar, boolean, timestamp } from 'drizzle-orm/pg-core';
import { users } from './users.js';
import { versions } from './tracks.js';
export const shareLinks = pgTable('share_links', {
id: uuid('id').defaultRandom().primaryKey(),
versionId: uuid('version_id')
.references(() => versions.id, { onDelete: 'cascade' })
.notNull(),
token: varchar('token', { length: 64 }).notNull().unique(),
createdById: uuid('created_by_id')
.references(() => users.id)
.notNull(),
expiresAt: timestamp('expires_at'),
allowComments: boolean('allow_comments').default(true).notNull(),
allowDownload: boolean('allow_download').default(false).notNull(),
passwordHash: varchar('password_hash', { length: 255 }),
createdAt: timestamp('created_at').defaultNow().notNull(),
});

View File

@@ -8,8 +8,8 @@ import {
bigint,
real,
timestamp,
unique,
} from 'drizzle-orm/pg-core';
import type { AnyPgColumn } from 'drizzle-orm/pg-core';
import { users } from './users.js';
import { projects } from './projects.js';
@@ -36,33 +36,34 @@ export const tracks = pgTable('tracks', {
updatedAt: timestamp('updated_at').defaultNow().notNull(),
});
export const versions = pgTable(
'versions',
{
id: uuid('id').defaultRandom().primaryKey(),
trackId: uuid('track_id')
.references(() => tracks.id, { onDelete: 'cascade' })
.notNull(),
versionNumber: integer('version_number').notNull(),
label: varchar('label', { length: 100 }),
notes: text('notes'),
status: versionStatusEnum('status').default('uploaded').notNull(),
export const versions = pgTable('versions', {
id: uuid('id').defaultRandom().primaryKey(),
trackId: uuid('track_id')
.references(() => tracks.id, { onDelete: 'cascade' })
.notNull(),
versionNumber: integer('version_number').notNull(),
label: varchar('label', { length: 100 }),
notes: text('notes'),
status: versionStatusEnum('status').default('uploaded').notNull(),
originalFileName: varchar('original_file_name', { length: 500 }).notNull(),
mimeType: varchar('mime_type', { length: 100 }).notNull(),
fileSize: bigint('file_size', { mode: 'number' }).notNull(),
duration: real('duration'),
sampleRate: integer('sample_rate'),
bitDepth: integer('bit_depth'),
parentVersionId: uuid('parent_version_id').references((): AnyPgColumn => versions.id, {
onDelete: 'set null',
}),
branchLabel: varchar('branch_label', { length: 100 }),
originalFileKey: text('original_file_key').notNull(),
streamFileKey: text('stream_file_key'),
waveformDataKey: text('waveform_data_key'),
originalFileName: varchar('original_file_name', { length: 500 }).notNull(),
mimeType: varchar('mime_type', { length: 100 }).notNull(),
fileSize: bigint('file_size', { mode: 'number' }).notNull(),
duration: real('duration'),
sampleRate: integer('sample_rate'),
bitDepth: integer('bit_depth'),
createdById: uuid('created_by_id')
.references(() => users.id)
.notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
},
(table) => [unique().on(table.trackId, table.versionNumber)],
);
originalFileKey: text('original_file_key').notNull(),
streamFileKey: text('stream_file_key'),
waveformDataKey: text('waveform_data_key'),
createdById: uuid('created_by_id')
.references(() => users.id)
.notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});

View File

@@ -24,9 +24,27 @@ export const createVersionSchema = z.object({
originalFileName: z.string().min(1),
mimeType: z.string().min(1),
fileSize: z.number().int().positive(),
parentVersionId: z.string().uuid().optional(),
branchLabel: z.string().max(100).optional(),
});
export const createShareLinkSchema = z.object({
expiresAt: z.string().datetime().optional(),
allowComments: z.boolean().optional(),
allowDownload: z.boolean().optional(),
password: z.string().min(1).max(255).optional(),
});
export const guestCommentSchema = z.object({
body: z.string().min(1).max(5000),
timestampSeconds: z.number().nonnegative().optional(),
parentId: z.string().uuid().optional(),
guestName: z.string().min(1).max(100),
});
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 CreateShareLinkInput = z.infer<typeof createShareLinkSchema>;
export type GuestCommentInput = z.infer<typeof guestCommentSchema>;