Update README and REVIEW with all new features (kiosk, pricing, cancelled status, revision history, stems, sales, backups)

This commit is contained in:
Troll (Hermes Agent) 2026-08-04 20:46:47 +00:00
parent 8de32d91b7
commit b8ddff9afe
2 changed files with 93 additions and 43 deletions

View file

@ -2,41 +2,48 @@
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. 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.
## Features ## Customer-facing
### 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.
- **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. The email field 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. - **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. - **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. - **Confirmation page** (`/thanks/<id>`) — shows the request number after submission.
- **FAQ page** (`/faq`) — answers common customer questions. - **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. - **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. - **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. - **Rate limiting** — the public request form is capped at 5 submissions per minute per IP.
### Operator/admin ## Operator / admin
- **Admin login** (`/admin/login`) — simple session-based login protected by `ADMIN_PASSWORD`. - **Admin login** (`/admin/login`) — simple session-based login protected by `ADMIN_PASSWORD`.
- **Dashboard queue** (`/admin`) — filter by status (All, Pending, Needs Upload, Awaiting Payment, Delivered) and auto-refresh at a configurable interval. - **Dashboard queue** (`/admin`) — filter by status and auto-refresh at a configurable interval.
- **Per-request detail page** (`/admin/request/<id>`): - **Per-request detail page** (`/admin/request/<id>`):
- Generate and save a Suno prompt from customer info. - Generate and save a Suno prompt from customer info.
- One-click copy of request details + a signed callback URL for Hermes. - 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. - Hermes POSTs back the generated Title/Style/Lyrics; status becomes **Prompt Ready** automatically.
- Edit the customer's email address if they mistyped it. - Edit all customer info fields (name, email, hobbies, notable facts, style/genre, vocal gender, extra requests).
- Upload Version A and Version B MP3s (with automatic ID3 metadata tagging). - 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. - Send a preview email with a private player link.
- Mark paid, enter a Square payment reference, and deliver selected MP3 attachments. - 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`): - **Settings / maintenance page** (`/admin/settings`):
- Database health check with optional schema repair. - Database health check with optional schema repair.
- Database statistics, size, upload counts. - Database statistics, size, upload counts.
- Configure customer revision limit. - Configure customer revision limit.
- Configure dashboard auto-refresh interval. - 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 SMTP host/port/user/from; store the SMTP password encrypted.
- Configure default MP3 metadata tags (artist, album, year, comment). - 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). - Generate/regenerate the Hermes API key used by the prompt callback endpoint (shown once, otherwise masked).
- Send a test email.
- Download or restore the SQLite database backup. - 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. - 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. - **Per-request delete** and **system reset** — remove requests and uploaded files; reset auto-increment back to 1.
@ -50,33 +57,35 @@ pending → prompt_ready → songs_uploaded → awaiting_payment → paid → de
|---|---| |---|---|
| `pending` | Customer submitted a request; operator has not saved a prompt yet. | | `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. | | `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. Dashboard filter label: **Needs Upload** (shown for this state when filtering). | | `songs_uploaded` | Both MP3s uploaded; preview link can be sent. |
| `revisions_requested` | Customer asked for changes; current files were archived. | | `revisions_requested` | Customer asked for changes; current files were archived. |
| `awaiting_payment` | Customer approved a version; waiting for operator to collect payment and deliver. | | `awaiting_payment` | Customer approved a version; waiting for operator to collect payment and deliver. |
| `paid` | Payment recorded (used internally). | | `paid` | Payment recorded internally. |
| `delivered` | MP3 attachment(s) emailed to the customer. | | `delivered` | MP3 attachment(s) emailed to the customer. |
| `cancelled` | Request was cancelled by the operator. |
## File layout ## File layout
| File | Purpose | | File | Purpose |
|---|---| |---|---|
| `app.py` | Flask routes, helpers, email layer, runtime settings, MP3 tagging, rate limiting, DB maintenance, and Hermes callback endpoint. | | `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`. | | `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. | | `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`. | | `init_db.py` | Run once after deploy: `python init_db.py`. |
| `templates/admin/request.html` | Biggest template; prompt copy helpers and JS live here. Customer email and operator notes are editable here. | | `templates/admin/dashboard.html` | Queue table + filters + auto-refresh + topbar links to Kiosk, Pricing, Sales, Settings. |
| `templates/admin/dashboard.html` | Queue table + filters + auto-refresh + topbar Reset System button. | | `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, and Hermes API key management. | | `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/status.html` | Customer order status lookup. |
| `templates/faq.html` | Customer FAQ page. | | `templates/faq.html` | Customer FAQ page. |
| `templates/player.html` | Customer audio player, approval, and revision form. | | `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. | | `templates/admin/login.html` | Admin login page. |
| `templates/admin/dashboard.html` | Operator queue with filters and auto-refresh. | | `static/Trollgorithm_booth.jpg` | Banner image shown when booth is open. |
| `templates/admin/request.html` | Single-request detail / prompt / upload / delivery page. Customer email and operator notes are editable here. | | `static/Booth_closed.png` | Banner image shown when booth is closed. |
| `templates/admin/settings.html` | Maintenance, settings, backup/restore, and reset page. |
| `static/Trollgorithm_booth.jpg` | Banner image on the request page. |
| `static/DM-Logo_email.png` | Inline Dionysis Media logo attached to emails. | | `static/DM-Logo_email.png` | Inline Dionysis Media logo attached to emails. |
| `static/Booth_closed.png` | Banner shown when the booth is marked closed. | | `static/qr-code.png` | QR code displayed on the kiosk page. |
| `Dockerfile` | Production container image. | | `Dockerfile` | Production container image. |
| `docker-compose.yml` | Portainer stack definition. | | `docker-compose.yml` | Portainer stack definition. |
| `requirements.txt` | Python dependencies. | | `requirements.txt` | Python dependencies. |
@ -98,6 +107,7 @@ cp .env.example .env
Visit: Visit:
- Customer form: http://127.0.0.1:5000/request - Customer form: http://127.0.0.1:5000/request
- Admin login: http://127.0.0.1:5000/admin - Admin login: http://127.0.0.1:5000/admin
- Kiosk: http://127.0.0.1:5000/kiosk
## Deployment with Portainer ## Deployment with Portainer
@ -117,11 +127,11 @@ Visit:
| `PUBLIC_BASE_URL` | Yes | Public HTTPS URL, e.g. `https://booth.dionysismedia.ca`. | | `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`. | | `HOST_PORT` | No | Host-side port mapping, default `127.0.0.1:8000`. |
| `INTERNAL_PORT` | No | Port gunicorn binds inside container, default `8000`. | | `INTERNAL_PORT` | No | Port gunicorn binds inside container, default `8000`. |
|| `BOOTH_NAME` | No | Name used in emails, default `Trollgorithm Theme Songs`. | | `BOOTH_NAME` | No | Name used in emails, default `Trollgorithm Theme Songs`. |
|| `PRICE_PER_VERSION` | No | Shown to the operator/customer, default `10.00`. | | `PRICE_PER_VERSION` | No | Shown to the operator/customer, default `10.00`. |
|| `CURRENCY` | No | Currency label, default `CAD`. | | `CURRENCY` | No | Currency label, default `CAD`. |
|| `MAX_REVISIONS` | No | Default customer revision limit if not changed in settings, default `2`. | | `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`. | | `HERMES_API_KEY` | No | API key for the `/api/prompt` callback. If omitted, generate one from `/admin/settings`. |
5. Deploy the stack. 5. Deploy the stack.
6. Open a console in the `theme-song-booth` container and run once: 6. Open a console in the `theme-song-booth` container and run once:
@ -142,14 +152,16 @@ After each push to GitLab, go to Portainer → **Stacks** → `theme-song-booth`
## Important notes ## Important notes
- **No `.env` file in production.** `docker-compose.yml` passes variables directly from Portainer. This avoids Portainer's `env_file not found` error. - **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, and MP3 metadata defaults are stored in `booth_settings.json` inside the persistent uploads volume. They survive redeploys. - **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` shows a closed banner and message instead of the form. - **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. - **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`. - **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 becomes `prompt_ready`. - **Hermes callback workflow.** Operators copy request details + a signed callback URL from `/admin/request/<id>`. 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. - **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. - **Email logo.** `static/DM-Logo_email.png` is attached inline to all customer emails as the Dionysis Media signature.
- **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 encrypted in `booth_settings.json` and masked in the admin UI. - **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 ## Common troubleshooting
@ -160,7 +172,8 @@ After each push to GitLab, go to Portainer → **Stacks** → `theme-song-booth`
| Can't reach app through domain | Reverse proxy points to wrong host port. | Match `HOST_PORT` to your proxy upstream. | | 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. | | 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. | | Logo missing from email | Logo file missing from `static/`. | Ensure `static/DM-Logo_email.png` is in the container. |
| Database schema mismatch | New column added but old DB not migrated. | Go to `/admin/settings` and click **Fix Missing Columns**, or run `python init_db.py`. | | 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 ## License / ownership

View file

@ -27,16 +27,19 @@ Flask app that lets convention attendees request custom AI-generated theme songs
| File | Notes | | File | Notes |
|---|---| |---|---|
| `app.py` | All routes, helpers, email function, status labels, runtime settings, MP3 tagging, rate limiting, DB health, and the `/api/prompt/<id>` Hermes callback endpoint. | | `app.py` | All routes, helpers, email function, status labels, runtime settings, MP3 tagging, rate limiting, DB health, kiosk route, pricing route, sales report, ZIP backup, and the `/api/prompt/<id>` Hermes callback endpoint. |
| `config.py` | Env vars. `ADMIN_PASSWORD` is plain text. `HERMES_API_KEY` can be overridden at runtime. | | `config.py` | Env vars. `ADMIN_PASSWORD` is plain text. `HERMES_API_KEY` can be overridden at runtime. |
| `models.py` | SQLite schema + CRUD. `player_token` is a secret URL-safe token. | | `models.py` | SQLite schema + CRUD. `player_token` is a secret URL-safe token. `revision_history` table tracks each customer revision. |
| `init_db.py` | Run once after deploy: `python init_db.py`. | | `init_db.py` | Run once after deploy: `python init_db.py`. |
| `templates/admin/request.html` | Biggest template; prompt copy helpers and JS live here. Customer email and operator notes are editable here. | | `templates/admin/request.html` | Biggest template. Customer info editing, revision history, operator notes, Suno prompt fields, upload/delivery, cancel request button. |
| `templates/admin/dashboard.html` | Queue table + filters + auto-refresh + topbar Reset System button. | | `templates/admin/dashboard.html` | Queue table + filters + auto-refresh + topbar links to Kiosk, Pricing, Sales, Settings. |
| `templates/admin/settings.html` | SMTP config, MP3 metadata defaults, DB backup/restore, health check, reset, and Hermes API key management. | | `templates/admin/settings.html` | SMTP config, MP3 metadata defaults, DB backup/restore/health, reset, kiosk mode, Hermes API key display. |
| `templates/admin/pricing.html` | Fixed and custom pricing configuration. |
| `templates/admin/sales.html` | Sales report of delivered requests. |
| `templates/faq.html` | Customer FAQ page. | | `templates/faq.html` | Customer FAQ page. |
| `templates/status.html` | Customer order status lookup. | | `templates/status.html` | Customer order status lookup. |
| `templates/closed.html` | Message shown on `/request` when the booth is marked closed. | | `templates/closed.html` | Message shown on `/request` when the booth is marked closed. |
| `templates/kiosk.html` | Public full-screen display: open/closed banner, QR code, price list, auto-refresh. |
| `docker-compose.yml` | No `env_file`; variables come from Portainer. | | `docker-compose.yml` | No `env_file`; variables come from Portainer. |
## Status meanings ## Status meanings
@ -45,16 +48,19 @@ Flask app that lets convention attendees request custom AI-generated theme songs
pending → prompt_ready → songs_uploaded → awaiting_payment → paid → delivered pending → prompt_ready → songs_uploaded → awaiting_payment → paid → delivered
``` ```
`revisions_requested` is a branch used when the customer asks for changes. Branch states:
- `revisions_requested` — customer asked for changes; current files are archived and a note is logged.
- `cancelled` — operator cancelled the request.
## Operator workflow ## Operator workflow
1. Customer fills `/request`. 1. Customer fills `/request`.
2. Open `/admin`, click request row (or filter by status). 2. Open `/admin`, click request row (or filter by status).
3. On `/admin/request/<id>`, fix the customer's email if needed, then click **Copy customer info for Hermes**, paste result to Hermes. 3. On `/admin/request/<id>`, fix customer info if needed, then click **Copy customer info for Hermes**, paste result to Hermes.
4. Hermes POSTs Title/Style/Lyrics back to the signed callback URL; the request becomes **Prompt Ready**. 4. Hermes POSTs Title/Style/Lyrics back to the signed callback URL; the request becomes **Prompt Ready**.
5. If the callback fails, paste Hermes' response into the Title/Style/Lyrics fields and click **Save Prompt**. 5. If the callback fails, paste Hermes' response into the Title/Style/Lyrics fields and click **Save Prompt**.
6. Copy Style/Lyrics into Suno Custom Mode, generate two versions. 6. Copy Lyrics, Style, Title into Suno Custom Mode in that order, generate two versions.
7. Upload Version A and B MP3s. 7. Upload Version A and B MP3s.
8. Click **Send Preview Link**. 8. Click **Send Preview Link**.
9. Customer receives email, visits player, picks version. 9. Customer receives email, visits player, picks version.
@ -77,6 +83,19 @@ HERMES_API_KEY
Most can be overridden at runtime from `/admin/settings` and stored in `booth_settings.json`. Most can be overridden at runtime from `/admin/settings` and stored in `booth_settings.json`.
## Runtime settings stored in `booth_settings.json`
- SMTP host/port/user/from and encrypted password
- Dashboard auto-refresh interval
- Max revisions allowed per customer
- Booth open/closed state
- Default MP3 metadata tags (artist, album, year, comment)
- Hermes API key
- Pricing: fixed items (One Song, Both Songs, WAV per song, STEMs per song) and up to 5 custom named items
- Kiosk cycle mode (QR only, pricing only, or N seconds per slide)
These survive redeploys because `booth_settings.json` lives in the persistent uploads volume.
## Gotchas ## Gotchas
- Multiple forms on `admin/request.html` must stay properly closed; nested forms break buttons. - Multiple forms on `admin/request.html` must stay properly closed; nested forms break buttons.
@ -84,14 +103,32 @@ Most can be overridden at runtime from `/admin/settings` and stored in `booth_se
- The dashboard uses `basename()` as a function, not a Jinja filter. - The dashboard uses `basename()` as a function, not a Jinja filter.
- Reset System deletes DB rows **and** all files under `UPLOAD_FOLDER`, then resets `sqlite_sequence`. - Reset System deletes DB rows **and** all files under `UPLOAD_FOLDER`, then resets `sqlite_sequence`.
- Runtime settings are stored in the persistent uploads volume (`booth_settings.json`). - Runtime settings are stored in the persistent uploads volume (`booth_settings.json`).
- The `booth_open` setting controls whether `/request` shows the form or the closed banner. - The `booth_open` setting controls whether `/request` and `/kiosk` show the open banner or the closed banner.
- Container cannot read host paths; all static assets used at runtime (logo, banner, favicons, closed banner) must be in the repo or a mounted volume. - Container cannot read host paths; all static assets used at runtime (logo, banners, QR code) must be in `static/` or a mounted volume.
- The Hermes callback URL is signed with `APP_SECRET_KEY` and expires after 1 hour. - The Hermes callback URL is signed with `APP_SECRET_KEY` and expires after 1 hour.
- If you regenerate the Hermes API key, update the Hermes skill/config immediately; old key requests will 401. - If you regenerate the Hermes API key, update the Hermes skill/config immediately; old key requests will 401.
- New columns/tables are added via `models.py`. Use `/admin/settings`**Fix Database Schema** after redeploying a schema change.
## How to redeploy ## How to redeploy
1. Push changes to GitLab `main`. 1. Push changes to GitLab `main`.
2. In Portainer: Stacks → `theme-song-booth`**Pull and redeploy**. 2. In Portainer: Stacks → `theme-song-booth`**Pull and redeploy**.
3. If schema changed, open container console and run `python init_db.py`, or use `/admin/settings`**Fix Missing Columns**. 3. If schema changed, open container console and run `python init_db.py`, or use `/admin/settings`**Fix Database Schema**.
## Recent major additions
- **Cancelled status** — operators can mark any request as cancelled from the top of `/admin/request/<id>`.
- **Revision history log** — each customer revision is recorded with revision count, note, and archived file names.
- **Stems / Extras link** — operators paste a file-share link on the request page; customers see a download button after delivery.
- **Sales report**`/admin/sales` lists all delivered requests with payment references.
- **Pricing page**`/admin/pricing` configures fixed prices plus up to 5 custom items; used by `/kiosk`.
- **Public kiosk**`/kiosk` is a full-screen tablet display with QR code and price list cycling.
- **Music ZIP backup**`/admin/settings` can download all uploaded MP3s as a ZIP.
- **Database schema repair** — health check detects missing columns and tables and can repair them.
## Static assets to keep in the repo
- `static/Trollgorithm_booth.jpg` — open banner (request page and kiosk)
- `static/Booth_closed.png` — closed banner
- `static/DM-Logo_email.png` — email signature logo
- `static/qr-code.png` — kiosk QR code pointing to `/request`