From 5bb864f34fa0d4b006fb99e276a57fc5c523b199 Mon Sep 17 00:00:00 2001 From: grillkol Date: Mon, 27 Apr 2026 18:31:57 +0200 Subject: [PATCH] Add admin management endpoints --- api/app/main.py | 59 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/api/app/main.py b/api/app/main.py index 7a8daf1..4e6dc24 100644 --- a/api/app/main.py +++ b/api/app/main.py @@ -1,20 +1,47 @@ -# Add to api/app/main.py (after the existing imports and app definition) -import os -from fastapi import Request, Query +from fastapi import FastAPI, HTTPException, Depends, Header, Query, Request from fastapi.responses import JSONResponse -from .influx import client, INFLUX_ORG, INFLUX_BUCKET -import sqlite3 +from .auth import init_db, verify_api_key, get_db # make sure auth.py exports get_db +from .models import Event, BatchEvents +from .enrichment import enrich_event +from .influx import write_event, client, INFLUX_ORG, INFLUX_BUCKET +import logging +import os +import secrets import datetime -ADMIN_KEY = os.getenv("ADMIN_API_KEY", "admin-secret-change-me") +logging.basicConfig(level=logging.INFO) -# ---------- simple admin auth helper ---------- +# Environment variables +ADMIN_API_KEY = os.getenv("ADMIN_API_KEY", "admin-secret-change-me") + +# Initialise database (creates table if missing) +init_db() + +app = FastAPI(title="Signal - Roblox Telemetry") + +# ---------- Original endpoints ---------- +@app.get("/health") +async def health(): + return {"status": "ok"} + +@app.post("/api/log") +async def ingest_event(payload: Event | BatchEvents, game: str = Depends(verify_api_key)): + if isinstance(payload, BatchEvents): + for event in payload.events: + enriched = enrich_event(event, game) + write_event(enriched) + else: + enriched = enrich_event(payload, game) + write_event(enriched) + return {"success": True} + +# ---------- Admin authentication helper ---------- def require_admin(x_admin_key: str = Header(None)): - if not x_admin_key or x_admin_key != ADMIN_KEY: + if not x_admin_key or x_admin_key != ADMIN_API_KEY: raise HTTPException(status_code=403, detail="Forbidden") return True -# ---------- API Keys Management ---------- +# ---------- Admin: API Keys ---------- @app.get("/admin/keys") async def list_keys(admin: bool = Depends(require_admin)): conn = get_db() @@ -25,8 +52,7 @@ async def list_keys(admin: bool = Depends(require_admin)): @app.post("/admin/keys") async def create_key(game: str = Query(...), key: str = Query(None), admin: bool = Depends(require_admin)): if not key: - import secrets - key = secrets.token_hex(16) # auto-generate if not provided + key = secrets.token_hex(16) conn = get_db() conn.execute("INSERT OR REPLACE INTO api_keys(key, game, active) VALUES(?, ?, 1)", (key, game)) conn.commit() @@ -49,16 +75,16 @@ async def delete_key(key: str, admin: bool = Depends(require_admin)): conn.close() return {"ok": True} -# ---------- InfluxDB Bucket Info ---------- +# ---------- Admin: InfluxDB Buckets ---------- @app.get("/admin/buckets") async def list_buckets(admin: bool = Depends(require_admin)): if not client: return JSONResponse({"error": "InfluxDB client not initialised"}, status_code=503) buckets_api = client.buckets_api() buckets = buckets_api.find_buckets().buckets - return [{"name": b.name, "id": b.id, "org": b.org_id} for b in buckets] + return [{"name": b.name, "id": b.id} for b in buckets] -# ---------- View recent events ---------- +# ---------- Admin: Events ---------- @app.get("/admin/events") async def list_events(hours: float = 24, limit: int = 100, admin: bool = Depends(require_admin)): if not client: @@ -80,11 +106,10 @@ async def list_events(hours: float = 24, limit: int = 100, admin: bool = Depends }) return events[:limit] -# ---------- Delete events (by time range + optional measurement) ---------- @app.delete("/admin/events") async def delete_events( - start: str = Query(..., description="ISO timestamp, e.g. 2026-04-27T00:00:00Z"), - stop: str = Query(..., description="ISO timestamp"), + start: str = Query(..., description="ISO timestamp"), + stop: str = Query(...), measurement: str = Query(None), admin: bool = Depends(require_admin) ):