# 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
