# Laravel Jukebox API - Complete Documentation

## Project Overview
- **Framework**: Laravel 13.0
- **PHP Version**: ^8.3
- **Database**: MySQL (configured via Docker at 172.17.0.1:3306)
- **Authentication**: Laravel Sanctum 4.0 (Token-based API authentication)
- **Purpose**: Music streaming/management API with role-based access control

---

## 1. API ROUTES

### Location: `/routes/api.php`

#### Public Routes (No Authentication Required)
```
POST   /register              - User registration
POST   /login                 - User login
```

#### Authenticated Routes (Requires Sanctum token)

**Auth Endpoints:**
```
POST   /logout                - Logout user
GET    /me                    - Get current user info
```

**User Likes (Authenticated):**
```
GET    /me/likes              - Get user's liked tracks
POST   /tracks/{track}/like   - Like a track
DELETE /tracks/{track}/like   - Unlike a track
```

**Browse Routes (Read-only for authenticated users):**
```
GET    /labels                - List all labels
GET    /labels/{label}        - Get label details
GET    /genres                - List all genres
GET    /genres/{genre}        - Get genre details
GET    /artists               - List all artists
GET    /artists/{artist}      - Get artist details
GET    /albums                - List all albums
GET    /albums/{album}        - Get album details
GET    /tracks                - List all tracks
GET    /tracks/{track}        - Get track details
```

**Admin-Only Routes (Requires 'admin' role):**
```
POST   /labels                - Create label
PUT    /labels/{label}        - Update label
DELETE /labels/{label}        - Delete label

POST   /genres                - Create genre
PUT    /genres/{genre}        - Update genre
DELETE /genres/{genre}        - Delete genre

POST   /artists               - Create artist
PUT    /artists/{artist}      - Update artist
DELETE /artists/{artist}      - Delete artist

POST   /albums                - Create album
PUT    /albums/{album}        - Update album
DELETE /albums/{album}        - Delete album

POST   /tracks                - Create track
PUT    /tracks/{track}        - Update track
DELETE /tracks/{track}        - Delete track

GET    /users                 - List all users
GET    /users/{user}          - Get user details
PUT    /users/{user}          - Update user
DELETE /users/{user}          - Delete user
```

#### Web Routes (`routes/web.php`)
- Minimal: Single `GET /` returning welcome view (for blade/HTML views)

---

## 2. CONTROLLERS & METHODS

### AuthController (`app/Http/Controllers/AuthController.php`)
**Methods:**
- `register(Request $request): JsonResponse` - Handles user registration
  - Validates: name, email, password (min 8 chars, confirmed)
  - Creates user with default 'user' role
  - Returns token + user object
  - HTTP 201

- `login(Request $request): JsonResponse` - Handles user login
  - Validates: email, password
  - Returns token + user object if credentials valid
  - Throws ValidationException if invalid

- `logout(Request $request): JsonResponse` - Deletes current access token
  - Returns success message

- `me(Request $request): JsonResponse` - Returns current authenticated user
  - Loads user role relationship

---

### UserController (`app/Http/Controllers/UserController.php`)
**Methods:**
- `index(): JsonResponse` - List all users with roles
- `show(User $user): JsonResponse` - Get single user with role
- `update(Request $request, User $user): JsonResponse`
  - Validates: name, email (unique), password, role_id
  - Returns updated user with role
- `destroy(User $user): JsonResponse` - Delete user
  - Returns HTTP 204

---

### ArtistController (`app/Http/Controllers/ArtistController.php`)
**Methods:**
- `index(): JsonResponse` - List artists with label relationship
- `show(Artist $artist): JsonResponse` - Get artist with label, tracks, genres
- `store(Request $request): JsonResponse`
  - Validates: name, cover_path (nullable), release_date (nullable), label_id, duration
  - Returns HTTP 201
- `update(Request $request, Artist $artist): JsonResponse` - Update artist
- `destroy(Artist $artist): JsonResponse` - Delete artist

---

### AlbumController (`app/Http/Controllers/AlbumController.php`)
**Methods:**
- `index(): JsonResponse` - List albums with labels
- `show(Album $album): JsonResponse` - Get album with label, tracks, artists, genres
- `store(Request $request): JsonResponse`
  - Validates: title, cover_path, release_date, duration_seconds, type (enum: album|single|ep), label_id
  - Returns HTTP 201
