Skip to content

GEET3001/Muzer

Repository files navigation

Muzer 🎧

A collaborative, real-time music queue. A host opens a room, shares two codes, friends join and queue YouTube tracks, everyone votes, and the highest-voted track plays next.


Big picture

flowchart LR
  A([Sign in<br/>Google]) --> B{Host or<br/>Guest?}
  B -->|Host| C[Create room<br/>/dashboard]
  C --> D[Share Join +<br/>Access codes]
  B -->|Guest| E[Enter both codes<br/>/join]
  D -. codes .-> E
  E --> F[Two-code check]
  F --> G[[Queue tracks<br/>and vote]]
  C --> G
  G --> H{Host ends<br/>room?}
  H -->|No| G
  H -->|Yes| I[Wipe all data<br/>+ new codes]
Loading

Tech stack

flowchart LR
  UI[Next.js 15 + React 19<br/>Tailwind] --> API[Route Handlers<br/>NextAuth + Zod]
  API --> PG[(PostgreSQL<br/>Prisma)]
  API -. optional .-> RD[(Redis<br/>ioredis)]
  API --> YT[YouTube<br/>metadata]
Loading
Concern Choice
Framework Next.js 15 (App Router), React 19, TypeScript
Auth NextAuth v4 (Google)
DB PostgreSQL + Prisma
Cache / limits Redis via ioredis (optional)
Validation Zod

Data model

erDiagram
  User ||--o{ Session : hosts
  User ||--o{ SessionMember : joins
  User ||--o{ Stream : adds
  User ||--o{ Upvotes : casts
  Session ||--o{ SessionMember : "has members"
  Session ||--o{ Stream : queues
  Stream ||--o{ Upvotes : receives

  Session {
    string code "unique join code"
    string accessCode "numeric PIN"
    string hostId
  }
  Stream {
    string extractedId "youtube id"
    string title
    string sessionId
  }
  Upvotes {
    int value "+1 up / -1 down"
  }
Loading

One vote per (user, stream) is enforced by a composite unique on Upvotes; net score = sum of value.


Workflows

πŸ”‘ Join β€” two-code auth

sequenceDiagram
  participant G as Guest
  participant S as Server
  participant DB as Postgres
  G->>S: POST /join {code, accessCode}
  S->>S: rate limit (10/min)
  S->>DB: find session by code
  alt code AND accessCode match
    S->>DB: upsert SessionMember
    S-->>G: 200 joined
  else either wrong
    S-->>G: 403 generic error
  end
Loading

βž• Add a track

flowchart TD
  A[Paste YouTube URL] --> B[Extract 11-char id]
  B -->|invalid| X[400]
  B -->|valid| C{Participant?}
  C -->|no| Y[403]
  C -->|yes| D{Already in queue?}
  D -->|yes| Z[409 duplicate]
  D -->|no| E[Fetch metadata<br/>Redis cache 1h]
  E --> F[Create Stream] --> G[Shows in queue]
Loading

πŸ‘πŸ‘Ž Vote β€” one per song, toggleable

stateDiagram-v2
  [*] --> NoVote
  NoVote --> Up: β–²
  NoVote --> Down: β–Ό
  Up --> NoVote: β–² (toggle off)
  Down --> NoVote: β–Ό (toggle off)
  Up --> Down: β–Ό
  Down --> Up: β–²
Loading

πŸŽ›οΈ Now playing / next

flowchart LR
  Q[Queue sorted<br/>by net score] --> N[Top = Now Spinning]
  N --> H{Host: Next Track}
  H -->|DELETE stream + votes| Q
Loading

⏹️ End session β€” wipe everything

flowchart LR
  A[Host: End Session] --> B[DELETE /api/sessions]
  B --> C[txn: votes β†’ streams<br/>β†’ members β†’ session]
  C --> D[Fresh room<br/>+ new codes]
  C -. queue 404 .-> E[Guests: 'Stream ended']
Loading

API

Method & path Who Purpose
GET /api/sessions host Current room + codes
POST /api/sessions host Create room (idempotent)
DELETE /api/sessions host End room + delete its data
POST /api/sessions/join user Join { code, accessCode }
GET /api/streams?code= member Queue (upvotes, myVote)
GET /api/streams/events?code= member SSE stream of queue-changed events
GET /api/streams/search?code=&q= member YouTube search results
POST /api/streams member Add { url, sessionCode }
DELETE /api/streams host Remove { streamId }
POST /api/streams/upvote member Up / toggle { streamId }
POST /api/streams/downvote member Down / toggle { streamId }

Status codes: 401 no auth Β· 403 not a member / wrong codes Β· 404 gone Β· 409 duplicate Β· 429 rate-limited.


Setup

flowchart LR
  A[npm install] --> B[set .env] --> C[start Postgres] --> D[prisma migrate deploy] --> E[npm run dev]
Loading
npm install
npx prisma migrate deploy
npm run dev          # http://localhost:3000

Environment variables

Variable Required Purpose
DATABASE_URL βœ… Postgres connection string
NEXTAUTH_SECRET βœ… Signs the session JWT
NEXTAUTH_URL βœ… (prod) Base URL
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET βœ… Google OAuth
REDIS_URL ⬜ Enables rate limiting + metadata cache

⚠️ .env (secrets) is gitignored β€” only .env.example is tracked. DB options (Neon / Docker / etc.) are in DATABASE.md.


Security at a glance

flowchart TD
  R[Request] --> A{Signed in?}
  A -->|no| E401[401]
  A -->|yes| L{Rate limit OK?}
  L -->|no| E429[429]
  L -->|yes| P{Participant /<br/>host?}
  P -->|no| E403[403]
  P -->|yes| OK[Proceed]
Loading
  • Two-code join Β· CSPRNG codes (crypto.randomInt) Β· per-user rate limits (Redis + in-proc fallback) Β· host-only deck/end controls.

Scripts

Command Does
npm run dev Dev server (Turbopack)
npm run build Production build
npm start Run production build
npm run lint ESLint
npm run prisma:migrate prisma migrate dev
npm run prisma:generate Regenerate Prisma client

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors