Files
PunktFri/app.py

200 lines
6.1 KiB
Python
Raw Normal View History

import os
import logging
import smtplib
import sqlite3
import threading
from email.message import EmailMessage
from functools import wraps
from flask import Flask, render_template, request, redirect, url_for, g, Response, send_from_directory
DATABASE = os.environ.get("DATABASE", "punktfri.db")
LOG_FILE = os.environ.get("LOG_FILE", "signups.log")
ADMIN_USER = os.environ.get("ADMIN_USER", "admin")
ADMIN_PASS = os.environ.get("ADMIN_PASS", "punktfri2024")
PORT = int(os.environ.get("PORT", 5000))
NOTIFY_EMAIL = os.environ.get("NOTIFY_EMAIL", "henrik@i80.dk")
SMTP_HOST = os.environ.get("SMTP_HOST", "")
SMTP_PORT = int(os.environ.get("SMTP_PORT", 587))
SMTP_USER = os.environ.get("SMTP_USER", "")
SMTP_PASS = os.environ.get("SMTP_PASS", "")
SMTP_FROM = os.environ.get("SMTP_FROM", "") or SMTP_USER
app = Flask(__name__)
signup_logger = logging.getLogger("signups")
signup_logger.setLevel(logging.INFO)
_fh = logging.FileHandler(LOG_FILE)
_fh.setFormatter(logging.Formatter("%(asctime)s %(message)s"))
signup_logger.addHandler(_fh)
def build_notification(navn, email, domaener, egne_ns, kommentar):
msg = EmailMessage()
msg["Subject"] = f"PunktFri: ny tilmelding fra {navn}"
msg["From"] = SMTP_FROM
msg["To"] = NOTIFY_EMAIL
msg["Reply-To"] = email
msg.set_content(
f"Ny interessetilkendegivelse på punktfri.dk\n\n"
f"Navn: {navn}\n"
f"E-mail: {email}\n"
f"Antal domæner: {domaener}\n"
f"Egne navneservere: {egne_ns}\n"
f"Kommentar:\n{kommentar or '(ingen)'}\n"
)
return msg
def _send(msg):
try:
with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=15) as s:
s.starttls()
if SMTP_USER:
s.login(SMTP_USER, SMTP_PASS)
s.send_message(msg)
except Exception:
app.logger.exception("Kunne ikke sende tilmeldingsnotifikation til %s", NOTIFY_EMAIL)
def notify_signup(**fields):
if not SMTP_HOST:
app.logger.warning("SMTP_HOST ikke sat — springer notifikation over")
return
threading.Thread(target=_send, args=(build_notification(**fields),), daemon=True).start()
def get_db():
db = getattr(g, "_database", None)
if db is None:
db = g._database = sqlite3.connect(DATABASE)
db.row_factory = sqlite3.Row
return db
@app.teardown_appcontext
def close_db(exception):
db = getattr(g, "_database", None)
if db is not None:
db.close()
def init_db():
with app.app_context():
db = get_db()
db.execute(
"""
CREATE TABLE IF NOT EXISTS signups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
navn TEXT NOT NULL,
email TEXT NOT NULL,
domaener INTEGER NOT NULL,
egne_ns TEXT NOT NULL,
kommentar TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
)
"""
)
db.commit()
def require_admin(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or auth.username != ADMIN_USER or auth.password != ADMIN_PASS:
return Response(
"Login krævet.",
401,
{"WWW-Authenticate": 'Basic realm="PunktFri Admin"'},
)
return f(*args, **kwargs)
return decorated
@app.route("/", methods=["GET", "POST"])
def index():
error = None
if request.method == "POST":
navn = request.form.get("navn", "").strip()
email = request.form.get("email", "").strip().lower()
domaener_raw = request.form.get("domaener", "").strip()
egne_ns = request.form.get("egne_ns", "").strip()
kommentar = request.form.get("kommentar", "").strip()
if not navn or not email or not domaener_raw or egne_ns not in ("ja", "nej"):
error = "Udfyld venligst alle påkrævede felter."
else:
try:
domaener = int(domaener_raw)
if domaener < 1:
raise ValueError
except ValueError:
error = "Angiv et gyldigt antal domæner (mindst 1)."
else:
db = get_db()
existing = db.execute(
"SELECT id FROM signups WHERE email = ?", (email,)
).fetchone()
if existing:
error = "Denne e-mail er allerede tilmeldt — tak for din interesse!"
else:
db.execute(
"INSERT INTO signups (navn, email, domaener, egne_ns, kommentar) VALUES (?, ?, ?, ?, ?)",
(navn, email, domaener, egne_ns, kommentar or None),
)
db.commit()
signup_logger.info(
f"SIGNUP navn={navn!r} email={email!r} domaener={domaener} "
f"egne_ns={egne_ns} kommentar={kommentar!r}"
)
notify_signup(
navn=navn, email=email, domaener=domaener,
egne_ns=egne_ns, kommentar=kommentar,
)
return redirect(url_for("tak"))
return render_template("index.html", error=error)
@app.route("/tak")
def tak():
return render_template("tak.html")
@app.route("/admin")
@require_admin
def admin():
db = get_db()
signups = db.execute(
"SELECT * FROM signups ORDER BY timestamp DESC"
).fetchall()
count = db.execute("SELECT COUNT(*) FROM signups").fetchone()[0]
return render_template("admin.html", signups=signups, count=count)
@app.route("/health")
def health():
return {
"status": "ok",
"version": os.environ.get("BUILD_VERSION", "dev"),
"commit": os.environ.get("GIT_COMMIT", "unknown"),
}, 200
@app.route("/robots.txt")
def robots():
return send_from_directory(app.static_folder, "robots.txt")
@app.route("/sitemap.xml")
def sitemap():
return send_from_directory(app.static_folder, "sitemap.xml")
init_db()
if __name__ == "__main__":
app.run(host="0.0.0.0", port=PORT, debug=False)