From ae6e96813521ec49076d14c4a10ce0b137c39397 Mon Sep 17 00:00:00 2001 From: rhpidfyre Date: Sat, 14 Dec 2024 00:37:52 -0500 Subject: [PATCH 001/454] web: remove Roblox.ts --- .../app/submissions/[submissionId]/_map.tsx | 17 +------ .../[submissionId]/_reviewButtons.tsx | 2 +- .../app/submissions/[submissionId]/page.tsx | 8 ++-- web/src/app/ts/Roblox.ts | 48 ------------------- 4 files changed, 7 insertions(+), 68 deletions(-) delete mode 100644 web/src/app/ts/Roblox.ts diff --git a/web/src/app/submissions/[submissionId]/_map.tsx b/web/src/app/submissions/[submissionId]/_map.tsx index 425e630..e364f70 100644 --- a/web/src/app/submissions/[submissionId]/_map.tsx +++ b/web/src/app/submissions/[submissionId]/_map.tsx @@ -1,24 +1,11 @@ -"use client" - -import { useState, useEffect } from "react" import { SubmissionInfo } from "@/app/ts/Submission" -import { AssetImage } from "@/app/ts/Roblox" -import Image from "next/image" interface AssetID { id: SubmissionInfo["AssetID"] } -function MapImage(asset: AssetID) { - const [assetImage, setAssetImage] = useState(""); - - useEffect(() => { - AssetImage(asset.id, "420x420").then(image => setAssetImage(image)) - }, [asset.id]); - if (!assetImage) { - return

Fetching map image...

; - } - return Map Image +function MapImage() { + return

Fetching map image...

} export { diff --git a/web/src/app/submissions/[submissionId]/_reviewButtons.tsx b/web/src/app/submissions/[submissionId]/_reviewButtons.tsx index d98162a..ae66adc 100644 --- a/web/src/app/submissions/[submissionId]/_reviewButtons.tsx +++ b/web/src/app/submissions/[submissionId]/_reviewButtons.tsx @@ -9,7 +9,7 @@ interface ReviewButton { } function ReviewButtonClicked(action: Action) { - const post = fetch(`http://localhost:3000/v1/submissions/1/status/${action}`, { + fetch(`http://localhost:3000/v1/submissions/1/status/${action}`, { method: "POST", headers: { "Content-type": "application/json", diff --git a/web/src/app/submissions/[submissionId]/page.tsx b/web/src/app/submissions/[submissionId]/page.tsx index e270d77..8f52fd6 100644 --- a/web/src/app/submissions/[submissionId]/page.tsx +++ b/web/src/app/submissions/[submissionId]/page.tsx @@ -2,7 +2,7 @@ import { SubmissionStatus, SubmissionStatusToString } from "@/app/ts/Submission"; import type { CreatorAndReviewStatus } from "./_comments"; -import { MapImage, type AssetID } from "./_map"; +import { MapImage } from "./_map"; import { useParams } from "next/navigation"; import ReviewButtons from "./_reviewButtons"; import { Rating } from "@mui/material"; @@ -34,11 +34,11 @@ function Ratings() { ) } -function RatingArea(asset: AssetID) { +function RatingArea() { return ( ) @@ -70,7 +70,7 @@ function TitleAndComments(stats: CreatorAndReviewStatus) { export default function SubmissionInfoPage() { const dynamicId = useParams<{submissionId: string}>() - const [submission, setSubmission] = useState(null) + const [submission, setSubmission] = useState(null) useEffect(() => { // needs to be client sided since server doesn't have a session, nextjs got mad at me for exporting an async function: (https://nextjs.org/docs/messages/no-async-client-component) async function getSubmission() { @@ -79,7 +79,7 @@ export default function SubmissionInfoPage() { setSubmission(data) } getSubmission() - }, []) + }, [dynamicId.submissionId]) if (!submission) return ( diff --git a/web/src/app/submissions/_card.tsx b/web/src/app/submissions/_card.tsx index 5446573..a58fdb9 100644 --- a/web/src/app/submissions/_card.tsx +++ b/web/src/app/submissions/_card.tsx @@ -2,7 +2,15 @@ import React from "react"; import Image from "next/image"; import Link from "next/link"; -export default function SubmissionCard({ id, displayName, author, rating }) { +interface SubmissionCardProps { + id: number; + assetId: number; + displayName: string; + author: string; + rating: number; +} + +export default function SubmissionCard({ id, displayName, author, rating }: SubmissionCardProps) { return (
diff --git a/web/src/app/submissions/page.tsx b/web/src/app/submissions/page.tsx index 57ba65a..866e2e6 100644 --- a/web/src/app/submissions/page.tsx +++ b/web/src/app/submissions/page.tsx @@ -7,9 +7,10 @@ import SubmissionCard from "./_card"; import SkeletonGrid from './_loading'; import "./(styles)/page.scss"; +import { SubmissionInfo } from '../ts/Submission'; export default function SubmissionInfoPage() { - const [submissions, setSubmissions] = useState(null) + const [submissions, setSubmissions] = useState([]) useEffect(() => { // needs to be client sided since server doesn't have a session, nextjs got mad at me for exporting an async function: (https://nextjs.org/docs/messages/no-async-client-component) async function fetchSubmissions() { @@ -45,7 +46,7 @@ export default function SubmissionInfoPage() { >; + game: number; + setGame: React.Dispatch>; }; const BootstrapInput = styled(InputBase)(({ theme }) => ({ @@ -44,14 +45,14 @@ const BootstrapInput = styled(InputBase)(({ theme }) => ({ export default function GameSelection({ game, setGame }: GameSelectionProps) { const handleChange = (event: SelectChangeEvent) => { - setGame(event.target.value); + setGame(Number(event.target.value)); // TODO: Change later!! there's 100% a proper way of doing this }; return ( Game + + + ) +} + +export default function Comments(stats: CommentersProps) { + return (<> +
+ {stats.comments_data.comments.length===0 + &&

There are no comments.

+ || stats.comments_data.comments.map(comment => ( + + ))} +
+ + ) +} + +export { + type CreatorAndReviewStatus +} diff --git a/web/src/app/mapfixes/[mapfixId]/_map.tsx b/web/src/app/mapfixes/[mapfixId]/_map.tsx new file mode 100644 index 0000000..e364f70 --- /dev/null +++ b/web/src/app/mapfixes/[mapfixId]/_map.tsx @@ -0,0 +1,14 @@ +import { SubmissionInfo } from "@/app/ts/Submission" + +interface AssetID { + id: SubmissionInfo["AssetID"] +} + +function MapImage() { + return

Fetching map image...

+} + +export { + type AssetID, + MapImage +} \ No newline at end of file diff --git a/web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx b/web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx new file mode 100644 index 0000000..822327c --- /dev/null +++ b/web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx @@ -0,0 +1,74 @@ +import { Button, ButtonOwnProps } from "@mui/material"; + +type Actions = "Completed" | "Submit" | "Reject" | "Revoke" +type ApiActions = Lowercase | "trigger-validate" | "retry-validate" | "trigger-upload" | "reset-uploading" | "reset-validating" +type Review = Actions | "Accept" | "Validate" | "Upload" | "Reset Uploading (fix softlocked status)" | "Reset Validating (fix softlocked status)" | "Request Changes" + +interface ReviewButton { + name: Review, + action: ApiActions, + submissionId: string, + color: ButtonOwnProps["color"] +} + +interface ReviewId { + submissionId: string +} + +async function ReviewButtonClicked(action: ApiActions, submissionId: string) { + try { + const response = await fetch(`/api/submissions/${submissionId}/status/${action}`, { + method: "POST", + headers: { + "Content-type": "application/json", + } + }); + // Check if the HTTP request was successful + if (!response.ok) { + const errorDetails = await response.text(); + + // Throw an error with detailed information + throw new Error(`HTTP error! status: ${response.status}, details: ${errorDetails}`); + } + + window.location.reload(); + } catch (error) { + console.error("Error updating submission status:", error); + } +} + +function ReviewButton(props: ReviewButton) { + return +} + +export default function ReviewButtons(props: ReviewId) { + const submissionId = props.submissionId + // When is each button visible? + // Multiple buttons can be visible at once. + // Action | Role | When Current Status is One of: + // ---------------|-----------|----------------------- + // Submit | Submitter | UnderConstruction, ChangesRequested + // Revoke | Submitter | Submitted, ChangesRequested + // Accept | Reviewer | Submitted + // Validate | Reviewer | Accepted + // ResetValidating| Reviewer | Validating + // Reject | Reviewer | Submitted + // RequestChanges | Reviewer | Validated, Accepted, Submitted + // Upload | MapAdmin | Validated + // ResetUploading | MapAdmin | Uploading + return ( +
+ + + + + + + + +
+ ) +} diff --git a/web/src/app/mapfixes/[mapfixId]/_window.tsx b/web/src/app/mapfixes/[mapfixId]/_window.tsx new file mode 100644 index 0000000..866b5a4 --- /dev/null +++ b/web/src/app/mapfixes/[mapfixId]/_window.tsx @@ -0,0 +1,20 @@ +interface WindowStruct { + className: string, + title: string, + children: React.ReactNode +} + +export default function Window(window: WindowStruct) { + return ( +
+
+

{window.title}

+
+
{window.children}
+
+ ) +} + +export { + type WindowStruct +} \ No newline at end of file diff --git a/web/src/app/mapfixes/[mapfixId]/page.tsx b/web/src/app/mapfixes/[mapfixId]/page.tsx new file mode 100644 index 0000000..9dae81f --- /dev/null +++ b/web/src/app/mapfixes/[mapfixId]/page.tsx @@ -0,0 +1,105 @@ +"use client" + +import { SubmissionInfo, SubmissionStatusToString } from "@/app/ts/Submission"; +import type { CreatorAndReviewStatus } from "./_comments"; +import { MapImage } from "./_map"; +import { useParams } from "next/navigation"; +import ReviewButtons from "./_reviewButtons"; +import { Rating } from "@mui/material"; +import Comments from "./_comments"; +import Webpage from "@/app/_components/webpage"; +import Window from "./_window"; +import Link from "next/link"; +import { useState, useEffect } from "react"; + +import "./(styles)/page.scss"; + +interface ReviewId { + submissionId: string +} + +function Ratings() { + return ( + +
+ + +
+
+ ) +} + +function RatingArea(submission: ReviewId) { + return ( + + ) +} + +function TitleAndComments(stats: CreatorAndReviewStatus) { + const Review = SubmissionStatusToString(stats.review) + + // TODO: hide status message when status is not "Accepted" + return ( +
+
+

{stats.name}

+ +
+

by {stats.creator}

+

Model Asset ID {stats.asset_id}

+

Validation Error: {stats.status_message}

+ + +
+ ) +} + +export default function SubmissionInfoPage() { + const dynamicId = useParams<{submissionId: string}>() + + const [submission, setSubmission] = useState(null) + + useEffect(() => { // needs to be client sided since server doesn't have a session, nextjs got mad at me for exporting an async function: (https://nextjs.org/docs/messages/no-async-client-component) + async function getSubmission() { + const res = await fetch(`/api/submissions/${dynamicId.submissionId}`) + if (res.ok) { + setSubmission(await res.json()) + } + } + getSubmission() + }, [dynamicId.submissionId]) + + if (!submission) { + return + {/* TODO: Add skeleton loading thingy ? Maybe ? (https://mui.com/material-ui/react-skeleton/) */} + + } + return ( + +
+
+ + +
+
+
+ ) +} diff --git a/web/src/app/mapfixes/_card.tsx b/web/src/app/mapfixes/_card.tsx new file mode 100644 index 0000000..29a6793 --- /dev/null +++ b/web/src/app/mapfixes/_card.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import Image from "next/image"; +import Link from "next/link"; +import { Rating } from "@mui/material"; + +interface SubmissionCardProps { + displayName: string; + assetId: number; + rating: number; + author: string; + id: number; +} + +export default function SubmissionCard(props: SubmissionCardProps) { + return ( + +
+
+
+ {/* TODO: Grab image of model */} + {props.displayName} +
+
+
+ {props.displayName} +
+ +
+
+
+
+ {props.author}/ + {props.author} +
+
+
+
+
+ + ); +} \ No newline at end of file diff --git a/web/src/app/mapfixes/_window.tsx b/web/src/app/mapfixes/_window.tsx new file mode 100644 index 0000000..b71e7e2 --- /dev/null +++ b/web/src/app/mapfixes/_window.tsx @@ -0,0 +1,18 @@ +interface WindowStruct { + children: React.ReactNode, + className: string, + title: string, +} + +export default function Window(window: WindowStruct) { + return
+
+

{window.title}

+
+
{window.children}
+
+} + +export { + type WindowStruct +} \ No newline at end of file diff --git a/web/src/app/mapfixes/page.tsx b/web/src/app/mapfixes/page.tsx new file mode 100644 index 0000000..2b803a7 --- /dev/null +++ b/web/src/app/mapfixes/page.tsx @@ -0,0 +1,110 @@ +'use client' + +import React, { useState, useEffect } from "react"; +import { SubmissionInfo } from "../ts/Submission"; +import SubmissionCard from "./_card"; +import Webpage from "@/app/_components/webpage"; + +import "./(styles)/page.scss"; + +export default function SubmissionInfoPage() { + const [submissions, setSubmissions] = useState([]) + const [currentPage, setCurrentPage] = useState(0); + const cardsPerPage = 24; // built to fit on a 1920x1080 monitor + + const totalPages = Math.ceil(submissions.length / cardsPerPage); + + const currentCards = submissions.slice( + currentPage * cardsPerPage, + (currentPage + 1) * cardsPerPage + ); + + const nextPage = () => { + if (currentPage < totalPages - 1) { + setCurrentPage(currentPage + 1); + } + }; + + const prevPage = () => { + if (currentPage > 0) { + setCurrentPage(currentPage - 1); + } + }; + + useEffect(() => { + async function fetchSubmissions() { + const res = await fetch('/api/submissions?Page=1&Limit=100') + if (res.ok) { + setSubmissions(await res.json()) + } + } + + setTimeout(() => { + fetchSubmissions() + }, 50); + }, []) + + if (!submissions) { + return +
+ Loading... +
+
+ } + + if (submissions && submissions.length == 0) { + return +
+ Submissions list is empty. +
+
+ } + + return ( + // TODO: Add filter settings & searchbar & page selector + +
+
+ {Array.from({ length: totalPages }).map((_, index) => ( + setCurrentPage(index)} + > + ))} +
+
+ + + Page {currentPage + 1} of {totalPages} + + +
+
+ {currentCards.map((submission) => ( + + ))} +
+
+
+ ) +} diff --git a/web/src/app/ts/Mapfix.ts b/web/src/app/ts/Mapfix.ts new file mode 100644 index 0000000..7888436 --- /dev/null +++ b/web/src/app/ts/Mapfix.ts @@ -0,0 +1,59 @@ +const enum MapfixStatus { + UnderConstruction = 0, + Submitted = 1, + ChangesRequested = 2, + Accepted = 3, + Validating = 4, + Validated = 5, + Uploading = 6, + Uploaded = 7, + Rejected = 8, +} + +interface MapfixInfo { + readonly ID: number, + readonly DisplayName: string, + readonly Creator: string, + readonly GameID: number, + readonly Date: number, + readonly Submitter: number, + readonly AssetID: number, + readonly AssetVersion: number, + readonly ValidatedAssetID: number, + readonly ValidatedAssetVersion: number, + readonly Completed: boolean, + readonly TargetAssetID: number, + readonly StatusID: MapfixStatus + readonly StatusMessage: string, +} + +function MapfixStatusToString(submission_status: MapfixStatus): string { + switch (submission_status) { + case MapfixStatus.Rejected: + return "REJECTED" + case MapfixStatus.Uploading: + return "UPLOADING" + case MapfixStatus.Uploaded: + return "UPLOADED" + case MapfixStatus.Validated: + return "VALIDATED" + case MapfixStatus.Validating: + return "VALIDATING" + case MapfixStatus.Accepted: + return "ACCEPTED" + case MapfixStatus.ChangesRequested: + return "CHANGES REQUESTED" + case MapfixStatus.Submitted: + return "SUBMITTED" + case MapfixStatus.UnderConstruction: + return "UNDER CONSTRUCTION" + default: + return "UNKNOWN" + } +} + +export { + MapfixStatus, + MapfixStatusToString, + type MapfixInfo +} -- 2.49.1 From 146d627534e766c53e4f81237d49561c208addeb Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 1 Apr 2025 14:24:36 -0700 Subject: [PATCH 245/454] web: mapfixes: rename all occurrences of submission with mapfix --- web/src/app/mapfixes/[mapfixId]/_comments.tsx | 10 ++--- web/src/app/mapfixes/[mapfixId]/_map.tsx | 6 +-- .../mapfixes/[mapfixId]/_reviewButtons.tsx | 30 ++++++------- web/src/app/mapfixes/[mapfixId]/page.tsx | 32 +++++++------- web/src/app/mapfixes/_card.tsx | 2 +- web/src/app/mapfixes/page.tsx | 42 +++++++++---------- web/src/app/ts/Mapfix.ts | 4 +- 7 files changed, 63 insertions(+), 63 deletions(-) diff --git a/web/src/app/mapfixes/[mapfixId]/_comments.tsx b/web/src/app/mapfixes/[mapfixId]/_comments.tsx index 8e5c84a..7c5286a 100644 --- a/web/src/app/mapfixes/[mapfixId]/_comments.tsx +++ b/web/src/app/mapfixes/[mapfixId]/_comments.tsx @@ -1,4 +1,4 @@ -import type { SubmissionInfo } from "@/app/ts/Submission"; +import type { MapfixInfo } from "@/app/ts/Mapfix"; import { Button } from "@mui/material" import Window from "./_window"; import SendIcon from '@mui/icons-material/Send'; @@ -9,10 +9,10 @@ interface CommentersProps { } interface CreatorAndReviewStatus { - asset_id: SubmissionInfo["AssetID"], - creator: SubmissionInfo["DisplayName"], - review: SubmissionInfo["StatusID"], - status_message: SubmissionInfo["StatusMessage"], + asset_id: MapfixInfo["AssetID"], + creator: MapfixInfo["DisplayName"], + review: MapfixInfo["StatusID"], + status_message: MapfixInfo["StatusMessage"], comments: Comment[], name: string } diff --git a/web/src/app/mapfixes/[mapfixId]/_map.tsx b/web/src/app/mapfixes/[mapfixId]/_map.tsx index e364f70..f515db2 100644 --- a/web/src/app/mapfixes/[mapfixId]/_map.tsx +++ b/web/src/app/mapfixes/[mapfixId]/_map.tsx @@ -1,7 +1,7 @@ -import { SubmissionInfo } from "@/app/ts/Submission" +import { MapfixInfo } from "@/app/ts/Mapfix" interface AssetID { - id: SubmissionInfo["AssetID"] + id: MapfixInfo["AssetID"] } function MapImage() { @@ -11,4 +11,4 @@ function MapImage() { export { type AssetID, MapImage -} \ No newline at end of file +} diff --git a/web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx b/web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx index 822327c..24d75c4 100644 --- a/web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx +++ b/web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx @@ -7,17 +7,17 @@ type Review = Actions | "Accept" | "Validate" | "Upload" | "Reset Uploading interface ReviewButton { name: Review, action: ApiActions, - submissionId: string, + mapfixId: string, color: ButtonOwnProps["color"] } interface ReviewId { - submissionId: string + mapfixId: string } -async function ReviewButtonClicked(action: ApiActions, submissionId: string) { +async function ReviewButtonClicked(action: ApiActions, mapfixId: string) { try { - const response = await fetch(`/api/submissions/${submissionId}/status/${action}`, { + const response = await fetch(`/api/mapfixes/${mapfixId}/status/${action}`, { method: "POST", headers: { "Content-type": "application/json", @@ -33,7 +33,7 @@ async function ReviewButtonClicked(action: ApiActions, submissionId: string) { window.location.reload(); } catch (error) { - console.error("Error updating submission status:", error); + console.error("Error updating mapfix status:", error); } } @@ -41,11 +41,11 @@ function ReviewButton(props: ReviewButton) { return + onClick={() => { ReviewButtonClicked(props.action, props.mapfixId) }}>{props.name} } export default function ReviewButtons(props: ReviewId) { - const submissionId = props.submissionId + const mapfixId = props.mapfixId // When is each button visible? // Multiple buttons can be visible at once. // Action | Role | When Current Status is One of: @@ -61,14 +61,14 @@ export default function ReviewButtons(props: ReviewId) { // ResetUploading | MapAdmin | Uploading return (
- - - - - - - - + + + + + + + +
) } diff --git a/web/src/app/mapfixes/[mapfixId]/page.tsx b/web/src/app/mapfixes/[mapfixId]/page.tsx index 9dae81f..cc38cb3 100644 --- a/web/src/app/mapfixes/[mapfixId]/page.tsx +++ b/web/src/app/mapfixes/[mapfixId]/page.tsx @@ -1,6 +1,6 @@ "use client" -import { SubmissionInfo, SubmissionStatusToString } from "@/app/ts/Submission"; +import { MapfixInfo, MapfixStatusToString } from "@/app/ts/Mapfix"; import type { CreatorAndReviewStatus } from "./_comments"; import { MapImage } from "./_map"; import { useParams } from "next/navigation"; @@ -15,7 +15,7 @@ import { useState, useEffect } from "react"; import "./(styles)/page.scss"; interface ReviewId { - submissionId: string + mapfixId: string } function Ratings() { @@ -39,20 +39,20 @@ function Ratings() { ) } -function RatingArea(submission: ReviewId) { +function RatingArea(mapfix: ReviewId) { return ( ) } function TitleAndComments(stats: CreatorAndReviewStatus) { - const Review = SubmissionStatusToString(stats.review) + const Review = MapfixStatusToString(stats.review) // TODO: hide status message when status is not "Accepted" return ( @@ -72,22 +72,22 @@ function TitleAndComments(stats: CreatorAndReviewStatus) { ) } -export default function SubmissionInfoPage() { - const dynamicId = useParams<{submissionId: string}>() +export default function MapfixInfoPage() { + const dynamicId = useParams<{mapfixId: string}>() - const [submission, setSubmission] = useState(null) + const [mapfix, setMapfix] = useState(null) useEffect(() => { // needs to be client sided since server doesn't have a session, nextjs got mad at me for exporting an async function: (https://nextjs.org/docs/messages/no-async-client-component) - async function getSubmission() { - const res = await fetch(`/api/submissions/${dynamicId.submissionId}`) + async function getMapfix() { + const res = await fetch(`/api/mapfixes/${dynamicId.mapfixId}`) if (res.ok) { - setSubmission(await res.json()) + setMapfix(await res.json()) } } - getSubmission() - }, [dynamicId.submissionId]) + getMapfix() + }, [dynamicId.mapfixId]) - if (!submission) { + if (!mapfix) { return {/* TODO: Add skeleton loading thingy ? Maybe ? (https://mui.com/material-ui/react-skeleton/) */} @@ -96,8 +96,8 @@ export default function SubmissionInfoPage() {
- - + +
diff --git a/web/src/app/mapfixes/_card.tsx b/web/src/app/mapfixes/_card.tsx index 29a6793..a1c594a 100644 --- a/web/src/app/mapfixes/_card.tsx +++ b/web/src/app/mapfixes/_card.tsx @@ -38,4 +38,4 @@ export default function SubmissionCard(props: SubmissionCardProps) {
); -} \ No newline at end of file +} diff --git a/web/src/app/mapfixes/page.tsx b/web/src/app/mapfixes/page.tsx index 2b803a7..cf6eccb 100644 --- a/web/src/app/mapfixes/page.tsx +++ b/web/src/app/mapfixes/page.tsx @@ -1,20 +1,20 @@ 'use client' import React, { useState, useEffect } from "react"; -import { SubmissionInfo } from "../ts/Submission"; -import SubmissionCard from "./_card"; +import { MapfixInfo } from "../ts/Mapfix"; +import MapfixCard from "./_card"; import Webpage from "@/app/_components/webpage"; import "./(styles)/page.scss"; -export default function SubmissionInfoPage() { - const [submissions, setSubmissions] = useState([]) +export default function MapfixInfoPage() { + const [mapfixes, setMapfixes] = useState([]) const [currentPage, setCurrentPage] = useState(0); const cardsPerPage = 24; // built to fit on a 1920x1080 monitor - const totalPages = Math.ceil(submissions.length / cardsPerPage); + const totalPages = Math.ceil(mapfixes.length / cardsPerPage); - const currentCards = submissions.slice( + const currentCards = mapfixes.slice( currentPage * cardsPerPage, (currentPage + 1) * cardsPerPage ); @@ -32,19 +32,19 @@ export default function SubmissionInfoPage() { }; useEffect(() => { - async function fetchSubmissions() { - const res = await fetch('/api/submissions?Page=1&Limit=100') + async function fetchMapfixes() { + const res = await fetch('/api/mapfixes?Page=1&Limit=100') if (res.ok) { - setSubmissions(await res.json()) + setMapfixes(await res.json()) } } setTimeout(() => { - fetchSubmissions() + fetchMapfixes() }, 50); }, []) - if (!submissions) { + if (!mapfixes) { return
@@ -78,7 +77,7 @@ export default function SubmissionInfoPage() {
- +
diff --git a/web/src/app/ts/Mapfix.ts b/web/src/app/ts/Mapfix.ts index 0a2c282..93de092 100644 --- a/web/src/app/ts/Mapfix.ts +++ b/web/src/app/ts/Mapfix.ts @@ -25,8 +25,7 @@ interface MapfixInfo { readonly ValidatedAssetVersion: number, readonly Completed: boolean, readonly TargetAssetID: number, - readonly StatusID: MapfixStatus - readonly StatusMessage: string, + readonly StatusID: MapfixStatus, } interface MapfixList { diff --git a/web/src/app/ts/Submission.ts b/web/src/app/ts/Submission.ts index 6d24051..4904726 100644 --- a/web/src/app/ts/Submission.ts +++ b/web/src/app/ts/Submission.ts @@ -25,8 +25,7 @@ interface SubmissionInfo { readonly ValidatedAssetVersion: number, readonly Completed: boolean, readonly UploadedAssetID: number, - readonly StatusID: SubmissionStatus - readonly StatusMessage: string, + readonly StatusID: SubmissionStatus, } interface SubmissionList { -- 2.49.1 From a42501d254da68c646ada52c243b4179429b18b1 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 9 Apr 2025 20:20:52 -0700 Subject: [PATCH 386/454] submissions-api: change StatusMessage to ErrorMessage --- validation/api/src/internal.rs | 8 ++++---- validation/api/src/types.rs | 8 ++++---- validation/src/check_mapfix.rs | 4 ++-- validation/src/check_submission.rs | 4 ++-- validation/src/validate_mapfix.rs | 2 +- validation/src/validate_submission.rs | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/validation/api/src/internal.rs b/validation/api/src/internal.rs index a281dcc..d689981 100644 --- a/validation/api/src/internal.rs +++ b/validation/api/src/internal.rs @@ -163,7 +163,7 @@ impl Context{ } // simple submission endpoints action!("submissions",action_submission_request_changes,config,ActionSubmissionRequestChangesRequest,"status/validator-request-changes",config.SubmissionID, - ("StatusMessage",config.StatusMessage.as_str()) + ("ErrorMessage",config.ErrorMessage.as_str()) ); action!("submissions",action_submission_submitted,config,ActionSubmissionSubmittedRequest,"status/validator-submitted",config.SubmissionID, ("ModelVersion",config.ModelVersion.to_string().as_str()) @@ -177,7 +177,7 @@ impl Context{ ("UploadedAssetID",config.UploadedAssetID.to_string().as_str()) ); action!("submissions",action_submission_accepted,config,ActionSubmissionAcceptedRequest,"status/validator-failed",config.SubmissionID, - ("StatusMessage",config.StatusMessage.as_str()) + ("ErrorMessage",config.ErrorMessage.as_str()) ); pub async fn create_mapfix<'a>(&self,config:CreateMapfixRequest<'a>)->Result{ let url_raw=format!("{}/mapfixes",self.0.base_url); @@ -192,7 +192,7 @@ impl Context{ } // simple mapfixes endpoints action!("mapfixes",action_mapfix_request_changes,config,ActionMapfixRequestChangesRequest,"status/validator-request-changes",config.MapfixID, - ("StatusMessage",config.StatusMessage.as_str()) + ("ErrorMessage",config.ErrorMessage.as_str()) ); action!("mapfixes",action_mapfix_submitted,config,ActionMapfixSubmittedRequest,"status/validator-submitted",config.MapfixID, ("ModelVersion",config.ModelVersion.to_string().as_str()) @@ -204,7 +204,7 @@ impl Context{ ); action!("mapfixes",action_mapfix_uploaded,config,ActionMapfixUploadedRequest,"status/validator-uploaded",config.MapfixID,); action!("mapfixes",action_mapfix_accepted,config,ActionMapfixAcceptedRequest,"status/validator-failed",config.MapfixID, - ("StatusMessage",config.StatusMessage.as_str()) + ("ErrorMessage",config.ErrorMessage.as_str()) ); // simple operation endpoint action!("operations",action_operation_failed,config,ActionOperationFailedRequest,"status/operation-failed",config.OperationID, diff --git a/validation/api/src/types.rs b/validation/api/src/types.rs index 9ff38bf..671b6d1 100644 --- a/validation/api/src/types.rs +++ b/validation/api/src/types.rs @@ -233,7 +233,7 @@ pub struct ActionSubmissionSubmittedRequest{ #[derive(Clone,Debug)] pub struct ActionSubmissionRequestChangesRequest{ pub SubmissionID:i64, - pub StatusMessage:String, + pub ErrorMessage:String, } #[allow(nonstandard_style)] @@ -247,7 +247,7 @@ pub struct ActionSubmissionUploadedRequest{ #[derive(Clone,Debug)] pub struct ActionSubmissionAcceptedRequest{ pub SubmissionID:i64, - pub StatusMessage:String, + pub ErrorMessage:String, } #[derive(Clone,Copy,Debug,serde::Deserialize)] @@ -272,7 +272,7 @@ pub struct ActionMapfixSubmittedRequest{ #[derive(Clone,Debug)] pub struct ActionMapfixRequestChangesRequest{ pub MapfixID:i64, - pub StatusMessage:String, + pub ErrorMessage:String, } #[allow(nonstandard_style)] @@ -285,7 +285,7 @@ pub struct ActionMapfixUploadedRequest{ #[derive(Clone,Debug)] pub struct ActionMapfixAcceptedRequest{ pub MapfixID:i64, - pub StatusMessage:String, + pub ErrorMessage:String, } #[derive(Clone,Copy,Debug,serde::Deserialize)] diff --git a/validation/src/check_mapfix.rs b/validation/src/check_mapfix.rs index b261f4e..a562ed0 100644 --- a/validation/src/check_mapfix.rs +++ b/validation/src/check_mapfix.rs @@ -35,7 +35,7 @@ impl crate::message_handler::MessageHandler{ self.api.action_mapfix_request_changes( submissions_api::types::ActionMapfixRequestChangesRequest{ MapfixID:mapfix_id, - StatusMessage:report.to_string(), + ErrorMessage:report.to_string(), } ).await.map_err(Error::ApiActionMapfixCheck)?; } @@ -46,7 +46,7 @@ impl crate::message_handler::MessageHandler{ self.api.action_mapfix_request_changes( submissions_api::types::ActionMapfixRequestChangesRequest{ MapfixID:mapfix_id, - StatusMessage:e.to_string(), + ErrorMessage:e.to_string(), } ).await.map_err(Error::ApiActionMapfixCheck)?; }, diff --git a/validation/src/check_submission.rs b/validation/src/check_submission.rs index 12f18e1..05d0f18 100644 --- a/validation/src/check_submission.rs +++ b/validation/src/check_submission.rs @@ -35,7 +35,7 @@ impl crate::message_handler::MessageHandler{ self.api.action_submission_request_changes( submissions_api::types::ActionSubmissionRequestChangesRequest{ SubmissionID:submission_id, - StatusMessage:report.to_string(), + ErrorMessage:report.to_string(), } ).await.map_err(Error::ApiActionSubmissionCheck)?; } @@ -46,7 +46,7 @@ impl crate::message_handler::MessageHandler{ self.api.action_submission_request_changes( submissions_api::types::ActionSubmissionRequestChangesRequest{ SubmissionID:submission_id, - StatusMessage:e.to_string(), + ErrorMessage:e.to_string(), } ).await.map_err(Error::ApiActionSubmissionCheck)?; }, diff --git a/validation/src/validate_mapfix.rs b/validation/src/validate_mapfix.rs index c1607f6..287f4ac 100644 --- a/validation/src/validate_mapfix.rs +++ b/validation/src/validate_mapfix.rs @@ -29,7 +29,7 @@ impl crate::message_handler::MessageHandler{ // update the mapfix model status to accepted self.api.action_mapfix_accepted(submissions_api::types::ActionMapfixAcceptedRequest{ MapfixID:mapfix_id, - StatusMessage:format!("{e}"), + ErrorMessage:format!("{e}"), }).await.map_err(Error::ApiActionMapfixValidate)?; }, } diff --git a/validation/src/validate_submission.rs b/validation/src/validate_submission.rs index 5859bd6..7072b4b 100644 --- a/validation/src/validate_submission.rs +++ b/validation/src/validate_submission.rs @@ -29,7 +29,7 @@ impl crate::message_handler::MessageHandler{ // update the submission model status to accepted self.api.action_submission_accepted(submissions_api::types::ActionSubmissionAcceptedRequest{ SubmissionID:submission_id, - StatusMessage:format!("{e}"), + ErrorMessage:format!("{e}"), }).await.map_err(Error::ApiActionSubmissionValidate)?; }, } -- 2.49.1 From c76ff3b68713f6bdf20d858ff2fda23898dde166 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 9 Apr 2025 20:25:38 -0700 Subject: [PATCH 387/454] validation: use to_string instead of format --- validation/src/create_mapfix.rs | 2 +- validation/src/create_submission.rs | 2 +- validation/src/validate_mapfix.rs | 2 +- validation/src/validate_submission.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/validation/src/create_mapfix.rs b/validation/src/create_mapfix.rs index 982fd91..e735af6 100644 --- a/validation/src/create_mapfix.rs +++ b/validation/src/create_mapfix.rs @@ -43,7 +43,7 @@ impl crate::message_handler::MessageHandler{ if let Err(e)=create_result{ self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{ OperationID:operation_id, - StatusMessage:format!("{e}"), + StatusMessage:e.to_string(), }).await?; } diff --git a/validation/src/create_submission.rs b/validation/src/create_submission.rs index 4949366..370e80d 100644 --- a/validation/src/create_submission.rs +++ b/validation/src/create_submission.rs @@ -40,7 +40,7 @@ impl crate::message_handler::MessageHandler{ if let Err(e)=create_result{ self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{ OperationID:operation_id, - StatusMessage:format!("{e}"), + StatusMessage:e.to_string(), }).await?; } diff --git a/validation/src/validate_mapfix.rs b/validation/src/validate_mapfix.rs index 287f4ac..56f29bf 100644 --- a/validation/src/validate_mapfix.rs +++ b/validation/src/validate_mapfix.rs @@ -29,7 +29,7 @@ impl crate::message_handler::MessageHandler{ // update the mapfix model status to accepted self.api.action_mapfix_accepted(submissions_api::types::ActionMapfixAcceptedRequest{ MapfixID:mapfix_id, - ErrorMessage:format!("{e}"), + ErrorMessage:e.to_string(), }).await.map_err(Error::ApiActionMapfixValidate)?; }, } diff --git a/validation/src/validate_submission.rs b/validation/src/validate_submission.rs index 7072b4b..c60a88b 100644 --- a/validation/src/validate_submission.rs +++ b/validation/src/validate_submission.rs @@ -29,7 +29,7 @@ impl crate::message_handler::MessageHandler{ // update the submission model status to accepted self.api.action_submission_accepted(submissions_api::types::ActionSubmissionAcceptedRequest{ SubmissionID:submission_id, - ErrorMessage:format!("{e}"), + ErrorMessage:e.to_string(), }).await.map_err(Error::ApiActionSubmissionValidate)?; }, } -- 2.49.1 From d1ca9bdab9e7f813bf510cb43bd9406f6c5075bf Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 12:29:39 -0700 Subject: [PATCH 388/454] openapi: add Description to mapfix create --- openapi-internal.yaml | 4 ++++ openapi.yaml | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/openapi-internal.yaml b/openapi-internal.yaml index bb057c9..664295a 100644 --- a/openapi-internal.yaml +++ b/openapi-internal.yaml @@ -629,6 +629,7 @@ components: - AssetID - AssetVersion - TargetAssetID + - Description type: object properties: OperationID: @@ -661,6 +662,9 @@ components: type: integer format: int64 minimum: 0 + Description: + type: string + maxLength: 256 SubmissionCreate: required: - OperationID diff --git a/openapi.yaml b/openapi.yaml index 16869ba..abb3424 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1440,7 +1440,7 @@ components: - Completed - TargetAssetID - StatusID - - StatusMessage + - Description type: object properties: ID: @@ -1487,7 +1487,7 @@ components: type: integer format: int32 minimum: 0 - StatusMessage: + Description: type: string maxLength: 256 Mapfixes: @@ -1508,6 +1508,7 @@ components: required: - AssetID - TargetAssetID + - Description type: object properties: AssetID: @@ -1518,6 +1519,9 @@ components: type: integer format: int64 minimum: 0 + Description: + type: string + maxLength: 256 Operation: required: - OperationID @@ -1566,7 +1570,6 @@ components: - Completed # - UploadedAssetID - StatusID - - StatusMessage type: object properties: ID: @@ -1621,9 +1624,6 @@ components: type: integer format: int32 minimum: 0 - StatusMessage: - type: string - maxLength: 256 Submissions: required: - Total -- 2.49.1 From 1ff6bdbd4c43949b7254493b68f5258f97070730 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 12:29:16 -0700 Subject: [PATCH 389/454] openapi: generate --- pkg/api/oas_json_gen.go | 54 +++++++++++++++--------------- pkg/api/oas_schemas_gen.go | 40 +++++++++++----------- pkg/api/oas_validators_gen.go | 42 +++++++++++------------ pkg/internal/oas_json_gen.go | 24 +++++++++++-- pkg/internal/oas_schemas_gen.go | 11 ++++++ pkg/internal/oas_validators_gen.go | 19 +++++++++++ 6 files changed, 119 insertions(+), 71 deletions(-) diff --git a/pkg/api/oas_json_gen.go b/pkg/api/oas_json_gen.go index 6d63001..11defac 100644 --- a/pkg/api/oas_json_gen.go +++ b/pkg/api/oas_json_gen.go @@ -602,8 +602,8 @@ func (s *Mapfix) encodeFields(e *jx.Encoder) { e.Int32(s.StatusID) } { - e.FieldStart("StatusMessage") - e.Str(s.StatusMessage) + e.FieldStart("Description") + e.Str(s.Description) } } @@ -620,7 +620,7 @@ var jsonFieldsNameOfMapfix = [13]string{ 9: "Completed", 10: "TargetAssetID", 11: "StatusID", - 12: "StatusMessage", + 12: "Description", } // Decode decodes Mapfix from json. @@ -776,17 +776,17 @@ func (s *Mapfix) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"StatusID\"") } - case "StatusMessage": + case "Description": requiredBitSet[1] |= 1 << 4 if err := func() error { v, err := d.Str() - s.StatusMessage = string(v) + s.Description = string(v) if err != nil { return err } return nil }(); err != nil { - return errors.Wrap(err, "decode field \"StatusMessage\"") + return errors.Wrap(err, "decode field \"Description\"") } default: return d.Skip() @@ -862,11 +862,16 @@ func (s *MapfixTriggerCreate) encodeFields(e *jx.Encoder) { e.FieldStart("TargetAssetID") e.Int64(s.TargetAssetID) } + { + e.FieldStart("Description") + e.Str(s.Description) + } } -var jsonFieldsNameOfMapfixTriggerCreate = [2]string{ +var jsonFieldsNameOfMapfixTriggerCreate = [3]string{ 0: "AssetID", 1: "TargetAssetID", + 2: "Description", } // Decode decodes MapfixTriggerCreate from json. @@ -902,6 +907,18 @@ func (s *MapfixTriggerCreate) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"TargetAssetID\"") } + case "Description": + requiredBitSet[0] |= 1 << 2 + if err := func() error { + v, err := d.Str() + s.Description = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Description\"") + } default: return d.Skip() } @@ -912,7 +929,7 @@ func (s *MapfixTriggerCreate) Decode(d *jx.Decoder) error { // Validate required fields. var failures []validate.FieldError for i, mask := range [1]uint8{ - 0b00000011, + 0b00000111, } { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { // Mask only required fields and check equality to mask using XOR. @@ -2851,13 +2868,9 @@ func (s *Submission) encodeFields(e *jx.Encoder) { e.FieldStart("StatusID") e.Int32(s.StatusID) } - { - e.FieldStart("StatusMessage") - e.Str(s.StatusMessage) - } } -var jsonFieldsNameOfSubmission = [15]string{ +var jsonFieldsNameOfSubmission = [14]string{ 0: "ID", 1: "DisplayName", 2: "Creator", @@ -2872,7 +2885,6 @@ var jsonFieldsNameOfSubmission = [15]string{ 11: "Completed", 12: "UploadedAssetID", 13: "StatusID", - 14: "StatusMessage", } // Decode decodes Submission from json. @@ -3046,18 +3058,6 @@ func (s *Submission) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"StatusID\"") } - case "StatusMessage": - requiredBitSet[1] |= 1 << 6 - if err := func() error { - v, err := d.Str() - s.StatusMessage = string(v) - if err != nil { - return err - } - return nil - }(); err != nil { - return errors.Wrap(err, "decode field \"StatusMessage\"") - } default: return d.Skip() } @@ -3069,7 +3069,7 @@ func (s *Submission) Decode(d *jx.Decoder) error { var failures []validate.FieldError for i, mask := range [2]uint8{ 0b11111111, - 0b01101001, + 0b00101001, } { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { // Mask only required fields and check equality to mask using XOR. diff --git a/pkg/api/oas_schemas_gen.go b/pkg/api/oas_schemas_gen.go index b54f84e..54d2557 100644 --- a/pkg/api/oas_schemas_gen.go +++ b/pkg/api/oas_schemas_gen.go @@ -349,7 +349,7 @@ type Mapfix struct { Completed bool `json:"Completed"` TargetAssetID int64 `json:"TargetAssetID"` StatusID int32 `json:"StatusID"` - StatusMessage string `json:"StatusMessage"` + Description string `json:"Description"` } // GetID returns the value of ID. @@ -412,9 +412,9 @@ func (s *Mapfix) GetStatusID() int32 { return s.StatusID } -// GetStatusMessage returns the value of StatusMessage. -func (s *Mapfix) GetStatusMessage() string { - return s.StatusMessage +// GetDescription returns the value of Description. +func (s *Mapfix) GetDescription() string { + return s.Description } // SetID sets the value of ID. @@ -477,15 +477,16 @@ func (s *Mapfix) SetStatusID(val int32) { s.StatusID = val } -// SetStatusMessage sets the value of StatusMessage. -func (s *Mapfix) SetStatusMessage(val string) { - s.StatusMessage = val +// SetDescription sets the value of Description. +func (s *Mapfix) SetDescription(val string) { + s.Description = val } // Ref: #/components/schemas/MapfixTriggerCreate type MapfixTriggerCreate struct { - AssetID int64 `json:"AssetID"` - TargetAssetID int64 `json:"TargetAssetID"` + AssetID int64 `json:"AssetID"` + TargetAssetID int64 `json:"TargetAssetID"` + Description string `json:"Description"` } // GetAssetID returns the value of AssetID. @@ -498,6 +499,11 @@ func (s *MapfixTriggerCreate) GetTargetAssetID() int64 { return s.TargetAssetID } +// GetDescription returns the value of Description. +func (s *MapfixTriggerCreate) GetDescription() string { + return s.Description +} + // SetAssetID sets the value of AssetID. func (s *MapfixTriggerCreate) SetAssetID(val int64) { s.AssetID = val @@ -508,6 +514,11 @@ func (s *MapfixTriggerCreate) SetTargetAssetID(val int64) { s.TargetAssetID = val } +// SetDescription sets the value of Description. +func (s *MapfixTriggerCreate) SetDescription(val string) { + s.Description = val +} + // Ref: #/components/schemas/Mapfixes type Mapfixes struct { Total int64 `json:"Total"` @@ -1163,7 +1174,6 @@ type Submission struct { Completed bool `json:"Completed"` UploadedAssetID OptInt64 `json:"UploadedAssetID"` StatusID int32 `json:"StatusID"` - StatusMessage string `json:"StatusMessage"` } // GetID returns the value of ID. @@ -1236,11 +1246,6 @@ func (s *Submission) GetStatusID() int32 { return s.StatusID } -// GetStatusMessage returns the value of StatusMessage. -func (s *Submission) GetStatusMessage() string { - return s.StatusMessage -} - // SetID sets the value of ID. func (s *Submission) SetID(val int64) { s.ID = val @@ -1311,11 +1316,6 @@ func (s *Submission) SetStatusID(val int32) { s.StatusID = val } -// SetStatusMessage sets the value of StatusMessage. -func (s *Submission) SetStatusMessage(val string) { - s.StatusMessage = val -} - // Ref: #/components/schemas/SubmissionTriggerCreate type SubmissionTriggerCreate struct { AssetID int64 `json:"AssetID"` diff --git a/pkg/api/oas_validators_gen.go b/pkg/api/oas_validators_gen.go index 1443d54..456bf4d 100644 --- a/pkg/api/oas_validators_gen.go +++ b/pkg/api/oas_validators_gen.go @@ -408,13 +408,13 @@ func (s *Mapfix) Validate() error { Email: false, Hostname: false, Regex: nil, - }).Validate(string(s.StatusMessage)); err != nil { + }).Validate(string(s.Description)); err != nil { return errors.Wrap(err, "string") } return nil }(); err != nil { failures = append(failures, validate.FieldError{ - Name: "StatusMessage", + Name: "Description", Error: err, }) } @@ -470,6 +470,25 @@ func (s *MapfixTriggerCreate) Validate() error { Error: err, }) } + if err := func() error { + if err := (validate.String{ + MinLength: 0, + MinLengthSet: false, + MaxLength: 256, + MaxLengthSet: true, + Email: false, + Hostname: false, + Regex: nil, + }).Validate(string(s.Description)); err != nil { + return errors.Wrap(err, "string") + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "Description", + Error: err, + }) + } if len(failures) > 0 { return &validate.Error{Fields: failures} } @@ -1751,25 +1770,6 @@ func (s *Submission) Validate() error { Error: err, }) } - if err := func() error { - if err := (validate.String{ - MinLength: 0, - MinLengthSet: false, - MaxLength: 256, - MaxLengthSet: true, - Email: false, - Hostname: false, - Regex: nil, - }).Validate(string(s.StatusMessage)); err != nil { - return errors.Wrap(err, "string") - } - return nil - }(); err != nil { - failures = append(failures, validate.FieldError{ - Name: "StatusMessage", - Error: err, - }) - } if len(failures) > 0 { return &validate.Error{Fields: failures} } diff --git a/pkg/internal/oas_json_gen.go b/pkg/internal/oas_json_gen.go index 4de742d..e89d035 100644 --- a/pkg/internal/oas_json_gen.go +++ b/pkg/internal/oas_json_gen.go @@ -166,9 +166,13 @@ func (s *MapfixCreate) encodeFields(e *jx.Encoder) { e.FieldStart("TargetAssetID") e.Int64(s.TargetAssetID) } + { + e.FieldStart("Description") + e.Str(s.Description) + } } -var jsonFieldsNameOfMapfixCreate = [8]string{ +var jsonFieldsNameOfMapfixCreate = [9]string{ 0: "OperationID", 1: "AssetOwner", 2: "DisplayName", @@ -177,6 +181,7 @@ var jsonFieldsNameOfMapfixCreate = [8]string{ 5: "AssetID", 6: "AssetVersion", 7: "TargetAssetID", + 8: "Description", } // Decode decodes MapfixCreate from json. @@ -184,7 +189,7 @@ func (s *MapfixCreate) Decode(d *jx.Decoder) error { if s == nil { return errors.New("invalid: unable to decode MapfixCreate to nil") } - var requiredBitSet [1]uint8 + var requiredBitSet [2]uint8 if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { switch string(k) { @@ -284,6 +289,18 @@ func (s *MapfixCreate) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"TargetAssetID\"") } + case "Description": + requiredBitSet[1] |= 1 << 0 + if err := func() error { + v, err := d.Str() + s.Description = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Description\"") + } default: return d.Skip() } @@ -293,8 +310,9 @@ func (s *MapfixCreate) Decode(d *jx.Decoder) error { } // Validate required fields. var failures []validate.FieldError - for i, mask := range [1]uint8{ + for i, mask := range [2]uint8{ 0b11111111, + 0b00000001, } { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { // Mask only required fields and check equality to mask using XOR. diff --git a/pkg/internal/oas_schemas_gen.go b/pkg/internal/oas_schemas_gen.go index f8c3cd4..cb3ca30 100644 --- a/pkg/internal/oas_schemas_gen.go +++ b/pkg/internal/oas_schemas_gen.go @@ -106,6 +106,7 @@ type MapfixCreate struct { AssetID int64 `json:"AssetID"` AssetVersion int64 `json:"AssetVersion"` TargetAssetID int64 `json:"TargetAssetID"` + Description string `json:"Description"` } // GetOperationID returns the value of OperationID. @@ -148,6 +149,11 @@ func (s *MapfixCreate) GetTargetAssetID() int64 { return s.TargetAssetID } +// GetDescription returns the value of Description. +func (s *MapfixCreate) GetDescription() string { + return s.Description +} + // SetOperationID sets the value of OperationID. func (s *MapfixCreate) SetOperationID(val int32) { s.OperationID = val @@ -188,6 +194,11 @@ func (s *MapfixCreate) SetTargetAssetID(val int64) { s.TargetAssetID = val } +// SetDescription sets the value of Description. +func (s *MapfixCreate) SetDescription(val string) { + s.Description = val +} + // Ref: #/components/schemas/MapfixID type MapfixID struct { MapfixID int64 `json:"MapfixID"` diff --git a/pkg/internal/oas_validators_gen.go b/pkg/internal/oas_validators_gen.go index 083e5c3..159c9f4 100644 --- a/pkg/internal/oas_validators_gen.go +++ b/pkg/internal/oas_validators_gen.go @@ -227,6 +227,25 @@ func (s *MapfixCreate) Validate() error { Error: err, }) } + if err := func() error { + if err := (validate.String{ + MinLength: 0, + MinLengthSet: false, + MaxLength: 256, + MaxLengthSet: true, + Email: false, + Hostname: false, + Regex: nil, + }).Validate(string(s.Description)); err != nil { + return errors.Wrap(err, "string") + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "Description", + Error: err, + }) + } if len(failures) > 0 { return &validate.Error{Fields: failures} } -- 2.49.1 From 2519c9faa149c92d06a45bfb4bfcd282351a2c48 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 12:40:47 -0700 Subject: [PATCH 390/454] submissions: description plumbing --- pkg/model/mapfix.go | 1 + pkg/model/nats.go | 1 + pkg/service/mapfixes.go | 3 +++ pkg/service_internal/mapfixes.go | 1 + 4 files changed, 6 insertions(+) diff --git a/pkg/model/mapfix.go b/pkg/model/mapfix.go index a296359..950c6ec 100644 --- a/pkg/model/mapfix.go +++ b/pkg/model/mapfix.go @@ -39,4 +39,5 @@ type Mapfix struct { Completed bool // Has this version of the map been completed at least once on maptest TargetAssetID uint64 // where to upload map fix. if the TargetAssetID is 0, it's a new map. StatusID MapfixStatus + Description string // mapfix description } diff --git a/pkg/model/nats.go b/pkg/model/nats.go index 0808a89..91c0453 100644 --- a/pkg/model/nats.go +++ b/pkg/model/nats.go @@ -15,6 +15,7 @@ type CreateMapfixRequest struct { OperationID int32 ModelID uint64 TargetAssetID uint64 + Description string } diff --git a/pkg/service/mapfixes.go b/pkg/service/mapfixes.go index 0d37705..8c42171 100644 --- a/pkg/service/mapfixes.go +++ b/pkg/service/mapfixes.go @@ -114,6 +114,7 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *api.MapfixTrigger OperationID: operation.ID, ModelID: ModelID, TargetAssetID: TargetAssetID, + Description: request.Description, } j, err := json.Marshal(create_request) @@ -154,6 +155,7 @@ func (svc *Service) GetMapfix(ctx context.Context, params api.GetMapfixParams) ( Completed: mapfix.Completed, TargetAssetID: int64(mapfix.TargetAssetID), StatusID: int32(mapfix.StatusID), + Description: mapfix.Description, }, nil } @@ -213,6 +215,7 @@ func (svc *Service) ListMapfixes(ctx context.Context, params api.ListMapfixesPar Completed: item.Completed, TargetAssetID: int64(item.TargetAssetID), StatusID: int32(item.StatusID), + Description: item.Description, }) } diff --git a/pkg/service_internal/mapfixes.go b/pkg/service_internal/mapfixes.go index c52c533..0785aeb 100644 --- a/pkg/service_internal/mapfixes.go +++ b/pkg/service_internal/mapfixes.go @@ -347,6 +347,7 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *internal.MapfixCr Completed: false, TargetAssetID: TargetAssetID, StatusID: model.MapfixStatusUnderConstruction, + Description: request.Description, }) if err != nil { return nil, err -- 2.49.1 From 169007f16e247d7cbd743d3a5ec6e50289c58577 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 12:40:56 -0700 Subject: [PATCH 391/454] validator: description plumbing --- validation/api/src/types.rs | 1 + validation/src/create_mapfix.rs | 1 + validation/src/nats_types.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/validation/api/src/types.rs b/validation/api/src/types.rs index 671b6d1..2a329fc 100644 --- a/validation/api/src/types.rs +++ b/validation/api/src/types.rs @@ -73,6 +73,7 @@ pub struct CreateMapfixRequest<'a>{ pub AssetID:u64, pub AssetVersion:u64, pub TargetAssetID:u64, + pub Description:&'a str, } #[allow(nonstandard_style)] #[derive(Clone,Debug,serde::Deserialize)] diff --git a/validation/src/create_mapfix.rs b/validation/src/create_mapfix.rs index e735af6..010df43 100644 --- a/validation/src/create_mapfix.rs +++ b/validation/src/create_mapfix.rs @@ -31,6 +31,7 @@ impl crate::message_handler::MessageHandler{ AssetID:create_info.ModelID, AssetVersion:create_request.AssetVersion, TargetAssetID:create_info.TargetAssetID, + Description:create_info.Description.as_str(), }).await.map_err(Error::ApiActionMapfixCreate)?; Ok(()) diff --git a/validation/src/nats_types.rs b/validation/src/nats_types.rs index a1126ae..bca6646 100644 --- a/validation/src/nats_types.rs +++ b/validation/src/nats_types.rs @@ -18,6 +18,7 @@ pub struct CreateMapfixRequest{ pub OperationID:i32, pub ModelID:u64, pub TargetAssetID:u64, + pub Description:String, } #[allow(nonstandard_style)] -- 2.49.1 From b7e5d82c13da3e8602cf8ffdc5741b5801911673 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 12:45:27 -0700 Subject: [PATCH 392/454] web: add description form field --- web/src/app/maps/[mapId]/fix/page.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/src/app/maps/[mapId]/fix/page.tsx b/web/src/app/maps/[mapId]/fix/page.tsx index e6c58f0..adac6c2 100644 --- a/web/src/app/maps/[mapId]/fix/page.tsx +++ b/web/src/app/maps/[mapId]/fix/page.tsx @@ -11,6 +11,7 @@ import "./(styles)/page.scss" interface MapfixPayload { AssetID: number; TargetAssetID: number; + Description: string; } interface IdResponse { OperationID: number; @@ -28,6 +29,7 @@ export default function MapfixInfoPage() { const payload: MapfixPayload = { AssetID: Number((formData.get("asset-id") as string) ?? "0"), TargetAssetID: Number(dynamicId.mapId), + Description: (formData.get("description") as string) ?? "unknown", // TEMPORARY! TODO: Change }; console.log(payload) @@ -70,6 +72,7 @@ export default function MapfixInfoPage() { {/* TODO: Add form data for mapfixes, such as changes they did, and any times that need to be deleted & what styles */} + + }}>Create Mapfix diff --git a/web/src/app/submit/page.tsx b/web/src/app/submit/page.tsx index b0a71e9..7e89ba8 100644 --- a/web/src/app/submit/page.tsx +++ b/web/src/app/submit/page.tsx @@ -82,7 +82,7 @@ export default function SubmissionInfoPage() { width: "400px", height: "50px", marginInline: "auto" - }}>Submit + }}>Create Submission -- 2.49.1 From 123b0c9a818fa4aa1255ff84f0649047d74d8d0f Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sun, 13 Apr 2025 16:37:22 -0700 Subject: [PATCH 435/454] web: add Username field to AuditEvent --- web/src/app/ts/AuditEvent.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/app/ts/AuditEvent.ts b/web/src/app/ts/AuditEvent.ts index 089f468..18f91a0 100644 --- a/web/src/app/ts/AuditEvent.ts +++ b/web/src/app/ts/AuditEvent.ts @@ -56,6 +56,7 @@ export interface AuditEvent { Id: number; CreatedAt: string; // ISO string, can convert to Date if needed User: number; + Username: string; ResourceType: string; // Assuming this is a string enum or similar ResourceId: number; EventType: AuditEventType; -- 2.49.1 From 6cc6da48790fba73d0cbfac1f51b0f73fc5ca8c8 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 15 Apr 2025 12:46:35 -0700 Subject: [PATCH 436/454] web: display username in audit events --- web/src/app/mapfixes/[mapfixId]/page.tsx | 10 +++++----- web/src/app/submissions/[submissionId]/page.tsx | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/web/src/app/mapfixes/[mapfixId]/page.tsx b/web/src/app/mapfixes/[mapfixId]/page.tsx index 97bd0a1..c7e5047 100644 --- a/web/src/app/mapfixes/[mapfixId]/page.tsx +++ b/web/src/app/mapfixes/[mapfixId]/page.tsx @@ -86,16 +86,16 @@ export default function MapfixInfoPage() { }, [mapfixId]) const comments:Comment[] = auditEvents.map((auditEvent) => { - let user = auditEvent.User.toString(); + let username = auditEvent.Username; if (auditEvent.User == 9223372036854776000) { - user = "[Validator]"; + username = "[Validator]"; } - if (mapfix && auditEvent.User == mapfix.Submitter) { - user = "[Submitter]"; + if (username === "" && mapfix && auditEvent.User == mapfix.Submitter) { + username = "[Submitter]"; } return { date: auditEvent.CreatedAt, - name: user, + name: username, comment: auditEventMessage(auditEvent), } }) diff --git a/web/src/app/submissions/[submissionId]/page.tsx b/web/src/app/submissions/[submissionId]/page.tsx index 2a0b83e..e2097d5 100644 --- a/web/src/app/submissions/[submissionId]/page.tsx +++ b/web/src/app/submissions/[submissionId]/page.tsx @@ -77,16 +77,16 @@ export default function SubmissionInfoPage() { }, [submissionId]) const comments:Comment[] = auditEvents.map((auditEvent) => { - let user = auditEvent.User.toString(); + let username = auditEvent.Username; if (auditEvent.User == 9223372036854776000) { - user = "[Validator]"; + username = "[Validator]"; } - if (submission && auditEvent.User == submission.Submitter) { - user = "[Submitter]"; + if (username === "" && submission && auditEvent.User == submission.Submitter) { + username = "[Submitter]"; } return { date: auditEvent.CreatedAt, - name: user, + name: username, comment: auditEventMessage(auditEvent), } }) -- 2.49.1 From 3a124b8190061379c1471cca9f03bbe62fd3be76 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 15 Apr 2025 14:23:25 -0700 Subject: [PATCH 437/454] web: add hidden admin submit page --- web/src/app/admin-submit/(styles)/page.scss | 54 +++++++++++++ web/src/app/admin-submit/_game.tsx | 65 +++++++++++++++ web/src/app/admin-submit/page.tsx | 90 +++++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 web/src/app/admin-submit/(styles)/page.scss create mode 100644 web/src/app/admin-submit/_game.tsx create mode 100644 web/src/app/admin-submit/page.tsx diff --git a/web/src/app/admin-submit/(styles)/page.scss b/web/src/app/admin-submit/(styles)/page.scss new file mode 100644 index 0000000..23bf59a --- /dev/null +++ b/web/src/app/admin-submit/(styles)/page.scss @@ -0,0 +1,54 @@ +@use "../../globals.scss"; + +::placeholder { + color: var(--placeholder-text) +} + +.form-spacer { + margin-bottom: 20px; + + &:last-of-type { + margin-top: 15px; + } +} + +#target-asset-radio { + color: var(--text-color); + font-size: globals.$form-label-fontsize; +} + +.form-field { + width: 850px; + + & label, & input { + color: var(--text-color); + } + & fieldset { + border-color: rgb(100,100,100); + } + & span { + color: white; + } +} + +main { + display: grid; + justify-content: center; + align-items: center; + margin-inline: auto; + width: 700px; +} + +header h1 { + text-align: center; + color: var(--text-color); +} + +form { + display: grid; + gap: 25px; + + fieldset { + border: blue + } +} diff --git a/web/src/app/admin-submit/_game.tsx b/web/src/app/admin-submit/_game.tsx new file mode 100644 index 0000000..e754601 --- /dev/null +++ b/web/src/app/admin-submit/_game.tsx @@ -0,0 +1,65 @@ +import { FormControl, Select, InputLabel, MenuItem } from "@mui/material"; +import { styled } from '@mui/material/styles'; +import InputBase from '@mui/material/InputBase'; +import React from "react"; +import { SelectChangeEvent } from "@mui/material"; + +// TODO: Properly style everything instead of pasting 🤚 + +type GameSelectionProps = { + game: number; + setGame: React.Dispatch>; +}; + +const BootstrapInput = styled(InputBase)(({ theme }) => ({ + 'label + &': { + marginTop: theme.spacing(3), + }, + '& .MuiInputBase-input': { + backgroundColor: '#0000', + color: '#FFF', + border: '1px solid rgba(175, 175, 175, 0.66)', + fontSize: 16, + padding: '10px 26px 10px 12px', + transition: theme.transitions.create(['border-color', 'box-shadow']), + fontFamily: [ + '-apple-system', + 'BlinkMacSystemFont', + '"Segoe UI"', + 'Roboto', + '"Helvetica Neue"', + 'Arial', + 'sans-serif', + '"Apple Color Emoji"', + '"Segoe UI Emoji"', + '"Segoe UI Symbol"', + ].join(','), + '&:focus': { + borderRadius: 4, + borderColor: '#80bdff', + boxShadow: '0 0 0 0.2rem rgba(0,123,255,.25)', + }, + }, + })); + +export default function GameSelection({ game, setGame }: GameSelectionProps) { + const handleChange = (event: SelectChangeEvent) => { + setGame(Number(event.target.value)); // TODO: Change later!! there's 100% a proper way of doing this + }; + + return ( + + Game + + + ); +} \ No newline at end of file diff --git a/web/src/app/admin-submit/page.tsx b/web/src/app/admin-submit/page.tsx new file mode 100644 index 0000000..a7a5170 --- /dev/null +++ b/web/src/app/admin-submit/page.tsx @@ -0,0 +1,90 @@ +"use client" + +import { Button, TextField } from "@mui/material" + +import GameSelection from "./_game"; +import SendIcon from '@mui/icons-material/Send'; +import Webpage from "@/app/_components/webpage" +import React, { useState } from "react"; + +import "./(styles)/page.scss" + +interface SubmissionPayload { + AssetID: number; + DisplayName: string; + Creator: string; + GameID: number; +} +interface IdResponse { + OperationID: number; +} + +export default function SubmissionInfoPage() { + const [game, setGame] = useState(1); + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + const form = event.currentTarget; + const formData = new FormData(form); + + const payload: SubmissionPayload = { + DisplayName: (formData.get("display-name") as string) ?? "unknown", // TEMPORARY! TODO: Change + Creator: (formData.get("creator") as string) ?? "unknown", // TEMPORARY! TODO: Change + GameID: game, + AssetID: Number((formData.get("asset-id") as string) ?? "0"), + }; + + console.log(payload) + console.log(JSON.stringify(payload)) + + try { + // Send the POST request + const response = await fetch("/api/submissions-admin", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + + // Check if the HTTP request was successful + if (!response.ok) { + const errorDetails = await response.text(); + + // Throw an error with detailed information + throw new Error(`HTTP error! status: ${response.status}, details: ${errorDetails}`); + } + + // Allow any HTTP status + const id_response:IdResponse = await response.json(); + + // navigate to newly created submission + window.location.assign(`/operations/${id_response.OperationID}`) + + } catch (error) { + console.error("Error submitting data:", error); + } + }; + + return ( + +
+
+

Submit New Map on Behalf of Another User (Admin)

+ +
+
+ + + + + + + +
+
+ ) +} -- 2.49.1 From 2f36877cb673a0dd2d616ea2da21f87811f12efe Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 15 Apr 2025 14:29:03 -0700 Subject: [PATCH 438/454] openapi: admin create endpoint --- openapi-internal.yaml | 10 ++++++++++ openapi.yaml | 25 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/openapi-internal.yaml b/openapi-internal.yaml index 1cde29e..29a4d12 100644 --- a/openapi-internal.yaml +++ b/openapi-internal.yaml @@ -712,6 +712,8 @@ components: - GameID - AssetID - AssetVersion + - Status + - Roles type: object properties: OperationID: @@ -740,6 +742,14 @@ components: type: integer format: int64 minimum: 0 + Status: + type: integer + format: uint32 + minimum: 0 + maximum: 9 + Roles: + type: integer + format: uint32 Script: required: - ID diff --git a/openapi.yaml b/openapi.yaml index 2e94f30..2879ae8 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -654,6 +654,31 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" + /submissions-admin: + post: + summary: Trigger the validator to create a new submission + operationId: createSubmissionAdmin + tags: + - Submissions + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SubmissionTriggerCreate' + responses: + "201": + description: Successful response + content: + application/json: + schema: + $ref: "#/components/schemas/OperationID" + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" /submissions/{SubmissionID}: get: summary: Retrieve map with ID -- 2.49.1 From d34a5c70913cc553d6769341218a715c0f180f3c Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 15 Apr 2025 14:38:03 -0700 Subject: [PATCH 439/454] openapi: generate --- pkg/api/oas_client_gen.go | 114 ++++++++++++++++ pkg/api/oas_handlers_gen.go | 195 +++++++++++++++++++++++++++ pkg/api/oas_operations_gen.go | 1 + pkg/api/oas_request_decoders_gen.go | 71 ++++++++++ pkg/api/oas_request_encoders_gen.go | 14 ++ pkg/api/oas_response_decoders_gen.go | 101 ++++++++++++++ pkg/api/oas_response_encoders_gen.go | 14 ++ pkg/api/oas_router_gen.go | 44 ++++++ pkg/api/oas_server_gen.go | 6 + pkg/api/oas_unimplemented_gen.go | 9 ++ pkg/internal/oas_json_gen.go | 43 +++++- pkg/internal/oas_schemas_gen.go | 22 +++ pkg/internal/oas_validators_gen.go | 20 +++ 13 files changed, 650 insertions(+), 4 deletions(-) diff --git a/pkg/api/oas_client_gen.go b/pkg/api/oas_client_gen.go index 89085f9..7951723 100644 --- a/pkg/api/oas_client_gen.go +++ b/pkg/api/oas_client_gen.go @@ -181,6 +181,12 @@ type Invoker interface { // // POST /submissions CreateSubmission(ctx context.Context, request *SubmissionTriggerCreate) (*OperationID, error) + // CreateSubmissionAdmin invokes createSubmissionAdmin operation. + // + // Trigger the validator to create a new submission. + // + // POST /submissions-admin + CreateSubmissionAdmin(ctx context.Context, request *SubmissionTriggerCreate) (*OperationID, error) // CreateSubmissionAuditComment invokes createSubmissionAuditComment operation. // // Post a comment to the audit log. @@ -3429,6 +3435,114 @@ func (c *Client) sendCreateSubmission(ctx context.Context, request *SubmissionTr return result, nil } +// CreateSubmissionAdmin invokes createSubmissionAdmin operation. +// +// Trigger the validator to create a new submission. +// +// POST /submissions-admin +func (c *Client) CreateSubmissionAdmin(ctx context.Context, request *SubmissionTriggerCreate) (*OperationID, error) { + res, err := c.sendCreateSubmissionAdmin(ctx, request) + return res, err +} + +func (c *Client) sendCreateSubmissionAdmin(ctx context.Context, request *SubmissionTriggerCreate) (res *OperationID, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("createSubmissionAdmin"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/submissions-admin"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, CreateSubmissionAdminOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/submissions-admin" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + if err := encodeCreateSubmissionAdminRequest(request, r); err != nil { + return res, errors.Wrap(err, "encode request") + } + + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:CookieAuth" + switch err := c.securityCookieAuth(ctx, CreateSubmissionAdminOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"CookieAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeCreateSubmissionAdminResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // CreateSubmissionAuditComment invokes createSubmissionAuditComment operation. // // Post a comment to the audit log. diff --git a/pkg/api/oas_handlers_gen.go b/pkg/api/oas_handlers_gen.go index e6bcc7b..13081b7 100644 --- a/pkg/api/oas_handlers_gen.go +++ b/pkg/api/oas_handlers_gen.go @@ -4922,6 +4922,201 @@ func (s *Server) handleCreateSubmissionRequest(args [0]string, argsEscaped bool, } } +// handleCreateSubmissionAdminRequest handles createSubmissionAdmin operation. +// +// Trigger the validator to create a new submission. +// +// POST /submissions-admin +func (s *Server) handleCreateSubmissionAdminRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("createSubmissionAdmin"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/submissions-admin"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), CreateSubmissionAdminOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: CreateSubmissionAdminOperation, + ID: "createSubmissionAdmin", + } + ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securityCookieAuth(ctx, CreateSubmissionAdminOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "CookieAuth", + Err: err, + } + if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil { + defer recordError("Security:CookieAuth", err) + } + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil { + defer recordError("Security", err) + } + return + } + } + request, close, err := s.decodeCreateSubmissionAdminRequest(r) + if err != nil { + err = &ogenerrors.DecodeRequestError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeRequest", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + defer func() { + if err := close(); err != nil { + recordError("CloseRequest", err) + } + }() + + var response *OperationID + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: CreateSubmissionAdminOperation, + OperationSummary: "Trigger the validator to create a new submission", + OperationID: "createSubmissionAdmin", + Body: request, + Params: middleware.Parameters{}, + Raw: r, + } + + type ( + Request = *SubmissionTriggerCreate + Params = struct{} + Response = *OperationID + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + nil, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.CreateSubmissionAdmin(ctx, request) + return response, err + }, + ) + } else { + response, err = s.h.CreateSubmissionAdmin(ctx, request) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeCreateSubmissionAdminResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleCreateSubmissionAuditCommentRequest handles createSubmissionAuditComment operation. // // Post a comment to the audit log. diff --git a/pkg/api/oas_operations_gen.go b/pkg/api/oas_operations_gen.go index bbc161d..bb64e95 100644 --- a/pkg/api/oas_operations_gen.go +++ b/pkg/api/oas_operations_gen.go @@ -31,6 +31,7 @@ const ( CreateScriptOperation OperationName = "CreateScript" CreateScriptPolicyOperation OperationName = "CreateScriptPolicy" CreateSubmissionOperation OperationName = "CreateSubmission" + CreateSubmissionAdminOperation OperationName = "CreateSubmissionAdmin" CreateSubmissionAuditCommentOperation OperationName = "CreateSubmissionAuditComment" DeleteScriptOperation OperationName = "DeleteScript" DeleteScriptPolicyOperation OperationName = "DeleteScriptPolicy" diff --git a/pkg/api/oas_request_decoders_gen.go b/pkg/api/oas_request_decoders_gen.go index 72c4ed7..a53e8e7 100644 --- a/pkg/api/oas_request_decoders_gen.go +++ b/pkg/api/oas_request_decoders_gen.go @@ -334,6 +334,77 @@ func (s *Server) decodeCreateSubmissionRequest(r *http.Request) ( } } +func (s *Server) decodeCreateSubmissionAdminRequest(r *http.Request) ( + req *SubmissionTriggerCreate, + close func() error, + rerr error, +) { + var closers []func() error + close = func() error { + var merr error + // Close in reverse order, to match defer behavior. + for i := len(closers) - 1; i >= 0; i-- { + c := closers[i] + merr = multierr.Append(merr, c()) + } + return merr + } + defer func() { + if rerr != nil { + rerr = multierr.Append(rerr, close()) + } + }() + ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + return req, close, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + if r.ContentLength == 0 { + return req, close, validate.ErrBodyRequired + } + buf, err := io.ReadAll(r.Body) + if err != nil { + return req, close, err + } + + if len(buf) == 0 { + return req, close, validate.ErrBodyRequired + } + + d := jx.DecodeBytes(buf) + + var request SubmissionTriggerCreate + if err := func() error { + if err := request.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return req, close, err + } + if err := func() error { + if err := request.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return req, close, errors.Wrap(err, "validate") + } + return &request, close, nil + default: + return req, close, validate.InvalidContentType(ct) + } +} + func (s *Server) decodeCreateSubmissionAuditCommentRequest(r *http.Request) ( req CreateSubmissionAuditCommentReq, close func() error, diff --git a/pkg/api/oas_request_encoders_gen.go b/pkg/api/oas_request_encoders_gen.go index 84923a2..09c700c 100644 --- a/pkg/api/oas_request_encoders_gen.go +++ b/pkg/api/oas_request_encoders_gen.go @@ -77,6 +77,20 @@ func encodeCreateSubmissionRequest( return nil } +func encodeCreateSubmissionAdminRequest( + req *SubmissionTriggerCreate, + r *http.Request, +) error { + const contentType = "application/json" + e := new(jx.Encoder) + { + req.Encode(e) + } + encoded := e.Bytes() + ht.SetBody(r, bytes.NewReader(encoded), contentType) + return nil +} + func encodeCreateSubmissionAuditCommentRequest( req CreateSubmissionAuditCommentReq, r *http.Request, diff --git a/pkg/api/oas_response_decoders_gen.go b/pkg/api/oas_response_decoders_gen.go index d92f378..a017f83 100644 --- a/pkg/api/oas_response_decoders_gen.go +++ b/pkg/api/oas_response_decoders_gen.go @@ -1679,6 +1679,107 @@ func decodeCreateSubmissionResponse(resp *http.Response) (res *OperationID, _ er return res, errors.Wrap(defRes, "error") } +func decodeCreateSubmissionAdminResponse(resp *http.Response) (res *OperationID, _ error) { + switch resp.StatusCode { + case 201: + // Code 201. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response OperationID + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + func decodeCreateSubmissionAuditCommentResponse(resp *http.Response) (res *CreateSubmissionAuditCommentNoContent, _ error) { switch resp.StatusCode { case 204: diff --git a/pkg/api/oas_response_encoders_gen.go b/pkg/api/oas_response_encoders_gen.go index 33e5527..151b325 100644 --- a/pkg/api/oas_response_encoders_gen.go +++ b/pkg/api/oas_response_encoders_gen.go @@ -216,6 +216,20 @@ func encodeCreateSubmissionResponse(response *OperationID, w http.ResponseWriter return nil } +func encodeCreateSubmissionAdminResponse(response *OperationID, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(201) + span.SetStatus(codes.Ok, http.StatusText(201)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil +} + func encodeCreateSubmissionAuditCommentResponse(response *CreateSubmissionAuditCommentNoContent, w http.ResponseWriter, span trace.Span) error { w.WriteHeader(204) span.SetStatus(codes.Ok, http.StatusText(204)) diff --git a/pkg/api/oas_router_gen.go b/pkg/api/oas_router_gen.go index 4244ce2..00ff848 100644 --- a/pkg/api/oas_router_gen.go +++ b/pkg/api/oas_router_gen.go @@ -870,6 +870,26 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } switch elem[0] { + case '-': // Prefix: "-admin" + + if l := len("-admin"); len(elem) >= l && elem[0:l] == "-admin" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleCreateSubmissionAdminRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + case '/': // Prefix: "/" if l := len("/"); len(elem) >= l && elem[0:l] == "/" { @@ -2315,6 +2335,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { } } switch elem[0] { + case '-': // Prefix: "-admin" + + if l := len("-admin"); len(elem) >= l && elem[0:l] == "-admin" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = CreateSubmissionAdminOperation + r.summary = "Trigger the validator to create a new submission" + r.operationID = "createSubmissionAdmin" + r.pathPattern = "/submissions-admin" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + case '/': // Prefix: "/" if l := len("/"); len(elem) >= l && elem[0:l] == "/" { diff --git a/pkg/api/oas_server_gen.go b/pkg/api/oas_server_gen.go index aa46501..08df749 100644 --- a/pkg/api/oas_server_gen.go +++ b/pkg/api/oas_server_gen.go @@ -160,6 +160,12 @@ type Handler interface { // // POST /submissions CreateSubmission(ctx context.Context, req *SubmissionTriggerCreate) (*OperationID, error) + // CreateSubmissionAdmin implements createSubmissionAdmin operation. + // + // Trigger the validator to create a new submission. + // + // POST /submissions-admin + CreateSubmissionAdmin(ctx context.Context, req *SubmissionTriggerCreate) (*OperationID, error) // CreateSubmissionAuditComment implements createSubmissionAuditComment operation. // // Post a comment to the audit log. diff --git a/pkg/api/oas_unimplemented_gen.go b/pkg/api/oas_unimplemented_gen.go index 239065f..9f0e804 100644 --- a/pkg/api/oas_unimplemented_gen.go +++ b/pkg/api/oas_unimplemented_gen.go @@ -240,6 +240,15 @@ func (UnimplementedHandler) CreateSubmission(ctx context.Context, req *Submissio return r, ht.ErrNotImplemented } +// CreateSubmissionAdmin implements createSubmissionAdmin operation. +// +// Trigger the validator to create a new submission. +// +// POST /submissions-admin +func (UnimplementedHandler) CreateSubmissionAdmin(ctx context.Context, req *SubmissionTriggerCreate) (r *OperationID, _ error) { + return r, ht.ErrNotImplemented +} + // CreateSubmissionAuditComment implements createSubmissionAuditComment operation. // // Post a comment to the audit log. diff --git a/pkg/internal/oas_json_gen.go b/pkg/internal/oas_json_gen.go index e89d035..9a74a1a 100644 --- a/pkg/internal/oas_json_gen.go +++ b/pkg/internal/oas_json_gen.go @@ -1323,9 +1323,17 @@ func (s *SubmissionCreate) encodeFields(e *jx.Encoder) { e.FieldStart("AssetVersion") e.Int64(s.AssetVersion) } + { + e.FieldStart("Status") + e.UInt32(s.Status) + } + { + e.FieldStart("Roles") + e.UInt32(s.Roles) + } } -var jsonFieldsNameOfSubmissionCreate = [7]string{ +var jsonFieldsNameOfSubmissionCreate = [9]string{ 0: "OperationID", 1: "AssetOwner", 2: "DisplayName", @@ -1333,6 +1341,8 @@ var jsonFieldsNameOfSubmissionCreate = [7]string{ 4: "GameID", 5: "AssetID", 6: "AssetVersion", + 7: "Status", + 8: "Roles", } // Decode decodes SubmissionCreate from json. @@ -1340,7 +1350,7 @@ func (s *SubmissionCreate) Decode(d *jx.Decoder) error { if s == nil { return errors.New("invalid: unable to decode SubmissionCreate to nil") } - var requiredBitSet [1]uint8 + var requiredBitSet [2]uint8 if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { switch string(k) { @@ -1428,6 +1438,30 @@ func (s *SubmissionCreate) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"AssetVersion\"") } + case "Status": + requiredBitSet[0] |= 1 << 7 + if err := func() error { + v, err := d.UInt32() + s.Status = uint32(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Status\"") + } + case "Roles": + requiredBitSet[1] |= 1 << 0 + if err := func() error { + v, err := d.UInt32() + s.Roles = uint32(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Roles\"") + } default: return d.Skip() } @@ -1437,8 +1471,9 @@ func (s *SubmissionCreate) Decode(d *jx.Decoder) error { } // Validate required fields. var failures []validate.FieldError - for i, mask := range [1]uint8{ - 0b01111111, + for i, mask := range [2]uint8{ + 0b11111111, + 0b00000001, } { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { // Mask only required fields and check equality to mask using XOR. diff --git a/pkg/internal/oas_schemas_gen.go b/pkg/internal/oas_schemas_gen.go index cb3ca30..86ef488 100644 --- a/pkg/internal/oas_schemas_gen.go +++ b/pkg/internal/oas_schemas_gen.go @@ -594,6 +594,8 @@ type SubmissionCreate struct { GameID int32 `json:"GameID"` AssetID int64 `json:"AssetID"` AssetVersion int64 `json:"AssetVersion"` + Status uint32 `json:"Status"` + Roles uint32 `json:"Roles"` } // GetOperationID returns the value of OperationID. @@ -631,6 +633,16 @@ func (s *SubmissionCreate) GetAssetVersion() int64 { return s.AssetVersion } +// GetStatus returns the value of Status. +func (s *SubmissionCreate) GetStatus() uint32 { + return s.Status +} + +// GetRoles returns the value of Roles. +func (s *SubmissionCreate) GetRoles() uint32 { + return s.Roles +} + // SetOperationID sets the value of OperationID. func (s *SubmissionCreate) SetOperationID(val int32) { s.OperationID = val @@ -666,6 +678,16 @@ func (s *SubmissionCreate) SetAssetVersion(val int64) { s.AssetVersion = val } +// SetStatus sets the value of Status. +func (s *SubmissionCreate) SetStatus(val uint32) { + s.Status = val +} + +// SetRoles sets the value of Roles. +func (s *SubmissionCreate) SetRoles(val uint32) { + s.Roles = val +} + // Ref: #/components/schemas/SubmissionID type SubmissionID struct { SubmissionID int64 `json:"SubmissionID"` diff --git a/pkg/internal/oas_validators_gen.go b/pkg/internal/oas_validators_gen.go index 159c9f4..86e8c6a 100644 --- a/pkg/internal/oas_validators_gen.go +++ b/pkg/internal/oas_validators_gen.go @@ -881,6 +881,26 @@ func (s *SubmissionCreate) Validate() error { Error: err, }) } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: true, + Max: 9, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(s.Status)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "Status", + Error: err, + }) + } if len(failures) > 0 { return &validate.Error{Fields: failures} } -- 2.49.1 From 1b4456f30a0c7d207489a538e0921fda0e992325 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 15 Apr 2025 15:20:24 -0700 Subject: [PATCH 440/454] submissions: add initial fields --- pkg/model/nats.go | 2 ++ pkg/service/submissions.go | 2 ++ pkg/service_internal/submissions.go | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/model/nats.go b/pkg/model/nats.go index 0d422af..a0882e6 100644 --- a/pkg/model/nats.go +++ b/pkg/model/nats.go @@ -12,6 +12,8 @@ type CreateSubmissionRequest struct { DisplayName string Creator string GameID uint32 + Status uint32 + Roles uint32 } type CreateMapfixRequest struct { diff --git a/pkg/service/submissions.go b/pkg/service/submissions.go index 955f5eb..be67fdc 100644 --- a/pkg/service/submissions.go +++ b/pkg/service/submissions.go @@ -106,6 +106,8 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *api.Submissio DisplayName: request.DisplayName, Creator: request.Creator, GameID: uint32(request.GameID), + Status: uint32(model.SubmissionStatusUnderConstruction), + Roles: uint32(RolesEmpty), } j, err := json.Marshal(create_request) diff --git a/pkg/service_internal/submissions.go b/pkg/service_internal/submissions.go index ddbe54f..e8f8c39 100644 --- a/pkg/service_internal/submissions.go +++ b/pkg/service_internal/submissions.go @@ -337,6 +337,7 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *internal.Subm var Submitter=uint64(request.AssetOwner); var AssetID=uint64(request.AssetID); var AssetVersion=uint64(request.AssetVersion); + var Status=model.SubmissionStatus(request.Status); // Check if an active submission with the same asset id exists { @@ -376,7 +377,7 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *internal.Subm AssetID: AssetID, AssetVersion: AssetVersion, Completed: false, - StatusID: model.SubmissionStatusUnderConstruction, + StatusID: Status, }) if err != nil { return nil, err -- 2.49.1 From 649b941d5f9e574702deb658bf07a51fff70576d Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 15 Apr 2025 15:31:48 -0700 Subject: [PATCH 441/454] submissions: implement CreateSubmissionAdmin endpoint --- pkg/service/submissions.go | 76 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/pkg/service/submissions.go b/pkg/service/submissions.go index be67fdc..f8de940 100644 --- a/pkg/service/submissions.go +++ b/pkg/service/submissions.go @@ -124,6 +124,82 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *api.Submissio OperationID: operation.ID, }, nil } +// POST /submissions-admin +func (svc *Service) CreateSubmissionAdmin(ctx context.Context, request *api.SubmissionTriggerCreate) (*api.OperationID, error) { + // sanitization + if request.AssetID<0{ + return nil, ErrNegativeID + } + var ModelID=uint64(request.AssetID); + + userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) + if !ok { + return nil, ErrUserInfo + } + + userId, err := userInfo.GetUserID() + if err != nil { + return nil, err + } + + roles, err := userInfo.GetRoles() + if err != nil { + return nil, err + } + + // check if caller has required role + has_role := roles & RolesSubmissionReview == RolesSubmissionReview + if !has_role { + return nil, ErrPermissionDeniedNeedRoleSubmissionReview + } + + // Check if too many operations have been created recently + { + count, err := svc.DB.Operations().CountSince(ctx, + int64(userId), + time.Now().Add(-CreateSubmissionRecencyWindow), + ) + if err != nil { + return nil, err + } + + if CreateSubmissionRateLimit < count { + return nil, ErrCreateSubmissionRateLimit + } + } + + operation, err := svc.DB.Operations().Create(ctx, model.Operation{ + Owner: userId, + StatusID: model.OperationStatusCreated, + }) + if err != nil { + return nil, err + } + + create_request := model.CreateSubmissionRequest{ + OperationID: operation.ID, + ModelID: ModelID, + DisplayName: request.DisplayName, + Creator: request.Creator, + GameID: uint32(request.GameID), + Status: uint32(model.SubmissionStatusChangesRequested), + Roles: uint32(roles), + } + + j, err := json.Marshal(create_request) + if err != nil { + return nil, err + } + + _, err = svc.Nats.Publish("maptest.submissions.create", []byte(j)) + if err != nil { + return nil, err + } + + return &api.OperationID{ + OperationID: operation.ID, + }, nil +} // GetSubmission implements getSubmission operation. // -- 2.49.1 From a669de3c0b69c914d72acc137f72d0b8dfac103b Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 15 Apr 2025 15:31:22 -0700 Subject: [PATCH 442/454] submissions: allow bypass by admin in internal CreateSubmission --- pkg/service_internal/submissions.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/service_internal/submissions.go b/pkg/service_internal/submissions.go index e8f8c39..1ce1e4c 100644 --- a/pkg/service_internal/submissions.go +++ b/pkg/service_internal/submissions.go @@ -9,6 +9,7 @@ import ( "git.itzana.me/strafesnet/maps-service/pkg/datastore" internal "git.itzana.me/strafesnet/maps-service/pkg/internal" "git.itzana.me/strafesnet/maps-service/pkg/model" + "git.itzana.me/strafesnet/maps-service/pkg/service" ) var( @@ -338,6 +339,7 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *internal.Subm var AssetID=uint64(request.AssetID); var AssetVersion=uint64(request.AssetVersion); var Status=model.SubmissionStatus(request.Status); + var roles=service.Roles(request.Roles); // Check if an active submission with the same asset id exists { @@ -363,8 +365,11 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *internal.Subm } // check if user owns asset - // TODO: allow bypass by admin - if operation.Owner != Submitter { + is_submitter := operation.Owner == Submitter + // check if user is map admin + has_submission_review := roles & service.RolesSubmissionReview == service.RolesSubmissionReview + // if neither, u not allowed + if !is_submitter && !has_submission_review { return nil, ErrNotAssetOwner } -- 2.49.1 From 8f8d685f71f8bbba61ba737b4656181283aa9b4f Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 15 Apr 2025 15:01:10 -0700 Subject: [PATCH 443/454] validator: plumb fields --- validation/api/src/types.rs | 2 ++ validation/src/create_submission.rs | 2 ++ validation/src/nats_types.rs | 3 +++ 3 files changed, 7 insertions(+) diff --git a/validation/api/src/types.rs b/validation/api/src/types.rs index eb4e87e..c5a1988 100644 --- a/validation/api/src/types.rs +++ b/validation/api/src/types.rs @@ -91,6 +91,8 @@ pub struct CreateSubmissionRequest<'a>{ pub GameID:i32, pub AssetID:u64, pub AssetVersion:u64, + pub Status:u32, + pub Roles:u32, } #[allow(nonstandard_style)] #[derive(Clone,Debug,serde::Deserialize)] diff --git a/validation/src/create_submission.rs b/validation/src/create_submission.rs index 06485d4..3cfc3cc 100644 --- a/validation/src/create_submission.rs +++ b/validation/src/create_submission.rs @@ -45,6 +45,8 @@ impl crate::message_handler::MessageHandler{ GameID:game_id as i32, AssetID:create_info.ModelID, AssetVersion:create_request.AssetVersion, + Status:create_info.Status, + Roles:create_info.Roles, }).await.map_err(Error::ApiActionSubmissionCreate)?; Ok(()) diff --git a/validation/src/nats_types.rs b/validation/src/nats_types.rs index 357e69b..6d8d712 100644 --- a/validation/src/nats_types.rs +++ b/validation/src/nats_types.rs @@ -13,6 +13,9 @@ pub struct CreateSubmissionRequest{ pub DisplayName:String, pub Creator:String, pub GameID:u32, + // initial status is passed back on create + pub Status:u32, + pub Roles:u32, } #[allow(nonstandard_style)] -- 2.49.1 From adbcbed9ac95bb73bc9c300853307300b971224c Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 15 Apr 2025 15:25:29 -0700 Subject: [PATCH 444/454] submissions: allow admin to submit from changes requested --- pkg/service/submissions.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/service/submissions.go b/pkg/service/submissions.go index f8de940..3ec4516 100644 --- a/pkg/service/submissions.go +++ b/pkg/service/submissions.go @@ -584,9 +584,15 @@ func (svc *Service) ActionSubmissionTriggerSubmit(ctx context.Context, params ap return err } + has_submission_review, err := userInfo.HasRoleSubmissionReview() + if err != nil { + return err + } + // check if caller is the submitter - has_role := userId == submission.Submitter - if !has_role { + is_submitter := userId == submission.Submitter + // neither = deny + if !is_submitter && !has_submission_review { return ErrPermissionDeniedNotSubmitter } -- 2.49.1 From b600ca582b31444a839c94da68b0a72d2384a198 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 15 Apr 2025 15:28:11 -0700 Subject: [PATCH 445/454] web: show submit button for admin on ChangesRequested status --- web/src/app/submissions/[submissionId]/_reviewButtons.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/src/app/submissions/[submissionId]/_reviewButtons.tsx b/web/src/app/submissions/[submissionId]/_reviewButtons.tsx index 2ac0ad8..ba34c54 100644 --- a/web/src/app/submissions/[submissionId]/_reviewButtons.tsx +++ b/web/src/app/submissions/[submissionId]/_reviewButtons.tsx @@ -119,6 +119,10 @@ export default function ReviewButtons(props: ReviewId) { } if (roles&RolesConstants.SubmissionReview) { + // you can force submit a map in ChangesRequested status + if (!is_submitter && submissionStatus === SubmissionStatus.ChangesRequested) { + visibleButtons.push({ action: ReviewActions.Submit, color: "info", submissionId }); + } // you can't review your own submission! // note that this means there needs to be more than one person with SubmissionReview if (!is_submitter && submissionStatus === SubmissionStatus.Submitted) { -- 2.49.1 From d5c8477869e119bad34886e01da600ffae59ab92 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 15 Apr 2025 15:28:22 -0700 Subject: [PATCH 446/454] web: const enum typescript xD --- web/src/app/ts/AuditEvent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/app/ts/AuditEvent.ts b/web/src/app/ts/AuditEvent.ts index 18f91a0..f9882a1 100644 --- a/web/src/app/ts/AuditEvent.ts +++ b/web/src/app/ts/AuditEvent.ts @@ -1,7 +1,7 @@ import { SubmissionStatusToString } from "./Submission"; // Shared audit event types -export enum AuditEventType { +export const enum AuditEventType { Action = 0, Comment = 1, ChangeModel = 2, -- 2.49.1 From 872b98aa744ae140928dc3a1ac0c76816cda78c3 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 15 Apr 2025 15:53:29 -0700 Subject: [PATCH 447/454] web: explain admin buttons a bit better --- web/src/app/admin-submit/page.tsx | 2 +- web/src/app/submissions/[submissionId]/_reviewButtons.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/web/src/app/admin-submit/page.tsx b/web/src/app/admin-submit/page.tsx index a7a5170..52adc56 100644 --- a/web/src/app/admin-submit/page.tsx +++ b/web/src/app/admin-submit/page.tsx @@ -82,7 +82,7 @@ export default function SubmissionInfoPage() { width: "400px", height: "50px", marginInline: "auto" - }}>Create Submission and Directly Submit + }}>Create Submission in ChangesRequested Status (Ready to Force-Submit) diff --git a/web/src/app/submissions/[submissionId]/_reviewButtons.tsx b/web/src/app/submissions/[submissionId]/_reviewButtons.tsx index ba34c54..a5eac74 100644 --- a/web/src/app/submissions/[submissionId]/_reviewButtons.tsx +++ b/web/src/app/submissions/[submissionId]/_reviewButtons.tsx @@ -10,6 +10,7 @@ interface ReviewAction { const ReviewActions = { Submit: {name:"Submit",action:"trigger-submit"} as ReviewAction, + ForceSubmit: {name:"Force Submit",action:"trigger-submit"} as ReviewAction, ResetSubmitting: {name:"Reset Submitting (fix softlocked status)",action:"reset-submitting"} as ReviewAction, Revoke: {name:"Revoke",action:"revoke"} as ReviewAction, Accept: {name:"Accept",action:"trigger-validate"} as ReviewAction, @@ -121,7 +122,7 @@ export default function ReviewButtons(props: ReviewId) { if (roles&RolesConstants.SubmissionReview) { // you can force submit a map in ChangesRequested status if (!is_submitter && submissionStatus === SubmissionStatus.ChangesRequested) { - visibleButtons.push({ action: ReviewActions.Submit, color: "info", submissionId }); + visibleButtons.push({ action: ReviewActions.ForceSubmit, color: "error", submissionId }); } // you can't review your own submission! // note that this means there needs to be more than one person with SubmissionReview -- 2.49.1 From 3614018794b5e1dd74ea8c724d5bf2d085e87558 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 15 Apr 2025 16:20:48 -0700 Subject: [PATCH 448/454] web: remove redirect --- web/src/app/_components/webpage.tsx | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/web/src/app/_components/webpage.tsx b/web/src/app/_components/webpage.tsx index 2c64aba..fc386fd 100644 --- a/web/src/app/_components/webpage.tsx +++ b/web/src/app/_components/webpage.tsx @@ -1,25 +1,8 @@ "use client" -import { redirect } from "next/navigation"; -import { useEffect } from "react"; - import Header from "./header"; -async function login_check() { - const response = await fetch("/api/session/validate") - if (response.ok) { - const logged_in = await response.json() - if (!logged_in) { - redirect("https://auth.staging.strafes.net/oauth2/login?redirect=" + window.location.href) - } - } else { - console.error("No response from /api/session/validate") - } -} - export default function Webpage({children}: Readonly<{children?: React.ReactNode}>) { - useEffect(() => { login_check() }, []) - return <>
{children} -- 2.49.1 From 49b9b41085aa52b46c2be50c2b51eab1e45d10aa Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 15 Apr 2025 16:20:52 -0700 Subject: [PATCH 449/454] web: create login button --- web/src/app/_components/header.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web/src/app/_components/header.tsx b/web/src/app/_components/header.tsx index c2274aa..0618bc9 100644 --- a/web/src/app/_components/header.tsx +++ b/web/src/app/_components/header.tsx @@ -15,6 +15,10 @@ function HeaderButton(header: HeaderButton) { } export default function Header() { + const handleLoginClick = () => { + window.location.href = "https://auth.staging.strafes.net/oauth2/login?redirect=" + window.location.href; + }; + return (
) -- 2.49.1 From 41663624d37cd1e39d644d9a8af967c8e305f522 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 15 Apr 2025 16:43:04 -0700 Subject: [PATCH 450/454] web: conditionally show avatar when logged in --- web/src/app/_components/header.tsx | 31 +++++++++++++++++++++++++++++- web/src/app/ts/User.ts | 5 +++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 web/src/app/ts/User.ts diff --git a/web/src/app/_components/header.tsx b/web/src/app/_components/header.tsx index 0618bc9..30259bf 100644 --- a/web/src/app/_components/header.tsx +++ b/web/src/app/_components/header.tsx @@ -1,6 +1,11 @@ +"use client" + import Link from "next/link" +import Image from "next/image"; import "./styles/header.scss" +import { UserInfo } from "@/app/ts/User"; +import { useState, useEffect } from "react"; interface HeaderButton { name: string, @@ -19,6 +24,21 @@ export default function Header() { window.location.href = "https://auth.staging.strafes.net/oauth2/login?redirect=" + window.location.href; }; + const [valid, setValid] = useState(false) + const [user, setUser] = useState(null) + + useEffect(() => { + async function getLoginInfo() { + const [validateData, userData] = await Promise.all([ + fetch("/api/session/validate").then(validateResponse => validateResponse.json()), + fetch("/api/session/user").then(userResponse => userResponse.json()) + ]); + setValid(validateData) + setUser(userData) + } + getLoginInfo() + }, []) + return (
) diff --git a/web/src/app/ts/User.ts b/web/src/app/ts/User.ts new file mode 100644 index 0000000..98ffb12 --- /dev/null +++ b/web/src/app/ts/User.ts @@ -0,0 +1,5 @@ +export interface UserInfo { + readonly UserID: number, + readonly Username: string, + readonly AvatarURL: string, +} -- 2.49.1 From 6d14047f57817094db3524fa1769e58cc77dc442 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 15 Apr 2025 16:43:21 -0700 Subject: [PATCH 451/454] web: unused imports --- web/src/app/mapfixes/page.tsx | 2 +- web/src/app/submissions/page.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/app/mapfixes/page.tsx b/web/src/app/mapfixes/page.tsx index 2945e1e..98b31e2 100644 --- a/web/src/app/mapfixes/page.tsx +++ b/web/src/app/mapfixes/page.tsx @@ -1,6 +1,6 @@ 'use client' -import React, { useState, useEffect } from "react"; +import { useState, useEffect } from "react"; import { MapfixList } from "../ts/Mapfix"; import { MapfixCard } from "../_components/mapCard"; import Webpage from "@/app/_components/webpage"; diff --git a/web/src/app/submissions/page.tsx b/web/src/app/submissions/page.tsx index 2ae27dd..8a7c017 100644 --- a/web/src/app/submissions/page.tsx +++ b/web/src/app/submissions/page.tsx @@ -1,6 +1,6 @@ 'use client' -import React, { useState, useEffect } from "react"; +import { useState, useEffect } from "react"; import { SubmissionList } from "../ts/Submission"; import { SubmissionCard } from "../_components/mapCard"; import Webpage from "@/app/_components/webpage"; -- 2.49.1 From c98d1704231bfbef4bb9d098011892a8a4678ac4 Mon Sep 17 00:00:00 2001 From: ic3w0lf Date: Tue, 15 Apr 2025 18:50:40 -0600 Subject: [PATCH 452/454] Remove hardcoded auth URLs --- web/src/app/_components/header.tsx | 4 ++-- web/src/middleware.ts | 28 ++++++++++++++++++++++------ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/web/src/app/_components/header.tsx b/web/src/app/_components/header.tsx index 30259bf..b611533 100644 --- a/web/src/app/_components/header.tsx +++ b/web/src/app/_components/header.tsx @@ -21,7 +21,7 @@ function HeaderButton(header: HeaderButton) { export default function Header() { const handleLoginClick = () => { - window.location.href = "https://auth.staging.strafes.net/oauth2/login?redirect=" + window.location.href; + window.location.href = "/auth/oauth2/login?redirect=" + window.location.href; }; const [valid, setValid] = useState(false) @@ -50,7 +50,7 @@ export default function Header() { {valid && user ? (
- + {user.Username}/ diff --git a/web/src/middleware.ts b/web/src/middleware.ts index ce20731..0a25a76 100644 --- a/web/src/middleware.ts +++ b/web/src/middleware.ts @@ -1,13 +1,29 @@ import { NextRequest, NextResponse } from "next/server" export const config = { - matcher: ["/api/:path*"], + matcher: ["/api/:path*", "/auth/:path*"], } export function middleware(request: NextRequest) { - if (!process.env.API_HOST) { - throw new Error("env variable \"API_HOST\" is not set") - } - const url = new URL(process.env.API_HOST + request.nextUrl.pathname.replace(/^\/api/, '') + request.nextUrl.search) - return NextResponse.rewrite(url, { request }) + const { pathname, search } = request.nextUrl + + if (pathname.startsWith("/api")) { + if (!process.env.API_HOST) { + throw new Error('env variable "API_HOST" is not set') + } + const apiUrl = new URL(process.env.API_HOST + pathname.replace(/^\/api/, '') + search) + return NextResponse.rewrite(apiUrl, { request }) + } else if (pathname.startsWith("/auth")) { + if (!process.env.AUTH_HOST) { + throw new Error('env variable "AUTH_HOST" is not set') + } + + const authHost = process.env.AUTH_HOST.replace(/\/$/, "") + const path = pathname.replace(/^\/auth/, "") + const redirectUrl = new URL(authHost + path + search) + + return NextResponse.redirect(redirectUrl, 302) + } + + return NextResponse.next() } \ No newline at end of file -- 2.49.1 From 4c1aef9113a4a940f34b6459f662f92cf1ce4529 Mon Sep 17 00:00:00 2001 From: ic3w0lf Date: Tue, 15 Apr 2025 18:57:35 -0600 Subject: [PATCH 453/454] Update README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fec86ec..75dc884 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,11 @@ Prerequisite: golang installed Prerequisite: bun installed -The environment variable `API_HOST` will need to be set for the middleware. +The environment variables `API_HOST` and `AUTH_HOST` will need to be set for the middleware. Example `.env` in web's root: ``` API_HOST="http://localhost:8082/v1/" +AUTH_HOST="http://localhost:8083/" ``` 1. `cd web` -- 2.49.1 From a95e6b7a9a1faefda1390f9e2e4044064b504241 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 15 Apr 2025 18:50:47 -0700 Subject: [PATCH 454/454] docker: add AUTH_HOST env var to docker compose --- compose.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/compose.yaml b/compose.yaml index 4445c1d..e4ddfac 100644 --- a/compose.yaml +++ b/compose.yaml @@ -50,6 +50,7 @@ services: - "3000:3000" environment: - API_HOST=http://submissions:8082/v1 + - AUTH_HOST=http://localhost:8080/ validation: image: -- 2.49.1
Loading... @@ -52,10 +52,10 @@ export default function SubmissionInfoPage() { } - if (submissions && submissions.length == 0) { + if (mapfixes && mapfixes.length == 0) { return
- Submissions list is empty. + Mapfixes list is empty.
} @@ -93,14 +93,14 @@ export default function SubmissionInfoPage() {
- {currentCards.map((submission) => ( - ( + ))}
diff --git a/web/src/app/ts/Mapfix.ts b/web/src/app/ts/Mapfix.ts index 7888436..0a5bd25 100644 --- a/web/src/app/ts/Mapfix.ts +++ b/web/src/app/ts/Mapfix.ts @@ -27,8 +27,8 @@ interface MapfixInfo { readonly StatusMessage: string, } -function MapfixStatusToString(submission_status: MapfixStatus): string { - switch (submission_status) { +function MapfixStatusToString(mapfix_status: MapfixStatus): string { + switch (mapfix_status) { case MapfixStatus.Rejected: return "REJECTED" case MapfixStatus.Uploading: -- 2.49.1 From 4cf7889db937a38209143dcd243126a4db04fea6 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 1 Apr 2025 14:58:58 -0700 Subject: [PATCH 246/454] web: add submit page at /maps/[mapId]/fix --- .../app/maps/[mapId]/fix/(styles)/page.scss | 54 ++++++++++ web/src/app/maps/[mapId]/fix/_game.tsx | 65 ++++++++++++ web/src/app/maps/[mapId]/fix/page.tsx | 98 +++++++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 web/src/app/maps/[mapId]/fix/(styles)/page.scss create mode 100644 web/src/app/maps/[mapId]/fix/_game.tsx create mode 100644 web/src/app/maps/[mapId]/fix/page.tsx diff --git a/web/src/app/maps/[mapId]/fix/(styles)/page.scss b/web/src/app/maps/[mapId]/fix/(styles)/page.scss new file mode 100644 index 0000000..8c7dcf8 --- /dev/null +++ b/web/src/app/maps/[mapId]/fix/(styles)/page.scss @@ -0,0 +1,54 @@ +@use "../../../../globals.scss"; + +::placeholder { + color: var(--placeholder-text) +} + +.form-spacer { + margin-bottom: 20px; + + &:last-of-type { + margin-top: 15px; + } +} + +#target-asset-radio { + color: var(--text-color); + font-size: globals.$form-label-fontsize; +} + +.form-field { + width: 850px; + + & label, & input { + color: var(--text-color); + } + & fieldset { + border-color: rgb(100,100,100); + } + & span { + color: white; + } +} + +main { + display: grid; + justify-content: center; + align-items: center; + margin-inline: auto; + width: 700px; +} + +header h1 { + text-align: center; + color: var(--text-color); +} + +form { + display: grid; + gap: 25px; + + fieldset { + border: blue + } +} diff --git a/web/src/app/maps/[mapId]/fix/_game.tsx b/web/src/app/maps/[mapId]/fix/_game.tsx new file mode 100644 index 0000000..e754601 --- /dev/null +++ b/web/src/app/maps/[mapId]/fix/_game.tsx @@ -0,0 +1,65 @@ +import { FormControl, Select, InputLabel, MenuItem } from "@mui/material"; +import { styled } from '@mui/material/styles'; +import InputBase from '@mui/material/InputBase'; +import React from "react"; +import { SelectChangeEvent } from "@mui/material"; + +// TODO: Properly style everything instead of pasting 🤚 + +type GameSelectionProps = { + game: number; + setGame: React.Dispatch>; +}; + +const BootstrapInput = styled(InputBase)(({ theme }) => ({ + 'label + &': { + marginTop: theme.spacing(3), + }, + '& .MuiInputBase-input': { + backgroundColor: '#0000', + color: '#FFF', + border: '1px solid rgba(175, 175, 175, 0.66)', + fontSize: 16, + padding: '10px 26px 10px 12px', + transition: theme.transitions.create(['border-color', 'box-shadow']), + fontFamily: [ + '-apple-system', + 'BlinkMacSystemFont', + '"Segoe UI"', + 'Roboto', + '"Helvetica Neue"', + 'Arial', + 'sans-serif', + '"Apple Color Emoji"', + '"Segoe UI Emoji"', + '"Segoe UI Symbol"', + ].join(','), + '&:focus': { + borderRadius: 4, + borderColor: '#80bdff', + boxShadow: '0 0 0 0.2rem rgba(0,123,255,.25)', + }, + }, + })); + +export default function GameSelection({ game, setGame }: GameSelectionProps) { + const handleChange = (event: SelectChangeEvent) => { + setGame(Number(event.target.value)); // TODO: Change later!! there's 100% a proper way of doing this + }; + + return ( + + Game + + + ); +} \ No newline at end of file diff --git a/web/src/app/maps/[mapId]/fix/page.tsx b/web/src/app/maps/[mapId]/fix/page.tsx new file mode 100644 index 0000000..5e68374 --- /dev/null +++ b/web/src/app/maps/[mapId]/fix/page.tsx @@ -0,0 +1,98 @@ +"use client" + +import { Button, TextField } from "@mui/material" + +import GameSelection from "./_game"; +import SendIcon from '@mui/icons-material/Send'; +import Webpage from "@/app/_components/webpage"; +import { useParams } from "next/navigation"; +import React, { useState } from "react"; + +import "./(styles)/page.scss" + +interface MapfixPayload { + DisplayName: string; + Creator: string; + GameID: number; + AssetID: number; + AssetVersion: number; + TargetAssetID: number; +} +interface IdResponse { + ID: number; +} + +export default function MapfixInfoPage() { + const [game, setGame] = useState(1); + const dynamicId = useParams<{ mapId: string }>(); + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + const form = event.currentTarget; + const formData = new FormData(form); + + const payload: MapfixPayload = { + DisplayName: (formData.get("display-name") as string) ?? "unknown", // TEMPORARY! TODO: Change + Creator: (formData.get("creator") as string) ?? "unknown", // TEMPORARY! TODO: Change + GameID: game, + AssetID: Number((formData.get("asset-id") as string) ?? "0"), + AssetVersion: 0, + TargetAssetID: Number(dynamicId.mapId), + }; + + console.log(payload) + console.log(JSON.stringify(payload)) + + try { + // Send the POST request + const response = await fetch("/api/mapfixes", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + + // Check if the HTTP request was successful + if (!response.ok) { + const errorDetails = await response.text(); + + // Throw an error with detailed information + throw new Error(`HTTP error! status: ${response.status}, details: ${errorDetails}`); + } + + // Allow any HTTP status + const id_response:IdResponse = await response.json(); + + // navigate to newly created mapfix + window.location.assign(`/mapfixes/${id_response.ID}`) + + } catch (error) { + console.error("Error submitting data:", error); + } + }; + + return ( + +
+
+

Submit Mapfix

+ +
+
+ {/* TODO: Add form data for mapfixes, such as changes they did, and any times that need to be deleted & what styles */} + + + + {/* I think this is Quat's job to figure this one out (to be set when someone clicks review(?)) */} {/* */} + + + + +
+
+ ) +} -- 2.49.1 From a119c4292ead3aee8c65d1bd91afa13b4be19bfb Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 1 Apr 2025 15:01:28 -0700 Subject: [PATCH 247/454] web: change submit text to match mapfix submit page --- web/src/app/submit/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/app/submit/page.tsx b/web/src/app/submit/page.tsx index 38cc9e3..b7108e9 100644 --- a/web/src/app/submit/page.tsx +++ b/web/src/app/submit/page.tsx @@ -71,7 +71,7 @@ export default function SubmissionInfoPage() {
-

Submit Asset

+

Submit New Map

-- 2.49.1 From c401d2436605c659c796bed210a67fb5f7de8c5e Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 1 Apr 2025 15:26:09 -0700 Subject: [PATCH 248/454] submissions: fix mapfixes auto migrate --- pkg/datastore/gormstore/db.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/datastore/gormstore/db.go b/pkg/datastore/gormstore/db.go index 938fec0..01d001c 100644 --- a/pkg/datastore/gormstore/db.go +++ b/pkg/datastore/gormstore/db.go @@ -31,6 +31,7 @@ func New(ctx *cli.Context) (datastore.Datastore, error) { if ctx.Bool("migrate") { if err := db.AutoMigrate( + &model.Mapfix{}, &model.Submission{}, &model.Script{}, &model.ScriptPolicy{}, -- 2.49.1 From 3bda4803aa84e8a4a2be749612915d70964c684d Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 1 Apr 2025 16:15:31 -0700 Subject: [PATCH 249/454] submissions: refine roles --- pkg/service/security.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/pkg/service/security.go b/pkg/service/security.go index 8bc2efb..2896aca 100644 --- a/pkg/service/security.go +++ b/pkg/service/security.go @@ -17,10 +17,12 @@ var ( // Submissions roles bitflag type Roles int32 var ( + RolesSubmissionUpload Roles = 1<<6 + RolesSubmissionReview Roles = 1<<5 RolesSubmissionRelease Roles = 1<<4 RolesScriptWrite Roles = 1<<3 - RolesMapUpload Roles = 1<<2 - RolesMapReview Roles = 1<<1 + RolesMapfixUpload Roles = 1<<2 + RolesMapfixReview Roles = 1<<1 RolesMapDownload Roles = 1<<0 RolesEmpty Roles = 0 ) @@ -32,13 +34,13 @@ var ( RoleQuat GroupRole = 255 RoleItzaname GroupRole = 254 RoleStagingDeveloper GroupRole = 240 - RolesAll Roles = RolesScriptWrite|RolesSubmissionRelease|RolesMapUpload|RolesMapReview|RolesMapDownload + RolesAll Roles = ^RolesEmpty // has SubmissionUpload RoleMapAdmin GroupRole = 128 - RolesMapAdmin Roles = RolesSubmissionRelease|RolesMapUpload|RolesMapReview|RolesMapDownload - // has SubmissionReview + RolesMapAdmin Roles = RolesSubmissionRelease|RolesSubmissionUpload|RolesSubmissionReview|RolesMapCouncil + // has MapfixReview RoleMapCouncil GroupRole = 64 - RolesMapCouncil Roles = RolesMapReview|RolesMapUpload|RolesMapDownload + RolesMapCouncil Roles = RolesMapfixReview|RolesMapfixUpload|RolesMapAccess // access to downloading maps RoleMapAccess GroupRole = 32 RolesMapAccess Roles = RolesMapDownload @@ -129,10 +131,10 @@ func (usr UserInfoHandle) GetRoles() (Roles, error) { // RoleThumbnail func (usr UserInfoHandle) HasRoleMapfixUpload() (bool, error) { - return usr.hasRoles(RolesMapUpload) + return usr.hasRoles(RolesMapfixUpload) } func (usr UserInfoHandle) HasRoleMapfixReview() (bool, error) { - return usr.hasRoles(RolesMapReview) + return usr.hasRoles(RolesMapfixReview) } func (usr UserInfoHandle) HasRoleMapDownload() (bool, error) { return usr.hasRoles(RolesMapDownload) @@ -141,10 +143,10 @@ func (usr UserInfoHandle) HasRoleSubmissionRelease() (bool, error) { return usr.hasRoles(RolesSubmissionRelease) } func (usr UserInfoHandle) HasRoleSubmissionUpload() (bool, error) { - return usr.hasRoles(RolesMapUpload) + return usr.hasRoles(RolesSubmissionUpload) } func (usr UserInfoHandle) HasRoleSubmissionReview() (bool, error) { - return usr.hasRoles(RolesMapReview) + return usr.hasRoles(RolesSubmissionReview) } func (usr UserInfoHandle) HasRoleScriptWrite() (bool, error) { return usr.hasRoles(RolesScriptWrite) -- 2.49.1 From 082c573ffb0cd3cc0fa69b34e6f6ee6f00d93541 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 1 Apr 2025 16:45:55 -0700 Subject: [PATCH 250/454] openapi: maps endpoints --- openapi.yaml | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/openapi.yaml b/openapi.yaml index b7e8e0f..bb8bebc 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -8,8 +8,10 @@ servers: tags: - name: Mapfixes description: Mapfix operations + - name: Maps + description: Map queries - name: Session - description: Session operations + description: Session queries - name: Submissions description: Submission operations - name: Scripts @@ -76,6 +78,73 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" + /maps: + get: + summary: Get list of maps + operationId: listMaps + tags: + - Maps + security: [] + parameters: + - $ref: "#/components/parameters/Page" + - $ref: "#/components/parameters/Limit" + - name: DisplayName + in: query + schema: + type: string + maxLength: 128 + - name: Creator + in: query + schema: + type: string + maxLength: 128 + - name: GameID + in: query + schema: + type: integer + format: int32 + - name: Sort + in: query + schema: + type: integer + format: int32 + responses: + "200": + description: Successful response + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Map" + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /maps/{MapID}: + get: + summary: Retrieve map with ID + operationId: getMap + tags: + - Maps + security: [] + parameters: + - $ref: '#/components/parameters/MapID' + responses: + "200": + description: Successful response + content: + application/json: + schema: + $ref: "#/components/schemas/Map" + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" /mapfixes: get: summary: Get list of mapfixes @@ -948,6 +1017,14 @@ components: in: cookie name: session_id parameters: + MapID: + name: MapID + in: path + required: true + description: The unique identifier for a map. + schema: + type: integer + format: int64 MapfixID: name: MapfixID in: path @@ -1030,6 +1107,30 @@ components: AvatarURL: type: string maxLength: 256 + Map: + required: + - ID + - DisplayName + - Creator + - GameID + - Date + type: object + properties: + ID: + type: integer + format: int64 + DisplayName: + type: string + maxLength: 128 + Creator: + type: string + maxLength: 128 + GameID: + type: integer + format: int32 + Date: + type: integer + format: int64 Mapfix: required: - ID -- 2.49.1 From bfd287f3ccfe46330353e4c4df66d2d350d3751e Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 1 Apr 2025 16:46:18 -0700 Subject: [PATCH 251/454] openapi: generate --- pkg/api/oas_client_gen.go | 274 ++++++ pkg/api/oas_handlers_gen.go | 318 ++++++ pkg/api/oas_json_gen.go | 164 ++++ pkg/api/oas_operations_gen.go | 2 + pkg/api/oas_parameters_gen.go | 451 +++++++++ pkg/api/oas_response_decoders_gen.go | 209 ++++ pkg/api/oas_response_encoders_gen.go | 32 + pkg/api/oas_router_gen.go | 1358 ++++++++++++++------------ pkg/api/oas_schemas_gen.go | 59 ++ pkg/api/oas_server_gen.go | 12 + pkg/api/oas_unimplemented_gen.go | 18 + pkg/api/oas_validators_gen.go | 50 + 12 files changed, 2337 insertions(+), 610 deletions(-) diff --git a/pkg/api/oas_client_gen.go b/pkg/api/oas_client_gen.go index 2ef1dad..714161e 100644 --- a/pkg/api/oas_client_gen.go +++ b/pkg/api/oas_client_gen.go @@ -173,6 +173,12 @@ type Invoker interface { // // DELETE /script-policy/{ScriptPolicyID} DeleteScriptPolicy(ctx context.Context, params DeleteScriptPolicyParams) error + // GetMap invokes getMap operation. + // + // Retrieve map with ID. + // + // GET /maps/{MapID} + GetMap(ctx context.Context, params GetMapParams) (*Map, error) // GetMapfix invokes getMapfix operation. // // Retrieve map with ID. @@ -203,6 +209,12 @@ type Invoker interface { // // GET /mapfixes ListMapfixes(ctx context.Context, params ListMapfixesParams) ([]Mapfix, error) + // ListMaps invokes listMaps operation. + // + // Get list of maps. + // + // GET /maps + ListMaps(ctx context.Context, params ListMapsParams) ([]Map, error) // ListScriptPolicy invokes listScriptPolicy operation. // // Get list of script policies. @@ -3242,6 +3254,96 @@ func (c *Client) sendDeleteScriptPolicy(ctx context.Context, params DeleteScript return result, nil } +// GetMap invokes getMap operation. +// +// Retrieve map with ID. +// +// GET /maps/{MapID} +func (c *Client) GetMap(ctx context.Context, params GetMapParams) (*Map, error) { + res, err := c.sendGetMap(ctx, params) + return res, err +} + +func (c *Client) sendGetMap(ctx context.Context, params GetMapParams) (res *Map, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("getMap"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/maps/{MapID}"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, GetMapOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [2]string + pathParts[0] = "/maps/" + { + // Encode "MapID" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "MapID", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.Int64ToString(params.MapID)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[1] = encoded + } + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "GET", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeGetMapResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // GetMapfix invokes getMapfix operation. // // Retrieve map with ID. @@ -3774,6 +3876,178 @@ func (c *Client) sendListMapfixes(ctx context.Context, params ListMapfixesParams return result, nil } +// ListMaps invokes listMaps operation. +// +// Get list of maps. +// +// GET /maps +func (c *Client) ListMaps(ctx context.Context, params ListMapsParams) ([]Map, error) { + res, err := c.sendListMaps(ctx, params) + return res, err +} + +func (c *Client) sendListMaps(ctx context.Context, params ListMapsParams) (res []Map, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listMaps"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/maps"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, ListMapsOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/maps" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeQueryParams" + q := uri.NewQueryEncoder() + { + // Encode "Page" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "Page", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.Int32ToString(params.Page)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "Limit" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "Limit", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.Int32ToString(params.Limit)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "DisplayName" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "DisplayName", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.DisplayName.Get(); ok { + return e.EncodeValue(conv.StringToString(val)) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "Creator" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "Creator", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.Creator.Get(); ok { + return e.EncodeValue(conv.StringToString(val)) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "GameID" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "GameID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.GameID.Get(); ok { + return e.EncodeValue(conv.Int32ToString(val)) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "Sort" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "Sort", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.Sort.Get(); ok { + return e.EncodeValue(conv.Int32ToString(val)) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + u.RawQuery = q.Values().Encode() + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "GET", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeListMapsResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // ListScriptPolicy invokes listScriptPolicy operation. // // Get list of script policies. diff --git a/pkg/api/oas_handlers_gen.go b/pkg/api/oas_handlers_gen.go index e7f8893..95e1799 100644 --- a/pkg/api/oas_handlers_gen.go +++ b/pkg/api/oas_handlers_gen.go @@ -4710,6 +4710,155 @@ func (s *Server) handleDeleteScriptPolicyRequest(args [1]string, argsEscaped boo } } +// handleGetMapRequest handles getMap operation. +// +// Retrieve map with ID. +// +// GET /maps/{MapID} +func (s *Server) handleGetMapRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("getMap"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/maps/{MapID}"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), GetMapOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: GetMapOperation, + ID: "getMap", + } + ) + params, err := decodeGetMapParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response *Map + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: GetMapOperation, + OperationSummary: "Retrieve map with ID", + OperationID: "getMap", + Body: nil, + Params: middleware.Parameters{ + { + Name: "MapID", + In: "path", + }: params.MapID, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = GetMapParams + Response = *Map + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackGetMapParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.GetMap(ctx, params) + return response, err + }, + ) + } else { + response, err = s.h.GetMap(ctx, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeGetMapResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleGetMapfixRequest handles getMapfix operation. // // Retrieve map with ID. @@ -5475,6 +5624,175 @@ func (s *Server) handleListMapfixesRequest(args [0]string, argsEscaped bool, w h } } +// handleListMapsRequest handles listMaps operation. +// +// Get list of maps. +// +// GET /maps +func (s *Server) handleListMapsRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listMaps"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/maps"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), ListMapsOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: ListMapsOperation, + ID: "listMaps", + } + ) + params, err := decodeListMapsParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response []Map + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: ListMapsOperation, + OperationSummary: "Get list of maps", + OperationID: "listMaps", + Body: nil, + Params: middleware.Parameters{ + { + Name: "Page", + In: "query", + }: params.Page, + { + Name: "Limit", + In: "query", + }: params.Limit, + { + Name: "DisplayName", + In: "query", + }: params.DisplayName, + { + Name: "Creator", + In: "query", + }: params.Creator, + { + Name: "GameID", + In: "query", + }: params.GameID, + { + Name: "Sort", + In: "query", + }: params.Sort, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = ListMapsParams + Response = []Map + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackListMapsParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.ListMaps(ctx, params) + return response, err + }, + ) + } else { + response, err = s.h.ListMaps(ctx, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeListMapsResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleListScriptPolicyRequest handles listScriptPolicy operation. // // Get list of script policies. diff --git a/pkg/api/oas_json_gen.go b/pkg/api/oas_json_gen.go index bb97445..9061475 100644 --- a/pkg/api/oas_json_gen.go +++ b/pkg/api/oas_json_gen.go @@ -222,6 +222,170 @@ func (s *ID) UnmarshalJSON(data []byte) error { return s.Decode(d) } +// Encode implements json.Marshaler. +func (s *Map) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *Map) encodeFields(e *jx.Encoder) { + { + e.FieldStart("ID") + e.Int64(s.ID) + } + { + e.FieldStart("DisplayName") + e.Str(s.DisplayName) + } + { + e.FieldStart("Creator") + e.Str(s.Creator) + } + { + e.FieldStart("GameID") + e.Int32(s.GameID) + } + { + e.FieldStart("Date") + e.Int64(s.Date) + } +} + +var jsonFieldsNameOfMap = [5]string{ + 0: "ID", + 1: "DisplayName", + 2: "Creator", + 3: "GameID", + 4: "Date", +} + +// Decode decodes Map from json. +func (s *Map) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode Map to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "ID": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Int64() + s.ID = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"ID\"") + } + case "DisplayName": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + v, err := d.Str() + s.DisplayName = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"DisplayName\"") + } + case "Creator": + requiredBitSet[0] |= 1 << 2 + if err := func() error { + v, err := d.Str() + s.Creator = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Creator\"") + } + case "GameID": + requiredBitSet[0] |= 1 << 3 + if err := func() error { + v, err := d.Int32() + s.GameID = int32(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"GameID\"") + } + case "Date": + requiredBitSet[0] |= 1 << 4 + if err := func() error { + v, err := d.Int64() + s.Date = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Date\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode Map") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00011111, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfMap) { + name = jsonFieldsNameOfMap[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *Map) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *Map) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + // Encode implements json.Marshaler. func (s *Mapfix) Encode(e *jx.Encoder) { e.ObjStart() diff --git a/pkg/api/oas_operations_gen.go b/pkg/api/oas_operations_gen.go index 04127ea..f2235e9 100644 --- a/pkg/api/oas_operations_gen.go +++ b/pkg/api/oas_operations_gen.go @@ -30,11 +30,13 @@ const ( CreateSubmissionOperation OperationName = "CreateSubmission" DeleteScriptOperation OperationName = "DeleteScript" DeleteScriptPolicyOperation OperationName = "DeleteScriptPolicy" + GetMapOperation OperationName = "GetMap" GetMapfixOperation OperationName = "GetMapfix" GetScriptOperation OperationName = "GetScript" GetScriptPolicyOperation OperationName = "GetScriptPolicy" GetSubmissionOperation OperationName = "GetSubmission" ListMapfixesOperation OperationName = "ListMapfixes" + ListMapsOperation OperationName = "ListMaps" ListScriptPolicyOperation OperationName = "ListScriptPolicy" ListScriptsOperation OperationName = "ListScripts" ListSubmissionsOperation OperationName = "ListSubmissions" diff --git a/pkg/api/oas_parameters_gen.go b/pkg/api/oas_parameters_gen.go index 0acb395..6ea7cb4 100644 --- a/pkg/api/oas_parameters_gen.go +++ b/pkg/api/oas_parameters_gen.go @@ -1335,6 +1335,72 @@ func decodeDeleteScriptPolicyParams(args [1]string, argsEscaped bool, r *http.Re return params, nil } +// GetMapParams is parameters of getMap operation. +type GetMapParams struct { + // The unique identifier for a map. + MapID int64 +} + +func unpackGetMapParams(packed middleware.Parameters) (params GetMapParams) { + { + key := middleware.ParameterKey{ + Name: "MapID", + In: "path", + } + params.MapID = packed[key].(int64) + } + return params +} + +func decodeGetMapParams(args [1]string, argsEscaped bool, r *http.Request) (params GetMapParams, _ error) { + // Decode path: MapID. + if err := func() error { + param := args[0] + if argsEscaped { + unescaped, err := url.PathUnescape(args[0]) + if err != nil { + return errors.Wrap(err, "unescape path") + } + param = unescaped + } + if len(param) > 0 { + d := uri.NewPathDecoder(uri.PathDecoderConfig{ + Param: "MapID", + Value: param, + Style: uri.PathStyleSimple, + Explode: false, + }) + + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + params.MapID = c + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "MapID", + In: "path", + Err: err, + } + } + return params, nil +} + // GetMapfixParams is parameters of getMapfix operation. type GetMapfixParams struct { // The unique identifier for a mapfix. @@ -1984,6 +2050,391 @@ func decodeListMapfixesParams(args [0]string, argsEscaped bool, r *http.Request) return params, nil } +// ListMapsParams is parameters of listMaps operation. +type ListMapsParams struct { + Page int32 + Limit int32 + DisplayName OptString + Creator OptString + GameID OptInt32 + Sort OptInt32 +} + +func unpackListMapsParams(packed middleware.Parameters) (params ListMapsParams) { + { + key := middleware.ParameterKey{ + Name: "Page", + In: "query", + } + params.Page = packed[key].(int32) + } + { + key := middleware.ParameterKey{ + Name: "Limit", + In: "query", + } + params.Limit = packed[key].(int32) + } + { + key := middleware.ParameterKey{ + Name: "DisplayName", + In: "query", + } + if v, ok := packed[key]; ok { + params.DisplayName = v.(OptString) + } + } + { + key := middleware.ParameterKey{ + Name: "Creator", + In: "query", + } + if v, ok := packed[key]; ok { + params.Creator = v.(OptString) + } + } + { + key := middleware.ParameterKey{ + Name: "GameID", + In: "query", + } + if v, ok := packed[key]; ok { + params.GameID = v.(OptInt32) + } + } + { + key := middleware.ParameterKey{ + Name: "Sort", + In: "query", + } + if v, ok := packed[key]; ok { + params.Sort = v.(OptInt32) + } + } + return params +} + +func decodeListMapsParams(args [0]string, argsEscaped bool, r *http.Request) (params ListMapsParams, _ error) { + q := uri.NewQueryDecoder(r.URL.Query()) + // Decode query: Page. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "Page", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + params.Page = c + return nil + }); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 1, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.Page)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return err + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "Page", + In: "query", + Err: err, + } + } + // Decode query: Limit. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "Limit", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + params.Limit = c + return nil + }); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 1, + MaxSet: true, + Max: 100, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.Limit)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return err + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "Limit", + In: "query", + Err: err, + } + } + // Decode query: DisplayName. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "DisplayName", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotDisplayNameVal string + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + paramsDotDisplayNameVal = c + return nil + }(); err != nil { + return err + } + params.DisplayName.SetTo(paramsDotDisplayNameVal) + return nil + }); err != nil { + return err + } + if err := func() error { + if value, ok := params.DisplayName.Get(); ok { + if err := func() error { + if err := (validate.String{ + MinLength: 0, + MinLengthSet: false, + MaxLength: 128, + MaxLengthSet: true, + Email: false, + Hostname: false, + Regex: nil, + }).Validate(string(value)); err != nil { + return errors.Wrap(err, "string") + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "DisplayName", + In: "query", + Err: err, + } + } + // Decode query: Creator. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "Creator", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotCreatorVal string + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + paramsDotCreatorVal = c + return nil + }(); err != nil { + return err + } + params.Creator.SetTo(paramsDotCreatorVal) + return nil + }); err != nil { + return err + } + if err := func() error { + if value, ok := params.Creator.Get(); ok { + if err := func() error { + if err := (validate.String{ + MinLength: 0, + MinLengthSet: false, + MaxLength: 128, + MaxLengthSet: true, + Email: false, + Hostname: false, + Regex: nil, + }).Validate(string(value)); err != nil { + return errors.Wrap(err, "string") + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "Creator", + In: "query", + Err: err, + } + } + // Decode query: GameID. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "GameID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotGameIDVal int32 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + paramsDotGameIDVal = c + return nil + }(); err != nil { + return err + } + params.GameID.SetTo(paramsDotGameIDVal) + return nil + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "GameID", + In: "query", + Err: err, + } + } + // Decode query: Sort. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "Sort", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotSortVal int32 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + paramsDotSortVal = c + return nil + }(); err != nil { + return err + } + params.Sort.SetTo(paramsDotSortVal) + return nil + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "Sort", + In: "query", + Err: err, + } + } + return params, nil +} + // ListScriptPolicyParams is parameters of listScriptPolicy operation. type ListScriptPolicyParams struct { Page int32 diff --git a/pkg/api/oas_response_decoders_gen.go b/pkg/api/oas_response_decoders_gen.go index 828a516..6a6b258 100644 --- a/pkg/api/oas_response_decoders_gen.go +++ b/pkg/api/oas_response_decoders_gen.go @@ -1367,6 +1367,98 @@ func decodeDeleteScriptPolicyResponse(resp *http.Response) (res *DeleteScriptPol return res, errors.Wrap(defRes, "error") } +func decodeGetMapResponse(resp *http.Response) (res *Map, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Map + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + func decodeGetMapfixResponse(resp *http.Response) (res *Mapfix, _ error) { switch resp.StatusCode { case 200: @@ -1852,6 +1944,123 @@ func decodeListMapfixesResponse(resp *http.Response) (res []Mapfix, _ error) { return res, errors.Wrap(defRes, "error") } +func decodeListMapsResponse(resp *http.Response) (res []Map, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response []Map + if err := func() error { + response = make([]Map, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem Map + if err := elem.Decode(d); err != nil { + return err + } + response = append(response, elem) + return nil + }); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if response == nil { + return errors.New("nil is invalid value") + } + var failures []validate.FieldError + for i, elem := range response { + if err := func() error { + if err := elem.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: fmt.Sprintf("[%d]", i), + Error: err, + }) + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + func decodeListScriptPolicyResponse(resp *http.Response) (res []ScriptPolicy, _ error) { switch resp.StatusCode { case 200: diff --git a/pkg/api/oas_response_encoders_gen.go b/pkg/api/oas_response_encoders_gen.go index 692f548..ba6c549 100644 --- a/pkg/api/oas_response_encoders_gen.go +++ b/pkg/api/oas_response_encoders_gen.go @@ -209,6 +209,20 @@ func encodeDeleteScriptPolicyResponse(response *DeleteScriptPolicyNoContent, w h return nil } +func encodeGetMapResponse(response *Map, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil +} + func encodeGetMapfixResponse(response *Mapfix, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(200) @@ -283,6 +297,24 @@ func encodeListMapfixesResponse(response []Mapfix, w http.ResponseWriter, span t return nil } +func encodeListMapsResponse(response []Map, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + e.ArrStart() + for _, elem := range response { + elem.Encode(e) + } + e.ArrEnd() + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil +} + func encodeListScriptPolicyResponse(response []ScriptPolicy, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(200) diff --git a/pkg/api/oas_router_gen.go b/pkg/api/oas_router_gen.go index fb0e008..6ebc1ef 100644 --- a/pkg/api/oas_router_gen.go +++ b/pkg/api/oas_router_gen.go @@ -61,50 +61,397 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { break } switch elem[0] { - case 'm': // Prefix: "mapfixes" + case 'm': // Prefix: "map" - if l := len("mapfixes"); len(elem) >= l && elem[0:l] == "mapfixes" { + if l := len("map"); len(elem) >= l && elem[0:l] == "map" { elem = elem[l:] } else { break } if len(elem) == 0 { - switch r.Method { - case "GET": - s.handleListMapfixesRequest([0]string{}, elemIsEscaped, w, r) - case "POST": - s.handleCreateMapfixRequest([0]string{}, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "GET,POST") - } - - return + break } switch elem[0] { - case '/': // Prefix: "/" + case 'f': // Prefix: "fixes" - if l := len("/"); len(elem) >= l && elem[0:l] == "/" { + if l := len("fixes"); len(elem) >= l && elem[0:l] == "fixes" { elem = elem[l:] } else { break } - // Param: "MapfixID" - // Match until "/" - idx := strings.IndexByte(elem, '/') - if idx < 0 { - idx = len(elem) + if len(elem) == 0 { + switch r.Method { + case "GET": + s.handleListMapfixesRequest([0]string{}, elemIsEscaped, w, r) + case "POST": + s.handleCreateMapfixRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "GET,POST") + } + + return + } + switch elem[0] { + case '/': // Prefix: "/" + + if l := len("/"); len(elem) >= l && elem[0:l] == "/" { + elem = elem[l:] + } else { + break + } + + // Param: "MapfixID" + // Match until "/" + idx := strings.IndexByte(elem, '/') + if idx < 0 { + idx = len(elem) + } + args[0] = elem[:idx] + elem = elem[idx:] + + if len(elem) == 0 { + switch r.Method { + case "GET": + s.handleGetMapfixRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "GET") + } + + return + } + switch elem[0] { + case '/': // Prefix: "/" + + if l := len("/"); len(elem) >= l && elem[0:l] == "/" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'c': // Prefix: "completed" + + if l := len("completed"); len(elem) >= l && elem[0:l] == "completed" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleSetMapfixCompletedRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + case 'm': // Prefix: "model" + + if l := len("model"); len(elem) >= l && elem[0:l] == "model" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleUpdateMapfixModelRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + case 's': // Prefix: "status/" + + if l := len("status/"); len(elem) >= l && elem[0:l] == "status/" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'r': // Prefix: "re" + + if l := len("re"); len(elem) >= l && elem[0:l] == "re" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'j': // Prefix: "ject" + + if l := len("ject"); len(elem) >= l && elem[0:l] == "ject" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionMapfixRejectRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + case 'q': // Prefix: "quest-changes" + + if l := len("quest-changes"); len(elem) >= l && elem[0:l] == "quest-changes" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionMapfixRequestChangesRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + case 's': // Prefix: "set-" + + if l := len("set-"); len(elem) >= l && elem[0:l] == "set-" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'u': // Prefix: "uploading" + + if l := len("uploading"); len(elem) >= l && elem[0:l] == "uploading" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionMapfixValidatedRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + case 'v': // Prefix: "validating" + + if l := len("validating"); len(elem) >= l && elem[0:l] == "validating" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionMapfixAcceptedRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + } + + case 't': // Prefix: "try-validate" + + if l := len("try-validate"); len(elem) >= l && elem[0:l] == "try-validate" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionMapfixRetryValidateRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + case 'v': // Prefix: "voke" + + if l := len("voke"); len(elem) >= l && elem[0:l] == "voke" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionMapfixRevokeRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + } + + case 's': // Prefix: "submit" + + if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionMapfixSubmitRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + case 't': // Prefix: "trigger-" + + if l := len("trigger-"); len(elem) >= l && elem[0:l] == "trigger-" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'u': // Prefix: "upload" + + if l := len("upload"); len(elem) >= l && elem[0:l] == "upload" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionMapfixTriggerUploadRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + case 'v': // Prefix: "validate" + + if l := len("validate"); len(elem) >= l && elem[0:l] == "validate" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionMapfixTriggerValidateRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + } + + } + + } + + } + + } + + case 's': // Prefix: "s" + + if l := len("s"); len(elem) >= l && elem[0:l] == "s" { + elem = elem[l:] + } else { + break } - args[0] = elem[:idx] - elem = elem[idx:] if len(elem) == 0 { switch r.Method { case "GET": - s.handleGetMapfixRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) + s.handleListMapsRequest([0]string{}, elemIsEscaped, w, r) default: s.notAllowed(w, r, "GET") } @@ -120,308 +467,27 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { break } - if len(elem) == 0 { + // Param: "MapID" + // Leaf parameter, slashes are prohibited + idx := strings.IndexByte(elem, '/') + if idx >= 0 { break } - switch elem[0] { - case 'c': // Prefix: "completed" - - if l := len("completed"); len(elem) >= l && elem[0:l] == "completed" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleSetMapfixCompletedRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return - } - - case 'm': // Prefix: "model" - - if l := len("model"); len(elem) >= l && elem[0:l] == "model" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleUpdateMapfixModelRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return - } - - case 's': // Prefix: "status/" - - if l := len("status/"); len(elem) >= l && elem[0:l] == "status/" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - break - } - switch elem[0] { - case 'r': // Prefix: "re" - - if l := len("re"); len(elem) >= l && elem[0:l] == "re" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - break - } - switch elem[0] { - case 'j': // Prefix: "ject" - - if l := len("ject"); len(elem) >= l && elem[0:l] == "ject" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleActionMapfixRejectRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return - } - - case 'q': // Prefix: "quest-changes" - - if l := len("quest-changes"); len(elem) >= l && elem[0:l] == "quest-changes" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleActionMapfixRequestChangesRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return - } - - case 's': // Prefix: "set-" - - if l := len("set-"); len(elem) >= l && elem[0:l] == "set-" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - break - } - switch elem[0] { - case 'u': // Prefix: "uploading" - - if l := len("uploading"); len(elem) >= l && elem[0:l] == "uploading" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleActionMapfixValidatedRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return - } - - case 'v': // Prefix: "validating" - - if l := len("validating"); len(elem) >= l && elem[0:l] == "validating" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleActionMapfixAcceptedRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return - } - - } - - case 't': // Prefix: "try-validate" - - if l := len("try-validate"); len(elem) >= l && elem[0:l] == "try-validate" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleActionMapfixRetryValidateRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return - } - - case 'v': // Prefix: "voke" - - if l := len("voke"); len(elem) >= l && elem[0:l] == "voke" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleActionMapfixRevokeRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return - } - - } - - case 's': // Prefix: "submit" - - if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleActionMapfixSubmitRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return - } - - case 't': // Prefix: "trigger-" - - if l := len("trigger-"); len(elem) >= l && elem[0:l] == "trigger-" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - break - } - switch elem[0] { - case 'u': // Prefix: "upload" - - if l := len("upload"); len(elem) >= l && elem[0:l] == "upload" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleActionMapfixTriggerUploadRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return - } - - case 'v': // Prefix: "validate" - - if l := len("validate"); len(elem) >= l && elem[0:l] == "validate" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleActionMapfixTriggerValidateRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return - } - - } + args[0] = elem + elem = "" + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "GET": + s.handleGetMapRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "GET") } + return } } @@ -1135,63 +1201,43 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { break } switch elem[0] { - case 'm': // Prefix: "mapfixes" + case 'm': // Prefix: "map" - if l := len("mapfixes"); len(elem) >= l && elem[0:l] == "mapfixes" { + if l := len("map"); len(elem) >= l && elem[0:l] == "map" { elem = elem[l:] } else { break } if len(elem) == 0 { - switch method { - case "GET": - r.name = ListMapfixesOperation - r.summary = "Get list of mapfixes" - r.operationID = "listMapfixes" - r.pathPattern = "/mapfixes" - r.args = args - r.count = 0 - return r, true - case "POST": - r.name = CreateMapfixOperation - r.summary = "Create new mapfix" - r.operationID = "createMapfix" - r.pathPattern = "/mapfixes" - r.args = args - r.count = 0 - return r, true - default: - return - } + break } switch elem[0] { - case '/': // Prefix: "/" + case 'f': // Prefix: "fixes" - if l := len("/"); len(elem) >= l && elem[0:l] == "/" { + if l := len("fixes"); len(elem) >= l && elem[0:l] == "fixes" { elem = elem[l:] } else { break } - // Param: "MapfixID" - // Match until "/" - idx := strings.IndexByte(elem, '/') - if idx < 0 { - idx = len(elem) - } - args[0] = elem[:idx] - elem = elem[idx:] - if len(elem) == 0 { switch method { case "GET": - r.name = GetMapfixOperation - r.summary = "Retrieve map with ID" - r.operationID = "getMapfix" - r.pathPattern = "/mapfixes/{MapfixID}" + r.name = ListMapfixesOperation + r.summary = "Get list of mapfixes" + r.operationID = "listMapfixes" + r.pathPattern = "/mapfixes" r.args = args - r.count = 1 + r.count = 0 + return r, true + case "POST": + r.name = CreateMapfixOperation + r.summary = "Create new mapfix" + r.operationID = "createMapfix" + r.pathPattern = "/mapfixes" + r.args = args + r.count = 0 return r, true default: return @@ -1206,61 +1252,33 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { break } + // Param: "MapfixID" + // Match until "/" + idx := strings.IndexByte(elem, '/') + if idx < 0 { + idx = len(elem) + } + args[0] = elem[:idx] + elem = elem[idx:] + if len(elem) == 0 { - break + switch method { + case "GET": + r.name = GetMapfixOperation + r.summary = "Retrieve map with ID" + r.operationID = "getMapfix" + r.pathPattern = "/mapfixes/{MapfixID}" + r.args = args + r.count = 1 + return r, true + default: + return + } } switch elem[0] { - case 'c': // Prefix: "completed" + case '/': // Prefix: "/" - if l := len("completed"); len(elem) >= l && elem[0:l] == "completed" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch method { - case "POST": - r.name = SetMapfixCompletedOperation - r.summary = "Called by maptest when a player completes the map" - r.operationID = "setMapfixCompleted" - r.pathPattern = "/mapfixes/{MapfixID}/completed" - r.args = args - r.count = 1 - return r, true - default: - return - } - } - - case 'm': // Prefix: "model" - - if l := len("model"); len(elem) >= l && elem[0:l] == "model" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch method { - case "POST": - r.name = UpdateMapfixModelOperation - r.summary = "Update model following role restrictions" - r.operationID = "updateMapfixModel" - r.pathPattern = "/mapfixes/{MapfixID}/model" - r.args = args - r.count = 1 - return r, true - default: - return - } - } - - case 's': // Prefix: "status/" - - if l := len("status/"); len(elem) >= l && elem[0:l] == "status/" { + if l := len("/"); len(elem) >= l && elem[0:l] == "/" { elem = elem[l:] } else { break @@ -1270,181 +1288,9 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { break } switch elem[0] { - case 'r': // Prefix: "re" + case 'c': // Prefix: "completed" - if l := len("re"); len(elem) >= l && elem[0:l] == "re" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - break - } - switch elem[0] { - case 'j': // Prefix: "ject" - - if l := len("ject"); len(elem) >= l && elem[0:l] == "ject" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch method { - case "POST": - r.name = ActionMapfixRejectOperation - r.summary = "Role Reviewer changes status from Submitted -> Rejected" - r.operationID = "actionMapfixReject" - r.pathPattern = "/mapfixes/{MapfixID}/status/reject" - r.args = args - r.count = 1 - return r, true - default: - return - } - } - - case 'q': // Prefix: "quest-changes" - - if l := len("quest-changes"); len(elem) >= l && elem[0:l] == "quest-changes" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch method { - case "POST": - r.name = ActionMapfixRequestChangesOperation - r.summary = "Role Reviewer changes status from Validated|Accepted|Submitted -> ChangesRequested" - r.operationID = "actionMapfixRequestChanges" - r.pathPattern = "/mapfixes/{MapfixID}/status/request-changes" - r.args = args - r.count = 1 - return r, true - default: - return - } - } - - case 's': // Prefix: "set-" - - if l := len("set-"); len(elem) >= l && elem[0:l] == "set-" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - break - } - switch elem[0] { - case 'u': // Prefix: "uploading" - - if l := len("uploading"); len(elem) >= l && elem[0:l] == "uploading" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch method { - case "POST": - r.name = ActionMapfixValidatedOperation - r.summary = "Role Admin manually resets uploading softlock and changes status from Uploading -> Validated" - r.operationID = "actionMapfixValidated" - r.pathPattern = "/mapfixes/{MapfixID}/status/reset-uploading" - r.args = args - r.count = 1 - return r, true - default: - return - } - } - - case 'v': // Prefix: "validating" - - if l := len("validating"); len(elem) >= l && elem[0:l] == "validating" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch method { - case "POST": - r.name = ActionMapfixAcceptedOperation - r.summary = "Role Reviewer manually resets validating softlock and changes status from Validating -> Accepted" - r.operationID = "actionMapfixAccepted" - r.pathPattern = "/mapfixes/{MapfixID}/status/reset-validating" - r.args = args - r.count = 1 - return r, true - default: - return - } - } - - } - - case 't': // Prefix: "try-validate" - - if l := len("try-validate"); len(elem) >= l && elem[0:l] == "try-validate" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch method { - case "POST": - r.name = ActionMapfixRetryValidateOperation - r.summary = "Role Reviewer re-runs validation and changes status from Accepted -> Validating" - r.operationID = "actionMapfixRetryValidate" - r.pathPattern = "/mapfixes/{MapfixID}/status/retry-validate" - r.args = args - r.count = 1 - return r, true - default: - return - } - } - - case 'v': // Prefix: "voke" - - if l := len("voke"); len(elem) >= l && elem[0:l] == "voke" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch method { - case "POST": - r.name = ActionMapfixRevokeOperation - r.summary = "Role Submitter changes status from Submitted|ChangesRequested -> UnderConstruction" - r.operationID = "actionMapfixRevoke" - r.pathPattern = "/mapfixes/{MapfixID}/status/revoke" - r.args = args - r.count = 1 - return r, true - default: - return - } - } - - } - - case 's': // Prefix: "submit" - - if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" { + if l := len("completed"); len(elem) >= l && elem[0:l] == "completed" { elem = elem[l:] } else { break @@ -1454,10 +1300,10 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { // Leaf node. switch method { case "POST": - r.name = ActionMapfixSubmitOperation - r.summary = "Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted" - r.operationID = "actionMapfixSubmit" - r.pathPattern = "/mapfixes/{MapfixID}/status/submit" + r.name = SetMapfixCompletedOperation + r.summary = "Called by maptest when a player completes the map" + r.operationID = "setMapfixCompleted" + r.pathPattern = "/mapfixes/{MapfixID}/completed" r.args = args r.count = 1 return r, true @@ -1466,9 +1312,33 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { } } - case 't': // Prefix: "trigger-" + case 'm': // Prefix: "model" - if l := len("trigger-"); len(elem) >= l && elem[0:l] == "trigger-" { + if l := len("model"); len(elem) >= l && elem[0:l] == "model" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = UpdateMapfixModelOperation + r.summary = "Update model following role restrictions" + r.operationID = "updateMapfixModel" + r.pathPattern = "/mapfixes/{MapfixID}/model" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + case 's': // Prefix: "status/" + + if l := len("status/"); len(elem) >= l && elem[0:l] == "status/" { elem = elem[l:] } else { break @@ -1478,9 +1348,181 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { break } switch elem[0] { - case 'u': // Prefix: "upload" + case 'r': // Prefix: "re" - if l := len("upload"); len(elem) >= l && elem[0:l] == "upload" { + if l := len("re"); len(elem) >= l && elem[0:l] == "re" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'j': // Prefix: "ject" + + if l := len("ject"); len(elem) >= l && elem[0:l] == "ject" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionMapfixRejectOperation + r.summary = "Role Reviewer changes status from Submitted -> Rejected" + r.operationID = "actionMapfixReject" + r.pathPattern = "/mapfixes/{MapfixID}/status/reject" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + case 'q': // Prefix: "quest-changes" + + if l := len("quest-changes"); len(elem) >= l && elem[0:l] == "quest-changes" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionMapfixRequestChangesOperation + r.summary = "Role Reviewer changes status from Validated|Accepted|Submitted -> ChangesRequested" + r.operationID = "actionMapfixRequestChanges" + r.pathPattern = "/mapfixes/{MapfixID}/status/request-changes" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + case 's': // Prefix: "set-" + + if l := len("set-"); len(elem) >= l && elem[0:l] == "set-" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'u': // Prefix: "uploading" + + if l := len("uploading"); len(elem) >= l && elem[0:l] == "uploading" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionMapfixValidatedOperation + r.summary = "Role Admin manually resets uploading softlock and changes status from Uploading -> Validated" + r.operationID = "actionMapfixValidated" + r.pathPattern = "/mapfixes/{MapfixID}/status/reset-uploading" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + case 'v': // Prefix: "validating" + + if l := len("validating"); len(elem) >= l && elem[0:l] == "validating" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionMapfixAcceptedOperation + r.summary = "Role Reviewer manually resets validating softlock and changes status from Validating -> Accepted" + r.operationID = "actionMapfixAccepted" + r.pathPattern = "/mapfixes/{MapfixID}/status/reset-validating" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + } + + case 't': // Prefix: "try-validate" + + if l := len("try-validate"); len(elem) >= l && elem[0:l] == "try-validate" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionMapfixRetryValidateOperation + r.summary = "Role Reviewer re-runs validation and changes status from Accepted -> Validating" + r.operationID = "actionMapfixRetryValidate" + r.pathPattern = "/mapfixes/{MapfixID}/status/retry-validate" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + case 'v': // Prefix: "voke" + + if l := len("voke"); len(elem) >= l && elem[0:l] == "voke" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionMapfixRevokeOperation + r.summary = "Role Submitter changes status from Submitted|ChangesRequested -> UnderConstruction" + r.operationID = "actionMapfixRevoke" + r.pathPattern = "/mapfixes/{MapfixID}/status/revoke" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + } + + case 's': // Prefix: "submit" + + if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" { elem = elem[l:] } else { break @@ -1490,10 +1532,10 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { // Leaf node. switch method { case "POST": - r.name = ActionMapfixTriggerUploadOperation - r.summary = "Role Admin changes status from Validated -> Uploading" - r.operationID = "actionMapfixTriggerUpload" - r.pathPattern = "/mapfixes/{MapfixID}/status/trigger-upload" + r.name = ActionMapfixSubmitOperation + r.summary = "Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted" + r.operationID = "actionMapfixSubmit" + r.pathPattern = "/mapfixes/{MapfixID}/status/submit" r.args = args r.count = 1 return r, true @@ -1502,28 +1544,66 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { } } - case 'v': // Prefix: "validate" + case 't': // Prefix: "trigger-" - if l := len("validate"); len(elem) >= l && elem[0:l] == "validate" { + if l := len("trigger-"); len(elem) >= l && elem[0:l] == "trigger-" { elem = elem[l:] } else { break } if len(elem) == 0 { - // Leaf node. - switch method { - case "POST": - r.name = ActionMapfixTriggerValidateOperation - r.summary = "Role Reviewer triggers validation and changes status from Submitted -> Validating" - r.operationID = "actionMapfixTriggerValidate" - r.pathPattern = "/mapfixes/{MapfixID}/status/trigger-validate" - r.args = args - r.count = 1 - return r, true - default: - return + break + } + switch elem[0] { + case 'u': // Prefix: "upload" + + if l := len("upload"); len(elem) >= l && elem[0:l] == "upload" { + elem = elem[l:] + } else { + break } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionMapfixTriggerUploadOperation + r.summary = "Role Admin changes status from Validated -> Uploading" + r.operationID = "actionMapfixTriggerUpload" + r.pathPattern = "/mapfixes/{MapfixID}/status/trigger-upload" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + case 'v': // Prefix: "validate" + + if l := len("validate"); len(elem) >= l && elem[0:l] == "validate" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionMapfixTriggerValidateOperation + r.summary = "Role Reviewer triggers validation and changes status from Submitted -> Validating" + r.operationID = "actionMapfixTriggerValidate" + r.pathPattern = "/mapfixes/{MapfixID}/status/trigger-validate" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + } } @@ -1534,6 +1614,64 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { } + case 's': // Prefix: "s" + + if l := len("s"); len(elem) >= l && elem[0:l] == "s" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + switch method { + case "GET": + r.name = ListMapsOperation + r.summary = "Get list of maps" + r.operationID = "listMaps" + r.pathPattern = "/maps" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + switch elem[0] { + case '/': // Prefix: "/" + + if l := len("/"); len(elem) >= l && elem[0:l] == "/" { + elem = elem[l:] + } else { + break + } + + // Param: "MapID" + // Leaf parameter, slashes are prohibited + idx := strings.IndexByte(elem, '/') + if idx >= 0 { + break + } + args[0] = elem + elem = "" + + if len(elem) == 0 { + // Leaf node. + switch method { + case "GET": + r.name = GetMapOperation + r.summary = "Retrieve map with ID" + r.operationID = "getMap" + r.pathPattern = "/maps/{MapID}" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + } + } case 'r': // Prefix: "release-submissions" diff --git a/pkg/api/oas_schemas_gen.go b/pkg/api/oas_schemas_gen.go index 9fd7e10..4c368c2 100644 --- a/pkg/api/oas_schemas_gen.go +++ b/pkg/api/oas_schemas_gen.go @@ -153,6 +153,65 @@ func (s *ID) SetID(val int64) { s.ID = val } +// Ref: #/components/schemas/Map +type Map struct { + ID int64 `json:"ID"` + DisplayName string `json:"DisplayName"` + Creator string `json:"Creator"` + GameID int32 `json:"GameID"` + Date int64 `json:"Date"` +} + +// GetID returns the value of ID. +func (s *Map) GetID() int64 { + return s.ID +} + +// GetDisplayName returns the value of DisplayName. +func (s *Map) GetDisplayName() string { + return s.DisplayName +} + +// GetCreator returns the value of Creator. +func (s *Map) GetCreator() string { + return s.Creator +} + +// GetGameID returns the value of GameID. +func (s *Map) GetGameID() int32 { + return s.GameID +} + +// GetDate returns the value of Date. +func (s *Map) GetDate() int64 { + return s.Date +} + +// SetID sets the value of ID. +func (s *Map) SetID(val int64) { + s.ID = val +} + +// SetDisplayName sets the value of DisplayName. +func (s *Map) SetDisplayName(val string) { + s.DisplayName = val +} + +// SetCreator sets the value of Creator. +func (s *Map) SetCreator(val string) { + s.Creator = val +} + +// SetGameID sets the value of GameID. +func (s *Map) SetGameID(val int32) { + s.GameID = val +} + +// SetDate sets the value of Date. +func (s *Map) SetDate(val int64) { + s.Date = val +} + // Ref: #/components/schemas/Mapfix type Mapfix struct { ID int64 `json:"ID"` diff --git a/pkg/api/oas_server_gen.go b/pkg/api/oas_server_gen.go index 8b0b85c..9f294eb 100644 --- a/pkg/api/oas_server_gen.go +++ b/pkg/api/oas_server_gen.go @@ -152,6 +152,12 @@ type Handler interface { // // DELETE /script-policy/{ScriptPolicyID} DeleteScriptPolicy(ctx context.Context, params DeleteScriptPolicyParams) error + // GetMap implements getMap operation. + // + // Retrieve map with ID. + // + // GET /maps/{MapID} + GetMap(ctx context.Context, params GetMapParams) (*Map, error) // GetMapfix implements getMapfix operation. // // Retrieve map with ID. @@ -182,6 +188,12 @@ type Handler interface { // // GET /mapfixes ListMapfixes(ctx context.Context, params ListMapfixesParams) ([]Mapfix, error) + // ListMaps implements listMaps operation. + // + // Get list of maps. + // + // GET /maps + ListMaps(ctx context.Context, params ListMapsParams) ([]Map, error) // ListScriptPolicy implements listScriptPolicy operation. // // Get list of script policies. diff --git a/pkg/api/oas_unimplemented_gen.go b/pkg/api/oas_unimplemented_gen.go index 78953e1..738f398 100644 --- a/pkg/api/oas_unimplemented_gen.go +++ b/pkg/api/oas_unimplemented_gen.go @@ -229,6 +229,15 @@ func (UnimplementedHandler) DeleteScriptPolicy(ctx context.Context, params Delet return ht.ErrNotImplemented } +// GetMap implements getMap operation. +// +// Retrieve map with ID. +// +// GET /maps/{MapID} +func (UnimplementedHandler) GetMap(ctx context.Context, params GetMapParams) (r *Map, _ error) { + return r, ht.ErrNotImplemented +} + // GetMapfix implements getMapfix operation. // // Retrieve map with ID. @@ -274,6 +283,15 @@ func (UnimplementedHandler) ListMapfixes(ctx context.Context, params ListMapfixe return r, ht.ErrNotImplemented } +// ListMaps implements listMaps operation. +// +// Get list of maps. +// +// GET /maps +func (UnimplementedHandler) ListMaps(ctx context.Context, params ListMapsParams) (r []Map, _ error) { + return r, ht.ErrNotImplemented +} + // ListScriptPolicy implements listScriptPolicy operation. // // Get list of script policies. diff --git a/pkg/api/oas_validators_gen.go b/pkg/api/oas_validators_gen.go index 7aca2dd..4dc04e6 100644 --- a/pkg/api/oas_validators_gen.go +++ b/pkg/api/oas_validators_gen.go @@ -8,6 +8,56 @@ import ( "github.com/ogen-go/ogen/validate" ) +func (s *Map) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := (validate.String{ + MinLength: 0, + MinLengthSet: false, + MaxLength: 128, + MaxLengthSet: true, + Email: false, + Hostname: false, + Regex: nil, + }).Validate(string(s.DisplayName)); err != nil { + return errors.Wrap(err, "string") + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "DisplayName", + Error: err, + }) + } + if err := func() error { + if err := (validate.String{ + MinLength: 0, + MinLengthSet: false, + MaxLength: 128, + MaxLengthSet: true, + Email: false, + Hostname: false, + Regex: nil, + }).Validate(string(s.Creator)); err != nil { + return errors.Wrap(err, "string") + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "Creator", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + func (s *Mapfix) Validate() error { if s == nil { return validate.ErrNilPointer -- 2.49.1 From e9f79241f10c4ab9f8da1273ff182c3bac8a9481 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 1 Apr 2025 16:48:49 -0700 Subject: [PATCH 252/454] submissions: maps endpoints --- pkg/service/maps.go | 73 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 pkg/service/maps.go diff --git a/pkg/service/maps.go b/pkg/service/maps.go new file mode 100644 index 0000000..f789da6 --- /dev/null +++ b/pkg/service/maps.go @@ -0,0 +1,73 @@ +package service + +import ( + "context" + + "git.itzana.me/strafesnet/go-grpc/maps" + "git.itzana.me/strafesnet/maps-service/pkg/api" +) + +// ListMaps implements listMaps operation. +// +// Get list of maps. +// +// GET /maps +func (svc *Service) ListMaps(ctx context.Context, params api.ListMapsParams) ([]api.Map, error) { + filter := maps.MapFilter{} + + if params.DisplayName.IsSet(){ + filter.DisplayName = ¶ms.DisplayName.Value + } + if params.Creator.IsSet(){ + filter.Creator = ¶ms.Creator.Value + } + if params.GameID.IsSet(){ + filter.GameID = ¶ms.GameID.Value + } + + mapList, err := svc.Client.List(ctx, &maps.ListRequest{ + Filter: &filter, + Page: &maps.Pagination{ + Size: params.Limit, + Number: params.Page, + }, + }) + if err != nil { + return nil, err + } + + var resp []api.Map + for _, item := range mapList.Maps { + resp = append(resp, api.Map{ + ID: item.ID, + DisplayName: item.DisplayName, + Creator: item.Creator, + GameID: item.GameID, + Date: item.Date, + }) + } + + return resp, nil +} + +// GetMap implements getScript operation. +// +// Get the specified script by ID. +// +// GET /maps/{MapID} +func (svc *Service) GetMap(ctx context.Context, params api.GetMapParams) (*api.Map, error) { + mapResponse, err := svc.Client.Get(ctx, &maps.IdMessage{ + ID: params.MapID, + }) + if err != nil { + return nil, err + } + + return &api.Map{ + ID: mapResponse.ID, + DisplayName: mapResponse.DisplayName, + Creator: mapResponse.Creator, + GameID: mapResponse.GameID, + Date: mapResponse.Date, + }, nil +} -- 2.49.1 From 8a28d6cfcfe479a0c9777253cc9cbf6298673c4b Mon Sep 17 00:00:00 2001 From: ic3w0lf Date: Wed, 2 Apr 2025 21:20:30 -0600 Subject: [PATCH 253/454] model/user thumbnails --- web/next.config.ts | 10 +-- .../_card.tsx => _components/mapCard.tsx} | 7 +- .../styles/mapCard.scss} | 6 +- web/src/app/mapfixes/(styles)/page.scss | 2 +- web/src/app/mapfixes/page.tsx | 5 +- web/src/app/submissions/(styles)/page.scss | 4 +- .../app/submissions/(styles)/page/card.scss | 87 ------------------- .../[submissionId]/(styles)/page/map.scss | 8 +- .../[submissionId]/(styles)/page/review.scss | 3 +- .../app/submissions/[submissionId]/_map.tsx | 14 --- .../submissions/[submissionId]/_mapImage.tsx | 28 ++++++ .../app/submissions/[submissionId]/page.tsx | 9 +- web/src/app/submissions/_card.tsx | 41 --------- web/src/app/submissions/page.tsx | 3 +- .../app/thumbnails/asset/[assetId]/route.ts | 69 +++++++++++++++ web/src/app/thumbnails/user/[userId]/route.ts | 55 ++++++++++++ 16 files changed, 184 insertions(+), 167 deletions(-) rename web/src/app/{mapfixes/_card.tsx => _components/mapCard.tsx} (79%) rename web/src/app/{mapfixes/(styles)/page/card.scss => _components/styles/mapCard.scss} (93%) delete mode 100644 web/src/app/submissions/(styles)/page/card.scss delete mode 100644 web/src/app/submissions/[submissionId]/_map.tsx create mode 100644 web/src/app/submissions/[submissionId]/_mapImage.tsx delete mode 100644 web/src/app/submissions/_card.tsx create mode 100644 web/src/app/thumbnails/asset/[assetId]/route.ts create mode 100644 web/src/app/thumbnails/user/[userId]/route.ts diff --git a/web/next.config.ts b/web/next.config.ts index 4d4174c..d68a004 100644 --- a/web/next.config.ts +++ b/web/next.config.ts @@ -6,11 +6,11 @@ const nextConfig: NextConfig = { images: { remotePatterns: [ { - protocol: 'https', - hostname: 'api.ic3.space', - pathname: '/strafe/map-images/**', - port: '', - search: '', + protocol: "https", + hostname: "tr.rbxcdn.com", + pathname: "/**", + port: "", + search: "", }, ], }, diff --git a/web/src/app/mapfixes/_card.tsx b/web/src/app/_components/mapCard.tsx similarity index 79% rename from web/src/app/mapfixes/_card.tsx rename to web/src/app/_components/mapCard.tsx index a1c594a..3e4269a 100644 --- a/web/src/app/mapfixes/_card.tsx +++ b/web/src/app/_components/mapCard.tsx @@ -6,8 +6,9 @@ import { Rating } from "@mui/material"; interface SubmissionCardProps { displayName: string; assetId: number; - rating: number; + authorId: number; author: string; + rating: number; id: number; } @@ -18,7 +19,7 @@ export default function SubmissionCard(props: SubmissionCardProps) {
{/* TODO: Grab image of model */} - {props.displayName} + {props.displayName}
@@ -29,7 +30,7 @@ export default function SubmissionCard(props: SubmissionCardProps) {
- {props.author}/ + {props.author}/ {props.author}
diff --git a/web/src/app/mapfixes/(styles)/page/card.scss b/web/src/app/_components/styles/mapCard.scss similarity index 93% rename from web/src/app/mapfixes/(styles)/page/card.scss rename to web/src/app/_components/styles/mapCard.scss index dce43eb..cfcb9d2 100644 --- a/web/src/app/mapfixes/(styles)/page/card.scss +++ b/web/src/app/_components/styles/mapCard.scss @@ -1,4 +1,4 @@ -@use "../../../globals.scss"; +@use "../../globals.scss"; .submissionCard { display: flex; @@ -9,7 +9,7 @@ flex-direction: column; justify-content: space-between; height: 100%; - min-width: 180px; + min-width: 230px; max-width: 340px; } @@ -43,8 +43,6 @@ overflow: hidden; > img { - width: 100%; - height: 100%; object-fit: cover; } } diff --git a/web/src/app/mapfixes/(styles)/page.scss b/web/src/app/mapfixes/(styles)/page.scss index bf98951..daf65f9 100644 --- a/web/src/app/mapfixes/(styles)/page.scss +++ b/web/src/app/mapfixes/(styles)/page.scss @@ -1,4 +1,4 @@ -@forward "./page/card.scss"; +@forward "../../_components/styles/mapCard.scss"; @use "../../globals.scss"; diff --git a/web/src/app/mapfixes/page.tsx b/web/src/app/mapfixes/page.tsx index cf6eccb..743062d 100644 --- a/web/src/app/mapfixes/page.tsx +++ b/web/src/app/mapfixes/page.tsx @@ -2,9 +2,11 @@ import React, { useState, useEffect } from "react"; import { MapfixInfo } from "../ts/Mapfix"; -import MapfixCard from "./_card"; +import MapfixCard from "../_components/mapCard"; import Webpage from "@/app/_components/webpage"; +// TODO: MAKE MAPFIX & SUBMISSIONS USE THE SAME COMPONENTS :angry: (currently too lazy) + import "./(styles)/page.scss"; export default function MapfixInfoPage() { @@ -100,6 +102,7 @@ export default function MapfixInfoPage() { assetId={mapfix.AssetID} displayName={mapfix.DisplayName} author={mapfix.Creator} + authorId={mapfix.Submitter} rating={mapfix.StatusID} /> ))} diff --git a/web/src/app/submissions/(styles)/page.scss b/web/src/app/submissions/(styles)/page.scss index bf98951..d54fd38 100644 --- a/web/src/app/submissions/(styles)/page.scss +++ b/web/src/app/submissions/(styles)/page.scss @@ -1,4 +1,4 @@ -@forward "./page/card.scss"; +@forward "../../_components/styles/mapCard.scss"; @use "../../globals.scss"; @@ -27,7 +27,7 @@ a { @media (max-width: 768px) { .grid { - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); } } diff --git a/web/src/app/submissions/(styles)/page/card.scss b/web/src/app/submissions/(styles)/page/card.scss deleted file mode 100644 index dce43eb..0000000 --- a/web/src/app/submissions/(styles)/page/card.scss +++ /dev/null @@ -1,87 +0,0 @@ -@use "../../../globals.scss"; - -.submissionCard { - display: flex; - background-color: #2020207c; - border: 1px solid #97979783; - border-radius: 10px; - box-sizing: border-box; - flex-direction: column; - justify-content: space-between; - height: 100%; - min-width: 180px; - max-width: 340px; -} - -.content { - display: flex; - flex-grow: 1; - flex-direction: column; - justify-content: space-between; -} - -.details { - padding: 2px 4px; -} - -.header { - display: flex; - justify-content: space-between; - align-items: center; - margin: 3px 0px; -} - -.footer { - display: flex; - justify-content: space-between; - align-items: center; - margin: 3px 0px; -} - -.map-image { - border-radius: 10px 10px 0 0; - overflow: hidden; - - > img { - width: 100%; - height: 100%; - object-fit: cover; - } -} - -.displayName { - font-size: 1rem; - font-weight: bold; - overflow: hidden; - max-width: 70%; - text-overflow: ellipsis; - white-space: nowrap; -} - -.rating { - flex-shrink: 0; -} - -.author { - display: flex; - align-items: center; - gap: 0.5rem; - flex-grow: 1; - min-width: 0; -} - -.author span { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.rating { - margin-left: auto; - flex-shrink: 0; -} - -.avatar { - border-radius: 50%; - object-fit: cover; -} \ No newline at end of file diff --git a/web/src/app/submissions/[submissionId]/(styles)/page/map.scss b/web/src/app/submissions/[submissionId]/(styles)/page/map.scss index ede388e..12469f0 100644 --- a/web/src/app/submissions/[submissionId]/(styles)/page/map.scss +++ b/web/src/app/submissions/[submissionId]/(styles)/page/map.scss @@ -5,8 +5,12 @@ display: flex; justify-content: center; align-items: center; - width: 350px; - height: 350px; + width: 100%; + height: auto; + margin-left: auto; + margin-right: auto; + border-radius: 12px; + overflow: hidden; > p { text-align: center; diff --git a/web/src/app/submissions/[submissionId]/(styles)/page/review.scss b/web/src/app/submissions/[submissionId]/(styles)/page/review.scss index 08fc5c0..2ca4539 100644 --- a/web/src/app/submissions/[submissionId]/(styles)/page/review.scss +++ b/web/src/app/submissions/[submissionId]/(styles)/page/review.scss @@ -40,8 +40,7 @@ gap: 25px; img { - width: 100%; - height: 350px; + height: 100%; object-fit: contain } } \ No newline at end of file diff --git a/web/src/app/submissions/[submissionId]/_map.tsx b/web/src/app/submissions/[submissionId]/_map.tsx deleted file mode 100644 index e364f70..0000000 --- a/web/src/app/submissions/[submissionId]/_map.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { SubmissionInfo } from "@/app/ts/Submission" - -interface AssetID { - id: SubmissionInfo["AssetID"] -} - -function MapImage() { - return

Fetching map image...

-} - -export { - type AssetID, - MapImage -} \ No newline at end of file diff --git a/web/src/app/submissions/[submissionId]/_mapImage.tsx b/web/src/app/submissions/[submissionId]/_mapImage.tsx new file mode 100644 index 0000000..96f9059 --- /dev/null +++ b/web/src/app/submissions/[submissionId]/_mapImage.tsx @@ -0,0 +1,28 @@ +import Image from "next/image"; +import { SubmissionInfo } from "@/app/ts/Submission"; + +interface AssetID { + id: SubmissionInfo["AssetID"]; +} + +function MapImage({ id }: AssetID) { + if (!id) { + return

Missing asset ID

; + } + + const imageUrl = `/thumbnails/asset/${id}`; + + return ( + Map Thumbnail + ); +} + +export { type AssetID, MapImage }; diff --git a/web/src/app/submissions/[submissionId]/page.tsx b/web/src/app/submissions/[submissionId]/page.tsx index 9dae81f..c78ba8b 100644 --- a/web/src/app/submissions/[submissionId]/page.tsx +++ b/web/src/app/submissions/[submissionId]/page.tsx @@ -2,7 +2,7 @@ import { SubmissionInfo, SubmissionStatusToString } from "@/app/ts/Submission"; import type { CreatorAndReviewStatus } from "./_comments"; -import { MapImage } from "./_map"; +import { MapImage } from "./_mapImage"; import { useParams } from "next/navigation"; import ReviewButtons from "./_reviewButtons"; import { Rating } from "@mui/material"; @@ -15,7 +15,8 @@ import { useState, useEffect } from "react"; import "./(styles)/page.scss"; interface ReviewId { - submissionId: string + submissionId: string; + assetId: number; } function Ratings() { @@ -43,7 +44,7 @@ function RatingArea(submission: ReviewId) { return (
+ )} + + + ) : ( + No operation found. + )} +
+ + ); +} \ No newline at end of file diff --git a/web/src/app/submit/page.tsx b/web/src/app/submit/page.tsx index caaef78..a0b09ee 100644 --- a/web/src/app/submit/page.tsx +++ b/web/src/app/submit/page.tsx @@ -11,7 +11,7 @@ interface SubmissionPayload { AssetID: number; } interface IdResponse { - ID: number; + OperationID: number; } export default function SubmissionInfoPage() { @@ -49,7 +49,7 @@ export default function SubmissionInfoPage() { const id_response:IdResponse = await response.json(); // navigate to newly created submission - window.location.assign(`/submissions/${id_response.ID}`) + window.location.assign(`/operations/${id_response.OperationID}`) } catch (error) { console.error("Error submitting data:", error); -- 2.49.1 From 54b4cf2d139dbe4d02223cc0a7c8c4e723d20619 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 2 Apr 2025 13:57:41 -0700 Subject: [PATCH 282/454] openapi: make explicit types for returned IDs --- openapi-internal.yaml | 38 +++++++++++++++++++++++++++++++------- openapi.yaml | 28 ++++++++++++++++++---------- 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/openapi-internal.yaml b/openapi-internal.yaml index 6a74bd4..f92b03b 100644 --- a/openapi-internal.yaml +++ b/openapi-internal.yaml @@ -27,7 +27,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Id" + $ref: "#/components/schemas/MapfixID" default: description: General Error content: @@ -163,7 +163,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Id" + $ref: "#/components/schemas/SubmissionID" default: description: General Error content: @@ -320,7 +320,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Id" + $ref: "#/components/schemas/ScriptPolicyID" default: description: General Error content: @@ -394,7 +394,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Id" + $ref: "#/components/schemas/ScriptID" default: description: General Error content: @@ -474,12 +474,36 @@ components: minimum: 1 maximum: 100 schemas: - Id: + MapfixID: required: - - ID + - MapfixID type: object properties: - ID: + MapfixID: + type: integer + format: int64 + SubmissionID: + required: + - SubmissionID + type: object + properties: + SubmissionID: + type: integer + format: int64 + ScriptID: + required: + - ScriptID + type: object + properties: + ScriptID: + type: integer + format: int64 + ScriptPolicyID: + required: + - ScriptPolicyID + type: object + properties: + ScriptPolicyID: type: integer format: int64 MapfixCreate: diff --git a/openapi.yaml b/openapi.yaml index 3970378..6e4e93f 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -831,7 +831,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Id" + $ref: "#/components/schemas/ScriptPolicyID" default: description: General Error content: @@ -966,7 +966,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Id" + $ref: "#/components/schemas/ScriptID" default: description: General Error content: @@ -1106,14 +1106,6 @@ components: minimum: 1 maximum: 100 schemas: - Id: - required: - - ID - type: object - properties: - ID: - type: integer - format: int64 OperationID: required: - OperationID @@ -1122,6 +1114,22 @@ components: OperationID: type: integer format: int32 + ScriptID: + required: + - ScriptID + type: object + properties: + ScriptID: + type: integer + format: int64 + ScriptPolicyID: + required: + - ScriptPolicyID + type: object + properties: + ScriptPolicyID: + type: integer + format: int64 Roles: required: - Roles -- 2.49.1 From 1e012af52e80f4f067f80d6c60fc9cb90b7c457d Mon Sep 17 00:00:00 2001 From: Quaternions Date: Thu, 3 Apr 2025 11:08:32 -0700 Subject: [PATCH 283/454] openapi: generate --- pkg/api/oas_client_gen.go | 12 +- pkg/api/oas_handlers_gen.go | 8 +- pkg/api/oas_json_gen.go | 288 ++++++++----- pkg/api/oas_response_decoders_gen.go | 8 +- pkg/api/oas_response_encoders_gen.go | 4 +- pkg/api/oas_schemas_gen.go | 45 +- pkg/api/oas_server_gen.go | 4 +- pkg/api/oas_unimplemented_gen.go | 4 +- pkg/internal/oas_client_gen.go | 24 +- pkg/internal/oas_handlers_gen.go | 16 +- pkg/internal/oas_json_gen.go | 480 +++++++++++++++++----- pkg/internal/oas_response_decoders_gen.go | 16 +- pkg/internal/oas_response_encoders_gen.go | 8 +- pkg/internal/oas_schemas_gen.go | 75 +++- pkg/internal/oas_server_gen.go | 8 +- pkg/internal/oas_unimplemented_gen.go | 8 +- 16 files changed, 726 insertions(+), 282 deletions(-) diff --git a/pkg/api/oas_client_gen.go b/pkg/api/oas_client_gen.go index 3fac351..5135a88 100644 --- a/pkg/api/oas_client_gen.go +++ b/pkg/api/oas_client_gen.go @@ -148,13 +148,13 @@ type Invoker interface { // Create a new script. // // POST /scripts - CreateScript(ctx context.Context, request *ScriptCreate) (*ID, error) + CreateScript(ctx context.Context, request *ScriptCreate) (*ScriptID, error) // CreateScriptPolicy invokes createScriptPolicy operation. // // Create a new script policy. // // POST /script-policy - CreateScriptPolicy(ctx context.Context, request *ScriptPolicyCreate) (*ID, error) + CreateScriptPolicy(ctx context.Context, request *ScriptPolicyCreate) (*ScriptPolicyID, error) // CreateSubmission invokes createSubmission operation. // // Trigger the validator to create a new submission. @@ -2695,12 +2695,12 @@ func (c *Client) sendCreateMapfix(ctx context.Context, request *MapfixTriggerCre // Create a new script. // // POST /scripts -func (c *Client) CreateScript(ctx context.Context, request *ScriptCreate) (*ID, error) { +func (c *Client) CreateScript(ctx context.Context, request *ScriptCreate) (*ScriptID, error) { res, err := c.sendCreateScript(ctx, request) return res, err } -func (c *Client) sendCreateScript(ctx context.Context, request *ScriptCreate) (res *ID, err error) { +func (c *Client) sendCreateScript(ctx context.Context, request *ScriptCreate) (res *ScriptID, err error) { otelAttrs := []attribute.KeyValue{ otelogen.OperationID("createScript"), semconv.HTTPRequestMethodKey.String("POST"), @@ -2803,12 +2803,12 @@ func (c *Client) sendCreateScript(ctx context.Context, request *ScriptCreate) (r // Create a new script policy. // // POST /script-policy -func (c *Client) CreateScriptPolicy(ctx context.Context, request *ScriptPolicyCreate) (*ID, error) { +func (c *Client) CreateScriptPolicy(ctx context.Context, request *ScriptPolicyCreate) (*ScriptPolicyID, error) { res, err := c.sendCreateScriptPolicy(ctx, request) return res, err } -func (c *Client) sendCreateScriptPolicy(ctx context.Context, request *ScriptPolicyCreate) (res *ID, err error) { +func (c *Client) sendCreateScriptPolicy(ctx context.Context, request *ScriptPolicyCreate) (res *ScriptPolicyID, err error) { otelAttrs := []attribute.KeyValue{ otelogen.OperationID("createScriptPolicy"), semconv.HTTPRequestMethodKey.String("POST"), diff --git a/pkg/api/oas_handlers_gen.go b/pkg/api/oas_handlers_gen.go index d667263..f848f69 100644 --- a/pkg/api/oas_handlers_gen.go +++ b/pkg/api/oas_handlers_gen.go @@ -3871,7 +3871,7 @@ func (s *Server) handleCreateScriptRequest(args [0]string, argsEscaped bool, w h } }() - var response *ID + var response *ScriptID if m := s.cfg.Middleware; m != nil { mreq := middleware.Request{ Context: ctx, @@ -3886,7 +3886,7 @@ func (s *Server) handleCreateScriptRequest(args [0]string, argsEscaped bool, w h type ( Request = *ScriptCreate Params = struct{} - Response = *ID + Response = *ScriptID ) response, err = middleware.HookMiddleware[ Request, @@ -4066,7 +4066,7 @@ func (s *Server) handleCreateScriptPolicyRequest(args [0]string, argsEscaped boo } }() - var response *ID + var response *ScriptPolicyID if m := s.cfg.Middleware; m != nil { mreq := middleware.Request{ Context: ctx, @@ -4081,7 +4081,7 @@ func (s *Server) handleCreateScriptPolicyRequest(args [0]string, argsEscaped boo type ( Request = *ScriptPolicyCreate Params = struct{} - Response = *ID + Response = *ScriptPolicyID ) response, err = middleware.HookMiddleware[ Request, diff --git a/pkg/api/oas_json_gen.go b/pkg/api/oas_json_gen.go index 6833f15..4e2c532 100644 --- a/pkg/api/oas_json_gen.go +++ b/pkg/api/oas_json_gen.go @@ -126,102 +126,6 @@ func (s *Error) UnmarshalJSON(data []byte) error { return s.Decode(d) } -// Encode implements json.Marshaler. -func (s *ID) Encode(e *jx.Encoder) { - e.ObjStart() - s.encodeFields(e) - e.ObjEnd() -} - -// encodeFields encodes fields. -func (s *ID) encodeFields(e *jx.Encoder) { - { - e.FieldStart("ID") - e.Int64(s.ID) - } -} - -var jsonFieldsNameOfID = [1]string{ - 0: "ID", -} - -// Decode decodes ID from json. -func (s *ID) Decode(d *jx.Decoder) error { - if s == nil { - return errors.New("invalid: unable to decode ID to nil") - } - var requiredBitSet [1]uint8 - - if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { - switch string(k) { - case "ID": - requiredBitSet[0] |= 1 << 0 - if err := func() error { - v, err := d.Int64() - s.ID = int64(v) - if err != nil { - return err - } - return nil - }(); err != nil { - return errors.Wrap(err, "decode field \"ID\"") - } - default: - return d.Skip() - } - return nil - }); err != nil { - return errors.Wrap(err, "decode ID") - } - // Validate required fields. - var failures []validate.FieldError - for i, mask := range [1]uint8{ - 0b00000001, - } { - if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { - // Mask only required fields and check equality to mask using XOR. - // - // If XOR result is not zero, result is not equal to expected, so some fields are missed. - // Bits of fields which would be set are actually bits of missed fields. - missed := bits.OnesCount8(result) - for bitN := 0; bitN < missed; bitN++ { - bitIdx := bits.TrailingZeros8(result) - fieldIdx := i*8 + bitIdx - var name string - if fieldIdx < len(jsonFieldsNameOfID) { - name = jsonFieldsNameOfID[fieldIdx] - } else { - name = strconv.Itoa(fieldIdx) - } - failures = append(failures, validate.FieldError{ - Name: name, - Error: validate.ErrFieldRequired, - }) - // Reset bit. - result &^= 1 << bitIdx - } - } - } - if len(failures) > 0 { - return &validate.Error{Fields: failures} - } - - return nil -} - -// MarshalJSON implements stdjson.Marshaler. -func (s *ID) MarshalJSON() ([]byte, error) { - e := jx.Encoder{} - s.Encode(&e) - return e.Bytes(), nil -} - -// UnmarshalJSON implements stdjson.Unmarshaler. -func (s *ID) UnmarshalJSON(data []byte) error { - d := jx.DecodeBytes(data) - return s.Decode(d) -} - // Encode implements json.Marshaler. func (s *Map) Encode(e *jx.Encoder) { e.ObjStart() @@ -1719,6 +1623,102 @@ func (s *ScriptCreate) UnmarshalJSON(data []byte) error { return s.Decode(d) } +// Encode implements json.Marshaler. +func (s *ScriptID) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ScriptID) encodeFields(e *jx.Encoder) { + { + e.FieldStart("ScriptID") + e.Int64(s.ScriptID) + } +} + +var jsonFieldsNameOfScriptID = [1]string{ + 0: "ScriptID", +} + +// Decode decodes ScriptID from json. +func (s *ScriptID) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ScriptID to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "ScriptID": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Int64() + s.ScriptID = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"ScriptID\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ScriptID") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfScriptID) { + name = jsonFieldsNameOfScriptID[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *ScriptID) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ScriptID) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + // Encode implements json.Marshaler. func (s *ScriptPolicy) Encode(e *jx.Encoder) { e.ObjStart() @@ -1996,6 +1996,102 @@ func (s *ScriptPolicyCreate) UnmarshalJSON(data []byte) error { return s.Decode(d) } +// Encode implements json.Marshaler. +func (s *ScriptPolicyID) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ScriptPolicyID) encodeFields(e *jx.Encoder) { + { + e.FieldStart("ScriptPolicyID") + e.Int64(s.ScriptPolicyID) + } +} + +var jsonFieldsNameOfScriptPolicyID = [1]string{ + 0: "ScriptPolicyID", +} + +// Decode decodes ScriptPolicyID from json. +func (s *ScriptPolicyID) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ScriptPolicyID to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "ScriptPolicyID": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Int64() + s.ScriptPolicyID = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"ScriptPolicyID\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ScriptPolicyID") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfScriptPolicyID) { + name = jsonFieldsNameOfScriptPolicyID[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *ScriptPolicyID) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ScriptPolicyID) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + // Encode implements json.Marshaler. func (s *ScriptPolicyUpdate) Encode(e *jx.Encoder) { e.ObjStart() diff --git a/pkg/api/oas_response_decoders_gen.go b/pkg/api/oas_response_decoders_gen.go index 197eacd..c75e01d 100644 --- a/pkg/api/oas_response_decoders_gen.go +++ b/pkg/api/oas_response_decoders_gen.go @@ -1016,7 +1016,7 @@ func decodeCreateMapfixResponse(resp *http.Response) (res *OperationID, _ error) return res, errors.Wrap(defRes, "error") } -func decodeCreateScriptResponse(resp *http.Response) (res *ID, _ error) { +func decodeCreateScriptResponse(resp *http.Response) (res *ScriptID, _ error) { switch resp.StatusCode { case 201: // Code 201. @@ -1032,7 +1032,7 @@ func decodeCreateScriptResponse(resp *http.Response) (res *ID, _ error) { } d := jx.DecodeBytes(buf) - var response ID + var response ScriptID if err := func() error { if err := response.Decode(d); err != nil { return err @@ -1099,7 +1099,7 @@ func decodeCreateScriptResponse(resp *http.Response) (res *ID, _ error) { return res, errors.Wrap(defRes, "error") } -func decodeCreateScriptPolicyResponse(resp *http.Response) (res *ID, _ error) { +func decodeCreateScriptPolicyResponse(resp *http.Response) (res *ScriptPolicyID, _ error) { switch resp.StatusCode { case 201: // Code 201. @@ -1115,7 +1115,7 @@ func decodeCreateScriptPolicyResponse(resp *http.Response) (res *ID, _ error) { } d := jx.DecodeBytes(buf) - var response ID + var response ScriptPolicyID if err := func() error { if err := response.Decode(d); err != nil { return err diff --git a/pkg/api/oas_response_encoders_gen.go b/pkg/api/oas_response_encoders_gen.go index 58f61b3..24ef6b4 100644 --- a/pkg/api/oas_response_encoders_gen.go +++ b/pkg/api/oas_response_encoders_gen.go @@ -153,7 +153,7 @@ func encodeCreateMapfixResponse(response *OperationID, w http.ResponseWriter, sp return nil } -func encodeCreateScriptResponse(response *ID, w http.ResponseWriter, span trace.Span) error { +func encodeCreateScriptResponse(response *ScriptID, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(201) span.SetStatus(codes.Ok, http.StatusText(201)) @@ -167,7 +167,7 @@ func encodeCreateScriptResponse(response *ID, w http.ResponseWriter, span trace. return nil } -func encodeCreateScriptPolicyResponse(response *ID, w http.ResponseWriter, span trace.Span) error { +func encodeCreateScriptPolicyResponse(response *ScriptPolicyID, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(201) span.SetStatus(codes.Ok, http.StatusText(201)) diff --git a/pkg/api/oas_schemas_gen.go b/pkg/api/oas_schemas_gen.go index 5141d86..8e241af 100644 --- a/pkg/api/oas_schemas_gen.go +++ b/pkg/api/oas_schemas_gen.go @@ -138,21 +138,6 @@ func (s *ErrorStatusCode) SetResponse(val Error) { s.Response = val } -// Ref: #/components/schemas/Id -type ID struct { - ID int64 `json:"ID"` -} - -// GetID returns the value of ID. -func (s *ID) GetID() int64 { - return s.ID -} - -// SetID sets the value of ID. -func (s *ID) SetID(val int64) { - s.ID = val -} - // Ref: #/components/schemas/Map type Map struct { ID int64 `json:"ID"` @@ -770,6 +755,21 @@ func (s *ScriptCreate) SetResourceID(val OptInt64) { s.ResourceID = val } +// Ref: #/components/schemas/ScriptID +type ScriptID struct { + ScriptID int64 `json:"ScriptID"` +} + +// GetScriptID returns the value of ScriptID. +func (s *ScriptID) GetScriptID() int64 { + return s.ScriptID +} + +// SetScriptID sets the value of ScriptID. +func (s *ScriptID) SetScriptID(val int64) { + s.ScriptID = val +} + // Ref: #/components/schemas/ScriptPolicy type ScriptPolicy struct { ID int64 `json:"ID"` @@ -855,6 +855,21 @@ func (s *ScriptPolicyCreate) SetPolicy(val int32) { s.Policy = val } +// Ref: #/components/schemas/ScriptPolicyID +type ScriptPolicyID struct { + ScriptPolicyID int64 `json:"ScriptPolicyID"` +} + +// GetScriptPolicyID returns the value of ScriptPolicyID. +func (s *ScriptPolicyID) GetScriptPolicyID() int64 { + return s.ScriptPolicyID +} + +// SetScriptPolicyID sets the value of ScriptPolicyID. +func (s *ScriptPolicyID) SetScriptPolicyID(val int64) { + s.ScriptPolicyID = val +} + // Ref: #/components/schemas/ScriptPolicyUpdate type ScriptPolicyUpdate struct { ID int64 `json:"ID"` diff --git a/pkg/api/oas_server_gen.go b/pkg/api/oas_server_gen.go index b06f259..8a0473b 100644 --- a/pkg/api/oas_server_gen.go +++ b/pkg/api/oas_server_gen.go @@ -127,13 +127,13 @@ type Handler interface { // Create a new script. // // POST /scripts - CreateScript(ctx context.Context, req *ScriptCreate) (*ID, error) + CreateScript(ctx context.Context, req *ScriptCreate) (*ScriptID, error) // CreateScriptPolicy implements createScriptPolicy operation. // // Create a new script policy. // // POST /script-policy - CreateScriptPolicy(ctx context.Context, req *ScriptPolicyCreate) (*ID, error) + CreateScriptPolicy(ctx context.Context, req *ScriptPolicyCreate) (*ScriptPolicyID, error) // CreateSubmission implements createSubmission operation. // // Trigger the validator to create a new submission. diff --git a/pkg/api/oas_unimplemented_gen.go b/pkg/api/oas_unimplemented_gen.go index 3e92782..6bc9775 100644 --- a/pkg/api/oas_unimplemented_gen.go +++ b/pkg/api/oas_unimplemented_gen.go @@ -189,7 +189,7 @@ func (UnimplementedHandler) CreateMapfix(ctx context.Context, req *MapfixTrigger // Create a new script. // // POST /scripts -func (UnimplementedHandler) CreateScript(ctx context.Context, req *ScriptCreate) (r *ID, _ error) { +func (UnimplementedHandler) CreateScript(ctx context.Context, req *ScriptCreate) (r *ScriptID, _ error) { return r, ht.ErrNotImplemented } @@ -198,7 +198,7 @@ func (UnimplementedHandler) CreateScript(ctx context.Context, req *ScriptCreate) // Create a new script policy. // // POST /script-policy -func (UnimplementedHandler) CreateScriptPolicy(ctx context.Context, req *ScriptPolicyCreate) (r *ID, _ error) { +func (UnimplementedHandler) CreateScriptPolicy(ctx context.Context, req *ScriptPolicyCreate) (r *ScriptPolicyID, _ error) { return r, ht.ErrNotImplemented } diff --git a/pkg/internal/oas_client_gen.go b/pkg/internal/oas_client_gen.go index 21152b2..d57735f 100644 --- a/pkg/internal/oas_client_gen.go +++ b/pkg/internal/oas_client_gen.go @@ -75,25 +75,25 @@ type Invoker interface { // Create a mapfix. // // POST /mapfixes - CreateMapfix(ctx context.Context, request *MapfixCreate) (*ID, error) + CreateMapfix(ctx context.Context, request *MapfixCreate) (*MapfixID, error) // CreateScript invokes createScript operation. // // Create a new script. // // POST /scripts - CreateScript(ctx context.Context, request *ScriptCreate) (*ID, error) + CreateScript(ctx context.Context, request *ScriptCreate) (*ScriptID, error) // CreateScriptPolicy invokes createScriptPolicy operation. // // Create a new script policy. // // POST /script-policy - CreateScriptPolicy(ctx context.Context, request *ScriptPolicyCreate) (*ID, error) + CreateScriptPolicy(ctx context.Context, request *ScriptPolicyCreate) (*ScriptPolicyID, error) // CreateSubmission invokes createSubmission operation. // // Create a new submission. // // POST /submissions - CreateSubmission(ctx context.Context, request *SubmissionCreate) (*ID, error) + CreateSubmission(ctx context.Context, request *SubmissionCreate) (*SubmissionID, error) // GetScript invokes getScript operation. // // Get the specified script by ID. @@ -887,12 +887,12 @@ func (c *Client) sendActionSubmissionValidated(ctx context.Context, params Actio // Create a mapfix. // // POST /mapfixes -func (c *Client) CreateMapfix(ctx context.Context, request *MapfixCreate) (*ID, error) { +func (c *Client) CreateMapfix(ctx context.Context, request *MapfixCreate) (*MapfixID, error) { res, err := c.sendCreateMapfix(ctx, request) return res, err } -func (c *Client) sendCreateMapfix(ctx context.Context, request *MapfixCreate) (res *ID, err error) { +func (c *Client) sendCreateMapfix(ctx context.Context, request *MapfixCreate) (res *MapfixID, err error) { otelAttrs := []attribute.KeyValue{ otelogen.OperationID("createMapfix"), semconv.HTTPRequestMethodKey.String("POST"), @@ -962,12 +962,12 @@ func (c *Client) sendCreateMapfix(ctx context.Context, request *MapfixCreate) (r // Create a new script. // // POST /scripts -func (c *Client) CreateScript(ctx context.Context, request *ScriptCreate) (*ID, error) { +func (c *Client) CreateScript(ctx context.Context, request *ScriptCreate) (*ScriptID, error) { res, err := c.sendCreateScript(ctx, request) return res, err } -func (c *Client) sendCreateScript(ctx context.Context, request *ScriptCreate) (res *ID, err error) { +func (c *Client) sendCreateScript(ctx context.Context, request *ScriptCreate) (res *ScriptID, err error) { otelAttrs := []attribute.KeyValue{ otelogen.OperationID("createScript"), semconv.HTTPRequestMethodKey.String("POST"), @@ -1037,12 +1037,12 @@ func (c *Client) sendCreateScript(ctx context.Context, request *ScriptCreate) (r // Create a new script policy. // // POST /script-policy -func (c *Client) CreateScriptPolicy(ctx context.Context, request *ScriptPolicyCreate) (*ID, error) { +func (c *Client) CreateScriptPolicy(ctx context.Context, request *ScriptPolicyCreate) (*ScriptPolicyID, error) { res, err := c.sendCreateScriptPolicy(ctx, request) return res, err } -func (c *Client) sendCreateScriptPolicy(ctx context.Context, request *ScriptPolicyCreate) (res *ID, err error) { +func (c *Client) sendCreateScriptPolicy(ctx context.Context, request *ScriptPolicyCreate) (res *ScriptPolicyID, err error) { otelAttrs := []attribute.KeyValue{ otelogen.OperationID("createScriptPolicy"), semconv.HTTPRequestMethodKey.String("POST"), @@ -1112,12 +1112,12 @@ func (c *Client) sendCreateScriptPolicy(ctx context.Context, request *ScriptPoli // Create a new submission. // // POST /submissions -func (c *Client) CreateSubmission(ctx context.Context, request *SubmissionCreate) (*ID, error) { +func (c *Client) CreateSubmission(ctx context.Context, request *SubmissionCreate) (*SubmissionID, error) { res, err := c.sendCreateSubmission(ctx, request) return res, err } -func (c *Client) sendCreateSubmission(ctx context.Context, request *SubmissionCreate) (res *ID, err error) { +func (c *Client) sendCreateSubmission(ctx context.Context, request *SubmissionCreate) (res *SubmissionID, err error) { otelAttrs := []attribute.KeyValue{ otelogen.OperationID("createSubmission"), semconv.HTTPRequestMethodKey.String("POST"), diff --git a/pkg/internal/oas_handlers_gen.go b/pkg/internal/oas_handlers_gen.go index 99977b4..96b8c4b 100644 --- a/pkg/internal/oas_handlers_gen.go +++ b/pkg/internal/oas_handlers_gen.go @@ -1179,7 +1179,7 @@ func (s *Server) handleCreateMapfixRequest(args [0]string, argsEscaped bool, w h } }() - var response *ID + var response *MapfixID if m := s.cfg.Middleware; m != nil { mreq := middleware.Request{ Context: ctx, @@ -1194,7 +1194,7 @@ func (s *Server) handleCreateMapfixRequest(args [0]string, argsEscaped bool, w h type ( Request = *MapfixCreate Params = struct{} - Response = *ID + Response = *MapfixID ) response, err = middleware.HookMiddleware[ Request, @@ -1328,7 +1328,7 @@ func (s *Server) handleCreateScriptRequest(args [0]string, argsEscaped bool, w h } }() - var response *ID + var response *ScriptID if m := s.cfg.Middleware; m != nil { mreq := middleware.Request{ Context: ctx, @@ -1343,7 +1343,7 @@ func (s *Server) handleCreateScriptRequest(args [0]string, argsEscaped bool, w h type ( Request = *ScriptCreate Params = struct{} - Response = *ID + Response = *ScriptID ) response, err = middleware.HookMiddleware[ Request, @@ -1477,7 +1477,7 @@ func (s *Server) handleCreateScriptPolicyRequest(args [0]string, argsEscaped boo } }() - var response *ID + var response *ScriptPolicyID if m := s.cfg.Middleware; m != nil { mreq := middleware.Request{ Context: ctx, @@ -1492,7 +1492,7 @@ func (s *Server) handleCreateScriptPolicyRequest(args [0]string, argsEscaped boo type ( Request = *ScriptPolicyCreate Params = struct{} - Response = *ID + Response = *ScriptPolicyID ) response, err = middleware.HookMiddleware[ Request, @@ -1626,7 +1626,7 @@ func (s *Server) handleCreateSubmissionRequest(args [0]string, argsEscaped bool, } }() - var response *ID + var response *SubmissionID if m := s.cfg.Middleware; m != nil { mreq := middleware.Request{ Context: ctx, @@ -1641,7 +1641,7 @@ func (s *Server) handleCreateSubmissionRequest(args [0]string, argsEscaped bool, type ( Request = *SubmissionCreate Params = struct{} - Response = *ID + Response = *SubmissionID ) response, err = middleware.HookMiddleware[ Request, diff --git a/pkg/internal/oas_json_gen.go b/pkg/internal/oas_json_gen.go index 7ad30b5..4de742d 100644 --- a/pkg/internal/oas_json_gen.go +++ b/pkg/internal/oas_json_gen.go @@ -125,102 +125,6 @@ func (s *Error) UnmarshalJSON(data []byte) error { return s.Decode(d) } -// Encode implements json.Marshaler. -func (s *ID) Encode(e *jx.Encoder) { - e.ObjStart() - s.encodeFields(e) - e.ObjEnd() -} - -// encodeFields encodes fields. -func (s *ID) encodeFields(e *jx.Encoder) { - { - e.FieldStart("ID") - e.Int64(s.ID) - } -} - -var jsonFieldsNameOfID = [1]string{ - 0: "ID", -} - -// Decode decodes ID from json. -func (s *ID) Decode(d *jx.Decoder) error { - if s == nil { - return errors.New("invalid: unable to decode ID to nil") - } - var requiredBitSet [1]uint8 - - if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { - switch string(k) { - case "ID": - requiredBitSet[0] |= 1 << 0 - if err := func() error { - v, err := d.Int64() - s.ID = int64(v) - if err != nil { - return err - } - return nil - }(); err != nil { - return errors.Wrap(err, "decode field \"ID\"") - } - default: - return d.Skip() - } - return nil - }); err != nil { - return errors.Wrap(err, "decode ID") - } - // Validate required fields. - var failures []validate.FieldError - for i, mask := range [1]uint8{ - 0b00000001, - } { - if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { - // Mask only required fields and check equality to mask using XOR. - // - // If XOR result is not zero, result is not equal to expected, so some fields are missed. - // Bits of fields which would be set are actually bits of missed fields. - missed := bits.OnesCount8(result) - for bitN := 0; bitN < missed; bitN++ { - bitIdx := bits.TrailingZeros8(result) - fieldIdx := i*8 + bitIdx - var name string - if fieldIdx < len(jsonFieldsNameOfID) { - name = jsonFieldsNameOfID[fieldIdx] - } else { - name = strconv.Itoa(fieldIdx) - } - failures = append(failures, validate.FieldError{ - Name: name, - Error: validate.ErrFieldRequired, - }) - // Reset bit. - result &^= 1 << bitIdx - } - } - } - if len(failures) > 0 { - return &validate.Error{Fields: failures} - } - - return nil -} - -// MarshalJSON implements stdjson.Marshaler. -func (s *ID) MarshalJSON() ([]byte, error) { - e := jx.Encoder{} - s.Encode(&e) - return e.Bytes(), nil -} - -// UnmarshalJSON implements stdjson.Unmarshaler. -func (s *ID) UnmarshalJSON(data []byte) error { - d := jx.DecodeBytes(data) - return s.Decode(d) -} - // Encode implements json.Marshaler. func (s *MapfixCreate) Encode(e *jx.Encoder) { e.ObjStart() @@ -436,6 +340,102 @@ func (s *MapfixCreate) UnmarshalJSON(data []byte) error { return s.Decode(d) } +// Encode implements json.Marshaler. +func (s *MapfixID) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *MapfixID) encodeFields(e *jx.Encoder) { + { + e.FieldStart("MapfixID") + e.Int64(s.MapfixID) + } +} + +var jsonFieldsNameOfMapfixID = [1]string{ + 0: "MapfixID", +} + +// Decode decodes MapfixID from json. +func (s *MapfixID) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode MapfixID to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "MapfixID": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Int64() + s.MapfixID = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"MapfixID\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode MapfixID") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfMapfixID) { + name = jsonFieldsNameOfMapfixID[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *MapfixID) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *MapfixID) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + // Encode encodes int64 as json. func (o OptInt64) Encode(e *jx.Encoder) { if !o.Set { @@ -799,6 +799,102 @@ func (s *ScriptCreate) UnmarshalJSON(data []byte) error { return s.Decode(d) } +// Encode implements json.Marshaler. +func (s *ScriptID) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ScriptID) encodeFields(e *jx.Encoder) { + { + e.FieldStart("ScriptID") + e.Int64(s.ScriptID) + } +} + +var jsonFieldsNameOfScriptID = [1]string{ + 0: "ScriptID", +} + +// Decode decodes ScriptID from json. +func (s *ScriptID) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ScriptID to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "ScriptID": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Int64() + s.ScriptID = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"ScriptID\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ScriptID") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfScriptID) { + name = jsonFieldsNameOfScriptID[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *ScriptID) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ScriptID) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + // Encode implements json.Marshaler. func (s *ScriptPolicy) Encode(e *jx.Encoder) { e.ObjStart() @@ -1076,6 +1172,102 @@ func (s *ScriptPolicyCreate) UnmarshalJSON(data []byte) error { return s.Decode(d) } +// Encode implements json.Marshaler. +func (s *ScriptPolicyID) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ScriptPolicyID) encodeFields(e *jx.Encoder) { + { + e.FieldStart("ScriptPolicyID") + e.Int64(s.ScriptPolicyID) + } +} + +var jsonFieldsNameOfScriptPolicyID = [1]string{ + 0: "ScriptPolicyID", +} + +// Decode decodes ScriptPolicyID from json. +func (s *ScriptPolicyID) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ScriptPolicyID to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "ScriptPolicyID": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Int64() + s.ScriptPolicyID = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"ScriptPolicyID\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ScriptPolicyID") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfScriptPolicyID) { + name = jsonFieldsNameOfScriptPolicyID[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *ScriptPolicyID) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ScriptPolicyID) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + // Encode implements json.Marshaler. func (s *SubmissionCreate) Encode(e *jx.Encoder) { e.ObjStart() @@ -1273,3 +1465,99 @@ func (s *SubmissionCreate) UnmarshalJSON(data []byte) error { d := jx.DecodeBytes(data) return s.Decode(d) } + +// Encode implements json.Marshaler. +func (s *SubmissionID) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *SubmissionID) encodeFields(e *jx.Encoder) { + { + e.FieldStart("SubmissionID") + e.Int64(s.SubmissionID) + } +} + +var jsonFieldsNameOfSubmissionID = [1]string{ + 0: "SubmissionID", +} + +// Decode decodes SubmissionID from json. +func (s *SubmissionID) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode SubmissionID to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "SubmissionID": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Int64() + s.SubmissionID = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"SubmissionID\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode SubmissionID") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfSubmissionID) { + name = jsonFieldsNameOfSubmissionID[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *SubmissionID) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *SubmissionID) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} diff --git a/pkg/internal/oas_response_decoders_gen.go b/pkg/internal/oas_response_decoders_gen.go index 1c492e1..bdabeaa 100644 --- a/pkg/internal/oas_response_decoders_gen.go +++ b/pkg/internal/oas_response_decoders_gen.go @@ -372,7 +372,7 @@ func decodeActionSubmissionValidatedResponse(resp *http.Response) (res *ActionSu return res, errors.Wrap(defRes, "error") } -func decodeCreateMapfixResponse(resp *http.Response) (res *ID, _ error) { +func decodeCreateMapfixResponse(resp *http.Response) (res *MapfixID, _ error) { switch resp.StatusCode { case 201: // Code 201. @@ -388,7 +388,7 @@ func decodeCreateMapfixResponse(resp *http.Response) (res *ID, _ error) { } d := jx.DecodeBytes(buf) - var response ID + var response MapfixID if err := func() error { if err := response.Decode(d); err != nil { return err @@ -455,7 +455,7 @@ func decodeCreateMapfixResponse(resp *http.Response) (res *ID, _ error) { return res, errors.Wrap(defRes, "error") } -func decodeCreateScriptResponse(resp *http.Response) (res *ID, _ error) { +func decodeCreateScriptResponse(resp *http.Response) (res *ScriptID, _ error) { switch resp.StatusCode { case 201: // Code 201. @@ -471,7 +471,7 @@ func decodeCreateScriptResponse(resp *http.Response) (res *ID, _ error) { } d := jx.DecodeBytes(buf) - var response ID + var response ScriptID if err := func() error { if err := response.Decode(d); err != nil { return err @@ -538,7 +538,7 @@ func decodeCreateScriptResponse(resp *http.Response) (res *ID, _ error) { return res, errors.Wrap(defRes, "error") } -func decodeCreateScriptPolicyResponse(resp *http.Response) (res *ID, _ error) { +func decodeCreateScriptPolicyResponse(resp *http.Response) (res *ScriptPolicyID, _ error) { switch resp.StatusCode { case 201: // Code 201. @@ -554,7 +554,7 @@ func decodeCreateScriptPolicyResponse(resp *http.Response) (res *ID, _ error) { } d := jx.DecodeBytes(buf) - var response ID + var response ScriptPolicyID if err := func() error { if err := response.Decode(d); err != nil { return err @@ -621,7 +621,7 @@ func decodeCreateScriptPolicyResponse(resp *http.Response) (res *ID, _ error) { return res, errors.Wrap(defRes, "error") } -func decodeCreateSubmissionResponse(resp *http.Response) (res *ID, _ error) { +func decodeCreateSubmissionResponse(resp *http.Response) (res *SubmissionID, _ error) { switch resp.StatusCode { case 201: // Code 201. @@ -637,7 +637,7 @@ func decodeCreateSubmissionResponse(resp *http.Response) (res *ID, _ error) { } d := jx.DecodeBytes(buf) - var response ID + var response SubmissionID if err := func() error { if err := response.Decode(d); err != nil { return err diff --git a/pkg/internal/oas_response_encoders_gen.go b/pkg/internal/oas_response_encoders_gen.go index ab1b12d..03bd236 100644 --- a/pkg/internal/oas_response_encoders_gen.go +++ b/pkg/internal/oas_response_encoders_gen.go @@ -62,7 +62,7 @@ func encodeActionSubmissionValidatedResponse(response *ActionSubmissionValidated return nil } -func encodeCreateMapfixResponse(response *ID, w http.ResponseWriter, span trace.Span) error { +func encodeCreateMapfixResponse(response *MapfixID, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(201) span.SetStatus(codes.Ok, http.StatusText(201)) @@ -76,7 +76,7 @@ func encodeCreateMapfixResponse(response *ID, w http.ResponseWriter, span trace. return nil } -func encodeCreateScriptResponse(response *ID, w http.ResponseWriter, span trace.Span) error { +func encodeCreateScriptResponse(response *ScriptID, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(201) span.SetStatus(codes.Ok, http.StatusText(201)) @@ -90,7 +90,7 @@ func encodeCreateScriptResponse(response *ID, w http.ResponseWriter, span trace. return nil } -func encodeCreateScriptPolicyResponse(response *ID, w http.ResponseWriter, span trace.Span) error { +func encodeCreateScriptPolicyResponse(response *ScriptPolicyID, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(201) span.SetStatus(codes.Ok, http.StatusText(201)) @@ -104,7 +104,7 @@ func encodeCreateScriptPolicyResponse(response *ID, w http.ResponseWriter, span return nil } -func encodeCreateSubmissionResponse(response *ID, w http.ResponseWriter, span trace.Span) error { +func encodeCreateSubmissionResponse(response *SubmissionID, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(201) span.SetStatus(codes.Ok, http.StatusText(201)) diff --git a/pkg/internal/oas_schemas_gen.go b/pkg/internal/oas_schemas_gen.go index cc5c4ed..1fecc20 100644 --- a/pkg/internal/oas_schemas_gen.go +++ b/pkg/internal/oas_schemas_gen.go @@ -84,21 +84,6 @@ func (s *ErrorStatusCode) SetResponse(val Error) { s.Response = val } -// Ref: #/components/schemas/Id -type ID struct { - ID int64 `json:"ID"` -} - -// GetID returns the value of ID. -func (s *ID) GetID() int64 { - return s.ID -} - -// SetID sets the value of ID. -func (s *ID) SetID(val int64) { - s.ID = val -} - // Ref: #/components/schemas/MapfixCreate type MapfixCreate struct { OperationID int32 `json:"OperationID"` @@ -191,6 +176,21 @@ func (s *MapfixCreate) SetTargetAssetID(val int64) { s.TargetAssetID = val } +// Ref: #/components/schemas/MapfixID +type MapfixID struct { + MapfixID int64 `json:"MapfixID"` +} + +// GetMapfixID returns the value of MapfixID. +func (s *MapfixID) GetMapfixID() int64 { + return s.MapfixID +} + +// SetMapfixID sets the value of MapfixID. +func (s *MapfixID) SetMapfixID(val int64) { + s.MapfixID = val +} + // NewOptInt32 returns new OptInt32 with value set to v. func NewOptInt32(v int32) OptInt32 { return OptInt32{ @@ -447,6 +447,21 @@ func (s *ScriptCreate) SetResourceID(val OptInt64) { s.ResourceID = val } +// Ref: #/components/schemas/ScriptID +type ScriptID struct { + ScriptID int64 `json:"ScriptID"` +} + +// GetScriptID returns the value of ScriptID. +func (s *ScriptID) GetScriptID() int64 { + return s.ScriptID +} + +// SetScriptID sets the value of ScriptID. +func (s *ScriptID) SetScriptID(val int64) { + s.ScriptID = val +} + // Ref: #/components/schemas/ScriptPolicy type ScriptPolicy struct { ID int64 `json:"ID"` @@ -532,6 +547,21 @@ func (s *ScriptPolicyCreate) SetPolicy(val int32) { s.Policy = val } +// Ref: #/components/schemas/ScriptPolicyID +type ScriptPolicyID struct { + ScriptPolicyID int64 `json:"ScriptPolicyID"` +} + +// GetScriptPolicyID returns the value of ScriptPolicyID. +func (s *ScriptPolicyID) GetScriptPolicyID() int64 { + return s.ScriptPolicyID +} + +// SetScriptPolicyID sets the value of ScriptPolicyID. +func (s *ScriptPolicyID) SetScriptPolicyID(val int64) { + s.ScriptPolicyID = val +} + // Ref: #/components/schemas/SubmissionCreate type SubmissionCreate struct { OperationID int32 `json:"OperationID"` @@ -613,6 +643,21 @@ func (s *SubmissionCreate) SetAssetVersion(val int64) { s.AssetVersion = val } +// Ref: #/components/schemas/SubmissionID +type SubmissionID struct { + SubmissionID int64 `json:"SubmissionID"` +} + +// GetSubmissionID returns the value of SubmissionID. +func (s *SubmissionID) GetSubmissionID() int64 { + return s.SubmissionID +} + +// SetSubmissionID sets the value of SubmissionID. +func (s *SubmissionID) SetSubmissionID(val int64) { + s.SubmissionID = val +} + // UpdateMapfixValidatedModelNoContent is response for UpdateMapfixValidatedModel operation. type UpdateMapfixValidatedModelNoContent struct{} diff --git a/pkg/internal/oas_server_gen.go b/pkg/internal/oas_server_gen.go index 0423592..b19609a 100644 --- a/pkg/internal/oas_server_gen.go +++ b/pkg/internal/oas_server_gen.go @@ -55,25 +55,25 @@ type Handler interface { // Create a mapfix. // // POST /mapfixes - CreateMapfix(ctx context.Context, req *MapfixCreate) (*ID, error) + CreateMapfix(ctx context.Context, req *MapfixCreate) (*MapfixID, error) // CreateScript implements createScript operation. // // Create a new script. // // POST /scripts - CreateScript(ctx context.Context, req *ScriptCreate) (*ID, error) + CreateScript(ctx context.Context, req *ScriptCreate) (*ScriptID, error) // CreateScriptPolicy implements createScriptPolicy operation. // // Create a new script policy. // // POST /script-policy - CreateScriptPolicy(ctx context.Context, req *ScriptPolicyCreate) (*ID, error) + CreateScriptPolicy(ctx context.Context, req *ScriptPolicyCreate) (*ScriptPolicyID, error) // CreateSubmission implements createSubmission operation. // // Create a new submission. // // POST /submissions - CreateSubmission(ctx context.Context, req *SubmissionCreate) (*ID, error) + CreateSubmission(ctx context.Context, req *SubmissionCreate) (*SubmissionID, error) // GetScript implements getScript operation. // // Get the specified script by ID. diff --git a/pkg/internal/oas_unimplemented_gen.go b/pkg/internal/oas_unimplemented_gen.go index 0f56dad..1adcfdf 100644 --- a/pkg/internal/oas_unimplemented_gen.go +++ b/pkg/internal/oas_unimplemented_gen.go @@ -81,7 +81,7 @@ func (UnimplementedHandler) ActionSubmissionValidated(ctx context.Context, param // Create a mapfix. // // POST /mapfixes -func (UnimplementedHandler) CreateMapfix(ctx context.Context, req *MapfixCreate) (r *ID, _ error) { +func (UnimplementedHandler) CreateMapfix(ctx context.Context, req *MapfixCreate) (r *MapfixID, _ error) { return r, ht.ErrNotImplemented } @@ -90,7 +90,7 @@ func (UnimplementedHandler) CreateMapfix(ctx context.Context, req *MapfixCreate) // Create a new script. // // POST /scripts -func (UnimplementedHandler) CreateScript(ctx context.Context, req *ScriptCreate) (r *ID, _ error) { +func (UnimplementedHandler) CreateScript(ctx context.Context, req *ScriptCreate) (r *ScriptID, _ error) { return r, ht.ErrNotImplemented } @@ -99,7 +99,7 @@ func (UnimplementedHandler) CreateScript(ctx context.Context, req *ScriptCreate) // Create a new script policy. // // POST /script-policy -func (UnimplementedHandler) CreateScriptPolicy(ctx context.Context, req *ScriptPolicyCreate) (r *ID, _ error) { +func (UnimplementedHandler) CreateScriptPolicy(ctx context.Context, req *ScriptPolicyCreate) (r *ScriptPolicyID, _ error) { return r, ht.ErrNotImplemented } @@ -108,7 +108,7 @@ func (UnimplementedHandler) CreateScriptPolicy(ctx context.Context, req *ScriptP // Create a new submission. // // POST /submissions -func (UnimplementedHandler) CreateSubmission(ctx context.Context, req *SubmissionCreate) (r *ID, _ error) { +func (UnimplementedHandler) CreateSubmission(ctx context.Context, req *SubmissionCreate) (r *SubmissionID, _ error) { return r, ht.ErrNotImplemented } -- 2.49.1 From c9041168e56eb6d7b758af0b1d6adb049c82233d Mon Sep 17 00:00:00 2001 From: Quaternions Date: Thu, 3 Apr 2025 11:10:34 -0700 Subject: [PATCH 284/454] submissions: use explicit ID types --- pkg/service/script_policy.go | 6 +++--- pkg/service/scripts.go | 6 +++--- pkg/service_internal/mapfixes.go | 6 +++--- pkg/service_internal/script_policy.go | 6 +++--- pkg/service_internal/scripts.go | 6 +++--- pkg/service_internal/submissions.go | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/pkg/service/script_policy.go b/pkg/service/script_policy.go index 044a925..9471736 100644 --- a/pkg/service/script_policy.go +++ b/pkg/service/script_policy.go @@ -13,7 +13,7 @@ import ( // Create a new script policy. // // POST /script-policy -func (svc *Service) CreateScriptPolicy(ctx context.Context, req *api.ScriptPolicyCreate) (*api.ID, error) { +func (svc *Service) CreateScriptPolicy(ctx context.Context, req *api.ScriptPolicyCreate) (*api.ScriptPolicyID, error) { userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) if !ok { return nil, ErrUserInfo @@ -44,8 +44,8 @@ func (svc *Service) CreateScriptPolicy(ctx context.Context, req *api.ScriptPolic return nil, err } - return &api.ID{ - ID: script.ID, + return &api.ScriptPolicyID{ + ScriptPolicyID: script.ID, }, nil } diff --git a/pkg/service/scripts.go b/pkg/service/scripts.go index bd40ded..c8907f4 100644 --- a/pkg/service/scripts.go +++ b/pkg/service/scripts.go @@ -13,7 +13,7 @@ import ( // Create a new script. // // POST /scripts -func (svc *Service) CreateScript(ctx context.Context, req *api.ScriptCreate) (*api.ID, error) { +func (svc *Service) CreateScript(ctx context.Context, req *api.ScriptCreate) (*api.ScriptID, error) { userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) if !ok { return nil, ErrUserInfo @@ -39,8 +39,8 @@ func (svc *Service) CreateScript(ctx context.Context, req *api.ScriptCreate) (*a return nil, err } - return &api.ID{ - ID: script.ID, + return &api.ScriptID{ + ScriptID: script.ID, }, nil } diff --git a/pkg/service_internal/mapfixes.go b/pkg/service_internal/mapfixes.go index c8a8c79..b792f72 100644 --- a/pkg/service_internal/mapfixes.go +++ b/pkg/service_internal/mapfixes.go @@ -80,7 +80,7 @@ func (svc *Service) ActionMapfixUploaded(ctx context.Context, params internal.Ac } // POST /mapfixes -func (svc *Service) CreateMapfix(ctx context.Context, request *internal.MapfixCreate) (*internal.ID, error) { +func (svc *Service) CreateMapfix(ctx context.Context, request *internal.MapfixCreate) (*internal.MapfixID, error) { // Check if an active mapfix with the same asset id exists { filter := datastore.Optional() @@ -125,7 +125,7 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *internal.MapfixCr if err != nil { return nil, err } - return &internal.ID{ - ID: mapfix.ID, + return &internal.MapfixID{ + MapfixID: mapfix.ID, }, nil } diff --git a/pkg/service_internal/script_policy.go b/pkg/service_internal/script_policy.go index e38b4bf..d61955d 100644 --- a/pkg/service_internal/script_policy.go +++ b/pkg/service_internal/script_policy.go @@ -13,7 +13,7 @@ import ( // Create a new script policy. // // POST /script-policy -func (svc *Service) CreateScriptPolicy(ctx context.Context, req *api.ScriptPolicyCreate) (*api.ID, error) { +func (svc *Service) CreateScriptPolicy(ctx context.Context, req *api.ScriptPolicyCreate) (*api.ScriptPolicyID, error) { from_script, err := svc.DB.Scripts().Get(ctx, req.FromScriptID) if err != nil { return nil, err @@ -31,8 +31,8 @@ func (svc *Service) CreateScriptPolicy(ctx context.Context, req *api.ScriptPolic return nil, err } - return &api.ID{ - ID: script.ID, + return &api.ScriptPolicyID{ + ScriptPolicyID: script.ID, }, nil } diff --git a/pkg/service_internal/scripts.go b/pkg/service_internal/scripts.go index cf4191d..2b964f1 100644 --- a/pkg/service_internal/scripts.go +++ b/pkg/service_internal/scripts.go @@ -13,7 +13,7 @@ import ( // Create a new script. // // POST /scripts -func (svc *Service) CreateScript(ctx context.Context, req *api.ScriptCreate) (*api.ID, error) { +func (svc *Service) CreateScript(ctx context.Context, req *api.ScriptCreate) (*api.ScriptID, error) { script, err := svc.DB.Scripts().Create(ctx, model.Script{ ID: 0, Name: req.Name, @@ -26,8 +26,8 @@ func (svc *Service) CreateScript(ctx context.Context, req *api.ScriptCreate) (*a return nil, err } - return &api.ID{ - ID: script.ID, + return &api.ScriptID{ + ScriptID: script.ID, }, nil } diff --git a/pkg/service_internal/submissions.go b/pkg/service_internal/submissions.go index 02a5533..3322230 100644 --- a/pkg/service_internal/submissions.go +++ b/pkg/service_internal/submissions.go @@ -80,7 +80,7 @@ func (svc *Service) ActionSubmissionUploaded(ctx context.Context, params interna } // POST /submissions -func (svc *Service) CreateSubmission(ctx context.Context, request *internal.SubmissionCreate) (*internal.ID, error) { +func (svc *Service) CreateSubmission(ctx context.Context, request *internal.SubmissionCreate) (*internal.SubmissionID, error) { // Check if an active submission with the same asset id exists { filter := datastore.Optional() @@ -124,7 +124,7 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *internal.Subm if err != nil { return nil, err } - return &internal.ID{ - ID: submission.ID, + return &internal.SubmissionID{ + SubmissionID: submission.ID, }, nil } -- 2.49.1 From 719ef95b6d42bcf5d1d3ae80a96c6ca7be1f07f0 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Thu, 3 Apr 2025 11:12:40 -0700 Subject: [PATCH 285/454] submissions-api: use explicit ID types --- validation/api/src/types.rs | 8 ++++---- validation/src/validator.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/validation/api/src/types.rs b/validation/api/src/types.rs index 56743b2..580a97e 100644 --- a/validation/api/src/types.rs +++ b/validation/api/src/types.rs @@ -77,7 +77,7 @@ pub struct CreateMapfixRequest<'a>{ #[allow(nonstandard_style)] #[derive(Clone,Debug,serde::Deserialize)] pub struct MapfixIDResponse{ - pub ID:MapfixID, + pub MapfixID:MapfixID, } #[allow(nonstandard_style)] @@ -94,7 +94,7 @@ pub struct CreateSubmissionRequest<'a>{ #[allow(nonstandard_style)] #[derive(Clone,Debug,serde::Deserialize)] pub struct SubmissionIDResponse{ - pub ID:SubmissionID, + pub SubmissionID:SubmissionID, } #[derive(Clone,Copy,Debug,PartialEq,Eq,serde::Serialize,serde::Deserialize)] @@ -156,7 +156,7 @@ pub struct CreateScriptRequest<'a>{ #[allow(nonstandard_style)] #[derive(Clone,Debug,serde::Deserialize)] pub struct ScriptIDResponse{ - pub ID:ScriptID, + pub ScriptID:ScriptID, } #[derive(Clone,Copy,Debug,PartialEq,Eq,serde_repr::Serialize_repr,serde_repr::Deserialize_repr)] @@ -199,7 +199,7 @@ pub struct CreateScriptPolicyRequest{ #[allow(nonstandard_style)] #[derive(Clone,Debug,serde::Deserialize)] pub struct ScriptPolicyIDResponse{ - pub ID:ScriptPolicyID, + pub ScriptPolicyID:ScriptPolicyID, } #[allow(nonstandard_style)] diff --git a/validation/src/validator.rs b/validation/src/validator.rs index c114ca3..0f66fcd 100644 --- a/validation/src/validator.rs +++ b/validation/src/validator.rs @@ -164,8 +164,8 @@ impl crate::message_handler::MessageHandler{ // create a None policy (pending review by yours truly) self.api.create_script_policy(submissions_api::types::CreateScriptPolicyRequest{ - ToScriptID:script.ID, - FromScriptID:script.ID, + ToScriptID:script.ScriptID, + FromScriptID:script.ScriptID, Policy:submissions_api::types::Policy::None, }).await.map_err(ValidateError::ApiCreateScriptPolicy)?; } -- 2.49.1 From d0634fc141fe166e0690262f748e1b611a3aaf9a Mon Sep 17 00:00:00 2001 From: Quaternions Date: Thu, 3 Apr 2025 12:58:29 -0700 Subject: [PATCH 286/454] validator: update rbx_asset --- Cargo.lock | 4 ++-- validation/Cargo.toml | 2 +- validation/src/create.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f71fec..28333f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1297,9 +1297,9 @@ dependencies = [ [[package]] name = "rbx_asset" -version = "0.2.5" +version = "0.3.3" source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" -checksum = "dcf243f46bd41b3880a27278177a3f9996f95ab231d9a04345ad9dd381c3a54a" +checksum = "91722b37549ded270f39556194ca03d03e08bd70674d239ec845765ed9e42b7d" dependencies = [ "chrono", "flate2", diff --git a/validation/Cargo.toml b/validation/Cargo.toml index 28ce533..ee5dffa 100644 --- a/validation/Cargo.toml +++ b/validation/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" submissions-api = { path = "api", features = ["internal"], default-features = false, registry = "strafesnet" } async-nats = "0.40.0" futures = "0.3.31" -rbx_asset = { version = "0.2.5", registry = "strafesnet" } +rbx_asset = { version = "0.3.3", registry = "strafesnet" } rbx_binary = { version = "0.7.4", registry = "strafesnet"} rbx_dom_weak = { version = "2.9.0", registry = "strafesnet"} rbx_reflection_database = { version = "0.2.12", registry = "strafesnet"} diff --git a/validation/src/create.rs b/validation/src/create.rs index 1513904..76c4a05 100644 --- a/validation/src/create.rs +++ b/validation/src/create.rs @@ -41,7 +41,7 @@ impl crate::message_handler::MessageHandler{ // grab version info let first_version=asset_versions_page.data.first().ok_or(Error::EmptyVersionsPage)?; - if first_version.creatorType!="User"{ + if first_version.creatorType!=rbx_asset::cookie::CreatorType::User{ return Err(Error::WrongCreatorType); } -- 2.49.1 From a3d644f57207eb0623dc249753072d762fe9db87 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Thu, 3 Apr 2025 13:10:48 -0700 Subject: [PATCH 287/454] validator: use different endpoints to fill in the submission details --- validation/src/create.rs | 47 +++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/validation/src/create.rs b/validation/src/create.rs index 76c4a05..747a824 100644 --- a/validation/src/create.rs +++ b/validation/src/create.rs @@ -5,8 +5,11 @@ use crate::rbx_util::{get_mapinfo,read_dom,MapInfo,ReadDomError,GetMapInfoError, pub enum Error{ ModelVersionsPage(rbx_asset::cookie::PageError), EmptyVersionsPage, - WrongCreatorType, + CreatorTypeMustBeUser(rbx_asset::cookie::CreatorType), + ModelDetails(rbx_asset::cookie::GetError), + ModelInfoDownload(rbx_asset::cookie::GetAssetV2Error), ModelFileDownload(rbx_asset::cookie::GetError), + NoLocations, ModelFileDecode(ReadDomError), GetMapInfo(GetMapInfoError), ParseGameID(ParseGameIDError), @@ -32,28 +35,32 @@ pub struct CreateResult{ } impl crate::message_handler::MessageHandler{ pub async fn create_inner(&self,create_info:CreateRequest)->Result{ - // discover the latest asset version - let asset_versions_page=self.cookie_context.get_asset_versions_page(rbx_asset::cookie::AssetVersionsPageRequest{ - asset_id:create_info.ModelID, - cursor:None - }).await.map_err(Error::ModelVersionsPage)?; + // discover asset creator + let creator_fut=async{ + self.cookie_context.get_asset_details( + rbx_asset::cookie::GetAssetDetailsRequest{asset_id:create_info.ModelID} + ).await.map_err(Error::ModelDetails) + }; - // grab version info - let first_version=asset_versions_page.data.first().ok_or(Error::EmptyVersionsPage)?; + // download the map model + let asset_fut=async{ + let asset_info=self.cookie_context.get_asset_v2(rbx_asset::cookie::GetAssetRequest{ + asset_id:create_info.ModelID, + version:None, + }).await.map_err(Error::ModelInfoDownload)?; - if first_version.creatorType!=rbx_asset::cookie::CreatorType::User{ - return Err(Error::WrongCreatorType); + let location=asset_info.info.locations.first().ok_or(Error::NoLocations)?; + let data=self.cookie_context.get_asset_v2_download(location).await.map_err(Error::ModelFileDownload)?; + + Ok((asset_info.version,data)) + }; + + let (details,(asset_version,model_data))=tokio::try_join!(creator_fut,asset_fut)?; + + if details.Creator.CreatorType!=rbx_asset::cookie::CreatorType::User{ + return Err(Error::CreatorTypeMustBeUser(details.Creator.CreatorType)); } - let asset_creator_id=first_version.creatorTargetId; - let asset_version=first_version.assetVersionNumber; - - // download the map model version - let model_data=self.cookie_context.get_asset(rbx_asset::cookie::GetAssetRequest{ - asset_id:create_info.ModelID, - version:Some(asset_version), - }).await.map_err(Error::ModelFileDownload)?; - // decode dom (slow!) let dom=read_dom(&mut std::io::Cursor::new(model_data)).map_err(Error::ModelFileDecode)?; @@ -67,7 +74,7 @@ impl crate::message_handler::MessageHandler{ let game_id=game_id.map_err(Error::ParseGameID)?; Ok(CreateResult{ - AssetOwner:asset_creator_id as i64, + AssetOwner:details.Creator.Id as i64, DisplayName:display_name.unwrap_or_default().to_owned(), Creator:creator.unwrap_or_default().to_owned(), GameID:game_id as i32, -- 2.49.1 From 027a55661bc835032a9dc45797e003a9b394d123 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Thu, 3 Apr 2025 13:17:43 -0700 Subject: [PATCH 288/454] openapi: be consistent --- openapi-internal.yaml | 2 +- pkg/service_internal/operations.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openapi-internal.yaml b/openapi-internal.yaml index f92b03b..9040e92 100644 --- a/openapi-internal.yaml +++ b/openapi-internal.yaml @@ -121,7 +121,7 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" - /operations/{OperationID}/failed: + /operations/{OperationID}/status/operation-failed: post: summary: (Internal endpoint) Fail an operation and write a StatusMessage operationId: actionOperationFailed diff --git a/pkg/service_internal/operations.go b/pkg/service_internal/operations.go index 2deeffd..87ccccc 100644 --- a/pkg/service_internal/operations.go +++ b/pkg/service_internal/operations.go @@ -12,7 +12,7 @@ import ( // // Fail the specified OperationID with a StatusMessage. // -// POST /operations/{OperationID}/failed +// POST /operations/{OperationID}/status/operation-failed func (svc *Service) ActionOperationFailed(ctx context.Context, params internal.ActionOperationFailedParams) (error) { pmap := datastore.Optional() pmap.Add("status_id", model.OperationStatusFailed) -- 2.49.1 From fe2c20bd72e5064364e412489fa07c871719ea60 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Thu, 3 Apr 2025 13:17:59 -0700 Subject: [PATCH 289/454] openapi: generate --- pkg/internal/oas_client_gen.go | 8 ++++---- pkg/internal/oas_handlers_gen.go | 4 ++-- pkg/internal/oas_router_gen.go | 10 +++++----- pkg/internal/oas_server_gen.go | 2 +- pkg/internal/oas_unimplemented_gen.go | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pkg/internal/oas_client_gen.go b/pkg/internal/oas_client_gen.go index d57735f..0248a3f 100644 --- a/pkg/internal/oas_client_gen.go +++ b/pkg/internal/oas_client_gen.go @@ -50,7 +50,7 @@ type Invoker interface { // // (Internal endpoint) Fail an operation and write a StatusMessage. // - // POST /operations/{OperationID}/failed + // POST /operations/{OperationID}/status/operation-failed ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error // ActionSubmissionAccepted invokes actionSubmissionAccepted operation. // @@ -468,7 +468,7 @@ func (c *Client) sendActionMapfixValidated(ctx context.Context, params ActionMap // // (Internal endpoint) Fail an operation and write a StatusMessage. // -// POST /operations/{OperationID}/failed +// POST /operations/{OperationID}/status/operation-failed func (c *Client) ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error { _, err := c.sendActionOperationFailed(ctx, params) return err @@ -478,7 +478,7 @@ func (c *Client) sendActionOperationFailed(ctx context.Context, params ActionOpe otelAttrs := []attribute.KeyValue{ otelogen.OperationID("actionOperationFailed"), semconv.HTTPRequestMethodKey.String("POST"), - semconv.HTTPRouteKey.String("/operations/{OperationID}/failed"), + semconv.HTTPRouteKey.String("/operations/{OperationID}/status/operation-failed"), } // Run stopwatch. @@ -530,7 +530,7 @@ func (c *Client) sendActionOperationFailed(ctx context.Context, params ActionOpe } pathParts[1] = encoded } - pathParts[2] = "/failed" + pathParts[2] = "/status/operation-failed" uri.AddPathParts(u, pathParts[:]...) stage = "EncodeQueryParams" diff --git a/pkg/internal/oas_handlers_gen.go b/pkg/internal/oas_handlers_gen.go index 96b8c4b..b382980 100644 --- a/pkg/internal/oas_handlers_gen.go +++ b/pkg/internal/oas_handlers_gen.go @@ -485,14 +485,14 @@ func (s *Server) handleActionMapfixValidatedRequest(args [1]string, argsEscaped // // (Internal endpoint) Fail an operation and write a StatusMessage. // -// POST /operations/{OperationID}/failed +// POST /operations/{OperationID}/status/operation-failed func (s *Server) handleActionOperationFailedRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { statusWriter := &codeRecorder{ResponseWriter: w} w = statusWriter otelAttrs := []attribute.KeyValue{ otelogen.OperationID("actionOperationFailed"), semconv.HTTPRequestMethodKey.String("POST"), - semconv.HTTPRouteKey.String("/operations/{OperationID}/failed"), + semconv.HTTPRouteKey.String("/operations/{OperationID}/status/operation-failed"), } // Start a span for this request. diff --git a/pkg/internal/oas_router_gen.go b/pkg/internal/oas_router_gen.go index 0531e26..ecc609b 100644 --- a/pkg/internal/oas_router_gen.go +++ b/pkg/internal/oas_router_gen.go @@ -242,9 +242,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { break } switch elem[0] { - case '/': // Prefix: "/failed" + case '/': // Prefix: "/status/operation-failed" - if l := len("/failed"); len(elem) >= l && elem[0:l] == "/failed" { + if l := len("/status/operation-failed"); len(elem) >= l && elem[0:l] == "/status/operation-failed" { elem = elem[l:] } else { break @@ -817,9 +817,9 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { break } switch elem[0] { - case '/': // Prefix: "/failed" + case '/': // Prefix: "/status/operation-failed" - if l := len("/failed"); len(elem) >= l && elem[0:l] == "/failed" { + if l := len("/status/operation-failed"); len(elem) >= l && elem[0:l] == "/status/operation-failed" { elem = elem[l:] } else { break @@ -832,7 +832,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { r.name = ActionOperationFailedOperation r.summary = "(Internal endpoint) Fail an operation and write a StatusMessage" r.operationID = "actionOperationFailed" - r.pathPattern = "/operations/{OperationID}/failed" + r.pathPattern = "/operations/{OperationID}/status/operation-failed" r.args = args r.count = 1 return r, true diff --git a/pkg/internal/oas_server_gen.go b/pkg/internal/oas_server_gen.go index b19609a..4fd886c 100644 --- a/pkg/internal/oas_server_gen.go +++ b/pkg/internal/oas_server_gen.go @@ -30,7 +30,7 @@ type Handler interface { // // (Internal endpoint) Fail an operation and write a StatusMessage. // - // POST /operations/{OperationID}/failed + // POST /operations/{OperationID}/status/operation-failed ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error // ActionSubmissionAccepted implements actionSubmissionAccepted operation. // diff --git a/pkg/internal/oas_unimplemented_gen.go b/pkg/internal/oas_unimplemented_gen.go index 1adcfdf..e231989 100644 --- a/pkg/internal/oas_unimplemented_gen.go +++ b/pkg/internal/oas_unimplemented_gen.go @@ -44,7 +44,7 @@ func (UnimplementedHandler) ActionMapfixValidated(ctx context.Context, params Ac // // (Internal endpoint) Fail an operation and write a StatusMessage. // -// POST /operations/{OperationID}/failed +// POST /operations/{OperationID}/status/operation-failed func (UnimplementedHandler) ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error { return ht.ErrNotImplemented } -- 2.49.1 From 56681f88622b7c57314883b3413614b653f544b5 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Thu, 3 Apr 2025 13:31:32 -0700 Subject: [PATCH 290/454] submissions: mark operation as completed --- pkg/service_internal/mapfixes.go | 11 +++++++++++ pkg/service_internal/submissions.go | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/pkg/service_internal/mapfixes.go b/pkg/service_internal/mapfixes.go index b792f72..e0f7375 100644 --- a/pkg/service_internal/mapfixes.go +++ b/pkg/service_internal/mapfixes.go @@ -3,6 +3,7 @@ package service_internal import ( "context" "errors" + "fmt" "git.itzana.me/strafesnet/maps-service/pkg/datastore" internal "git.itzana.me/strafesnet/maps-service/pkg/internal" @@ -125,6 +126,16 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *internal.MapfixCr if err != nil { return nil, err } + + // mark the operation as completed and provide the path + pmap := datastore.Optional() + pmap.Add("status_id", model.OperationStatusCompleted) + pmap.Add("path", fmt.Sprintf("/mapfixes/%d", mapfix.ID)) + err = svc.DB.Operations().Update(ctx, request.OperationID, pmap) + if err != nil { + return nil, err + } + return &internal.MapfixID{ MapfixID: mapfix.ID, }, nil diff --git a/pkg/service_internal/submissions.go b/pkg/service_internal/submissions.go index 3322230..b445c64 100644 --- a/pkg/service_internal/submissions.go +++ b/pkg/service_internal/submissions.go @@ -3,6 +3,7 @@ package service_internal import ( "context" "errors" + "fmt" "git.itzana.me/strafesnet/maps-service/pkg/datastore" internal "git.itzana.me/strafesnet/maps-service/pkg/internal" @@ -124,6 +125,16 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *internal.Subm if err != nil { return nil, err } + + // mark the operation as completed and provide the path + pmap := datastore.Optional() + pmap.Add("status_id", model.OperationStatusCompleted) + pmap.Add("path", fmt.Sprintf("/submissions/%d", submission.ID)) + err = svc.DB.Operations().Update(ctx, request.OperationID, pmap) + if err != nil { + return nil, err + } + return &internal.SubmissionID{ SubmissionID: submission.ID, }, nil -- 2.49.1 From ca1676db0021a25e57b036365b0fde9aa0b92f08 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Thu, 3 Apr 2025 13:43:43 -0700 Subject: [PATCH 291/454] validation: catch final error --- validation/src/create_mapfix.rs | 53 ++++++++++++++++------------- validation/src/create_submission.rs | 49 +++++++++++++------------- validation/src/message_handler.rs | 4 +-- 3 files changed, 57 insertions(+), 49 deletions(-) diff --git a/validation/src/create_mapfix.rs b/validation/src/create_mapfix.rs index 6dbe905..982fd91 100644 --- a/validation/src/create_mapfix.rs +++ b/validation/src/create_mapfix.rs @@ -4,8 +4,8 @@ use crate::create::CreateRequest; #[allow(dead_code)] #[derive(Debug)] pub enum Error{ + Create(crate::create::Error), ApiActionMapfixCreate(submissions_api::Error), - ApiActionOperationFailed(submissions_api::Error), } impl std::fmt::Display for Error{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ @@ -15,31 +15,36 @@ impl std::fmt::Display for Error{ impl std::error::Error for Error{} impl crate::message_handler::MessageHandler{ - pub async fn create_mapfix(&self,create_info:CreateMapfixRequest)->Result<(),Error>{ - let create_result=self.create_inner(CreateRequest{ + async fn create_mapfix_inner(&self,create_info:CreateMapfixRequest)->Result<(),Error>{ + // call deduplicated inner code + let create_request=self.create_inner(CreateRequest{ ModelID:create_info.ModelID, - }).await; + }).await.map_err(Error::Create)?; - match create_result{ - Ok(create_request)=>{ - // call create on api - self.api.create_mapfix(submissions_api::types::CreateMapfixRequest{ - OperationID:create_info.OperationID, - AssetOwner:create_request.AssetOwner, - DisplayName:create_request.DisplayName.as_str(), - Creator:create_request.Creator.as_str(), - GameID:create_request.GameID, - AssetID:create_info.ModelID, - AssetVersion:create_request.AssetVersion, - TargetAssetID:create_info.TargetAssetID, - }).await.map_err(Error::ApiActionMapfixCreate)?; - }, - Err(e)=>{ - self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{ - OperationID:create_info.OperationID, - StatusMessage:format!("{e}"), - }).await.map_err(Error::ApiActionOperationFailed)?; - }, + // call create on api + self.api.create_mapfix(submissions_api::types::CreateMapfixRequest{ + OperationID:create_info.OperationID, + AssetOwner:create_request.AssetOwner, + DisplayName:create_request.DisplayName.as_str(), + Creator:create_request.Creator.as_str(), + GameID:create_request.GameID, + AssetID:create_info.ModelID, + AssetVersion:create_request.AssetVersion, + TargetAssetID:create_info.TargetAssetID, + }).await.map_err(Error::ApiActionMapfixCreate)?; + + Ok(()) + } + pub async fn create_mapfix(&self,create_info:CreateMapfixRequest)->Result<(),submissions_api::Error>{ + let operation_id=create_info.OperationID; + + let create_result=self.create_mapfix_inner(create_info).await; + + if let Err(e)=create_result{ + self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{ + OperationID:operation_id, + StatusMessage:format!("{e}"), + }).await?; } Ok(()) diff --git a/validation/src/create_submission.rs b/validation/src/create_submission.rs index 31f2a0e..4949366 100644 --- a/validation/src/create_submission.rs +++ b/validation/src/create_submission.rs @@ -4,8 +4,8 @@ use crate::create::CreateRequest; #[allow(dead_code)] #[derive(Debug)] pub enum Error{ + Create(crate::create::Error), ApiActionSubmissionCreate(submissions_api::Error), - ApiActionOperationFailed(submissions_api::Error), } impl std::fmt::Display for Error{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ @@ -15,30 +15,33 @@ impl std::fmt::Display for Error{ impl std::error::Error for Error{} impl crate::message_handler::MessageHandler{ - pub async fn create_submission(&self,create_info:CreateSubmissionRequest)->Result<(),Error>{ - let create_result=self.create_inner(CreateRequest{ + async fn create_submission_inner(&self,create_info:CreateSubmissionRequest)->Result<(),Error>{ + let create_request=self.create_inner(CreateRequest{ ModelID:create_info.ModelID, - }).await; + }).await.map_err(Error::Create)?; + // call create on api + self.api.create_submission(submissions_api::types::CreateSubmissionRequest{ + OperationID:create_info.OperationID, + AssetOwner:create_request.AssetOwner, + DisplayName:create_request.DisplayName.as_str(), + Creator:create_request.Creator.as_str(), + GameID:create_request.GameID, + AssetID:create_info.ModelID, + AssetVersion:create_request.AssetVersion, + }).await.map_err(Error::ApiActionSubmissionCreate)?; - match create_result{ - Ok(create_request)=>{ - // call create on api - self.api.create_submission(submissions_api::types::CreateSubmissionRequest{ - OperationID:create_info.OperationID, - AssetOwner:create_request.AssetOwner, - DisplayName:create_request.DisplayName.as_str(), - Creator:create_request.Creator.as_str(), - GameID:create_request.GameID, - AssetID:create_info.ModelID, - AssetVersion:create_request.AssetVersion, - }).await.map_err(Error::ApiActionSubmissionCreate)?; - }, - Err(e)=>{ - self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{ - OperationID:create_info.OperationID, - StatusMessage:format!("{e}"), - }).await.map_err(Error::ApiActionOperationFailed)?; - }, + Ok(()) + } + pub async fn create_submission(&self,create_info:CreateSubmissionRequest)->Result<(),submissions_api::Error>{ + let operation_id=create_info.OperationID; + + let create_result=self.create_submission_inner(create_info).await; + + if let Err(e)=create_result{ + self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{ + OperationID:operation_id, + StatusMessage:format!("{e}"), + }).await?; } Ok(()) diff --git a/validation/src/message_handler.rs b/validation/src/message_handler.rs index bff0f10..fe79ee5 100644 --- a/validation/src/message_handler.rs +++ b/validation/src/message_handler.rs @@ -5,8 +5,8 @@ pub enum HandleMessageError{ DoubleAck(async_nats::Error), Json(serde_json::Error), UnknownSubject(String), - CreateMapfix(crate::create_mapfix::Error), - CreateSubmission(crate::create_submission::Error), + CreateMapfix(submissions_api::Error), + CreateSubmission(submissions_api::Error), UploadMapfix(crate::upload_mapfix::Error), UploadSubmission(crate::upload_submission::Error), ValidateMapfix(crate::validate_mapfix::Error), -- 2.49.1 From 15dd6b417800b1e00e12f4686264efcf35140302 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Thu, 3 Apr 2025 14:22:39 -0700 Subject: [PATCH 292/454] web: tweak header + add maps link --- web/src/app/_components/header.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/web/src/app/_components/header.tsx b/web/src/app/_components/header.tsx index cc094b6..c2274aa 100644 --- a/web/src/app/_components/header.tsx +++ b/web/src/app/_components/header.tsx @@ -20,12 +20,10 @@ export default function Header() { ) -- 2.49.1 From c6ebe5a360f65fe0af9e93725514881abf85510a Mon Sep 17 00:00:00 2001 From: ic3w0lf Date: Thu, 3 Apr 2025 15:45:59 -0600 Subject: [PATCH 293/454] stop polling on completeion/fail --- web/src/app/operations/[operationId]/page.tsx | 67 ++++++++++--------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/web/src/app/operations/[operationId]/page.tsx b/web/src/app/operations/[operationId]/page.tsx index 6f0763c..d712cdd 100644 --- a/web/src/app/operations/[operationId]/page.tsx +++ b/web/src/app/operations/[operationId]/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useRef } from "react"; import { useParams, useRouter } from "next/navigation"; import { CircularProgress, Typography, Card, CardContent, Button } from "@mui/material"; import Webpage from "@/app/_components/webpage"; @@ -17,23 +17,31 @@ interface Operation { } export default function OperationStatusPage() { - const { operationId } = useParams(); const router = useRouter(); - const [operation, setOperation] = useState(null); + const { operationId } = useParams(); + const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const [operation, setOperation] = useState(null); + + const intervalRef = useRef(null); useEffect(() => { if (!operationId) return; - + const fetchOperation = async () => { try { const response = await fetch(`/api/operations/${operationId}`); - + if (!response.ok) throw new Error("Failed to fetch operation"); - - const data = await response.json(); + + const data: Operation = await response.json(); setOperation(data); + + if (data.Status !== 0 && intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } } catch (err: unknown) { if (err instanceof Error) { setError(err.message); @@ -44,38 +52,33 @@ export default function OperationStatusPage() { setLoading(false); } }; - + fetchOperation(); - const interval = setInterval(fetchOperation, 5000); - - return () => clearInterval(interval); + if (!intervalRef.current) { + intervalRef.current = setInterval(fetchOperation, 5000); + } + + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + } + }; }, [operationId]); - const getStatusClass = (status: number) => { + const getStatusText = (status: number) => { switch (status) { - case 0: - return "created"; - case 1: - return "completed"; - case 2: - return "failed"; - default: - return ""; + case 0: + return "Created"; + case 1: + return "Completed"; + case 2: + return "Failed"; + default: + return "Unknown"; } }; - const getStatusText = (status: number) => { - switch (status) { - case 0: - return "Created"; - case 1: - return "Completed"; - case 2: - return "Failed"; - default: - return "Unknown"; - } - }; + const getStatusClass = (status: number) => getStatusText(status).toLowerCase(); return ( -- 2.49.1 From 1ff1cae7093e60d95b19c41e2fec5c7eeee69f83 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Thu, 3 Apr 2025 15:12:18 -0700 Subject: [PATCH 294/454] web: reduce polling interval The operations will usually take half a second. --- web/src/app/operations/[operationId]/page.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/web/src/app/operations/[operationId]/page.tsx b/web/src/app/operations/[operationId]/page.tsx index d712cdd..36f1c8a 100644 --- a/web/src/app/operations/[operationId]/page.tsx +++ b/web/src/app/operations/[operationId]/page.tsx @@ -19,12 +19,12 @@ interface Operation { export default function OperationStatusPage() { const router = useRouter(); const { operationId } = useParams(); - + const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [operation, setOperation] = useState(null); - - const intervalRef = useRef(null); + + const intervalRef = useRef(null); useEffect(() => { if (!operationId) return; @@ -55,7 +55,7 @@ export default function OperationStatusPage() { fetchOperation(); if (!intervalRef.current) { - intervalRef.current = setInterval(fetchOperation, 5000); + intervalRef.current = setInterval(fetchOperation, 1000); } return () => { @@ -77,9 +77,9 @@ export default function OperationStatusPage() { return "Unknown"; } }; - - const getStatusClass = (status: number) => getStatusText(status).toLowerCase(); - + + const getStatusClass = (status: number) => getStatusText(status).toLowerCase(); + return (
@@ -99,7 +99,7 @@ export default function OperationStatusPage() { Owner: {operation.Owner} Date: {new Date(operation.Date * 1000).toLocaleString()} Path: {operation.Path} - + {operation.Status === 1 && (
+ + ) +} export default function Map() { - const { mapId } = useParams<{mapId: string}>() + const { mapId } = useParams() return (

map { mapId }

+ -- 2.49.1 From ec15c1f2e56747a8f4a4f2b6a2e358c6e75419f9 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 4 Apr 2025 22:10:31 +0000 Subject: [PATCH 303/454] Hide Irrelevant Review Buttons (#86) Closes #17. Reviewed-on: https://git.itzana.me/StrafesNET/maps-service/pulls/86 Co-authored-by: Quaternions Co-committed-by: Quaternions --- .../mapfixes/[mapfixId]/_reviewButtons.tsx | 100 +++++++++++++++--- web/src/app/mapfixes/[mapfixId]/page.tsx | 8 +- .../[submissionId]/_reviewButtons.tsx | 100 +++++++++++++++--- .../app/submissions/[submissionId]/page.tsx | 6 +- web/src/app/ts/Mapfix.ts | 1 + web/src/app/ts/Roles.ts | 25 +++++ 6 files changed, 209 insertions(+), 31 deletions(-) create mode 100644 web/src/app/ts/Roles.ts diff --git a/web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx b/web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx index 24d75c4..55480b6 100644 --- a/web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx +++ b/web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx @@ -1,8 +1,11 @@ +import { Roles, RolesConstants } from "@/app/ts/Roles"; +import { MapfixStatus } from "@/app/ts/Mapfix"; import { Button, ButtonOwnProps } from "@mui/material"; +import { useState, useEffect } from "react"; type Actions = "Completed" | "Submit" | "Reject" | "Revoke" -type ApiActions = Lowercase | "trigger-validate" | "retry-validate" | "trigger-upload" | "reset-uploading" | "reset-validating" -type Review = Actions | "Accept" | "Validate" | "Upload" | "Reset Uploading (fix softlocked status)" | "Reset Validating (fix softlocked status)" | "Request Changes" +type ApiActions = Lowercase | "request-changes" | "trigger-validate" | "retry-validate" | "trigger-upload" | "reset-uploading" | "reset-validating" +type Review = Actions | "Request Changes" | "Accept" | "Validate" | "Upload" | "Reset Uploading (fix softlocked status)" | "Reset Validating (fix softlocked status)" | "Request Changes" interface ReviewButton { name: Review, @@ -12,7 +15,9 @@ interface ReviewButton { } interface ReviewId { - mapfixId: string + mapfixId: string, + mapfixStatus: number, + mapfixSubmitter: number, } async function ReviewButtonClicked(action: ApiActions, mapfixId: string) { @@ -45,7 +50,6 @@ function ReviewButton(props: ReviewButton) { } export default function ReviewButtons(props: ReviewId) { - const mapfixId = props.mapfixId // When is each button visible? // Multiple buttons can be visible at once. // Action | Role | When Current Status is One of: @@ -59,16 +63,86 @@ export default function ReviewButtons(props: ReviewId) { // RequestChanges | Reviewer | Validated, Accepted, Submitted // Upload | MapAdmin | Validated // ResetUploading | MapAdmin | Uploading + const { mapfixId, mapfixStatus } = props; + const [user, setUser] = useState(null); + const [roles, setRoles] = useState(RolesConstants.Empty); + const [loading, setLoading] = useState(true); + + useEffect(() => { + async function fetchData() { + try { + const [rolesData, userData] = await Promise.all([ + fetch("/api/session/roles").then(rolesResponse => rolesResponse.json()), + fetch("/api/session/user").then(userResponse => userResponse.json()) + ]); + + setRoles(rolesData.Roles); + setUser(userData.UserID); + } catch (error) { + console.error("Error fetching data:", error); + } finally { + setLoading(false); + } + } + + fetchData(); + }, [mapfixId]); + + if (loading) return

Loading...

; + + const visibleButtons: ReviewButton[] = []; + + const is_submitter = user === props.mapfixSubmitter; + if (is_submitter) { + if ([MapfixStatus.UnderConstruction, MapfixStatus.ChangesRequested].includes(mapfixStatus!)) { + visibleButtons.push({ name: "Submit", action: "submit", color: "info", mapfixId }); + } + if ([MapfixStatus.Submitted, MapfixStatus.ChangesRequested].includes(mapfixStatus!)) { + visibleButtons.push({ name: "Revoke", action: "revoke", color: "info", mapfixId }); + } + } + + if (roles&RolesConstants.MapfixReview) { + // you can't review your own mapfix! + // note that this means there needs to be more than one person with MapfixReview + if (!is_submitter && mapfixStatus === MapfixStatus.Submitted) { + visibleButtons.push({ name: "Accept", action: "trigger-validate", color: "info", mapfixId }); + visibleButtons.push({ name: "Reject", action: "reject", color: "error", mapfixId }); + } + if (mapfixStatus === MapfixStatus.Accepted) { + visibleButtons.push({ name: "Validate", action: "retry-validate", color: "info", mapfixId }); + } + if (mapfixStatus === MapfixStatus.Validating) { + visibleButtons.push({ name: "Reset Validating (fix softlocked status)", action: "reset-validating", color: "error", mapfixId }); + } + // this button serves the same purpose as Revoke if you are both + // the map submitter and have MapfixReview when status is Submitted + if ( + [MapfixStatus.Validated, MapfixStatus.Accepted].includes(mapfixStatus!) + || !is_submitter && mapfixStatus == MapfixStatus.Submitted + ) { + visibleButtons.push({ name: "Request Changes", action: "request-changes", color: "error", mapfixId }); + } + } + + if (roles&RolesConstants.MapfixUpload) { + if (mapfixStatus === MapfixStatus.Validated) { + visibleButtons.push({ name: "Upload", action: "trigger-upload", color: "info", mapfixId }); + } + if (mapfixStatus === MapfixStatus.Uploading) { + visibleButtons.push({ name: "Reset Uploading (fix softlocked status)", action: "reset-uploading", color: "error", mapfixId }); + } + } + return (
- - - - - - - - + {visibleButtons.length === 0 ? ( +

No available actions

+ ) : ( + visibleButtons.map((btn) => ( + + )) + )}
- ) + ); } diff --git a/web/src/app/mapfixes/[mapfixId]/page.tsx b/web/src/app/mapfixes/[mapfixId]/page.tsx index cc38cb3..43d3f2e 100644 --- a/web/src/app/mapfixes/[mapfixId]/page.tsx +++ b/web/src/app/mapfixes/[mapfixId]/page.tsx @@ -15,7 +15,9 @@ import { useState, useEffect } from "react"; import "./(styles)/page.scss"; interface ReviewId { - mapfixId: string + mapfixId: string, + mapfixStatus: number; + mapfixSubmitter: number, } function Ratings() { @@ -46,7 +48,7 @@ function RatingArea(mapfix: ReviewId) { - + ) } @@ -96,7 +98,7 @@ export default function MapfixInfoPage() {
- +
diff --git a/web/src/app/submissions/[submissionId]/_reviewButtons.tsx b/web/src/app/submissions/[submissionId]/_reviewButtons.tsx index 822327c..069cfd6 100644 --- a/web/src/app/submissions/[submissionId]/_reviewButtons.tsx +++ b/web/src/app/submissions/[submissionId]/_reviewButtons.tsx @@ -1,8 +1,11 @@ +import { Roles, RolesConstants } from "@/app/ts/Roles"; +import { SubmissionStatus } from "@/app/ts/Submission"; import { Button, ButtonOwnProps } from "@mui/material"; +import { useState, useEffect } from "react"; type Actions = "Completed" | "Submit" | "Reject" | "Revoke" -type ApiActions = Lowercase | "trigger-validate" | "retry-validate" | "trigger-upload" | "reset-uploading" | "reset-validating" -type Review = Actions | "Accept" | "Validate" | "Upload" | "Reset Uploading (fix softlocked status)" | "Reset Validating (fix softlocked status)" | "Request Changes" +type ApiActions = Lowercase | "request-changes" | "trigger-validate" | "retry-validate" | "trigger-upload" | "reset-uploading" | "reset-validating" +type Review = Actions | "Request Changes" | "Accept" | "Validate" | "Upload" | "Reset Uploading (fix softlocked status)" | "Reset Validating (fix softlocked status)" | "Request Changes" interface ReviewButton { name: Review, @@ -12,7 +15,9 @@ interface ReviewButton { } interface ReviewId { - submissionId: string + submissionId: string, + submissionStatus: number, + submissionSubmitter: number, } async function ReviewButtonClicked(action: ApiActions, submissionId: string) { @@ -45,7 +50,6 @@ function ReviewButton(props: ReviewButton) { } export default function ReviewButtons(props: ReviewId) { - const submissionId = props.submissionId // When is each button visible? // Multiple buttons can be visible at once. // Action | Role | When Current Status is One of: @@ -59,16 +63,86 @@ export default function ReviewButtons(props: ReviewId) { // RequestChanges | Reviewer | Validated, Accepted, Submitted // Upload | MapAdmin | Validated // ResetUploading | MapAdmin | Uploading + const { submissionId, submissionStatus } = props; + const [user, setUser] = useState(null); + const [roles, setRoles] = useState(RolesConstants.Empty); + const [loading, setLoading] = useState(true); + + useEffect(() => { + async function fetchData() { + try { + const [rolesData, userData] = await Promise.all([ + fetch("/api/session/roles").then(rolesResponse => rolesResponse.json()), + fetch("/api/session/user").then(userResponse => userResponse.json()) + ]); + + setRoles(rolesData.Roles); + setUser(userData.UserID); + } catch (error) { + console.error("Error fetching data:", error); + } finally { + setLoading(false); + } + } + + fetchData(); + }, [submissionId]); + + if (loading) return

Loading...

; + + const visibleButtons: ReviewButton[] = []; + + const is_submitter = user === props.submissionSubmitter; + if (is_submitter) { + if ([SubmissionStatus.UnderConstruction, SubmissionStatus.ChangesRequested].includes(submissionStatus!)) { + visibleButtons.push({ name: "Submit", action: "submit", color: "info", submissionId }); + } + if ([SubmissionStatus.Submitted, SubmissionStatus.ChangesRequested].includes(submissionStatus!)) { + visibleButtons.push({ name: "Revoke", action: "revoke", color: "info", submissionId }); + } + } + + if (roles&RolesConstants.SubmissionReview) { + // you can't review your own submission! + // note that this means there needs to be more than one person with SubmissionReview + if (!is_submitter && submissionStatus === SubmissionStatus.Submitted) { + visibleButtons.push({ name: "Accept", action: "trigger-validate", color: "info", submissionId }); + visibleButtons.push({ name: "Reject", action: "reject", color: "error", submissionId }); + } + if (submissionStatus === SubmissionStatus.Accepted) { + visibleButtons.push({ name: "Validate", action: "retry-validate", color: "info", submissionId }); + } + if (submissionStatus === SubmissionStatus.Validating) { + visibleButtons.push({ name: "Reset Validating (fix softlocked status)", action: "reset-validating", color: "error", submissionId }); + } + // this button serves the same purpose as Revoke if you are both + // the map submitter and have SubmissionReview when status is Submitted + if ( + [SubmissionStatus.Validated, SubmissionStatus.Accepted].includes(submissionStatus!) + || !is_submitter && submissionStatus == SubmissionStatus.Submitted + ) { + visibleButtons.push({ name: "Request Changes", action: "request-changes", color: "error", submissionId }); + } + } + + if (roles&RolesConstants.SubmissionUpload) { + if (submissionStatus === SubmissionStatus.Validated) { + visibleButtons.push({ name: "Upload", action: "trigger-upload", color: "info", submissionId }); + } + if (submissionStatus === SubmissionStatus.Uploading) { + visibleButtons.push({ name: "Reset Uploading (fix softlocked status)", action: "reset-uploading", color: "error", submissionId }); + } + } + return (
- - - - - - - - + {visibleButtons.length === 0 ? ( +

No available actions

+ ) : ( + visibleButtons.map((btn) => ( + + )) + )}
- ) + ); } diff --git a/web/src/app/submissions/[submissionId]/page.tsx b/web/src/app/submissions/[submissionId]/page.tsx index c78ba8b..9b8d4d1 100644 --- a/web/src/app/submissions/[submissionId]/page.tsx +++ b/web/src/app/submissions/[submissionId]/page.tsx @@ -17,6 +17,8 @@ import "./(styles)/page.scss"; interface ReviewId { submissionId: string; assetId: number; + submissionStatus: number; + submissionSubmitter: number, } function Ratings() { @@ -47,7 +49,7 @@ function RatingArea(submission: ReviewId) { - + ) } @@ -97,7 +99,7 @@ export default function SubmissionInfoPage() {
- +
diff --git a/web/src/app/ts/Mapfix.ts b/web/src/app/ts/Mapfix.ts index 0a5bd25..0570db0 100644 --- a/web/src/app/ts/Mapfix.ts +++ b/web/src/app/ts/Mapfix.ts @@ -8,6 +8,7 @@ const enum MapfixStatus { Uploading = 6, Uploaded = 7, Rejected = 8, + // MapfixStatus does not have a Released state } interface MapfixInfo { diff --git a/web/src/app/ts/Roles.ts b/web/src/app/ts/Roles.ts new file mode 100644 index 0000000..57a7d9d --- /dev/null +++ b/web/src/app/ts/Roles.ts @@ -0,0 +1,25 @@ +type Roles = number; + +// Constants +const RolesConstants = { + All: -1 as Roles, + SubmissionUpload: 1 << 6 as Roles, + SubmissionReview: 1 << 5 as Roles, + SubmissionRelease: 1 << 4 as Roles, + ScriptWrite: 1 << 3 as Roles, + MapfixUpload: 1 << 2 as Roles, + MapfixReview: 1 << 1 as Roles, + MapDownload: 1 << 0 as Roles, + Empty: 0 as Roles, +}; + +// Operations +function hasRole(flags: Roles, role: Roles): boolean { + return (flags & role) === role; +} + +export { + type Roles, + RolesConstants, + hasRole, +}; -- 2.49.1 From 66890ccd44ea23f6f5538ffedfe36b8f2d1a75e1 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 4 Apr 2025 15:35:38 -0700 Subject: [PATCH 304/454] validation: detect nats filter_subject mismatch and update consumer --- validation/src/main.rs | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/validation/src/main.rs b/validation/src/main.rs index 30b443d..5e59e43 100644 --- a/validation/src/main.rs +++ b/validation/src/main.rs @@ -20,6 +20,7 @@ pub enum StartupError{ NatsConnect(async_nats::ConnectError), NatsGetStream(async_nats::jetstream::context::GetStreamError), NatsConsumer(async_nats::jetstream::stream::ConsumerError), + NatsConsumerUpdate(async_nats::jetstream::stream::ConsumerUpdateError), NatsStream(async_nats::jetstream::consumer::StreamError), } impl std::fmt::Display for StartupError{ @@ -52,17 +53,32 @@ async fn main()->Result<(),StartupError>{ // nats let nats_host=std::env::var("NATS_HOST").expect("NATS_HOST env required"); let nats_fut=async{ + const STREAM_NAME:&str="maptest"; + const DURABLE_NAME:&str="validation"; + const FILTER_SUBJECT:&str="maptest.>"; + + let nats_config=async_nats::jetstream::consumer::pull::Config{ + name:Some(DURABLE_NAME.to_owned()), + durable_name:Some(DURABLE_NAME.to_owned()), + filter_subject:FILTER_SUBJECT.to_owned(), + ..Default::default() + }; + let nasty=async_nats::connect(nats_host).await.map_err(StartupError::NatsConnect)?; + // use nats jetstream - async_nats::jetstream::new(nasty) - .get_stream("maptest").await.map_err(StartupError::NatsGetStream)? - .get_or_create_consumer("validation",async_nats::jetstream::consumer::pull::Config{ - name:Some("validation".to_owned()), - durable_name:Some("validation".to_owned()), - filter_subject:"maptest.>".to_owned(), - ..Default::default() - }).await.map_err(StartupError::NatsConsumer)? - .messages().await.map_err(StartupError::NatsStream) + let stream=async_nats::jetstream::new(nasty) + .get_stream(STREAM_NAME).await.map_err(StartupError::NatsGetStream)?; + + let consumer=stream.get_or_create_consumer(DURABLE_NAME,nats_config.clone()).await.map_err(StartupError::NatsConsumer)?; + + // check if config matches expected config + if consumer.cached_info().config.filter_subject!=FILTER_SUBJECT{ + stream.update_consumer(nats_config).await.map_err(StartupError::NatsConsumerUpdate)?; + } + + // only need messages + consumer.messages().await.map_err(StartupError::NatsStream) }; let message_handler=message_handler::MessageHandler::new(cookie_context,group_id,api); -- 2.49.1 From 986ecfc7ad667095886ea1f0b7a7687e5b93d181 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 4 Apr 2025 15:42:43 -0700 Subject: [PATCH 305/454] docker: use tagged muslrust --- validation/Containerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validation/Containerfile b/validation/Containerfile index 2e76d3e..43d3ca7 100644 --- a/validation/Containerfile +++ b/validation/Containerfile @@ -1,6 +1,6 @@ # Using the `rust-musl-builder` as base image, instead of # the official Rust toolchain -FROM registry.itzana.me/docker-proxy/clux/muslrust:stable AS chef +FROM registry.itzana.me/docker-proxy/clux/muslrust:1.86.0-stable AS chef USER root RUN cargo install cargo-chef WORKDIR /app -- 2.49.1 From 66e0d22ccd72af01b1c88accb63a55123516d6f5 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 4 Apr 2025 15:56:18 -0700 Subject: [PATCH 306/454] web: add bare bones map info --- web/src/app/maps/[mapId]/page.tsx | 37 +++++++++++++++++++++++++------ web/src/app/ts/Map.ts | 11 +++++++++ 2 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 web/src/app/ts/Map.ts diff --git a/web/src/app/maps/[mapId]/page.tsx b/web/src/app/maps/[mapId]/page.tsx index 4985e3c..6123535 100644 --- a/web/src/app/maps/[mapId]/page.tsx +++ b/web/src/app/maps/[mapId]/page.tsx @@ -1,8 +1,10 @@ "use client" +import { MapInfo } from "@/app/ts/Map"; import Webpage from "@/app/_components/webpage"; import { useParams } from "next/navigation"; import Link from "next/link"; +import { useState, useEffect } from "react"; interface Button { name: string, @@ -17,12 +19,33 @@ function Button(button: Button) { } export default function Map() { - const { mapId } = useParams() + const {mapId} = useParams() - return ( - -

map { mapId }

- - - ) + +function Button({ name, href }: ButtonProps) { + return ( + + + {name} + + + ); } export default function Map() { - const {mapId} = useParams() + const { mapId } = useParams(); + const [map, setMap] = useState(null); - const [map, setMap] = useState(null) + useEffect(() => { + async function getMap() { + const res = await fetch(`/api/maps/${mapId}`); + if (res.ok) { + setMap(await res.json()); + } + } + getMap(); + }, [mapId]); - useEffect(() => { // needs to be client sided since server doesn't have a session, nextjs got mad at me for exporting an async function: (https://nextjs.org/docs/messages/no-async-client-component) - async function getMap() { - const res = await fetch(`/api/maps/${mapId}`) - if (res.ok) { - setMap(await res.json()) - } - } - getMap() - }, [mapId]) - - if (!map) { - return - {/* TODO: Add skeleton loading thingy ? Maybe ? (https://mui.com/material-ui/react-skeleton/) */} - - } return ( - -

MapID: { mapId }

-

Display Name: { map.DisplayName }

-

Creator: { map.Creator }

-

GameID: { map.GameID }

-

Release Date: { new Date(map.Date * 1000).toLocaleString() }

-
- + - Page {currentPage + 1} of {totalPages} + Page {currentPage} of {totalPages} - +
{currentCards.map((mapfix) => ( diff --git a/web/src/app/submissions/page.tsx b/web/src/app/submissions/page.tsx index 05b877c..1a4b831 100644 --- a/web/src/app/submissions/page.tsx +++ b/web/src/app/submissions/page.tsx @@ -1,39 +1,39 @@ 'use client' import React, { useState, useEffect } from "react"; -import { SubmissionInfo } from "../ts/Submission"; +import { SubmissionList } from "../ts/Submission"; import { SubmissionCard } from "../_components/mapCard"; import Webpage from "@/app/_components/webpage"; import "./(styles)/page.scss"; export default function SubmissionInfoPage() { - const [submissions, setSubmissions] = useState([]) - const [currentPage, setCurrentPage] = useState(0); + const [submissions, setSubmissions] = useState({Total:0,Submissions:[]}) + const [currentPage, setCurrentPage] = useState(1); const cardsPerPage = 24; // built to fit on a 1920x1080 monitor - const totalPages = Math.ceil(submissions.length / cardsPerPage); + const totalPages = Math.ceil(submissions.Total / cardsPerPage); - const currentCards = submissions.slice( - currentPage * cardsPerPage, - (currentPage + 1) * cardsPerPage + const currentCards = submissions.Submissions.slice( + (currentPage - 1) * cardsPerPage, + currentPage * cardsPerPage ); const nextPage = () => { - if (currentPage < totalPages - 1) { + if (currentPage < totalPages) { setCurrentPage(currentPage + 1); } }; const prevPage = () => { - if (currentPage > 0) { + if (currentPage > 1) { setCurrentPage(currentPage - 1); } }; useEffect(() => { async function fetchSubmissions() { - const res = await fetch('/api/submissions?Page=1&Limit=100') + const res = await fetch(`/api/submissions?Page=${currentPage}&Limit=${cardsPerPage}`) if (res.ok) { setSubmissions(await res.json()) } @@ -42,7 +42,7 @@ export default function SubmissionInfoPage() { setTimeout(() => { fetchSubmissions() }, 50); - }, []) + }, [currentPage]) if (!submissions) { return @@ -52,7 +52,7 @@ export default function SubmissionInfoPage() { } - if (submissions && submissions.length == 0) { + if (submissions && submissions.Total == 0) { return
Submissions list is empty. @@ -86,11 +86,11 @@ export default function SubmissionInfoPage() { ))}
- + - Page {currentPage + 1} of {totalPages} + Page {currentPage} of {totalPages} - +
{currentCards.map((submission) => ( diff --git a/web/src/app/ts/Mapfix.ts b/web/src/app/ts/Mapfix.ts index b761fb1..e9727d8 100644 --- a/web/src/app/ts/Mapfix.ts +++ b/web/src/app/ts/Mapfix.ts @@ -28,6 +28,11 @@ interface MapfixInfo { readonly StatusMessage: string, } +interface MapfixList { + readonly Total: number, + readonly Mapfixes: MapfixInfo[], +} + function MapfixStatusToString(mapfix_status: MapfixStatus): string { switch (mapfix_status) { case MapfixStatus.Rejected: @@ -56,5 +61,6 @@ function MapfixStatusToString(mapfix_status: MapfixStatus): string { export { MapfixStatus, MapfixStatusToString, - type MapfixInfo + type MapfixInfo, + type MapfixList, } diff --git a/web/src/app/ts/Submission.ts b/web/src/app/ts/Submission.ts index cbae508..a2003f1 100644 --- a/web/src/app/ts/Submission.ts +++ b/web/src/app/ts/Submission.ts @@ -28,6 +28,11 @@ interface SubmissionInfo { readonly StatusMessage: string, } +interface SubmissionList { + readonly Total: number, + readonly Submissions: SubmissionInfo[], +} + function SubmissionStatusToString(submission_status: SubmissionStatus): string { switch (submission_status) { case SubmissionStatus.Released: @@ -58,5 +63,6 @@ function SubmissionStatusToString(submission_status: SubmissionStatus): string { export { SubmissionStatus, SubmissionStatusToString, - type SubmissionInfo + type SubmissionInfo, + type SubmissionList, } -- 2.49.1 From c9ba2e3e6e48a26064d0b3552c81f8d5836e97a9 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sun, 6 Apr 2025 15:15:23 -0700 Subject: [PATCH 336/454] web: use date descending sort --- web/src/app/mapfixes/page.tsx | 3 ++- web/src/app/submissions/page.tsx | 3 ++- web/src/app/ts/Sort.ts | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 web/src/app/ts/Sort.ts diff --git a/web/src/app/mapfixes/page.tsx b/web/src/app/mapfixes/page.tsx index 46f97f9..33057ff 100644 --- a/web/src/app/mapfixes/page.tsx +++ b/web/src/app/mapfixes/page.tsx @@ -8,6 +8,7 @@ import Webpage from "@/app/_components/webpage"; // TODO: MAKE MAPFIX & SUBMISSIONS USE THE SAME COMPONENTS :angry: (currently too lazy) import "./(styles)/page.scss"; +import { ListSortConstants } from "../ts/Sort"; export default function MapfixInfoPage() { const [mapfixes, setMapfixes] = useState({Total:0,Mapfixes:[]}) @@ -35,7 +36,7 @@ export default function MapfixInfoPage() { useEffect(() => { async function fetchMapfixes() { - const res = await fetch(`/api/mapfixes?Page=${currentPage}&Limit=${cardsPerPage}`) + const res = await fetch(`/api/mapfixes?Page=${currentPage}&Limit=${cardsPerPage}&Sort=${ListSortConstants.ListSortDateDescending}`) if (res.ok) { setMapfixes(await res.json()) } diff --git a/web/src/app/submissions/page.tsx b/web/src/app/submissions/page.tsx index 1a4b831..e3bf37c 100644 --- a/web/src/app/submissions/page.tsx +++ b/web/src/app/submissions/page.tsx @@ -6,6 +6,7 @@ import { SubmissionCard } from "../_components/mapCard"; import Webpage from "@/app/_components/webpage"; import "./(styles)/page.scss"; +import { ListSortConstants } from "../ts/Sort"; export default function SubmissionInfoPage() { const [submissions, setSubmissions] = useState({Total:0,Submissions:[]}) @@ -33,7 +34,7 @@ export default function SubmissionInfoPage() { useEffect(() => { async function fetchSubmissions() { - const res = await fetch(`/api/submissions?Page=${currentPage}&Limit=${cardsPerPage}`) + const res = await fetch(`/api/submissions?Page=${currentPage}&Limit=${cardsPerPage}&Sort=${ListSortConstants.ListSortDateDescending}`) if (res.ok) { setSubmissions(await res.json()) } diff --git a/web/src/app/ts/Sort.ts b/web/src/app/ts/Sort.ts new file mode 100644 index 0000000..2df66e6 --- /dev/null +++ b/web/src/app/ts/Sort.ts @@ -0,0 +1,15 @@ +type ListSort = number; + +// Constants +const ListSortConstants = { + ListSortDisabled: 0, + ListSortDisplayNameAscending: 1, + ListSortDisplayNameDescending: 2, + ListSortDateAscending: 3, + ListSortDateDescending: 4, +}; + +export { + type ListSort, + ListSortConstants, +}; -- 2.49.1 From 29e414d6e74f1ecc95665da8de302e3f649431c3 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sun, 6 Apr 2025 15:29:20 -0700 Subject: [PATCH 337/454] openapi: more filtering options for listing submissions --- openapi.yaml | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/openapi.yaml b/openapi.yaml index 8179ac6..b4646d3 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -185,6 +185,31 @@ paths: format: int32 minimum: 0 maximum: 4 + - name: Submitter + in: query + schema: + type: integer + format: int64 + minimum: 0 + - name: AssetID + in: query + schema: + type: integer + format: int64 + minimum: 0 + - name: TargetAssetID + in: query + schema: + type: integer + format: int64 + minimum: 0 + - name: StatusID + in: query + schema: + type: integer + format: int32 + minimum: 0 + maximum: 8 responses: "200": description: Successful response @@ -500,6 +525,31 @@ paths: format: int32 minimum: 0 maximum: 4 + - name: Submitter + in: query + schema: + type: integer + format: int64 + minimum: 0 + - name: AssetID + in: query + schema: + type: integer + format: int64 + minimum: 0 + - name: UploadedAssetID + in: query + schema: + type: integer + format: int64 + minimum: 0 + - name: StatusID + in: query + schema: + type: integer + format: int32 + minimum: 0 + maximum: 9 responses: "200": description: Successful response -- 2.49.1 From cac288d73b95abe75b790fa87329e40e56b47a04 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sun, 6 Apr 2025 15:29:50 -0700 Subject: [PATCH 338/454] openapi: generate --- pkg/api/oas_client_gen.go | 136 ++++++++ pkg/api/oas_handlers_gen.go | 32 ++ pkg/api/oas_parameters_gen.go | 624 +++++++++++++++++++++++++++++++++- 3 files changed, 780 insertions(+), 12 deletions(-) diff --git a/pkg/api/oas_client_gen.go b/pkg/api/oas_client_gen.go index 5130a84..6f6b0a7 100644 --- a/pkg/api/oas_client_gen.go +++ b/pkg/api/oas_client_gen.go @@ -3981,6 +3981,74 @@ func (c *Client) sendListMapfixes(ctx context.Context, params ListMapfixesParams return res, errors.Wrap(err, "encode query") } } + { + // Encode "Submitter" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "Submitter", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.Submitter.Get(); ok { + return e.EncodeValue(conv.Int64ToString(val)) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "AssetID" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "AssetID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.AssetID.Get(); ok { + return e.EncodeValue(conv.Int64ToString(val)) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "TargetAssetID" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "TargetAssetID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.TargetAssetID.Get(); ok { + return e.EncodeValue(conv.Int64ToString(val)) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "StatusID" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "StatusID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.StatusID.Get(); ok { + return e.EncodeValue(conv.Int32ToString(val)) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } u.RawQuery = q.Values().Encode() stage = "EncodeRequest" @@ -4669,6 +4737,74 @@ func (c *Client) sendListSubmissions(ctx context.Context, params ListSubmissions return res, errors.Wrap(err, "encode query") } } + { + // Encode "Submitter" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "Submitter", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.Submitter.Get(); ok { + return e.EncodeValue(conv.Int64ToString(val)) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "AssetID" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "AssetID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.AssetID.Get(); ok { + return e.EncodeValue(conv.Int64ToString(val)) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "UploadedAssetID" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "UploadedAssetID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.UploadedAssetID.Get(); ok { + return e.EncodeValue(conv.Int64ToString(val)) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "StatusID" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "StatusID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.StatusID.Get(); ok { + return e.EncodeValue(conv.Int32ToString(val)) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } u.RawQuery = q.Values().Encode() stage = "EncodeRequest" diff --git a/pkg/api/oas_handlers_gen.go b/pkg/api/oas_handlers_gen.go index 887991e..7a5dee0 100644 --- a/pkg/api/oas_handlers_gen.go +++ b/pkg/api/oas_handlers_gen.go @@ -5768,6 +5768,22 @@ func (s *Server) handleListMapfixesRequest(args [0]string, argsEscaped bool, w h Name: "Sort", In: "query", }: params.Sort, + { + Name: "Submitter", + In: "query", + }: params.Submitter, + { + Name: "AssetID", + In: "query", + }: params.AssetID, + { + Name: "TargetAssetID", + In: "query", + }: params.TargetAssetID, + { + Name: "StatusID", + In: "query", + }: params.StatusID, }, Raw: r, } @@ -6444,6 +6460,22 @@ func (s *Server) handleListSubmissionsRequest(args [0]string, argsEscaped bool, Name: "Sort", In: "query", }: params.Sort, + { + Name: "Submitter", + In: "query", + }: params.Submitter, + { + Name: "AssetID", + In: "query", + }: params.AssetID, + { + Name: "UploadedAssetID", + In: "query", + }: params.UploadedAssetID, + { + Name: "StatusID", + In: "query", + }: params.StatusID, }, Raw: r, } diff --git a/pkg/api/oas_parameters_gen.go b/pkg/api/oas_parameters_gen.go index 8906f37..ed558bd 100644 --- a/pkg/api/oas_parameters_gen.go +++ b/pkg/api/oas_parameters_gen.go @@ -2175,12 +2175,16 @@ func decodeGetSubmissionParams(args [1]string, argsEscaped bool, r *http.Request // ListMapfixesParams is parameters of listMapfixes operation. type ListMapfixesParams struct { - Page int32 - Limit int32 - DisplayName OptString - Creator OptString - GameID OptInt32 - Sort OptInt32 + Page int32 + Limit int32 + DisplayName OptString + Creator OptString + GameID OptInt32 + Sort OptInt32 + Submitter OptInt64 + AssetID OptInt64 + TargetAssetID OptInt64 + StatusID OptInt32 } func unpackListMapfixesParams(packed middleware.Parameters) (params ListMapfixesParams) { @@ -2234,6 +2238,42 @@ func unpackListMapfixesParams(packed middleware.Parameters) (params ListMapfixes params.Sort = v.(OptInt32) } } + { + key := middleware.ParameterKey{ + Name: "Submitter", + In: "query", + } + if v, ok := packed[key]; ok { + params.Submitter = v.(OptInt64) + } + } + { + key := middleware.ParameterKey{ + Name: "AssetID", + In: "query", + } + if v, ok := packed[key]; ok { + params.AssetID = v.(OptInt64) + } + } + { + key := middleware.ParameterKey{ + Name: "TargetAssetID", + In: "query", + } + if v, ok := packed[key]; ok { + params.TargetAssetID = v.(OptInt64) + } + } + { + key := middleware.ParameterKey{ + Name: "StatusID", + In: "query", + } + if v, ok := packed[key]; ok { + params.StatusID = v.(OptInt32) + } + } return params } @@ -2603,6 +2643,266 @@ func decodeListMapfixesParams(args [0]string, argsEscaped bool, r *http.Request) Err: err, } } + // Decode query: Submitter. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "Submitter", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotSubmitterVal int64 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + paramsDotSubmitterVal = c + return nil + }(); err != nil { + return err + } + params.Submitter.SetTo(paramsDotSubmitterVal) + return nil + }); err != nil { + return err + } + if err := func() error { + if value, ok := params.Submitter.Get(); ok { + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(value)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "Submitter", + In: "query", + Err: err, + } + } + // Decode query: AssetID. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "AssetID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotAssetIDVal int64 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + paramsDotAssetIDVal = c + return nil + }(); err != nil { + return err + } + params.AssetID.SetTo(paramsDotAssetIDVal) + return nil + }); err != nil { + return err + } + if err := func() error { + if value, ok := params.AssetID.Get(); ok { + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(value)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "AssetID", + In: "query", + Err: err, + } + } + // Decode query: TargetAssetID. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "TargetAssetID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotTargetAssetIDVal int64 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + paramsDotTargetAssetIDVal = c + return nil + }(); err != nil { + return err + } + params.TargetAssetID.SetTo(paramsDotTargetAssetIDVal) + return nil + }); err != nil { + return err + } + if err := func() error { + if value, ok := params.TargetAssetID.Get(); ok { + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(value)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "TargetAssetID", + In: "query", + Err: err, + } + } + // Decode query: StatusID. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "StatusID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotStatusIDVal int32 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + paramsDotStatusIDVal = c + return nil + }(); err != nil { + return err + } + params.StatusID.SetTo(paramsDotStatusIDVal) + return nil + }); err != nil { + return err + } + if err := func() error { + if value, ok := params.StatusID.Get(); ok { + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: true, + Max: 8, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(value)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "StatusID", + In: "query", + Err: err, + } + } return params, nil } @@ -3907,12 +4207,16 @@ func decodeListScriptsParams(args [0]string, argsEscaped bool, r *http.Request) // ListSubmissionsParams is parameters of listSubmissions operation. type ListSubmissionsParams struct { - Page int32 - Limit int32 - DisplayName OptString - Creator OptString - GameID OptInt32 - Sort OptInt32 + Page int32 + Limit int32 + DisplayName OptString + Creator OptString + GameID OptInt32 + Sort OptInt32 + Submitter OptInt64 + AssetID OptInt64 + UploadedAssetID OptInt64 + StatusID OptInt32 } func unpackListSubmissionsParams(packed middleware.Parameters) (params ListSubmissionsParams) { @@ -3966,6 +4270,42 @@ func unpackListSubmissionsParams(packed middleware.Parameters) (params ListSubmi params.Sort = v.(OptInt32) } } + { + key := middleware.ParameterKey{ + Name: "Submitter", + In: "query", + } + if v, ok := packed[key]; ok { + params.Submitter = v.(OptInt64) + } + } + { + key := middleware.ParameterKey{ + Name: "AssetID", + In: "query", + } + if v, ok := packed[key]; ok { + params.AssetID = v.(OptInt64) + } + } + { + key := middleware.ParameterKey{ + Name: "UploadedAssetID", + In: "query", + } + if v, ok := packed[key]; ok { + params.UploadedAssetID = v.(OptInt64) + } + } + { + key := middleware.ParameterKey{ + Name: "StatusID", + In: "query", + } + if v, ok := packed[key]; ok { + params.StatusID = v.(OptInt32) + } + } return params } @@ -4335,6 +4675,266 @@ func decodeListSubmissionsParams(args [0]string, argsEscaped bool, r *http.Reque Err: err, } } + // Decode query: Submitter. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "Submitter", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotSubmitterVal int64 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + paramsDotSubmitterVal = c + return nil + }(); err != nil { + return err + } + params.Submitter.SetTo(paramsDotSubmitterVal) + return nil + }); err != nil { + return err + } + if err := func() error { + if value, ok := params.Submitter.Get(); ok { + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(value)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "Submitter", + In: "query", + Err: err, + } + } + // Decode query: AssetID. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "AssetID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotAssetIDVal int64 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + paramsDotAssetIDVal = c + return nil + }(); err != nil { + return err + } + params.AssetID.SetTo(paramsDotAssetIDVal) + return nil + }); err != nil { + return err + } + if err := func() error { + if value, ok := params.AssetID.Get(); ok { + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(value)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "AssetID", + In: "query", + Err: err, + } + } + // Decode query: UploadedAssetID. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "UploadedAssetID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotUploadedAssetIDVal int64 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + paramsDotUploadedAssetIDVal = c + return nil + }(); err != nil { + return err + } + params.UploadedAssetID.SetTo(paramsDotUploadedAssetIDVal) + return nil + }); err != nil { + return err + } + if err := func() error { + if value, ok := params.UploadedAssetID.Get(); ok { + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(value)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "UploadedAssetID", + In: "query", + Err: err, + } + } + // Decode query: StatusID. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "StatusID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotStatusIDVal int32 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + paramsDotStatusIDVal = c + return nil + }(); err != nil { + return err + } + params.StatusID.SetTo(paramsDotStatusIDVal) + return nil + }); err != nil { + return err + } + if err := func() error { + if value, ok := params.StatusID.Get(); ok { + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: true, + Max: 9, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(value)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "StatusID", + In: "query", + Err: err, + } + } return params, nil } -- 2.49.1 From 412f34817c73b892efa778dda046600739bddd36 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sun, 6 Apr 2025 15:31:45 -0700 Subject: [PATCH 339/454] submissions: more filtering options for listing submissions --- pkg/service/mapfixes.go | 12 ++++++++++++ pkg/service/submissions.go | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/pkg/service/mapfixes.go b/pkg/service/mapfixes.go index 3c102b9..903b3d8 100644 --- a/pkg/service/mapfixes.go +++ b/pkg/service/mapfixes.go @@ -172,6 +172,18 @@ func (svc *Service) ListMapfixes(ctx context.Context, params api.ListMapfixesPar if params.GameID.IsSet(){ filter.Add("game_id", params.GameID.Value) } + if params.Submitter.IsSet(){ + filter.Add("submitter", params.Submitter.Value) + } + if params.AssetID.IsSet(){ + filter.Add("asset_id", params.AssetID.Value) + } + if params.TargetAssetID.IsSet(){ + filter.Add("target_asset_id", params.TargetAssetID.Value) + } + if params.StatusID.IsSet(){ + filter.Add("status_id", params.StatusID.Value) + } sort := datastore.ListSort(params.Sort.Or(int32(datastore.ListSortDisabled))) diff --git a/pkg/service/submissions.go b/pkg/service/submissions.go index 4b2bdc0..1bd630e 100644 --- a/pkg/service/submissions.go +++ b/pkg/service/submissions.go @@ -161,6 +161,18 @@ func (svc *Service) ListSubmissions(ctx context.Context, params api.ListSubmissi if params.GameID.IsSet(){ filter.Add("game_id", params.GameID.Value) } + if params.Submitter.IsSet(){ + filter.Add("submitter", params.Submitter.Value) + } + if params.AssetID.IsSet(){ + filter.Add("asset_id", params.AssetID.Value) + } + if params.UploadedAssetID.IsSet(){ + filter.Add("uploaded_asset_id", params.UploadedAssetID.Value) + } + if params.StatusID.IsSet(){ + filter.Add("status_id", params.StatusID.Value) + } sort := datastore.ListSort(params.Sort.Or(int32(datastore.ListSortDisabled))) -- 2.49.1 From 77222c84db273710be68a53c34d2aefd8dcab3f8 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sun, 6 Apr 2025 15:48:37 -0700 Subject: [PATCH 340/454] web: plumb target asset id and submitter --- web/src/app/mapfixes/[mapfixId]/_comments.tsx | 2 ++ web/src/app/mapfixes/[mapfixId]/page.tsx | 4 +++- web/src/app/submissions/[submissionId]/_comments.tsx | 2 ++ web/src/app/submissions/[submissionId]/page.tsx | 4 +++- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/web/src/app/mapfixes/[mapfixId]/_comments.tsx b/web/src/app/mapfixes/[mapfixId]/_comments.tsx index 7c5286a..ca42988 100644 --- a/web/src/app/mapfixes/[mapfixId]/_comments.tsx +++ b/web/src/app/mapfixes/[mapfixId]/_comments.tsx @@ -13,6 +13,8 @@ interface CreatorAndReviewStatus { creator: MapfixInfo["DisplayName"], review: MapfixInfo["StatusID"], status_message: MapfixInfo["StatusMessage"], + submitter: MapfixInfo["Submitter"], + target_asset_id: MapfixInfo["TargetAssetID"], comments: Comment[], name: string } diff --git a/web/src/app/mapfixes/[mapfixId]/page.tsx b/web/src/app/mapfixes/[mapfixId]/page.tsx index 6a1828a..e4ae480 100644 --- a/web/src/app/mapfixes/[mapfixId]/page.tsx +++ b/web/src/app/mapfixes/[mapfixId]/page.tsx @@ -43,7 +43,9 @@ function TitleAndComments(stats: CreatorAndReviewStatus) {

by {stats.creator}

+

Submitter {stats.submitter}

Model Asset ID {stats.asset_id}

+

Target Asset ID {stats.target_asset_id}

Validation Error: {stats.status_message}

@@ -76,7 +78,7 @@ export default function MapfixInfoPage() {
- +
diff --git a/web/src/app/submissions/[submissionId]/_comments.tsx b/web/src/app/submissions/[submissionId]/_comments.tsx index 8e5c84a..ddb61db 100644 --- a/web/src/app/submissions/[submissionId]/_comments.tsx +++ b/web/src/app/submissions/[submissionId]/_comments.tsx @@ -13,6 +13,8 @@ interface CreatorAndReviewStatus { creator: SubmissionInfo["DisplayName"], review: SubmissionInfo["StatusID"], status_message: SubmissionInfo["StatusMessage"], + submitter: SubmissionInfo["Submitter"], + uploaded_asset_id: SubmissionInfo["UploadedAssetID"], comments: Comment[], name: string } diff --git a/web/src/app/submissions/[submissionId]/page.tsx b/web/src/app/submissions/[submissionId]/page.tsx index 6d08bce..81502de 100644 --- a/web/src/app/submissions/[submissionId]/page.tsx +++ b/web/src/app/submissions/[submissionId]/page.tsx @@ -43,7 +43,9 @@ function TitleAndComments(stats: CreatorAndReviewStatus) {

by {stats.creator}

+

Submitter {stats.submitter}

Model Asset ID {stats.asset_id}

+

Uploaded Asset ID {stats.uploaded_asset_id}

Validation Error: {stats.status_message}

@@ -76,7 +78,7 @@ export default function SubmissionInfoPage() {
- +
-- 2.49.1 From d02e3776f3e6f9344534f059e6762e4fd0b4108f Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sun, 6 Apr 2025 15:50:08 -0700 Subject: [PATCH 341/454] web: fix page dots --- web/src/app/mapfixes/page.tsx | 4 ++-- web/src/app/submissions/page.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/src/app/mapfixes/page.tsx b/web/src/app/mapfixes/page.tsx index 33057ff..2945e1e 100644 --- a/web/src/app/mapfixes/page.tsx +++ b/web/src/app/mapfixes/page.tsx @@ -83,8 +83,8 @@ export default function MapfixInfoPage() { {Array.from({ length: totalPages }).map((_, index) => ( setCurrentPage(index)} + className={`dot ${index+1 === currentPage ? 'active' : ''}`} + onClick={() => setCurrentPage(index+1)} > ))} diff --git a/web/src/app/submissions/page.tsx b/web/src/app/submissions/page.tsx index e3bf37c..2ae27dd 100644 --- a/web/src/app/submissions/page.tsx +++ b/web/src/app/submissions/page.tsx @@ -81,8 +81,8 @@ export default function SubmissionInfoPage() { {Array.from({ length: totalPages }).map((_, index) => ( setCurrentPage(index)} + className={`dot ${index+1 === currentPage ? 'active' : ''}`} + onClick={() => setCurrentPage(index+1)} > ))} -- 2.49.1 From 3c3d09c4a7cebb08520d9c381360135a0297dda2 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sun, 6 Apr 2025 15:56:02 -0700 Subject: [PATCH 342/454] web: display target asset thumbnail alongside mapfix --- web/src/app/mapfixes/[mapfixId]/page.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/web/src/app/mapfixes/[mapfixId]/page.tsx b/web/src/app/mapfixes/[mapfixId]/page.tsx index e4ae480..5316490 100644 --- a/web/src/app/mapfixes/[mapfixId]/page.tsx +++ b/web/src/app/mapfixes/[mapfixId]/page.tsx @@ -14,16 +14,24 @@ import "./(styles)/page.scss"; interface ReviewId { mapfixId: string, - mapfixStatus: number; + mapfixStatus: number, mapfixSubmitter: number, - mapfixAssetId: number; + mapfixAssetId: number, + mapfixTargetAssetId: number, } function RatingArea(mapfix: ReviewId) { return ( @@ -77,7 +85,7 @@ export default function MapfixInfoPage() {
- +
-- 2.49.1 From e67d6799010c546c2bff67436cba0adffe7e8546 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Mon, 7 Apr 2025 13:10:24 -0700 Subject: [PATCH 343/454] submissions: rename mapfix const to match submissions --- pkg/model/mapfix.go | 2 +- pkg/service/mapfixes.go | 8 ++++---- pkg/service_internal/mapfixes.go | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/model/mapfix.go b/pkg/model/mapfix.go index a64c71e..b2b4b92 100644 --- a/pkg/model/mapfix.go +++ b/pkg/model/mapfix.go @@ -13,7 +13,7 @@ const ( MapfixStatusUploading MapfixStatus = 6 MapfixStatusValidated MapfixStatus = 5 MapfixStatusValidating MapfixStatus = 4 - MapfixStatusAccepted MapfixStatus = 3 // pending script review, can re-trigger validation + MapfixStatusAcceptedUnvalidated MapfixStatus = 3 // pending script review, can re-trigger validation // Phase: Creation MapfixStatusChangesRequested MapfixStatus = 2 diff --git a/pkg/service/mapfixes.go b/pkg/service/mapfixes.go index 903b3d8..b9d5b78 100644 --- a/pkg/service/mapfixes.go +++ b/pkg/service/mapfixes.go @@ -25,7 +25,7 @@ var( model.MapfixStatusUploading, model.MapfixStatusValidated, model.MapfixStatusValidating, - model.MapfixStatusAccepted, + model.MapfixStatusAcceptedUnvalidated, } // Allow 5 mapfixes every 10 minutes CreateMapfixRateLimit int64 = 5 @@ -326,7 +326,7 @@ func (svc *Service) ActionMapfixRequestChanges(ctx context.Context, params api.A // transaction smap := datastore.Optional() smap.Add("status_id", model.MapfixStatusChangesRequested) - return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidated, model.MapfixStatusAccepted, model.MapfixStatusSubmitted}, smap) + return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidated, model.MapfixStatusAcceptedUnvalidated, model.MapfixStatusSubmitted}, smap) } // ActionMapfixRevoke invokes actionMapfixRevoke operation. @@ -580,7 +580,7 @@ func (svc *Service) ActionMapfixRetryValidate(ctx context.Context, params api.Ac // transaction smap := datastore.Optional() smap.Add("status_id", model.MapfixStatusValidating) - mapfix, err := svc.DB.Mapfixes().IfStatusThenUpdateAndGet(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusAccepted}, smap) + mapfix, err := svc.DB.Mapfixes().IfStatusThenUpdateAndGet(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusAcceptedUnvalidated}, smap) if err != nil { return err } @@ -639,7 +639,7 @@ func (svc *Service) ActionMapfixAccepted(ctx context.Context, params api.ActionM // transaction smap := datastore.Optional() - smap.Add("status_id", model.MapfixStatusAccepted) + smap.Add("status_id", model.MapfixStatusAcceptedUnvalidated) smap.Add("status_message", "Manually forced reset") return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, smap) } diff --git a/pkg/service_internal/mapfixes.go b/pkg/service_internal/mapfixes.go index 1897398..b3e428f 100644 --- a/pkg/service_internal/mapfixes.go +++ b/pkg/service_internal/mapfixes.go @@ -16,7 +16,7 @@ var( model.MapfixStatusUploading, model.MapfixStatusValidated, model.MapfixStatusValidating, - model.MapfixStatusAccepted, + model.MapfixStatusAcceptedUnvalidated, model.MapfixStatusChangesRequested, model.MapfixStatusSubmitted, model.MapfixStatusUnderConstruction, @@ -63,7 +63,7 @@ func (svc *Service) ActionMapfixValidated(ctx context.Context, params internal.A func (svc *Service) ActionMapfixAccepted(ctx context.Context, params internal.ActionMapfixAcceptedParams) error { // transaction smap := datastore.Optional() - smap.Add("status_id", model.MapfixStatusAccepted) + smap.Add("status_id", model.MapfixStatusAcceptedUnvalidated) smap.Add("status_message", params.StatusMessage) return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, smap) } -- 2.49.1 From f610fc1c0f4accb0916986c75f28b89a2f644b35 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Mon, 7 Apr 2025 13:11:01 -0700 Subject: [PATCH 344/454] submissions: change up status ids in preparation of submission validation --- pkg/model/mapfix.go | 25 ++++++++++++++----------- pkg/model/submission.go | 27 +++++++++++++++------------ 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/pkg/model/mapfix.go b/pkg/model/mapfix.go index b2b4b92..6c9d9a7 100644 --- a/pkg/model/mapfix.go +++ b/pkg/model/mapfix.go @@ -5,20 +5,23 @@ import "time" type MapfixStatus int32 const ( - // Phase: Final MapfixStatus - MapfixStatusRejected MapfixStatus = 8 - MapfixStatusUploaded MapfixStatus = 7 // uploaded to the group, final status for mapfixes + // Phase: Creation + MapfixStatusUnderConstruction MapfixStatus = 0 + MapfixStatusChangesRequested MapfixStatus = 1 + + // Phase: Review + MapfixStatusSubmitting MapfixStatus = 2 + MapfixStatusSubmitted MapfixStatus = 3 // Phase: Testing - MapfixStatusUploading MapfixStatus = 6 - MapfixStatusValidated MapfixStatus = 5 - MapfixStatusValidating MapfixStatus = 4 - MapfixStatusAcceptedUnvalidated MapfixStatus = 3 // pending script review, can re-trigger validation + MapfixStatusAcceptedUnvalidated MapfixStatus = 4 // pending script review, can re-trigger validation + MapfixStatusValidating MapfixStatus = 5 + MapfixStatusValidated MapfixStatus = 6 + MapfixStatusUploading MapfixStatus = 7 - // Phase: Creation - MapfixStatusChangesRequested MapfixStatus = 2 - MapfixStatusSubmitted MapfixStatus = 1 - MapfixStatusUnderConstruction MapfixStatus = 0 + // Phase: Final MapfixStatus + MapfixStatusUploaded MapfixStatus = 8 // uploaded to the group, but pending release + MapfixStatusRejected MapfixStatus = 9 ) type Mapfix struct { diff --git a/pkg/model/submission.go b/pkg/model/submission.go index 1bf2130..8001697 100644 --- a/pkg/model/submission.go +++ b/pkg/model/submission.go @@ -5,21 +5,24 @@ import "time" type SubmissionStatus int32 const ( - // Phase: Final SubmissionStatus - SubmissionStatusReleased SubmissionStatus = 9 - SubmissionStatusRejected SubmissionStatus = 8 + // Phase: Creation + SubmissionStatusUnderConstruction SubmissionStatus = 0 + SubmissionStatusChangesRequested SubmissionStatus = 1 + + // Phase: Review + SubmissionStatusSubmitting SubmissionStatus = 2 + SubmissionStatusSubmitted SubmissionStatus = 3 // Phase: Testing - SubmissionStatusUploaded SubmissionStatus = 7 // uploaded to the group, but pending release - SubmissionStatusUploading SubmissionStatus = 6 - SubmissionStatusValidated SubmissionStatus = 5 - SubmissionStatusValidating SubmissionStatus = 4 - SubmissionStatusAcceptedUnvalidated SubmissionStatus = 3 // pending script review, can re-trigger validation + SubmissionStatusAcceptedUnvalidated SubmissionStatus = 4 // pending script review, can re-trigger validation + SubmissionStatusValidating SubmissionStatus = 5 + SubmissionStatusValidated SubmissionStatus = 6 + SubmissionStatusUploading SubmissionStatus = 7 + SubmissionStatusUploaded SubmissionStatus = 8 // uploaded to the group, but pending release - // Phase: Creation - SubmissionStatusChangesRequested SubmissionStatus = 2 - SubmissionStatusSubmitted SubmissionStatus = 1 - SubmissionStatusUnderConstruction SubmissionStatus = 0 + // Phase: Final SubmissionStatus + SubmissionStatusRejected SubmissionStatus = 9 + SubmissionStatusReleased SubmissionStatus = 10 ) type Submission struct { -- 2.49.1 From 4ba3b5cd01b01c22ebd91107dcb871cf849ef9c0 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Mon, 7 Apr 2025 13:15:04 -0700 Subject: [PATCH 345/454] web: change up status ids --- web/src/app/ts/Mapfix.ts | 25 ++++++++++++++----------- web/src/app/ts/Submission.ts | 31 +++++++++++++++++-------------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/web/src/app/ts/Mapfix.ts b/web/src/app/ts/Mapfix.ts index e9727d8..0a2c282 100644 --- a/web/src/app/ts/Mapfix.ts +++ b/web/src/app/ts/Mapfix.ts @@ -1,13 +1,14 @@ const enum MapfixStatus { - UnderConstruction = 0, - Submitted = 1, - ChangesRequested = 2, - AcceptedUnvalidated = 3, - Validating = 4, - Validated = 5, - Uploading = 6, - Uploaded = 7, - Rejected = 8, + UnderConstruction = 0, + ChangesRequested = 1, + Submitting = 2, + Submitted = 3, + AcceptedUnvalidated = 4, + Validating = 5, + Validated = 6, + Uploading = 7, + Uploaded = 8, + Rejected = 9, // MapfixStatus does not have a Released state } @@ -35,8 +36,8 @@ interface MapfixList { function MapfixStatusToString(mapfix_status: MapfixStatus): string { switch (mapfix_status) { - case MapfixStatus.Rejected: - return "REJECTED" + case MapfixStatus.Rejected: + return "REJECTED" case MapfixStatus.Uploading: return "UPLOADING" case MapfixStatus.Uploaded: @@ -51,6 +52,8 @@ function MapfixStatusToString(mapfix_status: MapfixStatus): string { return "CHANGES REQUESTED" case MapfixStatus.Submitted: return "SUBMITTED" + case MapfixStatus.Submitting: + return "SUBMITTING" case MapfixStatus.UnderConstruction: return "UNDER CONSTRUCTION" default: diff --git a/web/src/app/ts/Submission.ts b/web/src/app/ts/Submission.ts index a2003f1..6d24051 100644 --- a/web/src/app/ts/Submission.ts +++ b/web/src/app/ts/Submission.ts @@ -1,14 +1,15 @@ const enum SubmissionStatus { - UnderConstruction = 0, - Submitted = 1, - ChangesRequested = 2, - AcceptedUnvalidated = 3, - Validating = 4, - Validated = 5, - Uploading = 6, - Uploaded = 7, - Rejected = 8, - Released = 9, + UnderConstruction = 0, + ChangesRequested = 1, + Submitting = 2, + Submitted = 3, + AcceptedUnvalidated = 4, + Validating = 5, + Validated = 6, + Uploading = 7, + Uploaded = 8, + Rejected = 9, + Released = 10, } interface SubmissionInfo { @@ -35,10 +36,10 @@ interface SubmissionList { function SubmissionStatusToString(submission_status: SubmissionStatus): string { switch (submission_status) { - case SubmissionStatus.Released: - return "RELEASED" - case SubmissionStatus.Rejected: - return "REJECTED" + case SubmissionStatus.Released: + return "RELEASED" + case SubmissionStatus.Rejected: + return "REJECTED" case SubmissionStatus.Uploading: return "UPLOADING" case SubmissionStatus.Uploaded: @@ -53,6 +54,8 @@ function SubmissionStatusToString(submission_status: SubmissionStatus): string { return "CHANGES REQUESTED" case SubmissionStatus.Submitted: return "SUBMITTED" + case SubmissionStatus.Submitting: + return "SUBMITTING" case SubmissionStatus.UnderConstruction: return "UNDER CONSTRUCTION" default: -- 2.49.1 From 24a5baae7758287532ff91db6dcc2f9416db5538 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Mon, 7 Apr 2025 13:18:20 -0700 Subject: [PATCH 346/454] web: todo: hide Reset buttons for 10 seconds --- web/src/app/submissions/[submissionId]/_reviewButtons.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/app/submissions/[submissionId]/_reviewButtons.tsx b/web/src/app/submissions/[submissionId]/_reviewButtons.tsx index 44862be..345d435 100644 --- a/web/src/app/submissions/[submissionId]/_reviewButtons.tsx +++ b/web/src/app/submissions/[submissionId]/_reviewButtons.tsx @@ -129,6 +129,7 @@ export default function ReviewButtons(props: ReviewId) { if (submissionStatus === SubmissionStatus.Validated) { visibleButtons.push({ name: "Upload", action: "trigger-upload", color: "info", submissionId }); } + // TODO: hide Reset buttons for 10 seconds if (submissionStatus === SubmissionStatus.Uploading) { visibleButtons.push({ name: "Reset Uploading (fix softlocked status)", action: "reset-uploading", color: "error", submissionId }); } -- 2.49.1 From 383bc783a489cc3c1ffab6eb9f85ef78cb0aa5a8 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Mon, 7 Apr 2025 12:54:12 -0700 Subject: [PATCH 347/454] submissions: audit model --- pkg/model/audit_event.go | 53 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 pkg/model/audit_event.go diff --git a/pkg/model/audit_event.go b/pkg/model/audit_event.go new file mode 100644 index 0000000..998f76d --- /dev/null +++ b/pkg/model/audit_event.go @@ -0,0 +1,53 @@ +package model + +import ( + "encoding/json" + "time" +) + +type AuditEventType int32 + +// User clicked "Submit", "Accept" etc +const AuditEventTypeAction AuditEventType = 0 +type AuditEventDataAction struct { + TargetStatus uint32 `json:"target_status"` +} + +// User wrote a comment +const AuditEventTypeComment AuditEventType = 1 +type AuditEventDataComment struct { + Comment string `json:"comment"` +} + +// User changed Model +const AuditEventTypeChangeModel AuditEventType = 2 +type AuditEventDataChangeModel struct { + OldModelID uint64 `json:"old_model_id"` + OldModelVersion uint64 `json:"old_model_version"` + NewModelID uint64 `json:"new_model_id"` + NewModelVersion uint64 `json:"new_model_version"` +} +// Validator validates model +const AuditEventTypeChangeValidatedModel AuditEventType = 3 +type AuditEventDataChangeValidatedModel struct { + ValidatedModelID uint64 `json:"validated_model_id"` + ValidatedModelVersion uint64 `json:"validated_model_version"` +} + +// User changed DisplayName / Creator +const AuditEventTypeChangeDisplayName AuditEventType = 4 +const AuditEventTypeChangeCreator AuditEventType = 5 +type AuditEventDataChangeName struct { + OldName string `json:"old_name"` + NewName string `json:"new_name"` +} + +type AuditEvent struct { + ID int64 `gorm:"primaryKey"` + CreatedAt time.Time + User uint64 + ResourceType ResourceType // is this a submission or is it a mapfix + ResourceID int64 // submission / mapfix / map ID + EventType AuditEventType + EventData json.RawMessage `gorm:"type:jsonb"` +} -- 2.49.1 From 219a15f656bc003b71e2a27f4fdb222e6c532bbf Mon Sep 17 00:00:00 2001 From: Quaternions Date: Mon, 7 Apr 2025 13:40:22 -0700 Subject: [PATCH 348/454] submissions: audit events db table --- pkg/datastore/datastore.go | 9 ++++ pkg/datastore/gormstore/audit_events.go | 64 +++++++++++++++++++++++++ pkg/datastore/gormstore/db.go | 1 + pkg/datastore/gormstore/gormstore.go | 4 ++ 4 files changed, 78 insertions(+) create mode 100644 pkg/datastore/gormstore/audit_events.go diff --git a/pkg/datastore/datastore.go b/pkg/datastore/datastore.go index 3bb704e..1903bd4 100644 --- a/pkg/datastore/datastore.go +++ b/pkg/datastore/datastore.go @@ -24,6 +24,7 @@ const ( ) type Datastore interface { + AuditEvents() AuditEvents Mapfixes() Mapfixes Operations() Operations Submissions() Submissions @@ -31,6 +32,14 @@ type Datastore interface { ScriptPolicy() ScriptPolicy } +type AuditEvents interface { + Get(ctx context.Context, id int64) (model.AuditEvent, error) + Create(ctx context.Context, smap model.AuditEvent) (model.AuditEvent, error) + Update(ctx context.Context, id int64, values OptionalMap) error + Delete(ctx context.Context, id int64) error + List(ctx context.Context, filters OptionalMap, page model.Page) ([]model.AuditEvent, error) +} + type Mapfixes interface { Get(ctx context.Context, id int64) (model.Mapfix, error) GetList(ctx context.Context, id []int64) ([]model.Mapfix, error) diff --git a/pkg/datastore/gormstore/audit_events.go b/pkg/datastore/gormstore/audit_events.go new file mode 100644 index 0000000..efaab4d --- /dev/null +++ b/pkg/datastore/gormstore/audit_events.go @@ -0,0 +1,64 @@ +package gormstore + +import ( + "context" + "errors" + + "git.itzana.me/strafesnet/maps-service/pkg/datastore" + "git.itzana.me/strafesnet/maps-service/pkg/model" + "gorm.io/gorm" +) + +type AuditEvents struct { + db *gorm.DB +} + +func (env *AuditEvents) Get(ctx context.Context, id int64) (model.AuditEvent, error) { + var mdl model.AuditEvent + if err := env.db.First(&mdl, id).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return mdl, datastore.ErrNotExist + } + return mdl, err + } + return mdl, nil +} + +func (env *AuditEvents) Create(ctx context.Context, smap model.AuditEvent) (model.AuditEvent, error) { + if err := env.db.Create(&smap).Error; err != nil { + return smap, err + } + + return smap, nil +} + +func (env *AuditEvents) Update(ctx context.Context, id int64, values datastore.OptionalMap) error { + if err := env.db.Model(&model.AuditEvent{}).Where("id = ?", id).Updates(values.Map()).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return datastore.ErrNotExist + } + return err + } + + return nil +} + +func (env *AuditEvents) Delete(ctx context.Context, id int64) error { + if err := env.db.Delete(&model.AuditEvent{}, id).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return datastore.ErrNotExist + } + return err + } + + return nil +} + +func (env *AuditEvents) List(ctx context.Context, filters datastore.OptionalMap, page model.Page) ([]model.AuditEvent, error) { + var events []model.AuditEvent + if err := env.db.Where(filters.Map()).Offset(int((page.Number - 1) * page.Size)).Limit(int(page.Size)).Find(&events).Error; err != nil { + return nil, err + } + + return events, nil +} diff --git a/pkg/datastore/gormstore/db.go b/pkg/datastore/gormstore/db.go index e4d0ed3..127cfe0 100644 --- a/pkg/datastore/gormstore/db.go +++ b/pkg/datastore/gormstore/db.go @@ -31,6 +31,7 @@ func New(ctx *cli.Context) (datastore.Datastore, error) { if ctx.Bool("migrate") { if err := db.AutoMigrate( + &model.AuditEvent{}, &model.Mapfix{}, &model.Operation{}, &model.Submission{}, diff --git a/pkg/datastore/gormstore/gormstore.go b/pkg/datastore/gormstore/gormstore.go index 7d88b12..da8fe14 100644 --- a/pkg/datastore/gormstore/gormstore.go +++ b/pkg/datastore/gormstore/gormstore.go @@ -9,6 +9,10 @@ type Gormstore struct { db *gorm.DB } +func (g Gormstore) AuditEvents() datastore.AuditEvents { + return &AuditEvents{db: g.db} +} + func (g Gormstore) Mapfixes() datastore.Mapfixes { return &Mapfixes{db: g.db} } -- 2.49.1 From 044033cabfb700b375ad591fa4c7c3cfd89a34c4 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Mon, 7 Apr 2025 14:25:05 -0700 Subject: [PATCH 349/454] submissions: implement audit logging - use uint for Operation.Owner - remove IsSubmitter --- pkg/model/operation.go | 2 +- pkg/service/mapfixes.go | 348 +++++++++++++++++++-- pkg/service/operations.go | 8 +- pkg/service/security.go | 7 - pkg/service/submissions.go | 366 +++++++++++++++++++++-- pkg/service_internal/mapfixes.go | 101 ++++++- pkg/service_internal/service_internal.go | 5 + pkg/service_internal/submissions.go | 132 +++++++- 8 files changed, 888 insertions(+), 81 deletions(-) diff --git a/pkg/model/operation.go b/pkg/model/operation.go index 4949a92..a3fefbc 100644 --- a/pkg/model/operation.go +++ b/pkg/model/operation.go @@ -12,7 +12,7 @@ const ( type Operation struct { ID int32 `gorm:"primaryKey"` CreatedAt time.Time - Owner int64 // UserID + Owner uint64 // UserID StatusID OperationStatus StatusMessage string Path string // redirect to view completed operation e.g. "/mapfixes/4" diff --git a/pkg/service/mapfixes.go b/pkg/service/mapfixes.go index b9d5b78..722d437 100644 --- a/pkg/service/mapfixes.go +++ b/pkg/service/mapfixes.go @@ -103,7 +103,7 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *api.MapfixTrigger } operation, err := svc.DB.Operations().Create(ctx, model.Operation{ - Owner: int64(userId), + Owner: userId, StatusID: model.OperationStatusCreated, }) if err != nil { @@ -121,7 +121,10 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *api.MapfixTrigger return nil, err } - svc.Nats.Publish("maptest.mapfixes.create", []byte(j)) + _, err = svc.Nats.Publish("maptest.mapfixes.create", []byte(j)) + if err != nil { + return nil, err + } return &api.OperationID{ OperationID: operation.ID, @@ -259,22 +262,58 @@ func (svc *Service) UpdateMapfixModel(ctx context.Context, params api.UpdateMapf return err } - has_role, err := userInfo.IsSubmitter(uint64(mapfix.Submitter)) + userId, err := userInfo.GetUserID() if err != nil { return err } + // check if caller is the submitter + has_role := userId == mapfix.Submitter if !has_role { return ErrPermissionDeniedNotSubmitter } + OldModelID := mapfix.AssetID + OldModelVersion := mapfix.AssetVersion + NewModelID := uint64(params.ModelID) + NewModelVersion := uint64(params.ModelVersion) + // check if Status is ChangesRequested|Submitted|UnderConstruction pmap := datastore.Optional() - pmap.Add("asset_id", params.ModelID) - pmap.Add("asset_version", params.ModelVersion) + pmap.Add("asset_id", NewModelID) + pmap.Add("asset_version", NewModelVersion) //always reset completed when model changes pmap.Add("completed", false) - return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusChangesRequested, model.MapfixStatusSubmitted, model.MapfixStatusUnderConstruction}, pmap) + err = svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusChangesRequested, model.MapfixStatusSubmitted, model.MapfixStatusUnderConstruction}, pmap) + if err != nil { + return err + } + + event_data := model.AuditEventDataChangeModel{ + OldModelID: OldModelID, + OldModelVersion: OldModelVersion, + NewModelID: NewModelID, + NewModelVersion: NewModelVersion, + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceMapfix, + ResourceID: params.MapfixID, + EventType: model.AuditEventTypeChangeModel, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } // ActionMapfixReject invokes actionMapfixReject operation. @@ -297,10 +336,42 @@ func (svc *Service) ActionMapfixReject(ctx context.Context, params api.ActionMap return ErrPermissionDeniedNeedRoleMapReview } + userId, err := userInfo.GetUserID() + if err != nil { + return err + } + // transaction + target_status := model.MapfixStatusRejected smap := datastore.Optional() - smap.Add("status_id", model.MapfixStatusRejected) - return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitted}, smap) + smap.Add("status_id", target_status) + err = svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitted}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceMapfix, + ResourceID: params.MapfixID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } // ActionMapfixRequestChanges invokes actionMapfixRequestChanges operation. @@ -346,19 +417,48 @@ func (svc *Service) ActionMapfixRevoke(ctx context.Context, params api.ActionMap return err } - has_role, err := userInfo.IsSubmitter(uint64(mapfix.Submitter)) + userId, err := userInfo.GetUserID() if err != nil { return err } + // check if caller is the submitter + has_role := userId == mapfix.Submitter if !has_role { return ErrPermissionDeniedNotSubmitter } // transaction + target_status := model.MapfixStatusUnderConstruction smap := datastore.Optional() - smap.Add("status_id", model.MapfixStatusUnderConstruction) - return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitted, model.MapfixStatusChangesRequested}, smap) + smap.Add("status_id", target_status) + err = svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitted, model.MapfixStatusChangesRequested}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceMapfix, + ResourceID: params.MapfixID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } // ActionMapfixSubmit invokes actionMapfixSubmit operation. @@ -378,19 +478,48 @@ func (svc *Service) ActionMapfixSubmit(ctx context.Context, params api.ActionMap return err } - has_role, err := userInfo.IsSubmitter(uint64(mapfix.Submitter)) + userId, err := userInfo.GetUserID() if err != nil { return err } + // check if caller is the submitter + has_role := userId == mapfix.Submitter if !has_role { return ErrPermissionDeniedNotSubmitter } // transaction + target_status := model.MapfixStatusSubmitted smap := datastore.Optional() - smap.Add("status_id", model.MapfixStatusSubmitted) - return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUnderConstruction, model.MapfixStatusChangesRequested}, smap) + smap.Add("status_id", target_status) + err = svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUnderConstruction, model.MapfixStatusChangesRequested}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceMapfix, + ResourceID: params.MapfixID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } // ActionMapfixTriggerUpload invokes actionMapfixTriggerUpload operation. @@ -413,9 +542,15 @@ func (svc *Service) ActionMapfixTriggerUpload(ctx context.Context, params api.Ac return ErrPermissionDeniedNeedRoleMapUpload } + userId, err := userInfo.GetUserID() + if err != nil { + return err + } + // transaction + target_status := model.MapfixStatusUploading smap := datastore.Optional() - smap.Add("status_id", model.MapfixStatusUploading) + smap.Add("status_id", target_status) mapfix, err := svc.DB.Mapfixes().IfStatusThenUpdateAndGet(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidated}, smap) if err != nil { return err @@ -434,7 +569,31 @@ func (svc *Service) ActionMapfixTriggerUpload(ctx context.Context, params api.Ac return err } - svc.Nats.Publish("maptest.mapfixes.uploadfix", []byte(j)) + _, err = svc.Nats.Publish("maptest.mapfixes.uploadfix", []byte(j)) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceMapfix, + ResourceID: params.MapfixID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } return nil } @@ -459,6 +618,11 @@ func (svc *Service) ActionMapfixValidated(ctx context.Context, params api.Action return ErrPermissionDeniedNeedRoleMapUpload } + userId, err := userInfo.GetUserID() + if err != nil { + return err + } + // check when mapfix was updated mapfix, err := svc.DB.Mapfixes().Get(ctx, params.MapfixID) if err != nil { @@ -470,9 +634,54 @@ func (svc *Service) ActionMapfixValidated(ctx context.Context, params api.Action } // transaction + target_status := model.MapfixStatusValidated smap := datastore.Optional() - smap.Add("status_id", model.MapfixStatusValidated) - return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUploading}, smap) + smap.Add("status_id", target_status) + err = svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUploading}, smap) + if err != nil { + return err + } + + // this is a map fix + upload_fix_request := model.UploadMapfixRequest{ + MapfixID: mapfix.ID, + ModelID: mapfix.ValidatedAssetID, + ModelVersion: mapfix.ValidatedAssetVersion, + TargetAssetID: mapfix.TargetAssetID, + } + + j, err := json.Marshal(upload_fix_request) + if err != nil { + return err + } + + _, err = svc.Nats.Publish("maptest.mapfixes.uploadfix", []byte(j)) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceMapfix, + ResourceID: params.MapfixID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } // ActionMapfixTriggerValidate invokes actionMapfixTriggerValidate operation. @@ -501,11 +710,13 @@ func (svc *Service) ActionMapfixTriggerValidate(ctx context.Context, params api. return err } - has_role, err = userInfo.IsSubmitter(uint64(mapfix.Submitter)) + userId, err := userInfo.GetUserID() if err != nil { return err } + // check if caller is NOT the submitter + has_role = userId == mapfix.Submitter if has_role { return ErrAcceptOwnMapfix } @@ -528,8 +739,9 @@ func (svc *Service) ActionMapfixTriggerValidate(ctx context.Context, params api. } // transaction + target_status := model.MapfixStatusValidating smap := datastore.Optional() - smap.Add("status_id", model.MapfixStatusValidating) + smap.Add("status_id", target_status) mapfix, err = svc.DB.Mapfixes().IfStatusThenUpdateAndGet(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitted}, smap) if err != nil { return err @@ -552,7 +764,31 @@ func (svc *Service) ActionMapfixTriggerValidate(ctx context.Context, params api. return err } - svc.Nats.Publish("maptest.mapfixes.validate", []byte(j)) + _, err = svc.Nats.Publish("maptest.mapfixes.validate", []byte(j)) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceMapfix, + ResourceID: params.MapfixID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } return nil } @@ -577,9 +813,15 @@ func (svc *Service) ActionMapfixRetryValidate(ctx context.Context, params api.Ac return ErrPermissionDeniedNeedRoleMapReview } + userId, err := userInfo.GetUserID() + if err != nil { + return err + } + // transaction + target_status := model.MapfixStatusValidating smap := datastore.Optional() - smap.Add("status_id", model.MapfixStatusValidating) + smap.Add("status_id", target_status) mapfix, err := svc.DB.Mapfixes().IfStatusThenUpdateAndGet(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusAcceptedUnvalidated}, smap) if err != nil { return err @@ -602,7 +844,31 @@ func (svc *Service) ActionMapfixRetryValidate(ctx context.Context, params api.Ac return err } - svc.Nats.Publish("maptest.mapfixes.validate", []byte(j)) + _, err = svc.Nats.Publish("maptest.mapfixes.validate", []byte(j)) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceMapfix, + ResourceID: params.MapfixID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } return nil } @@ -627,6 +893,11 @@ func (svc *Service) ActionMapfixAccepted(ctx context.Context, params api.ActionM return ErrPermissionDeniedNeedRoleMapReview } + userId, err := userInfo.GetUserID() + if err != nil { + return err + } + // check when mapfix was updated mapfix, err := svc.DB.Mapfixes().Get(ctx, params.MapfixID) if err != nil { @@ -638,8 +909,35 @@ func (svc *Service) ActionMapfixAccepted(ctx context.Context, params api.ActionM } // transaction + target_status := model.MapfixStatusAcceptedUnvalidated smap := datastore.Optional() - smap.Add("status_id", model.MapfixStatusAcceptedUnvalidated) + smap.Add("status_id", target_status) smap.Add("status_message", "Manually forced reset") - return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, smap) + err = svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceMapfix, + ResourceID: params.MapfixID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } diff --git a/pkg/service/operations.go b/pkg/service/operations.go index eb48adc..5a906f4 100644 --- a/pkg/service/operations.go +++ b/pkg/service/operations.go @@ -24,11 +24,13 @@ func (svc *Service) GetOperation(ctx context.Context, params api.GetOperationPar return nil, err } - has_role, err := userInfo.IsSubmitter(uint64(operation.Owner)) + userId, err := userInfo.GetUserID() if err != nil { return nil, err } - // check if caller is operation owner + + // check if caller is the submitter + has_role := userId == operation.Owner if !has_role { return nil, ErrPermissionDeniedNotSubmitter } @@ -36,7 +38,7 @@ func (svc *Service) GetOperation(ctx context.Context, params api.GetOperationPar return &api.Operation{ OperationID: operation.ID, Date: operation.CreatedAt.Unix(), - Owner: operation.Owner, + Owner: int64(operation.Owner), Status: int32(operation.StatusID), StatusMessage: operation.StatusMessage, Path: operation.Path, diff --git a/pkg/service/security.go b/pkg/service/security.go index 2896aca..33f283b 100644 --- a/pkg/service/security.go +++ b/pkg/service/security.go @@ -88,13 +88,6 @@ func (usr UserInfoHandle) Validate() (bool, error) { } return validate.Valid, nil } -func (usr UserInfoHandle) IsSubmitter(submitter uint64) (bool, error) { - userId, err := usr.GetUserID() - if err != nil { - return false, err - } - return userId == submitter, nil -} func (usr UserInfoHandle) hasRoles(wantRoles Roles) (bool, error) { haveroles, err := usr.GetRoles() if err != nil { diff --git a/pkg/service/submissions.go b/pkg/service/submissions.go index 1bd630e..e7963d1 100644 --- a/pkg/service/submissions.go +++ b/pkg/service/submissions.go @@ -93,7 +93,7 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *api.Submissio } operation, err := svc.DB.Operations().Create(ctx, model.Operation{ - Owner: int64(userId), + Owner: userId, StatusID: model.OperationStatusCreated, }) if err != nil { @@ -110,7 +110,10 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *api.Submissio return nil, err } - svc.Nats.Publish("maptest.submissions.create", []byte(j)) + _, err = svc.Nats.Publish("maptest.submissions.create", []byte(j)) + if err != nil { + return nil, err + } return &api.OperationID{ OperationID: operation.ID, @@ -248,22 +251,58 @@ func (svc *Service) UpdateSubmissionModel(ctx context.Context, params api.Update return err } - has_role, err := userInfo.IsSubmitter(uint64(submission.Submitter)) + userId, err := userInfo.GetUserID() if err != nil { return err } + // check if caller is the submitter + has_role := userId == submission.Submitter if !has_role { return ErrPermissionDeniedNotSubmitter } + OldModelID := submission.AssetID + OldModelVersion := submission.AssetVersion + NewModelID := uint64(params.ModelID) + NewModelVersion := uint64(params.ModelVersion) + // check if Status is ChangesRequested|Submitted|UnderConstruction pmap := datastore.Optional() - pmap.Add("asset_id", params.ModelID) - pmap.Add("asset_version", params.ModelVersion) + pmap.Add("asset_id", NewModelID) + pmap.Add("asset_version", NewModelVersion) //always reset completed when model changes pmap.Add("completed", false) - return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusChangesRequested, model.SubmissionStatusUnderConstruction}, pmap) + err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusChangesRequested, model.SubmissionStatusUnderConstruction}, pmap) + if err != nil { + return err + } + + event_data := model.AuditEventDataChangeModel{ + OldModelID: OldModelID, + OldModelVersion: OldModelVersion, + NewModelID: NewModelID, + NewModelVersion: NewModelVersion, + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceSubmission, + ResourceID: submission.ID, + EventType: model.AuditEventTypeChangeModel, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } // ActionSubmissionReject invokes actionSubmissionReject operation. @@ -286,10 +325,42 @@ func (svc *Service) ActionSubmissionReject(ctx context.Context, params api.Actio return ErrPermissionDeniedNeedRoleMapReview } + userId, err := userInfo.GetUserID() + if err != nil { + return err + } + // transaction + target_status := model.SubmissionStatusRejected smap := datastore.Optional() - smap.Add("status_id", model.SubmissionStatusRejected) - return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitted}, smap) + smap.Add("status_id", target_status) + err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitted}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceSubmission, + ResourceID: params.SubmissionID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } // ActionSubmissionRequestChanges invokes actionSubmissionRequestChanges operation. @@ -312,10 +383,42 @@ func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params a return ErrPermissionDeniedNeedRoleMapReview } + userId, err := userInfo.GetUserID() + if err != nil { + return err + } + // transaction + target_status := model.SubmissionStatusChangesRequested smap := datastore.Optional() - smap.Add("status_id", model.SubmissionStatusChangesRequested) - return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidated, model.SubmissionStatusAcceptedUnvalidated, model.SubmissionStatusSubmitted}, smap) + smap.Add("status_id", target_status) + err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidated, model.SubmissionStatusAcceptedUnvalidated, model.SubmissionStatusSubmitted}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceSubmission, + ResourceID: params.SubmissionID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } // ActionSubmissionRevoke invokes actionSubmissionRevoke operation. @@ -335,19 +438,48 @@ func (svc *Service) ActionSubmissionRevoke(ctx context.Context, params api.Actio return err } - has_role, err := userInfo.IsSubmitter(uint64(submission.Submitter)) + userId, err := userInfo.GetUserID() if err != nil { return err } + // check if caller is the submitter + has_role := userId == submission.Submitter if !has_role { return ErrPermissionDeniedNotSubmitter } // transaction + target_status := model.SubmissionStatusUnderConstruction smap := datastore.Optional() - smap.Add("status_id", model.SubmissionStatusUnderConstruction) - return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitted, model.SubmissionStatusChangesRequested}, smap) + smap.Add("status_id", target_status) + err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitted, model.SubmissionStatusChangesRequested}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceSubmission, + ResourceID: params.SubmissionID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } // ActionSubmissionSubmit invokes actionSubmissionSubmit operation. @@ -367,19 +499,48 @@ func (svc *Service) ActionSubmissionSubmit(ctx context.Context, params api.Actio return err } - has_role, err := userInfo.IsSubmitter(uint64(submission.Submitter)) + userId, err := userInfo.GetUserID() if err != nil { return err } + // check if caller is the submitter + has_role := userId == submission.Submitter if !has_role { return ErrPermissionDeniedNotSubmitter } // transaction + target_status := model.SubmissionStatusSubmitted smap := datastore.Optional() - smap.Add("status_id", model.SubmissionStatusSubmitted) - return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUnderConstruction, model.SubmissionStatusChangesRequested}, smap) + smap.Add("status_id", target_status) + err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUnderConstruction, model.SubmissionStatusChangesRequested}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceSubmission, + ResourceID: params.SubmissionID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } // ActionSubmissionTriggerUpload invokes actionSubmissionTriggerUpload operation. @@ -402,9 +563,15 @@ func (svc *Service) ActionSubmissionTriggerUpload(ctx context.Context, params ap return ErrPermissionDeniedNeedRoleMapUpload } + userId, err := userInfo.GetUserID() + if err != nil { + return err + } + // transaction + target_status := model.SubmissionStatusUploading smap := datastore.Optional() - smap.Add("status_id", model.SubmissionStatusUploading) + smap.Add("status_id", target_status) submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidated}, smap) if err != nil { return err @@ -426,12 +593,36 @@ func (svc *Service) ActionSubmissionTriggerUpload(ctx context.Context, params ap return err } - svc.Nats.Publish("maptest.submissions.upload", []byte(j)) + _, err = svc.Nats.Publish("maptest.submissions.upload", []byte(j)) + if err != nil { + return err + } } else { // refuse to operate return ErrUploadedAssetIDAlreadyExists } + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceSubmission, + ResourceID: params.SubmissionID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + return nil } @@ -455,6 +646,11 @@ func (svc *Service) ActionSubmissionValidated(ctx context.Context, params api.Ac return ErrPermissionDeniedNeedRoleMapUpload } + userId, err := userInfo.GetUserID() + if err != nil { + return err + } + // check when submission was updated submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID) if err != nil { @@ -466,9 +662,36 @@ func (svc *Service) ActionSubmissionValidated(ctx context.Context, params api.Ac } // transaction + target_status := model.SubmissionStatusValidated smap := datastore.Optional() - smap.Add("status_id", model.SubmissionStatusValidated) - return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUploading}, smap) + smap.Add("status_id", target_status) + err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUploading}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceSubmission, + ResourceID: params.SubmissionID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } // ActionSubmissionTriggerValidate invokes actionSubmissionTriggerValidate operation. @@ -497,18 +720,21 @@ func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params return err } - has_role, err = userInfo.IsSubmitter(uint64(submission.Submitter)) + userId, err := userInfo.GetUserID() if err != nil { return err } + // check if caller is NOT the submitter + has_role = userId == submission.Submitter if has_role { return ErrAcceptOwnSubmission } // transaction + target_status := model.SubmissionStatusValidating smap := datastore.Optional() - smap.Add("status_id", model.SubmissionStatusValidating) + smap.Add("status_id", target_status) submission, err = svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitted}, smap) if err != nil { return err @@ -531,7 +757,31 @@ func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params return err } - svc.Nats.Publish("maptest.submissions.validate", []byte(j)) + _, err = svc.Nats.Publish("maptest.submissions.validate", []byte(j)) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceSubmission, + ResourceID: params.SubmissionID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } return nil } @@ -556,9 +806,15 @@ func (svc *Service) ActionSubmissionRetryValidate(ctx context.Context, params ap return ErrPermissionDeniedNeedRoleMapReview } + userId, err := userInfo.GetUserID() + if err != nil { + return err + } + // transaction + target_status := model.SubmissionStatusValidating smap := datastore.Optional() - smap.Add("status_id", model.SubmissionStatusValidating) + smap.Add("status_id", target_status) submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusAcceptedUnvalidated}, smap) if err != nil { return err @@ -581,7 +837,31 @@ func (svc *Service) ActionSubmissionRetryValidate(ctx context.Context, params ap return err } - svc.Nats.Publish("maptest.submissions.validate", []byte(j)) + _, err = svc.Nats.Publish("maptest.submissions.validate", []byte(j)) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceSubmission, + ResourceID: params.SubmissionID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } return nil } @@ -606,6 +886,11 @@ func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params api.Act return ErrPermissionDeniedNeedRoleMapReview } + userId, err := userInfo.GetUserID() + if err != nil { + return err + } + // check when submission was updated submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID) if err != nil { @@ -617,10 +902,37 @@ func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params api.Act } // transaction + target_status := model.SubmissionStatusAcceptedUnvalidated smap := datastore.Optional() - smap.Add("status_id", model.SubmissionStatusAcceptedUnvalidated) + smap.Add("status_id", target_status) smap.Add("status_message", "Manually forced reset") - return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap) + err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceSubmission, + ResourceID: params.SubmissionID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } // ReleaseSubmissions invokes releaseSubmissions operation. diff --git a/pkg/service_internal/mapfixes.go b/pkg/service_internal/mapfixes.go index b3e428f..5526b20 100644 --- a/pkg/service_internal/mapfixes.go +++ b/pkg/service_internal/mapfixes.go @@ -2,6 +2,7 @@ package service_internal import ( "context" + "encoding/json" "errors" "fmt" @@ -34,13 +35,43 @@ var( // // POST /mapfixes/{MapfixID}/validated-model func (svc *Service) UpdateMapfixValidatedModel(ctx context.Context, params internal.UpdateMapfixValidatedModelParams) error { + ValidatedModelID := uint64(params.ValidatedModelID) + ValidatedModelVersion := uint64(params.ValidatedModelVersion) + // check if Status is ChangesRequested|Submitted|UnderConstruction pmap := datastore.Optional() - pmap.Add("validated_asset_id", params.ValidatedModelID) - pmap.Add("validated_asset_version", params.ValidatedModelVersion) + pmap.Add("validated_asset_id", ValidatedModelID) + pmap.Add("validated_asset_version", ValidatedModelVersion) // DO NOT reset completed when validated model is updated // pmap.Add("completed", false) - return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, pmap) + err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, pmap) + if err != nil { + return err + } + + event_data := model.AuditEventDataChangeValidatedModel{ + ValidatedModelID: ValidatedModelID, + ValidatedModelVersion: ValidatedModelVersion, + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: ValidtorUserID, + ResourceType: model.ResourceMapfix, + ResourceID: params.MapfixID, + EventType: model.AuditEventTypeChangeValidatedModel, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } // ActionMapfixValidate invokes actionMapfixValidate operation. @@ -62,10 +93,37 @@ func (svc *Service) ActionMapfixValidated(ctx context.Context, params internal.A // POST /mapfixes/{MapfixID}/status/validator-failed func (svc *Service) ActionMapfixAccepted(ctx context.Context, params internal.ActionMapfixAcceptedParams) error { // transaction + target_status := model.MapfixStatusAcceptedUnvalidated smap := datastore.Optional() - smap.Add("status_id", model.MapfixStatusAcceptedUnvalidated) + smap.Add("status_id", target_status) smap.Add("status_message", params.StatusMessage) - return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, smap) + err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: ValidtorUserID, + ResourceType: model.ResourceMapfix, + ResourceID: params.MapfixID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } // ActionMapfixUploaded implements actionMapfixUploaded operation. @@ -75,9 +133,36 @@ func (svc *Service) ActionMapfixAccepted(ctx context.Context, params internal.Ac // POST /mapfixes/{MapfixID}/status/validator-uploaded func (svc *Service) ActionMapfixUploaded(ctx context.Context, params internal.ActionMapfixUploadedParams) error { // transaction + target_status := model.MapfixStatusUploaded smap := datastore.Optional() - smap.Add("status_id", model.MapfixStatusUploaded) - return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUploading}, smap) + smap.Add("status_id", target_status) + err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUploading}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: ValidtorUserID, + ResourceType: model.ResourceMapfix, + ResourceID: params.MapfixID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } // POST /mapfixes @@ -121,7 +206,7 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *internal.MapfixCr // check if user owns asset // TODO: allow bypass by admin - if operation.Owner != request.AssetOwner { + if operation.Owner != Submitter { return nil, ErrNotAssetOwner } diff --git a/pkg/service_internal/service_internal.go b/pkg/service_internal/service_internal.go index 6b4d94f..6aefe80 100644 --- a/pkg/service_internal/service_internal.go +++ b/pkg/service_internal/service_internal.go @@ -3,12 +3,17 @@ package service_internal import ( "context" "errors" + "math" "git.itzana.me/strafesnet/maps-service/pkg/datastore" internal "git.itzana.me/strafesnet/maps-service/pkg/internal" "github.com/nats-io/nats.go" ) +const ( + ValidtorUserID uint64 = uint64(math.MaxInt64) +) + var ( ErrNegativeID = errors.New("A negative ID was provided") ) diff --git a/pkg/service_internal/submissions.go b/pkg/service_internal/submissions.go index 2fc0465..164eb60 100644 --- a/pkg/service_internal/submissions.go +++ b/pkg/service_internal/submissions.go @@ -2,6 +2,7 @@ package service_internal import ( "context" + "encoding/json" "errors" "fmt" @@ -33,13 +34,43 @@ var( // // POST /submissions/{SubmissionID}/validated-model func (svc *Service) UpdateSubmissionValidatedModel(ctx context.Context, params internal.UpdateSubmissionValidatedModelParams) error { + ValidatedModelID := uint64(params.ValidatedModelID) + ValidatedModelVersion := uint64(params.ValidatedModelVersion) + // check if Status is ChangesRequested|Submitted|UnderConstruction pmap := datastore.Optional() - pmap.Add("validated_asset_id", params.ValidatedModelID) - pmap.Add("validated_asset_version", params.ValidatedModelVersion) + pmap.Add("validated_asset_id", ValidatedModelID) + pmap.Add("validated_asset_version", ValidatedModelVersion) // DO NOT reset completed when validated model is updated // pmap.Add("completed", false) - return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, pmap) + err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, pmap) + if err != nil { + return err + } + + event_data := model.AuditEventDataChangeValidatedModel{ + ValidatedModelID: ValidatedModelID, + ValidatedModelVersion: ValidatedModelVersion, + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: ValidtorUserID, + ResourceType: model.ResourceSubmission, + ResourceID: params.SubmissionID, + EventType: model.AuditEventTypeChangeValidatedModel, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } // ActionSubmissionValidate invokes actionSubmissionValidate operation. @@ -49,9 +80,36 @@ func (svc *Service) UpdateSubmissionValidatedModel(ctx context.Context, params i // POST /submissions/{SubmissionID}/status/validator-validated func (svc *Service) ActionSubmissionValidated(ctx context.Context, params internal.ActionSubmissionValidatedParams) error { // transaction + target_status := model.SubmissionStatusValidated smap := datastore.Optional() - smap.Add("status_id", model.SubmissionStatusValidated) - return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap) + smap.Add("status_id", target_status) + err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: ValidtorUserID, + ResourceType: model.ResourceSubmission, + ResourceID: params.SubmissionID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } // ActionSubmissionAccepted implements actionSubmissionAccepted operation. @@ -61,10 +119,37 @@ func (svc *Service) ActionSubmissionValidated(ctx context.Context, params intern // POST /submissions/{SubmissionID}/status/validator-failed func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params internal.ActionSubmissionAcceptedParams) error { // transaction + target_status := model.SubmissionStatusAcceptedUnvalidated smap := datastore.Optional() - smap.Add("status_id", model.SubmissionStatusAcceptedUnvalidated) + smap.Add("status_id", target_status) smap.Add("status_message", params.StatusMessage) - return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap) + err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: ValidtorUserID, + ResourceType: model.ResourceSubmission, + ResourceID: params.SubmissionID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } // ActionSubmissionUploaded implements actionSubmissionUploaded operation. @@ -74,10 +159,37 @@ func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params interna // POST /submissions/{SubmissionID}/status/validator-uploaded func (svc *Service) ActionSubmissionUploaded(ctx context.Context, params internal.ActionSubmissionUploadedParams) error { // transaction + target_status := model.SubmissionStatusUploaded smap := datastore.Optional() - smap.Add("status_id", model.SubmissionStatusUploaded) + smap.Add("status_id", target_status) smap.Add("uploaded_asset_id", params.UploadedAssetID) - return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUploading}, smap) + err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUploading}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: ValidtorUserID, + ResourceType: model.ResourceSubmission, + ResourceID: params.SubmissionID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil } // POST /submissions @@ -119,7 +231,7 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *internal.Subm // check if user owns asset // TODO: allow bypass by admin - if operation.Owner != request.AssetOwner { + if operation.Owner != Submitter { return nil, ErrNotAssetOwner } -- 2.49.1 From 163412a2537357be7f889727f6ff0125db8e7752 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 8 Apr 2025 00:01:07 -0700 Subject: [PATCH 350/454] openapi: extend api StatusID maximum to match changes --- openapi.yaml | 4 ++-- pkg/api/oas_parameters_gen.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index b4646d3..77b4e39 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -209,7 +209,7 @@ paths: type: integer format: int32 minimum: 0 - maximum: 8 + maximum: 9 responses: "200": description: Successful response @@ -549,7 +549,7 @@ paths: type: integer format: int32 minimum: 0 - maximum: 9 + maximum: 10 responses: "200": description: Successful response diff --git a/pkg/api/oas_parameters_gen.go b/pkg/api/oas_parameters_gen.go index ed558bd..157e1e3 100644 --- a/pkg/api/oas_parameters_gen.go +++ b/pkg/api/oas_parameters_gen.go @@ -2877,7 +2877,7 @@ func decodeListMapfixesParams(args [0]string, argsEscaped bool, r *http.Request) MinSet: true, Min: 0, MaxSet: true, - Max: 8, + Max: 9, MinExclusive: false, MaxExclusive: false, MultipleOfSet: false, @@ -4909,7 +4909,7 @@ func decodeListSubmissionsParams(args [0]string, argsEscaped bool, r *http.Reque MinSet: true, Min: 0, MaxSet: true, - Max: 9, + Max: 10, MinExclusive: false, MaxExclusive: false, MultipleOfSet: false, -- 2.49.1 From 68f23116587cee4ff0febf84ecb36c77ccd6e14e Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 8 Apr 2025 12:14:08 -0700 Subject: [PATCH 351/454] openapi: audit endpoints --- openapi.yaml | 134 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/openapi.yaml b/openapi.yaml index 77b4e39..9f76d25 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -269,6 +269,56 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" + /mapfixes/{MapfixID}/audit-events: + get: + summary: Retrieve a list of audit events + operationId: listMapfixAuditEvents + tags: + - Mapfixes + security: [] + parameters: + - $ref: '#/components/parameters/MapfixID' + - $ref: "#/components/parameters/Page" + - $ref: "#/components/parameters/Limit" + responses: + "200": + description: Successful response + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/AuditEvent" + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /mapfixes/{MapfixID}/comment: + post: + summary: Post a comment to the audit log + operationId: createMapfixAuditComment + tags: + - Mapfixes + parameters: + - $ref: '#/components/parameters/MapfixID' + requestBody: + required: true + content: + text/plain: + schema: + type: string + maxLength: 1024 + responses: + "204": + description: Successful response + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" /mapfixes/{MapfixID}/model: post: summary: Update model following role restrictions @@ -609,6 +659,56 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" + /submissions/{SubmissionID}/audit-events: + get: + summary: Retrieve a list of audit events + operationId: listSubmissionAuditEvents + tags: + - Submissions + security: [] + parameters: + - $ref: '#/components/parameters/SubmissionID' + - $ref: "#/components/parameters/Page" + - $ref: "#/components/parameters/Limit" + responses: + "200": + description: Successful response + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/AuditEvent" + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /submissions/{SubmissionID}/comment: + post: + summary: Post a comment to the audit log + operationId: createSubmissionAuditComment + tags: + - Submissions + parameters: + - $ref: '#/components/parameters/SubmissionID' + requestBody: + required: true + content: + text/plain: + schema: + type: string + maxLength: 1024 + responses: + "204": + description: Successful response + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" /submissions/{SubmissionID}/model: post: summary: Update model following role restrictions @@ -1178,6 +1278,40 @@ components: minimum: 1 maximum: 100 schemas: + AuditEvent: + type: object + required: + - ID + - Date + - User + - ResourceType + - ResourceID + - EventType + - EventData + properties: + ID: + type: integer + format: int64 + Date: + type: integer + format: int64 + User: + type: integer + format: int64 + ResourceType: + type: integer + format: int32 + description: Is this a submission or is it a mapfix + ResourceID: + type: integer + format: int64 + EventType: + type: integer + format: int32 + EventData: + type: object + description: Arbitrary event data + additionalProperties: true OperationID: required: - OperationID -- 2.49.1 From c24db2c3a042523953cd514f773198d0845fa7bb Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 8 Apr 2025 12:20:23 -0700 Subject: [PATCH 352/454] openapi: allow listing 0 items --- openapi.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi.yaml b/openapi.yaml index 9f76d25..00b1140 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1275,7 +1275,7 @@ components: schema: type: integer format: int32 - minimum: 1 + minimum: 0 maximum: 100 schemas: AuditEvent: -- 2.49.1 From bfc2a2cbcaa65ac3602bfdd750e4e2ef730ad324 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 8 Apr 2025 12:15:00 -0700 Subject: [PATCH 353/454] openapi: generate --- pkg/api/oas_client_gen.go | 524 +++++++++++++++++++ pkg/api/oas_handlers_gen.go | 734 +++++++++++++++++++++++++++ pkg/api/oas_json_gen.go | 254 +++++++++ pkg/api/oas_operations_gen.go | 4 + pkg/api/oas_parameters_gen.go | 588 ++++++++++++++++++++- pkg/api/oas_request_decoders_gen.go | 68 +++ pkg/api/oas_request_encoders_gen.go | 20 + pkg/api/oas_response_decoders_gen.go | 338 ++++++++++++ pkg/api/oas_response_encoders_gen.go | 50 ++ pkg/api/oas_router_gen.go | 288 ++++++++++- pkg/api/oas_schemas_gen.go | 132 +++++ pkg/api/oas_server_gen.go | 24 + pkg/api/oas_unimplemented_gen.go | 36 ++ 13 files changed, 3031 insertions(+), 29 deletions(-) diff --git a/pkg/api/oas_client_gen.go b/pkg/api/oas_client_gen.go index 6f6b0a7..f865e3a 100644 --- a/pkg/api/oas_client_gen.go +++ b/pkg/api/oas_client_gen.go @@ -143,6 +143,12 @@ type Invoker interface { // // POST /mapfixes CreateMapfix(ctx context.Context, request *MapfixTriggerCreate) (*OperationID, error) + // CreateMapfixAuditComment invokes createMapfixAuditComment operation. + // + // Post a comment to the audit log. + // + // POST /mapfixes/{MapfixID}/comment + CreateMapfixAuditComment(ctx context.Context, request CreateMapfixAuditCommentReq, params CreateMapfixAuditCommentParams) error // CreateScript invokes createScript operation. // // Create a new script. @@ -161,6 +167,12 @@ type Invoker interface { // // POST /submissions CreateSubmission(ctx context.Context, request *SubmissionTriggerCreate) (*OperationID, error) + // CreateSubmissionAuditComment invokes createSubmissionAuditComment operation. + // + // Post a comment to the audit log. + // + // POST /submissions/{SubmissionID}/comment + CreateSubmissionAuditComment(ctx context.Context, request CreateSubmissionAuditCommentReq, params CreateSubmissionAuditCommentParams) error // DeleteScript invokes deleteScript operation. // // Delete the specified script by ID. @@ -209,6 +221,12 @@ type Invoker interface { // // GET /submissions/{SubmissionID} GetSubmission(ctx context.Context, params GetSubmissionParams) (*Submission, error) + // ListMapfixAuditEvents invokes listMapfixAuditEvents operation. + // + // Retrieve a list of audit events. + // + // GET /mapfixes/{MapfixID}/audit-events + ListMapfixAuditEvents(ctx context.Context, params ListMapfixAuditEventsParams) ([]AuditEvent, error) // ListMapfixes invokes listMapfixes operation. // // Get list of mapfixes. @@ -233,6 +251,12 @@ type Invoker interface { // // GET /scripts ListScripts(ctx context.Context, params ListScriptsParams) ([]Script, error) + // ListSubmissionAuditEvents invokes listSubmissionAuditEvents operation. + // + // Retrieve a list of audit events. + // + // GET /submissions/{SubmissionID}/audit-events + ListSubmissionAuditEvents(ctx context.Context, params ListSubmissionAuditEventsParams) ([]AuditEvent, error) // ListSubmissions invokes listSubmissions operation. // // Get list of submissions. @@ -2690,6 +2714,133 @@ func (c *Client) sendCreateMapfix(ctx context.Context, request *MapfixTriggerCre return result, nil } +// CreateMapfixAuditComment invokes createMapfixAuditComment operation. +// +// Post a comment to the audit log. +// +// POST /mapfixes/{MapfixID}/comment +func (c *Client) CreateMapfixAuditComment(ctx context.Context, request CreateMapfixAuditCommentReq, params CreateMapfixAuditCommentParams) error { + _, err := c.sendCreateMapfixAuditComment(ctx, request, params) + return err +} + +func (c *Client) sendCreateMapfixAuditComment(ctx context.Context, request CreateMapfixAuditCommentReq, params CreateMapfixAuditCommentParams) (res *CreateMapfixAuditCommentNoContent, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("createMapfixAuditComment"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/comment"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, CreateMapfixAuditCommentOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [3]string + pathParts[0] = "/mapfixes/" + { + // Encode "MapfixID" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "MapfixID", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.Int64ToString(params.MapfixID)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[1] = encoded + } + pathParts[2] = "/comment" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + if err := encodeCreateMapfixAuditCommentRequest(request, r); err != nil { + return res, errors.Wrap(err, "encode request") + } + + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:CookieAuth" + switch err := c.securityCookieAuth(ctx, CreateMapfixAuditCommentOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"CookieAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeCreateMapfixAuditCommentResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // CreateScript invokes createScript operation. // // Create a new script. @@ -3014,6 +3165,133 @@ func (c *Client) sendCreateSubmission(ctx context.Context, request *SubmissionTr return result, nil } +// CreateSubmissionAuditComment invokes createSubmissionAuditComment operation. +// +// Post a comment to the audit log. +// +// POST /submissions/{SubmissionID}/comment +func (c *Client) CreateSubmissionAuditComment(ctx context.Context, request CreateSubmissionAuditCommentReq, params CreateSubmissionAuditCommentParams) error { + _, err := c.sendCreateSubmissionAuditComment(ctx, request, params) + return err +} + +func (c *Client) sendCreateSubmissionAuditComment(ctx context.Context, request CreateSubmissionAuditCommentReq, params CreateSubmissionAuditCommentParams) (res *CreateSubmissionAuditCommentNoContent, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("createSubmissionAuditComment"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/comment"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, CreateSubmissionAuditCommentOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [3]string + pathParts[0] = "/submissions/" + { + // Encode "SubmissionID" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "SubmissionID", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.Int64ToString(params.SubmissionID)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[1] = encoded + } + pathParts[2] = "/comment" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + if err := encodeCreateSubmissionAuditCommentRequest(request, r); err != nil { + return res, errors.Wrap(err, "encode request") + } + + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:CookieAuth" + switch err := c.securityCookieAuth(ctx, CreateSubmissionAuditCommentOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"CookieAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeCreateSubmissionAuditCommentResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // DeleteScript invokes deleteScript operation. // // Delete the specified script by ID. @@ -3833,6 +4111,129 @@ func (c *Client) sendGetSubmission(ctx context.Context, params GetSubmissionPara return result, nil } +// ListMapfixAuditEvents invokes listMapfixAuditEvents operation. +// +// Retrieve a list of audit events. +// +// GET /mapfixes/{MapfixID}/audit-events +func (c *Client) ListMapfixAuditEvents(ctx context.Context, params ListMapfixAuditEventsParams) ([]AuditEvent, error) { + res, err := c.sendListMapfixAuditEvents(ctx, params) + return res, err +} + +func (c *Client) sendListMapfixAuditEvents(ctx context.Context, params ListMapfixAuditEventsParams) (res []AuditEvent, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listMapfixAuditEvents"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/audit-events"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, ListMapfixAuditEventsOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [3]string + pathParts[0] = "/mapfixes/" + { + // Encode "MapfixID" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "MapfixID", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.Int64ToString(params.MapfixID)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[1] = encoded + } + pathParts[2] = "/audit-events" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeQueryParams" + q := uri.NewQueryEncoder() + { + // Encode "Page" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "Page", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.Int32ToString(params.Page)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "Limit" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "Limit", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.Int32ToString(params.Limit)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + u.RawQuery = q.Values().Encode() + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "GET", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeListMapfixAuditEventsResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // ListMapfixes invokes listMapfixes operation. // // Get list of mapfixes. @@ -4589,6 +4990,129 @@ func (c *Client) sendListScripts(ctx context.Context, params ListScriptsParams) return result, nil } +// ListSubmissionAuditEvents invokes listSubmissionAuditEvents operation. +// +// Retrieve a list of audit events. +// +// GET /submissions/{SubmissionID}/audit-events +func (c *Client) ListSubmissionAuditEvents(ctx context.Context, params ListSubmissionAuditEventsParams) ([]AuditEvent, error) { + res, err := c.sendListSubmissionAuditEvents(ctx, params) + return res, err +} + +func (c *Client) sendListSubmissionAuditEvents(ctx context.Context, params ListSubmissionAuditEventsParams) (res []AuditEvent, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listSubmissionAuditEvents"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/audit-events"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, ListSubmissionAuditEventsOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [3]string + pathParts[0] = "/submissions/" + { + // Encode "SubmissionID" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "SubmissionID", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.Int64ToString(params.SubmissionID)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[1] = encoded + } + pathParts[2] = "/audit-events" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeQueryParams" + q := uri.NewQueryEncoder() + { + // Encode "Page" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "Page", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.Int32ToString(params.Page)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "Limit" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "Limit", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.Int32ToString(params.Limit)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + u.RawQuery = q.Values().Encode() + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "GET", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeListSubmissionAuditEventsResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // ListSubmissions invokes listSubmissions operation. // // Get list of submissions. diff --git a/pkg/api/oas_handlers_gen.go b/pkg/api/oas_handlers_gen.go index 7a5dee0..995a780 100644 --- a/pkg/api/oas_handlers_gen.go +++ b/pkg/api/oas_handlers_gen.go @@ -3735,6 +3735,216 @@ func (s *Server) handleCreateMapfixRequest(args [0]string, argsEscaped bool, w h } } +// handleCreateMapfixAuditCommentRequest handles createMapfixAuditComment operation. +// +// Post a comment to the audit log. +// +// POST /mapfixes/{MapfixID}/comment +func (s *Server) handleCreateMapfixAuditCommentRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("createMapfixAuditComment"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/comment"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), CreateMapfixAuditCommentOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: CreateMapfixAuditCommentOperation, + ID: "createMapfixAuditComment", + } + ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securityCookieAuth(ctx, CreateMapfixAuditCommentOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "CookieAuth", + Err: err, + } + if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil { + defer recordError("Security:CookieAuth", err) + } + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil { + defer recordError("Security", err) + } + return + } + } + params, err := decodeCreateMapfixAuditCommentParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + request, close, err := s.decodeCreateMapfixAuditCommentRequest(r) + if err != nil { + err = &ogenerrors.DecodeRequestError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeRequest", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + defer func() { + if err := close(); err != nil { + recordError("CloseRequest", err) + } + }() + + var response *CreateMapfixAuditCommentNoContent + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: CreateMapfixAuditCommentOperation, + OperationSummary: "Post a comment to the audit log", + OperationID: "createMapfixAuditComment", + Body: request, + Params: middleware.Parameters{ + { + Name: "MapfixID", + In: "path", + }: params.MapfixID, + }, + Raw: r, + } + + type ( + Request = CreateMapfixAuditCommentReq + Params = CreateMapfixAuditCommentParams + Response = *CreateMapfixAuditCommentNoContent + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackCreateMapfixAuditCommentParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + err = s.h.CreateMapfixAuditComment(ctx, request, params) + return response, err + }, + ) + } else { + err = s.h.CreateMapfixAuditComment(ctx, request, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeCreateMapfixAuditCommentResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleCreateScriptRequest handles createScript operation. // // Create a new script. @@ -4320,6 +4530,216 @@ func (s *Server) handleCreateSubmissionRequest(args [0]string, argsEscaped bool, } } +// handleCreateSubmissionAuditCommentRequest handles createSubmissionAuditComment operation. +// +// Post a comment to the audit log. +// +// POST /submissions/{SubmissionID}/comment +func (s *Server) handleCreateSubmissionAuditCommentRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("createSubmissionAuditComment"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/comment"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), CreateSubmissionAuditCommentOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: CreateSubmissionAuditCommentOperation, + ID: "createSubmissionAuditComment", + } + ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securityCookieAuth(ctx, CreateSubmissionAuditCommentOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "CookieAuth", + Err: err, + } + if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil { + defer recordError("Security:CookieAuth", err) + } + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil { + defer recordError("Security", err) + } + return + } + } + params, err := decodeCreateSubmissionAuditCommentParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + request, close, err := s.decodeCreateSubmissionAuditCommentRequest(r) + if err != nil { + err = &ogenerrors.DecodeRequestError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeRequest", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + defer func() { + if err := close(); err != nil { + recordError("CloseRequest", err) + } + }() + + var response *CreateSubmissionAuditCommentNoContent + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: CreateSubmissionAuditCommentOperation, + OperationSummary: "Post a comment to the audit log", + OperationID: "createSubmissionAuditComment", + Body: request, + Params: middleware.Parameters{ + { + Name: "SubmissionID", + In: "path", + }: params.SubmissionID, + }, + Raw: r, + } + + type ( + Request = CreateSubmissionAuditCommentReq + Params = CreateSubmissionAuditCommentParams + Response = *CreateSubmissionAuditCommentNoContent + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackCreateSubmissionAuditCommentParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + err = s.h.CreateSubmissionAuditComment(ctx, request, params) + return response, err + }, + ) + } else { + err = s.h.CreateSubmissionAuditComment(ctx, request, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeCreateSubmissionAuditCommentResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleDeleteScriptRequest handles deleteScript operation. // // Delete the specified script by ID. @@ -5650,6 +6070,163 @@ func (s *Server) handleGetSubmissionRequest(args [1]string, argsEscaped bool, w } } +// handleListMapfixAuditEventsRequest handles listMapfixAuditEvents operation. +// +// Retrieve a list of audit events. +// +// GET /mapfixes/{MapfixID}/audit-events +func (s *Server) handleListMapfixAuditEventsRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listMapfixAuditEvents"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/audit-events"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), ListMapfixAuditEventsOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: ListMapfixAuditEventsOperation, + ID: "listMapfixAuditEvents", + } + ) + params, err := decodeListMapfixAuditEventsParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response []AuditEvent + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: ListMapfixAuditEventsOperation, + OperationSummary: "Retrieve a list of audit events", + OperationID: "listMapfixAuditEvents", + Body: nil, + Params: middleware.Parameters{ + { + Name: "MapfixID", + In: "path", + }: params.MapfixID, + { + Name: "Page", + In: "query", + }: params.Page, + { + Name: "Limit", + In: "query", + }: params.Limit, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = ListMapfixAuditEventsParams + Response = []AuditEvent + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackListMapfixAuditEventsParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.ListMapfixAuditEvents(ctx, params) + return response, err + }, + ) + } else { + response, err = s.h.ListMapfixAuditEvents(ctx, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeListMapfixAuditEventsResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleListMapfixesRequest handles listMapfixes operation. // // Get list of mapfixes. @@ -6342,6 +6919,163 @@ func (s *Server) handleListScriptsRequest(args [0]string, argsEscaped bool, w ht } } +// handleListSubmissionAuditEventsRequest handles listSubmissionAuditEvents operation. +// +// Retrieve a list of audit events. +// +// GET /submissions/{SubmissionID}/audit-events +func (s *Server) handleListSubmissionAuditEventsRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listSubmissionAuditEvents"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/audit-events"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), ListSubmissionAuditEventsOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: ListSubmissionAuditEventsOperation, + ID: "listSubmissionAuditEvents", + } + ) + params, err := decodeListSubmissionAuditEventsParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response []AuditEvent + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: ListSubmissionAuditEventsOperation, + OperationSummary: "Retrieve a list of audit events", + OperationID: "listSubmissionAuditEvents", + Body: nil, + Params: middleware.Parameters{ + { + Name: "SubmissionID", + In: "path", + }: params.SubmissionID, + { + Name: "Page", + In: "query", + }: params.Page, + { + Name: "Limit", + In: "query", + }: params.Limit, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = ListSubmissionAuditEventsParams + Response = []AuditEvent + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackListSubmissionAuditEventsParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.ListSubmissionAuditEvents(ctx, params) + return response, err + }, + ) + } else { + response, err = s.h.ListSubmissionAuditEvents(ctx, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeListSubmissionAuditEventsResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleListSubmissionsRequest handles listSubmissions operation. // // Get list of submissions. diff --git a/pkg/api/oas_json_gen.go b/pkg/api/oas_json_gen.go index 391642f..6d63001 100644 --- a/pkg/api/oas_json_gen.go +++ b/pkg/api/oas_json_gen.go @@ -13,6 +13,260 @@ import ( "github.com/ogen-go/ogen/validate" ) +// Encode implements json.Marshaler. +func (s *AuditEvent) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *AuditEvent) encodeFields(e *jx.Encoder) { + { + e.FieldStart("ID") + e.Int64(s.ID) + } + { + e.FieldStart("Date") + e.Int64(s.Date) + } + { + e.FieldStart("User") + e.Int64(s.User) + } + { + e.FieldStart("ResourceType") + e.Int32(s.ResourceType) + } + { + e.FieldStart("ResourceID") + e.Int64(s.ResourceID) + } + { + e.FieldStart("EventType") + e.Int32(s.EventType) + } + { + e.FieldStart("EventData") + s.EventData.Encode(e) + } +} + +var jsonFieldsNameOfAuditEvent = [7]string{ + 0: "ID", + 1: "Date", + 2: "User", + 3: "ResourceType", + 4: "ResourceID", + 5: "EventType", + 6: "EventData", +} + +// Decode decodes AuditEvent from json. +func (s *AuditEvent) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode AuditEvent to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "ID": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Int64() + s.ID = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"ID\"") + } + case "Date": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + v, err := d.Int64() + s.Date = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Date\"") + } + case "User": + requiredBitSet[0] |= 1 << 2 + if err := func() error { + v, err := d.Int64() + s.User = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"User\"") + } + case "ResourceType": + requiredBitSet[0] |= 1 << 3 + if err := func() error { + v, err := d.Int32() + s.ResourceType = int32(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"ResourceType\"") + } + case "ResourceID": + requiredBitSet[0] |= 1 << 4 + if err := func() error { + v, err := d.Int64() + s.ResourceID = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"ResourceID\"") + } + case "EventType": + requiredBitSet[0] |= 1 << 5 + if err := func() error { + v, err := d.Int32() + s.EventType = int32(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"EventType\"") + } + case "EventData": + requiredBitSet[0] |= 1 << 6 + if err := func() error { + if err := s.EventData.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"EventData\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode AuditEvent") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b01111111, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfAuditEvent) { + name = jsonFieldsNameOfAuditEvent[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *AuditEvent) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *AuditEvent) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s AuditEventEventData) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields implements json.Marshaler. +func (s AuditEventEventData) encodeFields(e *jx.Encoder) { + for k, elem := range s { + e.FieldStart(k) + + if len(elem) != 0 { + e.Raw(elem) + } + } +} + +// Decode decodes AuditEventEventData from json. +func (s *AuditEventEventData) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode AuditEventEventData to nil") + } + m := s.init() + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + var elem jx.Raw + if err := func() error { + v, err := d.RawAppend(nil) + elem = jx.Raw(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrapf(err, "decode field %q", k) + } + m[string(k)] = elem + return nil + }); err != nil { + return errors.Wrap(err, "decode AuditEventEventData") + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s AuditEventEventData) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *AuditEventEventData) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + // Encode implements json.Marshaler. func (s *Error) Encode(e *jx.Encoder) { e.ObjStart() diff --git a/pkg/api/oas_operations_gen.go b/pkg/api/oas_operations_gen.go index 9806eb1..09d06f4 100644 --- a/pkg/api/oas_operations_gen.go +++ b/pkg/api/oas_operations_gen.go @@ -25,9 +25,11 @@ const ( ActionSubmissionTriggerValidateOperation OperationName = "ActionSubmissionTriggerValidate" ActionSubmissionValidatedOperation OperationName = "ActionSubmissionValidated" CreateMapfixOperation OperationName = "CreateMapfix" + CreateMapfixAuditCommentOperation OperationName = "CreateMapfixAuditComment" CreateScriptOperation OperationName = "CreateScript" CreateScriptPolicyOperation OperationName = "CreateScriptPolicy" CreateSubmissionOperation OperationName = "CreateSubmission" + CreateSubmissionAuditCommentOperation OperationName = "CreateSubmissionAuditComment" DeleteScriptOperation OperationName = "DeleteScript" DeleteScriptPolicyOperation OperationName = "DeleteScriptPolicy" GetMapOperation OperationName = "GetMap" @@ -36,10 +38,12 @@ const ( GetScriptOperation OperationName = "GetScript" GetScriptPolicyOperation OperationName = "GetScriptPolicy" GetSubmissionOperation OperationName = "GetSubmission" + ListMapfixAuditEventsOperation OperationName = "ListMapfixAuditEvents" ListMapfixesOperation OperationName = "ListMapfixes" ListMapsOperation OperationName = "ListMaps" ListScriptPolicyOperation OperationName = "ListScriptPolicy" ListScriptsOperation OperationName = "ListScripts" + ListSubmissionAuditEventsOperation OperationName = "ListSubmissionAuditEvents" ListSubmissionsOperation OperationName = "ListSubmissions" ReleaseSubmissionsOperation OperationName = "ReleaseSubmissions" SessionRolesOperation OperationName = "SessionRoles" diff --git a/pkg/api/oas_parameters_gen.go b/pkg/api/oas_parameters_gen.go index 157e1e3..b604dc9 100644 --- a/pkg/api/oas_parameters_gen.go +++ b/pkg/api/oas_parameters_gen.go @@ -1509,6 +1509,172 @@ func decodeActionSubmissionValidatedParams(args [1]string, argsEscaped bool, r * return params, nil } +// CreateMapfixAuditCommentParams is parameters of createMapfixAuditComment operation. +type CreateMapfixAuditCommentParams struct { + // The unique identifier for a mapfix. + MapfixID int64 +} + +func unpackCreateMapfixAuditCommentParams(packed middleware.Parameters) (params CreateMapfixAuditCommentParams) { + { + key := middleware.ParameterKey{ + Name: "MapfixID", + In: "path", + } + params.MapfixID = packed[key].(int64) + } + return params +} + +func decodeCreateMapfixAuditCommentParams(args [1]string, argsEscaped bool, r *http.Request) (params CreateMapfixAuditCommentParams, _ error) { + // Decode path: MapfixID. + if err := func() error { + param := args[0] + if argsEscaped { + unescaped, err := url.PathUnescape(args[0]) + if err != nil { + return errors.Wrap(err, "unescape path") + } + param = unescaped + } + if len(param) > 0 { + d := uri.NewPathDecoder(uri.PathDecoderConfig{ + Param: "MapfixID", + Value: param, + Style: uri.PathStyleSimple, + Explode: false, + }) + + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + params.MapfixID = c + return nil + }(); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.MapfixID)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "MapfixID", + In: "path", + Err: err, + } + } + return params, nil +} + +// CreateSubmissionAuditCommentParams is parameters of createSubmissionAuditComment operation. +type CreateSubmissionAuditCommentParams struct { + // The unique identifier for a submission. + SubmissionID int64 +} + +func unpackCreateSubmissionAuditCommentParams(packed middleware.Parameters) (params CreateSubmissionAuditCommentParams) { + { + key := middleware.ParameterKey{ + Name: "SubmissionID", + In: "path", + } + params.SubmissionID = packed[key].(int64) + } + return params +} + +func decodeCreateSubmissionAuditCommentParams(args [1]string, argsEscaped bool, r *http.Request) (params CreateSubmissionAuditCommentParams, _ error) { + // Decode path: SubmissionID. + if err := func() error { + param := args[0] + if argsEscaped { + unescaped, err := url.PathUnescape(args[0]) + if err != nil { + return errors.Wrap(err, "unescape path") + } + param = unescaped + } + if len(param) > 0 { + d := uri.NewPathDecoder(uri.PathDecoderConfig{ + Param: "SubmissionID", + Value: param, + Style: uri.PathStyleSimple, + Explode: false, + }) + + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + params.SubmissionID = c + return nil + }(); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.SubmissionID)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "SubmissionID", + In: "path", + Err: err, + } + } + return params, nil +} + // DeleteScriptParams is parameters of deleteScript operation. type DeleteScriptParams struct { // The unique identifier for a script. @@ -2173,6 +2339,212 @@ func decodeGetSubmissionParams(args [1]string, argsEscaped bool, r *http.Request return params, nil } +// ListMapfixAuditEventsParams is parameters of listMapfixAuditEvents operation. +type ListMapfixAuditEventsParams struct { + // The unique identifier for a mapfix. + MapfixID int64 + Page int32 + Limit int32 +} + +func unpackListMapfixAuditEventsParams(packed middleware.Parameters) (params ListMapfixAuditEventsParams) { + { + key := middleware.ParameterKey{ + Name: "MapfixID", + In: "path", + } + params.MapfixID = packed[key].(int64) + } + { + key := middleware.ParameterKey{ + Name: "Page", + In: "query", + } + params.Page = packed[key].(int32) + } + { + key := middleware.ParameterKey{ + Name: "Limit", + In: "query", + } + params.Limit = packed[key].(int32) + } + return params +} + +func decodeListMapfixAuditEventsParams(args [1]string, argsEscaped bool, r *http.Request) (params ListMapfixAuditEventsParams, _ error) { + q := uri.NewQueryDecoder(r.URL.Query()) + // Decode path: MapfixID. + if err := func() error { + param := args[0] + if argsEscaped { + unescaped, err := url.PathUnescape(args[0]) + if err != nil { + return errors.Wrap(err, "unescape path") + } + param = unescaped + } + if len(param) > 0 { + d := uri.NewPathDecoder(uri.PathDecoderConfig{ + Param: "MapfixID", + Value: param, + Style: uri.PathStyleSimple, + Explode: false, + }) + + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + params.MapfixID = c + return nil + }(); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.MapfixID)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "MapfixID", + In: "path", + Err: err, + } + } + // Decode query: Page. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "Page", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + params.Page = c + return nil + }); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 1, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.Page)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return err + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "Page", + In: "query", + Err: err, + } + } + // Decode query: Limit. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "Limit", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + params.Limit = c + return nil + }); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: true, + Max: 100, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.Limit)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return err + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "Limit", + In: "query", + Err: err, + } + } + return params, nil +} + // ListMapfixesParams is parameters of listMapfixes operation. type ListMapfixesParams struct { Page int32 @@ -2360,7 +2732,7 @@ func decodeListMapfixesParams(args [0]string, argsEscaped bool, r *http.Request) if err := func() error { if err := (validate.Int{ MinSet: true, - Min: 1, + Min: 0, MaxSet: true, Max: 100, MinExclusive: false, @@ -3053,7 +3425,7 @@ func decodeListMapsParams(args [0]string, argsEscaped bool, r *http.Request) (pa if err := func() error { if err := (validate.Int{ MinSet: true, - Min: 1, + Min: 0, MaxSet: true, Max: 100, MinExclusive: false, @@ -3476,7 +3848,7 @@ func decodeListScriptPolicyParams(args [0]string, argsEscaped bool, r *http.Requ if err := func() error { if err := (validate.Int{ MinSet: true, - Min: 1, + Min: 0, MaxSet: true, Max: 100, MinExclusive: false, @@ -3855,7 +4227,7 @@ func decodeListScriptsParams(args [0]string, argsEscaped bool, r *http.Request) if err := func() error { if err := (validate.Int{ MinSet: true, - Min: 1, + Min: 0, MaxSet: true, Max: 100, MinExclusive: false, @@ -4205,6 +4577,212 @@ func decodeListScriptsParams(args [0]string, argsEscaped bool, r *http.Request) return params, nil } +// ListSubmissionAuditEventsParams is parameters of listSubmissionAuditEvents operation. +type ListSubmissionAuditEventsParams struct { + // The unique identifier for a submission. + SubmissionID int64 + Page int32 + Limit int32 +} + +func unpackListSubmissionAuditEventsParams(packed middleware.Parameters) (params ListSubmissionAuditEventsParams) { + { + key := middleware.ParameterKey{ + Name: "SubmissionID", + In: "path", + } + params.SubmissionID = packed[key].(int64) + } + { + key := middleware.ParameterKey{ + Name: "Page", + In: "query", + } + params.Page = packed[key].(int32) + } + { + key := middleware.ParameterKey{ + Name: "Limit", + In: "query", + } + params.Limit = packed[key].(int32) + } + return params +} + +func decodeListSubmissionAuditEventsParams(args [1]string, argsEscaped bool, r *http.Request) (params ListSubmissionAuditEventsParams, _ error) { + q := uri.NewQueryDecoder(r.URL.Query()) + // Decode path: SubmissionID. + if err := func() error { + param := args[0] + if argsEscaped { + unescaped, err := url.PathUnescape(args[0]) + if err != nil { + return errors.Wrap(err, "unescape path") + } + param = unescaped + } + if len(param) > 0 { + d := uri.NewPathDecoder(uri.PathDecoderConfig{ + Param: "SubmissionID", + Value: param, + Style: uri.PathStyleSimple, + Explode: false, + }) + + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + params.SubmissionID = c + return nil + }(); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.SubmissionID)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "SubmissionID", + In: "path", + Err: err, + } + } + // Decode query: Page. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "Page", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + params.Page = c + return nil + }); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 1, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.Page)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return err + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "Page", + In: "query", + Err: err, + } + } + // Decode query: Limit. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "Limit", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + params.Limit = c + return nil + }); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: true, + Max: 100, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.Limit)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return err + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "Limit", + In: "query", + Err: err, + } + } + return params, nil +} + // ListSubmissionsParams is parameters of listSubmissions operation. type ListSubmissionsParams struct { Page int32 @@ -4392,7 +4970,7 @@ func decodeListSubmissionsParams(args [0]string, argsEscaped bool, r *http.Reque if err := func() error { if err := (validate.Int{ MinSet: true, - Min: 1, + Min: 0, MaxSet: true, Max: 100, MinExclusive: false, diff --git a/pkg/api/oas_request_decoders_gen.go b/pkg/api/oas_request_decoders_gen.go index a105ace..72c4ed7 100644 --- a/pkg/api/oas_request_decoders_gen.go +++ b/pkg/api/oas_request_decoders_gen.go @@ -87,6 +87,40 @@ func (s *Server) decodeCreateMapfixRequest(r *http.Request) ( } } +func (s *Server) decodeCreateMapfixAuditCommentRequest(r *http.Request) ( + req CreateMapfixAuditCommentReq, + close func() error, + rerr error, +) { + var closers []func() error + close = func() error { + var merr error + // Close in reverse order, to match defer behavior. + for i := len(closers) - 1; i >= 0; i-- { + c := closers[i] + merr = multierr.Append(merr, c()) + } + return merr + } + defer func() { + if rerr != nil { + rerr = multierr.Append(rerr, close()) + } + }() + ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + return req, close, errors.Wrap(err, "parse media type") + } + switch { + case ct == "text/plain": + reader := r.Body + request := CreateMapfixAuditCommentReq{Data: reader} + return request, close, nil + default: + return req, close, validate.InvalidContentType(ct) + } +} + func (s *Server) decodeCreateScriptRequest(r *http.Request) ( req *ScriptCreate, close func() error, @@ -300,6 +334,40 @@ func (s *Server) decodeCreateSubmissionRequest(r *http.Request) ( } } +func (s *Server) decodeCreateSubmissionAuditCommentRequest(r *http.Request) ( + req CreateSubmissionAuditCommentReq, + close func() error, + rerr error, +) { + var closers []func() error + close = func() error { + var merr error + // Close in reverse order, to match defer behavior. + for i := len(closers) - 1; i >= 0; i-- { + c := closers[i] + merr = multierr.Append(merr, c()) + } + return merr + } + defer func() { + if rerr != nil { + rerr = multierr.Append(rerr, close()) + } + }() + ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + return req, close, errors.Wrap(err, "parse media type") + } + switch { + case ct == "text/plain": + reader := r.Body + request := CreateSubmissionAuditCommentReq{Data: reader} + return request, close, nil + default: + return req, close, validate.InvalidContentType(ct) + } +} + func (s *Server) decodeReleaseSubmissionsRequest(r *http.Request) ( req []ReleaseInfo, close func() error, diff --git a/pkg/api/oas_request_encoders_gen.go b/pkg/api/oas_request_encoders_gen.go index 52f1046..84923a2 100644 --- a/pkg/api/oas_request_encoders_gen.go +++ b/pkg/api/oas_request_encoders_gen.go @@ -25,6 +25,16 @@ func encodeCreateMapfixRequest( return nil } +func encodeCreateMapfixAuditCommentRequest( + req CreateMapfixAuditCommentReq, + r *http.Request, +) error { + const contentType = "text/plain" + body := req + ht.SetBody(r, body, contentType) + return nil +} + func encodeCreateScriptRequest( req *ScriptCreate, r *http.Request, @@ -67,6 +77,16 @@ func encodeCreateSubmissionRequest( return nil } +func encodeCreateSubmissionAuditCommentRequest( + req CreateSubmissionAuditCommentReq, + r *http.Request, +) error { + const contentType = "text/plain" + body := req + ht.SetBody(r, body, contentType) + return nil +} + func encodeReleaseSubmissionsRequest( req []ReleaseInfo, r *http.Request, diff --git a/pkg/api/oas_response_decoders_gen.go b/pkg/api/oas_response_decoders_gen.go index b23ae6c..242b3b7 100644 --- a/pkg/api/oas_response_decoders_gen.go +++ b/pkg/api/oas_response_decoders_gen.go @@ -1196,6 +1196,66 @@ func decodeCreateMapfixResponse(resp *http.Response) (res *OperationID, _ error) return res, errors.Wrap(defRes, "error") } +func decodeCreateMapfixAuditCommentResponse(resp *http.Response) (res *CreateMapfixAuditCommentNoContent, _ error) { + switch resp.StatusCode { + case 204: + // Code 204. + return &CreateMapfixAuditCommentNoContent{}, nil + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + func decodeCreateScriptResponse(resp *http.Response) (res *ScriptID, _ error) { switch resp.StatusCode { case 201: @@ -1499,6 +1559,66 @@ func decodeCreateSubmissionResponse(resp *http.Response) (res *OperationID, _ er return res, errors.Wrap(defRes, "error") } +func decodeCreateSubmissionAuditCommentResponse(resp *http.Response) (res *CreateSubmissionAuditCommentNoContent, _ error) { + switch resp.StatusCode { + case 204: + // Code 204. + return &CreateSubmissionAuditCommentNoContent{}, nil + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + func decodeDeleteScriptResponse(resp *http.Response) (res *DeleteScriptNoContent, _ error) { switch resp.StatusCode { case 204: @@ -2225,6 +2345,115 @@ func decodeGetSubmissionResponse(resp *http.Response) (res *Submission, _ error) return res, errors.Wrap(defRes, "error") } +func decodeListMapfixAuditEventsResponse(resp *http.Response) (res []AuditEvent, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response []AuditEvent + if err := func() error { + response = make([]AuditEvent, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem AuditEvent + if err := elem.Decode(d); err != nil { + return err + } + response = append(response, elem) + return nil + }); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if response == nil { + return errors.New("nil is invalid value") + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + func decodeListMapfixesResponse(resp *http.Response) (res *Mapfixes, _ error) { switch resp.StatusCode { case 200: @@ -2704,6 +2933,115 @@ func decodeListScriptsResponse(resp *http.Response) (res []Script, _ error) { return res, errors.Wrap(defRes, "error") } +func decodeListSubmissionAuditEventsResponse(resp *http.Response) (res []AuditEvent, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response []AuditEvent + if err := func() error { + response = make([]AuditEvent, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem AuditEvent + if err := elem.Decode(d); err != nil { + return err + } + response = append(response, elem) + return nil + }); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if response == nil { + return errors.New("nil is invalid value") + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + func decodeListSubmissionsResponse(resp *http.Response) (res *Submissions, _ error) { switch resp.StatusCode { case 200: diff --git a/pkg/api/oas_response_encoders_gen.go b/pkg/api/oas_response_encoders_gen.go index 362dfc5..cb0a98a 100644 --- a/pkg/api/oas_response_encoders_gen.go +++ b/pkg/api/oas_response_encoders_gen.go @@ -153,6 +153,13 @@ func encodeCreateMapfixResponse(response *OperationID, w http.ResponseWriter, sp return nil } +func encodeCreateMapfixAuditCommentResponse(response *CreateMapfixAuditCommentNoContent, w http.ResponseWriter, span trace.Span) error { + w.WriteHeader(204) + span.SetStatus(codes.Ok, http.StatusText(204)) + + return nil +} + func encodeCreateScriptResponse(response *ScriptID, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(201) @@ -195,6 +202,13 @@ func encodeCreateSubmissionResponse(response *OperationID, w http.ResponseWriter return nil } +func encodeCreateSubmissionAuditCommentResponse(response *CreateSubmissionAuditCommentNoContent, w http.ResponseWriter, span trace.Span) error { + w.WriteHeader(204) + span.SetStatus(codes.Ok, http.StatusText(204)) + + return nil +} + func encodeDeleteScriptResponse(response *DeleteScriptNoContent, w http.ResponseWriter, span trace.Span) error { w.WriteHeader(204) span.SetStatus(codes.Ok, http.StatusText(204)) @@ -293,6 +307,24 @@ func encodeGetSubmissionResponse(response *Submission, w http.ResponseWriter, sp return nil } +func encodeListMapfixAuditEventsResponse(response []AuditEvent, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + e.ArrStart() + for _, elem := range response { + elem.Encode(e) + } + e.ArrEnd() + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil +} + func encodeListMapfixesResponse(response *Mapfixes, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(200) @@ -361,6 +393,24 @@ func encodeListScriptsResponse(response []Script, w http.ResponseWriter, span tr return nil } +func encodeListSubmissionAuditEventsResponse(response []AuditEvent, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + e.ArrStart() + for _, elem := range response { + elem.Encode(e) + } + e.ArrEnd() + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil +} + func encodeListSubmissionsResponse(response *Submissions, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(200) diff --git a/pkg/api/oas_router_gen.go b/pkg/api/oas_router_gen.go index 414d7e4..6989865 100644 --- a/pkg/api/oas_router_gen.go +++ b/pkg/api/oas_router_gen.go @@ -136,9 +136,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { break } switch elem[0] { - case 'c': // Prefix: "completed" + case 'a': // Prefix: "audit-events" - if l := len("completed"); len(elem) >= l && elem[0:l] == "completed" { + if l := len("audit-events"); len(elem) >= l && elem[0:l] == "audit-events" { elem = elem[l:] } else { break @@ -147,17 +147,75 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { if len(elem) == 0 { // Leaf node. switch r.Method { - case "POST": - s.handleSetMapfixCompletedRequest([1]string{ + case "GET": + s.handleListMapfixAuditEventsRequest([1]string{ args[0], }, elemIsEscaped, w, r) default: - s.notAllowed(w, r, "POST") + s.notAllowed(w, r, "GET") } return } + case 'c': // Prefix: "com" + + if l := len("com"); len(elem) >= l && elem[0:l] == "com" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'm': // Prefix: "ment" + + if l := len("ment"); len(elem) >= l && elem[0:l] == "ment" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleCreateMapfixAuditCommentRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + case 'p': // Prefix: "pleted" + + if l := len("pleted"); len(elem) >= l && elem[0:l] == "pleted" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleSetMapfixCompletedRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + } + case 'm': // Prefix: "model" if l := len("model"); len(elem) >= l && elem[0:l] == "model" { @@ -832,9 +890,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { break } switch elem[0] { - case 'c': // Prefix: "completed" + case 'a': // Prefix: "audit-events" - if l := len("completed"); len(elem) >= l && elem[0:l] == "completed" { + if l := len("audit-events"); len(elem) >= l && elem[0:l] == "audit-events" { elem = elem[l:] } else { break @@ -843,17 +901,75 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { if len(elem) == 0 { // Leaf node. switch r.Method { - case "POST": - s.handleSetSubmissionCompletedRequest([1]string{ + case "GET": + s.handleListSubmissionAuditEventsRequest([1]string{ args[0], }, elemIsEscaped, w, r) default: - s.notAllowed(w, r, "POST") + s.notAllowed(w, r, "GET") } return } + case 'c': // Prefix: "com" + + if l := len("com"); len(elem) >= l && elem[0:l] == "com" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'm': // Prefix: "ment" + + if l := len("ment"); len(elem) >= l && elem[0:l] == "ment" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleCreateSubmissionAuditCommentRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + case 'p': // Prefix: "pleted" + + if l := len("pleted"); len(elem) >= l && elem[0:l] == "pleted" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleSetSubmissionCompletedRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + } + case 'm': // Prefix: "model" if l := len("model"); len(elem) >= l && elem[0:l] == "model" { @@ -1319,9 +1435,9 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { break } switch elem[0] { - case 'c': // Prefix: "completed" + case 'a': // Prefix: "audit-events" - if l := len("completed"); len(elem) >= l && elem[0:l] == "completed" { + if l := len("audit-events"); len(elem) >= l && elem[0:l] == "audit-events" { elem = elem[l:] } else { break @@ -1330,11 +1446,11 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { if len(elem) == 0 { // Leaf node. switch method { - case "POST": - r.name = SetMapfixCompletedOperation - r.summary = "Called by maptest when a player completes the map" - r.operationID = "setMapfixCompleted" - r.pathPattern = "/mapfixes/{MapfixID}/completed" + case "GET": + r.name = ListMapfixAuditEventsOperation + r.summary = "Retrieve a list of audit events" + r.operationID = "listMapfixAuditEvents" + r.pathPattern = "/mapfixes/{MapfixID}/audit-events" r.args = args r.count = 1 return r, true @@ -1343,6 +1459,68 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { } } + case 'c': // Prefix: "com" + + if l := len("com"); len(elem) >= l && elem[0:l] == "com" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'm': // Prefix: "ment" + + if l := len("ment"); len(elem) >= l && elem[0:l] == "ment" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = CreateMapfixAuditCommentOperation + r.summary = "Post a comment to the audit log" + r.operationID = "createMapfixAuditComment" + r.pathPattern = "/mapfixes/{MapfixID}/comment" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + case 'p': // Prefix: "pleted" + + if l := len("pleted"); len(elem) >= l && elem[0:l] == "pleted" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = SetMapfixCompletedOperation + r.summary = "Called by maptest when a player completes the map" + r.operationID = "setMapfixCompleted" + r.pathPattern = "/mapfixes/{MapfixID}/completed" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + } + case 'm': // Prefix: "model" if l := len("model"); len(elem) >= l && elem[0:l] == "model" { @@ -2113,9 +2291,9 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { break } switch elem[0] { - case 'c': // Prefix: "completed" + case 'a': // Prefix: "audit-events" - if l := len("completed"); len(elem) >= l && elem[0:l] == "completed" { + if l := len("audit-events"); len(elem) >= l && elem[0:l] == "audit-events" { elem = elem[l:] } else { break @@ -2124,11 +2302,11 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { if len(elem) == 0 { // Leaf node. switch method { - case "POST": - r.name = SetSubmissionCompletedOperation - r.summary = "Called by maptest when a player completes the map" - r.operationID = "setSubmissionCompleted" - r.pathPattern = "/submissions/{SubmissionID}/completed" + case "GET": + r.name = ListSubmissionAuditEventsOperation + r.summary = "Retrieve a list of audit events" + r.operationID = "listSubmissionAuditEvents" + r.pathPattern = "/submissions/{SubmissionID}/audit-events" r.args = args r.count = 1 return r, true @@ -2137,6 +2315,68 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { } } + case 'c': // Prefix: "com" + + if l := len("com"); len(elem) >= l && elem[0:l] == "com" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'm': // Prefix: "ment" + + if l := len("ment"); len(elem) >= l && elem[0:l] == "ment" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = CreateSubmissionAuditCommentOperation + r.summary = "Post a comment to the audit log" + r.operationID = "createSubmissionAuditComment" + r.pathPattern = "/submissions/{SubmissionID}/comment" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + case 'p': // Prefix: "pleted" + + if l := len("pleted"); len(elem) >= l && elem[0:l] == "pleted" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = SetSubmissionCompletedOperation + r.summary = "Called by maptest when a player completes the map" + r.operationID = "setSubmissionCompleted" + r.pathPattern = "/submissions/{SubmissionID}/completed" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + } + case 'm': // Prefix: "model" if l := len("model"); len(elem) >= l && elem[0:l] == "model" { diff --git a/pkg/api/oas_schemas_gen.go b/pkg/api/oas_schemas_gen.go index 80ee714..5590292 100644 --- a/pkg/api/oas_schemas_gen.go +++ b/pkg/api/oas_schemas_gen.go @@ -4,7 +4,10 @@ package api import ( "fmt" + "io" "time" + + "github.com/go-faster/jx" ) func (s *ErrorStatusCode) Error() string { @@ -65,6 +68,101 @@ type ActionSubmissionTriggerValidateNoContent struct{} // ActionSubmissionValidatedNoContent is response for ActionSubmissionValidated operation. type ActionSubmissionValidatedNoContent struct{} +// Ref: #/components/schemas/AuditEvent +type AuditEvent struct { + ID int64 `json:"ID"` + Date int64 `json:"Date"` + User int64 `json:"User"` + // Is this a submission or is it a mapfix. + ResourceType int32 `json:"ResourceType"` + ResourceID int64 `json:"ResourceID"` + EventType int32 `json:"EventType"` + // Arbitrary event data. + EventData AuditEventEventData `json:"EventData"` +} + +// GetID returns the value of ID. +func (s *AuditEvent) GetID() int64 { + return s.ID +} + +// GetDate returns the value of Date. +func (s *AuditEvent) GetDate() int64 { + return s.Date +} + +// GetUser returns the value of User. +func (s *AuditEvent) GetUser() int64 { + return s.User +} + +// GetResourceType returns the value of ResourceType. +func (s *AuditEvent) GetResourceType() int32 { + return s.ResourceType +} + +// GetResourceID returns the value of ResourceID. +func (s *AuditEvent) GetResourceID() int64 { + return s.ResourceID +} + +// GetEventType returns the value of EventType. +func (s *AuditEvent) GetEventType() int32 { + return s.EventType +} + +// GetEventData returns the value of EventData. +func (s *AuditEvent) GetEventData() AuditEventEventData { + return s.EventData +} + +// SetID sets the value of ID. +func (s *AuditEvent) SetID(val int64) { + s.ID = val +} + +// SetDate sets the value of Date. +func (s *AuditEvent) SetDate(val int64) { + s.Date = val +} + +// SetUser sets the value of User. +func (s *AuditEvent) SetUser(val int64) { + s.User = val +} + +// SetResourceType sets the value of ResourceType. +func (s *AuditEvent) SetResourceType(val int32) { + s.ResourceType = val +} + +// SetResourceID sets the value of ResourceID. +func (s *AuditEvent) SetResourceID(val int64) { + s.ResourceID = val +} + +// SetEventType sets the value of EventType. +func (s *AuditEvent) SetEventType(val int32) { + s.EventType = val +} + +// SetEventData sets the value of EventData. +func (s *AuditEvent) SetEventData(val AuditEventEventData) { + s.EventData = val +} + +// Arbitrary event data. +type AuditEventEventData map[string]jx.Raw + +func (s *AuditEventEventData) init() AuditEventEventData { + m := *s + if m == nil { + m = map[string]jx.Raw{} + *s = m + } + return m +} + type CookieAuth struct { APIKey string } @@ -79,6 +177,40 @@ func (s *CookieAuth) SetAPIKey(val string) { s.APIKey = val } +// CreateMapfixAuditCommentNoContent is response for CreateMapfixAuditComment operation. +type CreateMapfixAuditCommentNoContent struct{} + +type CreateMapfixAuditCommentReq struct { + Data io.Reader +} + +// Read reads data from the Data reader. +// +// Kept to satisfy the io.Reader interface. +func (s CreateMapfixAuditCommentReq) Read(p []byte) (n int, err error) { + if s.Data == nil { + return 0, io.EOF + } + return s.Data.Read(p) +} + +// CreateSubmissionAuditCommentNoContent is response for CreateSubmissionAuditComment operation. +type CreateSubmissionAuditCommentNoContent struct{} + +type CreateSubmissionAuditCommentReq struct { + Data io.Reader +} + +// Read reads data from the Data reader. +// +// Kept to satisfy the io.Reader interface. +func (s CreateSubmissionAuditCommentReq) Read(p []byte) (n int, err error) { + if s.Data == nil { + return 0, io.EOF + } + return s.Data.Read(p) +} + // DeleteScriptNoContent is response for DeleteScript operation. type DeleteScriptNoContent struct{} diff --git a/pkg/api/oas_server_gen.go b/pkg/api/oas_server_gen.go index 99b8e44..0fddb0c 100644 --- a/pkg/api/oas_server_gen.go +++ b/pkg/api/oas_server_gen.go @@ -122,6 +122,12 @@ type Handler interface { // // POST /mapfixes CreateMapfix(ctx context.Context, req *MapfixTriggerCreate) (*OperationID, error) + // CreateMapfixAuditComment implements createMapfixAuditComment operation. + // + // Post a comment to the audit log. + // + // POST /mapfixes/{MapfixID}/comment + CreateMapfixAuditComment(ctx context.Context, req CreateMapfixAuditCommentReq, params CreateMapfixAuditCommentParams) error // CreateScript implements createScript operation. // // Create a new script. @@ -140,6 +146,12 @@ type Handler interface { // // POST /submissions CreateSubmission(ctx context.Context, req *SubmissionTriggerCreate) (*OperationID, error) + // CreateSubmissionAuditComment implements createSubmissionAuditComment operation. + // + // Post a comment to the audit log. + // + // POST /submissions/{SubmissionID}/comment + CreateSubmissionAuditComment(ctx context.Context, req CreateSubmissionAuditCommentReq, params CreateSubmissionAuditCommentParams) error // DeleteScript implements deleteScript operation. // // Delete the specified script by ID. @@ -188,6 +200,12 @@ type Handler interface { // // GET /submissions/{SubmissionID} GetSubmission(ctx context.Context, params GetSubmissionParams) (*Submission, error) + // ListMapfixAuditEvents implements listMapfixAuditEvents operation. + // + // Retrieve a list of audit events. + // + // GET /mapfixes/{MapfixID}/audit-events + ListMapfixAuditEvents(ctx context.Context, params ListMapfixAuditEventsParams) ([]AuditEvent, error) // ListMapfixes implements listMapfixes operation. // // Get list of mapfixes. @@ -212,6 +230,12 @@ type Handler interface { // // GET /scripts ListScripts(ctx context.Context, params ListScriptsParams) ([]Script, error) + // ListSubmissionAuditEvents implements listSubmissionAuditEvents operation. + // + // Retrieve a list of audit events. + // + // GET /submissions/{SubmissionID}/audit-events + ListSubmissionAuditEvents(ctx context.Context, params ListSubmissionAuditEventsParams) ([]AuditEvent, error) // ListSubmissions implements listSubmissions operation. // // Get list of submissions. diff --git a/pkg/api/oas_unimplemented_gen.go b/pkg/api/oas_unimplemented_gen.go index 56392ad..cacd62c 100644 --- a/pkg/api/oas_unimplemented_gen.go +++ b/pkg/api/oas_unimplemented_gen.go @@ -184,6 +184,15 @@ func (UnimplementedHandler) CreateMapfix(ctx context.Context, req *MapfixTrigger return r, ht.ErrNotImplemented } +// CreateMapfixAuditComment implements createMapfixAuditComment operation. +// +// Post a comment to the audit log. +// +// POST /mapfixes/{MapfixID}/comment +func (UnimplementedHandler) CreateMapfixAuditComment(ctx context.Context, req CreateMapfixAuditCommentReq, params CreateMapfixAuditCommentParams) error { + return ht.ErrNotImplemented +} + // CreateScript implements createScript operation. // // Create a new script. @@ -211,6 +220,15 @@ func (UnimplementedHandler) CreateSubmission(ctx context.Context, req *Submissio return r, ht.ErrNotImplemented } +// CreateSubmissionAuditComment implements createSubmissionAuditComment operation. +// +// Post a comment to the audit log. +// +// POST /submissions/{SubmissionID}/comment +func (UnimplementedHandler) CreateSubmissionAuditComment(ctx context.Context, req CreateSubmissionAuditCommentReq, params CreateSubmissionAuditCommentParams) error { + return ht.ErrNotImplemented +} + // DeleteScript implements deleteScript operation. // // Delete the specified script by ID. @@ -283,6 +301,15 @@ func (UnimplementedHandler) GetSubmission(ctx context.Context, params GetSubmiss return r, ht.ErrNotImplemented } +// ListMapfixAuditEvents implements listMapfixAuditEvents operation. +// +// Retrieve a list of audit events. +// +// GET /mapfixes/{MapfixID}/audit-events +func (UnimplementedHandler) ListMapfixAuditEvents(ctx context.Context, params ListMapfixAuditEventsParams) (r []AuditEvent, _ error) { + return r, ht.ErrNotImplemented +} + // ListMapfixes implements listMapfixes operation. // // Get list of mapfixes. @@ -319,6 +346,15 @@ func (UnimplementedHandler) ListScripts(ctx context.Context, params ListScriptsP return r, ht.ErrNotImplemented } +// ListSubmissionAuditEvents implements listSubmissionAuditEvents operation. +// +// Retrieve a list of audit events. +// +// GET /submissions/{SubmissionID}/audit-events +func (UnimplementedHandler) ListSubmissionAuditEvents(ctx context.Context, params ListSubmissionAuditEventsParams) (r []AuditEvent, _ error) { + return r, ht.ErrNotImplemented +} + // ListSubmissions implements listSubmissions operation. // // Get list of submissions. -- 2.49.1 From 434cd295f5defed20126278654647c127d753a92 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 8 Apr 2025 12:29:44 -0700 Subject: [PATCH 354/454] submissions: implement audit endpoints --- pkg/service/audit_events.go | 200 ++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 pkg/service/audit_events.go diff --git a/pkg/service/audit_events.go b/pkg/service/audit_events.go new file mode 100644 index 0000000..38522f6 --- /dev/null +++ b/pkg/service/audit_events.go @@ -0,0 +1,200 @@ +package service + +import ( + "context" + "encoding/json" + + "git.itzana.me/strafesnet/maps-service/pkg/api" + "git.itzana.me/strafesnet/maps-service/pkg/datastore" + "git.itzana.me/strafesnet/maps-service/pkg/model" +) + +// CreateMapfixAuditComment implements createMapfixAuditComment operation. +// +// Post a comment to the audit log +// +// POST /mapfixes/{MapfixID}/comment +func (svc *Service) CreateMapfixAuditComment(ctx context.Context, req api.CreateMapfixAuditCommentReq, params api.CreateMapfixAuditCommentParams) (error) { + userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) + if !ok { + return ErrUserInfo + } + + has_role, err := userInfo.HasRoleMapfixReview() + if err != nil { + return err + } + if !has_role { + return ErrPermissionDeniedNeedRoleMapReview + } + + userId, err := userInfo.GetUserID() + if err != nil { + return err + } + + data := []byte{} + _, err = req.Read(data) + if err != nil { + return err + } + Comment := string(data) + + event_data := model.AuditEventDataComment{ + Comment: Comment, + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceMapfix, + ResourceID: params.MapfixID, + EventType: model.AuditEventTypeComment, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil +} + +// ListMapfixAuditEvents invokes listMapfixAuditEvents operation. +// +// Retrieve a list of audit events. +// +// GET /mapfixes/{MapfixID}/audit-events +func (svc *Service) ListMapfixAuditEvents(ctx context.Context, params api.ListMapfixAuditEventsParams) ([]api.AuditEvent, error) { + filter := datastore.Optional() + + filter.Add("resource_type", model.ResourceMapfix) + filter.Add("resource_id", params.MapfixID) + + items, err := svc.DB.AuditEvents().List(ctx, filter, model.Page{ + Number: params.Page, + Size: params.Limit, + }) + if err != nil { + return nil, err + } + + var resp []api.AuditEvent + for _, item := range items { + EventData := api.AuditEventEventData{} + err = EventData.UnmarshalJSON(item.EventData) + if err != nil { + return nil, err + } + resp = append(resp, api.AuditEvent{ + ID: item.ID, + Date: item.CreatedAt.Unix(), + User: int64(item.User), + ResourceType: int32(item.ResourceType), + ResourceID: item.ResourceID, + EventType: int32(item.EventType), + EventData: EventData, + }) + } + + return resp, nil +} + +// CreateSubmissionAuditComment implements createSubmissionAuditComment operation. +// +// Post a comment to the audit log +// +// POST /submissions/{SubmissionID}/comment +func (svc *Service) CreateSubmissionAuditComment(ctx context.Context, req api.CreateSubmissionAuditCommentReq, params api.CreateSubmissionAuditCommentParams) (error) { + userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) + if !ok { + return ErrUserInfo + } + + has_role, err := userInfo.HasRoleSubmissionReview() + if err != nil { + return err + } + if !has_role { + return ErrPermissionDeniedNeedRoleMapReview + } + + userId, err := userInfo.GetUserID() + if err != nil { + return err + } + + data := []byte{} + _, err = req.Read(data) + if err != nil { + return err + } + Comment := string(data) + + event_data := model.AuditEventDataComment{ + Comment: Comment, + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceSubmission, + ResourceID: params.SubmissionID, + EventType: model.AuditEventTypeComment, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil +} + +// ListSubmissionAuditEvents invokes listSubmissionAuditEvents operation. +// +// Retrieve a list of audit events. +// +// GET /submissions/{SubmissionID}/audit-events +func (svc *Service) ListSubmissionAuditEvents(ctx context.Context, params api.ListSubmissionAuditEventsParams) ([]api.AuditEvent, error) { + filter := datastore.Optional() + + filter.Add("resource_type", model.ResourceSubmission) + filter.Add("resource_id", params.SubmissionID) + + items, err := svc.DB.AuditEvents().List(ctx, filter, model.Page{ + Number: params.Page, + Size: params.Limit, + }) + if err != nil { + return nil, err + } + + var resp []api.AuditEvent + for _, item := range items { + EventData := api.AuditEventEventData{} + err = EventData.UnmarshalJSON(item.EventData) + if err != nil { + return nil, err + } + resp = append(resp, api.AuditEvent{ + ID: item.ID, + Date: item.CreatedAt.Unix(), + User: int64(item.User), + ResourceType: int32(item.ResourceType), + ResourceID: item.ResourceID, + EventType: int32(item.EventType), + EventData: EventData, + }) + } + + return resp, nil +} -- 2.49.1 From 99a082afb53d0dad1015d140c05f5e383d6c5b33 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 8 Apr 2025 12:56:07 -0700 Subject: [PATCH 355/454] submisions: improve error precision --- pkg/service/audit_events.go | 4 ++-- pkg/service/mapfixes.go | 14 +++++++------- pkg/service/service.go | 6 ++++-- pkg/service/submissions.go | 14 +++++++------- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/pkg/service/audit_events.go b/pkg/service/audit_events.go index 38522f6..61dd65a 100644 --- a/pkg/service/audit_events.go +++ b/pkg/service/audit_events.go @@ -25,7 +25,7 @@ func (svc *Service) CreateMapfixAuditComment(ctx context.Context, req api.Create return err } if !has_role { - return ErrPermissionDeniedNeedRoleMapReview + return ErrPermissionDeniedNeedRoleMapfixReview } userId, err := userInfo.GetUserID() @@ -120,7 +120,7 @@ func (svc *Service) CreateSubmissionAuditComment(ctx context.Context, req api.Cr return err } if !has_role { - return ErrPermissionDeniedNeedRoleMapReview + return ErrPermissionDeniedNeedRoleSubmissionReview } userId, err := userInfo.GetUserID() diff --git a/pkg/service/mapfixes.go b/pkg/service/mapfixes.go index 722d437..2b17ea0 100644 --- a/pkg/service/mapfixes.go +++ b/pkg/service/mapfixes.go @@ -333,7 +333,7 @@ func (svc *Service) ActionMapfixReject(ctx context.Context, params api.ActionMap } // check if caller has required role if !has_role { - return ErrPermissionDeniedNeedRoleMapReview + return ErrPermissionDeniedNeedRoleMapfixReview } userId, err := userInfo.GetUserID() @@ -391,7 +391,7 @@ func (svc *Service) ActionMapfixRequestChanges(ctx context.Context, params api.A } // check if caller has required role if !has_role { - return ErrPermissionDeniedNeedRoleMapReview + return ErrPermissionDeniedNeedRoleMapfixReview } // transaction @@ -539,7 +539,7 @@ func (svc *Service) ActionMapfixTriggerUpload(ctx context.Context, params api.Ac } // check if caller has required role if !has_role { - return ErrPermissionDeniedNeedRoleMapUpload + return ErrPermissionDeniedNeedRoleMapfixUpload } userId, err := userInfo.GetUserID() @@ -615,7 +615,7 @@ func (svc *Service) ActionMapfixValidated(ctx context.Context, params api.Action } // check if caller has required role if !has_role { - return ErrPermissionDeniedNeedRoleMapUpload + return ErrPermissionDeniedNeedRoleMapfixUpload } userId, err := userInfo.GetUserID() @@ -701,7 +701,7 @@ func (svc *Service) ActionMapfixTriggerValidate(ctx context.Context, params api. } // check if caller has required role if !has_role { - return ErrPermissionDeniedNeedRoleMapReview + return ErrPermissionDeniedNeedRoleMapfixReview } // read mapfix (this could be done with a transaction WHERE clause) @@ -810,7 +810,7 @@ func (svc *Service) ActionMapfixRetryValidate(ctx context.Context, params api.Ac } // check if caller has required role if !has_role { - return ErrPermissionDeniedNeedRoleMapReview + return ErrPermissionDeniedNeedRoleMapfixReview } userId, err := userInfo.GetUserID() @@ -890,7 +890,7 @@ func (svc *Service) ActionMapfixAccepted(ctx context.Context, params api.ActionM } // check if caller has required role if !has_role { - return ErrPermissionDeniedNeedRoleMapReview + return ErrPermissionDeniedNeedRoleMapfixReview } userId, err := userInfo.GetUserID() diff --git a/pkg/service/service.go b/pkg/service/service.go index 6c6f3e7..2114850 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -19,8 +19,10 @@ var ( ErrDelayReset = errors.New("Please give the validator at least 10 seconds to operate before attempting to reset the status") ErrPermissionDeniedNotSubmitter = fmt.Errorf("%w: You must be the submitter to perform this action", ErrPermissionDenied) ErrPermissionDeniedNeedRoleSubmissionRelease = fmt.Errorf("%w: Need Role SubmissionRelease", ErrPermissionDenied) - ErrPermissionDeniedNeedRoleMapUpload = fmt.Errorf("%w: Need Role MapUpload", ErrPermissionDenied) - ErrPermissionDeniedNeedRoleMapReview = fmt.Errorf("%w: Need Role MapReview", ErrPermissionDenied) + ErrPermissionDeniedNeedRoleMapfixUpload = fmt.Errorf("%w: Need Role MapfixUpload", ErrPermissionDenied) + ErrPermissionDeniedNeedRoleMapfixReview = fmt.Errorf("%w: Need Role MapfixReview", ErrPermissionDenied) + ErrPermissionDeniedNeedRoleSubmissionUpload = fmt.Errorf("%w: Need Role SubmissionUpload", ErrPermissionDenied) + ErrPermissionDeniedNeedRoleSubmissionReview = fmt.Errorf("%w: Need Role SubmissionReview", ErrPermissionDenied) ErrPermissionDeniedNeedRoleMapDownload = fmt.Errorf("%w: Need Role MapDownload", ErrPermissionDenied) ErrPermissionDeniedNeedRoleScriptWrite = fmt.Errorf("%w: Need Role ScriptWrite", ErrPermissionDenied) ErrPermissionDeniedNeedRoleMaptest = fmt.Errorf("%w: Need Role Maptest", ErrPermissionDenied) diff --git a/pkg/service/submissions.go b/pkg/service/submissions.go index e7963d1..e15003d 100644 --- a/pkg/service/submissions.go +++ b/pkg/service/submissions.go @@ -322,7 +322,7 @@ func (svc *Service) ActionSubmissionReject(ctx context.Context, params api.Actio } // check if caller has required role if !has_role { - return ErrPermissionDeniedNeedRoleMapReview + return ErrPermissionDeniedNeedRoleSubmissionReview } userId, err := userInfo.GetUserID() @@ -380,7 +380,7 @@ func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params a } // check if caller has required role if !has_role { - return ErrPermissionDeniedNeedRoleMapReview + return ErrPermissionDeniedNeedRoleSubmissionReview } userId, err := userInfo.GetUserID() @@ -560,7 +560,7 @@ func (svc *Service) ActionSubmissionTriggerUpload(ctx context.Context, params ap } // check if caller has required role if !has_role { - return ErrPermissionDeniedNeedRoleMapUpload + return ErrPermissionDeniedNeedRoleSubmissionUpload } userId, err := userInfo.GetUserID() @@ -643,7 +643,7 @@ func (svc *Service) ActionSubmissionValidated(ctx context.Context, params api.Ac } // check if caller has required role if !has_role { - return ErrPermissionDeniedNeedRoleMapUpload + return ErrPermissionDeniedNeedRoleSubmissionUpload } userId, err := userInfo.GetUserID() @@ -711,7 +711,7 @@ func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params } // check if caller has required role if !has_role { - return ErrPermissionDeniedNeedRoleMapReview + return ErrPermissionDeniedNeedRoleSubmissionReview } // read submission (this could be done with a transaction WHERE clause) @@ -803,7 +803,7 @@ func (svc *Service) ActionSubmissionRetryValidate(ctx context.Context, params ap } // check if caller has required role if !has_role { - return ErrPermissionDeniedNeedRoleMapReview + return ErrPermissionDeniedNeedRoleSubmissionReview } userId, err := userInfo.GetUserID() @@ -883,7 +883,7 @@ func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params api.Act } // check if caller has required role if !has_role { - return ErrPermissionDeniedNeedRoleMapReview + return ErrPermissionDeniedNeedRoleSubmissionReview } userId, err := userInfo.GetUserID() -- 2.49.1 From 6c865e88413a9b99709a8c28183eeeef6d69e134 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 8 Apr 2025 13:06:26 -0700 Subject: [PATCH 356/454] openapi: prepare for map checks --- openapi-internal.yaml | 34 ++++++++++++++++++++++++++++++++ openapi.yaml | 46 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/openapi-internal.yaml b/openapi-internal.yaml index 155efe4..1fb1fd7 100644 --- a/openapi-internal.yaml +++ b/openapi-internal.yaml @@ -65,6 +65,23 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" + /mapfixes/{MapfixID}/status/validator-submitted: + post: + summary: (Internal endpoint) Role Validator changes status from Submitting -> Submitted + operationId: actionMapfixSubmitted + tags: + - Mapfixes + parameters: + - $ref: '#/components/parameters/MapfixID' + responses: + "204": + description: Successful response + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" /mapfixes/{MapfixID}/status/validator-validated: post: summary: (Internal endpoint) Role Validator changes status from Validating -> Validated @@ -203,6 +220,23 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" + /submissions/{SubmissionID}/status/validator-submitted: + post: + summary: (Internal endpoint) Role Validator changes status from Submitting -> Submitted + operationId: actionSubmissionSubmitted + tags: + - Submissions + parameters: + - $ref: '#/components/parameters/SubmissionID' + responses: + "204": + description: Successful response + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" /submissions/{SubmissionID}/status/validator-validated: post: summary: (Internal endpoint) Role Validator changes status from Validating -> Validated diff --git a/openapi.yaml b/openapi.yaml index 00b1140..16869ba 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -367,10 +367,27 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" - /mapfixes/{MapfixID}/status/submit: + /mapfixes/{MapfixID}/status/trigger-submit: post: - summary: Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted - operationId: actionMapfixSubmit + summary: Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting + operationId: actionMapfixTriggerSubmit + tags: + - Mapfixes + parameters: + - $ref: '#/components/parameters/MapfixID' + responses: + "204": + description: Successful response + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /mapfixes/{MapfixID}/status/reset-submitting: + post: + summary: Role Submitter manually resets submitting softlock and changes status from Submitting -> UnderConstruction + operationId: actionMapfixResetSubmitting tags: - Mapfixes parameters: @@ -757,10 +774,27 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" - /submissions/{SubmissionID}/status/submit: + /submissions/{SubmissionID}/status/trigger-submit: post: - summary: Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted - operationId: actionSubmissionSubmit + summary: Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting + operationId: actionSubmissionTriggerSubmit + tags: + - Submissions + parameters: + - $ref: '#/components/parameters/SubmissionID' + responses: + "204": + description: Successful response + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /submissions/{SubmissionID}/status/reset-submitting: + post: + summary: Role Submitter manually resets submitting softlock and changes status from Submitting -> UnderConstruction + operationId: actionSubmissionResetSubmitting tags: - Submissions parameters: -- 2.49.1 From c85cb63639a950fb0d5d4a6c4995852674e0deda Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 8 Apr 2025 13:06:35 -0700 Subject: [PATCH 357/454] openapi: generate --- pkg/api/oas_client_gen.go | 328 +++++++++++++-- pkg/api/oas_handlers_gen.go | 476 ++++++++++++++++++++-- pkg/api/oas_operations_gen.go | 6 +- pkg/api/oas_parameters_gen.go | 182 ++++++++- pkg/api/oas_response_decoders_gen.go | 128 +++++- pkg/api/oas_response_encoders_gen.go | 18 +- pkg/api/oas_router_gen.go | 276 ++++++++----- pkg/api/oas_schemas_gen.go | 14 +- pkg/api/oas_server_gen.go | 30 +- pkg/api/oas_unimplemented_gen.go | 36 +- pkg/internal/oas_client_gen.go | 194 +++++++++ pkg/internal/oas_handlers_gen.go | 298 ++++++++++++++ pkg/internal/oas_operations_gen.go | 2 + pkg/internal/oas_parameters_gen.go | 166 ++++++++ pkg/internal/oas_response_decoders_gen.go | 120 ++++++ pkg/internal/oas_response_encoders_gen.go | 14 + pkg/internal/oas_router_gen.go | 92 +++++ pkg/internal/oas_schemas_gen.go | 6 + pkg/internal/oas_server_gen.go | 12 + pkg/internal/oas_unimplemented_gen.go | 18 + 20 files changed, 2214 insertions(+), 202 deletions(-) diff --git a/pkg/api/oas_client_gen.go b/pkg/api/oas_client_gen.go index f865e3a..89085f9 100644 --- a/pkg/api/oas_client_gen.go +++ b/pkg/api/oas_client_gen.go @@ -47,6 +47,13 @@ type Invoker interface { // // POST /mapfixes/{MapfixID}/status/request-changes ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error + // ActionMapfixResetSubmitting invokes actionMapfixResetSubmitting operation. + // + // Role Submitter manually resets submitting softlock and changes status from Submitting -> + // UnderConstruction. + // + // POST /mapfixes/{MapfixID}/status/reset-submitting + ActionMapfixResetSubmitting(ctx context.Context, params ActionMapfixResetSubmittingParams) error // ActionMapfixRetryValidate invokes actionMapfixRetryValidate operation. // // Role Reviewer re-runs validation and changes status from Accepted -> Validating. @@ -59,12 +66,12 @@ type Invoker interface { // // POST /mapfixes/{MapfixID}/status/revoke ActionMapfixRevoke(ctx context.Context, params ActionMapfixRevokeParams) error - // ActionMapfixSubmit invokes actionMapfixSubmit operation. + // ActionMapfixTriggerSubmit invokes actionMapfixTriggerSubmit operation. // - // Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted. + // Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting. // - // POST /mapfixes/{MapfixID}/status/submit - ActionMapfixSubmit(ctx context.Context, params ActionMapfixSubmitParams) error + // POST /mapfixes/{MapfixID}/status/trigger-submit + ActionMapfixTriggerSubmit(ctx context.Context, params ActionMapfixTriggerSubmitParams) error // ActionMapfixTriggerUpload invokes actionMapfixTriggerUpload operation. // // Role Admin changes status from Validated -> Uploading. @@ -101,6 +108,13 @@ type Invoker interface { // // POST /submissions/{SubmissionID}/status/request-changes ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error + // ActionSubmissionResetSubmitting invokes actionSubmissionResetSubmitting operation. + // + // Role Submitter manually resets submitting softlock and changes status from Submitting -> + // UnderConstruction. + // + // POST /submissions/{SubmissionID}/status/reset-submitting + ActionSubmissionResetSubmitting(ctx context.Context, params ActionSubmissionResetSubmittingParams) error // ActionSubmissionRetryValidate invokes actionSubmissionRetryValidate operation. // // Role Reviewer re-runs validation and changes status from Accepted -> Validating. @@ -113,12 +127,12 @@ type Invoker interface { // // POST /submissions/{SubmissionID}/status/revoke ActionSubmissionRevoke(ctx context.Context, params ActionSubmissionRevokeParams) error - // ActionSubmissionSubmit invokes actionSubmissionSubmit operation. + // ActionSubmissionTriggerSubmit invokes actionSubmissionTriggerSubmit operation. // - // Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted. + // Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting. // - // POST /submissions/{SubmissionID}/status/submit - ActionSubmissionSubmit(ctx context.Context, params ActionSubmissionSubmitParams) error + // POST /submissions/{SubmissionID}/status/trigger-submit + ActionSubmissionTriggerSubmit(ctx context.Context, params ActionSubmissionTriggerSubmitParams) error // ActionSubmissionTriggerUpload invokes actionSubmissionTriggerUpload operation. // // Role Admin changes status from Validated -> Uploading. @@ -746,6 +760,131 @@ func (c *Client) sendActionMapfixRequestChanges(ctx context.Context, params Acti return result, nil } +// ActionMapfixResetSubmitting invokes actionMapfixResetSubmitting operation. +// +// Role Submitter manually resets submitting softlock and changes status from Submitting -> +// UnderConstruction. +// +// POST /mapfixes/{MapfixID}/status/reset-submitting +func (c *Client) ActionMapfixResetSubmitting(ctx context.Context, params ActionMapfixResetSubmittingParams) error { + _, err := c.sendActionMapfixResetSubmitting(ctx, params) + return err +} + +func (c *Client) sendActionMapfixResetSubmitting(ctx context.Context, params ActionMapfixResetSubmittingParams) (res *ActionMapfixResetSubmittingNoContent, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("actionMapfixResetSubmitting"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/status/reset-submitting"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, ActionMapfixResetSubmittingOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [3]string + pathParts[0] = "/mapfixes/" + { + // Encode "MapfixID" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "MapfixID", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.Int64ToString(params.MapfixID)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[1] = encoded + } + pathParts[2] = "/status/reset-submitting" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:CookieAuth" + switch err := c.securityCookieAuth(ctx, ActionMapfixResetSubmittingOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"CookieAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeActionMapfixResetSubmittingResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // ActionMapfixRetryValidate invokes actionMapfixRetryValidate operation. // // Role Reviewer re-runs validation and changes status from Accepted -> Validating. @@ -994,21 +1133,21 @@ func (c *Client) sendActionMapfixRevoke(ctx context.Context, params ActionMapfix return result, nil } -// ActionMapfixSubmit invokes actionMapfixSubmit operation. +// ActionMapfixTriggerSubmit invokes actionMapfixTriggerSubmit operation. // -// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted. +// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting. // -// POST /mapfixes/{MapfixID}/status/submit -func (c *Client) ActionMapfixSubmit(ctx context.Context, params ActionMapfixSubmitParams) error { - _, err := c.sendActionMapfixSubmit(ctx, params) +// POST /mapfixes/{MapfixID}/status/trigger-submit +func (c *Client) ActionMapfixTriggerSubmit(ctx context.Context, params ActionMapfixTriggerSubmitParams) error { + _, err := c.sendActionMapfixTriggerSubmit(ctx, params) return err } -func (c *Client) sendActionMapfixSubmit(ctx context.Context, params ActionMapfixSubmitParams) (res *ActionMapfixSubmitNoContent, err error) { +func (c *Client) sendActionMapfixTriggerSubmit(ctx context.Context, params ActionMapfixTriggerSubmitParams) (res *ActionMapfixTriggerSubmitNoContent, err error) { otelAttrs := []attribute.KeyValue{ - otelogen.OperationID("actionMapfixSubmit"), + otelogen.OperationID("actionMapfixTriggerSubmit"), semconv.HTTPRequestMethodKey.String("POST"), - semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/status/submit"), + semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/status/trigger-submit"), } // Run stopwatch. @@ -1023,7 +1162,7 @@ func (c *Client) sendActionMapfixSubmit(ctx context.Context, params ActionMapfix c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) // Start a span for this request. - ctx, span := c.cfg.Tracer.Start(ctx, ActionMapfixSubmitOperation, + ctx, span := c.cfg.Tracer.Start(ctx, ActionMapfixTriggerSubmitOperation, trace.WithAttributes(otelAttrs...), clientSpanKind, ) @@ -1060,7 +1199,7 @@ func (c *Client) sendActionMapfixSubmit(ctx context.Context, params ActionMapfix } pathParts[1] = encoded } - pathParts[2] = "/status/submit" + pathParts[2] = "/status/trigger-submit" uri.AddPathParts(u, pathParts[:]...) stage = "EncodeRequest" @@ -1074,7 +1213,7 @@ func (c *Client) sendActionMapfixSubmit(ctx context.Context, params ActionMapfix var satisfied bitset { stage = "Security:CookieAuth" - switch err := c.securityCookieAuth(ctx, ActionMapfixSubmitOperation, r); { + switch err := c.securityCookieAuth(ctx, ActionMapfixTriggerSubmitOperation, r); { case err == nil: // if NO error satisfied[0] |= 1 << 0 case errors.Is(err, ogenerrors.ErrSkipClientSecurity): @@ -1110,7 +1249,7 @@ func (c *Client) sendActionMapfixSubmit(ctx context.Context, params ActionMapfix defer resp.Body.Close() stage = "DecodeResponse" - result, err := decodeActionMapfixSubmitResponse(resp) + result, err := decodeActionMapfixTriggerSubmitResponse(resp) if err != nil { return res, errors.Wrap(err, "decode response") } @@ -1862,6 +2001,131 @@ func (c *Client) sendActionSubmissionRequestChanges(ctx context.Context, params return result, nil } +// ActionSubmissionResetSubmitting invokes actionSubmissionResetSubmitting operation. +// +// Role Submitter manually resets submitting softlock and changes status from Submitting -> +// UnderConstruction. +// +// POST /submissions/{SubmissionID}/status/reset-submitting +func (c *Client) ActionSubmissionResetSubmitting(ctx context.Context, params ActionSubmissionResetSubmittingParams) error { + _, err := c.sendActionSubmissionResetSubmitting(ctx, params) + return err +} + +func (c *Client) sendActionSubmissionResetSubmitting(ctx context.Context, params ActionSubmissionResetSubmittingParams) (res *ActionSubmissionResetSubmittingNoContent, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("actionSubmissionResetSubmitting"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/reset-submitting"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, ActionSubmissionResetSubmittingOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [3]string + pathParts[0] = "/submissions/" + { + // Encode "SubmissionID" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "SubmissionID", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.Int64ToString(params.SubmissionID)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[1] = encoded + } + pathParts[2] = "/status/reset-submitting" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:CookieAuth" + switch err := c.securityCookieAuth(ctx, ActionSubmissionResetSubmittingOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"CookieAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeActionSubmissionResetSubmittingResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // ActionSubmissionRetryValidate invokes actionSubmissionRetryValidate operation. // // Role Reviewer re-runs validation and changes status from Accepted -> Validating. @@ -2110,21 +2374,21 @@ func (c *Client) sendActionSubmissionRevoke(ctx context.Context, params ActionSu return result, nil } -// ActionSubmissionSubmit invokes actionSubmissionSubmit operation. +// ActionSubmissionTriggerSubmit invokes actionSubmissionTriggerSubmit operation. // -// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted. +// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting. // -// POST /submissions/{SubmissionID}/status/submit -func (c *Client) ActionSubmissionSubmit(ctx context.Context, params ActionSubmissionSubmitParams) error { - _, err := c.sendActionSubmissionSubmit(ctx, params) +// POST /submissions/{SubmissionID}/status/trigger-submit +func (c *Client) ActionSubmissionTriggerSubmit(ctx context.Context, params ActionSubmissionTriggerSubmitParams) error { + _, err := c.sendActionSubmissionTriggerSubmit(ctx, params) return err } -func (c *Client) sendActionSubmissionSubmit(ctx context.Context, params ActionSubmissionSubmitParams) (res *ActionSubmissionSubmitNoContent, err error) { +func (c *Client) sendActionSubmissionTriggerSubmit(ctx context.Context, params ActionSubmissionTriggerSubmitParams) (res *ActionSubmissionTriggerSubmitNoContent, err error) { otelAttrs := []attribute.KeyValue{ - otelogen.OperationID("actionSubmissionSubmit"), + otelogen.OperationID("actionSubmissionTriggerSubmit"), semconv.HTTPRequestMethodKey.String("POST"), - semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/submit"), + semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/trigger-submit"), } // Run stopwatch. @@ -2139,7 +2403,7 @@ func (c *Client) sendActionSubmissionSubmit(ctx context.Context, params ActionSu c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) // Start a span for this request. - ctx, span := c.cfg.Tracer.Start(ctx, ActionSubmissionSubmitOperation, + ctx, span := c.cfg.Tracer.Start(ctx, ActionSubmissionTriggerSubmitOperation, trace.WithAttributes(otelAttrs...), clientSpanKind, ) @@ -2176,7 +2440,7 @@ func (c *Client) sendActionSubmissionSubmit(ctx context.Context, params ActionSu } pathParts[1] = encoded } - pathParts[2] = "/status/submit" + pathParts[2] = "/status/trigger-submit" uri.AddPathParts(u, pathParts[:]...) stage = "EncodeRequest" @@ -2190,7 +2454,7 @@ func (c *Client) sendActionSubmissionSubmit(ctx context.Context, params ActionSu var satisfied bitset { stage = "Security:CookieAuth" - switch err := c.securityCookieAuth(ctx, ActionSubmissionSubmitOperation, r); { + switch err := c.securityCookieAuth(ctx, ActionSubmissionTriggerSubmitOperation, r); { case err == nil: // if NO error satisfied[0] |= 1 << 0 case errors.Is(err, ogenerrors.ErrSkipClientSecurity): @@ -2226,7 +2490,7 @@ func (c *Client) sendActionSubmissionSubmit(ctx context.Context, params ActionSu defer resp.Body.Close() stage = "DecodeResponse" - result, err := decodeActionSubmissionSubmitResponse(resp) + result, err := decodeActionSubmissionTriggerSubmitResponse(resp) if err != nil { return res, errors.Wrap(err, "decode response") } diff --git a/pkg/api/oas_handlers_gen.go b/pkg/api/oas_handlers_gen.go index 995a780..e6bcc7b 100644 --- a/pkg/api/oas_handlers_gen.go +++ b/pkg/api/oas_handlers_gen.go @@ -615,6 +615,202 @@ func (s *Server) handleActionMapfixRequestChangesRequest(args [1]string, argsEsc } } +// handleActionMapfixResetSubmittingRequest handles actionMapfixResetSubmitting operation. +// +// Role Submitter manually resets submitting softlock and changes status from Submitting -> +// UnderConstruction. +// +// POST /mapfixes/{MapfixID}/status/reset-submitting +func (s *Server) handleActionMapfixResetSubmittingRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("actionMapfixResetSubmitting"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/status/reset-submitting"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), ActionMapfixResetSubmittingOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: ActionMapfixResetSubmittingOperation, + ID: "actionMapfixResetSubmitting", + } + ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securityCookieAuth(ctx, ActionMapfixResetSubmittingOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "CookieAuth", + Err: err, + } + if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil { + defer recordError("Security:CookieAuth", err) + } + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil { + defer recordError("Security", err) + } + return + } + } + params, err := decodeActionMapfixResetSubmittingParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response *ActionMapfixResetSubmittingNoContent + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: ActionMapfixResetSubmittingOperation, + OperationSummary: "Role Submitter manually resets submitting softlock and changes status from Submitting -> UnderConstruction", + OperationID: "actionMapfixResetSubmitting", + Body: nil, + Params: middleware.Parameters{ + { + Name: "MapfixID", + In: "path", + }: params.MapfixID, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = ActionMapfixResetSubmittingParams + Response = *ActionMapfixResetSubmittingNoContent + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackActionMapfixResetSubmittingParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + err = s.h.ActionMapfixResetSubmitting(ctx, params) + return response, err + }, + ) + } else { + err = s.h.ActionMapfixResetSubmitting(ctx, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeActionMapfixResetSubmittingResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleActionMapfixRetryValidateRequest handles actionMapfixRetryValidate operation. // // Role Reviewer re-runs validation and changes status from Accepted -> Validating. @@ -1005,22 +1201,22 @@ func (s *Server) handleActionMapfixRevokeRequest(args [1]string, argsEscaped boo } } -// handleActionMapfixSubmitRequest handles actionMapfixSubmit operation. +// handleActionMapfixTriggerSubmitRequest handles actionMapfixTriggerSubmit operation. // -// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted. +// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting. // -// POST /mapfixes/{MapfixID}/status/submit -func (s *Server) handleActionMapfixSubmitRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { +// POST /mapfixes/{MapfixID}/status/trigger-submit +func (s *Server) handleActionMapfixTriggerSubmitRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { statusWriter := &codeRecorder{ResponseWriter: w} w = statusWriter otelAttrs := []attribute.KeyValue{ - otelogen.OperationID("actionMapfixSubmit"), + otelogen.OperationID("actionMapfixTriggerSubmit"), semconv.HTTPRequestMethodKey.String("POST"), - semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/status/submit"), + semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/status/trigger-submit"), } // Start a span for this request. - ctx, span := s.cfg.Tracer.Start(r.Context(), ActionMapfixSubmitOperation, + ctx, span := s.cfg.Tracer.Start(r.Context(), ActionMapfixTriggerSubmitOperation, trace.WithAttributes(otelAttrs...), serverSpanKind, ) @@ -1075,15 +1271,15 @@ func (s *Server) handleActionMapfixSubmitRequest(args [1]string, argsEscaped boo } err error opErrContext = ogenerrors.OperationContext{ - Name: ActionMapfixSubmitOperation, - ID: "actionMapfixSubmit", + Name: ActionMapfixTriggerSubmitOperation, + ID: "actionMapfixTriggerSubmit", } ) { type bitset = [1]uint8 var satisfied bitset { - sctx, ok, err := s.securityCookieAuth(ctx, ActionMapfixSubmitOperation, r) + sctx, ok, err := s.securityCookieAuth(ctx, ActionMapfixTriggerSubmitOperation, r) if err != nil { err = &ogenerrors.SecurityError{ OperationContext: opErrContext, @@ -1125,7 +1321,7 @@ func (s *Server) handleActionMapfixSubmitRequest(args [1]string, argsEscaped boo return } } - params, err := decodeActionMapfixSubmitParams(args, argsEscaped, r) + params, err := decodeActionMapfixTriggerSubmitParams(args, argsEscaped, r) if err != nil { err = &ogenerrors.DecodeParamsError{ OperationContext: opErrContext, @@ -1136,13 +1332,13 @@ func (s *Server) handleActionMapfixSubmitRequest(args [1]string, argsEscaped boo return } - var response *ActionMapfixSubmitNoContent + var response *ActionMapfixTriggerSubmitNoContent if m := s.cfg.Middleware; m != nil { mreq := middleware.Request{ Context: ctx, - OperationName: ActionMapfixSubmitOperation, - OperationSummary: "Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted", - OperationID: "actionMapfixSubmit", + OperationName: ActionMapfixTriggerSubmitOperation, + OperationSummary: "Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting", + OperationID: "actionMapfixTriggerSubmit", Body: nil, Params: middleware.Parameters{ { @@ -1155,8 +1351,8 @@ func (s *Server) handleActionMapfixSubmitRequest(args [1]string, argsEscaped boo type ( Request = struct{} - Params = ActionMapfixSubmitParams - Response = *ActionMapfixSubmitNoContent + Params = ActionMapfixTriggerSubmitParams + Response = *ActionMapfixTriggerSubmitNoContent ) response, err = middleware.HookMiddleware[ Request, @@ -1165,14 +1361,14 @@ func (s *Server) handleActionMapfixSubmitRequest(args [1]string, argsEscaped boo ]( m, mreq, - unpackActionMapfixSubmitParams, + unpackActionMapfixTriggerSubmitParams, func(ctx context.Context, request Request, params Params) (response Response, err error) { - err = s.h.ActionMapfixSubmit(ctx, params) + err = s.h.ActionMapfixTriggerSubmit(ctx, params) return response, err }, ) } else { - err = s.h.ActionMapfixSubmit(ctx, params) + err = s.h.ActionMapfixTriggerSubmit(ctx, params) } if err != nil { if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { @@ -1191,7 +1387,7 @@ func (s *Server) handleActionMapfixSubmitRequest(args [1]string, argsEscaped boo return } - if err := encodeActionMapfixSubmitResponse(response, w, span); err != nil { + if err := encodeActionMapfixTriggerSubmitResponse(response, w, span); err != nil { defer recordError("EncodeResponse", err) if !errors.Is(err, ht.ErrInternalServerErrorResponse) { s.cfg.ErrorHandler(ctx, w, r, err) @@ -2370,6 +2566,202 @@ func (s *Server) handleActionSubmissionRequestChangesRequest(args [1]string, arg } } +// handleActionSubmissionResetSubmittingRequest handles actionSubmissionResetSubmitting operation. +// +// Role Submitter manually resets submitting softlock and changes status from Submitting -> +// UnderConstruction. +// +// POST /submissions/{SubmissionID}/status/reset-submitting +func (s *Server) handleActionSubmissionResetSubmittingRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("actionSubmissionResetSubmitting"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/reset-submitting"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), ActionSubmissionResetSubmittingOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: ActionSubmissionResetSubmittingOperation, + ID: "actionSubmissionResetSubmitting", + } + ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securityCookieAuth(ctx, ActionSubmissionResetSubmittingOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "CookieAuth", + Err: err, + } + if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil { + defer recordError("Security:CookieAuth", err) + } + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil { + defer recordError("Security", err) + } + return + } + } + params, err := decodeActionSubmissionResetSubmittingParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response *ActionSubmissionResetSubmittingNoContent + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: ActionSubmissionResetSubmittingOperation, + OperationSummary: "Role Submitter manually resets submitting softlock and changes status from Submitting -> UnderConstruction", + OperationID: "actionSubmissionResetSubmitting", + Body: nil, + Params: middleware.Parameters{ + { + Name: "SubmissionID", + In: "path", + }: params.SubmissionID, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = ActionSubmissionResetSubmittingParams + Response = *ActionSubmissionResetSubmittingNoContent + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackActionSubmissionResetSubmittingParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + err = s.h.ActionSubmissionResetSubmitting(ctx, params) + return response, err + }, + ) + } else { + err = s.h.ActionSubmissionResetSubmitting(ctx, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeActionSubmissionResetSubmittingResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleActionSubmissionRetryValidateRequest handles actionSubmissionRetryValidate operation. // // Role Reviewer re-runs validation and changes status from Accepted -> Validating. @@ -2760,22 +3152,22 @@ func (s *Server) handleActionSubmissionRevokeRequest(args [1]string, argsEscaped } } -// handleActionSubmissionSubmitRequest handles actionSubmissionSubmit operation. +// handleActionSubmissionTriggerSubmitRequest handles actionSubmissionTriggerSubmit operation. // -// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted. +// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting. // -// POST /submissions/{SubmissionID}/status/submit -func (s *Server) handleActionSubmissionSubmitRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { +// POST /submissions/{SubmissionID}/status/trigger-submit +func (s *Server) handleActionSubmissionTriggerSubmitRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { statusWriter := &codeRecorder{ResponseWriter: w} w = statusWriter otelAttrs := []attribute.KeyValue{ - otelogen.OperationID("actionSubmissionSubmit"), + otelogen.OperationID("actionSubmissionTriggerSubmit"), semconv.HTTPRequestMethodKey.String("POST"), - semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/submit"), + semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/trigger-submit"), } // Start a span for this request. - ctx, span := s.cfg.Tracer.Start(r.Context(), ActionSubmissionSubmitOperation, + ctx, span := s.cfg.Tracer.Start(r.Context(), ActionSubmissionTriggerSubmitOperation, trace.WithAttributes(otelAttrs...), serverSpanKind, ) @@ -2830,15 +3222,15 @@ func (s *Server) handleActionSubmissionSubmitRequest(args [1]string, argsEscaped } err error opErrContext = ogenerrors.OperationContext{ - Name: ActionSubmissionSubmitOperation, - ID: "actionSubmissionSubmit", + Name: ActionSubmissionTriggerSubmitOperation, + ID: "actionSubmissionTriggerSubmit", } ) { type bitset = [1]uint8 var satisfied bitset { - sctx, ok, err := s.securityCookieAuth(ctx, ActionSubmissionSubmitOperation, r) + sctx, ok, err := s.securityCookieAuth(ctx, ActionSubmissionTriggerSubmitOperation, r) if err != nil { err = &ogenerrors.SecurityError{ OperationContext: opErrContext, @@ -2880,7 +3272,7 @@ func (s *Server) handleActionSubmissionSubmitRequest(args [1]string, argsEscaped return } } - params, err := decodeActionSubmissionSubmitParams(args, argsEscaped, r) + params, err := decodeActionSubmissionTriggerSubmitParams(args, argsEscaped, r) if err != nil { err = &ogenerrors.DecodeParamsError{ OperationContext: opErrContext, @@ -2891,13 +3283,13 @@ func (s *Server) handleActionSubmissionSubmitRequest(args [1]string, argsEscaped return } - var response *ActionSubmissionSubmitNoContent + var response *ActionSubmissionTriggerSubmitNoContent if m := s.cfg.Middleware; m != nil { mreq := middleware.Request{ Context: ctx, - OperationName: ActionSubmissionSubmitOperation, - OperationSummary: "Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted", - OperationID: "actionSubmissionSubmit", + OperationName: ActionSubmissionTriggerSubmitOperation, + OperationSummary: "Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting", + OperationID: "actionSubmissionTriggerSubmit", Body: nil, Params: middleware.Parameters{ { @@ -2910,8 +3302,8 @@ func (s *Server) handleActionSubmissionSubmitRequest(args [1]string, argsEscaped type ( Request = struct{} - Params = ActionSubmissionSubmitParams - Response = *ActionSubmissionSubmitNoContent + Params = ActionSubmissionTriggerSubmitParams + Response = *ActionSubmissionTriggerSubmitNoContent ) response, err = middleware.HookMiddleware[ Request, @@ -2920,14 +3312,14 @@ func (s *Server) handleActionSubmissionSubmitRequest(args [1]string, argsEscaped ]( m, mreq, - unpackActionSubmissionSubmitParams, + unpackActionSubmissionTriggerSubmitParams, func(ctx context.Context, request Request, params Params) (response Response, err error) { - err = s.h.ActionSubmissionSubmit(ctx, params) + err = s.h.ActionSubmissionTriggerSubmit(ctx, params) return response, err }, ) } else { - err = s.h.ActionSubmissionSubmit(ctx, params) + err = s.h.ActionSubmissionTriggerSubmit(ctx, params) } if err != nil { if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { @@ -2946,7 +3338,7 @@ func (s *Server) handleActionSubmissionSubmitRequest(args [1]string, argsEscaped return } - if err := encodeActionSubmissionSubmitResponse(response, w, span); err != nil { + if err := encodeActionSubmissionTriggerSubmitResponse(response, w, span); err != nil { defer recordError("EncodeResponse", err) if !errors.Is(err, ht.ErrInternalServerErrorResponse) { s.cfg.ErrorHandler(ctx, w, r, err) diff --git a/pkg/api/oas_operations_gen.go b/pkg/api/oas_operations_gen.go index 09d06f4..bbc161d 100644 --- a/pkg/api/oas_operations_gen.go +++ b/pkg/api/oas_operations_gen.go @@ -9,18 +9,20 @@ const ( ActionMapfixAcceptedOperation OperationName = "ActionMapfixAccepted" ActionMapfixRejectOperation OperationName = "ActionMapfixReject" ActionMapfixRequestChangesOperation OperationName = "ActionMapfixRequestChanges" + ActionMapfixResetSubmittingOperation OperationName = "ActionMapfixResetSubmitting" ActionMapfixRetryValidateOperation OperationName = "ActionMapfixRetryValidate" ActionMapfixRevokeOperation OperationName = "ActionMapfixRevoke" - ActionMapfixSubmitOperation OperationName = "ActionMapfixSubmit" + ActionMapfixTriggerSubmitOperation OperationName = "ActionMapfixTriggerSubmit" ActionMapfixTriggerUploadOperation OperationName = "ActionMapfixTriggerUpload" ActionMapfixTriggerValidateOperation OperationName = "ActionMapfixTriggerValidate" ActionMapfixValidatedOperation OperationName = "ActionMapfixValidated" ActionSubmissionAcceptedOperation OperationName = "ActionSubmissionAccepted" ActionSubmissionRejectOperation OperationName = "ActionSubmissionReject" ActionSubmissionRequestChangesOperation OperationName = "ActionSubmissionRequestChanges" + ActionSubmissionResetSubmittingOperation OperationName = "ActionSubmissionResetSubmitting" ActionSubmissionRetryValidateOperation OperationName = "ActionSubmissionRetryValidate" ActionSubmissionRevokeOperation OperationName = "ActionSubmissionRevoke" - ActionSubmissionSubmitOperation OperationName = "ActionSubmissionSubmit" + ActionSubmissionTriggerSubmitOperation OperationName = "ActionSubmissionTriggerSubmit" ActionSubmissionTriggerUploadOperation OperationName = "ActionSubmissionTriggerUpload" ActionSubmissionTriggerValidateOperation OperationName = "ActionSubmissionTriggerValidate" ActionSubmissionValidatedOperation OperationName = "ActionSubmissionValidated" diff --git a/pkg/api/oas_parameters_gen.go b/pkg/api/oas_parameters_gen.go index b604dc9..f434a5f 100644 --- a/pkg/api/oas_parameters_gen.go +++ b/pkg/api/oas_parameters_gen.go @@ -264,6 +264,89 @@ func decodeActionMapfixRequestChangesParams(args [1]string, argsEscaped bool, r return params, nil } +// ActionMapfixResetSubmittingParams is parameters of actionMapfixResetSubmitting operation. +type ActionMapfixResetSubmittingParams struct { + // The unique identifier for a mapfix. + MapfixID int64 +} + +func unpackActionMapfixResetSubmittingParams(packed middleware.Parameters) (params ActionMapfixResetSubmittingParams) { + { + key := middleware.ParameterKey{ + Name: "MapfixID", + In: "path", + } + params.MapfixID = packed[key].(int64) + } + return params +} + +func decodeActionMapfixResetSubmittingParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionMapfixResetSubmittingParams, _ error) { + // Decode path: MapfixID. + if err := func() error { + param := args[0] + if argsEscaped { + unescaped, err := url.PathUnescape(args[0]) + if err != nil { + return errors.Wrap(err, "unescape path") + } + param = unescaped + } + if len(param) > 0 { + d := uri.NewPathDecoder(uri.PathDecoderConfig{ + Param: "MapfixID", + Value: param, + Style: uri.PathStyleSimple, + Explode: false, + }) + + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + params.MapfixID = c + return nil + }(); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.MapfixID)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "MapfixID", + In: "path", + Err: err, + } + } + return params, nil +} + // ActionMapfixRetryValidateParams is parameters of actionMapfixRetryValidate operation. type ActionMapfixRetryValidateParams struct { // The unique identifier for a mapfix. @@ -430,13 +513,13 @@ func decodeActionMapfixRevokeParams(args [1]string, argsEscaped bool, r *http.Re return params, nil } -// ActionMapfixSubmitParams is parameters of actionMapfixSubmit operation. -type ActionMapfixSubmitParams struct { +// ActionMapfixTriggerSubmitParams is parameters of actionMapfixTriggerSubmit operation. +type ActionMapfixTriggerSubmitParams struct { // The unique identifier for a mapfix. MapfixID int64 } -func unpackActionMapfixSubmitParams(packed middleware.Parameters) (params ActionMapfixSubmitParams) { +func unpackActionMapfixTriggerSubmitParams(packed middleware.Parameters) (params ActionMapfixTriggerSubmitParams) { { key := middleware.ParameterKey{ Name: "MapfixID", @@ -447,7 +530,7 @@ func unpackActionMapfixSubmitParams(packed middleware.Parameters) (params Action return params } -func decodeActionMapfixSubmitParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionMapfixSubmitParams, _ error) { +func decodeActionMapfixTriggerSubmitParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionMapfixTriggerSubmitParams, _ error) { // Decode path: MapfixID. if err := func() error { param := args[0] @@ -1011,6 +1094,89 @@ func decodeActionSubmissionRequestChangesParams(args [1]string, argsEscaped bool return params, nil } +// ActionSubmissionResetSubmittingParams is parameters of actionSubmissionResetSubmitting operation. +type ActionSubmissionResetSubmittingParams struct { + // The unique identifier for a submission. + SubmissionID int64 +} + +func unpackActionSubmissionResetSubmittingParams(packed middleware.Parameters) (params ActionSubmissionResetSubmittingParams) { + { + key := middleware.ParameterKey{ + Name: "SubmissionID", + In: "path", + } + params.SubmissionID = packed[key].(int64) + } + return params +} + +func decodeActionSubmissionResetSubmittingParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionSubmissionResetSubmittingParams, _ error) { + // Decode path: SubmissionID. + if err := func() error { + param := args[0] + if argsEscaped { + unescaped, err := url.PathUnescape(args[0]) + if err != nil { + return errors.Wrap(err, "unescape path") + } + param = unescaped + } + if len(param) > 0 { + d := uri.NewPathDecoder(uri.PathDecoderConfig{ + Param: "SubmissionID", + Value: param, + Style: uri.PathStyleSimple, + Explode: false, + }) + + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + params.SubmissionID = c + return nil + }(); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.SubmissionID)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "SubmissionID", + In: "path", + Err: err, + } + } + return params, nil +} + // ActionSubmissionRetryValidateParams is parameters of actionSubmissionRetryValidate operation. type ActionSubmissionRetryValidateParams struct { // The unique identifier for a submission. @@ -1177,13 +1343,13 @@ func decodeActionSubmissionRevokeParams(args [1]string, argsEscaped bool, r *htt return params, nil } -// ActionSubmissionSubmitParams is parameters of actionSubmissionSubmit operation. -type ActionSubmissionSubmitParams struct { +// ActionSubmissionTriggerSubmitParams is parameters of actionSubmissionTriggerSubmit operation. +type ActionSubmissionTriggerSubmitParams struct { // The unique identifier for a submission. SubmissionID int64 } -func unpackActionSubmissionSubmitParams(packed middleware.Parameters) (params ActionSubmissionSubmitParams) { +func unpackActionSubmissionTriggerSubmitParams(packed middleware.Parameters) (params ActionSubmissionTriggerSubmitParams) { { key := middleware.ParameterKey{ Name: "SubmissionID", @@ -1194,7 +1360,7 @@ func unpackActionSubmissionSubmitParams(packed middleware.Parameters) (params Ac return params } -func decodeActionSubmissionSubmitParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionSubmissionSubmitParams, _ error) { +func decodeActionSubmissionTriggerSubmitParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionSubmissionTriggerSubmitParams, _ error) { // Decode path: SubmissionID. if err := func() error { param := args[0] diff --git a/pkg/api/oas_response_decoders_gen.go b/pkg/api/oas_response_decoders_gen.go index 242b3b7..e6336d5 100644 --- a/pkg/api/oas_response_decoders_gen.go +++ b/pkg/api/oas_response_decoders_gen.go @@ -195,6 +195,66 @@ func decodeActionMapfixRequestChangesResponse(resp *http.Response) (res *ActionM return res, errors.Wrap(defRes, "error") } +func decodeActionMapfixResetSubmittingResponse(resp *http.Response) (res *ActionMapfixResetSubmittingNoContent, _ error) { + switch resp.StatusCode { + case 204: + // Code 204. + return &ActionMapfixResetSubmittingNoContent{}, nil + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + func decodeActionMapfixRetryValidateResponse(resp *http.Response) (res *ActionMapfixRetryValidateNoContent, _ error) { switch resp.StatusCode { case 204: @@ -315,11 +375,11 @@ func decodeActionMapfixRevokeResponse(resp *http.Response) (res *ActionMapfixRev return res, errors.Wrap(defRes, "error") } -func decodeActionMapfixSubmitResponse(resp *http.Response) (res *ActionMapfixSubmitNoContent, _ error) { +func decodeActionMapfixTriggerSubmitResponse(resp *http.Response) (res *ActionMapfixTriggerSubmitNoContent, _ error) { switch resp.StatusCode { case 204: // Code 204. - return &ActionMapfixSubmitNoContent{}, nil + return &ActionMapfixTriggerSubmitNoContent{}, nil } // Convenient error response. defRes, err := func() (res *ErrorStatusCode, err error) { @@ -735,6 +795,66 @@ func decodeActionSubmissionRequestChangesResponse(resp *http.Response) (res *Act return res, errors.Wrap(defRes, "error") } +func decodeActionSubmissionResetSubmittingResponse(resp *http.Response) (res *ActionSubmissionResetSubmittingNoContent, _ error) { + switch resp.StatusCode { + case 204: + // Code 204. + return &ActionSubmissionResetSubmittingNoContent{}, nil + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + func decodeActionSubmissionRetryValidateResponse(resp *http.Response) (res *ActionSubmissionRetryValidateNoContent, _ error) { switch resp.StatusCode { case 204: @@ -855,11 +975,11 @@ func decodeActionSubmissionRevokeResponse(resp *http.Response) (res *ActionSubmi return res, errors.Wrap(defRes, "error") } -func decodeActionSubmissionSubmitResponse(resp *http.Response) (res *ActionSubmissionSubmitNoContent, _ error) { +func decodeActionSubmissionTriggerSubmitResponse(resp *http.Response) (res *ActionSubmissionTriggerSubmitNoContent, _ error) { switch resp.StatusCode { case 204: // Code 204. - return &ActionSubmissionSubmitNoContent{}, nil + return &ActionSubmissionTriggerSubmitNoContent{}, nil } // Convenient error response. defRes, err := func() (res *ErrorStatusCode, err error) { diff --git a/pkg/api/oas_response_encoders_gen.go b/pkg/api/oas_response_encoders_gen.go index cb0a98a..33e5527 100644 --- a/pkg/api/oas_response_encoders_gen.go +++ b/pkg/api/oas_response_encoders_gen.go @@ -34,6 +34,13 @@ func encodeActionMapfixRequestChangesResponse(response *ActionMapfixRequestChang return nil } +func encodeActionMapfixResetSubmittingResponse(response *ActionMapfixResetSubmittingNoContent, w http.ResponseWriter, span trace.Span) error { + w.WriteHeader(204) + span.SetStatus(codes.Ok, http.StatusText(204)) + + return nil +} + func encodeActionMapfixRetryValidateResponse(response *ActionMapfixRetryValidateNoContent, w http.ResponseWriter, span trace.Span) error { w.WriteHeader(204) span.SetStatus(codes.Ok, http.StatusText(204)) @@ -48,7 +55,7 @@ func encodeActionMapfixRevokeResponse(response *ActionMapfixRevokeNoContent, w h return nil } -func encodeActionMapfixSubmitResponse(response *ActionMapfixSubmitNoContent, w http.ResponseWriter, span trace.Span) error { +func encodeActionMapfixTriggerSubmitResponse(response *ActionMapfixTriggerSubmitNoContent, w http.ResponseWriter, span trace.Span) error { w.WriteHeader(204) span.SetStatus(codes.Ok, http.StatusText(204)) @@ -97,6 +104,13 @@ func encodeActionSubmissionRequestChangesResponse(response *ActionSubmissionRequ return nil } +func encodeActionSubmissionResetSubmittingResponse(response *ActionSubmissionResetSubmittingNoContent, w http.ResponseWriter, span trace.Span) error { + w.WriteHeader(204) + span.SetStatus(codes.Ok, http.StatusText(204)) + + return nil +} + func encodeActionSubmissionRetryValidateResponse(response *ActionSubmissionRetryValidateNoContent, w http.ResponseWriter, span trace.Span) error { w.WriteHeader(204) span.SetStatus(codes.Ok, http.StatusText(204)) @@ -111,7 +125,7 @@ func encodeActionSubmissionRevokeResponse(response *ActionSubmissionRevokeNoCont return nil } -func encodeActionSubmissionSubmitResponse(response *ActionSubmissionSubmitNoContent, w http.ResponseWriter, span trace.Span) error { +func encodeActionSubmissionTriggerSubmitResponse(response *ActionSubmissionTriggerSubmitNoContent, w http.ResponseWriter, span trace.Span) error { w.WriteHeader(204) span.SetStatus(codes.Ok, http.StatusText(204)) diff --git a/pkg/api/oas_router_gen.go b/pkg/api/oas_router_gen.go index 6989865..4244ce2 100644 --- a/pkg/api/oas_router_gen.go +++ b/pkg/api/oas_router_gen.go @@ -318,6 +318,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { break } switch elem[0] { + case 's': // Prefix: "submitting" + + if l := len("submitting"); len(elem) >= l && elem[0:l] == "submitting" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionMapfixResetSubmittingRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + case 'u': // Prefix: "uploading" if l := len("uploading"); len(elem) >= l && elem[0:l] == "uploading" { @@ -410,28 +432,6 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } - case 's': // Prefix: "submit" - - if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleActionMapfixSubmitRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return - } - case 't': // Prefix: "trigger-" if l := len("trigger-"); len(elem) >= l && elem[0:l] == "trigger-" { @@ -444,6 +444,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { break } switch elem[0] { + case 's': // Prefix: "submit" + + if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionMapfixTriggerSubmitRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + case 'u': // Prefix: "upload" if l := len("upload"); len(elem) >= l && elem[0:l] == "upload" { @@ -1072,6 +1094,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { break } switch elem[0] { + case 's': // Prefix: "submitting" + + if l := len("submitting"); len(elem) >= l && elem[0:l] == "submitting" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionSubmissionResetSubmittingRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + case 'u': // Prefix: "uploading" if l := len("uploading"); len(elem) >= l && elem[0:l] == "uploading" { @@ -1164,28 +1208,6 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } - case 's': // Prefix: "submit" - - if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleActionSubmissionSubmitRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return - } - case 't': // Prefix: "trigger-" if l := len("trigger-"); len(elem) >= l && elem[0:l] == "trigger-" { @@ -1198,6 +1220,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { break } switch elem[0] { + case 's': // Prefix: "submit" + + if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionSubmissionTriggerSubmitRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + case 'u': // Prefix: "upload" if l := len("upload"); len(elem) >= l && elem[0:l] == "upload" { @@ -1629,6 +1673,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { break } switch elem[0] { + case 's': // Prefix: "submitting" + + if l := len("submitting"); len(elem) >= l && elem[0:l] == "submitting" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionMapfixResetSubmittingOperation + r.summary = "Role Submitter manually resets submitting softlock and changes status from Submitting -> UnderConstruction" + r.operationID = "actionMapfixResetSubmitting" + r.pathPattern = "/mapfixes/{MapfixID}/status/reset-submitting" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + case 'u': // Prefix: "uploading" if l := len("uploading"); len(elem) >= l && elem[0:l] == "uploading" { @@ -1729,30 +1797,6 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { } - case 's': // Prefix: "submit" - - if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch method { - case "POST": - r.name = ActionMapfixSubmitOperation - r.summary = "Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted" - r.operationID = "actionMapfixSubmit" - r.pathPattern = "/mapfixes/{MapfixID}/status/submit" - r.args = args - r.count = 1 - return r, true - default: - return - } - } - case 't': // Prefix: "trigger-" if l := len("trigger-"); len(elem) >= l && elem[0:l] == "trigger-" { @@ -1765,6 +1809,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { break } switch elem[0] { + case 's': // Prefix: "submit" + + if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionMapfixTriggerSubmitOperation + r.summary = "Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting" + r.operationID = "actionMapfixTriggerSubmit" + r.pathPattern = "/mapfixes/{MapfixID}/status/trigger-submit" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + case 'u': // Prefix: "upload" if l := len("upload"); len(elem) >= l && elem[0:l] == "upload" { @@ -2485,6 +2553,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { break } switch elem[0] { + case 's': // Prefix: "submitting" + + if l := len("submitting"); len(elem) >= l && elem[0:l] == "submitting" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionSubmissionResetSubmittingOperation + r.summary = "Role Submitter manually resets submitting softlock and changes status from Submitting -> UnderConstruction" + r.operationID = "actionSubmissionResetSubmitting" + r.pathPattern = "/submissions/{SubmissionID}/status/reset-submitting" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + case 'u': // Prefix: "uploading" if l := len("uploading"); len(elem) >= l && elem[0:l] == "uploading" { @@ -2585,30 +2677,6 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { } - case 's': // Prefix: "submit" - - if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch method { - case "POST": - r.name = ActionSubmissionSubmitOperation - r.summary = "Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted" - r.operationID = "actionSubmissionSubmit" - r.pathPattern = "/submissions/{SubmissionID}/status/submit" - r.args = args - r.count = 1 - return r, true - default: - return - } - } - case 't': // Prefix: "trigger-" if l := len("trigger-"); len(elem) >= l && elem[0:l] == "trigger-" { @@ -2621,6 +2689,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { break } switch elem[0] { + case 's': // Prefix: "submit" + + if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionSubmissionTriggerSubmitOperation + r.summary = "Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting" + r.operationID = "actionSubmissionTriggerSubmit" + r.pathPattern = "/submissions/{SubmissionID}/status/trigger-submit" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + case 'u': // Prefix: "upload" if l := len("upload"); len(elem) >= l && elem[0:l] == "upload" { diff --git a/pkg/api/oas_schemas_gen.go b/pkg/api/oas_schemas_gen.go index 5590292..b54f84e 100644 --- a/pkg/api/oas_schemas_gen.go +++ b/pkg/api/oas_schemas_gen.go @@ -23,14 +23,17 @@ type ActionMapfixRejectNoContent struct{} // ActionMapfixRequestChangesNoContent is response for ActionMapfixRequestChanges operation. type ActionMapfixRequestChangesNoContent struct{} +// ActionMapfixResetSubmittingNoContent is response for ActionMapfixResetSubmitting operation. +type ActionMapfixResetSubmittingNoContent struct{} + // ActionMapfixRetryValidateNoContent is response for ActionMapfixRetryValidate operation. type ActionMapfixRetryValidateNoContent struct{} // ActionMapfixRevokeNoContent is response for ActionMapfixRevoke operation. type ActionMapfixRevokeNoContent struct{} -// ActionMapfixSubmitNoContent is response for ActionMapfixSubmit operation. -type ActionMapfixSubmitNoContent struct{} +// ActionMapfixTriggerSubmitNoContent is response for ActionMapfixTriggerSubmit operation. +type ActionMapfixTriggerSubmitNoContent struct{} // ActionMapfixTriggerUploadNoContent is response for ActionMapfixTriggerUpload operation. type ActionMapfixTriggerUploadNoContent struct{} @@ -50,14 +53,17 @@ type ActionSubmissionRejectNoContent struct{} // ActionSubmissionRequestChangesNoContent is response for ActionSubmissionRequestChanges operation. type ActionSubmissionRequestChangesNoContent struct{} +// ActionSubmissionResetSubmittingNoContent is response for ActionSubmissionResetSubmitting operation. +type ActionSubmissionResetSubmittingNoContent struct{} + // ActionSubmissionRetryValidateNoContent is response for ActionSubmissionRetryValidate operation. type ActionSubmissionRetryValidateNoContent struct{} // ActionSubmissionRevokeNoContent is response for ActionSubmissionRevoke operation. type ActionSubmissionRevokeNoContent struct{} -// ActionSubmissionSubmitNoContent is response for ActionSubmissionSubmit operation. -type ActionSubmissionSubmitNoContent struct{} +// ActionSubmissionTriggerSubmitNoContent is response for ActionSubmissionTriggerSubmit operation. +type ActionSubmissionTriggerSubmitNoContent struct{} // ActionSubmissionTriggerUploadNoContent is response for ActionSubmissionTriggerUpload operation. type ActionSubmissionTriggerUploadNoContent struct{} diff --git a/pkg/api/oas_server_gen.go b/pkg/api/oas_server_gen.go index 0fddb0c..aa46501 100644 --- a/pkg/api/oas_server_gen.go +++ b/pkg/api/oas_server_gen.go @@ -26,6 +26,13 @@ type Handler interface { // // POST /mapfixes/{MapfixID}/status/request-changes ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error + // ActionMapfixResetSubmitting implements actionMapfixResetSubmitting operation. + // + // Role Submitter manually resets submitting softlock and changes status from Submitting -> + // UnderConstruction. + // + // POST /mapfixes/{MapfixID}/status/reset-submitting + ActionMapfixResetSubmitting(ctx context.Context, params ActionMapfixResetSubmittingParams) error // ActionMapfixRetryValidate implements actionMapfixRetryValidate operation. // // Role Reviewer re-runs validation and changes status from Accepted -> Validating. @@ -38,12 +45,12 @@ type Handler interface { // // POST /mapfixes/{MapfixID}/status/revoke ActionMapfixRevoke(ctx context.Context, params ActionMapfixRevokeParams) error - // ActionMapfixSubmit implements actionMapfixSubmit operation. + // ActionMapfixTriggerSubmit implements actionMapfixTriggerSubmit operation. // - // Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted. + // Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting. // - // POST /mapfixes/{MapfixID}/status/submit - ActionMapfixSubmit(ctx context.Context, params ActionMapfixSubmitParams) error + // POST /mapfixes/{MapfixID}/status/trigger-submit + ActionMapfixTriggerSubmit(ctx context.Context, params ActionMapfixTriggerSubmitParams) error // ActionMapfixTriggerUpload implements actionMapfixTriggerUpload operation. // // Role Admin changes status from Validated -> Uploading. @@ -80,6 +87,13 @@ type Handler interface { // // POST /submissions/{SubmissionID}/status/request-changes ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error + // ActionSubmissionResetSubmitting implements actionSubmissionResetSubmitting operation. + // + // Role Submitter manually resets submitting softlock and changes status from Submitting -> + // UnderConstruction. + // + // POST /submissions/{SubmissionID}/status/reset-submitting + ActionSubmissionResetSubmitting(ctx context.Context, params ActionSubmissionResetSubmittingParams) error // ActionSubmissionRetryValidate implements actionSubmissionRetryValidate operation. // // Role Reviewer re-runs validation and changes status from Accepted -> Validating. @@ -92,12 +106,12 @@ type Handler interface { // // POST /submissions/{SubmissionID}/status/revoke ActionSubmissionRevoke(ctx context.Context, params ActionSubmissionRevokeParams) error - // ActionSubmissionSubmit implements actionSubmissionSubmit operation. + // ActionSubmissionTriggerSubmit implements actionSubmissionTriggerSubmit operation. // - // Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted. + // Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting. // - // POST /submissions/{SubmissionID}/status/submit - ActionSubmissionSubmit(ctx context.Context, params ActionSubmissionSubmitParams) error + // POST /submissions/{SubmissionID}/status/trigger-submit + ActionSubmissionTriggerSubmit(ctx context.Context, params ActionSubmissionTriggerSubmitParams) error // ActionSubmissionTriggerUpload implements actionSubmissionTriggerUpload operation. // // Role Admin changes status from Validated -> Uploading. diff --git a/pkg/api/oas_unimplemented_gen.go b/pkg/api/oas_unimplemented_gen.go index cacd62c..239065f 100644 --- a/pkg/api/oas_unimplemented_gen.go +++ b/pkg/api/oas_unimplemented_gen.go @@ -40,6 +40,16 @@ func (UnimplementedHandler) ActionMapfixRequestChanges(ctx context.Context, para return ht.ErrNotImplemented } +// ActionMapfixResetSubmitting implements actionMapfixResetSubmitting operation. +// +// Role Submitter manually resets submitting softlock and changes status from Submitting -> +// UnderConstruction. +// +// POST /mapfixes/{MapfixID}/status/reset-submitting +func (UnimplementedHandler) ActionMapfixResetSubmitting(ctx context.Context, params ActionMapfixResetSubmittingParams) error { + return ht.ErrNotImplemented +} + // ActionMapfixRetryValidate implements actionMapfixRetryValidate operation. // // Role Reviewer re-runs validation and changes status from Accepted -> Validating. @@ -58,12 +68,12 @@ func (UnimplementedHandler) ActionMapfixRevoke(ctx context.Context, params Actio return ht.ErrNotImplemented } -// ActionMapfixSubmit implements actionMapfixSubmit operation. +// ActionMapfixTriggerSubmit implements actionMapfixTriggerSubmit operation. // -// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted. +// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting. // -// POST /mapfixes/{MapfixID}/status/submit -func (UnimplementedHandler) ActionMapfixSubmit(ctx context.Context, params ActionMapfixSubmitParams) error { +// POST /mapfixes/{MapfixID}/status/trigger-submit +func (UnimplementedHandler) ActionMapfixTriggerSubmit(ctx context.Context, params ActionMapfixTriggerSubmitParams) error { return ht.ErrNotImplemented } @@ -121,6 +131,16 @@ func (UnimplementedHandler) ActionSubmissionRequestChanges(ctx context.Context, return ht.ErrNotImplemented } +// ActionSubmissionResetSubmitting implements actionSubmissionResetSubmitting operation. +// +// Role Submitter manually resets submitting softlock and changes status from Submitting -> +// UnderConstruction. +// +// POST /submissions/{SubmissionID}/status/reset-submitting +func (UnimplementedHandler) ActionSubmissionResetSubmitting(ctx context.Context, params ActionSubmissionResetSubmittingParams) error { + return ht.ErrNotImplemented +} + // ActionSubmissionRetryValidate implements actionSubmissionRetryValidate operation. // // Role Reviewer re-runs validation and changes status from Accepted -> Validating. @@ -139,12 +159,12 @@ func (UnimplementedHandler) ActionSubmissionRevoke(ctx context.Context, params A return ht.ErrNotImplemented } -// ActionSubmissionSubmit implements actionSubmissionSubmit operation. +// ActionSubmissionTriggerSubmit implements actionSubmissionTriggerSubmit operation. // -// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted. +// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting. // -// POST /submissions/{SubmissionID}/status/submit -func (UnimplementedHandler) ActionSubmissionSubmit(ctx context.Context, params ActionSubmissionSubmitParams) error { +// POST /submissions/{SubmissionID}/status/trigger-submit +func (UnimplementedHandler) ActionSubmissionTriggerSubmit(ctx context.Context, params ActionSubmissionTriggerSubmitParams) error { return ht.ErrNotImplemented } diff --git a/pkg/internal/oas_client_gen.go b/pkg/internal/oas_client_gen.go index 0248a3f..3a33e68 100644 --- a/pkg/internal/oas_client_gen.go +++ b/pkg/internal/oas_client_gen.go @@ -34,6 +34,12 @@ type Invoker interface { // // POST /mapfixes/{MapfixID}/status/validator-failed ActionMapfixAccepted(ctx context.Context, params ActionMapfixAcceptedParams) error + // ActionMapfixSubmitted invokes actionMapfixSubmitted operation. + // + // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. + // + // POST /mapfixes/{MapfixID}/status/validator-submitted + ActionMapfixSubmitted(ctx context.Context, params ActionMapfixSubmittedParams) error // ActionMapfixUploaded invokes actionMapfixUploaded operation. // // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. @@ -58,6 +64,12 @@ type Invoker interface { // // POST /submissions/{SubmissionID}/status/validator-failed ActionSubmissionAccepted(ctx context.Context, params ActionSubmissionAcceptedParams) error + // ActionSubmissionSubmitted invokes actionSubmissionSubmitted operation. + // + // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. + // + // POST /submissions/{SubmissionID}/status/validator-submitted + ActionSubmissionSubmitted(ctx context.Context, params ActionSubmissionSubmittedParams) error // ActionSubmissionUploaded invokes actionSubmissionUploaded operation. // // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. @@ -282,6 +294,97 @@ func (c *Client) sendActionMapfixAccepted(ctx context.Context, params ActionMapf return result, nil } +// ActionMapfixSubmitted invokes actionMapfixSubmitted operation. +// +// (Internal endpoint) Role Validator changes status from Submitting -> Submitted. +// +// POST /mapfixes/{MapfixID}/status/validator-submitted +func (c *Client) ActionMapfixSubmitted(ctx context.Context, params ActionMapfixSubmittedParams) error { + _, err := c.sendActionMapfixSubmitted(ctx, params) + return err +} + +func (c *Client) sendActionMapfixSubmitted(ctx context.Context, params ActionMapfixSubmittedParams) (res *ActionMapfixSubmittedNoContent, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("actionMapfixSubmitted"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/status/validator-submitted"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, ActionMapfixSubmittedOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [3]string + pathParts[0] = "/mapfixes/" + { + // Encode "MapfixID" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "MapfixID", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.Int64ToString(params.MapfixID)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[1] = encoded + } + pathParts[2] = "/status/validator-submitted" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeActionMapfixSubmittedResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // ActionMapfixUploaded invokes actionMapfixUploaded operation. // // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. @@ -682,6 +785,97 @@ func (c *Client) sendActionSubmissionAccepted(ctx context.Context, params Action return result, nil } +// ActionSubmissionSubmitted invokes actionSubmissionSubmitted operation. +// +// (Internal endpoint) Role Validator changes status from Submitting -> Submitted. +// +// POST /submissions/{SubmissionID}/status/validator-submitted +func (c *Client) ActionSubmissionSubmitted(ctx context.Context, params ActionSubmissionSubmittedParams) error { + _, err := c.sendActionSubmissionSubmitted(ctx, params) + return err +} + +func (c *Client) sendActionSubmissionSubmitted(ctx context.Context, params ActionSubmissionSubmittedParams) (res *ActionSubmissionSubmittedNoContent, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("actionSubmissionSubmitted"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/validator-submitted"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, ActionSubmissionSubmittedOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [3]string + pathParts[0] = "/submissions/" + { + // Encode "SubmissionID" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "SubmissionID", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.Int64ToString(params.SubmissionID)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[1] = encoded + } + pathParts[2] = "/status/validator-submitted" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeActionSubmissionSubmittedResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // ActionSubmissionUploaded invokes actionSubmissionUploaded operation. // // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. diff --git a/pkg/internal/oas_handlers_gen.go b/pkg/internal/oas_handlers_gen.go index b382980..a205b0e 100644 --- a/pkg/internal/oas_handlers_gen.go +++ b/pkg/internal/oas_handlers_gen.go @@ -183,6 +183,155 @@ func (s *Server) handleActionMapfixAcceptedRequest(args [1]string, argsEscaped b } } +// handleActionMapfixSubmittedRequest handles actionMapfixSubmitted operation. +// +// (Internal endpoint) Role Validator changes status from Submitting -> Submitted. +// +// POST /mapfixes/{MapfixID}/status/validator-submitted +func (s *Server) handleActionMapfixSubmittedRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("actionMapfixSubmitted"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/status/validator-submitted"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), ActionMapfixSubmittedOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: ActionMapfixSubmittedOperation, + ID: "actionMapfixSubmitted", + } + ) + params, err := decodeActionMapfixSubmittedParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response *ActionMapfixSubmittedNoContent + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: ActionMapfixSubmittedOperation, + OperationSummary: "(Internal endpoint) Role Validator changes status from Submitting -> Submitted", + OperationID: "actionMapfixSubmitted", + Body: nil, + Params: middleware.Parameters{ + { + Name: "MapfixID", + In: "path", + }: params.MapfixID, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = ActionMapfixSubmittedParams + Response = *ActionMapfixSubmittedNoContent + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackActionMapfixSubmittedParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + err = s.h.ActionMapfixSubmitted(ctx, params) + return response, err + }, + ) + } else { + err = s.h.ActionMapfixSubmitted(ctx, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeActionMapfixSubmittedResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleActionMapfixUploadedRequest handles actionMapfixUploaded operation. // // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. @@ -787,6 +936,155 @@ func (s *Server) handleActionSubmissionAcceptedRequest(args [1]string, argsEscap } } +// handleActionSubmissionSubmittedRequest handles actionSubmissionSubmitted operation. +// +// (Internal endpoint) Role Validator changes status from Submitting -> Submitted. +// +// POST /submissions/{SubmissionID}/status/validator-submitted +func (s *Server) handleActionSubmissionSubmittedRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("actionSubmissionSubmitted"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/validator-submitted"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), ActionSubmissionSubmittedOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: ActionSubmissionSubmittedOperation, + ID: "actionSubmissionSubmitted", + } + ) + params, err := decodeActionSubmissionSubmittedParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response *ActionSubmissionSubmittedNoContent + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: ActionSubmissionSubmittedOperation, + OperationSummary: "(Internal endpoint) Role Validator changes status from Submitting -> Submitted", + OperationID: "actionSubmissionSubmitted", + Body: nil, + Params: middleware.Parameters{ + { + Name: "SubmissionID", + In: "path", + }: params.SubmissionID, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = ActionSubmissionSubmittedParams + Response = *ActionSubmissionSubmittedNoContent + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackActionSubmissionSubmittedParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + err = s.h.ActionSubmissionSubmitted(ctx, params) + return response, err + }, + ) + } else { + err = s.h.ActionSubmissionSubmitted(ctx, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeActionSubmissionSubmittedResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleActionSubmissionUploadedRequest handles actionSubmissionUploaded operation. // // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. diff --git a/pkg/internal/oas_operations_gen.go b/pkg/internal/oas_operations_gen.go index 9e4ecd3..cc5e6af 100644 --- a/pkg/internal/oas_operations_gen.go +++ b/pkg/internal/oas_operations_gen.go @@ -7,10 +7,12 @@ type OperationName = string const ( ActionMapfixAcceptedOperation OperationName = "ActionMapfixAccepted" + ActionMapfixSubmittedOperation OperationName = "ActionMapfixSubmitted" ActionMapfixUploadedOperation OperationName = "ActionMapfixUploaded" ActionMapfixValidatedOperation OperationName = "ActionMapfixValidated" ActionOperationFailedOperation OperationName = "ActionOperationFailed" ActionSubmissionAcceptedOperation OperationName = "ActionSubmissionAccepted" + ActionSubmissionSubmittedOperation OperationName = "ActionSubmissionSubmitted" ActionSubmissionUploadedOperation OperationName = "ActionSubmissionUploaded" ActionSubmissionValidatedOperation OperationName = "ActionSubmissionValidated" CreateMapfixOperation OperationName = "CreateMapfix" diff --git a/pkg/internal/oas_parameters_gen.go b/pkg/internal/oas_parameters_gen.go index 0832e8f..f564af1 100644 --- a/pkg/internal/oas_parameters_gen.go +++ b/pkg/internal/oas_parameters_gen.go @@ -159,6 +159,89 @@ func decodeActionMapfixAcceptedParams(args [1]string, argsEscaped bool, r *http. return params, nil } +// ActionMapfixSubmittedParams is parameters of actionMapfixSubmitted operation. +type ActionMapfixSubmittedParams struct { + // The unique identifier for a submission. + MapfixID int64 +} + +func unpackActionMapfixSubmittedParams(packed middleware.Parameters) (params ActionMapfixSubmittedParams) { + { + key := middleware.ParameterKey{ + Name: "MapfixID", + In: "path", + } + params.MapfixID = packed[key].(int64) + } + return params +} + +func decodeActionMapfixSubmittedParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionMapfixSubmittedParams, _ error) { + // Decode path: MapfixID. + if err := func() error { + param := args[0] + if argsEscaped { + unescaped, err := url.PathUnescape(args[0]) + if err != nil { + return errors.Wrap(err, "unescape path") + } + param = unescaped + } + if len(param) > 0 { + d := uri.NewPathDecoder(uri.PathDecoderConfig{ + Param: "MapfixID", + Value: param, + Style: uri.PathStyleSimple, + Explode: false, + }) + + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + params.MapfixID = c + return nil + }(); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.MapfixID)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "MapfixID", + In: "path", + Err: err, + } + } + return params, nil +} + // ActionMapfixUploadedParams is parameters of actionMapfixUploaded operation. type ActionMapfixUploadedParams struct { // The unique identifier for a submission. @@ -613,6 +696,89 @@ func decodeActionSubmissionAcceptedParams(args [1]string, argsEscaped bool, r *h return params, nil } +// ActionSubmissionSubmittedParams is parameters of actionSubmissionSubmitted operation. +type ActionSubmissionSubmittedParams struct { + // The unique identifier for a submission. + SubmissionID int64 +} + +func unpackActionSubmissionSubmittedParams(packed middleware.Parameters) (params ActionSubmissionSubmittedParams) { + { + key := middleware.ParameterKey{ + Name: "SubmissionID", + In: "path", + } + params.SubmissionID = packed[key].(int64) + } + return params +} + +func decodeActionSubmissionSubmittedParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionSubmissionSubmittedParams, _ error) { + // Decode path: SubmissionID. + if err := func() error { + param := args[0] + if argsEscaped { + unescaped, err := url.PathUnescape(args[0]) + if err != nil { + return errors.Wrap(err, "unescape path") + } + param = unescaped + } + if len(param) > 0 { + d := uri.NewPathDecoder(uri.PathDecoderConfig{ + Param: "SubmissionID", + Value: param, + Style: uri.PathStyleSimple, + Explode: false, + }) + + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + params.SubmissionID = c + return nil + }(); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.SubmissionID)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "SubmissionID", + In: "path", + Err: err, + } + } + return params, nil +} + // ActionSubmissionUploadedParams is parameters of actionSubmissionUploaded operation. type ActionSubmissionUploadedParams struct { // The unique identifier for a submission. diff --git a/pkg/internal/oas_response_decoders_gen.go b/pkg/internal/oas_response_decoders_gen.go index d86dfef..8b7f1da 100644 --- a/pkg/internal/oas_response_decoders_gen.go +++ b/pkg/internal/oas_response_decoders_gen.go @@ -75,6 +75,66 @@ func decodeActionMapfixAcceptedResponse(resp *http.Response) (res *ActionMapfixA return res, errors.Wrap(defRes, "error") } +func decodeActionMapfixSubmittedResponse(resp *http.Response) (res *ActionMapfixSubmittedNoContent, _ error) { + switch resp.StatusCode { + case 204: + // Code 204. + return &ActionMapfixSubmittedNoContent{}, nil + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + func decodeActionMapfixUploadedResponse(resp *http.Response) (res *ActionMapfixUploadedNoContent, _ error) { switch resp.StatusCode { case 204: @@ -315,6 +375,66 @@ func decodeActionSubmissionAcceptedResponse(resp *http.Response) (res *ActionSub return res, errors.Wrap(defRes, "error") } +func decodeActionSubmissionSubmittedResponse(resp *http.Response) (res *ActionSubmissionSubmittedNoContent, _ error) { + switch resp.StatusCode { + case 204: + // Code 204. + return &ActionSubmissionSubmittedNoContent{}, nil + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + func decodeActionSubmissionUploadedResponse(resp *http.Response) (res *ActionSubmissionUploadedNoContent, _ error) { switch resp.StatusCode { case 204: diff --git a/pkg/internal/oas_response_encoders_gen.go b/pkg/internal/oas_response_encoders_gen.go index 03bd236..38bfb96 100644 --- a/pkg/internal/oas_response_encoders_gen.go +++ b/pkg/internal/oas_response_encoders_gen.go @@ -20,6 +20,13 @@ func encodeActionMapfixAcceptedResponse(response *ActionMapfixAcceptedNoContent, return nil } +func encodeActionMapfixSubmittedResponse(response *ActionMapfixSubmittedNoContent, w http.ResponseWriter, span trace.Span) error { + w.WriteHeader(204) + span.SetStatus(codes.Ok, http.StatusText(204)) + + return nil +} + func encodeActionMapfixUploadedResponse(response *ActionMapfixUploadedNoContent, w http.ResponseWriter, span trace.Span) error { w.WriteHeader(204) span.SetStatus(codes.Ok, http.StatusText(204)) @@ -48,6 +55,13 @@ func encodeActionSubmissionAcceptedResponse(response *ActionSubmissionAcceptedNo return nil } +func encodeActionSubmissionSubmittedResponse(response *ActionSubmissionSubmittedNoContent, w http.ResponseWriter, span trace.Span) error { + w.WriteHeader(204) + span.SetStatus(codes.Ok, http.StatusText(204)) + + return nil +} + func encodeActionSubmissionUploadedResponse(response *ActionSubmissionUploadedNoContent, w http.ResponseWriter, span trace.Span) error { w.WriteHeader(204) span.SetStatus(codes.Ok, http.StatusText(204)) diff --git a/pkg/internal/oas_router_gen.go b/pkg/internal/oas_router_gen.go index ecc609b..3e9fba8 100644 --- a/pkg/internal/oas_router_gen.go +++ b/pkg/internal/oas_router_gen.go @@ -147,6 +147,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + case 's': // Prefix: "submitted" + + if l := len("submitted"); len(elem) >= l && elem[0:l] == "submitted" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionMapfixSubmittedRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + case 'u': // Prefix: "uploaded" if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" { @@ -454,6 +476,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + case 's': // Prefix: "submitted" + + if l := len("submitted"); len(elem) >= l && elem[0:l] == "submitted" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionSubmissionSubmittedRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + case 'u': // Prefix: "uploaded" if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" { @@ -716,6 +760,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { } } + case 's': // Prefix: "submitted" + + if l := len("submitted"); len(elem) >= l && elem[0:l] == "submitted" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionMapfixSubmittedOperation + r.summary = "(Internal endpoint) Role Validator changes status from Submitting -> Submitted" + r.operationID = "actionMapfixSubmitted" + r.pathPattern = "/mapfixes/{MapfixID}/status/validator-submitted" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + case 'u': // Prefix: "uploaded" if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" { @@ -1059,6 +1127,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { } } + case 's': // Prefix: "submitted" + + if l := len("submitted"); len(elem) >= l && elem[0:l] == "submitted" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionSubmissionSubmittedOperation + r.summary = "(Internal endpoint) Role Validator changes status from Submitting -> Submitted" + r.operationID = "actionSubmissionSubmitted" + r.pathPattern = "/submissions/{SubmissionID}/status/validator-submitted" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + case 'u': // Prefix: "uploaded" if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" { diff --git a/pkg/internal/oas_schemas_gen.go b/pkg/internal/oas_schemas_gen.go index 1fecc20..e68e85a 100644 --- a/pkg/internal/oas_schemas_gen.go +++ b/pkg/internal/oas_schemas_gen.go @@ -13,6 +13,9 @@ func (s *ErrorStatusCode) Error() string { // ActionMapfixAcceptedNoContent is response for ActionMapfixAccepted operation. type ActionMapfixAcceptedNoContent struct{} +// ActionMapfixSubmittedNoContent is response for ActionMapfixSubmitted operation. +type ActionMapfixSubmittedNoContent struct{} + // ActionMapfixUploadedNoContent is response for ActionMapfixUploaded operation. type ActionMapfixUploadedNoContent struct{} @@ -25,6 +28,9 @@ type ActionOperationFailedNoContent struct{} // ActionSubmissionAcceptedNoContent is response for ActionSubmissionAccepted operation. type ActionSubmissionAcceptedNoContent struct{} +// ActionSubmissionSubmittedNoContent is response for ActionSubmissionSubmitted operation. +type ActionSubmissionSubmittedNoContent struct{} + // ActionSubmissionUploadedNoContent is response for ActionSubmissionUploaded operation. type ActionSubmissionUploadedNoContent struct{} diff --git a/pkg/internal/oas_server_gen.go b/pkg/internal/oas_server_gen.go index 4fd886c..94c8192 100644 --- a/pkg/internal/oas_server_gen.go +++ b/pkg/internal/oas_server_gen.go @@ -14,6 +14,12 @@ type Handler interface { // // POST /mapfixes/{MapfixID}/status/validator-failed ActionMapfixAccepted(ctx context.Context, params ActionMapfixAcceptedParams) error + // ActionMapfixSubmitted implements actionMapfixSubmitted operation. + // + // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. + // + // POST /mapfixes/{MapfixID}/status/validator-submitted + ActionMapfixSubmitted(ctx context.Context, params ActionMapfixSubmittedParams) error // ActionMapfixUploaded implements actionMapfixUploaded operation. // // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. @@ -38,6 +44,12 @@ type Handler interface { // // POST /submissions/{SubmissionID}/status/validator-failed ActionSubmissionAccepted(ctx context.Context, params ActionSubmissionAcceptedParams) error + // ActionSubmissionSubmitted implements actionSubmissionSubmitted operation. + // + // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. + // + // POST /submissions/{SubmissionID}/status/validator-submitted + ActionSubmissionSubmitted(ctx context.Context, params ActionSubmissionSubmittedParams) error // ActionSubmissionUploaded implements actionSubmissionUploaded operation. // // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. diff --git a/pkg/internal/oas_unimplemented_gen.go b/pkg/internal/oas_unimplemented_gen.go index e231989..7baa6e9 100644 --- a/pkg/internal/oas_unimplemented_gen.go +++ b/pkg/internal/oas_unimplemented_gen.go @@ -22,6 +22,15 @@ func (UnimplementedHandler) ActionMapfixAccepted(ctx context.Context, params Act return ht.ErrNotImplemented } +// ActionMapfixSubmitted implements actionMapfixSubmitted operation. +// +// (Internal endpoint) Role Validator changes status from Submitting -> Submitted. +// +// POST /mapfixes/{MapfixID}/status/validator-submitted +func (UnimplementedHandler) ActionMapfixSubmitted(ctx context.Context, params ActionMapfixSubmittedParams) error { + return ht.ErrNotImplemented +} + // ActionMapfixUploaded implements actionMapfixUploaded operation. // // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. @@ -58,6 +67,15 @@ func (UnimplementedHandler) ActionSubmissionAccepted(ctx context.Context, params return ht.ErrNotImplemented } +// ActionSubmissionSubmitted implements actionSubmissionSubmitted operation. +// +// (Internal endpoint) Role Validator changes status from Submitting -> Submitted. +// +// POST /submissions/{SubmissionID}/status/validator-submitted +func (UnimplementedHandler) ActionSubmissionSubmitted(ctx context.Context, params ActionSubmissionSubmittedParams) error { + return ht.ErrNotImplemented +} + // ActionSubmissionUploaded implements actionSubmissionUploaded operation. // // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. -- 2.49.1 From 0dc7aec3954b2f3a2e7cd004dcfa446b71391743 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 8 Apr 2025 13:12:13 -0700 Subject: [PATCH 358/454] submissions: rename endpoints --- pkg/service/mapfixes.go | 8 ++++---- pkg/service/submissions.go | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/service/mapfixes.go b/pkg/service/mapfixes.go index 2b17ea0..1f4d25f 100644 --- a/pkg/service/mapfixes.go +++ b/pkg/service/mapfixes.go @@ -461,12 +461,12 @@ func (svc *Service) ActionMapfixRevoke(ctx context.Context, params api.ActionMap return nil } -// ActionMapfixSubmit invokes actionMapfixSubmit operation. +// ActionMapfixTriggerSubmit invokes actionMapfixTriggerSubmit operation. // -// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted. +// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting. // -// POST /mapfixes/{MapfixID}/status/submit -func (svc *Service) ActionMapfixSubmit(ctx context.Context, params api.ActionMapfixSubmitParams) error { +// POST /mapfixes/{MapfixID}/status/trigger-submit +func (svc *Service) ActionMapfixTriggerSubmit(ctx context.Context, params api.ActionMapfixTriggerSubmitParams) error { userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) if !ok { return ErrUserInfo diff --git a/pkg/service/submissions.go b/pkg/service/submissions.go index e15003d..8e3abd6 100644 --- a/pkg/service/submissions.go +++ b/pkg/service/submissions.go @@ -482,12 +482,12 @@ func (svc *Service) ActionSubmissionRevoke(ctx context.Context, params api.Actio return nil } -// ActionSubmissionSubmit invokes actionSubmissionSubmit operation. +// ActionSubmissionTriggerSubmit invokes actionSubmissionTriggerSubmit operation. // -// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted. +// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting. // -// POST /submissions/{SubmissionID}/status/submit -func (svc *Service) ActionSubmissionSubmit(ctx context.Context, params api.ActionSubmissionSubmitParams) error { +// POST /submissions/{SubmissionID}/status/trigger-submit +func (svc *Service) ActionSubmissionTriggerSubmit(ctx context.Context, params api.ActionSubmissionTriggerSubmitParams) error { userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) if !ok { return ErrUserInfo -- 2.49.1 From d6da6f003e5bd9c2bac8a61e6514889aa336676b Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 8 Apr 2025 13:17:39 -0700 Subject: [PATCH 359/454] submissions: implement reset-submitting endpoint --- pkg/service/mapfixes.go | 66 ++++++++++++++++++++++++++++++++++++++ pkg/service/submissions.go | 66 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/pkg/service/mapfixes.go b/pkg/service/mapfixes.go index 1f4d25f..b9ff7d7 100644 --- a/pkg/service/mapfixes.go +++ b/pkg/service/mapfixes.go @@ -522,6 +522,72 @@ func (svc *Service) ActionMapfixTriggerSubmit(ctx context.Context, params api.Ac return nil } +// ActionMapfixResetSubmitting implements actionMapfixResetSubmitting operation. +// +// Role MapfixReview changes status from Submitting -> UnderConstruction. +// +// POST /mapfixes/{MapfixID}/status/reset-submitting +func (svc *Service) ActionMapfixResetSubmitting(ctx context.Context, params api.ActionMapfixResetSubmittingParams) error { + userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) + if !ok { + return ErrUserInfo + } + + userId, err := userInfo.GetUserID() + if err != nil { + return err + } + + // check when mapfix was updated + mapfix, err := svc.DB.Mapfixes().Get(ctx, params.MapfixID) + if err != nil { + return err + } + if time.Now().Before(mapfix.UpdatedAt.Add(time.Second*10)) { + // the last time the mapfix was updated must be longer than 10 seconds ago + return ErrDelayReset + } + + // check if caller has required role + has_role := userId == mapfix.Submitter + if !has_role { + return ErrPermissionDeniedNotSubmitter + } + + // transaction + target_status := model.MapfixStatusUnderConstruction + smap := datastore.Optional() + smap.Add("status_id", target_status) + smap.Add("status_message", "Manually forced reset") + err = svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitting}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceMapfix, + ResourceID: params.MapfixID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil +} + // ActionMapfixTriggerUpload invokes actionMapfixTriggerUpload operation. // // Role Admin changes status from Validated -> Uploading. diff --git a/pkg/service/submissions.go b/pkg/service/submissions.go index 8e3abd6..c81c24a 100644 --- a/pkg/service/submissions.go +++ b/pkg/service/submissions.go @@ -543,6 +543,72 @@ func (svc *Service) ActionSubmissionTriggerSubmit(ctx context.Context, params ap return nil } +// ActionSubmissionResetSubmitting implements actionSubmissionResetSubmitting operation. +// +// Role SubmissionReview changes status from Submitting -> UnderConstruction. +// +// POST /submissions/{SubmissionID}/status/reset-submitting +func (svc *Service) ActionSubmissionResetSubmitting(ctx context.Context, params api.ActionSubmissionResetSubmittingParams) error { + userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) + if !ok { + return ErrUserInfo + } + + userId, err := userInfo.GetUserID() + if err != nil { + return err + } + + // check when submission was updated + submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID) + if err != nil { + return err + } + if time.Now().Before(submission.UpdatedAt.Add(time.Second*10)) { + // the last time the submission was updated must be longer than 10 seconds ago + return ErrDelayReset + } + + // check if caller has required role + has_role := userId == submission.Submitter + if !has_role { + return ErrPermissionDeniedNotSubmitter + } + + // transaction + target_status := model.SubmissionStatusUnderConstruction + smap := datastore.Optional() + smap.Add("status_id", target_status) + smap.Add("status_message", "Manually forced reset") + err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitting}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: model.ResourceSubmission, + ResourceID: params.SubmissionID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil +} + // ActionSubmissionTriggerUpload invokes actionSubmissionTriggerUpload operation. // // Role Admin changes status from Validated -> Uploading. -- 2.49.1 From c923a8a076a6693ab7fc59b5e177427e1145a9d6 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 8 Apr 2025 13:21:07 -0700 Subject: [PATCH 360/454] submissions: implement validator-submitted endpoint --- pkg/service_internal/mapfixes.go | 39 +++++++++++++++++++++++++++++ pkg/service_internal/submissions.go | 39 +++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/pkg/service_internal/mapfixes.go b/pkg/service_internal/mapfixes.go index 5526b20..0827424 100644 --- a/pkg/service_internal/mapfixes.go +++ b/pkg/service_internal/mapfixes.go @@ -74,6 +74,45 @@ func (svc *Service) UpdateMapfixValidatedModel(ctx context.Context, params inter return nil } +// ActionMapfixValidate invokes actionMapfixValidate operation. +// +// Role Validator changes status from Submitting -> Submitted. +// +// POST /mapfixes/{MapfixID}/status/validator-submitted +func (svc *Service) ActionMapfixSubmitted(ctx context.Context, params internal.ActionMapfixSubmittedParams) error { + // transaction + target_status := model.MapfixStatusSubmitted + smap := datastore.Optional() + smap.Add("status_id", target_status) + err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitting}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: ValidtorUserID, + ResourceType: model.ResourceMapfix, + ResourceID: params.MapfixID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil +} + // ActionMapfixValidate invokes actionMapfixValidate operation. // // Role Validator changes status from Validating -> Validated. diff --git a/pkg/service_internal/submissions.go b/pkg/service_internal/submissions.go index 164eb60..3250ca7 100644 --- a/pkg/service_internal/submissions.go +++ b/pkg/service_internal/submissions.go @@ -73,6 +73,45 @@ func (svc *Service) UpdateSubmissionValidatedModel(ctx context.Context, params i return nil } +// ActionSubmissionValidate invokes actionSubmissionValidate operation. +// +// Role Validator changes status from Submitting -> Submitted. +// +// POST /submissions/{SubmissionID}/status/validator-submitted +func (svc *Service) ActionSubmissionSubmitted(ctx context.Context, params internal.ActionSubmissionSubmittedParams) error { + // transaction + target_status := model.SubmissionStatusSubmitted + smap := datastore.Optional() + smap.Add("status_id", target_status) + err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitting}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: ValidtorUserID, + ResourceType: model.ResourceSubmission, + ResourceID: params.SubmissionID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil +} + // ActionSubmissionValidate invokes actionSubmissionValidate operation. // // Role Validator changes status from Validating -> Validated. -- 2.49.1 From 18abbd92ce1edccb33b254cafc6c241d9abdeff1 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 8 Apr 2025 13:42:12 -0700 Subject: [PATCH 361/454] web: implement trigger-submit + transpose weakly associated action list --- .../mapfixes/[mapfixId]/_reviewButtons.tsx | 46 ++++++++++++------- .../[submissionId]/_reviewButtons.tsx | 46 ++++++++++++------- 2 files changed, 58 insertions(+), 34 deletions(-) diff --git a/web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx b/web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx index 008fc11..c9138c0 100644 --- a/web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx +++ b/web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx @@ -3,13 +3,25 @@ import { MapfixStatus } from "@/app/ts/Mapfix"; import { Button, ButtonOwnProps } from "@mui/material"; import { useState, useEffect } from "react"; -type Actions = "Completed" | "Submit" | "Reject" | "Revoke" -type ApiActions = Lowercase | "request-changes" | "trigger-validate" | "retry-validate" | "trigger-upload" | "reset-uploading" | "reset-validating" -type Review = Actions | "Request Changes" | "Accept" | "Validate" | "Upload" | "Reset Uploading (fix softlocked status)" | "Reset Validating (fix softlocked status)" | "Request Changes" +interface ReviewAction { + name: string, + action: string, +} + +const ReviewActions = { + Submit: {name:"Submit",action:"trigger-submit"} as ReviewAction, + Revoke: {name:"Revoke",action:"revoke"} as ReviewAction, + Accept: {name:"Accept",action:"trigger-validate"} as ReviewAction, + Reject: {name:"Reject",action:"reject"} as ReviewAction, + Validate: {name:"Validate",action:"retry-validate"} as ReviewAction, + ResetValidating: {name:"Reset Validating (fix softlocked status)",action:"reset-validating"} as ReviewAction, + RequestChanges: {name:"Request Changes",action:"request-changes"} as ReviewAction, + Upload: {name:"Upload",action:"trigger-upload"} as ReviewAction, + ResetUploading: {name:"Reset Uploading (fix softlocked status)",action:"reset-uploading"} as ReviewAction, +} interface ReviewButton { - name: Review, - action: ApiActions, + action: ReviewAction, mapfixId: string, color: ButtonOwnProps["color"] } @@ -20,7 +32,7 @@ interface ReviewId { mapfixSubmitter: number, } -async function ReviewButtonClicked(action: ApiActions, mapfixId: string) { +async function ReviewButtonClicked(action: string, mapfixId: string) { try { const response = await fetch(`/api/mapfixes/${mapfixId}/status/${action}`, { method: "POST", @@ -46,7 +58,7 @@ function ReviewButton(props: ReviewButton) { return + onClick={() => { ReviewButtonClicked(props.action.action, props.mapfixId) }}>{props.action.name} } export default function ReviewButtons(props: ReviewId) { @@ -95,10 +107,10 @@ export default function ReviewButtons(props: ReviewId) { const is_submitter = user === props.mapfixSubmitter; if (is_submitter) { if ([MapfixStatus.UnderConstruction, MapfixStatus.ChangesRequested].includes(mapfixStatus!)) { - visibleButtons.push({ name: "Submit", action: "submit", color: "info", mapfixId }); + visibleButtons.push({ action: ReviewActions.Submit, color: "info", mapfixId }); } if ([MapfixStatus.Submitted, MapfixStatus.ChangesRequested].includes(mapfixStatus!)) { - visibleButtons.push({ name: "Revoke", action: "revoke", color: "info", mapfixId }); + visibleButtons.push({ action: ReviewActions.Revoke, color: "info", mapfixId }); } } @@ -106,14 +118,14 @@ export default function ReviewButtons(props: ReviewId) { // you can't review your own mapfix! // note that this means there needs to be more than one person with MapfixReview if (!is_submitter && mapfixStatus === MapfixStatus.Submitted) { - visibleButtons.push({ name: "Accept", action: "trigger-validate", color: "info", mapfixId }); - visibleButtons.push({ name: "Reject", action: "reject", color: "error", mapfixId }); + visibleButtons.push({ action: ReviewActions.Accept, color: "info", mapfixId }); + visibleButtons.push({ action: ReviewActions.Reject, color: "error", mapfixId }); } if (mapfixStatus === MapfixStatus.AcceptedUnvalidated) { - visibleButtons.push({ name: "Validate", action: "retry-validate", color: "info", mapfixId }); + visibleButtons.push({ action: ReviewActions.Validate, color: "info", mapfixId }); } if (mapfixStatus === MapfixStatus.Validating) { - visibleButtons.push({ name: "Reset Validating (fix softlocked status)", action: "reset-validating", color: "error", mapfixId }); + visibleButtons.push({ action: ReviewActions.ResetValidating, color: "error", mapfixId }); } // this button serves the same purpose as Revoke if you are both // the map submitter and have MapfixReview when status is Submitted @@ -121,16 +133,16 @@ export default function ReviewButtons(props: ReviewId) { [MapfixStatus.Validated, MapfixStatus.AcceptedUnvalidated].includes(mapfixStatus!) || !is_submitter && mapfixStatus == MapfixStatus.Submitted ) { - visibleButtons.push({ name: "Request Changes", action: "request-changes", color: "error", mapfixId }); + visibleButtons.push({ action: ReviewActions.RequestChanges, color: "error", mapfixId }); } } if (roles&RolesConstants.MapfixUpload) { if (mapfixStatus === MapfixStatus.Validated) { - visibleButtons.push({ name: "Upload", action: "trigger-upload", color: "info", mapfixId }); + visibleButtons.push({ action: ReviewActions.Upload, color: "info", mapfixId }); } if (mapfixStatus === MapfixStatus.Uploading) { - visibleButtons.push({ name: "Reset Uploading (fix softlocked status)", action: "reset-uploading", color: "error", mapfixId }); + visibleButtons.push({ action: ReviewActions.ResetUploading, color: "error", mapfixId }); } } @@ -140,7 +152,7 @@ export default function ReviewButtons(props: ReviewId) {

No available actions

) : ( visibleButtons.map((btn) => ( - + )) )} diff --git a/web/src/app/submissions/[submissionId]/_reviewButtons.tsx b/web/src/app/submissions/[submissionId]/_reviewButtons.tsx index 345d435..aa68897 100644 --- a/web/src/app/submissions/[submissionId]/_reviewButtons.tsx +++ b/web/src/app/submissions/[submissionId]/_reviewButtons.tsx @@ -3,13 +3,25 @@ import { SubmissionStatus } from "@/app/ts/Submission"; import { Button, ButtonOwnProps } from "@mui/material"; import { useState, useEffect } from "react"; -type Actions = "Completed" | "Submit" | "Reject" | "Revoke" -type ApiActions = Lowercase | "request-changes" | "trigger-validate" | "retry-validate" | "trigger-upload" | "reset-uploading" | "reset-validating" -type Review = Actions | "Request Changes" | "Accept" | "Validate" | "Upload" | "Reset Uploading (fix softlocked status)" | "Reset Validating (fix softlocked status)" | "Request Changes" +interface ReviewAction { + name: string, + action: string, +} + +const ReviewActions = { + Submit: {name:"Submit",action:"trigger-submit"} as ReviewAction, + Revoke: {name:"Revoke",action:"revoke"} as ReviewAction, + Accept: {name:"Accept",action:"trigger-validate"} as ReviewAction, + Reject: {name:"Reject",action:"reject"} as ReviewAction, + Validate: {name:"Validate",action:"retry-validate"} as ReviewAction, + ResetValidating: {name:"Reset Validating (fix softlocked status)",action:"reset-validating"} as ReviewAction, + RequestChanges: {name:"Request Changes",action:"request-changes"} as ReviewAction, + Upload: {name:"Upload",action:"trigger-upload"} as ReviewAction, + ResetUploading: {name:"Reset Uploading (fix softlocked status)",action:"reset-uploading"} as ReviewAction, +} interface ReviewButton { - name: Review, - action: ApiActions, + action: ReviewAction, submissionId: string, color: ButtonOwnProps["color"] } @@ -20,7 +32,7 @@ interface ReviewId { submissionSubmitter: number, } -async function ReviewButtonClicked(action: ApiActions, submissionId: string) { +async function ReviewButtonClicked(action: string, submissionId: string) { try { const response = await fetch(`/api/submissions/${submissionId}/status/${action}`, { method: "POST", @@ -46,7 +58,7 @@ function ReviewButton(props: ReviewButton) { return + onClick={() => { ReviewButtonClicked(props.action.action, props.submissionId) }}>{props.action.name} } export default function ReviewButtons(props: ReviewId) { @@ -95,10 +107,10 @@ export default function ReviewButtons(props: ReviewId) { const is_submitter = user === props.submissionSubmitter; if (is_submitter) { if ([SubmissionStatus.UnderConstruction, SubmissionStatus.ChangesRequested].includes(submissionStatus!)) { - visibleButtons.push({ name: "Submit", action: "submit", color: "info", submissionId }); + visibleButtons.push({ action: ReviewActions.Submit, color: "info", submissionId }); } if ([SubmissionStatus.Submitted, SubmissionStatus.ChangesRequested].includes(submissionStatus!)) { - visibleButtons.push({ name: "Revoke", action: "revoke", color: "info", submissionId }); + visibleButtons.push({ action: ReviewActions.Revoke, color: "info", submissionId }); } } @@ -106,14 +118,14 @@ export default function ReviewButtons(props: ReviewId) { // you can't review your own submission! // note that this means there needs to be more than one person with SubmissionReview if (!is_submitter && submissionStatus === SubmissionStatus.Submitted) { - visibleButtons.push({ name: "Accept", action: "trigger-validate", color: "info", submissionId }); - visibleButtons.push({ name: "Reject", action: "reject", color: "error", submissionId }); + visibleButtons.push({ action: ReviewActions.Accept, color: "info", submissionId }); + visibleButtons.push({ action: ReviewActions.Reject, color: "error", submissionId }); } if (submissionStatus === SubmissionStatus.AcceptedUnvalidated) { - visibleButtons.push({ name: "Validate", action: "retry-validate", color: "info", submissionId }); + visibleButtons.push({ action: ReviewActions.Validate, color: "info", submissionId }); } if (submissionStatus === SubmissionStatus.Validating) { - visibleButtons.push({ name: "Reset Validating (fix softlocked status)", action: "reset-validating", color: "error", submissionId }); + visibleButtons.push({ action: ReviewActions.ResetValidating, color: "error", submissionId }); } // this button serves the same purpose as Revoke if you are both // the map submitter and have SubmissionReview when status is Submitted @@ -121,17 +133,17 @@ export default function ReviewButtons(props: ReviewId) { [SubmissionStatus.Validated, SubmissionStatus.AcceptedUnvalidated].includes(submissionStatus!) || !is_submitter && submissionStatus == SubmissionStatus.Submitted ) { - visibleButtons.push({ name: "Request Changes", action: "request-changes", color: "error", submissionId }); + visibleButtons.push({ action: ReviewActions.RequestChanges, color: "error", submissionId }); } } if (roles&RolesConstants.SubmissionUpload) { if (submissionStatus === SubmissionStatus.Validated) { - visibleButtons.push({ name: "Upload", action: "trigger-upload", color: "info", submissionId }); + visibleButtons.push({ action: ReviewActions.Upload, color: "info", submissionId }); } // TODO: hide Reset buttons for 10 seconds if (submissionStatus === SubmissionStatus.Uploading) { - visibleButtons.push({ name: "Reset Uploading (fix softlocked status)", action: "reset-uploading", color: "error", submissionId }); + visibleButtons.push({ action: ReviewActions.ResetUploading, color: "error", submissionId }); } } @@ -141,7 +153,7 @@ export default function ReviewButtons(props: ReviewId) {

No available actions

) : ( visibleButtons.map((btn) => ( - + )) )} -- 2.49.1 From 926a90329b6be5adf12cc4038ba5dc9c09772e20 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 8 Apr 2025 14:22:55 -0700 Subject: [PATCH 362/454] submissions-api: v0.7.0 --- Cargo.lock | 2 +- validation/api/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13778ce..be96c50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1798,7 +1798,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "submissions-api" -version = "0.6.1" +version = "0.7.0" dependencies = [ "reqwest", "serde", diff --git a/validation/api/Cargo.toml b/validation/api/Cargo.toml index 54c0f51..0945e07 100644 --- a/validation/api/Cargo.toml +++ b/validation/api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "submissions-api" -version = "0.6.1" +version = "0.7.0" edition = "2021" publish = ["strafesnet"] repository = "https://git.itzana.me/StrafesNET/maps-service" -- 2.49.1 From b93c813deca23748871ab7c18d30c5ac9e004491 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 8 Apr 2025 16:40:39 -0700 Subject: [PATCH 363/454] submissions: fix faulty endpoints --- pkg/service/mapfixes.go | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/pkg/service/mapfixes.go b/pkg/service/mapfixes.go index b9ff7d7..18207f2 100644 --- a/pkg/service/mapfixes.go +++ b/pkg/service/mapfixes.go @@ -635,7 +635,7 @@ func (svc *Service) ActionMapfixTriggerUpload(ctx context.Context, params api.Ac return err } - _, err = svc.Nats.Publish("maptest.mapfixes.uploadfix", []byte(j)) + _, err = svc.Nats.Publish("maptest.mapfixes.upload", []byte(j)) if err != nil { return err } @@ -708,24 +708,6 @@ func (svc *Service) ActionMapfixValidated(ctx context.Context, params api.Action return err } - // this is a map fix - upload_fix_request := model.UploadMapfixRequest{ - MapfixID: mapfix.ID, - ModelID: mapfix.ValidatedAssetID, - ModelVersion: mapfix.ValidatedAssetVersion, - TargetAssetID: mapfix.TargetAssetID, - } - - j, err := json.Marshal(upload_fix_request) - if err != nil { - return err - } - - _, err = svc.Nats.Publish("maptest.mapfixes.uploadfix", []byte(j)) - if err != nil { - return err - } - event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } -- 2.49.1 From 7334e88b5521ca1f29c837b5ede089c903458529 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 8 Apr 2025 16:11:48 -0700 Subject: [PATCH 364/454] validation: update api to yield a better error --- Cargo.lock | 4 ++-- validation/Cargo.toml | 2 +- validation/src/create.rs | 6 ++++++ validation/src/upload_mapfix.rs | 6 ++++++ validation/src/upload_submission.rs | 6 ++++++ validation/src/validator.rs | 6 ++++++ 6 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be96c50..515368b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1297,9 +1297,9 @@ dependencies = [ [[package]] name = "rbx_asset" -version = "0.4.2" +version = "0.4.3" source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" -checksum = "866fc6e4a9ce7cf32148d266c342876eb3f7432bdc09913aff11b032e77e1638" +checksum = "077e2a0b201a777dfd2ff822766ae7d0c8c3003206115da57f7bce15ee73cbc7" dependencies = [ "chrono", "flate2", diff --git a/validation/Cargo.toml b/validation/Cargo.toml index 869b9aa..e30338d 100644 --- a/validation/Cargo.toml +++ b/validation/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" submissions-api = { path = "api", features = ["internal"], default-features = false, registry = "strafesnet" } async-nats = "0.40.0" futures = "0.3.31" -rbx_asset = { version = "0.4.2", registry = "strafesnet" } +rbx_asset = { version = "0.4.3", registry = "strafesnet" } rbx_binary = { version = "0.7.4", registry = "strafesnet"} rbx_dom_weak = { version = "2.9.0", registry = "strafesnet"} rbx_reflection_database = { version = "0.2.12", registry = "strafesnet"} diff --git a/validation/src/create.rs b/validation/src/create.rs index 44acf20..8c99f54 100644 --- a/validation/src/create.rs +++ b/validation/src/create.rs @@ -6,6 +6,7 @@ pub enum Error{ CreatorTypeMustBeUser, ModelInfoDownload(rbx_asset::cloud::GetError), ModelLocationDownload(rbx_asset::cloud::GetError), + NonFreeModel, ModelFileDownload(rbx_asset::cloud::GetError), ParseUserID(core::num::ParseIntError), ParseModelVersion(core::num::ParseIntError), @@ -54,6 +55,11 @@ impl crate::message_handler::MessageHandler{ version:asset_version, }).await.map_err(Error::ModelLocationDownload)?; + // if the location does not exist, you are not allowed to donwload it + let Some(location)=location.location else{ + return Err(Error::NonFreeModel); + }; + // download the map model let model_data=self.cloud_context.get_asset(&location).await.map_err(Error::ModelFileDownload)?; diff --git a/validation/src/upload_mapfix.rs b/validation/src/upload_mapfix.rs index c2abd25..8df288a 100644 --- a/validation/src/upload_mapfix.rs +++ b/validation/src/upload_mapfix.rs @@ -4,6 +4,7 @@ use crate::nats_types::UploadMapfixRequest; #[derive(Debug)] pub enum Error{ GetLocation(rbx_asset::cloud::GetError), + NonFreeModel, Get(rbx_asset::cloud::GetError), Json(serde_json::Error), Upload(rbx_asset::cookie::UploadError), @@ -24,6 +25,11 @@ impl crate::message_handler::MessageHandler{ version:upload_info.ModelVersion, }).await.map_err(Error::GetLocation)?; + // if the location does not exist, you are not allowed to donwload it + let Some(location)=location.location else{ + return Err(Error::NonFreeModel); + }; + // download the map model let model_data=self.cloud_context.get_asset(&location).await.map_err(Error::Get)?; diff --git a/validation/src/upload_submission.rs b/validation/src/upload_submission.rs index d5f85ab..9b9cc50 100644 --- a/validation/src/upload_submission.rs +++ b/validation/src/upload_submission.rs @@ -4,6 +4,7 @@ use crate::nats_types::UploadSubmissionRequest; #[derive(Debug)] pub enum Error{ GetLocation(rbx_asset::cloud::GetError), + NonFreeModel, Get(rbx_asset::cloud::GetError), Json(serde_json::Error), Create(rbx_asset::cookie::CreateError), @@ -25,6 +26,11 @@ impl crate::message_handler::MessageHandler{ version:upload_info.ModelVersion, }).await.map_err(Error::GetLocation)?; + // if the location does not exist, you are not allowed to donwload it + let Some(location)=location.location else{ + return Err(Error::NonFreeModel); + }; + // download the map model let model_data=self.cloud_context.get_asset(&location).await.map_err(Error::Get)?; diff --git a/validation/src/validator.rs b/validation/src/validator.rs index 19eeee0..1a980e9 100644 --- a/validation/src/validator.rs +++ b/validation/src/validator.rs @@ -37,6 +37,7 @@ pub enum Error{ ScriptBlocked(Option), ScriptNotYetReviewed(Option), ModelLocationDownload(rbx_asset::cloud::GetError), + NonFreeModel, ModelFileDownload(rbx_asset::cloud::GetError), ModelFileDecode(ReadDomError), ApiGetScriptPolicyFromHash(submissions_api::types::SingleItemError), @@ -96,6 +97,11 @@ impl crate::message_handler::MessageHandler{ version:validate_info.ModelVersion, }).await.map_err(Error::ModelLocationDownload)?; + // if the location does not exist, you are not allowed to donwload it + let Some(location)=location.location else{ + return Err(Error::NonFreeModel); + }; + // download the map model let model_data=self.cloud_context.get_asset(&location).await.map_err(Error::ModelFileDownload)?; -- 2.49.1 From e2c72c90c70c46ff60a336e7a7d01b42f6c3a03e Mon Sep 17 00:00:00 2001 From: Quaternions Date: Mon, 7 Apr 2025 16:54:57 -0700 Subject: [PATCH 365/454] validator: prepare for checks --- validation/src/create.rs | 9 ++++++--- validation/src/rbx_util.rs | 16 ++++++++++------ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/validation/src/create.rs b/validation/src/create.rs index 8c99f54..76d8ccf 100644 --- a/validation/src/create.rs +++ b/validation/src/create.rs @@ -1,4 +1,4 @@ -use crate::rbx_util::{get_mapinfo,read_dom,MapInfo,ReadDomError,GetMapInfoError,ParseGameIDError}; +use crate::rbx_util::{get_root_instance,get_mapinfo,read_dom,MapInfo,ReadDomError,GetRootInstanceError,ParseGameIDError}; #[allow(dead_code)] #[derive(Debug)] @@ -11,7 +11,7 @@ pub enum Error{ ParseUserID(core::num::ParseIntError), ParseModelVersion(core::num::ParseIntError), ModelFileDecode(ReadDomError), - GetMapInfo(GetMapInfoError), + GetRootInstance(GetRootInstanceError), ParseGameID(ParseGameIDError), } impl std::fmt::Display for Error{ @@ -66,12 +66,15 @@ impl crate::message_handler::MessageHandler{ // decode dom (slow!) let dom=read_dom(std::io::Cursor::new(model_data)).map_err(Error::ModelFileDecode)?; + // extract the root instance + let model_instance=get_root_instance(&dom).map_err(Error::GetRootInstance)?; + // parse create fields out of asset let MapInfo{ display_name, creator, game_id, - }=get_mapinfo(&dom).map_err(Error::GetMapInfo)?; + }=get_mapinfo(&dom,model_instance); let game_id=game_id.map_err(Error::ParseGameID)?; diff --git a/validation/src/rbx_util.rs b/validation/src/rbx_util.rs index 94f9344..4d49cd0 100644 --- a/validation/src/rbx_util.rs +++ b/validation/src/rbx_util.rs @@ -100,20 +100,24 @@ fn string_value(instance:Option<&rbx_dom_weak::Instance>)->Result<&str,StringVal } #[derive(Debug)] -pub enum GetMapInfoError{ +pub enum GetRootInstanceError{ ModelFileRootMustHaveOneChild, ModelFileChildRefIsNil, } -pub fn get_mapinfo(dom:&rbx_dom_weak::WeakDom)->Result{ +pub fn get_root_instance(dom:&rbx_dom_weak::WeakDom)->Result<&rbx_dom_weak::Instance,GetRootInstanceError>{ let &[map_ref]=dom.root().children()else{ - return Err(GetMapInfoError::ModelFileRootMustHaveOneChild); + return Err(GetRootInstanceError::ModelFileRootMustHaveOneChild); }; - let model_instance=dom.get_by_ref(map_ref).ok_or(GetMapInfoError::ModelFileChildRefIsNil)?; + let model_instance=dom.get_by_ref(map_ref).ok_or(GetRootInstanceError::ModelFileChildRefIsNil)?; - Ok(MapInfo{ + Ok(model_instance) +} + +pub fn get_mapinfo<'a>(dom:&'a rbx_dom_weak::WeakDom,model_instance:&rbx_dom_weak::Instance)->MapInfo<'a>{ + MapInfo{ display_name:string_value(find_first_child_class(dom,model_instance,"DisplayName","StringValue")), creator:string_value(find_first_child_class(dom,model_instance,"Creator","StringValue")), game_id:model_instance.name.parse(), - }) + } } -- 2.49.1 From 1d409218a55ee9591fd2f575b7c9ee6314a4f543 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 9 Apr 2025 18:16:05 -0700 Subject: [PATCH 366/454] validation: factor out asset download --- validation/src/create.rs | 19 +++++-------------- validation/src/download.rs | 26 ++++++++++++++++++++++++++ validation/src/main.rs | 1 + validation/src/upload_mapfix.rs | 19 +++++-------------- validation/src/upload_submission.rs | 19 +++++-------------- validation/src/validator.rs | 19 +++++-------------- 6 files changed, 47 insertions(+), 56 deletions(-) create mode 100644 validation/src/download.rs diff --git a/validation/src/create.rs b/validation/src/create.rs index 76d8ccf..9db62a8 100644 --- a/validation/src/create.rs +++ b/validation/src/create.rs @@ -1,3 +1,4 @@ +use crate::download::download_asset_version; use crate::rbx_util::{get_root_instance,get_mapinfo,read_dom,MapInfo,ReadDomError,GetRootInstanceError,ParseGameIDError}; #[allow(dead_code)] @@ -5,11 +6,9 @@ use crate::rbx_util::{get_root_instance,get_mapinfo,read_dom,MapInfo,ReadDomErro pub enum Error{ CreatorTypeMustBeUser, ModelInfoDownload(rbx_asset::cloud::GetError), - ModelLocationDownload(rbx_asset::cloud::GetError), - NonFreeModel, - ModelFileDownload(rbx_asset::cloud::GetError), ParseUserID(core::num::ParseIntError), ParseModelVersion(core::num::ParseIntError), + Download(crate::download::Error), ModelFileDecode(ReadDomError), GetRootInstance(GetRootInstanceError), ParseGameID(ParseGameIDError), @@ -49,19 +48,11 @@ impl crate::message_handler::MessageHandler{ let user_id:u64=user_id_string.parse().map_err(Error::ParseUserID)?; let asset_version=info.revisionId.parse().map_err(Error::ParseModelVersion)?; - // download the location of the map model - let location=self.cloud_context.get_asset_version_location(rbx_asset::cloud::GetAssetVersionRequest{ + // download the map model + let model_data=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{ asset_id:create_info.ModelID, version:asset_version, - }).await.map_err(Error::ModelLocationDownload)?; - - // if the location does not exist, you are not allowed to donwload it - let Some(location)=location.location else{ - return Err(Error::NonFreeModel); - }; - - // download the map model - let model_data=self.cloud_context.get_asset(&location).await.map_err(Error::ModelFileDownload)?; + }).await.map_err(Error::Download)?; // decode dom (slow!) let dom=read_dom(std::io::Cursor::new(model_data)).map_err(Error::ModelFileDecode)?; diff --git a/validation/src/download.rs b/validation/src/download.rs new file mode 100644 index 0000000..202be72 --- /dev/null +++ b/validation/src/download.rs @@ -0,0 +1,26 @@ +#[allow(dead_code)] +#[derive(Debug)] +pub enum Error{ + ModelLocationDownload(rbx_asset::cloud::GetError), + NonFreeModel, + ModelFileDownload(rbx_asset::cloud::GetError), +} +impl std::fmt::Display for Error{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + write!(f,"{self:?}") + } +} +impl std::error::Error for Error{} + +pub async fn download_asset_version(cloud_context:&rbx_asset::cloud::Context,request:rbx_asset::cloud::GetAssetVersionRequest)->Result,Error>{ + // download the location of the map model + let location=cloud_context.get_asset_version_location(request).await.map_err(Error::ModelLocationDownload)?; + + // if the location does not exist, you are not allowed to download it + let location=location.location.ok_or(Error::NonFreeModel)?; + + // download the map model + let model_data=cloud_context.get_asset(&location).await.map_err(Error::ModelFileDownload)?; + + Ok(model_data) +} diff --git a/validation/src/main.rs b/validation/src/main.rs index 00fb7f3..2b91fdc 100644 --- a/validation/src/main.rs +++ b/validation/src/main.rs @@ -4,6 +4,7 @@ mod rbx_util; mod message_handler; mod nats_types; mod types; +mod download; mod create; mod create_mapfix; mod create_submission; diff --git a/validation/src/upload_mapfix.rs b/validation/src/upload_mapfix.rs index 8df288a..b74e562 100644 --- a/validation/src/upload_mapfix.rs +++ b/validation/src/upload_mapfix.rs @@ -1,11 +1,10 @@ +use crate::download::download_asset_version; use crate::nats_types::UploadMapfixRequest; #[allow(dead_code)] #[derive(Debug)] pub enum Error{ - GetLocation(rbx_asset::cloud::GetError), - NonFreeModel, - Get(rbx_asset::cloud::GetError), + Download(crate::download::Error), Json(serde_json::Error), Upload(rbx_asset::cookie::UploadError), ApiActionMapfixUploaded(submissions_api::Error), @@ -19,19 +18,11 @@ impl std::error::Error for Error{} impl crate::message_handler::MessageHandler{ pub async fn upload_mapfix(&self,upload_info:UploadMapfixRequest)->Result<(),Error>{ - // download the location of the map model - let location=self.cloud_context.get_asset_version_location(rbx_asset::cloud::GetAssetVersionRequest{ + // download the map model + let model_data=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{ asset_id:upload_info.ModelID, version:upload_info.ModelVersion, - }).await.map_err(Error::GetLocation)?; - - // if the location does not exist, you are not allowed to donwload it - let Some(location)=location.location else{ - return Err(Error::NonFreeModel); - }; - - // download the map model - let model_data=self.cloud_context.get_asset(&location).await.map_err(Error::Get)?; + }).await.map_err(Error::Download)?; // upload the map to the strafesnet group let _upload_response=self.cookie_context.upload(rbx_asset::cookie::UploadRequest{ diff --git a/validation/src/upload_submission.rs b/validation/src/upload_submission.rs index 9b9cc50..9052433 100644 --- a/validation/src/upload_submission.rs +++ b/validation/src/upload_submission.rs @@ -1,11 +1,10 @@ +use crate::download::download_asset_version; use crate::nats_types::UploadSubmissionRequest; #[allow(dead_code)] #[derive(Debug)] pub enum Error{ - GetLocation(rbx_asset::cloud::GetError), - NonFreeModel, - Get(rbx_asset::cloud::GetError), + Download(crate::download::Error), Json(serde_json::Error), Create(rbx_asset::cookie::CreateError), SystemTime(std::time::SystemTimeError), @@ -20,19 +19,11 @@ impl std::error::Error for Error{} impl crate::message_handler::MessageHandler{ pub async fn upload_submission(&self,upload_info:UploadSubmissionRequest)->Result<(),Error>{ - // download the location of the map model - let location=self.cloud_context.get_asset_version_location(rbx_asset::cloud::GetAssetVersionRequest{ + // download the map model + let model_data=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{ asset_id:upload_info.ModelID, version:upload_info.ModelVersion, - }).await.map_err(Error::GetLocation)?; - - // if the location does not exist, you are not allowed to donwload it - let Some(location)=location.location else{ - return Err(Error::NonFreeModel); - }; - - // download the map model - let model_data=self.cloud_context.get_asset(&location).await.map_err(Error::Get)?; + }).await.map_err(Error::Download)?; // upload the map to the strafesnet group let upload_response=self.cookie_context.create(rbx_asset::cookie::CreateRequest{ diff --git a/validation/src/validator.rs b/validation/src/validator.rs index 1a980e9..1215ee4 100644 --- a/validation/src/validator.rs +++ b/validation/src/validator.rs @@ -1,6 +1,7 @@ use futures::TryStreamExt; use submissions_api::types::ResourceType; +use crate::download::download_asset_version; use crate::rbx_util::{class_is_a,read_dom,ReadDomError}; use crate::types::ResourceID; @@ -36,9 +37,7 @@ pub enum Error{ ScriptFlaggedIllegalKeyword(String), ScriptBlocked(Option), ScriptNotYetReviewed(Option), - ModelLocationDownload(rbx_asset::cloud::GetError), - NonFreeModel, - ModelFileDownload(rbx_asset::cloud::GetError), + Download(crate::download::Error), ModelFileDecode(ReadDomError), ApiGetScriptPolicyFromHash(submissions_api::types::SingleItemError), ApiGetScript(submissions_api::Error), @@ -91,19 +90,11 @@ impl From for ValidateRequest{ impl crate::message_handler::MessageHandler{ pub async fn validate_inner(&self,validate_info:ValidateRequest)->Result<(),Error>{ - // download the location of the map model - let location=self.cloud_context.get_asset_version_location(rbx_asset::cloud::GetAssetVersionRequest{ + // download the map model + let model_data=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{ asset_id:validate_info.ModelID, version:validate_info.ModelVersion, - }).await.map_err(Error::ModelLocationDownload)?; - - // if the location does not exist, you are not allowed to donwload it - let Some(location)=location.location else{ - return Err(Error::NonFreeModel); - }; - - // download the map model - let model_data=self.cloud_context.get_asset(&location).await.map_err(Error::ModelFileDownload)?; + }).await.map_err(Error::Download)?; // decode dom (slow!) let mut dom=read_dom(std::io::Cursor::new(model_data)).map_err(Error::ModelFileDecode)?; -- 2.49.1 From 6eebe404d5978d4429be7db3c66054b786cfdd7e Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 9 Apr 2025 18:31:17 -0700 Subject: [PATCH 367/454] openapi: validator-request-changes endpoint --- openapi-internal.yaml | 62 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/openapi-internal.yaml b/openapi-internal.yaml index 1fb1fd7..656cc4d 100644 --- a/openapi-internal.yaml +++ b/openapi-internal.yaml @@ -73,6 +73,37 @@ paths: - Mapfixes parameters: - $ref: '#/components/parameters/MapfixID' + - name: ModelVersion + in: query + required: true + schema: + type: integer + format: int64 + minimum: 0 + responses: + "204": + description: Successful response + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /mapfixes/{MapfixID}/status/validator-request-changes: + post: + summary: (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested + operationId: actionMapfixRequestChanges + tags: + - Mapfixes + parameters: + - $ref: '#/components/parameters/MapfixID' + - name: StatusMessage + in: query + required: true + schema: + type: string + minLength: 0 + maxLength: 4096 responses: "204": description: Successful response @@ -228,6 +259,37 @@ paths: - Submissions parameters: - $ref: '#/components/parameters/SubmissionID' + - name: ModelVersion + in: query + required: true + schema: + type: integer + format: int64 + minimum: 0 + responses: + "204": + description: Successful response + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /submissions/{SubmissionID}/status/validator-request-changes: + post: + summary: (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested + operationId: actionSubmissionRequestChanges + tags: + - Submissions + parameters: + - $ref: '#/components/parameters/SubmissionID' + - name: StatusMessage + in: query + required: true + schema: + type: string + minLength: 0 + maxLength: 4096 responses: "204": description: Successful response -- 2.49.1 From 67a03f394f13762a7d64583c3ff1a2e073c7b790 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 9 Apr 2025 18:31:31 -0700 Subject: [PATCH 368/454] openapi: generate --- pkg/internal/oas_client_gen.go | 266 +++++++++++++ pkg/internal/oas_handlers_gen.go | 314 ++++++++++++++++ pkg/internal/oas_operations_gen.go | 2 + pkg/internal/oas_parameters_gen.go | 432 +++++++++++++++++++++- pkg/internal/oas_response_decoders_gen.go | 120 ++++++ pkg/internal/oas_response_encoders_gen.go | 14 + pkg/internal/oas_router_gen.go | 92 +++++ pkg/internal/oas_schemas_gen.go | 6 + pkg/internal/oas_server_gen.go | 12 + pkg/internal/oas_unimplemented_gen.go | 18 + 10 files changed, 1266 insertions(+), 10 deletions(-) diff --git a/pkg/internal/oas_client_gen.go b/pkg/internal/oas_client_gen.go index 3a33e68..78d4f40 100644 --- a/pkg/internal/oas_client_gen.go +++ b/pkg/internal/oas_client_gen.go @@ -34,6 +34,12 @@ type Invoker interface { // // POST /mapfixes/{MapfixID}/status/validator-failed ActionMapfixAccepted(ctx context.Context, params ActionMapfixAcceptedParams) error + // ActionMapfixRequestChanges invokes actionMapfixRequestChanges operation. + // + // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. + // + // POST /mapfixes/{MapfixID}/status/validator-request-changes + ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error // ActionMapfixSubmitted invokes actionMapfixSubmitted operation. // // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. @@ -64,6 +70,12 @@ type Invoker interface { // // POST /submissions/{SubmissionID}/status/validator-failed ActionSubmissionAccepted(ctx context.Context, params ActionSubmissionAcceptedParams) error + // ActionSubmissionRequestChanges invokes actionSubmissionRequestChanges operation. + // + // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. + // + // POST /submissions/{SubmissionID}/status/validator-request-changes + ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error // ActionSubmissionSubmitted invokes actionSubmissionSubmitted operation. // // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. @@ -294,6 +306,115 @@ func (c *Client) sendActionMapfixAccepted(ctx context.Context, params ActionMapf return result, nil } +// ActionMapfixRequestChanges invokes actionMapfixRequestChanges operation. +// +// (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. +// +// POST /mapfixes/{MapfixID}/status/validator-request-changes +func (c *Client) ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error { + _, err := c.sendActionMapfixRequestChanges(ctx, params) + return err +} + +func (c *Client) sendActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) (res *ActionMapfixRequestChangesNoContent, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("actionMapfixRequestChanges"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/status/validator-request-changes"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, ActionMapfixRequestChangesOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [3]string + pathParts[0] = "/mapfixes/" + { + // Encode "MapfixID" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "MapfixID", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.Int64ToString(params.MapfixID)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[1] = encoded + } + pathParts[2] = "/status/validator-request-changes" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeQueryParams" + q := uri.NewQueryEncoder() + { + // Encode "StatusMessage" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "StatusMessage", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.StringToString(params.StatusMessage)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + u.RawQuery = q.Values().Encode() + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeActionMapfixRequestChangesResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // ActionMapfixSubmitted invokes actionMapfixSubmitted operation. // // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. @@ -363,6 +484,24 @@ func (c *Client) sendActionMapfixSubmitted(ctx context.Context, params ActionMap pathParts[2] = "/status/validator-submitted" uri.AddPathParts(u, pathParts[:]...) + stage = "EncodeQueryParams" + q := uri.NewQueryEncoder() + { + // Encode "ModelVersion" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "ModelVersion", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.Int64ToString(params.ModelVersion)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + u.RawQuery = q.Values().Encode() + stage = "EncodeRequest" r, err := ht.NewRequest(ctx, "POST", u) if err != nil { @@ -785,6 +924,115 @@ func (c *Client) sendActionSubmissionAccepted(ctx context.Context, params Action return result, nil } +// ActionSubmissionRequestChanges invokes actionSubmissionRequestChanges operation. +// +// (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. +// +// POST /submissions/{SubmissionID}/status/validator-request-changes +func (c *Client) ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error { + _, err := c.sendActionSubmissionRequestChanges(ctx, params) + return err +} + +func (c *Client) sendActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) (res *ActionSubmissionRequestChangesNoContent, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("actionSubmissionRequestChanges"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/validator-request-changes"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, ActionSubmissionRequestChangesOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [3]string + pathParts[0] = "/submissions/" + { + // Encode "SubmissionID" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "SubmissionID", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.Int64ToString(params.SubmissionID)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[1] = encoded + } + pathParts[2] = "/status/validator-request-changes" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeQueryParams" + q := uri.NewQueryEncoder() + { + // Encode "StatusMessage" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "StatusMessage", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.StringToString(params.StatusMessage)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + u.RawQuery = q.Values().Encode() + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeActionSubmissionRequestChangesResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // ActionSubmissionSubmitted invokes actionSubmissionSubmitted operation. // // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. @@ -854,6 +1102,24 @@ func (c *Client) sendActionSubmissionSubmitted(ctx context.Context, params Actio pathParts[2] = "/status/validator-submitted" uri.AddPathParts(u, pathParts[:]...) + stage = "EncodeQueryParams" + q := uri.NewQueryEncoder() + { + // Encode "ModelVersion" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "ModelVersion", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.Int64ToString(params.ModelVersion)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + u.RawQuery = q.Values().Encode() + stage = "EncodeRequest" r, err := ht.NewRequest(ctx, "POST", u) if err != nil { diff --git a/pkg/internal/oas_handlers_gen.go b/pkg/internal/oas_handlers_gen.go index a205b0e..ff02638 100644 --- a/pkg/internal/oas_handlers_gen.go +++ b/pkg/internal/oas_handlers_gen.go @@ -183,6 +183,159 @@ func (s *Server) handleActionMapfixAcceptedRequest(args [1]string, argsEscaped b } } +// handleActionMapfixRequestChangesRequest handles actionMapfixRequestChanges operation. +// +// (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. +// +// POST /mapfixes/{MapfixID}/status/validator-request-changes +func (s *Server) handleActionMapfixRequestChangesRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("actionMapfixRequestChanges"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/status/validator-request-changes"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), ActionMapfixRequestChangesOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: ActionMapfixRequestChangesOperation, + ID: "actionMapfixRequestChanges", + } + ) + params, err := decodeActionMapfixRequestChangesParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response *ActionMapfixRequestChangesNoContent + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: ActionMapfixRequestChangesOperation, + OperationSummary: "(Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested", + OperationID: "actionMapfixRequestChanges", + Body: nil, + Params: middleware.Parameters{ + { + Name: "MapfixID", + In: "path", + }: params.MapfixID, + { + Name: "StatusMessage", + In: "query", + }: params.StatusMessage, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = ActionMapfixRequestChangesParams + Response = *ActionMapfixRequestChangesNoContent + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackActionMapfixRequestChangesParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + err = s.h.ActionMapfixRequestChanges(ctx, params) + return response, err + }, + ) + } else { + err = s.h.ActionMapfixRequestChanges(ctx, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeActionMapfixRequestChangesResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleActionMapfixSubmittedRequest handles actionMapfixSubmitted operation. // // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. @@ -281,6 +434,10 @@ func (s *Server) handleActionMapfixSubmittedRequest(args [1]string, argsEscaped Name: "MapfixID", In: "path", }: params.MapfixID, + { + Name: "ModelVersion", + In: "query", + }: params.ModelVersion, }, Raw: r, } @@ -936,6 +1093,159 @@ func (s *Server) handleActionSubmissionAcceptedRequest(args [1]string, argsEscap } } +// handleActionSubmissionRequestChangesRequest handles actionSubmissionRequestChanges operation. +// +// (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. +// +// POST /submissions/{SubmissionID}/status/validator-request-changes +func (s *Server) handleActionSubmissionRequestChangesRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("actionSubmissionRequestChanges"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/validator-request-changes"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), ActionSubmissionRequestChangesOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: ActionSubmissionRequestChangesOperation, + ID: "actionSubmissionRequestChanges", + } + ) + params, err := decodeActionSubmissionRequestChangesParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response *ActionSubmissionRequestChangesNoContent + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: ActionSubmissionRequestChangesOperation, + OperationSummary: "(Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested", + OperationID: "actionSubmissionRequestChanges", + Body: nil, + Params: middleware.Parameters{ + { + Name: "SubmissionID", + In: "path", + }: params.SubmissionID, + { + Name: "StatusMessage", + In: "query", + }: params.StatusMessage, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = ActionSubmissionRequestChangesParams + Response = *ActionSubmissionRequestChangesNoContent + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackActionSubmissionRequestChangesParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + err = s.h.ActionSubmissionRequestChanges(ctx, params) + return response, err + }, + ) + } else { + err = s.h.ActionSubmissionRequestChanges(ctx, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeActionSubmissionRequestChangesResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleActionSubmissionSubmittedRequest handles actionSubmissionSubmitted operation. // // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. @@ -1034,6 +1344,10 @@ func (s *Server) handleActionSubmissionSubmittedRequest(args [1]string, argsEsca Name: "SubmissionID", In: "path", }: params.SubmissionID, + { + Name: "ModelVersion", + In: "query", + }: params.ModelVersion, }, Raw: r, } diff --git a/pkg/internal/oas_operations_gen.go b/pkg/internal/oas_operations_gen.go index cc5e6af..9ddc04b 100644 --- a/pkg/internal/oas_operations_gen.go +++ b/pkg/internal/oas_operations_gen.go @@ -7,11 +7,13 @@ type OperationName = string const ( ActionMapfixAcceptedOperation OperationName = "ActionMapfixAccepted" + ActionMapfixRequestChangesOperation OperationName = "ActionMapfixRequestChanges" ActionMapfixSubmittedOperation OperationName = "ActionMapfixSubmitted" ActionMapfixUploadedOperation OperationName = "ActionMapfixUploaded" ActionMapfixValidatedOperation OperationName = "ActionMapfixValidated" ActionOperationFailedOperation OperationName = "ActionOperationFailed" ActionSubmissionAcceptedOperation OperationName = "ActionSubmissionAccepted" + ActionSubmissionRequestChangesOperation OperationName = "ActionSubmissionRequestChanges" ActionSubmissionSubmittedOperation OperationName = "ActionSubmissionSubmitted" ActionSubmissionUploadedOperation OperationName = "ActionSubmissionUploaded" ActionSubmissionValidatedOperation OperationName = "ActionSubmissionValidated" diff --git a/pkg/internal/oas_parameters_gen.go b/pkg/internal/oas_parameters_gen.go index f564af1..d21e5e5 100644 --- a/pkg/internal/oas_parameters_gen.go +++ b/pkg/internal/oas_parameters_gen.go @@ -159,13 +159,14 @@ func decodeActionMapfixAcceptedParams(args [1]string, argsEscaped bool, r *http. return params, nil } -// ActionMapfixSubmittedParams is parameters of actionMapfixSubmitted operation. -type ActionMapfixSubmittedParams struct { +// ActionMapfixRequestChangesParams is parameters of actionMapfixRequestChanges operation. +type ActionMapfixRequestChangesParams struct { // The unique identifier for a submission. - MapfixID int64 + MapfixID int64 + StatusMessage string } -func unpackActionMapfixSubmittedParams(packed middleware.Parameters) (params ActionMapfixSubmittedParams) { +func unpackActionMapfixRequestChangesParams(packed middleware.Parameters) (params ActionMapfixRequestChangesParams) { { key := middleware.ParameterKey{ Name: "MapfixID", @@ -173,10 +174,18 @@ func unpackActionMapfixSubmittedParams(packed middleware.Parameters) (params Act } params.MapfixID = packed[key].(int64) } + { + key := middleware.ParameterKey{ + Name: "StatusMessage", + In: "query", + } + params.StatusMessage = packed[key].(string) + } return params } -func decodeActionMapfixSubmittedParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionMapfixSubmittedParams, _ error) { +func decodeActionMapfixRequestChangesParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionMapfixRequestChangesParams, _ error) { + q := uri.NewQueryDecoder(r.URL.Query()) // Decode path: MapfixID. if err := func() error { param := args[0] @@ -239,6 +248,203 @@ func decodeActionMapfixSubmittedParams(args [1]string, argsEscaped bool, r *http Err: err, } } + // Decode query: StatusMessage. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "StatusMessage", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + params.StatusMessage = c + return nil + }); err != nil { + return err + } + if err := func() error { + if err := (validate.String{ + MinLength: 0, + MinLengthSet: true, + MaxLength: 4096, + MaxLengthSet: true, + Email: false, + Hostname: false, + Regex: nil, + }).Validate(string(params.StatusMessage)); err != nil { + return errors.Wrap(err, "string") + } + return nil + }(); err != nil { + return err + } + } else { + return err + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "StatusMessage", + In: "query", + Err: err, + } + } + return params, nil +} + +// ActionMapfixSubmittedParams is parameters of actionMapfixSubmitted operation. +type ActionMapfixSubmittedParams struct { + // The unique identifier for a submission. + MapfixID int64 + ModelVersion int64 +} + +func unpackActionMapfixSubmittedParams(packed middleware.Parameters) (params ActionMapfixSubmittedParams) { + { + key := middleware.ParameterKey{ + Name: "MapfixID", + In: "path", + } + params.MapfixID = packed[key].(int64) + } + { + key := middleware.ParameterKey{ + Name: "ModelVersion", + In: "query", + } + params.ModelVersion = packed[key].(int64) + } + return params +} + +func decodeActionMapfixSubmittedParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionMapfixSubmittedParams, _ error) { + q := uri.NewQueryDecoder(r.URL.Query()) + // Decode path: MapfixID. + if err := func() error { + param := args[0] + if argsEscaped { + unescaped, err := url.PathUnescape(args[0]) + if err != nil { + return errors.Wrap(err, "unescape path") + } + param = unescaped + } + if len(param) > 0 { + d := uri.NewPathDecoder(uri.PathDecoderConfig{ + Param: "MapfixID", + Value: param, + Style: uri.PathStyleSimple, + Explode: false, + }) + + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + params.MapfixID = c + return nil + }(); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.MapfixID)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "MapfixID", + In: "path", + Err: err, + } + } + // Decode query: ModelVersion. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "ModelVersion", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + params.ModelVersion = c + return nil + }); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.ModelVersion)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return err + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "ModelVersion", + In: "query", + Err: err, + } + } return params, nil } @@ -696,13 +902,14 @@ func decodeActionSubmissionAcceptedParams(args [1]string, argsEscaped bool, r *h return params, nil } -// ActionSubmissionSubmittedParams is parameters of actionSubmissionSubmitted operation. -type ActionSubmissionSubmittedParams struct { +// ActionSubmissionRequestChangesParams is parameters of actionSubmissionRequestChanges operation. +type ActionSubmissionRequestChangesParams struct { // The unique identifier for a submission. - SubmissionID int64 + SubmissionID int64 + StatusMessage string } -func unpackActionSubmissionSubmittedParams(packed middleware.Parameters) (params ActionSubmissionSubmittedParams) { +func unpackActionSubmissionRequestChangesParams(packed middleware.Parameters) (params ActionSubmissionRequestChangesParams) { { key := middleware.ParameterKey{ Name: "SubmissionID", @@ -710,10 +917,18 @@ func unpackActionSubmissionSubmittedParams(packed middleware.Parameters) (params } params.SubmissionID = packed[key].(int64) } + { + key := middleware.ParameterKey{ + Name: "StatusMessage", + In: "query", + } + params.StatusMessage = packed[key].(string) + } return params } -func decodeActionSubmissionSubmittedParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionSubmissionSubmittedParams, _ error) { +func decodeActionSubmissionRequestChangesParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionSubmissionRequestChangesParams, _ error) { + q := uri.NewQueryDecoder(r.URL.Query()) // Decode path: SubmissionID. if err := func() error { param := args[0] @@ -776,6 +991,203 @@ func decodeActionSubmissionSubmittedParams(args [1]string, argsEscaped bool, r * Err: err, } } + // Decode query: StatusMessage. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "StatusMessage", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + params.StatusMessage = c + return nil + }); err != nil { + return err + } + if err := func() error { + if err := (validate.String{ + MinLength: 0, + MinLengthSet: true, + MaxLength: 4096, + MaxLengthSet: true, + Email: false, + Hostname: false, + Regex: nil, + }).Validate(string(params.StatusMessage)); err != nil { + return errors.Wrap(err, "string") + } + return nil + }(); err != nil { + return err + } + } else { + return err + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "StatusMessage", + In: "query", + Err: err, + } + } + return params, nil +} + +// ActionSubmissionSubmittedParams is parameters of actionSubmissionSubmitted operation. +type ActionSubmissionSubmittedParams struct { + // The unique identifier for a submission. + SubmissionID int64 + ModelVersion int64 +} + +func unpackActionSubmissionSubmittedParams(packed middleware.Parameters) (params ActionSubmissionSubmittedParams) { + { + key := middleware.ParameterKey{ + Name: "SubmissionID", + In: "path", + } + params.SubmissionID = packed[key].(int64) + } + { + key := middleware.ParameterKey{ + Name: "ModelVersion", + In: "query", + } + params.ModelVersion = packed[key].(int64) + } + return params +} + +func decodeActionSubmissionSubmittedParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionSubmissionSubmittedParams, _ error) { + q := uri.NewQueryDecoder(r.URL.Query()) + // Decode path: SubmissionID. + if err := func() error { + param := args[0] + if argsEscaped { + unescaped, err := url.PathUnescape(args[0]) + if err != nil { + return errors.Wrap(err, "unescape path") + } + param = unescaped + } + if len(param) > 0 { + d := uri.NewPathDecoder(uri.PathDecoderConfig{ + Param: "SubmissionID", + Value: param, + Style: uri.PathStyleSimple, + Explode: false, + }) + + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + params.SubmissionID = c + return nil + }(); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.SubmissionID)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "SubmissionID", + In: "path", + Err: err, + } + } + // Decode query: ModelVersion. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "ModelVersion", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + params.ModelVersion = c + return nil + }); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.ModelVersion)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return err + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "ModelVersion", + In: "query", + Err: err, + } + } return params, nil } diff --git a/pkg/internal/oas_response_decoders_gen.go b/pkg/internal/oas_response_decoders_gen.go index 8b7f1da..77616ac 100644 --- a/pkg/internal/oas_response_decoders_gen.go +++ b/pkg/internal/oas_response_decoders_gen.go @@ -75,6 +75,66 @@ func decodeActionMapfixAcceptedResponse(resp *http.Response) (res *ActionMapfixA return res, errors.Wrap(defRes, "error") } +func decodeActionMapfixRequestChangesResponse(resp *http.Response) (res *ActionMapfixRequestChangesNoContent, _ error) { + switch resp.StatusCode { + case 204: + // Code 204. + return &ActionMapfixRequestChangesNoContent{}, nil + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + func decodeActionMapfixSubmittedResponse(resp *http.Response) (res *ActionMapfixSubmittedNoContent, _ error) { switch resp.StatusCode { case 204: @@ -375,6 +435,66 @@ func decodeActionSubmissionAcceptedResponse(resp *http.Response) (res *ActionSub return res, errors.Wrap(defRes, "error") } +func decodeActionSubmissionRequestChangesResponse(resp *http.Response) (res *ActionSubmissionRequestChangesNoContent, _ error) { + switch resp.StatusCode { + case 204: + // Code 204. + return &ActionSubmissionRequestChangesNoContent{}, nil + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + func decodeActionSubmissionSubmittedResponse(resp *http.Response) (res *ActionSubmissionSubmittedNoContent, _ error) { switch resp.StatusCode { case 204: diff --git a/pkg/internal/oas_response_encoders_gen.go b/pkg/internal/oas_response_encoders_gen.go index 38bfb96..024126c 100644 --- a/pkg/internal/oas_response_encoders_gen.go +++ b/pkg/internal/oas_response_encoders_gen.go @@ -20,6 +20,13 @@ func encodeActionMapfixAcceptedResponse(response *ActionMapfixAcceptedNoContent, return nil } +func encodeActionMapfixRequestChangesResponse(response *ActionMapfixRequestChangesNoContent, w http.ResponseWriter, span trace.Span) error { + w.WriteHeader(204) + span.SetStatus(codes.Ok, http.StatusText(204)) + + return nil +} + func encodeActionMapfixSubmittedResponse(response *ActionMapfixSubmittedNoContent, w http.ResponseWriter, span trace.Span) error { w.WriteHeader(204) span.SetStatus(codes.Ok, http.StatusText(204)) @@ -55,6 +62,13 @@ func encodeActionSubmissionAcceptedResponse(response *ActionSubmissionAcceptedNo return nil } +func encodeActionSubmissionRequestChangesResponse(response *ActionSubmissionRequestChangesNoContent, w http.ResponseWriter, span trace.Span) error { + w.WriteHeader(204) + span.SetStatus(codes.Ok, http.StatusText(204)) + + return nil +} + func encodeActionSubmissionSubmittedResponse(response *ActionSubmissionSubmittedNoContent, w http.ResponseWriter, span trace.Span) error { w.WriteHeader(204) span.SetStatus(codes.Ok, http.StatusText(204)) diff --git a/pkg/internal/oas_router_gen.go b/pkg/internal/oas_router_gen.go index 3e9fba8..43be2cc 100644 --- a/pkg/internal/oas_router_gen.go +++ b/pkg/internal/oas_router_gen.go @@ -147,6 +147,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + case 'r': // Prefix: "request-changes" + + if l := len("request-changes"); len(elem) >= l && elem[0:l] == "request-changes" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionMapfixRequestChangesRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + case 's': // Prefix: "submitted" if l := len("submitted"); len(elem) >= l && elem[0:l] == "submitted" { @@ -476,6 +498,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + case 'r': // Prefix: "request-changes" + + if l := len("request-changes"); len(elem) >= l && elem[0:l] == "request-changes" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionSubmissionRequestChangesRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + case 's': // Prefix: "submitted" if l := len("submitted"); len(elem) >= l && elem[0:l] == "submitted" { @@ -760,6 +804,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { } } + case 'r': // Prefix: "request-changes" + + if l := len("request-changes"); len(elem) >= l && elem[0:l] == "request-changes" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionMapfixRequestChangesOperation + r.summary = "(Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested" + r.operationID = "actionMapfixRequestChanges" + r.pathPattern = "/mapfixes/{MapfixID}/status/validator-request-changes" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + case 's': // Prefix: "submitted" if l := len("submitted"); len(elem) >= l && elem[0:l] == "submitted" { @@ -1127,6 +1195,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { } } + case 'r': // Prefix: "request-changes" + + if l := len("request-changes"); len(elem) >= l && elem[0:l] == "request-changes" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionSubmissionRequestChangesOperation + r.summary = "(Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested" + r.operationID = "actionSubmissionRequestChanges" + r.pathPattern = "/submissions/{SubmissionID}/status/validator-request-changes" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + case 's': // Prefix: "submitted" if l := len("submitted"); len(elem) >= l && elem[0:l] == "submitted" { diff --git a/pkg/internal/oas_schemas_gen.go b/pkg/internal/oas_schemas_gen.go index e68e85a..f8c3cd4 100644 --- a/pkg/internal/oas_schemas_gen.go +++ b/pkg/internal/oas_schemas_gen.go @@ -13,6 +13,9 @@ func (s *ErrorStatusCode) Error() string { // ActionMapfixAcceptedNoContent is response for ActionMapfixAccepted operation. type ActionMapfixAcceptedNoContent struct{} +// ActionMapfixRequestChangesNoContent is response for ActionMapfixRequestChanges operation. +type ActionMapfixRequestChangesNoContent struct{} + // ActionMapfixSubmittedNoContent is response for ActionMapfixSubmitted operation. type ActionMapfixSubmittedNoContent struct{} @@ -28,6 +31,9 @@ type ActionOperationFailedNoContent struct{} // ActionSubmissionAcceptedNoContent is response for ActionSubmissionAccepted operation. type ActionSubmissionAcceptedNoContent struct{} +// ActionSubmissionRequestChangesNoContent is response for ActionSubmissionRequestChanges operation. +type ActionSubmissionRequestChangesNoContent struct{} + // ActionSubmissionSubmittedNoContent is response for ActionSubmissionSubmitted operation. type ActionSubmissionSubmittedNoContent struct{} diff --git a/pkg/internal/oas_server_gen.go b/pkg/internal/oas_server_gen.go index 94c8192..7350a9c 100644 --- a/pkg/internal/oas_server_gen.go +++ b/pkg/internal/oas_server_gen.go @@ -14,6 +14,12 @@ type Handler interface { // // POST /mapfixes/{MapfixID}/status/validator-failed ActionMapfixAccepted(ctx context.Context, params ActionMapfixAcceptedParams) error + // ActionMapfixRequestChanges implements actionMapfixRequestChanges operation. + // + // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. + // + // POST /mapfixes/{MapfixID}/status/validator-request-changes + ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error // ActionMapfixSubmitted implements actionMapfixSubmitted operation. // // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. @@ -44,6 +50,12 @@ type Handler interface { // // POST /submissions/{SubmissionID}/status/validator-failed ActionSubmissionAccepted(ctx context.Context, params ActionSubmissionAcceptedParams) error + // ActionSubmissionRequestChanges implements actionSubmissionRequestChanges operation. + // + // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. + // + // POST /submissions/{SubmissionID}/status/validator-request-changes + ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error // ActionSubmissionSubmitted implements actionSubmissionSubmitted operation. // // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. diff --git a/pkg/internal/oas_unimplemented_gen.go b/pkg/internal/oas_unimplemented_gen.go index 7baa6e9..46a4c7e 100644 --- a/pkg/internal/oas_unimplemented_gen.go +++ b/pkg/internal/oas_unimplemented_gen.go @@ -22,6 +22,15 @@ func (UnimplementedHandler) ActionMapfixAccepted(ctx context.Context, params Act return ht.ErrNotImplemented } +// ActionMapfixRequestChanges implements actionMapfixRequestChanges operation. +// +// (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. +// +// POST /mapfixes/{MapfixID}/status/validator-request-changes +func (UnimplementedHandler) ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error { + return ht.ErrNotImplemented +} + // ActionMapfixSubmitted implements actionMapfixSubmitted operation. // // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. @@ -67,6 +76,15 @@ func (UnimplementedHandler) ActionSubmissionAccepted(ctx context.Context, params return ht.ErrNotImplemented } +// ActionSubmissionRequestChanges implements actionSubmissionRequestChanges operation. +// +// (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. +// +// POST /submissions/{SubmissionID}/status/validator-request-changes +func (UnimplementedHandler) ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error { + return ht.ErrNotImplemented +} + // ActionSubmissionSubmitted implements actionSubmissionSubmitted operation. // // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. -- 2.49.1 From 174a210f81f4c9fa4c73043f58660b8434b1b1bc Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 9 Apr 2025 19:02:23 -0700 Subject: [PATCH 369/454] submissions: implement validator-request-changes endpoints --- pkg/service_internal/mapfixes.go | 42 ++++++++++++++++++++++++++++- pkg/service_internal/submissions.go | 42 ++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/pkg/service_internal/mapfixes.go b/pkg/service_internal/mapfixes.go index 0827424..49ef1b4 100644 --- a/pkg/service_internal/mapfixes.go +++ b/pkg/service_internal/mapfixes.go @@ -74,7 +74,7 @@ func (svc *Service) UpdateMapfixValidatedModel(ctx context.Context, params inter return nil } -// ActionMapfixValidate invokes actionMapfixValidate operation. +// ActionMapfixSubmitted invokes actionMapfixSubmitted operation. // // Role Validator changes status from Submitting -> Submitted. // @@ -113,6 +113,46 @@ func (svc *Service) ActionMapfixSubmitted(ctx context.Context, params internal.A return nil } +// ActionMapfixRequestChanges implements actionMapfixRequestChanges operation. +// +// (Internal endpoint) Role Validator changes status from Submitting -> RequestChanges. +// +// POST /mapfixes/{MapfixID}/status/validator-request-changes +func (svc *Service) ActionMapfixRequestChanges(ctx context.Context, params internal.ActionMapfixRequestChangesParams) error { + // transaction + target_status := model.MapfixStatusChangesRequested + smap := datastore.Optional() + smap.Add("status_id", target_status) + smap.Add("status_message", params.StatusMessage) + err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitting}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: ValidtorUserID, + ResourceType: model.ResourceMapfix, + ResourceID: params.MapfixID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil +} + // ActionMapfixValidate invokes actionMapfixValidate operation. // // Role Validator changes status from Validating -> Validated. diff --git a/pkg/service_internal/submissions.go b/pkg/service_internal/submissions.go index 3250ca7..3acb264 100644 --- a/pkg/service_internal/submissions.go +++ b/pkg/service_internal/submissions.go @@ -73,7 +73,7 @@ func (svc *Service) UpdateSubmissionValidatedModel(ctx context.Context, params i return nil } -// ActionSubmissionValidate invokes actionSubmissionValidate operation. +// ActionSubmissionSubmitted invokes actionSubmissionSubmitted operation. // // Role Validator changes status from Submitting -> Submitted. // @@ -112,6 +112,46 @@ func (svc *Service) ActionSubmissionSubmitted(ctx context.Context, params intern return nil } +// ActionSubmissionRequestChanges implements actionSubmissionRequestChanges operation. +// +// (Internal endpoint) Role Validator changes status from Submitting -> RequestChanges. +// +// POST /submissions/{SubmissionID}/status/validator-request-changes +func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params internal.ActionSubmissionRequestChangesParams) error { + // transaction + target_status := model.SubmissionStatusChangesRequested + smap := datastore.Optional() + smap.Add("status_id", target_status) + smap.Add("status_message", params.StatusMessage) + err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitting}, smap) + if err != nil { + return err + } + + event_data := model.AuditEventDataAction{ + TargetStatus: uint32(target_status), + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: ValidtorUserID, + ResourceType: model.ResourceSubmission, + ResourceID: params.SubmissionID, + EventType: model.AuditEventTypeAction, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil +} + // ActionSubmissionValidate invokes actionSubmissionValidate operation. // // Role Validator changes status from Validating -> Validated. -- 2.49.1 From 1380a00872195715791648a3971da0ac7722f4ee Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 9 Apr 2025 19:24:30 -0700 Subject: [PATCH 370/454] submissions: receive asset version --- pkg/service_internal/mapfixes.go | 1 + pkg/service_internal/submissions.go | 1 + 2 files changed, 2 insertions(+) diff --git a/pkg/service_internal/mapfixes.go b/pkg/service_internal/mapfixes.go index 49ef1b4..8d8b576 100644 --- a/pkg/service_internal/mapfixes.go +++ b/pkg/service_internal/mapfixes.go @@ -84,6 +84,7 @@ func (svc *Service) ActionMapfixSubmitted(ctx context.Context, params internal.A target_status := model.MapfixStatusSubmitted smap := datastore.Optional() smap.Add("status_id", target_status) + smap.Add("asset_version", params.ModelVersion) err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitting}, smap) if err != nil { return err diff --git a/pkg/service_internal/submissions.go b/pkg/service_internal/submissions.go index 3acb264..5b46efd 100644 --- a/pkg/service_internal/submissions.go +++ b/pkg/service_internal/submissions.go @@ -83,6 +83,7 @@ func (svc *Service) ActionSubmissionSubmitted(ctx context.Context, params intern target_status := model.SubmissionStatusSubmitted smap := datastore.Optional() smap.Add("status_id", target_status) + smap.Add("asset_version", params.ModelVersion) err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitting}, smap) if err != nil { return err -- 2.49.1 From f1fd826c6273853d3422ae41a03729f10352ab22 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 8 Apr 2025 13:53:41 -0700 Subject: [PATCH 371/454] submissions-api: implement validator-submitted endpoint --- validation/api/src/internal.rs | 12 ++++++++++++ validation/api/src/types.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/validation/api/src/internal.rs b/validation/api/src/internal.rs index 19d3c4e..a281dcc 100644 --- a/validation/api/src/internal.rs +++ b/validation/api/src/internal.rs @@ -162,6 +162,12 @@ impl Context{ .json().await.map_err(Error::ReqwestJson) } // simple submission endpoints + action!("submissions",action_submission_request_changes,config,ActionSubmissionRequestChangesRequest,"status/validator-request-changes",config.SubmissionID, + ("StatusMessage",config.StatusMessage.as_str()) + ); + action!("submissions",action_submission_submitted,config,ActionSubmissionSubmittedRequest,"status/validator-submitted",config.SubmissionID, + ("ModelVersion",config.ModelVersion.to_string().as_str()) + ); action!("submissions",action_submission_validated,config,SubmissionID,"status/validator-validated",config.0,); action!("submissions",update_submission_validated_model,config,UpdateSubmissionModelRequest,"validated-model",config.SubmissionID, ("ValidatedModelID",config.ModelID.to_string().as_str()) @@ -185,6 +191,12 @@ impl Context{ .json().await.map_err(Error::ReqwestJson) } // simple mapfixes endpoints + action!("mapfixes",action_mapfix_request_changes,config,ActionMapfixRequestChangesRequest,"status/validator-request-changes",config.MapfixID, + ("StatusMessage",config.StatusMessage.as_str()) + ); + action!("mapfixes",action_mapfix_submitted,config,ActionMapfixSubmittedRequest,"status/validator-submitted",config.MapfixID, + ("ModelVersion",config.ModelVersion.to_string().as_str()) + ); action!("mapfixes",action_mapfix_validated,config,MapfixID,"status/validator-validated",config.0,); action!("mapfixes",update_mapfix_validated_model,config,UpdateMapfixModelRequest,"validated-model",config.MapfixID, ("ValidatedModelID",config.ModelID.to_string().as_str()) diff --git a/validation/api/src/types.rs b/validation/api/src/types.rs index 580a97e..9ff38bf 100644 --- a/validation/api/src/types.rs +++ b/validation/api/src/types.rs @@ -222,6 +222,20 @@ pub struct UpdateSubmissionModelRequest{ pub ModelVersion:u64, } +#[allow(nonstandard_style)] +#[derive(Clone,Debug)] +pub struct ActionSubmissionSubmittedRequest{ + pub SubmissionID:i64, + pub ModelVersion:u64, +} + +#[allow(nonstandard_style)] +#[derive(Clone,Debug)] +pub struct ActionSubmissionRequestChangesRequest{ + pub SubmissionID:i64, + pub StatusMessage:String, +} + #[allow(nonstandard_style)] #[derive(Clone,Debug)] pub struct ActionSubmissionUploadedRequest{ @@ -247,6 +261,20 @@ pub struct UpdateMapfixModelRequest{ pub ModelVersion:u64, } +#[allow(nonstandard_style)] +#[derive(Clone,Debug)] +pub struct ActionMapfixSubmittedRequest{ + pub MapfixID:i64, + pub ModelVersion:u64, +} + +#[allow(nonstandard_style)] +#[derive(Clone,Debug)] +pub struct ActionMapfixRequestChangesRequest{ + pub MapfixID:i64, + pub StatusMessage:String, +} + #[allow(nonstandard_style)] #[derive(Clone,Debug)] pub struct ActionMapfixUploadedRequest{ -- 2.49.1 From de0cf37918a01958bec985c9875231490151de14 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Mon, 7 Apr 2025 18:13:39 -0700 Subject: [PATCH 372/454] validator: add heck + lazy_regex deps --- Cargo.lock | 31 +++++++++++++++++++++++++++++++ validation/Cargo.toml | 2 ++ 2 files changed, 33 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 515368b..380ffd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -601,6 +601,12 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "http" version = "1.3.1" @@ -908,6 +914,29 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy-regex" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60c7310b93682b36b98fa7ea4de998d3463ccbebd94d935d6b48ba5b6ffa7126" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba01db5ef81e17eb10a5e0f2109d1b3a3e29bac3070fdbd7d156bf7dbd206a1" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -963,6 +992,8 @@ version = "0.1.1" dependencies = [ "async-nats", "futures", + "heck", + "lazy-regex", "rbx_asset", "rbx_binary", "rbx_dom_weak", diff --git a/validation/Cargo.toml b/validation/Cargo.toml index e30338d..5f65441 100644 --- a/validation/Cargo.toml +++ b/validation/Cargo.toml @@ -16,3 +16,5 @@ serde = { version = "1.0.215", features = ["derive"] } serde_json = "1.0.133" siphasher = "1.0.1" tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread", "signal"] } +heck = "0.5.0" +lazy-regex = "3.4.1" -- 2.49.1 From 95bfb87c6ec04feaa4b4d7ffe321baeb3a55b2d7 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Mon, 7 Apr 2025 16:20:06 -0700 Subject: [PATCH 373/454] validator: implement map checks --- validation/src/check.rs | 315 +++++++++++++++++++++++++++++ validation/src/check_mapfix.rs | 57 ++++++ validation/src/check_submission.rs | 57 ++++++ validation/src/main.rs | 3 + validation/src/message_handler.rs | 4 + validation/src/nats_types.rs | 14 ++ 6 files changed, 450 insertions(+) create mode 100644 validation/src/check.rs create mode 100644 validation/src/check_mapfix.rs create mode 100644 validation/src/check_submission.rs diff --git a/validation/src/check.rs b/validation/src/check.rs new file mode 100644 index 0000000..87cdcc6 --- /dev/null +++ b/validation/src/check.rs @@ -0,0 +1,315 @@ +use crate::download::download_asset_version; +use crate::rbx_util::{class_is_a,get_mapinfo,get_root_instance,read_dom,MapInfo,ReadDomError}; + +use heck::{ToSnakeCase,ToTitleCase}; + +#[allow(dead_code)] +#[derive(Debug)] +pub enum Error{ + ModelInfoDownload(rbx_asset::cloud::GetError), + CreatorTypeMustBeUser, + ParseUserID(core::num::ParseIntError), + ParseModelVersion(core::num::ParseIntError), + Download(crate::download::Error), + ModelFileDecode(ReadDomError), +} +impl std::fmt::Display for Error{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + write!(f,"{self:?}") + } +} +impl std::error::Error for Error{} + +#[allow(nonstandard_style)] +pub struct CheckRequest{ + pub ModelID:u64, +} + +impl From for CheckRequest{ + fn from(value:crate::nats_types::CheckMapfixRequest)->Self{ + Self{ + ModelID:value.ModelID, + } + } +} +impl From for CheckRequest{ + fn from(value:crate::nats_types::CheckSubmissionRequest)->Self{ + Self{ + ModelID:value.ModelID, + } + } +} + +#[derive(Default)] +pub struct CheckReport{ + // === METADATA CHECKS === + // the model must have exactly 1 root part (models uploaded to roblox can have multiple roots) + exactly_one_root:bool, + // the root must be of class Model + root_is_model:bool, + // the prefix of the model's name must match the game it was submitted for. bhop_ for bhop, and surf_ for surf + model_name_prefix_is_valid:bool, + // your model's name must match this regex: ^[a-z0-9_] + model_name_is_snake_case:bool, + // map must have a StringValue named Creator and DisplayName. additionally, they must both have a value + has_display_name:bool, + has_creator:bool, + // the display name must be capitalized + display_name_is_title_case:bool, + // you cannot have any Scripts or ModuleScripts that have the keyword 'getfenv" or 'require' + // you cannot have more than 50 duplicate scripts + + // === MODE CHECKS === + // Exactly one MapStart + exactly_one_mapstart:bool, + // At least one MapFinish + at_least_one_mapfinish:bool, + // Spawn0 or Spawn1 must exist + spawn1_exists:bool, + // No duplicate Spawn# + no_duplicate_spawns:bool, + // No duplicate WormholeOut# (duplicate WormholeIn# ok) + no_duplicate_wormhole_out:bool, +} +impl CheckReport{ + pub fn pass(&self)->bool{ + return self.exactly_one_root + &&self.root_is_model + &&self.model_name_prefix_is_valid + &&self.model_name_is_snake_case + &&self.has_display_name + &&self.has_creator + &&self.display_name_is_title_case + &&self.exactly_one_mapstart + &&self.at_least_one_mapfinish + &&self.spawn1_exists + &&self.no_duplicate_spawns + &&self.no_duplicate_wormhole_out + } +} +impl std::fmt::Display for CheckReport{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + write!(f, + "exactly_one_root={}\ +root_is_model={}\ +model_name_prefix_is_valid={}\ +model_name_is_snake_case={}\ +has_display_name={}\ +has_creator={}\ +display_name_is_title_case={}\ +exactly_one_mapstart={}\ +at_least_one_mapfinish={}\ +spawn1_exists={}\ +no_duplicate_spawns={}\ +no_duplicate_wormhole_out={}", + self.exactly_one_root, + self.root_is_model, + self.model_name_prefix_is_valid, + self.model_name_is_snake_case, + self.has_display_name, + self.has_creator, + self.display_name_is_title_case, + self.exactly_one_mapstart, + self.at_least_one_mapfinish, + self.spawn1_exists, + self.no_duplicate_spawns, + self.no_duplicate_wormhole_out, + ) + } +} + +enum Zone{ + Start(ModeID), + Finish(ModeID), +} + +#[derive(Debug,Hash,Eq,PartialEq)] +struct ModeID(u64); +impl ModeID{ + const MAIN:Self=Self(0); + const BONUS:Self=Self(1); +} +#[allow(dead_code)] +pub enum ZoneParseError{ + NoCaptures, + ParseInt(core::num::ParseIntError) +} +impl std::str::FromStr for Zone{ + type Err=ZoneParseError; + fn from_str(s:&str)->Result{ + match s{ + "MapStart"=>Ok(Self::Start(ModeID::MAIN)), + "MapFinish"=>Ok(Self::Finish(ModeID::MAIN)), + "BonusStart"=>Ok(Self::Start(ModeID::BONUS)), + "BonusFinish"=>Ok(Self::Start(ModeID::BONUS)), + other=>{ + let bonus_start_pattern=lazy_regex::lazy_regex!(r"^Bonus(\d+)Start$|^BonusStart(\d+)$"); + if let Some(captures)=bonus_start_pattern.captures(other){ + return Ok(Self::Start(ModeID(captures[1].parse().map_err(ZoneParseError::ParseInt)?))); + } + let bonus_finish_pattern=lazy_regex::lazy_regex!(r"^Bonus(\d+)Finish$|^BonusFinish(\d+)$"); + if let Some(captures)=bonus_finish_pattern.captures(other){ + return Ok(Self::Finish(ModeID(captures[1].parse().map_err(ZoneParseError::ParseInt)?))); + } + Err(ZoneParseError::NoCaptures) + } + } + } +} + + +#[derive(Debug,Hash,Eq,PartialEq)] +struct SpawnID(u64); +impl SpawnID{ + const FIRST:Self=Self(1); +} +#[derive(Debug,Hash,Eq,PartialEq)] +struct WormholeOutID(u64); + +struct Counts{ + mode_start_counts:std::collections::HashMap, + mode_finish_counts:std::collections::HashMap, + spawn_counts:std::collections::HashMap, + wormhole_out_counts:std::collections::HashMap, +} +impl Counts{ + fn new()->Self{ + Self{ + mode_start_counts:std::collections::HashMap::new(), + mode_finish_counts:std::collections::HashMap::new(), + spawn_counts:std::collections::HashMap::new(), + wormhole_out_counts:std::collections::HashMap::new(), + } + } +} + +pub fn check(dom:&rbx_dom_weak::WeakDom)->CheckReport{ + // empty report with all checks failed + let mut report=CheckReport::default(); + + // extract the root instance, otherwise immediately return + let Ok(model_instance)=get_root_instance(&dom)else{ + return report; + }; + + report.exactly_one_root=true; + + if model_instance.class=="Model"{ + report.root_is_model=true; + } + if model_instance.name==model_instance.name.to_snake_case(){ + report.model_name_is_snake_case=true; + } + + // extract model info + let MapInfo{display_name,creator,game_id}=get_mapinfo(&dom,model_instance); + + // check DisplayName + if let Ok(display_name)=display_name{ + if !display_name.is_empty(){ + report.has_display_name=true; + if display_name==display_name.to_title_case(){ + report.display_name_is_title_case=true; + } + } + } + + // check Creator + if let Ok(creator)=creator{ + if !creator.is_empty(){ + report.has_creator=true; + } + } + + // check GameID + if game_id.is_ok(){ + report.model_name_prefix_is_valid=true; + } + + // === MODE CHECKS === + // count objects + let mut counts=Counts::new(); + for instance in dom.descendants_of(model_instance.referent()){ + if class_is_a(instance.class.as_str(),"BasePart"){ + // Zones + match instance.name.parse(){ + Ok(Zone::Start(mode_id))=>*counts.mode_start_counts.entry(mode_id).or_insert(0)+=1, + Ok(Zone::Finish(mode_id))=>*counts.mode_finish_counts.entry(mode_id).or_insert(0)+=1, + _=>(), + } + // Spawns + let spawn_pattern=lazy_regex::lazy_regex!(r"^Spawn(\d+)$"); + if let Some(captures)=spawn_pattern.captures(instance.name.as_str()){ + if let Ok(spawn_id)=captures[1].parse(){ + *counts.spawn_counts.entry(SpawnID(spawn_id)).or_insert(0)+=1; + } + } + // WormholeOuts + let wormhole_out_pattern=lazy_regex::lazy_regex!(r"^WormholeOut(\d+)$"); + if let Some(captures)=wormhole_out_pattern.captures(instance.name.as_str()){ + if let Ok(wormhole_out_id)=captures[1].parse(){ + *counts.wormhole_out_counts.entry(WormholeOutID(wormhole_out_id)).or_insert(0)+=1; + } + } + } + } + + // MapStart must exist && there must be exactly one of any bonus start zones. + if counts.mode_start_counts.get(&ModeID::MAIN)==Some(&1) + &&counts.mode_start_counts.iter().all(|(_,&c)|c==1){ + report.exactly_one_mapstart=true; + } + // iterate over start zones + if counts.mode_start_counts.iter().all(|(mode_id,_)| + // ensure that at least one end zone exists with the same mode id + counts.mode_finish_counts.get(mode_id).is_some_and(|&num|0Result{ + // discover asset creator and latest version + let info=self.cloud_context.get_asset_info( + rbx_asset::cloud::GetAssetLatestRequest{asset_id:check_info.ModelID} + ).await.map_err(Error::ModelInfoDownload)?; + + // reject models created by a group + let rbx_asset::cloud::Creator::userId(_user_id_string)=info.creationContext.creator else{ + return Err(Error::CreatorTypeMustBeUser); + }; + + // parse model version string + let version=info.revisionId.parse().map_err(Error::ParseModelVersion)?; + + let model_data=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{ + asset_id:check_info.ModelID, + version, + }).await.map_err(Error::Download)?; + + // decode dom (slow!) + let dom=read_dom(std::io::Cursor::new(model_data)).map_err(Error::ModelFileDecode)?; + + let report=check(&dom); + + Ok(CheckReportAndVersion{report,version}) + } +} diff --git a/validation/src/check_mapfix.rs b/validation/src/check_mapfix.rs new file mode 100644 index 0000000..b261f4e --- /dev/null +++ b/validation/src/check_mapfix.rs @@ -0,0 +1,57 @@ +use crate::check::CheckReportAndVersion; +use crate::nats_types::CheckMapfixRequest; + +#[allow(dead_code)] +#[derive(Debug)] +pub enum Error{ + Check(crate::check::Error), + ApiActionMapfixCheck(submissions_api::Error), +} +impl std::fmt::Display for Error{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + write!(f,"{self:?}") + } +} +impl std::error::Error for Error{} + +impl crate::message_handler::MessageHandler{ + pub async fn check_mapfix(&self,check_info:CheckMapfixRequest)->Result<(),Error>{ + let mapfix_id=check_info.MapfixID; + let check_result=self.check_inner(check_info.into()).await; + + // update the mapfix depending on the result + match check_result{ + Ok(CheckReportAndVersion{report,version})=>{ + if report.pass(){ + // update the mapfix model status to submitted + self.api.action_mapfix_submitted( + submissions_api::types::ActionMapfixSubmittedRequest{ + MapfixID:mapfix_id, + ModelVersion:version, + } + ).await.map_err(Error::ApiActionMapfixCheck)?; + }else{ + // update the mapfix model status to request changes + self.api.action_mapfix_request_changes( + submissions_api::types::ActionMapfixRequestChangesRequest{ + MapfixID:mapfix_id, + StatusMessage:report.to_string(), + } + ).await.map_err(Error::ApiActionMapfixCheck)?; + } + }, + Err(e)=>{ + // TODO: report the error + // update the mapfix model status to request changes + self.api.action_mapfix_request_changes( + submissions_api::types::ActionMapfixRequestChangesRequest{ + MapfixID:mapfix_id, + StatusMessage:e.to_string(), + } + ).await.map_err(Error::ApiActionMapfixCheck)?; + }, + } + + Ok(()) + } +} diff --git a/validation/src/check_submission.rs b/validation/src/check_submission.rs new file mode 100644 index 0000000..12f18e1 --- /dev/null +++ b/validation/src/check_submission.rs @@ -0,0 +1,57 @@ +use crate::check::CheckReportAndVersion; +use crate::nats_types::CheckSubmissionRequest; + +#[allow(dead_code)] +#[derive(Debug)] +pub enum Error{ + Check(crate::check::Error), + ApiActionSubmissionCheck(submissions_api::Error), +} +impl std::fmt::Display for Error{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + write!(f,"{self:?}") + } +} +impl std::error::Error for Error{} + +impl crate::message_handler::MessageHandler{ + pub async fn check_submission(&self,check_info:CheckSubmissionRequest)->Result<(),Error>{ + let submission_id=check_info.SubmissionID; + let check_result=self.check_inner(check_info.into()).await; + + // update the submission depending on the result + match check_result{ + Ok(CheckReportAndVersion{report,version})=>{ + if report.pass(){ + // update the submission model status to submitted + self.api.action_submission_submitted( + submissions_api::types::ActionSubmissionSubmittedRequest{ + SubmissionID:submission_id, + ModelVersion:version, + } + ).await.map_err(Error::ApiActionSubmissionCheck)?; + }else{ + // update the submission model status to request changes + self.api.action_submission_request_changes( + submissions_api::types::ActionSubmissionRequestChangesRequest{ + SubmissionID:submission_id, + StatusMessage:report.to_string(), + } + ).await.map_err(Error::ApiActionSubmissionCheck)?; + } + }, + Err(e)=>{ + // TODO: report the error + // update the submission model status to request changes + self.api.action_submission_request_changes( + submissions_api::types::ActionSubmissionRequestChangesRequest{ + SubmissionID:submission_id, + StatusMessage:e.to_string(), + } + ).await.map_err(Error::ApiActionSubmissionCheck)?; + }, + } + + Ok(()) + } +} diff --git a/validation/src/main.rs b/validation/src/main.rs index 2b91fdc..801ea3f 100644 --- a/validation/src/main.rs +++ b/validation/src/main.rs @@ -5,6 +5,9 @@ mod message_handler; mod nats_types; mod types; mod download; +mod check; +mod check_mapfix; +mod check_submission; mod create; mod create_mapfix; mod create_submission; diff --git a/validation/src/message_handler.rs b/validation/src/message_handler.rs index 43a6a6d..fc30782 100644 --- a/validation/src/message_handler.rs +++ b/validation/src/message_handler.rs @@ -7,6 +7,8 @@ pub enum HandleMessageError{ UnknownSubject(String), CreateMapfix(submissions_api::Error), CreateSubmission(submissions_api::Error), + CheckMapfix(crate::check_mapfix::Error), + CheckSubmission(crate::check_submission::Error), UploadMapfix(crate::upload_mapfix::Error), UploadSubmission(crate::upload_submission::Error), ValidateMapfix(crate::validate_mapfix::Error), @@ -52,6 +54,8 @@ impl MessageHandler{ match message.subject.as_str(){ "maptest.mapfixes.create"=>self.create_mapfix(from_slice(&message.payload)?).await.map_err(HandleMessageError::CreateMapfix), "maptest.submissions.create"=>self.create_submission(from_slice(&message.payload)?).await.map_err(HandleMessageError::CreateSubmission), + "maptest.mapfixes.check"=>self.check_mapfix(from_slice(&message.payload)?).await.map_err(HandleMessageError::CheckMapfix), + "maptest.submissions.check"=>self.check_submission(from_slice(&message.payload)?).await.map_err(HandleMessageError::CheckSubmission), "maptest.mapfixes.upload"=>self.upload_mapfix(from_slice(&message.payload)?).await.map_err(HandleMessageError::UploadMapfix), "maptest.submissions.upload"=>self.upload_submission(from_slice(&message.payload)?).await.map_err(HandleMessageError::UploadSubmission), "maptest.mapfixes.validate"=>self.validate_mapfix(from_slice(&message.payload)?).await.map_err(HandleMessageError::ValidateMapfix), diff --git a/validation/src/nats_types.rs b/validation/src/nats_types.rs index 1401f83..a1126ae 100644 --- a/validation/src/nats_types.rs +++ b/validation/src/nats_types.rs @@ -20,6 +20,20 @@ pub struct CreateMapfixRequest{ pub TargetAssetID:u64, } +#[allow(nonstandard_style)] +#[derive(serde::Deserialize)] +pub struct CheckSubmissionRequest{ + pub SubmissionID:i64, + pub ModelID:u64, +} + +#[allow(nonstandard_style)] +#[derive(serde::Deserialize)] +pub struct CheckMapfixRequest{ + pub MapfixID:i64, + pub ModelID:u64, +} + #[allow(nonstandard_style)] #[derive(serde::Deserialize)] pub struct ValidateSubmissionRequest{ -- 2.49.1 From d1a70509b73343e2cc4fe4e738eebe9518879f0d Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 9 Apr 2025 19:39:33 -0700 Subject: [PATCH 374/454] submissions: implement map checks --- pkg/model/nats.go | 11 +++++++++++ pkg/service/mapfixes.go | 17 ++++++++++++++++- pkg/service/submissions.go | 17 ++++++++++++++++- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/pkg/model/nats.go b/pkg/model/nats.go index 7a69450..0808a89 100644 --- a/pkg/model/nats.go +++ b/pkg/model/nats.go @@ -17,6 +17,17 @@ type CreateMapfixRequest struct { TargetAssetID uint64 } + +type CheckSubmissionRequest struct{ + SubmissionID int64 + ModelID uint64 +} + +type CheckMapfixRequest struct{ + MapfixID int64 + ModelID uint64 +} + type ValidateSubmissionRequest struct { // submission_id is passed back in the response message SubmissionID int64 diff --git a/pkg/service/mapfixes.go b/pkg/service/mapfixes.go index 18207f2..20529fd 100644 --- a/pkg/service/mapfixes.go +++ b/pkg/service/mapfixes.go @@ -490,7 +490,7 @@ func (svc *Service) ActionMapfixTriggerSubmit(ctx context.Context, params api.Ac } // transaction - target_status := model.MapfixStatusSubmitted + target_status := model.MapfixStatusSubmitting smap := datastore.Optional() smap.Add("status_id", target_status) err = svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUnderConstruction, model.MapfixStatusChangesRequested}, smap) @@ -498,6 +498,21 @@ func (svc *Service) ActionMapfixTriggerSubmit(ctx context.Context, params api.Ac return err } + validate_request := model.CheckMapfixRequest{ + MapfixID: mapfix.ID, + ModelID: mapfix.AssetID, + } + + j, err := json.Marshal(validate_request) + if err != nil { + return err + } + + _, err = svc.Nats.Publish("maptest.mapfixes.check", []byte(j)) + if err != nil { + return err + } + event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } diff --git a/pkg/service/submissions.go b/pkg/service/submissions.go index c81c24a..3487fac 100644 --- a/pkg/service/submissions.go +++ b/pkg/service/submissions.go @@ -511,7 +511,7 @@ func (svc *Service) ActionSubmissionTriggerSubmit(ctx context.Context, params ap } // transaction - target_status := model.SubmissionStatusSubmitted + target_status := model.SubmissionStatusSubmitting smap := datastore.Optional() smap.Add("status_id", target_status) err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUnderConstruction, model.SubmissionStatusChangesRequested}, smap) @@ -519,6 +519,21 @@ func (svc *Service) ActionSubmissionTriggerSubmit(ctx context.Context, params ap return err } + validate_request := model.CheckSubmissionRequest{ + SubmissionID: submission.ID, + ModelID: submission.AssetID, + } + + j, err := json.Marshal(validate_request) + if err != nil { + return err + } + + _, err = svc.Nats.Publish("maptest.submissions.check", []byte(j)) + if err != nil { + return err + } + event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } -- 2.49.1 From 4f586c6176e5ba29820fb6c2ba17c38cd8059c35 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 9 Apr 2025 19:55:38 -0700 Subject: [PATCH 375/454] web: add reset submit button --- web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx | 4 ++++ web/src/app/submissions/[submissionId]/_reviewButtons.tsx | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx b/web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx index c9138c0..b695f45 100644 --- a/web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx +++ b/web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx @@ -10,6 +10,7 @@ interface ReviewAction { const ReviewActions = { Submit: {name:"Submit",action:"trigger-submit"} as ReviewAction, + ResetSubmitting: {name:"Reset Submitting (fix softlocked status)",action:"reset-submitting"} as ReviewAction, Revoke: {name:"Revoke",action:"revoke"} as ReviewAction, Accept: {name:"Accept",action:"trigger-validate"} as ReviewAction, Reject: {name:"Reject",action:"reject"} as ReviewAction, @@ -112,6 +113,9 @@ export default function ReviewButtons(props: ReviewId) { if ([MapfixStatus.Submitted, MapfixStatus.ChangesRequested].includes(mapfixStatus!)) { visibleButtons.push({ action: ReviewActions.Revoke, color: "info", mapfixId }); } + if (mapfixStatus === MapfixStatus.Submitting) { + visibleButtons.push({ action: ReviewActions.ResetSubmitting, color: "error", mapfixId }); + } } if (roles&RolesConstants.MapfixReview) { diff --git a/web/src/app/submissions/[submissionId]/_reviewButtons.tsx b/web/src/app/submissions/[submissionId]/_reviewButtons.tsx index aa68897..2ac0ad8 100644 --- a/web/src/app/submissions/[submissionId]/_reviewButtons.tsx +++ b/web/src/app/submissions/[submissionId]/_reviewButtons.tsx @@ -10,6 +10,7 @@ interface ReviewAction { const ReviewActions = { Submit: {name:"Submit",action:"trigger-submit"} as ReviewAction, + ResetSubmitting: {name:"Reset Submitting (fix softlocked status)",action:"reset-submitting"} as ReviewAction, Revoke: {name:"Revoke",action:"revoke"} as ReviewAction, Accept: {name:"Accept",action:"trigger-validate"} as ReviewAction, Reject: {name:"Reject",action:"reject"} as ReviewAction, @@ -112,6 +113,9 @@ export default function ReviewButtons(props: ReviewId) { if ([SubmissionStatus.Submitted, SubmissionStatus.ChangesRequested].includes(submissionStatus!)) { visibleButtons.push({ action: ReviewActions.Revoke, color: "info", submissionId }); } + if (submissionStatus === SubmissionStatus.Submitting) { + visibleButtons.push({ action: ReviewActions.ResetSubmitting, color: "error", submissionId }); + } } if (roles&RolesConstants.SubmissionReview) { -- 2.49.1 From 19b8f7b7a2e0ab63ee6310db68d889383b289a36 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 9 Apr 2025 20:37:58 -0700 Subject: [PATCH 376/454] validator: use newlines in check report --- validation/src/check.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/validation/src/check.rs b/validation/src/check.rs index 87cdcc6..03c384b 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -90,17 +90,17 @@ impl CheckReport{ impl std::fmt::Display for CheckReport{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ write!(f, - "exactly_one_root={}\ -root_is_model={}\ -model_name_prefix_is_valid={}\ -model_name_is_snake_case={}\ -has_display_name={}\ -has_creator={}\ -display_name_is_title_case={}\ -exactly_one_mapstart={}\ -at_least_one_mapfinish={}\ -spawn1_exists={}\ -no_duplicate_spawns={}\ + "exactly_one_root={}\n +root_is_model={}\n +model_name_prefix_is_valid={}\n +model_name_is_snake_case={}\n +has_display_name={}\n +has_creator={}\n +display_name_is_title_case={}\n +exactly_one_mapstart={}\n +at_least_one_mapfinish={}\n +spawn1_exists={}\n +no_duplicate_spawns={}\n no_duplicate_wormhole_out={}", self.exactly_one_root, self.root_is_model, -- 2.49.1 From 60b6d3037902a10d4b863777933f770e22d24dd2 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 9 Apr 2025 20:53:25 -0700 Subject: [PATCH 377/454] validator: fix map check bug --- validation/src/check.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validation/src/check.rs b/validation/src/check.rs index 03c384b..66962e7 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -141,7 +141,7 @@ impl std::str::FromStr for Zone{ "MapStart"=>Ok(Self::Start(ModeID::MAIN)), "MapFinish"=>Ok(Self::Finish(ModeID::MAIN)), "BonusStart"=>Ok(Self::Start(ModeID::BONUS)), - "BonusFinish"=>Ok(Self::Start(ModeID::BONUS)), + "BonusFinish"=>Ok(Self::Finish(ModeID::BONUS)), other=>{ let bonus_start_pattern=lazy_regex::lazy_regex!(r"^Bonus(\d+)Start$|^BonusStart(\d+)$"); if let Some(captures)=bonus_start_pattern.captures(other){ -- 2.49.1 From 03519e9337b1189f400bbb709d57f88d8ac154e1 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 9 Apr 2025 20:58:51 -0700 Subject: [PATCH 378/454] validator: marginally improve map check clarity --- validation/src/check.rs | 95 +++++++++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 36 deletions(-) diff --git a/validation/src/check.rs b/validation/src/check.rs index 66962e7..5ee908b 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -40,51 +40,74 @@ impl From for CheckRequest{ } } +#[derive(Default)] +pub enum Check{ + Pass, + #[default] + Fail, +} +impl Check{ + fn pass(&self)->bool{ + match self{ + Check::Pass=>true, + Check::Fail=>false, + } + } +} +impl std::fmt::Display for Check{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + match self{ + Check::Pass=>write!(f,"passed"), + Check::Fail=>write!(f,"failed"), + } + } +} + #[derive(Default)] pub struct CheckReport{ // === METADATA CHECKS === // the model must have exactly 1 root part (models uploaded to roblox can have multiple roots) - exactly_one_root:bool, + exactly_one_root:Check, // the root must be of class Model - root_is_model:bool, + root_is_model:Check, // the prefix of the model's name must match the game it was submitted for. bhop_ for bhop, and surf_ for surf - model_name_prefix_is_valid:bool, + model_name_prefix_is_valid:Check, // your model's name must match this regex: ^[a-z0-9_] - model_name_is_snake_case:bool, + model_name_is_snake_case:Check, // map must have a StringValue named Creator and DisplayName. additionally, they must both have a value - has_display_name:bool, - has_creator:bool, + has_display_name:Check, + has_creator:Check, // the display name must be capitalized - display_name_is_title_case:bool, + display_name_is_title_case:Check, // you cannot have any Scripts or ModuleScripts that have the keyword 'getfenv" or 'require' // you cannot have more than 50 duplicate scripts // === MODE CHECKS === // Exactly one MapStart - exactly_one_mapstart:bool, + exactly_one_mapstart:Check, // At least one MapFinish - at_least_one_mapfinish:bool, + at_least_one_mapfinish:Check, // Spawn0 or Spawn1 must exist - spawn1_exists:bool, + spawn1_exists:Check, // No duplicate Spawn# - no_duplicate_spawns:bool, + no_duplicate_spawns:Check, // No duplicate WormholeOut# (duplicate WormholeIn# ok) - no_duplicate_wormhole_out:bool, + no_duplicate_wormhole_out:Check, } impl CheckReport{ pub fn pass(&self)->bool{ - return self.exactly_one_root - &&self.root_is_model - &&self.model_name_prefix_is_valid - &&self.model_name_is_snake_case - &&self.has_display_name - &&self.has_creator - &&self.display_name_is_title_case - &&self.exactly_one_mapstart - &&self.at_least_one_mapfinish - &&self.spawn1_exists - &&self.no_duplicate_spawns - &&self.no_duplicate_wormhole_out + return self.exactly_one_root.pass() + &&self.root_is_model.pass() + &&self.model_name_prefix_is_valid.pass() + &&self.model_name_is_snake_case.pass() + &&self.has_display_name.pass() + &&self.has_creator.pass() + &&self.display_name_is_title_case.pass() + &&self.exactly_one_mapstart.pass() + &&self.at_least_one_mapfinish.pass() + &&self.spawn1_exists.pass() + &&self.no_duplicate_spawns.pass() + &&self.no_duplicate_wormhole_out.pass() } } impl std::fmt::Display for CheckReport{ @@ -192,13 +215,13 @@ pub fn check(dom:&rbx_dom_weak::WeakDom)->CheckReport{ return report; }; - report.exactly_one_root=true; + report.exactly_one_root=Check::Pass; if model_instance.class=="Model"{ - report.root_is_model=true; + report.root_is_model=Check::Pass; } if model_instance.name==model_instance.name.to_snake_case(){ - report.model_name_is_snake_case=true; + report.model_name_is_snake_case=Check::Pass; } // extract model info @@ -207,9 +230,9 @@ pub fn check(dom:&rbx_dom_weak::WeakDom)->CheckReport{ // check DisplayName if let Ok(display_name)=display_name{ if !display_name.is_empty(){ - report.has_display_name=true; + report.has_display_name=Check::Pass; if display_name==display_name.to_title_case(){ - report.display_name_is_title_case=true; + report.display_name_is_title_case=Check::Pass; } } } @@ -217,13 +240,13 @@ pub fn check(dom:&rbx_dom_weak::WeakDom)->CheckReport{ // check Creator if let Ok(creator)=creator{ if !creator.is_empty(){ - report.has_creator=true; + report.has_creator=Check::Pass; } } // check GameID if game_id.is_ok(){ - report.model_name_prefix_is_valid=true; + report.model_name_prefix_is_valid=Check::Pass; } // === MODE CHECKS === @@ -257,24 +280,24 @@ pub fn check(dom:&rbx_dom_weak::WeakDom)->CheckReport{ // MapStart must exist && there must be exactly one of any bonus start zones. if counts.mode_start_counts.get(&ModeID::MAIN)==Some(&1) &&counts.mode_start_counts.iter().all(|(_,&c)|c==1){ - report.exactly_one_mapstart=true; + report.exactly_one_mapstart=Check::Pass; } // iterate over start zones if counts.mode_start_counts.iter().all(|(mode_id,_)| // ensure that at least one end zone exists with the same mode id counts.mode_finish_counts.get(mode_id).is_some_and(|&num|0 Date: Thu, 10 Apr 2025 17:21:45 -0700 Subject: [PATCH 379/454] update deps --- Cargo.lock | 121 ++++++++++++++++++---------- validation/Cargo.toml | 2 +- validation/src/check.rs | 10 +-- validation/src/create.rs | 12 +-- validation/src/download.rs | 6 +- validation/src/rbx_util.rs | 23 ++---- validation/src/upload_mapfix.rs | 6 +- validation/src/upload_submission.rs | 6 +- validation/src/validator.rs | 4 +- 9 files changed, 111 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 380ffd3..c26c440 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -148,9 +148,9 @@ checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "blake3" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17679a8d69b6d7fd9cd9801a536cec9fa5e5970b69f9d4747f70b39b031f5e7" +checksum = "389a099b34312839e16420d499a9cad9650541715937ffbdd40d36f49e77eeb3" dependencies = [ "arrayref", "arrayvec", @@ -191,9 +191,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.17" +version = "1.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" +checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" dependencies = [ "shlex", ] @@ -320,9 +320,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", "serde", @@ -388,9 +388,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", @@ -410,9 +410,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "flate2" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", "miniz_oxide", @@ -702,9 +702,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" dependencies = [ "bytes", "futures-channel", @@ -712,6 +712,7 @@ dependencies = [ "http", "http-body", "hyper", + "libc", "pin-project-lite", "socket2", "tokio", @@ -721,9 +722,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.62" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -884,9 +885,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown", @@ -951,9 +952,9 @@ checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "linux-raw-sys" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" @@ -1030,9 +1031,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", ] @@ -1121,9 +1122,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl" -version = "0.10.71" +version = "0.10.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" +checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" dependencies = [ "bitflags 2.9.0", "cfg-if", @@ -1153,9 +1154,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.106" +version = "0.9.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" dependencies = [ "cc", "libc", @@ -1328,10 +1329,11 @@ dependencies = [ [[package]] name = "rbx_asset" -version = "0.4.3" +version = "0.4.4-pre2" source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" -checksum = "077e2a0b201a777dfd2ff822766ae7d0c8c3003206115da57f7bce15ee73cbc7" +checksum = "a32b98d3f303002faae783b5c1087e35c36a260b56470fd14a0606f35169ab35" dependencies = [ + "bytes", "chrono", "flate2", "reqwest", @@ -1544,9 +1546,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" dependencies = [ "bitflags 2.9.0", "errno", @@ -1557,9 +1559,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.25" +version = "0.23.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" dependencies = [ "once_cell", "ring", @@ -1797,15 +1799,15 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1972,9 +1974,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.44.1" +version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "bytes", @@ -2302,11 +2304,37 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.52.0" +version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ - "windows-targets 0.52.6", + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings 0.4.0", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2322,7 +2350,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ "windows-result", - "windows-strings", + "windows-strings 0.3.1", "windows-targets 0.53.0", ] @@ -2344,6 +2372,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -2513,9 +2550,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "xml-rs" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" +checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" [[package]] name = "yoke" diff --git a/validation/Cargo.toml b/validation/Cargo.toml index 5f65441..52cd553 100644 --- a/validation/Cargo.toml +++ b/validation/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" submissions-api = { path = "api", features = ["internal"], default-features = false, registry = "strafesnet" } async-nats = "0.40.0" futures = "0.3.31" -rbx_asset = { version = "0.4.3", registry = "strafesnet" } +rbx_asset = { version = "0.4.4-pre2", registry = "strafesnet" } rbx_binary = { version = "0.7.4", registry = "strafesnet"} rbx_dom_weak = { version = "2.9.0", registry = "strafesnet"} rbx_reflection_database = { version = "0.2.12", registry = "strafesnet"} diff --git a/validation/src/check.rs b/validation/src/check.rs index 5ee908b..0aee2b2 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -8,8 +8,6 @@ use heck::{ToSnakeCase,ToTitleCase}; pub enum Error{ ModelInfoDownload(rbx_asset::cloud::GetError), CreatorTypeMustBeUser, - ParseUserID(core::num::ParseIntError), - ParseModelVersion(core::num::ParseIntError), Download(crate::download::Error), ModelFileDecode(ReadDomError), } @@ -316,20 +314,20 @@ impl crate::message_handler::MessageHandler{ ).await.map_err(Error::ModelInfoDownload)?; // reject models created by a group - let rbx_asset::cloud::Creator::userId(_user_id_string)=info.creationContext.creator else{ + let rbx_asset::cloud::Creator::userId(_user_id)=info.creationContext.creator else{ return Err(Error::CreatorTypeMustBeUser); }; // parse model version string - let version=info.revisionId.parse().map_err(Error::ParseModelVersion)?; + let version=info.revisionId; - let model_data=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{ + let maybe_gzip=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{ asset_id:check_info.ModelID, version, }).await.map_err(Error::Download)?; // decode dom (slow!) - let dom=read_dom(std::io::Cursor::new(model_data)).map_err(Error::ModelFileDecode)?; + let dom=maybe_gzip.read_with(read_dom,read_dom).map_err(Error::ModelFileDecode)?; let report=check(&dom); diff --git a/validation/src/create.rs b/validation/src/create.rs index 9db62a8..c6ea948 100644 --- a/validation/src/create.rs +++ b/validation/src/create.rs @@ -6,8 +6,6 @@ use crate::rbx_util::{get_root_instance,get_mapinfo,read_dom,MapInfo,ReadDomErro pub enum Error{ CreatorTypeMustBeUser, ModelInfoDownload(rbx_asset::cloud::GetError), - ParseUserID(core::num::ParseIntError), - ParseModelVersion(core::num::ParseIntError), Download(crate::download::Error), ModelFileDecode(ReadDomError), GetRootInstance(GetRootInstanceError), @@ -40,22 +38,20 @@ impl crate::message_handler::MessageHandler{ ).await.map_err(Error::ModelInfoDownload)?; // reject models created by a group - let rbx_asset::cloud::Creator::userId(user_id_string)=info.creationContext.creator else{ + let rbx_asset::cloud::Creator::userId(user_id)=info.creationContext.creator else{ return Err(Error::CreatorTypeMustBeUser); }; - // parse user string and model version string - let user_id:u64=user_id_string.parse().map_err(Error::ParseUserID)?; - let asset_version=info.revisionId.parse().map_err(Error::ParseModelVersion)?; + let asset_version=info.revisionId; // download the map model - let model_data=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{ + let maybe_gzip=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{ asset_id:create_info.ModelID, version:asset_version, }).await.map_err(Error::Download)?; // decode dom (slow!) - let dom=read_dom(std::io::Cursor::new(model_data)).map_err(Error::ModelFileDecode)?; + let dom=maybe_gzip.read_with(read_dom,read_dom).map_err(Error::ModelFileDecode)?; // extract the root instance let model_instance=get_root_instance(&dom).map_err(Error::GetRootInstance)?; diff --git a/validation/src/download.rs b/validation/src/download.rs index 202be72..63f16ae 100644 --- a/validation/src/download.rs +++ b/validation/src/download.rs @@ -12,7 +12,7 @@ impl std::fmt::Display for Error{ } impl std::error::Error for Error{} -pub async fn download_asset_version(cloud_context:&rbx_asset::cloud::Context,request:rbx_asset::cloud::GetAssetVersionRequest)->Result,Error>{ +pub async fn download_asset_version(cloud_context:&rbx_asset::cloud::Context,request:rbx_asset::cloud::GetAssetVersionRequest)->Result{ // download the location of the map model let location=cloud_context.get_asset_version_location(request).await.map_err(Error::ModelLocationDownload)?; @@ -20,7 +20,7 @@ pub async fn download_asset_version(cloud_context:&rbx_asset::cloud::Context,req let location=location.location.ok_or(Error::NonFreeModel)?; // download the map model - let model_data=cloud_context.get_asset(&location).await.map_err(Error::ModelFileDownload)?; + let maybe_gzip=cloud_context.get_asset(&location).await.map_err(Error::ModelFileDownload)?; - Ok(model_data) + Ok(maybe_gzip) } diff --git a/validation/src/rbx_util.rs b/validation/src/rbx_util.rs index 4d49cd0..92f8fa4 100644 --- a/validation/src/rbx_util.rs +++ b/validation/src/rbx_util.rs @@ -5,8 +5,7 @@ pub enum ReadDomError{ Binary(rbx_binary::DecodeError), Xml(rbx_xml::DecodeError), Read(std::io::Error), - Seek(std::io::Error), - UnknownFormat([u8;8]), + UnknownFormat(Vec), } impl std::fmt::Display for ReadDomError{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ @@ -15,19 +14,13 @@ impl std::fmt::Display for ReadDomError{ } impl std::error::Error for ReadDomError{} -pub fn read_dom(mut input:R)->Result{ - let mut first_8=[0u8;8]; - std::io::Read::read_exact(&mut input,&mut first_8).map_err(ReadDomError::Read)?; - std::io::Seek::rewind(&mut input).map_err(ReadDomError::Seek)?; - match &first_8[0..4]{ - b"{ - match &first_8[4..8]{ - b"lox!"=>rbx_binary::from_reader(input).map_err(ReadDomError::Binary), - b"lox "=>rbx_xml::from_reader(input,rbx_xml::DecodeOptions::default()).map_err(ReadDomError::Xml), - _=>Err(ReadDomError::UnknownFormat(first_8)), - } - }, - _=>Err(ReadDomError::UnknownFormat(first_8)), +pub fn read_dom(input:R)->Result{ + let mut buf=std::io::BufReader::new(input); + let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadDomError::Read)?; + match peek.get(0..8){ + Some(b"rbx_binary::from_reader(buf).map_err(ReadDomError::Binary), + Some(b"rbx_xml::from_reader_default(buf).map_err(ReadDomError::Xml), + _=>Err(ReadDomError::UnknownFormat(peek.to_owned())), } } diff --git a/validation/src/upload_mapfix.rs b/validation/src/upload_mapfix.rs index b74e562..3e3e8d8 100644 --- a/validation/src/upload_mapfix.rs +++ b/validation/src/upload_mapfix.rs @@ -5,6 +5,7 @@ use crate::nats_types::UploadMapfixRequest; #[derive(Debug)] pub enum Error{ Download(crate::download::Error), + IO(std::io::Error), Json(serde_json::Error), Upload(rbx_asset::cookie::UploadError), ApiActionMapfixUploaded(submissions_api::Error), @@ -19,11 +20,14 @@ impl std::error::Error for Error{} impl crate::message_handler::MessageHandler{ pub async fn upload_mapfix(&self,upload_info:UploadMapfixRequest)->Result<(),Error>{ // download the map model - let model_data=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{ + let maybe_gzip=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{ asset_id:upload_info.ModelID, version:upload_info.ModelVersion, }).await.map_err(Error::Download)?; + // transparently handle gzipped models + let model_data=maybe_gzip.to_vec().map_err(Error::IO)?; + // upload the map to the strafesnet group let _upload_response=self.cookie_context.upload(rbx_asset::cookie::UploadRequest{ assetid:upload_info.TargetAssetID, diff --git a/validation/src/upload_submission.rs b/validation/src/upload_submission.rs index 9052433..bc07b75 100644 --- a/validation/src/upload_submission.rs +++ b/validation/src/upload_submission.rs @@ -5,6 +5,7 @@ use crate::nats_types::UploadSubmissionRequest; #[derive(Debug)] pub enum Error{ Download(crate::download::Error), + IO(std::io::Error), Json(serde_json::Error), Create(rbx_asset::cookie::CreateError), SystemTime(std::time::SystemTimeError), @@ -20,11 +21,14 @@ impl std::error::Error for Error{} impl crate::message_handler::MessageHandler{ pub async fn upload_submission(&self,upload_info:UploadSubmissionRequest)->Result<(),Error>{ // download the map model - let model_data=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{ + let maybe_gzip=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{ asset_id:upload_info.ModelID, version:upload_info.ModelVersion, }).await.map_err(Error::Download)?; + // transparently handle gzipped models + let model_data=maybe_gzip.to_vec().map_err(Error::IO)?; + // upload the map to the strafesnet group let upload_response=self.cookie_context.create(rbx_asset::cookie::CreateRequest{ name:upload_info.ModelName.clone(), diff --git a/validation/src/validator.rs b/validation/src/validator.rs index 1215ee4..28cda8b 100644 --- a/validation/src/validator.rs +++ b/validation/src/validator.rs @@ -91,13 +91,13 @@ impl From for ValidateRequest{ impl crate::message_handler::MessageHandler{ pub async fn validate_inner(&self,validate_info:ValidateRequest)->Result<(),Error>{ // download the map model - let model_data=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{ + let maybe_gzip=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{ asset_id:validate_info.ModelID, version:validate_info.ModelVersion, }).await.map_err(Error::Download)?; // decode dom (slow!) - let mut dom=read_dom(std::io::Cursor::new(model_data)).map_err(Error::ModelFileDecode)?; + let mut dom=maybe_gzip.read_with(read_dom,read_dom).map_err(Error::ModelFileDecode)?; /* VALIDATE MAP */ -- 2.49.1 From 12ca1b7dab39fc12ec50c04e548afde9614f10a4 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 9 Apr 2025 20:06:08 -0700 Subject: [PATCH 380/454] submissions: AuditEventTypeError --- pkg/model/audit_event.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/model/audit_event.go b/pkg/model/audit_event.go index 998f76d..53b0632 100644 --- a/pkg/model/audit_event.go +++ b/pkg/model/audit_event.go @@ -42,6 +42,12 @@ type AuditEventDataChangeName struct { NewName string `json:"new_name"` } +// Validator had an error +const AuditEventTypeError AuditEventType = 6 +type AuditEventDataError struct { + Error string `json:"error"` +} + type AuditEvent struct { ID int64 `gorm:"primaryKey"` CreatedAt time.Time -- 2.49.1 From 99d1b38535fef08da0b8a8a2e9fca4122efa63e2 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 9 Apr 2025 20:09:11 -0700 Subject: [PATCH 381/454] submissions: remove StatusMessage --- pkg/model/mapfix.go | 1 - pkg/model/submission.go | 1 - pkg/service/mapfixes.go | 3 --- pkg/service/submissions.go | 3 --- pkg/service_internal/mapfixes.go | 2 -- pkg/service_internal/submissions.go | 2 -- 6 files changed, 12 deletions(-) diff --git a/pkg/model/mapfix.go b/pkg/model/mapfix.go index 6c9d9a7..a296359 100644 --- a/pkg/model/mapfix.go +++ b/pkg/model/mapfix.go @@ -39,5 +39,4 @@ type Mapfix struct { Completed bool // Has this version of the map been completed at least once on maptest TargetAssetID uint64 // where to upload map fix. if the TargetAssetID is 0, it's a new map. StatusID MapfixStatus - StatusMessage string } diff --git a/pkg/model/submission.go b/pkg/model/submission.go index 8001697..4840e5e 100644 --- a/pkg/model/submission.go +++ b/pkg/model/submission.go @@ -40,5 +40,4 @@ type Submission struct { Completed bool // Has this version of the map been completed at least once on maptest UploadedAssetID uint64 // where to upload map fix. if the TargetAssetID is 0, it's a new map. StatusID SubmissionStatus - StatusMessage string } diff --git a/pkg/service/mapfixes.go b/pkg/service/mapfixes.go index 20529fd..0d37705 100644 --- a/pkg/service/mapfixes.go +++ b/pkg/service/mapfixes.go @@ -154,7 +154,6 @@ func (svc *Service) GetMapfix(ctx context.Context, params api.GetMapfixParams) ( Completed: mapfix.Completed, TargetAssetID: int64(mapfix.TargetAssetID), StatusID: int32(mapfix.StatusID), - StatusMessage: mapfix.StatusMessage, }, nil } @@ -573,7 +572,6 @@ func (svc *Service) ActionMapfixResetSubmitting(ctx context.Context, params api. target_status := model.MapfixStatusUnderConstruction smap := datastore.Optional() smap.Add("status_id", target_status) - smap.Add("status_message", "Manually forced reset") err = svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitting}, smap) if err != nil { return err @@ -975,7 +973,6 @@ func (svc *Service) ActionMapfixAccepted(ctx context.Context, params api.ActionM target_status := model.MapfixStatusAcceptedUnvalidated smap := datastore.Optional() smap.Add("status_id", target_status) - smap.Add("status_message", "Manually forced reset") err = svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, smap) if err != nil { return err diff --git a/pkg/service/submissions.go b/pkg/service/submissions.go index 3487fac..5af693f 100644 --- a/pkg/service/submissions.go +++ b/pkg/service/submissions.go @@ -143,7 +143,6 @@ func (svc *Service) GetSubmission(ctx context.Context, params api.GetSubmissionP Completed: submission.Completed, UploadedAssetID: api.NewOptInt64(int64(submission.UploadedAssetID)), StatusID: int32(submission.StatusID), - StatusMessage: submission.StatusMessage, }, nil } @@ -594,7 +593,6 @@ func (svc *Service) ActionSubmissionResetSubmitting(ctx context.Context, params target_status := model.SubmissionStatusUnderConstruction smap := datastore.Optional() smap.Add("status_id", target_status) - smap.Add("status_message", "Manually forced reset") err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitting}, smap) if err != nil { return err @@ -986,7 +984,6 @@ func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params api.Act target_status := model.SubmissionStatusAcceptedUnvalidated smap := datastore.Optional() smap.Add("status_id", target_status) - smap.Add("status_message", "Manually forced reset") err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap) if err != nil { return err diff --git a/pkg/service_internal/mapfixes.go b/pkg/service_internal/mapfixes.go index 8d8b576..7b2c906 100644 --- a/pkg/service_internal/mapfixes.go +++ b/pkg/service_internal/mapfixes.go @@ -124,7 +124,6 @@ func (svc *Service) ActionMapfixRequestChanges(ctx context.Context, params inter target_status := model.MapfixStatusChangesRequested smap := datastore.Optional() smap.Add("status_id", target_status) - smap.Add("status_message", params.StatusMessage) err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitting}, smap) if err != nil { return err @@ -176,7 +175,6 @@ func (svc *Service) ActionMapfixAccepted(ctx context.Context, params internal.Ac target_status := model.MapfixStatusAcceptedUnvalidated smap := datastore.Optional() smap.Add("status_id", target_status) - smap.Add("status_message", params.StatusMessage) err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, smap) if err != nil { return err diff --git a/pkg/service_internal/submissions.go b/pkg/service_internal/submissions.go index 5b46efd..647ec65 100644 --- a/pkg/service_internal/submissions.go +++ b/pkg/service_internal/submissions.go @@ -123,7 +123,6 @@ func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params i target_status := model.SubmissionStatusChangesRequested smap := datastore.Optional() smap.Add("status_id", target_status) - smap.Add("status_message", params.StatusMessage) err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitting}, smap) if err != nil { return err @@ -202,7 +201,6 @@ func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params interna target_status := model.SubmissionStatusAcceptedUnvalidated smap := datastore.Optional() smap.Add("status_id", target_status) - smap.Add("status_message", params.StatusMessage) err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap) if err != nil { return err -- 2.49.1 From cc7e8905802de72258b26a5188a4273ea5edd503 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 9 Apr 2025 20:09:22 -0700 Subject: [PATCH 382/454] openapi: change StatusMessage to ErrorMessage --- openapi-internal.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openapi-internal.yaml b/openapi-internal.yaml index 656cc4d..bb057c9 100644 --- a/openapi-internal.yaml +++ b/openapi-internal.yaml @@ -97,7 +97,7 @@ paths: - Mapfixes parameters: - $ref: '#/components/parameters/MapfixID' - - name: StatusMessage + - name: ErrorMessage in: query required: true schema: @@ -138,7 +138,7 @@ paths: - Mapfixes parameters: - $ref: '#/components/parameters/MapfixID' - - name: StatusMessage + - name: ErrorMessage in: query required: true schema: @@ -283,7 +283,7 @@ paths: - Submissions parameters: - $ref: '#/components/parameters/SubmissionID' - - name: StatusMessage + - name: ErrorMessage in: query required: true schema: @@ -324,7 +324,7 @@ paths: - Submissions parameters: - $ref: '#/components/parameters/SubmissionID' - - name: StatusMessage + - name: ErrorMessage in: query required: true schema: -- 2.49.1 From 1dabd216aaa7727794a10dd78f9c73062dc3ac90 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 9 Apr 2025 20:13:42 -0700 Subject: [PATCH 383/454] openapi: generate --- pkg/internal/oas_client_gen.go | 24 +++++----- pkg/internal/oas_handlers_gen.go | 16 +++---- pkg/internal/oas_parameters_gen.go | 72 +++++++++++++++--------------- 3 files changed, 56 insertions(+), 56 deletions(-) diff --git a/pkg/internal/oas_client_gen.go b/pkg/internal/oas_client_gen.go index 78d4f40..c6fce31 100644 --- a/pkg/internal/oas_client_gen.go +++ b/pkg/internal/oas_client_gen.go @@ -269,15 +269,15 @@ func (c *Client) sendActionMapfixAccepted(ctx context.Context, params ActionMapf stage = "EncodeQueryParams" q := uri.NewQueryEncoder() { - // Encode "StatusMessage" parameter. + // Encode "ErrorMessage" parameter. cfg := uri.QueryParameterEncodingConfig{ - Name: "StatusMessage", + Name: "ErrorMessage", Style: uri.QueryStyleForm, Explode: true, } if err := q.EncodeParam(cfg, func(e uri.Encoder) error { - return e.EncodeValue(conv.StringToString(params.StatusMessage)) + return e.EncodeValue(conv.StringToString(params.ErrorMessage)) }); err != nil { return res, errors.Wrap(err, "encode query") } @@ -378,15 +378,15 @@ func (c *Client) sendActionMapfixRequestChanges(ctx context.Context, params Acti stage = "EncodeQueryParams" q := uri.NewQueryEncoder() { - // Encode "StatusMessage" parameter. + // Encode "ErrorMessage" parameter. cfg := uri.QueryParameterEncodingConfig{ - Name: "StatusMessage", + Name: "ErrorMessage", Style: uri.QueryStyleForm, Explode: true, } if err := q.EncodeParam(cfg, func(e uri.Encoder) error { - return e.EncodeValue(conv.StringToString(params.StatusMessage)) + return e.EncodeValue(conv.StringToString(params.ErrorMessage)) }); err != nil { return res, errors.Wrap(err, "encode query") } @@ -887,15 +887,15 @@ func (c *Client) sendActionSubmissionAccepted(ctx context.Context, params Action stage = "EncodeQueryParams" q := uri.NewQueryEncoder() { - // Encode "StatusMessage" parameter. + // Encode "ErrorMessage" parameter. cfg := uri.QueryParameterEncodingConfig{ - Name: "StatusMessage", + Name: "ErrorMessage", Style: uri.QueryStyleForm, Explode: true, } if err := q.EncodeParam(cfg, func(e uri.Encoder) error { - return e.EncodeValue(conv.StringToString(params.StatusMessage)) + return e.EncodeValue(conv.StringToString(params.ErrorMessage)) }); err != nil { return res, errors.Wrap(err, "encode query") } @@ -996,15 +996,15 @@ func (c *Client) sendActionSubmissionRequestChanges(ctx context.Context, params stage = "EncodeQueryParams" q := uri.NewQueryEncoder() { - // Encode "StatusMessage" parameter. + // Encode "ErrorMessage" parameter. cfg := uri.QueryParameterEncodingConfig{ - Name: "StatusMessage", + Name: "ErrorMessage", Style: uri.QueryStyleForm, Explode: true, } if err := q.EncodeParam(cfg, func(e uri.Encoder) error { - return e.EncodeValue(conv.StringToString(params.StatusMessage)) + return e.EncodeValue(conv.StringToString(params.ErrorMessage)) }); err != nil { return res, errors.Wrap(err, "encode query") } diff --git a/pkg/internal/oas_handlers_gen.go b/pkg/internal/oas_handlers_gen.go index ff02638..e8fe7a4 100644 --- a/pkg/internal/oas_handlers_gen.go +++ b/pkg/internal/oas_handlers_gen.go @@ -129,9 +129,9 @@ func (s *Server) handleActionMapfixAcceptedRequest(args [1]string, argsEscaped b In: "path", }: params.MapfixID, { - Name: "StatusMessage", + Name: "ErrorMessage", In: "query", - }: params.StatusMessage, + }: params.ErrorMessage, }, Raw: r, } @@ -282,9 +282,9 @@ func (s *Server) handleActionMapfixRequestChangesRequest(args [1]string, argsEsc In: "path", }: params.MapfixID, { - Name: "StatusMessage", + Name: "ErrorMessage", In: "query", - }: params.StatusMessage, + }: params.ErrorMessage, }, Raw: r, } @@ -1039,9 +1039,9 @@ func (s *Server) handleActionSubmissionAcceptedRequest(args [1]string, argsEscap In: "path", }: params.SubmissionID, { - Name: "StatusMessage", + Name: "ErrorMessage", In: "query", - }: params.StatusMessage, + }: params.ErrorMessage, }, Raw: r, } @@ -1192,9 +1192,9 @@ func (s *Server) handleActionSubmissionRequestChangesRequest(args [1]string, arg In: "path", }: params.SubmissionID, { - Name: "StatusMessage", + Name: "ErrorMessage", In: "query", - }: params.StatusMessage, + }: params.ErrorMessage, }, Raw: r, } diff --git a/pkg/internal/oas_parameters_gen.go b/pkg/internal/oas_parameters_gen.go index d21e5e5..558c0ea 100644 --- a/pkg/internal/oas_parameters_gen.go +++ b/pkg/internal/oas_parameters_gen.go @@ -18,8 +18,8 @@ import ( // ActionMapfixAcceptedParams is parameters of actionMapfixAccepted operation. type ActionMapfixAcceptedParams struct { // The unique identifier for a submission. - MapfixID int64 - StatusMessage string + MapfixID int64 + ErrorMessage string } func unpackActionMapfixAcceptedParams(packed middleware.Parameters) (params ActionMapfixAcceptedParams) { @@ -32,10 +32,10 @@ func unpackActionMapfixAcceptedParams(packed middleware.Parameters) (params Acti } { key := middleware.ParameterKey{ - Name: "StatusMessage", + Name: "ErrorMessage", In: "query", } - params.StatusMessage = packed[key].(string) + params.ErrorMessage = packed[key].(string) } return params } @@ -104,10 +104,10 @@ func decodeActionMapfixAcceptedParams(args [1]string, argsEscaped bool, r *http. Err: err, } } - // Decode query: StatusMessage. + // Decode query: ErrorMessage. if err := func() error { cfg := uri.QueryParameterDecodingConfig{ - Name: "StatusMessage", + Name: "ErrorMessage", Style: uri.QueryStyleForm, Explode: true, } @@ -124,7 +124,7 @@ func decodeActionMapfixAcceptedParams(args [1]string, argsEscaped bool, r *http. return err } - params.StatusMessage = c + params.ErrorMessage = c return nil }); err != nil { return err @@ -138,7 +138,7 @@ func decodeActionMapfixAcceptedParams(args [1]string, argsEscaped bool, r *http. Email: false, Hostname: false, Regex: nil, - }).Validate(string(params.StatusMessage)); err != nil { + }).Validate(string(params.ErrorMessage)); err != nil { return errors.Wrap(err, "string") } return nil @@ -151,7 +151,7 @@ func decodeActionMapfixAcceptedParams(args [1]string, argsEscaped bool, r *http. return nil }(); err != nil { return params, &ogenerrors.DecodeParamError{ - Name: "StatusMessage", + Name: "ErrorMessage", In: "query", Err: err, } @@ -162,8 +162,8 @@ func decodeActionMapfixAcceptedParams(args [1]string, argsEscaped bool, r *http. // ActionMapfixRequestChangesParams is parameters of actionMapfixRequestChanges operation. type ActionMapfixRequestChangesParams struct { // The unique identifier for a submission. - MapfixID int64 - StatusMessage string + MapfixID int64 + ErrorMessage string } func unpackActionMapfixRequestChangesParams(packed middleware.Parameters) (params ActionMapfixRequestChangesParams) { @@ -176,10 +176,10 @@ func unpackActionMapfixRequestChangesParams(packed middleware.Parameters) (param } { key := middleware.ParameterKey{ - Name: "StatusMessage", + Name: "ErrorMessage", In: "query", } - params.StatusMessage = packed[key].(string) + params.ErrorMessage = packed[key].(string) } return params } @@ -248,10 +248,10 @@ func decodeActionMapfixRequestChangesParams(args [1]string, argsEscaped bool, r Err: err, } } - // Decode query: StatusMessage. + // Decode query: ErrorMessage. if err := func() error { cfg := uri.QueryParameterDecodingConfig{ - Name: "StatusMessage", + Name: "ErrorMessage", Style: uri.QueryStyleForm, Explode: true, } @@ -268,7 +268,7 @@ func decodeActionMapfixRequestChangesParams(args [1]string, argsEscaped bool, r return err } - params.StatusMessage = c + params.ErrorMessage = c return nil }); err != nil { return err @@ -282,7 +282,7 @@ func decodeActionMapfixRequestChangesParams(args [1]string, argsEscaped bool, r Email: false, Hostname: false, Regex: nil, - }).Validate(string(params.StatusMessage)); err != nil { + }).Validate(string(params.ErrorMessage)); err != nil { return errors.Wrap(err, "string") } return nil @@ -295,7 +295,7 @@ func decodeActionMapfixRequestChangesParams(args [1]string, argsEscaped bool, r return nil }(); err != nil { return params, &ogenerrors.DecodeParamError{ - Name: "StatusMessage", + Name: "ErrorMessage", In: "query", Err: err, } @@ -761,8 +761,8 @@ func decodeActionOperationFailedParams(args [1]string, argsEscaped bool, r *http // ActionSubmissionAcceptedParams is parameters of actionSubmissionAccepted operation. type ActionSubmissionAcceptedParams struct { // The unique identifier for a submission. - SubmissionID int64 - StatusMessage string + SubmissionID int64 + ErrorMessage string } func unpackActionSubmissionAcceptedParams(packed middleware.Parameters) (params ActionSubmissionAcceptedParams) { @@ -775,10 +775,10 @@ func unpackActionSubmissionAcceptedParams(packed middleware.Parameters) (params } { key := middleware.ParameterKey{ - Name: "StatusMessage", + Name: "ErrorMessage", In: "query", } - params.StatusMessage = packed[key].(string) + params.ErrorMessage = packed[key].(string) } return params } @@ -847,10 +847,10 @@ func decodeActionSubmissionAcceptedParams(args [1]string, argsEscaped bool, r *h Err: err, } } - // Decode query: StatusMessage. + // Decode query: ErrorMessage. if err := func() error { cfg := uri.QueryParameterDecodingConfig{ - Name: "StatusMessage", + Name: "ErrorMessage", Style: uri.QueryStyleForm, Explode: true, } @@ -867,7 +867,7 @@ func decodeActionSubmissionAcceptedParams(args [1]string, argsEscaped bool, r *h return err } - params.StatusMessage = c + params.ErrorMessage = c return nil }); err != nil { return err @@ -881,7 +881,7 @@ func decodeActionSubmissionAcceptedParams(args [1]string, argsEscaped bool, r *h Email: false, Hostname: false, Regex: nil, - }).Validate(string(params.StatusMessage)); err != nil { + }).Validate(string(params.ErrorMessage)); err != nil { return errors.Wrap(err, "string") } return nil @@ -894,7 +894,7 @@ func decodeActionSubmissionAcceptedParams(args [1]string, argsEscaped bool, r *h return nil }(); err != nil { return params, &ogenerrors.DecodeParamError{ - Name: "StatusMessage", + Name: "ErrorMessage", In: "query", Err: err, } @@ -905,8 +905,8 @@ func decodeActionSubmissionAcceptedParams(args [1]string, argsEscaped bool, r *h // ActionSubmissionRequestChangesParams is parameters of actionSubmissionRequestChanges operation. type ActionSubmissionRequestChangesParams struct { // The unique identifier for a submission. - SubmissionID int64 - StatusMessage string + SubmissionID int64 + ErrorMessage string } func unpackActionSubmissionRequestChangesParams(packed middleware.Parameters) (params ActionSubmissionRequestChangesParams) { @@ -919,10 +919,10 @@ func unpackActionSubmissionRequestChangesParams(packed middleware.Parameters) (p } { key := middleware.ParameterKey{ - Name: "StatusMessage", + Name: "ErrorMessage", In: "query", } - params.StatusMessage = packed[key].(string) + params.ErrorMessage = packed[key].(string) } return params } @@ -991,10 +991,10 @@ func decodeActionSubmissionRequestChangesParams(args [1]string, argsEscaped bool Err: err, } } - // Decode query: StatusMessage. + // Decode query: ErrorMessage. if err := func() error { cfg := uri.QueryParameterDecodingConfig{ - Name: "StatusMessage", + Name: "ErrorMessage", Style: uri.QueryStyleForm, Explode: true, } @@ -1011,7 +1011,7 @@ func decodeActionSubmissionRequestChangesParams(args [1]string, argsEscaped bool return err } - params.StatusMessage = c + params.ErrorMessage = c return nil }); err != nil { return err @@ -1025,7 +1025,7 @@ func decodeActionSubmissionRequestChangesParams(args [1]string, argsEscaped bool Email: false, Hostname: false, Regex: nil, - }).Validate(string(params.StatusMessage)); err != nil { + }).Validate(string(params.ErrorMessage)); err != nil { return errors.Wrap(err, "string") } return nil @@ -1038,7 +1038,7 @@ func decodeActionSubmissionRequestChangesParams(args [1]string, argsEscaped bool return nil }(); err != nil { return params, &ogenerrors.DecodeParamError{ - Name: "StatusMessage", + Name: "ErrorMessage", In: "query", Err: err, } -- 2.49.1 From ff9da333eba96185a5425cd068fce7aefdad19cd Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 9 Apr 2025 20:14:19 -0700 Subject: [PATCH 384/454] submissions: push audit error event on error endpoints --- pkg/service_internal/mapfixes.go | 48 +++++++++++++++++++++++++++ pkg/service_internal/submissions.go | 51 +++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/pkg/service_internal/mapfixes.go b/pkg/service_internal/mapfixes.go index 7b2c906..c52c533 100644 --- a/pkg/service_internal/mapfixes.go +++ b/pkg/service_internal/mapfixes.go @@ -129,6 +129,29 @@ func (svc *Service) ActionMapfixRequestChanges(ctx context.Context, params inter return err } + { + event_data := model.AuditEventDataError{ + Error: params.ErrorMessage, + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: ValidtorUserID, + ResourceType: model.ResourceMapfix, + ResourceID: params.MapfixID, + EventType: model.AuditEventTypeError, + EventData: EventData, + }) + if err != nil { + return err + } + } + event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } @@ -180,6 +203,31 @@ func (svc *Service) ActionMapfixAccepted(ctx context.Context, params internal.Ac return err } + //push an error audit event + { + event_data := model.AuditEventDataError{ + Error: params.ErrorMessage, + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: ValidtorUserID, + ResourceType: model.ResourceMapfix, + ResourceID: params.MapfixID, + EventType: model.AuditEventTypeError, + EventData: EventData, + }) + if err != nil { + return err + } + } + + // push an action audit event event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } diff --git a/pkg/service_internal/submissions.go b/pkg/service_internal/submissions.go index 647ec65..5e6abd0 100644 --- a/pkg/service_internal/submissions.go +++ b/pkg/service_internal/submissions.go @@ -128,6 +128,31 @@ func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params i return err } + //push an error audit event + { + event_data := model.AuditEventDataError{ + Error: params.ErrorMessage, + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: ValidtorUserID, + ResourceType: model.ResourceSubmission, + ResourceID: params.SubmissionID, + EventType: model.AuditEventTypeError, + EventData: EventData, + }) + if err != nil { + return err + } + } + + // push an action audit event event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } @@ -206,6 +231,32 @@ func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params interna return err } + + //push an error audit event + { + event_data := model.AuditEventDataError{ + Error: params.ErrorMessage, + } + + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: ValidtorUserID, + ResourceType: model.ResourceSubmission, + ResourceID: params.SubmissionID, + EventType: model.AuditEventTypeError, + EventData: EventData, + }) + if err != nil { + return err + } + } + + // push an action audit event event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } -- 2.49.1 From f915c51ba43f71f2dd0f0ae44f9f0191e944cd1c Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 9 Apr 2025 20:18:00 -0700 Subject: [PATCH 385/454] web: remove StatusMessage --- web/src/app/mapfixes/[mapfixId]/_comments.tsx | 1 - web/src/app/mapfixes/[mapfixId]/page.tsx | 3 +-- web/src/app/submissions/[submissionId]/_comments.tsx | 1 - web/src/app/submissions/[submissionId]/page.tsx | 3 +-- web/src/app/ts/Mapfix.ts | 3 +-- web/src/app/ts/Submission.ts | 3 +-- 6 files changed, 4 insertions(+), 10 deletions(-) diff --git a/web/src/app/mapfixes/[mapfixId]/_comments.tsx b/web/src/app/mapfixes/[mapfixId]/_comments.tsx index ca42988..80f1046 100644 --- a/web/src/app/mapfixes/[mapfixId]/_comments.tsx +++ b/web/src/app/mapfixes/[mapfixId]/_comments.tsx @@ -12,7 +12,6 @@ interface CreatorAndReviewStatus { asset_id: MapfixInfo["AssetID"], creator: MapfixInfo["DisplayName"], review: MapfixInfo["StatusID"], - status_message: MapfixInfo["StatusMessage"], submitter: MapfixInfo["Submitter"], target_asset_id: MapfixInfo["TargetAssetID"], comments: Comment[], diff --git a/web/src/app/mapfixes/[mapfixId]/page.tsx b/web/src/app/mapfixes/[mapfixId]/page.tsx index 5316490..1cc7d60 100644 --- a/web/src/app/mapfixes/[mapfixId]/page.tsx +++ b/web/src/app/mapfixes/[mapfixId]/page.tsx @@ -54,7 +54,6 @@ function TitleAndComments(stats: CreatorAndReviewStatus) {

Submitter {stats.submitter}

Model Asset ID {stats.asset_id}

Target Asset ID {stats.target_asset_id}

-

Validation Error: {stats.status_message}

@@ -86,7 +85,7 @@ export default function MapfixInfoPage() {
- +
diff --git a/web/src/app/submissions/[submissionId]/_comments.tsx b/web/src/app/submissions/[submissionId]/_comments.tsx index ddb61db..ce82c92 100644 --- a/web/src/app/submissions/[submissionId]/_comments.tsx +++ b/web/src/app/submissions/[submissionId]/_comments.tsx @@ -12,7 +12,6 @@ interface CreatorAndReviewStatus { asset_id: SubmissionInfo["AssetID"], creator: SubmissionInfo["DisplayName"], review: SubmissionInfo["StatusID"], - status_message: SubmissionInfo["StatusMessage"], submitter: SubmissionInfo["Submitter"], uploaded_asset_id: SubmissionInfo["UploadedAssetID"], comments: Comment[], diff --git a/web/src/app/submissions/[submissionId]/page.tsx b/web/src/app/submissions/[submissionId]/page.tsx index 81502de..62b4e30 100644 --- a/web/src/app/submissions/[submissionId]/page.tsx +++ b/web/src/app/submissions/[submissionId]/page.tsx @@ -46,7 +46,6 @@ function TitleAndComments(stats: CreatorAndReviewStatus) {

Submitter {stats.submitter}

Model Asset ID {stats.asset_id}

Uploaded Asset ID {stats.uploaded_asset_id}

-

Validation Error: {stats.status_message}