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 .relationships import build_relationships
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("/relationships")
def relationships(repo: InventoryRepository = Depends(get_inventory_repo)) -> dict[str, object]:
try:
return build_relationships(repo)
except InventoryError as exc:
raise HTTPException(status_code=400, 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