Trollgorithm Theme Song Booth - customer-facing request form, admin dashboard, kiosk queue, and delivery pipeline for AI-generated theme songs.
| static | ||
| templates | ||
| .env.example | ||
| .gitignore | ||
| app.py | ||
| config.py | ||
| docker-compose.yml | ||
| Dockerfile | ||
| init_db.py | ||
| models.py | ||
| README.md | ||
| requirements.txt | ||
| REVIEW.md | ||
Theme Song Booth
A Flask web app for a convention booth where visitors request a custom AI-generated theme song, the operator manages the queue, and the final MP3(s) are delivered by email after payment.
Customer-facing
- Request form (
/request) — visitors enter name, email, hobbies, notable facts, preferred style/genre, vocal gender preference, and extra requests. A branded banner image is shown. Email is validated to reduce delivery problems. If the operator marks the booth as closed, this page shows a closed banner and message instead. - Closed page (
/request) — when the booth is marked closed from/admin/settings, visitors see the closed banner and a friendly "goblin engineers are on a break" message. - Order status lookup (
/status) — customers enter their email to see all their requests, current status, and the private player link once songs are uploaded. - Confirmation page (
/thanks/<id>) — shows the request number after submission. - FAQ page (
/faq) — answers common customer questions. - Private player page (
/play/<token>) — customer receives an email with a unique link. They can stream Version A and Version B, pick one (or both), or request a limited number of revisions. - Revision workflow — when a customer asks for changes, the current MP3s are archived and the operator sees the request as Revisions Requested in the dashboard.
- Rate limiting — the public request form is capped at 5 submissions per minute per IP.
Operator / admin
- Admin login (
/admin/login) — simple session-based login protected byADMIN_PASSWORD. - Dashboard queue (
/admin) — filter by status and auto-refresh at a configurable interval. - Per-request detail page (
/admin/request/<id>):- Generate and save a Suno prompt from customer info.
- One-click copy of request details + a signed callback URL for Hermes.
- Hermes POSTs back the generated Title/Style/Lyrics; status becomes Prompt Ready automatically.
- Edit all customer info fields (name, email, hobbies, notable facts, style/genre, vocal gender, extra requests).
- View revision history for the request.
- Mark the request as Cancelled.
- Internal operator notes.
- Upload Version A and Version B MP3s with automatic ID3 metadata tagging.
- Send a preview email with a private player link.
- Mark paid, enter a Square payment reference, and deliver selected MP3 attachments.
- Save a Stems / Extras share link that appears on the customer player page after delivery.
- Pricing page (
/admin/pricing) — configure fixed prices (One Song, Both Songs, WAV per song, STEMs per song) and up to 5 custom named items. - Sales report (
/admin/sales) — list all delivered requests with customer details and Square payment references. - Public kiosk (
/kiosk) — full-screen display for a booth tablet. Cycles between the request-form QR code and the price list. Banner switches based on booth open/closed state. Slide timing is configurable in settings. - Settings / maintenance page (
/admin/settings):- Database health check with optional schema repair.
- Database statistics, size, upload counts.
- Configure customer revision limit.
- Configure dashboard auto-refresh interval.
- Configure public kiosk cycle mode (QR only, pricing only, or N seconds per slide).
- Configure SMTP host/port/user/from; store the SMTP password encrypted.
- Configure default MP3 metadata tags (artist, album, year, comment).
- Generate/regenerate the Hermes API key used by the prompt callback endpoint (shown once, otherwise masked).
- Download or restore the SQLite database backup.
- Download all uploaded music files as a ZIP backup.
- Send a test email.
- Reset the entire system for a new event.
- Per-request delete and system reset — remove requests and uploaded files; reset auto-increment back to 1.
Status flow
pending → prompt_ready → songs_uploaded → awaiting_payment → paid → delivered
| Status | Meaning |
|---|---|
pending |
Customer submitted a request; operator has not saved a prompt yet. |
prompt_ready |
Hermes POSTed back the generated Suno prompt, or the operator saved it manually. |
songs_uploaded |
Both MP3s uploaded; preview link can be sent. |
revisions_requested |
Customer asked for changes; current files were archived. |
awaiting_payment |
Customer approved a version; waiting for operator to collect payment and deliver. |
paid |
Payment recorded internally. |
delivered |
MP3 attachment(s) emailed to the customer. |
cancelled |
Request was cancelled by the operator. |
File layout
| File | Purpose |
|---|---|
app.py |
Flask routes, helpers, email layer, runtime settings, MP3 tagging, rate limiting, DB maintenance, Hermes callback endpoint, kiosk route, pricing route, sales report, and ZIP backup. |
config.py |
Environment-variable based configuration; defines defaults for DB, uploads, SMTP, secrets, and HERMES_API_KEY. |
models.py |
SQLite schema + CRUD. player_token is a secret URL-safe token. Includes revision history table. |
init_db.py |
Run once after deploy: python init_db.py. |
templates/admin/dashboard.html |
Queue table + filters + auto-refresh + topbar links to Kiosk, Pricing, Sales, Settings. |
templates/admin/request.html |
Single-request detail page: customer info editing, revision history, operator notes, prompt fields, upload/delivery, cancel action. |
templates/admin/settings.html |
Maintenance, settings, backup/restore, reset, kiosk mode, Hermes API key display. |
templates/admin/pricing.html |
Configure fixed and custom pricing items. |
templates/admin/sales.html |
Delivered request sales report. |
templates/status.html |
Customer order status lookup. |
templates/faq.html |
Customer FAQ page. |
templates/player.html |
Customer audio player, approval, revision form, stems download button. |
templates/kiosk.html |
Public full-screen kiosk page with QR/pricing cycling and open/closed banner. |
templates/admin/login.html |
Admin login page. |
static/Trollgorithm_booth.jpg |
Banner image shown when booth is open. |
static/Booth_closed.png |
Banner image shown when booth is closed. |
static/DM-Logo_email.png |
Inline Dionysis Media logo attached to emails. |
static/qr-code.png |
QR code displayed on the kiosk page. |
Dockerfile |
Production container image. |
docker-compose.yml |
Portainer stack definition. |
requirements.txt |
Python dependencies. |
.env.example |
Template for local environment variables. |
REVIEW.md |
Quick-reference for returning to this project. |
Local development
cd /home/jess/workspace/theme-song-booth
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
cp .env.example .env
# Edit .env and set APP_SECRET_KEY, ADMIN_PASSWORD, SMTP_PASS, PUBLIC_BASE_URL
.venv/bin/python init_db.py
.venv/bin/python -m flask --app app run --host=0.0.0.0
Visit:
- Customer form: http://127.0.0.1:5000/request
- Admin login: http://127.0.0.1:5000/admin
- Kiosk: http://127.0.0.1:5000/kiosk
Deployment with Portainer
- Log in to Portainer.
- Go to Stacks → Add stack.
- Choose Repository:
- URL:
https://gitlab.hallsworth.ca/yrtria/theme-song-booth.git - Branch:
main - Compose path:
docker-compose.yml
- URL:
- Add environment variables:
| Variable | Required | Purpose |
|---|---|---|
APP_SECRET_KEY |
Yes | Long random string for Flask sessions and to encrypt stored SMTP password. Generate with python3 -c "import secrets; print(secrets.token_hex(32))". |
ADMIN_PASSWORD |
Yes | Password for /admin. |
SMTP_PASS |
Yes | Password for the SMTP account. |
PUBLIC_BASE_URL |
Yes | Public HTTPS URL, e.g. https://booth.dionysismedia.ca. |
HOST_PORT |
No | Host-side port mapping, default 127.0.0.1:8000. |
INTERNAL_PORT |
No | Port gunicorn binds inside container, default 8000. |
BOOTH_NAME |
No | Name used in emails, default Trollgorithm Theme Songs. |
PRICE_PER_VERSION |
No | Shown to the operator/customer, default 10.00. |
CURRENCY |
No | Currency label, default CAD. |
MAX_REVISIONS |
No | Default customer revision limit if not changed in settings, default 2. |
HERMES_API_KEY |
No | API key for the /api/prompt callback. If omitted, generate one from /admin/settings. |
- Deploy the stack.
- Open a console in the
theme-song-boothcontainer and run once:
python init_db.py
- Point your reverse proxy at the
HOST_PORTyou chose. - Visit
/admin/settingsand click Regenerate API Key to create the Hermes callback key. - Update your Hermes skill or config with the new key and the booth public URL.
- Print or display a QR code pointing to
https://your-domain/request.
Updating the deployment
After each push to GitLab, go to Portainer → Stacks → theme-song-booth → Pull and redeploy to rebuild from the repo.
Important notes
- No
.envfile in production.docker-compose.ymlpasses variables directly from Portainer. This avoids Portainer'senv_file not founderror. - Runtime settings persist. SMTP config, revision limit, auto-refresh interval, booth open/closed state, Hermes API key, MP3 metadata defaults, pricing, and kiosk cycle mode are stored in
booth_settings.jsoninside the persistent uploads volume. They survive redeploys. - Booth open/closed switch. Operators can flip the booth status from
/admin/settings. When closed,/requestand/kioskshow the closed banner. - Payments are manual. The app records a Square payment reference but does not integrate with Square's API. Use a Square Terminal/Reader at the booth.
- Operator queue is the dashboard. No operator email alerts are sent; approvals and revision notes appear as status changes in
/admin. - Hermes callback workflow. Operators copy request details + a signed callback URL from
/admin/request/<id>. Hermes POSTs back Title/Style/Lyrics; the record becomesprompt_ready. - MP3 metadata. Uploaded files are tagged with title (from the saved prompt), plus configured artist/album/year/comment values.
- Email logo.
static/DM-Logo_email.pngis attached inline to all customer emails as the Dionysis Media signature. - Stems / Extras delivery. Operators paste a file-share link from Pingvin Share (or similar) into the request page. The link appears as a download button on the customer player page after delivery.
- Kiosk page. Public, no login. Shows the open/closed banner, cycles between the request-form QR code and the configured price list, and auto-refreshes so updates to pricing or booth state appear.
- Security: the repo is public on GitLab. No secrets are committed. Admin password is plain text in the Portainer environment. The Hermes API key is stored in
booth_settings.jsonand masked in the admin UI.
Common troubleshooting
| Problem | Cause | Fix |
|---|---|---|
| "Send Preview Link" does nothing | Form tags were unbalanced (now fixed). | Redeploy the latest commit. |
| Emails not arriving | SMTP settings wrong or messages in spam. | Use Send Test Email on /admin/settings; verify host/port/password. |
| Can't reach app through domain | Reverse proxy points to wrong host port. | Match HOST_PORT to your proxy upstream. |
| Static banner not showing | Browser cached old image. | Hard-refresh or redeploy stack. |
| Logo missing from email | Logo file missing from static/. |
Ensure static/DM-Logo_email.png is in the container. |
| Database schema mismatch | New column/table added but old DB not migrated. | Go to /admin/settings and click Fix Database Schema, or run python init_db.py. |
| Kiosk shows old prices | Page auto-refreshes every 30s; check /admin/pricing. |
Verify pricing values and redeploy if templates changed. |
License / ownership
Built for Jess's Trollgorithm theme-song booth. All code and assets are private to that project.