diff --git a/polylan_submitter/games/__init__.py b/polylan_submitter/games/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/polylan_submitter/games/admin.py b/polylan_submitter/games/admin.py new file mode 100644 index 0000000..5e84eb2 --- /dev/null +++ b/polylan_submitter/games/admin.py @@ -0,0 +1,11 @@ +from django.contrib import admin + +from .models import Game + + +@admin.register(Game) +class GameAdmin(admin.ModelAdmin): + list_display = ["name", "steam_app_id", "enabled", "updated_at"] + list_filter = ["enabled"] + search_fields = ["name", "steam_app_id"] + readonly_fields = ["created_at", "updated_at"] diff --git a/polylan_submitter/games/api.py b/polylan_submitter/games/api.py new file mode 100644 index 0000000..e863b87 --- /dev/null +++ b/polylan_submitter/games/api.py @@ -0,0 +1,13 @@ +from typing import List + +from ninja import Router + +from .models import Game +from .schemas import GameOut + +router = Router() + + +@router.get("", response=List[GameOut]) +def list_games(request): + return Game.objects.filter(enabled=True) diff --git a/polylan_submitter/games/apps.py b/polylan_submitter/games/apps.py new file mode 100644 index 0000000..4c4fb1d --- /dev/null +++ b/polylan_submitter/games/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class GamesConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "games" diff --git a/polylan_submitter/games/decorators.py b/polylan_submitter/games/decorators.py new file mode 100644 index 0000000..339ebad --- /dev/null +++ b/polylan_submitter/games/decorators.py @@ -0,0 +1,22 @@ +from functools import wraps + +from django.core.exceptions import PermissionDenied + +from .models import Game + + +def require_game_enabled(steam_app_id: int): + def decorator(view_func): + @wraps(view_func) + def wrapper(request, *args, **kwargs): + try: + game = Game.objects.get(steam_app_id=steam_app_id) + except Game.DoesNotExist: + raise PermissionDenied + if not game.enabled: + raise PermissionDenied + return view_func(request, *args, **kwargs) + + return wrapper + + return decorator diff --git a/polylan_submitter/games/migrations/0001_initial.py b/polylan_submitter/games/migrations/0001_initial.py new file mode 100644 index 0000000..c8f365a --- /dev/null +++ b/polylan_submitter/games/migrations/0001_initial.py @@ -0,0 +1,32 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Game", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("steam_app_id", models.PositiveIntegerField(unique=True)), + ("name", models.CharField(max_length=255)), + ("enabled", models.BooleanField(default=True)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ], + options={ + "ordering": ["name"], + }, + ), + ] diff --git a/polylan_submitter/games/migrations/0002_seed_noita_and_opus_magnum.py b/polylan_submitter/games/migrations/0002_seed_noita_and_opus_magnum.py new file mode 100644 index 0000000..2b05614 --- /dev/null +++ b/polylan_submitter/games/migrations/0002_seed_noita_and_opus_magnum.py @@ -0,0 +1,31 @@ +from django.db import migrations + +NOITA_APP_ID = 881100 +OPUS_MAGNUM_APP_ID = 558990 + + +def seed_games(apps, schema_editor): + Game = apps.get_model("games", "Game") + Game.objects.get_or_create( + steam_app_id=NOITA_APP_ID, + defaults={"name": "Noita", "enabled": True}, + ) + Game.objects.get_or_create( + steam_app_id=OPUS_MAGNUM_APP_ID, + defaults={"name": "Opus Magnum", "enabled": True}, + ) + + +def unseed_games(apps, schema_editor): + Game = apps.get_model("games", "Game") + Game.objects.filter(steam_app_id__in=[NOITA_APP_ID, OPUS_MAGNUM_APP_ID]).delete() + + +class Migration(migrations.Migration): + dependencies = [ + ("games", "0001_initial"), + ] + + operations = [ + migrations.RunPython(seed_games, reverse_code=unseed_games), + ] diff --git a/polylan_submitter/games/migrations/0003_game_path.py b/polylan_submitter/games/migrations/0003_game_path.py new file mode 100644 index 0000000..dcc30f0 --- /dev/null +++ b/polylan_submitter/games/migrations/0003_game_path.py @@ -0,0 +1,31 @@ +from django.db import migrations, models + +NOITA_APP_ID = 881100 +OPUS_MAGNUM_APP_ID = 558990 + +PATHS = { + NOITA_APP_ID: "/noita", + OPUS_MAGNUM_APP_ID: "/opus-magnum", +} + + +def set_paths(apps, schema_editor): + Game = apps.get_model("games", "Game") + for app_id, path in PATHS.items(): + Game.objects.filter(steam_app_id=app_id).update(path=path) + + +class Migration(migrations.Migration): + dependencies = [ + ("games", "0002_seed_noita_and_opus_magnum"), + ] + + operations = [ + migrations.AddField( + model_name="game", + name="path", + field=models.CharField(default="", max_length=100), + preserve_default=False, + ), + migrations.RunPython(set_paths, reverse_code=migrations.RunPython.noop), + ] diff --git a/polylan_submitter/games/migrations/__init__.py b/polylan_submitter/games/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/polylan_submitter/games/models.py b/polylan_submitter/games/models.py new file mode 100644 index 0000000..42f1284 --- /dev/null +++ b/polylan_submitter/games/models.py @@ -0,0 +1,17 @@ +from django.db import models + + +class Game(models.Model): + steam_app_id = models.PositiveIntegerField(unique=True) + name = models.CharField(max_length=255) + path = models.CharField(max_length=100) + enabled = models.BooleanField(default=True) + + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + ordering = ["name"] + + def __str__(self) -> str: + return f"{self.name} ({self.steam_app_id})" diff --git a/polylan_submitter/games/schemas.py b/polylan_submitter/games/schemas.py new file mode 100644 index 0000000..8da0f75 --- /dev/null +++ b/polylan_submitter/games/schemas.py @@ -0,0 +1,7 @@ +from ninja import Schema + + +class GameOut(Schema): + steam_app_id: int + name: str + path: str diff --git a/polylan_submitter/noita/api.py b/polylan_submitter/noita/api.py index 26b2943..0c53982 100644 --- a/polylan_submitter/noita/api.py +++ b/polylan_submitter/noita/api.py @@ -12,18 +12,22 @@ from django.db.models import ( ) from ninja import Router, File from ninja.files import UploadedFile +from ninja.decorators import decorate_view from noita.schemas import ResultsOut, LeaderboardOut from noita.services.objectives import parse_objectives_and_store +from games.decorators import require_game_enabled from .models import LogfileSubmission, Objectiv, ObjectivPoint, DeathCounter from .schemas import NoitaSubmissionOut router = Router() +NOITA_APP_ID = 881100 @router.get("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}" cached_data = cache.get(cache_key) @@ -127,6 +131,7 @@ def get_results(request: HttpRequest): @router.get("leaderboard", response=LeaderboardOut) +@decorate_view(require_game_enabled(NOITA_APP_ID)) def get_leaderboard(request: HttpRequest): """ Get the global leaderboard for all users ranked by total score. @@ -232,6 +237,7 @@ def get_leaderboard(request: HttpRequest): @router.post("submit", response={200: NoitaSubmissionOut, 400: dict}) +@decorate_view(require_game_enabled(NOITA_APP_ID)) def submit_log_file(request: HttpRequest, file: UploadedFile = File(...)): """ Submit a Noita run file (log file, screenshot, or video). diff --git a/polylan_submitter/noita/services/decode.py b/polylan_submitter/noita/services/decode.py index a6e24f9..71b7c5b 100644 --- a/polylan_submitter/noita/services/decode.py +++ b/polylan_submitter/noita/services/decode.py @@ -72,7 +72,6 @@ POINTS = { "NOLLA": 10, "CHAOTIC_TRANSMUTATION": 10, "DUPLICATE": 5, - "OMEGA": 10, "BURST_2": 10, "BURST_3": 15, "BURST_4": 20, diff --git a/polylan_submitter/polylan_submitter/api.py b/polylan_submitter/polylan_submitter/api.py index 747e093..2a18131 100644 --- a/polylan_submitter/polylan_submitter/api.py +++ b/polylan_submitter/polylan_submitter/api.py @@ -5,6 +5,7 @@ from submissions.api import router as submissions_router from submissions.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 # Create the main API instance api = NinjaAPI( @@ -34,6 +35,7 @@ The Noita Submission API allows clients to upload the result of the log file of api.add_router("/submissions/", submissions_router, tags=["submissions"]) api.add_router("/results/", results_router, tags=["results"]) api.add_router("/noita/", noita_router, tags=["noita"]) +api.add_router("/games/", games_router, tags=["games"]) # Health check endpoint diff --git a/polylan_submitter/polylan_submitter/settings.py b/polylan_submitter/polylan_submitter/settings.py index 3642c36..b6f02f1 100644 --- a/polylan_submitter/polylan_submitter/settings.py +++ b/polylan_submitter/polylan_submitter/settings.py @@ -43,6 +43,7 @@ INSTALLED_APPS = [ "animations", "submissions", "noita", + "games", ] MIDDLEWARE = [ @@ -193,7 +194,7 @@ STATICFILES_DIRS = [ from polylan_submitter.settingsLocal import * # noqa -import sentry_sdk +import sentry_sdk # noqa sentry_sdk.init( dsn="https://cc62a4ce3f3470890b43accf02cc6d8c@sentry2.polylan.ch/12", diff --git a/polylan_submitter/polylan_submitter/urls.py b/polylan_submitter/polylan_submitter/urls.py index 9c220a5..28626a6 100644 --- a/polylan_submitter/polylan_submitter/urls.py +++ b/polylan_submitter/polylan_submitter/urls.py @@ -23,8 +23,12 @@ from django.contrib.auth.decorators import login_required from django.conf import settings from django.conf.urls.static import static from simple_cas_views import SimpleCASLoginView, SimpleCASLogoutView +from games.decorators import require_game_enabled from .api import api +NOITA_APP_ID = 881100 +OPUS_MAGNUM_APP_ID = 558990 + @login_required def home(request: HttpRequest): @@ -32,6 +36,7 @@ def home(request: HttpRequest): @login_required +@require_game_enabled(OPUS_MAGNUM_APP_ID) def opus_magnum_home(request: HttpRequest): from submissions.models import SteamCollection @@ -45,6 +50,7 @@ def opus_magnum_home(request: HttpRequest): @login_required +@require_game_enabled(NOITA_APP_ID) def noita_home(request: HttpRequest): return render(request, "noita.html", {}) diff --git a/polylan_submitter/src/Home.vue b/polylan_submitter/src/Home.vue index aec533c..488e1c9 100644 --- a/polylan_submitter/src/Home.vue +++ b/polylan_submitter/src/Home.vue @@ -1,22 +1,10 @@ @@ -44,13 +40,18 @@ const navigate = (path: string) => {
+ +{{ game.description }}
+