Files
2026-06-04 12:44:22 +02:00

8.8 KiB

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