Add issue resolution confirmation workflow
Magent CI/CD / verify (push) Successful in 10m35s
Magent CI/CD / deploy-prod (push) Skipped
Magent CI/CD / deploy-beta (push) Successful in 12s

This commit is contained in:
2026-09-01 11:40:43 +12:00
parent 393b8c2a88
commit 2adbed7259
14 changed files with 1155 additions and 9 deletions
+66 -1
View File
@@ -19,6 +19,8 @@ from ..auth import get_current_user
from ..runtime import get_runtime_settings
from .images import cache_tmdb_image, is_tmdb_cached
from ..db import (
add_portal_item_activity,
get_portal_item,
save_action,
get_recent_actions,
get_recent_snapshots,
@@ -1796,6 +1798,46 @@ def _replacement_file_payload(
}
def _linked_issue_for_replacement(
issue_id: Any,
*,
request_id: str,
user: Dict[str, str],
) -> Optional[Dict[str, Any]]:
if issue_id is None:
return None
if not isinstance(issue_id, int) or issue_id <= 0:
raise HTTPException(status_code=400, detail="A valid linked issue is required")
issue = get_portal_item(issue_id)
if not issue or str(issue.get("kind") or "").lower() != "issue":
raise HTTPException(status_code=404, detail="Linked issue not found")
if str(issue.get("external_ref") or "") != f"/requests/{request_id}":
raise HTTPException(status_code=409, detail="The issue is not linked to this request")
is_admin = str(user.get("role") or "").lower() == "admin"
is_owner = str(issue.get("created_by_username") or "").lower() == str(user.get("username") or "").lower()
if not (is_admin or is_owner):
raise HTTPException(status_code=403, detail="You cannot update this linked issue")
return issue
def _record_replacement_activity(
issue: Optional[Dict[str, Any]],
*,
user: Dict[str, str],
event_type: str,
message: str,
) -> None:
if not issue:
return
add_portal_item_activity(
int(issue["id"]),
event_type=event_type,
actor_username=str(user.get("username") or "unknown"),
actor_role=str(user.get("role") or "user"),
message=message,
)
async def _resolve_root_folder_path(client: Any, root_folder: str, service_name: str) -> str:
if root_folder.isdigit():
folders = await client.get_root_folders()
@@ -1915,6 +1957,11 @@ async def action_replace_media(
file_id = payload.get("file_id")
if not isinstance(file_id, int) or file_id <= 0:
raise HTTPException(status_code=400, detail="A valid managed file is required")
linked_issue = _linked_issue_for_replacement(
payload.get("issue_id"),
request_id=request_id,
user=user,
)
runtime = get_runtime_settings()
seerr = JellyseerrClient(runtime.jellyseerr_base_url, runtime.jellyseerr_api_key)
@@ -1976,7 +2023,13 @@ async def action_replace_media(
await sonarr.search_episodes(episode_ids)
else:
raise HTTPException(status_code=400, detail="Unknown request type")
except HTTPException:
except HTTPException as exc:
_record_replacement_activity(
linked_issue,
user=user,
event_type="replacement_failed",
message=f"The media replacement could not be started: {exc.detail}",
)
raise
except Exception as exc:
logger.exception("%s media replacement failed request_id=%s file_id=%s", collector, request_id, file_id)
@@ -1992,6 +2045,12 @@ async def action_replace_media(
"failed",
detail,
)
_record_replacement_activity(
linked_issue,
user=user,
event_type="replacement_failed",
message=detail,
)
raise HTTPException(status_code=502, detail=detail) from exc
message = f"{collector} removed {target_name} and started a replacement search."
@@ -2003,6 +2062,12 @@ async def action_replace_media(
"ok",
message,
)
_record_replacement_activity(
linked_issue,
user=user,
event_type="replacement_started",
message=message,
)
return {
"status": "ok",
"message": message,