# Jukebox — Viva Cheat Sheet --- ## 1. The big picture (say this as your opener) > "The project has three parts: a React Native mobile app built with Expo, a React + Vite admin panel, and a Laravel REST API backed by a MySQL database. The two front-ends never talk to each other — they each make HTTP requests to the API, which is the single source of truth. The mobile app is the consumer side (browse albums, like tracks); the admin panel is the content-management side (create/edit/delete albums, tracks, users, upload audio and cover art)." --- ## 2. Mobile app > "The mobile app is layered. `api.js` is the only file that knows the server exists — it wraps every `fetch` call, attaches the Bearer token, and handles 401s. Two React Contexts hold app-wide state: `AuthContext` for the logged-in user and token (persisted in AsyncStorage), and `LibraryContext` for albums and liked tracks. Screens consume those contexts through custom hooks (`useAuth`, `useLibrary`) and render reusable components like `TrackRow`. React Navigation handles screen transitions." **Key details:** - `10.0.2.2` = Android emulator alias for the host machine's localhost (emulator's own `localhost` is itself, not your PC) - `mapAlbum` = adapter that translates API field names (`cover_path`, `duration_seconds`, nested artists/genres) into the shape the UI expects - `likedTrackIds` is a **Set** — O(1) lookup for "is this track liked?" - `library.js` in `src/data/` = dead code, leftover mock data from before the API existed **What's unfinished:** - `MediaPlayer` is a UI mockup — hardcoded text, no audio engine, play button only flips a local boolean - `PlayerProvider` context exists but nothing ever calls `setCurrentTrack` - To finish: add `expo-av`, load `tracks.file_path` URL, wire tap → `setCurrentTrack` → player - Mobile has no `ProtectedRoute` — unauthenticated users aren't redirected to login (unlike the admin panel) --- ## 3. The three hooks | Hook | One-liner | |---|---| | `useState` | Gives a component a value that persists across renders; calling the setter updates it and triggers a redraw | | `useEffect` | Runs side-effect code (fetch, storage) after render; `[]` = once on mount, `[x]` = re-run when `x` changes | | `useContext` | Lets a deeply-nested component read shared state from a Provider above it, skipping prop-drilling | **Prop-drilling** = the problem Context solves: passing a prop through intermediate components that don't use it just to reach a deep one. --- ## 4. Database > "It's a normalized MySQL schema. Reference data (roles, labels, genres) live in their own tables. Core entities are users, albums, artists, tracks. One-to-many relationships use foreign keys on the 'many' side (a track has one `album_id`; an album has many tracks). Three many-to-many relationships use junction tables — `likes` (users↔tracks), `artist_track`, `track_genre` — each with a composite primary key to prevent duplicates." **Relationship types:** - **1:N** — FK on the many side: `tracks.album_id → albums.id` - **M:N** — junction table: `likes (user_id, track_id)`, `artist_track`, `track_genre` - Composite PK on `likes (user_id, track_id)` = **a user cannot like the same track twice** (enforced at DB level) **ON DELETE rules:** | Rule | Where | Why | |---|---|---| | `RESTRICT` | users → roles | Can't delete a role while users still have it | | `SET NULL` | tracks → albums | Delete an album, tracks survive with `album_id = null` | | `CASCADE` | all junction tables | A like/link is meaningless without both sides — delete it too | **Gotcha:** `script.sql` has no `position` column on `tracks` — the file is outdated. The live DB has `position`; the admin panel sets it and the mobile app sorts by it. Would fix by regenerating `script.sql` from the live schema. --- ## 5. API > "It's a RESTful API built with Laravel resource controllers, so every entity — labels, genres, artists, albums, tracks, users — exposes the standard list/show/create/update/delete routes using GET/POST/PUT/DELETE. Auth uses Laravel Sanctum: register and login are the only public endpoints and return a Bearer token; every other route requires that token in the Authorization header. Two privilege levels: any logged-in user can read the catalog and like tracks; only admins can create/update/delete catalog items or manage users." **Key endpoints:** ``` POST /login → { token, user } (public) POST /register → { token, user } (public) GET /me → current user (auth) GET /albums → album list (auth) POST /tracks → create track (admin) POST /tracks/{id}/like → insert likes row (auth) DELETE /tracks/{id}/like → delete likes row (auth) GET /me/likes → liked tracks list (auth) POST /upload/audio → save file, return path (admin) ``` **Create track body:** `{ title, file_path, duration_seconds, album_id, artist_ids: [...], genre_ids: [...] }` — `artist_ids`/`genre_ids` are arrays → server inserts one row per ID into junction tables. --- ## 6. Admin panel > "The admin panel is a React + Vite SPA using react-router for navigation. Same architecture as mobile — an AuthContext storing the Sanctum token in localStorage, and an api.js service. Routes are guarded by ProtectedRoute which checks for a token, but real authorization is enforced server-side — the API rejects non-admin tokens with 403. The panel handles full CRUD on albums/tracks plus file uploads for audio and cover art." **Key details:** - `localStorage` (admin) vs `AsyncStorage` (mobile) — same concept, different platform APIs; localStorage is synchronous so the token can be read directly in `useState` - Track upload = **two API calls**: `POST /upload/audio` (saves file, returns path), then `POST /tracks` (saves DB row with that path). Binary never touches the DB. - `onPickFile` creates an in-memory `Audio` object to **auto-read duration** from the file's metadata — no manual input needed - `isAdmin` is computed in AuthContext but never used to block UI — that's intentional: *front-end guards are UX, server middleware is the real security* - `String(user.id).slice(0, 8)` is dead code — IDs are integers, not UUIDs --- ## 7. End-to-end auth flow > "When a user logs in, the LoginPage calls `login()` from AuthContext, which POSTs email and password to `/api/login`. Laravel verifies the password against its bcrypt hash in the DB and returns a Sanctum token plus the user object. The context stores the token in two places — localStorage/AsyncStorage so it survives restarts, and React state so the UI updates. Setting the token in state triggers a cascade: ProtectedRoute unlocks, a useEffect calls `/me` to re-validate, and on mobile the LibraryProvider's useEffect sees the new token and auto-loads albums and likes. From then on every request attaches the token as a Bearer header — that's how the stateless API authenticates each call and checks the user's role. On startup the stored token is read and `/me` is called to restore the session; a 401 response wipes storage and redirects to login. Logout revokes the token server-side and clears both storage and state." **Status codes:** - **401 Unauthorized** = bad or missing token ("I don't know who you are") - **403 Forbidden** = valid token, wrong role ("I know you, but you can't do this") --- ## 8. Key vocabulary — drop these terms | Term | Use it when talking about… | |---|---| | Prop-drilling | Why you used Context instead of passing token through every component | | Junction table | `likes`, `artist_track`, `track_genre` — how M:N works | | Composite primary key | `(user_id, track_id)` on `likes` — prevents duplicate likes | | Normalization | Why labels, genres, roles are in their own tables | | Bearer token | How every request proves identity to the stateless API | | Stateless | Each request is self-contained — server has no memory of prior requests | | Laravel Sanctum | The auth package that issues and verifies the tokens | | Adapter / mapAlbum | Translates API response shape into UI shape | | `expo-av` | The library needed to add real audio playback | | ON DELETE CASCADE/RESTRICT/SET NULL | FK behaviour when a parent row is deleted | --- ## 9. The five things to volunteer before the teacher finds them 1. **`library.js`** in `src/data/` is dead code — old mock data, no longer imported 2. **MediaPlayer is a mockup** — hardcoded placeholder, no real playback wired up 3. **Mobile has no ProtectedRoute** — auth context and API are wired, but screens don't redirect unauthenticated users to login 4. **`script.sql` is outdated** — missing `position` column on tracks; would regenerate from live DB 5. **`getAlbumById`** in mobile `api.js` is defined but never called — the app finds albums from the already-loaded list in memory