596 lines
16 KiB
Markdown
596 lines
16 KiB
Markdown
# 🏗️ Jukebox Project Architecture
|
|
|
|
## System Overview Diagram
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
│ JUKEBOX MUSIC APPLICATION │
|
|
└─────────────────────────────────────────────────────────────────────┘
|
|
|
|
┌──────────────────────────────┐ ┌──────────────────────────────┐
|
|
│ 📱 MOBILE APP (EXPO) │ │ 🌐 WEB ADMIN PANEL │
|
|
│ React Native 0.81.5 │ │ React 19.2.0 + Vite │
|
|
└──────────────────────────────┘ └──────────────────────────────┘
|
|
│ │
|
|
│ HTTP/REST API │
|
|
│ (Bearer Token Auth) │
|
|
└────────────────┬────────────────┘
|
|
│
|
|
┌──────▼──────┐
|
|
│ 🔌 LARAVEL │
|
|
│ API │
|
|
│ Backend │
|
|
└──────┬──────┘
|
|
│
|
|
┌──────▼──────┐
|
|
│ 🗄️ MySQL │
|
|
│ Database │
|
|
└─────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Mobile App Architecture (React Native)
|
|
|
|
```
|
|
App.js (Root)
|
|
│
|
|
├─ PlayerProvider Context
|
|
│ └─ { currentTrack, isPlaying, ... }
|
|
│
|
|
└─ LibraryProvider Context
|
|
├─ albums (state from mock data)
|
|
├─ likedTracks (computed)
|
|
├─ toggleLike() (function)
|
|
│
|
|
└─ SafeAreaProvider
|
|
│
|
|
└─ AppNavigator (Stack Navigator)
|
|
│
|
|
├─ HomeScreen
|
|
│ ├─ Header (with icons)
|
|
│ ├─ Liked Tracks Preview (top 5)
|
|
│ └─ Albums Grid (2 columns)
|
|
│
|
|
├─ AlbumScreen
|
|
│ ├─ Album Info (cover, title, artist)
|
|
│ └─ TrackRow List
|
|
│ └─ TrackRow Component (with heart)
|
|
│
|
|
├─ LikedTracksScreen
|
|
│ ├─ Search Input
|
|
│ ├─ Sort Controls
|
|
│ └─ FlatList of Tracks
|
|
│
|
|
├─ LoginScreen
|
|
│ ├─ Email Input
|
|
│ ├─ Password Input
|
|
│ └─ Navigation Links
|
|
│
|
|
├─ SignUpScreen
|
|
├─ PasswordResetScreen
|
|
└─ SettingsScreen
|
|
├─ Notification Toggle
|
|
├─ Audio Quality Toggle
|
|
└─ Logout Button
|
|
|
|
MediaPlayer Component (Floating)
|
|
└─ Shows current track + play controls
|
|
```
|
|
|
|
---
|
|
|
|
## Data Flow (Current - Mock Data)
|
|
|
|
```
|
|
app.json (expo config)
|
|
│
|
|
index.js → App.js
|
|
│
|
|
├─ PlayerProvider
|
|
│ └─ { currentTrack, isPlaying }
|
|
│
|
|
└─ LibraryProvider
|
|
│
|
|
├─ useState(initialLibrary)
|
|
│ └─ src/data/library.js ◄─── MOCK DATA
|
|
│ ├─ Album 1: Swans (26 tracks)
|
|
│ └─ Album 2: Daft Punk (14 tracks)
|
|
│
|
|
├─ useMemo(likedTracks)
|
|
│ └─ Filters albums.flatMap(album.tracks).filter(liked)
|
|
│
|
|
└─ toggleLike(albumId, trackId)
|
|
└─ Updates album.tracks[].liked status
|
|
|
|
HomeScreen ──┐
|
|
AlbumScreen ├─► useLibrary() ──► Read-only access to:
|
|
LikedTracks │ • albums
|
|
LoginScreen ─┘ • likedTracks
|
|
• Call toggleLike()
|
|
```
|
|
|
|
---
|
|
|
|
## Data Flow (Future - With API Integration)
|
|
|
|
```
|
|
┌─────────────────────────────────────────────┐
|
|
│ BACKEND API (Laravel) │
|
|
├─────────────────────────────────────────────┤
|
|
│ GET /api/albums │
|
|
│ GET /api/albums/{id} │
|
|
│ GET /api/tracks │
|
|
│ POST /api/login │
|
|
│ POST /api/tracks/{id}/like │
|
|
│ GET /api/me/likes │
|
|
└─────────────────────────────────────────────┘
|
|
▲
|
|
│
|
|
LibraryContext (FUTURE)
|
|
├─ useEffect(() => {
|
|
│ fetch('/api/albums')
|
|
│ .then(data => setAlbums(data))
|
|
│ }, [])
|
|
│
|
|
├─ toggleLike() → API call instead
|
|
│
|
|
└─ All screens read from state
|
|
(same as before, but data from API)
|
|
```
|
|
|
|
---
|
|
|
|
## Component Hierarchy
|
|
|
|
```
|
|
App.js
|
|
│
|
|
└─ AppNavigator (Stack)
|
|
│
|
|
├─ HomeScreen
|
|
│ ├─ Header
|
|
│ │ ├─ Title
|
|
│ │ └─ Icon buttons (Person, Settings)
|
|
│ │
|
|
│ ├─ Pressable (navigate to LikedTracks)
|
|
│ │ └─ Section title
|
|
│ │
|
|
│ ├─ Map (liked tracks)
|
|
│ │ └─ TrackRow ✕ 5
|
|
│ │
|
|
│ └─ FlatList (albums grid)
|
|
│ └─ Album Item ✕ N
|
|
│
|
|
├─ AlbumScreen
|
|
│ ├─ Back Button
|
|
│ ├─ Album Header
|
|
│ │ ├─ Image (album cover)
|
|
│ │ ├─ Title
|
|
│ │ ├─ Artist
|
|
│ │ └─ Meta (date, label, duration)
|
|
│ │
|
|
│ └─ FlatList (tracks)
|
|
│ └─ TrackRow ✕ N
|
|
│
|
|
├─ LikedTracksScreen
|
|
│ ├─ Back Button
|
|
│ ├─ Header
|
|
│ ├─ TextInput (search)
|
|
│ ├─ Sort Controls
|
|
│ │ ├─ Dropdown (sortBy)
|
|
│ │ └─ Toggle (direction)
|
|
│ │
|
|
│ └─ FlatList
|
|
│ └─ TrackRow ✕ N
|
|
│
|
|
├─ LoginScreen
|
|
├─ SignUpScreen
|
|
├─ PasswordResetScreen
|
|
└─ SettingsScreen
|
|
|
|
└─ MediaPlayer (Floating)
|
|
├─ Track info (text)
|
|
├─ Progress bar (Slider)
|
|
│ └─ Time stamps
|
|
│
|
|
└─ Play/Pause button
|
|
```
|
|
|
|
---
|
|
|
|
## State Management Strategy
|
|
|
|
### Global State (Contexts)
|
|
|
|
```javascript
|
|
// 1. PlayerProvider (App.js)
|
|
{
|
|
currentTrack: Track | null,
|
|
isPlaying: boolean,
|
|
setCurrentTrack: (track) => void,
|
|
setIsPlaying: (bool) => void
|
|
}
|
|
|
|
// 2. LibraryProvider (LibraryContext.js)
|
|
{
|
|
albums: Album[],
|
|
likedTracks: Track[] (computed),
|
|
toggleLike: (albumId, trackId) => void
|
|
}
|
|
```
|
|
|
|
### Local State (Components)
|
|
|
|
```javascript
|
|
// HomeScreen
|
|
None (all from context)
|
|
|
|
// AlbumScreen
|
|
None (all from context & route params)
|
|
|
|
// LikedTracksScreen
|
|
{
|
|
query: string (search),
|
|
sortBy: 'dateAdded' | 'name' | 'length',
|
|
sortDir: 'asc' | 'desc',
|
|
showSortMenu: boolean
|
|
}
|
|
|
|
// LoginScreen
|
|
{
|
|
email: string,
|
|
password: string
|
|
}
|
|
|
|
// SettingsScreen
|
|
{
|
|
notificationsEnabled: boolean,
|
|
highQuality: boolean
|
|
}
|
|
|
|
// MediaPlayer
|
|
{
|
|
isPlaying: boolean,
|
|
progress: number (0-100)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Navigation Stack
|
|
|
|
```
|
|
┌────────────────────────────────────┐
|
|
│ React Navigation Stack │
|
|
├────────────────────────────────────┤
|
|
│ Navigator: createNativeStackNavigator()
|
|
│ Options: headerShown = false
|
|
│
|
|
├─ Screen: "Home" → HomeScreen
|
|
├─ Screen: "LikedTracks" → LikedTracksScreen
|
|
├─ Screen: "Album" → AlbumScreen
|
|
│ params: { album: Album }
|
|
├─ Screen: "SignUp" → SignUpScreen
|
|
├─ Screen: "Login" → LoginScreen
|
|
├─ Screen: "PasswordReset"→ PasswordResetScreen
|
|
└─ Screen: "Settings" → SettingsScreen
|
|
```
|
|
|
|
### Navigation Methods
|
|
|
|
```javascript
|
|
// From any screen with useNavigation hook:
|
|
|
|
navigation.navigate('Album', { album })
|
|
navigation.navigate('LikedTracks')
|
|
navigation.navigate('Login')
|
|
navigation.navigate('Settings')
|
|
navigation.goBack()
|
|
```
|
|
|
|
---
|
|
|
|
## Admin Panel Architecture (React Web)
|
|
|
|
```
|
|
admin_panel/
|
|
│
|
|
├─ main.jsx
|
|
│ └─ ReactDOM.createRoot()
|
|
│ │
|
|
│ └─ BrowserRouter
|
|
│ │
|
|
│ └─ App.jsx
|
|
│ │
|
|
│ ├─ AuthProvider
|
|
│ │ ├─ token (from localStorage)
|
|
│ │ ├─ user (from localStorage)
|
|
│ │ ├─ login() function
|
|
│ │ └─ logout() function
|
|
│ │
|
|
│ └─ Routes
|
|
│ │
|
|
│ ├─ Route: "/login"
|
|
│ │ └─ LoginPage
|
|
│ │
|
|
│ └─ ProtectedRoute (requires token)
|
|
│ │
|
|
│ ├─ Route: "/albums"
|
|
│ │ └─ AlbumsPage (stub)
|
|
│ │
|
|
│ ├─ Route: "/albums/:albumId/tracks"
|
|
│ │ └─ AlbumTracksPage (stub)
|
|
│ │
|
|
│ └─ Route: "/users"
|
|
│ └─ UsersPage (stub)
|
|
```
|
|
|
|
---
|
|
|
|
## API Integration Layer
|
|
|
|
### Admin Panel API Client (`services/api.js`)
|
|
|
|
```javascript
|
|
const api = {
|
|
// Albums (all return Promise)
|
|
getAlbums(), // GET /albums
|
|
getAlbumById(id), // GET /albums/{id}
|
|
addAlbum(data), // POST /albums
|
|
updateAlbum(id, data), // PUT /albums/{id}
|
|
deleteAlbum(id), // DELETE /albums/{id}
|
|
|
|
// Tracks
|
|
addTrack(data), // POST /tracks
|
|
updateTrack(id, data), // PUT /tracks/{id}
|
|
deleteTrack(id), // DELETE /tracks/{id}
|
|
reorderTracks(albumId, positions),
|
|
|
|
// Upload
|
|
uploadImage(file), // POST /upload/image
|
|
uploadAudio(file), // POST /upload/audio
|
|
|
|
// Artists & Genres
|
|
getArtists(), addArtist(),
|
|
getGenres(), addGenre(),
|
|
|
|
// Users (admin only)
|
|
getUsers(),
|
|
updateUser(id, data),
|
|
deleteUser(id)
|
|
}
|
|
```
|
|
|
|
### Authentication Flow
|
|
|
|
```
|
|
1. User inputs email/password
|
|
↓
|
|
2. POST /api/login
|
|
├─ Response: { token: "1|abc...", user: {...} }
|
|
↓
|
|
3. Store in localStorage
|
|
├─ token: "1|abc..."
|
|
├─ user: {...}
|
|
↓
|
|
4. Include in all requests
|
|
└─ Header: "Authorization: Bearer 1|abc..."
|
|
|
|
5. On 401 response
|
|
└─ Clear localStorage & redirect to /login
|
|
```
|
|
|
|
---
|
|
|
|
## Database Schema (Simplified)
|
|
|
|
```
|
|
Users
|
|
├─ id (PK)
|
|
├─ name
|
|
├─ email (unique)
|
|
├─ password_hash
|
|
├─ role_id (FK)
|
|
├─ created_at
|
|
└─ updated_at
|
|
├─ has_many: Likes (through likes table)
|
|
└─ belongs_to: Role
|
|
|
|
Albums
|
|
├─ id (PK)
|
|
├─ title
|
|
├─ cover_path
|
|
├─ release_date
|
|
├─ duration_seconds
|
|
├─ label_id (FK)
|
|
└─ has_many: Tracks
|
|
|
|
Tracks
|
|
├─ id (PK)
|
|
├─ title
|
|
├─ file_path
|
|
├─ duration_seconds
|
|
├─ album_id (FK)
|
|
├─ belongs_to_many: Artists
|
|
├─ belongs_to_many: Genres
|
|
└─ belongs_to_many: Users (likes table)
|
|
|
|
Artists
|
|
├─ id (PK)
|
|
├─ name
|
|
├─ label_id (FK)
|
|
└─ belongs_to_many: Tracks
|
|
|
|
Genres
|
|
├─ id (PK)
|
|
├─ name
|
|
└─ belongs_to_many: Tracks
|
|
|
|
Labels
|
|
├─ id (PK)
|
|
├─ name
|
|
├─ has_many: Artists
|
|
└─ has_many: Albums
|
|
|
|
Roles
|
|
├─ id (PK)
|
|
├─ name ('admin' or 'user')
|
|
└─ has_many: Users
|
|
```
|
|
|
|
---
|
|
|
|
## Dependency Tree
|
|
|
|
```
|
|
Mobile App (React Native)
|
|
├─ react 19.1.0
|
|
├─ react-native 0.81.5
|
|
├─ expo 54.0.33
|
|
│ ├─ expo-linear-gradient
|
|
│ ├─ expo-vector-icons
|
|
│ ├─ expo-status-bar
|
|
│ ├─ expo-av (NOT YET INSTALLED - needed for audio)
|
|
│ └─ ...
|
|
├─ @react-navigation/native 7.1.31
|
|
│ ├─ @react-navigation/native-stack 7.14.2
|
|
│ └─ @react-navigation/stack 7.8.2
|
|
├─ react-native-reanimated 4.1.1
|
|
├─ react-native-safe-area-context 5.6.0
|
|
├─ rn-inkpad 1.1.0
|
|
└─ react-native-vector-icons 10.3.0
|
|
|
|
Admin Panel (React Web)
|
|
├─ react 19.2.0
|
|
├─ react-dom 19.2.0
|
|
├─ react-router-dom 7.13.1
|
|
├─ vite 7.3.1
|
|
└─ tailwindcss 4.0.0
|
|
|
|
Backend (Laravel)
|
|
├─ laravel/framework
|
|
├─ laravel/sanctum (authentication)
|
|
├─ composer (PHP dependency manager)
|
|
└─ mysql (database)
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Considerations
|
|
|
|
### Current Issues
|
|
```
|
|
❌ No pagination - all albums/tracks loaded at once
|
|
❌ No caching - re-fetches on every mount
|
|
❌ No image optimization - full-res images
|
|
❌ No lazy loading - all screens bundled
|
|
❌ No code splitting - entire app in one JS bundle
|
|
```
|
|
|
|
### Optimization Opportunities
|
|
```
|
|
✅ Add pagination to API (limit: 20, offset)
|
|
✅ Implement React Query / SWR for caching
|
|
✅ Use Image.cache() or similar for images
|
|
✅ Code split screens with React.lazy()
|
|
✅ Use FlatList/VirtualizedList keyExtractor efficiently
|
|
✅ Memoize expensive computations (useMemo)
|
|
✅ Debounce search input
|
|
✅ Cache API responses in AsyncStorage
|
|
```
|
|
|
|
---
|
|
|
|
## Deployment Architecture (Future)
|
|
|
|
```
|
|
┌─────────────────────────────────────────┐
|
|
│ Apple App Store / Google Play │
|
|
│ (Built from React Native) │
|
|
└──────────────────┬──────────────────────┘
|
|
│
|
|
│ HTTPS API calls
|
|
▼
|
|
┌─────────────────────────────────────────┐
|
|
│ Production API Server (Laravel) │
|
|
├─────────────────────────────────────────┤
|
|
│ - Environment: Ubuntu/CentOS Linux │
|
|
│ - Server: Nginx + PHP-FPM │
|
|
│ - Database: MySQL 8.0 │
|
|
│ - Cache: Redis (optional) │
|
|
│ - SSL: Let's Encrypt HTTPS │
|
|
└────────────┬────────────────────────────┘
|
|
│
|
|
▼
|
|
┌────────────────┐
|
|
│ MySQL DB │
|
|
└────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Security Considerations
|
|
|
|
### Current
|
|
```
|
|
✅ Bearer token authentication (Sanctum)
|
|
✅ Password hashing
|
|
✅ HTTPS ready (needs SSL cert in production)
|
|
```
|
|
|
|
### Missing
|
|
```
|
|
❌ CORS configuration (using Sanctum defaults)
|
|
❌ Rate limiting
|
|
❌ API request validation/sanitization
|
|
❌ SQL injection prevention (use Eloquent ORM - good!)
|
|
❌ XSS prevention headers
|
|
❌ Token refresh mechanism
|
|
❌ Logout from all devices feature
|
|
```
|
|
|
|
---
|
|
|
|
## Integration Checklist
|
|
|
|
```
|
|
PHASE 1: API Integration
|
|
[ ] Connect HomeScreen to GET /api/albums
|
|
[ ] Connect AlbumScreen to GET /api/albums/{id}
|
|
[ ] Connect LoginScreen to POST /api/login
|
|
[ ] Store token & user in AsyncStorage
|
|
[ ] Add error handling for API failures
|
|
|
|
PHASE 2: Authentication
|
|
[ ] Implement token refresh logic
|
|
[ ] Add logout functionality
|
|
[ ] Persist login state across app restart
|
|
[ ] Redirect to login on 401 response
|
|
|
|
PHASE 3: Audio Playback
|
|
[ ] Install expo-av package
|
|
[ ] Implement TrackPlayer service
|
|
[ ] Connect MediaPlayer to real playback
|
|
[ ] Handle audio focus on mobile
|
|
|
|
PHASE 4: Admin Panel
|
|
[ ] Implement AlbumsPage.jsx
|
|
[ ] Implement AlbumTracksPage.jsx
|
|
[ ] Implement UsersPage.jsx
|
|
[ ] Add file upload handling
|
|
|
|
PHASE 5: Polish
|
|
[ ] Add loading indicators
|
|
[ ] Add error boundaries
|
|
[ ] Implement search/filter on backend
|
|
[ ] Add pagination
|
|
[ ] Add offline mode
|
|
```
|
|
|
|
---
|
|
|
|
**Architecture Version**: 1.0
|
|
**Last Updated**: May 12, 2026
|
|
**Status**: Pre-integration phase
|