from __future__ import annotations from pathlib import Path from uuid import uuid4 from flask import Blueprint, current_app, flash, jsonify, redirect, render_template, request, url_for from flask_login import current_user, login_required from werkzeug.utils import secure_filename from ..extensions import csrf, db from ..forms import SettingsProfileForm from ..models import BadgeDefinition, PushSubscription from ..services.notifications import push_enabled bp = Blueprint("settings", __name__, url_prefix="/settings") def _save_avatar(file_storage) -> str: filename = secure_filename(file_storage.filename or "") ext = Path(filename).suffix.lower() or ".png" relative_path = Path("avatars") / f"{uuid4().hex}{ext}" absolute_path = Path(current_app.config["UPLOAD_FOLDER"]) / relative_path absolute_path.parent.mkdir(parents=True, exist_ok=True) file_storage.save(absolute_path) return relative_path.as_posix() @bp.route("", methods=["GET", "POST"]) @login_required def index(): form = SettingsProfileForm(original_email=current_user.email, obj=current_user) if form.validate_on_submit(): current_user.name = form.name.data.strip() current_user.email = form.email.data.lower().strip() current_user.notification_task_due_enabled = form.notification_task_due_enabled.data current_user.notification_monthly_winner_enabled = form.notification_monthly_winner_enabled.data if form.password.data: current_user.set_password(form.password.data) if form.avatar.data: current_user.avatar_path = _save_avatar(form.avatar.data) db.session.commit() flash("Deine Einstellungen wurden gespeichert.", "success") return redirect(url_for("settings.index")) badges = BadgeDefinition.query.order_by(BadgeDefinition.name.asc()).all() subscriptions = PushSubscription.query.filter_by(user_id=current_user.id).all() return render_template( "settings/index.html", form=form, badges=badges, push_ready=push_enabled(), vapid_public_key=current_app.config["VAPID_PUBLIC_KEY"], has_subscription=bool(subscriptions), ) @bp.route("/badges/", methods=["POST"]) @login_required def update_badge(badge_id: int): badge = BadgeDefinition.query.get_or_404(badge_id) badge.threshold = max(1, request.form.get("threshold", type=int, default=badge.threshold)) badge.bonus_points = max(0, request.form.get("bonus_points", type=int, default=badge.bonus_points)) badge.active = request.form.get("active") == "on" db.session.commit() flash(f"Badge „{badge.name}“ wurde aktualisiert.", "success") return redirect(url_for("settings.index")) @bp.route("/push/subscribe", methods=["POST"]) @login_required @csrf.exempt def push_subscribe(): if not push_enabled(): return jsonify({"ok": False, "message": "VAPID ist nicht konfiguriert."}), 400 data = request.get_json(silent=True) or {} endpoint = data.get("endpoint") keys = data.get("keys", {}) if not endpoint or not keys.get("p256dh") or not keys.get("auth"): return jsonify({"ok": False, "message": "Subscription unvollständig."}), 400 subscription = PushSubscription.query.filter_by(endpoint=endpoint).first() if not subscription: subscription = PushSubscription(user_id=current_user.id, endpoint=endpoint, p256dh=keys["p256dh"], auth=keys["auth"]) db.session.add(subscription) else: subscription.user_id = current_user.id subscription.p256dh = keys["p256dh"] subscription.auth = keys["auth"] db.session.commit() return jsonify({"ok": True}) @bp.route("/push/unsubscribe", methods=["POST"]) @login_required @csrf.exempt def push_unsubscribe(): data = request.get_json(silent=True) or {} endpoint = data.get("endpoint") if endpoint: subscription = PushSubscription.query.filter_by(endpoint=endpoint, user_id=current_user.id).first() if subscription: db.session.delete(subscription) db.session.commit() return jsonify({"ok": True})