""" app/admin/utils.py Shared helpers used across all admin portal blueprints. """ import logging from functools import wraps from flask import abort, request from flask_login import current_user logger = logging.getLogger(__name__) def superadmin_required(f): """ Decorator: ensures the current user is an authenticated superadmin. Aborts 401 if unauthenticated, 403 if wrong role. Use on every admin blueprint route. """ @wraps(f) def decorated(*args, **kwargs): if not current_user.is_authenticated: abort(401) if current_user.role != "superadmin": logger.warning( "superadmin_required: denied user id=%s role=%s endpoint=%s", current_user.id, current_user.role, request.endpoint, ) abort(403) return f(*args, **kwargs) return decorated def log_admin_action(action, target_type=None, target_id=None, before=None, after=None): """ Convenience wrapper — creates an AuditLog entry and adds it to the current db session. Caller must commit. """ from app.models.platform import AuditLog AuditLog.log( actor_id=current_user.id, actor_type="system_user", action=action, target_type=target_type, target_id=target_id, before=before, after=after, ip_address=request.remote_addr, ) def model_to_dict(obj, fields): """ Return a plain JSON-serialisable dict of the named fields from an ORM object. Converts datetime -> ISO-8601 string, Decimal -> float, all others pass through. Safe to use directly as AuditLog before_json / after_json values. """ import datetime from decimal import Decimal result = {} for f in fields: val = getattr(obj, f, None) if isinstance(val, (datetime.datetime, datetime.date)): result[f] = val.isoformat() elif isinstance(val, Decimal): result[f] = float(val) else: result[f] = val return result