Skip to content

Repository files navigation

Melodex

Melodex is a full-stack music trivia game built with Next.js App Router, Supabase auth and leaderboards, Deezer-powered preview clips, and a 3D animated homepage. Players listen to short preview clips, choose the correct song title, and play across standard, health, timer, and ranked leaderboard modes.

Features

  • 3D animated homepage built with Three.js.
  • Deezer artist search and preview-clip based trivia questions.
  • Standard mode: get 10 correct answers at your own pace.
  • Health mode: get 10 correct answers before losing 3 hearts.
  • Timer mode: get 10 correct answers before the 3-minute clock runs out.
  • Leaderboard mode: authenticated ranked health and timer runs using random popular artists.
  • Supabase auth, public profiles, avatar uploads, and account deletion.
  • Leaderboard best-score saving per user and mode.
  • HMAC-signed answer tokens so the client cannot know the correct answer until it submits.
  • Track filtering for preview availability, duplicate title variants, and remix/mix variants.
  • Shared JSON API envelope and typed validation with Zod.

Stack

  • Next.js 15 App Router
  • React 19
  • TypeScript
  • Tailwind CSS 4
  • Supabase Auth, Database, and Storage
  • Deezer API
  • Three.js
  • Framer Motion
  • Zod
  • Vitest
  • Vercel Analytics

Project Structure

  • src/app: App Router pages, layouts, middleware-backed auth flows, and API route handlers.
  • src/app/ui: Homepage, play-mode UI, auth menu, leaderboard table, and shared game panels.
  • src/app/ui/play-mode/modes: Standard, health, timer, and leaderboard mode configs.
  • src/lib/game: Pure game logic for modes, scoring, shuffling, dedupe, remix filtering, question building, leaderboard pools, and answer tokens.
  • src/lib/providers: Music provider abstraction plus Deezer, Apple Music, and Spotify adapters.
  • src/lib/validation: Zod schemas for artist, track, question, and answer requests.
  • src/lib/leaderboard: Leaderboard request and response schemas.
  • src/lib/utils: Fetch wrapper, app errors, logging, and helpers.
  • src/utils/supabase: Browser, server, middleware, and service-role Supabase clients.
  • supabase/migrations: Database, storage bucket, RLS, function, and grant migrations.
  • docs/ui-components: Notes for reusable UI components.

Game Modes

Mode Route Auth Rules
Standard /play/standard No Pick an artist and reach 10 correct answers.
Health /play/health No Pick an artist, start with 3 hearts, and reach 10 correct answers before hearts run out.
Timer /play/timer No Pick an artist and reach 10 correct answers before the 3-minute clock expires. The timer freezes when the target is reached.
Leaderboard /play/leaderboard Yes Ranked health and timer runs using random popular artists. Scores save only when logged in.

Music Providers

All providers implement the MusicProvider interface and return normalized artists and tracks.

  • deezer: Fully working. Artist search uses Deezer search, and track lookup uses the public artist top-tracks feed.
  • apple: Scaffolded. Reports not configured unless APPLE_MUSIC_DEVELOPER_TOKEN is present, then reports not implemented.
  • spotify: Scaffolded. Reports not configured unless SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET are present, then reports not implemented.

Provider query parameters default to deezer.

Track Filtering

Before a track can appear in a question, the app:

  • Requires a non-empty preview URL.
  • Deduplicates near-identical titles such as live, remastered, radio edit, acoustic, instrumental, feature, and version variants.
  • Removes remix-style tracks using visible titles, Deezer full titles, and album titles. This catches examples like Remix, Remixes, Club Mix, Extended Mix, Bootleg, and Mashup.

Supabase

Supabase is used for auth, public profiles, avatars, and leaderboard persistence.

Tables

  • public.profiles
    • user_id uuid primary key
    • display_name text
    • avatar_url text
    • timestamps
  • public.leaderboard_scores
    • one best score per user_id and mode
    • supported modes: health, timer
    • stores score, rounds played, accuracy, display name, avatar URL, and timestamps

Storage

  • avatars bucket
    • public read
    • max file size: 2 MB
    • allowed MIME types: JPG, PNG, WebP
    • users can upload, update, and delete files in their own folder.

Database Function

  • public.submit_leaderboard_score(...)
    • called from the server with SUPABASE_SERVICE_ROLE_KEY
    • inserts a user's first score for a mode
    • updates only when the submitted score is better than the stored score
    • returns is_new_best

RLS and Grants

RLS is enabled for exposed public tables. Browser clients get limited access through policies; service-role server routes handle leaderboard reads and writes.

Important production env:

  • SUPABASE_SERVICE_ROLE_KEY must exist on the server only.
  • Never expose the service role key as NEXT_PUBLIC_*.

Environment Variables

Copy .env.local.example to .env.local:

GAME_ANSWER_SECRET=change-me-in-production
UPSTREAM_TIMEOUT_MS=8000

NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=
SUPABASE_SERVICE_ROLE_KEY=

APPLE_MUSIC_DEVELOPER_TOKEN=
APPLE_MUSIC_STOREFRONT=us

SPOTIFY_CLIENT_ID=
SPOTIFY_CLIENT_SECRET=

Notes:

  • GAME_ANSWER_SECRET should always be set in production. Local development falls back to a development secret if it is missing.
  • NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY are required for auth and browser Supabase calls.
  • SUPABASE_SERVICE_ROLE_KEY is required for leaderboard API routes and account deletion.
  • Apple Music and Spotify are optional scaffolds and do not block Deezer gameplay.

Local Development

Install dependencies:

npm.cmd install

Start the Next.js dev server:

npm.cmd run dev

The app runs on:

http://127.0.0.1:3030

If the Next.js cache gets corrupted on Windows, clear it:

npm.cmd run clean
npm.cmd run dev

Supabase Local Development

This repo includes Supabase config and migrations. If you are working locally with Supabase CLI:

supabase start
supabase db reset
supabase status -o env

Use the values from supabase status -o env to fill local Supabase env vars. For a hosted project, apply migrations with the Supabase CLI after linking the project:

supabase link --project-ref <project-ref>
supabase db push

Scripts

npm.cmd run dev        # Start Next.js on port 3030
npm.cmd run build      # Production build
npm.cmd run start      # Start production server on port 3030
npm.cmd run typegen    # Generate Next.js route types
npm.cmd run typecheck  # Generate route types and run TypeScript
npm.cmd run test       # Run Vitest
npm.cmd run clean      # Remove .next and .next-dev

Main Pages

  • /: 3D homepage and game-mode selector.
  • /play: redirects or resolves the default play flow.
  • /play/standard: standard artist-based trivia.
  • /play/health: hearts-based artist trivia.
  • /play/timer: timed artist trivia.
  • /play/leaderboard: authenticated ranked health and timer modes.
  • /leaderboard: public leaderboard table.
  • /login: sign in and sign up.
  • /settings: authenticated profile, avatar, and account settings.
  • /auth/callback: Supabase auth callback.

API Endpoints

All API success responses use:

{
  "data": {}
}

Errors use:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request payload was invalid.",
    "details": {}
  }
}

GET /api/health

Returns service health, current timestamp, whether the fallback answer secret is active, and provider readiness.

curl.exe http://127.0.0.1:3030/api/health

GET /api/artists/search?q=...&provider=deezer

Searches artists through the selected provider.

curl.exe "http://127.0.0.1:3030/api/artists/search?q=Taylor%20Swift&provider=deezer"

GET /api/artists/[artistId]/tracks?provider=deezer

Returns normalized tracks for an artist.

curl.exe "http://127.0.0.1:3030/api/artists/12246/tracks?provider=deezer"

GET /api/game/question?artistId=...&mode=standard&provider=deezer

Builds a question from filtered previewable tracks and returns a signed answer token.

curl.exe "http://127.0.0.1:3030/api/game/question?artistId=12246&mode=standard&provider=deezer"

Response shape:

{
  "data": {
    "questionId": "uuid",
    "artistId": "12246",
    "mode": "standard",
    "provider": "deezer",
    "previewUrl": "https://...",
    "options": [
      {
        "trackId": "1",
        "title": "Song A",
        "artworkUrl": "https://..."
      }
    ],
    "answerToken": "signed-token",
    "totalPlayableTrackCount": 24
  }
}

POST /api/game/answer

Validates a signed answer token and returns the result.

curl.exe -X POST "http://127.0.0.1:3030/api/game/answer" ^
  -H "Content-Type: application/json" ^
  -d "{\"mode\":\"standard\",\"questionId\":\"<uuid>\",\"selectedTrackId\":\"<track-id>\",\"answerToken\":\"<token>\"}"

GET /api/game/leaderboard-question?mode=health&provider=deezer

Authenticated endpoint for ranked runs. Chooses from a shuffled pool of popular artists and returns a signed question.

GET /api/leaderboard/scores?mode=health&limit=10

Returns ranked leaderboard scores for health or timer.

POST /api/leaderboard/scores

Authenticated endpoint that submits a completed ranked run.

{
  "mode": "health",
  "score": 12,
  "roundsPlayed": 15,
  "accuracy": 80
}

DELETE /api/account

Authenticated endpoint that signs the user out, removes their avatar, and deletes the Supabase auth user through the service-role client.

Error Codes

  • UNAUTHORIZED
  • VALIDATION_ERROR
  • PROVIDER_NOT_CONFIGURED
  • PROVIDER_NOT_IMPLEMENTED
  • NOT_FOUND
  • INSUFFICIENT_TRACKS
  • CATALOG_EXHAUSTED
  • UPSTREAM_PROVIDER_ERROR
  • INVALID_ANSWER_TOKEN
  • INTERNAL_ERROR

Typical HTTP mapping:

  • 400: validation, unsupported provider, or invalid answer token
  • 401: authenticated route without a valid session
  • 404: artist or tracks not found
  • 422: fewer than 4 usable tracks or exhausted catalog
  • 502: upstream provider error or timeout
  • 500: unexpected server error

Testing

Run:

npm.cmd run test
npm.cmd run typecheck

Current coverage focuses on pure logic:

  • answer token signing, verification, expiry, and tamper protection
  • question building
  • title dedupe and remix filtering
  • shuffle behavior
  • game mode resolution
  • route validation schemas

Deployment Notes

For production:

  • Set GAME_ANSWER_SECRET to a strong secret.
  • Set all Supabase env vars in the hosting platform.
  • Keep SUPABASE_SERVICE_ROLE_KEY server-only.
  • Apply all Supabase migrations to the production project.
  • Confirm Supabase auth redirect URLs include the production domain and /auth/callback.
  • Deezer works without provider credentials; Apple Music and Spotify are placeholders.

Known Limits

  • Deezer is the only fully working music provider.
  • Deezer track lookup uses the public artist top-tracks feed, not a full discography crawl.
  • Leaderboard mode supports ranked health and timer runs only.
  • Scores save one best score per user per ranked mode.
  • Answer validation is stateless and based on signed tokens.

About

A full-stack music trivia game with Next.js, Supabase leaderboards, Deezer preview clips, and a 3D animated homepage.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages