213 lines
6.7 KiB
Python
213 lines
6.7 KiB
Python
"""Meals blueprint — dashboard, responses, history."""
|
|
|
|
from datetime import date, datetime, timedelta
|
|
|
|
from flask import (
|
|
Blueprint,
|
|
render_template,
|
|
request,
|
|
redirect,
|
|
url_for,
|
|
session,
|
|
flash,
|
|
)
|
|
|
|
from werkzeug.security import check_password_hash
|
|
|
|
from models import (
|
|
get_dashboard_data,
|
|
upsert_response,
|
|
ensure_meal_periods,
|
|
get_user_by_id,
|
|
delete_user,
|
|
delete_household,
|
|
count_household_users,
|
|
)
|
|
|
|
meals_bp = Blueprint("meals", __name__)
|
|
|
|
|
|
def login_required(f):
|
|
"""Decorator: redirect to login if not authenticated or session is stale."""
|
|
from functools import wraps
|
|
|
|
@wraps(f)
|
|
def wrapper(*args, **kwargs):
|
|
if "user_id" not in session or "household_id" not in session:
|
|
session.clear()
|
|
return redirect(url_for("auth.login"))
|
|
return f(*args, **kwargs)
|
|
|
|
return wrapper
|
|
|
|
|
|
@meals_bp.route("/")
|
|
def index():
|
|
if "user_id" in session:
|
|
return redirect(url_for("meals.dashboard"))
|
|
return redirect(url_for("auth.login"))
|
|
|
|
|
|
@meals_bp.route("/dashboard")
|
|
@login_required
|
|
def dashboard():
|
|
today = date.today()
|
|
household_id = session["household_id"]
|
|
|
|
# Allow viewing other dates via query param
|
|
date_str = request.args.get("date")
|
|
if date_str:
|
|
try:
|
|
today = datetime.strptime(date_str, "%Y-%m-%d").date()
|
|
except ValueError:
|
|
flash("Invalid date format. Use YYYY-MM-DD.", "error")
|
|
|
|
if household_id is None:
|
|
session.clear()
|
|
return redirect(url_for("auth.login"))
|
|
|
|
data = get_dashboard_data(today, household_id)
|
|
return render_template(
|
|
"dashboard.html",
|
|
dashboard=data,
|
|
viewing_date=today,
|
|
today=date.today(),
|
|
prev_date=today - timedelta(days=1),
|
|
next_date=today + timedelta(days=1),
|
|
current_user_id=session["user_id"],
|
|
is_admin=session["is_admin"],
|
|
)
|
|
|
|
|
|
@meals_bp.route("/respond", methods=["POST"])
|
|
@login_required
|
|
def respond():
|
|
meal_type = request.form.get("meal_type", "").strip()
|
|
status = request.form.get("status", "").strip()
|
|
date_str = request.form.get("date", date.today().isoformat())
|
|
|
|
if meal_type not in ("lunch", "dinner"):
|
|
flash("Invalid meal type.", "error")
|
|
return redirect(url_for("meals.dashboard"))
|
|
|
|
if status not in ("yes", "no"):
|
|
flash("Invalid status.", "error")
|
|
return redirect(url_for("meals.dashboard"))
|
|
|
|
try:
|
|
target_date = datetime.strptime(date_str, "%Y-%m-%d").date()
|
|
except ValueError:
|
|
flash("Invalid date.", "error")
|
|
return redirect(url_for("meals.dashboard"))
|
|
|
|
if target_date < date.today():
|
|
flash("Cannot change responses for past dates.", "error")
|
|
return redirect(url_for("meals.dashboard"))
|
|
|
|
periods = ensure_meal_periods(target_date)
|
|
period = next((p for p in periods if p["meal_type"] == meal_type), None)
|
|
if period is None:
|
|
flash("Meal period not found.", "error")
|
|
return redirect(url_for("meals.dashboard"))
|
|
|
|
upsert_response(session["user_id"], period["id"], status)
|
|
return redirect(url_for("meals.dashboard"))
|
|
|
|
|
|
@meals_bp.route("/history")
|
|
@login_required
|
|
def history():
|
|
date_str = request.args.get("date", date.today().isoformat())
|
|
try:
|
|
target_date = datetime.strptime(date_str, "%Y-%m-%d").date()
|
|
except ValueError:
|
|
target_date = date.today()
|
|
|
|
data = get_dashboard_data(target_date, session["household_id"])
|
|
return render_template(
|
|
"dashboard.html",
|
|
dashboard=data,
|
|
viewing_date=target_date,
|
|
today=date.today(),
|
|
prev_date=target_date - timedelta(days=1),
|
|
next_date=target_date + timedelta(days=1),
|
|
current_user_id=session["user_id"],
|
|
is_admin=session["is_admin"],
|
|
history_mode=True,
|
|
)
|
|
|
|
|
|
# ── Account / household management ────────────────────────────────────────────
|
|
|
|
@meals_bp.route("/delete-account", methods=["POST"])
|
|
@login_required
|
|
def delete_account():
|
|
"""Delete the currently logged-in user's own account."""
|
|
password = request.form.get("password", "")
|
|
user = get_user_by_id(session["user_id"])
|
|
|
|
if not check_password_hash(user["password_hash"], password):
|
|
flash("Incorrect password. Account not deleted.", "error")
|
|
return redirect(url_for("meals.dashboard"))
|
|
|
|
household_id = session["household_id"]
|
|
is_admin = session["is_admin"]
|
|
user_count = count_household_users(household_id)
|
|
|
|
# If admin is the last user, delete the household too
|
|
if is_admin and user_count == 1:
|
|
delete_household(household_id)
|
|
flash("Your account and household have been deleted.", "success")
|
|
else:
|
|
delete_user(session["user_id"])
|
|
flash("Your account has been deleted.", "success")
|
|
|
|
# Clear auth keys but keep the session so flash survives the redirect
|
|
for key in ("user_id", "username", "is_admin", "household_id", "household_name"):
|
|
session.pop(key, None)
|
|
return redirect(url_for("auth.login"))
|
|
|
|
|
|
@meals_bp.route("/admin/remove-user/<int:user_id>", methods=["POST"])
|
|
@login_required
|
|
def admin_remove_user(user_id):
|
|
"""Admin removes a user from the household."""
|
|
if not session["is_admin"]:
|
|
flash("Only admins can remove users.", "error")
|
|
return redirect(url_for("meals.dashboard"))
|
|
|
|
user = get_user_by_id(user_id)
|
|
if user is None or user["household_id"] != session["household_id"]:
|
|
flash("User not found in your household.", "error")
|
|
return redirect(url_for("meals.dashboard"))
|
|
|
|
if user["is_admin"]:
|
|
flash("Cannot remove the admin. Transfer admin role first or delete the household.", "error")
|
|
return redirect(url_for("meals.dashboard"))
|
|
|
|
delete_user(user_id)
|
|
flash(f"User '{user['username']}' has been removed.", "success")
|
|
return redirect(url_for("meals.dashboard"))
|
|
|
|
|
|
@meals_bp.route("/admin/delete-household", methods=["POST"])
|
|
@login_required
|
|
def admin_delete_household():
|
|
"""Admin deletes the entire household."""
|
|
if not session["is_admin"]:
|
|
flash("Only admins can delete the household.", "error")
|
|
return redirect(url_for("meals.dashboard"))
|
|
|
|
password = request.form.get("password", "")
|
|
user = get_user_by_id(session["user_id"])
|
|
|
|
if not check_password_hash(user["password_hash"], password):
|
|
flash("Incorrect password. Household not deleted.", "error")
|
|
return redirect(url_for("meals.dashboard"))
|
|
|
|
delete_household(session["household_id"])
|
|
flash("Household and all members have been deleted.", "success")
|
|
for key in ("user_id", "username", "is_admin", "household_id", "household_name"):
|
|
session.pop(key, None)
|
|
return redirect(url_for("auth.login"))
|