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

16 KiB

🏗️ 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)

// 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)

// 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

// 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)

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