"""/sources — public transparency page: every source with tier, connectors and health (no credentials, no internal hosts).""" from __future__ import annotations from typing import Any from fastapi import APIRouter, Request from aiatlas.api.common import cached from aiatlas.db import connection, fetch_all router = APIRouter(prefix="/api/v1/sources", tags=["sources"]) @router.get("") @cached(300) async def list_sources(request: Request) -> dict[str, Any]: async with connection() as conn: rows = await fetch_all(conn, """ select s.id, s.key, s.name, s.domain, s.tier, s.kind, s.category, s.base_url, s.robots_policy, s.enabled, s.priority, s.notes, o.id as org_id, o.slug as org_slug, o.canonical_name as org_name, (select count(*) from documents d where d.source_id = s.id) as documents, (select count(*) from snapshots x join documents d on d.id = x.document_id where d.source_id = s.id) as snapshots, (select max(d.last_fetched_at) from documents d where d.source_id = s.id) as last_crawled_at, (select count(*) from claims c where c.source_id = s.id and c.status = 'current') as claims, coalesce((select jsonb_agg(jsonb_build_object('name', c.name, 'label', c.label, 'health', c.health, 'enabled', c.enabled, 'last_success_at', to_char(c.last_success_at at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"'), 'interval_seconds', c.interval_seconds, 'priority', c.priority, 'parser_version', c.parser_version) order by c.name) from connectors c where c.source_id = s.id), '[]'::jsonb) as connectors from sources s left join entities o on o.id = s.organization_id order by s.tier, s.priority, s.name""") items = [{"key": r["key"], "name": r["name"], "domain": r["domain"], "tier": r["tier"], "kind": r["kind"], "category": r["category"], "base_url": r["base_url"], "robots_policy": r["robots_policy"], "organization": {"id": r["org_id"], "slug": r["org_slug"], "name": r["org_name"]} if r["org_id"] else None, "enabled": r["enabled"], "priority": r["priority"], "notes": r["notes"], "documents": int(r["documents"] or 0), "snapshots": int(r["snapshots"] or 0), "claims": int(r["claims"] or 0), "last_crawled_at": r["last_crawled_at"], "connectors": r["connectors"] or []} for r in rows] return {"items": items, "total": len(items), "tiers": {1: "official / primary", 2: "quality secondary", 3: "community", 4: "unverified"}}