from __future__ import annotations import json import sqlite3 from datetime import datetime, timezone from typing import Any class AuditRepository: def __init__(self, conn: sqlite3.Connection) -> None: self._conn = conn @staticmethod def _now() -> str: return datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace( "+00:00", "Z", ) def create_event( self, event_type: str, job_id: str | None = None, client_id: str | None = None, handshake_id: int | None = None, method_id: str | None = None, method_version: int | None = None, details: dict[str, Any] | None = None, created_at: str | None = None, ) -> int: cursor = self._conn.execute( """ INSERT INTO crack_audit_log ( created_at, event_type, job_id, client_id, handshake_id, method_id, method_version, details_json ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) """, ( created_at or self._now(), event_type, job_id, client_id, handshake_id, method_id, method_version, ( json.dumps( details, ensure_ascii=False, separators=(",", ":"), sort_keys=True, ) if details is not None else None ), ), ) return int(cursor.lastrowid) def get_event( self, event_id: int, ) -> sqlite3.Row | None: return self._conn.execute( """ SELECT id, created_at, event_type, job_id, client_id, handshake_id, method_id, method_version, details_json FROM crack_audit_log WHERE id = ? """, (event_id,), ).fetchone() def list_events( self, event_type: str | None = None, job_id: str | None = None, client_id: str | None = None, handshake_id: int | None = None, method_id: str | None = None, method_version: int | None = None, ) -> list[sqlite3.Row]: query = """ SELECT id, created_at, event_type, job_id, client_id, handshake_id, method_id, method_version, details_json FROM crack_audit_log WHERE 1 = 1 """ params: list[Any] = [] if event_type is not None: query += "\n AND event_type = ?" params.append(event_type) if job_id is not None: query += "\n AND job_id = ?" params.append(job_id) if client_id is not None: query += "\n AND client_id = ?" params.append(client_id) if handshake_id is not None: query += "\n AND handshake_id = ?" params.append(handshake_id) if method_id is not None: query += "\n AND method_id = ?" params.append(method_id) if method_version is not None: query += "\n AND method_version = ?" params.append(method_version) query += "\n ORDER BY created_at DESC, id DESC" return self._conn.execute(query, params).fetchall() @staticmethod def decode_details(row: sqlite3.Row) -> dict[str, Any] | None: if row["details_json"] is None: return None return json.loads(row["details_json"])