from __future__ import annotations from pathlib import Path from flask import Blueprint, render_template, Response, request, redirect, url_for, abort, send_file from ..services.preferences import get_preferences, list_profiles, active_profile, BOOTSTRAP_THEMES, FONT_FAMILIES from ..services import auth from ..services.frontend_assets import asset_path # for favicon from flask import current_app, send_from_directory bp = Blueprint("main", __name__) def _asset_url(key: str) -> str: path = asset_path(key) return path if path.startswith("http") else url_for("static", filename=path) @bp.get("/favicon.ico") def favicon_ico(): response = send_from_directory( current_app.static_folder, "favicon.svg", mimetype="image/svg+xml", ) return response @bp.route("/login", methods=["GET", "POST"]) def login(): # Note: When optional authentication is disabled, /login is intentionally unavailable. if not auth.enabled(): abort(404) error = "" if request.method == "POST": user = auth.login_user(request.form.get("username", ""), request.form.get("password", "")) if user: return redirect(request.args.get("next") or url_for("main.index")) error = "Invalid username or password" return render_template("login.html", error=error) @bp.get("/logout") def logout(): auth.logout_user() if not auth.enabled(): return redirect(url_for("main.index")) return redirect(url_for("main.login")) @bp.get("/") def index(): prefs = get_preferences() return render_template( "index.html", prefs=prefs, profiles=list_profiles(), active_profile=active_profile(), bootstrap_themes=BOOTSTRAP_THEMES, font_families=FONT_FAMILIES, auth_enabled=auth.enabled(), current_user=auth.current_user(), ) @bp.get("/docs") def docs(): html = f"""pyTorrent API Docs
""" return Response(html, mimetype="text/html") @bp.get("/api/openapi.json") def openapi(): spec_path = Path(current_app.root_path) / "openapi" / "openapi.json" response = send_file(spec_path, mimetype="application/json", conditional=False, max_age=0) response.headers["Cache-Control"] = "no-store, no-cache, private" return response