1462 lines
40 KiB
Markdown
1462 lines
40 KiB
Markdown
# Sonic Cloud - Project Documentation
|
|
|
|
**Version**: 0.1.0
|
|
**Type**: Music Platform for Musicians and Listeners
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
### What is Sonic Cloud?
|
|
|
|
Sonic Cloud is a modern music platform that connects musicians with listeners. Think of it as a place where independent artists can upload their music, organize it into albums, and share it with the world - while listeners can discover new music, create playlists, and follow their favorite artists.
|
|
|
|
**Key Capabilities**:
|
|
- Musicians upload and manage their songs and albums
|
|
- Listeners discover trending music and new releases
|
|
- Everyone can create and share custom playlists
|
|
- Built-in audio player with waveform visualization
|
|
- Search across all songs, artists, and albums
|
|
|
|
### Who is it for?
|
|
|
|
**Musicians**: Upload your tracks, organize them into albums, build your artist profile, and track how many plays your music gets.
|
|
|
|
**Labels**: Manage multiple artists under your label, invite new talent, and view collective statistics.
|
|
|
|
**Listeners**: Discover new music by genre, create playlists, follow artists, and share your favorite songs.
|
|
|
|
---
|
|
|
|
## Quick Start
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
# Install dependencies
|
|
npm install
|
|
|
|
# Set up database
|
|
npx prisma generate
|
|
npx prisma db push
|
|
|
|
# Start development server
|
|
npm run dev
|
|
```
|
|
|
|
Open [http://localhost:3000](http://localhost:3000) in your browser.
|
|
|
|
### First Steps
|
|
|
|
1. **Register an account** at `/register`
|
|
2. **Become an artist** by creating an artist profile
|
|
3. **Upload your first song** using the upload form
|
|
4. **Create a playlist** to organize music you love
|
|
5. **Search and discover** trending songs and new releases
|
|
|
|
---
|
|
|
|
## Architecture Overview
|
|
|
|
### System Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ USER BROWSER │
|
|
│ (React 19 + Next.js 16) │
|
|
└────────────────────────────┬────────────────────────────────────┘
|
|
│
|
|
│ HTTP Requests
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ NEXT.JS APP ROUTER │
|
|
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
│ │ Pages (SSR) │ │ API Routes │ │
|
|
│ │ - Home │ │ - Auth │ │
|
|
│ │ - Artist │ │ - Songs │ │
|
|
│ │ - Album │ │ - Playlists │ │
|
|
│ │ - Playlist │ │ - Search │ │
|
|
│ │ - Profile │ │ - Share │ │
|
|
│ └──────────────────┘ └──────────────────┘ │
|
|
└────────────────────────────┬────────────────────────────────────┘
|
|
│
|
|
│ Prisma ORM
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ SQLITE DATABASE │
|
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
│ │ Users │ │ Artists │ │ Songs │ │ Playlists│ │
|
|
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
│ │ Albums │ │ Labels │ │ Genres │ │ Shares │ │
|
|
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Data Flow
|
|
|
|
```
|
|
User Action → React Component → API Endpoint → Prisma ORM → SQLite
|
|
│
|
|
▼
|
|
User Sees Update ← React Refresh ← JSON Response ← Data Query ───┘
|
|
```
|
|
|
|
### Technology Stack
|
|
|
|
| Layer | Technology | Purpose |
|
|
|-------|------------|---------|
|
|
| **Frontend** | React 19.2.1 | Interactive user interface |
|
|
| **Framework** | Next.js 16.0.10 | Server-side rendering, routing, API |
|
|
| **Styling** | Tailwind CSS 4 | Responsive, utility-first design |
|
|
| **Language** | TypeScript 5 | Type safety and better code quality |
|
|
| **Database** | SQLite | Lightweight, file-based storage |
|
|
| **ORM** | Prisma 5.22.0 | Type-safe database access |
|
|
| **Authentication** | JWT + bcryptjs | Secure user sessions |
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - Framework Decisions</summary>
|
|
|
|
**Why Next.js?**
|
|
- File-based routing simplifies page creation
|
|
- API routes eliminate need for separate backend
|
|
- Server-side rendering improves SEO and performance
|
|
- React 19 supports latest concurrent features
|
|
|
|
**Why SQLite?**
|
|
- Zero configuration for development
|
|
- Single file database for easy deployment
|
|
- Sufficient performance for small-to-medium scale
|
|
- Easy migration to PostgreSQL/MySQL later if needed
|
|
|
|
**Why Prisma?**
|
|
- Type-safe database queries prevent runtime errors
|
|
- Schema-first approach documents data model
|
|
- Automatic migration generation
|
|
- Built-in connection pooling
|
|
|
|
</details>
|
|
|
|
---
|
|
|
|
## Features
|
|
|
|
### For Musicians (Artists)
|
|
|
|
#### Upload and Manage Songs
|
|
Upload audio files with metadata like title, genre, cover art, and description. Your songs are stored with unique IDs and can be updated or deleted at any time.
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - Song Upload Flow</summary>
|
|
|
|
```typescript
|
|
POST /api/songs/upload
|
|
Headers: { Authorization: "Bearer <jwt_token>" }
|
|
Body: {
|
|
title: string,
|
|
artistId: string,
|
|
albumId?: string,
|
|
audioUrl: string, // URL to audio file
|
|
coverImageUrl?: string,
|
|
duration: number, // in seconds
|
|
isPublic: boolean,
|
|
genres: string[] // genre slugs
|
|
}
|
|
```
|
|
|
|
**Process**:
|
|
1. Validate JWT token → get authenticated user
|
|
2. Verify user owns the artist profile
|
|
3. Create Song record in database
|
|
4. Create SongGenre junction records
|
|
5. Return song with ID and metadata
|
|
|
|
**Validation**:
|
|
- Duration must be > 0
|
|
- Audio URL format validation
|
|
- Genre slugs must exist in Genre table
|
|
|
|
</details>
|
|
|
|
#### Organize Albums
|
|
Group songs into albums, EPs, or singles. Each album has a release date, cover art, and track listing that you control.
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - Album Structure</summary>
|
|
|
|
```typescript
|
|
// Album model fields
|
|
{
|
|
id: string, // UUID
|
|
artistId: string, // FK to Artist
|
|
title: string,
|
|
releaseDate: Date,
|
|
albumType: "ALBUM" | "EP" | "SINGLE",
|
|
coverImageUrl?: string,
|
|
description?: string,
|
|
songs: Song[] // hasMany relation
|
|
}
|
|
```
|
|
|
|
**Album Types**:
|
|
- **ALBUM**: 8+ tracks, typically 30-60 minutes
|
|
- **EP**: 3-7 tracks, typically 15-30 minutes
|
|
- **SINGLE**: 1-2 tracks
|
|
|
|
</details>
|
|
|
|
#### Artist Profile
|
|
Create a professional profile with bio, avatar, verified badge, and links to your label (if applicable). Track your total plays across all songs.
|
|
|
|
#### Analytics
|
|
See how many times each song has been played. Monitor which songs are trending and which need promotion.
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - Play Tracking</summary>
|
|
|
|
```typescript
|
|
POST /api/songs/{id}/play
|
|
|
|
// Updates play count atomically
|
|
await prisma.song.update({
|
|
where: { id: songId },
|
|
data: {
|
|
playCount: { increment: 1 }
|
|
}
|
|
});
|
|
```
|
|
|
|
**Note**: Play count is incremented on each play request. For production, consider:
|
|
- IP-based rate limiting to prevent abuse
|
|
- Session tracking to count unique plays
|
|
- Analytics aggregation for performance
|
|
|
|
</details>
|
|
|
|
---
|
|
|
|
### For Labels
|
|
|
|
#### Manage Multiple Artists
|
|
Create a label profile and invite artists to join your roster. View all artists under your label in one dashboard.
|
|
|
|
#### Label Statistics
|
|
See aggregate stats across all your artists: total songs, albums, and cumulative play counts.
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - Label Stats API</summary>
|
|
|
|
```typescript
|
|
GET /api/labels/{id}/stats
|
|
|
|
Response: {
|
|
artistCount: number,
|
|
songCount: number,
|
|
albumCount: number,
|
|
totalPlays: number
|
|
}
|
|
```
|
|
|
|
**Calculation**:
|
|
- Artist count: Direct count of Artist records with labelId
|
|
- Song/Album count: Sum across all label artists
|
|
- Total plays: Aggregate playCount from all songs by label artists
|
|
|
|
</details>
|
|
|
|
#### Invitation System
|
|
Send invitations to artists. They can accept or decline. Track invitation status (pending, accepted, declined, expired).
|
|
|
|
---
|
|
|
|
### For Listeners
|
|
|
|
#### Discover Music
|
|
Browse trending songs (sorted by play count) and new releases (sorted by upload date). Filter by genre to find music you love.
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - Discovery Queries</summary>
|
|
|
|
```typescript
|
|
// Trending songs
|
|
GET /api/discover/trending?limit=20
|
|
|
|
// SQL equivalent
|
|
SELECT * FROM Song
|
|
WHERE isPublic = true
|
|
ORDER BY playCount DESC
|
|
LIMIT 20;
|
|
|
|
// New releases
|
|
GET /api/discover/new-releases?limit=20
|
|
|
|
// SQL equivalent
|
|
SELECT * FROM Song
|
|
WHERE isPublic = true
|
|
ORDER BY createdAt DESC
|
|
LIMIT 20;
|
|
```
|
|
|
|
</details>
|
|
|
|
#### Create Playlists
|
|
Make custom playlists with any songs on the platform. Control playlist privacy (public or private). Reorder songs by dragging.
|
|
|
|
#### Search Everything
|
|
Type a query and search across songs (title), artists (name), and albums (title) simultaneously.
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - Search Implementation</summary>
|
|
|
|
```typescript
|
|
GET /api/search?q=rock
|
|
|
|
// Searches across three tables
|
|
Songs: WHERE title LIKE '%rock%' OR description LIKE '%rock%'
|
|
Artists: WHERE name LIKE '%rock%' OR bio LIKE '%rock%'
|
|
Albums: WHERE title LIKE '%rock%' OR description LIKE '%rock%'
|
|
|
|
Response: {
|
|
songs: Song[],
|
|
artists: Artist[],
|
|
albums: Album[]
|
|
}
|
|
```
|
|
|
|
**Current Limitations**:
|
|
- Case-insensitive substring matching only
|
|
- No relevance scoring
|
|
- No fuzzy matching
|
|
|
|
**Future Enhancements**:
|
|
- Full-text search engine (Algolia, Elasticsearch)
|
|
- Relevance ranking by play count and recency
|
|
- Typo tolerance and autocomplete
|
|
|
|
</details>
|
|
|
|
#### Audio Player
|
|
Play songs with a fixed bottom player that follows you across pages. Features include:
|
|
- Play/pause, next/previous track
|
|
- Visual waveform with seek functionality
|
|
- Volume control and mute
|
|
- Shuffle and repeat modes
|
|
|
|
---
|
|
|
|
### For Everyone
|
|
|
|
#### Share Content
|
|
Generate shareable links for songs, playlists, and albums. Track how many times your share links are clicked.
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - Share Link Generation</summary>
|
|
|
|
```typescript
|
|
POST /api/share/song/{id}
|
|
|
|
// Creates Share record
|
|
{
|
|
id: string,
|
|
type: "SONG" | "PLAYLIST" | "ALBUM",
|
|
targetId: string, // ID of shared content
|
|
token: string, // nanoid(10) - short unique code
|
|
userId?: string, // Who created the share
|
|
clickCount: number,
|
|
createdAt: Date
|
|
}
|
|
|
|
// Returns shareable URL
|
|
https://sonic-cloud.app/s/{token}
|
|
```
|
|
|
|
**Share Token Format**: 10-character nanoid (URL-safe, lowercase + numbers)
|
|
|
|
**Click Tracking**: Each time `/s/{token}` is visited, clickCount increments.
|
|
|
|
</details>
|
|
|
|
#### Genre System
|
|
Browse music by genre. Each genre has a name, slug (URL-friendly), description, and visual color code.
|
|
|
|
#### User Profiles
|
|
View other users' public playlists and activity. Update your own profile with display name, bio, and avatar.
|
|
|
|
---
|
|
|
|
## API Reference
|
|
|
|
The platform exposes 40 RESTful API endpoints across 8 functional domains.
|
|
|
|
### Authentication (`/api/auth`)
|
|
|
|
| Endpoint | Method | Auth Required | Description |
|
|
|----------|--------|---------------|-------------|
|
|
| `/auth/register` | POST | No | Create new user account |
|
|
| `/auth/login` | POST | No | Authenticate and receive JWT |
|
|
| `/auth/forgot-password` | POST | No | Request password reset email |
|
|
| `/auth/reset-password` | POST | No | Reset password with token |
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - Authentication Flow</summary>
|
|
|
|
**Registration**:
|
|
```typescript
|
|
POST /api/auth/register
|
|
Body: {
|
|
email: string,
|
|
password: string, // Min 8 chars
|
|
username: string, // Unique, 3-20 chars
|
|
displayName?: string
|
|
}
|
|
|
|
Response: {
|
|
user: { id, email, username, role },
|
|
token: string // JWT valid for 7 days
|
|
}
|
|
```
|
|
|
|
**Login**:
|
|
```typescript
|
|
POST /api/auth/login
|
|
Body: {
|
|
email: string,
|
|
password: string
|
|
}
|
|
|
|
Response: {
|
|
user: { id, email, username, role },
|
|
token: string
|
|
}
|
|
```
|
|
|
|
**JWT Payload**:
|
|
```typescript
|
|
{
|
|
userId: string,
|
|
email: string,
|
|
role: "USER" | "ADMIN",
|
|
iat: number, // issued at
|
|
exp: number // expires at (7 days)
|
|
}
|
|
```
|
|
|
|
**Password Hashing**: bcryptjs with 10 salt rounds
|
|
|
|
**Token Validation**: All protected routes verify JWT via `Authorization: Bearer <token>` header
|
|
|
|
</details>
|
|
|
|
---
|
|
|
|
### Users (`/api/users`)
|
|
|
|
| Endpoint | Method | Auth Required | Description |
|
|
|----------|--------|---------------|-------------|
|
|
| `/users/me` | GET | Yes | Get current user profile |
|
|
| `/users/me` | PUT | Yes | Update profile (displayName, bio, avatarUrl) |
|
|
|
|
---
|
|
|
|
### Artists (`/api/artists`)
|
|
|
|
| Endpoint | Method | Auth Required | Description |
|
|
|----------|--------|---------------|-------------|
|
|
| `/artists` | POST | Yes | Create artist profile for current user |
|
|
| `/artists/{id}` | GET | No | Get artist details with stats |
|
|
| `/artists/{id}` | PUT | Yes | Update artist profile (owner only) |
|
|
| `/artists/{id}/songs` | GET | No | Get all public songs by artist |
|
|
| `/artists/{id}/albums` | GET | No | Get all albums by artist |
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - Artist Endpoints</summary>
|
|
|
|
**Create Artist**:
|
|
```typescript
|
|
POST /api/artists
|
|
Body: {
|
|
name: string,
|
|
bio?: string,
|
|
avatarUrl?: string,
|
|
websiteUrl?: string
|
|
}
|
|
|
|
// Automatically generates slug from name
|
|
// Associates with authenticated userId
|
|
```
|
|
|
|
**Get Artist**:
|
|
```typescript
|
|
GET /api/artists/{id}
|
|
|
|
Response: {
|
|
id, name, slug, bio, avatarUrl, verified,
|
|
userId, labelId,
|
|
_count: {
|
|
songs: number,
|
|
albums: number
|
|
},
|
|
label?: { id, name, slug }
|
|
}
|
|
```
|
|
|
|
**Authorization**: Only artist owner can update their profile
|
|
|
|
</details>
|
|
|
|
---
|
|
|
|
### Songs (`/api/songs`)
|
|
|
|
| Endpoint | Method | Auth Required | Description |
|
|
|----------|--------|---------------|-------------|
|
|
| `/songs/upload` | POST | Yes | Upload new song (artist only) |
|
|
| `/songs/{id}` | GET | No | Get song details (respects privacy) |
|
|
| `/songs/{id}` | PUT | Yes | Update song metadata (owner only) |
|
|
| `/songs/{id}` | DELETE | Yes | Delete song (owner only) |
|
|
| `/songs/{id}/play` | POST | No | Increment play count |
|
|
|
|
---
|
|
|
|
### Albums (`/api/albums`)
|
|
|
|
| Endpoint | Method | Auth Required | Description |
|
|
|----------|--------|---------------|-------------|
|
|
| `/albums` | POST | Yes | Create album (artist only) |
|
|
| `/albums/{id}` | GET | No | Get album with all songs |
|
|
| `/albums/{id}` | PUT | Yes | Update album (owner only) |
|
|
| `/albums/{id}` | DELETE | Yes | Delete album (owner only) |
|
|
|
|
---
|
|
|
|
### Playlists (`/api/playlists`)
|
|
|
|
| Endpoint | Method | Auth Required | Description |
|
|
|----------|--------|---------------|-------------|
|
|
| `/playlists` | GET | Yes | Get current user's playlists |
|
|
| `/playlists` | POST | Yes | Create new playlist |
|
|
| `/playlists/{id}` | GET | No | Get playlist (respects privacy) |
|
|
| `/playlists/{id}` | PUT | Yes | Update playlist (owner only) |
|
|
| `/playlists/{id}` | DELETE | Yes | Delete playlist (owner only) |
|
|
| `/playlists/{id}/songs` | POST | Yes | Add song to playlist |
|
|
| `/playlists/{id}/reorder` | PUT | Yes | Reorder songs in playlist |
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - Playlist Reordering</summary>
|
|
|
|
```typescript
|
|
PUT /api/playlists/{id}/reorder
|
|
Body: {
|
|
songIds: string[] // Ordered array of song IDs
|
|
}
|
|
|
|
// Updates position field for each PlaylistSong junction record
|
|
for (let i = 0; i < songIds.length; i++) {
|
|
await prisma.playlistSong.updateMany({
|
|
where: { playlistId: id, songId: songIds[i] },
|
|
data: { position: i }
|
|
});
|
|
}
|
|
```
|
|
|
|
**Position Indexing**: 0-based integer positions for stable ordering
|
|
|
|
</details>
|
|
|
|
---
|
|
|
|
### Discovery (`/api/discover`)
|
|
|
|
| Endpoint | Method | Auth Required | Description |
|
|
|----------|--------|---------------|-------------|
|
|
| `/discover/trending` | GET | No | Get trending songs by play count |
|
|
| `/discover/new-releases` | GET | No | Get newest public songs |
|
|
| `/discover/genres` | GET | No | Get all genres with song counts |
|
|
| `/discover/genres/{slug}` | GET | No | Get songs in specific genre |
|
|
|
|
---
|
|
|
|
### Labels (`/api/labels`)
|
|
|
|
| Endpoint | Method | Auth Required | Description |
|
|
|----------|--------|---------------|-------------|
|
|
| `/labels` | POST | Yes | Create label profile |
|
|
| `/labels/{id}` | GET | No | Get label details with artists |
|
|
| `/labels/{id}` | PUT | Yes | Update label (owner only) |
|
|
| `/labels/{id}/stats` | GET | No | Get aggregate label statistics |
|
|
| `/labels/{id}/artists` | GET | No | Get all artists under label |
|
|
|
|
---
|
|
|
|
### Search (`/api/search`)
|
|
|
|
| Endpoint | Method | Auth Required | Description |
|
|
|----------|--------|---------------|-------------|
|
|
| `/search?q={query}` | GET | No | Search songs, artists, albums |
|
|
|
|
---
|
|
|
|
### Sharing (`/api/share`)
|
|
|
|
| Endpoint | Method | Auth Required | Description |
|
|
|----------|--------|---------------|-------------|
|
|
| `/share/song/{id}` | POST | No | Create shareable link for song |
|
|
| `/share/playlist/{id}` | POST | No | Create shareable link for playlist |
|
|
| `/share/album/{id}` | POST | No | Create shareable link for album |
|
|
| `/share/{token}` | GET | No | Resolve share link to content |
|
|
| `/share/{token}/click` | POST | No | Track share link click |
|
|
|
|
---
|
|
|
|
## Data Models
|
|
|
|
The database uses 10 core tables to represent the music platform.
|
|
|
|
### User
|
|
Base account entity for authentication and profile.
|
|
|
|
**Key Fields**:
|
|
- `id` (UUID): Primary key
|
|
- `email` (string): Unique, lowercase, used for login
|
|
- `username` (string): Unique, 3-20 characters
|
|
- `displayName` (string): Optional public name
|
|
- `passwordHash` (string): bcrypt hash
|
|
- `role` (enum): USER or ADMIN
|
|
|
|
**Relationships**:
|
|
- Has one Artist
|
|
- Has one Label
|
|
- Has many Playlists
|
|
- Has many Shares
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - User Schema</summary>
|
|
|
|
```prisma
|
|
model User {
|
|
id String @id @default(uuid())
|
|
email String @unique
|
|
username String @unique
|
|
displayName String?
|
|
passwordHash String
|
|
role Role @default(USER)
|
|
avatarUrl String?
|
|
bio String?
|
|
websiteUrl String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
artist Artist?
|
|
label Label?
|
|
playlists Playlist[]
|
|
shares Share[]
|
|
}
|
|
|
|
enum Role {
|
|
USER
|
|
ADMIN
|
|
}
|
|
```
|
|
|
|
</details>
|
|
|
|
---
|
|
|
|
### Artist
|
|
Extended profile for musicians who upload content.
|
|
|
|
**Key Fields**:
|
|
- `id` (UUID): Primary key
|
|
- `userId` (UUID): Foreign key to User (one-to-one)
|
|
- `name` (string): Artist name
|
|
- `slug` (string): URL-friendly identifier
|
|
- `verified` (boolean): Official verification badge
|
|
- `labelId` (UUID): Optional foreign key to Label
|
|
|
|
**Relationships**:
|
|
- Belongs to User
|
|
- Belongs to Label (optional)
|
|
- Has many Songs
|
|
- Has many Albums
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - Artist Schema</summary>
|
|
|
|
```prisma
|
|
model Artist {
|
|
id String @id @default(uuid())
|
|
userId String @unique
|
|
name String
|
|
slug String @unique
|
|
bio String?
|
|
avatarUrl String?
|
|
verified Boolean @default(false)
|
|
websiteUrl String?
|
|
labelId String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id])
|
|
label Label? @relation(fields: [labelId], references: [id])
|
|
songs Song[]
|
|
albums Album[]
|
|
|
|
@@index([slug])
|
|
@@index([labelId])
|
|
}
|
|
```
|
|
|
|
**Slug Generation**: Lowercase, hyphens for spaces, alphanumeric only
|
|
|
|
</details>
|
|
|
|
---
|
|
|
|
### Label
|
|
Organization that manages multiple artists.
|
|
|
|
**Key Fields**:
|
|
- `id` (UUID): Primary key
|
|
- `userId` (UUID): Foreign key to User (owner)
|
|
- `name` (string): Label name
|
|
- `slug` (string): URL-friendly identifier
|
|
- `description` (string): About the label
|
|
|
|
**Relationships**:
|
|
- Belongs to User (owner)
|
|
- Has many Artists
|
|
- Has many LabelInvitations
|
|
|
|
---
|
|
|
|
### Genre
|
|
Music category tags for organization and discovery.
|
|
|
|
**Key Fields**:
|
|
- `id` (UUID): Primary key
|
|
- `name` (string): Genre name
|
|
- `slug` (string): URL-friendly identifier
|
|
- `description` (string): Genre description
|
|
- `color` (string): Hex color code for UI
|
|
|
|
**Relationships**:
|
|
- Has many SongGenre (junction)
|
|
|
|
**Example Genres**: Rock, Jazz, Electronic, Hip-Hop, Classical, Pop
|
|
|
|
---
|
|
|
|
### Album
|
|
Collection of songs released together.
|
|
|
|
**Key Fields**:
|
|
- `id` (UUID): Primary key
|
|
- `artistId` (UUID): Foreign key to Artist
|
|
- `title` (string): Album title
|
|
- `releaseDate` (DateTime): Release date
|
|
- `albumType` (enum): ALBUM, EP, or SINGLE
|
|
- `coverImageUrl` (string): Album artwork URL
|
|
|
|
**Relationships**:
|
|
- Belongs to Artist
|
|
- Has many Songs
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - Album Schema</summary>
|
|
|
|
```prisma
|
|
model Album {
|
|
id String @id @default(uuid())
|
|
artistId String
|
|
title String
|
|
releaseDate DateTime
|
|
albumType AlbumType @default(ALBUM)
|
|
coverImageUrl String?
|
|
description String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
artist Artist @relation(fields: [artistId], references: [id])
|
|
songs Song[]
|
|
|
|
@@index([artistId])
|
|
@@index([releaseDate])
|
|
}
|
|
|
|
enum AlbumType {
|
|
ALBUM
|
|
EP
|
|
SINGLE
|
|
}
|
|
```
|
|
|
|
</details>
|
|
|
|
---
|
|
|
|
### Song
|
|
Individual audio track with metadata and analytics.
|
|
|
|
**Key Fields**:
|
|
- `id` (UUID): Primary key
|
|
- `artistId` (UUID): Foreign key to Artist
|
|
- `albumId` (UUID): Optional foreign key to Album
|
|
- `title` (string): Song title
|
|
- `audioUrl` (string): URL to audio file
|
|
- `duration` (number): Length in seconds
|
|
- `playCount` (number): Total plays across platform
|
|
- `isPublic` (boolean): Visibility control
|
|
|
|
**Relationships**:
|
|
- Belongs to Artist
|
|
- Belongs to Album (optional)
|
|
- Has many SongGenre (junction)
|
|
- Has many PlaylistSong (junction)
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - Song Schema</summary>
|
|
|
|
```prisma
|
|
model Song {
|
|
id String @id @default(uuid())
|
|
artistId String
|
|
albumId String?
|
|
title String
|
|
audioUrl String
|
|
coverImageUrl String?
|
|
duration Int // seconds
|
|
playCount Int @default(0)
|
|
isPublic Boolean @default(true)
|
|
description String?
|
|
lyrics String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
artist Artist @relation(fields: [artistId], references: [id])
|
|
album Album? @relation(fields: [albumId], references: [id])
|
|
genres SongGenre[]
|
|
playlists PlaylistSong[]
|
|
|
|
@@index([artistId])
|
|
@@index([albumId])
|
|
@@index([playCount])
|
|
@@index([createdAt])
|
|
}
|
|
```
|
|
|
|
**Indexes**: Optimized for queries on artist, album, play count (trending), and creation date (new releases)
|
|
|
|
</details>
|
|
|
|
---
|
|
|
|
### Playlist
|
|
User-created collection of songs.
|
|
|
|
**Key Fields**:
|
|
- `id` (UUID): Primary key
|
|
- `userId` (UUID): Foreign key to User (owner)
|
|
- `title` (string): Playlist name
|
|
- `description` (string): Optional description
|
|
- `isPublic` (boolean): Privacy control
|
|
- `coverImageUrl` (string): Playlist artwork URL
|
|
|
|
**Relationships**:
|
|
- Belongs to User
|
|
- Has many PlaylistSong (junction)
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - Playlist Schema</summary>
|
|
|
|
```prisma
|
|
model Playlist {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
title String
|
|
description String?
|
|
isPublic Boolean @default(false)
|
|
coverImageUrl String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id])
|
|
songs PlaylistSong[]
|
|
|
|
@@index([userId])
|
|
}
|
|
|
|
model PlaylistSong {
|
|
id String @id @default(uuid())
|
|
playlistId String
|
|
songId String
|
|
position Int // For ordering
|
|
addedAt DateTime @default(now())
|
|
|
|
playlist Playlist @relation(fields: [playlistId], references: [id])
|
|
song Song @relation(fields: [songId], references: [id])
|
|
|
|
@@unique([playlistId, songId])
|
|
@@index([playlistId, position])
|
|
}
|
|
```
|
|
|
|
**Position**: 0-based integer for stable song ordering within playlist
|
|
|
|
</details>
|
|
|
|
---
|
|
|
|
### LabelInvitation
|
|
Invitation workflow for labels recruiting artists.
|
|
|
|
**Key Fields**:
|
|
- `id` (UUID): Primary key
|
|
- `labelId` (UUID): Foreign key to Label
|
|
- `artistId` (UUID): Foreign key to Artist
|
|
- `status` (enum): PENDING, ACCEPTED, DECLINED, EXPIRED
|
|
- `expiresAt` (DateTime): Invitation expiration
|
|
|
|
**Relationships**:
|
|
- Belongs to Label
|
|
- Belongs to Artist
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - Invitation Lifecycle</summary>
|
|
|
|
**States**:
|
|
1. **PENDING**: Initial state after creation
|
|
2. **ACCEPTED**: Artist accepts → artistId gets labelId assigned
|
|
3. **DECLINED**: Artist rejects invitation
|
|
4. **EXPIRED**: expiresAt passes without action
|
|
|
|
**Expiration**: Default 7 days from creation
|
|
|
|
</details>
|
|
|
|
---
|
|
|
|
### Share
|
|
Shareable content links with analytics.
|
|
|
|
**Key Fields**:
|
|
- `id` (UUID): Primary key
|
|
- `type` (enum): SONG, PLAYLIST, or ALBUM
|
|
- `targetId` (UUID): ID of shared content
|
|
- `token` (string): Unique 10-character code
|
|
- `clickCount` (number): Tracking metric
|
|
- `userId` (UUID): Optional creator
|
|
|
|
**Relationships**:
|
|
- Belongs to User (optional)
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - Share Schema</summary>
|
|
|
|
```prisma
|
|
model Share {
|
|
id String @id @default(uuid())
|
|
type ShareType
|
|
targetId String
|
|
token String @unique
|
|
userId String?
|
|
clickCount Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
|
|
user User? @relation(fields: [userId], references: [id])
|
|
|
|
@@index([token])
|
|
@@index([targetId])
|
|
}
|
|
|
|
enum ShareType {
|
|
SONG
|
|
PLAYLIST
|
|
ALBUM
|
|
}
|
|
```
|
|
|
|
**Token Generation**: `nanoid(10)` - URL-safe, lowercase + numbers
|
|
|
|
</details>
|
|
|
|
---
|
|
|
|
## Entity Relationship Diagram
|
|
|
|
```
|
|
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
│ USER │──────<│ ARTIST │──────<│ SONG │
|
|
│ │ 1:1 │ │ 1:N │ │
|
|
│ - id │ │ - id │ │ - id │
|
|
│ - email │ │ - userId │ │ - artistId │
|
|
│ - username │ │ - name │ │ - title │
|
|
│ - password │ │ - slug │ │ - audioUrl │
|
|
│ - role │ │ - verified │ │ - playCount │
|
|
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
|
|
│ │ │
|
|
│ 1:1 │ N:1 │ N:M
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
│ LABEL │ │ ALBUM │ │ SONGGENRE │
|
|
│ │ │ │ │ (junction) │
|
|
│ - id │ │ - id │ │ │
|
|
│ - userId │ │ - artistId │ │ - songId │
|
|
│ - name │──────<│ - title │ │ - genreId │
|
|
│ - slug │ 1:N │ - releaseDate│ └──────┬───────┘
|
|
└──────┬───────┘ └──────┬───────┘ │ N:1
|
|
│ │ │
|
|
│ 1:N │ 1:N ▼
|
|
│ │ ┌──────────────┐
|
|
▼ │ │ GENRE │
|
|
┌──────────────┐ │ │ │
|
|
│ LABELINVITE │ │ │ - id │
|
|
│ │ │ │ - name │
|
|
│ - labelId │ │ │ - slug │
|
|
│ - artistId │ │ │ - color │
|
|
│ - status │ │ └──────────────┘
|
|
└──────────────┘ │
|
|
│
|
|
┌──────────────────────┘
|
|
│
|
|
▼
|
|
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
│ PLAYLIST │──────<│ PLAYLISTSONG │>──────│ SONG │
|
|
│ │ 1:N │ (junction) │ N:1 │ (see above) │
|
|
│ - id │ │ │ │ │
|
|
│ - userId │ │ - playlistId │ └──────────────┘
|
|
│ - title │ │ - songId │
|
|
│ - isPublic │ │ - position │
|
|
└──────────────┘ └──────────────┘
|
|
|
|
┌──────────────┐
|
|
│ SHARE │
|
|
│ │
|
|
│ - id │
|
|
│ - type │──── References SONG | PLAYLIST | ALBUM
|
|
│ - targetId │
|
|
│ - token │
|
|
│ - clickCount │
|
|
└──────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Components
|
|
|
|
The UI is built from 20 reusable React components organized by function.
|
|
|
|
### Audio Components
|
|
|
|
#### AudioPlayer
|
|
Fixed bottom player that follows across all pages.
|
|
|
|
**Features**:
|
|
- Song info display (title, artist, cover)
|
|
- Play/pause, next/previous buttons
|
|
- Progress bar with seek functionality
|
|
- Volume slider and mute toggle
|
|
- Current time / total duration display
|
|
|
|
**Location**: `components/AudioPlayer.tsx`
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - AudioPlayer Implementation</summary>
|
|
|
|
**State Management**:
|
|
```typescript
|
|
interface PlayerState {
|
|
currentSong: Song | null;
|
|
isPlaying: boolean;
|
|
currentTime: number;
|
|
duration: number;
|
|
volume: number;
|
|
isMuted: boolean;
|
|
queue: Song[];
|
|
queueIndex: number;
|
|
repeatMode: "off" | "one" | "all";
|
|
isShuffled: boolean;
|
|
}
|
|
```
|
|
|
|
**Audio Element**: HTML5 `<audio>` element controlled by React state
|
|
|
|
**Time Updates**: `ontimeupdate` event updates progress bar every ~250ms
|
|
|
|
</details>
|
|
|
|
---
|
|
|
|
#### PlayerControls
|
|
Reusable control buttons for audio playback.
|
|
|
|
**Buttons**:
|
|
- Play/pause toggle
|
|
- Previous track
|
|
- Next track
|
|
- Shuffle toggle
|
|
- Repeat cycle (off → all → one)
|
|
|
|
**Location**: `components/PlayerControls.tsx`
|
|
|
|
---
|
|
|
|
#### WaveformDisplay
|
|
Visual audio waveform with interactive seeking.
|
|
|
|
**Features**:
|
|
- Canvas-based rendering
|
|
- Progress indicator overlay
|
|
- Click-to-seek functionality
|
|
- Responsive width
|
|
|
|
**Location**: `components/WaveformDisplay.tsx`
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - Waveform Rendering</summary>
|
|
|
|
**Canvas Drawing**:
|
|
1. Parse audio amplitude data (Web Audio API)
|
|
2. Draw vertical bars for each sample
|
|
3. Color code: played (blue), unplayed (gray)
|
|
4. Update on time change
|
|
|
|
**Performance**: Debounced redraw to prevent excessive rendering
|
|
|
|
</details>
|
|
|
|
---
|
|
|
|
### Card Components
|
|
|
|
Display content previews in grid layouts.
|
|
|
|
#### SongCard
|
|
Shows song thumbnail, title, artist, duration, play count.
|
|
|
|
#### AlbumCard
|
|
Shows album art, title, artist, release year, track count.
|
|
|
|
#### ArtistCard
|
|
Shows circular avatar, artist name, verified badge.
|
|
|
|
#### PlaylistCard
|
|
Shows playlist cover, title, song count, privacy indicator.
|
|
|
|
#### LabelCard
|
|
Shows label logo, name, artist count.
|
|
|
|
#### GenreBadge
|
|
Clickable colored badge with genre name.
|
|
|
|
**All card locations**: `components/*.tsx`
|
|
|
|
---
|
|
|
|
### Form Components
|
|
|
|
#### AuthForm
|
|
Unified form for login, register, password reset modes.
|
|
|
|
**Fields**:
|
|
- Email (all modes)
|
|
- Password (login, register)
|
|
- Username (register only)
|
|
- Display name (register, optional)
|
|
|
|
**Location**: `components/AuthForm.tsx`
|
|
|
|
---
|
|
|
|
#### UploadForm
|
|
Multi-field form for song uploads.
|
|
|
|
**Fields**:
|
|
- Title (required)
|
|
- Audio file URL (required)
|
|
- Cover image URL (optional)
|
|
- Duration in seconds (required)
|
|
- Album selection (dropdown)
|
|
- Genre tags (multi-select)
|
|
- Description (textarea)
|
|
- Lyrics (textarea)
|
|
- Public/private toggle
|
|
|
|
**Location**: `components/UploadForm.tsx`
|
|
|
|
---
|
|
|
|
#### ProfileForm
|
|
Edit user profile data.
|
|
|
|
**Fields**:
|
|
- Display name
|
|
- Email (read-only)
|
|
- Bio (textarea)
|
|
- Website URL
|
|
- Avatar URL
|
|
|
|
**Location**: `components/ProfileForm.tsx`
|
|
|
|
---
|
|
|
|
#### CreatePlaylistModal
|
|
Modal dialog for new playlist creation.
|
|
|
|
**Fields**:
|
|
- Title (required)
|
|
- Description (optional)
|
|
- Public/private toggle
|
|
- Cover image URL (optional)
|
|
|
|
**Location**: `components/CreatePlaylistModal.tsx`
|
|
|
|
---
|
|
|
|
### Navigation Components
|
|
|
|
#### Header
|
|
Fixed top navigation bar.
|
|
|
|
**Elements**:
|
|
- Logo (link to home)
|
|
- Nav links (Discover, Genres, Artists, Labels)
|
|
- Search bar
|
|
- User menu (if logged in)
|
|
- Login button (if logged out)
|
|
- Mobile menu toggle
|
|
|
|
**Location**: `components/Header.tsx`
|
|
|
|
---
|
|
|
|
#### NavLink
|
|
Navigation link with active state.
|
|
|
|
**Features**:
|
|
- Highlights current page
|
|
- Uses Next.js router for path matching
|
|
|
|
**Location**: `components/NavLink.tsx`
|
|
|
|
---
|
|
|
|
#### UserMenu
|
|
Dropdown menu for authenticated users.
|
|
|
|
**Links**:
|
|
- Profile
|
|
- Upload Song
|
|
- My Playlists
|
|
- Logout
|
|
|
|
**Location**: `components/UserMenu.tsx`
|
|
|
|
---
|
|
|
|
#### SearchBar
|
|
Real-time search input with suggestions.
|
|
|
|
**Features**:
|
|
- Debounced input (300ms)
|
|
- Dropdown with results preview
|
|
- Loading indicator
|
|
- Keyboard navigation (arrow keys, enter)
|
|
|
|
**Location**: `components/SearchBar.tsx`
|
|
|
|
<details>
|
|
<summary>🔧 Technical Details - Search Debouncing</summary>
|
|
|
|
```typescript
|
|
const [query, setQuery] = useState("");
|
|
const [results, setResults] = useState([]);
|
|
|
|
// Debounce search API calls
|
|
useEffect(() => {
|
|
const timer = setTimeout(async () => {
|
|
if (query.length >= 2) {
|
|
const data = await fetch(`/api/search?q=${query}`);
|
|
setResults(data);
|
|
}
|
|
}, 300);
|
|
|
|
return () => clearTimeout(timer);
|
|
}, [query]);
|
|
```
|
|
|
|
</details>
|
|
|
|
---
|
|
|
|
### Sharing Components
|
|
|
|
#### ShareButton
|
|
Button that generates shareable links.
|
|
|
|
**Actions**:
|
|
1. Click button
|
|
2. Generate share token (POST to `/api/share/*`)
|
|
3. Open ShareModal with link
|
|
|
|
**Location**: `components/ShareButton.tsx`
|
|
|
|
---
|
|
|
|
#### ShareModal
|
|
Modal displaying shareable link.
|
|
|
|
**Features**:
|
|
- Copy-to-clipboard button
|
|
- QR code (optional)
|
|
- Social media share buttons
|
|
- Click count display
|
|
|
|
**Location**: `components/ShareModal.tsx`
|
|
|
|
---
|
|
|
|
### Display Components
|
|
|
|
#### TrackList
|
|
Scrollable list of songs with metadata.
|
|
|
|
**Columns**:
|
|
- Position number
|
|
- Cover image
|
|
- Title
|
|
- Artist name
|
|
- Duration
|
|
|
|
**Actions**:
|
|
- Click to play
|
|
- Right-click context menu (add to playlist, share)
|
|
|
|
**Location**: `components/TrackList.tsx`
|
|
|
|
---
|
|
|
|
## Glossary
|
|
|
|
**API Endpoint**: A specific URL where a program can request data or send commands. Like an address where your app talks to the server.
|
|
|
|
**Album**: A collection of songs released together, like a CD or digital release.
|
|
|
|
**Artist**: A musician or band that creates and uploads music to the platform.
|
|
|
|
**Authentication**: The process of verifying who you are (usually with email and password).
|
|
|
|
**bcryptjs**: A library that scrambles passwords so they can't be read if the database is stolen.
|
|
|
|
**Cover Image**: The picture displayed for a song, album, or playlist.
|
|
|
|
**Duration**: How long a song is in minutes and seconds.
|
|
|
|
**EP**: Extended Play - a short music release with 3-7 tracks (less than an album, more than a single).
|
|
|
|
**Genre**: A category of music like Rock, Jazz, Hip-Hop, or Electronic.
|
|
|
|
**JWT (JSON Web Token)**: A secure way to remember that you're logged in without storing passwords. Like a temporary pass that expires after 7 days.
|
|
|
|
**Label**: A music organization that manages multiple artists (like a record company).
|
|
|
|
**ORM (Object-Relational Mapping)**: A tool that lets programmers work with databases using normal code instead of SQL queries. Prisma is the ORM used here.
|
|
|
|
**Play Count**: The total number of times a song has been played by all users.
|
|
|
|
**Playlist**: A custom collection of songs you create (like making a mixtape).
|
|
|
|
**Prisma**: The database tool used to manage data in this project. It ensures type safety and generates database queries automatically.
|
|
|
|
**Public/Private**: Public content is visible to everyone; private content is only visible to you.
|
|
|
|
**REST API**: A standard way for programs to communicate over the internet using HTTP requests (GET, POST, PUT, DELETE).
|
|
|
|
**Share Token**: A unique short code (like `aBc123XyZ`) used to create shareable links.
|
|
|
|
**Slug**: A URL-friendly version of a name. For example, "The Beatles" becomes "the-beatles" in URLs.
|
|
|
|
**SQLite**: A lightweight database stored in a single file. Good for development and small-to-medium projects.
|
|
|
|
**TypeScript**: A version of JavaScript that checks for errors before the code runs, making development safer.
|
|
|
|
**UUID (Universally Unique Identifier)**: A long random ID like `550e8400-e29b-41d4-a716-446655440000` that ensures every record is unique.
|
|
|
|
**Verified Badge**: A checkmark showing an artist is officially recognized (like Twitter's blue checkmark).
|
|
|
|
**Waveform**: A visual representation of audio that shows the loudness of a song over time. Looks like a series of vertical bars.
|
|
|
|
---
|
|
|
|
## Project Statistics
|
|
|
|
| Metric | Count |
|
|
|--------|-------|
|
|
| Total Files | 107 |
|
|
| TypeScript Files | 107 |
|
|
| API Endpoints | 40 |
|
|
| Database Models | 10 |
|
|
| Pages | 13 |
|
|
| UI Components | 20 |
|
|
| Type Definition Files | 4 |
|
|
| Utility Library Files | 3 |
|
|
|
|
---
|
|
|
|
## Development Workflow
|
|
|
|
This project uses the **Guardrail Workflow System** to enforce a structured development process.
|
|
|
|
### Workflow Phases
|
|
|
|
1. **Design Phase**: Plan changes and update manifest
|
|
2. **Approval Gate**: Review and approve design
|
|
3. **Implementation Phase**: Write code following approved design
|
|
4. **Review Phase**: Quality checks and validation
|
|
5. **Approval Gate**: Final approval before completion
|
|
|
|
### Quick Commands
|
|
|
|
```bash
|
|
# Start a new feature workflow
|
|
/workflow:spawn add user notifications
|
|
|
|
# Check current workflow status
|
|
/workflow:status
|
|
|
|
# Approve current phase
|
|
/workflow:approve
|
|
|
|
# Generate documentation
|
|
/eureka:index
|
|
```
|
|
|
|
See `CLAUDE.md` for full workflow documentation.
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
### For New Users
|
|
1. Explore the codebase structure in the sections above
|
|
2. Run the development server and test the UI
|
|
3. Read API endpoint documentation for integration
|
|
4. Review data models to understand relationships
|
|
|
|
### For Contributors
|
|
1. Read the workflow documentation in `CLAUDE.md`
|
|
2. Use `/workflow:spawn` to start new features
|
|
3. Follow the design-approve-implement cycle
|
|
4. Run `/workflow:review` before submitting changes
|
|
|
|
### For Operators
|
|
1. Review deployment requirements
|
|
2. Configure environment variables (JWT secret, database URL)
|
|
3. Set up file storage for audio uploads
|
|
4. Consider PostgreSQL migration for production scale
|
|
|
|
---
|
|
|
|
**Generated by Eureka Documentation System**
|
|
**Last Updated**: 2025-12-18
|