Skip to content

wordsandnumbers/djvb

Repository files navigation

DJ Voice Box (djvb)

A web application for managing karaoke song queues, rooms, playlists, and live session control. The backend is a Spring Boot service that integrates with the VoxBox platform (via the internal com.vpo:vbclient library) to drive song search, queueing, playback, lights, and on-screen popups for connected rooms. The frontend is an AngularJS / Ionic single-page app that DJs and hosts use from a phone or tablet.

Tech stack

Backend

  • Java 21 (virtual threads enabled via spring.threads.virtual.enabled=true), Spring Boot 3.5.x
  • Spring Web, Spring Security, Spring WebSocket, Spring Integration
  • Spring AI MCP Server (Streamable HTTP)
  • Spring Data MongoDB (domain persistence)
  • Spring Session backed by Redis (@EnableRedisHttpSession in DjvbApplication.java)
  • Firebase Admin SDK (auth)
  • com.vpo:vbclient — internal VoxBox client, resolved from GitHub Packages (see Consuming vbclient below)

Frontend (djvb-ui/)

  • AngularJS + Ionic + RequireJS
  • Built and bundled with Grunt + Bower (build config and package.json / bower.json live inside djvb-ui/)
  • Unit tests via Karma + Jasmine

Datastores

  • MongoDB — domain data (users, queues, playlists, avatars, managers)
  • Redis — HTTP session store

Project layout

