Adds the admin backend foundation: AuditLog + Setting models/migration, an admin-only dashboard with KPI cards, and user management (search, ban/suspend/activate, tier override, trust adjust) with audit logging on every write action. Smoke suite extended to 64 checks.
128 lines
4.8 KiB
Python
128 lines
4.8 KiB
Python
"""Admin backend: dashboard KPIs + user management. Admin-only."""
|
|
from flask import Blueprint, render_template, request, redirect, url_for, flash
|
|
from flask_login import current_user
|
|
from flask_babel import gettext as _
|
|
|
|
from app.extensions import db
|
|
from app.models.user import User
|
|
from app.models.listing import Listing
|
|
from app.models.plan import Plan
|
|
from app.models.trust import TrustEvent
|
|
from app.models.audit import AuditLog
|
|
from app.models.enums import UserStatus, TrustEventType
|
|
from app.utils import admin_required
|
|
from app.services import admin_users as usvc
|
|
from app.services import admin_dashboard as dash
|
|
|
|
admin_bp = Blueprint("admin", __name__)
|
|
|
|
PER_PAGE = 25
|
|
|
|
|
|
@admin_bp.route("/admin")
|
|
@admin_required
|
|
def dashboard():
|
|
kpis = {
|
|
"active_listings": dash.active_listings_count(),
|
|
"new_users_7d": dash.new_users_count(7),
|
|
"new_users_30d": dash.new_users_count(30),
|
|
"mrr_cents": dash.mrr_cents(),
|
|
"revenue_30d_cents": dash.revenue_30d_cents(),
|
|
"flag_queue_depth": dash.flag_queue_depth(),
|
|
}
|
|
return render_template("admin/dashboard.html", kpis=kpis)
|
|
|
|
|
|
@admin_bp.route("/admin/users")
|
|
@admin_required
|
|
def users():
|
|
page = request.args.get("page", 1, type=int)
|
|
role = request.args.get("role", type=str) or None
|
|
status = request.args.get("status", type=str) or None
|
|
q = request.args.get("q", type=str) or None
|
|
pagination = usvc.search_query(role=role, status=status, q=q).paginate(
|
|
page=page, per_page=PER_PAGE, error_out=False)
|
|
return render_template("admin/users.html", pagination=pagination,
|
|
filters=request.args)
|
|
|
|
|
|
@admin_bp.route("/admin/users/<int:user_id>")
|
|
@admin_required
|
|
def user_detail(user_id):
|
|
user = User.query.get_or_404(user_id)
|
|
listings = (Listing.query.filter_by(user_id=user.id)
|
|
.order_by(Listing.created_at.desc()).limit(50).all())
|
|
trust_events = (user.trust_events.order_by(TrustEvent.created_at.desc())
|
|
.limit(50).all())
|
|
audit_history = (AuditLog.query.filter_by(target_type="user", target_id=user.id)
|
|
.order_by(AuditLog.created_at.desc()).limit(50).all())
|
|
plans = Plan.query.order_by(Plan.sort_order).all()
|
|
return render_template("admin/user_detail.html", user=user, listings=listings,
|
|
trust_events=trust_events, audit_history=audit_history,
|
|
plans=plans, TrustEventType=TrustEventType)
|
|
|
|
|
|
def _guard_self_action(user):
|
|
if user.id == current_user.id:
|
|
flash(_("You cannot perform this action on your own account."), "danger")
|
|
return True
|
|
return False
|
|
|
|
|
|
@admin_bp.route("/admin/users/<int:user_id>/ban", methods=["POST"])
|
|
@admin_required
|
|
def ban_user(user_id):
|
|
user = User.query.get_or_404(user_id)
|
|
if _guard_self_action(user):
|
|
return redirect(url_for("admin.user_detail", user_id=user.id))
|
|
usvc.set_status(user, UserStatus.banned, actor=current_user)
|
|
db.session.commit()
|
|
flash(_("User banned."), "warning")
|
|
return redirect(url_for("admin.user_detail", user_id=user.id))
|
|
|
|
|
|
@admin_bp.route("/admin/users/<int:user_id>/suspend", methods=["POST"])
|
|
@admin_required
|
|
def suspend_user(user_id):
|
|
user = User.query.get_or_404(user_id)
|
|
if _guard_self_action(user):
|
|
return redirect(url_for("admin.user_detail", user_id=user.id))
|
|
usvc.set_status(user, UserStatus.suspended, actor=current_user)
|
|
db.session.commit()
|
|
flash(_("User suspended."), "warning")
|
|
return redirect(url_for("admin.user_detail", user_id=user.id))
|
|
|
|
|
|
@admin_bp.route("/admin/users/<int:user_id>/activate", methods=["POST"])
|
|
@admin_required
|
|
def activate_user(user_id):
|
|
user = User.query.get_or_404(user_id)
|
|
usvc.set_status(user, UserStatus.active, actor=current_user)
|
|
db.session.commit()
|
|
flash(_("User activated."), "success")
|
|
return redirect(url_for("admin.user_detail", user_id=user.id))
|
|
|
|
|
|
@admin_bp.route("/admin/users/<int:user_id>/tier", methods=["POST"])
|
|
@admin_required
|
|
def set_tier(user_id):
|
|
user = User.query.get_or_404(user_id)
|
|
plan_id = request.form.get("plan_id", type=int)
|
|
plan = db.session.get(Plan, plan_id) if plan_id else None
|
|
usvc.set_tier(user, plan, actor=current_user)
|
|
db.session.commit()
|
|
flash(_("Tier updated."), "success")
|
|
return redirect(url_for("admin.user_detail", user_id=user.id))
|
|
|
|
|
|
@admin_bp.route("/admin/users/<int:user_id>/trust", methods=["POST"])
|
|
@admin_required
|
|
def trust_adjust(user_id):
|
|
user = User.query.get_or_404(user_id)
|
|
delta = request.form.get("delta", type=int) or 0
|
|
event_type = request.form.get("event_type", type=str)
|
|
usvc.adjust_trust(user, TrustEventType(event_type), delta, actor=current_user)
|
|
db.session.commit()
|
|
flash(_("Trust adjusted."), "success")
|
|
return redirect(url_for("admin.user_detail", user_id=user.id))
|