wip, claude + docker-compose modifications
This commit is contained in:
100
blueprints/admin.py
Normal file
100
blueprints/admin.py
Normal file
@@ -0,0 +1,100 @@
|
||||
from flask import Blueprint, abort, flash, redirect, render_template, request, url_for
|
||||
from flask_babel import _
|
||||
from flask_login import current_user, login_required
|
||||
|
||||
from extensions import db
|
||||
from models import User
|
||||
|
||||
admin_bp = Blueprint("admin", __name__, url_prefix="/admin")
|
||||
|
||||
ROLES = ["user", "admin"]
|
||||
|
||||
|
||||
def _require_admin():
|
||||
if not current_user.is_authenticated or current_user.role != "admin":
|
||||
abort(403)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# User list
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@admin_bp.route("/")
|
||||
@login_required
|
||||
def index():
|
||||
_require_admin()
|
||||
users = db.session.scalars(
|
||||
db.select(User).order_by(User.created_at.desc())
|
||||
).all()
|
||||
return render_template("admin/users.html", users=users, roles=ROLES)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Change role
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@admin_bp.route("/users/<int:user_id>/role", methods=["POST"])
|
||||
@login_required
|
||||
def change_role(user_id: int):
|
||||
_require_admin()
|
||||
user = db.session.get(User, user_id)
|
||||
if user is None:
|
||||
abort(404)
|
||||
new_role = request.form.get("role", "user")
|
||||
if new_role not in ROLES:
|
||||
flash(_("Invalid role."), "error")
|
||||
return redirect(url_for("admin.index"))
|
||||
# Prevent removing the last admin
|
||||
if user.role == "admin" and new_role != "admin":
|
||||
admin_count = db.session.scalar(
|
||||
db.select(db.func.count()).select_from(User).where(User.role == "admin")
|
||||
)
|
||||
if admin_count <= 1:
|
||||
flash(_("Cannot remove the last admin."), "error")
|
||||
return redirect(url_for("admin.index"))
|
||||
user.role = new_role
|
||||
db.session.commit()
|
||||
flash(_("Role updated for %(email)s.", email=user.email), "success")
|
||||
return redirect(url_for("admin.index"))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Reset password
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@admin_bp.route("/users/<int:user_id>/password", methods=["POST"])
|
||||
@login_required
|
||||
def reset_password(user_id: int):
|
||||
_require_admin()
|
||||
user = db.session.get(User, user_id)
|
||||
if user is None:
|
||||
abort(404)
|
||||
new_pw = request.form.get("new_password", "").strip()
|
||||
if len(new_pw) < 8:
|
||||
flash(_("Password must be at least 8 characters."), "error")
|
||||
return redirect(url_for("admin.index"))
|
||||
user.set_password(new_pw)
|
||||
db.session.commit()
|
||||
flash(_("Password reset for %(email)s.", email=user.email), "success")
|
||||
return redirect(url_for("admin.index"))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Delete user
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@admin_bp.route("/users/<int:user_id>/delete", methods=["POST"])
|
||||
@login_required
|
||||
def delete_user(user_id: int):
|
||||
_require_admin()
|
||||
if user_id == current_user.id:
|
||||
flash(_("You cannot delete your own account."), "error")
|
||||
return redirect(url_for("admin.index"))
|
||||
user = db.session.get(User, user_id)
|
||||
if user is None:
|
||||
abort(404)
|
||||
email = user.email
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
flash(_("User %(email)s deleted.", email=email), "success")
|
||||
return redirect(url_for("admin.index"))
|
||||
@@ -12,6 +12,7 @@ from flask import (
|
||||
render_template,
|
||||
request,
|
||||
send_from_directory,
|
||||
session as flask_session,
|
||||
url_for,
|
||||
)
|
||||
from flask_babel import _
|
||||
@@ -28,6 +29,13 @@ auth_bp = Blueprint("auth", __name__, url_prefix="/auth")
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _login(user: "User") -> None:
|
||||
"""Log in user and restore their language preference into the session."""
|
||||
login_user(user)
|
||||
if user.language:
|
||||
flask_session["lang"] = user.language
|
||||
|
||||
|
||||
def _safe_next() -> str:
|
||||
target = request.args.get("next") or ""
|
||||
if target and urlparse(target).netloc == "":
|
||||
@@ -107,7 +115,7 @@ def login():
|
||||
|
||||
user.last_login_at = datetime.now(timezone.utc)
|
||||
db.session.commit()
|
||||
login_user(user)
|
||||
_login(user)
|
||||
return redirect(_safe_next())
|
||||
|
||||
return render_template("auth/login.html")
|
||||
@@ -167,7 +175,7 @@ def register():
|
||||
_dispatch_confirmation(user)
|
||||
return render_template("auth/confirm_pending.html", email=email)
|
||||
|
||||
login_user(user)
|
||||
_login(user)
|
||||
flash(_("Account created! Welcome."), "success")
|
||||
return redirect(url_for("dashboard.index"))
|
||||
|
||||
@@ -186,7 +194,7 @@ def confirm_email(token: str):
|
||||
user.email_confirmed = True
|
||||
user.email_confirm_token = None
|
||||
db.session.commit()
|
||||
login_user(user)
|
||||
_login(user)
|
||||
flash(_("Email confirmed! Welcome."), "success")
|
||||
return redirect(url_for("dashboard.index"))
|
||||
|
||||
@@ -243,7 +251,7 @@ def callback_google():
|
||||
flash(_("This email is already registered with a different login method."), "error")
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
login_user(user)
|
||||
_login(user)
|
||||
return redirect(_safe_next())
|
||||
|
||||
|
||||
@@ -293,7 +301,7 @@ def callback_github():
|
||||
flash(_("This email is already registered with a different login method."), "error")
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
login_user(user)
|
||||
_login(user)
|
||||
return redirect(_safe_next())
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user