Use plain admin password and remove operator email alerts; admin dashboard is the queue
This commit is contained in:
parent
7acf7bcca9
commit
fa01ac0f32
4 changed files with 17 additions and 41 deletions
|
|
@ -1,11 +1,11 @@
|
||||||
APP_SECRET_KEY=change-me-in-production
|
APP_SECRET_KEY=change-me-in-production
|
||||||
ADMIN_PASSWORD_HASH=
|
ADMIN_PASSWORD=change-me
|
||||||
SMTP_HOST=mailroot8.namespro.ca
|
SMTP_HOST=mailroot8.namespro.ca
|
||||||
SMTP_PORT=465
|
SMTP_PORT=465
|
||||||
SMTP_USER=ai@hallsworth.ca
|
SMTP_USER=ai@hallsworth.ca
|
||||||
SMTP_PASS=
|
SMTP_PASS=
|
||||||
SMTP_FROM=ai@hallsworth.ca
|
SMTP_FROM=ai@hallsworth.ca
|
||||||
ADMIN_ALERT_EMAIL=ai@hallsworth.ca
|
ADMIN_ALERT_EMAIL=
|
||||||
PUBLIC_BASE_URL=http://127.0.0.1:5000
|
PUBLIC_BASE_URL=http://127.0.0.1:5000
|
||||||
BOOTH_NAME=Trollgorithm Theme Songs
|
BOOTH_NAME=Trollgorithm Theme Songs
|
||||||
DATABASE=/app/data/booth.db
|
DATABASE=/app/data/booth.db
|
||||||
|
|
|
||||||
12
README.md
12
README.md
|
|
@ -19,17 +19,11 @@ cd /home/jess/workspace/theme-song-booth
|
||||||
python3 -m venv .venv
|
python3 -m venv .venv
|
||||||
.venv/bin/pip install -r requirements.txt
|
.venv/bin/pip install -r requirements.txt
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
# Edit .env and set APP_SECRET_KEY, ADMIN_PASSWORD_HASH, SMTP_PASS, PUBLIC_BASE_URL
|
# Edit .env and set APP_SECRET_KEY, ADMIN_PASSWORD, SMTP_PASS, PUBLIC_BASE_URL
|
||||||
.venv/bin/python init_db.py
|
.venv/bin/python init_db.py
|
||||||
.venv/bin/python -m flask --app app run --host=0.0.0.0
|
.venv/bin/python -m flask --app app run --host=0.0.0.0
|
||||||
```
|
```
|
||||||
|
|
||||||
Generate an admin password hash with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
.venv/bin/python -c "from werkzeug.security import generate_password_hash; print(generate_password_hash('yourpassword'))"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Deployment with Portainer
|
## Deployment with Portainer
|
||||||
|
|
||||||
The repo includes a `docker-compose.yml` that builds directly from GitLab, so Portainer can pull and deploy it as a stack.
|
The repo includes a `docker-compose.yml` that builds directly from GitLab, so Portainer can pull and deploy it as a stack.
|
||||||
|
|
@ -42,10 +36,10 @@ The repo includes a `docker-compose.yml` that builds directly from GitLab, so Po
|
||||||
- Compose path: `docker-compose.yml`
|
- Compose path: `docker-compose.yml`
|
||||||
4. Add environment variables in Portainer (or upload a `.env` file):
|
4. Add environment variables in Portainer (or upload a `.env` file):
|
||||||
- `APP_SECRET_KEY`
|
- `APP_SECRET_KEY`
|
||||||
- `ADMIN_PASSWORD_HASH`
|
- `ADMIN_PASSWORD`
|
||||||
- `SMTP_PASS`
|
- `SMTP_PASS`
|
||||||
- `PUBLIC_BASE_URL` (your HTTPS domain)
|
- `PUBLIC_BASE_URL` (your HTTPS domain)
|
||||||
- `ADMIN_ALERT_EMAIL`
|
- `BOOTH_NAME` (optional)
|
||||||
5. Deploy the stack.
|
5. Deploy the stack.
|
||||||
6. Open a console into the `booth` container and run once:
|
6. Open a console into the `booth` container and run once:
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
38
app.py
38
app.py
|
|
@ -5,7 +5,6 @@ from email.message import EmailMessage
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from flask import Flask, request, render_template, redirect, url_for, flash, session, send_from_directory, abort, current_app
|
from flask import Flask, request, render_template, redirect, url_for, flash, session, send_from_directory, abort, current_app
|
||||||
from werkzeug.security import check_password_hash, generate_password_hash
|
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
from config import Config
|
from config import Config
|
||||||
|
|
@ -33,6 +32,9 @@ def require_admin():
|
||||||
if not is_admin():
|
if not is_admin():
|
||||||
return redirect(url_for('admin_login'))
|
return redirect(url_for('admin_login'))
|
||||||
|
|
||||||
|
def admin_password_ok(pw):
|
||||||
|
return pw and pw == current_app.config['ADMIN_PASSWORD']
|
||||||
|
|
||||||
def allowed_file(filename):
|
def allowed_file(filename):
|
||||||
return '.' in filename and filename.rsplit('.', 1)[1].lower() in current_app.config['ALLOWED_EXTENSIONS']
|
return '.' in filename and filename.rsplit('.', 1)[1].lower() in current_app.config['ALLOWED_EXTENSIONS']
|
||||||
|
|
||||||
|
|
@ -120,17 +122,9 @@ def approve(token):
|
||||||
|
|
||||||
update_request(req['id'], customer_approved=choice, status='awaiting_payment', approval_notified_at=now_utc())
|
update_request(req['id'], customer_approved=choice, status='awaiting_payment', approval_notified_at=now_utc())
|
||||||
|
|
||||||
# alert operator
|
# alert operator (disabled — admin dashboard is the queue)
|
||||||
alert_to = current_app.config['ADMIN_ALERT_EMAIL'] or current_app.config['SMTP_FROM']
|
# alert_to = current_app.config['ADMIN_ALERT_EMAIL'] or current_app.config['SMTP_FROM']
|
||||||
if alert_to:
|
# if alert_to: ...
|
||||||
admin_link = f"{current_app.config['PUBLIC_BASE_URL']}/admin/request/{req['id']}"
|
|
||||||
version_label = {'a': 'A', 'b': 'B', 'both': 'Both'}[choice]
|
|
||||||
body = f"{req['name']} ({req['email']}) approved: Version {version_label}.\n\nRequest #{req['id']}\nPayment is now due.\n\nOpen admin: {admin_link}"
|
|
||||||
try:
|
|
||||||
send_email(alert_to, f"{req['name']} approved their theme song", body)
|
|
||||||
except Exception as e:
|
|
||||||
flash(f'Approval saved, but operator alert failed: {e}', 'warning')
|
|
||||||
return redirect(url_for('play', token=token))
|
|
||||||
|
|
||||||
flash('Thanks! Please return to the booth to finalize payment.', 'success')
|
flash('Thanks! Please return to the booth to finalize payment.', 'success')
|
||||||
return redirect(url_for('play', token=token))
|
return redirect(url_for('play', token=token))
|
||||||
|
|
@ -143,17 +137,9 @@ def revise(token):
|
||||||
note = request.form.get('revision_note', '').strip()
|
note = request.form.get('revision_note', '').strip()
|
||||||
update_request(req['id'], revision_note=note, status='songs_uploaded')
|
update_request(req['id'], revision_note=note, status='songs_uploaded')
|
||||||
|
|
||||||
alert_to = current_app.config['ADMIN_ALERT_EMAIL'] or current_app.config['SMTP_FROM']
|
# Revision feedback is stored in the DB and surfaced on the admin dashboard.
|
||||||
if alert_to and note:
|
# No operator email is sent — the dashboard is the single queue.
|
||||||
admin_link = f"{current_app.config['PUBLIC_BASE_URL']}/admin/request/{req['id']}"
|
flash('Your feedback has been saved. We will regenerate and update you.', 'success')
|
||||||
body = f"{req['name']} ({req['email']}) requested changes for request #{req['id']}.\n\nNote:\n{note}\n\nOpen admin: {admin_link}"
|
|
||||||
try:
|
|
||||||
send_email(alert_to, f"{req['name']} requested changes", body)
|
|
||||||
except Exception as e:
|
|
||||||
flash(f'Revision saved, but operator alert failed: {e}', 'warning')
|
|
||||||
return redirect(url_for('play', token=token))
|
|
||||||
|
|
||||||
flash('Your feedback has been sent. We will regenerate and update you.', 'success')
|
|
||||||
return redirect(url_for('play', token=token))
|
return redirect(url_for('play', token=token))
|
||||||
|
|
||||||
@app.route('/audio/<token>/<version>.mp3')
|
@app.route('/audio/<token>/<version>.mp3')
|
||||||
|
|
@ -176,13 +162,9 @@ def admin_login():
|
||||||
if is_admin():
|
if is_admin():
|
||||||
return redirect(url_for('admin_dashboard'))
|
return redirect(url_for('admin_dashboard'))
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
pw_hash = current_app.config['ADMIN_PASSWORD_HASH']
|
if admin_password_ok(request.form.get('password', '')):
|
||||||
if not pw_hash:
|
|
||||||
flash('Admin password is not configured.', 'error')
|
|
||||||
elif check_password_hash(pw_hash, request.form.get('password', '')):
|
|
||||||
session['admin'] = True
|
session['admin'] = True
|
||||||
return redirect(url_for('admin_dashboard'))
|
return redirect(url_for('admin_dashboard'))
|
||||||
else:
|
|
||||||
flash('Invalid password.', 'error')
|
flash('Invalid password.', 'error')
|
||||||
return render_template('admin/login.html')
|
return render_template('admin/login.html')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class Config:
|
||||||
SMTP_PASS = os.environ.get('SMTP_PASS', '')
|
SMTP_PASS = os.environ.get('SMTP_PASS', '')
|
||||||
SMTP_FROM = os.environ.get('SMTP_FROM', 'ai@hallsworth.ca')
|
SMTP_FROM = os.environ.get('SMTP_FROM', 'ai@hallsworth.ca')
|
||||||
|
|
||||||
ADMIN_PASSWORD_HASH = os.environ.get('ADMIN_PASSWORD_HASH', '')
|
ADMIN_PASSWORD = os.environ.get('ADMIN_PASSWORD', '')
|
||||||
ADMIN_ALERT_EMAIL = os.environ.get('ADMIN_ALERT_EMAIL', '')
|
ADMIN_ALERT_EMAIL = os.environ.get('ADMIN_ALERT_EMAIL', '')
|
||||||
|
|
||||||
PUBLIC_BASE_URL = os.environ.get('PUBLIC_BASE_URL', 'http://127.0.0.1:5000')
|
PUBLIC_BASE_URL = os.environ.get('PUBLIC_BASE_URL', 'http://127.0.0.1:5000')
|
||||||
|
|
|
||||||
Reference in a new issue