fix(submissions): fix upload + bucket path
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
import { ref, onMounted } from "vue";
|
import { ref, onMounted } from "vue";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
|
import { submissionsApiCreateSubmission, submissionsApiListUserSubmissions } from "@/api";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
gamePath?: string;
|
gamePath?: string;
|
||||||
@@ -27,6 +28,8 @@ const notes = ref("");
|
|||||||
const selectedFile = ref<File | null>(null);
|
const selectedFile = ref<File | null>(null);
|
||||||
const filePreviewUrl = ref<string>("");
|
const filePreviewUrl = ref<string>("");
|
||||||
const isModalOpen = ref(false);
|
const isModalOpen = ref(false);
|
||||||
|
const userSubmissions = ref<any[]>([]);
|
||||||
|
const isLoadingSubmissions = ref(false);
|
||||||
|
|
||||||
const openFilePreview = async (file: File) => {
|
const openFilePreview = async (file: File) => {
|
||||||
selectedFile.value = file;
|
selectedFile.value = file;
|
||||||
@@ -65,13 +68,13 @@ const getGameHeaderImage = () => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFileIcon = (file: File | null) => {
|
const getFileIcon = (file: File | null | { type?: string }) => {
|
||||||
if (!file) return "mdi-file";
|
if (!file) return "mdi-file";
|
||||||
const type = file.type;
|
const type = (file as any).type;
|
||||||
if (type.startsWith("image/")) return "mdi-image";
|
if (type?.startsWith("image/")) return "mdi-image";
|
||||||
if (type === "application/pdf") return "mdi-file-pdf";
|
if (type === "application/pdf") return "mdi-file-pdf";
|
||||||
if (type.startsWith("text/")) return "mdi-file-document";
|
if (type?.startsWith("text/")) return "mdi-file-document";
|
||||||
if (type.includes("video")) return "mdi-video";
|
if (type?.includes("video")) return "mdi-video";
|
||||||
return "mdi-file";
|
return "mdi-file";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -113,36 +116,18 @@ const submitSubmission = async () => {
|
|||||||
|
|
||||||
isUploading.value = true;
|
isUploading.value = true;
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
await submissionsApiCreateSubmission({
|
||||||
|
query: { game_path: props.gamePath },
|
||||||
// Add files
|
body: {
|
||||||
uploadedFiles.value.forEach((file) => {
|
files: uploadedFiles.value,
|
||||||
formData.append("files", file);
|
notes: notes.value || null,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add form fields
|
|
||||||
formData.append("notes", notes.value);
|
|
||||||
|
|
||||||
// Build URL with query parameters
|
|
||||||
const url = new URL("/api/submissions/create", window.location.origin);
|
|
||||||
url.searchParams.append("game_path", props.gamePath);
|
|
||||||
|
|
||||||
const response = await fetch(url.toString(), {
|
|
||||||
method: "POST",
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json();
|
|
||||||
const errorMsg = Array.isArray(errorData.detail)
|
|
||||||
? errorData.detail.map((e: any) => e.msg).join(", ")
|
|
||||||
: errorData.detail || "Failed to submit";
|
|
||||||
throw new Error(errorMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadedFiles.value = [];
|
uploadedFiles.value = [];
|
||||||
notes.value = "";
|
notes.value = "";
|
||||||
alert("Submission successful!");
|
alert("Submission successful!");
|
||||||
|
await loadUserSubmissions();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error submitting:", error);
|
console.error("Error submitting:", error);
|
||||||
alert(`Error submitting: ${error instanceof Error ? error.message : "Please try again."}`);
|
alert(`Error submitting: ${error instanceof Error ? error.message : "Please try again."}`);
|
||||||
@@ -163,8 +148,46 @@ const formatFileSize = (bytes: number) => {
|
|||||||
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
|
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatDate = (dateString: string) => {
|
||||||
|
return new Date(dateString).toLocaleString();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOCRStatusColor = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case "processed":
|
||||||
|
return "badge badge-success";
|
||||||
|
case "processing":
|
||||||
|
return "badge badge-warning";
|
||||||
|
case "to_process":
|
||||||
|
return "badge badge-info";
|
||||||
|
case "error":
|
||||||
|
return "badge badge-error";
|
||||||
|
default:
|
||||||
|
return "badge";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadUserSubmissions = async () => {
|
||||||
|
if (!props.gamePath) return;
|
||||||
|
|
||||||
|
isLoadingSubmissions.value = true;
|
||||||
|
try {
|
||||||
|
const { data } = await submissionsApiListUserSubmissions({
|
||||||
|
query: { game_path: props.gamePath },
|
||||||
|
});
|
||||||
|
if (data && Array.isArray(data)) {
|
||||||
|
userSubmissions.value = data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading submissions:", error);
|
||||||
|
} finally {
|
||||||
|
isLoadingSubmissions.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
userStore.loadUserData();
|
userStore.loadUserData();
|
||||||
|
loadUserSubmissions();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -188,7 +211,23 @@ onMounted(() => {
|
|||||||
<div class="container min-w-3/4 mx-auto px-4 py-8">
|
<div class="container min-w-3/4 mx-auto px-4 py-8">
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||||
<!-- Right Column: Upload -->
|
<!-- Right Column: Upload -->
|
||||||
<div class="lg:col-span-1">
|
<div class="lg:col-span-1 flex flex-col gap-8 order-1 lg:order-2">
|
||||||
|
<!-- Game Card -->
|
||||||
|
<div class="card bg-base-100 shadow-lg">
|
||||||
|
<figure v-if="getGameHeaderImage()" class="relative h-48 bg-base-300 overflow-hidden">
|
||||||
|
<img :src="getGameHeaderImage()!" :alt="gameName" class="w-full h-full object-cover" />
|
||||||
|
<div class="absolute inset-0 bg-black/30"></div>
|
||||||
|
</figure>
|
||||||
|
<figure v-else class="relative h-48 bg-gradient-to-br from-blue-600 to-blue-400 flex items-center justify-center">
|
||||||
|
<i class="mdi mdi-gamepad-variant text-6xl text-white opacity-30"></i>
|
||||||
|
</figure>
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title text-2xl">{{ gameName }}</h2>
|
||||||
|
<p class="text-base-content/70">Submit your best results for this game</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Upload Card -->
|
||||||
<div class="card bg-base-100 shadow-lg">
|
<div class="card bg-base-100 shadow-lg">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title text-2xl mb-6">
|
<h2 class="card-title text-2xl mb-6">
|
||||||
@@ -269,22 +308,7 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Left Column: Info -->
|
<!-- Left Column: Info -->
|
||||||
<div class="lg:col-span-1 flex flex-col gap-8">
|
<div class="lg:col-span-1 order-2 lg:order-1">
|
||||||
<!-- Game Card -->
|
|
||||||
<div class="card bg-base-100 shadow-lg">
|
|
||||||
<figure v-if="getGameHeaderImage()" class="relative h-48 bg-base-300 overflow-hidden">
|
|
||||||
<img :src="getGameHeaderImage()!" :alt="gameName" class="w-full h-full object-cover" />
|
|
||||||
<div class="absolute inset-0 bg-black/30"></div>
|
|
||||||
</figure>
|
|
||||||
<figure v-else class="relative h-48 bg-gradient-to-br from-blue-600 to-blue-400 flex items-center justify-center">
|
|
||||||
<i class="mdi mdi-gamepad-variant text-6xl text-white opacity-30"></i>
|
|
||||||
</figure>
|
|
||||||
<div class="card-body">
|
|
||||||
<h2 class="card-title text-2xl">{{ gameName }}</h2>
|
|
||||||
<p class="text-base-content/70">Submit your best results for this game</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Guidelines Card -->
|
<!-- Guidelines Card -->
|
||||||
<div class="card bg-base-100 shadow-lg sticky top-8">
|
<div class="card bg-base-100 shadow-lg sticky top-8">
|
||||||
<div class="bg-gradient-to-br from-blue-600 to-blue-400 p-8 text-white rounded-t-2xl">
|
<div class="bg-gradient-to-br from-blue-600 to-blue-400 p-8 text-white rounded-t-2xl">
|
||||||
@@ -347,6 +371,77 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- User Submissions Section -->
|
||||||
|
<div class="container min-w-3/4 mx-auto px-4 py-8">
|
||||||
|
<div class="card bg-base-100 shadow-lg">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title text-2xl mb-6">
|
||||||
|
<i class="mdi mdi-history text-primary mr-2"></i>
|
||||||
|
Your Submissions
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div v-if="isLoadingSubmissions" class="flex justify-center py-8">
|
||||||
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="userSubmissions.length === 0" class="text-center py-8">
|
||||||
|
<p class="text-base-content/70 mb-2">No submissions yet</p>
|
||||||
|
<p class="text-sm text-base-content/50">Upload your first submission above to get started</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="overflow-x-auto">
|
||||||
|
<table class="table w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Files</th>
|
||||||
|
<th>File Status</th>
|
||||||
|
<th>OCR Status</th>
|
||||||
|
<th>Notes</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="submission in userSubmissions" :key="submission.id" class="hover">
|
||||||
|
<td class="text-sm">{{ formatDate(submission.created_at) }}</td>
|
||||||
|
<td class="text-sm">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<div v-for="file in submission.files" :key="file.id" class="flex items-center gap-2">
|
||||||
|
<i :class="['mdi text-primary text-sm', getFileIcon({ type: file.content_type })]"></i>
|
||||||
|
<a :href="file.file_url" target="_blank" class="link link-primary truncate max-w-xs">
|
||||||
|
{{ file.original_filename }}
|
||||||
|
</a>
|
||||||
|
<span class="text-xs text-base-content/50">({{ formatFileSize(file.file_size) }})</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="text-sm">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<div v-for="file in submission.files" :key="file.id" class="text-xs">
|
||||||
|
{{ file.content_type }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="text-sm">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<div v-for="file in submission.files" :key="file.id">
|
||||||
|
<span :class="getOCRStatusColor(file.ocr_status)">{{ file.ocr_status }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="text-sm text-base-content/70">
|
||||||
|
<span v-if="submission.notes" :title="submission.notes" class="truncate block max-w-xs">
|
||||||
|
{{ submission.notes }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="text-base-content/50">-</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- File Preview Modal -->
|
<!-- File Preview Modal -->
|
||||||
<div :class="['modal', { 'modal-open': isModalOpen }]">
|
<div :class="['modal', { 'modal-open': isModalOpen }]">
|
||||||
<div class="modal-box w-11/12 max-w-4xl">
|
<div class="modal-box w-11/12 max-w-4xl">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
export { animationsApiPuzzleResults, animationsApiResults, animationsApiTopSubmissions, gamesApiListGames, noitaApiGetLeaderboard, noitaApiGetResults, noitaApiSubmitLogFile, type Options, opusMagnumApiCreateSubmission, opusMagnumApiDeleteSubmission, opusMagnumApiGetCollection, opusMagnumApiGetStats, opusMagnumApiGetSubmission, opusMagnumApiListPuzzles, opusMagnumApiListResponsesNeedingValidation, opusMagnumApiListSubmissions, opusMagnumApiValidateAuto, opusMagnumApiValidateResponse, opusMagnumApiValidateSubmission, polylanSubmitterApiClearCache, polylanSubmitterApiGetUserInfo, polylanSubmitterApiHealthCheck, submissionsApiCreateSubmission, submissionsApiGetSubmission, submissionsApiListSubmissionsByGame } from './sdk.gen';
|
export { animationsApiPuzzleResults, animationsApiResults, animationsApiTopSubmissions, gamesApiListGames, noitaApiGetLeaderboard, noitaApiGetResults, noitaApiSubmitLogFile, type Options, opusMagnumApiCreateSubmission, opusMagnumApiDeleteSubmission, opusMagnumApiGetCollection, opusMagnumApiGetStats, opusMagnumApiGetSubmission, opusMagnumApiListPuzzles, opusMagnumApiListResponsesNeedingValidation, opusMagnumApiListSubmissions, opusMagnumApiValidateAuto, opusMagnumApiValidateResponse, opusMagnumApiValidateSubmission, polylanSubmitterApiClearCache, polylanSubmitterApiGetUserInfo, polylanSubmitterApiHealthCheck, submissionsApiCreateSubmission, submissionsApiGetSubmission, submissionsApiListSubmissionsByGame, submissionsApiListUserSubmissions } from './sdk.gen';
|
||||||
export type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponse, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponse, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponse, AnimationsApiTopSubmissionsResponses, ClientOptions, GameOut, GamesApiListGamesData, GamesApiListGamesResponse, GamesApiListGamesResponses, Input, LeaderboardEntryOut, LeaderboardOut, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponse, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponse, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileError, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponse, NoitaApiSubmitLogFileResponses, NoitaSubmissionOut, ObjectivResultOut, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionResponse, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponse, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponse, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponse, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationResponse, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponse, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoResponse, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseResponse, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionResponse, OpusMagnumApiValidateSubmissionResponses, PagedSubmissionOut, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponse, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, PuzzlePointsFactorOut, PuzzleResponseIn, PuzzleResponseOut, PuzzleResponseRankingOut, PuzzleResultsOut, PuzzleSubmissionsOut, PuzzleSubmissionWithRankOut, RankingSchema, ResultsOut, SteamCollectionItemOut, SteamCollectionOut, SubmissionFileOut, SubmissionIn, SubmissionOut, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionResponse, SubmissionsApiCreateSubmissionResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionResponse, SubmissionsApiGetSubmissionResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameResponses, TournamentPuzzleResultsOut, TournamentSubmissionsOut, UserDisplayOut, UserInfoOut, ValidationIn, WinnerFileOut, WinnerResponseOut } from './types.gen';
|
export type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponse, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponse, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponse, AnimationsApiTopSubmissionsResponses, ClientOptions, GameOut, GamesApiListGamesData, GamesApiListGamesResponse, GamesApiListGamesResponses, Input, LeaderboardEntryOut, LeaderboardOut, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponse, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponse, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileError, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponse, NoitaApiSubmitLogFileResponses, NoitaSubmissionOut, ObjectivResultOut, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionResponse, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponse, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponse, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponse, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationResponse, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponse, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoResponse, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseResponse, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionResponse, OpusMagnumApiValidateSubmissionResponses, PagedSubmissionOut, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponse, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, PuzzlePointsFactorOut, PuzzleResponseIn, PuzzleResponseOut, PuzzleResponseRankingOut, PuzzleResultsOut, PuzzleSubmissionsOut, PuzzleSubmissionWithRankOut, RankingSchema, ResultsOut, SteamCollectionItemOut, SteamCollectionOut, SubmissionFileOut, SubmissionIn, SubmissionOut, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionResponse, SubmissionsApiCreateSubmissionResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionResponse, SubmissionsApiGetSubmissionResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameResponses, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsResponses, TournamentPuzzleResultsOut, TournamentSubmissionsOut, UserDisplayOut, UserInfoOut, ValidationIn, WinnerFileOut, WinnerResponseOut } from './types.gen';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { type Client, formDataBodySerializer, type Options as Options2, type TDataShape } from './client';
|
import { type Client, formDataBodySerializer, type Options as Options2, type TDataShape } from './client';
|
||||||
import { client } from './client.gen';
|
import { client } from './client.gen';
|
||||||
import type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponses, GamesApiListGamesData, GamesApiListGamesResponses, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponses, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionResponses, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameResponses } from './types.gen';
|
import type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponses, GamesApiListGamesData, GamesApiListGamesResponses, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponses, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionResponses, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameResponses, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsResponses } from './types.gen';
|
||||||
|
|
||||||
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean, TResponse = unknown> = Options2<TData, ThrowOnError, TResponse> & {
|
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean, TResponse = unknown> = Options2<TData, ThrowOnError, TResponse> & {
|
||||||
/**
|
/**
|
||||||
@@ -209,7 +209,12 @@ export const submissionsApiCreateSubmission = <ThrowOnError extends boolean = fa
|
|||||||
*/
|
*/
|
||||||
export const submissionsApiGetSubmission = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiGetSubmissionData, ThrowOnError>) => (options.client ?? client).get<SubmissionsApiGetSubmissionResponses, unknown, ThrowOnError>({ url: '/api/submissions/{submission_id}', ...options });
|
export const submissionsApiGetSubmission = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiGetSubmissionData, ThrowOnError>) => (options.client ?? client).get<SubmissionsApiGetSubmissionResponses, unknown, ThrowOnError>({ url: '/api/submissions/{submission_id}', ...options });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List User Submissions
|
||||||
|
*/
|
||||||
|
export const submissionsApiListUserSubmissions = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiListUserSubmissionsData, ThrowOnError>) => (options.client ?? client).get<SubmissionsApiListUserSubmissionsResponses, unknown, ThrowOnError>({ url: '/api/submissions/user', ...options });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List Submissions By Game
|
* List Submissions By Game
|
||||||
*/
|
*/
|
||||||
export const submissionsApiListSubmissionsByGame = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiListSubmissionsByGameData, ThrowOnError>) => (options.client ?? client).get<SubmissionsApiListSubmissionsByGameResponses, unknown, ThrowOnError>({ url: '/api/submissions/game/{game_id}', ...options });
|
export const submissionsApiListSubmissionsByGame = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiListSubmissionsByGameData, ThrowOnError>) => (options.client ?? client).get<SubmissionsApiListSubmissionsByGameResponses, unknown, ThrowOnError>({ url: '/api/submissions/game/{game_path}', ...options });
|
||||||
|
|||||||
@@ -1318,25 +1318,25 @@ export type GamesApiListGamesResponse = GamesApiListGamesResponses[keyof GamesAp
|
|||||||
|
|
||||||
export type SubmissionsApiCreateSubmissionData = {
|
export type SubmissionsApiCreateSubmissionData = {
|
||||||
/**
|
/**
|
||||||
* FileParams
|
* MultiPartBodyParams
|
||||||
*/
|
*/
|
||||||
body: {
|
body: {
|
||||||
/**
|
/**
|
||||||
* Files
|
* Files
|
||||||
*/
|
*/
|
||||||
files: Array<Blob | File>;
|
files: Array<Blob | File>;
|
||||||
};
|
|
||||||
path?: never;
|
|
||||||
query: {
|
|
||||||
/**
|
|
||||||
* Game Id
|
|
||||||
*/
|
|
||||||
game_id: number;
|
|
||||||
/**
|
/**
|
||||||
* Notes
|
* Notes
|
||||||
*/
|
*/
|
||||||
notes?: string | null;
|
notes?: string | null;
|
||||||
};
|
};
|
||||||
|
path?: never;
|
||||||
|
query: {
|
||||||
|
/**
|
||||||
|
* Game Path
|
||||||
|
*/
|
||||||
|
game_path: string;
|
||||||
|
};
|
||||||
url: '/api/submissions/create';
|
url: '/api/submissions/create';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1370,16 +1370,35 @@ export type SubmissionsApiGetSubmissionResponses = {
|
|||||||
|
|
||||||
export type SubmissionsApiGetSubmissionResponse = SubmissionsApiGetSubmissionResponses[keyof SubmissionsApiGetSubmissionResponses];
|
export type SubmissionsApiGetSubmissionResponse = SubmissionsApiGetSubmissionResponses[keyof SubmissionsApiGetSubmissionResponses];
|
||||||
|
|
||||||
|
export type SubmissionsApiListUserSubmissionsData = {
|
||||||
|
body?: never;
|
||||||
|
path?: never;
|
||||||
|
query: {
|
||||||
|
/**
|
||||||
|
* Game Path
|
||||||
|
*/
|
||||||
|
game_path: string;
|
||||||
|
};
|
||||||
|
url: '/api/submissions/user';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SubmissionsApiListUserSubmissionsResponses = {
|
||||||
|
/**
|
||||||
|
* OK
|
||||||
|
*/
|
||||||
|
200: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
export type SubmissionsApiListSubmissionsByGameData = {
|
export type SubmissionsApiListSubmissionsByGameData = {
|
||||||
body?: never;
|
body?: never;
|
||||||
path: {
|
path: {
|
||||||
/**
|
/**
|
||||||
* Game Id
|
* Game Path
|
||||||
*/
|
*/
|
||||||
game_id: number;
|
game_path: string;
|
||||||
};
|
};
|
||||||
query?: never;
|
query?: never;
|
||||||
url: '/api/submissions/game/{game_id}';
|
url: '/api/submissions/game/{game_path}';
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SubmissionsApiListSubmissionsByGameResponses = {
|
export type SubmissionsApiListSubmissionsByGameResponses = {
|
||||||
|
|||||||
@@ -37,6 +37,29 @@ def create_submission(
|
|||||||
return submission
|
return submission
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/user", response=list[SubmissionOut])
|
||||||
|
def list_user_submissions(request: HttpRequest, game_path: str):
|
||||||
|
game = get_object_or_404(Game, path=game_path)
|
||||||
|
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
return 403, {"detail": "Authentication required"}
|
||||||
|
|
||||||
|
submissions = Submission.objects.filter(game=game, user=request.user).order_by(
|
||||||
|
"-created_at"
|
||||||
|
)
|
||||||
|
return submissions
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/game", response=list[SubmissionOut])
|
||||||
|
def list_submissions_by_game(request: HttpRequest, game_path: str):
|
||||||
|
game = get_object_or_404(Game, path=game_path)
|
||||||
|
|
||||||
|
if not request.user.is_staff:
|
||||||
|
return 403, {"detail": "Admin access required"}
|
||||||
|
|
||||||
|
return Submission.objects.filter(game=game)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{submission_id}", response=SubmissionOut)
|
@router.get("/{submission_id}", response=SubmissionOut)
|
||||||
def get_submission(request: HttpRequest, submission_id: str):
|
def get_submission(request: HttpRequest, submission_id: str):
|
||||||
submission = get_object_or_404(Submission, id=submission_id)
|
submission = get_object_or_404(Submission, id=submission_id)
|
||||||
@@ -45,14 +68,3 @@ def get_submission(request: HttpRequest, submission_id: str):
|
|||||||
return 403, {"detail": "Not authorized"}
|
return 403, {"detail": "Not authorized"}
|
||||||
|
|
||||||
return submission
|
return submission
|
||||||
|
|
||||||
|
|
||||||
@router.get("/game/{game_path}")
|
|
||||||
def list_submissions_by_game(request: HttpRequest, game_path: str):
|
|
||||||
game = get_object_or_404(Game, path=game_path)
|
|
||||||
|
|
||||||
if not request.user.is_staff:
|
|
||||||
return 403, {"detail": "Admin access required"}
|
|
||||||
|
|
||||||
submissions = Submission.objects.filter(game=game)
|
|
||||||
return [SubmissionOut.from_orm(s) for s in submissions]
|
|
||||||
|
|||||||
@@ -18,8 +18,10 @@ def submission_file_upload_path(instance, filename):
|
|||||||
ext = filename.split(".")[-1] if "." in filename else ""
|
ext = filename.split(".")[-1] if "." in filename else ""
|
||||||
|
|
||||||
new_filename = f"{instance.id}_{filename}" if ext else str(instance.id)
|
new_filename = f"{instance.id}_{filename}" if ext else str(instance.id)
|
||||||
|
date = instance.created_at.strftime("%Y/%m/%d")
|
||||||
|
user = instance.submission.user.username
|
||||||
|
|
||||||
return f"submissions/{instance.submission.id}/{new_filename}"
|
return f"{instance.submission.game.path.lstrip('/')}/{date}/{user}/{instance.submission.id}/{new_filename}"
|
||||||
|
|
||||||
|
|
||||||
class BaseModel(models.Model):
|
class BaseModel(models.Model):
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from datetime import datetime
|
||||||
|
from uuid import UUID
|
||||||
from ninja import Schema
|
from ninja import Schema
|
||||||
|
|
||||||
|
|
||||||
class SubmissionFileOut(Schema):
|
class SubmissionFileOut(Schema):
|
||||||
id: str
|
id: UUID
|
||||||
original_filename: str
|
original_filename: str
|
||||||
file_size: int
|
file_size: int
|
||||||
content_type: str
|
content_type: str
|
||||||
@@ -12,9 +14,9 @@ class SubmissionFileOut(Schema):
|
|||||||
|
|
||||||
|
|
||||||
class SubmissionOut(Schema):
|
class SubmissionOut(Schema):
|
||||||
id: str
|
id: UUID
|
||||||
game_id: Optional[int] = None
|
game_id: Optional[int] = None
|
||||||
notes: Optional[str] = None
|
notes: Optional[str] = None
|
||||||
files: list[SubmissionFileOut] = []
|
files: list[SubmissionFileOut] = []
|
||||||
created_at: str
|
created_at: datetime
|
||||||
updated_at: str
|
updated_at: datetime
|
||||||
|
|||||||
Reference in New Issue
Block a user