profile id support in api requests

This commit is contained in:
Mateusz Gruszczyński
2026-06-16 19:46:16 +02:00
parent c796a740d1
commit a73aeb5544
18 changed files with 1823 additions and 185 deletions
+73 -1
View File
@@ -23,7 +23,7 @@ from flask import Blueprint, jsonify, request, abort, send_file, redirect, Respo
from ..config import DB_PATH, JOBS_RETENTION_DAYS, SMART_QUEUE_HISTORY_RETENTION_DAYS, LOG_RETENTION_DAYS, WORKERS, PYTORRENT_TMP_DIR
from ..db import connect, utcnow
from ..services.auth import current_user_id as default_user_id, current_user, list_users, save_user, delete_user, login_user, logout_user, enabled as auth_enabled, require_profile_write, require_admin, is_admin
from ..services import preferences, rtorrent, torrent_stats, speed_peaks, tracker_cache, rss as rss_service, ratio_rules, backup as backup_service, download_planner, operation_logs, poller_control, database_maintenance
from ..services import auth, preferences, rtorrent, torrent_stats, speed_peaks, tracker_cache, rss as rss_service, ratio_rules, backup as backup_service, download_planner, operation_logs, poller_control, database_maintenance
from ..services.torrent_cache import torrent_cache
from ..services.torrent_summary import cached_summary
from ..services.workers import enqueue, list_jobs, cancel_job, retry_job, force_job, clear_jobs, emergency_clear_jobs
@@ -39,6 +39,78 @@ from .auth_api import register_auth_routes
register_auth_routes(bp)
def _request_profile_selector() -> tuple[int | None, str]:
"""Return the optional profile selector supplied by external API clients."""
payload = {}
if request.method in {"POST", "PUT", "PATCH", "DELETE"}:
try:
payload = request.get_json(silent=True) or {}
except Exception:
payload = {}
profile_id = request.args.get("profile_id") or request.form.get("profile_id") or payload.get("profile_id") or request.headers.get("X-PyTorrent-Profile-Id")
profile_name = request.args.get("profile_name") or request.form.get("profile_name") or payload.get("profile_name") or request.headers.get("X-PyTorrent-Profile-Name") or ""
try:
return (int(profile_id), "") if profile_id not in (None, "") else (None, str(profile_name or "").strip())
except (TypeError, ValueError):
raise ValueError("profile_id must be an integer")
def _profile_by_name(profile_name: str, user_id: int | None = None):
name = str(profile_name or "").strip()
if not name:
return None
user_id = user_id or default_user_id()
visible = auth.visible_profile_ids(user_id)
with connect() as conn:
if visible is None:
return conn.execute(
"SELECT * FROM rtorrent_profiles WHERE lower(name)=lower(?) ORDER BY is_default DESC, id LIMIT 1",
(name,),
).fetchone()
if not visible:
return None
placeholders = ",".join("?" for _ in visible)
return conn.execute(
f"SELECT * FROM rtorrent_profiles WHERE id IN ({placeholders}) AND lower(name)=lower(?) ORDER BY is_default DESC, id LIMIT 1",
(*tuple(visible), name),
).fetchone()
def request_profile(require_write: bool = False):
"""Resolve API profile context from profile_id/profile_name, then active profile for compatibility."""
try:
profile_id, profile_name = _request_profile_selector()
except ValueError:
raise
user_id = default_user_id()
profile = None
if profile_id:
profile = preferences.get_profile(int(profile_id), user_id)
elif profile_name:
profile = _profile_by_name(profile_name, user_id)
else:
profile = preferences.active_profile(user_id)
if not profile and auth.can_access_profile(1, user_id):
profile = preferences.get_profile(1, user_id)
if not profile and (profile_id or profile_name):
abort(404)
if not profile:
return None
pid = int(profile["id"])
if require_write and not auth.can_write_profile(pid, user_id):
abort(403)
if not require_write and not auth.can_access_profile(pid, user_id):
abort(403)
return profile
def request_profile_id(require_write: bool = False) -> int | None:
profile = request_profile(require_write=require_write)
return int(profile["id"]) if profile else None
def _job_profile_id(job_id: str) -> int | None:
with connect() as conn:
row = conn.execute("SELECT profile_id FROM jobs WHERE id=?", (job_id,)).fetchone()