fix(blocklist): get_candidate for O(1) push/unblock, 400 on malformed device_names JSON

This commit is contained in:
pyr0ball 2026-05-15 21:19:02 -07:00
parent 1e186591d7
commit 5263a67fb3
2 changed files with 17 additions and 7 deletions

View file

@ -29,6 +29,7 @@ from app.ingest.base import load_compiled_patterns
from app.ingest.tautulli import parse_webhook as _parse_tautulli from app.ingest.tautulli import parse_webhook as _parse_tautulli
from app.services.blocklist import ( from app.services.blocklist import (
BlocklistCandidate, BlocklistCandidate,
get_candidate,
list_candidates, list_candidates,
load_telemetry_rules, load_telemetry_rules,
mark_pushed, mark_pushed,
@ -591,7 +592,7 @@ def scan_blocklist(background_tasks: BackgroundTasks) -> dict:
try: try:
device_map = json.loads(raw_devices) device_map = json.loads(raw_devices)
except (ValueError, TypeError): except (ValueError, TypeError):
pass raise HTTPException(status_code=400, detail="device_names is not valid JSON — update it in Settings")
telemetry_path = PATTERN_DIR / "telemetry.yaml" telemetry_path = PATTERN_DIR / "telemetry.yaml"
telemetry_rules = load_telemetry_rules(telemetry_path) if telemetry_path.exists() else [] telemetry_rules = load_telemetry_rules(telemetry_path) if telemetry_path.exists() else []
background_tasks.add_task(run_scan, DB_PATH, source_ids, device_map, telemetry_rules) background_tasks.add_task(run_scan, DB_PATH, source_ids, device_map, telemetry_rules)
@ -611,9 +612,9 @@ def update_blocklist_status(candidate_id: str, body: BlocklistStatusBody) -> dic
@router.post("/api/blocklist/push/{candidate_id}") @router.post("/api/blocklist/push/{candidate_id}")
def push_to_pihole(candidate_id: str) -> dict: def push_to_pihole(candidate_id: str) -> dict:
candidates = list_candidates(DB_PATH) try:
candidate = next((c for c in candidates if c.id == candidate_id), None) candidate = get_candidate(DB_PATH, candidate_id)
if candidate is None: except KeyError:
raise HTTPException(status_code=404, detail="Candidate not found") raise HTTPException(status_code=404, detail="Candidate not found")
if candidate.status != "approved": if candidate.status != "approved":
raise HTTPException( raise HTTPException(
@ -628,9 +629,9 @@ def push_to_pihole(candidate_id: str) -> dict:
@router.delete("/api/blocklist/push/{candidate_id}") @router.delete("/api/blocklist/push/{candidate_id}")
def unblock_from_pihole(candidate_id: str) -> dict: def unblock_from_pihole(candidate_id: str) -> dict:
candidates = list_candidates(DB_PATH) try:
candidate = next((c for c in candidates if c.id == candidate_id), None) candidate = get_candidate(DB_PATH, candidate_id)
if candidate is None: except KeyError:
raise HTTPException(status_code=404, detail="Candidate not found") raise HTTPException(status_code=404, detail="Candidate not found")
if candidate.status != "pushed": if candidate.status != "pushed":
raise HTTPException( raise HTTPException(

View file

@ -253,6 +253,15 @@ def _get_candidate(conn: sqlite3.Connection, candidate_id: str) -> BlocklistCand
return _row_to_candidate(row) return _row_to_candidate(row)
def get_candidate(db_path: Path, candidate_id: str) -> BlocklistCandidate:
"""Fetch a single candidate by ID. Raises KeyError if not found."""
conn = sqlite3.connect(str(db_path))
try:
return _get_candidate(conn, candidate_id)
finally:
conn.close()
def update_candidate_status(db_path: Path, candidate_id: str, new_status: str) -> BlocklistCandidate: def update_candidate_status(db_path: Path, candidate_id: str, new_status: str) -> BlocklistCandidate:
if new_status not in _VALID_STATUSES: if new_status not in _VALID_STATUSES:
raise ValueError(f"Invalid status {new_status!r}. Must be one of {_VALID_STATUSES}") raise ValueError(f"Invalid status {new_status!r}. Must be one of {_VALID_STATUSES}")