# 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.