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
This commit is contained in:
parent
f62803bc7e
commit
f6858e6ea1
14 changed files with 58679 additions and 0 deletions
94
scripts/manage_keys.py
Normal file
94
scripts/manage_keys.py
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#!/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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue