weloveovo

WE LOVE OVO / Toronto After Dark

A cinematic Drake discovery app: four rooms over a persistent Toronto nightscape, connected by a branching timeline of eras, records, tracks, artists, and milestones. The app preserves the original 414-track collection, 117 releases, 104 credited artists, and six career chapters.

Run locally

Use Node 24 LTS (or Node 22+), then:

npm ci
npm run dev

Open http://127.0.0.1:3000. No service credentials are needed to explore the public app. It runs its SQL queries against an embedded, in-memory PostgreSQL database (PGlite), seeded from the versioned catalog. The database is rebuilt when that server process restarts. This mode has no curator access or downloadable audio.

The former static index.html has been replaced by Next.js routes. npm run build and npm start provide the production server; opening a local HTML file is no longer the entrypoint.

The experience

Spotify loads only after Listen is selected. Its iframe remains mounted during room navigation and while the tray is collapsed; closing the tray removes it. Playback availability depends on Spotify and the visitor’s account/browser. External Spotify and Apple Music links remain available. No audio is autoplayed or extracted from Spotify.

Favorites reuse the existing weloveovo.favorites storage key and Spotify IDs. If storage is blocked, they work for the current visit. Effects honor reduced motion, pause when the page is hidden, and can be disabled independently. There are no public accounts, comments, ratings, locked chapters, or progress gates.

Architecture

flowchart LR
  Visitor[Public rooms and interactions] --> Next[Next.js server pages and read APIs]
  Next --> Catalog[Shared SQL catalog services]
  Catalog --> DB[(Supabase PostgreSQL)]
  Catalog -. local development .-> Local[(PGlite)]
  Curator[Curator studio] --> Auth[Supabase Auth and server authorization]
  Auth --> Editing[Validated edits and publication]
  Editing --> DB
  Curator -->|Signed resumable uploads| Storage[Private audio storage]
  Next -->|Published track download link| Storage
  Visitor -->|User initiated| Spotify[Official Spotify embed]

Connect Supabase

  1. Create a Supabase project. Copy .env.example to .env.local and set:
    • DATABASE_URL: the project’s PostgreSQL connection string. A session/transaction pooler connection is supported; prepared statements are disabled.
    • NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY.
    • SUPABASE_SERVICE_ROLE_KEY: server only; never prefix this with NEXT_PUBLIC_.
    • CURATOR_EMAILS: comma-separated invited email addresses.
    • NEXT_PUBLIC_SITE_URL: the exact origin used in the browser, such as http://127.0.0.1:3000 locally or your production HTTPS origin.
  2. Apply schema and policies, then import the catalog:

    npm run db:migrate
    npm run db:seed
    
  3. In Supabase Auth, disable public signups and invite/create your curator user. Add its UUID with the SQL editor:

    insert into public.curators (user_id)
    values ('YOUR-INVITED-USER-UUID')
    on conflict do nothing;
    
  4. Add the app origin to the Supabase Site URL and allow the /auth/confirm callback. Configure the magic-link email template to use:

    /auth/confirm?token_hash=&type=email
    
  5. Restart the app. Open /admin, request a sign-in link, and use the link in your email. The app never creates users through the public login form.

The migration creates a private track-audio bucket with a 100 MB per-file limit and MP3 MIME restriction. Supabase project-level storage limits must also permit your file sizes. Authenticated visitors cannot grant themselves curator status. Both database and bucket access policies are versioned with the schema.

