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:
parent
14ed80fe4b
commit
4af9322c9d
11 changed files with 148 additions and 111 deletions
79
app.py
79
app.py
|
|
@ -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:
|
||||
|
|
|
|||
Reference in a new issue