"""Admin route registration.""" import logging import os import shutil from datetime import datetime from flask import flash, redirect, render_template, request, url_for from flask_login import current_user, login_required from database import db from extensions import get_polling_service from models import PollLog, PollSource from services import load_platform_config, post_service logger = logging.getLogger(__name__) def _require_admin(redirect_endpoint="index", message="Access denied"): if current_user.is_admin: return None flash(message, "error") return redirect(url_for(redirect_endpoint)) def register_admin_routes(app, user_service): """Register admin routes while preserving endpoint names.""" @app.route("/admin") @login_required def admin_panel(): """Admin panel - user management.""" denied = _require_admin("index", "Access denied. Admin privileges required.") if denied: return denied if not user_service: flash("User service not available", "error") return redirect(url_for("index")) users = user_service.get_all_users() return render_template("admin.html", users=users) @app.route("/admin/user//delete", methods=["POST"]) @login_required def admin_delete_user(user_id): """Delete user (admin only).""" denied = _require_admin("index") if denied: return denied if current_user.id == user_id: flash("You cannot delete your own account!", "error") return redirect(url_for("admin_panel")) user = user_service.get_user_by_id(user_id) if user: username = user.username if user_service.delete_user(user_id): flash(f"User {username} has been deleted.", "success") logger.info(f"Admin {current_user.id} deleted user {username} ({user_id})") else: flash("Error deleting user", "error") logger.error(f"Failed to delete user {user_id}") else: flash("User not found", "error") return redirect(url_for("admin_panel")) @app.route("/admin/user//toggle-admin", methods=["POST"]) @login_required def admin_toggle_admin(user_id): """Toggle user admin status.""" denied = _require_admin("index") if denied: return denied target_user = user_service.get_user_by_id(user_id) if target_user: user_service.update_user_admin_status(user_id, not target_user.is_admin) flash("Admin status updated", "success") else: flash("User not found", "error") return redirect(url_for("admin_panel")) @app.route("/admin/clear_cache", methods=["POST"]) @login_required def admin_clear_cache(): """Clear application cache.""" denied = _require_admin("admin_panel") if denied: return denied try: for cache_dir in ["cache", "temp"]: if os.path.exists(cache_dir): shutil.rmtree(cache_dir) post_service.invalidate() flash("Cache cleared successfully", "success") logger.info(f"Cache cleared by admin user {current_user.id}") except Exception as e: flash(f"Error clearing cache: {str(e)}", "error") logger.error(f"Cache clearing error: {e}") return redirect(url_for("admin_panel")) @app.route("/admin/backup_data", methods=["POST"]) @login_required def admin_backup_data(): """Create backup of application data.""" denied = _require_admin("admin_panel") if denied: return denied try: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") backup_name = f"balanceboard_backup_{timestamp}" backup_dir = f"backups/{backup_name}" os.makedirs(backup_dir, exist_ok=True) for dir_name in ["data", "templates", "themes", "static"]: if os.path.exists(dir_name): shutil.copytree(dir_name, f"{backup_dir}/{dir_name}") for file_name in ["app.py", "models.py", "database.py", "filtersets.json"]: if os.path.exists(file_name): shutil.copy2(file_name, backup_dir) flash(f"Backup created: {backup_name}", "success") except Exception as e: flash(f"Error creating backup: {str(e)}", "error") return redirect(url_for("admin_panel")) @app.route("/admin/polling") @login_required def admin_polling(): """Admin polling management page.""" denied = _require_admin("index", "Access denied. Admin privileges required.") if denied: return denied sources = PollSource.query.order_by(PollSource.platform, PollSource.display_name).all() scheduler_status = get_polling_service().get_status() platform_config = load_platform_config() return render_template( "admin_polling.html", sources=sources, scheduler_status=scheduler_status, platform_config=platform_config, ) @app.route("/admin/polling/add", methods=["POST"]) @login_required def admin_polling_add(): """Add a new poll source.""" denied = _require_admin("index") if denied: return denied platform = request.form.get("platform") source_id = request.form.get("source_id") custom_source_id = request.form.get("custom_source_id") display_name = request.form.get("display_name") poll_interval = int(request.form.get("poll_interval", 60)) max_posts = int(request.form.get("max_posts", 100)) fetch_comments = request.form.get("fetch_comments", "true") == "true" priority = request.form.get("priority", "medium") if custom_source_id and custom_source_id.strip(): source_id = custom_source_id.strip() if not platform or not source_id or not display_name: flash("Missing required fields", "error") return redirect(url_for("admin_polling")) existing = PollSource.query.filter_by(platform=platform, source_id=source_id).first() if existing: flash(f"Source {platform}:{source_id} already exists", "warning") return redirect(url_for("admin_polling")) source = PollSource( platform=platform, source_id=source_id, display_name=display_name, poll_interval_minutes=poll_interval, max_posts=max_posts, fetch_comments=fetch_comments, priority=priority, enabled=False, created_by=current_user.id, ) db.session.add(source) db.session.commit() flash(f"Added polling source: {display_name}", "success") logger.info(f"Admin {current_user.id} added poll source {platform}:{source_id}") return redirect(url_for("admin_polling")) @app.route("/admin/polling//toggle", methods=["POST"]) @login_required def admin_polling_toggle(source_id): """Toggle a poll source on/off.""" denied = _require_admin("index") if denied: return denied source = PollSource.query.get(source_id) if not source: flash("Source not found", "error") return redirect(url_for("admin_polling")) source.enabled = not source.enabled db.session.commit() status = "enabled" if source.enabled else "disabled" flash(f"Polling {status} for {source.display_name}", "success") return redirect(url_for("admin_polling")) @app.route("/admin/polling//update", methods=["POST"]) @login_required def admin_polling_update(source_id): """Update poll source configuration.""" denied = _require_admin("index") if denied: return denied source = PollSource.query.get(source_id) if not source: flash("Source not found", "error") return redirect(url_for("admin_polling")) if request.form.get("poll_interval"): source.poll_interval_minutes = int(request.form.get("poll_interval")) if request.form.get("max_posts"): source.max_posts = int(request.form.get("max_posts")) if request.form.get("fetch_comments") is not None: source.fetch_comments = request.form.get("fetch_comments") == "true" if request.form.get("priority"): source.priority = request.form.get("priority") if request.form.get("display_name"): source.display_name = request.form.get("display_name") db.session.commit() flash(f"Updated settings for {source.display_name}", "success") return redirect(url_for("admin_polling")) @app.route("/admin/polling//poll-now", methods=["POST"]) @login_required def admin_polling_poll_now(source_id): """Manually trigger polling for a source.""" denied = _require_admin("index") if denied: return denied source = PollSource.query.get(source_id) if not source: flash("Source not found", "error") return redirect(url_for("admin_polling")) try: get_polling_service().poll_now(source_id) flash(f"Polling started for {source.display_name}", "success") except Exception as e: flash(f"Error starting poll: {str(e)}", "error") logger.error(f"Error triggering poll for {source_id}: {e}") return redirect(url_for("admin_polling")) @app.route("/admin/polling//delete", methods=["POST"]) @login_required def admin_polling_delete(source_id): """Delete a poll source.""" denied = _require_admin("index") if denied: return denied source = PollSource.query.get(source_id) if not source: flash("Source not found", "error") return redirect(url_for("admin_polling")) display_name = source.display_name db.session.delete(source) db.session.commit() flash(f"Deleted polling source: {display_name}", "success") logger.info(f"Admin {current_user.id} deleted poll source {source_id}") return redirect(url_for("admin_polling")) @app.route("/admin/polling//logs") @login_required def admin_polling_logs(source_id): """View logs for a specific poll source.""" denied = _require_admin("index") if denied: return denied source = PollSource.query.get(source_id) if not source: flash("Source not found", "error") return redirect(url_for("admin_polling")) logs = source.logs.limit(50).all() return render_template("admin_polling_logs.html", source=source, logs=logs)