- `update(Request $request, Album $album): JsonResponse` - Update album
- `destroy(Album $album): JsonResponse` - Delete album

---

### TrackController (`app/Http/Controllers/TrackController.php`)
**Methods:**
- `index(): JsonResponse` - List tracks with album, artists, genres
- `show(Track $track): JsonResponse` - Get track with album.label, artists, genres
- `store(Request $request): JsonResponse`
  - Validates: title, file_path, duration_seconds (nullable), album_id (nullable), artist_ids (array), genre_ids (array)
  - Syncs artist and genre relationships
  - Returns HTTP 201
- `update(Request $request, Track $track): JsonResponse`
  - Syncs artist and genre relationships
- `destroy(Track $track): JsonResponse` - Delete track

---

### LikeController (`app/Http/Controllers/LikeController.php`)
**Methods:**
- `like(Request $request, Track $track): JsonResponse`
  - Uses `syncWithoutDetaching()` to add like without removing existing ones
- `unlike(Request $request, Track $track): JsonResponse`
  - Uses `detach()` to remove like
- `index(Request $request): JsonResponse`
  - Returns user's liked tracks with album, artists, genres relationships

---

### GenreController (`app/Http/Controllers/GenreController.php`)
**Methods:**
- `index(): JsonResponse` - List all genres
- `show(Genre $genre): JsonResponse` - Get single genre
- `store(Request $request): JsonResponse` - Create genre (validates: name, max 100 chars)
- `update(Request $request, Genre $genre): JsonResponse` - Update genre
- `destroy(Genre $genre): JsonResponse` - Delete genre

---

### LabelController (`app/Http/Controllers/LabelController.php`)
**Methods:**
- `index(): JsonResponse` - List all labels
- `show(Label $label): JsonResponse` - Get single label
- `store(Request $request): JsonResponse` - Create label (validates: name, max 100 chars)
- `update(Request $request, Label $label): JsonResponse` - Update label
- `destroy(Label $label): JsonResponse` - Delete label

---

## 3. MODELS & RELATIONSHIPS

### User Model
**Attributes:** id, name, email, password (hashed), role_id, timestamps
**Fillable:** name, email, password, role_id
**Hidden:** password, remember_token
**Traits:** HasApiTokens, HasFactory, Notifiable

**Relationships:**
- `role()`: BelongsTo(Role) - User's role
- `likes()`: BelongsToMany(Track, 'likes') - Tracks user has liked

---

### Role Model
**Attributes:** id, name, timestamps
**Fillable:** name

**Relationships:**
- `users()`: HasMany(User) - Users with this role

---

### Artist Model
**Attributes:** id, name, cover_path, release_date, label_id, duration, timestamps
**Fillable:** name, cover_path, release_date, label_id, duration

**Relationships:**
- `label()`: BelongsTo(Label) - Artist's label
- `tracks()`: BelongsToMany(Track, 'artist_track') - All tracks by artist

---

### Album Model
**Attributes:** id, title, cover_path, release_date, duration_seconds, type (enum), label_id, timestamps
**Fillable:** title, cover_path, release_date, duration_seconds, type, label_id

**Relationships:**
- `label()`: BelongsTo(Label) - Album's label
- `tracks()`: HasMany(Track) - All tracks in album

---

### Track Model
**Attributes:** id, title, file_path, duration_seconds, album_id, timestamps
**Fillable:** title, file_path, duration_seconds, album_id

**Relationships:**
- `album()`: BelongsTo(Album) - Album containing track
- `artists()`: BelongsToMany(Artist, 'artist_track') - Contributing artists
- `genres()`: BelongsToMany(Genre, 'track_genre') - Track genres
- `likedBy()`: BelongsToMany(User, 'likes') - Users who liked track

---

### Genre Model
**Attributes:** id, name, timestamps
**Fillable:** name

**Relationships:**
- `tracks()`: BelongsToMany(Track, 'track_genre') - Tracks in genre

---

### Label Model
**Attributes:** id, name, timestamps
**Fillable:** name

**Relationships:**
- `artists()`: HasMany(Artist) - Artists on label
- `albums()`: HasMany(Album) - Albums on label

---

## 4. DATABASE SCHEMA & MIGRATIONS

### Tables Created (in order)

**1. roles**
- id (PRIMARY)
- name (VARCHAR)
- timestamps

