"""Frontoffice routes — visible to all authenticated users. Sections: 1. View Data (everyone) 2. Add/Remove/Edit (admins only) """ import json from datetime import date from flask import Blueprint, render_template, redirect, url_for, flash, request, abort from flask_login import current_user from .models import db, Alert, AppSetting, AuditLog from .forms import AlertForm from .auth_utils import admin_required, login_required_full, get_remote_ip main_bp = Blueprint("main", __name__) def _parse_days(text: str) -> list[int]: return sorted({int(x.strip()) for x in text.split(",") if x.strip().isdigit() and int(x.strip()) >= 0}, reverse=True) # --------------------------------------------------------------------------- # # Section 1 — View Data # --------------------------------------------------------------------------- # @main_bp.route("/") @login_required_full def index(): today = date.today() alerts = Alert.query.filter_by(is_active=True).order_by(Alert.expiration_date.asc()).all() payload = [] for a in alerts: days_left = (a.expiration_date - today).days payload.append({ "id": a.id, "title": a.title, "category": a.category, "description": a.description, "expiration_date": a.expiration_date.isoformat(), "days_left": days_left, "reminder_days": a.reminder_days, }) return render_template("index.html", alerts=payload, today_iso=today.isoformat()) # --------------------------------------------------------------------------- # # Section 2 — Add / Remove / Edit # --------------------------------------------------------------------------- # @main_bp.route("/alerts") @login_required_full def alerts_list(): alerts = Alert.query.order_by(Alert.expiration_date.asc()).all() return render_template("alerts_list.html", alerts=alerts) @main_bp.route("/alerts/new", methods=["GET", "POST"]) @admin_required def alert_new(): default_reminder_days = AppSetting.get("default_reminder_days", "30,14,7,1") form = AlertForm(reminder_days=default_reminder_days) if form.validate_on_submit(): days = _parse_days(form.reminder_days.data) if not days: flash("Reminder days must contain at least one valid number.", "error") else: alert = Alert( title=form.title.data.strip(), category=(form.category.data or "").strip() or None, description=(form.description.data or "").strip() or None, expiration_date=form.expiration_date.data, is_active=form.is_active.data, created_by=current_user.id, ) alert.reminder_days = days db.session.add(alert) db.session.commit() AuditLog.record("alert.create", user=current_user, details=f"id={alert.id} title={alert.title}", ip=get_remote_ip()) flash("Alert created.", "success") return redirect(url_for("main.alerts_list")) return render_template("alert_form.html", form=form, alert=None) @main_bp.route("/alerts//edit", methods=["GET", "POST"]) @admin_required def alert_edit(alert_id: int): alert = db.session.get(Alert, alert_id) if alert is None: abort(404) form = AlertForm(obj=alert) if request.method == "GET": form.reminder_days.data = ",".join(str(d) for d in alert.reminder_days) if form.validate_on_submit(): days = _parse_days(form.reminder_days.data) if not days: flash("Reminder days must contain at least one valid number.", "error") else: alert.title = form.title.data.strip() alert.category = (form.category.data or "").strip() or None alert.description = (form.description.data or "").strip() or None alert.expiration_date = form.expiration_date.data alert.is_active = form.is_active.data # If reminder_days changed OR expiration_date changed, reset sent log if days != alert.reminder_days: alert.reminders_sent = [] alert.reminder_days = days db.session.commit() AuditLog.record("alert.update", user=current_user, details=f"id={alert.id}", ip=get_remote_ip()) flash("Alert updated.", "success") return redirect(url_for("main.alerts_list")) return render_template("alert_form.html", form=form, alert=alert) @main_bp.route("/alerts//delete", methods=["POST"]) @admin_required def alert_delete(alert_id: int): alert = db.session.get(Alert, alert_id) if alert is None: abort(404) title = alert.title db.session.delete(alert) db.session.commit() AuditLog.record("alert.delete", user=current_user, details=f"id={alert_id} title={title}", ip=get_remote_ip()) flash(f"Alert '{title}' deleted.", "success") return redirect(url_for("main.alerts_list")) @main_bp.route("/alerts//reset-reminders", methods=["POST"]) @admin_required def alert_reset_reminders(alert_id: int): alert = db.session.get(Alert, alert_id) if alert is None: abort(404) alert.reminders_sent = [] db.session.commit() AuditLog.record("alert.reset_reminders", user=current_user, details=f"id={alert_id}", ip=get_remote_ip()) flash("Reminder send-history cleared. Pending reminders will be re-sent on next scheduler run.", "info") return redirect(url_for("main.alerts_list"))