fix profile-scoped backups and shared profile rules

This commit is contained in:
Mateusz Gruszczyński
2026-06-07 23:12:00 +02:00
parent 51e00a4e37
commit 8990f2b404
10 changed files with 264 additions and 86 deletions
+6 -3
View File
@@ -35,9 +35,12 @@ def operation_logs_settings_save():
profile = _active_profile_or_400()
if not profile:
return jsonify({"ok": False, "error": "No profile"}), 400
settings = operation_logs.save_settings(int(profile["id"]), request.get_json(silent=True) or {})
result = operation_logs.apply_retention(int(profile["id"]))
return ok({"settings": settings, "retention": result})
try:
settings = operation_logs.save_settings(int(profile["id"]), request.get_json(silent=True) or {})
result = operation_logs.apply_retention(int(profile["id"]))
return ok({"settings": settings, "retention": result})
except Exception as exc:
return jsonify({"ok": False, "error": str(exc)}), 403 if isinstance(exc, PermissionError) else 400
@bp.post("/operation-logs/clear")
+49 -11
View File
@@ -2,6 +2,7 @@ from __future__ import annotations
from ._shared import *
from ..services.rtorrent.diagnostics import profile_diagnostics
from ..services import auth
@bp.get("/profiles")
def profiles_list():
@@ -108,8 +109,19 @@ def prefs_table_columns_recommended():
def labels_list():
profile = preferences.active_profile()
pid = profile["id"] if profile else None
if not pid:
return ok({"labels": []})
with connect() as conn:
rows = conn.execute("SELECT * FROM labels WHERE user_id=? AND (profile_id=? OR profile_id IS NULL) ORDER BY name COLLATE NOCASE", (default_user_id(), pid)).fetchall()
rows = conn.execute(
"""
SELECT l.*, COALESCE(u.display_name,u.username,u.email,'user ' || l.user_id) AS owner_name
FROM labels l
LEFT JOIN users u ON u.id=l.user_id
WHERE l.profile_id=?
ORDER BY l.name COLLATE NOCASE, l.id
""",
(pid,),
).fetchall()
return ok({"labels": rows})
@@ -123,9 +135,15 @@ def labels_save():
name = str(data.get("name") or "").strip()
if not name:
return jsonify({"ok": False, "error": "Missing label name"}), 400
if not auth.can_write_profile(int(profile["id"]), default_user_id()):
return jsonify({"ok": False, "error": "No write access to profile"}), 403
now = utcnow()
with connect() as conn:
conn.execute("INSERT OR IGNORE INTO labels(user_id,profile_id,name,color,created_at,updated_at) VALUES(?,?,?,?,?,?)", (default_user_id(), profile["id"], name, data.get("color") or "#64748b", now, now))
existing = conn.execute("SELECT id FROM labels WHERE profile_id=? AND lower(name)=lower(?) ORDER BY id LIMIT 1", (profile["id"], name)).fetchone()
if existing:
conn.execute("UPDATE labels SET color=?, updated_at=? WHERE id=?", (data.get("color") or "#64748b", now, existing["id"]))
else:
conn.execute("INSERT INTO labels(user_id,profile_id,name,color,created_at,updated_at) VALUES(?,?,?,?,?,?)", (default_user_id(), profile["id"], name, data.get("color") or "#64748b", now, now))
return labels_list()
@@ -134,8 +152,10 @@ def labels_save():
def labels_delete(label_id: int):
profile = preferences.active_profile()
pid = profile["id"] if profile else None
if not pid or not auth.can_write_profile(int(pid), default_user_id()):
return jsonify({"ok": False, "error": "No write access to profile"}), 403
with connect() as conn:
conn.execute("DELETE FROM labels WHERE id=? AND user_id=? AND (profile_id=? OR profile_id IS NULL)", (label_id, default_user_id(), pid))
conn.execute("DELETE FROM labels WHERE id=? AND profile_id=?", (label_id, pid))
return labels_list()
@@ -145,8 +165,17 @@ def ratio_groups_list():
profile = preferences.active_profile()
pid = profile["id"] if profile else None
with connect() as conn:
rows = conn.execute("SELECT * FROM ratio_groups WHERE user_id=? AND (profile_id=? OR profile_id IS NULL) ORDER BY name COLLATE NOCASE", (default_user_id(), pid)).fetchall()
history = conn.execute("SELECT * FROM ratio_history WHERE user_id=? AND profile_id=? ORDER BY id DESC LIMIT 50", (default_user_id(), pid or 0)).fetchall() if pid else []
rows = conn.execute(
"""
SELECT g.*, COALESCE(u.display_name,u.username,u.email,'user ' || g.user_id) AS owner_name
FROM ratio_groups g
LEFT JOIN users u ON u.id=g.user_id
WHERE g.profile_id=?
ORDER BY g.name COLLATE NOCASE, g.id
""",
(pid or 0,),
).fetchall() if pid else []
history = conn.execute("SELECT * FROM ratio_history WHERE profile_id=? ORDER BY id DESC LIMIT 50", (pid or 0,)).fetchall() if pid else []
return ok({"groups": rows, "history": history})
@@ -160,14 +189,23 @@ def ratio_groups_save():
name = str(data.get("name") or "").strip()
if not name:
return jsonify({"ok": False, "error": "Missing group name"}), 400
if not auth.can_write_profile(int(profile["id"]), default_user_id()):
return jsonify({"ok": False, "error": "No write access to profile"}), 403
now = utcnow()
with connect() as conn:
conn.execute(
"""INSERT INTO ratio_groups(user_id,profile_id,name,min_ratio,max_ratio,seed_time_minutes,min_seed_time_minutes,ignore_private,ignore_active_upload,active_upload_min_bytes,move_path,set_label,action,enabled,created_at,updated_at)
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
ON CONFLICT(user_id,profile_id,name) DO UPDATE SET min_ratio=excluded.min_ratio,max_ratio=excluded.max_ratio,seed_time_minutes=excluded.seed_time_minutes,min_seed_time_minutes=excluded.min_seed_time_minutes,ignore_private=excluded.ignore_private,ignore_active_upload=excluded.ignore_active_upload,active_upload_min_bytes=excluded.active_upload_min_bytes,move_path=excluded.move_path,set_label=excluded.set_label,action=excluded.action,enabled=excluded.enabled,updated_at=excluded.updated_at""",
(default_user_id(), profile["id"], name, float(data.get("min_ratio") or 1), float(data.get("max_ratio") or 2), int(data.get("seed_time_minutes") or 0), int(data.get("min_seed_time_minutes") or 0), 1 if data.get("ignore_private", True) else 0, 1 if data.get("ignore_active_upload", True) else 0, int(data.get("active_upload_min_bytes") or 1024), data.get("move_path") or "", data.get("set_label") or "", data.get("action") or "stop", 1 if data.get("enabled", True) else 0, now, now),
)
existing = conn.execute("SELECT id,user_id FROM ratio_groups WHERE profile_id=? AND lower(name)=lower(?) ORDER BY id LIMIT 1", (profile["id"], name)).fetchone()
values = (float(data.get("min_ratio") or 1), float(data.get("max_ratio") or 2), int(data.get("seed_time_minutes") or 0), int(data.get("min_seed_time_minutes") or 0), 1 if data.get("ignore_private", True) else 0, 1 if data.get("ignore_active_upload", True) else 0, int(data.get("active_upload_min_bytes") or 1024), data.get("move_path") or "", data.get("set_label") or "", data.get("action") or "stop", 1 if data.get("enabled", True) else 0, now)
if existing:
conn.execute(
"""UPDATE ratio_groups SET min_ratio=?,max_ratio=?,seed_time_minutes=?,min_seed_time_minutes=?,ignore_private=?,ignore_active_upload=?,active_upload_min_bytes=?,move_path=?,set_label=?,action=?,enabled=?,updated_at=? WHERE id=? AND profile_id=?""",
(*values, existing["id"], profile["id"]),
)
else:
conn.execute(
"""INSERT INTO ratio_groups(user_id,profile_id,name,min_ratio,max_ratio,seed_time_minutes,min_seed_time_minutes,ignore_private,ignore_active_upload,active_upload_min_bytes,move_path,set_label,action,enabled,created_at,updated_at)
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(default_user_id(), profile["id"], name, *values[:-1], now, now),
)
return ratio_groups_list()