**2. genres**
- id (PRIMARY)
- name (VARCHAR 100)
- timestamps

**3. labels**
- id (PRIMARY)
- name (VARCHAR 100)
- timestamps

**4. users**
- id (PRIMARY)
- name (VARCHAR)
- email (VARCHAR)
- password (VARCHAR)
- role_id (FOREIGN KEY → roles.id)
- timestamps

**5. artists**
- id (PRIMARY)
- name (VARCHAR 255)
- cover_path (VARCHAR 255, nullable)
- release_date (DATETIME, nullable)
- label_id (FOREIGN KEY → labels.id, nullable)
- duration (INTEGER, nullable)
- timestamps

**6. albums**
- id (PRIMARY)
- title (VARCHAR 255)
- cover_path (VARCHAR 255, nullable)
- release_date (DATETIME, nullable)
- duration_seconds (INTEGER, nullable)
- type (ENUM: 'album', 'single', 'ep', default: 'album')
- label_id (FOREIGN KEY → labels.id, nullable)
- timestamps

**7. tracks**
- id (PRIMARY)
- title (VARCHAR 255)
- file_path (VARCHAR 255)
- duration_seconds (INTEGER, nullable)
- album_id (FOREIGN KEY → albums.id, nullable)
- timestamps

**8. artist_track** (pivot table)
- artist_id (FOREIGN KEY → artists.id)
- track_id (FOREIGN KEY → tracks.id)
- PRIMARY KEY: (artist_id, track_id)
- timestamps

**9. track_genre** (pivot table)
- track_id (FOREIGN KEY → tracks.id)
- genre_id (FOREIGN KEY → genres.id)
- PRIMARY KEY: (track_id, genre_id)
- timestamps

**10. likes** (pivot table)
- user_id (FOREIGN KEY → users.id)
- track_id (FOREIGN KEY → tracks.id)
- PRIMARY KEY: (user_id, track_id)
- timestamps

**11. personal_access_tokens** (Sanctum)
- id (PRIMARY)
- tokenable_id, tokenable_type (polymorphic)
- name (TEXT)
- token (VARCHAR 64, UNIQUE)
- abilities (TEXT, nullable)
- last_used_at (TIMESTAMP, nullable)
- expires_at (TIMESTAMP, nullable, indexed)
- timestamps

---

## 5. AUTHENTICATION & AUTHORIZATION

### Sanctum Configuration (`config/sanctum.php`)

**Stateful Domains:**
- localhost, localhost:3000, 127.0.0.1, 127.0.0.1:8000, ::1
- Plus current application URL

**Guard:** web (session guard, though API uses bearer tokens)