src/main/java/com/vpo/djvoxbox/
  DjvbApplication.java        Spring Boot entry point
  app/                        Services (QueueManagementService, UserService, UpdateService)
  web/                        REST controllers under /api/v1/* (queue, songs, playlists, avatar, user, ...)
  domain/                     Mongo documents + repositories (User, UserQueue, Playlists, Avatar, Manager)
  config/                     SecurityConfiguration, FirebaseConfig, SimpleCORSFilter, VoxBoxConfig
  faye/                       Faye/CometD subscription to VoxBox push events (replaces the old 25s poll)
  mcp/                        Hosted vbsongs MCP tools, OAuth endpoints, and operation wrappers
  security/                   Custom remember-me / session pieces
  util/                       Shared helpers (e.g. SessionUtils)

src/main/resources/
  application.properties             Production config
  application-development.properties Local dev config
  firebaseServiceAccountKey.json     Firebase Admin credentials (do NOT commit real keys)
  static/                            Frontend bundle served by Spring Boot

djvb-ui/                      AngularJS/Ionic frontend (sources, build pipeline, deps, tests)
  Gruntfile.js                  Frontend build pipeline
  package.json / bower.json     Frontend tooling + bower deps
  karma.conf.js                 Karma unit-test config
  test/                         Karma/Jasmine specs
docker-compose.yml            Local MongoDB + Redis
Procfile                      Heroku-style run command

Prerequisites

  • Java 21 and Maven 3.x
  • Docker / Docker Compose (for local MongoDB and Redis)
  • Node.js ≥ 18 and npmdjvb-ui/package.json still pins engines.node to 6.11.1 for legacy reasons; modern Node works for the tasks we use. Pass --legacy-peer-deps to npm install to tolerate the old dependency graph.
  • A Firebase service account JSON placed at src/main/resources/firebaseServiceAccountKey.json

Grunt and Bower CLIs are pulled in as local dev dependencies — no global install needed. Run them from inside djvb-ui/ via ./node_modules/.bin/grunt and ./node_modules/.bin/bower.

Consuming vbclient

com.vpo:vbclient is published to GitHub Packages at maven.pkg.github.com/wordsandnumbers/vbclient. GitHub Packages requires authentication even for read, so every dev machine needs a one-time setup:

  1. Create a GitHub PAT with read:packages scope at https://github.com/settings/tokens.

  2. Export it from your shell profile:

    export GITHUB_PACKAGES_TOKEN=ghp_...
  3. Add a server entry to ~/.m2/settings.xml (create the file if it doesn't exist):

    <settings>
      <servers>
        <server>
          <id>github-vbclient</id>
          <username>YOUR_GITHUB_USERNAME</username>
          <password>${env.GITHUB_PACKAGES_TOKEN}</password>
        </server>
      </servers>
    </settings>

The <id> must match the repository id in pom.xml. Once configured, mvn resolves vbclient transparently.

Local setup

  1. Start MongoDB and Redis:

    docker compose up -d

    This brings up mongo:6 on 27017 and redis:7-alpine on 6379 (see docker-compose.yml).

  2. Install frontend dependencies (one-time):

    cd djvb-ui
    npm install --legacy-peer-deps
    ./node_modules/.bin/bower install
    cd ..

    npm install brings in Grunt, the Bower CLI, and all the Grunt plugins listed in djvb-ui/package.json. bower install then pulls the AngularJS / Ionic / Firebase frontend libraries into djvb-ui/bower_components/ per djvb-ui/bower.json.

  3. Run the backend against the development profile:

    SPRING_PROFILES_ACTIVE=development mvn spring-boot:run -DskipTests

    The dev profile reads application-development.properties, which points at the local Mongo (mongodb://localhost:27017/djvb) and Redis. The backend listens on http://localhost:8080.

  4. In a separate terminal, sync the UI into the backend's static-resources directory and watch for changes:

    (cd djvb-ui && ./node_modules/.bin/grunt java)

    The java task (djvb-ui/Gruntfile.js):

    • cleans target/classes/static/ (at the project root)
    • runs wiredep to inject Bower CSS @imports into djvb-ui/styles/main.css
    • copies djvb-ui/bower_components/ into target/classes/static/resources/bower_components/
    • syncs djvb-ui/ into target/classes/static/resources/
    • rewrites the RequireJS config in djvb-ui/scripts/main.js
    • watches djvb-ui/** and re-syncs on every change

    Then open http://localhost:8080/ — it will redirect through the formLogin to resources/index.html once you're authenticated. Edits in djvb-ui/ show up after a browser reload (no livereload wired into the java task).

Configuration

Key properties (set in application.properties or application-development.properties, or overridden via environment / -D flags):

Property Purpose
spring.data.mongodb.uri MongoDB connection string
spring.data.redis.host / spring.data.redis.port / spring.data.redis.password Redis (session store)
vb.organization VoxBox organization ID this instance is bound to
manager.name Manager record name used to bootstrap state
default.language Default song search language
server.session.timeout HTTP session timeout (seconds)
mcp.apiToken Optional static bearer token accepted by /mcp for compatibility and local smoke tests
mcp.publicBaseUrl Public origin used in MCP OAuth metadata, for example https://djvb.example.com
mcp.oauth.consentCode Shared human-entered authorization code used by the lightweight MCP OAuth consent page
mcp.oauth.clientId / mcp.oauth.clientSecret Optional pre-registered OAuth client credentials
mcp.oauth.authorizationCodeTtlSeconds / mcp.oauth.accessTokenTtlSeconds / mcp.oauth.refreshTokenTtlSeconds MCP OAuth token lifetimes

The committed application.properties contains real-looking credentials. Rotate them and move them to environment variables / a secrets manager before deploying.

MCP server

The backend hosts a vbsongs MCP server at /mcp using Spring AI's WebMVC Streamable HTTP transport. MCP tools delegate to com.vpo:vbclient and use this instance's configured vb.rootUrl, vb.organization, and default.language.

The server is intentionally client-neutral. Claude, Codex, or any other MCP client that supports remote Streamable HTTP plus OAuth discovery can connect to the same endpoint.

Authentication supports two paths:

  • OAuth: /mcp responds to unauthenticated requests with a WWW-Authenticate challenge pointing at /.well-known/oauth-protected-resource/mcp. The OAuth layer exposes protected-resource metadata, authorization-server metadata, dynamic client registration, authorization-code + PKCE, and refresh tokens.
  • Static bearer: clients may also call /mcp with Authorization: Bearer <mcp.apiToken>. This is useful for local tests and simple internal clients.

For local development, application-development.properties sets both mcp.apiToken and mcp.oauth.consentCode to development-mcp-token.

Useful local checks:

curl -i http://localhost:8080/mcp
curl http://localhost:8080/.well-known/oauth-protected-resource/mcp
curl http://localhost:8080/.well-known/oauth-authorization-server
curl -i -H 'Authorization: Bearer development-mcp-token' http://localhost:8080/mcp

The last command is only an auth-boundary smoke test; use an MCP client for a real Streamable HTTP session.

For production, set:

MCP_PUBLIC_BASE_URL=https://your-djvb-host
MCP_OAUTH_CONSENT_CODE=<operator-shared-auth-code>
MCP_API_TOKEN=<optional-static-bearer-token>

Current limitation: OAuth clients, authorization codes, access tokens, and refresh tokens are stored in memory. Restarting the server invalidates OAuth sessions, and multiple server instances would not share tokens without adding Redis/DB-backed storage or a full authorization server.

Build & test

  • Backend tests: mvn test (entry point: src/test/java/com/vpo/djvoxbox/DjvbApplicationTests.java)
  • Backend jar: mvn package -DskipTests produces target/djvb-0.0.1-SNAPSHOT.jar
  • Frontend commands run from inside djvb-ui/. The production build (grunt default task) and grunt test (Karma + Jasmine) are currently not wired up for modern Node — they relied on node-sass and phantomjs, which were dropped during the Node 24 / Spring Boot 3 upgrade. The dev loop (grunt java) is the supported workflow.

Containers

The whole stack runs as containers — server (Spring Boot) and UI (Caddy + static SPA) — alongside MongoDB and Redis. Local end-to-end mirrors what runs on ECS.

# server build needs a GitHub PAT with read:packages to fetch vbclient
export GITHUB_PACKAGES_TOKEN=ghp_...

DOCKER_BUILDKIT=1 docker compose up --build

Open http://localhost/ — Caddy serves the SPA from /resources/* and reverse-proxies everything else (/, /api/*, /login*, /logout, /actuator/*) to the server. Health check: curl http://localhost/actuator/health.

Build images individually:

DOCKER_BUILDKIT=1 docker build -f Dockerfile.server \
  --secret id=gh_pat,env=GITHUB_PACKAGES_TOKEN \
  -t djvb-server:dev .

docker build -f Dockerfile.ui -t djvb-ui:dev .

Deployment (ECS on EC2)

Production runs as four ECS services (caddy-ui, server, mongo, redis) on a single t4g.small EC2 instance in us-west-2, with a separate EBS data volume for Mongo and Redis persistence across instance replacement. Caddy terminates TLS with auto-issued Let's Encrypt certs — no ALB, no ACM. DNS lives at Cloudflare; an A record pointed at the EIP is the only thing AWS doesn't manage. Estimated steady-state cost ~$14/mo.

See infra/terraform/:

cd infra/terraform
cp terraform.tfvars.example terraform.tfvars   # then edit if needed
terraform init
terraform apply

After terraform apply completes, grab the EIP from the host_public_ip output and add an A record at Cloudflare:

  • Name: djvb (the subdomain part of your domain_name)
  • IPv4 address: the host_public_ip output
  • Proxy status: DNS only (grey cloud, NOT orange/proxied) — Caddy needs direct access to the origin to complete the ACME HTTP-01 challenge.

Populate the SecureString secrets that Terraform left as placeholders:

aws ssm put-parameter --name /djvb/mongo_uri        --type SecureString --overwrite --value 'mongodb://localhost:27017/djvb'
aws ssm put-parameter --name /djvb/redis_password   --type SecureString --overwrite --value '<strong-redis-pw>'
aws ssm put-parameter --name /djvb/vb_organization  --type SecureString --overwrite --value '<vb-org-id>'
aws ssm put-parameter --name /djvb/firebase_key     --type SecureString --overwrite --value file://src/main/resources/firebaseServiceAccountKey.json
aws ssm put-parameter --name /djvb/mcp_oauth_consent_code --type SecureString --overwrite --value '<strong-mcp-operator-code>'

MCP_PUBLIC_BASE_URL is set automatically in the ECS server task from domain_name as https://<domain_name>.

Then force a redeploy so the tasks pick up the new secret values:

aws ecs update-service --cluster djvb --service djvb-server --force-new-deployment
aws ecs update-service --cluster djvb --service djvb-ui     --force-new-deployment

CI/CD is wired up in .github/workflows/deploy.yml: pushes to main build both images for linux/arm64, push to ECR with both <sha> and latest tags, then register a new ECS task-definition revision pinned to the <sha> tag and update each service to it (via .github/scripts/deploy-service.sh). The only repo secret needed is AWS_DEPLOY_ROLE (output by Terraform as github_deploy_role_arn); the workflow fetches vbclient via the auto-provided GITHUB_TOKEN with packages: read permission, so no PAT is stored in GitHub.

For GITHUB_TOKEN to read the vbclient package, that package must grant access to this repo: in the vbclient package settings under Manage Actions access, add wordsandnumbers/djvb with Read role.

Rollback

Every deploy registers a new ECS task-definition revision pinned to its git SHA, so prior revisions remain valid rollback targets. ECR keeps the last 10 image revisions, so any of the last ~10 deployed SHAs are available.

In order of preference:

  1. Manual rollback workflow — re-pins both services to an existing SHA already in ECR (no rebuild, ~1 min):

    # find a recent good SHA
    gh run list --workflow=deploy.yml --branch=main --limit 10
    
    gh workflow run rollback.yml -f sha=<good-sha>

    Optionally pass -f services=djvb-server to roll back only one service. See .github/workflows/rollback.yml.

  2. Revert the commit on main — slower (rebuilds), but keeps main HEAD = what's running:

    git revert <bad-sha> && git push
  3. Auto-rollback (passive)djvb-server and djvb-ui have the ECS deployment circuit breaker enabled with rollback = true (infra/terraform/ecs.tf), so a deploy whose tasks fail to start will auto-restore the previous task-def revision after ~5–15 min without operator action.

Note: rollback only restores the container images, not MongoDB data. There are no scheduled Mongo snapshots — a data-corrupting bug is not recoverable by these steps.

About

Realtime karaoke room queue management and recommendation engine.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors