dnd-srd-api/scripts/manage_keys.py
Cupcake f6858e6ea1 feat: D&D SRD 5.2 API — FastAPI app with flat JSON caching
- Data fetched from Open5e API: 3,215 items (339 spells, 330 creatures,
  24 classes, 2,319 magic items, 203 equipment)
- FastAPI app with API key auth (X-API-Key header or ?api_key= param)
- Sliding window rate limiting (60 req/min, 10K req/day)
- Dice rolling endpoint (e.g., /api/dice/roll?spec=2d20+5)
- Full-text search across all resource types
- Pagination, filtering (name, level, school, class, etc.)
- Admin CLI for API key management
- nginx + systemd service ready for deployment
2026-06-03 18:13:00 +00:00

94 lines
No EOL
2.6 KiB
Python

#!/usr/bin/env python3
"""
Admin CLI for managing D&D SRD API keys.
Usage:
python scripts/manage_keys.py list
python scripts/manage_keys.py create <name> [--rate-limit N]
python scripts/manage_keys.py revoke <key>
"""
import argparse
import json
import os
import secrets
import sys
from pathlib import Path
API_KEYS_FILE = Path(__file__).resolve().parent.parent / "data" / "api_keys.json"
def load_keys() -> dict:
if API_KEYS_FILE.exists():
with open(API_KEYS_FILE) as f:
return json.load(f)
return {"keys": {}}
def save_keys(keys: dict):
API_KEYS_FILE.parent.mkdir(parents=True, exist_ok=True)
with open(API_KEYS_FILE, "w") as f:
json.dump(keys, f, indent=2)
def cmd_list(args):
keys = load_keys()
if not keys["keys"]:
print("No API keys configured.")
return
print(f"{'Name':<20} {'Key':<40} {'Created':<20}")
print("-" * 80)
for key, info in keys["keys"].items():
print(f"{info.get('name', 'unknown'):<20} {key:<40} {info.get('created', ''):<20}")
def cmd_create(args):
keys = load_keys()
new_key = "dnd_" + secrets.token_hex(16)
keys["keys"][new_key] = {
"name": args.name,
"created": os.popen("date -Iseconds").read().strip(),
"rate_limit": args.rate_limit or 60,
}
save_keys(keys)
print(f"✅ Created API key for '{args.name}':")
print(f" Key: {new_key}")
print(f" Rate limit: {args.rate_limit or 60} req/min")
def cmd_revoke(args):
keys = load_keys()
if args.key in keys["keys"]:
info = keys["keys"].pop(args.key)
save_keys(keys)
print(f"✅ Revoked key for '{info['name']}' ({args.key[:20]}...)")
else:
print(f"❌ Key not found: {args.key[:20]}...")
def main():
parser = argparse.ArgumentParser(description="D&D SRD API Key Manager")
sub = parser.add_subparsers(dest="command")
p_list = sub.add_parser("list", help="List all API keys")
p_create = sub.add_parser("create", help="Create a new API key")
p_create.add_argument("name", help="Name for the key (e.g., 'jez-personal')")
p_create.add_argument("--rate-limit", type=int, default=60, help="Requests per minute limit")
p_revoke = sub.add_parser("revoke", help="Revoke an API key")
p_revoke.add_argument("key", help="Full API key to revoke")
args = parser.parse_args()
if args.command == "list":
cmd_list(args)
elif args.command == "create":
cmd_create(args)
elif args.command == "revoke":
cmd_revoke(args)
else:
parser.print_help()
if __name__ == "__main__":
main()