fix user_id on bypass
This commit is contained in:
@@ -9,7 +9,8 @@ def automations_get():
|
|||||||
if not profile:
|
if not profile:
|
||||||
return ok({'rules': [], 'history': [], 'error': 'No profile'})
|
return ok({'rules': [], 'history': [], 'error': 'No profile'})
|
||||||
try:
|
try:
|
||||||
return ok({'rules': automation_rules.list_rules(profile['id']), 'history': automation_rules.list_history(profile['id'])})
|
user_id = default_user_id()
|
||||||
|
return ok({'rules': automation_rules.list_rules(profile['id'], user_id=user_id), 'history': automation_rules.list_history(profile['id'], user_id=user_id)})
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return jsonify({'ok': False, 'error': str(exc), 'rules': [], 'history': []}), 500
|
return jsonify({'ok': False, 'error': str(exc), 'rules': [], 'history': []}), 500
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ def automations_export():
|
|||||||
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
||||||
try:
|
try:
|
||||||
# Note: JSON export is profile-scoped and excludes execution history/cooldown state.
|
# Note: JSON export is profile-scoped and excludes execution history/cooldown state.
|
||||||
data = automation_rules.export_rules(profile['id'])
|
data = automation_rules.export_rules(profile['id'], user_id=default_user_id())
|
||||||
return ok({'export': data, 'count': len(data.get('rules') or [])})
|
return ok({'export': data, 'count': len(data.get('rules') or [])})
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return jsonify({'ok': False, 'error': str(exc)}), 400
|
return jsonify({'ok': False, 'error': str(exc)}), 400
|
||||||
@@ -40,8 +41,9 @@ def automations_import():
|
|||||||
payload = request.get_json(silent=True) or {}
|
payload = request.get_json(silent=True) or {}
|
||||||
replace = str(request.args.get('replace') or '').lower() in {'1', 'true', 'yes'} or bool(payload.get('replace')) if isinstance(payload, dict) else False
|
replace = str(request.args.get('replace') or '').lower() in {'1', 'true', 'yes'} or bool(payload.get('replace')) if isinstance(payload, dict) else False
|
||||||
# Note: Import appends rules by default, so existing automations remain untouched.
|
# Note: Import appends rules by default, so existing automations remain untouched.
|
||||||
imported = automation_rules.import_rules(profile['id'], payload, replace=replace)
|
user_id = default_user_id()
|
||||||
return ok({'imported': len(imported), 'rules': automation_rules.list_rules(profile['id'])})
|
imported = automation_rules.import_rules(profile['id'], payload, user_id=user_id, replace=replace)
|
||||||
|
return ok({'imported': len(imported), 'rules': automation_rules.list_rules(profile['id'], user_id=user_id)})
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return jsonify({'ok': False, 'error': str(exc)}), 400
|
return jsonify({'ok': False, 'error': str(exc)}), 400
|
||||||
|
|
||||||
@@ -54,8 +56,9 @@ def automations_save():
|
|||||||
if not profile:
|
if not profile:
|
||||||
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
||||||
try:
|
try:
|
||||||
rule = automation_rules.save_rule(profile['id'], request.get_json(silent=True) or {})
|
user_id = default_user_id()
|
||||||
return ok({'rule': rule, 'rules': automation_rules.list_rules(profile['id'])})
|
rule = automation_rules.save_rule(profile['id'], request.get_json(silent=True) or {}, user_id=user_id)
|
||||||
|
return ok({'rule': rule, 'rules': automation_rules.list_rules(profile['id'], user_id=user_id)})
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return jsonify({'ok': False, 'error': str(exc)}), 400
|
return jsonify({'ok': False, 'error': str(exc)}), 400
|
||||||
|
|
||||||
@@ -68,8 +71,9 @@ def automations_delete(rule_id: int):
|
|||||||
if not profile:
|
if not profile:
|
||||||
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
||||||
try:
|
try:
|
||||||
automation_rules.delete_rule(rule_id, profile['id'])
|
user_id = default_user_id()
|
||||||
return ok({'rules': automation_rules.list_rules(profile['id'])})
|
automation_rules.delete_rule(rule_id, profile['id'], user_id=user_id)
|
||||||
|
return ok({'rules': automation_rules.list_rules(profile['id'], user_id=user_id)})
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return jsonify({'ok': False, 'error': str(exc)}), 400
|
return jsonify({'ok': False, 'error': str(exc)}), 400
|
||||||
|
|
||||||
@@ -83,7 +87,8 @@ def automations_run_rule(rule_id: int):
|
|||||||
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
||||||
try:
|
try:
|
||||||
# Note: Single-rule run ignores disabled state and cooldown for manual troubleshooting.
|
# Note: Single-rule run ignores disabled state and cooldown for manual troubleshooting.
|
||||||
return ok({'result': automation_rules.check(profile, force=True, rule_id=rule_id), 'rules': automation_rules.list_rules(profile['id']), 'history': automation_rules.list_history(profile['id'])})
|
user_id = default_user_id()
|
||||||
|
return ok({'result': automation_rules.check(profile, user_id=user_id, force=True, rule_id=rule_id), 'rules': automation_rules.list_rules(profile['id'], user_id=user_id), 'history': automation_rules.list_history(profile['id'], user_id=user_id)})
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return jsonify({'ok': False, 'error': str(exc)}), 500
|
return jsonify({'ok': False, 'error': str(exc)}), 500
|
||||||
|
|
||||||
@@ -96,7 +101,8 @@ def automations_check():
|
|||||||
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
||||||
try:
|
try:
|
||||||
# Note: Force check ignores disabled state and cooldown, allowing a one-off manual automation pass.
|
# Note: Force check ignores disabled state and cooldown, allowing a one-off manual automation pass.
|
||||||
return ok({'result': automation_rules.check(profile, force=True), 'rules': automation_rules.list_rules(profile['id']), 'history': automation_rules.list_history(profile['id'])})
|
user_id = default_user_id()
|
||||||
|
return ok({'result': automation_rules.check(profile, user_id=user_id, force=True), 'rules': automation_rules.list_rules(profile['id'], user_id=user_id), 'history': automation_rules.list_history(profile['id'], user_id=user_id)})
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return jsonify({'ok': False, 'error': str(exc)}), 500
|
return jsonify({'ok': False, 'error': str(exc)}), 500
|
||||||
|
|
||||||
@@ -110,7 +116,8 @@ def automations_history_clear():
|
|||||||
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
||||||
try:
|
try:
|
||||||
# Note: Clear only automation execution logs; rules and cooldown state stay unchanged.
|
# Note: Clear only automation execution logs; rules and cooldown state stay unchanged.
|
||||||
deleted = automation_rules.clear_history(profile['id'])
|
user_id = default_user_id()
|
||||||
return ok({'deleted': deleted, 'history': automation_rules.list_history(profile['id']), 'cleanup': cleanup_summary()})
|
deleted = automation_rules.clear_history(profile['id'], user_id=user_id)
|
||||||
|
return ok({'deleted': deleted, 'history': automation_rules.list_history(profile['id'], user_id=user_id), 'cleanup': cleanup_summary()})
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return jsonify({'ok': False, 'error': str(exc)}), 500
|
return jsonify({'ok': False, 'error': str(exc)}), 500
|
||||||
|
|||||||
@@ -260,13 +260,14 @@ def cleanup_automations():
|
|||||||
if not profile:
|
if not profile:
|
||||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||||
profile_id = int(profile["id"])
|
profile_id = int(profile["id"])
|
||||||
|
user_id = default_user_id()
|
||||||
with connect() as conn:
|
with connect() as conn:
|
||||||
exists = conn.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='automation_history'").fetchone()
|
exists = conn.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='automation_history'").fetchone()
|
||||||
if not exists:
|
if not exists:
|
||||||
deleted = 0
|
deleted = 0
|
||||||
else:
|
else:
|
||||||
# Note: Cleanup panel removes only active profile automation logs, not saved automation rules.
|
# Note: Cleanup panel removes only current-user automation logs for the active profile; saved rules stay intact.
|
||||||
cur = conn.execute("DELETE FROM automation_history WHERE profile_id=?", (profile_id,))
|
cur = conn.execute("DELETE FROM automation_history WHERE user_id=? AND profile_id=?", (user_id, profile_id))
|
||||||
deleted = int(cur.rowcount or 0)
|
deleted = int(cur.rowcount or 0)
|
||||||
return ok({"deleted": deleted, "cleanup": cleanup_summary()})
|
return ok({"deleted": deleted, "cleanup": cleanup_summary()})
|
||||||
|
|
||||||
@@ -302,7 +303,8 @@ def cleanup_all():
|
|||||||
if not exists_auto:
|
if not exists_auto:
|
||||||
deleted_auto = 0
|
deleted_auto = 0
|
||||||
else:
|
else:
|
||||||
cur = conn.execute("DELETE FROM automation_history WHERE profile_id=?", (active_profile_id,))
|
# Note: Full cleanup clears only the current user's automation history for the active profile.
|
||||||
|
cur = conn.execute("DELETE FROM automation_history WHERE user_id=? AND profile_id=?", (default_user_id(), active_profile_id))
|
||||||
deleted_auto = int(cur.rowcount or 0)
|
deleted_auto = int(cur.rowcount or 0)
|
||||||
return ok({"deleted": {"jobs": deleted_jobs, "smart_queue_history": deleted_smart, "operation_logs": deleted_logs, "planner_history": deleted_planner, "automation_history": deleted_auto}, "cleanup": cleanup_summary()})
|
return ok({"deleted": {"jobs": deleted_jobs, "smart_queue_history": deleted_smart, "operation_logs": deleted_logs, "planner_history": deleted_planner, "automation_history": deleted_auto}, "cleanup": cleanup_summary()})
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from datetime import datetime, timezone
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
import json
|
import json
|
||||||
from ..db import connect, default_user_id, utcnow
|
from ..db import connect, default_user_id, utcnow
|
||||||
from . import rtorrent
|
from . import rtorrent, auth
|
||||||
from .preferences import active_profile
|
from .preferences import active_profile
|
||||||
from .workers import enqueue
|
from .workers import enqueue
|
||||||
|
|
||||||
@@ -11,6 +11,21 @@ AUTOMATION_JOB_CHUNK_SIZE = 100
|
|||||||
AUTOMATION_LIGHT_ACTIONS = {'start', 'stop', 'pause', 'resume', 'set_label'}
|
AUTOMATION_LIGHT_ACTIONS = {'start', 'stop', 'pause', 'resume', 'set_label'}
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_user_id(profile: dict[str, Any] | None = None, user_id: int | None = None) -> int:
|
||||||
|
"""Return the user id that should own automation reads, jobs and history.
|
||||||
|
|
||||||
|
Note: Request-bound calls must keep the authenticated/bypass user, while
|
||||||
|
background poller calls can safely fall back to the profile owner.
|
||||||
|
"""
|
||||||
|
if user_id:
|
||||||
|
return int(user_id)
|
||||||
|
request_user_id = auth.current_user_id()
|
||||||
|
if request_user_id:
|
||||||
|
return int(request_user_id)
|
||||||
|
if profile and profile.get('user_id'):
|
||||||
|
return int(profile.get('user_id') or 0)
|
||||||
|
return int(default_user_id())
|
||||||
|
|
||||||
|
|
||||||
def _loads(value: str | None, default: Any) -> Any:
|
def _loads(value: str | None, default: Any) -> Any:
|
||||||
try: return json.loads(value or '')
|
try: return json.loads(value or '')
|
||||||
@@ -51,7 +66,7 @@ def _rule_row(row: dict[str, Any]) -> dict[str, Any]:
|
|||||||
|
|
||||||
|
|
||||||
def list_rules(profile_id: int | None = None, user_id: int | None = None) -> list[dict[str, Any]]:
|
def list_rules(profile_id: int | None = None, user_id: int | None = None) -> list[dict[str, Any]]:
|
||||||
user_id = user_id or default_user_id()
|
user_id = _resolve_user_id(user_id=user_id)
|
||||||
if profile_id is None:
|
if profile_id is None:
|
||||||
profile = active_profile(); profile_id = int(profile['id']) if profile else None
|
profile = active_profile(); profile_id = int(profile['id']) if profile else None
|
||||||
with connect() as conn:
|
with connect() as conn:
|
||||||
@@ -71,7 +86,7 @@ def list_rules(profile_id: int | None = None, user_id: int | None = None) -> lis
|
|||||||
|
|
||||||
|
|
||||||
def get_rule(rule_id: int, profile_id: int, user_id: int | None = None) -> dict[str, Any]:
|
def get_rule(rule_id: int, profile_id: int, user_id: int | None = None) -> dict[str, Any]:
|
||||||
user_id = user_id or default_user_id()
|
user_id = _resolve_user_id(user_id=user_id)
|
||||||
with connect() as conn:
|
with connect() as conn:
|
||||||
row = conn.execute('SELECT * FROM automation_rules WHERE id=? AND user_id=? AND profile_id=?', (rule_id, user_id, profile_id)).fetchone()
|
row = conn.execute('SELECT * FROM automation_rules WHERE id=? AND user_id=? AND profile_id=?', (rule_id, user_id, profile_id)).fetchone()
|
||||||
if not row: raise ValueError('Rule not found')
|
if not row: raise ValueError('Rule not found')
|
||||||
@@ -95,7 +110,7 @@ def export_rules(profile_id: int, user_id: int | None = None) -> dict[str, Any]:
|
|||||||
|
|
||||||
|
|
||||||
def import_rules(profile_id: int, payload: dict[str, Any] | list[Any], user_id: int | None = None, replace: bool = False) -> list[dict[str, Any]]:
|
def import_rules(profile_id: int, payload: dict[str, Any] | list[Any], user_id: int | None = None, replace: bool = False) -> list[dict[str, Any]]:
|
||||||
user_id = user_id or default_user_id()
|
user_id = _resolve_user_id(user_id=user_id)
|
||||||
raw_rules = payload if isinstance(payload, list) else payload.get('rules', []) if isinstance(payload, dict) else []
|
raw_rules = payload if isinstance(payload, list) else payload.get('rules', []) if isinstance(payload, dict) else []
|
||||||
if not isinstance(raw_rules, list) or not raw_rules:
|
if not isinstance(raw_rules, list) or not raw_rules:
|
||||||
raise ValueError('Import file does not contain automation rules')
|
raise ValueError('Import file does not contain automation rules')
|
||||||
@@ -117,7 +132,7 @@ def import_rules(profile_id: int, payload: dict[str, Any] | list[Any], user_id:
|
|||||||
|
|
||||||
|
|
||||||
def save_rule(profile_id: int, data: dict[str, Any], user_id: int | None = None) -> dict[str, Any]:
|
def save_rule(profile_id: int, data: dict[str, Any], user_id: int | None = None) -> dict[str, Any]:
|
||||||
user_id = user_id or default_user_id()
|
user_id = _resolve_user_id(user_id=user_id)
|
||||||
name = str(data.get('name') or 'Automation rule').strip() or 'Automation rule'
|
name = str(data.get('name') or 'Automation rule').strip() or 'Automation rule'
|
||||||
conditions = data.get('conditions') or []
|
conditions = data.get('conditions') or []
|
||||||
effects = data.get('effects') or []
|
effects = data.get('effects') or []
|
||||||
@@ -136,20 +151,20 @@ def save_rule(profile_id: int, data: dict[str, Any], user_id: int | None = None)
|
|||||||
|
|
||||||
|
|
||||||
def delete_rule(rule_id: int, profile_id: int, user_id: int | None = None) -> None:
|
def delete_rule(rule_id: int, profile_id: int, user_id: int | None = None) -> None:
|
||||||
user_id = user_id or default_user_id()
|
user_id = _resolve_user_id(user_id=user_id)
|
||||||
with connect() as conn:
|
with connect() as conn:
|
||||||
conn.execute('DELETE FROM automation_rules WHERE id=? AND user_id=? AND profile_id=?', (rule_id, user_id, profile_id))
|
conn.execute('DELETE FROM automation_rules WHERE id=? AND user_id=? AND profile_id=?', (rule_id, user_id, profile_id))
|
||||||
conn.execute('DELETE FROM automation_rule_state WHERE rule_id=? AND profile_id=?', (rule_id, profile_id))
|
conn.execute('DELETE FROM automation_rule_state WHERE rule_id=? AND profile_id=?', (rule_id, profile_id))
|
||||||
|
|
||||||
|
|
||||||
def list_history(profile_id: int, user_id: int | None = None, limit: int = 30) -> list[dict[str, Any]]:
|
def list_history(profile_id: int, user_id: int | None = None, limit: int = 30) -> list[dict[str, Any]]:
|
||||||
user_id = user_id or default_user_id()
|
user_id = _resolve_user_id(user_id=user_id)
|
||||||
with connect() as conn:
|
with connect() as conn:
|
||||||
return conn.execute('SELECT * FROM automation_history WHERE user_id=? AND profile_id=? ORDER BY created_at DESC LIMIT ?', (user_id, profile_id, max(1, min(int(limit or 30), 100)))).fetchall()
|
return conn.execute('SELECT * FROM automation_history WHERE user_id=? AND profile_id=? ORDER BY created_at DESC LIMIT ?', (user_id, profile_id, max(1, min(int(limit or 30), 100)))).fetchall()
|
||||||
|
|
||||||
|
|
||||||
def clear_history(profile_id: int, user_id: int | None = None) -> int:
|
def clear_history(profile_id: int, user_id: int | None = None) -> int:
|
||||||
user_id = user_id or default_user_id()
|
user_id = _resolve_user_id(user_id=user_id)
|
||||||
with connect() as conn:
|
with connect() as conn:
|
||||||
# Note: Manual automation log cleanup is scoped to the active profile and current user.
|
# Note: Manual automation log cleanup is scoped to the active profile and current user.
|
||||||
cur = conn.execute('DELETE FROM automation_history WHERE user_id=? AND profile_id=?', (user_id, profile_id))
|
cur = conn.execute('DELETE FROM automation_history WHERE user_id=? AND profile_id=?', (user_id, profile_id))
|
||||||
@@ -337,7 +352,7 @@ def _apply_effects_bulk(c: Any, profile: dict[str, Any], torrents: list[dict[str
|
|||||||
def check(profile: dict | None = None, user_id: int | None = None, force: bool = False, rule_id: int | None = None) -> dict[str, Any]:
|
def check(profile: dict | None = None, user_id: int | None = None, force: bool = False, rule_id: int | None = None) -> dict[str, Any]:
|
||||||
profile = profile or active_profile()
|
profile = profile or active_profile()
|
||||||
if not profile: return {'ok': False, 'error': 'No active rTorrent profile'}
|
if not profile: return {'ok': False, 'error': 'No active rTorrent profile'}
|
||||||
user_id = user_id or default_user_id(); profile_id = int(profile['id'])
|
user_id = _resolve_user_id(profile, user_id); profile_id = int(profile['id'])
|
||||||
rules = [r for r in list_rules(profile_id, user_id) if (rule_id is None or int(r.get('id') or 0) == int(rule_id)) and (force or int(r.get('enabled') or 0))]
|
rules = [r for r in list_rules(profile_id, user_id) if (rule_id is None or int(r.get('id') or 0) == int(rule_id)) and (force or int(r.get('enabled') or 0))]
|
||||||
if not rules: return {'ok': True, 'checked': 0, 'applied': [], 'batches': [], 'rules': 0}
|
if not rules: return {'ok': True, 'checked': 0, 'applied': [], 'batches': [], 'rules': 0}
|
||||||
torrents = rtorrent.list_torrents(profile); applied = []; batches = []; now = utcnow()
|
torrents = rtorrent.list_torrents(profile); applied = []; batches = []; now = utcnow()
|
||||||
|
|||||||
Reference in New Issue
Block a user