Files
polylan-submitter/polylan_submitter/polylan_submitter/api.py
T
2026-09-17 01:33:54 +02:00

108 lines
4.0 KiB
Python

from ninja import NinjaAPI, Schema
from django.conf import settings
from django.core.cache import cache
from django.http import HttpRequest
from opus_magnum.api import router as opus_magnum_router
from opus_magnum.schemas import UserInfoOut
from animations.api import router as results_router
from noita.api import router as noita_router
from games.api import router as games_router, event_router as games_event_router
from market.api import router as market_router
from submissions.api import router as submissions_router
from clock.api import router as clock_router
from place.api import router as place_router
# Create the main API instance
api = NinjaAPI(
title="PolyLAN Submission API",
version="1.0.0",
description="""API for managing Opus Magnum puzzle submissions, and Noita runs.
The Opus Magnum Submission API allows clients to upload, manage, validate, and review puzzle solution submissions for the Opus Magnum puzzle game community.
It provides features for user authentication, puzzle listing, submission uploads, automated and manual OCR validation, and administrative workflows.
The Noita Submission API allows clients to upload the result of the log file of the PolyLAN noita mod. It parses the output, and store each objectiv made by the user.
""",
openapi_extra={
"info": {
"contact": {
"name": "Legrems",
"email": "loic.gremaud@polylan.ch",
},
}
},
)
# Add authentication for protected endpoints
# api.auth = django_auth # Uncomment if you want global auth
# Include the routers
api.add_router("/games/", games_router, tags=["games"])
# Event-scoped routers: all tournament/competition activity belongs to one event.
# Each operation's own path already starts with "/events/{event_path}/<resource>/..."
# (see the PREFIX constant in each app's api.py) - django-ninja resolves a path
# parameter's source (path vs query) from the operation's own registered path
# string at decoration time, not from whatever prefix add_router() attaches later,
# so {event_path} has to live there to show up as a real templated path parameter
# in the generated OpenAPI schema (which the frontend's generated API client needs).
# These routers are therefore mounted at the root, with no extra prefix here.
api.add_router("", opus_magnum_router, tags=["opus-magnum"])
api.add_router("", results_router, tags=["results"])
api.add_router("", noita_router, tags=["noita"])
api.add_router("", games_event_router, tags=["games"])
api.add_router("", submissions_router, tags=["submissions"])
api.add_router("", clock_router, tags=["clock"])
api.add_router("", place_router, tags=["place"])
if settings.MARKET_ENABLED:
api.add_router("", market_router, tags=["market"])
# Health check endpoint
@api.get("/health")
def health_check(request):
"""Health check endpoint"""
return {"status": "healthy", "service": "polylan-submitter-api"}
class ConfigOut(Schema):
market_enabled: bool
# Site-wide config the SPA needs but that isn't tied to any one event
# (e.g. whether to render the market card at all on the home page).
@api.get("/config", response=ConfigOut)
def get_config(request):
return {"market_enabled": settings.MARKET_ENABLED}
# Cache management endpoint
@api.post("/cache/clear")
def clear_cache(request: HttpRequest, event_path: str | None = None):
"""Clear API caches (admin only). Pass event_path to clear just that event's keys."""
if not request.user.is_authenticated or not request.user.is_staff:
return 403, {"detail": "Admin access required"}
pattern = f"api:{event_path}:*" if event_path else "api:*"
keys = cache.keys(pattern)
cache.delete_many(keys)
return {"detail": f"Cleared {len(keys)} cache entries"}
# User info endpoint
@api.get("/user", response=UserInfoOut)
def get_user_info(request):
"""Get current user information"""
user = request.user
if user.is_authenticated:
return user
return {
"is_authenticated": False,
"is_staff": False,
"is_superuser": False,
}