Add admin management endpoints

This commit is contained in:
2026-04-27 18:31:57 +02:00
parent d62aa92590
commit 5bb864f34f

View File

@@ -1,20 +1,47 @@
# Add to api/app/main.py (after the existing imports and app definition) from fastapi import FastAPI, HTTPException, Depends, Header, Query, Request
import os
from fastapi import Request, Query
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from .influx import client, INFLUX_ORG, INFLUX_BUCKET from .auth import init_db, verify_api_key, get_db # make sure auth.py exports get_db
import sqlite3 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 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)): 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") raise HTTPException(status_code=403, detail="Forbidden")
return True return True
# ---------- API Keys Management ---------- # ---------- Admin: API Keys ----------
@app.get("/admin/keys") @app.get("/admin/keys")
async def list_keys(admin: bool = Depends(require_admin)): async def list_keys(admin: bool = Depends(require_admin)):
conn = get_db() conn = get_db()
@@ -25,8 +52,7 @@ async def list_keys(admin: bool = Depends(require_admin)):
@app.post("/admin/keys") @app.post("/admin/keys")
async def create_key(game: str = Query(...), key: str = Query(None), admin: bool = Depends(require_admin)): async def create_key(game: str = Query(...), key: str = Query(None), admin: bool = Depends(require_admin)):
if not key: if not key:
import secrets key = secrets.token_hex(16)
key = secrets.token_hex(16) # auto-generate if not provided
conn = get_db() conn = get_db()
conn.execute("INSERT OR REPLACE INTO api_keys(key, game, active) VALUES(?, ?, 1)", (key, game)) conn.execute("INSERT OR REPLACE INTO api_keys(key, game, active) VALUES(?, ?, 1)", (key, game))
conn.commit() conn.commit()
@@ -49,16 +75,16 @@ async def delete_key(key: str, admin: bool = Depends(require_admin)):
conn.close() conn.close()
return {"ok": True} return {"ok": True}
# ---------- InfluxDB Bucket Info ---------- # ---------- Admin: InfluxDB Buckets ----------
@app.get("/admin/buckets") @app.get("/admin/buckets")
async def list_buckets(admin: bool = Depends(require_admin)): async def list_buckets(admin: bool = Depends(require_admin)):
if not client: if not client:
return JSONResponse({"error": "InfluxDB client not initialised"}, status_code=503) return JSONResponse({"error": "InfluxDB client not initialised"}, status_code=503)
buckets_api = client.buckets_api() buckets_api = client.buckets_api()
buckets = buckets_api.find_buckets().buckets 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") @app.get("/admin/events")
async def list_events(hours: float = 24, limit: int = 100, admin: bool = Depends(require_admin)): async def list_events(hours: float = 24, limit: int = 100, admin: bool = Depends(require_admin)):
if not client: if not client:
@@ -80,11 +106,10 @@ async def list_events(hours: float = 24, limit: int = 100, admin: bool = Depends
}) })
return events[:limit] return events[:limit]
# ---------- Delete events (by time range + optional measurement) ----------
@app.delete("/admin/events") @app.delete("/admin/events")
async def delete_events( async def delete_events(
start: str = Query(..., description="ISO timestamp, e.g. 2026-04-27T00:00:00Z"), start: str = Query(..., description="ISO timestamp"),
stop: str = Query(..., description="ISO timestamp"), stop: str = Query(...),
measurement: str = Query(None), measurement: str = Query(None),
admin: bool = Depends(require_admin) admin: bool = Depends(require_admin)
): ):