**Expiration:** null (tokens don't expire automatically)

**Token Prefix:** env('SANCTUM_TOKEN_PREFIX', '')

**Middleware:**
- authenticate_session: Laravel\Sanctum\Http\Middleware\AuthenticateSession
- encrypt_cookies: Illuminate\Cookie\Middleware\EncryptCookies
- validate_csrf_token: Illuminate\Foundation\Http\Middleware\ValidateCsrfToken

### Auth Configuration (`config/auth.php`)

**Default Guard:** web (session-based)

**User Provider:** Eloquent (uses App\Models\User)

**Password Broker:** users (uses users table)

**Token Expiry:** 60 minutes

### Custom Middleware

**EnsureAdmin** (`app/Http/Middleware/EnsureAdmin.php`)
- Checks if user's role name is 'admin'
- Returns 403 Forbidden if not admin
- Applied to routes group via 'admin' alias in `bootstrap/app.php`

---

## 6. SEEDERS & FACTORIES

### DatabaseSeeder (`database/seeders/DatabaseSeeder.php`)
Currently creates a single test user:
```
Name: Test User
Email: test@example.com
Password: Uses UserFactory default
```

The seeder is commented to create 10 users but currently just creates 1.

### UserFactory (`database/factories/UserFactory.php`)
**Attributes Generated:**
- name: fake()->name()
- email: fake()->unique()->safeEmail()
- email_verified_at: now()
- password: Hash::make('password')
- remember_token: Str::random(10)

**Methods:**
- `unverified()`: Sets email_verified_at to null

**Note:** No factory exists for other models (Artist, Album, Track, Genre, Label)

---

## 7. CORS CONFIGURATION

**Status:** No explicit CORS configuration found

**Implications:**
- No `config/cors.php` file
- Sanctum handles CORS implicitly via stateful domains configuration
- Likely relying on Laravel default CORS handling or missing CORS setup
- Frontend on localhost:3000 should work (listed in stateful domains)

**Recommendation:** May need explicit CORS middleware for production cross-origin requests

---

## 8. RESOURCES & TRANSFORMERS

**Status:** No JSON:API Resources or Transformers found

**Current Approach:**
- Direct Eloquent model casting to JSON
- Raw `response()->json()` returns from controllers
- Relationships loaded via `load()` and `with()` methods
- No API resource classes (Laravel Resource classes) implemented

**Data Transformation:**
- Manual eager loading in controllers
- Example: `$artist->load(['label', 'tracks.album', 'tracks.genres'])`

---

## 9. ENVIRONMENT CONFIGURATION

### .env (Development)
```
APP_NAME=Laravel
APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost

DB_CONNECTION=mysql
DB_HOST=172.17.0.1
DB_PORT=3306
DB_DATABASE=jukebox
DB_USERNAME=jukebox_admin
DB_PASSWORD=Super

SESSION_DRIVER=database
CACHE_STORE=database
QUEUE_CONNECTION=database
```

### Key Settings
- Debug mode enabled (local)
- MySQL via Docker (172.17.0.1)
- Database-backed sessions
- Database-backed queue
- Database-backed cache

---

## 10. DEPENDENCIES

**Core:**
- laravel/framework: ^13.0
- laravel/sanctum: ^4.0
- laravel/tinker: ^3.0

**Dev:**
- fakerphp/faker: ^1.23
- laravel/pail: ^1.2.5
- laravel/pint: ^1.27
- mockery/mockery: ^1.6
- nunomaduro/collision: ^8.6
- phpunit/phpunit: ^12.5.12

---

## 11. KEY OBSERVATIONS & NOTES

### Strengths
1. **Clean Role-Based Architecture** - Admin/User separation via middleware
2. **Proper Eloquent Relationships** - All pivot tables correctly implemented
3. **Token-Based Auth** - Sanctum for stateless API authentication
4. **Comprehensive Data Model** - Well-structured music catalog schema

### Areas for Enhancement
1. **No Resources/Transformers** - Consider Laravel API Resources for consistent responses
2. **No CORS Config** - Explicit CORS setup recommended for production
3. **Limited Factories** - Only UserFactory; others could enable better testing
4. **No API Documentation** - Consider OpenAPI/Swagger docs
5. **No Request DTOs** - Could use Form Requests for centralized validation
6. **No Response Standardization** - No consistent error/success response format
7. **No Pagination** - List endpoints return all results (performance concern)
8. **No Query Filtering** - No search/filter capabilities on GET endpoints
9. **No Rate Limiting** - No API rate limiting configured
10. **Minimal Seeding** - Only test user; could seed sample data

### Security Considerations
1. Password hashing properly handled via Laravel
2. CSRF protection configured (though API uses tokens)
3. Access control via admin middleware is solid
4. Foreign key constraints prevent orphaned records
5. Role-based authorization working correctly

### API Response Format
Current format (no wrapper):
```json
{
  "id": 1,
  "name": "Track Name",
  "duration_seconds": 240,
  "created_at": "2026-04-23T...",
  "updated_at": "2026-04-23T..."
}
```

Could benefit from standardized wrapper:
```json
{
  "success": true,
  "data": { ... },
  "message": "Success"
}
```

---

## 12. TESTING STRUCTURE

**Test Framework:** PHPUnit 12.5.12

**Test Directories:**
- `tests/Feature/` - Feature tests
- `tests/Unit/` - Unit tests
- `tests/TestCase.php` - Base test class

**Example Tests:** ExampleTest.php files (placeholder)

**No comprehensive tests currently implemented**

---

## Summary

This is a **music streaming/library API** built with Laravel 13, featuring:
- ✅ User registration & login with Sanctum tokens
- ✅ Role-based access control (admin/user)
- ✅ Full CRUD for Artists, Albums, Tracks, Genres, Labels
- ✅ User favorites/likes system
- ✅ Proper database schema with relationships
- ⚠️ Minimal documentation, testing, and optimization
- ⚠️ Missing common API best practices

Ready for development and enhancement with proper testing, documentation, and optimization work.
