diff --git a/polylan_submitter/src/Submissions.vue b/polylan_submitter/src/Submissions.vue index d3b0ca8..12523be 100644 --- a/polylan_submitter/src/Submissions.vue +++ b/polylan_submitter/src/Submissions.vue @@ -8,6 +8,7 @@ import { useUser } from "@/composables/useUser"; import { eventGamesQuery } from "@/queries"; import { usePageHeaderStore } from "@/stores/pageHeader"; import { submissionsApiListAvailableScopes, submissionsApiCreateSubmission, submissionsApiListUserSubmissions, submissionsApiCreateImageCrop, submissionsApiDeleteImageCrop, submissionsApiProcessFileOcr, submissionsApiProcessCropOcr } from "@/api"; +import type { SubmissionOut } from "@/api"; import { useSubmissionsWebSocket } from "@/composables/useSubmissionsWebSocket"; import { provideEventPath } from "@/composables/useEventPath"; @@ -42,7 +43,7 @@ const notes = ref(""); const selectedFile = ref(null); const filePreviewUrl = ref(""); const isModalOpen = ref(false); -const userSubmissions = ref([]); +const userSubmissions = ref([]); const isLoadingSubmissions = ref(false); const selectedSubmissionFile = ref(null); const isSubmissionFileModalOpen = ref(false); @@ -728,7 +729,7 @@ onUnmounted(() => {

- {{ t("submissions.yourSubmissions") }} + {{ userInfo.isStaff ? t("submissions.allSubmissions") : t("submissions.yourSubmissions") }}

@@ -745,6 +746,7 @@ onUnmounted(() => { {{ t("common.date") }} + {{ t("common.user") }} {{ t("common.files") }} {{ t("submissions.fileStatus") }} {{ t("submissions.ocrStatus") }} @@ -754,6 +756,7 @@ onUnmounted(() => { {{ formatDate(submission.created_at) }} + {{ submission.username ?? "-" }}
diff --git a/polylan_submitter/src/api/types.gen.ts b/polylan_submitter/src/api/types.gen.ts index 8dbcdfd..c08a66b 100644 --- a/polylan_submitter/src/api/types.gen.ts +++ b/polylan_submitter/src/api/types.gen.ts @@ -423,6 +423,10 @@ export type SubmissionOut = { * Scope Id */ scope_id: string; + /** + * Username + */ + username?: string | null; /** * Notes */ diff --git a/polylan_submitter/src/locales/en.json b/polylan_submitter/src/locales/en.json index df48785..f866715 100644 --- a/polylan_submitter/src/locales/en.json +++ b/polylan_submitter/src/locales/en.json @@ -24,6 +24,7 @@ "puzzle": "Puzzle", "files": "Files", "date": "Date", + "user": "User", "notes": "Notes", "actions": "Actions", "noRankYet": "No rank yet", @@ -168,6 +169,7 @@ } }, "yourSubmissions": "Your Submissions", + "allSubmissions": "All Submissions", "noSubmissionsHint": "Upload your first submission above to get started", "fileStatus": "File Status", "ocrStatus": "OCR Status", diff --git a/polylan_submitter/submissions/api.py b/polylan_submitter/submissions/api.py index aae566b..84fc5cf 100644 --- a/polylan_submitter/submissions/api.py +++ b/polylan_submitter/submissions/api.py @@ -202,10 +202,12 @@ def list_user_submissions(request: HttpRequest, event_path: str, scope_id: UUID) if not request.user.is_authenticated: return 403, {"detail": "Authentication required"} - submissions = Submission.for_event.filter( - scope_id=scope_id, user=request.user - ).order_by("-created_at") - return submissions + submissions = Submission.for_event.filter(scope_id=scope_id) + # Admins see every player's submissions for the scope + if not request.user.is_staff: + submissions = submissions.filter(user=request.user) + + return submissions.select_related("user").order_by("-created_at") @router.get(f"{PREFIX}/submission/by/game", response=list[SubmissionOut]) diff --git a/polylan_submitter/submissions/schemas.py b/polylan_submitter/submissions/schemas.py index fcbe1bd..bd6a907 100644 --- a/polylan_submitter/submissions/schemas.py +++ b/polylan_submitter/submissions/schemas.py @@ -71,7 +71,12 @@ class SubmissionFileOut(Schema): class SubmissionOut(Schema): id: UUID scope_id: UUID + username: str | None = None notes: str | None = None files: list[SubmissionFileOut] = [] created_at: datetime updated_at: datetime + + @staticmethod + def resolve_username(obj) -> str | None: + return obj.user.username if obj.user else None diff --git a/polylan_submitter/submissions/tests.py b/polylan_submitter/submissions/tests.py index 7553b72..301f9dd 100644 --- a/polylan_submitter/submissions/tests.py +++ b/polylan_submitter/submissions/tests.py @@ -11,7 +11,7 @@ from events.models import Event from games.models import EventGame, Game from . import tasks -from .models import Scope +from .models import Scope, Submission from .services.aandt_client.models import ( AandtApiInfosResponse200, AandtApiMatchesResponse200, @@ -388,3 +388,49 @@ class SubmissionHomeViewTests(TestCase): response = self.client.get("/nope/submissions/unknown-game") self.assertEqual(response.status_code, 404) + + +class ListUserSubmissionsApiTests(TestCase): + def setUp(self): + self.event = Event.objects.create(name="Event", path="event") + game = Game.objects.create(name="Game", path="game") + event_game = EventGame.objects.create(event=self.event, game=game) + self.scope = Scope.objects.create(event_game=event_game, name="M1") + other_scope = Scope.objects.create(event_game=event_game, name="M2") + + self.player = CustomUser.objects.create_user(username="player", password="x") + other = CustomUser.objects.create_user(username="other", password="x") + self.mine = Submission.objects.create( + event=self.event, scope=self.scope, user=self.player + ) + self.theirs = Submission.objects.create( + event=self.event, scope=self.scope, user=other + ) + Submission.objects.create(event=self.event, scope=other_scope, user=other) + + self.url = f"/api/events/{self.event.path}/submissions/submission/by/user" + + def _list(self): + response = self.client.get(self.url, {"scope_id": self.scope.id}) + self.assertEqual(response.status_code, 200) + return response.json() + + def test_player_sees_only_own_submissions(self): + self.client.force_login(self.player) + + data = self._list() + + self.assertEqual([s["id"] for s in data], [str(self.mine.id)]) + self.assertEqual(data[0]["username"], "player") + + def test_staff_sees_all_submissions_of_scope(self): + staff = CustomUser.objects.create_user( + username="staff", password="x", is_staff=True + ) + self.client.force_login(staff) + + data = self._list() + + self.assertEqual( + {s["id"] for s in data}, {str(self.mine.id), str(self.theirs.id)} + )