File size: 3,772 Bytes
42a0d15
 
 
37b5223
42a0d15
 
 
 
 
 
 
 
 
 
37b5223
42a0d15
37b5223
42a0d15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37b5223
42a0d15
 
 
 
37b5223
42a0d15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37b5223
42a0d15
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
"""
Ukweli — Document Catalog Endpoint
GET /documents — Browse and filter the document registry.
Public tier (IP rate-limited) per Endpoint Security Matrix.
"""

from __future__ import annotations

import logging

from fastapi import APIRouter, Depends, Query
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.auth import AuthContext, require_auth
from app.db.session import get_db_session
from app.models.database import Document
from app.models.schemas import DocumentListResponse, DocumentOut

logger = logging.getLogger("ukweli.api.documents")
router = APIRouter(tags=["Documents"])


@router.get("/documents", response_model=DocumentListResponse)
async def list_documents(
    auditee: str | None = Query(None, description="Filter by auditee name (partial match)"),
    fy: str | None = Query(None, description="Filter by fiscal year, e.g. 2024/25"),
    report_type: str | None = Query(None, description="Filter by report type enum value"),
    status: str | None = Query(None, description="Filter by processing status"),
    limit: int = Query(20, ge=1, le=100, description="Number of results to return"),
    offset: int = Query(0, ge=0, description="Pagination offset"),
    db: AsyncSession = Depends(get_db_session),
    _auth: AuthContext = Depends(require_auth),  # public tier rate limit
) -> DocumentListResponse:
    """
    List documents from the catalog with optional filters.
    Supports pagination via limit/offset.
    No authentication required, but public-tier rate limits apply.
    """
    query = select(Document)
    count_query = select(func.count(Document.id))

    # Apply filters
    if auditee:
        query = query.where(Document.auditee.ilike(f"%{auditee}%"))
        count_query = count_query.where(Document.auditee.ilike(f"%{auditee}%"))

    if fy:
        query = query.where(Document.fiscal_year == fy)
        count_query = count_query.where(Document.fiscal_year == fy)

    if report_type:
        query = query.where(Document.report_type == report_type)
        count_query = count_query.where(Document.report_type == report_type)

    if status:
        query = query.where(Document.status == status)
        count_query = count_query.where(Document.status == status)

    # Get total count
    total_result = await db.execute(count_query)
    total = total_result.scalar_one()

    # Apply pagination and ordering
    query = query.order_by(Document.published_date.desc().nullslast(), Document.created_at.desc())
    query = query.limit(limit).offset(offset)

    result = await db.execute(query)
    documents = result.scalars().all()

    return DocumentListResponse(
        documents=[
            DocumentOut(
                id=doc.id,
                title=doc.title,
                fiscal_year=doc.fiscal_year,
                auditee=doc.auditee,
                report_type=doc.report_type,
                page_count=doc.page_count,
                download_url=doc.pdf_url,
                published_date=doc.published_date,
                status=doc.status,
            )
            for doc in documents
        ],
        total=total,
        limit=limit,
        offset=offset,
    )


@router.post("/documents/trigger-scraper", status_code=202)
async def trigger_scraper(
    max_pages: int | None = Query(None, description="Max pages to crawl"),
    _auth: AuthContext = Depends(require_auth),  # public tier rate limit
) -> dict[str, str]:
    """
    Trigger the OAG Kenya spider to discover and crawl new reports.
    Runs asynchronously in the background.
    """
    from app.scraper.workers.pdf_processor import run_scraper
    run_scraper.delay(max_pages=max_pages)
    return {"status": "triggered", "message": "Scraper task has been queued."}