""" audit_seal_router.py — HTTP surface for the Polygon PoS sealing module Author: Damir Radulić (damir@rinet.one) / dradulic@outlook.com Date: 2026-05-04 Endpoints (all under /sport/api): POST /audit/seal body: { action: "sufinanciranje.approved", ref_type: "sufinanciranje", (optional) ref_id: "2026-001", (string or number) payload: { ... }, (sha256 computed server-side) data_hash: "abc..." (optional — if you already have the hash) } returns the seal record (seal_id, tx_hash, polygonscan_url, status, ...). GET /audit/seal/list?action=&ref_type=&ref_id=&limit= Recent seals for the audit-log UI. GET /audit/seal/{seal_id} Single seal with on-chain receipt cross-check (if web3 wired up). The legacy hash-chain audit endpoints (/api/admin/audit-chain*) live in pgz_sport_api.py and remain unchanged. """ from __future__ import annotations import sys, os from typing import Any, Optional from fastapi import APIRouter, Body, HTTPException, Header # blockchain.seal lives at /opt/pgz-sport/blockchain/seal.py sys.path.insert(0, '/opt/pgz-sport') from blockchain import seal as seal_mod # noqa: E402 router = APIRouter() @router.post("/audit/seal") def audit_seal(body: dict = Body(...), x_user_email: Optional[str] = Header(default=None), x_user_id: Optional[int] = Header(default=None)): """Seal an audit event to Polygon PoS (or queue for later if no key).""" if not isinstance(body, dict): raise HTTPException(400, "JSON body required") action = (body.get('action') or '').strip() if not action: raise HTTPException(400, "action is required") payload = body.get('payload') data_hash = (body.get('data_hash') or '').strip().lower().lstrip('0x') if not data_hash: if payload is None: raise HTTPException(400, "either data_hash or payload required") data_hash = seal_mod.hash_payload(payload) ref_id = body.get('ref_id') if ref_id is None: raise HTTPException(400, "ref_id is required") ref_type = body.get('ref_type') try: result = seal_mod.seal_to_polygon( data_hash=data_hash, ref_id=str(ref_id), action=action, ref_type=ref_type, payload=payload, user_id=x_user_id, user_email=x_user_email, ) except ValueError as e: raise HTTPException(400, str(e)) return result @router.get("/audit/seal/list") def audit_seal_list(action: Optional[str] = None, ref_type: Optional[str] = None, ref_id: Optional[str] = None, limit: int = 50): rows = seal_mod.list_seals(action=action, ref_type=ref_type, ref_id=ref_id, limit=limit) return {'count': len(rows), 'rows': rows, 'wallet': seal_mod.POLYGON_WALLET, 'chain_id': seal_mod.POLYGON_CHAIN_ID, 'live': seal_mod.HAS_WEB3 and bool(seal_mod.POLYGON_PRIVKEY)} @router.get("/audit/seal/{seal_id}") def audit_seal_get(seal_id: str): row = seal_mod.verify_seal(seal_id) if not row: raise HTTPException(404, "seal not found") return row