119 lines
3.5 KiB
Python
119 lines
3.5 KiB
Python
"""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()
|