Big commit whatever
This commit is contained in:
+129
@@ -0,0 +1,129 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user