Newer
Older
gnexus-book / server / app / main.py
from __future__ import annotations

from fastapi import Depends, FastAPI, HTTPException, Query

from .config import Settings, get_settings
from .docs_repository import DocsRepository, RepositoryError
from .freshness import build_freshness_report
from .git_adapter import CommitRequest, GitAdapter, GitAdapterError
from .inventory import InventoryError, InventoryRepository
from .pending_changes import (
    PendingChangeError,
    PendingChangeRepository,
    ProposedChangeRequest,
)
from .validation import validate_repository


app = FastAPI(
    title="Gnexus Book Server",
    version="0.1.0",
    description="Read-only documentation and inventory API for Gnexus Book.",
    docs_url="/api-docs",
    swagger_ui_oauth2_redirect_url="/api-docs/oauth2-redirect",
    redoc_url="/api-redoc",
)


def get_docs_repo(settings: Settings = Depends(get_settings)) -> DocsRepository:
    return DocsRepository(settings)


def get_inventory_repo(settings: Settings = Depends(get_settings)) -> InventoryRepository:
    return InventoryRepository(settings)


def get_pending_change_repo(settings: Settings = Depends(get_settings)) -> PendingChangeRepository:
    return PendingChangeRepository(settings)


def get_git_adapter(settings: Settings = Depends(get_settings)) -> GitAdapter:
    return GitAdapter(settings)


@app.get("/health")
def health(settings: Settings = Depends(get_settings)) -> dict[str, str]:
    return {"status": "ok", "repo_root": settings.repo_root.as_posix()}


@app.get("/docs")
def list_docs(repo: DocsRepository = Depends(get_docs_repo)) -> list[dict[str, object]]:
    return repo.list_docs()


@app.get("/docs/read")
def read_doc(path: str = Query(...), repo: DocsRepository = Depends(get_docs_repo)) -> dict[str, object]:
    try:
        return repo.read_doc(path)
    except RepositoryError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc


@app.get("/search")
def search(q: str = Query(...), repo: DocsRepository = Depends(get_docs_repo)) -> list[dict[str, object]]:
    return repo.search(q)


@app.get("/inventory")
def list_inventory_types(repo: InventoryRepository = Depends(get_inventory_repo)) -> list[str]:
    return repo.list_types()


@app.get("/inventory/{inventory_type}")
def read_inventory(
    inventory_type: str,
    raw: bool = False,
    repo: InventoryRepository = Depends(get_inventory_repo),
) -> object:
    try:
        if raw:
            return {"type": inventory_type, "raw": repo.read_raw(inventory_type)}
        return repo.read_parsed(inventory_type)
    except InventoryError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc


@app.get("/inventory/{inventory_type}/{item_id}")
def read_inventory_item(
    inventory_type: str,
    item_id: str,
    repo: InventoryRepository = Depends(get_inventory_repo),
) -> dict[str, object]:
    try:
        return repo.read_item(inventory_type, item_id)
    except InventoryError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc


@app.get("/traffic-routes")
def read_traffic_routes(repo: InventoryRepository = Depends(get_inventory_repo)) -> object:
    try:
        return repo.read_parsed("traffic-routes")
    except InventoryError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc


@app.get("/health/freshness")
def freshness(settings: Settings = Depends(get_settings)) -> dict[str, object]:
    return build_freshness_report(settings)


@app.get("/validate")
def validate(settings: Settings = Depends(get_settings)) -> dict[str, object]:
    return validate_repository(settings)


@app.get("/changes")
def list_changes(repo: PendingChangeRepository = Depends(get_pending_change_repo)) -> list[dict[str, object]]:
    return repo.list()


@app.get("/changes/{change_id}")
def read_change(
    change_id: str,
    repo: PendingChangeRepository = Depends(get_pending_change_repo),
) -> dict[str, object]:
    try:
        return repo.get(change_id)
    except PendingChangeError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc


@app.post("/changes")
def propose_change(
    request: ProposedChangeRequest,
    repo: PendingChangeRepository = Depends(get_pending_change_repo),
) -> dict[str, object]:
    return repo.create(request)


@app.post("/changes/{change_id}/apply")
def apply_change(
    change_id: str,
    repo: PendingChangeRepository = Depends(get_pending_change_repo),
) -> dict[str, object]:
    try:
        return repo.apply(change_id)
    except PendingChangeError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc


@app.get("/git/status")
def git_status(git: GitAdapter = Depends(get_git_adapter)) -> dict[str, object]:
    try:
        return git.status()
    except GitAdapterError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc


@app.get("/git/diff")
def git_diff(files: list[str] | None = Query(default=None), git: GitAdapter = Depends(get_git_adapter)) -> dict[str, str]:
    try:
        return git.diff(files)
    except GitAdapterError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc


@app.post("/commit")
def commit(request: CommitRequest, git: GitAdapter = Depends(get_git_adapter)) -> dict[str, object]:
    try:
        return git.commit(request)
    except GitAdapterError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc