Merge pull request 'Mapfix Release' (#264) from mapfix-release into staging
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: #264
This commit was merged in pull request #264.
This commit is contained in:
2025-08-16 02:47:28 +00:00
52 changed files with 3273 additions and 326 deletions

96
Cargo.lock generated
View File

@@ -56,9 +56,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.98"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
[[package]]
name = "arrayref"
@@ -266,9 +266,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.31"
version = "1.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2"
checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
dependencies = [
"jobserver",
"libc",
@@ -640,9 +640,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "h2"
version = "0.4.11"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785"
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
dependencies = [
"atomic-waker",
"bytes",
@@ -659,9 +659,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.4"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
[[package]]
name = "heck"
@@ -784,7 +784,7 @@ dependencies = [
"libc",
"percent-encoding",
"pin-project-lite",
"socket2 0.6.0",
"socket2 0.5.10",
"system-configuration",
"tokio",
"tower-service",
@@ -1026,9 +1026,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.174"
version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "litemap"
@@ -1330,9 +1330,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.95"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1"
dependencies = [
"unicode-ident",
]
@@ -1358,9 +1358,9 @@ dependencies = [
[[package]]
name = "prost"
version = "0.13.5"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d"
dependencies = [
"bytes",
"prost-derive",
@@ -1368,9 +1368,9 @@ dependencies = [
[[package]]
name = "prost-derive"
version = "0.13.5"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425"
dependencies = [
"anyhow",
"itertools",
@@ -1381,9 +1381,9 @@ dependencies = [
[[package]]
name = "prost-types"
version = "0.13.5-serde3"
version = "0.14.1-serde2"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "e42128b6e3a6655aa5f72ac65a33848a512eb9b23e98986adc4bbe6559ea88ce"
checksum = "c6bdb43aea117477820c164442f4e943ac7690d0dbe66cde45d78e0f7bb34386"
dependencies = [
"prost",
"serde",
@@ -1403,7 +1403,7 @@ dependencies = [
"rustc-hash",
"rustls",
"socket2 0.5.10",
"thiserror 2.0.12",
"thiserror 2.0.14",
"tokio",
"tracing",
"web-time",
@@ -1424,7 +1424,7 @@ dependencies = [
"rustls",
"rustls-pki-types",
"slab",
"thiserror 2.0.12",
"thiserror 2.0.14",
"tinyvec",
"tracing",
"web-time",
@@ -1441,7 +1441,7 @@ dependencies = [
"once_cell",
"socket2 0.5.10",
"tracing",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -1655,9 +1655,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "reqwest"
version = "0.12.22"
version = "0.12.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531"
checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb"
dependencies = [
"base64 0.22.1",
"bytes",
@@ -1734,14 +1734,15 @@ dependencies = [
[[package]]
name = "rust-grpc"
version = "1.3.4"
version = "1.6.1"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "ffab535c98c3a298cd092126036d5f8e40b9e600d24941823fb67a788be387ee"
checksum = "0793cf131a9c4746000533af36aadbfb34ec6877c9f1664f94c1a110df6628ce"
dependencies = [
"prost",
"prost-types",
"serde",
"tonic",
"tonic-prost",
]
[[package]]
@@ -1834,9 +1835,9 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.21"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
@@ -2008,9 +2009,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "slab"
version = "0.4.10"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
[[package]]
name = "smallvec"
@@ -2056,7 +2057,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "submissions-api"
version = "0.8.2"
version = "0.9.1"
dependencies = [
"chrono",
"reqwest",
@@ -2074,9 +2075,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.104"
version = "2.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619"
dependencies = [
"proc-macro2",
"quote",
@@ -2135,11 +2136,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.12"
version = "2.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e"
dependencies = [
"thiserror-impl 2.0.12",
"thiserror-impl 2.0.14",
]
[[package]]
@@ -2155,9 +2156,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.12"
version = "2.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227"
dependencies = [
"proc-macro2",
"quote",
@@ -2307,9 +2308,9 @@ dependencies = [
[[package]]
name = "tonic"
version = "0.13.1"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9"
checksum = "67ac5a8627ada0968acec063a4746bf79588aa03ccb66db2f75d7dce26722a40"
dependencies = [
"async-trait",
"axum",
@@ -2324,8 +2325,8 @@ dependencies = [
"hyper-util",
"percent-encoding",
"pin-project",
"prost",
"socket2 0.5.10",
"socket2 0.6.0",
"sync_wrapper",
"tokio",
"tokio-stream",
"tower",
@@ -2334,6 +2335,17 @@ dependencies = [
"tracing",
]
[[package]]
name = "tonic-prost"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9c511b9a96d40cb12b7d5d00464446acf3b9105fd3ce25437cfe41c92b1c87d"
dependencies = [
"bytes",
"prost",
"tonic",
]
[[package]]
name = "tower"
version = "0.5.2"

View File

@@ -64,6 +64,8 @@ services:
- ROBLOX_GROUP_ID=17032139 # "None" is special case string value
- API_HOST_INTERNAL=http://submissions:8083/v1
- NATS_HOST=nats:4222
- LOAD_ASSET_VERSION_PLACE_ID=14001440964
- LOAD_ASSET_VERSION_UNIVERSE_ID=4850603885
depends_on:
- nats
# note: this races the submissions which creates a nats stream

2
go.mod
View File

@@ -6,7 +6,7 @@ toolchain go1.24.5
require (
git.itzana.me/StrafesNET/dev-service v0.0.0-20250628052121-92af8193b5ed
git.itzana.me/strafesnet/go-grpc v0.0.0-20250807005013-301d35b914ef
git.itzana.me/strafesnet/go-grpc v0.0.0-20250814235656-6a41b31a1a33
git.itzana.me/strafesnet/utils v0.0.0-20220716194944-d8ca164052f9
github.com/dchest/siphash v1.2.3
github.com/gin-gonic/gin v1.10.1

4
go.sum
View File

@@ -2,8 +2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
git.itzana.me/StrafesNET/dev-service v0.0.0-20250628052121-92af8193b5ed h1:eGWIQx2AOrSsLC2dieuSs8MCliRE60tvpZnmxsTBtKc=
git.itzana.me/StrafesNET/dev-service v0.0.0-20250628052121-92af8193b5ed/go.mod h1:KJal0K++M6HEzSry6JJ2iDPZtOQn5zSstNlDbU3X4Jg=
git.itzana.me/strafesnet/go-grpc v0.0.0-20250807005013-301d35b914ef h1:SJi4V4+xzScFnbMRN1gkZxcqR1xKfiT7CaXanLltEzw=
git.itzana.me/strafesnet/go-grpc v0.0.0-20250807005013-301d35b914ef/go.mod h1:X7XTRUScRkBWq8q8bplbeso105RPDlnY7J6Wy1IwBMs=
git.itzana.me/strafesnet/go-grpc v0.0.0-20250814235656-6a41b31a1a33 h1:7dzfLFXcrNHRSrS1CxTDC9yQLIfbcsQHLFIPeTyMFbE=
git.itzana.me/strafesnet/go-grpc v0.0.0-20250814235656-6a41b31a1a33/go.mod h1:X7XTRUScRkBWq8q8bplbeso105RPDlnY7J6Wy1IwBMs=
git.itzana.me/strafesnet/utils v0.0.0-20220716194944-d8ca164052f9 h1:7lU6jyR7S7Rhh1dnUp7GyIRHUTBXZagw8F4n4hOyxLw=
git.itzana.me/strafesnet/utils v0.0.0-20220716194944-d8ca164052f9/go.mod h1:uyYerSieEt4v0MJCdPLppG0LtJ4Yj035vuTetWGsxjY=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=

View File

@@ -244,6 +244,12 @@ paths:
type: integer
format: int64
minimum: 0
- name: AssetVersion
in: query
schema:
type: integer
format: int64
minimum: 0
- name: TargetAssetID
in: query
schema:
@@ -312,6 +318,21 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/Error"
/mapfixes/migrate:
post:
summary: Perform the Uploaded -> Released migration.
operationId: migrateMapfixes
tags:
- Mapfixes
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/mapfixes/{MapfixID}:
get:
summary: Retrieve map with ID
@@ -587,7 +608,7 @@ paths:
$ref: "#/components/schemas/Error"
/mapfixes/{MapfixID}/status/trigger-upload:
post:
summary: Role Admin changes status from Validated -> Uploading
summary: Role MapfixUpload changes status from Validated -> Uploading
operationId: actionMapfixTriggerUpload
tags:
- Mapfixes
@@ -604,7 +625,7 @@ paths:
$ref: "#/components/schemas/Error"
/mapfixes/{MapfixID}/status/reset-uploading:
post:
summary: Role Admin manually resets uploading softlock and changes status from Uploading -> Validated
summary: Role MapfixUpload manually resets uploading softlock and changes status from Uploading -> Validated
operationId: actionMapfixValidated
tags:
- Mapfixes
@@ -619,6 +640,40 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/Error"
/mapfixes/{MapfixID}/status/trigger-release:
post:
summary: Role MapfixUpload changes status from Uploaded -> Releasing
operationId: actionMapfixTriggerRelease
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-releasing:
post:
summary: Role MapfixUpload manually resets releasing softlock and changes status from Releasing -> Uploaded
operationId: actionMapfixUploaded
tags:
- Mapfixes
parameters:
- $ref: '#/components/parameters/MapfixID'
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/operations/{OperationID}:
get:
summary: Retrieve operation with ID
@@ -698,6 +753,12 @@ paths:
type: integer
format: int64
minimum: 0
- name: AssetVersion
in: query
schema:
type: integer
format: int64
minimum: 0
- name: UploadedAssetID
in: query
schema:
@@ -1067,7 +1128,7 @@ paths:
$ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/trigger-upload:
post:
summary: Role Admin changes status from Validated -> Uploading
summary: Role SubmissionUpload changes status from Validated -> Uploading
operationId: actionSubmissionTriggerUpload
tags:
- Submissions
@@ -1084,7 +1145,7 @@ paths:
$ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/reset-uploading:
post:
summary: Role Admin manually resets uploading softlock and changes status from Uploading -> Validated
summary: Role SubmissionUpload manually resets uploading softlock and changes status from Uploading -> Validated
operationId: actionSubmissionValidated
tags:
- Submissions
@@ -1101,7 +1162,7 @@ paths:
$ref: "#/components/schemas/Error"
/release-submissions:
post:
summary: Release a set of uploaded maps
summary: Release a set of uploaded maps. Role SubmissionRelease
operationId: releaseSubmissions
tags:
- Submissions
@@ -1624,6 +1685,8 @@ components:
- Submitter
- AssetID
- AssetVersion
# - ValidatedAssetID
# - ValidatedAssetVersion
- Completed
- TargetAssetID
- StatusID
@@ -1664,6 +1727,14 @@ components:
type: integer
format: int64
minimum: 0
ValidatedAssetID:
type: integer
format: int64
minimum: 0
ValidatedAssetVersion:
type: integer
format: int64
minimum: 0
Completed:
type: boolean
TargetAssetID:

View File

@@ -66,6 +66,12 @@ type Invoker interface {
//
// POST /mapfixes/{MapfixID}/status/revoke
ActionMapfixRevoke(ctx context.Context, params ActionMapfixRevokeParams) error
// ActionMapfixTriggerRelease invokes actionMapfixTriggerRelease operation.
//
// Role MapfixUpload changes status from Uploaded -> Releasing.
//
// POST /mapfixes/{MapfixID}/status/trigger-release
ActionMapfixTriggerRelease(ctx context.Context, params ActionMapfixTriggerReleaseParams) error
// ActionMapfixTriggerSubmit invokes actionMapfixTriggerSubmit operation.
//
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting.
@@ -80,7 +86,7 @@ type Invoker interface {
ActionMapfixTriggerSubmitUnchecked(ctx context.Context, params ActionMapfixTriggerSubmitUncheckedParams) error
// ActionMapfixTriggerUpload invokes actionMapfixTriggerUpload operation.
//
// Role Admin changes status from Validated -> Uploading.
// Role MapfixUpload changes status from Validated -> Uploading.
//
// POST /mapfixes/{MapfixID}/status/trigger-upload
ActionMapfixTriggerUpload(ctx context.Context, params ActionMapfixTriggerUploadParams) error
@@ -90,9 +96,15 @@ type Invoker interface {
//
// POST /mapfixes/{MapfixID}/status/trigger-validate
ActionMapfixTriggerValidate(ctx context.Context, params ActionMapfixTriggerValidateParams) error
// ActionMapfixUploaded invokes actionMapfixUploaded operation.
//
// Role MapfixUpload manually resets releasing softlock and changes status from Releasing -> Uploaded.
//
// POST /mapfixes/{MapfixID}/status/reset-releasing
ActionMapfixUploaded(ctx context.Context, params ActionMapfixUploadedParams) error
// ActionMapfixValidated invokes actionMapfixValidated operation.
//
// Role Admin manually resets uploading softlock and changes status from Uploading -> Validated.
// Role MapfixUpload manually resets uploading softlock and changes status from Uploading -> Validated.
//
// POST /mapfixes/{MapfixID}/status/reset-uploading
ActionMapfixValidated(ctx context.Context, params ActionMapfixValidatedParams) error
@@ -147,7 +159,7 @@ type Invoker interface {
ActionSubmissionTriggerSubmitUnchecked(ctx context.Context, params ActionSubmissionTriggerSubmitUncheckedParams) error
// ActionSubmissionTriggerUpload invokes actionSubmissionTriggerUpload operation.
//
// Role Admin changes status from Validated -> Uploading.
// Role SubmissionUpload changes status from Validated -> Uploading.
//
// POST /submissions/{SubmissionID}/status/trigger-upload
ActionSubmissionTriggerUpload(ctx context.Context, params ActionSubmissionTriggerUploadParams) error
@@ -159,7 +171,8 @@ type Invoker interface {
ActionSubmissionTriggerValidate(ctx context.Context, params ActionSubmissionTriggerValidateParams) error
// ActionSubmissionValidated invokes actionSubmissionValidated operation.
//
// Role Admin manually resets uploading softlock and changes status from Uploading -> Validated.
// Role SubmissionUpload manually resets uploading softlock and changes status from Uploading ->
// Validated.
//
// POST /submissions/{SubmissionID}/status/reset-uploading
ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error
@@ -301,9 +314,15 @@ type Invoker interface {
//
// GET /submissions
ListSubmissions(ctx context.Context, params ListSubmissionsParams) (*Submissions, error)
// MigrateMapfixes invokes migrateMapfixes operation.
//
// Perform the Uploaded -> Released migration.
//
// POST /mapfixes/migrate
MigrateMapfixes(ctx context.Context) error
// ReleaseSubmissions invokes releaseSubmissions operation.
//
// Release a set of uploaded maps.
// Release a set of uploaded maps. Role SubmissionRelease.
//
// POST /release-submissions
ReleaseSubmissions(ctx context.Context, request []ReleaseInfo) error
@@ -1157,6 +1176,130 @@ func (c *Client) sendActionMapfixRevoke(ctx context.Context, params ActionMapfix
return result, nil
}
// ActionMapfixTriggerRelease invokes actionMapfixTriggerRelease operation.
//
// Role MapfixUpload changes status from Uploaded -> Releasing.
//
// POST /mapfixes/{MapfixID}/status/trigger-release
func (c *Client) ActionMapfixTriggerRelease(ctx context.Context, params ActionMapfixTriggerReleaseParams) error {
_, err := c.sendActionMapfixTriggerRelease(ctx, params)
return err
}
func (c *Client) sendActionMapfixTriggerRelease(ctx context.Context, params ActionMapfixTriggerReleaseParams) (res *ActionMapfixTriggerReleaseNoContent, err error) {
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionMapfixTriggerRelease"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/status/trigger-release"),
}
// 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, ActionMapfixTriggerReleaseOperation,
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/trigger-release"
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, ActionMapfixTriggerReleaseOperation, 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 := decodeActionMapfixTriggerReleaseResponse(resp)
if err != nil {
return res, errors.Wrap(err, "decode response")
}
return result, nil
}
// ActionMapfixTriggerSubmit invokes actionMapfixTriggerSubmit operation.
//
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting.
@@ -1407,7 +1550,7 @@ func (c *Client) sendActionMapfixTriggerSubmitUnchecked(ctx context.Context, par
// ActionMapfixTriggerUpload invokes actionMapfixTriggerUpload operation.
//
// Role Admin changes status from Validated -> Uploading.
// Role MapfixUpload changes status from Validated -> Uploading.
//
// POST /mapfixes/{MapfixID}/status/trigger-upload
func (c *Client) ActionMapfixTriggerUpload(ctx context.Context, params ActionMapfixTriggerUploadParams) error {
@@ -1653,9 +1796,133 @@ func (c *Client) sendActionMapfixTriggerValidate(ctx context.Context, params Act
return result, nil
}
// ActionMapfixUploaded invokes actionMapfixUploaded operation.
//
// Role MapfixUpload manually resets releasing softlock and changes status from Releasing -> Uploaded.
//
// POST /mapfixes/{MapfixID}/status/reset-releasing
func (c *Client) ActionMapfixUploaded(ctx context.Context, params ActionMapfixUploadedParams) error {
_, err := c.sendActionMapfixUploaded(ctx, params)
return err
}
func (c *Client) sendActionMapfixUploaded(ctx context.Context, params ActionMapfixUploadedParams) (res *ActionMapfixUploadedNoContent, err error) {
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionMapfixUploaded"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/status/reset-releasing"),
}
// 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, ActionMapfixUploadedOperation,
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-releasing"
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, ActionMapfixUploadedOperation, 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 := decodeActionMapfixUploadedResponse(resp)
if err != nil {
return res, errors.Wrap(err, "decode response")
}
return result, nil
}
// ActionMapfixValidated invokes actionMapfixValidated operation.
//
// Role Admin manually resets uploading softlock and changes status from Uploading -> Validated.
// Role MapfixUpload manually resets uploading softlock and changes status from Uploading -> Validated.
//
// POST /mapfixes/{MapfixID}/status/reset-uploading
func (c *Client) ActionMapfixValidated(ctx context.Context, params ActionMapfixValidatedParams) error {
@@ -2772,7 +3039,7 @@ func (c *Client) sendActionSubmissionTriggerSubmitUnchecked(ctx context.Context,
// ActionSubmissionTriggerUpload invokes actionSubmissionTriggerUpload operation.
//
// Role Admin changes status from Validated -> Uploading.
// Role SubmissionUpload changes status from Validated -> Uploading.
//
// POST /submissions/{SubmissionID}/status/trigger-upload
func (c *Client) ActionSubmissionTriggerUpload(ctx context.Context, params ActionSubmissionTriggerUploadParams) error {
@@ -3020,7 +3287,8 @@ func (c *Client) sendActionSubmissionTriggerValidate(ctx context.Context, params
// ActionSubmissionValidated invokes actionSubmissionValidated operation.
//
// Role Admin manually resets uploading softlock and changes status from Uploading -> Validated.
// Role SubmissionUpload manually resets uploading softlock and changes status from Uploading ->
// Validated.
//
// POST /submissions/{SubmissionID}/status/reset-uploading
func (c *Client) ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error {
@@ -5184,6 +5452,23 @@ func (c *Client) sendListMapfixes(ctx context.Context, params ListMapfixesParams
return res, errors.Wrap(err, "encode query")
}
}
{
// Encode "AssetVersion" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "AssetVersion",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
if val, ok := params.AssetVersion.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{
@@ -6063,6 +6348,23 @@ func (c *Client) sendListSubmissions(ctx context.Context, params ListSubmissions
return res, errors.Wrap(err, "encode query")
}
}
{
// Encode "AssetVersion" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "AssetVersion",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
if val, ok := params.AssetVersion.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{
@@ -6121,9 +6423,114 @@ func (c *Client) sendListSubmissions(ctx context.Context, params ListSubmissions
return result, nil
}
// MigrateMapfixes invokes migrateMapfixes operation.
//
// Perform the Uploaded -> Released migration.
//
// POST /mapfixes/migrate
func (c *Client) MigrateMapfixes(ctx context.Context) error {
_, err := c.sendMigrateMapfixes(ctx)
return err
}
func (c *Client) sendMigrateMapfixes(ctx context.Context) (res *MigrateMapfixesNoContent, err error) {
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("migrateMapfixes"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/mapfixes/migrate"),
}
// 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, MigrateMapfixesOperation,
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] = "/mapfixes/migrate"
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, MigrateMapfixesOperation, 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 := decodeMigrateMapfixesResponse(resp)
if err != nil {
return res, errors.Wrap(err, "decode response")
}
return result, nil
}
// ReleaseSubmissions invokes releaseSubmissions operation.
//
// Release a set of uploaded maps.
// Release a set of uploaded maps. Role SubmissionRelease.
//
// POST /release-submissions
func (c *Client) ReleaseSubmissions(ctx context.Context, request []ReleaseInfo) error {

View File

@@ -1201,6 +1201,201 @@ func (s *Server) handleActionMapfixRevokeRequest(args [1]string, argsEscaped boo
}
}
// handleActionMapfixTriggerReleaseRequest handles actionMapfixTriggerRelease operation.
//
// Role MapfixUpload changes status from Uploaded -> Releasing.
//
// POST /mapfixes/{MapfixID}/status/trigger-release
func (s *Server) handleActionMapfixTriggerReleaseRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
statusWriter := &codeRecorder{ResponseWriter: w}
w = statusWriter
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionMapfixTriggerRelease"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/status/trigger-release"),
}
// Start a span for this request.
ctx, span := s.cfg.Tracer.Start(r.Context(), ActionMapfixTriggerReleaseOperation,
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: ActionMapfixTriggerReleaseOperation,
ID: "actionMapfixTriggerRelease",
}
)
{
type bitset = [1]uint8
var satisfied bitset
{
sctx, ok, err := s.securityCookieAuth(ctx, ActionMapfixTriggerReleaseOperation, 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 := decodeActionMapfixTriggerReleaseParams(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 *ActionMapfixTriggerReleaseNoContent
if m := s.cfg.Middleware; m != nil {
mreq := middleware.Request{
Context: ctx,
OperationName: ActionMapfixTriggerReleaseOperation,
OperationSummary: "Role MapfixUpload changes status from Uploaded -> Releasing",
OperationID: "actionMapfixTriggerRelease",
Body: nil,
Params: middleware.Parameters{
{
Name: "MapfixID",
In: "path",
}: params.MapfixID,
},
Raw: r,
}
type (
Request = struct{}
Params = ActionMapfixTriggerReleaseParams
Response = *ActionMapfixTriggerReleaseNoContent
)
response, err = middleware.HookMiddleware[
Request,
Params,
Response,
](
m,
mreq,
unpackActionMapfixTriggerReleaseParams,
func(ctx context.Context, request Request, params Params) (response Response, err error) {
err = s.h.ActionMapfixTriggerRelease(ctx, params)
return response, err
},
)
} else {
err = s.h.ActionMapfixTriggerRelease(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 := encodeActionMapfixTriggerReleaseResponse(response, w, span); err != nil {
defer recordError("EncodeResponse", err)
if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
s.cfg.ErrorHandler(ctx, w, r, err)
}
return
}
}
// handleActionMapfixTriggerSubmitRequest handles actionMapfixTriggerSubmit operation.
//
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting.
@@ -1593,7 +1788,7 @@ func (s *Server) handleActionMapfixTriggerSubmitUncheckedRequest(args [1]string,
// handleActionMapfixTriggerUploadRequest handles actionMapfixTriggerUpload operation.
//
// Role Admin changes status from Validated -> Uploading.
// Role MapfixUpload changes status from Validated -> Uploading.
//
// POST /mapfixes/{MapfixID}/status/trigger-upload
func (s *Server) handleActionMapfixTriggerUploadRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
@@ -1727,7 +1922,7 @@ func (s *Server) handleActionMapfixTriggerUploadRequest(args [1]string, argsEsca
mreq := middleware.Request{
Context: ctx,
OperationName: ActionMapfixTriggerUploadOperation,
OperationSummary: "Role Admin changes status from Validated -> Uploading",
OperationSummary: "Role MapfixUpload changes status from Validated -> Uploading",
OperationID: "actionMapfixTriggerUpload",
Body: nil,
Params: middleware.Parameters{
@@ -1981,9 +2176,204 @@ func (s *Server) handleActionMapfixTriggerValidateRequest(args [1]string, argsEs
}
}
// handleActionMapfixUploadedRequest handles actionMapfixUploaded operation.
//
// Role MapfixUpload manually resets releasing softlock and changes status from Releasing -> Uploaded.
//
// POST /mapfixes/{MapfixID}/status/reset-releasing
func (s *Server) handleActionMapfixUploadedRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
statusWriter := &codeRecorder{ResponseWriter: w}
w = statusWriter
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionMapfixUploaded"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/status/reset-releasing"),
}
// Start a span for this request.
ctx, span := s.cfg.Tracer.Start(r.Context(), ActionMapfixUploadedOperation,
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: ActionMapfixUploadedOperation,
ID: "actionMapfixUploaded",
}
)
{
type bitset = [1]uint8
var satisfied bitset
{
sctx, ok, err := s.securityCookieAuth(ctx, ActionMapfixUploadedOperation, 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 := decodeActionMapfixUploadedParams(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 *ActionMapfixUploadedNoContent
if m := s.cfg.Middleware; m != nil {
mreq := middleware.Request{
Context: ctx,
OperationName: ActionMapfixUploadedOperation,
OperationSummary: "Role MapfixUpload manually resets releasing softlock and changes status from Releasing -> Uploaded",
OperationID: "actionMapfixUploaded",
Body: nil,
Params: middleware.Parameters{
{
Name: "MapfixID",
In: "path",
}: params.MapfixID,
},
Raw: r,
}
type (
Request = struct{}
Params = ActionMapfixUploadedParams
Response = *ActionMapfixUploadedNoContent
)
response, err = middleware.HookMiddleware[
Request,
Params,
Response,
](
m,
mreq,
unpackActionMapfixUploadedParams,
func(ctx context.Context, request Request, params Params) (response Response, err error) {
err = s.h.ActionMapfixUploaded(ctx, params)
return response, err
},
)
} else {
err = s.h.ActionMapfixUploaded(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 := encodeActionMapfixUploadedResponse(response, w, span); err != nil {
defer recordError("EncodeResponse", err)
if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
s.cfg.ErrorHandler(ctx, w, r, err)
}
return
}
}
// handleActionMapfixValidatedRequest handles actionMapfixValidated operation.
//
// Role Admin manually resets uploading softlock and changes status from Uploading -> Validated.
// Role MapfixUpload manually resets uploading softlock and changes status from Uploading -> Validated.
//
// POST /mapfixes/{MapfixID}/status/reset-uploading
func (s *Server) handleActionMapfixValidatedRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
@@ -2117,7 +2507,7 @@ func (s *Server) handleActionMapfixValidatedRequest(args [1]string, argsEscaped
mreq := middleware.Request{
Context: ctx,
OperationName: ActionMapfixValidatedOperation,
OperationSummary: "Role Admin manually resets uploading softlock and changes status from Uploading -> Validated",
OperationSummary: "Role MapfixUpload manually resets uploading softlock and changes status from Uploading -> Validated",
OperationID: "actionMapfixValidated",
Body: nil,
Params: middleware.Parameters{
@@ -3739,7 +4129,7 @@ func (s *Server) handleActionSubmissionTriggerSubmitUncheckedRequest(args [1]str
// handleActionSubmissionTriggerUploadRequest handles actionSubmissionTriggerUpload operation.
//
// Role Admin changes status from Validated -> Uploading.
// Role SubmissionUpload changes status from Validated -> Uploading.
//
// POST /submissions/{SubmissionID}/status/trigger-upload
func (s *Server) handleActionSubmissionTriggerUploadRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
@@ -3873,7 +4263,7 @@ func (s *Server) handleActionSubmissionTriggerUploadRequest(args [1]string, args
mreq := middleware.Request{
Context: ctx,
OperationName: ActionSubmissionTriggerUploadOperation,
OperationSummary: "Role Admin changes status from Validated -> Uploading",
OperationSummary: "Role SubmissionUpload changes status from Validated -> Uploading",
OperationID: "actionSubmissionTriggerUpload",
Body: nil,
Params: middleware.Parameters{
@@ -4129,7 +4519,8 @@ func (s *Server) handleActionSubmissionTriggerValidateRequest(args [1]string, ar
// handleActionSubmissionValidatedRequest handles actionSubmissionValidated operation.
//
// Role Admin manually resets uploading softlock and changes status from Uploading -> Validated.
// Role SubmissionUpload manually resets uploading softlock and changes status from Uploading ->
// Validated.
//
// POST /submissions/{SubmissionID}/status/reset-uploading
func (s *Server) handleActionSubmissionValidatedRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
@@ -4263,7 +4654,7 @@ func (s *Server) handleActionSubmissionValidatedRequest(args [1]string, argsEsca
mreq := middleware.Request{
Context: ctx,
OperationName: ActionSubmissionValidatedOperation,
OperationSummary: "Role Admin manually resets uploading softlock and changes status from Uploading -> Validated",
OperationSummary: "Role SubmissionUpload manually resets uploading softlock and changes status from Uploading -> Validated",
OperationID: "actionSubmissionValidated",
Body: nil,
Params: middleware.Parameters{
@@ -7525,6 +7916,10 @@ func (s *Server) handleListMapfixesRequest(args [0]string, argsEscaped bool, w h
Name: "AssetID",
In: "query",
}: params.AssetID,
{
Name: "AssetVersion",
In: "query",
}: params.AssetVersion,
{
Name: "TargetAssetID",
In: "query",
@@ -8374,6 +8769,10 @@ func (s *Server) handleListSubmissionsRequest(args [0]string, argsEscaped bool,
Name: "AssetID",
In: "query",
}: params.AssetID,
{
Name: "AssetVersion",
In: "query",
}: params.AssetVersion,
{
Name: "UploadedAssetID",
In: "query",
@@ -8433,9 +8832,189 @@ func (s *Server) handleListSubmissionsRequest(args [0]string, argsEscaped bool,
}
}
// handleMigrateMapfixesRequest handles migrateMapfixes operation.
//
// Perform the Uploaded -> Released migration.
//
// POST /mapfixes/migrate
func (s *Server) handleMigrateMapfixesRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
statusWriter := &codeRecorder{ResponseWriter: w}
w = statusWriter
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("migrateMapfixes"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/mapfixes/migrate"),
}
// Start a span for this request.
ctx, span := s.cfg.Tracer.Start(r.Context(), MigrateMapfixesOperation,
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: MigrateMapfixesOperation,
ID: "migrateMapfixes",
}
)
{
type bitset = [1]uint8
var satisfied bitset
{
sctx, ok, err := s.securityCookieAuth(ctx, MigrateMapfixesOperation, 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
}
}
var response *MigrateMapfixesNoContent
if m := s.cfg.Middleware; m != nil {
mreq := middleware.Request{
Context: ctx,
OperationName: MigrateMapfixesOperation,
OperationSummary: "Perform the Uploaded -> Released migration.",
OperationID: "migrateMapfixes",
Body: nil,
Params: middleware.Parameters{},
Raw: r,
}
type (
Request = struct{}
Params = struct{}
Response = *MigrateMapfixesNoContent
)
response, err = middleware.HookMiddleware[
Request,
Params,
Response,
](
m,
mreq,
nil,
func(ctx context.Context, request Request, params Params) (response Response, err error) {
err = s.h.MigrateMapfixes(ctx)
return response, err
},
)
} else {
err = s.h.MigrateMapfixes(ctx)
}
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 := encodeMigrateMapfixesResponse(response, w, span); err != nil {
defer recordError("EncodeResponse", err)
if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
s.cfg.ErrorHandler(ctx, w, r, err)
}
return
}
}
// handleReleaseSubmissionsRequest handles releaseSubmissions operation.
//
// Release a set of uploaded maps.
// Release a set of uploaded maps. Role SubmissionRelease.
//
// POST /release-submissions
func (s *Server) handleReleaseSubmissionsRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
@@ -8574,7 +9153,7 @@ func (s *Server) handleReleaseSubmissionsRequest(args [0]string, argsEscaped boo
mreq := middleware.Request{
Context: ctx,
OperationName: ReleaseSubmissionsOperation,
OperationSummary: "Release a set of uploaded maps",
OperationSummary: "Release a set of uploaded maps. Role SubmissionRelease",
OperationID: "releaseSubmissions",
Body: request,
Params: middleware.Parameters{},

View File

@@ -726,6 +726,18 @@ func (s *Mapfix) encodeFields(e *jx.Encoder) {
e.FieldStart("AssetVersion")
e.Int64(s.AssetVersion)
}
{
if s.ValidatedAssetID.Set {
e.FieldStart("ValidatedAssetID")
s.ValidatedAssetID.Encode(e)
}
}
{
if s.ValidatedAssetVersion.Set {
e.FieldStart("ValidatedAssetVersion")
s.ValidatedAssetVersion.Encode(e)
}
}
{
e.FieldStart("Completed")
e.Bool(s.Completed)
@@ -744,7 +756,7 @@ func (s *Mapfix) encodeFields(e *jx.Encoder) {
}
}
var jsonFieldsNameOfMapfix = [13]string{
var jsonFieldsNameOfMapfix = [15]string{
0: "ID",
1: "DisplayName",
2: "Creator",
@@ -754,10 +766,12 @@ var jsonFieldsNameOfMapfix = [13]string{
6: "Submitter",
7: "AssetID",
8: "AssetVersion",
9: "Completed",
10: "TargetAssetID",
11: "StatusID",
12: "Description",
9: "ValidatedAssetID",
10: "ValidatedAssetVersion",
11: "Completed",
12: "TargetAssetID",
13: "StatusID",
14: "Description",
}
// Decode decodes Mapfix from json.
@@ -877,8 +891,28 @@ func (s *Mapfix) Decode(d *jx.Decoder) error {
}(); err != nil {
return errors.Wrap(err, "decode field \"AssetVersion\"")
}
case "ValidatedAssetID":
if err := func() error {
s.ValidatedAssetID.Reset()
if err := s.ValidatedAssetID.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"ValidatedAssetID\"")
}
case "ValidatedAssetVersion":
if err := func() error {
s.ValidatedAssetVersion.Reset()
if err := s.ValidatedAssetVersion.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"ValidatedAssetVersion\"")
}
case "Completed":
requiredBitSet[1] |= 1 << 1
requiredBitSet[1] |= 1 << 3
if err := func() error {
v, err := d.Bool()
s.Completed = bool(v)
@@ -890,7 +924,7 @@ func (s *Mapfix) Decode(d *jx.Decoder) error {
return errors.Wrap(err, "decode field \"Completed\"")
}
case "TargetAssetID":
requiredBitSet[1] |= 1 << 2
requiredBitSet[1] |= 1 << 4
if err := func() error {
v, err := d.Int64()
s.TargetAssetID = int64(v)
@@ -902,7 +936,7 @@ func (s *Mapfix) Decode(d *jx.Decoder) error {
return errors.Wrap(err, "decode field \"TargetAssetID\"")
}
case "StatusID":
requiredBitSet[1] |= 1 << 3
requiredBitSet[1] |= 1 << 5
if err := func() error {
v, err := d.Int32()
s.StatusID = int32(v)
@@ -914,7 +948,7 @@ func (s *Mapfix) Decode(d *jx.Decoder) error {
return errors.Wrap(err, "decode field \"StatusID\"")
}
case "Description":
requiredBitSet[1] |= 1 << 4
requiredBitSet[1] |= 1 << 6
if err := func() error {
v, err := d.Str()
s.Description = string(v)
@@ -936,7 +970,7 @@ func (s *Mapfix) Decode(d *jx.Decoder) error {
var failures []validate.FieldError
for i, mask := range [2]uint8{
0b11111111,
0b00011111,
0b01111001,
} {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR.

View File

@@ -12,10 +12,12 @@ const (
ActionMapfixResetSubmittingOperation OperationName = "ActionMapfixResetSubmitting"
ActionMapfixRetryValidateOperation OperationName = "ActionMapfixRetryValidate"
ActionMapfixRevokeOperation OperationName = "ActionMapfixRevoke"
ActionMapfixTriggerReleaseOperation OperationName = "ActionMapfixTriggerRelease"
ActionMapfixTriggerSubmitOperation OperationName = "ActionMapfixTriggerSubmit"
ActionMapfixTriggerSubmitUncheckedOperation OperationName = "ActionMapfixTriggerSubmitUnchecked"
ActionMapfixTriggerUploadOperation OperationName = "ActionMapfixTriggerUpload"
ActionMapfixTriggerValidateOperation OperationName = "ActionMapfixTriggerValidate"
ActionMapfixUploadedOperation OperationName = "ActionMapfixUploaded"
ActionMapfixValidatedOperation OperationName = "ActionMapfixValidated"
ActionSubmissionAcceptedOperation OperationName = "ActionSubmissionAccepted"
ActionSubmissionRejectOperation OperationName = "ActionSubmissionReject"
@@ -51,6 +53,7 @@ const (
ListScriptsOperation OperationName = "ListScripts"
ListSubmissionAuditEventsOperation OperationName = "ListSubmissionAuditEvents"
ListSubmissionsOperation OperationName = "ListSubmissions"
MigrateMapfixesOperation OperationName = "MigrateMapfixes"
ReleaseSubmissionsOperation OperationName = "ReleaseSubmissions"
SessionRolesOperation OperationName = "SessionRoles"
SessionUserOperation OperationName = "SessionUser"

View File

@@ -513,6 +513,89 @@ func decodeActionMapfixRevokeParams(args [1]string, argsEscaped bool, r *http.Re
return params, nil
}
// ActionMapfixTriggerReleaseParams is parameters of actionMapfixTriggerRelease operation.
type ActionMapfixTriggerReleaseParams struct {
// The unique identifier for a mapfix.
MapfixID int64
}
func unpackActionMapfixTriggerReleaseParams(packed middleware.Parameters) (params ActionMapfixTriggerReleaseParams) {
{
key := middleware.ParameterKey{
Name: "MapfixID",
In: "path",
}
params.MapfixID = packed[key].(int64)
}
return params
}
func decodeActionMapfixTriggerReleaseParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionMapfixTriggerReleaseParams, _ 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
}
// ActionMapfixTriggerSubmitParams is parameters of actionMapfixTriggerSubmit operation.
type ActionMapfixTriggerSubmitParams struct {
// The unique identifier for a mapfix.
@@ -845,6 +928,89 @@ func decodeActionMapfixTriggerValidateParams(args [1]string, argsEscaped bool, r
return params, nil
}
// ActionMapfixUploadedParams is parameters of actionMapfixUploaded operation.
type ActionMapfixUploadedParams struct {
// The unique identifier for a mapfix.
MapfixID int64
}
func unpackActionMapfixUploadedParams(packed middleware.Parameters) (params ActionMapfixUploadedParams) {
{
key := middleware.ParameterKey{
Name: "MapfixID",
In: "path",
}
params.MapfixID = packed[key].(int64)
}
return params
}
func decodeActionMapfixUploadedParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionMapfixUploadedParams, _ 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
}
// ActionMapfixValidatedParams is parameters of actionMapfixValidated operation.
type ActionMapfixValidatedParams struct {
// The unique identifier for a mapfix.
@@ -2972,6 +3138,7 @@ type ListMapfixesParams struct {
Sort OptInt32
Submitter OptInt64
AssetID OptInt64
AssetVersion OptInt64
TargetAssetID OptInt64
// // Phase: Creation * `0` - UnderConstruction * `1` - ChangesRequested
// // Phase: Review * `2` - Submitting * `3` - Submitted
@@ -3051,6 +3218,15 @@ func unpackListMapfixesParams(packed middleware.Parameters) (params ListMapfixes
params.AssetID = v.(OptInt64)
}
}
{
key := middleware.ParameterKey{
Name: "AssetVersion",
In: "query",
}
if v, ok := packed[key]; ok {
params.AssetVersion = v.(OptInt64)
}
}
{
key := middleware.ParameterKey{
Name: "TargetAssetID",
@@ -3568,6 +3744,71 @@ func decodeListMapfixesParams(args [0]string, argsEscaped bool, r *http.Request)
Err: err,
}
}
// Decode query: AssetVersion.
if err := func() error {
cfg := uri.QueryParameterDecodingConfig{
Name: "AssetVersion",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.HasParam(cfg); err == nil {
if err := q.DecodeParam(cfg, func(d uri.Decoder) error {
var paramsDotAssetVersionVal int64
if err := func() error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToInt64(val)
if err != nil {
return err
}
paramsDotAssetVersionVal = c
return nil
}(); err != nil {
return err
}
params.AssetVersion.SetTo(paramsDotAssetVersionVal)
return nil
}); err != nil {
return err
}
if err := func() error {
if value, ok := params.AssetVersion.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: "AssetVersion",
In: "query",
Err: err,
}
}
// Decode query: TargetAssetID.
if err := func() error {
cfg := uri.QueryParameterDecodingConfig{
@@ -5221,6 +5462,7 @@ type ListSubmissionsParams struct {
Sort OptInt32
Submitter OptInt64
AssetID OptInt64
AssetVersion OptInt64
UploadedAssetID OptInt64
// // Phase: Creation * `0` - UnderConstruction * `1` - ChangesRequested
// // Phase: Review * `2` - Submitting * `3` - Submitted
@@ -5300,6 +5542,15 @@ func unpackListSubmissionsParams(packed middleware.Parameters) (params ListSubmi
params.AssetID = v.(OptInt64)
}
}
{
key := middleware.ParameterKey{
Name: "AssetVersion",
In: "query",
}
if v, ok := packed[key]; ok {
params.AssetVersion = v.(OptInt64)
}
}
{
key := middleware.ParameterKey{
Name: "UploadedAssetID",
@@ -5817,6 +6068,71 @@ func decodeListSubmissionsParams(args [0]string, argsEscaped bool, r *http.Reque
Err: err,
}
}
// Decode query: AssetVersion.
if err := func() error {
cfg := uri.QueryParameterDecodingConfig{
Name: "AssetVersion",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.HasParam(cfg); err == nil {
if err := q.DecodeParam(cfg, func(d uri.Decoder) error {
var paramsDotAssetVersionVal int64
if err := func() error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToInt64(val)
if err != nil {
return err
}
paramsDotAssetVersionVal = c
return nil
}(); err != nil {
return err
}
params.AssetVersion.SetTo(paramsDotAssetVersionVal)
return nil
}); err != nil {
return err
}
if err := func() error {
if value, ok := params.AssetVersion.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: "AssetVersion",
In: "query",
Err: err,
}
}
// Decode query: UploadedAssetID.
if err := func() error {
cfg := uri.QueryParameterDecodingConfig{

View File

@@ -376,6 +376,66 @@ func decodeActionMapfixRevokeResponse(resp *http.Response) (res *ActionMapfixRev
return res, errors.Wrap(defRes, "error")
}
func decodeActionMapfixTriggerReleaseResponse(resp *http.Response) (res *ActionMapfixTriggerReleaseNoContent, _ error) {
switch resp.StatusCode {
case 204:
// Code 204.
return &ActionMapfixTriggerReleaseNoContent{}, 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 decodeActionMapfixTriggerSubmitResponse(resp *http.Response) (res *ActionMapfixTriggerSubmitNoContent, _ error) {
switch resp.StatusCode {
case 204:
@@ -616,6 +676,66 @@ func decodeActionMapfixTriggerValidateResponse(resp *http.Response) (res *Action
return res, errors.Wrap(defRes, "error")
}
func decodeActionMapfixUploadedResponse(resp *http.Response) (res *ActionMapfixUploadedNoContent, _ error) {
switch resp.StatusCode {
case 204:
// Code 204.
return &ActionMapfixUploadedNoContent{}, 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 decodeActionMapfixValidatedResponse(resp *http.Response) (res *ActionMapfixValidatedNoContent, _ error) {
switch resp.StatusCode {
case 204:
@@ -3595,6 +3715,66 @@ func decodeListSubmissionsResponse(resp *http.Response) (res *Submissions, _ err
return res, errors.Wrap(defRes, "error")
}
func decodeMigrateMapfixesResponse(resp *http.Response) (res *MigrateMapfixesNoContent, _ error) {
switch resp.StatusCode {
case 204:
// Code 204.
return &MigrateMapfixesNoContent{}, 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 decodeReleaseSubmissionsResponse(resp *http.Response) (res *ReleaseSubmissionsCreated, _ error) {
switch resp.StatusCode {
case 201:

View File

@@ -56,6 +56,13 @@ func encodeActionMapfixRevokeResponse(response *ActionMapfixRevokeNoContent, w h
return nil
}
func encodeActionMapfixTriggerReleaseResponse(response *ActionMapfixTriggerReleaseNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeActionMapfixTriggerSubmitResponse(response *ActionMapfixTriggerSubmitNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
@@ -84,6 +91,13 @@ func encodeActionMapfixTriggerValidateResponse(response *ActionMapfixTriggerVali
return nil
}
func encodeActionMapfixUploadedResponse(response *ActionMapfixUploadedNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeActionMapfixValidatedResponse(response *ActionMapfixValidatedNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
@@ -484,6 +498,13 @@ func encodeListSubmissionsResponse(response *Submissions, w http.ResponseWriter,
return nil
}
func encodeMigrateMapfixesResponse(response *MigrateMapfixesNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeReleaseSubmissionsResponse(response *ReleaseSubmissionsCreated, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(201)
span.SetStatus(codes.Ok, http.StatusText(201))

View File

@@ -102,6 +102,32 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 'm': // Prefix: "migrate"
origElem := elem
if l := len("migrate"); len(elem) >= l && elem[0:l] == "migrate" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleMigrateMapfixesRequest([0]string{}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
elem = origElem
}
// Param: "MapfixID"
// Match until "/"
idx := strings.IndexByte(elem, '/')
@@ -318,6 +344,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
break
}
switch elem[0] {
case 'r': // Prefix: "releasing"
if l := len("releasing"); len(elem) >= l && elem[0:l] == "releasing" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionMapfixUploadedRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
case 's': // Prefix: "submitting"
if l := len("submitting"); len(elem) >= l && elem[0:l] == "submitting" {
@@ -444,6 +492,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
break
}
switch elem[0] {
case 'r': // Prefix: "release"
if l := len("release"); len(elem) >= l && elem[0:l] == "release" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionMapfixTriggerReleaseRequest([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" {
@@ -1532,6 +1602,36 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 'm': // Prefix: "migrate"
origElem := elem
if l := len("migrate"); len(elem) >= l && elem[0:l] == "migrate" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = MigrateMapfixesOperation
r.summary = "Perform the Uploaded -> Released migration."
r.operationID = "migrateMapfixes"
r.pathPattern = "/mapfixes/migrate"
r.args = args
r.count = 0
return r, true
default:
return
}
}
elem = origElem
}
// Param: "MapfixID"
// Match until "/"
idx := strings.IndexByte(elem, '/')
@@ -1762,6 +1862,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
break
}
switch elem[0] {
case 'r': // Prefix: "releasing"
if l := len("releasing"); len(elem) >= l && elem[0:l] == "releasing" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionMapfixUploadedOperation
r.summary = "Role MapfixUpload manually resets releasing softlock and changes status from Releasing -> Uploaded"
r.operationID = "actionMapfixUploaded"
r.pathPattern = "/mapfixes/{MapfixID}/status/reset-releasing"
r.args = args
r.count = 1
return r, true
default:
return
}
}
case 's': // Prefix: "submitting"
if l := len("submitting"); len(elem) >= l && elem[0:l] == "submitting" {
@@ -1799,7 +1923,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
switch method {
case "POST":
r.name = ActionMapfixValidatedOperation
r.summary = "Role Admin manually resets uploading softlock and changes status from Uploading -> Validated"
r.summary = "Role MapfixUpload manually resets uploading softlock and changes status from Uploading -> Validated"
r.operationID = "actionMapfixValidated"
r.pathPattern = "/mapfixes/{MapfixID}/status/reset-uploading"
r.args = args
@@ -1898,6 +2022,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
break
}
switch elem[0] {
case 'r': // Prefix: "release"
if l := len("release"); len(elem) >= l && elem[0:l] == "release" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionMapfixTriggerReleaseOperation
r.summary = "Role MapfixUpload changes status from Uploaded -> Releasing"
r.operationID = "actionMapfixTriggerRelease"
r.pathPattern = "/mapfixes/{MapfixID}/status/trigger-release"
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" {
@@ -1960,7 +2108,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
switch method {
case "POST":
r.name = ActionMapfixTriggerUploadOperation
r.summary = "Role Admin changes status from Validated -> Uploading"
r.summary = "Role MapfixUpload changes status from Validated -> Uploading"
r.operationID = "actionMapfixTriggerUpload"
r.pathPattern = "/mapfixes/{MapfixID}/status/trigger-upload"
r.args = args
@@ -2136,7 +2284,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
switch method {
case "POST":
r.name = ReleaseSubmissionsOperation
r.summary = "Release a set of uploaded maps"
r.summary = "Release a set of uploaded maps. Role SubmissionRelease"
r.operationID = "releaseSubmissions"
r.pathPattern = "/release-submissions"
r.args = args
@@ -2753,7 +2901,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
switch method {
case "POST":
r.name = ActionSubmissionValidatedOperation
r.summary = "Role Admin manually resets uploading softlock and changes status from Uploading -> Validated"
r.summary = "Role SubmissionUpload manually resets uploading softlock and changes status from Uploading -> Validated"
r.operationID = "actionSubmissionValidated"
r.pathPattern = "/submissions/{SubmissionID}/status/reset-uploading"
r.args = args
@@ -2914,7 +3062,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
switch method {
case "POST":
r.name = ActionSubmissionTriggerUploadOperation
r.summary = "Role Admin changes status from Validated -> Uploading"
r.summary = "Role SubmissionUpload changes status from Validated -> Uploading"
r.operationID = "actionSubmissionTriggerUpload"
r.pathPattern = "/submissions/{SubmissionID}/status/trigger-upload"
r.args = args

View File

@@ -32,6 +32,9 @@ type ActionMapfixRetryValidateNoContent struct{}
// ActionMapfixRevokeNoContent is response for ActionMapfixRevoke operation.
type ActionMapfixRevokeNoContent struct{}
// ActionMapfixTriggerReleaseNoContent is response for ActionMapfixTriggerRelease operation.
type ActionMapfixTriggerReleaseNoContent struct{}
// ActionMapfixTriggerSubmitNoContent is response for ActionMapfixTriggerSubmit operation.
type ActionMapfixTriggerSubmitNoContent struct{}
@@ -44,6 +47,9 @@ type ActionMapfixTriggerUploadNoContent struct{}
// ActionMapfixTriggerValidateNoContent is response for ActionMapfixTriggerValidate operation.
type ActionMapfixTriggerValidateNoContent struct{}
// ActionMapfixUploadedNoContent is response for ActionMapfixUploaded operation.
type ActionMapfixUploadedNoContent struct{}
// ActionMapfixValidatedNoContent is response for ActionMapfixValidated operation.
type ActionMapfixValidatedNoContent struct{}
@@ -456,19 +462,21 @@ func (s *Map) SetModes(val uint32) {
// Ref: #/components/schemas/Mapfix
type Mapfix struct {
ID int64 `json:"ID"`
DisplayName string `json:"DisplayName"`
Creator string `json:"Creator"`
GameID int32 `json:"GameID"`
CreatedAt int64 `json:"CreatedAt"`
UpdatedAt int64 `json:"UpdatedAt"`
Submitter int64 `json:"Submitter"`
AssetID int64 `json:"AssetID"`
AssetVersion int64 `json:"AssetVersion"`
Completed bool `json:"Completed"`
TargetAssetID int64 `json:"TargetAssetID"`
StatusID int32 `json:"StatusID"`
Description string `json:"Description"`
ID int64 `json:"ID"`
DisplayName string `json:"DisplayName"`
Creator string `json:"Creator"`
GameID int32 `json:"GameID"`
CreatedAt int64 `json:"CreatedAt"`
UpdatedAt int64 `json:"UpdatedAt"`
Submitter int64 `json:"Submitter"`
AssetID int64 `json:"AssetID"`
AssetVersion int64 `json:"AssetVersion"`
ValidatedAssetID OptInt64 `json:"ValidatedAssetID"`
ValidatedAssetVersion OptInt64 `json:"ValidatedAssetVersion"`
Completed bool `json:"Completed"`
TargetAssetID int64 `json:"TargetAssetID"`
StatusID int32 `json:"StatusID"`
Description string `json:"Description"`
}
// GetID returns the value of ID.
@@ -516,6 +524,16 @@ func (s *Mapfix) GetAssetVersion() int64 {
return s.AssetVersion
}
// GetValidatedAssetID returns the value of ValidatedAssetID.
func (s *Mapfix) GetValidatedAssetID() OptInt64 {
return s.ValidatedAssetID
}
// GetValidatedAssetVersion returns the value of ValidatedAssetVersion.
func (s *Mapfix) GetValidatedAssetVersion() OptInt64 {
return s.ValidatedAssetVersion
}
// GetCompleted returns the value of Completed.
func (s *Mapfix) GetCompleted() bool {
return s.Completed
@@ -581,6 +599,16 @@ func (s *Mapfix) SetAssetVersion(val int64) {
s.AssetVersion = val
}
// SetValidatedAssetID sets the value of ValidatedAssetID.
func (s *Mapfix) SetValidatedAssetID(val OptInt64) {
s.ValidatedAssetID = val
}
// SetValidatedAssetVersion sets the value of ValidatedAssetVersion.
func (s *Mapfix) SetValidatedAssetVersion(val OptInt64) {
s.ValidatedAssetVersion = val
}
// SetCompleted sets the value of Completed.
func (s *Mapfix) SetCompleted(val bool) {
s.Completed = val
@@ -664,6 +692,9 @@ func (s *Mapfixes) SetMapfixes(val []Mapfix) {
s.Mapfixes = val
}
// MigrateMapfixesNoContent is response for MigrateMapfixes operation.
type MigrateMapfixesNoContent struct{}
// Ref: #/components/schemas/Operation
type Operation struct {
OperationID int32 `json:"OperationID"`

View File

@@ -40,10 +40,12 @@ var operationRolesCookieAuth = map[string][]string{
ActionMapfixResetSubmittingOperation: []string{},
ActionMapfixRetryValidateOperation: []string{},
ActionMapfixRevokeOperation: []string{},
ActionMapfixTriggerReleaseOperation: []string{},
ActionMapfixTriggerSubmitOperation: []string{},
ActionMapfixTriggerSubmitUncheckedOperation: []string{},
ActionMapfixTriggerUploadOperation: []string{},
ActionMapfixTriggerValidateOperation: []string{},
ActionMapfixUploadedOperation: []string{},
ActionMapfixValidatedOperation: []string{},
ActionSubmissionAcceptedOperation: []string{},
ActionSubmissionRejectOperation: []string{},
@@ -67,6 +69,7 @@ var operationRolesCookieAuth = map[string][]string{
DeleteScriptPolicyOperation: []string{},
DownloadMapAssetOperation: []string{},
GetOperationOperation: []string{},
MigrateMapfixesOperation: []string{},
ReleaseSubmissionsOperation: []string{},
SessionRolesOperation: []string{},
SessionUserOperation: []string{},

View File

@@ -45,6 +45,12 @@ type Handler interface {
//
// POST /mapfixes/{MapfixID}/status/revoke
ActionMapfixRevoke(ctx context.Context, params ActionMapfixRevokeParams) error
// ActionMapfixTriggerRelease implements actionMapfixTriggerRelease operation.
//
// Role MapfixUpload changes status from Uploaded -> Releasing.
//
// POST /mapfixes/{MapfixID}/status/trigger-release
ActionMapfixTriggerRelease(ctx context.Context, params ActionMapfixTriggerReleaseParams) error
// ActionMapfixTriggerSubmit implements actionMapfixTriggerSubmit operation.
//
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting.
@@ -59,7 +65,7 @@ type Handler interface {
ActionMapfixTriggerSubmitUnchecked(ctx context.Context, params ActionMapfixTriggerSubmitUncheckedParams) error
// ActionMapfixTriggerUpload implements actionMapfixTriggerUpload operation.
//
// Role Admin changes status from Validated -> Uploading.
// Role MapfixUpload changes status from Validated -> Uploading.
//
// POST /mapfixes/{MapfixID}/status/trigger-upload
ActionMapfixTriggerUpload(ctx context.Context, params ActionMapfixTriggerUploadParams) error
@@ -69,9 +75,15 @@ type Handler interface {
//
// POST /mapfixes/{MapfixID}/status/trigger-validate
ActionMapfixTriggerValidate(ctx context.Context, params ActionMapfixTriggerValidateParams) error
// ActionMapfixUploaded implements actionMapfixUploaded operation.
//
// Role MapfixUpload manually resets releasing softlock and changes status from Releasing -> Uploaded.
//
// POST /mapfixes/{MapfixID}/status/reset-releasing
ActionMapfixUploaded(ctx context.Context, params ActionMapfixUploadedParams) error
// ActionMapfixValidated implements actionMapfixValidated operation.
//
// Role Admin manually resets uploading softlock and changes status from Uploading -> Validated.
// Role MapfixUpload manually resets uploading softlock and changes status from Uploading -> Validated.
//
// POST /mapfixes/{MapfixID}/status/reset-uploading
ActionMapfixValidated(ctx context.Context, params ActionMapfixValidatedParams) error
@@ -126,7 +138,7 @@ type Handler interface {
ActionSubmissionTriggerSubmitUnchecked(ctx context.Context, params ActionSubmissionTriggerSubmitUncheckedParams) error
// ActionSubmissionTriggerUpload implements actionSubmissionTriggerUpload operation.
//
// Role Admin changes status from Validated -> Uploading.
// Role SubmissionUpload changes status from Validated -> Uploading.
//
// POST /submissions/{SubmissionID}/status/trigger-upload
ActionSubmissionTriggerUpload(ctx context.Context, params ActionSubmissionTriggerUploadParams) error
@@ -138,7 +150,8 @@ type Handler interface {
ActionSubmissionTriggerValidate(ctx context.Context, params ActionSubmissionTriggerValidateParams) error
// ActionSubmissionValidated implements actionSubmissionValidated operation.
//
// Role Admin manually resets uploading softlock and changes status from Uploading -> Validated.
// Role SubmissionUpload manually resets uploading softlock and changes status from Uploading ->
// Validated.
//
// POST /submissions/{SubmissionID}/status/reset-uploading
ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error
@@ -280,9 +293,15 @@ type Handler interface {
//
// GET /submissions
ListSubmissions(ctx context.Context, params ListSubmissionsParams) (*Submissions, error)
// MigrateMapfixes implements migrateMapfixes operation.
//
// Perform the Uploaded -> Released migration.
//
// POST /mapfixes/migrate
MigrateMapfixes(ctx context.Context) error
// ReleaseSubmissions implements releaseSubmissions operation.
//
// Release a set of uploaded maps.
// Release a set of uploaded maps. Role SubmissionRelease.
//
// POST /release-submissions
ReleaseSubmissions(ctx context.Context, req []ReleaseInfo) error

View File

@@ -68,6 +68,15 @@ func (UnimplementedHandler) ActionMapfixRevoke(ctx context.Context, params Actio
return ht.ErrNotImplemented
}
// ActionMapfixTriggerRelease implements actionMapfixTriggerRelease operation.
//
// Role MapfixUpload changes status from Uploaded -> Releasing.
//
// POST /mapfixes/{MapfixID}/status/trigger-release
func (UnimplementedHandler) ActionMapfixTriggerRelease(ctx context.Context, params ActionMapfixTriggerReleaseParams) error {
return ht.ErrNotImplemented
}
// ActionMapfixTriggerSubmit implements actionMapfixTriggerSubmit operation.
//
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting.
@@ -88,7 +97,7 @@ func (UnimplementedHandler) ActionMapfixTriggerSubmitUnchecked(ctx context.Conte
// ActionMapfixTriggerUpload implements actionMapfixTriggerUpload operation.
//
// Role Admin changes status from Validated -> Uploading.
// Role MapfixUpload changes status from Validated -> Uploading.
//
// POST /mapfixes/{MapfixID}/status/trigger-upload
func (UnimplementedHandler) ActionMapfixTriggerUpload(ctx context.Context, params ActionMapfixTriggerUploadParams) error {
@@ -104,9 +113,18 @@ func (UnimplementedHandler) ActionMapfixTriggerValidate(ctx context.Context, par
return ht.ErrNotImplemented
}
// ActionMapfixUploaded implements actionMapfixUploaded operation.
//
// Role MapfixUpload manually resets releasing softlock and changes status from Releasing -> Uploaded.
//
// POST /mapfixes/{MapfixID}/status/reset-releasing
func (UnimplementedHandler) ActionMapfixUploaded(ctx context.Context, params ActionMapfixUploadedParams) error {
return ht.ErrNotImplemented
}
// ActionMapfixValidated implements actionMapfixValidated operation.
//
// Role Admin manually resets uploading softlock and changes status from Uploading -> Validated.
// Role MapfixUpload manually resets uploading softlock and changes status from Uploading -> Validated.
//
// POST /mapfixes/{MapfixID}/status/reset-uploading
func (UnimplementedHandler) ActionMapfixValidated(ctx context.Context, params ActionMapfixValidatedParams) error {
@@ -188,7 +206,7 @@ func (UnimplementedHandler) ActionSubmissionTriggerSubmitUnchecked(ctx context.C
// ActionSubmissionTriggerUpload implements actionSubmissionTriggerUpload operation.
//
// Role Admin changes status from Validated -> Uploading.
// Role SubmissionUpload changes status from Validated -> Uploading.
//
// POST /submissions/{SubmissionID}/status/trigger-upload
func (UnimplementedHandler) ActionSubmissionTriggerUpload(ctx context.Context, params ActionSubmissionTriggerUploadParams) error {
@@ -206,7 +224,8 @@ func (UnimplementedHandler) ActionSubmissionTriggerValidate(ctx context.Context,
// ActionSubmissionValidated implements actionSubmissionValidated operation.
//
// Role Admin manually resets uploading softlock and changes status from Uploading -> Validated.
// Role SubmissionUpload manually resets uploading softlock and changes status from Uploading ->
// Validated.
//
// POST /submissions/{SubmissionID}/status/reset-uploading
func (UnimplementedHandler) ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error {
@@ -420,9 +439,18 @@ func (UnimplementedHandler) ListSubmissions(ctx context.Context, params ListSubm
return r, ht.ErrNotImplemented
}
// MigrateMapfixes implements migrateMapfixes operation.
//
// Perform the Uploaded -> Released migration.
//
// POST /mapfixes/migrate
func (UnimplementedHandler) MigrateMapfixes(ctx context.Context) error {
return ht.ErrNotImplemented
}
// ReleaseSubmissions implements releaseSubmissions operation.
//
// Release a set of uploaded maps.
// Release a set of uploaded maps. Role SubmissionRelease.
//
// POST /release-submissions
func (UnimplementedHandler) ReleaseSubmissions(ctx context.Context, req []ReleaseInfo) error {

View File

@@ -390,6 +390,60 @@ func (s *Mapfix) Validate() error {
Error: err,
})
}
if err := func() error {
if value, ok := s.ValidatedAssetID.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 {
failures = append(failures, validate.FieldError{
Name: "ValidatedAssetID",
Error: err,
})
}
if err := func() error {
if value, ok := s.ValidatedAssetVersion.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 {
failures = append(failures, validate.FieldError{
Name: "ValidatedAssetVersion",
Error: err,
})
}
if err := func() error {
if err := (validate.Int{
MinSet: true,

View File

@@ -61,6 +61,7 @@ type Mapfixes interface {
Delete(ctx context.Context, id int64) error
List(ctx context.Context, filters OptionalMap, page model.Page, sort ListSort) ([]model.Mapfix, error)
ListWithTotal(ctx context.Context, filters OptionalMap, page model.Page, sort ListSort) (int64, []model.Mapfix, error)
Migrate(ctx context.Context) error
}
type Operations interface {

View File

@@ -8,6 +8,8 @@ import (
"git.itzana.me/strafesnet/maps-service/pkg/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
log "github.com/sirupsen/logrus"
)
type Mapfixes struct {
@@ -151,3 +153,18 @@ func (env *Mapfixes) ListWithTotal(ctx context.Context, filters datastore.Option
return total, maps, nil
}
func (env *Mapfixes) Migrate(ctx context.Context) error {
migrate_from := model.MapfixStatusUploaded
migrate_to := model.MapfixStatusReleased
count := int64(0)
err := env.db.
Model(&model.Mapfix{}).
Where("status_id = ?", migrate_from).
Set("status_id = ?", migrate_to).
Count(&count).
Error
log.Println("Mapfixes Migration rows affected:",count)
return err
}

View File

@@ -18,10 +18,12 @@ const (
MapfixStatusValidating MapfixStatus = 5
MapfixStatusValidated MapfixStatus = 6
MapfixStatusUploading MapfixStatus = 7
MapfixStatusUploaded MapfixStatus = 8 // uploaded to the group, but pending release
MapfixStatusReleasing MapfixStatus = 11
// Phase: Final MapfixStatus
MapfixStatusUploaded MapfixStatus = 8 // uploaded to the group, but pending release
MapfixStatusRejected MapfixStatus = 9
MapfixStatusReleased MapfixStatus = 10
)
type Mapfix struct {

View File

@@ -65,3 +65,29 @@ type UploadMapfixRequest struct {
ModelVersion uint64
TargetAssetID uint64
}
type ReleaseSubmissionRequest struct {
// Release schedule
SubmissionID int64
ReleaseDate int64
// Model download info
ModelID uint64
ModelVersion uint64
// MapCreate
UploadedAssetID uint64
DisplayName string
Creator string
GameID uint32
Submitter uint64
}
type BatchReleaseSubmissionsRequest struct {
Submissions []ReleaseSubmissionRequest
OperationID int32
}
type ReleaseMapfixRequest struct {
MapfixID int64
ModelID uint64
ModelVersion uint64
TargetAssetID uint64
}

View File

@@ -114,3 +114,7 @@ func (svc *Service) UpdateMapfixIfStatus(ctx context.Context, id int64, statuses
func (svc *Service) UpdateAndGetMapfixIfStatus(ctx context.Context, id int64, statuses []model.MapfixStatus, pmap MapfixUpdate) (model.Mapfix, error) {
return svc.db.Mapfixes().IfStatusThenUpdateAndGet(ctx, id, statuses, datastore.OptionalMap(pmap))
}
func (svc *Service) MigrateMapfixes(ctx context.Context) error {
return svc.db.Mapfixes().Migrate(ctx)
}

View File

@@ -112,3 +112,29 @@ func (svc *Service) NatsValidateMapfix(
return nil
}
func (svc *Service) NatsReleaseMapfix(
MapfixID int64,
ModelID uint64,
ModelVersion uint64,
TargetAssetID uint64,
) error {
release_fix_request := model.ReleaseMapfixRequest{
MapfixID: MapfixID,
ModelID: ModelID,
ModelVersion: ModelVersion,
TargetAssetID: TargetAssetID,
}
j, err := json.Marshal(release_fix_request)
if err != nil {
return err
}
_, err = svc.nats.Publish("maptest.mapfixes.release", []byte(j))
if err != nil {
return err
}
return nil
}

View File

@@ -88,6 +88,28 @@ func (svc *Service) NatsUploadSubmission(
return nil
}
func (svc *Service) NatsBatchReleaseSubmissions(
Submissions []model.ReleaseSubmissionRequest,
operation int32,
) error {
release_new_request := model.BatchReleaseSubmissionsRequest{
Submissions: Submissions,
OperationID: operation,
}
j, err := json.Marshal(release_new_request)
if err != nil {
return err
}
_, err = svc.nats.Publish("maptest.submissions.batchrelease", []byte(j))
if err != nil {
return err
}
return nil
}
func (svc *Service) NatsValidateSubmission(
SubmissionID int64,
ModelID uint64,

View File

@@ -26,6 +26,8 @@ func NewMapfixesController(
var(
// prevent two mapfixes with same asset id
ActiveMapfixStatuses = []model.MapfixStatus{
model.MapfixStatusReleasing,
model.MapfixStatusUploaded,
model.MapfixStatusUploading,
model.MapfixStatusValidated,
model.MapfixStatusValidating,
@@ -184,7 +186,7 @@ func (svc *Mapfixes) SetStatusValidated(ctx context.Context, params *validator.M
// (Internal endpoint) Role Validator changes status from Validating -> Accepted.
//
// POST /mapfixes/{MapfixID}/status/validator-failed
func (svc *Mapfixes) SetStatusFailed(ctx context.Context, params *validator.MapfixID) (*validator.NullResponse, error) {
func (svc *Mapfixes) SetStatusNotValidated(ctx context.Context, params *validator.MapfixID) (*validator.NullResponse, error) {
MapfixID := int64(params.ID)
// transaction
target_status := model.MapfixStatusAcceptedUnvalidated
@@ -253,6 +255,117 @@ func (svc *Mapfixes) SetStatusUploaded(ctx context.Context, params *validator.Ma
return &validator.NullResponse{}, nil
}
func (svc *Mapfixes) SetStatusNotUploaded(ctx context.Context, params *validator.MapfixID) (*validator.NullResponse, error) {
MapfixID := int64(params.ID)
// transaction
target_status := model.MapfixStatusValidated
update := service.NewMapfixUpdate()
update.SetStatusID(target_status)
allow_statuses := []model.MapfixStatus{model.MapfixStatusUploading}
err := svc.inner.UpdateMapfixIfStatus(ctx, MapfixID, allow_statuses, update)
if err != nil {
return nil, err
}
// push an action audit event
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
err = svc.inner.CreateAuditEventAction(
ctx,
model.ValidatorUserID,
model.Resource{
ID: MapfixID,
Type: model.ResourceMapfix,
},
event_data,
)
if err != nil {
return nil, err
}
return &validator.NullResponse{}, nil
}
// ActionMapfixReleased implements actionMapfixReleased operation.
//
// (Internal endpoint) Role Validator changes status from Releasing -> Released.
//
// POST /mapfixes/{MapfixID}/status/validator-released
func (svc *Mapfixes) SetStatusReleased(ctx context.Context, params *validator.MapfixReleaseRequest) (*validator.NullResponse, error) {
MapfixID := int64(params.MapfixID)
// transaction
target_status := model.MapfixStatusReleased
update := service.NewMapfixUpdate()
update.SetStatusID(target_status)
allow_statuses := []model.MapfixStatus{model.MapfixStatusReleasing}
err := svc.inner.UpdateMapfixIfStatus(ctx, MapfixID, allow_statuses, update)
if err != nil {
return nil, err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
err = svc.inner.CreateAuditEventAction(
ctx,
model.ValidatorUserID,
model.Resource{
ID: MapfixID,
Type: model.ResourceMapfix,
},
event_data,
)
if err != nil {
return nil, err
}
// metadata maintenance
map_update := service.NewMapUpdate()
map_update.SetAssetVersion(params.AssetVersion)
map_update.SetModes(params.Modes)
err = svc.inner.UpdateMap(ctx, int64(params.TargetAssetID), map_update)
if err != nil {
return nil, err
}
return &validator.NullResponse{}, nil
}
func (svc *Mapfixes) SetStatusNotReleased(ctx context.Context, params *validator.MapfixID) (*validator.NullResponse, error) {
MapfixID := int64(params.ID)
// transaction
target_status := model.MapfixStatusUploaded
update := service.NewMapfixUpdate()
update.SetStatusID(target_status)
allow_statuses := []model.MapfixStatus{model.MapfixStatusReleasing}
err := svc.inner.UpdateMapfixIfStatus(ctx, MapfixID, allow_statuses, update)
if err != nil {
return nil, err
}
// push an action audit event
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
err = svc.inner.CreateAuditEventAction(
ctx,
model.ValidatorUserID,
model.Resource{
ID: MapfixID,
Type: model.ResourceMapfix,
},
event_data,
)
if err != nil {
return nil, err
}
return &validator.NullResponse{}, nil
}
// CreateMapfixAuditError implements createMapfixAuditError operation.
//

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"time"
"git.itzana.me/strafesnet/go-grpc/validator"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
@@ -24,7 +25,7 @@ func NewSubmissionsController(
}
var(
// prevent two mapfixes with same asset id
// prevent two submissions with same asset id
ActiveSubmissionStatuses = []model.SubmissionStatus{
model.SubmissionStatusUploading,
model.SubmissionStatusValidated,
@@ -202,7 +203,7 @@ func (svc *Submissions) SetStatusValidated(ctx context.Context, params *validato
// (Internal endpoint) Role Validator changes status from Validating -> Accepted.
//
// POST /submissions/{SubmissionID}/status/validator-failed
func (svc *Submissions) SetStatusFailed(ctx context.Context, params *validator.SubmissionID) (*validator.NullResponse, error) {
func (svc *Submissions) SetStatusNotValidated(ctx context.Context, params *validator.SubmissionID) (*validator.NullResponse, error) {
SubmissionID := int64(params.ID)
// transaction
target_status := model.SubmissionStatusAcceptedUnvalidated
@@ -273,6 +274,68 @@ func (svc *Submissions) SetStatusUploaded(ctx context.Context, params *validator
return &validator.NullResponse{}, nil
}
func (svc *Submissions) SetStatusNotUploaded(ctx context.Context, params *validator.SubmissionID) (*validator.NullResponse, error) {
SubmissionID := int64(params.ID)
// transaction
target_status := model.SubmissionStatusValidated
update := service.NewSubmissionUpdate()
update.SetStatusID(target_status)
allowed_statuses :=[]model.SubmissionStatus{model.SubmissionStatusUploading}
err := svc.inner.UpdateSubmissionIfStatus(ctx, SubmissionID, allowed_statuses, update)
if err != nil {
return nil, err
}
// push an action audit event
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
err = svc.inner.CreateAuditEventAction(
ctx,
model.ValidatorUserID,
model.Resource{
ID: SubmissionID,
Type: model.ResourceSubmission,
},
event_data,
)
if err != nil {
return nil, err
}
return &validator.NullResponse{}, nil
}
func (svc *Submissions) SetStatusReleased(ctx context.Context, params *validator.SubmissionReleaseRequest) (*validator.NullResponse, error){
// create map with go-grpc
_, err := svc.inner.CreateMap(ctx, model.Map{
ID: params.MapCreate.ID,
DisplayName: params.MapCreate.DisplayName,
Creator: params.MapCreate.Creator,
GameID: params.MapCreate.GameID,
Date: time.Unix(params.MapCreate.Date, 0),
Submitter: params.MapCreate.Submitter,
Thumbnail: 0,
AssetVersion: params.MapCreate.AssetVersion,
LoadCount: 0,
Modes: params.MapCreate.Modes,
})
if err != nil {
return nil, err
}
// update status to Released
update := service.NewSubmissionUpdate()
update.SetStatusID(model.SubmissionStatusReleased)
err = svc.inner.UpdateSubmissionIfStatus(ctx, int64(params.SubmissionID), []model.SubmissionStatus{model.SubmissionStatusUploaded}, update)
if err != nil {
return nil, err
}
return &validator.NullResponse{}, nil
}
// CreateSubmissionAuditError implements createSubmissionAuditError operation.
//
// Post an error to the audit log

View File

@@ -22,6 +22,8 @@ var(
}
// limit mapfixes in the pipeline to one per target map
ActiveAcceptedMapfixStatuses = []model.MapfixStatus{
model.MapfixStatusReleasing,
model.MapfixStatusUploaded,
model.MapfixStatusUploading,
model.MapfixStatusValidated,
model.MapfixStatusValidating,
@@ -193,6 +195,9 @@ func (svc *Service) ListMapfixes(ctx context.Context, params api.ListMapfixesPar
if asset_id, asset_id_ok := params.AssetID.Get(); asset_id_ok{
filter.SetAssetID(uint64(asset_id))
}
if asset_version, asset_version_ok := params.AssetVersion.Get(); asset_version_ok{
filter.SetAssetVersion(uint64(asset_version))
}
if target_asset_id, target_asset_id_ok := params.TargetAssetID.Get(); target_asset_id_ok{
filter.SetTargetAssetID(uint64(target_asset_id))
}
@@ -786,6 +791,127 @@ func (svc *Service) ActionMapfixValidated(ctx context.Context, params api.Action
)
}
// ActionMapfixTriggerRelease invokes actionMapfixTriggerRelease operation.
//
// Role MapfixUpload changes status from Uploaded -> Releasing.
//
// POST /mapfixes/{MapfixID}/status/trigger-release
func (svc *Service) ActionMapfixTriggerRelease(ctx context.Context, params api.ActionMapfixTriggerReleaseParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return ErrUserInfo
}
has_role, err := userInfo.HasRoleMapfixUpload()
if err != nil {
return err
}
// check if caller has required role
if !has_role {
return ErrPermissionDeniedNeedRoleMapfixUpload
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
}
// transaction
target_status := model.MapfixStatusReleasing
update := service.NewMapfixUpdate()
update.SetStatusID(target_status)
allow_statuses := []model.MapfixStatus{model.MapfixStatusUploaded}
mapfix, err := svc.inner.UpdateAndGetMapfixIfStatus(ctx, params.MapfixID, allow_statuses, update)
if err != nil {
return err
}
// this is a map fix
err = svc.inner.NatsReleaseMapfix(
mapfix.ID,
mapfix.ValidatedAssetID,
mapfix.ValidatedAssetVersion,
mapfix.TargetAssetID,
)
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
return svc.inner.CreateAuditEventAction(
ctx,
userId,
model.Resource{
ID: params.MapfixID,
Type: model.ResourceMapfix,
},
event_data,
)
}
// ActionMapfixUploaded invokes actionMapfixUploaded operation.
//
// Role MapfixUpload manually resets releasing softlock and changes status from Releasing -> Uploaded.
//
// POST /mapfixes/{MapfixID}/status/reset-releasing
func (svc *Service) ActionMapfixUploaded(ctx context.Context, params api.ActionMapfixUploadedParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return ErrUserInfo
}
has_role, err := userInfo.HasRoleMapfixUpload()
if err != nil {
return err
}
// check if caller has required role
if !has_role {
return ErrPermissionDeniedNeedRoleMapfixUpload
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
}
// check when mapfix was updated
mapfix, err := svc.inner.GetMapfix(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
}
// transaction
target_status := model.MapfixStatusUploaded
update := service.NewMapfixUpdate()
update.SetStatusID(target_status)
allow_statuses := []model.MapfixStatus{model.MapfixStatusReleasing}
err = svc.inner.UpdateMapfixIfStatus(ctx, params.MapfixID, allow_statuses, update)
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
return svc.inner.CreateAuditEventAction(
ctx,
userId,
model.Resource{
ID: params.MapfixID,
Type: model.ResourceMapfix,
},
event_data,
)
}
// ActionMapfixTriggerValidate invokes actionMapfixTriggerValidate operation.
//
// Role Reviewer triggers validation and changes status from Submitted -> Validating.
@@ -1066,3 +1192,26 @@ func (svc *Service) ListMapfixAuditEvents(ctx context.Context, params api.ListMa
},
)
}
// MigrateMapfixes invokes MigrateMapfixes operation.
//
// Retrieve a list of audit events.
//
// GET /mapfixes/{MapfixID}/audit-events
func (svc *Service) MigrateMapfixes(ctx context.Context) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return ErrUserInfo
}
has_role, err := userInfo.HasRoleSubmissionRelease()
if err != nil {
return err
}
// check if caller has required role
if !has_role {
return ErrPermissionDeniedNeedRoleSubmissionRelease
}
return svc.inner.MigrateMapfixes(ctx)
}

View File

@@ -20,13 +20,6 @@ var(
model.SubmissionStatusSubmitted,
model.SubmissionStatusUnderConstruction,
}
// limit submissions in the pipeline to one per target map
ActiveAcceptedSubmissionStatuses = []model.SubmissionStatus{
model.SubmissionStatusUploading,
model.SubmissionStatusValidated,
model.SubmissionStatusValidating,
model.SubmissionStatusAcceptedUnvalidated,
}
// Allow 5 submissions every 10 minutes
CreateSubmissionRateLimit int64 = 5
CreateSubmissionRecencyWindow = time.Second*600
@@ -236,6 +229,9 @@ func (svc *Service) ListSubmissions(ctx context.Context, params api.ListSubmissi
if asset_id, asset_id_ok := params.AssetID.Get(); asset_id_ok{
filter.SetAssetID(uint64(asset_id))
}
if asset_version, asset_version_ok := params.AssetVersion.Get(); asset_version_ok{
filter.SetAssetVersion(uint64(asset_version))
}
if uploaded_asset_id, uploaded_asset_id_ok := params.UploadedAssetID.Get(); uploaded_asset_id_ok{
filter.SetUploadedAssetID(uint64(uploaded_asset_id))
}
@@ -1053,6 +1049,11 @@ func (svc *Service) ReleaseSubmissions(ctx context.Context, request []api.Releas
return ErrPermissionDeniedNeedRoleSubmissionRelease
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
}
idList := make([]int64, len(request))
for i, releaseInfo := range request {
idList[i] = releaseInfo.SubmissionID
@@ -1074,32 +1075,39 @@ func (svc *Service) ReleaseSubmissions(ctx context.Context, request []api.Releas
}
}
for i,submission := range submissions{
date := request[i].Date.Unix()
// create each map with go-grpc
_, err := svc.inner.CreateMap(ctx, model.Map{
ID: int64(submission.UploadedAssetID),
DisplayName: submission.DisplayName,
Creator: submission.Creator,
GameID: submission.GameID,
Date: time.Unix(date, 0),
Submitter: submission.Submitter,
// Thumbnail: 0,
// AssetVersion: 0,
// LoadCount: 0,
// Modes: 0,
})
if err != nil {
return err
}
// construct batch release nats message
release_submissions := make([]model.ReleaseSubmissionRequest, len(request))
for i, submission := range submissions {
// from request
release_submissions[i].ReleaseDate = request[i].Date.Unix()
release_submissions[i].SubmissionID = request[i].SubmissionID
// from submission
release_submissions[i].ModelID = submission.ValidatedAssetID
release_submissions[i].ModelVersion = submission.ValidatedAssetVersion
// for map create
release_submissions[i].UploadedAssetID = submission.UploadedAssetID
release_submissions[i].DisplayName = submission.DisplayName
release_submissions[i].Creator = submission.Creator
release_submissions[i].GameID = submission.GameID
release_submissions[i].Submitter = submission.Submitter
}
// update each status to Released
update := service.NewSubmissionUpdate()
update.SetStatusID(model.SubmissionStatusReleased)
err = svc.inner.UpdateSubmissionIfStatus(ctx, submission.ID, []model.SubmissionStatus{model.SubmissionStatusUploaded}, update)
if err != nil {
return err
}
// create a trackable long-running operation
operation, err := svc.inner.CreateOperation(ctx, model.Operation{
Owner: userId,
StatusID: model.OperationStatusCreated,
})
if err != nil {
return err
}
// this is a map fix
err = svc.inner.NatsBatchReleaseSubmissions(
release_submissions,
operation.ID,
)
if err != nil {
return err
}
return nil

View File

@@ -1,6 +1,6 @@
[package]
name = "submissions-api"
version = "0.8.2"
version = "0.9.1"
edition = "2024"
publish = ["strafesnet"]
repository = "https://git.itzana.me/StrafesNET/maps-service"

View File

@@ -152,6 +152,48 @@ impl Context{
Ok(())
}
pub async fn get_mapfixes(&self,config:GetMapfixesRequest<'_>)->Result<MapfixesResponse,Error>{
let url_raw=format!("{}/mapfixes",self.0.base_url);
let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
{
let mut query_pairs=url.query_pairs_mut();
query_pairs.append_pair("Page",config.Page.to_string().as_str());
query_pairs.append_pair("Limit",config.Limit.to_string().as_str());
if let Some(sort)=config.Sort{
query_pairs.append_pair("Sort",(sort as u8).to_string().as_str());
}
if let Some(display_name)=config.DisplayName{
query_pairs.append_pair("DisplayName",display_name);
}
if let Some(creator)=config.Creator{
query_pairs.append_pair("Creator",creator);
}
if let Some(game_id)=config.GameID{
query_pairs.append_pair("GameID",(game_id as u8).to_string().as_str());
}
if let Some(submitter)=config.Submitter{
query_pairs.append_pair("Submitter",submitter.to_string().as_str());
}
if let Some(asset_id)=config.AssetID{
query_pairs.append_pair("AssetID",asset_id.to_string().as_str());
}
if let Some(asset_version)=config.AssetVersion{
query_pairs.append_pair("AssetVersion",asset_version.to_string().as_str());
}
if let Some(uploaded_asset_id)=config.TargetAssetID{
query_pairs.append_pair("TargetAssetID",uploaded_asset_id.to_string().as_str());
}
if let Some(status_id)=config.StatusID{
query_pairs.append_pair("StatusID",(status_id as u8).to_string().as_str());
}
}
response_ok(
self.0.get(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::ReqwestJson)
}
pub async fn get_submissions(&self,config:GetSubmissionsRequest<'_>)->Result<SubmissionsResponse,Error>{
let url_raw=format!("{}/submissions",self.0.base_url);
let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
@@ -178,6 +220,9 @@ impl Context{
if let Some(asset_id)=config.AssetID{
query_pairs.append_pair("AssetID",asset_id.to_string().as_str());
}
if let Some(asset_version)=config.AssetVersion{
query_pairs.append_pair("AssetVersion",asset_version.to_string().as_str());
}
if let Some(uploaded_asset_id)=config.UploadedAssetID{
query_pairs.append_pair("UploadedAssetID",uploaded_asset_id.to_string().as_str());
}

View File

@@ -252,6 +252,73 @@ pub enum Sort{
DateDescending=4,
}
#[derive(Clone,Debug,serde_repr::Serialize_repr,serde_repr::Deserialize_repr)]
#[repr(u8)]
pub enum MapfixStatus{
// Phase: Creation
UnderConstruction=0,
ChangesRequested=1,
// Phase: Review
Submitting=2,
Submitted=3,
// Phase: Testing
AcceptedUnvalidated=4, // pending script review, can re-trigger validation
Validating=5,
Validated=6,
Uploading=7,
Uploaded=8, // uploaded to the group, but pending release
Releasing=11,
// Phase: Final MapfixStatus
Rejected=9,
Released=10,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct GetMapfixesRequest<'a>{
pub Page:u32,
pub Limit:u32,
pub Sort:Option<Sort>,
pub DisplayName:Option<&'a str>,
pub Creator:Option<&'a str>,
pub GameID:Option<GameID>,
pub Submitter:Option<u64>,
pub AssetID:Option<u64>,
pub AssetVersion:Option<u64>,
pub TargetAssetID:Option<u64>,
pub StatusID:Option<MapfixStatus>,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug,serde::Serialize,serde::Deserialize)]
pub struct MapfixResponse{
pub ID:MapfixID,
pub DisplayName:String,
pub Creator:String,
pub GameID:u32,
pub CreatedAt:i64,
pub UpdatedAt:i64,
pub Submitter:u64,
pub AssetID:u64,
pub AssetVersion:u64,
pub ValidatedAssetID:Option<u64>,
pub ValidatedAssetVersion:Option<u64>,
pub Completed:bool,
pub TargetAssetID:u64,
pub StatusID:MapfixStatus,
pub Description:String,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)]
pub struct MapfixesResponse{
pub Total:u64,
pub Mapfixes:Vec<MapfixResponse>,
}
#[derive(Clone,Debug,serde_repr::Deserialize_repr)]
#[repr(u8)]
pub enum SubmissionStatus{
@@ -286,6 +353,7 @@ pub struct GetSubmissionsRequest<'a>{
pub GameID:Option<GameID>,
pub Submitter:Option<u64>,
pub AssetID:Option<u64>,
pub AssetVersion:Option<u64>,
pub UploadedAssetID:Option<u64>,
pub StatusID:Option<SubmissionStatus>,
}
@@ -302,6 +370,8 @@ pub struct SubmissionResponse{
pub Submitter:u64,
pub AssetID:u64,
pub AssetVersion:u64,
pub ValidatedAssetID:Option<u64>,
pub ValidatedAssetVersion:Option<u64>,
pub UploadedAssetID:u64,
pub StatusID:SubmissionStatus,
}

View File

@@ -17,5 +17,5 @@ siphasher = "1.0.1"
tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread", "signal"] }
heck = "0.5.0"
lazy-regex = "3.4.1"
rust-grpc = { version = "1.2.1", registry = "strafesnet" }
tonic = "0.13.1"
rust-grpc = { version = "1.6.1", registry = "strafesnet" }
tonic = "0.14.1"

View File

@@ -18,6 +18,9 @@ impl Service{
endpoint!(set_status_submitted,SubmittedRequest,NullResponse);
endpoint!(set_status_request_changes,MapfixId,NullResponse);
endpoint!(set_status_validated,MapfixId,NullResponse);
endpoint!(set_status_failed,MapfixId,NullResponse);
endpoint!(set_status_not_validated,MapfixId,NullResponse);
endpoint!(set_status_uploaded,MapfixId,NullResponse);
endpoint!(set_status_not_uploaded,MapfixId,NullResponse);
endpoint!(set_status_released,MapfixReleaseRequest,NullResponse);
endpoint!(set_status_not_released,MapfixId,NullResponse);
}

View File

@@ -1,21 +0,0 @@
use crate::endpoint;
use rust_grpc::maps_extended::*;
pub type ServiceClient=rust_grpc::maps_extended::maps_service_client::MapsServiceClient<tonic::transport::channel::Channel>;
#[derive(Clone)]
pub struct Client{
client:ServiceClient,
}
impl Client{
pub fn new(
client:ServiceClient,
)->Self{
Self{client}
}
// endpoint!(get,MapId,MapResponse);
// endpoint!(get_list,MapIdList,MapList);
endpoint!(update,MapUpdate,NullResponse);
// endpoint!(create,MapCreate,MapId);
// endpoint!(delete,MapId,NullResponse);
// endpoint!(list,ListRequest,MapList);
// endpoint!(increment_load_count,MapId,NullResponse);
}

View File

@@ -1,7 +1,6 @@
pub mod error;
pub mod maps_extended;
pub mod mapfixes;
pub mod operations;
pub mod scripts;

View File

@@ -11,5 +11,6 @@ impl Service{
)->Self{
Self{client}
}
endpoint!(success,OperationSuccessRequest,NullResponse);
endpoint!(fail,OperationFailRequest,NullResponse);
}

View File

@@ -18,6 +18,8 @@ impl Service{
endpoint!(set_status_submitted,SubmittedRequest,NullResponse);
endpoint!(set_status_request_changes,SubmissionId,NullResponse);
endpoint!(set_status_validated,SubmissionId,NullResponse);
endpoint!(set_status_failed,SubmissionId,NullResponse);
endpoint!(set_status_not_validated,SubmissionId,NullResponse);
endpoint!(set_status_uploaded,StatusUploadedRequest,NullResponse);
endpoint!(set_status_not_uploaded,SubmissionId,NullResponse);
endpoint!(set_status_released,SubmissionReleaseRequest,NullResponse);
}

View File

@@ -13,6 +13,9 @@ mod check_submission;
mod create;
mod create_mapfix;
mod create_submission;
mod release;
mod release_mapfix;
mod release_submissions_batch;
mod upload_mapfix;
mod upload_submission;
mod validator;
@@ -53,9 +56,12 @@ async fn main()->Result<(),StartupError>{
// create / upload models through STRAFESNET_CI2 account
let cookie=std::env::var("RBXCOOKIE").expect("RBXCOOKIE env required");
let cookie_context=rbx_asset::cookie::Context::new(rbx_asset::cookie::Cookie::new(cookie));
// download models through cloud api
// download models through cloud api (STRAFESNET_CI2 account)
let api_key=std::env::var("RBX_API_KEY").expect("RBX_API_KEY env required");
let cloud_context=rbx_asset::cloud::Context::new(rbx_asset::cloud::ApiKey::new(api_key));
// luau execution cloud api (StrafesNET group)
let api_key=std::env::var("RBX_API_KEY_LUAU_EXECUTION").expect("RBX_API_KEY_LUAU_EXECUTION env required");
let cloud_context_luau_execution=rbx_asset::cloud::Context::new(rbx_asset::cloud::ApiKey::new(api_key));
// maps-service api
let api_host_internal=std::env::var("API_HOST_INTERNAL").expect("API_HOST_INTERNAL env required");
@@ -66,14 +72,16 @@ async fn main()->Result<(),StartupError>{
let scripts=crate::grpc::scripts::Service::new(crate::grpc::scripts::ValidatorScriptsServiceClient::new(channel.clone()));
let script_policy=crate::grpc::script_policy::Service::new(crate::grpc::script_policy::ValidatorScriptPolicyServiceClient::new(channel.clone()));
let submissions=crate::grpc::submissions::Service::new(crate::grpc::submissions::ValidatorSubmissionsServiceClient::new(channel.clone()));
let maps_extended=crate::grpc::maps_extended::Client::new(crate::grpc::maps_extended::ServiceClient::new(channel));
let load_asset_version_runtime=rbx_asset::cloud::LuauSessionLatestRequest{
place_id:load_asset_version_place_id,
universe_id:load_asset_version_universe_id,
};
let message_handler=message_handler::MessageHandler{
cloud_context,
cookie_context,
cloud_context_luau_execution,
group_id,
load_asset_version_place_id,
load_asset_version_universe_id,
maps_extended,
load_asset_version_runtime,
mapfixes,
operations,
scripts,

View File

@@ -9,6 +9,8 @@ pub enum HandleMessageError{
CreateSubmission(tonic::Status),
CheckMapfix(crate::check_mapfix::Error),
CheckSubmission(crate::check_submission::Error),
ReleaseMapfix(crate::release_mapfix::Error),
ReleaseSubmissionsBatch(crate::release_submissions_batch::Error),
UploadMapfix(crate::upload_mapfix::Error),
UploadSubmission(crate::upload_submission::Error),
ValidateMapfix(crate::validate_mapfix::Error),
@@ -30,10 +32,9 @@ fn from_slice<'a,T:serde::de::Deserialize<'a>>(slice:&'a [u8])->Result<T,HandleM
pub struct MessageHandler{
pub(crate) cloud_context:rbx_asset::cloud::Context,
pub(crate) cookie_context:rbx_asset::cookie::Context,
pub(crate) cloud_context_luau_execution:rbx_asset::cloud::Context,
pub(crate) group_id:Option<u64>,
pub(crate) load_asset_version_place_id:u64,
pub(crate) load_asset_version_universe_id:u64,
pub(crate) maps_extended:crate::grpc::maps_extended::Client,
pub(crate) load_asset_version_runtime:rbx_asset::cloud::LuauSessionLatestRequest,
pub(crate) mapfixes:crate::grpc::mapfixes::Service,
pub(crate) operations:crate::grpc::operations::Service,
pub(crate) scripts:crate::grpc::scripts::Service,
@@ -50,6 +51,8 @@ impl MessageHandler{
"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.release"=>self.release_mapfix(from_slice(&message.payload)?).await.map_err(HandleMessageError::ReleaseMapfix),
"maptest.submissions.batchrelease"=>self.release_submissions_batch(from_slice(&message.payload)?).await.map_err(HandleMessageError::ReleaseSubmissionsBatch),
"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),

View File

@@ -81,3 +81,34 @@ pub struct UploadMapfixRequest{
pub ModelVersion:u64,
pub TargetAssetID:u64,
}
// Release a new map
#[allow(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct ReleaseSubmissionRequest{
pub SubmissionID:u64,
pub ReleaseDate:i64,
pub ModelID:u64,
pub ModelVersion:u64,
pub UploadedAssetID:u64,
pub DisplayName:String,
pub Creator:String,
pub GameID:u32,
pub Submitter:u64,
}
#[allow(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct ReleaseSubmissionsBatchRequest{
pub Submissions:Vec<ReleaseSubmissionRequest>,
pub OperationID:u32,
}
#[allow(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct ReleaseMapfixRequest{
pub MapfixID:u64,
pub ModelID:u64,
pub ModelVersion:u64,
pub TargetAssetID:u64,
}

104
validation/src/release.rs Normal file
View File

@@ -0,0 +1,104 @@
use crate::rbx_util::read_dom;
#[expect(unused)]
#[derive(Debug)]
pub enum ModesError{
ApiActionMapfixReleased(tonic::Status),
ModelFileDecode(crate::rbx_util::ReadDomError),
GetRootInstance(crate::rbx_util::GetRootInstanceError),
NonSequentialModes,
TooManyModes(usize),
}
impl std::fmt::Display for ModesError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for ModesError{}
// decode and get modes function
pub fn count_modes(maybe_gzip:rbx_asset::types::MaybeGzippedBytes)->Result<u32,ModesError>{
// decode dom (slow!)
let dom=maybe_gzip.read_with(read_dom,read_dom).map_err(ModesError::ModelFileDecode)?;
// extract the root instance
let model_instance=crate::rbx_util::get_root_instance(&dom).map_err(ModesError::GetRootInstance)?;
// extract information from the model
let model_info=crate::check::get_model_info(&dom,model_instance);
// count modes
let modes=model_info.count_modes().ok_or(ModesError::NonSequentialModes)?;
// hard limit LOL
let modes=if modes<u32::MAX as usize{
modes as u32
}else{
return Err(ModesError::TooManyModes(modes));
};
Ok(modes)
}
#[expect(unused)]
#[derive(Debug)]
pub enum LoadAssetVersionsError{
CreateSession(rbx_asset::cloud::CreateError),
NonPositiveNumber(serde_json::Value),
Script(rbx_asset::cloud::LuauError),
InvalidResult(Vec<serde_json::Value>),
LuauSession(rbx_asset::cloud::LuauSessionError),
}
impl std::fmt::Display for LoadAssetVersionsError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for LoadAssetVersionsError{}
// get asset versions in bulk using Roblox Luau API
pub async fn load_asset_versions<I:IntoIterator<Item=u64>>(
context:&rbx_asset::cloud::Context,
runtime:&rbx_asset::cloud::LuauSessionLatestRequest,
assets:I,
)->Result<Vec<u64>,LoadAssetVersionsError>{
// construct script with inline IDs
// TODO: concurrent execution
let mut script="local InsertService=game:GetService(\"InsertService\")\n".to_string();
script+="return {\n";
for asset in assets{
script+="InsertService:GetLatestAssetVersionAsync(";
script+=&asset.to_string();
script+="),\n";
}
script+="}\n";
let session=rbx_asset::cloud::LuauSessionCreate{
script:&script,
user:None,
timeout:None,
binaryInput:None,
enableBinaryOutput:None,
binaryOutputUri:None,
};
let session_response=context.create_luau_session(runtime,session).await.map_err(LoadAssetVersionsError::CreateSession)?;
let result=crate::rbx_util::get_luau_result_exp_backoff(&context,&session_response).await;
// * Note that only one mapfix can be active per map
// * so it's theoretically impossible for the map to be updated unexpectedly.
// * This means that the incremental asset version does not
// * need to be checked before and after the load asset version is checked.
match result{
Ok(Ok(rbx_asset::cloud::LuauResults{results}))=>{
results.into_iter().map(|load_asset_version|
load_asset_version.as_u64().ok_or_else(||LoadAssetVersionsError::NonPositiveNumber(load_asset_version.clone()))
).collect()
},
Ok(Err(e))=>Err(LoadAssetVersionsError::Script(e)),
Err(e)=>Err(LoadAssetVersionsError::LuauSession(e)),
}
// * Don't need to check asset version to make sure it hasn't been updated
}

View File

@@ -0,0 +1,101 @@
use crate::download::download_asset_version;
use crate::nats_types::ReleaseMapfixRequest;
use crate::release::{count_modes,load_asset_versions};
#[expect(unused)]
#[derive(Debug)]
pub enum InnerError{
Download(crate::download::Error),
Modes(crate::release::ModesError),
LoadAssetVersions(crate::release::LoadAssetVersionsError),
LoadAssetVersionsListLength,
}
impl std::fmt::Display for InnerError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for InnerError{}
async fn release_inner(
cloud_context:&rbx_asset::cloud::Context,
cloud_context_luau_execution:&rbx_asset::cloud::Context,
load_asset_version_runtime:&rbx_asset::cloud::LuauSessionLatestRequest,
release_info:ReleaseMapfixRequest,
)->Result<rust_grpc::validator::MapfixReleaseRequest,InnerError>{
// download the map model
let maybe_gzip=download_asset_version(cloud_context,rbx_asset::cloud::GetAssetVersionRequest{
asset_id:release_info.ModelID,
version:release_info.ModelVersion,
}).await.map_err(InnerError::Download)?;
// count modes
let modes=count_modes(maybe_gzip).map_err(InnerError::Modes)?;
// fetch load asset version
let load_asset_versions=load_asset_versions(
cloud_context_luau_execution,
load_asset_version_runtime,
[release_info.TargetAssetID],
).await.map_err(InnerError::LoadAssetVersions)?;
// exactly one value in the results
let &[load_asset_version]=load_asset_versions.as_slice()else{
return Err(InnerError::LoadAssetVersionsListLength);
};
Ok(rust_grpc::validator::MapfixReleaseRequest{
mapfix_id:release_info.MapfixID,
target_asset_id:release_info.TargetAssetID,
asset_version:load_asset_version,
modes:modes,
})
}
#[expect(unused)]
#[derive(Debug)]
pub enum Error{
ApiActionMapfixRelease(tonic::Status),
}
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 release_mapfix(&self,release_info:ReleaseMapfixRequest)->Result<(),Error>{
let mapfix_id=release_info.MapfixID;
let result=release_inner(
&self.cloud_context,
&self.cloud_context_luau_execution,
&self.load_asset_version_runtime,
release_info,
).await;
match result{
Ok(request)=>{
// update map metadata
self.mapfixes.set_status_released(request).await.map_err(Error::ApiActionMapfixRelease)?;
},
Err(e)=>{
// log error
println!("[release_mapfix] Error: {e}");
// post an error message to the audit log
self.mapfixes.create_audit_error(rust_grpc::validator::AuditErrorRequest{
id:mapfix_id,
error_message:e.to_string(),
}).await.map_err(Error::ApiActionMapfixRelease)?;
// update the mapfix model status to uploaded
self.mapfixes.set_status_not_released(rust_grpc::validator::MapfixId{
id:mapfix_id,
}).await.map_err(Error::ApiActionMapfixRelease)?;
},
}
Ok(())
}
}

View File

@@ -0,0 +1,227 @@
use futures::StreamExt;
use crate::download::download_asset_version;
use crate::nats_types::ReleaseSubmissionsBatchRequest;
use crate::release::{count_modes,load_asset_versions};
#[expect(unused)]
#[derive(Debug)]
pub enum DownloadFutError{
Download(crate::download::Error),
Join(tokio::task::JoinError),
Modes(crate::release::ModesError),
}
impl std::fmt::Display for DownloadFutError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for DownloadFutError{}
#[derive(Debug)]
pub struct ErrorContext<E>{
submission_id:u64,
error:E,
}
impl<E:std::fmt::Debug> std::fmt::Display for ErrorContext<E>{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"SubmissionID({})={:?}",self.submission_id,self.error)
}
}
impl<E:std::fmt::Debug> std::error::Error for ErrorContext<E>{}
async fn download_fut(
cloud_context:&rbx_asset::cloud::Context,
asset_version:rbx_asset::cloud::GetAssetVersionRequest,
)->Result<u32,DownloadFutError>{
// download
let maybe_gzip=download_asset_version(cloud_context,asset_version)
.await
.map_err(DownloadFutError::Download)?;
// count modes in a green thread
let modes=tokio::task::spawn_blocking(||
count_modes(maybe_gzip)
)
.await
.map_err(DownloadFutError::Join)?
.map_err(DownloadFutError::Modes)?;
Ok::<_,DownloadFutError>(modes)
}
#[expect(unused)]
#[derive(Debug)]
pub enum InnerError{
Io(std::io::Error),
LoadAssetVersions(crate::release::LoadAssetVersionsError),
LoadAssetVersionsListLength,
DownloadFutErrors(Vec<ErrorContext<DownloadFutError>>),
ReleaseErrors(Vec<ErrorContext<tonic::Status>>),
}
impl std::fmt::Display for InnerError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for InnerError{}
const MAX_PARALLEL_DECODE:usize=6;
const MAX_CONCURRENT_RELEASE:usize=16;
async fn release_inner(
release_info:ReleaseSubmissionsBatchRequest,
cloud_context:&rbx_asset::cloud::Context,
cloud_context_luau_execution:&rbx_asset::cloud::Context,
load_asset_version_runtime:&rbx_asset::cloud::LuauSessionLatestRequest,
submissions:&crate::grpc::submissions::Service,
)->Result<(),InnerError>{
let available_parallelism=std::thread::available_parallelism().map_err(InnerError::Io)?.get();
// set up futures
// unnecessary allocation :(
let asset_versions:Vec<_> =release_info
.Submissions
.iter()
.map(|submission|rbx_asset::cloud::GetAssetVersionRequest{
asset_id:submission.ModelID,
version:submission.ModelVersion,
})
.enumerate()
.collect();
// fut_download
let fut_download=futures::stream::iter(asset_versions)
.map(|(index,asset_version)|async move{
let modes=download_fut(cloud_context,asset_version).await;
(index,modes)
})
.buffer_unordered(available_parallelism.min(MAX_PARALLEL_DECODE))
.collect::<Vec<(usize,Result<_,DownloadFutError>)>>();
// fut_luau
let fut_load_asset_versions=load_asset_versions(
cloud_context_luau_execution,
load_asset_version_runtime,
release_info.Submissions.iter().map(|submission|submission.UploadedAssetID),
);
// execute futures
let (mut modes_unordered,load_asset_versions_result)=tokio::join!(fut_download,fut_load_asset_versions);
let load_asset_versions=load_asset_versions_result.map_err(InnerError::LoadAssetVersions)?;
// sanity check roblox output
if load_asset_versions.len()!=release_info.Submissions.len(){
return Err(InnerError::LoadAssetVersionsListLength);
};
// rip asymptotic complexity (hash map would be better)
modes_unordered.sort_by_key(|&(index,_)|index);
// check modes calculations for all success
let mut modes=Vec::with_capacity(modes_unordered.len());
let mut errors=Vec::with_capacity(modes_unordered.len());
for (index,result) in modes_unordered{
match result{
Ok(value)=>modes.push(value),
Err(error)=>errors.push(ErrorContext{
submission_id:release_info.Submissions[index].SubmissionID,
error:error,
}),
}
}
if !errors.is_empty(){
return Err(InnerError::DownloadFutErrors(errors));
}
// concurrently dispatch results
let release_results:Vec<_> =futures::stream::iter(
release_info
.Submissions
.into_iter()
.zip(modes)
.zip(load_asset_versions)
.map(|((submission,modes),asset_version)|async move{
let result=submissions.set_status_released(rust_grpc::validator::SubmissionReleaseRequest{
submission_id:submission.SubmissionID,
map_create:Some(rust_grpc::maps_extended::MapCreate{
id:submission.UploadedAssetID as i64,
display_name:submission.DisplayName,
creator:submission.Creator,
game_id:submission.GameID,
date:submission.ReleaseDate,
submitter:submission.Submitter,
thumbnail:0,
asset_version,
modes,
}),
}).await;
(submission.SubmissionID,result)
})
)
.buffer_unordered(MAX_CONCURRENT_RELEASE)
.collect().await;
// check for errors
let errors:Vec<_> =
release_results
.into_iter()
.filter_map(|(submission_id,result)|
result.err().map(|e|ErrorContext{
submission_id,
error:e,
})
)
.collect();
if !errors.is_empty(){
return Err(InnerError::ReleaseErrors(errors));
}
Ok(())
}
#[allow(dead_code)]
#[derive(Debug)]
pub enum Error{
UpdateOperation(tonic::Status),
}
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 release_submissions_batch(&self,release_info:ReleaseSubmissionsBatchRequest)->Result<(),Error>{
let operation_id=release_info.OperationID;
let result=release_inner(
release_info,
&self.cloud_context,
&self.cloud_context_luau_execution,
&self.load_asset_version_runtime,
&self.submissions,
).await;
match result{
Ok(())=>{
// operation success
self.operations.success(rust_grpc::validator::OperationSuccessRequest{
operation_id,
path:String::new(),
}).await.map_err(Error::UpdateOperation)?;
},
Err(e)=>{
// operation error
self.operations.fail(rust_grpc::validator::OperationFailRequest{
operation_id,
status_message:e.to_string(),
}).await.map_err(Error::UpdateOperation)?;
},
}
Ok(())
}
}

View File

@@ -3,27 +3,50 @@ use crate::nats_types::UploadMapfixRequest;
#[allow(dead_code)]
#[derive(Debug)]
pub enum Error{
pub enum InnerError{
Download(crate::download::Error),
IO(std::io::Error),
Json(serde_json::Error),
Upload(rbx_asset::cookie::UploadError),
ApiActionMapfixUploaded(tonic::Status),
ModelFileDecode(crate::rbx_util::ReadDomError),
GetRootInstance(crate::rbx_util::GetRootInstanceError),
NonSequentialModes,
TooManyModes(usize),
CreateSession(rbx_asset::cloud::CreateError),
NonPositiveNumber(serde_json::Number),
Script(rbx_asset::cloud::LuauError),
InvalidResult(Vec<serde_json::Value>),
LuauSession(rbx_asset::cloud::LuauSessionError),
GetAssetInfo(rbx_asset::cloud::GetError),
RevisionMismatch{
after:u64,
before:u64,
},
}
impl std::fmt::Display for InnerError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for InnerError{}
async fn upload_inner(
upload_info:UploadMapfixRequest,
cloud_context:&rbx_asset::cloud::Context,
cookie_context:&rbx_asset::cookie::Context,
group_id:Option<u64>,
)->Result<(),InnerError>{
// download the map model
let maybe_gzip=download_asset_version(cloud_context,rbx_asset::cloud::GetAssetVersionRequest{
asset_id:upload_info.ModelID,
version:upload_info.ModelVersion,
}).await.map_err(InnerError::Download)?;
// transparently handle gzipped models
let model_data=maybe_gzip.to_vec().map_err(InnerError::IO)?;
// upload the map to the strafesnet group
let _upload_response=cookie_context.upload(rbx_asset::cookie::UploadRequest{
assetid:upload_info.TargetAssetID,
groupId:group_id,
name:None,
description:None,
ispublic:None,
allowComments:None,
},model_data).await.map_err(InnerError::Upload)?;
Ok(())
}
#[allow(dead_code)]
#[derive(Debug)]
pub enum Error{
ApiActionMapfixUploaded(tonic::Status),
}
impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
@@ -34,105 +57,40 @@ 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 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)?;
let mapfix_id=upload_info.MapfixID;
let result=upload_inner(
upload_info,
&self.cloud_context,
&self.cookie_context,
self.group_id,
).await;
// 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,
groupId:self.group_id,
name:None,
description:None,
ispublic:None,
allowComments:None,
},model_data.clone()).await.map_err(Error::Upload)?;
// mark mapfix as uploaded, TargetAssetID is unchanged
self.mapfixes.set_status_uploaded(rust_grpc::validator::MapfixId{
id:upload_info.MapfixID,
}).await.map_err(Error::ApiActionMapfixUploaded)?;
// count modes
// decode dom (slow!)
let dom=crate::rbx_util::read_dom(model_data.as_slice()).map_err(Error::ModelFileDecode)?;
// extract the root instance
let model_instance=crate::rbx_util::get_root_instance(&dom).map_err(Error::GetRootInstance)?;
// extract information from the model
let model_info=crate::check::get_model_info(&dom,model_instance);
// count modes
let modes=model_info.count_modes().ok_or(Error::NonSequentialModes)?;
// hard limit LOL
let modes=if modes<u32::MAX as usize{
modes as u32
}else{
return Err(Error::TooManyModes(modes));
};
// update asset version and modes using Roblox Luau API
let script=format!("return game:GetService(\"InsertService\"):GetLatestAssetVersionAsync({})",upload_info.TargetAssetID);
let request=rbx_asset::cloud::LuauSessionLatestRequest{
place_id:self.load_asset_version_place_id,
universe_id:self.load_asset_version_universe_id,
};
let session=rbx_asset::cloud::LuauSessionCreate{
script:&script,
user:None,
timeout:None,
binaryInput:None,
enableBinaryOutput:None,
binaryOutputUri:None,
};
let session_response=self.cloud_context.create_luau_session(&request,session).await.map_err(Error::CreateSession)?;
let result=crate::rbx_util::get_luau_result_exp_backoff(&self.cloud_context,&session_response).await;
let load_asset_version=match result{
Ok(Ok(rbx_asset::cloud::LuauResults{results}))=>match results.as_slice(){
[serde_json::Value::Number(load_asset_version)]=>load_asset_version.as_u64().ok_or_else(||Error::NonPositiveNumber(load_asset_version.clone())),
_=>Err(Error::InvalidResult(results))
// update the mapfix depending on the result
match result{
Ok(())=>{
// mark mapfix as uploaded, TargetAssetID is unchanged
self.mapfixes.set_status_uploaded(rust_grpc::validator::MapfixId{
id:mapfix_id,
}).await.map_err(Error::ApiActionMapfixUploaded)?;
},
Ok(Err(e))=>Err(Error::Script(e)),
Err(e)=>Err(Error::LuauSession(e)),
}?;
Err(e)=>{
// log error
println!("[upload_mapfix] Error: {e}");
// check asset version to make sure it hasn't been updated
let asset_response=self.cloud_context.get_asset_info(rbx_asset::cloud::GetAssetLatestRequest{
asset_id:upload_info.TargetAssetID,
}).await.map_err(Error::GetAssetInfo)?;
self.mapfixes.create_audit_error(
rust_grpc::validator::AuditErrorRequest{
id:mapfix_id,
error_message:e.to_string(),
}
).await.map_err(Error::ApiActionMapfixUploaded)?;
if upload_response.AssetVersion!=asset_response.revisionId{
// the model was updated while we were fetching LoadAssetVersion.
// the number we got may be invalid.
return Err(Error::RevisionMismatch{
before:upload_response.AssetVersion,
after:asset_response.revisionId,
});
// update the mapfix model status to accepted
self.mapfixes.set_status_not_uploaded(rust_grpc::validator::MapfixId{
id:mapfix_id,
}).await.map_err(Error::ApiActionMapfixUploaded)?;
},
}
// write AssetVersion directly to map
self.maps_extended.update(rust_grpc::maps_extended::MapUpdate{
id:upload_info.TargetAssetID as i64,
asset_version:Some(load_asset_version),
modes:Some(modes),
display_name:None,
creator:None,
game_id:None,
date:None,
submitter:None,
thumbnail:None,
}).await.map_err(Error::ApiActionMapfixUploaded)?;
Ok(())
}
}

View File

@@ -3,12 +3,50 @@ use crate::nats_types::UploadSubmissionRequest;
#[allow(dead_code)]
#[derive(Debug)]
pub enum Error{
pub enum InnerError{
Download(crate::download::Error),
IO(std::io::Error),
Json(serde_json::Error),
Create(rbx_asset::cookie::CreateError),
SystemTime(std::time::SystemTimeError),
}
impl std::fmt::Display for InnerError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for InnerError{}
async fn upload_inner(
upload_info:UploadSubmissionRequest,
cloud_context:&rbx_asset::cloud::Context,
cookie_context:&rbx_asset::cookie::Context,
group_id:Option<u64>,
)->Result<u64,InnerError>{
// download the map model
let maybe_gzip=download_asset_version(cloud_context,rbx_asset::cloud::GetAssetVersionRequest{
asset_id:upload_info.ModelID,
version:upload_info.ModelVersion,
}).await.map_err(InnerError::Download)?;
// transparently handle gzipped models
let model_data=maybe_gzip.to_vec().map_err(InnerError::IO)?;
// upload the map to the strafesnet group
let upload_response=cookie_context.create(rbx_asset::cookie::CreateRequest{
name:upload_info.ModelName.clone(),
description:"".to_owned(),
ispublic:false,
allowComments:false,
groupId:group_id,
},model_data).await.map_err(InnerError::Create)?;
Ok(upload_response.AssetId)
}
#[allow(dead_code)]
#[derive(Debug)]
pub enum Error{
ApiActionSubmissionUploaded(tonic::Status),
}
impl std::fmt::Display for Error{
@@ -20,29 +58,40 @@ 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 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)?;
let submission_id=upload_info.SubmissionID;
let result=upload_inner(
upload_info,
&self.cloud_context,
&self.cookie_context,
self.group_id,
).await;
// transparently handle gzipped models
let model_data=maybe_gzip.to_vec().map_err(Error::IO)?;
// update the submission depending on the result
match result{
Ok(uploaded_asset_id)=>{
// note the asset id of the created model for later release, and mark the submission as uploaded
self.submissions.set_status_uploaded(rust_grpc::validator::StatusUploadedRequest{
id:submission_id,
uploaded_asset_id,
}).await.map_err(Error::ApiActionSubmissionUploaded)?;
},
Err(e)=>{
// log error
println!("[upload_submission] Error: {e}");
// upload the map to the strafesnet group
let upload_response=self.cookie_context.create(rbx_asset::cookie::CreateRequest{
name:upload_info.ModelName.clone(),
description:"".to_owned(),
ispublic:false,
allowComments:false,
groupId:self.group_id,
},model_data).await.map_err(Error::Create)?;
self.submissions.create_audit_error(
rust_grpc::validator::AuditErrorRequest{
id:submission_id,
error_message:e.to_string(),
}
).await.map_err(Error::ApiActionSubmissionUploaded)?;
// note the asset id of the created model for later release, and mark the submission as uploaded
self.submissions.set_status_uploaded(rust_grpc::validator::StatusUploadedRequest{
id:upload_info.SubmissionID,
uploaded_asset_id:upload_response.AssetId,
}).await.map_err(Error::ApiActionSubmissionUploaded)?;
// update the submission model status to accepted
self.submissions.set_status_not_uploaded(rust_grpc::validator::SubmissionId{
id:submission_id,
}).await.map_err(Error::ApiActionSubmissionUploaded)?;
},
}
Ok(())
}

View File

@@ -37,7 +37,7 @@ impl crate::message_handler::MessageHandler{
).await.map_err(Error::ApiActionMapfixValidate)?;
// update the mapfix model status to accepted
self.mapfixes.set_status_failed(rust_grpc::validator::MapfixId{
self.mapfixes.set_status_not_validated(rust_grpc::validator::MapfixId{
id:mapfix_id,
}).await.map_err(Error::ApiActionMapfixValidate)?;
},

View File

@@ -37,7 +37,7 @@ impl crate::message_handler::MessageHandler{
).await.map_err(Error::ApiActionSubmissionValidate)?;
// update the submission model status to accepted
self.submissions.set_status_failed(rust_grpc::validator::SubmissionId{
self.submissions.set_status_not_validated(rust_grpc::validator::SubmissionId{
id:submission_id,
}).await.map_err(Error::ApiActionSubmissionValidate)?;
},

View File

@@ -30,6 +30,8 @@ const ReviewActions = {
RequestChanges: {name:"Request Changes",action:"request-changes"} as ReviewAction,
Upload: {name:"Upload",action:"trigger-upload"} as ReviewAction,
ResetUploading: {name:"Reset Uploading",action:"reset-uploading"} as ReviewAction,
Release: {name:"Release",action:"trigger-release"} as ReviewAction,
ResetReleasing: {name:"Reset Releasing",action:"reset-releasing"} as ReviewAction,
}
const ReviewButtons: React.FC<ReviewButtonsProps> = ({
@@ -54,6 +56,7 @@ const ReviewButtons: React.FC<ReviewButtonsProps> = ({
const reviewRole = type === "submission" ? RolesConstants.SubmissionReview : RolesConstants.MapfixReview;
const uploadRole = type === "submission" ? RolesConstants.SubmissionUpload : RolesConstants.MapfixUpload;
const releaseRole = type === "submission" ? RolesConstants.SubmissionRelease : RolesConstants.MapfixRelease;
if (is_submitter) {
if (StatusMatches(status, [Status.UnderConstruction, Status.ChangesRequested])) {
@@ -139,6 +142,24 @@ const ReviewButtons: React.FC<ReviewButtonsProps> = ({
}
}
// Buttons for release role
if (hasRole(roles, releaseRole)) {
// submissions do not have a release button
if (type === "mapfix" && status === Status.Uploaded) {
buttons.push({
action: ReviewActions.Release,
color: "success"
});
}
if (status === Status.Releasing) {
buttons.push({
action: ReviewActions.ResetReleasing,
color: "warning"
});
}
}
return buttons;
};

View File

@@ -8,8 +8,9 @@ const enum MapfixStatus {
Validated = 6,
Uploading = 7,
Uploaded = 8,
Releasing = 11,
Rejected = 9,
// MapfixStatus does not have a Released state
Released = 10,
}
interface MapfixInfo {
@@ -36,8 +37,12 @@ interface MapfixList {
function MapfixStatusToString(mapfix_status: MapfixStatus): string {
switch (mapfix_status) {
case MapfixStatus.Released:
return "RELEASED"
case MapfixStatus.Rejected:
return "REJECTED"
case MapfixStatus.Releasing:
return "RELEASING"
case MapfixStatus.Uploading:
return "UPLOADING"
case MapfixStatus.Uploaded:
@@ -47,7 +52,7 @@ function MapfixStatusToString(mapfix_status: MapfixStatus): string {
case MapfixStatus.Validating:
return "VALIDATING"
case MapfixStatus.AcceptedUnvalidated:
return "ACCEPTED, NOT VALIDATED"
return "SCRIPT REVIEW"
case MapfixStatus.ChangesRequested:
return "CHANGES REQUESTED"
case MapfixStatus.Submitted:

View File

@@ -9,6 +9,7 @@ const RolesConstants = {
ScriptWrite: 1 << 3 as Roles,
MapfixUpload: 1 << 2 as Roles,
MapfixReview: 1 << 1 as Roles,
MapfixRelease: 1 << 2 as Roles, // same as upload
MapDownload: 1 << 0 as Roles,
Empty: 0 as Roles,
};

View File

@@ -1,19 +1,20 @@
import {SubmissionStatus} from "@/app/ts/Submission";
import {MapfixStatus} from "@/app/ts/Mapfix";
export const Status = {
UnderConstruction: SubmissionStatus.UnderConstruction,
ChangesRequested: SubmissionStatus.ChangesRequested,
Submitting: SubmissionStatus.Submitting,
Submitted: SubmissionStatus.Submitted,
AcceptedUnvalidated: SubmissionStatus.AcceptedUnvalidated,
Validating: SubmissionStatus.Validating,
Validated: SubmissionStatus.Validated,
Uploading: SubmissionStatus.Uploading,
Uploaded: SubmissionStatus.Uploaded,
Rejected: SubmissionStatus.Rejected,
Release: SubmissionStatus.Released
UnderConstruction: MapfixStatus.UnderConstruction,
ChangesRequested: MapfixStatus.ChangesRequested,
Submitting: MapfixStatus.Submitting,
Submitted: MapfixStatus.Submitted,
AcceptedUnvalidated: MapfixStatus.AcceptedUnvalidated,
Validating: MapfixStatus.Validating,
Validated: MapfixStatus.Validated,
Uploading: MapfixStatus.Uploading,
Uploaded: MapfixStatus.Uploaded,
Rejected: MapfixStatus.Rejected,
Release: MapfixStatus.Released,
Releasing: MapfixStatus.Releasing,
};
export const StatusMatches = (status: number, statusValues: number[]) => {
return statusValues.includes(status);
};
};