335 lines
30 KiB
Python
335 lines
30 KiB
Python
from __future__ import annotations
|
|
|
|
from flask import Blueprint, render_template, jsonify, Response, request, redirect, url_for, abort
|
|
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
|
|
|
|
bp = Blueprint("main", __name__)
|
|
|
|
|
|
def _asset_url(key: str) -> str:
|
|
# Notatka: API docs korzysta z tego samego przełącznika CDN/offline co reszta aplikacji.
|
|
path = asset_path(key)
|
|
return path if path.startswith("http") else url_for("static", filename=path)
|
|
|
|
|
|
|
|
@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"""<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>pyTorrent API Docs</title><link rel="stylesheet" href="{_asset_url('swagger_css')}"></head><body><div id="swagger-ui"></div><script src="{_asset_url('swagger_js')}"></script><script>window.onload=()=>SwaggerUIBundle({{url:'/api/openapi.json',dom_id:'#swagger-ui',deepLinking:true,persistAuthorization:true}});</script></body></html>"""
|
|
return Response(html, mimetype="text/html")
|
|
|
|
|
|
@bp.get("/api/openapi.json")
|
|
def openapi():
|
|
paths = {
|
|
"/api/profiles": {
|
|
"get": {"summary": "List rTorrent profiles", "responses": {"200": {"description": "Profiles"}}},
|
|
"post": {"summary": "Create rTorrent profile", "requestBody": {"required": True, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string"}, "scgi_url": {"type": "string"}, "timeout_seconds": {"type": "integer"}, "max_parallel_jobs": {"type": "integer", "default": 5, "description": "Maximum queued jobs that may run at once for this rTorrent. Move/remove jobs keep request order."}, "is_remote": {"type": "boolean", "description": "When true, CPU/RAM host usage is hidden; public IP checks try remote rTorrent commands when supported."}}}}}}, "responses": {"200": {"description": "Created"}}}
|
|
},
|
|
|
|
"/api/profiles/{profile_id}": {
|
|
"put": {"summary": "Update rTorrent profile", "parameters": [{"name": "profile_id", "in": "path", "required": True, "schema": {"type": "integer"}}], "requestBody": {"required": True, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string"}, "scgi_url": {"type": "string"}, "timeout_seconds": {"type": "integer"}, "max_parallel_jobs": {"type": "integer", "default": 5, "description": "Maximum queued jobs that may run at once for this rTorrent. Move/remove jobs keep request order."}, "is_remote": {"type": "boolean", "description": "When true, CPU/RAM host usage is hidden; public IP checks try remote rTorrent commands when supported."}}}}}}, "responses": {"200": {"description": "Updated"}}},
|
|
"delete": {"summary": "Delete rTorrent profile", "parameters": [{"name": "profile_id", "in": "path", "required": True, "schema": {"type": "integer"}}], "responses": {"200": {"description": "Deleted"}}}
|
|
},
|
|
"/api/profiles/{profile_id}/activate": {"post": {"summary": "Activate profile", "parameters": [{"name": "profile_id", "in": "path", "required": True, "schema": {"type": "integer"}}], "responses": {"200": {"description": "Activated"}}}},
|
|
"/api/preferences": {
|
|
"get": {"summary": "Get preferences", "responses": {"200": {"description": "Preferences including theme, bootstrap_theme and font_family"}}},
|
|
"post": {
|
|
"summary": "Save preferences",
|
|
"requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {
|
|
"theme": {"type": "string", "enum": ["light", "dark"]},
|
|
"bootstrap_theme": {"type": "string", "enum": list(BOOTSTRAP_THEMES.keys())},
|
|
"font_family": {"type": "string", "enum": list(FONT_FAMILIES.keys())},
|
|
"table_columns_json": {"type": "string"},
|
|
"peers_refresh_seconds": {"type": "integer", "enum": [0, 10, 15, 30, 60]},
|
|
"port_check_enabled": {"type": "boolean"},
|
|
}}}}},
|
|
"responses": {"200": {"description": "Saved"}},
|
|
},
|
|
},
|
|
"/api/torrents": {"get": {"summary": "Get cached torrent snapshot", "responses": {"200": {"description": "Torrent list"}}}},
|
|
"/api/torrents/{action_name}": {"post": {"summary": "Queue torrent action", "description": "For move, path is the target directory; move_data=true physically moves data on the rTorrent host using a detached shell move with status polling, force-overwrites an existing destination, tolerates rTorrent execute timeouts around mkdir/start/polling, handles retries after a partially completed move, avoids SCGI timeout on long mv operations, and recheck defaults to move_data. Large move selections are split into ordered bulk parts of up to 100 hashes. Move and remove jobs are ordered per profile, so a later remove waits for earlier move/remove jobs to finish.", "parameters": [{"name": "action_name", "in": "path", "required": True, "schema": {"type": "string", "enum": ["start", "pause", "stop", "resume", "recheck", "remove", "move", "set_label", "set_ratio_group"]}}], "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"hashes": {"type": "array", "items": {"type": "string"}}, "path": {"type": "string", "description": "Target directory for move"}, "move_data": {"type": "boolean", "description": "Physically move data before setting torrent directory"}, "recheck": {"type": "boolean", "description": "Run hash check after physical move; defaults to move_data"}, "label": {"type": "string"}, "ratio_group": {"type": "string"}, "remove_data": {"type": "boolean"}}}}}}, "responses": {"200": {"description": "Job queued"}}}},
|
|
"/api/torrents/add": {"post": {"summary": "Add magnet links or torrent files", "requestBody": {"content": {"multipart/form-data": {"schema": {"type": "object", "properties": {"uris": {"type": "string"}, "directory": {"type": "string"}, "label": {"type": "string"}, "start": {"type": "boolean"}, "files": {"type": "array", "items": {"type": "string", "format": "binary"}}}}}, "application/json": {"schema": {"type": "object"}}}}, "responses": {"200": {"description": "Jobs queued"}}}},
|
|
"/api/torrents/{torrent_hash}/files": {"get": {"summary": "Torrent files", "parameters": [{"name": "torrent_hash", "in": "path", "required": True, "schema": {"type": "string"}}], "responses": {"200": {"description": "Files"}}}},
|
|
"/api/torrents/{torrent_hash}/peers": {"get": {"summary": "Torrent peers with GeoIP", "parameters": [{"name": "torrent_hash", "in": "path", "required": True, "schema": {"type": "string"}}], "responses": {"200": {"description": "Peers"}}}},
|
|
"/api/torrents/{torrent_hash}/trackers": {"get": {"summary": "Torrent trackers", "parameters": [{"name": "torrent_hash", "in": "path", "required": True, "schema": {"type": "string"}}], "responses": {"200": {"description": "Trackers"}}}},
|
|
"/api/speed/limits": {"post": {"summary": "Queue global speed limit change", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"down": {"type": "integer", "description": "Bytes per second, 0 unlimited"}, "up": {"type": "integer", "description": "Bytes per second, 0 unlimited"}}}}}}, "responses": {"200": {"description": "Job queued"}}}},
|
|
"/api/system/status": {"get": {"summary": "rTorrent/system status", "description": "For remote profiles CPU/RAM host usage is not returned and usage_available is false.", "responses": {"200": {"description": "Status"}}}},
|
|
"/api/port-check": {"get": {"summary": "Read cached incoming port check status", "responses": {"200": {"description": "Port check status including status, port, public_ip, source, cached, checked_at and checked_at_epoch"}}}, "post": {"summary": "Run incoming port check immediately, bypassing cache", "responses": {"200": {"description": "Fresh port check status including checked_at and checked_at_epoch"}}}},
|
|
"/api/jobs": {"get": {"summary": "List job queue history", "parameters": [{"name": "limit", "in": "query", "schema": {"type": "integer", "default": 50}}, {"name": "offset", "in": "query", "schema": {"type": "integer", "default": 0}}], "responses": {"200": {"description": "Jobs"}}}},
|
|
"/api/jobs/clear": {"post": {"summary": "Clear finished job history", "description": "Deletes jobs that are not pending or running.", "responses": {"200": {"description": "Deleted count"}}}},
|
|
"/api/jobs/{job_id}/cancel": {"post": {"summary": "Cancel pending or failed job", "parameters": [{"name": "job_id", "in": "path", "required": True, "schema": {"type": "string"}}], "responses": {"200": {"description": "Cancelled"}}}},
|
|
"/api/jobs/{job_id}/retry": {"post": {"summary": "Retry failed or cancelled job", "parameters": [{"name": "job_id", "in": "path", "required": True, "schema": {"type": "string"}}], "responses": {"200": {"description": "Retried"}}}},
|
|
"/api/path/browse": {"get": {"summary": "Browse server directories", "parameters": [{"name": "path", "in": "query", "schema": {"type": "string"}}], "responses": {"200": {"description": "Directory listing"}}}},
|
|
"/api/labels": {"get": {"summary": "List labels", "responses": {"200": {"description": "Labels"}}}, "post": {"summary": "Create label", "requestBody": {"content": {"application/json": {"schema": {"type": "object"}}}}, "responses": {"200": {"description": "Labels"}}}},
|
|
"/api/ratio-groups": {"get": {"summary": "List ratio groups", "responses": {"200": {"description": "Ratio groups"}}}, "post": {"summary": "Create or update ratio group", "requestBody": {"content": {"application/json": {"schema": {"type": "object"}}}}, "responses": {"200": {"description": "Ratio groups"}}}},
|
|
"/api/rss": {"get": {"summary": "List RSS feeds and rules", "responses": {"200": {"description": "RSS config"}}}},
|
|
"/api/rss/feeds": {"post": {"summary": "Add RSS feed", "requestBody": {"content": {"application/json": {"schema": {"type": "object"}}}}, "responses": {"200": {"description": "RSS config"}}}},
|
|
"/api/rss/rules": {"post": {"summary": "Add RSS rule", "requestBody": {"content": {"application/json": {"schema": {"type": "object"}}}}, "responses": {"200": {"description": "RSS config"}}}},
|
|
"/api/rss/check": {"post": {"summary": "Manually check RSS feeds", "responses": {"200": {"description": "Queued matches"}}}},
|
|
"/api/smart-queue": {"get": {"summary": "Get Smart Queue settings, exceptions and history", "parameters": [{"name": "history_limit", "in": "query", "schema": {"type": "integer", "default": 10, "minimum": 1, "maximum": 100}, "description": "Number of Smart Queue history rows to return"}], "responses": {"200": {"description": "Smart Queue config with history and history_total"}}}, "post": {"summary": "Save Smart Queue settings", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"enabled": {"type": "boolean"}, "max_active_downloads": {"type": "integer"}, "stalled_seconds": {"type": "integer"}, "min_speed_bytes": {"type": "integer"}, "min_seeds": {"type": "integer"}, "min_peers": {"type": "integer"}}}}}}, "responses": {"200": {"description": "Saved"}}}},
|
|
"/api/smart-queue/check": {"post": {"summary": "Run Smart Queue immediately", "responses": {"200": {"description": "Smart Queue action result"}}}},
|
|
"/api/smart-queue/exclusion": {"post": {"summary": "Add or remove a torrent Smart Queue exception", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"hash": {"type": "string"}, "excluded": {"type": "boolean"}, "reason": {"type": "string"}}}}}}, "responses": {"200": {"description": "Exception list"}}}},
|
|
"/api/traffic/history": {"get": {"summary": "Transfer history for charts", "parameters": [{"name": "range", "in": "query", "schema": {"type": "string", "enum": ["15m", "1h", "3h", "6h", "24h", "7d", "30d", "90d"]}}], "responses": {"200": {"description": "Aggregated traffic history"}}}}
|
|
}
|
|
paths.update({
|
|
"/api/auth/login": {"post": {"summary": "Log in with username and password when authentication is enabled", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"username": {"type": "string"}, "password": {"type": "string", "format": "password"}}, "required": ["username", "password"]}}}}, "responses": {"200": {"description": "Logged in"}, "401": {"description": "Invalid credentials"}, "404": {"description": "Authentication disabled"}}}},
|
|
"/api/auth/me": {"get": {"summary": "Return current authenticated user", "responses": {"200": {"description": "Current user"}, "404": {"description": "Authentication disabled"}}}},
|
|
"/api/auth/logout": {"post": {"summary": "Log out current user", "responses": {"200": {"description": "Logged out"}, "404": {"description": "Authentication disabled"}}}},
|
|
"/api/auth/users": {"get": {"summary": "List users, admin only", "responses": {"200": {"description": "Users"}, "403": {"description": "Admin only"}}}, "post": {"summary": "Create user, admin only", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AuthUserInput"}}}}, "responses": {"200": {"description": "User created"}, "403": {"description": "Admin only"}}}},
|
|
"/api/auth/users/{user_id}": {"put": {"summary": "Update user, admin only", "parameters": [{"name": "user_id", "in": "path", "required": True, "schema": {"type": "integer"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AuthUserInput"}}}}, "responses": {"200": {"description": "User updated"}, "403": {"description": "Admin only"}}}, "delete": {"summary": "Delete user, admin only", "parameters": [{"name": "user_id", "in": "path", "required": True, "schema": {"type": "integer"}}], "responses": {"200": {"description": "User deleted"}, "403": {"description": "Admin only"}}}},
|
|
"/api/profiles/{profile_id}": {"delete": {"summary": "Delete rTorrent profile", "parameters": [{"name": "profile_id", "in": "path", "required": True, "schema": {"type": "integer"}}], "responses": {"200": {"description": "Deleted"}}}},
|
|
"/api/torrent-stats": {"get": {"summary": "Torrent statistics and cached file metadata", "parameters": [{"name": "force", "in": "query", "schema": {"type": "boolean", "default": False}}], "responses": {"200": {"description": "Torrent statistics"}}}},
|
|
"/api/path/default": {"get": {"summary": "Read active rTorrent default download path", "responses": {"200": {"description": "Default path"}}}},
|
|
"/api/torrents/{torrent_hash}/files/priority": {"post": {"summary": "Set file priorities", "parameters": [{"name": "torrent_hash", "in": "path", "required": True, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"files": {"type": "array", "items": {"type": "object", "properties": {"index": {"type": "integer"}, "priority": {"type": "integer", "enum": [0, 1, 2]}}}}}}}}}, "responses": {"200": {"description": "Updated priorities"}, "207": {"description": "Partial update"}}}},
|
|
"/api/labels/{label_id}": {"delete": {"summary": "Delete saved label", "parameters": [{"name": "label_id", "in": "path", "required": True, "schema": {"type": "integer"}}], "responses": {"200": {"description": "Labels"}}}},
|
|
"/api/rtorrent-config": {"get": {"summary": "Read supported rTorrent config fields", "responses": {"200": {"description": "Config fields"}}}, "post": {"summary": "Save supported rTorrent config fields", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"values": {"type": "object"}}}}}}, "responses": {"200": {"description": "Save result"}}}},
|
|
"/api/rtorrent-config/generate": {"post": {"summary": "Generate rTorrent config text from provided values", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"values": {"type": "object"}}}}}}, "responses": {"200": {"description": "Generated config text"}}}},
|
|
"/api/automations": {"get": {"summary": "List automation rules and history", "responses": {"200": {"description": "Rules and history"}}}, "post": {"summary": "Create or update automation rule", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string"}, "enabled": {"type": "boolean"}, "cooldown_minutes": {"type": "integer"}, "conditions": {"type": "array"}, "effects": {"type": "array"}}}}}}, "responses": {"200": {"description": "Rule saved"}}}},
|
|
"/api/automations/{rule_id}": {"delete": {"summary": "Delete automation rule", "parameters": [{"name": "rule_id", "in": "path", "required": True, "schema": {"type": "integer"}}], "responses": {"200": {"description": "Deleted"}}}},
|
|
"/api/automations/check": {"post": {"summary": "Run automation rules immediately", "responses": {"200": {"description": "Automation result"}}}}
|
|
})
|
|
components = {
|
|
"schemas": {
|
|
"ApiOk": {
|
|
"type": "object",
|
|
"properties": {"ok": {"type": "boolean"}},
|
|
"required": ["ok"],
|
|
},
|
|
"AuthUserInput": {
|
|
"type": "object",
|
|
"properties": {
|
|
"username": {"type": "string"},
|
|
"password": {"type": "string", "format": "password", "description": "Optional on update"},
|
|
"role": {"type": "string", "enum": ["admin", "user"]},
|
|
"is_active": {"type": "boolean"},
|
|
"permissions": {"type": "array", "items": {"type": "object", "properties": {"profile_id": {"type": "integer", "description": "0 means all profiles"}, "access_level": {"type": "string", "enum": ["ro", "full"]}}}},
|
|
},
|
|
"required": ["username"],
|
|
},
|
|
"Profile": {
|
|
"type": "object",
|
|
"additionalProperties": True,
|
|
"properties": {
|
|
"id": {"type": "integer"},
|
|
"name": {"type": "string"},
|
|
"scgi_url": {"type": "string"},
|
|
"timeout_seconds": {"type": "integer"},
|
|
"max_parallel_jobs": {"type": "integer"},
|
|
},
|
|
},
|
|
"Torrent": {
|
|
"type": "object",
|
|
"additionalProperties": True,
|
|
"properties": {
|
|
"hash": {"type": "string"},
|
|
"name": {"type": "string"},
|
|
"path": {"type": "string"},
|
|
"status": {"type": "string"},
|
|
"size": {"type": "integer", "format": "int64"},
|
|
"completed_bytes": {"type": "integer", "format": "int64"},
|
|
"down_total": {"type": "integer", "format": "int64"},
|
|
"up_total": {"type": "integer", "format": "int64"},
|
|
"complete": {"type": "boolean"},
|
|
"state": {"type": "boolean"},
|
|
"paused": {"type": "boolean"},
|
|
"hashing": {"type": "integer"},
|
|
"message": {"type": "string"},
|
|
},
|
|
},
|
|
"TorrentFilterSummary": {
|
|
"type": "object",
|
|
"properties": {
|
|
"count": {"type": "integer", "description": "Number of torrents in this filter."},
|
|
"size": {"type": "integer", "format": "int64", "description": "Total torrent payload size in bytes."},
|
|
"disk_bytes": {"type": "integer", "format": "int64", "description": "Completed bytes reported by rTorrent; used as the displayed Data value."},
|
|
"completed_bytes": {"type": "integer", "format": "int64", "description": "Completed bytes reported by rTorrent."},
|
|
"remaining_bytes": {"type": "integer", "format": "int64", "description": "size - completed_bytes, never below zero."},
|
|
"progress_percent": {"type": "number", "format": "float", "description": "Completed percentage for this filter."},
|
|
"remaining_percent": {"type": "number", "format": "float", "description": "Remaining percentage for this filter."},
|
|
"down_total": {"type": "integer", "format": "int64", "deprecated": True, "description": "Backward compatibility field; not used by the filters UI."},
|
|
"up_total": {"type": "integer", "format": "int64", "deprecated": True, "description": "Backward compatibility field; not used by the filters UI."},
|
|
},
|
|
"required": ["count", "size", "disk_bytes", "completed_bytes", "remaining_bytes", "progress_percent", "remaining_percent"],
|
|
},
|
|
"TorrentSummaryFilters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"all": {"$ref": "#/components/schemas/TorrentFilterSummary"},
|
|
"downloading": {"$ref": "#/components/schemas/TorrentFilterSummary"},
|
|
"seeding": {"$ref": "#/components/schemas/TorrentFilterSummary"},
|
|
"paused": {"$ref": "#/components/schemas/TorrentFilterSummary"},
|
|
"checking": {"$ref": "#/components/schemas/TorrentFilterSummary"},
|
|
"error": {"$ref": "#/components/schemas/TorrentFilterSummary"},
|
|
"stopped": {"$ref": "#/components/schemas/TorrentFilterSummary"},
|
|
},
|
|
"required": ["all", "downloading", "seeding", "paused", "checking", "error", "stopped"],
|
|
},
|
|
"TorrentSummary": {
|
|
"type": "object",
|
|
"properties": {
|
|
"filters": {"$ref": "#/components/schemas/TorrentSummaryFilters"},
|
|
"cache_ttl_seconds": {"type": "integer", "description": "Summary cache TTL in seconds."},
|
|
"generated_at_epoch": {"type": "number", "format": "double", "description": "Unix timestamp when summary was generated."},
|
|
"cached": {"type": "boolean", "description": "True when returned from cache."},
|
|
},
|
|
"required": ["filters", "cache_ttl_seconds", "generated_at_epoch", "cached"],
|
|
},
|
|
"TorrentListResponse": {
|
|
"allOf": [
|
|
{"$ref": "#/components/schemas/ApiOk"},
|
|
{"type": "object", "properties": {
|
|
"profile_id": {"type": "integer"},
|
|
"torrents": {"type": "array", "items": {"$ref": "#/components/schemas/Torrent"}},
|
|
"summary": {"$ref": "#/components/schemas/TorrentSummary"},
|
|
"error": {"type": "string", "nullable": True},
|
|
}, "required": ["torrents", "summary"]},
|
|
],
|
|
},
|
|
"CleanupSummary": {
|
|
"type": "object",
|
|
"properties": {
|
|
"jobs_total": {"type": "integer"},
|
|
"jobs_clearable": {"type": "integer"},
|
|
"smart_queue_history_total": {"type": "integer"},
|
|
"retention_days": {"type": "object", "properties": {"jobs": {"type": "integer"}, "smart_queue_history": {"type": "integer"}}},
|
|
"database": {"type": "object", "properties": {"path": {"type": "string"}, "size": {"type": "integer", "format": "int64"}, "size_h": {"type": "string"}, "error": {"type": "string"}}},
|
|
},
|
|
"required": ["jobs_total", "jobs_clearable", "smart_queue_history_total", "retention_days", "database"],
|
|
},
|
|
"CleanupResponse": {
|
|
"allOf": [
|
|
{"$ref": "#/components/schemas/ApiOk"},
|
|
{"type": "object", "properties": {"cleanup": {"$ref": "#/components/schemas/CleanupSummary"}, "deleted": {"oneOf": [{"type": "integer"}, {"type": "object"}]}}},
|
|
],
|
|
},
|
|
"PortCheckStatus": {
|
|
"type": "object",
|
|
"additionalProperties": True,
|
|
"properties": {
|
|
"status": {"type": "string", "enum": ["open", "closed", "unknown", "disabled", "error"]},
|
|
"enabled": {"type": "boolean"},
|
|
"port": {"type": "integer"},
|
|
"public_ip": {"type": "string"},
|
|
"source": {"type": "string"},
|
|
"cached": {"type": "boolean"},
|
|
"checked_at": {"type": "string", "format": "date-time"},
|
|
"checked_at_epoch": {"type": "number", "format": "double"},
|
|
"error": {"type": "string"},
|
|
},
|
|
},
|
|
"AppStatus": {
|
|
"type": "object",
|
|
"properties": {
|
|
"pytorrent": {"type": "object", "additionalProperties": True},
|
|
"cleanup": {"$ref": "#/components/schemas/CleanupSummary"},
|
|
"profile": {"$ref": "#/components/schemas/Profile"},
|
|
"scgi": {"type": "object", "nullable": True, "additionalProperties": True},
|
|
"port_check": {"$ref": "#/components/schemas/PortCheckStatus"},
|
|
"api_ms": {"type": "number", "format": "float"},
|
|
},
|
|
"required": ["pytorrent", "cleanup", "scgi", "port_check", "api_ms"],
|
|
},
|
|
"AppStatusResponse": {
|
|
"allOf": [
|
|
{"$ref": "#/components/schemas/ApiOk"},
|
|
{"type": "object", "properties": {"status": {"$ref": "#/components/schemas/AppStatus"}}, "required": ["status"]},
|
|
],
|
|
},
|
|
"JobQueuedResponse": {
|
|
"allOf": [
|
|
{"$ref": "#/components/schemas/ApiOk"},
|
|
{"type": "object", "properties": {"job_id": {"type": "string"}, "job_ids": {"type": "array", "items": {"type": "string"}}, "hash_count": {"type": "integer"}, "bulk": {"type": "boolean"}}},
|
|
],
|
|
},
|
|
"TrackerActionResponse": {
|
|
"allOf": [
|
|
{"$ref": "#/components/schemas/ApiOk"},
|
|
{"type": "object", "properties": {"result": {"type": "object", "additionalProperties": True}, "message": {"type": "string"}}},
|
|
],
|
|
},
|
|
}
|
|
}
|
|
|
|
def response_ref(schema_name: str, description: str = "OK") -> dict:
|
|
return {"description": description, "content": {"application/json": {"schema": {"$ref": f"#/components/schemas/{schema_name}"}}}}
|
|
|
|
paths["/api/torrents"]["get"]["responses"]["200"] = response_ref("TorrentListResponse", "Torrent list with cached filter summary")
|
|
paths["/api/torrents/{action_name}"]["post"]["responses"]["200"] = response_ref("JobQueuedResponse", "Job queued")
|
|
paths["/api/torrents/add"]["post"]["responses"]["200"] = response_ref("JobQueuedResponse", "Jobs queued")
|
|
|
|
paths.update({
|
|
"/api/torrents/{torrent_hash}/trackers/{action_name}": {
|
|
"post": {
|
|
"summary": "Run tracker action",
|
|
"parameters": [
|
|
{"name": "torrent_hash", "in": "path", "required": True, "schema": {"type": "string"}},
|
|
{"name": "action_name", "in": "path", "required": True, "schema": {"type": "string"}},
|
|
],
|
|
"requestBody": {"content": {"application/json": {"schema": {"type": "object"}}}},
|
|
"responses": {"200": response_ref("TrackerActionResponse", "Tracker action result")},
|
|
}
|
|
},
|
|
"/api/app/status": {
|
|
"get": {"summary": "pyTorrent application status", "responses": {"200": response_ref("AppStatusResponse", "Application status")}}
|
|
},
|
|
"/api/cleanup/summary": {
|
|
"get": {"summary": "Cleanup summary", "responses": {"200": response_ref("CleanupResponse", "Cleanup summary")}}
|
|
},
|
|
"/api/cleanup/jobs": {
|
|
"post": {"summary": "Clear finished job history", "responses": {"200": response_ref("CleanupResponse", "Cleanup result")}}
|
|
},
|
|
"/api/cleanup/smart-queue": {
|
|
"post": {"summary": "Clear Smart Queue history", "responses": {"200": response_ref("CleanupResponse", "Cleanup result")}}
|
|
},
|
|
"/api/cleanup/all": {
|
|
"post": {"summary": "Clear all cleanup-supported history", "responses": {"200": response_ref("CleanupResponse", "Cleanup result")}}
|
|
},
|
|
})
|
|
|
|
components.setdefault("securitySchemes", {})["sessionCookie"] = {"type": "apiKey", "in": "cookie", "name": "session"}
|
|
for path, methods in paths.items():
|
|
if path != "/api/auth/login":
|
|
for operation in methods.values():
|
|
operation.setdefault("security", [{"sessionCookie": []}])
|
|
return jsonify({"openapi": "3.0.3", "info": {"title": "pyTorrent API", "version": "0.0.1"}, "paths": paths, "components": components})
|