"""PostgreSQL persistence for typed miner control operations.""" from collections.abc import Callable from typing import Any from uuid import UUID import psycopg from psycopg.errors import UniqueViolation from psycopg.rows import dict_row from psycopg.types.json import Jsonb from app.config import settings from app.miner_control.exceptions import MinerControlConflict ConnectionFactory = Callable[[], psycopg.Connection] LIVE_CONTROL_CONSTRAINT = "uq_agent_queue_live_miner_control" def default_connection_factory() -> psycopg.Connection: return psycopg.connect( host=settings.postgres_host, port=settings.postgres_port, dbname=settings.postgres_db, user=settings.postgres_user, password=settings.postgres_password, connect_timeout=5, ) class MinerControlRepository: """Resolve control facts and persist queue/audit rows atomically.""" def __init__(self, connection_factory: ConnectionFactory | None = None): self._connection_factory = connection_factory or default_connection_factory def get_control_context(self, miner_id: UUID) -> dict | None: connection = self._connection_factory() try: with connection.cursor(row_factory=dict_row) as cur: cur.execute( """ SELECT miners.id, miners.site_id, miners.agent_zone_id, zones.id IS NOT NULL AS agent_zone_exists, zones.is_enabled AS agent_zone_enabled, host(miners.current_ip) AS current_ip, miners.status AS runtime_state, miners.configured_performance_mode, miners.api3_supported, miners.api3_api_switch, agents.agent_id, agents.last_checkin_at, agents.version AS agent_version, agents.last_payload FROM miners LEFT JOIN agent_zones AS zones ON zones.id = miners.agent_zone_id LEFT JOIN minerops_site_agents AS agents ON agents.agent_zone_id = miners.agent_zone_id WHERE miners.id = %s """, (miner_id,), ) row = cur.fetchone() return dict(row) if row else None finally: connection.close() def list_recent(self, miner_id: UUID, limit: int = 5) -> list[dict]: connection = self._connection_factory() try: with connection.cursor(row_factory=dict_row) as cur: cur.execute( """ SELECT id AS operation_id, command_status AS status, request_payload, result_payload, created_at, claimed_at, completed_at FROM minerops_agent_command_queue WHERE command_type = 'miner_control' AND request_payload ->> 'miner_id' = %s AND request_payload ->> 'schema_version' = '1' ORDER BY created_at DESC, id DESC LIMIT %s """, (str(miner_id), limit), ) return [dict(row) for row in cur.fetchall()] finally: connection.close() def enqueue_with_audit( self, *, operation_id: str, miner_id: UUID, agent_id: str, actor_user_id: UUID, actor_username: str, request_payload: dict[str, Any], audit_payload: dict[str, Any], ) -> dict: connection = self._connection_factory() try: with connection.cursor(row_factory=dict_row) as cur: cur.execute( """ INSERT INTO minerops_agent_command_queue ( id, agent_id, command_type, command_status, requested_by_user_id, requested_by_username, request_payload ) VALUES (%s, %s, 'miner_control', 'pending', %s, %s, %s) RETURNING id AS operation_id, command_status AS status, created_at """, ( operation_id, agent_id, str(actor_user_id), actor_username, Jsonb(request_payload), ), ) operation = dict(cur.fetchone()) cur.execute( """ INSERT INTO audit_logs ( actor_user_id, action, entity_type, entity_id, new_value_json, related_miner_asset_id ) VALUES (%s, 'miner.control.queued', 'miner', %s, %s, %s) """, (actor_user_id, miner_id, Jsonb(audit_payload), miner_id), ) connection.commit() return operation except UniqueViolation as exc: connection.rollback() if exc.diag.constraint_name == LIVE_CONTROL_CONSTRAINT: raise MinerControlConflict( "A pending or running control operation already exists for this miner" ) from exc raise except Exception: connection.rollback() raise finally: connection.close()