feat(cg1+cg2+cg3): billing endpoints, IAP middleware, static serving
This commit is contained in:
parent
c79b7d87b4
commit
324867e289
81
main.py
81
main.py
|
|
@ -3,26 +3,78 @@
|
|||
main.py — Cloud Run entrypoint, OSVauco OPAX.
|
||||
Modes: light (gemini-2.5-flash) | heavy (gemini-2.5-pro)
|
||||
ML-1: telemetri, state store, DAG.
|
||||
CG1+CG2: billing endpoints + IAP-beskyttelse.
|
||||
CG3: static-mappe serveres fra /static/*.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import pathlib
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "agents", "core-logic"))
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi import FastAPI, HTTPException, Request
|
||||
from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List
|
||||
|
||||
from agent import run, authorize_mode
|
||||
from ml import build_agent_dag, execute_dag, get_store, log_agent_call
|
||||
from ml.telemetry import log_dag_execution
|
||||
from ml.billing_agent import BillingAgent
|
||||
from ml.anomaly_detector import AnomalyDetector
|
||||
|
||||
app = FastAPI(title="OSVauco OPAX Agent")
|
||||
AGENT_ID = "opax-core"
|
||||
|
||||
# ── IAP-MIDDLEWARE ────────────────────────────────────────────────────────────
|
||||
# Cloud Run + IAP: verifiserer X-Goog-IAP-JWT-Assertion på /billing/*.
|
||||
# Sett env-variabelen IAP_AUDIENCE til IAP backend service audience:
|
||||
# /projects/PROJECT_NUMBER/global/backendServices/SERVICE_ID
|
||||
# Sett IAP_ENABLED=true i Cloud Run for å aktivere sjekken.
|
||||
# Lokalt (IAP_ENABLED ikke satt): alle kall tillates.
|
||||
|
||||
IAP_ENABLED = os.environ.get("IAP_ENABLED", "").lower() == "true"
|
||||
IAP_AUDIENCE = os.environ.get("IAP_AUDIENCE", "")
|
||||
|
||||
|
||||
def _verify_iap_jwt(token: str, audience: str) -> dict:
|
||||
"""Verifiserer IAP JWT med google-auth. Kaster ValueError ved feil."""
|
||||
try:
|
||||
from google.auth.transport import requests as google_requests
|
||||
from google.oauth2 import id_token
|
||||
request = google_requests.Request()
|
||||
return id_token.verify_token(token, request, audience=audience,
|
||||
certs_url="https://www.gstatic.com/iap/verify/public_key")
|
||||
except Exception as exc:
|
||||
raise ValueError(f"IAP JWT ugyldig: {exc}") from exc
|
||||
|
||||
|
||||
@app.middleware("http")
|
||||
async def iap_guard(request: Request, call_next):
|
||||
"""Blokkerer /billing/* uten gyldig IAP-token når IAP_ENABLED=true."""
|
||||
if IAP_ENABLED and request.url.path.startswith("/billing"):
|
||||
token = request.headers.get("X-Goog-IAP-JWT-Assertion", "")
|
||||
if not token:
|
||||
return JSONResponse(status_code=401,
|
||||
content={"error": "IAP-token mangler (X-Goog-IAP-JWT-Assertion)"})
|
||||
try:
|
||||
_verify_iap_jwt(token, IAP_AUDIENCE)
|
||||
except ValueError as exc:
|
||||
return JSONResponse(status_code=403, content={"error": str(exc)})
|
||||
return await call_next(request)
|
||||
|
||||
|
||||
# ── STATIC FILES ──────────────────────────────────────────────────────────────
|
||||
# Serverer static/ direkte: GET /static/billing-dashboard.html osv.
|
||||
_static_dir = pathlib.Path(__file__).parent / "static"
|
||||
if _static_dir.is_dir():
|
||||
app.mount("/static", StaticFiles(directory=str(_static_dir), html=True), name="static")
|
||||
|
||||
|
||||
# ── MODELLER ──────────────────────────────────────────────────────────────────
|
||||
class RunRequest(BaseModel):
|
||||
message: str
|
||||
user_id: str = "opax"
|
||||
|
|
@ -38,11 +90,38 @@ class DagRequest(BaseModel):
|
|||
scheduler: str = Field("threads")
|
||||
|
||||
|
||||
# ── HEALTH ────────────────────────────────────────────────────────────────────
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
# ── BILLING ENDPOINTS (CG1 + CG2) ────────────────────────────────────────────
|
||||
@app.get("/billing/summary")
|
||||
def billing_summary():
|
||||
try:
|
||||
return BillingAgent().get_summary()
|
||||
except Exception as exc:
|
||||
return JSONResponse(status_code=500, content={"error": str(exc)})
|
||||
|
||||
|
||||
@app.get("/billing/forecast")
|
||||
def billing_forecast():
|
||||
try:
|
||||
return BillingAgent().get_forecast()
|
||||
except Exception as exc:
|
||||
return JSONResponse(status_code=500, content={"error": str(exc)})
|
||||
|
||||
|
||||
@app.get("/billing/anomalies")
|
||||
def billing_anomalies():
|
||||
try:
|
||||
return AnomalyDetector().detect_anomalies()
|
||||
except Exception as exc:
|
||||
return JSONResponse(status_code=500, content={"error": str(exc)})
|
||||
|
||||
|
||||
# ── AGENT ENDPOINTS ───────────────────────────────────────────────────────────
|
||||
@app.post("/run")
|
||||
def run_agent(req: RunRequest):
|
||||
try:
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user