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:
Robin Choice
2026-04-02 13:23:10 +02:00
commit e420ed198b
88 changed files with 7306 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
export const SUPPORTED_AUDIO_FORMATS = [
'audio/wav',
'audio/x-wav',
'audio/mp3',
'audio/mpeg',
'audio/flac',
'audio/x-flac',
'audio/aiff',
'audio/x-aiff',
] as const;
export const SUPPORTED_EXTENSIONS = ['.wav', '.mp3', '.flac', '.aiff', '.aif'] as const;
export const MAX_FILE_SIZE = 500 * 1024 * 1024; // 500 MB
export const VERSION_STATUSES = [
'uploaded',
'processing',
'ready',
'approved',
'rejected',
] as const;
export type VersionStatus = (typeof VERSION_STATUSES)[number];

View File

@@ -0,0 +1,3 @@
export * from './roles.js';
export * from './permissions.js';
export * from './audio.js';

View File

@@ -0,0 +1,69 @@
import type { ProjectRole } from './roles.js';
export type Permission =
| 'project.edit'
| 'project.invite'
| 'track.upload'
| 'track.listen'
| 'track.download'
| 'version.comment'
| 'version.approve';
const PERMISSIONS: Record<ProjectRole, Permission[]> = {
owner: [
'project.edit',
'project.invite',
'track.upload',
'track.listen',
'track.download',
'version.comment',
'version.approve',
],
recording_engineer: [
'track.upload',
'track.listen',
'track.download',
'version.comment',
],
mixing_engineer: [
'track.upload',
'track.listen',
'track.download',
'version.comment',
],
mastering_engineer: [
'track.upload',
'track.listen',
'track.download',
'version.comment',
],
artist: [
'track.listen',
'track.download',
'version.comment',
'version.approve',
],
label: [
'track.listen',
'version.comment',
'version.approve',
],
management: [
'project.invite',
'track.listen',
'track.download',
'version.comment',
'version.approve',
],
viewer: [
'track.listen',
],
};
export function hasPermission(role: ProjectRole, permission: Permission): boolean {
return PERMISSIONS[role].includes(permission);
}
export function getPermissions(role: ProjectRole): Permission[] {
return PERMISSIONS[role];
}

View File

@@ -0,0 +1,29 @@
export const PROJECT_ROLES = [
'owner',
'recording_engineer',
'mixing_engineer',
'mastering_engineer',
'artist',
'label',
'management',
'viewer',
] as const;
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',
artist: 'Artist',
label: 'Label',
management: 'Management',
viewer: 'Viewer',
};
export const ENGINEER_ROLES: ProjectRole[] = [
'recording_engineer',
'mixing_engineer',
'mastering_engineer',
];