Import and publish your MP3s

  1. Open Curator → Audio and select your MP3 files. Files remain outside this Git repository.
  2. Review suggested matches from embedded titles, filenames, or Spotify IDs. Ambiguous/unmatched files require choosing the track; each match must be confirmed.
  3. Upload confirmed files. Transfers go directly to Supabase using expiring signed upload tokens and resumable TUS chunks. Progress and retry are available. Recent upload tickets stay in session storage so reselecting the same file in the same tab can resume an interrupted transfer.
  4. The server checks the uploaded size and parses the actual MPEG Layer III audio stream. Pending or failed files cannot be published.
  5. Publish verified attachments. Only one attachment is published per track; replacing it safely retires the previous attachment. The coverage counter shows how many of the 414 tracks have downloads.
  6. Track pages issue short-lived download URLs with a readable .mp3 filename. Unpublish removes the download from the app immediately; an already-issued URL can remain usable for up to 60 seconds.

Use audio you are authorized to distribute. Spotify embeds and supplied download files are separate sources. No empty links are shown for tracks without a published file. Curator uploads require a configured Supabase project; this repository does not include the owner’s audio collection.

Read interfaces

Endpoint Behavior
GET /api/tracks Search using q, release, artist, era, year, mood, clean, sort, page, limit, and optional comma-separated favorite ids. Returns tracks, total, page, pages, and limit.
GET /api/tracks/:id/related Related tracks with deterministic scores and explanations.
GET /api/graph?root=… Nodes and labeled edges for an era, release, or track neighborhood.
GET /api/tracks/:id/download Redirect to a signed attachment URL for published audio; 404 when unavailable.

Search limits are validated: page size 1–50, query length up to 120 characters, and an explicit sort/mood allowlist. Bad filters return 400; missing tracks return 404; unexpected service failures return a recoverable 503. Admin JSON endpoints under /api/admin are session-protected and are not public content APIs.

The download endpoint also accepts Accept: application/json to return { url } without redirecting. The track page uses this to display retry feedback if signing fails; the MP3 still downloads directly from storage.

Checks

npm run typecheck
npm test
npm run test:ui
npm run build
npm run test:e2e
npm run format:check

Backend tests run real PostgreSQL queries through PGlite, including metadata preservation, search, imports, draft visibility, publication transitions, and access policies with isolated Auth/Storage schema fixtures. Audio tests parse synthetic MPEG frames and exercise storage failure/retry and signed-download behavior with a test provider; they do not claim to verify a live Supabase account. Curator component tests cover matching review, resumed transfers, verification retries after reload, and explicit editorial/audio publication.

Playwright covers direct routes, history, URL filters, favorite migration/persistence, map/list navigation, persistent embeds, mobile widths, reduced motion, and anonymous access rejection. It uses installed Google Chrome on macOS when available; elsewhere run npx playwright install chromium. Set PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH to use another Chrome/Chromium installation. Visual captures and failure traces go into the ignored test-results/ directory.

To run these browser checks against the production build, stop other servers on port 3000, run npm run build, then PLAYWRIGHT_PRODUCTION=true npm run test:e2e.

Deploy to Vercel

  1. Import this GitHub repository into Vercel using the Next.js preset and the repository root directory.
  2. Configure the environment values above; use the deployed HTTPS origin for NEXT_PUBLIC_SITE_URL. Apply migrations and seed once to the connected Supabase project before enabling curator access.
  3. Add the deployed callback URL in Supabase Auth. For curator-enabled previews, configure that preview origin explicitly; use a separate database/project when experimenting with data.
  4. Deploy and check the lobby, a directly loaded record/track URL, database search, curator sign-in, a test upload, publication, and the resulting download.

Without hosted credentials the public app runs the bundled PostgreSQL catalog; curator features stay unavailable. For the full hosted experience, monitor Vercel function logs and Supabase database, authentication, storage, and egress usage. Service provisioning, SMTP delivery, and real audio availability depend on the owner’s configured accounts and supplied files.

Content and credits

The collection reflects the supplied library, not a claim to contain every recording or every version. data/tracks.json preserves the original source and data/catalog.json is its normalized seed. To add catalog content through version control, maintain the normalized seed and run the idempotent import; public metadata can be edited through the curator studio.

Independent fan-made project. Not affiliated with Drake, OVO, Spotify, or Apple Music.