565 lines
16 KiB
Markdown
565 lines
16 KiB
Markdown
# 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.
|