aurask/api/aurask/api.py
Aaron 4e2639ea43
All checks were successful
aurask-release / build-and-deploy (push) Successful in 1m52s
Remove LY SSO sign-in flow
2026-04-19 21:23:27 +08:00

187 lines
8.7 KiB
Python

"""Small standard-library HTTP gateway for the first Aurask backend."""
from __future__ import annotations
import json
import os
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from typing import Any
from urllib.parse import urlparse
from aurask.app import AuraskApp
from aurask.bridge_status import bridge_status
from aurask.errors import AuraskError
class AuraskHTTPServer(ThreadingHTTPServer):
def __init__(self, server_address: tuple[str, int], RequestHandlerClass, app: AuraskApp) -> None:
super().__init__(server_address, RequestHandlerClass)
self.app = app
def make_handler(app: AuraskApp):
class GatewayHandler(BaseHTTPRequestHandler):
server_version = "AuraskMVP/0.1"
def do_GET(self) -> None:
self._handle("GET")
def do_POST(self) -> None:
self._handle("POST")
def do_OPTIONS(self) -> None:
self._handle("OPTIONS")
def log_message(self, format: str, *args: Any) -> None:
app.audit.record("gateway.access", summary=format % args)
def _handle(self, method: str) -> None:
path = urlparse(self.path).path.rstrip("/") or "/"
try:
if method == "OPTIONS":
self._send(204, {})
return
if method == "GET" and path == "/health":
self._send(200, {"status": "ok", "service": "aurask"})
return
if method == "GET" and path == "/plans":
self._send(200, app.billing.list_plans())
return
if method == "GET" and path == "/auth/config":
self._send(200, app.public_auth_config())
return
if method == "POST" and path == "/auth/google/login":
body = self._read_json()
self._send(200, app.login_with_google(id_token=body.get("id_token", "")))
return
if method == "POST" and path == "/demo/bootstrap":
body = self._read_json()
self._send(201, app.bootstrap_demo(tenant_name=body.get("tenant_name", "Aurask Demo"), email=body.get("email", "owner@example.com")))
return
if method == "POST" and path == "/tenants":
body = self._read_json()
tenant = app.auth.create_tenant(body.get("name", "Aurask Tenant"))
user = app.auth.create_user(tenant["id"], body.get("email", "owner@example.com"))
app.billing.grant_plan_without_payment(tenant["id"], "free_trial", source="self_signup")
self._send(201, {"tenant": tenant, "user": user, "api_key": user["api_key"], "quota": app.quota.get_account(tenant["id"])})
return
context = self._authenticate()
tenant_id = context["tenant"]["id"]
user_id = context["user"]["id"]
if method == "GET" and path in {"/auth/session", "/me"}:
self._send(200, app.session_profile(context))
return
if method == "POST" and path == "/auth/logout":
self._send(200, app.auth.revoke_session(self.headers.get("Authorization")))
return
if method == "GET" and path == "/admin/bridge-status":
self._send(200, bridge_status())
return
if method == "GET" and path == "/quota":
self._send(200, app.quota.get_account(tenant_id))
return
if method == "GET" and path == "/workflow-templates":
self._send(200, {"templates": app.orchestrator.list_templates()})
return
if method == "GET" and path == "/workspaces":
self._send(200, {"workspaces": app.list_workspaces(tenant_id)})
return
if method == "POST" and path == "/workspaces":
body = self._read_json()
self._send(201, app.knowledge.create_workspace(tenant_id, user_id, body.get("name", "")))
return
if method == "POST" and path == "/documents":
body = self._read_json()
document = app.knowledge.upload_document(
tenant_id,
user_id,
body.get("workspace_id", ""),
filename=body.get("filename", ""),
size_bytes=int(body.get("size_bytes", 0)),
content_type=body.get("content_type", ""),
content_preview=body.get("content_preview", ""),
)
self._send(201, document)
return
if method == "POST" and path == "/orders":
body = self._read_json()
order = app.billing.create_order(tenant_id, user_id, body.get("product_code", ""), quantity=int(body.get("quantity", 1)))
self._send(201, order)
return
if method == "POST" and path == "/payments/match":
body = self._read_json()
payment = app.payments.match_trc20_payment(
tenant_id,
body.get("order_id", ""),
tx_hash=body.get("tx_hash", ""),
amount_usdt=float(body.get("amount_usdt", 0)),
confirmations=int(body.get("confirmations", 20)),
)
self._send(201, payment)
return
if method == "POST" and path == "/workflow-runs":
body = self._read_json()
run = app.orchestrator.run_template(
tenant_id,
user_id,
body.get("template_id", ""),
workspace_id=body.get("workspace_id"),
inputs=body.get("inputs", {}),
)
self._send(201, run)
return
if method == "GET" and path.startswith("/workflow-runs/"):
run_id = path.split("/", 2)[2]
run = app.store.get("workflow_runs", run_id)
if not run or run["tenant_id"] != tenant_id:
self._send(404, {"error": {"code": "not_found", "message": "workflow run not found"}})
return
self._send(200, run)
return
self._send(404, {"error": {"code": "not_found", "message": "route not found"}})
except AuraskError as exc:
self._send(exc.status_code, {"error": {"code": exc.code, "message": exc.message, "details": exc.details}})
except Exception as exc:
app.audit.record("gateway.error", summary=exc.__class__.__name__, metadata={"message": str(exc)})
self._send(500, {"error": {"code": "internal_error", "message": "internal server error"}})
def _authenticate(self) -> dict:
return app.auth.authenticate(self.headers.get("Authorization"))
def _read_json(self) -> dict:
length = int(self.headers.get("Content-Length", "0"))
if not length:
return {}
payload = self.rfile.read(length).decode("utf-8")
decoded = json.loads(payload)
if not isinstance(decoded, dict):
return {}
return decoded
def _send(self, status: int, payload: Any) -> None:
encoded = json.dumps(payload, ensure_ascii=False, indent=2).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(encoded)))
self.send_header("Access-Control-Allow-Origin", os.getenv("AURASK_CORS_ALLOW_ORIGIN", "*"))
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept")
self.end_headers()
self.wfile.write(encoded)
return GatewayHandler
def run_server(app: AuraskApp, *, host: str = "127.0.0.1", port: int = 8080) -> None:
handler = make_handler(app)
server = AuraskHTTPServer((host, port), handler, app)
try:
print(f"Aurask gateway listening on http://{host}:{port}")
server.serve_forever()
except KeyboardInterrupt:
print("\nAurask gateway stopped")
finally:
server.server_close()