mirror of
https://18126008609:longquanjian123@gitee.com/feigong123/aurask.git
synced 2026-04-19 16:18:24 +00:00
187 lines
8.7 KiB
Python
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()
|