# 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/`) — shows the request number after submission. - **FAQ page** (`/faq`) — answers common customer questions. - **Private player page** (`/play/`) — 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 by `ADMIN_PASSWORD`. - **Dashboard queue** (`/admin`) — filter by status and auto-refresh at a configurable interval. - **Per-request detail page** (`/admin/request/`): - 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 ```bash 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 1. Log in to Portainer. 2. Go to **Stacks** → **Add stack**. 3. Choose **Repository**: - URL: `https://gitlab.hallsworth.ca/yrtria/theme-song-booth.git` - Branch: `main` - Compose path: `docker-compose.yml` 4. 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`. | 5. Deploy the stack. 6. Open a console in the `theme-song-booth` container and run once: ```bash python init_db.py ``` 7. Point your reverse proxy at the `HOST_PORT` you chose. 8. Visit `/admin/settings` and click **Regenerate API Key** to create the Hermes callback key. 9. Update your Hermes skill or config with the new key and the booth public URL. 10. 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 `.env` file in production.** `docker-compose.yml` passes variables directly from Portainer. This avoids Portainer's `env_file not found` error. - **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.json` inside the persistent uploads volume. They survive redeploys. - **Booth open/closed switch.** Operators can flip the booth status from `/admin/settings`. When closed, `/request` and `/kiosk` show 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/`. Hermes POSTs back Title/Style/Lyrics; the record becomes `prompt_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.png` is 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.json` and 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.