diff --git a/README.md b/README.md index c2eae4e..fa2fabf 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A Flask web app for a convention booth where visitors request a custom AI-genera ### 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. +- **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. - **Confirmation page** (`/thanks/`) — shows the request number after submission. - **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. @@ -18,6 +18,7 @@ A Flask web app for a convention booth where visitors request a custom AI-genera - **Dashboard queue** (`/admin`) — filter by status (All, Pending, Needs Upload, Awaiting Payment, Delivered) and auto-refresh at a configurable interval. - **Per-request detail page** (`/admin/request/`): - Generate and save a Suno prompt from customer info. + - Edit the customer's email address if they mistyped it. - 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. @@ -62,7 +63,7 @@ pending → prompt_ready → songs_uploaded → awaiting_payment → paid → de | `templates/player.html` | Customer audio player, approval, and revision form. | | `templates/admin/login.html` | Admin login page. | | `templates/admin/dashboard.html` | Operator queue with filters and auto-refresh. | -| `templates/admin/request.html` | Single-request detail / prompt / upload / delivery page. | +| `templates/admin/request.html` | Single-request detail / prompt / upload / delivery page. Customer email is editable here. | | `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. | diff --git a/REVIEW.md b/REVIEW.md index 7aedfb8..1f7a476 100644 --- a/REVIEW.md +++ b/REVIEW.md @@ -48,7 +48,7 @@ pending → prompt_ready → songs_uploaded → awaiting_payment → paid → de 1. Customer fills `/request`. 2. Open `/admin`, click request row (or filter by status). -3. On `/admin/request/`, click **Copy customer info for Hermes**, paste result to Hermes. +3. On `/admin/request/`, fix the customer's email if needed, then click **Copy customer info for Hermes**, paste result to Hermes. 4. Paste Hermes response (Title/Style/Lyrics format) into the fields and click **Save Prompt**. 5. Copy Style/Lyrics into Suno Custom Mode, generate two versions. 6. Upload Version A and B MP3s. diff --git a/app.py b/app.py index d75c4c8..0950bbb 100644 --- a/app.py +++ b/app.py @@ -30,6 +30,7 @@ Admin routes: # Standard library imports import os +import re import shutil import smtplib import ssl @@ -38,7 +39,6 @@ from email.message import EmailMessage from pathlib import Path import json -import time from cryptography.fernet import Fernet from cryptography.hazmat.primitives import hashes @@ -318,14 +318,18 @@ def request_form(): """ Public request form. GET -> shows the form with the banner image. - POST -> creates a database record, sends a confirmation email, - and redirects to the thanks page. + POST -> validates the email, 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': + email = request.form.get('email', '').strip().lower() + if not is_valid_email(email): + flash('Please enter a valid email address.', 'error') + return render_template('request.html'), 400 rid = create_request( name=request.form.get('name', '').strip(), - email=request.form.get('email', '').strip(), + email=email, hobbies=request.form.get('hobbies', '').strip(), notable_facts=request.form.get('notable_facts', '').strip(), style_genre=request.form.get('style_genre', '').strip(), @@ -491,6 +495,15 @@ def admin_login(): return render_template('admin/login.html') +def is_valid_email(email): + """Return True if the given string looks like a valid email address.""" + if not email: + return False + # Very loose regex: local@domain.tld, no spaces, with a real TLD part. + pattern = r"^[^\s@]+@[^\s@]+\.[^\s@]+$" + return re.match(pattern, email) is not None + + @app.route('/admin/logout') def admin_logout(): """Clear the admin session.""" @@ -517,9 +530,11 @@ def admin_dashboard(): def admin_request(rid): """ Detail/edit page for a single request. - 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 + GET -> render customer info (email editable), prompt, upload status, + email status, and delivery forms. + POST -> handle one of five actions: + update_customer_email, save_prompt, upload_songs, + notify_customer, mark_paid_deliver Uploaded MP3s are tagged with metadata defaults from /admin/settings. """ redir = require_admin() @@ -549,7 +564,16 @@ def admin_request(rid): if request.method == 'POST': action = request.form.get('action') - if action == 'save_prompt': + if action == 'update_customer_email': + new_email = request.form.get('email', '').strip().lower() + if not is_valid_email(new_email): + flash('Please enter a valid email address.', 'error') + return redirect(url_for('admin_request', rid=rid)) + update_request(rid, email=new_email) + flash('Customer email updated.', 'success') + return redirect(url_for('admin_request', rid=rid)) + + elif action == 'save_prompt': # Store the generated title/style/lyrics and mark prompt ready. update_request(rid, suno_title=request.form.get('suno_title', '').strip(), diff --git a/templates/admin/request.html b/templates/admin/request.html index 883bfe1..c328173 100644 --- a/templates/admin/request.html +++ b/templates/admin/request.html @@ -133,8 +133,18 @@

Customer Info

-
-
Email: {{ req.email }}
+

Operators can correct the customer's email address here.

+
+ + + +
+ +
+
+ +
+
Request #: {{ req.id }}
Status: {{ statuses[req.status] }}

Hobbies:
{{ req.hobbies or '-' }}