Files
jukebox/ARCHITECTURE.md
T
2026-06-04 12:44:22 +02:00

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