docs: update comments and README to reflect current feature set

- Refresh app.py module and route docstrings for runtime settings,
  MP3 tagging, rate limiting, revision workflow, and admin actions.
- Clarify config.py and models.py comments.
- Update template comments/CSS for dashboard filters, request detail,
  player page, and settings page.
- Rewrite README.md with current features, status flow, file layout,
  deployment variables, and troubleshooting.
- Refresh REVIEW.md quick-reference.
- Add MAX_REVISIONS to docker-compose.yml environment list.
- Expand requirements.txt comment coverage.

No version history or changelog included.
This commit is contained in:
Troll (Hermes Agent) 2026-08-02 00:07:24 +00:00
parent 14ed80fe4b
commit 4af9322c9d
11 changed files with 148 additions and 111 deletions

79
app.py
View file

@ -3,26 +3,29 @@ app.py
======
Main Flask application for the Theme Song Booth.
This module defines all HTTP routes, helper functions, and the email layer.
This module defines all HTTP routes, helper functions, the email layer,
runtime settings persistence, MP3 metadata tagging, rate limiting, and
database health/maintenance helpers.
It is meant to be served by gunicorn inside a Docker container (see Dockerfile).
Public routes (customers):
- / -> redirects to /request
- /request -> customer submits their info
- /thanks/<id> -> confirmation page after submission
- /play/<token> -> private player page with Version A and B
- /play/<token>/approve -> customer picks a version
- /play/<token>/revise -> customer asks for changes
- / -> redirects to /request
- /request -> customer submits their info
- /thanks/<id> -> confirmation page after submission
- /play/<token> -> private player page with Version A and B
- /play/<token>/approve -> customer picks a version
- /play/<token>/revise -> customer asks for changes
- /audio/<token>/<v>.mp3 -> serves the uploaded MP3 files
Admin routes:
- /admin/login -> password login
- /admin/logout -> clears session
- /admin -> dashboard queue
- /admin/settings -> health check, DB stats, disk usage, system reset
- /admin/request/<id> -> detail/edit page for a single request
- /admin/request/<id>/delete -> deletes one request and its files
- /admin/reset -> deletes ALL requests and ALL files
- /admin/login -> password login
- /admin/logout -> clears session
- /admin -> dashboard queue with status filters and auto-refresh
- /admin/settings -> runtime settings, health check, DB stats, backup/restore, reset
- /admin/request/<id> -> detail/edit page for a single request
- /admin/request/<id>/delete -> deletes one request and its uploaded files
- /admin/reset -> deletes ALL requests and ALL files
"""
# Standard library imports
@ -65,7 +68,7 @@ from mutagen.easyid3 import EasyID3
app = Flask(__name__)
app.config.from_object(Config)
# Request rate limiting: by remote IP. Defaults can be overridden via Limiter storage when configured.
# Global request rate limiting by remote IP (default 60/min). The public form is further limited to 5/min.
limiter = Limiter(get_remote_address, app=app, default_limits=["60 per minute"])
# Ensure the SQLite connection is closed at the end of each request.
@ -136,8 +139,10 @@ def save_upload(request_id, file_obj, version, song_title=None):
def apply_mp3_tags(path, title=None):
"""
Write or overwrite common ID3 tags on an MP3 file using values from
runtime booth settings. The saved Suno title is written to the Title tag.
Write common ID3 tags on an uploaded MP3 using the runtime metadata defaults.
Writes title, artist, album, and date via EasyID3, plus a comment using both
a COMM frame and a TXXX:Comment frame for broad reader compatibility.
Failures are logged as a warning and do not block the upload.
"""
cfg = load_booth_settings()
try:
@ -230,7 +235,8 @@ def save_booth_settings(settings):
def get_email_config():
"""
Return the effective SMTP configuration.
Runtime-encrypted settings from disk override env defaults.
Runtime settings in booth_settings.json override environment defaults.
The SMTP password is decrypted from the encrypted value stored on disk.
"""
cfg = load_booth_settings()
return {
@ -251,16 +257,6 @@ def get_refresh_seconds():
val = 10
return val if val in (10, 20, 30) else 10
def save_booth_settings(settings):
"""Persist runtime settings to JSON file."""
cfg_path = Path(current_app.config['UPLOAD_FOLDER']).parent / 'booth_settings.json'
try:
cfg_path.write_text(json.dumps(settings, indent=2))
except OSError as e:
flash(f'Warning: could not save settings: {e}', 'error')
def send_email(to, subject, body, attachments=None, inline_images=None):
"""Send an email using the configured or runtime SMTP settings."""
cfg = get_email_config()
@ -299,7 +295,7 @@ def send_email(to, subject, body, attachments=None, inline_images=None):
server.send_message(msg)
def build_signature_images():
"""Return inline image tuple list for the Dionysis Media logo."""
"""Return inline image tuple list for static/DM-Logo_email.png (Dionysis Media logo)."""
logo_path = Path(current_app.root_path) / 'static' / 'DM-Logo_email.png'
if not logo_path.exists():
return []
@ -322,7 +318,9 @@ def request_form():
"""
Public request form.
GET -> shows the form with the banner image.
POST -> creates a database record and redirects to the thanks page.
POST -> creates a database record, sends a confirmation email,
and redirects to the thanks page.
Rate limited to 5 submissions per minute per IP.
"""
if request.method == 'POST':
rid = create_request(
@ -380,6 +378,7 @@ def play(token):
"""
Private player page for a customer.
The token is a cryptographically random URL-safe string generated at request time.
Shows A/B audio players, approval buttons, or a revision note depending on status.
"""
req = get_request_by_token(token)
if not req:
@ -398,6 +397,7 @@ def approve(token):
"""
Customer has chosen Version A, Version B, or both.
Updates the request status to 'awaiting_payment' so the operator can collect payment.
Operator email alerts are intentionally disabled; the dashboard is the single queue.
"""
req = get_request_by_token(token)
if not req:
@ -409,10 +409,6 @@ def approve(token):
update_request(req['id'], customer_approved=choice, status='awaiting_payment', approval_notified_at=now_utc())
# NOTE: Operator email alerts are intentionally disabled. The admin dashboard is the single queue.
# alert_to = current_app.config['ADMIN_ALERT_EMAIL'] or current_app.config['SMTP_FROM']
# if alert_to: ...
flash('Thanks! Please return to the booth to finalize payment.', 'success')
return redirect(url_for('play', token=token))
@ -420,8 +416,9 @@ def approve(token):
@app.route('/play/<token>/revise', methods=['POST'])
def revise(token):
"""
Customer asked for changes. Store the note and reset status to 'songs_uploaded'
so the operator sees it in the dashboard queue.
Customer asked for changes.
Enforces the runtime max revisions limit, archives the current A/B MP3 files,
stores the revision note, and resets status to 'revisions_requested'.
"""
note = request.form.get('revision_note', '').strip()
req = get_request_by_token(token)
@ -506,6 +503,7 @@ def admin_dashboard():
"""
Main operator queue.
Optional ?status= filter lets operators focus on one state at a time.
Auto-refresh interval is controlled from /admin/settings.
"""
redir = require_admin()
if redir:
@ -519,9 +517,10 @@ def admin_dashboard():
def admin_request(rid):
"""
Detail/edit page for a single request.
GET -> render the request details and editing forms.
GET -> render customer info, prompt, upload status, email status, and delivery forms.
POST -> handle one of four actions:
save_prompt, upload_songs, notify_customer, mark_paid_deliver
Uploaded MP3s are tagged with metadata defaults from /admin/settings.
"""
redir = require_admin()
if redir:
@ -658,8 +657,10 @@ def admin_delete_request(rid):
def admin_settings():
"""
Settings / maintenance page for operators.
GET -> show database health, statistics, disk usage, and reset button.
POST -> either run a health check/fix or reset the system.
GET -> show database health, statistics, disk usage, runtime settings forms,
SMTP/email config, MP3 metadata defaults, backup/restore, and reset.
POST -> handle one of: fix_db, reset_system, save_max_revisions, save_metadata,
save_email_config, send_test_email, save_refresh, download_db, restore_db.
"""
redir = require_admin()
if redir: