Initial commit: Music Hub collaboration platform
Full-stack music production collaboration tool with: - SvelteKit frontend with Design System (CSS vars, 8 shared components) - Hono API with auth, projects, tracks, versions, comments - PostgreSQL + Drizzle ORM (8 tables, roles, permissions) - S3-compatible storage with presigned upload URLs - wavesurfer.js audio player with waveform visualization - A/B version comparison with synchronized playback - Timestamped comments with threading and resolve workflow - Magic Link authentication with Resend email integration - Background audio processing (ffmpeg transcode + waveform peaks) - Role-based access control (Owner, Engineers, Artist, Label, Management, Viewer) - Toast notifications, skeleton loading, responsive layout - Docker deployment setup (API + Web + Postgres) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
10
packages/db/drizzle.config.ts
Normal file
10
packages/db/drizzle.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from 'drizzle-kit';
|
||||
|
||||
export default defineConfig({
|
||||
schema: './src/schema/index.ts',
|
||||
out: './src/migrations',
|
||||
dialect: 'postgresql',
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL!,
|
||||
},
|
||||
});
|
||||
21
packages/db/package.json
Normal file
21
packages/db/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@music-hub/db",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:seed": "bun run seed.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@music-hub/shared": "workspace:*",
|
||||
"drizzle-orm": "^0.44",
|
||||
"postgres": "^3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"drizzle-kit": "^0.31"
|
||||
}
|
||||
}
|
||||
12
packages/db/src/index.ts
Normal file
12
packages/db/src/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { drizzle } from 'drizzle-orm/postgres-js';
|
||||
import postgres from 'postgres';
|
||||
import * as schema from './schema/index.js';
|
||||
|
||||
export function createDb(connectionString: string) {
|
||||
const client = postgres(connectionString);
|
||||
return drizzle(client, { schema });
|
||||
}
|
||||
|
||||
export type Database = ReturnType<typeof createDb>;
|
||||
|
||||
export * from './schema/index.js';
|
||||
108
packages/db/src/migrations/0000_magenta_apocalypse.sql
Normal file
108
packages/db/src/migrations/0000_magenta_apocalypse.sql
Normal file
@@ -0,0 +1,108 @@
|
||||
CREATE TYPE "public"."project_role" AS ENUM('owner', 'recording_engineer', 'mixing_engineer', 'mastering_engineer', 'artist', 'label', 'management', 'viewer');--> statement-breakpoint
|
||||
CREATE TYPE "public"."version_status" AS ENUM('uploaded', 'processing', 'ready', 'approved', 'rejected');--> statement-breakpoint
|
||||
CREATE TABLE "users" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"email" varchar(255) NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"avatar_url" text,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "users_email_unique" UNIQUE("email")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "magic_links" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"email" varchar(255) NOT NULL,
|
||||
"token" varchar(64) NOT NULL,
|
||||
"expires_at" timestamp NOT NULL,
|
||||
"used_at" timestamp,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "magic_links_token_unique" UNIQUE("token")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "sessions" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"token_hash" varchar(128) NOT NULL,
|
||||
"expires_at" timestamp NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "sessions_token_hash_unique" UNIQUE("token_hash")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "project_members" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"project_id" uuid NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"role" "project_role" NOT NULL,
|
||||
"can_upload" boolean DEFAULT false NOT NULL,
|
||||
"can_comment" boolean DEFAULT true NOT NULL,
|
||||
"can_approve" boolean DEFAULT false NOT NULL,
|
||||
"invited_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "project_members_project_id_user_id_unique" UNIQUE("project_id","user_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "projects" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"description" text,
|
||||
"cover_image_url" text,
|
||||
"created_by_id" uuid NOT NULL,
|
||||
"is_archived" boolean DEFAULT false NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "tracks" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"project_id" uuid NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"description" text,
|
||||
"sort_order" integer DEFAULT 0 NOT NULL,
|
||||
"created_by_id" uuid NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "versions" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"track_id" uuid NOT NULL,
|
||||
"version_number" integer NOT NULL,
|
||||
"label" varchar(100),
|
||||
"notes" text,
|
||||
"status" "version_status" DEFAULT 'uploaded' NOT NULL,
|
||||
"original_file_name" varchar(500) NOT NULL,
|
||||
"mime_type" varchar(100) NOT NULL,
|
||||
"file_size" bigint NOT NULL,
|
||||
"duration" real,
|
||||
"sample_rate" integer,
|
||||
"bit_depth" integer,
|
||||
"original_file_key" text NOT NULL,
|
||||
"stream_file_key" text,
|
||||
"waveform_data_key" text,
|
||||
"created_by_id" uuid NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "versions_track_id_version_number_unique" UNIQUE("track_id","version_number")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "comments" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"version_id" uuid NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"body" text NOT NULL,
|
||||
"timestamp_seconds" real,
|
||||
"parent_id" uuid,
|
||||
"resolved_at" timestamp,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "project_members" ADD CONSTRAINT "project_members_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "project_members" ADD CONSTRAINT "project_members_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "projects" ADD CONSTRAINT "projects_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 "tracks" ADD CONSTRAINT "tracks_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "tracks" ADD CONSTRAINT "tracks_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_track_id_tracks_id_fk" FOREIGN KEY ("track_id") REFERENCES "public"."tracks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "versions" ADD CONSTRAINT "versions_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 "comments" ADD CONSTRAINT "comments_version_id_versions_id_fk" FOREIGN KEY ("version_id") REFERENCES "public"."versions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "comments" ADD CONSTRAINT "comments_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;
|
||||
757
packages/db/src/migrations/meta/0000_snapshot.json
Normal file
757
packages/db/src/migrations/meta/0000_snapshot.json
Normal file
@@ -0,0 +1,757 @@
|
||||
{
|
||||
"id": "4e5be5fd-2fae-43d2-8273-5a372d714cc5",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"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'"
|
||||
},
|
||||
"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_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": {
|
||||
"versions_track_id_version_number_unique": {
|
||||
"name": "versions_track_id_version_number_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"track_id",
|
||||
"version_number"
|
||||
]
|
||||
}
|
||||
},
|
||||
"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": true
|
||||
},
|
||||
"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
|
||||
}
|
||||
},
|
||||
"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": {}
|
||||
}
|
||||
}
|
||||
13
packages/db/src/migrations/meta/_journal.json
Normal file
13
packages/db/src/migrations/meta/_journal.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1775122377765,
|
||||
"tag": "0000_magenta_apocalypse",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
21
packages/db/src/schema/auth.ts
Normal file
21
packages/db/src/schema/auth.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { pgTable, uuid, varchar, timestamp } from 'drizzle-orm/pg-core';
|
||||
import { users } from './users.js';
|
||||
|
||||
export const magicLinks = pgTable('magic_links', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
email: varchar('email', { length: 255 }).notNull(),
|
||||
token: varchar('token', { length: 64 }).notNull().unique(),
|
||||
expiresAt: timestamp('expires_at').notNull(),
|
||||
usedAt: timestamp('used_at'),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const sessions = pgTable('sessions', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
userId: uuid('user_id')
|
||||
.references(() => users.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
tokenHash: varchar('token_hash', { length: 128 }).notNull().unique(),
|
||||
expiresAt: timestamp('expires_at').notNull(),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
});
|
||||
19
packages/db/src/schema/comments.ts
Normal file
19
packages/db/src/schema/comments.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { pgTable, uuid, text, real, timestamp } from 'drizzle-orm/pg-core';
|
||||
import { users } from './users.js';
|
||||
import { versions } from './tracks.js';
|
||||
|
||||
export const comments = pgTable('comments', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
versionId: uuid('version_id')
|
||||
.references(() => versions.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
userId: uuid('user_id')
|
||||
.references(() => users.id)
|
||||
.notNull(),
|
||||
body: text('body').notNull(),
|
||||
timestampSeconds: real('timestamp_seconds'),
|
||||
parentId: uuid('parent_id'),
|
||||
resolvedAt: timestamp('resolved_at'),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
});
|
||||
5
packages/db/src/schema/index.ts
Normal file
5
packages/db/src/schema/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from './users.js';
|
||||
export * from './auth.js';
|
||||
export * from './projects.js';
|
||||
export * from './tracks.js';
|
||||
export * from './comments.js';
|
||||
54
packages/db/src/schema/projects.ts
Normal file
54
packages/db/src/schema/projects.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import {
|
||||
pgTable,
|
||||
pgEnum,
|
||||
uuid,
|
||||
varchar,
|
||||
text,
|
||||
boolean,
|
||||
timestamp,
|
||||
unique,
|
||||
} from 'drizzle-orm/pg-core';
|
||||
import { users } from './users.js';
|
||||
|
||||
export const projectRoleEnum = pgEnum('project_role', [
|
||||
'owner',
|
||||
'recording_engineer',
|
||||
'mixing_engineer',
|
||||
'mastering_engineer',
|
||||
'artist',
|
||||
'label',
|
||||
'management',
|
||||
'viewer',
|
||||
]);
|
||||
|
||||
export const projects = pgTable('projects', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
name: varchar('name', { length: 255 }).notNull(),
|
||||
description: text('description'),
|
||||
coverImageUrl: text('cover_image_url'),
|
||||
createdById: uuid('created_by_id')
|
||||
.references(() => users.id)
|
||||
.notNull(),
|
||||
isArchived: boolean('is_archived').default(false).notNull(),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const projectMembers = pgTable(
|
||||
'project_members',
|
||||
{
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
projectId: uuid('project_id')
|
||||
.references(() => projects.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
userId: uuid('user_id')
|
||||
.references(() => users.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
role: projectRoleEnum('role').notNull(),
|
||||
canUpload: boolean('can_upload').default(false).notNull(),
|
||||
canComment: boolean('can_comment').default(true).notNull(),
|
||||
canApprove: boolean('can_approve').default(false).notNull(),
|
||||
invitedAt: timestamp('invited_at').defaultNow().notNull(),
|
||||
},
|
||||
(table) => [unique().on(table.projectId, table.userId)],
|
||||
);
|
||||
68
packages/db/src/schema/tracks.ts
Normal file
68
packages/db/src/schema/tracks.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import {
|
||||
pgTable,
|
||||
pgEnum,
|
||||
uuid,
|
||||
varchar,
|
||||
text,
|
||||
integer,
|
||||
bigint,
|
||||
real,
|
||||
timestamp,
|
||||
unique,
|
||||
} from 'drizzle-orm/pg-core';
|
||||
import { users } from './users.js';
|
||||
import { projects } from './projects.js';
|
||||
|
||||
export const versionStatusEnum = pgEnum('version_status', [
|
||||
'uploaded',
|
||||
'processing',
|
||||
'ready',
|
||||
'approved',
|
||||
'rejected',
|
||||
]);
|
||||
|
||||
export const tracks = pgTable('tracks', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
projectId: uuid('project_id')
|
||||
.references(() => projects.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
name: varchar('name', { length: 255 }).notNull(),
|
||||
description: text('description'),
|
||||
sortOrder: integer('sort_order').default(0).notNull(),
|
||||
createdById: uuid('created_by_id')
|
||||
.references(() => users.id)
|
||||
.notNull(),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
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(),
|
||||
|
||||
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'),
|
||||
|
||||
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(),
|
||||
},
|
||||
(table) => [unique().on(table.trackId, table.versionNumber)],
|
||||
);
|
||||
10
packages/db/src/schema/users.ts
Normal file
10
packages/db/src/schema/users.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { pgTable, uuid, varchar, text, timestamp } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const users = pgTable('users', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
email: varchar('email', { length: 255 }).notNull().unique(),
|
||||
name: varchar('name', { length: 255 }).notNull(),
|
||||
avatarUrl: text('avatar_url'),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
});
|
||||
13
packages/db/tsconfig.json
Normal file
13
packages/db/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"declaration": true,
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Reference in New Issue
Block a user