"""Earth asset management APIs.""" from __future__ import annotations from typing import Any from fastapi import APIRouter, Depends, HTTPException, Request, status from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from pydantic import BaseModel, Field from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncSession from app.core.security import decode_token, get_current_user, redis_client from app.db.session import get_db from app.models.user import User from app.services.earth_boundaries import ( EarthBoundaryBuildError, get_boundary_build_status, get_boundary_status, save_boundary_config, start_boundary_build_job, ) router = APIRouter() optional_bearer = HTTPBearer(auto_error=False) class EarthBoundaryConfigPayload(BaseModel): config: dict[str, Any] = Field(default_factory=dict) @router.get("/boundaries/status") async def get_earth_boundary_status(): return get_boundary_status() async def _get_optional_current_user( credentials: HTTPAuthorizationCredentials | None = Depends(optional_bearer), db: AsyncSession = Depends(get_db), ) -> User | None: if credentials is None: return None token = credentials.credentials if redis_client.sismember("blacklisted_tokens", token): return None payload = decode_token(token) if payload is None or payload.get("type") != "access": return None user_id = payload.get("sub") if user_id is None: return None result = await db.execute( text( "SELECT id, username, email, password_hash, role, is_active, gatekeeper_groups FROM users WHERE id = :id" ), {"id": int(user_id)}, ) row = result.fetchone() if row is None or not row[5]: return None user = User() user.id = row[0] user.username = row[1] user.email = row[2] user.password_hash = row[3] user.role = row[4] user.is_active = row[5] user.gatekeeper_groups = row[6] or [] return user def _is_loopback_request(request: Request) -> bool: host = request.client.host if request.client else "" return host in {"127.0.0.1", "::1", "localhost"} or host.startswith("127.") def _require_local_or_user(request: Request, user: User | None) -> None: if user is not None or _is_loopback_request(request): return raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Authentication required outside localhost", ) @router.put("/boundaries/config") async def update_earth_boundary_config( payload: EarthBoundaryConfigPayload, _current_user: User = Depends(get_current_user), ): try: return save_boundary_config(payload.config) except EarthBoundaryBuildError as exc: raise HTTPException( status_code=400, detail={"code": exc.code, "message": str(exc), "details": exc.details}, ) from exc @router.post("/boundaries/build") async def build_earth_boundary_assets( request: Request, current_user: User | None = Depends(_get_optional_current_user), ): _require_local_or_user(request, current_user) try: return await start_boundary_build_job() except EarthBoundaryBuildError as exc: raise HTTPException( status_code=400, detail={"code": exc.code, "message": str(exc), "details": exc.details}, ) from exc @router.get("/boundaries/build/status") async def get_earth_boundary_build_status(): return get_boundary_build_status()