feat: add events app + "multi-tenants" aka multi-events

This commit is contained in:
2026-09-16 00:03:26 +02:00
parent b5c2cdc037
commit 8b9c254fd2
62 changed files with 1343 additions and 362 deletions
+23 -17
View File
@@ -25,11 +25,13 @@ from .schemas import NoitaSubmissionOut
router = Router()
NOITA_APP_ID = 881100
PREFIX = "/events/{event_path}/noita"
@router.get("results", response=ResultsOut)
@router.get(f"{PREFIX}/results", response=ResultsOut)
@decorate_view(require_game_enabled(NOITA_APP_ID))
def get_results(request: HttpRequest):
cache_key = f"api:noita:results:{request.user.id}"
def get_results(request: HttpRequest, event_path: str):
cache_key = f"api:{event_path}:noita:results:{request.user.id}"
cached_data = cache.get(cache_key)
if cached_data is not None:
@@ -42,7 +44,7 @@ def get_results(request: HttpRequest):
"""
# Group objectives by objectiv_id and count occurrences
user_objectives = (
Objectiv.objects.filter(user=request.user)
Objectiv.objects.filter(user=request.user, submission__event=request.event)
.values("objectiv_id")
.annotate(count=Count("id"))
)
@@ -118,7 +120,7 @@ def get_results(request: HttpRequest):
total_score += points
# Count deaths for the user
deaths_count = DeathCounter.objects.filter(user=request.user).count()
deaths_count = DeathCounter.for_event.filter(user=request.user).count()
data = {
"total_score": total_score,
@@ -126,19 +128,19 @@ def get_results(request: HttpRequest):
"objectives": list(with_points.values()),
}
cache.set(f"api:noita:results:{request.user.id}", data, 300)
cache.set(cache_key, data, 300)
return data
@router.get("leaderboard", response=LeaderboardOut)
@router.get(f"{PREFIX}/leaderboard", response=LeaderboardOut)
@decorate_view(require_game_enabled(NOITA_APP_ID))
def get_leaderboard(request: HttpRequest):
def get_leaderboard(request: HttpRequest, event_path: str):
"""
Get the global leaderboard for all users ranked by total score.
Uses Window functions to rank users by their total score in descending order.
"""
cache_key = "api:noita:leaderboard"
cache_key = f"api:{event_path}:noita:leaderboard"
cached_data = cache.get(cache_key)
if cached_data is not None:
@@ -150,7 +152,8 @@ def get_leaderboard(request: HttpRequest):
# Get all objectives with calculated points (grouped by objectiv_id and user)
all_objectives = (
Objectiv.objects.values("user", "objectiv_id")
Objectiv.objects.filter(submission__event=request.event)
.values("user", "objectiv_id")
.annotate(count=Count("id"))
.annotate(
# Fetch points from ObjectivPoint using Subquery
@@ -201,12 +204,12 @@ def get_leaderboard(request: HttpRequest):
for user_id, total_score in user_totals_dict.items():
user = User.objects.get(id=user_id)
objectives_count = (
Objectiv.objects.filter(user_id=user_id)
Objectiv.objects.filter(user_id=user_id, submission__event=request.event)
.values("objectiv_id")
.distinct()
.count()
)
deaths_count = DeathCounter.objects.filter(user_id=user_id).count()
deaths_count = DeathCounter.for_event.filter(user_id=user_id).count()
users_with_scores.append(
{
"user_id": user_id,
@@ -232,13 +235,15 @@ def get_leaderboard(request: HttpRequest):
]
data = {"leaderboard": leaderboard}
cache.set("api:noita:leaderboard", data, 300)
cache.set(cache_key, data, 300)
return data
@router.post("submit", response={200: NoitaSubmissionOut, 400: dict})
@router.post(f"{PREFIX}/submit", response={200: NoitaSubmissionOut, 400: dict})
@decorate_view(require_game_enabled(NOITA_APP_ID))
def submit_log_file(request: HttpRequest, file: UploadedFile = File(...)):
def submit_log_file(
request: HttpRequest, event_path: str, file: UploadedFile = File(...)
):
"""
Submit a Noita run file (log file, screenshot, or video).
@@ -267,6 +272,7 @@ def submit_log_file(request: HttpRequest, file: UploadedFile = File(...)):
try:
# Create submission
submission = LogfileSubmission.objects.create(
event=request.event,
user=request.user if request.user.is_authenticated else None,
content_type=file.content_type,
file_size=file.size,
@@ -284,8 +290,8 @@ def submit_log_file(request: HttpRequest, file: UploadedFile = File(...)):
# Invalidate caches on successful submission
if submission.user:
cache.delete(f"api:noita:results:{submission.user.id}")
cache.delete("api:noita:leaderboard")
cache.delete(f"api:{event_path}:noita:results:{submission.user.id}")
cache.delete(f"api:{event_path}:noita:leaderboard")
return {
"id": str(submission.id),