Architecture Overview¶
This page describes the parts of the Melodex architecture that are most relevant to the Spotify Playlist Export feature and explains how they influenced the testing approach.
System context¶
- Frontend: React (
Vite) app that renders rankings, export UI, and progress/error states. - Backend: Express application (
app.js) deployed on Elastic Beanstalk. - Data:
MongoDB Atlaswithuser_songsand ranking metadata. - External services:
Spotify Web APIfor playlist creation and track addition.Deezerfor song metadata, preview URLs, and album art.
- Auth:
Cognitofor Melodex user accounts.- Spotify OAuth (handled by
AuthController) for playlist permissions.
The export feature sits at the intersection of all of these.
Export pipeline¶
The backend export pipeline is implemented primarily in:
routes/api.js(route wiring)utils/exportWorker.js(core pipeline)utils/mappingService.js(mapping Deezer-backed records to Spotify track URIs)utils/spotifyClient.js(thin client for Spotify API calls)utils/errorContract.js(shared error and envelope contracts)
At a conceptual level, the pipeline is:
- Frontend gathers ranked songs, filter state, and metadata from
/user-songs/ranked. - Frontend posts the export request to
POST /api/playlist/export. - The API validates authentication via
requireSpotifyAuth. exportWorker:- Uses
mappingServiceto translate input items into Spotify URIs and per-track reasons. - Chunks URIs into batches of at most
100per add call, per Spotify API constraints. - Calls
spotifyClientto create the playlist and add tracks. - Aggregates per-track outcomes into a structured envelope.
- Uses
- Backend returns a response with:
- Playlist identifiers (
playlistId,playlistUrl) - Structured
kept,skipped, andfailedarrays - A top-level
okflag and error code when needed.
- Playlist identifiers (
The export envelope used in tests looked like this:
{
"ok": true,
"playlistId": "pl_123",
"playlistUrl": "https://open.spotify.com/playlist/pl_123",
"kept": ["spotify:track:a", "spotify:track:b"],
"skipped": [{ "id": "spotify:track:c", "reason": "NOT_FOUND" }],
"failed": [{ "id": "spotify:track:d", "reason": "RATE_LIMIT" }]
}
The frontend does not need to know how mapping or chunking is implemented; it only needs to render based on this contract.
Export pipeline diagram¶
flowchart LR
User["User (Browser)"] --> Frontend["React App (AWS Amplify)"]
Frontend --> API["Express API (Elastic Beanstalk)"]
API --> Mongo["MongoDB Atlas"]
Frontend --> Cognito["Cognito User Pools"]
API --> Cognito
API --> ExportWorker["exportWorker / scripts helpers"]
ExportWorker --> Spotify["Spotify Web API"]
ExportWorker --> Deezer["Deezer API"]
Frontend --> S3["S3 (Profile Images)"]
Mapping layer¶
mappingService converts ranked items into Spotify track URIs. It handles:
- Reusing valid existing
spotifyUri. - Generating URIs from
deezerID,deezerId, or_idwhen needed. - Filtering out unchecked or explicitly skipped items.
- Returning structured reasons for items that cannot be mapped (for example,
NOT_FOUND,REGION_BLOCKED).
A stubbed mapper exists for tests that do not require real Spotify lookups, while integration and E2E tests use deterministic fixtures so the structure of skipped and failed arrays remains consistent.
This mapping layer is a major source of per-track outcomes, which is why several integration and E2E tests assert on the skipped and failed arrays, not just on HTTP status.
Spotify client and rate limits¶
spotifyClient wraps Spotify’s playlist endpoints:
POST /v1/users/{user_id}/playlistsfor playlist creation.POST /v1/playlists/{playlist_id}/tracksfor adding tracks.
exportWorker applies the rate-limit policy:
- Honors
Retry-Afterin seconds or HTTP-date format. - Falls back to bounded backoff if the header is missing or malformed.
- Preserves partial successes and marks remaining items as
RATE_LIMITafter exhaustion.
This behavior is validated in UT-005 and IT-008, and surfaced to the UI for AC-06.2 and AC-06.3.
OAuth and session model¶
Spotify OAuth is handled in controllers/AuthController.js and wired in:
GET /auth/startGET /auth/callbackGET /auth/sessionPOST /auth/revoke
Key points:
- Access and refresh tokens are stored in secure
httpOnlycookies; they never enter local storage. /auth/sessionexposes a small JSON shape consumed by the frontend to determine whether export is allowed./auth/revokeclears server-side tokens and invalidates the session.- External revocation (removing Melodex from Spotify “Connected Apps”) is handled by detecting
invalid_grantand forcing a reconnect.
The export route /api/playlist/export is guarded by requireSpotifyAuth, which relies on this session model. This interplay between OAuth endpoints and the export route is exercised in IT-001, IT-002, IT-010, and E2E tests such as E2E-003 and E2E-007.
OAuth / session flow diagram¶
flowchart TD
Start["User on /rankings"] --> ClickConnect["Click 'Connect Spotify'"]
ClickConnect --> AuthStart["GET /auth/start"]
AuthStart --> SpotifyAuth["Spotify login and consent"]
SpotifyAuth --> Callback["GET /auth/callback"]
Callback --> StoreTokens["Store tokens in httpOnly cookies"]
StoreTokens --> RedirectBack["Redirect to /rankings?export=1"]
RedirectBack --> SessionCheck["GET /auth/session"]
SessionCheck --> Connected["{ connected: true }"]
Connected --> ExportAttempt["POST /api/playlist/export (requireSpotifyAuth)"]
ExportAttempt -.-> Revoke["/auth/revoke or external revoke"]
Revoke -.-> Invalid["Next /auth/session shows { connected: false }"]
Invalid -.-> Reconnect["User must reconnect before exporting again"]
Impact on testing¶
This architecture led to several design decisions in the test suite:
- Contract-style tests for the export envelope to avoid regressions in
kept,skipped, andfailedstructures. - Focused integration tests around rate limiting and
Retry-Afterparsing. - E2E tests that cover OAuth initiation, callback, session exposure, revoke flows, and export blocking/unblocking.
- Clear separation between stubbed and real-worker modes, with tests ensuring the environment flags
PLAYLIST_MODEandEXPORT_STUBare respected.
The next pages describe how this architecture was exercised using unit, integration, UI, and E2E tests.