Files
DevOpsDash/app/redis_client.py

133 lines
5.2 KiB
Python
Raw Normal View History

"""Redis client for DevOpsDash — reads the same keys as DevOpsMCP."""
from __future__ import annotations
import json
import os
from typing import Any, Dict, List, Optional
import redis
# ── Key prefixes (must match DevOpsMCP exactly) ───────────────────────────────
BOARD_KEY_PREFIX = "devops-mcp:taskboard:"
TASK_KEY_PREFIX = "devops-mcp:task:"
BOARD_INDEX_KEY = "devops-mcp:taskboard:_index"
ADR_KEY_PREFIX = "devops-mcp:adr:"
ADR_INDEX_KEY = "devops-mcp:adr:_index"
MEMORY_KEY_PREFIX = "devops-mcp:memory:"
PROJECT_KEY_PREFIX = "devops-mcp:projects:"
PROJECT_INDEX_KEY = "devops-mcp:projects:_index"
def get_redis() -> redis.Redis:
url = os.environ.get("REDIS_URL", "redis://localhost:6379")
return redis.from_url(url, decode_responses=True, socket_connect_timeout=3)
# ── Task boards ───────────────────────────────────────────────────────────────
def list_boards(r: redis.Redis, project: Optional[str] = None, status: Optional[str] = None) -> List[Dict[str, Any]]:
index: List[str] = json.loads(r.get(BOARD_INDEX_KEY) or "[]")
boards = []
for bid in index:
raw = r.get(f"{BOARD_KEY_PREFIX}{bid}")
if raw:
board = json.loads(raw)
if project and project.lower() not in board.get("project", "").lower():
continue
if status and board.get("status") != status:
continue
boards.append(board)
boards.sort(key=lambda b: b.get("updated_at", ""), reverse=True)
return boards
def get_board(r: redis.Redis, board_id: str) -> Optional[Dict[str, Any]]:
raw = r.get(f"{BOARD_KEY_PREFIX}{board_id}")
return json.loads(raw) if raw else None
def save_board(r: redis.Redis, board: Dict[str, Any]) -> None:
bid = board["board_id"]
r.set(f"{BOARD_KEY_PREFIX}{bid}", json.dumps(board), ex=86400 * 365 * 3)
index: List[str] = json.loads(r.get(BOARD_INDEX_KEY) or "[]")
if bid not in index:
index.append(bid)
r.set(BOARD_INDEX_KEY, json.dumps(index), ex=86400 * 365 * 3)
def save_task(r: redis.Redis, task: Dict[str, Any]) -> None:
tid = task["task_id"]
r.set(f"{TASK_KEY_PREFIX}{tid}", json.dumps(task), ex=86400 * 365 * 3)
board = get_board(r, task["board_id"])
if board:
tasks = board.setdefault("tasks", [])
existing = next((i for i, t in enumerate(tasks) if t["task_id"] == tid), None)
if existing is not None:
tasks[existing] = task
else:
tasks.append(task)
from datetime import datetime, timezone
board["updated_at"] = datetime.now(timezone.utc).isoformat()
save_board(r, board)
# ── ADRs ──────────────────────────────────────────────────────────────────────
def list_adrs(r: redis.Redis, status_filter: Optional[str] = None) -> List[Dict[str, Any]]:
index: List[str] = json.loads(r.get(ADR_INDEX_KEY) or "[]")
adrs = []
for adr_id in index:
raw = r.get(f"{ADR_KEY_PREFIX}{adr_id}")
if raw:
adr = json.loads(raw)
if status_filter and adr.get("status", "").lower() != status_filter.lower():
continue
adrs.append(adr)
adrs.sort(key=lambda a: a.get("number", 0))
return adrs
def get_adr(r: redis.Redis, adr_id: str) -> Optional[Dict[str, Any]]:
raw = r.get(f"{ADR_KEY_PREFIX}{adr_id}")
return json.loads(raw) if raw else None
# ── Memories ──────────────────────────────────────────────────────────────────
def list_memories(r: redis.Redis, entity_type: Optional[str] = None, limit: int = 50) -> List[Dict[str, Any]]:
keys = r.keys(f"{MEMORY_KEY_PREFIX}*")
memories = []
for key in keys:
if key.endswith("_index"):
continue
raw = r.get(key)
if raw:
try:
mem = json.loads(raw)
if entity_type and mem.get("entity_type", "").lower() != entity_type.lower():
continue
memories.append(mem)
except json.JSONDecodeError:
pass
memories.sort(key=lambda m: m.get("created_at", ""), reverse=True)
return memories[:limit]
# ── Projects ──────────────────────────────────────────────────────────────────
def list_projects(r: redis.Redis, context: Optional[str] = None) -> List[Dict[str, Any]]:
index: List[str] = json.loads(r.get(PROJECT_INDEX_KEY) or "[]")
projects = []
for path in index:
raw = r.get(f"{PROJECT_KEY_PREFIX}{path}")
if raw:
proj = json.loads(raw)
if context and proj.get("context", "").lower() != context.lower():
continue
projects.append(proj)
return projects