Add admin management endpoints
This commit is contained in:
@@ -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)
|
||||||
):
|
):
|
||||||
|
|||||||
Reference in New Issue
Block a user