# utils.py # Shared helper functions and decorators from functools import wraps from flask import session, redirect, url_for, request, jsonify from datetime import datetime, timezone import random import string # ── AUTH DECORATORS ──────────────────────────────────────── def login_required(f): """Redirect to login if user is not in session. Returns 401 for API calls.""" @wraps(f) def decorated(*args, **kwargs): if "user_id" not in session: if request.is_json or request.path.startswith("/api/"): return jsonify({"success": False, "message": "Login required."}), 401 return redirect(url_for("auth.login_page")) return f(*args, **kwargs) return decorated def admin_required(f): """Returns 403 if the current user is not an admin.""" @wraps(f) def decorated(*args, **kwargs): if not session.get("is_admin"): return jsonify({"success": False, "message": "Admin access required."}), 403 return f(*args, **kwargs) return decorated # ── TIME HELPERS ─────────────────────────────────────────── def time_ago(dt: datetime) -> str: """Convert a datetime to a human-readable 'X ago' string.""" now = datetime.now(timezone.utc) if dt.tzinfo is None: dt = dt.replace(tzinfo=timezone.utc) seconds = int((now - dt).total_seconds()) if seconds < 60: return "Just now" elif seconds < 3600: m = seconds // 60 return f"{m} minute{'s' if m > 1 else ''} ago" elif seconds < 86400: h = seconds // 3600 return f"{h} hour{'s' if h > 1 else ''} ago" elif seconds < 604800: d = seconds // 86400 return f"{d} day{'s' if d > 1 else ''} ago" else: return dt.strftime("%b %d, %Y") # ── ID GENERATOR ─────────────────────────────────────────── def generate_student_id() -> str: """Generate a unique student ID like STU123456.""" return "STU" + "".join(random.choices(string.digits, k=6)) # ── PAGINATION HELPER ────────────────────────────────────── def get_pagination_args(): """Extract page and limit from request args.""" page = max(1, int(request.args.get("page", 1))) limit = min(50, int(request.args.get("limit", 10))) return page, limit, (page - 1) * limit