216 Commits

Author SHA1 Message Date
3f8a6fdff6 w
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-07 02:30:36 -07:00
c5274ff194 entire cli 2025-06-07 02:23:30 -07:00
170e7c64b6 Merge pull request 'submissions-api: add external delete endpoints' (#166) from pr1 into staging
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #166
2025-06-07 05:38:30 +00:00
b443866dd6 Merge pull request 'update deps' (#169) from deps into staging
Some checks are pending
continuous-integration/drone/push Build is running
Reviewed-on: #169
2025-06-07 05:35:45 +00:00
ebe37ad6a2 update deps
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-06-06 22:29:35 -07:00
131dad7ae0 submissions-api: v0.7.2 script policy delete endpoints
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-06-06 22:28:07 -07:00
127402fa77 Merge pull request 'fix regex capture groups' (#167) from pr2 into staging
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Reviewed-on: #167
2025-06-07 03:58:16 +00:00
40f83a4e30 fix regex capture groups
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-06-06 20:52:17 -07:00
b6d4ce4f80 submissions-api: add external delete endpoints
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-06-06 17:14:27 -07:00
07391a84cb Merge pull request 'thumbnail fix - will this WORK THIS TIME?' (#154) from thumbnail-fix-1 into staging
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #154
Reviewed-by: Quaternions <quaternions@noreply@itzana.me>
2025-06-06 02:51:35 +00:00
ic3w0lf
3f848a35c8 implement cache de-exister
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-06-05 17:42:34 -06:00
e5e2387502 Merge pull request 'Refactor MapChecks Summary' (#160) from summary into staging
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Reviewed-on: #160
2025-06-05 01:25:56 +00:00
90d13d28ae use closure instead of iterator
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-06-04 18:18:18 -07:00
513b9722b1 Merge pull request 'Add Bypass Submit Button' (#159) from force-submit into staging
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #159
2025-06-05 00:56:37 +00:00
3da8e414e6 submissions: fix comment
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-06-04 17:53:21 -07:00
2acc30e18c submissions: add bypass-submit 2025-06-04 17:53:13 -07:00
a990ed458c submissions: optimize trigger-submit 2025-06-04 17:41:40 -07:00
4055ef550e openapi: generate
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-04 17:32:44 -07:00
555844e6ee openapi: bypass-submit endpoints
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-04 17:30:10 -07:00
80f30d20fa web: introduce Force Submit button 2025-06-04 17:28:51 -07:00
489a8c9c10 web: rename force submit to admin submit 2025-06-04 17:22:53 -07:00
534598ba70 box list to appease clippy
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-06-04 17:13:33 -07:00
fdc0240698 MapCheckSummary
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-04 17:05:38 -07:00
b0829bc1fc refactor WormholeID
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-04 14:52:19 -07:00
845f8e69d9 refactor ModeID 2025-06-04 14:52:19 -07:00
0d8937e896 refactor SpawnID 2025-06-04 14:46:30 -07:00
2927afd848 Merge pull request 'web: use invalid id for submit to invoke error' (#155) from empty-submit into staging
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #155
2025-06-04 05:32:34 +00:00
8f6c543f81 Merge pull request 'validation: log errors' (#156) from log into staging
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #156
2025-06-04 05:09:57 +00:00
7c95f8ddd0 validation: log errors
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-06-03 22:07:39 -07:00
24964407bd web: use invalid id for submit to invoke error
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-06-03 21:55:19 -07:00
ic3w0lf
8d5bd9e523 Fix error & include error message in response headers
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-06-03 20:52:43 -06:00
ic3w0lf
e1fc637619 Implement errorImageResponse
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2025-06-03 20:42:37 -06:00
ic3w0lf
762ee874a0 thumbnail fix - will this WORK THIS TIME?
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-06-03 20:03:09 -06:00
a1c84ff225 Merge pull request 'web: fix api middleware' (#153) from pr1 into staging
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #153
2025-06-04 01:45:36 +00:00
cea6242dd7 web: fix api middleware
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-06-03 18:42:21 -07:00
fefe116611 Merge pull request 'Allow Submitter Comments' (#151) from submitter-can-comment into staging
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #151
2025-06-04 00:19:35 +00:00
0ada77421f fix bug
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-06-03 17:17:59 -07:00
fa2d611534 submissions: allow submitter special permission to comment on their posts
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Previously only map council could comment.
2025-06-03 17:11:53 -07:00
81539a606c Merge pull request 'API_HOST changes, thumbnail fix & cache, "list is empty" fix' (#150) from thumbnail-fix into staging
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #150
Reviewed-by: Quaternions <quaternions@noreply@itzana.me>
2025-06-03 23:54:13 +00:00
32095296c2 Merge branch 'staging' into thumbnail-fix
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-06-03 23:53:55 +00:00
8ea5ee2d41 use null instead of sentinel value
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-06-03 16:29:29 -07:00
954dbaeac6 env var name change requires deployment configuration change 2025-06-03 16:27:42 -07:00
5b7efa2426 Merge pull request 'Add a favicon (#141)' (#149) from aidan9382/maps-service:favicon into staging
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #149
Reviewed-by: Quaternions <quaternions@noreply@itzana.me>
2025-06-03 23:16:38 +00:00
ic3w0lf
740e3c8932 API_HOST changes, thumbnail fix & cache, "list is empty" fix
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
API_HOST was replaced in order for thumbnail/any redirects to work properly, this also assumes the API will be at `{BASE_URL}/api`, assuming the reverse proxy causes issues with the way redirects were initially setup to work.

Also no more "Submissions list is empty." while it's loading.
2025-06-03 15:58:33 -06:00
4f31f8c75a Add a favicon (#141) 2025-06-03 22:32:43 +01:00
d39a8c0208 submissions-api v0.7.1 make error type smaller
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-05-14 00:50:34 -07:00
636282b993 submissions-api: make error type smaller 2025-05-14 00:50:34 -07:00
e3667fec0c validation: refactor some goofy roblox functions 2025-05-14 00:50:34 -07:00
0dff464561 validation: checks: write many documentation 2025-05-14 00:50:34 -07:00
e30fb5f916 validation: checks: named dummy types for readability 2025-05-14 00:50:34 -07:00
9dd7156f38 validation: avoid passing large struct in Err 2025-05-14 00:50:34 -07:00
fc9aae4235 submissions-api: appease clippy 2025-05-14 00:50:34 -07:00
a11a0d2fd5 validation: clippy fixes 2025-05-14 00:50:34 -07:00
9dad1a6b4d validation: update deps 2025-05-14 00:50:34 -07:00
a95e6b7a9a docker: add AUTH_HOST env var to docker compose
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-04-15 18:55:34 -07:00
ic3w0lf
4c1aef9113 Update README
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-04-15 18:57:35 -06:00
ic3w0lf
c98d170423 Remove hardcoded auth URLs
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-04-15 18:50:40 -06:00
6d14047f57 web: unused imports
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-04-15 16:49:05 -07:00
41663624d3 web: conditionally show avatar when logged in 2025-04-15 16:49:05 -07:00
49b9b41085 web: create login button
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-15 16:20:52 -07:00
3614018794 web: remove redirect 2025-04-15 16:20:48 -07:00
872b98aa74 web: explain admin buttons a bit better
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-15 15:56:52 -07:00
d5c8477869 web: const enum typescript xD
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-15 15:45:16 -07:00
b600ca582b web: show submit button for admin on ChangesRequested status 2025-04-15 15:45:16 -07:00
adbcbed9ac submissions: allow admin to submit from changes requested 2025-04-15 15:45:16 -07:00
8f8d685f71 validator: plumb fields 2025-04-15 15:45:16 -07:00
a669de3c0b submissions: allow bypass by admin in internal CreateSubmission 2025-04-15 15:45:16 -07:00
649b941d5f submissions: implement CreateSubmissionAdmin endpoint 2025-04-15 15:45:16 -07:00
1b4456f30a submissions: add initial fields 2025-04-15 15:31:55 -07:00
d34a5c7091 openapi: generate 2025-04-15 15:13:53 -07:00
2f36877cb6 openapi: admin create endpoint 2025-04-15 15:13:44 -07:00
3a124b8190 web: add hidden admin submit page 2025-04-15 14:23:25 -07:00
6cc6da4879 web: display username in audit events
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-15 12:46:35 -07:00
123b0c9a81 web: add Username field to AuditEvent
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-15 12:43:36 -07:00
54b0abbbf3 web: tweak submit button text
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-13 17:16:55 -07:00
1b0384da11 submissions: fetch usernames from data service
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-13 17:14:44 -07:00
e0cebfd80e submissions: rename svc.Client to svc.Maps 2025-04-13 17:14:21 -07:00
5ba52ecb57 openapi: generate 2025-04-13 17:02:31 -07:00
9e42050a65 openapi: include usernames in AuditEvent 2025-04-13 17:02:28 -07:00
c817bfc8c8 validator: flatten check matches 2025-04-13 16:33:23 -07:00
8f97ca6690 validator: tweak error message
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-12 21:01:23 -07:00
f220cb62bc validator: fix empty check
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-12 12:31:05 -07:00
f090fd7d68 validator: fix duplicate checks
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-12 12:29:17 -07:00
404e1281ff validator: improve "extra" error messages
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-12 12:02:34 -07:00
e4f710c83f validator: include original names of some objects in error message
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-12 11:58:27 -07:00
a942c81ea8 validator: add teleport and wormhole set difference checks
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-12 11:39:25 -07:00
109b24061a validator: pluralize some error messages 2025-04-12 11:33:32 -07:00
ddef30984f validator: remove placeholder comments 2025-04-11 23:42:43 -07:00
9331f37d70 validator: remove explicit StringEmptyCheck newtype
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-11 23:20:48 -07:00
c4f910c1f0 validator: comment ModelInfo::check
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-04-11 23:11:59 -07:00
343a4011dd validator: tweak write_zone macro
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-11 23:06:10 -07:00
c63997d161 validator: implement dangling anticheat zone check
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-11 22:59:37 -07:00
ea58fcedc9 validator: save some loc with default
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-11 22:30:55 -07:00
50e3fb283c validator: comment ModelInfo::check
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-11 22:25:49 -07:00
aa513a7973 validator: code tweaks 2025-04-11 22:20:59 -07:00
eff9097456 validator: remove newline
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-11 21:59:37 -07:00
668c5fef51 validator: move function call so get_model_info is infallible
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-11 21:46:44 -07:00
57db5f738e todo
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-11 21:03:57 -07:00
3789755a19 submissions: add updated info to validator-submitted
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-04-11 21:01:58 -07:00
ee6c37ab9d openapi: generate 2025-04-11 21:01:58 -07:00
12bfbfb0a0 openapi: add updated info to validator-submitted 2025-04-11 21:01:58 -07:00
c57a53692d validator: code tweaks 2025-04-11 21:01:58 -07:00
ccf07c5931 validator: rename AtLeastOneMatchingAndNoExtraCheck to SetDifferenceCheck
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-11 20:37:37 -07:00
6efab4f411 validator: annotate MapCheck fields
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-11 20:19:49 -07:00
34d1db02a5 web: implement audit log on submissions
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-11 19:36:54 -07:00
d86ed0cdf5 web: marginally improve audit events
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-04-11 19:27:25 -07:00
d19763349e web: fetch audit events and generate comments
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-11 19:03:01 -07:00
5846e92924 validator: write check error message
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-11 18:03:45 -07:00
34b8d7475d validator: rustify map check 2025-04-11 17:55:59 -07:00
a5daa2df4a validator: update metadata on Submitted 2025-04-11 17:38:21 -07:00
1b73af9fe2 validator: allow create without valid metadata 2025-04-11 17:38:21 -07:00
8433030562 web: add submission fields 2025-04-11 17:38:21 -07:00
8372665fd3 submissions: fields plumbing 2025-04-11 17:38:21 -07:00
d24b342738 openapi: generate 2025-04-11 13:11:51 -07:00
796f31aadf openapi: add fields to submission create 2025-04-11 13:08:22 -07:00
44f8736838 web: add description on mapfix page
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-11 13:04:52 -07:00
b7e5d82c13 web: add description form field 2025-04-11 13:04:52 -07:00
169007f16e validator: description plumbing 2025-04-11 13:04:52 -07:00
2519c9faa1 submissions: description plumbing 2025-04-11 13:04:52 -07:00
1ff6bdbd4c openapi: generate 2025-04-11 13:00:10 -07:00
d1ca9bdab9 openapi: add Description to mapfix create 2025-04-11 12:59:42 -07:00
c76ff3b687 validation: use to_string instead of format
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-11 01:51:32 +00:00
a42501d254 submissions-api: change StatusMessage to ErrorMessage 2025-04-11 01:51:32 +00:00
f915c51ba4 web: remove StatusMessage 2025-04-11 01:51:32 +00:00
ff9da333eb submissions: push audit error event on error endpoints 2025-04-11 01:51:32 +00:00
1dabd216aa openapi: generate 2025-04-11 01:51:32 +00:00
cc7e890580 openapi: change StatusMessage to ErrorMessage 2025-04-11 01:51:32 +00:00
99d1b38535 submissions: remove StatusMessage 2025-04-11 01:51:32 +00:00
12ca1b7dab submissions: AuditEventTypeError 2025-04-11 01:51:32 +00:00
fa1b44f172 update deps
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-10 17:21:45 -07:00
03519e9337 validator: marginally improve map check clarity
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-09 21:04:51 -07:00
60b6d30379 validator: fix map check bug
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-09 20:53:25 -07:00
19b8f7b7a2 validator: use newlines in check report
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-09 20:37:58 -07:00
4f586c6176 web: add reset submit button
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-09 19:55:38 -07:00
d1a70509b7 submissions: implement map checks
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-04-09 19:50:17 -07:00
95bfb87c6e validator: implement map checks 2025-04-09 19:50:17 -07:00
de0cf37918 validator: add heck + lazy_regex deps 2025-04-09 19:48:44 -07:00
f1fd826c62 submissions-api: implement validator-submitted endpoint 2025-04-09 19:48:44 -07:00
1380a00872 submissions: receive asset version 2025-04-09 19:39:13 -07:00
174a210f81 submissions: implement validator-request-changes endpoints 2025-04-09 19:39:13 -07:00
67a03f394f openapi: generate 2025-04-09 19:39:13 -07:00
6eebe404d5 openapi: validator-request-changes endpoint 2025-04-09 19:39:13 -07:00
1d409218a5 validation: factor out asset download 2025-04-09 19:39:13 -07:00
e2c72c90c7 validator: prepare for checks 2025-04-08 17:06:54 -07:00
7334e88b55 validation: update api to yield a better error
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-08 16:56:48 -07:00
b93c813dec submissions: fix faulty endpoints
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-08 16:40:39 -07:00
926a90329b submissions-api: v0.7.0
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-08 14:22:55 -07:00
18abbd92ce web: implement trigger-submit + transpose weakly associated action list
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-08 13:45:53 -07:00
c923a8a076 submissions: implement validator-submitted endpoint
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-04-08 13:21:07 -07:00
d6da6f003e submissions: implement reset-submitting endpoint 2025-04-08 13:17:39 -07:00
0dc7aec395 submissions: rename endpoints 2025-04-08 13:12:13 -07:00
c85cb63639 openapi: generate
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-08 13:06:35 -07:00
6c865e8841 openapi: prepare for map checks 2025-04-08 13:06:26 -07:00
99a082afb5 submisions: improve error precision
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-08 12:56:07 -07:00
434cd295f5 submissions: implement audit endpoints
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-04-08 12:49:16 -07:00
bfc2a2cbca openapi: generate 2025-04-08 12:41:56 -07:00
c24db2c3a0 openapi: allow listing 0 items 2025-04-08 12:41:16 -07:00
68f2311658 openapi: audit endpoints 2025-04-08 12:41:15 -07:00
163412a253 openapi: extend api StatusID maximum to match changes
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-08 00:01:07 -07:00
044033cabf submissions: implement audit logging
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
- use uint for Operation.Owner
- remove IsSubmitter
2025-04-07 20:29:32 -07:00
219a15f656 submissions: audit events db table 2025-04-07 20:29:29 -07:00
383bc783a4 submissions: audit model 2025-04-07 20:29:29 -07:00
24a5baae77 web: todo: hide Reset buttons for 10 seconds
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-07 13:35:41 -07:00
4ba3b5cd01 web: change up status ids 2025-04-07 13:35:41 -07:00
f610fc1c0f submissions: change up status ids in preparation of submission validation 2025-04-07 13:35:41 -07:00
e67d679901 submissions: rename mapfix const to match submissions 2025-04-07 13:10:24 -07:00
3c3d09c4a7 web: display target asset thumbnail alongside mapfix
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-06 16:09:00 -07:00
d02e3776f3 web: fix page dots
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-04-06 15:50:08 -07:00
77222c84db web: plumb target asset id and submitter
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-06 15:48:37 -07:00
412f34817c submissions: more filtering options for listing submissions
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-06 15:31:45 -07:00
cac288d73b openapi: generate
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-06 15:29:50 -07:00
29e414d6e7 openapi: more filtering options for listing submissions 2025-04-06 15:29:27 -07:00
c9ba2e3e6e web: use date descending sort
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-06 15:15:31 -07:00
0666685a49 web: implement new list api with Total field for pages 2025-04-06 15:15:19 -07:00
ff9237e453 submissions: count total items 2025-04-06 15:13:27 -07:00
9b5f7e0b0c openapi: generate 2025-04-06 15:13:24 -07:00
e28c7e8149 openapi: include total count in list requests 2025-04-06 15:13:20 -07:00
220ea84e22 submissions: AddNotNil is for pointers
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-05 19:36:36 -07:00
7648f407c5 openapi: generate
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-05 19:27:55 -07:00
e0266c5d24 openapi: set minimum for all integers, maximum for some 2025-04-05 19:27:44 -07:00
9ab2e23fa9 submissions: do not allow changing model after submit
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-05 19:00:08 -07:00
6b2f5e29e7 api: improve consistency with internal api
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-05 18:56:39 -07:00
d42e89fcb4 submissions: switch to unsigned integers in database and nats messages
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-05 17:26:35 -07:00
7e881e6ac5 submissions: omit user info check
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-05 17:12:19 -07:00
2d57b945f2 submissions: what??? how did this ever work?
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-05 17:11:10 -07:00
005e99424e validator: update rbx_asset to fix model info download
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-05 14:54:26 -07:00
a330b1c43b validator: update rbx_asset api
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-05 14:39:42 -07:00
d2662eb833 validator: switch to cloud api where possible 2025-04-05 14:39:42 -07:00
3ba599114d validator: relax read_dom trait bound 2025-04-05 14:39:42 -07:00
d53f61fb5b submissions: fix operations CountSince (#99)
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #99
Co-authored-by: Quaternions <krakow20@gmail.com>
Co-committed-by: Quaternions <krakow20@gmail.com>
2025-04-05 19:41:42 +00:00
5d259e20f2 submissions: rate limit submit
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-04 20:08:48 -07:00
21b6903943 submissions: count recent operations 2025-04-04 20:07:09 -07:00
14c7979310 web: activate ai dark mode
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-04 19:33:48 -07:00
e376e02dc1 web: ai the maps page 2025-04-04 19:33:38 -07:00
4e7ee9dc5a rename "Accepted" status to "AcceptedUnvalidated"
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-04 19:04:48 -07:00
ceaec14242 submissions-api: fix validated-model request
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-04 18:41:33 -07:00
9372caa157 web: fix mapfix thumbnails
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-04 17:11:41 -07:00
f73c274367 web: move _map to _mapImage 2025-04-04 17:11:32 -07:00
c50a28443e web: remove ratings
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-04-04 17:02:51 -07:00
c7150f1e23 web: fix mapfixes cards linking to submissions 2025-04-04 17:02:51 -07:00
f16a817da2 web: maps: format date
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-04 16:26:37 -07:00
e858d252ab web: add map image to map page
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-04 16:13:22 -07:00
66e0d22ccd web: add bare bones map info
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-04 15:56:18 -07:00
986ecfc7ad docker: use tagged muslrust
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-04 15:42:43 -07:00
66890ccd44 validation: detect nats filter_subject mismatch and update consumer
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-04-04 15:36:52 -07:00
ec15c1f2e5 Hide Irrelevant Review Buttons (#86)
All checks were successful
continuous-integration/drone/push Build is passing
Closes #17.

Reviewed-on: #86
Co-authored-by: Quaternions <krakow20@gmail.com>
Co-committed-by: Quaternions <krakow20@gmail.com>
2025-04-04 22:10:31 +00:00
8be9475ee5 web: route to provided path on operation success
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-03 20:03:48 -07:00
0cb2bec6e0 web: fix mapfix submit
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-03 19:24:43 -07:00
cf1906acaa web: fix mapfix href
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-04 02:10:44 +00:00
7e93807298 docker: use itzaname docker proxy to avoid getting rate-limited
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-03 19:05:15 -07:00
ee5b3331b4 validator: write correct asset version
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2025-04-03 16:18:31 -07:00
29c0acf3b2 web: add fix map button
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-03 15:47:44 -07:00
a844c4e90a validation: skip upload if model validates as-is
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-03 15:36:06 -07:00
5ed15a6847 validation: rename error
All checks were successful
continuous-integration/drone/push Build is passing
2025-04-03 15:25:02 -07:00
1ff1cae709 web: reduce polling interval
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
The operations will usually take half a second.
2025-04-03 15:12:18 -07:00
ic3w0lf
c6ebe5a360 stop polling on completeion/fail
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-04-03 15:45:59 -06:00
125 changed files with 20400 additions and 1536 deletions

867
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,5 +2,6 @@
members = [ members = [
"validation", "validation",
"validation/api", "validation/api",
"validation/cli",
] ]
resolver = "2" resolver = "2"

View File

@@ -1,5 +1,5 @@
# Stage 1: Build # Stage 1: Build
FROM docker.io/golang:1.23 AS builder FROM registry.itzana.me/docker-proxy/golang:1.24 AS builder
# Set the working directory in the container # Set the working directory in the container
WORKDIR /app WORKDIR /app
@@ -14,7 +14,7 @@ COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o service ./cmd/maps-service/service.go RUN CGO_ENABLED=0 GOOS=linux go build -o service ./cmd/maps-service/service.go
# Stage 2: Run # Stage 2: Run
FROM alpine FROM registry.itzana.me/docker-proxy/alpine:3.21
# Set up a non-root user for security # Set up a non-root user for security
RUN addgroup -S appgroup && adduser -S appuser -G appgroup RUN addgroup -S appgroup && adduser -S appuser -G appgroup

View File

@@ -26,10 +26,11 @@ Prerequisite: golang installed
Prerequisite: bun installed Prerequisite: bun installed
The environment variable `API_HOST` will need to be set for the middleware. The environment variables `API_HOST` and `AUTH_HOST` will need to be set for the middleware.
Example `.env` in web's root: Example `.env` in web's root:
``` ```
API_HOST="http://localhost:8082/v1/" API_HOST="http://localhost:8082/"
AUTH_HOST="http://localhost:8083/"
``` ```
1. `cd web` 1. `cd web`

View File

@@ -50,6 +50,7 @@ services:
- "3000:3000" - "3000:3000"
environment: environment:
- API_HOST=http://submissions:8082/v1 - API_HOST=http://submissions:8082/v1
- AUTH_HOST=http://localhost:8080/
validation: validation:
image: image:

View File

@@ -48,12 +48,81 @@ paths:
schema: schema:
type: integer type: integer
format: int64 format: int64
minimum: 0
- name: ValidatedModelVersion - name: ValidatedModelVersion
in: query in: query
required: true required: true
schema: schema:
type: integer type: integer
format: int64 format: int64
minimum: 0
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/mapfixes/{MapfixID}/status/validator-submitted:
post:
summary: (Internal endpoint) Role Validator changes status from Submitting -> Submitted
operationId: actionMapfixSubmitted
tags:
- Mapfixes
parameters:
- $ref: '#/components/parameters/MapfixID'
- name: ModelVersion
in: query
required: true
schema:
type: integer
format: int64
minimum: 0
- name: DisplayName
in: query
required: true
schema:
type: string
maxLength: 128
- name: Creator
in: query
required: true
schema:
type: string
maxLength: 128
- name: GameID
in: query
required: true
schema:
type: integer
format: int32
minimum: 0
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/mapfixes/{MapfixID}/status/validator-request-changes:
post:
summary: (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested
operationId: actionMapfixRequestChanges
tags:
- Mapfixes
parameters:
- $ref: '#/components/parameters/MapfixID'
- name: ErrorMessage
in: query
required: true
schema:
type: string
minLength: 0
maxLength: 4096
responses: responses:
"204": "204":
description: Successful response description: Successful response
@@ -88,7 +157,7 @@ paths:
- Mapfixes - Mapfixes
parameters: parameters:
- $ref: '#/components/parameters/MapfixID' - $ref: '#/components/parameters/MapfixID'
- name: StatusMessage - name: ErrorMessage
in: query in: query
required: true required: true
schema: schema:
@@ -184,12 +253,81 @@ paths:
schema: schema:
type: integer type: integer
format: int64 format: int64
minimum: 0
- name: ValidatedModelVersion - name: ValidatedModelVersion
in: query in: query
required: true required: true
schema: schema:
type: integer type: integer
format: int64 format: int64
minimum: 0
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/validator-submitted:
post:
summary: (Internal endpoint) Role Validator changes status from Submitting -> Submitted
operationId: actionSubmissionSubmitted
tags:
- Submissions
parameters:
- $ref: '#/components/parameters/SubmissionID'
- name: ModelVersion
in: query
required: true
schema:
type: integer
format: int64
minimum: 0
- name: DisplayName
in: query
required: true
schema:
type: string
maxLength: 128
- name: Creator
in: query
required: true
schema:
type: string
maxLength: 128
- name: GameID
in: query
required: true
schema:
type: integer
format: int32
minimum: 0
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/validator-request-changes:
post:
summary: (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested
operationId: actionSubmissionRequestChanges
tags:
- Submissions
parameters:
- $ref: '#/components/parameters/SubmissionID'
- name: ErrorMessage
in: query
required: true
schema:
type: string
minLength: 0
maxLength: 4096
responses: responses:
"204": "204":
description: Successful response description: Successful response
@@ -224,7 +362,7 @@ paths:
- Submissions - Submissions
parameters: parameters:
- $ref: '#/components/parameters/SubmissionID' - $ref: '#/components/parameters/SubmissionID'
- name: StatusMessage - name: ErrorMessage
in: query in: query
required: true required: true
schema: schema:
@@ -254,6 +392,7 @@ paths:
schema: schema:
type: integer type: integer
format: int64 format: int64
minimum: 0
responses: responses:
"204": "204":
description: Successful response description: Successful response
@@ -283,11 +422,13 @@ paths:
schema: schema:
type: integer type: integer
format: int64 format: int64
minimum: 0
- name: Policy - name: Policy
in: query in: query
schema: schema:
type: integer type: integer
format: int32 format: int32
minimum: 0
responses: responses:
"200": "200":
description: Successful response description: Successful response
@@ -357,11 +498,13 @@ paths:
schema: schema:
type: integer type: integer
format: int32 format: int32
minimum: 0
- name: ResourceID - name: ResourceID
in: query in: query
schema: schema:
type: integer type: integer
format: int64 format: int64
minimum: 0
responses: responses:
"200": "200":
description: Successful response description: Successful response
@@ -432,6 +575,7 @@ components:
schema: schema:
type: integer type: integer
format: int64 format: int64
minimum: 0
OperationID: OperationID:
name: OperationID name: OperationID
in: path in: path
@@ -440,6 +584,7 @@ components:
schema: schema:
type: integer type: integer
format: int32 format: int32
minimum: 0
SubmissionID: SubmissionID:
name: SubmissionID name: SubmissionID
in: path in: path
@@ -448,6 +593,7 @@ components:
schema: schema:
type: integer type: integer
format: int64 format: int64
minimum: 0
ScriptID: ScriptID:
name: ScriptID name: ScriptID
in: path in: path
@@ -456,6 +602,7 @@ components:
schema: schema:
type: integer type: integer
format: int64 format: int64
minimum: 0
Page: Page:
name: Page name: Page
in: query in: query
@@ -482,6 +629,7 @@ components:
MapfixID: MapfixID:
type: integer type: integer
format: int64 format: int64
minimum: 0
SubmissionID: SubmissionID:
required: required:
- SubmissionID - SubmissionID
@@ -490,6 +638,7 @@ components:
SubmissionID: SubmissionID:
type: integer type: integer
format: int64 format: int64
minimum: 0
ScriptID: ScriptID:
required: required:
- ScriptID - ScriptID
@@ -498,6 +647,7 @@ components:
ScriptID: ScriptID:
type: integer type: integer
format: int64 format: int64
minimum: 0
ScriptPolicyID: ScriptPolicyID:
required: required:
- ScriptPolicyID - ScriptPolicyID
@@ -506,6 +656,7 @@ components:
ScriptPolicyID: ScriptPolicyID:
type: integer type: integer
format: int64 format: int64
minimum: 0
MapfixCreate: MapfixCreate:
required: required:
- OperationID - OperationID
@@ -516,14 +667,17 @@ components:
- AssetID - AssetID
- AssetVersion - AssetVersion
- TargetAssetID - TargetAssetID
- Description
type: object type: object
properties: properties:
OperationID: OperationID:
type: integer type: integer
format: int32 format: int32
minimum: 0
AssetOwner: AssetOwner:
type: integer type: integer
format: int64 format: int64
minimum: 0
DisplayName: DisplayName:
type: string type: string
maxLength: 128 maxLength: 128
@@ -533,15 +687,22 @@ components:
GameID: GameID:
type: integer type: integer
format: int32 format: int32
minimum: 0
AssetID: AssetID:
type: integer type: integer
format: int64 format: int64
minimum: 0
AssetVersion: AssetVersion:
type: integer type: integer
format: int64 format: int64
minimum: 0
TargetAssetID: TargetAssetID:
type: integer type: integer
format: int64 format: int64
minimum: 0
Description:
type: string
maxLength: 256
SubmissionCreate: SubmissionCreate:
required: required:
- OperationID - OperationID
@@ -551,14 +712,18 @@ components:
- GameID - GameID
- AssetID - AssetID
- AssetVersion - AssetVersion
- Status
- Roles
type: object type: object
properties: properties:
OperationID: OperationID:
type: integer type: integer
format: int32 format: int32
minimum: 0
AssetOwner: AssetOwner:
type: integer type: integer
format: int64 format: int64
minimum: 0
DisplayName: DisplayName:
type: string type: string
maxLength: 128 maxLength: 128
@@ -568,12 +733,23 @@ components:
GameID: GameID:
type: integer type: integer
format: int32 format: int32
minimum: 0
AssetID: AssetID:
type: integer type: integer
format: int64 format: int64
minimum: 0
AssetVersion: AssetVersion:
type: integer type: integer
format: int64 format: int64
minimum: 0
Status:
type: integer
format: uint32
minimum: 0
maximum: 9
Roles:
type: integer
format: uint32
Script: Script:
required: required:
- ID - ID
@@ -587,6 +763,7 @@ components:
ID: ID:
type: integer type: integer
format: int64 format: int64
minimum: 0
Name: Name:
type: string type: string
maxLength: 128 maxLength: 128
@@ -600,9 +777,11 @@ components:
ResourceType: ResourceType:
type: integer type: integer
format: int32 format: int32
minimum: 0
ResourceID: ResourceID:
type: integer type: integer
format: int64 format: int64
minimum: 0
ScriptCreate: ScriptCreate:
required: required:
- Name - Name
@@ -620,9 +799,11 @@ components:
ResourceType: ResourceType:
type: integer type: integer
format: int32 format: int32
minimum: 0
ResourceID: ResourceID:
type: integer type: integer
format: int64 format: int64
minimum: 0
ScriptPolicy: ScriptPolicy:
required: required:
- ID - ID
@@ -634,6 +815,7 @@ components:
ID: ID:
type: integer type: integer
format: int64 format: int64
minimum: 0
FromScriptHash: FromScriptHash:
type: string type: string
minLength: 16 minLength: 16
@@ -641,9 +823,11 @@ components:
ToScriptID: ToScriptID:
type: integer type: integer
format: int64 format: int64
minimum: 0
Policy: Policy:
type: integer type: integer
format: int32 format: int32
minimum: 0
ScriptPolicyCreate: ScriptPolicyCreate:
required: required:
- FromScriptID - FromScriptID
@@ -654,12 +838,15 @@ components:
FromScriptID: FromScriptID:
type: integer type: integer
format: int64 format: int64
minimum: 0
ToScriptID: ToScriptID:
type: integer type: integer
format: int64 format: int64
minimum: 0
Policy: Policy:
type: integer type: integer
format: int32 format: int32
minimum: 0
Error: Error:
description: Represents error object description: Represents error object
type: object type: object
@@ -667,6 +854,7 @@ components:
code: code:
type: integer type: integer
format: int64 format: int64
minimum: 0
message: message:
type: string type: string
required: required:

View File

@@ -105,11 +105,15 @@ paths:
schema: schema:
type: integer type: integer
format: int32 format: int32
minimum: 1
maximum: 5
- name: Sort - name: Sort
in: query in: query
schema: schema:
type: integer type: integer
format: int32 format: int32
minimum: 0
maximum: 4
responses: responses:
"200": "200":
description: Successful response description: Successful response
@@ -172,20 +176,47 @@ paths:
schema: schema:
type: integer type: integer
format: int32 format: int32
minimum: 1
maximum: 5
- name: Sort - name: Sort
in: query in: query
schema: schema:
type: integer type: integer
format: int32 format: int32
minimum: 0
maximum: 4
- name: Submitter
in: query
schema:
type: integer
format: int64
minimum: 0
- name: AssetID
in: query
schema:
type: integer
format: int64
minimum: 0
- name: TargetAssetID
in: query
schema:
type: integer
format: int64
minimum: 0
- name: StatusID
in: query
schema:
type: integer
format: int32
minimum: 0
maximum: 9
responses: responses:
"200": "200":
description: Successful response description: Successful response
content: content:
application/json: application/json:
schema: schema:
type: array $ref: "#/components/schemas/Mapfixes"
items:
$ref: "#/components/schemas/Mapfix"
default: default:
description: General Error description: General Error
content: content:
@@ -238,6 +269,56 @@ paths:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Error" $ref: "#/components/schemas/Error"
/mapfixes/{MapfixID}/audit-events:
get:
summary: Retrieve a list of audit events
operationId: listMapfixAuditEvents
tags:
- Mapfixes
security: []
parameters:
- $ref: '#/components/parameters/MapfixID'
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/Limit"
responses:
"200":
description: Successful response
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/AuditEvent"
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/mapfixes/{MapfixID}/comment:
post:
summary: Post a comment to the audit log
operationId: createMapfixAuditComment
tags:
- Mapfixes
parameters:
- $ref: '#/components/parameters/MapfixID'
requestBody:
required: true
content:
text/plain:
schema:
type: string
maxLength: 1024
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/mapfixes/{MapfixID}/model: /mapfixes/{MapfixID}/model:
post: post:
summary: Update model following role restrictions summary: Update model following role restrictions
@@ -252,12 +333,14 @@ paths:
schema: schema:
type: integer type: integer
format: int64 format: int64
- name: VersionID minimum: 0
- name: ModelVersion
in: query in: query
required: true required: true
schema: schema:
type: integer type: integer
format: int64 format: int64
minimum: 0
responses: responses:
"204": "204":
description: Successful response description: Successful response
@@ -284,10 +367,44 @@ paths:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Error" $ref: "#/components/schemas/Error"
/mapfixes/{MapfixID}/status/submit: /mapfixes/{MapfixID}/status/trigger-submit:
post: post:
summary: Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted summary: Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting
operationId: actionMapfixSubmit operationId: actionMapfixTriggerSubmit
tags:
- Mapfixes
parameters:
- $ref: '#/components/parameters/MapfixID'
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/mapfixes/{MapfixID}/status/bypass-submit:
post:
summary: Role Reviewer changes status from ChangesRequested -> Submitted
operationId: actionMapfixBypassSubmit
tags:
- Mapfixes
parameters:
- $ref: '#/components/parameters/MapfixID'
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/mapfixes/{MapfixID}/status/reset-submitting:
post:
summary: Role Submitter manually resets submitting softlock and changes status from Submitting -> UnderConstruction
operationId: actionMapfixResetSubmitting
tags: tags:
- Mapfixes - Mapfixes
parameters: parameters:
@@ -483,20 +600,47 @@ paths:
schema: schema:
type: integer type: integer
format: int32 format: int32
minimum: 1
maximum: 5
- name: Sort - name: Sort
in: query in: query
schema: schema:
type: integer type: integer
format: int32 format: int32
minimum: 0
maximum: 4
- name: Submitter
in: query
schema:
type: integer
format: int64
minimum: 0
- name: AssetID
in: query
schema:
type: integer
format: int64
minimum: 0
- name: UploadedAssetID
in: query
schema:
type: integer
format: int64
minimum: 0
- name: StatusID
in: query
schema:
type: integer
format: int32
minimum: 0
maximum: 10
responses: responses:
"200": "200":
description: Successful response description: Successful response
content: content:
application/json: application/json:
schema: schema:
type: array $ref: "#/components/schemas/Submissions"
items:
$ref: "#/components/schemas/Submission"
default: default:
description: General Error description: General Error
content: content:
@@ -527,6 +671,31 @@ paths:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Error" $ref: "#/components/schemas/Error"
/submissions-admin:
post:
summary: Trigger the validator to create a new submission
operationId: createSubmissionAdmin
tags:
- Submissions
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SubmissionTriggerCreate'
responses:
"201":
description: Successful response
content:
application/json:
schema:
$ref: "#/components/schemas/OperationID"
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/submissions/{SubmissionID}: /submissions/{SubmissionID}:
get: get:
summary: Retrieve map with ID summary: Retrieve map with ID
@@ -549,6 +718,56 @@ paths:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Error" $ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/audit-events:
get:
summary: Retrieve a list of audit events
operationId: listSubmissionAuditEvents
tags:
- Submissions
security: []
parameters:
- $ref: '#/components/parameters/SubmissionID'
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/Limit"
responses:
"200":
description: Successful response
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/AuditEvent"
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/comment:
post:
summary: Post a comment to the audit log
operationId: createSubmissionAuditComment
tags:
- Submissions
parameters:
- $ref: '#/components/parameters/SubmissionID'
requestBody:
required: true
content:
text/plain:
schema:
type: string
maxLength: 1024
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/model: /submissions/{SubmissionID}/model:
post: post:
summary: Update model following role restrictions summary: Update model following role restrictions
@@ -563,12 +782,14 @@ paths:
schema: schema:
type: integer type: integer
format: int64 format: int64
- name: VersionID minimum: 0
- name: ModelVersion
in: query in: query
required: true required: true
schema: schema:
type: integer type: integer
format: int64 format: int64
minimum: 0
responses: responses:
"204": "204":
description: Successful response description: Successful response
@@ -595,10 +816,44 @@ paths:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Error" $ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/submit: /submissions/{SubmissionID}/status/trigger-submit:
post: post:
summary: Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted summary: Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting
operationId: actionSubmissionSubmit operationId: actionSubmissionTriggerSubmit
tags:
- Submissions
parameters:
- $ref: '#/components/parameters/SubmissionID'
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/bypass-submit:
post:
summary: Role Reviewer changes status from ChangesRequested -> Submitted
operationId: actionSubmissionBypassSubmit
tags:
- Submissions
parameters:
- $ref: '#/components/parameters/SubmissionID'
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/reset-submitting:
post:
summary: Role Submitter manually resets submitting softlock and changes status from Submitting -> UnderConstruction
operationId: actionSubmissionResetSubmitting
tags: tags:
- Submissions - Submissions
parameters: parameters:
@@ -794,11 +1049,13 @@ paths:
schema: schema:
type: integer type: integer
format: int64 format: int64
minimum: 0
- name: Policy - name: Policy
in: query in: query
schema: schema:
type: integer type: integer
format: int32 format: int32
minimum: 0
responses: responses:
"200": "200":
description: Successful response description: Successful response
@@ -929,11 +1186,13 @@ paths:
schema: schema:
type: integer type: integer
format: int32 format: int32
minimum: 0
- name: ResourceID - name: ResourceID
in: query in: query
schema: schema:
type: integer type: integer
format: int64 format: int64
minimum: 0
responses: responses:
"200": "200":
description: Successful response description: Successful response
@@ -1048,6 +1307,7 @@ components:
schema: schema:
type: integer type: integer
format: int64 format: int64
minimum: 0
MapfixID: MapfixID:
name: MapfixID name: MapfixID
in: path in: path
@@ -1056,6 +1316,7 @@ components:
schema: schema:
type: integer type: integer
format: int64 format: int64
minimum: 0
OperationID: OperationID:
name: OperationID name: OperationID
in: path in: path
@@ -1064,6 +1325,7 @@ components:
schema: schema:
type: integer type: integer
format: int32 format: int32
minimum: 0
SubmissionID: SubmissionID:
name: SubmissionID name: SubmissionID
in: path in: path
@@ -1072,6 +1334,7 @@ components:
schema: schema:
type: integer type: integer
format: int64 format: int64
minimum: 0
ScriptID: ScriptID:
name: ScriptID name: ScriptID
in: path in: path
@@ -1080,6 +1343,7 @@ components:
schema: schema:
type: integer type: integer
format: int64 format: int64
minimum: 0
ScriptPolicyID: ScriptPolicyID:
name: ScriptPolicyID name: ScriptPolicyID
in: path in: path
@@ -1088,6 +1352,7 @@ components:
schema: schema:
type: integer type: integer
format: int64 format: int64
minimum: 0
Page: Page:
name: Page name: Page
in: query in: query
@@ -1103,9 +1368,47 @@ components:
schema: schema:
type: integer type: integer
format: int32 format: int32
minimum: 1 minimum: 0
maximum: 100 maximum: 100
schemas: schemas:
AuditEvent:
type: object
required:
- ID
- Date
- User
- Username
- ResourceType
- ResourceID
- EventType
- EventData
properties:
ID:
type: integer
format: int64
Date:
type: integer
format: int64
User:
type: integer
format: int64
Username:
type: string
maxLength: 64
ResourceType:
type: integer
format: int32
description: Is this a submission or is it a mapfix
ResourceID:
type: integer
format: int64
EventType:
type: integer
format: int32
EventData:
type: object
description: Arbitrary event data
additionalProperties: true
OperationID: OperationID:
required: required:
- OperationID - OperationID
@@ -1114,6 +1417,7 @@ components:
OperationID: OperationID:
type: integer type: integer
format: int32 format: int32
minimum: 0
ScriptID: ScriptID:
required: required:
- ScriptID - ScriptID
@@ -1122,6 +1426,7 @@ components:
ScriptID: ScriptID:
type: integer type: integer
format: int64 format: int64
minimum: 0
ScriptPolicyID: ScriptPolicyID:
required: required:
- ScriptPolicyID - ScriptPolicyID
@@ -1130,6 +1435,7 @@ components:
ScriptPolicyID: ScriptPolicyID:
type: integer type: integer
format: int64 format: int64
minimum: 0
Roles: Roles:
required: required:
- Roles - Roles
@@ -1138,6 +1444,7 @@ components:
Roles: Roles:
type: integer type: integer
format: int32 format: int32
minimum: 0
User: User:
required: required:
- UserID - UserID
@@ -1148,6 +1455,7 @@ components:
UserID: UserID:
type: integer type: integer
format: int64 format: int64
minimum: 0
Username: Username:
type: string type: string
maxLength: 128 maxLength: 128
@@ -1166,6 +1474,7 @@ components:
ID: ID:
type: integer type: integer
format: int64 format: int64
minimum: 0
DisplayName: DisplayName:
type: string type: string
maxLength: 128 maxLength: 128
@@ -1175,9 +1484,11 @@ components:
GameID: GameID:
type: integer type: integer
format: int32 format: int32
minimum: 0
Date: Date:
type: integer type: integer
format: int64 format: int64
minimum: 0
Mapfix: Mapfix:
required: required:
- ID - ID
@@ -1192,12 +1503,13 @@ components:
- Completed - Completed
- TargetAssetID - TargetAssetID
- StatusID - StatusID
- StatusMessage - Description
type: object type: object
properties: properties:
ID: ID:
type: integer type: integer
format: int64 format: int64
minimum: 0
DisplayName: DisplayName:
type: string type: string
maxLength: 128 maxLength: 128
@@ -1207,44 +1519,72 @@ components:
GameID: GameID:
type: integer type: integer
format: int32 format: int32
minimum: 0
CreatedAt: CreatedAt:
type: integer type: integer
format: int64 format: int64
minimum: 0
UpdatedAt: UpdatedAt:
type: integer type: integer
format: int64 format: int64
minimum: 0
Submitter: Submitter:
type: integer type: integer
format: int64 format: int64
minimum: 0
AssetID: AssetID:
type: integer type: integer
format: int64 format: int64
minimum: 0
AssetVersion: AssetVersion:
type: integer type: integer
format: int64 format: int64
minimum: 0
Completed: Completed:
type: boolean type: boolean
TargetAssetID: TargetAssetID:
type: integer type: integer
format: int64 format: int64
minimum: 0
StatusID: StatusID:
type: integer type: integer
format: int32 format: int32
StatusMessage: minimum: 0
Description:
type: string type: string
maxLength: 256 maxLength: 256
Mapfixes:
type: object
required:
- Total
- Mapfixes
properties:
Total:
type: integer
format: int64
minimum: 0
Mapfixes:
type: array
items:
$ref: "#/components/schemas/Mapfix"
MapfixTriggerCreate: MapfixTriggerCreate:
required: required:
- AssetID - AssetID
- TargetAssetID - TargetAssetID
- Description
type: object type: object
properties: properties:
AssetID: AssetID:
type: integer type: integer
format: int64 format: int64
minimum: 0
TargetAssetID: TargetAssetID:
type: integer type: integer
format: int64 format: int64
minimum: 0
Description:
type: string
maxLength: 256
Operation: Operation:
required: required:
- OperationID - OperationID
@@ -1258,15 +1598,19 @@ components:
OperationID: OperationID:
type: integer type: integer
format: int32 format: int32
minimum: 0
Date: Date:
type: integer type: integer
format: int64 format: int64
minimum: 0
Owner: Owner:
type: integer type: integer
format: int64 format: int64
minimum: 0
Status: Status:
type: integer type: integer
format: int32 format: int32
minimum: 0
StatusMessage: StatusMessage:
type: string type: string
maxLength: 256 maxLength: 256
@@ -1289,12 +1633,12 @@ components:
- Completed - Completed
# - UploadedAssetID # - UploadedAssetID
- StatusID - StatusID
- StatusMessage
type: object type: object
properties: properties:
ID: ID:
type: integer type: integer
format: int64 format: int64
minimum: 0
DisplayName: DisplayName:
type: string type: string
maxLength: 128 maxLength: 128
@@ -1304,46 +1648,81 @@ components:
GameID: GameID:
type: integer type: integer
format: int32 format: int32
minimum: 0
CreatedAt: CreatedAt:
type: integer type: integer
format: int64 format: int64
minimum: 0
UpdatedAt: UpdatedAt:
type: integer type: integer
format: int64 format: int64
minimum: 0
Submitter: Submitter:
type: integer type: integer
format: int64 format: int64
minimum: 0
AssetID: AssetID:
type: integer type: integer
format: int64 format: int64
minimum: 0
AssetVersion: AssetVersion:
type: integer type: integer
format: int64 format: int64
minimum: 0
ValidatedAssetID: ValidatedAssetID:
type: integer type: integer
format: int64 format: int64
minimum: 0
ValidatedAssetVersion: ValidatedAssetVersion:
type: integer type: integer
format: int64 format: int64
minimum: 0
Completed: Completed:
type: boolean type: boolean
UploadedAssetID: UploadedAssetID:
type: integer type: integer
format: int64 format: int64
minimum: 0
StatusID: StatusID:
type: integer type: integer
format: int32 format: int32
StatusMessage: minimum: 0
type: string Submissions:
maxLength: 256 required:
- Total
- Submissions
type: object
properties:
Total:
type: integer
format: int64
minimum: 0
Submissions:
type: array
items:
$ref: "#/components/schemas/Submission"
SubmissionTriggerCreate: SubmissionTriggerCreate:
required: required:
- AssetID - AssetID
- DisplayName
- Creator
- GameID
type: object type: object
properties: properties:
AssetID: AssetID:
type: integer type: integer
format: int64 format: int64
minimum: 0
DisplayName:
type: string
maxLength: 128
Creator:
type: string
maxLength: 128
GameID:
type: integer
format: int32
minimum: 0
ReleaseInfo: ReleaseInfo:
required: required:
- SubmissionID - SubmissionID
@@ -1353,6 +1732,7 @@ components:
SubmissionID: SubmissionID:
type: integer type: integer
format: int64 format: int64
minimum: 0
Date: Date:
type: string type: string
format: date-time format: date-time
@@ -1369,6 +1749,7 @@ components:
ID: ID:
type: integer type: integer
format: int64 format: int64
minimum: 0
Name: Name:
type: string type: string
maxLength: 128 maxLength: 128
@@ -1382,9 +1763,11 @@ components:
ResourceType: ResourceType:
type: integer type: integer
format: int32 format: int32
minimum: 0
ResourceID: ResourceID:
type: integer type: integer
format: int64 format: int64
minimum: 0
ScriptCreate: ScriptCreate:
required: required:
- Name - Name
@@ -1402,9 +1785,11 @@ components:
ResourceType: ResourceType:
type: integer type: integer
format: int32 format: int32
minimum: 0
ResourceID: ResourceID:
type: integer type: integer
format: int64 format: int64
minimum: 0
ScriptUpdate: ScriptUpdate:
required: required:
- ID - ID
@@ -1413,6 +1798,7 @@ components:
ID: ID:
type: integer type: integer
format: int64 format: int64
minimum: 0
Name: Name:
type: string type: string
maxLength: 128 maxLength: 128
@@ -1422,9 +1808,11 @@ components:
ResourceType: ResourceType:
type: integer type: integer
format: int32 format: int32
minimum: 0
ResourceID: ResourceID:
type: integer type: integer
format: int64 format: int64
minimum: 0
ScriptPolicy: ScriptPolicy:
required: required:
- ID - ID
@@ -1436,6 +1824,7 @@ components:
ID: ID:
type: integer type: integer
format: int64 format: int64
minimum: 0
FromScriptHash: FromScriptHash:
type: string type: string
minLength: 16 minLength: 16
@@ -1443,9 +1832,11 @@ components:
ToScriptID: ToScriptID:
type: integer type: integer
format: int64 format: int64
minimum: 0
Policy: Policy:
type: integer type: integer
format: int32 format: int32
minimum: 0
ScriptPolicyCreate: ScriptPolicyCreate:
required: required:
- FromScriptID - FromScriptID
@@ -1456,12 +1847,15 @@ components:
FromScriptID: FromScriptID:
type: integer type: integer
format: int64 format: int64
minimum: 0
ToScriptID: ToScriptID:
type: integer type: integer
format: int64 format: int64
minimum: 0
Policy: Policy:
type: integer type: integer
format: int32 format: int32
minimum: 0
ScriptPolicyUpdate: ScriptPolicyUpdate:
required: required:
- ID - ID
@@ -1470,15 +1864,19 @@ components:
ID: ID:
type: integer type: integer
format: int64 format: int64
minimum: 0
FromScriptID: FromScriptID:
type: integer type: integer
format: int64 format: int64
minimum: 0
ToScriptID: ToScriptID:
type: integer type: integer
format: int64 format: int64
minimum: 0
Policy: Policy:
type: integer type: integer
format: int32 format: int32
minimum: 0
Error: Error:
description: Represents error object description: Represents error object
type: object type: object
@@ -1486,6 +1884,7 @@ components:
code: code:
type: integer type: integer
format: int64 format: int64
minimum: 0
message: message:
type: string type: string
required: required:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,277 @@ import (
"github.com/ogen-go/ogen/validate" "github.com/ogen-go/ogen/validate"
) )
// Encode implements json.Marshaler.
func (s *AuditEvent) Encode(e *jx.Encoder) {
e.ObjStart()
s.encodeFields(e)
e.ObjEnd()
}
// encodeFields encodes fields.
func (s *AuditEvent) encodeFields(e *jx.Encoder) {
{
e.FieldStart("ID")
e.Int64(s.ID)
}
{
e.FieldStart("Date")
e.Int64(s.Date)
}
{
e.FieldStart("User")
e.Int64(s.User)
}
{
e.FieldStart("Username")
e.Str(s.Username)
}
{
e.FieldStart("ResourceType")
e.Int32(s.ResourceType)
}
{
e.FieldStart("ResourceID")
e.Int64(s.ResourceID)
}
{
e.FieldStart("EventType")
e.Int32(s.EventType)
}
{
e.FieldStart("EventData")
s.EventData.Encode(e)
}
}
var jsonFieldsNameOfAuditEvent = [8]string{
0: "ID",
1: "Date",
2: "User",
3: "Username",
4: "ResourceType",
5: "ResourceID",
6: "EventType",
7: "EventData",
}
// Decode decodes AuditEvent from json.
func (s *AuditEvent) Decode(d *jx.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode AuditEvent to nil")
}
var requiredBitSet [1]uint8
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
switch string(k) {
case "ID":
requiredBitSet[0] |= 1 << 0
if err := func() error {
v, err := d.Int64()
s.ID = int64(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"ID\"")
}
case "Date":
requiredBitSet[0] |= 1 << 1
if err := func() error {
v, err := d.Int64()
s.Date = int64(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Date\"")
}
case "User":
requiredBitSet[0] |= 1 << 2
if err := func() error {
v, err := d.Int64()
s.User = int64(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"User\"")
}
case "Username":
requiredBitSet[0] |= 1 << 3
if err := func() error {
v, err := d.Str()
s.Username = string(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Username\"")
}
case "ResourceType":
requiredBitSet[0] |= 1 << 4
if err := func() error {
v, err := d.Int32()
s.ResourceType = int32(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"ResourceType\"")
}
case "ResourceID":
requiredBitSet[0] |= 1 << 5
if err := func() error {
v, err := d.Int64()
s.ResourceID = int64(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"ResourceID\"")
}
case "EventType":
requiredBitSet[0] |= 1 << 6
if err := func() error {
v, err := d.Int32()
s.EventType = int32(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"EventType\"")
}
case "EventData":
requiredBitSet[0] |= 1 << 7
if err := func() error {
if err := s.EventData.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"EventData\"")
}
default:
return d.Skip()
}
return nil
}); err != nil {
return errors.Wrap(err, "decode AuditEvent")
}
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b11111111,
} {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR.
//
// If XOR result is not zero, result is not equal to expected, so some fields are missed.
// Bits of fields which would be set are actually bits of missed fields.
missed := bits.OnesCount8(result)
for bitN := 0; bitN < missed; bitN++ {
bitIdx := bits.TrailingZeros8(result)
fieldIdx := i*8 + bitIdx
var name string
if fieldIdx < len(jsonFieldsNameOfAuditEvent) {
name = jsonFieldsNameOfAuditEvent[fieldIdx]
} else {
name = strconv.Itoa(fieldIdx)
}
failures = append(failures, validate.FieldError{
Name: name,
Error: validate.ErrFieldRequired,
})
// Reset bit.
result &^= 1 << bitIdx
}
}
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
// MarshalJSON implements stdjson.Marshaler.
func (s *AuditEvent) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *AuditEvent) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}
// Encode implements json.Marshaler.
func (s AuditEventEventData) Encode(e *jx.Encoder) {
e.ObjStart()
s.encodeFields(e)
e.ObjEnd()
}
// encodeFields implements json.Marshaler.
func (s AuditEventEventData) encodeFields(e *jx.Encoder) {
for k, elem := range s {
e.FieldStart(k)
if len(elem) != 0 {
e.Raw(elem)
}
}
}
// Decode decodes AuditEventEventData from json.
func (s *AuditEventEventData) Decode(d *jx.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode AuditEventEventData to nil")
}
m := s.init()
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
var elem jx.Raw
if err := func() error {
v, err := d.RawAppend(nil)
elem = jx.Raw(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrapf(err, "decode field %q", k)
}
m[string(k)] = elem
return nil
}); err != nil {
return errors.Wrap(err, "decode AuditEventEventData")
}
return nil
}
// MarshalJSON implements stdjson.Marshaler.
func (s AuditEventEventData) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *AuditEventEventData) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}
// Encode implements json.Marshaler. // Encode implements json.Marshaler.
func (s *Error) Encode(e *jx.Encoder) { func (s *Error) Encode(e *jx.Encoder) {
e.ObjStart() e.ObjStart()
@@ -348,8 +619,8 @@ func (s *Mapfix) encodeFields(e *jx.Encoder) {
e.Int32(s.StatusID) e.Int32(s.StatusID)
} }
{ {
e.FieldStart("StatusMessage") e.FieldStart("Description")
e.Str(s.StatusMessage) e.Str(s.Description)
} }
} }
@@ -366,7 +637,7 @@ var jsonFieldsNameOfMapfix = [13]string{
9: "Completed", 9: "Completed",
10: "TargetAssetID", 10: "TargetAssetID",
11: "StatusID", 11: "StatusID",
12: "StatusMessage", 12: "Description",
} }
// Decode decodes Mapfix from json. // Decode decodes Mapfix from json.
@@ -522,17 +793,17 @@ func (s *Mapfix) Decode(d *jx.Decoder) error {
}(); err != nil { }(); err != nil {
return errors.Wrap(err, "decode field \"StatusID\"") return errors.Wrap(err, "decode field \"StatusID\"")
} }
case "StatusMessage": case "Description":
requiredBitSet[1] |= 1 << 4 requiredBitSet[1] |= 1 << 4
if err := func() error { if err := func() error {
v, err := d.Str() v, err := d.Str()
s.StatusMessage = string(v) s.Description = string(v)
if err != nil { if err != nil {
return err return err
} }
return nil return nil
}(); err != nil { }(); err != nil {
return errors.Wrap(err, "decode field \"StatusMessage\"") return errors.Wrap(err, "decode field \"Description\"")
} }
default: default:
return d.Skip() return d.Skip()
@@ -608,11 +879,16 @@ func (s *MapfixTriggerCreate) encodeFields(e *jx.Encoder) {
e.FieldStart("TargetAssetID") e.FieldStart("TargetAssetID")
e.Int64(s.TargetAssetID) e.Int64(s.TargetAssetID)
} }
{
e.FieldStart("Description")
e.Str(s.Description)
}
} }
var jsonFieldsNameOfMapfixTriggerCreate = [2]string{ var jsonFieldsNameOfMapfixTriggerCreate = [3]string{
0: "AssetID", 0: "AssetID",
1: "TargetAssetID", 1: "TargetAssetID",
2: "Description",
} }
// Decode decodes MapfixTriggerCreate from json. // Decode decodes MapfixTriggerCreate from json.
@@ -648,6 +924,18 @@ func (s *MapfixTriggerCreate) Decode(d *jx.Decoder) error {
}(); err != nil { }(); err != nil {
return errors.Wrap(err, "decode field \"TargetAssetID\"") return errors.Wrap(err, "decode field \"TargetAssetID\"")
} }
case "Description":
requiredBitSet[0] |= 1 << 2
if err := func() error {
v, err := d.Str()
s.Description = string(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Description\"")
}
default: default:
return d.Skip() return d.Skip()
} }
@@ -658,7 +946,7 @@ func (s *MapfixTriggerCreate) Decode(d *jx.Decoder) error {
// Validate required fields. // Validate required fields.
var failures []validate.FieldError var failures []validate.FieldError
for i, mask := range [1]uint8{ for i, mask := range [1]uint8{
0b00000011, 0b00000111,
} { } {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR. // Mask only required fields and check equality to mask using XOR.
@@ -704,6 +992,129 @@ func (s *MapfixTriggerCreate) UnmarshalJSON(data []byte) error {
return s.Decode(d) return s.Decode(d)
} }
// Encode implements json.Marshaler.
func (s *Mapfixes) Encode(e *jx.Encoder) {
e.ObjStart()
s.encodeFields(e)
e.ObjEnd()
}
// encodeFields encodes fields.
func (s *Mapfixes) encodeFields(e *jx.Encoder) {
{
e.FieldStart("Total")
e.Int64(s.Total)
}
{
e.FieldStart("Mapfixes")
e.ArrStart()
for _, elem := range s.Mapfixes {
elem.Encode(e)
}
e.ArrEnd()
}
}
var jsonFieldsNameOfMapfixes = [2]string{
0: "Total",
1: "Mapfixes",
}
// Decode decodes Mapfixes from json.
func (s *Mapfixes) Decode(d *jx.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode Mapfixes to nil")
}
var requiredBitSet [1]uint8
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
switch string(k) {
case "Total":
requiredBitSet[0] |= 1 << 0
if err := func() error {
v, err := d.Int64()
s.Total = int64(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Total\"")
}
case "Mapfixes":
requiredBitSet[0] |= 1 << 1
if err := func() error {
s.Mapfixes = make([]Mapfix, 0)
if err := d.Arr(func(d *jx.Decoder) error {
var elem Mapfix
if err := elem.Decode(d); err != nil {
return err
}
s.Mapfixes = append(s.Mapfixes, elem)
return nil
}); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Mapfixes\"")
}
default:
return d.Skip()
}
return nil
}); err != nil {
return errors.Wrap(err, "decode Mapfixes")
}
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b00000011,
} {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR.
//
// If XOR result is not zero, result is not equal to expected, so some fields are missed.
// Bits of fields which would be set are actually bits of missed fields.
missed := bits.OnesCount8(result)
for bitN := 0; bitN < missed; bitN++ {
bitIdx := bits.TrailingZeros8(result)
fieldIdx := i*8 + bitIdx
var name string
if fieldIdx < len(jsonFieldsNameOfMapfixes) {
name = jsonFieldsNameOfMapfixes[fieldIdx]
} else {
name = strconv.Itoa(fieldIdx)
}
failures = append(failures, validate.FieldError{
Name: name,
Error: validate.ErrFieldRequired,
})
// Reset bit.
result &^= 1 << bitIdx
}
}
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
// MarshalJSON implements stdjson.Marshaler.
func (s *Mapfixes) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *Mapfixes) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}
// Encode implements json.Marshaler. // Encode implements json.Marshaler.
func (s *Operation) Encode(e *jx.Encoder) { func (s *Operation) Encode(e *jx.Encoder) {
e.ObjStart() e.ObjStart()
@@ -2474,13 +2885,9 @@ func (s *Submission) encodeFields(e *jx.Encoder) {
e.FieldStart("StatusID") e.FieldStart("StatusID")
e.Int32(s.StatusID) e.Int32(s.StatusID)
} }
{
e.FieldStart("StatusMessage")
e.Str(s.StatusMessage)
}
} }
var jsonFieldsNameOfSubmission = [15]string{ var jsonFieldsNameOfSubmission = [14]string{
0: "ID", 0: "ID",
1: "DisplayName", 1: "DisplayName",
2: "Creator", 2: "Creator",
@@ -2495,7 +2902,6 @@ var jsonFieldsNameOfSubmission = [15]string{
11: "Completed", 11: "Completed",
12: "UploadedAssetID", 12: "UploadedAssetID",
13: "StatusID", 13: "StatusID",
14: "StatusMessage",
} }
// Decode decodes Submission from json. // Decode decodes Submission from json.
@@ -2669,18 +3075,6 @@ func (s *Submission) Decode(d *jx.Decoder) error {
}(); err != nil { }(); err != nil {
return errors.Wrap(err, "decode field \"StatusID\"") return errors.Wrap(err, "decode field \"StatusID\"")
} }
case "StatusMessage":
requiredBitSet[1] |= 1 << 6
if err := func() error {
v, err := d.Str()
s.StatusMessage = string(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"StatusMessage\"")
}
default: default:
return d.Skip() return d.Skip()
} }
@@ -2692,7 +3086,7 @@ func (s *Submission) Decode(d *jx.Decoder) error {
var failures []validate.FieldError var failures []validate.FieldError
for i, mask := range [2]uint8{ for i, mask := range [2]uint8{
0b11111111, 0b11111111,
0b01101001, 0b00101001,
} { } {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR. // Mask only required fields and check equality to mask using XOR.
@@ -2751,10 +3145,25 @@ func (s *SubmissionTriggerCreate) encodeFields(e *jx.Encoder) {
e.FieldStart("AssetID") e.FieldStart("AssetID")
e.Int64(s.AssetID) e.Int64(s.AssetID)
} }
{
e.FieldStart("DisplayName")
e.Str(s.DisplayName)
}
{
e.FieldStart("Creator")
e.Str(s.Creator)
}
{
e.FieldStart("GameID")
e.Int32(s.GameID)
}
} }
var jsonFieldsNameOfSubmissionTriggerCreate = [1]string{ var jsonFieldsNameOfSubmissionTriggerCreate = [4]string{
0: "AssetID", 0: "AssetID",
1: "DisplayName",
2: "Creator",
3: "GameID",
} }
// Decode decodes SubmissionTriggerCreate from json. // Decode decodes SubmissionTriggerCreate from json.
@@ -2778,6 +3187,42 @@ func (s *SubmissionTriggerCreate) Decode(d *jx.Decoder) error {
}(); err != nil { }(); err != nil {
return errors.Wrap(err, "decode field \"AssetID\"") return errors.Wrap(err, "decode field \"AssetID\"")
} }
case "DisplayName":
requiredBitSet[0] |= 1 << 1
if err := func() error {
v, err := d.Str()
s.DisplayName = string(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"DisplayName\"")
}
case "Creator":
requiredBitSet[0] |= 1 << 2
if err := func() error {
v, err := d.Str()
s.Creator = string(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Creator\"")
}
case "GameID":
requiredBitSet[0] |= 1 << 3
if err := func() error {
v, err := d.Int32()
s.GameID = int32(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"GameID\"")
}
default: default:
return d.Skip() return d.Skip()
} }
@@ -2788,7 +3233,7 @@ func (s *SubmissionTriggerCreate) Decode(d *jx.Decoder) error {
// Validate required fields. // Validate required fields.
var failures []validate.FieldError var failures []validate.FieldError
for i, mask := range [1]uint8{ for i, mask := range [1]uint8{
0b00000001, 0b00001111,
} { } {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR. // Mask only required fields and check equality to mask using XOR.
@@ -2834,6 +3279,129 @@ func (s *SubmissionTriggerCreate) UnmarshalJSON(data []byte) error {
return s.Decode(d) return s.Decode(d)
} }
// Encode implements json.Marshaler.
func (s *Submissions) Encode(e *jx.Encoder) {
e.ObjStart()
s.encodeFields(e)
e.ObjEnd()
}
// encodeFields encodes fields.
func (s *Submissions) encodeFields(e *jx.Encoder) {
{
e.FieldStart("Total")
e.Int64(s.Total)
}
{
e.FieldStart("Submissions")
e.ArrStart()
for _, elem := range s.Submissions {
elem.Encode(e)
}
e.ArrEnd()
}
}
var jsonFieldsNameOfSubmissions = [2]string{
0: "Total",
1: "Submissions",
}
// Decode decodes Submissions from json.
func (s *Submissions) Decode(d *jx.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode Submissions to nil")
}
var requiredBitSet [1]uint8
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
switch string(k) {
case "Total":
requiredBitSet[0] |= 1 << 0
if err := func() error {
v, err := d.Int64()
s.Total = int64(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Total\"")
}
case "Submissions":
requiredBitSet[0] |= 1 << 1
if err := func() error {
s.Submissions = make([]Submission, 0)
if err := d.Arr(func(d *jx.Decoder) error {
var elem Submission
if err := elem.Decode(d); err != nil {
return err
}
s.Submissions = append(s.Submissions, elem)
return nil
}); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Submissions\"")
}
default:
return d.Skip()
}
return nil
}); err != nil {
return errors.Wrap(err, "decode Submissions")
}
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b00000011,
} {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR.
//
// If XOR result is not zero, result is not equal to expected, so some fields are missed.
// Bits of fields which would be set are actually bits of missed fields.
missed := bits.OnesCount8(result)
for bitN := 0; bitN < missed; bitN++ {
bitIdx := bits.TrailingZeros8(result)
fieldIdx := i*8 + bitIdx
var name string
if fieldIdx < len(jsonFieldsNameOfSubmissions) {
name = jsonFieldsNameOfSubmissions[fieldIdx]
} else {
name = strconv.Itoa(fieldIdx)
}
failures = append(failures, validate.FieldError{
Name: name,
Error: validate.ErrFieldRequired,
})
// Reset bit.
result &^= 1 << bitIdx
}
}
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
// MarshalJSON implements stdjson.Marshaler.
func (s *Submissions) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *Submissions) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}
// Encode implements json.Marshaler. // Encode implements json.Marshaler.
func (s *User) Encode(e *jx.Encoder) { func (s *User) Encode(e *jx.Encoder) {
e.ObjStart() e.ObjStart()

View File

@@ -7,27 +7,34 @@ type OperationName = string
const ( const (
ActionMapfixAcceptedOperation OperationName = "ActionMapfixAccepted" ActionMapfixAcceptedOperation OperationName = "ActionMapfixAccepted"
ActionMapfixBypassSubmitOperation OperationName = "ActionMapfixBypassSubmit"
ActionMapfixRejectOperation OperationName = "ActionMapfixReject" ActionMapfixRejectOperation OperationName = "ActionMapfixReject"
ActionMapfixRequestChangesOperation OperationName = "ActionMapfixRequestChanges" ActionMapfixRequestChangesOperation OperationName = "ActionMapfixRequestChanges"
ActionMapfixResetSubmittingOperation OperationName = "ActionMapfixResetSubmitting"
ActionMapfixRetryValidateOperation OperationName = "ActionMapfixRetryValidate" ActionMapfixRetryValidateOperation OperationName = "ActionMapfixRetryValidate"
ActionMapfixRevokeOperation OperationName = "ActionMapfixRevoke" ActionMapfixRevokeOperation OperationName = "ActionMapfixRevoke"
ActionMapfixSubmitOperation OperationName = "ActionMapfixSubmit" ActionMapfixTriggerSubmitOperation OperationName = "ActionMapfixTriggerSubmit"
ActionMapfixTriggerUploadOperation OperationName = "ActionMapfixTriggerUpload" ActionMapfixTriggerUploadOperation OperationName = "ActionMapfixTriggerUpload"
ActionMapfixTriggerValidateOperation OperationName = "ActionMapfixTriggerValidate" ActionMapfixTriggerValidateOperation OperationName = "ActionMapfixTriggerValidate"
ActionMapfixValidatedOperation OperationName = "ActionMapfixValidated" ActionMapfixValidatedOperation OperationName = "ActionMapfixValidated"
ActionSubmissionAcceptedOperation OperationName = "ActionSubmissionAccepted" ActionSubmissionAcceptedOperation OperationName = "ActionSubmissionAccepted"
ActionSubmissionBypassSubmitOperation OperationName = "ActionSubmissionBypassSubmit"
ActionSubmissionRejectOperation OperationName = "ActionSubmissionReject" ActionSubmissionRejectOperation OperationName = "ActionSubmissionReject"
ActionSubmissionRequestChangesOperation OperationName = "ActionSubmissionRequestChanges" ActionSubmissionRequestChangesOperation OperationName = "ActionSubmissionRequestChanges"
ActionSubmissionResetSubmittingOperation OperationName = "ActionSubmissionResetSubmitting"
ActionSubmissionRetryValidateOperation OperationName = "ActionSubmissionRetryValidate" ActionSubmissionRetryValidateOperation OperationName = "ActionSubmissionRetryValidate"
ActionSubmissionRevokeOperation OperationName = "ActionSubmissionRevoke" ActionSubmissionRevokeOperation OperationName = "ActionSubmissionRevoke"
ActionSubmissionSubmitOperation OperationName = "ActionSubmissionSubmit" ActionSubmissionTriggerSubmitOperation OperationName = "ActionSubmissionTriggerSubmit"
ActionSubmissionTriggerUploadOperation OperationName = "ActionSubmissionTriggerUpload" ActionSubmissionTriggerUploadOperation OperationName = "ActionSubmissionTriggerUpload"
ActionSubmissionTriggerValidateOperation OperationName = "ActionSubmissionTriggerValidate" ActionSubmissionTriggerValidateOperation OperationName = "ActionSubmissionTriggerValidate"
ActionSubmissionValidatedOperation OperationName = "ActionSubmissionValidated" ActionSubmissionValidatedOperation OperationName = "ActionSubmissionValidated"
CreateMapfixOperation OperationName = "CreateMapfix" CreateMapfixOperation OperationName = "CreateMapfix"
CreateMapfixAuditCommentOperation OperationName = "CreateMapfixAuditComment"
CreateScriptOperation OperationName = "CreateScript" CreateScriptOperation OperationName = "CreateScript"
CreateScriptPolicyOperation OperationName = "CreateScriptPolicy" CreateScriptPolicyOperation OperationName = "CreateScriptPolicy"
CreateSubmissionOperation OperationName = "CreateSubmission" CreateSubmissionOperation OperationName = "CreateSubmission"
CreateSubmissionAdminOperation OperationName = "CreateSubmissionAdmin"
CreateSubmissionAuditCommentOperation OperationName = "CreateSubmissionAuditComment"
DeleteScriptOperation OperationName = "DeleteScript" DeleteScriptOperation OperationName = "DeleteScript"
DeleteScriptPolicyOperation OperationName = "DeleteScriptPolicy" DeleteScriptPolicyOperation OperationName = "DeleteScriptPolicy"
GetMapOperation OperationName = "GetMap" GetMapOperation OperationName = "GetMap"
@@ -36,10 +43,12 @@ const (
GetScriptOperation OperationName = "GetScript" GetScriptOperation OperationName = "GetScript"
GetScriptPolicyOperation OperationName = "GetScriptPolicy" GetScriptPolicyOperation OperationName = "GetScriptPolicy"
GetSubmissionOperation OperationName = "GetSubmission" GetSubmissionOperation OperationName = "GetSubmission"
ListMapfixAuditEventsOperation OperationName = "ListMapfixAuditEvents"
ListMapfixesOperation OperationName = "ListMapfixes" ListMapfixesOperation OperationName = "ListMapfixes"
ListMapsOperation OperationName = "ListMaps" ListMapsOperation OperationName = "ListMaps"
ListScriptPolicyOperation OperationName = "ListScriptPolicy" ListScriptPolicyOperation OperationName = "ListScriptPolicy"
ListScriptsOperation OperationName = "ListScripts" ListScriptsOperation OperationName = "ListScripts"
ListSubmissionAuditEventsOperation OperationName = "ListSubmissionAuditEvents"
ListSubmissionsOperation OperationName = "ListSubmissions" ListSubmissionsOperation OperationName = "ListSubmissions"
ReleaseSubmissionsOperation OperationName = "ReleaseSubmissions" ReleaseSubmissionsOperation OperationName = "ReleaseSubmissions"
SessionRolesOperation OperationName = "SessionRoles" SessionRolesOperation OperationName = "SessionRoles"

File diff suppressed because it is too large Load Diff

View File

@@ -3,13 +3,13 @@
package api package api
import ( import (
"fmt"
"io" "io"
"mime" "mime"
"net/http" "net/http"
"github.com/go-faster/errors" "github.com/go-faster/errors"
"github.com/go-faster/jx" "github.com/go-faster/jx"
"go.uber.org/multierr"
"github.com/ogen-go/ogen/ogenerrors" "github.com/ogen-go/ogen/ogenerrors"
"github.com/ogen-go/ogen/validate" "github.com/ogen-go/ogen/validate"
@@ -26,13 +26,13 @@ func (s *Server) decodeCreateMapfixRequest(r *http.Request) (
// Close in reverse order, to match defer behavior. // Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- { for i := len(closers) - 1; i >= 0; i-- {
c := closers[i] c := closers[i]
merr = multierr.Append(merr, c()) merr = errors.Join(merr, c())
} }
return merr return merr
} }
defer func() { defer func() {
if rerr != nil { if rerr != nil {
rerr = multierr.Append(rerr, close()) rerr = errors.Join(rerr, close())
} }
}() }()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
@@ -72,12 +72,54 @@ func (s *Server) decodeCreateMapfixRequest(r *http.Request) (
} }
return req, close, err return req, close, err
} }
if err := func() error {
if err := request.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return req, close, errors.Wrap(err, "validate")
}
return &request, close, nil return &request, close, nil
default: default:
return req, close, validate.InvalidContentType(ct) return req, close, validate.InvalidContentType(ct)
} }
} }
func (s *Server) decodeCreateMapfixAuditCommentRequest(r *http.Request) (
req CreateMapfixAuditCommentReq,
close func() error,
rerr error,
) {
var closers []func() error
close = func() error {
var merr error
// Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- {
c := closers[i]
merr = errors.Join(merr, c())
}
return merr
}
defer func() {
if rerr != nil {
rerr = errors.Join(rerr, close())
}
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "text/plain":
reader := r.Body
request := CreateMapfixAuditCommentReq{Data: reader}
return request, close, nil
default:
return req, close, validate.InvalidContentType(ct)
}
}
func (s *Server) decodeCreateScriptRequest(r *http.Request) ( func (s *Server) decodeCreateScriptRequest(r *http.Request) (
req *ScriptCreate, req *ScriptCreate,
close func() error, close func() error,
@@ -89,13 +131,13 @@ func (s *Server) decodeCreateScriptRequest(r *http.Request) (
// Close in reverse order, to match defer behavior. // Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- { for i := len(closers) - 1; i >= 0; i-- {
c := closers[i] c := closers[i]
merr = multierr.Append(merr, c()) merr = errors.Join(merr, c())
} }
return merr return merr
} }
defer func() { defer func() {
if rerr != nil { if rerr != nil {
rerr = multierr.Append(rerr, close()) rerr = errors.Join(rerr, close())
} }
}() }()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
@@ -160,13 +202,13 @@ func (s *Server) decodeCreateScriptPolicyRequest(r *http.Request) (
// Close in reverse order, to match defer behavior. // Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- { for i := len(closers) - 1; i >= 0; i-- {
c := closers[i] c := closers[i]
merr = multierr.Append(merr, c()) merr = errors.Join(merr, c())
} }
return merr return merr
} }
defer func() { defer func() {
if rerr != nil { if rerr != nil {
rerr = multierr.Append(rerr, close()) rerr = errors.Join(rerr, close())
} }
}() }()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
@@ -206,6 +248,14 @@ func (s *Server) decodeCreateScriptPolicyRequest(r *http.Request) (
} }
return req, close, err return req, close, err
} }
if err := func() error {
if err := request.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return req, close, errors.Wrap(err, "validate")
}
return &request, close, nil return &request, close, nil
default: default:
return req, close, validate.InvalidContentType(ct) return req, close, validate.InvalidContentType(ct)
@@ -223,13 +273,13 @@ func (s *Server) decodeCreateSubmissionRequest(r *http.Request) (
// Close in reverse order, to match defer behavior. // Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- { for i := len(closers) - 1; i >= 0; i-- {
c := closers[i] c := closers[i]
merr = multierr.Append(merr, c()) merr = errors.Join(merr, c())
} }
return merr return merr
} }
defer func() { defer func() {
if rerr != nil { if rerr != nil {
rerr = multierr.Append(rerr, close()) rerr = errors.Join(rerr, close())
} }
}() }()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
@@ -269,12 +319,125 @@ func (s *Server) decodeCreateSubmissionRequest(r *http.Request) (
} }
return req, close, err return req, close, err
} }
if err := func() error {
if err := request.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return req, close, errors.Wrap(err, "validate")
}
return &request, close, nil return &request, close, nil
default: default:
return req, close, validate.InvalidContentType(ct) return req, close, validate.InvalidContentType(ct)
} }
} }
func (s *Server) decodeCreateSubmissionAdminRequest(r *http.Request) (
req *SubmissionTriggerCreate,
close func() error,
rerr error,
) {
var closers []func() error
close = func() error {
var merr error
// Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- {
c := closers[i]
merr = errors.Join(merr, c())
}
return merr
}
defer func() {
if rerr != nil {
rerr = errors.Join(rerr, close())
}
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
if r.ContentLength == 0 {
return req, close, validate.ErrBodyRequired
}
buf, err := io.ReadAll(r.Body)
if err != nil {
return req, close, err
}
if len(buf) == 0 {
return req, close, validate.ErrBodyRequired
}
d := jx.DecodeBytes(buf)
var request SubmissionTriggerCreate
if err := func() error {
if err := request.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return req, close, err
}
if err := func() error {
if err := request.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return req, close, errors.Wrap(err, "validate")
}
return &request, close, nil
default:
return req, close, validate.InvalidContentType(ct)
}
}
func (s *Server) decodeCreateSubmissionAuditCommentRequest(r *http.Request) (
req CreateSubmissionAuditCommentReq,
close func() error,
rerr error,
) {
var closers []func() error
close = func() error {
var merr error
// Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- {
c := closers[i]
merr = errors.Join(merr, c())
}
return merr
}
defer func() {
if rerr != nil {
rerr = errors.Join(rerr, close())
}
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "text/plain":
reader := r.Body
request := CreateSubmissionAuditCommentReq{Data: reader}
return request, close, nil
default:
return req, close, validate.InvalidContentType(ct)
}
}
func (s *Server) decodeReleaseSubmissionsRequest(r *http.Request) ( func (s *Server) decodeReleaseSubmissionsRequest(r *http.Request) (
req []ReleaseInfo, req []ReleaseInfo,
close func() error, close func() error,
@@ -286,13 +449,13 @@ func (s *Server) decodeReleaseSubmissionsRequest(r *http.Request) (
// Close in reverse order, to match defer behavior. // Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- { for i := len(closers) - 1; i >= 0; i-- {
c := closers[i] c := closers[i]
merr = multierr.Append(merr, c()) merr = errors.Join(merr, c())
} }
return merr return merr
} }
defer func() { defer func() {
if rerr != nil { if rerr != nil {
rerr = multierr.Append(rerr, close()) rerr = errors.Join(rerr, close())
} }
}() }()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
@@ -352,6 +515,23 @@ func (s *Server) decodeReleaseSubmissionsRequest(r *http.Request) (
}).ValidateLength(len(request)); err != nil { }).ValidateLength(len(request)); err != nil {
return errors.Wrap(err, "array") return errors.Wrap(err, "array")
} }
var failures []validate.FieldError
for i, elem := range request {
if err := func() error {
if err := elem.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: fmt.Sprintf("[%d]", i),
Error: err,
})
}
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil return nil
}(); err != nil { }(); err != nil {
return req, close, errors.Wrap(err, "validate") return req, close, errors.Wrap(err, "validate")
@@ -373,13 +553,13 @@ func (s *Server) decodeUpdateScriptRequest(r *http.Request) (
// Close in reverse order, to match defer behavior. // Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- { for i := len(closers) - 1; i >= 0; i-- {
c := closers[i] c := closers[i]
merr = multierr.Append(merr, c()) merr = errors.Join(merr, c())
} }
return merr return merr
} }
defer func() { defer func() {
if rerr != nil { if rerr != nil {
rerr = multierr.Append(rerr, close()) rerr = errors.Join(rerr, close())
} }
}() }()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
@@ -444,13 +624,13 @@ func (s *Server) decodeUpdateScriptPolicyRequest(r *http.Request) (
// Close in reverse order, to match defer behavior. // Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- { for i := len(closers) - 1; i >= 0; i-- {
c := closers[i] c := closers[i]
merr = multierr.Append(merr, c()) merr = errors.Join(merr, c())
} }
return merr return merr
} }
defer func() { defer func() {
if rerr != nil { if rerr != nil {
rerr = multierr.Append(rerr, close()) rerr = errors.Join(rerr, close())
} }
}() }()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
@@ -490,6 +670,14 @@ func (s *Server) decodeUpdateScriptPolicyRequest(r *http.Request) (
} }
return req, close, err return req, close, err
} }
if err := func() error {
if err := request.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return req, close, errors.Wrap(err, "validate")
}
return &request, close, nil return &request, close, nil
default: default:
return req, close, validate.InvalidContentType(ct) return req, close, validate.InvalidContentType(ct)

View File

@@ -25,6 +25,16 @@ func encodeCreateMapfixRequest(
return nil return nil
} }
func encodeCreateMapfixAuditCommentRequest(
req CreateMapfixAuditCommentReq,
r *http.Request,
) error {
const contentType = "text/plain"
body := req
ht.SetBody(r, body, contentType)
return nil
}
func encodeCreateScriptRequest( func encodeCreateScriptRequest(
req *ScriptCreate, req *ScriptCreate,
r *http.Request, r *http.Request,
@@ -67,6 +77,30 @@ func encodeCreateSubmissionRequest(
return nil return nil
} }
func encodeCreateSubmissionAdminRequest(
req *SubmissionTriggerCreate,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeCreateSubmissionAuditCommentRequest(
req CreateSubmissionAuditCommentReq,
r *http.Request,
) error {
const contentType = "text/plain"
body := req
ht.SetBody(r, body, contentType)
return nil
}
func encodeReleaseSubmissionsRequest( func encodeReleaseSubmissionsRequest(
req []ReleaseInfo, req []ReleaseInfo,
r *http.Request, r *http.Request,

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,13 @@ func encodeActionMapfixAcceptedResponse(response *ActionMapfixAcceptedNoContent,
return nil return nil
} }
func encodeActionMapfixBypassSubmitResponse(response *ActionMapfixBypassSubmitNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeActionMapfixRejectResponse(response *ActionMapfixRejectNoContent, w http.ResponseWriter, span trace.Span) error { func encodeActionMapfixRejectResponse(response *ActionMapfixRejectNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204) w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204)) span.SetStatus(codes.Ok, http.StatusText(204))
@@ -34,6 +41,13 @@ func encodeActionMapfixRequestChangesResponse(response *ActionMapfixRequestChang
return nil return nil
} }
func encodeActionMapfixResetSubmittingResponse(response *ActionMapfixResetSubmittingNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeActionMapfixRetryValidateResponse(response *ActionMapfixRetryValidateNoContent, w http.ResponseWriter, span trace.Span) error { func encodeActionMapfixRetryValidateResponse(response *ActionMapfixRetryValidateNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204) w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204)) span.SetStatus(codes.Ok, http.StatusText(204))
@@ -48,7 +62,7 @@ func encodeActionMapfixRevokeResponse(response *ActionMapfixRevokeNoContent, w h
return nil return nil
} }
func encodeActionMapfixSubmitResponse(response *ActionMapfixSubmitNoContent, w http.ResponseWriter, span trace.Span) error { func encodeActionMapfixTriggerSubmitResponse(response *ActionMapfixTriggerSubmitNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204) w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204)) span.SetStatus(codes.Ok, http.StatusText(204))
@@ -83,6 +97,13 @@ func encodeActionSubmissionAcceptedResponse(response *ActionSubmissionAcceptedNo
return nil return nil
} }
func encodeActionSubmissionBypassSubmitResponse(response *ActionSubmissionBypassSubmitNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeActionSubmissionRejectResponse(response *ActionSubmissionRejectNoContent, w http.ResponseWriter, span trace.Span) error { func encodeActionSubmissionRejectResponse(response *ActionSubmissionRejectNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204) w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204)) span.SetStatus(codes.Ok, http.StatusText(204))
@@ -97,6 +118,13 @@ func encodeActionSubmissionRequestChangesResponse(response *ActionSubmissionRequ
return nil return nil
} }
func encodeActionSubmissionResetSubmittingResponse(response *ActionSubmissionResetSubmittingNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeActionSubmissionRetryValidateResponse(response *ActionSubmissionRetryValidateNoContent, w http.ResponseWriter, span trace.Span) error { func encodeActionSubmissionRetryValidateResponse(response *ActionSubmissionRetryValidateNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204) w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204)) span.SetStatus(codes.Ok, http.StatusText(204))
@@ -111,7 +139,7 @@ func encodeActionSubmissionRevokeResponse(response *ActionSubmissionRevokeNoCont
return nil return nil
} }
func encodeActionSubmissionSubmitResponse(response *ActionSubmissionSubmitNoContent, w http.ResponseWriter, span trace.Span) error { func encodeActionSubmissionTriggerSubmitResponse(response *ActionSubmissionTriggerSubmitNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204) w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204)) span.SetStatus(codes.Ok, http.StatusText(204))
@@ -153,6 +181,13 @@ func encodeCreateMapfixResponse(response *OperationID, w http.ResponseWriter, sp
return nil return nil
} }
func encodeCreateMapfixAuditCommentResponse(response *CreateMapfixAuditCommentNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeCreateScriptResponse(response *ScriptID, w http.ResponseWriter, span trace.Span) error { func encodeCreateScriptResponse(response *ScriptID, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(201) w.WriteHeader(201)
@@ -195,6 +230,27 @@ func encodeCreateSubmissionResponse(response *OperationID, w http.ResponseWriter
return nil return nil
} }
func encodeCreateSubmissionAdminResponse(response *OperationID, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(201)
span.SetStatus(codes.Ok, http.StatusText(201))
e := new(jx.Encoder)
response.Encode(e)
if _, err := e.WriteTo(w); err != nil {
return errors.Wrap(err, "write")
}
return nil
}
func encodeCreateSubmissionAuditCommentResponse(response *CreateSubmissionAuditCommentNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeDeleteScriptResponse(response *DeleteScriptNoContent, w http.ResponseWriter, span trace.Span) error { func encodeDeleteScriptResponse(response *DeleteScriptNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204) w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204)) span.SetStatus(codes.Ok, http.StatusText(204))
@@ -293,7 +349,7 @@ func encodeGetSubmissionResponse(response *Submission, w http.ResponseWriter, sp
return nil return nil
} }
func encodeListMapfixesResponse(response []Mapfix, w http.ResponseWriter, span trace.Span) error { func encodeListMapfixAuditEventsResponse(response []AuditEvent, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200) w.WriteHeader(200)
span.SetStatus(codes.Ok, http.StatusText(200)) span.SetStatus(codes.Ok, http.StatusText(200))
@@ -311,6 +367,20 @@ func encodeListMapfixesResponse(response []Mapfix, w http.ResponseWriter, span t
return nil return nil
} }
func encodeListMapfixesResponse(response *Mapfixes, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200)
span.SetStatus(codes.Ok, http.StatusText(200))
e := new(jx.Encoder)
response.Encode(e)
if _, err := e.WriteTo(w); err != nil {
return errors.Wrap(err, "write")
}
return nil
}
func encodeListMapsResponse(response []Map, w http.ResponseWriter, span trace.Span) error { func encodeListMapsResponse(response []Map, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200) w.WriteHeader(200)
@@ -365,7 +435,7 @@ func encodeListScriptsResponse(response []Script, w http.ResponseWriter, span tr
return nil return nil
} }
func encodeListSubmissionsResponse(response []Submission, w http.ResponseWriter, span trace.Span) error { func encodeListSubmissionAuditEventsResponse(response []AuditEvent, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200) w.WriteHeader(200)
span.SetStatus(codes.Ok, http.StatusText(200)) span.SetStatus(codes.Ok, http.StatusText(200))
@@ -383,6 +453,20 @@ func encodeListSubmissionsResponse(response []Submission, w http.ResponseWriter,
return nil return nil
} }
func encodeListSubmissionsResponse(response *Submissions, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200)
span.SetStatus(codes.Ok, http.StatusText(200))
e := new(jx.Encoder)
response.Encode(e)
if _, err := e.WriteTo(w); err != nil {
return errors.Wrap(err, "write")
}
return nil
}
func encodeReleaseSubmissionsResponse(response *ReleaseSubmissionsCreated, w http.ResponseWriter, span trace.Span) error { func encodeReleaseSubmissionsResponse(response *ReleaseSubmissionsCreated, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(201) w.WriteHeader(201)
span.SetStatus(codes.Ok, http.StatusText(201)) span.SetStatus(codes.Ok, http.StatusText(201))

View File

@@ -136,9 +136,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
break break
} }
switch elem[0] { switch elem[0] {
case 'c': // Prefix: "completed" case 'a': // Prefix: "audit-events"
if l := len("completed"); len(elem) >= l && elem[0:l] == "completed" { if l := len("audit-events"); len(elem) >= l && elem[0:l] == "audit-events" {
elem = elem[l:] elem = elem[l:]
} else { } else {
break break
@@ -147,17 +147,75 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if len(elem) == 0 { if len(elem) == 0 {
// Leaf node. // Leaf node.
switch r.Method { switch r.Method {
case "POST": case "GET":
s.handleSetMapfixCompletedRequest([1]string{ s.handleListMapfixAuditEventsRequest([1]string{
args[0], args[0],
}, elemIsEscaped, w, r) }, elemIsEscaped, w, r)
default: default:
s.notAllowed(w, r, "POST") s.notAllowed(w, r, "GET")
} }
return return
} }
case 'c': // Prefix: "com"
if l := len("com"); len(elem) >= l && elem[0:l] == "com" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 'm': // Prefix: "ment"
if l := len("ment"); len(elem) >= l && elem[0:l] == "ment" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleCreateMapfixAuditCommentRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
case 'p': // Prefix: "pleted"
if l := len("pleted"); len(elem) >= l && elem[0:l] == "pleted" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleSetMapfixCompletedRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
}
case 'm': // Prefix: "model" case 'm': // Prefix: "model"
if l := len("model"); len(elem) >= l && elem[0:l] == "model" { if l := len("model"); len(elem) >= l && elem[0:l] == "model" {
@@ -192,6 +250,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
break break
} }
switch elem[0] { switch elem[0] {
case 'b': // Prefix: "bypass-submit"
if l := len("bypass-submit"); len(elem) >= l && elem[0:l] == "bypass-submit" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionMapfixBypassSubmitRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
case 'r': // Prefix: "re" case 'r': // Prefix: "re"
if l := len("re"); len(elem) >= l && elem[0:l] == "re" { if l := len("re"); len(elem) >= l && elem[0:l] == "re" {
@@ -260,6 +340,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
break break
} }
switch elem[0] { switch elem[0] {
case 's': // Prefix: "submitting"
if l := len("submitting"); len(elem) >= l && elem[0:l] == "submitting" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionMapfixResetSubmittingRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
case 'u': // Prefix: "uploading" case 'u': // Prefix: "uploading"
if l := len("uploading"); len(elem) >= l && elem[0:l] == "uploading" { if l := len("uploading"); len(elem) >= l && elem[0:l] == "uploading" {
@@ -352,28 +454,6 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
case 's': // Prefix: "submit"
if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionMapfixSubmitRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
case 't': // Prefix: "trigger-" case 't': // Prefix: "trigger-"
if l := len("trigger-"); len(elem) >= l && elem[0:l] == "trigger-" { if l := len("trigger-"); len(elem) >= l && elem[0:l] == "trigger-" {
@@ -386,6 +466,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
break break
} }
switch elem[0] { switch elem[0] {
case 's': // Prefix: "submit"
if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionMapfixTriggerSubmitRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
case 'u': // Prefix: "upload" case 'u': // Prefix: "upload"
if l := len("upload"); len(elem) >= l && elem[0:l] == "upload" { if l := len("upload"); len(elem) >= l && elem[0:l] == "upload" {
@@ -790,6 +892,26 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
switch elem[0] { switch elem[0] {
case '-': // Prefix: "-admin"
if l := len("-admin"); len(elem) >= l && elem[0:l] == "-admin" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleCreateSubmissionAdminRequest([0]string{}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
case '/': // Prefix: "/" case '/': // Prefix: "/"
if l := len("/"); len(elem) >= l && elem[0:l] == "/" { if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
@@ -832,9 +954,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
break break
} }
switch elem[0] { switch elem[0] {
case 'c': // Prefix: "completed" case 'a': // Prefix: "audit-events"
if l := len("completed"); len(elem) >= l && elem[0:l] == "completed" { if l := len("audit-events"); len(elem) >= l && elem[0:l] == "audit-events" {
elem = elem[l:] elem = elem[l:]
} else { } else {
break break
@@ -843,17 +965,75 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if len(elem) == 0 { if len(elem) == 0 {
// Leaf node. // Leaf node.
switch r.Method { switch r.Method {
case "POST": case "GET":
s.handleSetSubmissionCompletedRequest([1]string{ s.handleListSubmissionAuditEventsRequest([1]string{
args[0], args[0],
}, elemIsEscaped, w, r) }, elemIsEscaped, w, r)
default: default:
s.notAllowed(w, r, "POST") s.notAllowed(w, r, "GET")
} }
return return
} }
case 'c': // Prefix: "com"
if l := len("com"); len(elem) >= l && elem[0:l] == "com" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 'm': // Prefix: "ment"
if l := len("ment"); len(elem) >= l && elem[0:l] == "ment" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleCreateSubmissionAuditCommentRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
case 'p': // Prefix: "pleted"
if l := len("pleted"); len(elem) >= l && elem[0:l] == "pleted" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleSetSubmissionCompletedRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
}
case 'm': // Prefix: "model" case 'm': // Prefix: "model"
if l := len("model"); len(elem) >= l && elem[0:l] == "model" { if l := len("model"); len(elem) >= l && elem[0:l] == "model" {
@@ -888,6 +1068,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
break break
} }
switch elem[0] { switch elem[0] {
case 'b': // Prefix: "bypass-submit"
if l := len("bypass-submit"); len(elem) >= l && elem[0:l] == "bypass-submit" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionSubmissionBypassSubmitRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
case 'r': // Prefix: "re" case 'r': // Prefix: "re"
if l := len("re"); len(elem) >= l && elem[0:l] == "re" { if l := len("re"); len(elem) >= l && elem[0:l] == "re" {
@@ -956,6 +1158,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
break break
} }
switch elem[0] { switch elem[0] {
case 's': // Prefix: "submitting"
if l := len("submitting"); len(elem) >= l && elem[0:l] == "submitting" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionSubmissionResetSubmittingRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
case 'u': // Prefix: "uploading" case 'u': // Prefix: "uploading"
if l := len("uploading"); len(elem) >= l && elem[0:l] == "uploading" { if l := len("uploading"); len(elem) >= l && elem[0:l] == "uploading" {
@@ -1048,28 +1272,6 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
case 's': // Prefix: "submit"
if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionSubmissionSubmitRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
case 't': // Prefix: "trigger-" case 't': // Prefix: "trigger-"
if l := len("trigger-"); len(elem) >= l && elem[0:l] == "trigger-" { if l := len("trigger-"); len(elem) >= l && elem[0:l] == "trigger-" {
@@ -1082,6 +1284,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
break break
} }
switch elem[0] { switch elem[0] {
case 's': // Prefix: "submit"
if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionSubmissionTriggerSubmitRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
case 'u': // Prefix: "upload" case 'u': // Prefix: "upload"
if l := len("upload"); len(elem) >= l && elem[0:l] == "upload" { if l := len("upload"); len(elem) >= l && elem[0:l] == "upload" {
@@ -1319,9 +1543,9 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
break break
} }
switch elem[0] { switch elem[0] {
case 'c': // Prefix: "completed" case 'a': // Prefix: "audit-events"
if l := len("completed"); len(elem) >= l && elem[0:l] == "completed" { if l := len("audit-events"); len(elem) >= l && elem[0:l] == "audit-events" {
elem = elem[l:] elem = elem[l:]
} else { } else {
break break
@@ -1330,11 +1554,11 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
if len(elem) == 0 { if len(elem) == 0 {
// Leaf node. // Leaf node.
switch method { switch method {
case "POST": case "GET":
r.name = SetMapfixCompletedOperation r.name = ListMapfixAuditEventsOperation
r.summary = "Called by maptest when a player completes the map" r.summary = "Retrieve a list of audit events"
r.operationID = "setMapfixCompleted" r.operationID = "listMapfixAuditEvents"
r.pathPattern = "/mapfixes/{MapfixID}/completed" r.pathPattern = "/mapfixes/{MapfixID}/audit-events"
r.args = args r.args = args
r.count = 1 r.count = 1
return r, true return r, true
@@ -1343,6 +1567,68 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
} }
} }
case 'c': // Prefix: "com"
if l := len("com"); len(elem) >= l && elem[0:l] == "com" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 'm': // Prefix: "ment"
if l := len("ment"); len(elem) >= l && elem[0:l] == "ment" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = CreateMapfixAuditCommentOperation
r.summary = "Post a comment to the audit log"
r.operationID = "createMapfixAuditComment"
r.pathPattern = "/mapfixes/{MapfixID}/comment"
r.args = args
r.count = 1
return r, true
default:
return
}
}
case 'p': // Prefix: "pleted"
if l := len("pleted"); len(elem) >= l && elem[0:l] == "pleted" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = SetMapfixCompletedOperation
r.summary = "Called by maptest when a player completes the map"
r.operationID = "setMapfixCompleted"
r.pathPattern = "/mapfixes/{MapfixID}/completed"
r.args = args
r.count = 1
return r, true
default:
return
}
}
}
case 'm': // Prefix: "model" case 'm': // Prefix: "model"
if l := len("model"); len(elem) >= l && elem[0:l] == "model" { if l := len("model"); len(elem) >= l && elem[0:l] == "model" {
@@ -1379,6 +1665,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
break break
} }
switch elem[0] { switch elem[0] {
case 'b': // Prefix: "bypass-submit"
if l := len("bypass-submit"); len(elem) >= l && elem[0:l] == "bypass-submit" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionMapfixBypassSubmitOperation
r.summary = "Role Reviewer changes status from ChangesRequested -> Submitted"
r.operationID = "actionMapfixBypassSubmit"
r.pathPattern = "/mapfixes/{MapfixID}/status/bypass-submit"
r.args = args
r.count = 1
return r, true
default:
return
}
}
case 'r': // Prefix: "re" case 'r': // Prefix: "re"
if l := len("re"); len(elem) >= l && elem[0:l] == "re" { if l := len("re"); len(elem) >= l && elem[0:l] == "re" {
@@ -1451,6 +1761,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
break break
} }
switch elem[0] { switch elem[0] {
case 's': // Prefix: "submitting"
if l := len("submitting"); len(elem) >= l && elem[0:l] == "submitting" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionMapfixResetSubmittingOperation
r.summary = "Role Submitter manually resets submitting softlock and changes status from Submitting -> UnderConstruction"
r.operationID = "actionMapfixResetSubmitting"
r.pathPattern = "/mapfixes/{MapfixID}/status/reset-submitting"
r.args = args
r.count = 1
return r, true
default:
return
}
}
case 'u': // Prefix: "uploading" case 'u': // Prefix: "uploading"
if l := len("uploading"); len(elem) >= l && elem[0:l] == "uploading" { if l := len("uploading"); len(elem) >= l && elem[0:l] == "uploading" {
@@ -1551,30 +1885,6 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
} }
case 's': // Prefix: "submit"
if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionMapfixSubmitOperation
r.summary = "Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted"
r.operationID = "actionMapfixSubmit"
r.pathPattern = "/mapfixes/{MapfixID}/status/submit"
r.args = args
r.count = 1
return r, true
default:
return
}
}
case 't': // Prefix: "trigger-" case 't': // Prefix: "trigger-"
if l := len("trigger-"); len(elem) >= l && elem[0:l] == "trigger-" { if l := len("trigger-"); len(elem) >= l && elem[0:l] == "trigger-" {
@@ -1587,6 +1897,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
break break
} }
switch elem[0] { switch elem[0] {
case 's': // Prefix: "submit"
if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionMapfixTriggerSubmitOperation
r.summary = "Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting"
r.operationID = "actionMapfixTriggerSubmit"
r.pathPattern = "/mapfixes/{MapfixID}/status/trigger-submit"
r.args = args
r.count = 1
return r, true
default:
return
}
}
case 'u': // Prefix: "upload" case 'u': // Prefix: "upload"
if l := len("upload"); len(elem) >= l && elem[0:l] == "upload" { if l := len("upload"); len(elem) >= l && elem[0:l] == "upload" {
@@ -2069,6 +2403,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
} }
} }
switch elem[0] { switch elem[0] {
case '-': // Prefix: "-admin"
if l := len("-admin"); len(elem) >= l && elem[0:l] == "-admin" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = CreateSubmissionAdminOperation
r.summary = "Trigger the validator to create a new submission"
r.operationID = "createSubmissionAdmin"
r.pathPattern = "/submissions-admin"
r.args = args
r.count = 0
return r, true
default:
return
}
}
case '/': // Prefix: "/" case '/': // Prefix: "/"
if l := len("/"); len(elem) >= l && elem[0:l] == "/" { if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
@@ -2113,9 +2471,9 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
break break
} }
switch elem[0] { switch elem[0] {
case 'c': // Prefix: "completed" case 'a': // Prefix: "audit-events"
if l := len("completed"); len(elem) >= l && elem[0:l] == "completed" { if l := len("audit-events"); len(elem) >= l && elem[0:l] == "audit-events" {
elem = elem[l:] elem = elem[l:]
} else { } else {
break break
@@ -2124,11 +2482,11 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
if len(elem) == 0 { if len(elem) == 0 {
// Leaf node. // Leaf node.
switch method { switch method {
case "POST": case "GET":
r.name = SetSubmissionCompletedOperation r.name = ListSubmissionAuditEventsOperation
r.summary = "Called by maptest when a player completes the map" r.summary = "Retrieve a list of audit events"
r.operationID = "setSubmissionCompleted" r.operationID = "listSubmissionAuditEvents"
r.pathPattern = "/submissions/{SubmissionID}/completed" r.pathPattern = "/submissions/{SubmissionID}/audit-events"
r.args = args r.args = args
r.count = 1 r.count = 1
return r, true return r, true
@@ -2137,6 +2495,68 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
} }
} }
case 'c': // Prefix: "com"
if l := len("com"); len(elem) >= l && elem[0:l] == "com" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 'm': // Prefix: "ment"
if l := len("ment"); len(elem) >= l && elem[0:l] == "ment" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = CreateSubmissionAuditCommentOperation
r.summary = "Post a comment to the audit log"
r.operationID = "createSubmissionAuditComment"
r.pathPattern = "/submissions/{SubmissionID}/comment"
r.args = args
r.count = 1
return r, true
default:
return
}
}
case 'p': // Prefix: "pleted"
if l := len("pleted"); len(elem) >= l && elem[0:l] == "pleted" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = SetSubmissionCompletedOperation
r.summary = "Called by maptest when a player completes the map"
r.operationID = "setSubmissionCompleted"
r.pathPattern = "/submissions/{SubmissionID}/completed"
r.args = args
r.count = 1
return r, true
default:
return
}
}
}
case 'm': // Prefix: "model" case 'm': // Prefix: "model"
if l := len("model"); len(elem) >= l && elem[0:l] == "model" { if l := len("model"); len(elem) >= l && elem[0:l] == "model" {
@@ -2173,6 +2593,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
break break
} }
switch elem[0] { switch elem[0] {
case 'b': // Prefix: "bypass-submit"
if l := len("bypass-submit"); len(elem) >= l && elem[0:l] == "bypass-submit" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionSubmissionBypassSubmitOperation
r.summary = "Role Reviewer changes status from ChangesRequested -> Submitted"
r.operationID = "actionSubmissionBypassSubmit"
r.pathPattern = "/submissions/{SubmissionID}/status/bypass-submit"
r.args = args
r.count = 1
return r, true
default:
return
}
}
case 'r': // Prefix: "re" case 'r': // Prefix: "re"
if l := len("re"); len(elem) >= l && elem[0:l] == "re" { if l := len("re"); len(elem) >= l && elem[0:l] == "re" {
@@ -2245,6 +2689,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
break break
} }
switch elem[0] { switch elem[0] {
case 's': // Prefix: "submitting"
if l := len("submitting"); len(elem) >= l && elem[0:l] == "submitting" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionSubmissionResetSubmittingOperation
r.summary = "Role Submitter manually resets submitting softlock and changes status from Submitting -> UnderConstruction"
r.operationID = "actionSubmissionResetSubmitting"
r.pathPattern = "/submissions/{SubmissionID}/status/reset-submitting"
r.args = args
r.count = 1
return r, true
default:
return
}
}
case 'u': // Prefix: "uploading" case 'u': // Prefix: "uploading"
if l := len("uploading"); len(elem) >= l && elem[0:l] == "uploading" { if l := len("uploading"); len(elem) >= l && elem[0:l] == "uploading" {
@@ -2345,30 +2813,6 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
} }
case 's': // Prefix: "submit"
if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionSubmissionSubmitOperation
r.summary = "Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted"
r.operationID = "actionSubmissionSubmit"
r.pathPattern = "/submissions/{SubmissionID}/status/submit"
r.args = args
r.count = 1
return r, true
default:
return
}
}
case 't': // Prefix: "trigger-" case 't': // Prefix: "trigger-"
if l := len("trigger-"); len(elem) >= l && elem[0:l] == "trigger-" { if l := len("trigger-"); len(elem) >= l && elem[0:l] == "trigger-" {
@@ -2381,6 +2825,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
break break
} }
switch elem[0] { switch elem[0] {
case 's': // Prefix: "submit"
if l := len("submit"); len(elem) >= l && elem[0:l] == "submit" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionSubmissionTriggerSubmitOperation
r.summary = "Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting"
r.operationID = "actionSubmissionTriggerSubmit"
r.pathPattern = "/submissions/{SubmissionID}/status/trigger-submit"
r.args = args
r.count = 1
return r, true
default:
return
}
}
case 'u': // Prefix: "upload" case 'u': // Prefix: "upload"
if l := len("upload"); len(elem) >= l && elem[0:l] == "upload" { if l := len("upload"); len(elem) >= l && elem[0:l] == "upload" {

View File

@@ -4,7 +4,10 @@ package api
import ( import (
"fmt" "fmt"
"io"
"time" "time"
"github.com/go-faster/jx"
) )
func (s *ErrorStatusCode) Error() string { func (s *ErrorStatusCode) Error() string {
@@ -14,20 +17,26 @@ func (s *ErrorStatusCode) Error() string {
// ActionMapfixAcceptedNoContent is response for ActionMapfixAccepted operation. // ActionMapfixAcceptedNoContent is response for ActionMapfixAccepted operation.
type ActionMapfixAcceptedNoContent struct{} type ActionMapfixAcceptedNoContent struct{}
// ActionMapfixBypassSubmitNoContent is response for ActionMapfixBypassSubmit operation.
type ActionMapfixBypassSubmitNoContent struct{}
// ActionMapfixRejectNoContent is response for ActionMapfixReject operation. // ActionMapfixRejectNoContent is response for ActionMapfixReject operation.
type ActionMapfixRejectNoContent struct{} type ActionMapfixRejectNoContent struct{}
// ActionMapfixRequestChangesNoContent is response for ActionMapfixRequestChanges operation. // ActionMapfixRequestChangesNoContent is response for ActionMapfixRequestChanges operation.
type ActionMapfixRequestChangesNoContent struct{} type ActionMapfixRequestChangesNoContent struct{}
// ActionMapfixResetSubmittingNoContent is response for ActionMapfixResetSubmitting operation.
type ActionMapfixResetSubmittingNoContent struct{}
// ActionMapfixRetryValidateNoContent is response for ActionMapfixRetryValidate operation. // ActionMapfixRetryValidateNoContent is response for ActionMapfixRetryValidate operation.
type ActionMapfixRetryValidateNoContent struct{} type ActionMapfixRetryValidateNoContent struct{}
// ActionMapfixRevokeNoContent is response for ActionMapfixRevoke operation. // ActionMapfixRevokeNoContent is response for ActionMapfixRevoke operation.
type ActionMapfixRevokeNoContent struct{} type ActionMapfixRevokeNoContent struct{}
// ActionMapfixSubmitNoContent is response for ActionMapfixSubmit operation. // ActionMapfixTriggerSubmitNoContent is response for ActionMapfixTriggerSubmit operation.
type ActionMapfixSubmitNoContent struct{} type ActionMapfixTriggerSubmitNoContent struct{}
// ActionMapfixTriggerUploadNoContent is response for ActionMapfixTriggerUpload operation. // ActionMapfixTriggerUploadNoContent is response for ActionMapfixTriggerUpload operation.
type ActionMapfixTriggerUploadNoContent struct{} type ActionMapfixTriggerUploadNoContent struct{}
@@ -41,20 +50,26 @@ type ActionMapfixValidatedNoContent struct{}
// ActionSubmissionAcceptedNoContent is response for ActionSubmissionAccepted operation. // ActionSubmissionAcceptedNoContent is response for ActionSubmissionAccepted operation.
type ActionSubmissionAcceptedNoContent struct{} type ActionSubmissionAcceptedNoContent struct{}
// ActionSubmissionBypassSubmitNoContent is response for ActionSubmissionBypassSubmit operation.
type ActionSubmissionBypassSubmitNoContent struct{}
// ActionSubmissionRejectNoContent is response for ActionSubmissionReject operation. // ActionSubmissionRejectNoContent is response for ActionSubmissionReject operation.
type ActionSubmissionRejectNoContent struct{} type ActionSubmissionRejectNoContent struct{}
// ActionSubmissionRequestChangesNoContent is response for ActionSubmissionRequestChanges operation. // ActionSubmissionRequestChangesNoContent is response for ActionSubmissionRequestChanges operation.
type ActionSubmissionRequestChangesNoContent struct{} type ActionSubmissionRequestChangesNoContent struct{}
// ActionSubmissionResetSubmittingNoContent is response for ActionSubmissionResetSubmitting operation.
type ActionSubmissionResetSubmittingNoContent struct{}
// ActionSubmissionRetryValidateNoContent is response for ActionSubmissionRetryValidate operation. // ActionSubmissionRetryValidateNoContent is response for ActionSubmissionRetryValidate operation.
type ActionSubmissionRetryValidateNoContent struct{} type ActionSubmissionRetryValidateNoContent struct{}
// ActionSubmissionRevokeNoContent is response for ActionSubmissionRevoke operation. // ActionSubmissionRevokeNoContent is response for ActionSubmissionRevoke operation.
type ActionSubmissionRevokeNoContent struct{} type ActionSubmissionRevokeNoContent struct{}
// ActionSubmissionSubmitNoContent is response for ActionSubmissionSubmit operation. // ActionSubmissionTriggerSubmitNoContent is response for ActionSubmissionTriggerSubmit operation.
type ActionSubmissionSubmitNoContent struct{} type ActionSubmissionTriggerSubmitNoContent struct{}
// ActionSubmissionTriggerUploadNoContent is response for ActionSubmissionTriggerUpload operation. // ActionSubmissionTriggerUploadNoContent is response for ActionSubmissionTriggerUpload operation.
type ActionSubmissionTriggerUploadNoContent struct{} type ActionSubmissionTriggerUploadNoContent struct{}
@@ -65,8 +80,115 @@ type ActionSubmissionTriggerValidateNoContent struct{}
// ActionSubmissionValidatedNoContent is response for ActionSubmissionValidated operation. // ActionSubmissionValidatedNoContent is response for ActionSubmissionValidated operation.
type ActionSubmissionValidatedNoContent struct{} type ActionSubmissionValidatedNoContent struct{}
// Ref: #/components/schemas/AuditEvent
type AuditEvent struct {
ID int64 `json:"ID"`
Date int64 `json:"Date"`
User int64 `json:"User"`
Username string `json:"Username"`
// Is this a submission or is it a mapfix.
ResourceType int32 `json:"ResourceType"`
ResourceID int64 `json:"ResourceID"`
EventType int32 `json:"EventType"`
// Arbitrary event data.
EventData AuditEventEventData `json:"EventData"`
}
// GetID returns the value of ID.
func (s *AuditEvent) GetID() int64 {
return s.ID
}
// GetDate returns the value of Date.
func (s *AuditEvent) GetDate() int64 {
return s.Date
}
// GetUser returns the value of User.
func (s *AuditEvent) GetUser() int64 {
return s.User
}
// GetUsername returns the value of Username.
func (s *AuditEvent) GetUsername() string {
return s.Username
}
// GetResourceType returns the value of ResourceType.
func (s *AuditEvent) GetResourceType() int32 {
return s.ResourceType
}
// GetResourceID returns the value of ResourceID.
func (s *AuditEvent) GetResourceID() int64 {
return s.ResourceID
}
// GetEventType returns the value of EventType.
func (s *AuditEvent) GetEventType() int32 {
return s.EventType
}
// GetEventData returns the value of EventData.
func (s *AuditEvent) GetEventData() AuditEventEventData {
return s.EventData
}
// SetID sets the value of ID.
func (s *AuditEvent) SetID(val int64) {
s.ID = val
}
// SetDate sets the value of Date.
func (s *AuditEvent) SetDate(val int64) {
s.Date = val
}
// SetUser sets the value of User.
func (s *AuditEvent) SetUser(val int64) {
s.User = val
}
// SetUsername sets the value of Username.
func (s *AuditEvent) SetUsername(val string) {
s.Username = val
}
// SetResourceType sets the value of ResourceType.
func (s *AuditEvent) SetResourceType(val int32) {
s.ResourceType = val
}
// SetResourceID sets the value of ResourceID.
func (s *AuditEvent) SetResourceID(val int64) {
s.ResourceID = val
}
// SetEventType sets the value of EventType.
func (s *AuditEvent) SetEventType(val int32) {
s.EventType = val
}
// SetEventData sets the value of EventData.
func (s *AuditEvent) SetEventData(val AuditEventEventData) {
s.EventData = val
}
// Arbitrary event data.
type AuditEventEventData map[string]jx.Raw
func (s *AuditEventEventData) init() AuditEventEventData {
m := *s
if m == nil {
m = map[string]jx.Raw{}
*s = m
}
return m
}
type CookieAuth struct { type CookieAuth struct {
APIKey string APIKey string
Roles []string
} }
// GetAPIKey returns the value of APIKey. // GetAPIKey returns the value of APIKey.
@@ -74,11 +196,55 @@ func (s *CookieAuth) GetAPIKey() string {
return s.APIKey return s.APIKey
} }
// GetRoles returns the value of Roles.
func (s *CookieAuth) GetRoles() []string {
return s.Roles
}
// SetAPIKey sets the value of APIKey. // SetAPIKey sets the value of APIKey.
func (s *CookieAuth) SetAPIKey(val string) { func (s *CookieAuth) SetAPIKey(val string) {
s.APIKey = val s.APIKey = val
} }
// SetRoles sets the value of Roles.
func (s *CookieAuth) SetRoles(val []string) {
s.Roles = val
}
// CreateMapfixAuditCommentNoContent is response for CreateMapfixAuditComment operation.
type CreateMapfixAuditCommentNoContent struct{}
type CreateMapfixAuditCommentReq struct {
Data io.Reader
}
// Read reads data from the Data reader.
//
// Kept to satisfy the io.Reader interface.
func (s CreateMapfixAuditCommentReq) Read(p []byte) (n int, err error) {
if s.Data == nil {
return 0, io.EOF
}
return s.Data.Read(p)
}
// CreateSubmissionAuditCommentNoContent is response for CreateSubmissionAuditComment operation.
type CreateSubmissionAuditCommentNoContent struct{}
type CreateSubmissionAuditCommentReq struct {
Data io.Reader
}
// Read reads data from the Data reader.
//
// Kept to satisfy the io.Reader interface.
func (s CreateSubmissionAuditCommentReq) Read(p []byte) (n int, err error) {
if s.Data == nil {
return 0, io.EOF
}
return s.Data.Read(p)
}
// DeleteScriptNoContent is response for DeleteScript operation. // DeleteScriptNoContent is response for DeleteScript operation.
type DeleteScriptNoContent struct{} type DeleteScriptNoContent struct{}
@@ -211,7 +377,7 @@ type Mapfix struct {
Completed bool `json:"Completed"` Completed bool `json:"Completed"`
TargetAssetID int64 `json:"TargetAssetID"` TargetAssetID int64 `json:"TargetAssetID"`
StatusID int32 `json:"StatusID"` StatusID int32 `json:"StatusID"`
StatusMessage string `json:"StatusMessage"` Description string `json:"Description"`
} }
// GetID returns the value of ID. // GetID returns the value of ID.
@@ -274,9 +440,9 @@ func (s *Mapfix) GetStatusID() int32 {
return s.StatusID return s.StatusID
} }
// GetStatusMessage returns the value of StatusMessage. // GetDescription returns the value of Description.
func (s *Mapfix) GetStatusMessage() string { func (s *Mapfix) GetDescription() string {
return s.StatusMessage return s.Description
} }
// SetID sets the value of ID. // SetID sets the value of ID.
@@ -339,15 +505,16 @@ func (s *Mapfix) SetStatusID(val int32) {
s.StatusID = val s.StatusID = val
} }
// SetStatusMessage sets the value of StatusMessage. // SetDescription sets the value of Description.
func (s *Mapfix) SetStatusMessage(val string) { func (s *Mapfix) SetDescription(val string) {
s.StatusMessage = val s.Description = val
} }
// Ref: #/components/schemas/MapfixTriggerCreate // Ref: #/components/schemas/MapfixTriggerCreate
type MapfixTriggerCreate struct { type MapfixTriggerCreate struct {
AssetID int64 `json:"AssetID"` AssetID int64 `json:"AssetID"`
TargetAssetID int64 `json:"TargetAssetID"` TargetAssetID int64 `json:"TargetAssetID"`
Description string `json:"Description"`
} }
// GetAssetID returns the value of AssetID. // GetAssetID returns the value of AssetID.
@@ -360,6 +527,11 @@ func (s *MapfixTriggerCreate) GetTargetAssetID() int64 {
return s.TargetAssetID return s.TargetAssetID
} }
// GetDescription returns the value of Description.
func (s *MapfixTriggerCreate) GetDescription() string {
return s.Description
}
// SetAssetID sets the value of AssetID. // SetAssetID sets the value of AssetID.
func (s *MapfixTriggerCreate) SetAssetID(val int64) { func (s *MapfixTriggerCreate) SetAssetID(val int64) {
s.AssetID = val s.AssetID = val
@@ -370,6 +542,37 @@ func (s *MapfixTriggerCreate) SetTargetAssetID(val int64) {
s.TargetAssetID = val s.TargetAssetID = val
} }
// SetDescription sets the value of Description.
func (s *MapfixTriggerCreate) SetDescription(val string) {
s.Description = val
}
// Ref: #/components/schemas/Mapfixes
type Mapfixes struct {
Total int64 `json:"Total"`
Mapfixes []Mapfix `json:"Mapfixes"`
}
// GetTotal returns the value of Total.
func (s *Mapfixes) GetTotal() int64 {
return s.Total
}
// GetMapfixes returns the value of Mapfixes.
func (s *Mapfixes) GetMapfixes() []Mapfix {
return s.Mapfixes
}
// SetTotal sets the value of Total.
func (s *Mapfixes) SetTotal(val int64) {
s.Total = val
}
// SetMapfixes sets the value of Mapfixes.
func (s *Mapfixes) SetMapfixes(val []Mapfix) {
s.Mapfixes = val
}
// Ref: #/components/schemas/Operation // Ref: #/components/schemas/Operation
type Operation struct { type Operation struct {
OperationID int32 `json:"OperationID"` OperationID int32 `json:"OperationID"`
@@ -999,7 +1202,6 @@ type Submission struct {
Completed bool `json:"Completed"` Completed bool `json:"Completed"`
UploadedAssetID OptInt64 `json:"UploadedAssetID"` UploadedAssetID OptInt64 `json:"UploadedAssetID"`
StatusID int32 `json:"StatusID"` StatusID int32 `json:"StatusID"`
StatusMessage string `json:"StatusMessage"`
} }
// GetID returns the value of ID. // GetID returns the value of ID.
@@ -1072,11 +1274,6 @@ func (s *Submission) GetStatusID() int32 {
return s.StatusID return s.StatusID
} }
// GetStatusMessage returns the value of StatusMessage.
func (s *Submission) GetStatusMessage() string {
return s.StatusMessage
}
// SetID sets the value of ID. // SetID sets the value of ID.
func (s *Submission) SetID(val int64) { func (s *Submission) SetID(val int64) {
s.ID = val s.ID = val
@@ -1147,14 +1344,12 @@ func (s *Submission) SetStatusID(val int32) {
s.StatusID = val s.StatusID = val
} }
// SetStatusMessage sets the value of StatusMessage.
func (s *Submission) SetStatusMessage(val string) {
s.StatusMessage = val
}
// Ref: #/components/schemas/SubmissionTriggerCreate // Ref: #/components/schemas/SubmissionTriggerCreate
type SubmissionTriggerCreate struct { type SubmissionTriggerCreate struct {
AssetID int64 `json:"AssetID"` AssetID int64 `json:"AssetID"`
DisplayName string `json:"DisplayName"`
Creator string `json:"Creator"`
GameID int32 `json:"GameID"`
} }
// GetAssetID returns the value of AssetID. // GetAssetID returns the value of AssetID.
@@ -1162,11 +1357,67 @@ func (s *SubmissionTriggerCreate) GetAssetID() int64 {
return s.AssetID return s.AssetID
} }
// GetDisplayName returns the value of DisplayName.
func (s *SubmissionTriggerCreate) GetDisplayName() string {
return s.DisplayName
}
// GetCreator returns the value of Creator.
func (s *SubmissionTriggerCreate) GetCreator() string {
return s.Creator
}
// GetGameID returns the value of GameID.
func (s *SubmissionTriggerCreate) GetGameID() int32 {
return s.GameID
}
// SetAssetID sets the value of AssetID. // SetAssetID sets the value of AssetID.
func (s *SubmissionTriggerCreate) SetAssetID(val int64) { func (s *SubmissionTriggerCreate) SetAssetID(val int64) {
s.AssetID = val s.AssetID = val
} }
// SetDisplayName sets the value of DisplayName.
func (s *SubmissionTriggerCreate) SetDisplayName(val string) {
s.DisplayName = val
}
// SetCreator sets the value of Creator.
func (s *SubmissionTriggerCreate) SetCreator(val string) {
s.Creator = val
}
// SetGameID sets the value of GameID.
func (s *SubmissionTriggerCreate) SetGameID(val int32) {
s.GameID = val
}
// Ref: #/components/schemas/Submissions
type Submissions struct {
Total int64 `json:"Total"`
Submissions []Submission `json:"Submissions"`
}
// GetTotal returns the value of Total.
func (s *Submissions) GetTotal() int64 {
return s.Total
}
// GetSubmissions returns the value of Submissions.
func (s *Submissions) GetSubmissions() []Submission {
return s.Submissions
}
// SetTotal sets the value of Total.
func (s *Submissions) SetTotal(val int64) {
s.Total = val
}
// SetSubmissions sets the value of Submissions.
func (s *Submissions) SetSubmissions(val []Submission) {
s.Submissions = val
}
// UpdateMapfixModelNoContent is response for UpdateMapfixModel operation. // UpdateMapfixModelNoContent is response for UpdateMapfixModel operation.
type UpdateMapfixModelNoContent struct{} type UpdateMapfixModelNoContent struct{}

View File

@@ -33,6 +33,51 @@ func findAuthorization(h http.Header, prefix string) (string, bool) {
return "", false return "", false
} }
var operationRolesCookieAuth = map[string][]string{
ActionMapfixAcceptedOperation: []string{},
ActionMapfixBypassSubmitOperation: []string{},
ActionMapfixRejectOperation: []string{},
ActionMapfixRequestChangesOperation: []string{},
ActionMapfixResetSubmittingOperation: []string{},
ActionMapfixRetryValidateOperation: []string{},
ActionMapfixRevokeOperation: []string{},
ActionMapfixTriggerSubmitOperation: []string{},
ActionMapfixTriggerUploadOperation: []string{},
ActionMapfixTriggerValidateOperation: []string{},
ActionMapfixValidatedOperation: []string{},
ActionSubmissionAcceptedOperation: []string{},
ActionSubmissionBypassSubmitOperation: []string{},
ActionSubmissionRejectOperation: []string{},
ActionSubmissionRequestChangesOperation: []string{},
ActionSubmissionResetSubmittingOperation: []string{},
ActionSubmissionRetryValidateOperation: []string{},
ActionSubmissionRevokeOperation: []string{},
ActionSubmissionTriggerSubmitOperation: []string{},
ActionSubmissionTriggerUploadOperation: []string{},
ActionSubmissionTriggerValidateOperation: []string{},
ActionSubmissionValidatedOperation: []string{},
CreateMapfixOperation: []string{},
CreateMapfixAuditCommentOperation: []string{},
CreateScriptOperation: []string{},
CreateScriptPolicyOperation: []string{},
CreateSubmissionOperation: []string{},
CreateSubmissionAdminOperation: []string{},
CreateSubmissionAuditCommentOperation: []string{},
DeleteScriptOperation: []string{},
DeleteScriptPolicyOperation: []string{},
GetOperationOperation: []string{},
ReleaseSubmissionsOperation: []string{},
SessionRolesOperation: []string{},
SessionUserOperation: []string{},
SessionValidateOperation: []string{},
SetMapfixCompletedOperation: []string{},
SetSubmissionCompletedOperation: []string{},
UpdateMapfixModelOperation: []string{},
UpdateScriptOperation: []string{},
UpdateScriptPolicyOperation: []string{},
UpdateSubmissionModelOperation: []string{},
}
func (s *Server) securityCookieAuth(ctx context.Context, operationName OperationName, req *http.Request) (context.Context, bool, error) { func (s *Server) securityCookieAuth(ctx context.Context, operationName OperationName, req *http.Request) (context.Context, bool, error) {
var t CookieAuth var t CookieAuth
const parameterName = "session_id" const parameterName = "session_id"
@@ -46,6 +91,7 @@ func (s *Server) securityCookieAuth(ctx context.Context, operationName Operation
return nil, false, errors.Wrap(err, "get cookie value") return nil, false, errors.Wrap(err, "get cookie value")
} }
t.APIKey = value t.APIKey = value
t.Roles = operationRolesCookieAuth[operationName]
rctx, err := s.sec.HandleCookieAuth(ctx, operationName, t) rctx, err := s.sec.HandleCookieAuth(ctx, operationName, t)
if errors.Is(err, ogenerrors.ErrSkipServerSecurity) { if errors.Is(err, ogenerrors.ErrSkipServerSecurity) {
return nil, false, nil return nil, false, nil

View File

@@ -14,6 +14,12 @@ type Handler interface {
// //
// POST /mapfixes/{MapfixID}/status/reset-validating // POST /mapfixes/{MapfixID}/status/reset-validating
ActionMapfixAccepted(ctx context.Context, params ActionMapfixAcceptedParams) error ActionMapfixAccepted(ctx context.Context, params ActionMapfixAcceptedParams) error
// ActionMapfixBypassSubmit implements actionMapfixBypassSubmit operation.
//
// Role Reviewer changes status from ChangesRequested -> Submitted.
//
// POST /mapfixes/{MapfixID}/status/bypass-submit
ActionMapfixBypassSubmit(ctx context.Context, params ActionMapfixBypassSubmitParams) error
// ActionMapfixReject implements actionMapfixReject operation. // ActionMapfixReject implements actionMapfixReject operation.
// //
// Role Reviewer changes status from Submitted -> Rejected. // Role Reviewer changes status from Submitted -> Rejected.
@@ -26,6 +32,13 @@ type Handler interface {
// //
// POST /mapfixes/{MapfixID}/status/request-changes // POST /mapfixes/{MapfixID}/status/request-changes
ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error
// ActionMapfixResetSubmitting implements actionMapfixResetSubmitting operation.
//
// Role Submitter manually resets submitting softlock and changes status from Submitting ->
// UnderConstruction.
//
// POST /mapfixes/{MapfixID}/status/reset-submitting
ActionMapfixResetSubmitting(ctx context.Context, params ActionMapfixResetSubmittingParams) error
// ActionMapfixRetryValidate implements actionMapfixRetryValidate operation. // ActionMapfixRetryValidate implements actionMapfixRetryValidate operation.
// //
// Role Reviewer re-runs validation and changes status from Accepted -> Validating. // Role Reviewer re-runs validation and changes status from Accepted -> Validating.
@@ -38,12 +51,12 @@ type Handler interface {
// //
// POST /mapfixes/{MapfixID}/status/revoke // POST /mapfixes/{MapfixID}/status/revoke
ActionMapfixRevoke(ctx context.Context, params ActionMapfixRevokeParams) error ActionMapfixRevoke(ctx context.Context, params ActionMapfixRevokeParams) error
// ActionMapfixSubmit implements actionMapfixSubmit operation. // ActionMapfixTriggerSubmit implements actionMapfixTriggerSubmit operation.
// //
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted. // Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting.
// //
// POST /mapfixes/{MapfixID}/status/submit // POST /mapfixes/{MapfixID}/status/trigger-submit
ActionMapfixSubmit(ctx context.Context, params ActionMapfixSubmitParams) error ActionMapfixTriggerSubmit(ctx context.Context, params ActionMapfixTriggerSubmitParams) error
// ActionMapfixTriggerUpload implements actionMapfixTriggerUpload operation. // ActionMapfixTriggerUpload implements actionMapfixTriggerUpload operation.
// //
// Role Admin changes status from Validated -> Uploading. // Role Admin changes status from Validated -> Uploading.
@@ -68,6 +81,12 @@ type Handler interface {
// //
// POST /submissions/{SubmissionID}/status/reset-validating // POST /submissions/{SubmissionID}/status/reset-validating
ActionSubmissionAccepted(ctx context.Context, params ActionSubmissionAcceptedParams) error ActionSubmissionAccepted(ctx context.Context, params ActionSubmissionAcceptedParams) error
// ActionSubmissionBypassSubmit implements actionSubmissionBypassSubmit operation.
//
// Role Reviewer changes status from ChangesRequested -> Submitted.
//
// POST /submissions/{SubmissionID}/status/bypass-submit
ActionSubmissionBypassSubmit(ctx context.Context, params ActionSubmissionBypassSubmitParams) error
// ActionSubmissionReject implements actionSubmissionReject operation. // ActionSubmissionReject implements actionSubmissionReject operation.
// //
// Role Reviewer changes status from Submitted -> Rejected. // Role Reviewer changes status from Submitted -> Rejected.
@@ -80,6 +99,13 @@ type Handler interface {
// //
// POST /submissions/{SubmissionID}/status/request-changes // POST /submissions/{SubmissionID}/status/request-changes
ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error
// ActionSubmissionResetSubmitting implements actionSubmissionResetSubmitting operation.
//
// Role Submitter manually resets submitting softlock and changes status from Submitting ->
// UnderConstruction.
//
// POST /submissions/{SubmissionID}/status/reset-submitting
ActionSubmissionResetSubmitting(ctx context.Context, params ActionSubmissionResetSubmittingParams) error
// ActionSubmissionRetryValidate implements actionSubmissionRetryValidate operation. // ActionSubmissionRetryValidate implements actionSubmissionRetryValidate operation.
// //
// Role Reviewer re-runs validation and changes status from Accepted -> Validating. // Role Reviewer re-runs validation and changes status from Accepted -> Validating.
@@ -92,12 +118,12 @@ type Handler interface {
// //
// POST /submissions/{SubmissionID}/status/revoke // POST /submissions/{SubmissionID}/status/revoke
ActionSubmissionRevoke(ctx context.Context, params ActionSubmissionRevokeParams) error ActionSubmissionRevoke(ctx context.Context, params ActionSubmissionRevokeParams) error
// ActionSubmissionSubmit implements actionSubmissionSubmit operation. // ActionSubmissionTriggerSubmit implements actionSubmissionTriggerSubmit operation.
// //
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted. // Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting.
// //
// POST /submissions/{SubmissionID}/status/submit // POST /submissions/{SubmissionID}/status/trigger-submit
ActionSubmissionSubmit(ctx context.Context, params ActionSubmissionSubmitParams) error ActionSubmissionTriggerSubmit(ctx context.Context, params ActionSubmissionTriggerSubmitParams) error
// ActionSubmissionTriggerUpload implements actionSubmissionTriggerUpload operation. // ActionSubmissionTriggerUpload implements actionSubmissionTriggerUpload operation.
// //
// Role Admin changes status from Validated -> Uploading. // Role Admin changes status from Validated -> Uploading.
@@ -122,6 +148,12 @@ type Handler interface {
// //
// POST /mapfixes // POST /mapfixes
CreateMapfix(ctx context.Context, req *MapfixTriggerCreate) (*OperationID, error) CreateMapfix(ctx context.Context, req *MapfixTriggerCreate) (*OperationID, error)
// CreateMapfixAuditComment implements createMapfixAuditComment operation.
//
// Post a comment to the audit log.
//
// POST /mapfixes/{MapfixID}/comment
CreateMapfixAuditComment(ctx context.Context, req CreateMapfixAuditCommentReq, params CreateMapfixAuditCommentParams) error
// CreateScript implements createScript operation. // CreateScript implements createScript operation.
// //
// Create a new script. // Create a new script.
@@ -140,6 +172,18 @@ type Handler interface {
// //
// POST /submissions // POST /submissions
CreateSubmission(ctx context.Context, req *SubmissionTriggerCreate) (*OperationID, error) CreateSubmission(ctx context.Context, req *SubmissionTriggerCreate) (*OperationID, error)
// CreateSubmissionAdmin implements createSubmissionAdmin operation.
//
// Trigger the validator to create a new submission.
//
// POST /submissions-admin
CreateSubmissionAdmin(ctx context.Context, req *SubmissionTriggerCreate) (*OperationID, error)
// CreateSubmissionAuditComment implements createSubmissionAuditComment operation.
//
// Post a comment to the audit log.
//
// POST /submissions/{SubmissionID}/comment
CreateSubmissionAuditComment(ctx context.Context, req CreateSubmissionAuditCommentReq, params CreateSubmissionAuditCommentParams) error
// DeleteScript implements deleteScript operation. // DeleteScript implements deleteScript operation.
// //
// Delete the specified script by ID. // Delete the specified script by ID.
@@ -188,12 +232,18 @@ type Handler interface {
// //
// GET /submissions/{SubmissionID} // GET /submissions/{SubmissionID}
GetSubmission(ctx context.Context, params GetSubmissionParams) (*Submission, error) GetSubmission(ctx context.Context, params GetSubmissionParams) (*Submission, error)
// ListMapfixAuditEvents implements listMapfixAuditEvents operation.
//
// Retrieve a list of audit events.
//
// GET /mapfixes/{MapfixID}/audit-events
ListMapfixAuditEvents(ctx context.Context, params ListMapfixAuditEventsParams) ([]AuditEvent, error)
// ListMapfixes implements listMapfixes operation. // ListMapfixes implements listMapfixes operation.
// //
// Get list of mapfixes. // Get list of mapfixes.
// //
// GET /mapfixes // GET /mapfixes
ListMapfixes(ctx context.Context, params ListMapfixesParams) ([]Mapfix, error) ListMapfixes(ctx context.Context, params ListMapfixesParams) (*Mapfixes, error)
// ListMaps implements listMaps operation. // ListMaps implements listMaps operation.
// //
// Get list of maps. // Get list of maps.
@@ -212,12 +262,18 @@ type Handler interface {
// //
// GET /scripts // GET /scripts
ListScripts(ctx context.Context, params ListScriptsParams) ([]Script, error) ListScripts(ctx context.Context, params ListScriptsParams) ([]Script, error)
// ListSubmissionAuditEvents implements listSubmissionAuditEvents operation.
//
// Retrieve a list of audit events.
//
// GET /submissions/{SubmissionID}/audit-events
ListSubmissionAuditEvents(ctx context.Context, params ListSubmissionAuditEventsParams) ([]AuditEvent, error)
// ListSubmissions implements listSubmissions operation. // ListSubmissions implements listSubmissions operation.
// //
// Get list of submissions. // Get list of submissions.
// //
// GET /submissions // GET /submissions
ListSubmissions(ctx context.Context, params ListSubmissionsParams) ([]Submission, error) ListSubmissions(ctx context.Context, params ListSubmissionsParams) (*Submissions, error)
// ReleaseSubmissions implements releaseSubmissions operation. // ReleaseSubmissions implements releaseSubmissions operation.
// //
// Release a set of uploaded maps. // Release a set of uploaded maps.

View File

@@ -22,6 +22,15 @@ func (UnimplementedHandler) ActionMapfixAccepted(ctx context.Context, params Act
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
// ActionMapfixBypassSubmit implements actionMapfixBypassSubmit operation.
//
// Role Reviewer changes status from ChangesRequested -> Submitted.
//
// POST /mapfixes/{MapfixID}/status/bypass-submit
func (UnimplementedHandler) ActionMapfixBypassSubmit(ctx context.Context, params ActionMapfixBypassSubmitParams) error {
return ht.ErrNotImplemented
}
// ActionMapfixReject implements actionMapfixReject operation. // ActionMapfixReject implements actionMapfixReject operation.
// //
// Role Reviewer changes status from Submitted -> Rejected. // Role Reviewer changes status from Submitted -> Rejected.
@@ -40,6 +49,16 @@ func (UnimplementedHandler) ActionMapfixRequestChanges(ctx context.Context, para
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
// ActionMapfixResetSubmitting implements actionMapfixResetSubmitting operation.
//
// Role Submitter manually resets submitting softlock and changes status from Submitting ->
// UnderConstruction.
//
// POST /mapfixes/{MapfixID}/status/reset-submitting
func (UnimplementedHandler) ActionMapfixResetSubmitting(ctx context.Context, params ActionMapfixResetSubmittingParams) error {
return ht.ErrNotImplemented
}
// ActionMapfixRetryValidate implements actionMapfixRetryValidate operation. // ActionMapfixRetryValidate implements actionMapfixRetryValidate operation.
// //
// Role Reviewer re-runs validation and changes status from Accepted -> Validating. // Role Reviewer re-runs validation and changes status from Accepted -> Validating.
@@ -58,12 +77,12 @@ func (UnimplementedHandler) ActionMapfixRevoke(ctx context.Context, params Actio
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
// ActionMapfixSubmit implements actionMapfixSubmit operation. // ActionMapfixTriggerSubmit implements actionMapfixTriggerSubmit operation.
// //
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted. // Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting.
// //
// POST /mapfixes/{MapfixID}/status/submit // POST /mapfixes/{MapfixID}/status/trigger-submit
func (UnimplementedHandler) ActionMapfixSubmit(ctx context.Context, params ActionMapfixSubmitParams) error { func (UnimplementedHandler) ActionMapfixTriggerSubmit(ctx context.Context, params ActionMapfixTriggerSubmitParams) error {
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
@@ -103,6 +122,15 @@ func (UnimplementedHandler) ActionSubmissionAccepted(ctx context.Context, params
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
// ActionSubmissionBypassSubmit implements actionSubmissionBypassSubmit operation.
//
// Role Reviewer changes status from ChangesRequested -> Submitted.
//
// POST /submissions/{SubmissionID}/status/bypass-submit
func (UnimplementedHandler) ActionSubmissionBypassSubmit(ctx context.Context, params ActionSubmissionBypassSubmitParams) error {
return ht.ErrNotImplemented
}
// ActionSubmissionReject implements actionSubmissionReject operation. // ActionSubmissionReject implements actionSubmissionReject operation.
// //
// Role Reviewer changes status from Submitted -> Rejected. // Role Reviewer changes status from Submitted -> Rejected.
@@ -121,6 +149,16 @@ func (UnimplementedHandler) ActionSubmissionRequestChanges(ctx context.Context,
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
// ActionSubmissionResetSubmitting implements actionSubmissionResetSubmitting operation.
//
// Role Submitter manually resets submitting softlock and changes status from Submitting ->
// UnderConstruction.
//
// POST /submissions/{SubmissionID}/status/reset-submitting
func (UnimplementedHandler) ActionSubmissionResetSubmitting(ctx context.Context, params ActionSubmissionResetSubmittingParams) error {
return ht.ErrNotImplemented
}
// ActionSubmissionRetryValidate implements actionSubmissionRetryValidate operation. // ActionSubmissionRetryValidate implements actionSubmissionRetryValidate operation.
// //
// Role Reviewer re-runs validation and changes status from Accepted -> Validating. // Role Reviewer re-runs validation and changes status from Accepted -> Validating.
@@ -139,12 +177,12 @@ func (UnimplementedHandler) ActionSubmissionRevoke(ctx context.Context, params A
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
// ActionSubmissionSubmit implements actionSubmissionSubmit operation. // ActionSubmissionTriggerSubmit implements actionSubmissionTriggerSubmit operation.
// //
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted. // Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting.
// //
// POST /submissions/{SubmissionID}/status/submit // POST /submissions/{SubmissionID}/status/trigger-submit
func (UnimplementedHandler) ActionSubmissionSubmit(ctx context.Context, params ActionSubmissionSubmitParams) error { func (UnimplementedHandler) ActionSubmissionTriggerSubmit(ctx context.Context, params ActionSubmissionTriggerSubmitParams) error {
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
@@ -184,6 +222,15 @@ func (UnimplementedHandler) CreateMapfix(ctx context.Context, req *MapfixTrigger
return r, ht.ErrNotImplemented return r, ht.ErrNotImplemented
} }
// CreateMapfixAuditComment implements createMapfixAuditComment operation.
//
// Post a comment to the audit log.
//
// POST /mapfixes/{MapfixID}/comment
func (UnimplementedHandler) CreateMapfixAuditComment(ctx context.Context, req CreateMapfixAuditCommentReq, params CreateMapfixAuditCommentParams) error {
return ht.ErrNotImplemented
}
// CreateScript implements createScript operation. // CreateScript implements createScript operation.
// //
// Create a new script. // Create a new script.
@@ -211,6 +258,24 @@ func (UnimplementedHandler) CreateSubmission(ctx context.Context, req *Submissio
return r, ht.ErrNotImplemented return r, ht.ErrNotImplemented
} }
// CreateSubmissionAdmin implements createSubmissionAdmin operation.
//
// Trigger the validator to create a new submission.
//
// POST /submissions-admin
func (UnimplementedHandler) CreateSubmissionAdmin(ctx context.Context, req *SubmissionTriggerCreate) (r *OperationID, _ error) {
return r, ht.ErrNotImplemented
}
// CreateSubmissionAuditComment implements createSubmissionAuditComment operation.
//
// Post a comment to the audit log.
//
// POST /submissions/{SubmissionID}/comment
func (UnimplementedHandler) CreateSubmissionAuditComment(ctx context.Context, req CreateSubmissionAuditCommentReq, params CreateSubmissionAuditCommentParams) error {
return ht.ErrNotImplemented
}
// DeleteScript implements deleteScript operation. // DeleteScript implements deleteScript operation.
// //
// Delete the specified script by ID. // Delete the specified script by ID.
@@ -283,12 +348,21 @@ func (UnimplementedHandler) GetSubmission(ctx context.Context, params GetSubmiss
return r, ht.ErrNotImplemented return r, ht.ErrNotImplemented
} }
// ListMapfixAuditEvents implements listMapfixAuditEvents operation.
//
// Retrieve a list of audit events.
//
// GET /mapfixes/{MapfixID}/audit-events
func (UnimplementedHandler) ListMapfixAuditEvents(ctx context.Context, params ListMapfixAuditEventsParams) (r []AuditEvent, _ error) {
return r, ht.ErrNotImplemented
}
// ListMapfixes implements listMapfixes operation. // ListMapfixes implements listMapfixes operation.
// //
// Get list of mapfixes. // Get list of mapfixes.
// //
// GET /mapfixes // GET /mapfixes
func (UnimplementedHandler) ListMapfixes(ctx context.Context, params ListMapfixesParams) (r []Mapfix, _ error) { func (UnimplementedHandler) ListMapfixes(ctx context.Context, params ListMapfixesParams) (r *Mapfixes, _ error) {
return r, ht.ErrNotImplemented return r, ht.ErrNotImplemented
} }
@@ -319,12 +393,21 @@ func (UnimplementedHandler) ListScripts(ctx context.Context, params ListScriptsP
return r, ht.ErrNotImplemented return r, ht.ErrNotImplemented
} }
// ListSubmissionAuditEvents implements listSubmissionAuditEvents operation.
//
// Retrieve a list of audit events.
//
// GET /submissions/{SubmissionID}/audit-events
func (UnimplementedHandler) ListSubmissionAuditEvents(ctx context.Context, params ListSubmissionAuditEventsParams) (r []AuditEvent, _ error) {
return r, ht.ErrNotImplemented
}
// ListSubmissions implements listSubmissions operation. // ListSubmissions implements listSubmissions operation.
// //
// Get list of submissions. // Get list of submissions.
// //
// GET /submissions // GET /submissions
func (UnimplementedHandler) ListSubmissions(ctx context.Context, params ListSubmissionsParams) (r []Submission, _ error) { func (UnimplementedHandler) ListSubmissions(ctx context.Context, params ListSubmissionsParams) (r *Submissions, _ error) {
return r, ht.ErrNotImplemented return r, ht.ErrNotImplemented
} }

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,7 @@ import (
"git.itzana.me/strafesnet/go-grpc/auth" "git.itzana.me/strafesnet/go-grpc/auth"
"git.itzana.me/strafesnet/go-grpc/maps" "git.itzana.me/strafesnet/go-grpc/maps"
"git.itzana.me/strafesnet/go-grpc/users"
"git.itzana.me/strafesnet/maps-service/pkg/api" "git.itzana.me/strafesnet/maps-service/pkg/api"
"git.itzana.me/strafesnet/maps-service/pkg/datastore/gormstore" "git.itzana.me/strafesnet/maps-service/pkg/datastore/gormstore"
internal "git.itzana.me/strafesnet/maps-service/pkg/internal" internal "git.itzana.me/strafesnet/maps-service/pkg/internal"
@@ -125,7 +126,8 @@ func serve(ctx *cli.Context) error {
svc := &service.Service{ svc := &service.Service{
DB: db, DB: db,
Nats: js, Nats: js,
Client: maps.NewMapsServiceClient(conn), Maps: maps.NewMapsServiceClient(conn),
Users: users.NewUsersServiceClient(conn),
} }
conn, err = grpc.Dial(ctx.String("auth-rpc-host"), grpc.WithTransportCredentials(insecure.NewCredentials())) conn, err = grpc.Dial(ctx.String("auth-rpc-host"), grpc.WithTransportCredentials(insecure.NewCredentials()))

View File

@@ -3,6 +3,8 @@ package datastore
import ( import (
"context" "context"
"errors" "errors"
"time"
"git.itzana.me/strafesnet/maps-service/pkg/model" "git.itzana.me/strafesnet/maps-service/pkg/model"
) )
@@ -22,6 +24,7 @@ const (
) )
type Datastore interface { type Datastore interface {
AuditEvents() AuditEvents
Mapfixes() Mapfixes Mapfixes() Mapfixes
Operations() Operations Operations() Operations
Submissions() Submissions Submissions() Submissions
@@ -29,6 +32,14 @@ type Datastore interface {
ScriptPolicy() ScriptPolicy ScriptPolicy() ScriptPolicy
} }
type AuditEvents interface {
Get(ctx context.Context, id int64) (model.AuditEvent, error)
Create(ctx context.Context, smap model.AuditEvent) (model.AuditEvent, error)
Update(ctx context.Context, id int64, values OptionalMap) error
Delete(ctx context.Context, id int64) error
List(ctx context.Context, filters OptionalMap, page model.Page) ([]model.AuditEvent, error)
}
type Mapfixes interface { type Mapfixes interface {
Get(ctx context.Context, id int64) (model.Mapfix, error) Get(ctx context.Context, id int64) (model.Mapfix, error)
GetList(ctx context.Context, id []int64) ([]model.Mapfix, error) GetList(ctx context.Context, id []int64) ([]model.Mapfix, error)
@@ -38,6 +49,7 @@ type Mapfixes interface {
IfStatusThenUpdateAndGet(ctx context.Context, id int64, statuses []model.MapfixStatus, values OptionalMap) (model.Mapfix, error) IfStatusThenUpdateAndGet(ctx context.Context, id int64, statuses []model.MapfixStatus, values OptionalMap) (model.Mapfix, error)
Delete(ctx context.Context, id int64) error Delete(ctx context.Context, id int64) error
List(ctx context.Context, filters OptionalMap, page model.Page, sort ListSort) ([]model.Mapfix, 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)
} }
type Operations interface { type Operations interface {
@@ -45,6 +57,7 @@ type Operations interface {
Create(ctx context.Context, smap model.Operation) (model.Operation, error) Create(ctx context.Context, smap model.Operation) (model.Operation, error)
Update(ctx context.Context, id int32, values OptionalMap) error Update(ctx context.Context, id int32, values OptionalMap) error
Delete(ctx context.Context, id int32) error Delete(ctx context.Context, id int32) error
CountSince(ctx context.Context, owner int64, since time.Time) (int64, error)
} }
type Submissions interface { type Submissions interface {
@@ -56,6 +69,7 @@ type Submissions interface {
IfStatusThenUpdateAndGet(ctx context.Context, id int64, statuses []model.SubmissionStatus, values OptionalMap) (model.Submission, error) IfStatusThenUpdateAndGet(ctx context.Context, id int64, statuses []model.SubmissionStatus, values OptionalMap) (model.Submission, error)
Delete(ctx context.Context, id int64) error Delete(ctx context.Context, id int64) error
List(ctx context.Context, filters OptionalMap, page model.Page, sort ListSort) ([]model.Submission, error) List(ctx context.Context, filters OptionalMap, page model.Page, sort ListSort) ([]model.Submission, error)
ListWithTotal(ctx context.Context, filters OptionalMap, page model.Page, sort ListSort) (int64, []model.Submission, error)
} }
type Scripts interface { type Scripts interface {

View File

@@ -0,0 +1,64 @@
package gormstore
import (
"context"
"errors"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
"git.itzana.me/strafesnet/maps-service/pkg/model"
"gorm.io/gorm"
)
type AuditEvents struct {
db *gorm.DB
}
func (env *AuditEvents) Get(ctx context.Context, id int64) (model.AuditEvent, error) {
var mdl model.AuditEvent
if err := env.db.First(&mdl, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return mdl, datastore.ErrNotExist
}
return mdl, err
}
return mdl, nil
}
func (env *AuditEvents) Create(ctx context.Context, smap model.AuditEvent) (model.AuditEvent, error) {
if err := env.db.Create(&smap).Error; err != nil {
return smap, err
}
return smap, nil
}
func (env *AuditEvents) Update(ctx context.Context, id int64, values datastore.OptionalMap) error {
if err := env.db.Model(&model.AuditEvent{}).Where("id = ?", id).Updates(values.Map()).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return datastore.ErrNotExist
}
return err
}
return nil
}
func (env *AuditEvents) Delete(ctx context.Context, id int64) error {
if err := env.db.Delete(&model.AuditEvent{}, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return datastore.ErrNotExist
}
return err
}
return nil
}
func (env *AuditEvents) List(ctx context.Context, filters datastore.OptionalMap, page model.Page) ([]model.AuditEvent, error) {
var events []model.AuditEvent
if err := env.db.Where(filters.Map()).Offset(int((page.Number - 1) * page.Size)).Limit(int(page.Size)).Find(&events).Error; err != nil {
return nil, err
}
return events, nil
}

View File

@@ -31,6 +31,7 @@ func New(ctx *cli.Context) (datastore.Datastore, error) {
if ctx.Bool("migrate") { if ctx.Bool("migrate") {
if err := db.AutoMigrate( if err := db.AutoMigrate(
&model.AuditEvent{},
&model.Mapfix{}, &model.Mapfix{},
&model.Operation{}, &model.Operation{},
&model.Submission{}, &model.Submission{},

View File

@@ -9,6 +9,10 @@ type Gormstore struct {
db *gorm.DB db *gorm.DB
} }
func (g Gormstore) AuditEvents() datastore.AuditEvents {
return &AuditEvents{db: g.db}
}
func (g Gormstore) Mapfixes() datastore.Mapfixes { func (g Gormstore) Mapfixes() datastore.Mapfixes {
return &Mapfixes{db: g.db} return &Mapfixes{db: g.db}
} }

View File

@@ -130,3 +130,19 @@ func (env *Mapfixes) List(ctx context.Context, filters datastore.OptionalMap, pa
return maps, nil return maps, nil
} }
func (env *Mapfixes) ListWithTotal(ctx context.Context, filters datastore.OptionalMap, page model.Page, sort datastore.ListSort) (int64, []model.Mapfix, error) {
// grab page items
maps, err := env.List(ctx, filters, page, sort)
if err != nil{
return 0, nil, err
}
// count total with filters
var total int64
if err := env.db.Model(&model.Mapfix{}).Where(filters.Map()).Count(&total).Error; err != nil {
return 0, nil, err
}
return total, maps, nil
}

View File

@@ -3,6 +3,7 @@ package gormstore
import ( import (
"context" "context"
"errors" "errors"
"time"
"git.itzana.me/strafesnet/maps-service/pkg/datastore" "git.itzana.me/strafesnet/maps-service/pkg/datastore"
"git.itzana.me/strafesnet/maps-service/pkg/model" "git.itzana.me/strafesnet/maps-service/pkg/model"
@@ -53,3 +54,12 @@ func (env *Operations) Delete(ctx context.Context, id int32) error {
return nil return nil
} }
func (env *Operations) CountSince(ctx context.Context, owner int64, since time.Time) (int64, error) {
var count int64
if err := env.db.Model(&model.Operation{}).Where("owner = ? AND created_at > ?",owner,since).Count(&count).Error; err != nil {
return count, err
}
return count, nil
}

View File

@@ -130,3 +130,19 @@ func (env *Submissions) List(ctx context.Context, filters datastore.OptionalMap,
return maps, nil return maps, nil
} }
func (env *Submissions) ListWithTotal(ctx context.Context, filters datastore.OptionalMap, page model.Page, sort datastore.ListSort) (int64, []model.Submission, error) {
// grab page items
maps, err := env.List(ctx, filters, page, sort)
if err != nil{
return 0, nil, err
}
// count total with filters
var total int64
if err := env.db.Model(&model.Submission{}).Where(filters.Map()).Count(&total).Error; err != nil {
return 0, nil, err
}
return total, maps, nil
}

View File

@@ -34,6 +34,18 @@ type Invoker interface {
// //
// POST /mapfixes/{MapfixID}/status/validator-failed // POST /mapfixes/{MapfixID}/status/validator-failed
ActionMapfixAccepted(ctx context.Context, params ActionMapfixAcceptedParams) error ActionMapfixAccepted(ctx context.Context, params ActionMapfixAcceptedParams) error
// ActionMapfixRequestChanges invokes actionMapfixRequestChanges operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested.
//
// POST /mapfixes/{MapfixID}/status/validator-request-changes
ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error
// ActionMapfixSubmitted invokes actionMapfixSubmitted operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> Submitted.
//
// POST /mapfixes/{MapfixID}/status/validator-submitted
ActionMapfixSubmitted(ctx context.Context, params ActionMapfixSubmittedParams) error
// ActionMapfixUploaded invokes actionMapfixUploaded operation. // ActionMapfixUploaded invokes actionMapfixUploaded operation.
// //
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.
@@ -58,6 +70,18 @@ type Invoker interface {
// //
// POST /submissions/{SubmissionID}/status/validator-failed // POST /submissions/{SubmissionID}/status/validator-failed
ActionSubmissionAccepted(ctx context.Context, params ActionSubmissionAcceptedParams) error ActionSubmissionAccepted(ctx context.Context, params ActionSubmissionAcceptedParams) error
// ActionSubmissionRequestChanges invokes actionSubmissionRequestChanges operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested.
//
// POST /submissions/{SubmissionID}/status/validator-request-changes
ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error
// ActionSubmissionSubmitted invokes actionSubmissionSubmitted operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> Submitted.
//
// POST /submissions/{SubmissionID}/status/validator-submitted
ActionSubmissionSubmitted(ctx context.Context, params ActionSubmissionSubmittedParams) error
// ActionSubmissionUploaded invokes actionSubmissionUploaded operation. // ActionSubmissionUploaded invokes actionSubmissionUploaded operation.
// //
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.
@@ -245,15 +269,15 @@ func (c *Client) sendActionMapfixAccepted(ctx context.Context, params ActionMapf
stage = "EncodeQueryParams" stage = "EncodeQueryParams"
q := uri.NewQueryEncoder() q := uri.NewQueryEncoder()
{ {
// Encode "StatusMessage" parameter. // Encode "ErrorMessage" parameter.
cfg := uri.QueryParameterEncodingConfig{ cfg := uri.QueryParameterEncodingConfig{
Name: "StatusMessage", Name: "ErrorMessage",
Style: uri.QueryStyleForm, Style: uri.QueryStyleForm,
Explode: true, Explode: true,
} }
if err := q.EncodeParam(cfg, func(e uri.Encoder) error { if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
return e.EncodeValue(conv.StringToString(params.StatusMessage)) return e.EncodeValue(conv.StringToString(params.ErrorMessage))
}); err != nil { }); err != nil {
return res, errors.Wrap(err, "encode query") return res, errors.Wrap(err, "encode query")
} }
@@ -282,6 +306,266 @@ func (c *Client) sendActionMapfixAccepted(ctx context.Context, params ActionMapf
return result, nil return result, nil
} }
// ActionMapfixRequestChanges invokes actionMapfixRequestChanges operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested.
//
// POST /mapfixes/{MapfixID}/status/validator-request-changes
func (c *Client) ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error {
_, err := c.sendActionMapfixRequestChanges(ctx, params)
return err
}
func (c *Client) sendActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) (res *ActionMapfixRequestChangesNoContent, err error) {
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionMapfixRequestChanges"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/status/validator-request-changes"),
}
// Run stopwatch.
startTime := time.Now()
defer func() {
// Use floating point division here for higher precision (instead of Millisecond method).
elapsedDuration := time.Since(startTime)
c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...))
}()
// Increment request counter.
c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
// Start a span for this request.
ctx, span := c.cfg.Tracer.Start(ctx, ActionMapfixRequestChangesOperation,
trace.WithAttributes(otelAttrs...),
clientSpanKind,
)
// Track stage for error reporting.
var stage string
defer func() {
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, stage)
c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
}
span.End()
}()
stage = "BuildURL"
u := uri.Clone(c.requestURL(ctx))
var pathParts [3]string
pathParts[0] = "/mapfixes/"
{
// Encode "MapfixID" parameter.
e := uri.NewPathEncoder(uri.PathEncoderConfig{
Param: "MapfixID",
Style: uri.PathStyleSimple,
Explode: false,
})
if err := func() error {
return e.EncodeValue(conv.Int64ToString(params.MapfixID))
}(); err != nil {
return res, errors.Wrap(err, "encode path")
}
encoded, err := e.Result()
if err != nil {
return res, errors.Wrap(err, "encode path")
}
pathParts[1] = encoded
}
pathParts[2] = "/status/validator-request-changes"
uri.AddPathParts(u, pathParts[:]...)
stage = "EncodeQueryParams"
q := uri.NewQueryEncoder()
{
// Encode "ErrorMessage" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "ErrorMessage",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
return e.EncodeValue(conv.StringToString(params.ErrorMessage))
}); err != nil {
return res, errors.Wrap(err, "encode query")
}
}
u.RawQuery = q.Values().Encode()
stage = "EncodeRequest"
r, err := ht.NewRequest(ctx, "POST", u)
if err != nil {
return res, errors.Wrap(err, "create request")
}
stage = "SendRequest"
resp, err := c.cfg.Client.Do(r)
if err != nil {
return res, errors.Wrap(err, "do request")
}
defer resp.Body.Close()
stage = "DecodeResponse"
result, err := decodeActionMapfixRequestChangesResponse(resp)
if err != nil {
return res, errors.Wrap(err, "decode response")
}
return result, nil
}
// ActionMapfixSubmitted invokes actionMapfixSubmitted operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> Submitted.
//
// POST /mapfixes/{MapfixID}/status/validator-submitted
func (c *Client) ActionMapfixSubmitted(ctx context.Context, params ActionMapfixSubmittedParams) error {
_, err := c.sendActionMapfixSubmitted(ctx, params)
return err
}
func (c *Client) sendActionMapfixSubmitted(ctx context.Context, params ActionMapfixSubmittedParams) (res *ActionMapfixSubmittedNoContent, err error) {
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionMapfixSubmitted"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/status/validator-submitted"),
}
// Run stopwatch.
startTime := time.Now()
defer func() {
// Use floating point division here for higher precision (instead of Millisecond method).
elapsedDuration := time.Since(startTime)
c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...))
}()
// Increment request counter.
c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
// Start a span for this request.
ctx, span := c.cfg.Tracer.Start(ctx, ActionMapfixSubmittedOperation,
trace.WithAttributes(otelAttrs...),
clientSpanKind,
)
// Track stage for error reporting.
var stage string
defer func() {
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, stage)
c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
}
span.End()
}()
stage = "BuildURL"
u := uri.Clone(c.requestURL(ctx))
var pathParts [3]string
pathParts[0] = "/mapfixes/"
{
// Encode "MapfixID" parameter.
e := uri.NewPathEncoder(uri.PathEncoderConfig{
Param: "MapfixID",
Style: uri.PathStyleSimple,
Explode: false,
})
if err := func() error {
return e.EncodeValue(conv.Int64ToString(params.MapfixID))
}(); err != nil {
return res, errors.Wrap(err, "encode path")
}
encoded, err := e.Result()
if err != nil {
return res, errors.Wrap(err, "encode path")
}
pathParts[1] = encoded
}
pathParts[2] = "/status/validator-submitted"
uri.AddPathParts(u, pathParts[:]...)
stage = "EncodeQueryParams"
q := uri.NewQueryEncoder()
{
// Encode "ModelVersion" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "ModelVersion",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
return e.EncodeValue(conv.Int64ToString(params.ModelVersion))
}); err != nil {
return res, errors.Wrap(err, "encode query")
}
}
{
// Encode "DisplayName" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "DisplayName",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
return e.EncodeValue(conv.StringToString(params.DisplayName))
}); err != nil {
return res, errors.Wrap(err, "encode query")
}
}
{
// Encode "Creator" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "Creator",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
return e.EncodeValue(conv.StringToString(params.Creator))
}); err != nil {
return res, errors.Wrap(err, "encode query")
}
}
{
// Encode "GameID" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "GameID",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
return e.EncodeValue(conv.Int32ToString(params.GameID))
}); err != nil {
return res, errors.Wrap(err, "encode query")
}
}
u.RawQuery = q.Values().Encode()
stage = "EncodeRequest"
r, err := ht.NewRequest(ctx, "POST", u)
if err != nil {
return res, errors.Wrap(err, "create request")
}
stage = "SendRequest"
resp, err := c.cfg.Client.Do(r)
if err != nil {
return res, errors.Wrap(err, "do request")
}
defer resp.Body.Close()
stage = "DecodeResponse"
result, err := decodeActionMapfixSubmittedResponse(resp)
if err != nil {
return res, errors.Wrap(err, "decode response")
}
return result, nil
}
// ActionMapfixUploaded invokes actionMapfixUploaded operation. // ActionMapfixUploaded invokes actionMapfixUploaded operation.
// //
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.
@@ -645,15 +929,15 @@ func (c *Client) sendActionSubmissionAccepted(ctx context.Context, params Action
stage = "EncodeQueryParams" stage = "EncodeQueryParams"
q := uri.NewQueryEncoder() q := uri.NewQueryEncoder()
{ {
// Encode "StatusMessage" parameter. // Encode "ErrorMessage" parameter.
cfg := uri.QueryParameterEncodingConfig{ cfg := uri.QueryParameterEncodingConfig{
Name: "StatusMessage", Name: "ErrorMessage",
Style: uri.QueryStyleForm, Style: uri.QueryStyleForm,
Explode: true, Explode: true,
} }
if err := q.EncodeParam(cfg, func(e uri.Encoder) error { if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
return e.EncodeValue(conv.StringToString(params.StatusMessage)) return e.EncodeValue(conv.StringToString(params.ErrorMessage))
}); err != nil { }); err != nil {
return res, errors.Wrap(err, "encode query") return res, errors.Wrap(err, "encode query")
} }
@@ -682,6 +966,266 @@ func (c *Client) sendActionSubmissionAccepted(ctx context.Context, params Action
return result, nil return result, nil
} }
// ActionSubmissionRequestChanges invokes actionSubmissionRequestChanges operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested.
//
// POST /submissions/{SubmissionID}/status/validator-request-changes
func (c *Client) ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error {
_, err := c.sendActionSubmissionRequestChanges(ctx, params)
return err
}
func (c *Client) sendActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) (res *ActionSubmissionRequestChangesNoContent, err error) {
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionSubmissionRequestChanges"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/validator-request-changes"),
}
// Run stopwatch.
startTime := time.Now()
defer func() {
// Use floating point division here for higher precision (instead of Millisecond method).
elapsedDuration := time.Since(startTime)
c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...))
}()
// Increment request counter.
c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
// Start a span for this request.
ctx, span := c.cfg.Tracer.Start(ctx, ActionSubmissionRequestChangesOperation,
trace.WithAttributes(otelAttrs...),
clientSpanKind,
)
// Track stage for error reporting.
var stage string
defer func() {
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, stage)
c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
}
span.End()
}()
stage = "BuildURL"
u := uri.Clone(c.requestURL(ctx))
var pathParts [3]string
pathParts[0] = "/submissions/"
{
// Encode "SubmissionID" parameter.
e := uri.NewPathEncoder(uri.PathEncoderConfig{
Param: "SubmissionID",
Style: uri.PathStyleSimple,
Explode: false,
})
if err := func() error {
return e.EncodeValue(conv.Int64ToString(params.SubmissionID))
}(); err != nil {
return res, errors.Wrap(err, "encode path")
}
encoded, err := e.Result()
if err != nil {
return res, errors.Wrap(err, "encode path")
}
pathParts[1] = encoded
}
pathParts[2] = "/status/validator-request-changes"
uri.AddPathParts(u, pathParts[:]...)
stage = "EncodeQueryParams"
q := uri.NewQueryEncoder()
{
// Encode "ErrorMessage" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "ErrorMessage",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
return e.EncodeValue(conv.StringToString(params.ErrorMessage))
}); err != nil {
return res, errors.Wrap(err, "encode query")
}
}
u.RawQuery = q.Values().Encode()
stage = "EncodeRequest"
r, err := ht.NewRequest(ctx, "POST", u)
if err != nil {
return res, errors.Wrap(err, "create request")
}
stage = "SendRequest"
resp, err := c.cfg.Client.Do(r)
if err != nil {
return res, errors.Wrap(err, "do request")
}
defer resp.Body.Close()
stage = "DecodeResponse"
result, err := decodeActionSubmissionRequestChangesResponse(resp)
if err != nil {
return res, errors.Wrap(err, "decode response")
}
return result, nil
}
// ActionSubmissionSubmitted invokes actionSubmissionSubmitted operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> Submitted.
//
// POST /submissions/{SubmissionID}/status/validator-submitted
func (c *Client) ActionSubmissionSubmitted(ctx context.Context, params ActionSubmissionSubmittedParams) error {
_, err := c.sendActionSubmissionSubmitted(ctx, params)
return err
}
func (c *Client) sendActionSubmissionSubmitted(ctx context.Context, params ActionSubmissionSubmittedParams) (res *ActionSubmissionSubmittedNoContent, err error) {
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionSubmissionSubmitted"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/validator-submitted"),
}
// Run stopwatch.
startTime := time.Now()
defer func() {
// Use floating point division here for higher precision (instead of Millisecond method).
elapsedDuration := time.Since(startTime)
c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...))
}()
// Increment request counter.
c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
// Start a span for this request.
ctx, span := c.cfg.Tracer.Start(ctx, ActionSubmissionSubmittedOperation,
trace.WithAttributes(otelAttrs...),
clientSpanKind,
)
// Track stage for error reporting.
var stage string
defer func() {
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, stage)
c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
}
span.End()
}()
stage = "BuildURL"
u := uri.Clone(c.requestURL(ctx))
var pathParts [3]string
pathParts[0] = "/submissions/"
{
// Encode "SubmissionID" parameter.
e := uri.NewPathEncoder(uri.PathEncoderConfig{
Param: "SubmissionID",
Style: uri.PathStyleSimple,
Explode: false,
})
if err := func() error {
return e.EncodeValue(conv.Int64ToString(params.SubmissionID))
}(); err != nil {
return res, errors.Wrap(err, "encode path")
}
encoded, err := e.Result()
if err != nil {
return res, errors.Wrap(err, "encode path")
}
pathParts[1] = encoded
}
pathParts[2] = "/status/validator-submitted"
uri.AddPathParts(u, pathParts[:]...)
stage = "EncodeQueryParams"
q := uri.NewQueryEncoder()
{
// Encode "ModelVersion" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "ModelVersion",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
return e.EncodeValue(conv.Int64ToString(params.ModelVersion))
}); err != nil {
return res, errors.Wrap(err, "encode query")
}
}
{
// Encode "DisplayName" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "DisplayName",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
return e.EncodeValue(conv.StringToString(params.DisplayName))
}); err != nil {
return res, errors.Wrap(err, "encode query")
}
}
{
// Encode "Creator" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "Creator",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
return e.EncodeValue(conv.StringToString(params.Creator))
}); err != nil {
return res, errors.Wrap(err, "encode query")
}
}
{
// Encode "GameID" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "GameID",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
return e.EncodeValue(conv.Int32ToString(params.GameID))
}); err != nil {
return res, errors.Wrap(err, "encode query")
}
}
u.RawQuery = q.Values().Encode()
stage = "EncodeRequest"
r, err := ht.NewRequest(ctx, "POST", u)
if err != nil {
return res, errors.Wrap(err, "create request")
}
stage = "SendRequest"
resp, err := c.cfg.Client.Do(r)
if err != nil {
return res, errors.Wrap(err, "do request")
}
defer resp.Body.Close()
stage = "DecodeResponse"
result, err := decodeActionSubmissionSubmittedResponse(resp)
if err != nil {
return res, errors.Wrap(err, "decode response")
}
return result, nil
}
// ActionSubmissionUploaded invokes actionSubmissionUploaded operation. // ActionSubmissionUploaded invokes actionSubmissionUploaded operation.
// //
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.

View File

@@ -129,9 +129,9 @@ func (s *Server) handleActionMapfixAcceptedRequest(args [1]string, argsEscaped b
In: "path", In: "path",
}: params.MapfixID, }: params.MapfixID,
{ {
Name: "StatusMessage", Name: "ErrorMessage",
In: "query", In: "query",
}: params.StatusMessage, }: params.ErrorMessage,
}, },
Raw: r, Raw: r,
} }
@@ -183,6 +183,324 @@ func (s *Server) handleActionMapfixAcceptedRequest(args [1]string, argsEscaped b
} }
} }
// handleActionMapfixRequestChangesRequest handles actionMapfixRequestChanges operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested.
//
// POST /mapfixes/{MapfixID}/status/validator-request-changes
func (s *Server) handleActionMapfixRequestChangesRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
statusWriter := &codeRecorder{ResponseWriter: w}
w = statusWriter
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionMapfixRequestChanges"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/status/validator-request-changes"),
}
// Start a span for this request.
ctx, span := s.cfg.Tracer.Start(r.Context(), ActionMapfixRequestChangesOperation,
trace.WithAttributes(otelAttrs...),
serverSpanKind,
)
defer span.End()
// Add Labeler to context.
labeler := &Labeler{attrs: otelAttrs}
ctx = contextWithLabeler(ctx, labeler)
// Run stopwatch.
startTime := time.Now()
defer func() {
elapsedDuration := time.Since(startTime)
attrSet := labeler.AttributeSet()
attrs := attrSet.ToSlice()
code := statusWriter.status
if code != 0 {
codeAttr := semconv.HTTPResponseStatusCode(code)
attrs = append(attrs, codeAttr)
span.SetAttributes(codeAttr)
}
attrOpt := metric.WithAttributes(attrs...)
// Increment request counter.
s.requests.Add(ctx, 1, attrOpt)
// Use floating point division here for higher precision (instead of Millisecond method).
s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt)
}()
var (
recordError = func(stage string, err error) {
span.RecordError(err)
// https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status
// Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges,
// unless there was another error (e.g., network error receiving the response body; or 3xx codes with
// max redirects exceeded), in which case status MUST be set to Error.
code := statusWriter.status
if code >= 100 && code < 500 {
span.SetStatus(codes.Error, stage)
}
attrSet := labeler.AttributeSet()
attrs := attrSet.ToSlice()
if code != 0 {
attrs = append(attrs, semconv.HTTPResponseStatusCode(code))
}
s.errors.Add(ctx, 1, metric.WithAttributes(attrs...))
}
err error
opErrContext = ogenerrors.OperationContext{
Name: ActionMapfixRequestChangesOperation,
ID: "actionMapfixRequestChanges",
}
)
params, err := decodeActionMapfixRequestChangesParams(args, argsEscaped, r)
if err != nil {
err = &ogenerrors.DecodeParamsError{
OperationContext: opErrContext,
Err: err,
}
defer recordError("DecodeParams", err)
s.cfg.ErrorHandler(ctx, w, r, err)
return
}
var response *ActionMapfixRequestChangesNoContent
if m := s.cfg.Middleware; m != nil {
mreq := middleware.Request{
Context: ctx,
OperationName: ActionMapfixRequestChangesOperation,
OperationSummary: "(Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested",
OperationID: "actionMapfixRequestChanges",
Body: nil,
Params: middleware.Parameters{
{
Name: "MapfixID",
In: "path",
}: params.MapfixID,
{
Name: "ErrorMessage",
In: "query",
}: params.ErrorMessage,
},
Raw: r,
}
type (
Request = struct{}
Params = ActionMapfixRequestChangesParams
Response = *ActionMapfixRequestChangesNoContent
)
response, err = middleware.HookMiddleware[
Request,
Params,
Response,
](
m,
mreq,
unpackActionMapfixRequestChangesParams,
func(ctx context.Context, request Request, params Params) (response Response, err error) {
err = s.h.ActionMapfixRequestChanges(ctx, params)
return response, err
},
)
} else {
err = s.h.ActionMapfixRequestChanges(ctx, params)
}
if err != nil {
if errRes, ok := errors.Into[*ErrorStatusCode](err); ok {
if err := encodeErrorResponse(errRes, w, span); err != nil {
defer recordError("Internal", err)
}
return
}
if errors.Is(err, ht.ErrNotImplemented) {
s.cfg.ErrorHandler(ctx, w, r, err)
return
}
if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil {
defer recordError("Internal", err)
}
return
}
if err := encodeActionMapfixRequestChangesResponse(response, w, span); err != nil {
defer recordError("EncodeResponse", err)
if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
s.cfg.ErrorHandler(ctx, w, r, err)
}
return
}
}
// handleActionMapfixSubmittedRequest handles actionMapfixSubmitted operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> Submitted.
//
// POST /mapfixes/{MapfixID}/status/validator-submitted
func (s *Server) handleActionMapfixSubmittedRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
statusWriter := &codeRecorder{ResponseWriter: w}
w = statusWriter
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionMapfixSubmitted"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/status/validator-submitted"),
}
// Start a span for this request.
ctx, span := s.cfg.Tracer.Start(r.Context(), ActionMapfixSubmittedOperation,
trace.WithAttributes(otelAttrs...),
serverSpanKind,
)
defer span.End()
// Add Labeler to context.
labeler := &Labeler{attrs: otelAttrs}
ctx = contextWithLabeler(ctx, labeler)
// Run stopwatch.
startTime := time.Now()
defer func() {
elapsedDuration := time.Since(startTime)
attrSet := labeler.AttributeSet()
attrs := attrSet.ToSlice()
code := statusWriter.status
if code != 0 {
codeAttr := semconv.HTTPResponseStatusCode(code)
attrs = append(attrs, codeAttr)
span.SetAttributes(codeAttr)
}
attrOpt := metric.WithAttributes(attrs...)
// Increment request counter.
s.requests.Add(ctx, 1, attrOpt)
// Use floating point division here for higher precision (instead of Millisecond method).
s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt)
}()
var (
recordError = func(stage string, err error) {
span.RecordError(err)
// https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status
// Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges,
// unless there was another error (e.g., network error receiving the response body; or 3xx codes with
// max redirects exceeded), in which case status MUST be set to Error.
code := statusWriter.status
if code >= 100 && code < 500 {
span.SetStatus(codes.Error, stage)
}
attrSet := labeler.AttributeSet()
attrs := attrSet.ToSlice()
if code != 0 {
attrs = append(attrs, semconv.HTTPResponseStatusCode(code))
}
s.errors.Add(ctx, 1, metric.WithAttributes(attrs...))
}
err error
opErrContext = ogenerrors.OperationContext{
Name: ActionMapfixSubmittedOperation,
ID: "actionMapfixSubmitted",
}
)
params, err := decodeActionMapfixSubmittedParams(args, argsEscaped, r)
if err != nil {
err = &ogenerrors.DecodeParamsError{
OperationContext: opErrContext,
Err: err,
}
defer recordError("DecodeParams", err)
s.cfg.ErrorHandler(ctx, w, r, err)
return
}
var response *ActionMapfixSubmittedNoContent
if m := s.cfg.Middleware; m != nil {
mreq := middleware.Request{
Context: ctx,
OperationName: ActionMapfixSubmittedOperation,
OperationSummary: "(Internal endpoint) Role Validator changes status from Submitting -> Submitted",
OperationID: "actionMapfixSubmitted",
Body: nil,
Params: middleware.Parameters{
{
Name: "MapfixID",
In: "path",
}: params.MapfixID,
{
Name: "ModelVersion",
In: "query",
}: params.ModelVersion,
{
Name: "DisplayName",
In: "query",
}: params.DisplayName,
{
Name: "Creator",
In: "query",
}: params.Creator,
{
Name: "GameID",
In: "query",
}: params.GameID,
},
Raw: r,
}
type (
Request = struct{}
Params = ActionMapfixSubmittedParams
Response = *ActionMapfixSubmittedNoContent
)
response, err = middleware.HookMiddleware[
Request,
Params,
Response,
](
m,
mreq,
unpackActionMapfixSubmittedParams,
func(ctx context.Context, request Request, params Params) (response Response, err error) {
err = s.h.ActionMapfixSubmitted(ctx, params)
return response, err
},
)
} else {
err = s.h.ActionMapfixSubmitted(ctx, params)
}
if err != nil {
if errRes, ok := errors.Into[*ErrorStatusCode](err); ok {
if err := encodeErrorResponse(errRes, w, span); err != nil {
defer recordError("Internal", err)
}
return
}
if errors.Is(err, ht.ErrNotImplemented) {
s.cfg.ErrorHandler(ctx, w, r, err)
return
}
if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil {
defer recordError("Internal", err)
}
return
}
if err := encodeActionMapfixSubmittedResponse(response, w, span); err != nil {
defer recordError("EncodeResponse", err)
if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
s.cfg.ErrorHandler(ctx, w, r, err)
}
return
}
}
// handleActionMapfixUploadedRequest handles actionMapfixUploaded operation. // handleActionMapfixUploadedRequest handles actionMapfixUploaded operation.
// //
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.
@@ -733,9 +1051,9 @@ func (s *Server) handleActionSubmissionAcceptedRequest(args [1]string, argsEscap
In: "path", In: "path",
}: params.SubmissionID, }: params.SubmissionID,
{ {
Name: "StatusMessage", Name: "ErrorMessage",
In: "query", In: "query",
}: params.StatusMessage, }: params.ErrorMessage,
}, },
Raw: r, Raw: r,
} }
@@ -787,6 +1105,324 @@ func (s *Server) handleActionSubmissionAcceptedRequest(args [1]string, argsEscap
} }
} }
// handleActionSubmissionRequestChangesRequest handles actionSubmissionRequestChanges operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested.
//
// POST /submissions/{SubmissionID}/status/validator-request-changes
func (s *Server) handleActionSubmissionRequestChangesRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
statusWriter := &codeRecorder{ResponseWriter: w}
w = statusWriter
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionSubmissionRequestChanges"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/validator-request-changes"),
}
// Start a span for this request.
ctx, span := s.cfg.Tracer.Start(r.Context(), ActionSubmissionRequestChangesOperation,
trace.WithAttributes(otelAttrs...),
serverSpanKind,
)
defer span.End()
// Add Labeler to context.
labeler := &Labeler{attrs: otelAttrs}
ctx = contextWithLabeler(ctx, labeler)
// Run stopwatch.
startTime := time.Now()
defer func() {
elapsedDuration := time.Since(startTime)
attrSet := labeler.AttributeSet()
attrs := attrSet.ToSlice()
code := statusWriter.status
if code != 0 {
codeAttr := semconv.HTTPResponseStatusCode(code)
attrs = append(attrs, codeAttr)
span.SetAttributes(codeAttr)
}
attrOpt := metric.WithAttributes(attrs...)
// Increment request counter.
s.requests.Add(ctx, 1, attrOpt)
// Use floating point division here for higher precision (instead of Millisecond method).
s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt)
}()
var (
recordError = func(stage string, err error) {
span.RecordError(err)
// https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status
// Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges,
// unless there was another error (e.g., network error receiving the response body; or 3xx codes with
// max redirects exceeded), in which case status MUST be set to Error.
code := statusWriter.status
if code >= 100 && code < 500 {
span.SetStatus(codes.Error, stage)
}
attrSet := labeler.AttributeSet()
attrs := attrSet.ToSlice()
if code != 0 {
attrs = append(attrs, semconv.HTTPResponseStatusCode(code))
}
s.errors.Add(ctx, 1, metric.WithAttributes(attrs...))
}
err error
opErrContext = ogenerrors.OperationContext{
Name: ActionSubmissionRequestChangesOperation,
ID: "actionSubmissionRequestChanges",
}
)
params, err := decodeActionSubmissionRequestChangesParams(args, argsEscaped, r)
if err != nil {
err = &ogenerrors.DecodeParamsError{
OperationContext: opErrContext,
Err: err,
}
defer recordError("DecodeParams", err)
s.cfg.ErrorHandler(ctx, w, r, err)
return
}
var response *ActionSubmissionRequestChangesNoContent
if m := s.cfg.Middleware; m != nil {
mreq := middleware.Request{
Context: ctx,
OperationName: ActionSubmissionRequestChangesOperation,
OperationSummary: "(Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested",
OperationID: "actionSubmissionRequestChanges",
Body: nil,
Params: middleware.Parameters{
{
Name: "SubmissionID",
In: "path",
}: params.SubmissionID,
{
Name: "ErrorMessage",
In: "query",
}: params.ErrorMessage,
},
Raw: r,
}
type (
Request = struct{}
Params = ActionSubmissionRequestChangesParams
Response = *ActionSubmissionRequestChangesNoContent
)
response, err = middleware.HookMiddleware[
Request,
Params,
Response,
](
m,
mreq,
unpackActionSubmissionRequestChangesParams,
func(ctx context.Context, request Request, params Params) (response Response, err error) {
err = s.h.ActionSubmissionRequestChanges(ctx, params)
return response, err
},
)
} else {
err = s.h.ActionSubmissionRequestChanges(ctx, params)
}
if err != nil {
if errRes, ok := errors.Into[*ErrorStatusCode](err); ok {
if err := encodeErrorResponse(errRes, w, span); err != nil {
defer recordError("Internal", err)
}
return
}
if errors.Is(err, ht.ErrNotImplemented) {
s.cfg.ErrorHandler(ctx, w, r, err)
return
}
if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil {
defer recordError("Internal", err)
}
return
}
if err := encodeActionSubmissionRequestChangesResponse(response, w, span); err != nil {
defer recordError("EncodeResponse", err)
if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
s.cfg.ErrorHandler(ctx, w, r, err)
}
return
}
}
// handleActionSubmissionSubmittedRequest handles actionSubmissionSubmitted operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> Submitted.
//
// POST /submissions/{SubmissionID}/status/validator-submitted
func (s *Server) handleActionSubmissionSubmittedRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
statusWriter := &codeRecorder{ResponseWriter: w}
w = statusWriter
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionSubmissionSubmitted"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/validator-submitted"),
}
// Start a span for this request.
ctx, span := s.cfg.Tracer.Start(r.Context(), ActionSubmissionSubmittedOperation,
trace.WithAttributes(otelAttrs...),
serverSpanKind,
)
defer span.End()
// Add Labeler to context.
labeler := &Labeler{attrs: otelAttrs}
ctx = contextWithLabeler(ctx, labeler)
// Run stopwatch.
startTime := time.Now()
defer func() {
elapsedDuration := time.Since(startTime)
attrSet := labeler.AttributeSet()
attrs := attrSet.ToSlice()
code := statusWriter.status
if code != 0 {
codeAttr := semconv.HTTPResponseStatusCode(code)
attrs = append(attrs, codeAttr)
span.SetAttributes(codeAttr)
}
attrOpt := metric.WithAttributes(attrs...)
// Increment request counter.
s.requests.Add(ctx, 1, attrOpt)
// Use floating point division here for higher precision (instead of Millisecond method).
s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt)
}()
var (
recordError = func(stage string, err error) {
span.RecordError(err)
// https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status
// Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges,
// unless there was another error (e.g., network error receiving the response body; or 3xx codes with
// max redirects exceeded), in which case status MUST be set to Error.
code := statusWriter.status
if code >= 100 && code < 500 {
span.SetStatus(codes.Error, stage)
}
attrSet := labeler.AttributeSet()
attrs := attrSet.ToSlice()
if code != 0 {
attrs = append(attrs, semconv.HTTPResponseStatusCode(code))
}
s.errors.Add(ctx, 1, metric.WithAttributes(attrs...))
}
err error
opErrContext = ogenerrors.OperationContext{
Name: ActionSubmissionSubmittedOperation,
ID: "actionSubmissionSubmitted",
}
)
params, err := decodeActionSubmissionSubmittedParams(args, argsEscaped, r)
if err != nil {
err = &ogenerrors.DecodeParamsError{
OperationContext: opErrContext,
Err: err,
}
defer recordError("DecodeParams", err)
s.cfg.ErrorHandler(ctx, w, r, err)
return
}
var response *ActionSubmissionSubmittedNoContent
if m := s.cfg.Middleware; m != nil {
mreq := middleware.Request{
Context: ctx,
OperationName: ActionSubmissionSubmittedOperation,
OperationSummary: "(Internal endpoint) Role Validator changes status from Submitting -> Submitted",
OperationID: "actionSubmissionSubmitted",
Body: nil,
Params: middleware.Parameters{
{
Name: "SubmissionID",
In: "path",
}: params.SubmissionID,
{
Name: "ModelVersion",
In: "query",
}: params.ModelVersion,
{
Name: "DisplayName",
In: "query",
}: params.DisplayName,
{
Name: "Creator",
In: "query",
}: params.Creator,
{
Name: "GameID",
In: "query",
}: params.GameID,
},
Raw: r,
}
type (
Request = struct{}
Params = ActionSubmissionSubmittedParams
Response = *ActionSubmissionSubmittedNoContent
)
response, err = middleware.HookMiddleware[
Request,
Params,
Response,
](
m,
mreq,
unpackActionSubmissionSubmittedParams,
func(ctx context.Context, request Request, params Params) (response Response, err error) {
err = s.h.ActionSubmissionSubmitted(ctx, params)
return response, err
},
)
} else {
err = s.h.ActionSubmissionSubmitted(ctx, params)
}
if err != nil {
if errRes, ok := errors.Into[*ErrorStatusCode](err); ok {
if err := encodeErrorResponse(errRes, w, span); err != nil {
defer recordError("Internal", err)
}
return
}
if errors.Is(err, ht.ErrNotImplemented) {
s.cfg.ErrorHandler(ctx, w, r, err)
return
}
if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil {
defer recordError("Internal", err)
}
return
}
if err := encodeActionSubmissionSubmittedResponse(response, w, span); err != nil {
defer recordError("EncodeResponse", err)
if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
s.cfg.ErrorHandler(ctx, w, r, err)
}
return
}
}
// handleActionSubmissionUploadedRequest handles actionSubmissionUploaded operation. // handleActionSubmissionUploadedRequest handles actionSubmissionUploaded operation.
// //
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.

View File

@@ -166,9 +166,13 @@ func (s *MapfixCreate) encodeFields(e *jx.Encoder) {
e.FieldStart("TargetAssetID") e.FieldStart("TargetAssetID")
e.Int64(s.TargetAssetID) e.Int64(s.TargetAssetID)
} }
{
e.FieldStart("Description")
e.Str(s.Description)
}
} }
var jsonFieldsNameOfMapfixCreate = [8]string{ var jsonFieldsNameOfMapfixCreate = [9]string{
0: "OperationID", 0: "OperationID",
1: "AssetOwner", 1: "AssetOwner",
2: "DisplayName", 2: "DisplayName",
@@ -177,6 +181,7 @@ var jsonFieldsNameOfMapfixCreate = [8]string{
5: "AssetID", 5: "AssetID",
6: "AssetVersion", 6: "AssetVersion",
7: "TargetAssetID", 7: "TargetAssetID",
8: "Description",
} }
// Decode decodes MapfixCreate from json. // Decode decodes MapfixCreate from json.
@@ -184,7 +189,7 @@ func (s *MapfixCreate) Decode(d *jx.Decoder) error {
if s == nil { if s == nil {
return errors.New("invalid: unable to decode MapfixCreate to nil") return errors.New("invalid: unable to decode MapfixCreate to nil")
} }
var requiredBitSet [1]uint8 var requiredBitSet [2]uint8
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
switch string(k) { switch string(k) {
@@ -284,6 +289,18 @@ func (s *MapfixCreate) Decode(d *jx.Decoder) error {
}(); err != nil { }(); err != nil {
return errors.Wrap(err, "decode field \"TargetAssetID\"") return errors.Wrap(err, "decode field \"TargetAssetID\"")
} }
case "Description":
requiredBitSet[1] |= 1 << 0
if err := func() error {
v, err := d.Str()
s.Description = string(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Description\"")
}
default: default:
return d.Skip() return d.Skip()
} }
@@ -293,8 +310,9 @@ func (s *MapfixCreate) Decode(d *jx.Decoder) error {
} }
// Validate required fields. // Validate required fields.
var failures []validate.FieldError var failures []validate.FieldError
for i, mask := range [1]uint8{ for i, mask := range [2]uint8{
0b11111111, 0b11111111,
0b00000001,
} { } {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR. // Mask only required fields and check equality to mask using XOR.
@@ -1305,9 +1323,17 @@ func (s *SubmissionCreate) encodeFields(e *jx.Encoder) {
e.FieldStart("AssetVersion") e.FieldStart("AssetVersion")
e.Int64(s.AssetVersion) e.Int64(s.AssetVersion)
} }
{
e.FieldStart("Status")
e.UInt32(s.Status)
}
{
e.FieldStart("Roles")
e.UInt32(s.Roles)
}
} }
var jsonFieldsNameOfSubmissionCreate = [7]string{ var jsonFieldsNameOfSubmissionCreate = [9]string{
0: "OperationID", 0: "OperationID",
1: "AssetOwner", 1: "AssetOwner",
2: "DisplayName", 2: "DisplayName",
@@ -1315,6 +1341,8 @@ var jsonFieldsNameOfSubmissionCreate = [7]string{
4: "GameID", 4: "GameID",
5: "AssetID", 5: "AssetID",
6: "AssetVersion", 6: "AssetVersion",
7: "Status",
8: "Roles",
} }
// Decode decodes SubmissionCreate from json. // Decode decodes SubmissionCreate from json.
@@ -1322,7 +1350,7 @@ func (s *SubmissionCreate) Decode(d *jx.Decoder) error {
if s == nil { if s == nil {
return errors.New("invalid: unable to decode SubmissionCreate to nil") return errors.New("invalid: unable to decode SubmissionCreate to nil")
} }
var requiredBitSet [1]uint8 var requiredBitSet [2]uint8
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
switch string(k) { switch string(k) {
@@ -1410,6 +1438,30 @@ func (s *SubmissionCreate) Decode(d *jx.Decoder) error {
}(); err != nil { }(); err != nil {
return errors.Wrap(err, "decode field \"AssetVersion\"") return errors.Wrap(err, "decode field \"AssetVersion\"")
} }
case "Status":
requiredBitSet[0] |= 1 << 7
if err := func() error {
v, err := d.UInt32()
s.Status = uint32(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Status\"")
}
case "Roles":
requiredBitSet[1] |= 1 << 0
if err := func() error {
v, err := d.UInt32()
s.Roles = uint32(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Roles\"")
}
default: default:
return d.Skip() return d.Skip()
} }
@@ -1419,8 +1471,9 @@ func (s *SubmissionCreate) Decode(d *jx.Decoder) error {
} }
// Validate required fields. // Validate required fields.
var failures []validate.FieldError var failures []validate.FieldError
for i, mask := range [1]uint8{ for i, mask := range [2]uint8{
0b01111111, 0b11111111,
0b00000001,
} { } {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR. // Mask only required fields and check equality to mask using XOR.

View File

@@ -7,10 +7,14 @@ type OperationName = string
const ( const (
ActionMapfixAcceptedOperation OperationName = "ActionMapfixAccepted" ActionMapfixAcceptedOperation OperationName = "ActionMapfixAccepted"
ActionMapfixRequestChangesOperation OperationName = "ActionMapfixRequestChanges"
ActionMapfixSubmittedOperation OperationName = "ActionMapfixSubmitted"
ActionMapfixUploadedOperation OperationName = "ActionMapfixUploaded" ActionMapfixUploadedOperation OperationName = "ActionMapfixUploaded"
ActionMapfixValidatedOperation OperationName = "ActionMapfixValidated" ActionMapfixValidatedOperation OperationName = "ActionMapfixValidated"
ActionOperationFailedOperation OperationName = "ActionOperationFailed" ActionOperationFailedOperation OperationName = "ActionOperationFailed"
ActionSubmissionAcceptedOperation OperationName = "ActionSubmissionAccepted" ActionSubmissionAcceptedOperation OperationName = "ActionSubmissionAccepted"
ActionSubmissionRequestChangesOperation OperationName = "ActionSubmissionRequestChanges"
ActionSubmissionSubmittedOperation OperationName = "ActionSubmissionSubmitted"
ActionSubmissionUploadedOperation OperationName = "ActionSubmissionUploaded" ActionSubmissionUploadedOperation OperationName = "ActionSubmissionUploaded"
ActionSubmissionValidatedOperation OperationName = "ActionSubmissionValidated" ActionSubmissionValidatedOperation OperationName = "ActionSubmissionValidated"
CreateMapfixOperation OperationName = "CreateMapfix" CreateMapfixOperation OperationName = "CreateMapfix"

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,6 @@ import (
"github.com/go-faster/errors" "github.com/go-faster/errors"
"github.com/go-faster/jx" "github.com/go-faster/jx"
"go.uber.org/multierr"
"github.com/ogen-go/ogen/ogenerrors" "github.com/ogen-go/ogen/ogenerrors"
"github.com/ogen-go/ogen/validate" "github.com/ogen-go/ogen/validate"
@@ -26,13 +25,13 @@ func (s *Server) decodeCreateMapfixRequest(r *http.Request) (
// Close in reverse order, to match defer behavior. // Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- { for i := len(closers) - 1; i >= 0; i-- {
c := closers[i] c := closers[i]
merr = multierr.Append(merr, c()) merr = errors.Join(merr, c())
} }
return merr return merr
} }
defer func() { defer func() {
if rerr != nil { if rerr != nil {
rerr = multierr.Append(rerr, close()) rerr = errors.Join(rerr, close())
} }
}() }()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
@@ -97,13 +96,13 @@ func (s *Server) decodeCreateScriptRequest(r *http.Request) (
// Close in reverse order, to match defer behavior. // Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- { for i := len(closers) - 1; i >= 0; i-- {
c := closers[i] c := closers[i]
merr = multierr.Append(merr, c()) merr = errors.Join(merr, c())
} }
return merr return merr
} }
defer func() { defer func() {
if rerr != nil { if rerr != nil {
rerr = multierr.Append(rerr, close()) rerr = errors.Join(rerr, close())
} }
}() }()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
@@ -168,13 +167,13 @@ func (s *Server) decodeCreateScriptPolicyRequest(r *http.Request) (
// Close in reverse order, to match defer behavior. // Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- { for i := len(closers) - 1; i >= 0; i-- {
c := closers[i] c := closers[i]
merr = multierr.Append(merr, c()) merr = errors.Join(merr, c())
} }
return merr return merr
} }
defer func() { defer func() {
if rerr != nil { if rerr != nil {
rerr = multierr.Append(rerr, close()) rerr = errors.Join(rerr, close())
} }
}() }()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
@@ -214,6 +213,14 @@ func (s *Server) decodeCreateScriptPolicyRequest(r *http.Request) (
} }
return req, close, err return req, close, err
} }
if err := func() error {
if err := request.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return req, close, errors.Wrap(err, "validate")
}
return &request, close, nil return &request, close, nil
default: default:
return req, close, validate.InvalidContentType(ct) return req, close, validate.InvalidContentType(ct)
@@ -231,13 +238,13 @@ func (s *Server) decodeCreateSubmissionRequest(r *http.Request) (
// Close in reverse order, to match defer behavior. // Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- { for i := len(closers) - 1; i >= 0; i-- {
c := closers[i] c := closers[i]
merr = multierr.Append(merr, c()) merr = errors.Join(merr, c())
} }
return merr return merr
} }
defer func() { defer func() {
if rerr != nil { if rerr != nil {
rerr = multierr.Append(rerr, close()) rerr = errors.Join(rerr, close())
} }
}() }()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))

View File

@@ -52,6 +52,135 @@ func decodeActionMapfixAcceptedResponse(resp *http.Response) (res *ActionMapfixA
} }
return res, 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 decodeActionMapfixRequestChangesResponse(resp *http.Response) (res *ActionMapfixRequestChangesNoContent, _ error) {
switch resp.StatusCode {
case 204:
// Code 204.
return &ActionMapfixRequestChangesNoContent{}, nil
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
// Validate response.
if err := func() error {
if err := response.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeActionMapfixSubmittedResponse(resp *http.Response) (res *ActionMapfixSubmittedNoContent, _ error) {
switch resp.StatusCode {
case 204:
// Code 204.
return &ActionMapfixSubmittedNoContent{}, nil
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
// Validate response.
if err := func() error {
if err := response.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return &ErrorStatusCode{ return &ErrorStatusCode{
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
Response: response, Response: response,
@@ -103,6 +232,15 @@ func decodeActionMapfixUploadedResponse(resp *http.Response) (res *ActionMapfixU
} }
return res, 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{ return &ErrorStatusCode{
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
Response: response, Response: response,
@@ -154,6 +292,15 @@ func decodeActionMapfixValidatedResponse(resp *http.Response) (res *ActionMapfix
} }
return res, 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{ return &ErrorStatusCode{
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
Response: response, Response: response,
@@ -205,6 +352,15 @@ func decodeActionOperationFailedResponse(resp *http.Response) (res *ActionOperat
} }
return res, 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{ return &ErrorStatusCode{
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
Response: response, Response: response,
@@ -256,6 +412,135 @@ func decodeActionSubmissionAcceptedResponse(resp *http.Response) (res *ActionSub
} }
return res, 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 decodeActionSubmissionRequestChangesResponse(resp *http.Response) (res *ActionSubmissionRequestChangesNoContent, _ error) {
switch resp.StatusCode {
case 204:
// Code 204.
return &ActionSubmissionRequestChangesNoContent{}, nil
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
// Validate response.
if err := func() error {
if err := response.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeActionSubmissionSubmittedResponse(resp *http.Response) (res *ActionSubmissionSubmittedNoContent, _ error) {
switch resp.StatusCode {
case 204:
// Code 204.
return &ActionSubmissionSubmittedNoContent{}, nil
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
// Validate response.
if err := func() error {
if err := response.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return &ErrorStatusCode{ return &ErrorStatusCode{
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
Response: response, Response: response,
@@ -307,6 +592,15 @@ func decodeActionSubmissionUploadedResponse(resp *http.Response) (res *ActionSub
} }
return res, 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{ return &ErrorStatusCode{
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
Response: response, Response: response,
@@ -358,6 +652,15 @@ func decodeActionSubmissionValidatedResponse(resp *http.Response) (res *ActionSu
} }
return res, 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{ return &ErrorStatusCode{
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
Response: response, Response: response,
@@ -405,6 +708,15 @@ func decodeCreateMapfixResponse(resp *http.Response) (res *MapfixID, _ error) {
} }
return res, err return res, err
} }
// Validate response.
if err := func() error {
if err := response.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return &response, nil return &response, nil
default: default:
return res, validate.InvalidContentType(ct) return res, validate.InvalidContentType(ct)
@@ -441,6 +753,15 @@ func decodeCreateMapfixResponse(resp *http.Response) (res *MapfixID, _ error) {
} }
return res, 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{ return &ErrorStatusCode{
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
Response: response, Response: response,
@@ -488,6 +809,15 @@ func decodeCreateScriptResponse(resp *http.Response) (res *ScriptID, _ error) {
} }
return res, err return res, err
} }
// Validate response.
if err := func() error {
if err := response.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return &response, nil return &response, nil
default: default:
return res, validate.InvalidContentType(ct) return res, validate.InvalidContentType(ct)
@@ -524,6 +854,15 @@ func decodeCreateScriptResponse(resp *http.Response) (res *ScriptID, _ error) {
} }
return res, 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{ return &ErrorStatusCode{
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
Response: response, Response: response,
@@ -571,6 +910,15 @@ func decodeCreateScriptPolicyResponse(resp *http.Response) (res *ScriptPolicyID,
} }
return res, err return res, err
} }
// Validate response.
if err := func() error {
if err := response.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return &response, nil return &response, nil
default: default:
return res, validate.InvalidContentType(ct) return res, validate.InvalidContentType(ct)
@@ -607,6 +955,15 @@ func decodeCreateScriptPolicyResponse(resp *http.Response) (res *ScriptPolicyID,
} }
return res, 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{ return &ErrorStatusCode{
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
Response: response, Response: response,
@@ -654,6 +1011,15 @@ func decodeCreateSubmissionResponse(resp *http.Response) (res *SubmissionID, _ e
} }
return res, err return res, err
} }
// Validate response.
if err := func() error {
if err := response.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return &response, nil return &response, nil
default: default:
return res, validate.InvalidContentType(ct) return res, validate.InvalidContentType(ct)
@@ -690,6 +1056,15 @@ func decodeCreateSubmissionResponse(resp *http.Response) (res *SubmissionID, _ e
} }
return res, 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{ return &ErrorStatusCode{
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
Response: response, Response: response,
@@ -782,6 +1157,15 @@ func decodeGetScriptResponse(resp *http.Response) (res *Script, _ error) {
} }
return res, 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{ return &ErrorStatusCode{
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
Response: response, Response: response,
@@ -899,6 +1283,15 @@ func decodeListScriptPolicyResponse(resp *http.Response) (res []ScriptPolicy, _
} }
return res, 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{ return &ErrorStatusCode{
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
Response: response, Response: response,
@@ -1016,6 +1409,15 @@ func decodeListScriptsResponse(resp *http.Response) (res []Script, _ error) {
} }
return res, 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{ return &ErrorStatusCode{
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
Response: response, Response: response,
@@ -1067,6 +1469,15 @@ func decodeUpdateMapfixValidatedModelResponse(resp *http.Response) (res *UpdateM
} }
return res, 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{ return &ErrorStatusCode{
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
Response: response, Response: response,
@@ -1118,6 +1529,15 @@ func decodeUpdateSubmissionValidatedModelResponse(resp *http.Response) (res *Upd
} }
return res, 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{ return &ErrorStatusCode{
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
Response: response, Response: response,

View File

@@ -20,6 +20,20 @@ func encodeActionMapfixAcceptedResponse(response *ActionMapfixAcceptedNoContent,
return nil return nil
} }
func encodeActionMapfixRequestChangesResponse(response *ActionMapfixRequestChangesNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeActionMapfixSubmittedResponse(response *ActionMapfixSubmittedNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeActionMapfixUploadedResponse(response *ActionMapfixUploadedNoContent, w http.ResponseWriter, span trace.Span) error { func encodeActionMapfixUploadedResponse(response *ActionMapfixUploadedNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204) w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204)) span.SetStatus(codes.Ok, http.StatusText(204))
@@ -48,6 +62,20 @@ func encodeActionSubmissionAcceptedResponse(response *ActionSubmissionAcceptedNo
return nil return nil
} }
func encodeActionSubmissionRequestChangesResponse(response *ActionSubmissionRequestChangesNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeActionSubmissionSubmittedResponse(response *ActionSubmissionSubmittedNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeActionSubmissionUploadedResponse(response *ActionSubmissionUploadedNoContent, w http.ResponseWriter, span trace.Span) error { func encodeActionSubmissionUploadedResponse(response *ActionSubmissionUploadedNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204) w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204)) span.SetStatus(codes.Ok, http.StatusText(204))

View File

@@ -147,6 +147,50 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
case 'r': // Prefix: "request-changes"
if l := len("request-changes"); len(elem) >= l && elem[0:l] == "request-changes" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionMapfixRequestChangesRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
case 's': // Prefix: "submitted"
if l := len("submitted"); len(elem) >= l && elem[0:l] == "submitted" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionMapfixSubmittedRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
case 'u': // Prefix: "uploaded" case 'u': // Prefix: "uploaded"
if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" { if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" {
@@ -454,6 +498,50 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
case 'r': // Prefix: "request-changes"
if l := len("request-changes"); len(elem) >= l && elem[0:l] == "request-changes" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionSubmissionRequestChangesRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
case 's': // Prefix: "submitted"
if l := len("submitted"); len(elem) >= l && elem[0:l] == "submitted" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionSubmissionSubmittedRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
case 'u': // Prefix: "uploaded" case 'u': // Prefix: "uploaded"
if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" { if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" {
@@ -716,6 +804,54 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
} }
} }
case 'r': // Prefix: "request-changes"
if l := len("request-changes"); len(elem) >= l && elem[0:l] == "request-changes" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionMapfixRequestChangesOperation
r.summary = "(Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested"
r.operationID = "actionMapfixRequestChanges"
r.pathPattern = "/mapfixes/{MapfixID}/status/validator-request-changes"
r.args = args
r.count = 1
return r, true
default:
return
}
}
case 's': // Prefix: "submitted"
if l := len("submitted"); len(elem) >= l && elem[0:l] == "submitted" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionMapfixSubmittedOperation
r.summary = "(Internal endpoint) Role Validator changes status from Submitting -> Submitted"
r.operationID = "actionMapfixSubmitted"
r.pathPattern = "/mapfixes/{MapfixID}/status/validator-submitted"
r.args = args
r.count = 1
return r, true
default:
return
}
}
case 'u': // Prefix: "uploaded" case 'u': // Prefix: "uploaded"
if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" { if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" {
@@ -1059,6 +1195,54 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
} }
} }
case 'r': // Prefix: "request-changes"
if l := len("request-changes"); len(elem) >= l && elem[0:l] == "request-changes" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionSubmissionRequestChangesOperation
r.summary = "(Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested"
r.operationID = "actionSubmissionRequestChanges"
r.pathPattern = "/submissions/{SubmissionID}/status/validator-request-changes"
r.args = args
r.count = 1
return r, true
default:
return
}
}
case 's': // Prefix: "submitted"
if l := len("submitted"); len(elem) >= l && elem[0:l] == "submitted" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionSubmissionSubmittedOperation
r.summary = "(Internal endpoint) Role Validator changes status from Submitting -> Submitted"
r.operationID = "actionSubmissionSubmitted"
r.pathPattern = "/submissions/{SubmissionID}/status/validator-submitted"
r.args = args
r.count = 1
return r, true
default:
return
}
}
case 'u': // Prefix: "uploaded" case 'u': // Prefix: "uploaded"
if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" { if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" {

View File

@@ -13,6 +13,12 @@ func (s *ErrorStatusCode) Error() string {
// ActionMapfixAcceptedNoContent is response for ActionMapfixAccepted operation. // ActionMapfixAcceptedNoContent is response for ActionMapfixAccepted operation.
type ActionMapfixAcceptedNoContent struct{} type ActionMapfixAcceptedNoContent struct{}
// ActionMapfixRequestChangesNoContent is response for ActionMapfixRequestChanges operation.
type ActionMapfixRequestChangesNoContent struct{}
// ActionMapfixSubmittedNoContent is response for ActionMapfixSubmitted operation.
type ActionMapfixSubmittedNoContent struct{}
// ActionMapfixUploadedNoContent is response for ActionMapfixUploaded operation. // ActionMapfixUploadedNoContent is response for ActionMapfixUploaded operation.
type ActionMapfixUploadedNoContent struct{} type ActionMapfixUploadedNoContent struct{}
@@ -25,6 +31,12 @@ type ActionOperationFailedNoContent struct{}
// ActionSubmissionAcceptedNoContent is response for ActionSubmissionAccepted operation. // ActionSubmissionAcceptedNoContent is response for ActionSubmissionAccepted operation.
type ActionSubmissionAcceptedNoContent struct{} type ActionSubmissionAcceptedNoContent struct{}
// ActionSubmissionRequestChangesNoContent is response for ActionSubmissionRequestChanges operation.
type ActionSubmissionRequestChangesNoContent struct{}
// ActionSubmissionSubmittedNoContent is response for ActionSubmissionSubmitted operation.
type ActionSubmissionSubmittedNoContent struct{}
// ActionSubmissionUploadedNoContent is response for ActionSubmissionUploaded operation. // ActionSubmissionUploadedNoContent is response for ActionSubmissionUploaded operation.
type ActionSubmissionUploadedNoContent struct{} type ActionSubmissionUploadedNoContent struct{}
@@ -94,6 +106,7 @@ type MapfixCreate struct {
AssetID int64 `json:"AssetID"` AssetID int64 `json:"AssetID"`
AssetVersion int64 `json:"AssetVersion"` AssetVersion int64 `json:"AssetVersion"`
TargetAssetID int64 `json:"TargetAssetID"` TargetAssetID int64 `json:"TargetAssetID"`
Description string `json:"Description"`
} }
// GetOperationID returns the value of OperationID. // GetOperationID returns the value of OperationID.
@@ -136,6 +149,11 @@ func (s *MapfixCreate) GetTargetAssetID() int64 {
return s.TargetAssetID return s.TargetAssetID
} }
// GetDescription returns the value of Description.
func (s *MapfixCreate) GetDescription() string {
return s.Description
}
// SetOperationID sets the value of OperationID. // SetOperationID sets the value of OperationID.
func (s *MapfixCreate) SetOperationID(val int32) { func (s *MapfixCreate) SetOperationID(val int32) {
s.OperationID = val s.OperationID = val
@@ -176,6 +194,11 @@ func (s *MapfixCreate) SetTargetAssetID(val int64) {
s.TargetAssetID = val s.TargetAssetID = val
} }
// SetDescription sets the value of Description.
func (s *MapfixCreate) SetDescription(val string) {
s.Description = val
}
// Ref: #/components/schemas/MapfixID // Ref: #/components/schemas/MapfixID
type MapfixID struct { type MapfixID struct {
MapfixID int64 `json:"MapfixID"` MapfixID int64 `json:"MapfixID"`
@@ -571,6 +594,8 @@ type SubmissionCreate struct {
GameID int32 `json:"GameID"` GameID int32 `json:"GameID"`
AssetID int64 `json:"AssetID"` AssetID int64 `json:"AssetID"`
AssetVersion int64 `json:"AssetVersion"` AssetVersion int64 `json:"AssetVersion"`
Status uint32 `json:"Status"`
Roles uint32 `json:"Roles"`
} }
// GetOperationID returns the value of OperationID. // GetOperationID returns the value of OperationID.
@@ -608,6 +633,16 @@ func (s *SubmissionCreate) GetAssetVersion() int64 {
return s.AssetVersion return s.AssetVersion
} }
// GetStatus returns the value of Status.
func (s *SubmissionCreate) GetStatus() uint32 {
return s.Status
}
// GetRoles returns the value of Roles.
func (s *SubmissionCreate) GetRoles() uint32 {
return s.Roles
}
// SetOperationID sets the value of OperationID. // SetOperationID sets the value of OperationID.
func (s *SubmissionCreate) SetOperationID(val int32) { func (s *SubmissionCreate) SetOperationID(val int32) {
s.OperationID = val s.OperationID = val
@@ -643,6 +678,16 @@ func (s *SubmissionCreate) SetAssetVersion(val int64) {
s.AssetVersion = val s.AssetVersion = val
} }
// SetStatus sets the value of Status.
func (s *SubmissionCreate) SetStatus(val uint32) {
s.Status = val
}
// SetRoles sets the value of Roles.
func (s *SubmissionCreate) SetRoles(val uint32) {
s.Roles = val
}
// Ref: #/components/schemas/SubmissionID // Ref: #/components/schemas/SubmissionID
type SubmissionID struct { type SubmissionID struct {
SubmissionID int64 `json:"SubmissionID"` SubmissionID int64 `json:"SubmissionID"`

View File

@@ -14,6 +14,18 @@ type Handler interface {
// //
// POST /mapfixes/{MapfixID}/status/validator-failed // POST /mapfixes/{MapfixID}/status/validator-failed
ActionMapfixAccepted(ctx context.Context, params ActionMapfixAcceptedParams) error ActionMapfixAccepted(ctx context.Context, params ActionMapfixAcceptedParams) error
// ActionMapfixRequestChanges implements actionMapfixRequestChanges operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested.
//
// POST /mapfixes/{MapfixID}/status/validator-request-changes
ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error
// ActionMapfixSubmitted implements actionMapfixSubmitted operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> Submitted.
//
// POST /mapfixes/{MapfixID}/status/validator-submitted
ActionMapfixSubmitted(ctx context.Context, params ActionMapfixSubmittedParams) error
// ActionMapfixUploaded implements actionMapfixUploaded operation. // ActionMapfixUploaded implements actionMapfixUploaded operation.
// //
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.
@@ -38,6 +50,18 @@ type Handler interface {
// //
// POST /submissions/{SubmissionID}/status/validator-failed // POST /submissions/{SubmissionID}/status/validator-failed
ActionSubmissionAccepted(ctx context.Context, params ActionSubmissionAcceptedParams) error ActionSubmissionAccepted(ctx context.Context, params ActionSubmissionAcceptedParams) error
// ActionSubmissionRequestChanges implements actionSubmissionRequestChanges operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested.
//
// POST /submissions/{SubmissionID}/status/validator-request-changes
ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error
// ActionSubmissionSubmitted implements actionSubmissionSubmitted operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> Submitted.
//
// POST /submissions/{SubmissionID}/status/validator-submitted
ActionSubmissionSubmitted(ctx context.Context, params ActionSubmissionSubmittedParams) error
// ActionSubmissionUploaded implements actionSubmissionUploaded operation. // ActionSubmissionUploaded implements actionSubmissionUploaded operation.
// //
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.

View File

@@ -22,6 +22,24 @@ func (UnimplementedHandler) ActionMapfixAccepted(ctx context.Context, params Act
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
// ActionMapfixRequestChanges implements actionMapfixRequestChanges operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested.
//
// POST /mapfixes/{MapfixID}/status/validator-request-changes
func (UnimplementedHandler) ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error {
return ht.ErrNotImplemented
}
// ActionMapfixSubmitted implements actionMapfixSubmitted operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> Submitted.
//
// POST /mapfixes/{MapfixID}/status/validator-submitted
func (UnimplementedHandler) ActionMapfixSubmitted(ctx context.Context, params ActionMapfixSubmittedParams) error {
return ht.ErrNotImplemented
}
// ActionMapfixUploaded implements actionMapfixUploaded operation. // ActionMapfixUploaded implements actionMapfixUploaded operation.
// //
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.
@@ -58,6 +76,24 @@ func (UnimplementedHandler) ActionSubmissionAccepted(ctx context.Context, params
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
// ActionSubmissionRequestChanges implements actionSubmissionRequestChanges operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested.
//
// POST /submissions/{SubmissionID}/status/validator-request-changes
func (UnimplementedHandler) ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error {
return ht.ErrNotImplemented
}
// ActionSubmissionSubmitted implements actionSubmissionSubmitted operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> Submitted.
//
// POST /submissions/{SubmissionID}/status/validator-submitted
func (UnimplementedHandler) ActionSubmissionSubmitted(ctx context.Context, params ActionSubmissionSubmittedParams) error {
return ht.ErrNotImplemented
}
// ActionSubmissionUploaded implements actionSubmissionUploaded operation. // ActionSubmissionUploaded implements actionSubmissionUploaded operation.
// //
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. // (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.

View File

@@ -8,12 +8,107 @@ import (
"github.com/ogen-go/ogen/validate" "github.com/ogen-go/ogen/validate"
) )
func (s *Error) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
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(s.Code)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "code",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *ErrorStatusCode) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if err := s.Response.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Response",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *MapfixCreate) Validate() error { func (s *MapfixCreate) Validate() error {
if s == nil { if s == nil {
return validate.ErrNilPointer return validate.ErrNilPointer
} }
var failures []validate.FieldError var failures []validate.FieldError
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(s.OperationID)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "OperationID",
Error: 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(s.AssetOwner)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "AssetOwner",
Error: err,
})
}
if err := func() error { if err := func() error {
if err := (validate.String{ if err := (validate.String{
MinLength: 0, MinLength: 0,
@@ -52,6 +147,137 @@ func (s *MapfixCreate) Validate() error {
Error: err, Error: 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(s.GameID)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "GameID",
Error: 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(s.AssetID)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "AssetID",
Error: 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(s.AssetVersion)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "AssetVersion",
Error: 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(s.TargetAssetID)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "TargetAssetID",
Error: err,
})
}
if err := func() error {
if err := (validate.String{
MinLength: 0,
MinLengthSet: false,
MaxLength: 256,
MaxLengthSet: true,
Email: false,
Hostname: false,
Regex: nil,
}).Validate(string(s.Description)); err != nil {
return errors.Wrap(err, "string")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Description",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *MapfixID) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
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(s.MapfixID)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "MapfixID",
Error: err,
})
}
if len(failures) > 0 { if len(failures) > 0 {
return &validate.Error{Fields: failures} return &validate.Error{Fields: failures}
} }
@@ -64,6 +290,26 @@ func (s *Script) Validate() error {
} }
var failures []validate.FieldError var failures []validate.FieldError
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(s.ID)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "ID",
Error: err,
})
}
if err := func() error { if err := func() error {
if err := (validate.String{ if err := (validate.String{
MinLength: 0, MinLength: 0,
@@ -121,6 +367,46 @@ func (s *Script) Validate() error {
Error: err, Error: 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(s.ResourceType)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "ResourceType",
Error: 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(s.ResourceID)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "ResourceID",
Error: err,
})
}
if len(failures) > 0 { if len(failures) > 0 {
return &validate.Error{Fields: failures} return &validate.Error{Fields: failures}
} }
@@ -171,6 +457,85 @@ func (s *ScriptCreate) Validate() error {
Error: err, Error: 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(s.ResourceType)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "ResourceType",
Error: err,
})
}
if err := func() error {
if value, ok := s.ResourceID.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: "ResourceID",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *ScriptID) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
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(s.ScriptID)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "ScriptID",
Error: err,
})
}
if len(failures) > 0 { if len(failures) > 0 {
return &validate.Error{Fields: failures} return &validate.Error{Fields: failures}
} }
@@ -183,6 +548,26 @@ func (s *ScriptPolicy) Validate() error {
} }
var failures []validate.FieldError var failures []validate.FieldError
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(s.ID)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "ID",
Error: err,
})
}
if err := func() error { if err := func() error {
if err := (validate.String{ if err := (validate.String{
MinLength: 16, MinLength: 16,
@@ -202,6 +587,150 @@ func (s *ScriptPolicy) Validate() error {
Error: err, Error: 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(s.ToScriptID)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "ToScriptID",
Error: 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(s.Policy)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Policy",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *ScriptPolicyCreate) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
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(s.FromScriptID)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "FromScriptID",
Error: 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(s.ToScriptID)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "ToScriptID",
Error: 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(s.Policy)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Policy",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *ScriptPolicyID) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
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(s.ScriptPolicyID)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "ScriptPolicyID",
Error: err,
})
}
if len(failures) > 0 { if len(failures) > 0 {
return &validate.Error{Fields: failures} return &validate.Error{Fields: failures}
} }
@@ -214,6 +743,46 @@ func (s *SubmissionCreate) Validate() error {
} }
var failures []validate.FieldError var failures []validate.FieldError
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(s.OperationID)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "OperationID",
Error: 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(s.AssetOwner)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "AssetOwner",
Error: err,
})
}
if err := func() error { if err := func() error {
if err := (validate.String{ if err := (validate.String{
MinLength: 0, MinLength: 0,
@@ -252,6 +821,118 @@ func (s *SubmissionCreate) Validate() error {
Error: err, Error: 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(s.GameID)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "GameID",
Error: 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(s.AssetID)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "AssetID",
Error: 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(s.AssetVersion)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "AssetVersion",
Error: err,
})
}
if err := func() error {
if err := (validate.Int{
MinSet: true,
Min: 0,
MaxSet: true,
Max: 9,
MinExclusive: false,
MaxExclusive: false,
MultipleOfSet: false,
MultipleOf: 0,
}).Validate(int64(s.Status)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Status",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *SubmissionID) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
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(s.SubmissionID)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "SubmissionID",
Error: err,
})
}
if len(failures) > 0 { if len(failures) > 0 {
return &validate.Error{Fields: failures} return &validate.Error{Fields: failures}
} }

59
pkg/model/audit_event.go Normal file
View File

@@ -0,0 +1,59 @@
package model
import (
"encoding/json"
"time"
)
type AuditEventType int32
// User clicked "Submit", "Accept" etc
const AuditEventTypeAction AuditEventType = 0
type AuditEventDataAction struct {
TargetStatus uint32 `json:"target_status"`
}
// User wrote a comment
const AuditEventTypeComment AuditEventType = 1
type AuditEventDataComment struct {
Comment string `json:"comment"`
}
// User changed Model
const AuditEventTypeChangeModel AuditEventType = 2
type AuditEventDataChangeModel struct {
OldModelID uint64 `json:"old_model_id"`
OldModelVersion uint64 `json:"old_model_version"`
NewModelID uint64 `json:"new_model_id"`
NewModelVersion uint64 `json:"new_model_version"`
}
// Validator validates model
const AuditEventTypeChangeValidatedModel AuditEventType = 3
type AuditEventDataChangeValidatedModel struct {
ValidatedModelID uint64 `json:"validated_model_id"`
ValidatedModelVersion uint64 `json:"validated_model_version"`
}
// User changed DisplayName / Creator
const AuditEventTypeChangeDisplayName AuditEventType = 4
const AuditEventTypeChangeCreator AuditEventType = 5
type AuditEventDataChangeName struct {
OldName string `json:"old_name"`
NewName string `json:"new_name"`
}
// Validator had an error
const AuditEventTypeError AuditEventType = 6
type AuditEventDataError struct {
Error string `json:"error"`
}
type AuditEvent struct {
ID int64 `gorm:"primaryKey"`
CreatedAt time.Time
User uint64
ResourceType ResourceType // is this a submission or is it a mapfix
ResourceID int64 // submission / mapfix / map ID
EventType AuditEventType
EventData json.RawMessage `gorm:"type:jsonb"`
}

View File

@@ -5,36 +5,39 @@ import "time"
type MapfixStatus int32 type MapfixStatus int32
const ( const (
// Phase: Final MapfixStatus // Phase: Creation
MapfixStatusRejected MapfixStatus = 8 MapfixStatusUnderConstruction MapfixStatus = 0
MapfixStatusUploaded MapfixStatus = 7 // uploaded to the group, final status for mapfixes MapfixStatusChangesRequested MapfixStatus = 1
// Phase: Review
MapfixStatusSubmitting MapfixStatus = 2
MapfixStatusSubmitted MapfixStatus = 3
// Phase: Testing // Phase: Testing
MapfixStatusUploading MapfixStatus = 6 MapfixStatusAcceptedUnvalidated MapfixStatus = 4 // pending script review, can re-trigger validation
MapfixStatusValidated MapfixStatus = 5 MapfixStatusValidating MapfixStatus = 5
MapfixStatusValidating MapfixStatus = 4 MapfixStatusValidated MapfixStatus = 6
MapfixStatusAccepted MapfixStatus = 3 // pending script review, can re-trigger validation MapfixStatusUploading MapfixStatus = 7
// Phase: Creation // Phase: Final MapfixStatus
MapfixStatusChangesRequested MapfixStatus = 2 MapfixStatusUploaded MapfixStatus = 8 // uploaded to the group, but pending release
MapfixStatusSubmitted MapfixStatus = 1 MapfixStatusRejected MapfixStatus = 9
MapfixStatusUnderConstruction MapfixStatus = 0
) )
type Mapfix struct { type Mapfix struct {
ID int64 `gorm:"primaryKey"` ID int64 `gorm:"primaryKey"`
DisplayName string DisplayName string
Creator string Creator string
GameID int32 GameID uint32
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
Submitter int64 // UserID Submitter uint64 // UserID
AssetID int64 AssetID uint64
AssetVersion int64 AssetVersion uint64
ValidatedAssetID int64 ValidatedAssetID uint64
ValidatedAssetVersion int64 ValidatedAssetVersion uint64
Completed bool // Has this version of the map been completed at least once on maptest Completed bool // Has this version of the map been completed at least once on maptest
TargetAssetID int64 // where to upload map fix. if the TargetAssetID is 0, it's a new map. TargetAssetID uint64 // where to upload map fix. if the TargetAssetID is 0, it's a new map.
StatusID MapfixStatus StatusID MapfixStatus
StatusMessage string Description string // mapfix description
} }

View File

@@ -7,42 +7,59 @@ package model
type CreateSubmissionRequest struct { type CreateSubmissionRequest struct {
// operation_id is passed back in the response message // operation_id is passed back in the response message
OperationID int32 OperationID int32
ModelID int64 ModelID uint64
DisplayName string
Creator string
GameID uint32
Status uint32
Roles uint32
} }
type CreateMapfixRequest struct { type CreateMapfixRequest struct {
OperationID int32 OperationID int32
ModelID int64 ModelID uint64
TargetAssetID int64 TargetAssetID uint64
Description string
}
type CheckSubmissionRequest struct{
SubmissionID int64
ModelID uint64
}
type CheckMapfixRequest struct{
MapfixID int64
ModelID uint64
} }
type ValidateSubmissionRequest struct { type ValidateSubmissionRequest struct {
// submission_id is passed back in the response message // submission_id is passed back in the response message
SubmissionID int64 SubmissionID int64
ModelID int64 ModelID uint64
ModelVersion int64 ModelVersion uint64
ValidatedModelID *int64 // optional value ValidatedModelID *uint64 // optional value
} }
type ValidateMapfixRequest struct { type ValidateMapfixRequest struct {
MapfixID int64 MapfixID int64
ModelID int64 ModelID uint64
ModelVersion int64 ModelVersion uint64
ValidatedModelID *int64 // optional value ValidatedModelID *uint64 // optional value
} }
// Create a new map // Create a new map
type UploadSubmissionRequest struct { type UploadSubmissionRequest struct {
SubmissionID int64 SubmissionID int64
ModelID int64 ModelID uint64
ModelVersion int64 ModelVersion uint64
ModelName string ModelName string
} }
type UploadMapfixRequest struct { type UploadMapfixRequest struct {
MapfixID int64 MapfixID int64
ModelID int64 ModelID uint64
ModelVersion int64 ModelVersion uint64
TargetAssetID int64 TargetAssetID uint64
} }

View File

@@ -12,7 +12,7 @@ const (
type Operation struct { type Operation struct {
ID int32 `gorm:"primaryKey"` ID int32 `gorm:"primaryKey"`
CreatedAt time.Time CreatedAt time.Time
Owner int64 // UserID Owner uint64 // UserID
StatusID OperationStatus StatusID OperationStatus
StatusMessage string StatusMessage string
Path string // redirect to view completed operation e.g. "/mapfixes/4" Path string // redirect to view completed operation e.g. "/mapfixes/4"

View File

@@ -5,37 +5,39 @@ import "time"
type SubmissionStatus int32 type SubmissionStatus int32
const ( const (
// Phase: Final SubmissionStatus // Phase: Creation
SubmissionStatusReleased SubmissionStatus = 9 SubmissionStatusUnderConstruction SubmissionStatus = 0
SubmissionStatusRejected SubmissionStatus = 8 SubmissionStatusChangesRequested SubmissionStatus = 1
// Phase: Review
SubmissionStatusSubmitting SubmissionStatus = 2
SubmissionStatusSubmitted SubmissionStatus = 3
// Phase: Testing // Phase: Testing
SubmissionStatusUploaded SubmissionStatus = 7 // uploaded to the group, but pending release SubmissionStatusAcceptedUnvalidated SubmissionStatus = 4 // pending script review, can re-trigger validation
SubmissionStatusUploading SubmissionStatus = 6 SubmissionStatusValidating SubmissionStatus = 5
SubmissionStatusValidated SubmissionStatus = 5 SubmissionStatusValidated SubmissionStatus = 6
SubmissionStatusValidating SubmissionStatus = 4 SubmissionStatusUploading SubmissionStatus = 7
SubmissionStatusAccepted SubmissionStatus = 3 // pending script review, can re-trigger validation SubmissionStatusUploaded SubmissionStatus = 8 // uploaded to the group, but pending release
// Phase: Creation // Phase: Final SubmissionStatus
SubmissionStatusChangesRequested SubmissionStatus = 2 SubmissionStatusRejected SubmissionStatus = 9
SubmissionStatusSubmitted SubmissionStatus = 1 SubmissionStatusReleased SubmissionStatus = 10
SubmissionStatusUnderConstruction SubmissionStatus = 0
) )
type Submission struct { type Submission struct {
ID int64 `gorm:"primaryKey"` ID int64 `gorm:"primaryKey"`
DisplayName string DisplayName string
Creator string Creator string
GameID int32 GameID uint32
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
Submitter int64 // UserID Submitter uint64 // UserID
AssetID int64 AssetID uint64
AssetVersion int64 AssetVersion uint64
ValidatedAssetID int64 ValidatedAssetID uint64
ValidatedAssetVersion int64 ValidatedAssetVersion uint64
Completed bool // Has this version of the map been completed at least once on maptest Completed bool // Has this version of the map been completed at least once on maptest
UploadedAssetID int64 // where to upload map fix. if the TargetAssetID is 0, it's a new map. UploadedAssetID uint64 // where to upload map fix. if the TargetAssetID is 0, it's a new map.
StatusID SubmissionStatus StatusID SubmissionStatus
StatusMessage string
} }

271
pkg/service/audit_events.go Normal file
View File

@@ -0,0 +1,271 @@
package service
import (
"context"
"encoding/json"
"git.itzana.me/strafesnet/go-grpc/users"
"git.itzana.me/strafesnet/maps-service/pkg/api"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
"git.itzana.me/strafesnet/maps-service/pkg/model"
)
// CreateMapfixAuditComment implements createMapfixAuditComment operation.
//
// Post a comment to the audit log
//
// POST /mapfixes/{MapfixID}/comment
func (svc *Service) CreateMapfixAuditComment(ctx context.Context, req api.CreateMapfixAuditCommentReq, params api.CreateMapfixAuditCommentParams) (error) {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return ErrUserInfo
}
has_role, err := userInfo.HasRoleMapfixReview()
if err != nil {
return err
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
}
if !has_role {
// Submitter has special permission to comment on their mapfix
mapfix, err := svc.DB.Mapfixes().Get(ctx, params.MapfixID)
if err != nil {
return err
}
if mapfix.Submitter != userId {
return ErrPermissionDeniedNeedRoleMapfixReview
}
}
data := []byte{}
_, err = req.Read(data)
if err != nil {
return err
}
Comment := string(data)
event_data := model.AuditEventDataComment{
Comment: Comment,
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceMapfix,
ResourceID: params.MapfixID,
EventType: model.AuditEventTypeComment,
EventData: EventData,
})
if err != nil {
return err
}
return nil
}
// ListMapfixAuditEvents invokes listMapfixAuditEvents operation.
//
// Retrieve a list of audit events.
//
// GET /mapfixes/{MapfixID}/audit-events
func (svc *Service) ListMapfixAuditEvents(ctx context.Context, params api.ListMapfixAuditEventsParams) ([]api.AuditEvent, error) {
filter := datastore.Optional()
filter.Add("resource_type", model.ResourceMapfix)
filter.Add("resource_id", params.MapfixID)
items, err := svc.DB.AuditEvents().List(ctx, filter, model.Page{
Number: params.Page,
Size: params.Limit,
})
if err != nil {
return nil, err
}
idMap := make(map[int64]bool)
for _, item := range items {
idMap[int64(item.User)] = true
}
var idList users.IdList
idList.ID = make([]int64,len(idMap))
for userId := range idMap {
idList.ID = append(idList.ID, userId)
}
userList, err := svc.Users.GetList(ctx, &idList)
if err != nil {
return nil, err
}
userMap := make(map[int64]*users.UserResponse)
for _,user := range userList.Users {
userMap[user.ID] = user
}
var resp []api.AuditEvent
for _, item := range items {
EventData := api.AuditEventEventData{}
err = EventData.UnmarshalJSON(item.EventData)
if err != nil {
return nil, err
}
username := ""
if userMap[int64(item.User)] != nil {
username = userMap[int64(item.User)].Username
}
resp = append(resp, api.AuditEvent{
ID: item.ID,
Date: item.CreatedAt.Unix(),
User: int64(item.User),
Username: username,
ResourceType: int32(item.ResourceType),
ResourceID: item.ResourceID,
EventType: int32(item.EventType),
EventData: EventData,
})
}
return resp, nil
}
// CreateSubmissionAuditComment implements createSubmissionAuditComment operation.
//
// Post a comment to the audit log
//
// POST /submissions/{SubmissionID}/comment
func (svc *Service) CreateSubmissionAuditComment(ctx context.Context, req api.CreateSubmissionAuditCommentReq, params api.CreateSubmissionAuditCommentParams) (error) {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return ErrUserInfo
}
has_role, err := userInfo.HasRoleSubmissionReview()
if err != nil {
return err
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
}
if !has_role {
// Submitter has special permission to comment on their submission
submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID)
if err != nil {
return err
}
if submission.Submitter != userId {
return ErrPermissionDeniedNeedRoleSubmissionReview
}
}
data := []byte{}
_, err = req.Read(data)
if err != nil {
return err
}
Comment := string(data)
event_data := model.AuditEventDataComment{
Comment: Comment,
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeComment,
EventData: EventData,
})
if err != nil {
return err
}
return nil
}
// ListSubmissionAuditEvents invokes listSubmissionAuditEvents operation.
//
// Retrieve a list of audit events.
//
// GET /submissions/{SubmissionID}/audit-events
func (svc *Service) ListSubmissionAuditEvents(ctx context.Context, params api.ListSubmissionAuditEventsParams) ([]api.AuditEvent, error) {
filter := datastore.Optional()
filter.Add("resource_type", model.ResourceSubmission)
filter.Add("resource_id", params.SubmissionID)
items, err := svc.DB.AuditEvents().List(ctx, filter, model.Page{
Number: params.Page,
Size: params.Limit,
})
if err != nil {
return nil, err
}
idMap := make(map[int64]bool)
for _, item := range items {
idMap[int64(item.User)] = true
}
var idList users.IdList
idList.ID = make([]int64,len(idMap))
for userId := range idMap {
idList.ID = append(idList.ID, userId)
}
userList, err := svc.Users.GetList(ctx, &idList)
if err != nil {
return nil, err
}
userMap := make(map[int64]*users.UserResponse)
for _,user := range userList.Users {
userMap[user.ID] = user
}
var resp []api.AuditEvent
for _, item := range items {
EventData := api.AuditEventEventData{}
err = EventData.UnmarshalJSON(item.EventData)
if err != nil {
return nil, err
}
username := ""
if userMap[int64(item.User)] != nil {
username = userMap[int64(item.User)].Username
}
resp = append(resp, api.AuditEvent{
ID: item.ID,
Date: item.CreatedAt.Unix(),
User: int64(item.User),
Username: username,
ResourceType: int32(item.ResourceType),
ResourceID: item.ResourceID,
EventType: int32(item.EventType),
EventData: EventData,
})
}
return resp, nil
}

View File

@@ -25,18 +25,29 @@ var(
model.MapfixStatusUploading, model.MapfixStatusUploading,
model.MapfixStatusValidated, model.MapfixStatusValidated,
model.MapfixStatusValidating, model.MapfixStatusValidating,
model.MapfixStatusAccepted, model.MapfixStatusAcceptedUnvalidated,
} }
// Allow 5 mapfixes every 10 minutes
CreateMapfixRateLimit int64 = 5
CreateMapfixRecencyWindow = time.Second*600
) )
var ( var (
ErrCreationPhaseMapfixesLimit = errors.New("Active mapfixes limited to 20") ErrCreationPhaseMapfixesLimit = errors.New("Active mapfixes limited to 20")
ErrActiveMapfixSameTargetAssetID = errors.New("There is an active mapfix with the same TargetAssetID") ErrActiveMapfixSameTargetAssetID = errors.New("There is an active mapfix with the same TargetAssetID")
ErrAcceptOwnMapfix = fmt.Errorf("%w: You cannot accept your own mapfix as the submitter", ErrPermissionDenied) ErrAcceptOwnMapfix = fmt.Errorf("%w: You cannot accept your own mapfix as the submitter", ErrPermissionDenied)
ErrCreateMapfixRateLimit = errors.New("You must not create more than 5 mapfixes every 10 minutes")
) )
// POST /mapfixes // POST /mapfixes
func (svc *Service) CreateMapfix(ctx context.Context, request *api.MapfixTriggerCreate) (*api.OperationID, error) { func (svc *Service) CreateMapfix(ctx context.Context, request *api.MapfixTriggerCreate) (*api.OperationID, error) {
// sanitization
if request.AssetID<0 || request.TargetAssetID<0{
return nil, ErrNegativeID
}
var ModelID=uint64(request.AssetID);
var TargetAssetID=uint64(request.TargetAssetID);
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return nil, ErrUserInfo return nil, ErrUserInfo
@@ -67,7 +78,7 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *api.MapfixTrigger
// Check if TargetAssetID actually exists // Check if TargetAssetID actually exists
{ {
_, err := svc.Client.Get(ctx, &maps.IdMessage{ _, err := svc.Maps.Get(ctx, &maps.IdMessage{
ID: request.TargetAssetID, ID: request.TargetAssetID,
}) })
if err != nil { if err != nil {
@@ -76,8 +87,23 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *api.MapfixTrigger
} }
} }
// Check if too many operations have been created recently
{
count, err := svc.DB.Operations().CountSince(ctx,
int64(userId),
time.Now().Add(-CreateMapfixRecencyWindow),
)
if err != nil {
return nil, err
}
if CreateMapfixRateLimit < count {
return nil, ErrCreateMapfixRateLimit
}
}
operation, err := svc.DB.Operations().Create(ctx, model.Operation{ operation, err := svc.DB.Operations().Create(ctx, model.Operation{
Owner: int64(userId), Owner: userId,
StatusID: model.OperationStatusCreated, StatusID: model.OperationStatusCreated,
}) })
if err != nil { if err != nil {
@@ -86,8 +112,9 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *api.MapfixTrigger
create_request := model.CreateMapfixRequest{ create_request := model.CreateMapfixRequest{
OperationID: operation.ID, OperationID: operation.ID,
ModelID: request.AssetID, ModelID: ModelID,
TargetAssetID: request.TargetAssetID, TargetAssetID: TargetAssetID,
Description: request.Description,
} }
j, err := json.Marshal(create_request) j, err := json.Marshal(create_request)
@@ -95,7 +122,10 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *api.MapfixTrigger
return nil, err return nil, err
} }
svc.Nats.Publish("maptest.mapfixes.create", []byte(j)) _, err = svc.Nats.Publish("maptest.mapfixes.create", []byte(j))
if err != nil {
return nil, err
}
return &api.OperationID{ return &api.OperationID{
OperationID: operation.ID, OperationID: operation.ID,
@@ -116,7 +146,7 @@ func (svc *Service) GetMapfix(ctx context.Context, params api.GetMapfixParams) (
ID: mapfix.ID, ID: mapfix.ID,
DisplayName: mapfix.DisplayName, DisplayName: mapfix.DisplayName,
Creator: mapfix.Creator, Creator: mapfix.Creator,
GameID: mapfix.GameID, GameID: int32(mapfix.GameID),
CreatedAt: mapfix.CreatedAt.Unix(), CreatedAt: mapfix.CreatedAt.Unix(),
UpdatedAt: mapfix.UpdatedAt.Unix(), UpdatedAt: mapfix.UpdatedAt.Unix(),
Submitter: int64(mapfix.Submitter), Submitter: int64(mapfix.Submitter),
@@ -125,7 +155,7 @@ func (svc *Service) GetMapfix(ctx context.Context, params api.GetMapfixParams) (
Completed: mapfix.Completed, Completed: mapfix.Completed,
TargetAssetID: int64(mapfix.TargetAssetID), TargetAssetID: int64(mapfix.TargetAssetID),
StatusID: int32(mapfix.StatusID), StatusID: int32(mapfix.StatusID),
StatusMessage: mapfix.StatusMessage, Description: mapfix.Description,
}, nil }, nil
} }
@@ -134,7 +164,7 @@ func (svc *Service) GetMapfix(ctx context.Context, params api.GetMapfixParams) (
// Get list of mapfixes. // Get list of mapfixes.
// //
// GET /mapfixes // GET /mapfixes
func (svc *Service) ListMapfixes(ctx context.Context, params api.ListMapfixesParams) ([]api.Mapfix, error) { func (svc *Service) ListMapfixes(ctx context.Context, params api.ListMapfixesParams) (*api.Mapfixes, error) {
filter := datastore.Optional() filter := datastore.Optional()
if params.DisplayName.IsSet(){ if params.DisplayName.IsSet(){
@@ -146,10 +176,22 @@ func (svc *Service) ListMapfixes(ctx context.Context, params api.ListMapfixesPar
if params.GameID.IsSet(){ if params.GameID.IsSet(){
filter.Add("game_id", params.GameID.Value) filter.Add("game_id", params.GameID.Value)
} }
if params.Submitter.IsSet(){
filter.Add("submitter", params.Submitter.Value)
}
if params.AssetID.IsSet(){
filter.Add("asset_id", params.AssetID.Value)
}
if params.TargetAssetID.IsSet(){
filter.Add("target_asset_id", params.TargetAssetID.Value)
}
if params.StatusID.IsSet(){
filter.Add("status_id", params.StatusID.Value)
}
sort := datastore.ListSort(params.Sort.Or(int32(datastore.ListSortDisabled))) sort := datastore.ListSort(params.Sort.Or(int32(datastore.ListSortDisabled)))
items, err := svc.DB.Mapfixes().List(ctx, filter, model.Page{ total, items, err := svc.DB.Mapfixes().ListWithTotal(ctx, filter, model.Page{
Number: params.Page, Number: params.Page,
Size: params.Limit, Size: params.Limit,
},sort) },sort)
@@ -157,13 +199,14 @@ func (svc *Service) ListMapfixes(ctx context.Context, params api.ListMapfixesPar
return nil, err return nil, err
} }
var resp []api.Mapfix var resp api.Mapfixes
resp.Total=total
for _, item := range items { for _, item := range items {
resp = append(resp, api.Mapfix{ resp.Mapfixes = append(resp.Mapfixes, api.Mapfix{
ID: item.ID, ID: item.ID,
DisplayName: item.DisplayName, DisplayName: item.DisplayName,
Creator: item.Creator, Creator: item.Creator,
GameID: item.GameID, GameID: int32(item.GameID),
CreatedAt: item.CreatedAt.Unix(), CreatedAt: item.CreatedAt.Unix(),
UpdatedAt: item.UpdatedAt.Unix(), UpdatedAt: item.UpdatedAt.Unix(),
Submitter: int64(item.Submitter), Submitter: int64(item.Submitter),
@@ -172,10 +215,11 @@ func (svc *Service) ListMapfixes(ctx context.Context, params api.ListMapfixesPar
Completed: item.Completed, Completed: item.Completed,
TargetAssetID: int64(item.TargetAssetID), TargetAssetID: int64(item.TargetAssetID),
StatusID: int32(item.StatusID), StatusID: int32(item.StatusID),
Description: item.Description,
}) })
} }
return resp, nil return &resp, nil
} }
// PatchMapfixCompleted implements patchMapfixCompleted operation. // PatchMapfixCompleted implements patchMapfixCompleted operation.
@@ -220,22 +264,58 @@ func (svc *Service) UpdateMapfixModel(ctx context.Context, params api.UpdateMapf
return err return err
} }
has_role, err := userInfo.IsSubmitter(uint64(mapfix.Submitter)) userId, err := userInfo.GetUserID()
if err != nil { if err != nil {
return err return err
} }
// check if caller is the submitter // check if caller is the submitter
has_role := userId == mapfix.Submitter
if !has_role { if !has_role {
return ErrPermissionDeniedNotSubmitter return ErrPermissionDeniedNotSubmitter
} }
OldModelID := mapfix.AssetID
OldModelVersion := mapfix.AssetVersion
NewModelID := uint64(params.ModelID)
NewModelVersion := uint64(params.ModelVersion)
// check if Status is ChangesRequested|Submitted|UnderConstruction // check if Status is ChangesRequested|Submitted|UnderConstruction
pmap := datastore.Optional() pmap := datastore.Optional()
pmap.AddNotNil("asset_id", params.ModelID) pmap.Add("asset_id", NewModelID)
pmap.AddNotNil("asset_version", params.VersionID) pmap.Add("asset_version", NewModelVersion)
//always reset completed when model changes //always reset completed when model changes
pmap.Add("completed", false) pmap.Add("completed", false)
return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusChangesRequested, model.MapfixStatusSubmitted, model.MapfixStatusUnderConstruction}, pmap) err = svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusChangesRequested, model.MapfixStatusSubmitted, model.MapfixStatusUnderConstruction}, pmap)
if err != nil {
return err
}
event_data := model.AuditEventDataChangeModel{
OldModelID: OldModelID,
OldModelVersion: OldModelVersion,
NewModelID: NewModelID,
NewModelVersion: NewModelVersion,
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceMapfix,
ResourceID: params.MapfixID,
EventType: model.AuditEventTypeChangeModel,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }
// ActionMapfixReject invokes actionMapfixReject operation. // ActionMapfixReject invokes actionMapfixReject operation.
@@ -255,13 +335,45 @@ func (svc *Service) ActionMapfixReject(ctx context.Context, params api.ActionMap
} }
// check if caller has required role // check if caller has required role
if !has_role { if !has_role {
return ErrPermissionDeniedNeedRoleMapReview return ErrPermissionDeniedNeedRoleMapfixReview
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
} }
// transaction // transaction
target_status := model.MapfixStatusRejected
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.MapfixStatusRejected) smap.Add("status_id", target_status)
return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitted}, smap) err = svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitted}, smap)
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceMapfix,
ResourceID: params.MapfixID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }
// ActionMapfixRequestChanges invokes actionMapfixRequestChanges operation. // ActionMapfixRequestChanges invokes actionMapfixRequestChanges operation.
@@ -281,13 +393,13 @@ func (svc *Service) ActionMapfixRequestChanges(ctx context.Context, params api.A
} }
// check if caller has required role // check if caller has required role
if !has_role { if !has_role {
return ErrPermissionDeniedNeedRoleMapReview return ErrPermissionDeniedNeedRoleMapfixReview
} }
// transaction // transaction
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.MapfixStatusChangesRequested) smap.Add("status_id", model.MapfixStatusChangesRequested)
return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidated, model.MapfixStatusAccepted, model.MapfixStatusSubmitted}, smap) return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidated, model.MapfixStatusAcceptedUnvalidated, model.MapfixStatusSubmitted}, smap)
} }
// ActionMapfixRevoke invokes actionMapfixRevoke operation. // ActionMapfixRevoke invokes actionMapfixRevoke operation.
@@ -307,27 +419,56 @@ func (svc *Service) ActionMapfixRevoke(ctx context.Context, params api.ActionMap
return err return err
} }
has_role, err := userInfo.IsSubmitter(uint64(mapfix.Submitter)) userId, err := userInfo.GetUserID()
if err != nil { if err != nil {
return err return err
} }
// check if caller is the submitter // check if caller is the submitter
has_role := userId == mapfix.Submitter
if !has_role { if !has_role {
return ErrPermissionDeniedNotSubmitter return ErrPermissionDeniedNotSubmitter
} }
// transaction // transaction
target_status := model.MapfixStatusUnderConstruction
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.MapfixStatusUnderConstruction) smap.Add("status_id", target_status)
return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitted, model.MapfixStatusChangesRequested}, smap) err = svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitted, model.MapfixStatusChangesRequested}, smap)
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceMapfix,
ResourceID: params.MapfixID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }
// ActionMapfixSubmit invokes actionMapfixSubmit operation. // ActionMapfixTriggerSubmit invokes actionMapfixTriggerSubmit operation.
// //
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted. // Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting.
// //
// POST /mapfixes/{MapfixID}/status/submit // POST /mapfixes/{MapfixID}/status/trigger-submit
func (svc *Service) ActionMapfixSubmit(ctx context.Context, params api.ActionMapfixSubmitParams) error { func (svc *Service) ActionMapfixTriggerSubmit(ctx context.Context, params api.ActionMapfixTriggerSubmitParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return ErrUserInfo return ErrUserInfo
@@ -339,19 +480,198 @@ func (svc *Service) ActionMapfixSubmit(ctx context.Context, params api.ActionMap
return err return err
} }
has_role, err := userInfo.IsSubmitter(uint64(mapfix.Submitter)) userId, err := userInfo.GetUserID()
if err != nil { if err != nil {
return err return err
} }
// check if caller is the submitter // check if caller is the submitter
has_role := userId == mapfix.Submitter
if !has_role { if !has_role {
return ErrPermissionDeniedNotSubmitter return ErrPermissionDeniedNotSubmitter
} }
// transaction // transaction
target_status := model.MapfixStatusSubmitting
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.MapfixStatusSubmitted) smap.Add("status_id", target_status)
return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUnderConstruction, model.MapfixStatusChangesRequested}, smap) err = svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUnderConstruction, model.MapfixStatusChangesRequested}, smap)
if err != nil {
return err
}
validate_request := model.CheckMapfixRequest{
MapfixID: mapfix.ID,
ModelID: mapfix.AssetID,
}
j, err := json.Marshal(validate_request)
if err != nil {
return err
}
_, err = svc.Nats.Publish("maptest.mapfixes.check", []byte(j))
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceMapfix,
ResourceID: params.MapfixID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
}
// ActionMapfixBypassSubmit invokes actionMapfixBypassSubmit operation.
//
// Role Reviewer changes status from ChangesRequested -> Submitted.
//
// POST /mapfixes/{MapfixID}/status/bypass-submit
func (svc *Service) ActionMapfixBypassSubmit(ctx context.Context, params api.ActionMapfixBypassSubmitParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return ErrUserInfo
}
// read mapfix (this could be done with a transaction WHERE clause)
mapfix, err := svc.DB.Mapfixes().Get(ctx, params.MapfixID)
if err != nil {
return err
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
}
// check if caller is the submitter
is_submitter := userId == mapfix.Submitter
if is_submitter {
return ErrAcceptOwnMapfix
}
has_mapfix_review, err := userInfo.HasRoleMapfixReview()
if err != nil {
return err
}
if !has_mapfix_review {
return ErrPermissionDeniedNeedRoleMapfixReview
}
// transaction
target_status := model.MapfixStatusSubmitted
smap := datastore.Optional()
smap.Add("status_id", target_status)
err = svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusChangesRequested}, smap)
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceMapfix,
ResourceID: params.MapfixID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
}
// ActionMapfixResetSubmitting implements actionMapfixResetSubmitting operation.
//
// Role MapfixReview changes status from Submitting -> UnderConstruction.
//
// POST /mapfixes/{MapfixID}/status/reset-submitting
func (svc *Service) ActionMapfixResetSubmitting(ctx context.Context, params api.ActionMapfixResetSubmittingParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return ErrUserInfo
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
}
// check when mapfix was updated
mapfix, err := svc.DB.Mapfixes().Get(ctx, params.MapfixID)
if err != nil {
return err
}
if time.Now().Before(mapfix.UpdatedAt.Add(time.Second*10)) {
// the last time the mapfix was updated must be longer than 10 seconds ago
return ErrDelayReset
}
// check if caller has required role
has_role := userId == mapfix.Submitter
if !has_role {
return ErrPermissionDeniedNotSubmitter
}
// transaction
target_status := model.MapfixStatusUnderConstruction
smap := datastore.Optional()
smap.Add("status_id", target_status)
err = svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitting}, smap)
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceMapfix,
ResourceID: params.MapfixID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }
// ActionMapfixTriggerUpload invokes actionMapfixTriggerUpload operation. // ActionMapfixTriggerUpload invokes actionMapfixTriggerUpload operation.
@@ -371,12 +691,18 @@ func (svc *Service) ActionMapfixTriggerUpload(ctx context.Context, params api.Ac
} }
// check if caller has required role // check if caller has required role
if !has_role { if !has_role {
return ErrPermissionDeniedNeedRoleMapUpload return ErrPermissionDeniedNeedRoleMapfixUpload
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
} }
// transaction // transaction
target_status := model.MapfixStatusUploading
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.MapfixStatusUploading) smap.Add("status_id", target_status)
mapfix, err := svc.DB.Mapfixes().IfStatusThenUpdateAndGet(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidated}, smap) mapfix, err := svc.DB.Mapfixes().IfStatusThenUpdateAndGet(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidated}, smap)
if err != nil { if err != nil {
return err return err
@@ -395,7 +721,31 @@ func (svc *Service) ActionMapfixTriggerUpload(ctx context.Context, params api.Ac
return err return err
} }
svc.Nats.Publish("maptest.mapfixes.uploadfix", []byte(j)) _, err = svc.Nats.Publish("maptest.mapfixes.upload", []byte(j))
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceMapfix,
ResourceID: params.MapfixID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil return nil
} }
@@ -417,7 +767,12 @@ func (svc *Service) ActionMapfixValidated(ctx context.Context, params api.Action
} }
// check if caller has required role // check if caller has required role
if !has_role { if !has_role {
return ErrPermissionDeniedNeedRoleMapUpload return ErrPermissionDeniedNeedRoleMapfixUpload
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
} }
// check when mapfix was updated // check when mapfix was updated
@@ -431,9 +786,36 @@ func (svc *Service) ActionMapfixValidated(ctx context.Context, params api.Action
} }
// transaction // transaction
target_status := model.MapfixStatusValidated
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.MapfixStatusValidated) smap.Add("status_id", target_status)
return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUploading}, smap) err = svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUploading}, smap)
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceMapfix,
ResourceID: params.MapfixID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }
// ActionMapfixTriggerValidate invokes actionMapfixTriggerValidate operation. // ActionMapfixTriggerValidate invokes actionMapfixTriggerValidate operation.
@@ -453,7 +835,7 @@ func (svc *Service) ActionMapfixTriggerValidate(ctx context.Context, params api.
} }
// check if caller has required role // check if caller has required role
if !has_role { if !has_role {
return ErrPermissionDeniedNeedRoleMapReview return ErrPermissionDeniedNeedRoleMapfixReview
} }
// read mapfix (this could be done with a transaction WHERE clause) // read mapfix (this could be done with a transaction WHERE clause)
@@ -462,11 +844,13 @@ func (svc *Service) ActionMapfixTriggerValidate(ctx context.Context, params api.
return err return err
} }
has_role, err = userInfo.IsSubmitter(uint64(mapfix.Submitter)) userId, err := userInfo.GetUserID()
if err != nil { if err != nil {
return err return err
} }
// check if caller is NOT the submitter // check if caller is NOT the submitter
has_role = userId == mapfix.Submitter
if has_role { if has_role {
return ErrAcceptOwnMapfix return ErrAcceptOwnMapfix
} }
@@ -489,8 +873,9 @@ func (svc *Service) ActionMapfixTriggerValidate(ctx context.Context, params api.
} }
// transaction // transaction
target_status := model.MapfixStatusValidating
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.MapfixStatusValidating) smap.Add("status_id", target_status)
mapfix, err = svc.DB.Mapfixes().IfStatusThenUpdateAndGet(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitted}, smap) mapfix, err = svc.DB.Mapfixes().IfStatusThenUpdateAndGet(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitted}, smap)
if err != nil { if err != nil {
return err return err
@@ -513,7 +898,31 @@ func (svc *Service) ActionMapfixTriggerValidate(ctx context.Context, params api.
return err return err
} }
svc.Nats.Publish("maptest.mapfixes.validate", []byte(j)) _, err = svc.Nats.Publish("maptest.mapfixes.validate", []byte(j))
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceMapfix,
ResourceID: params.MapfixID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil return nil
} }
@@ -535,13 +944,19 @@ func (svc *Service) ActionMapfixRetryValidate(ctx context.Context, params api.Ac
} }
// check if caller has required role // check if caller has required role
if !has_role { if !has_role {
return ErrPermissionDeniedNeedRoleMapReview return ErrPermissionDeniedNeedRoleMapfixReview
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
} }
// transaction // transaction
target_status := model.MapfixStatusValidating
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.MapfixStatusValidating) smap.Add("status_id", target_status)
mapfix, err := svc.DB.Mapfixes().IfStatusThenUpdateAndGet(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusAccepted}, smap) mapfix, err := svc.DB.Mapfixes().IfStatusThenUpdateAndGet(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusAcceptedUnvalidated}, smap)
if err != nil { if err != nil {
return err return err
} }
@@ -563,7 +978,31 @@ func (svc *Service) ActionMapfixRetryValidate(ctx context.Context, params api.Ac
return err return err
} }
svc.Nats.Publish("maptest.mapfixes.validate", []byte(j)) _, err = svc.Nats.Publish("maptest.mapfixes.validate", []byte(j))
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceMapfix,
ResourceID: params.MapfixID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil return nil
} }
@@ -585,7 +1024,12 @@ func (svc *Service) ActionMapfixAccepted(ctx context.Context, params api.ActionM
} }
// check if caller has required role // check if caller has required role
if !has_role { if !has_role {
return ErrPermissionDeniedNeedRoleMapReview return ErrPermissionDeniedNeedRoleMapfixReview
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
} }
// check when mapfix was updated // check when mapfix was updated
@@ -599,8 +1043,34 @@ func (svc *Service) ActionMapfixAccepted(ctx context.Context, params api.ActionM
} }
// transaction // transaction
target_status := model.MapfixStatusAcceptedUnvalidated
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.MapfixStatusAccepted) smap.Add("status_id", target_status)
smap.Add("status_message", "Manually forced reset") err = svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, smap)
return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, smap) if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceMapfix,
ResourceID: params.MapfixID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }

View File

@@ -25,7 +25,7 @@ func (svc *Service) ListMaps(ctx context.Context, params api.ListMapsParams) ([]
filter.GameID = &params.GameID.Value filter.GameID = &params.GameID.Value
} }
mapList, err := svc.Client.List(ctx, &maps.ListRequest{ mapList, err := svc.Maps.List(ctx, &maps.ListRequest{
Filter: &filter, Filter: &filter,
Page: &maps.Pagination{ Page: &maps.Pagination{
Size: params.Limit, Size: params.Limit,
@@ -56,7 +56,7 @@ func (svc *Service) ListMaps(ctx context.Context, params api.ListMapsParams) ([]
// //
// GET /maps/{MapID} // GET /maps/{MapID}
func (svc *Service) GetMap(ctx context.Context, params api.GetMapParams) (*api.Map, error) { func (svc *Service) GetMap(ctx context.Context, params api.GetMapParams) (*api.Map, error) {
mapResponse, err := svc.Client.Get(ctx, &maps.IdMessage{ mapResponse, err := svc.Maps.Get(ctx, &maps.IdMessage{
ID: params.MapID, ID: params.MapID,
}) })
if err != nil { if err != nil {

View File

@@ -24,11 +24,13 @@ func (svc *Service) GetOperation(ctx context.Context, params api.GetOperationPar
return nil, err return nil, err
} }
has_role, err := userInfo.IsSubmitter(uint64(operation.Owner)) userId, err := userInfo.GetUserID()
if err != nil { if err != nil {
return nil, err return nil, err
} }
// check if caller is operation owner
// check if caller is the submitter
has_role := userId == operation.Owner
if !has_role { if !has_role {
return nil, ErrPermissionDeniedNotSubmitter return nil, ErrPermissionDeniedNotSubmitter
} }
@@ -36,7 +38,7 @@ func (svc *Service) GetOperation(ctx context.Context, params api.GetOperationPar
return &api.Operation{ return &api.Operation{
OperationID: operation.ID, OperationID: operation.ID,
Date: operation.CreatedAt.Unix(), Date: operation.CreatedAt.Unix(),
Owner: operation.Owner, Owner: int64(operation.Owner),
Status: int32(operation.StatusID), Status: int32(operation.StatusID),
StatusMessage: operation.StatusMessage, StatusMessage: operation.StatusMessage,
Path: operation.Path, Path: operation.Path,

View File

@@ -63,13 +63,13 @@ func (svc *Service) ListScriptPolicy(ctx context.Context, params api.ListScriptP
if err != nil { if err != nil {
return nil, err return nil, err
} }
filter.AddNotNil("from_script_hash", int64(hash)) // No type safety! filter.Add("from_script_hash", int64(hash)) // No type safety!
} }
if params.ToScriptID.IsSet(){ if params.ToScriptID.IsSet(){
filter.AddNotNil("to_script_id", params.ToScriptID.Value) filter.Add("to_script_id", params.ToScriptID.Value)
} }
if params.Policy.IsSet(){ if params.Policy.IsSet(){
filter.AddNotNil("policy", params.Policy.Value) filter.Add("policy", params.Policy.Value)
} }
items, err := svc.DB.ScriptPolicy().List(ctx, filter, model.Page{ items, err := svc.DB.ScriptPolicy().List(ctx, filter, model.Page{
@@ -121,13 +121,6 @@ func (svc *Service) DeleteScriptPolicy(ctx context.Context, params api.DeleteScr
// //
// GET /script-policy/{ScriptPolicyID} // GET /script-policy/{ScriptPolicyID}
func (svc *Service) GetScriptPolicy(ctx context.Context, params api.GetScriptPolicyParams) (*api.ScriptPolicy, error) { func (svc *Service) GetScriptPolicy(ctx context.Context, params api.GetScriptPolicyParams) (*api.ScriptPolicy, error) {
_, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return nil, ErrUserInfo
}
// Read permission for script policy only requires you to be logged in
policy, err := svc.DB.ScriptPolicy().Get(ctx, params.ScriptPolicyID) policy, err := svc.DB.ScriptPolicy().Get(ctx, params.ScriptPolicyID)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -57,19 +57,19 @@ func (svc *Service) ListScripts(ctx context.Context, params api.ListScriptsParam
if err != nil { if err != nil {
return nil, err return nil, err
} }
filter.AddNotNil("hash", int64(hash)) // No type safety! filter.Add("hash", int64(hash)) // No type safety!
} }
if params.Name.IsSet(){ if params.Name.IsSet(){
filter.AddNotNil("name", params.Name.Value) filter.Add("name", params.Name.Value)
} }
if params.Source.IsSet(){ if params.Source.IsSet(){
filter.AddNotNil("source", params.Source.Value) filter.Add("source", params.Source.Value)
} }
if params.ResourceType.IsSet(){ if params.ResourceType.IsSet(){
filter.AddNotNil("resource_type", params.ResourceType.Value) filter.Add("resource_type", params.ResourceType.Value)
} }
if params.ResourceID.IsSet(){ if params.ResourceID.IsSet(){
filter.AddNotNil("resource_id", params.ResourceID.Value) filter.Add("resource_id", params.ResourceID.Value)
} }
items, err := svc.DB.Scripts().List(ctx, filter, model.Page{ items, err := svc.DB.Scripts().List(ctx, filter, model.Page{
@@ -122,13 +122,6 @@ func (svc *Service) DeleteScript(ctx context.Context, params api.DeleteScriptPar
// //
// GET /scripts/{ScriptID} // GET /scripts/{ScriptID}
func (svc *Service) GetScript(ctx context.Context, params api.GetScriptParams) (*api.Script, error) { func (svc *Service) GetScript(ctx context.Context, params api.GetScriptParams) (*api.Script, error) {
_, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return nil, ErrUserInfo
}
// Read permission for scripts only requires you to be logged in
script, err := svc.DB.Scripts().Get(ctx, params.ScriptID) script, err := svc.DB.Scripts().Get(ctx, params.ScriptID)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -88,13 +88,6 @@ func (usr UserInfoHandle) Validate() (bool, error) {
} }
return validate.Valid, nil return validate.Valid, nil
} }
func (usr UserInfoHandle) IsSubmitter(submitter uint64) (bool, error) {
userId, err := usr.GetUserID()
if err != nil {
return false, err
}
return userId == submitter, nil
}
func (usr UserInfoHandle) hasRoles(wantRoles Roles) (bool, error) { func (usr UserInfoHandle) hasRoles(wantRoles Roles) (bool, error) {
haveroles, err := usr.GetRoles() haveroles, err := usr.GetRoles()
if err != nil { if err != nil {

View File

@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"git.itzana.me/strafesnet/go-grpc/maps" "git.itzana.me/strafesnet/go-grpc/maps"
"git.itzana.me/strafesnet/go-grpc/users"
"git.itzana.me/strafesnet/maps-service/pkg/api" "git.itzana.me/strafesnet/maps-service/pkg/api"
"git.itzana.me/strafesnet/maps-service/pkg/datastore" "git.itzana.me/strafesnet/maps-service/pkg/datastore"
"github.com/nats-io/nats.go" "github.com/nats-io/nats.go"
@@ -19,17 +20,21 @@ var (
ErrDelayReset = errors.New("Please give the validator at least 10 seconds to operate before attempting to reset the status") ErrDelayReset = errors.New("Please give the validator at least 10 seconds to operate before attempting to reset the status")
ErrPermissionDeniedNotSubmitter = fmt.Errorf("%w: You must be the submitter to perform this action", ErrPermissionDenied) ErrPermissionDeniedNotSubmitter = fmt.Errorf("%w: You must be the submitter to perform this action", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleSubmissionRelease = fmt.Errorf("%w: Need Role SubmissionRelease", ErrPermissionDenied) ErrPermissionDeniedNeedRoleSubmissionRelease = fmt.Errorf("%w: Need Role SubmissionRelease", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleMapUpload = fmt.Errorf("%w: Need Role MapUpload", ErrPermissionDenied) ErrPermissionDeniedNeedRoleMapfixUpload = fmt.Errorf("%w: Need Role MapfixUpload", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleMapReview = fmt.Errorf("%w: Need Role MapReview", ErrPermissionDenied) ErrPermissionDeniedNeedRoleMapfixReview = fmt.Errorf("%w: Need Role MapfixReview", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleSubmissionUpload = fmt.Errorf("%w: Need Role SubmissionUpload", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleSubmissionReview = fmt.Errorf("%w: Need Role SubmissionReview", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleMapDownload = fmt.Errorf("%w: Need Role MapDownload", ErrPermissionDenied) ErrPermissionDeniedNeedRoleMapDownload = fmt.Errorf("%w: Need Role MapDownload", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleScriptWrite = fmt.Errorf("%w: Need Role ScriptWrite", ErrPermissionDenied) ErrPermissionDeniedNeedRoleScriptWrite = fmt.Errorf("%w: Need Role ScriptWrite", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleMaptest = fmt.Errorf("%w: Need Role Maptest", ErrPermissionDenied) ErrPermissionDeniedNeedRoleMaptest = fmt.Errorf("%w: Need Role Maptest", ErrPermissionDenied)
ErrNegativeID = errors.New("A negative ID was provided")
) )
type Service struct { type Service struct {
DB datastore.Datastore DB datastore.Datastore
Nats nats.JetStreamContext Nats nats.JetStreamContext
Client maps.MapsServiceClient Maps maps.MapsServiceClient
Users users.UsersServiceClient
} }
// NewError creates *ErrorStatusCode from error returned by handler. // NewError creates *ErrorStatusCode from error returned by handler.

View File

@@ -20,13 +20,16 @@ var(
model.SubmissionStatusSubmitted, model.SubmissionStatusSubmitted,
model.SubmissionStatusUnderConstruction, model.SubmissionStatusUnderConstruction,
} }
// limit mapfixes in the pipeline to one per target map // limit submissions in the pipeline to one per target map
ActiveAcceptedSubmissionStatuses = []model.SubmissionStatus{ ActiveAcceptedSubmissionStatuses = []model.SubmissionStatus{
model.SubmissionStatusUploading, model.SubmissionStatusUploading,
model.SubmissionStatusValidated, model.SubmissionStatusValidated,
model.SubmissionStatusValidating, model.SubmissionStatusValidating,
model.SubmissionStatusAccepted, model.SubmissionStatusAcceptedUnvalidated,
} }
// Allow 5 submissions every 10 minutes
CreateSubmissionRateLimit int64 = 5
CreateSubmissionRecencyWindow = time.Second*600
) )
var ( var (
@@ -35,10 +38,17 @@ var (
ErrReleaseInvalidStatus = errors.New("Only submissions with Uploaded status can be released") ErrReleaseInvalidStatus = errors.New("Only submissions with Uploaded status can be released")
ErrReleaseNoUploadedAssetID = errors.New("Only submissions with a UploadedAssetID can be released") ErrReleaseNoUploadedAssetID = errors.New("Only submissions with a UploadedAssetID can be released")
ErrAcceptOwnSubmission = fmt.Errorf("%w: You cannot accept your own submission as the submitter", ErrPermissionDenied) ErrAcceptOwnSubmission = fmt.Errorf("%w: You cannot accept your own submission as the submitter", ErrPermissionDenied)
ErrCreateSubmissionRateLimit = errors.New("You must not create more than 5 submissions every 10 minutes")
) )
// POST /submissions // POST /submissions
func (svc *Service) CreateSubmission(ctx context.Context, request *api.SubmissionTriggerCreate) (*api.OperationID, error) { func (svc *Service) CreateSubmission(ctx context.Context, request *api.SubmissionTriggerCreate) (*api.OperationID, error) {
// sanitization
if request.AssetID<0{
return nil, ErrNegativeID
}
var ModelID=uint64(request.AssetID);
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return nil, ErrUserInfo return nil, ErrUserInfo
@@ -66,8 +76,24 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *api.Submissio
return nil, ErrCreationPhaseSubmissionsLimit return nil, ErrCreationPhaseSubmissionsLimit
} }
} }
// Check if too many operations have been created recently
{
count, err := svc.DB.Operations().CountSince(ctx,
int64(userId),
time.Now().Add(-CreateSubmissionRecencyWindow),
)
if err != nil {
return nil, err
}
if CreateSubmissionRateLimit < count {
return nil, ErrCreateSubmissionRateLimit
}
}
operation, err := svc.DB.Operations().Create(ctx, model.Operation{ operation, err := svc.DB.Operations().Create(ctx, model.Operation{
Owner: int64(userId), Owner: userId,
StatusID: model.OperationStatusCreated, StatusID: model.OperationStatusCreated,
}) })
if err != nil { if err != nil {
@@ -75,8 +101,13 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *api.Submissio
} }
create_request := model.CreateSubmissionRequest{ create_request := model.CreateSubmissionRequest{
OperationID: operation.ID, OperationID: operation.ID,
ModelID: request.AssetID, ModelID: ModelID,
DisplayName: request.DisplayName,
Creator: request.Creator,
GameID: uint32(request.GameID),
Status: uint32(model.SubmissionStatusUnderConstruction),
Roles: uint32(RolesEmpty),
} }
j, err := json.Marshal(create_request) j, err := json.Marshal(create_request)
@@ -84,7 +115,86 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *api.Submissio
return nil, err return nil, err
} }
svc.Nats.Publish("maptest.submissions.create", []byte(j)) _, err = svc.Nats.Publish("maptest.submissions.create", []byte(j))
if err != nil {
return nil, err
}
return &api.OperationID{
OperationID: operation.ID,
}, nil
}
// POST /submissions-admin
func (svc *Service) CreateSubmissionAdmin(ctx context.Context, request *api.SubmissionTriggerCreate) (*api.OperationID, error) {
// sanitization
if request.AssetID<0{
return nil, ErrNegativeID
}
var ModelID=uint64(request.AssetID);
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return nil, ErrUserInfo
}
userId, err := userInfo.GetUserID()
if err != nil {
return nil, err
}
roles, err := userInfo.GetRoles()
if err != nil {
return nil, err
}
// check if caller has required role
has_role := roles & RolesSubmissionReview == RolesSubmissionReview
if !has_role {
return nil, ErrPermissionDeniedNeedRoleSubmissionReview
}
// Check if too many operations have been created recently
{
count, err := svc.DB.Operations().CountSince(ctx,
int64(userId),
time.Now().Add(-CreateSubmissionRecencyWindow),
)
if err != nil {
return nil, err
}
if CreateSubmissionRateLimit < count {
return nil, ErrCreateSubmissionRateLimit
}
}
operation, err := svc.DB.Operations().Create(ctx, model.Operation{
Owner: userId,
StatusID: model.OperationStatusCreated,
})
if err != nil {
return nil, err
}
create_request := model.CreateSubmissionRequest{
OperationID: operation.ID,
ModelID: ModelID,
DisplayName: request.DisplayName,
Creator: request.Creator,
GameID: uint32(request.GameID),
Status: uint32(model.SubmissionStatusChangesRequested),
Roles: uint32(roles),
}
j, err := json.Marshal(create_request)
if err != nil {
return nil, err
}
_, err = svc.Nats.Publish("maptest.submissions.create", []byte(j))
if err != nil {
return nil, err
}
return &api.OperationID{ return &api.OperationID{
OperationID: operation.ID, OperationID: operation.ID,
@@ -105,7 +215,7 @@ func (svc *Service) GetSubmission(ctx context.Context, params api.GetSubmissionP
ID: submission.ID, ID: submission.ID,
DisplayName: submission.DisplayName, DisplayName: submission.DisplayName,
Creator: submission.Creator, Creator: submission.Creator,
GameID: submission.GameID, GameID: int32(submission.GameID),
CreatedAt: submission.CreatedAt.Unix(), CreatedAt: submission.CreatedAt.Unix(),
UpdatedAt: submission.UpdatedAt.Unix(), UpdatedAt: submission.UpdatedAt.Unix(),
Submitter: int64(submission.Submitter), Submitter: int64(submission.Submitter),
@@ -114,7 +224,6 @@ func (svc *Service) GetSubmission(ctx context.Context, params api.GetSubmissionP
Completed: submission.Completed, Completed: submission.Completed,
UploadedAssetID: api.NewOptInt64(int64(submission.UploadedAssetID)), UploadedAssetID: api.NewOptInt64(int64(submission.UploadedAssetID)),
StatusID: int32(submission.StatusID), StatusID: int32(submission.StatusID),
StatusMessage: submission.StatusMessage,
}, nil }, nil
} }
@@ -123,7 +232,7 @@ func (svc *Service) GetSubmission(ctx context.Context, params api.GetSubmissionP
// Get list of submissions. // Get list of submissions.
// //
// GET /submissions // GET /submissions
func (svc *Service) ListSubmissions(ctx context.Context, params api.ListSubmissionsParams) ([]api.Submission, error) { func (svc *Service) ListSubmissions(ctx context.Context, params api.ListSubmissionsParams) (*api.Submissions, error) {
filter := datastore.Optional() filter := datastore.Optional()
if params.DisplayName.IsSet(){ if params.DisplayName.IsSet(){
@@ -135,10 +244,22 @@ func (svc *Service) ListSubmissions(ctx context.Context, params api.ListSubmissi
if params.GameID.IsSet(){ if params.GameID.IsSet(){
filter.Add("game_id", params.GameID.Value) filter.Add("game_id", params.GameID.Value)
} }
if params.Submitter.IsSet(){
filter.Add("submitter", params.Submitter.Value)
}
if params.AssetID.IsSet(){
filter.Add("asset_id", params.AssetID.Value)
}
if params.UploadedAssetID.IsSet(){
filter.Add("uploaded_asset_id", params.UploadedAssetID.Value)
}
if params.StatusID.IsSet(){
filter.Add("status_id", params.StatusID.Value)
}
sort := datastore.ListSort(params.Sort.Or(int32(datastore.ListSortDisabled))) sort := datastore.ListSort(params.Sort.Or(int32(datastore.ListSortDisabled)))
items, err := svc.DB.Submissions().List(ctx, filter, model.Page{ total, items, err := svc.DB.Submissions().ListWithTotal(ctx, filter, model.Page{
Number: params.Page, Number: params.Page,
Size: params.Limit, Size: params.Limit,
},sort) },sort)
@@ -146,13 +267,14 @@ func (svc *Service) ListSubmissions(ctx context.Context, params api.ListSubmissi
return nil, err return nil, err
} }
var resp []api.Submission var resp api.Submissions
resp.Total=total
for _, item := range items { for _, item := range items {
resp = append(resp, api.Submission{ resp.Submissions = append(resp.Submissions, api.Submission{
ID: item.ID, ID: item.ID,
DisplayName: item.DisplayName, DisplayName: item.DisplayName,
Creator: item.Creator, Creator: item.Creator,
GameID: item.GameID, GameID: int32(item.GameID),
CreatedAt: item.CreatedAt.Unix(), CreatedAt: item.CreatedAt.Unix(),
UpdatedAt: item.UpdatedAt.Unix(), UpdatedAt: item.UpdatedAt.Unix(),
Submitter: int64(item.Submitter), Submitter: int64(item.Submitter),
@@ -164,7 +286,7 @@ func (svc *Service) ListSubmissions(ctx context.Context, params api.ListSubmissi
}) })
} }
return resp, nil return &resp, nil
} }
// PatchSubmissionCompleted implements patchSubmissionCompleted operation. // PatchSubmissionCompleted implements patchSubmissionCompleted operation.
@@ -209,22 +331,58 @@ func (svc *Service) UpdateSubmissionModel(ctx context.Context, params api.Update
return err return err
} }
has_role, err := userInfo.IsSubmitter(uint64(submission.Submitter)) userId, err := userInfo.GetUserID()
if err != nil { if err != nil {
return err return err
} }
// check if caller is the submitter // check if caller is the submitter
has_role := userId == submission.Submitter
if !has_role { if !has_role {
return ErrPermissionDeniedNotSubmitter return ErrPermissionDeniedNotSubmitter
} }
OldModelID := submission.AssetID
OldModelVersion := submission.AssetVersion
NewModelID := uint64(params.ModelID)
NewModelVersion := uint64(params.ModelVersion)
// check if Status is ChangesRequested|Submitted|UnderConstruction // check if Status is ChangesRequested|Submitted|UnderConstruction
pmap := datastore.Optional() pmap := datastore.Optional()
pmap.AddNotNil("asset_id", params.ModelID) pmap.Add("asset_id", NewModelID)
pmap.AddNotNil("asset_version", params.VersionID) pmap.Add("asset_version", NewModelVersion)
//always reset completed when model changes //always reset completed when model changes
pmap.Add("completed", false) pmap.Add("completed", false)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusChangesRequested, model.SubmissionStatusSubmitted, model.SubmissionStatusUnderConstruction}, pmap) err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusChangesRequested, model.SubmissionStatusUnderConstruction}, pmap)
if err != nil {
return err
}
event_data := model.AuditEventDataChangeModel{
OldModelID: OldModelID,
OldModelVersion: OldModelVersion,
NewModelID: NewModelID,
NewModelVersion: NewModelVersion,
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceSubmission,
ResourceID: submission.ID,
EventType: model.AuditEventTypeChangeModel,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }
// ActionSubmissionReject invokes actionSubmissionReject operation. // ActionSubmissionReject invokes actionSubmissionReject operation.
@@ -244,13 +402,45 @@ func (svc *Service) ActionSubmissionReject(ctx context.Context, params api.Actio
} }
// check if caller has required role // check if caller has required role
if !has_role { if !has_role {
return ErrPermissionDeniedNeedRoleMapReview return ErrPermissionDeniedNeedRoleSubmissionReview
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
} }
// transaction // transaction
target_status := model.SubmissionStatusRejected
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.SubmissionStatusRejected) smap.Add("status_id", target_status)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitted}, smap) err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitted}, smap)
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }
// ActionSubmissionRequestChanges invokes actionSubmissionRequestChanges operation. // ActionSubmissionRequestChanges invokes actionSubmissionRequestChanges operation.
@@ -270,13 +460,45 @@ func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params a
} }
// check if caller has required role // check if caller has required role
if !has_role { if !has_role {
return ErrPermissionDeniedNeedRoleMapReview return ErrPermissionDeniedNeedRoleSubmissionReview
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
} }
// transaction // transaction
target_status := model.SubmissionStatusChangesRequested
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.SubmissionStatusChangesRequested) smap.Add("status_id", target_status)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidated, model.SubmissionStatusAccepted, model.SubmissionStatusSubmitted}, smap) err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidated, model.SubmissionStatusAcceptedUnvalidated, model.SubmissionStatusSubmitted}, smap)
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }
// ActionSubmissionRevoke invokes actionSubmissionRevoke operation. // ActionSubmissionRevoke invokes actionSubmissionRevoke operation.
@@ -296,27 +518,56 @@ func (svc *Service) ActionSubmissionRevoke(ctx context.Context, params api.Actio
return err return err
} }
has_role, err := userInfo.IsSubmitter(uint64(submission.Submitter)) userId, err := userInfo.GetUserID()
if err != nil { if err != nil {
return err return err
} }
// check if caller is the submitter // check if caller is the submitter
has_role := userId == submission.Submitter
if !has_role { if !has_role {
return ErrPermissionDeniedNotSubmitter return ErrPermissionDeniedNotSubmitter
} }
// transaction // transaction
target_status := model.SubmissionStatusUnderConstruction
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.SubmissionStatusUnderConstruction) smap.Add("status_id", target_status)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitted, model.SubmissionStatusChangesRequested}, smap) err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitted, model.SubmissionStatusChangesRequested}, smap)
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }
// ActionSubmissionSubmit invokes actionSubmissionSubmit operation. // ActionSubmissionTriggerSubmit invokes actionSubmissionTriggerSubmit operation.
// //
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted. // Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting.
// //
// POST /submissions/{SubmissionID}/status/submit // POST /submissions/{SubmissionID}/status/trigger-submit
func (svc *Service) ActionSubmissionSubmit(ctx context.Context, params api.ActionSubmissionSubmitParams) error { func (svc *Service) ActionSubmissionTriggerSubmit(ctx context.Context, params api.ActionSubmissionTriggerSubmitParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return ErrUserInfo return ErrUserInfo
@@ -328,19 +579,206 @@ func (svc *Service) ActionSubmissionSubmit(ctx context.Context, params api.Actio
return err return err
} }
has_role, err := userInfo.IsSubmitter(uint64(submission.Submitter)) userId, err := userInfo.GetUserID()
if err != nil { if err != nil {
return err return err
} }
// check if caller is the submitter // check if caller is the submitter
is_submitter := userId == submission.Submitter
// neither = deny
if !is_submitter {
has_submission_review, err := userInfo.HasRoleSubmissionReview()
if err != nil {
return err
}
if !has_submission_review {
return ErrPermissionDeniedNotSubmitter
}
}
// transaction
target_status := model.SubmissionStatusSubmitting
smap := datastore.Optional()
smap.Add("status_id", target_status)
err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUnderConstruction, model.SubmissionStatusChangesRequested}, smap)
if err != nil {
return err
}
validate_request := model.CheckSubmissionRequest{
SubmissionID: submission.ID,
ModelID: submission.AssetID,
}
j, err := json.Marshal(validate_request)
if err != nil {
return err
}
_, err = svc.Nats.Publish("maptest.submissions.check", []byte(j))
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
}
// ActionSubmissionBypassSubmit invokes actionSubmissionBypassSubmit operation.
//
// Role Reviewer changes status from ChangesRequested -> Submitted.
//
// POST /submissions/{SubmissionID}/status/bypass-submit
func (svc *Service) ActionSubmissionBypassSubmit(ctx context.Context, params api.ActionSubmissionBypassSubmitParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return ErrUserInfo
}
// read submission (this could be done with a transaction WHERE clause)
submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID)
if err != nil {
return err
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
}
// check if caller is the submitter
is_submitter := userId == submission.Submitter
if is_submitter {
return ErrAcceptOwnSubmission
}
has_submission_review, err := userInfo.HasRoleSubmissionReview()
if err != nil {
return err
}
if !has_submission_review {
return ErrPermissionDeniedNeedRoleSubmissionReview
}
// transaction
target_status := model.SubmissionStatusSubmitted
smap := datastore.Optional()
smap.Add("status_id", target_status)
err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusChangesRequested}, smap)
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
}
// ActionSubmissionResetSubmitting implements actionSubmissionResetSubmitting operation.
//
// Role SubmissionReview changes status from Submitting -> UnderConstruction.
//
// POST /submissions/{SubmissionID}/status/reset-submitting
func (svc *Service) ActionSubmissionResetSubmitting(ctx context.Context, params api.ActionSubmissionResetSubmittingParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return ErrUserInfo
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
}
// check when submission was updated
submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID)
if err != nil {
return err
}
if time.Now().Before(submission.UpdatedAt.Add(time.Second*10)) {
// the last time the submission was updated must be longer than 10 seconds ago
return ErrDelayReset
}
// check if caller has required role
has_role := userId == submission.Submitter
if !has_role { if !has_role {
return ErrPermissionDeniedNotSubmitter return ErrPermissionDeniedNotSubmitter
} }
// transaction // transaction
target_status := model.SubmissionStatusUnderConstruction
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.SubmissionStatusSubmitted) smap.Add("status_id", target_status)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUnderConstruction, model.SubmissionStatusChangesRequested}, smap) err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitting}, smap)
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }
// ActionSubmissionTriggerUpload invokes actionSubmissionTriggerUpload operation. // ActionSubmissionTriggerUpload invokes actionSubmissionTriggerUpload operation.
@@ -360,12 +798,18 @@ func (svc *Service) ActionSubmissionTriggerUpload(ctx context.Context, params ap
} }
// check if caller has required role // check if caller has required role
if !has_role { if !has_role {
return ErrPermissionDeniedNeedRoleMapUpload return ErrPermissionDeniedNeedRoleSubmissionUpload
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
} }
// transaction // transaction
target_status := model.SubmissionStatusUploading
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.SubmissionStatusUploading) smap.Add("status_id", target_status)
submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidated}, smap) submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidated}, smap)
if err != nil { if err != nil {
return err return err
@@ -387,12 +831,36 @@ func (svc *Service) ActionSubmissionTriggerUpload(ctx context.Context, params ap
return err return err
} }
svc.Nats.Publish("maptest.submissions.upload", []byte(j)) _, err = svc.Nats.Publish("maptest.submissions.upload", []byte(j))
if err != nil {
return err
}
} else { } else {
// refuse to operate // refuse to operate
return ErrUploadedAssetIDAlreadyExists return ErrUploadedAssetIDAlreadyExists
} }
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil return nil
} }
@@ -413,7 +881,12 @@ func (svc *Service) ActionSubmissionValidated(ctx context.Context, params api.Ac
} }
// check if caller has required role // check if caller has required role
if !has_role { if !has_role {
return ErrPermissionDeniedNeedRoleMapUpload return ErrPermissionDeniedNeedRoleSubmissionUpload
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
} }
// check when submission was updated // check when submission was updated
@@ -427,9 +900,36 @@ func (svc *Service) ActionSubmissionValidated(ctx context.Context, params api.Ac
} }
// transaction // transaction
target_status := model.SubmissionStatusValidated
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.SubmissionStatusValidated) smap.Add("status_id", target_status)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUploading}, smap) err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUploading}, smap)
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }
// ActionSubmissionTriggerValidate invokes actionSubmissionTriggerValidate operation. // ActionSubmissionTriggerValidate invokes actionSubmissionTriggerValidate operation.
@@ -449,7 +949,7 @@ func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params
} }
// check if caller has required role // check if caller has required role
if !has_role { if !has_role {
return ErrPermissionDeniedNeedRoleMapReview return ErrPermissionDeniedNeedRoleSubmissionReview
} }
// read submission (this could be done with a transaction WHERE clause) // read submission (this could be done with a transaction WHERE clause)
@@ -458,18 +958,21 @@ func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params
return err return err
} }
has_role, err = userInfo.IsSubmitter(uint64(submission.Submitter)) userId, err := userInfo.GetUserID()
if err != nil { if err != nil {
return err return err
} }
// check if caller is NOT the submitter // check if caller is NOT the submitter
has_role = userId == submission.Submitter
if has_role { if has_role {
return ErrAcceptOwnSubmission return ErrAcceptOwnSubmission
} }
// transaction // transaction
target_status := model.SubmissionStatusValidating
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.SubmissionStatusValidating) smap.Add("status_id", target_status)
submission, err = svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitted}, smap) submission, err = svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitted}, smap)
if err != nil { if err != nil {
return err return err
@@ -492,7 +995,31 @@ func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params
return err return err
} }
svc.Nats.Publish("maptest.submissions.validate", []byte(j)) _, err = svc.Nats.Publish("maptest.submissions.validate", []byte(j))
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil return nil
} }
@@ -514,13 +1041,19 @@ func (svc *Service) ActionSubmissionRetryValidate(ctx context.Context, params ap
} }
// check if caller has required role // check if caller has required role
if !has_role { if !has_role {
return ErrPermissionDeniedNeedRoleMapReview return ErrPermissionDeniedNeedRoleSubmissionReview
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
} }
// transaction // transaction
target_status := model.SubmissionStatusValidating
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.SubmissionStatusValidating) smap.Add("status_id", target_status)
submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusAccepted}, smap) submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusAcceptedUnvalidated}, smap)
if err != nil { if err != nil {
return err return err
} }
@@ -542,7 +1075,31 @@ func (svc *Service) ActionSubmissionRetryValidate(ctx context.Context, params ap
return err return err
} }
svc.Nats.Publish("maptest.submissions.validate", []byte(j)) _, err = svc.Nats.Publish("maptest.submissions.validate", []byte(j))
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil return nil
} }
@@ -564,7 +1121,12 @@ func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params api.Act
} }
// check if caller has required role // check if caller has required role
if !has_role { if !has_role {
return ErrPermissionDeniedNeedRoleMapReview return ErrPermissionDeniedNeedRoleSubmissionReview
}
userId, err := userInfo.GetUserID()
if err != nil {
return err
} }
// check when submission was updated // check when submission was updated
@@ -578,10 +1140,36 @@ func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params api.Act
} }
// transaction // transaction
target_status := model.SubmissionStatusAcceptedUnvalidated
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.SubmissionStatusAccepted) smap.Add("status_id", target_status)
smap.Add("status_message", "Manually forced reset") err = svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap) if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }
// ReleaseSubmissions invokes releaseSubmissions operation. // ReleaseSubmissions invokes releaseSubmissions operation.
@@ -627,12 +1215,13 @@ func (svc *Service) ReleaseSubmissions(ctx context.Context, request []api.Releas
for i,submission := range submissions{ for i,submission := range submissions{
date := request[i].Date.Unix() date := request[i].Date.Unix()
var GameID = int32(submission.GameID)
// create each map with go-grpc // create each map with go-grpc
_, err := svc.Client.Create(ctx, &maps.MapRequest{ _, err := svc.Maps.Create(ctx, &maps.MapRequest{
ID: submission.UploadedAssetID, ID: int64(submission.UploadedAssetID),
DisplayName: &submission.DisplayName, DisplayName: &submission.DisplayName,
Creator: &submission.Creator, Creator: &submission.Creator,
GameID: &submission.GameID, GameID: &GameID,
Date: &date, Date: &date,
}) })
if err != nil { if err != nil {

View File

@@ -2,6 +2,7 @@ package service_internal
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
@@ -16,7 +17,7 @@ var(
model.MapfixStatusUploading, model.MapfixStatusUploading,
model.MapfixStatusValidated, model.MapfixStatusValidated,
model.MapfixStatusValidating, model.MapfixStatusValidating,
model.MapfixStatusAccepted, model.MapfixStatusAcceptedUnvalidated,
model.MapfixStatusChangesRequested, model.MapfixStatusChangesRequested,
model.MapfixStatusSubmitted, model.MapfixStatusSubmitted,
model.MapfixStatusUnderConstruction, model.MapfixStatusUnderConstruction,
@@ -34,13 +35,148 @@ var(
// //
// POST /mapfixes/{MapfixID}/validated-model // POST /mapfixes/{MapfixID}/validated-model
func (svc *Service) UpdateMapfixValidatedModel(ctx context.Context, params internal.UpdateMapfixValidatedModelParams) error { func (svc *Service) UpdateMapfixValidatedModel(ctx context.Context, params internal.UpdateMapfixValidatedModelParams) error {
ValidatedModelID := uint64(params.ValidatedModelID)
ValidatedModelVersion := uint64(params.ValidatedModelVersion)
// check if Status is ChangesRequested|Submitted|UnderConstruction // check if Status is ChangesRequested|Submitted|UnderConstruction
pmap := datastore.Optional() pmap := datastore.Optional()
pmap.AddNotNil("validated_asset_id", params.ValidatedModelID) pmap.Add("validated_asset_id", ValidatedModelID)
pmap.AddNotNil("validated_asset_version", params.ValidatedModelVersion) pmap.Add("validated_asset_version", ValidatedModelVersion)
// DO NOT reset completed when validated model is updated // DO NOT reset completed when validated model is updated
// pmap.Add("completed", false) // pmap.Add("completed", false)
return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, pmap) err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, pmap)
if err != nil {
return err
}
event_data := model.AuditEventDataChangeValidatedModel{
ValidatedModelID: ValidatedModelID,
ValidatedModelVersion: ValidatedModelVersion,
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: ValidtorUserID,
ResourceType: model.ResourceMapfix,
ResourceID: params.MapfixID,
EventType: model.AuditEventTypeChangeValidatedModel,
EventData: EventData,
})
if err != nil {
return err
}
return nil
}
// ActionMapfixSubmitted invokes actionMapfixSubmitted operation.
//
// Role Validator changes status from Submitting -> Submitted.
//
// POST /mapfixes/{MapfixID}/status/validator-submitted
func (svc *Service) ActionMapfixSubmitted(ctx context.Context, params internal.ActionMapfixSubmittedParams) error {
// transaction
target_status := model.MapfixStatusSubmitted
smap := datastore.Optional()
smap.Add("status_id", target_status)
smap.Add("asset_version", params.ModelVersion)
smap.Add("display_name", params.DisplayName)
smap.Add("creator", params.Creator)
smap.Add("game_id", params.GameID)
err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitting}, smap)
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: ValidtorUserID,
ResourceType: model.ResourceMapfix,
ResourceID: params.MapfixID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
}
// ActionMapfixRequestChanges implements actionMapfixRequestChanges operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> RequestChanges.
//
// POST /mapfixes/{MapfixID}/status/validator-request-changes
func (svc *Service) ActionMapfixRequestChanges(ctx context.Context, params internal.ActionMapfixRequestChangesParams) error {
// transaction
target_status := model.MapfixStatusChangesRequested
smap := datastore.Optional()
smap.Add("status_id", target_status)
err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitting}, smap)
if err != nil {
return err
}
{
event_data := model.AuditEventDataError{
Error: params.ErrorMessage,
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: ValidtorUserID,
ResourceType: model.ResourceMapfix,
ResourceID: params.MapfixID,
EventType: model.AuditEventTypeError,
EventData: EventData,
})
if err != nil {
return err
}
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: ValidtorUserID,
ResourceType: model.ResourceMapfix,
ResourceID: params.MapfixID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }
// ActionMapfixValidate invokes actionMapfixValidate operation. // ActionMapfixValidate invokes actionMapfixValidate operation.
@@ -62,10 +198,61 @@ func (svc *Service) ActionMapfixValidated(ctx context.Context, params internal.A
// POST /mapfixes/{MapfixID}/status/validator-failed // POST /mapfixes/{MapfixID}/status/validator-failed
func (svc *Service) ActionMapfixAccepted(ctx context.Context, params internal.ActionMapfixAcceptedParams) error { func (svc *Service) ActionMapfixAccepted(ctx context.Context, params internal.ActionMapfixAcceptedParams) error {
// transaction // transaction
target_status := model.MapfixStatusAcceptedUnvalidated
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.MapfixStatusAccepted) smap.Add("status_id", target_status)
smap.Add("status_message", params.StatusMessage) err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, smap)
return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, smap) if err != nil {
return err
}
//push an error audit event
{
event_data := model.AuditEventDataError{
Error: params.ErrorMessage,
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: ValidtorUserID,
ResourceType: model.ResourceMapfix,
ResourceID: params.MapfixID,
EventType: model.AuditEventTypeError,
EventData: EventData,
})
if err != nil {
return err
}
}
// push an action audit event
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: ValidtorUserID,
ResourceType: model.ResourceMapfix,
ResourceID: params.MapfixID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }
// ActionMapfixUploaded implements actionMapfixUploaded operation. // ActionMapfixUploaded implements actionMapfixUploaded operation.
@@ -75,13 +262,54 @@ func (svc *Service) ActionMapfixAccepted(ctx context.Context, params internal.Ac
// POST /mapfixes/{MapfixID}/status/validator-uploaded // POST /mapfixes/{MapfixID}/status/validator-uploaded
func (svc *Service) ActionMapfixUploaded(ctx context.Context, params internal.ActionMapfixUploadedParams) error { func (svc *Service) ActionMapfixUploaded(ctx context.Context, params internal.ActionMapfixUploadedParams) error {
// transaction // transaction
target_status := model.MapfixStatusUploaded
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.MapfixStatusUploaded) smap.Add("status_id", target_status)
return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUploading}, smap) err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUploading}, smap)
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: ValidtorUserID,
ResourceType: model.ResourceMapfix,
ResourceID: params.MapfixID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }
// POST /mapfixes // POST /mapfixes
func (svc *Service) CreateMapfix(ctx context.Context, request *internal.MapfixCreate) (*internal.MapfixID, error) { func (svc *Service) CreateMapfix(ctx context.Context, request *internal.MapfixCreate) (*internal.MapfixID, error) {
// sanitization
if request.GameID<0||
request.AssetOwner<0||
request.AssetID<0||
request.AssetVersion<0||
request.TargetAssetID<0{
return nil, ErrNegativeID
}
var GameID=uint32(request.GameID);
var Submitter=uint64(request.AssetOwner);
var AssetID=uint64(request.AssetID);
var AssetVersion=uint64(request.AssetVersion);
var TargetAssetID=uint64(request.TargetAssetID);
// Check if an active mapfix with the same asset id exists // Check if an active mapfix with the same asset id exists
{ {
filter := datastore.Optional() filter := datastore.Optional()
@@ -107,7 +335,7 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *internal.MapfixCr
// check if user owns asset // check if user owns asset
// TODO: allow bypass by admin // TODO: allow bypass by admin
if operation.Owner != request.AssetOwner { if operation.Owner != Submitter {
return nil, ErrNotAssetOwner return nil, ErrNotAssetOwner
} }
@@ -115,13 +343,14 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *internal.MapfixCr
ID: 0, ID: 0,
DisplayName: request.DisplayName, DisplayName: request.DisplayName,
Creator: request.Creator, Creator: request.Creator,
GameID: request.GameID, GameID: GameID,
Submitter: request.AssetOwner, Submitter: Submitter,
AssetID: request.AssetID, AssetID: AssetID,
AssetVersion: request.AssetVersion, AssetVersion: AssetVersion,
Completed: false, Completed: false,
TargetAssetID: request.TargetAssetID, TargetAssetID: TargetAssetID,
StatusID: model.MapfixStatusUnderConstruction, StatusID: model.MapfixStatusUnderConstruction,
Description: request.Description,
}) })
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -4,7 +4,7 @@ import (
"context" "context"
"git.itzana.me/strafesnet/maps-service/pkg/datastore" "git.itzana.me/strafesnet/maps-service/pkg/datastore"
"git.itzana.me/strafesnet/maps-service/pkg/internal" api "git.itzana.me/strafesnet/maps-service/pkg/internal"
"git.itzana.me/strafesnet/maps-service/pkg/model" "git.itzana.me/strafesnet/maps-service/pkg/model"
) )
@@ -49,13 +49,13 @@ func (svc *Service) ListScriptPolicy(ctx context.Context, params api.ListScriptP
if err != nil { if err != nil {
return nil, err return nil, err
} }
filter.AddNotNil("from_script_hash", int64(hash)) // No type safety! filter.Add("from_script_hash", int64(hash)) // No type safety!
} }
if params.ToScriptID.IsSet(){ if params.ToScriptID.IsSet(){
filter.AddNotNil("to_script_id", params.ToScriptID.Value) filter.Add("to_script_id", params.ToScriptID.Value)
} }
if params.Policy.IsSet(){ if params.Policy.IsSet(){
filter.AddNotNil("policy", params.Policy.Value) filter.Add("policy", params.Policy.Value)
} }
items, err := svc.DB.ScriptPolicy().List(ctx, filter, model.Page{ items, err := svc.DB.ScriptPolicy().List(ctx, filter, model.Page{

View File

@@ -4,7 +4,7 @@ import (
"context" "context"
"git.itzana.me/strafesnet/maps-service/pkg/datastore" "git.itzana.me/strafesnet/maps-service/pkg/datastore"
"git.itzana.me/strafesnet/maps-service/pkg/internal" api "git.itzana.me/strafesnet/maps-service/pkg/internal"
"git.itzana.me/strafesnet/maps-service/pkg/model" "git.itzana.me/strafesnet/maps-service/pkg/model"
) )
@@ -44,19 +44,19 @@ func (svc *Service) ListScripts(ctx context.Context, params api.ListScriptsParam
if err != nil { if err != nil {
return nil, err return nil, err
} }
filter.AddNotNil("hash", int64(hash)) // No type safety! filter.Add("hash", int64(hash)) // No type safety!
} }
if params.Name.IsSet(){ if params.Name.IsSet(){
filter.AddNotNil("name", params.Name.Value) filter.Add("name", params.Name.Value)
} }
if params.Source.IsSet(){ if params.Source.IsSet(){
filter.AddNotNil("source", params.Source.Value) filter.Add("source", params.Source.Value)
} }
if params.ResourceType.IsSet(){ if params.ResourceType.IsSet(){
filter.AddNotNil("resource_type", params.ResourceType.Value) filter.Add("resource_type", params.ResourceType.Value)
} }
if params.ResourceID.IsSet(){ if params.ResourceID.IsSet(){
filter.AddNotNil("resource_id", params.ResourceID.Value) filter.Add("resource_id", params.ResourceID.Value)
} }
items, err := svc.DB.Scripts().List(ctx, filter, model.Page{ items, err := svc.DB.Scripts().List(ctx, filter, model.Page{

View File

@@ -3,12 +3,21 @@ package service_internal
import ( import (
"context" "context"
"errors" "errors"
"math"
"git.itzana.me/strafesnet/maps-service/pkg/datastore" "git.itzana.me/strafesnet/maps-service/pkg/datastore"
internal "git.itzana.me/strafesnet/maps-service/pkg/internal" internal "git.itzana.me/strafesnet/maps-service/pkg/internal"
"github.com/nats-io/nats.go" "github.com/nats-io/nats.go"
) )
const (
ValidtorUserID uint64 = uint64(math.MaxInt64)
)
var (
ErrNegativeID = errors.New("A negative ID was provided")
)
type Service struct { type Service struct {
DB datastore.Datastore DB datastore.Datastore
Nats nats.JetStreamContext Nats nats.JetStreamContext

View File

@@ -2,12 +2,14 @@ package service_internal
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"git.itzana.me/strafesnet/maps-service/pkg/datastore" "git.itzana.me/strafesnet/maps-service/pkg/datastore"
internal "git.itzana.me/strafesnet/maps-service/pkg/internal" internal "git.itzana.me/strafesnet/maps-service/pkg/internal"
"git.itzana.me/strafesnet/maps-service/pkg/model" "git.itzana.me/strafesnet/maps-service/pkg/model"
"git.itzana.me/strafesnet/maps-service/pkg/service"
) )
var( var(
@@ -16,7 +18,7 @@ var(
model.SubmissionStatusUploading, model.SubmissionStatusUploading,
model.SubmissionStatusValidated, model.SubmissionStatusValidated,
model.SubmissionStatusValidating, model.SubmissionStatusValidating,
model.SubmissionStatusAccepted, model.SubmissionStatusAcceptedUnvalidated,
model.SubmissionStatusChangesRequested, model.SubmissionStatusChangesRequested,
model.SubmissionStatusSubmitted, model.SubmissionStatusSubmitted,
model.SubmissionStatusUnderConstruction, model.SubmissionStatusUnderConstruction,
@@ -33,13 +35,150 @@ var(
// //
// POST /submissions/{SubmissionID}/validated-model // POST /submissions/{SubmissionID}/validated-model
func (svc *Service) UpdateSubmissionValidatedModel(ctx context.Context, params internal.UpdateSubmissionValidatedModelParams) error { func (svc *Service) UpdateSubmissionValidatedModel(ctx context.Context, params internal.UpdateSubmissionValidatedModelParams) error {
ValidatedModelID := uint64(params.ValidatedModelID)
ValidatedModelVersion := uint64(params.ValidatedModelVersion)
// check if Status is ChangesRequested|Submitted|UnderConstruction // check if Status is ChangesRequested|Submitted|UnderConstruction
pmap := datastore.Optional() pmap := datastore.Optional()
pmap.AddNotNil("validated_asset_id", params.ValidatedModelID) pmap.Add("validated_asset_id", ValidatedModelID)
pmap.AddNotNil("validated_asset_version", params.ValidatedModelVersion) pmap.Add("validated_asset_version", ValidatedModelVersion)
// DO NOT reset completed when validated model is updated // DO NOT reset completed when validated model is updated
// pmap.Add("completed", false) // pmap.Add("completed", false)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, pmap) err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, pmap)
if err != nil {
return err
}
event_data := model.AuditEventDataChangeValidatedModel{
ValidatedModelID: ValidatedModelID,
ValidatedModelVersion: ValidatedModelVersion,
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: ValidtorUserID,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeChangeValidatedModel,
EventData: EventData,
})
if err != nil {
return err
}
return nil
}
// ActionSubmissionSubmitted invokes actionSubmissionSubmitted operation.
//
// Role Validator changes status from Submitting -> Submitted.
//
// POST /submissions/{SubmissionID}/status/validator-submitted
func (svc *Service) ActionSubmissionSubmitted(ctx context.Context, params internal.ActionSubmissionSubmittedParams) error {
// transaction
target_status := model.SubmissionStatusSubmitted
smap := datastore.Optional()
smap.Add("status_id", target_status)
smap.Add("asset_version", params.ModelVersion)
smap.Add("display_name", params.DisplayName)
smap.Add("creator", params.Creator)
smap.Add("game_id", params.GameID)
err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitting}, smap)
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: ValidtorUserID,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
}
// ActionSubmissionRequestChanges implements actionSubmissionRequestChanges operation.
//
// (Internal endpoint) Role Validator changes status from Submitting -> RequestChanges.
//
// POST /submissions/{SubmissionID}/status/validator-request-changes
func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params internal.ActionSubmissionRequestChangesParams) error {
// transaction
target_status := model.SubmissionStatusChangesRequested
smap := datastore.Optional()
smap.Add("status_id", target_status)
err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitting}, smap)
if err != nil {
return err
}
//push an error audit event
{
event_data := model.AuditEventDataError{
Error: params.ErrorMessage,
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: ValidtorUserID,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeError,
EventData: EventData,
})
if err != nil {
return err
}
}
// push an action audit event
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: ValidtorUserID,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }
// ActionSubmissionValidate invokes actionSubmissionValidate operation. // ActionSubmissionValidate invokes actionSubmissionValidate operation.
@@ -49,9 +188,36 @@ func (svc *Service) UpdateSubmissionValidatedModel(ctx context.Context, params i
// POST /submissions/{SubmissionID}/status/validator-validated // POST /submissions/{SubmissionID}/status/validator-validated
func (svc *Service) ActionSubmissionValidated(ctx context.Context, params internal.ActionSubmissionValidatedParams) error { func (svc *Service) ActionSubmissionValidated(ctx context.Context, params internal.ActionSubmissionValidatedParams) error {
// transaction // transaction
target_status := model.SubmissionStatusValidated
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.SubmissionStatusValidated) smap.Add("status_id", target_status)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap) err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap)
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: ValidtorUserID,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }
// ActionSubmissionAccepted implements actionSubmissionAccepted operation. // ActionSubmissionAccepted implements actionSubmissionAccepted operation.
@@ -61,10 +227,62 @@ func (svc *Service) ActionSubmissionValidated(ctx context.Context, params intern
// POST /submissions/{SubmissionID}/status/validator-failed // POST /submissions/{SubmissionID}/status/validator-failed
func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params internal.ActionSubmissionAcceptedParams) error { func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params internal.ActionSubmissionAcceptedParams) error {
// transaction // transaction
target_status := model.SubmissionStatusAcceptedUnvalidated
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.SubmissionStatusAccepted) smap.Add("status_id", target_status)
smap.Add("status_message", params.StatusMessage) err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap) if err != nil {
return err
}
//push an error audit event
{
event_data := model.AuditEventDataError{
Error: params.ErrorMessage,
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: ValidtorUserID,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeError,
EventData: EventData,
})
if err != nil {
return err
}
}
// push an action audit event
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: ValidtorUserID,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }
// ActionSubmissionUploaded implements actionSubmissionUploaded operation. // ActionSubmissionUploaded implements actionSubmissionUploaded operation.
@@ -74,14 +292,55 @@ func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params interna
// POST /submissions/{SubmissionID}/status/validator-uploaded // POST /submissions/{SubmissionID}/status/validator-uploaded
func (svc *Service) ActionSubmissionUploaded(ctx context.Context, params internal.ActionSubmissionUploadedParams) error { func (svc *Service) ActionSubmissionUploaded(ctx context.Context, params internal.ActionSubmissionUploadedParams) error {
// transaction // transaction
target_status := model.SubmissionStatusUploaded
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.SubmissionStatusUploaded) smap.Add("status_id", target_status)
smap.Add("uploaded_asset_id", params.UploadedAssetID) smap.Add("uploaded_asset_id", params.UploadedAssetID)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUploading}, smap) err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUploading}, smap)
if err != nil {
return err
}
event_data := model.AuditEventDataAction{
TargetStatus: uint32(target_status),
}
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: ValidtorUserID,
ResourceType: model.ResourceSubmission,
ResourceID: params.SubmissionID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
} }
// POST /submissions // POST /submissions
func (svc *Service) CreateSubmission(ctx context.Context, request *internal.SubmissionCreate) (*internal.SubmissionID, error) { func (svc *Service) CreateSubmission(ctx context.Context, request *internal.SubmissionCreate) (*internal.SubmissionID, error) {
// sanitization
if request.GameID<0||
request.AssetOwner<0||
request.AssetID<0||
request.AssetVersion<0{
return nil, ErrNegativeID
}
var GameID=uint32(request.GameID);
var Submitter=uint64(request.AssetOwner);
var AssetID=uint64(request.AssetID);
var AssetVersion=uint64(request.AssetVersion);
var Status=model.SubmissionStatus(request.Status);
var roles=service.Roles(request.Roles);
// Check if an active submission with the same asset id exists // Check if an active submission with the same asset id exists
{ {
filter := datastore.Optional() filter := datastore.Optional()
@@ -106,8 +365,11 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *internal.Subm
} }
// check if user owns asset // check if user owns asset
// TODO: allow bypass by admin is_submitter := operation.Owner == Submitter
if operation.Owner != request.AssetOwner { // check if user is map admin
has_submission_review := roles & service.RolesSubmissionReview == service.RolesSubmissionReview
// if neither, u not allowed
if !is_submitter && !has_submission_review {
return nil, ErrNotAssetOwner return nil, ErrNotAssetOwner
} }
@@ -115,12 +377,12 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *internal.Subm
ID: 0, ID: 0,
DisplayName: request.DisplayName, DisplayName: request.DisplayName,
Creator: request.Creator, Creator: request.Creator,
GameID: request.GameID, GameID: GameID,
Submitter: request.AssetOwner, Submitter: Submitter,
AssetID: request.AssetID, AssetID: AssetID,
AssetVersion: request.AssetVersion, AssetVersion: AssetVersion,
Completed: false, Completed: false,
StatusID: model.SubmissionStatusUnderConstruction, StatusID: Status,
}) })
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -5,14 +5,16 @@ edition = "2021"
[dependencies] [dependencies]
submissions-api = { path = "api", features = ["internal"], default-features = false, registry = "strafesnet" } submissions-api = { path = "api", features = ["internal"], default-features = false, registry = "strafesnet" }
async-nats = "0.40.0" async-nats = "0.41.0"
futures = "0.3.31" futures = "0.3.31"
rbx_asset = { version = "0.3.3", registry = "strafesnet" } rbx_asset = { version = "0.4.5", registry = "strafesnet" }
rbx_binary = { version = "0.7.4", registry = "strafesnet"} rbx_binary = "1.0.0"
rbx_dom_weak = { version = "2.9.0", registry = "strafesnet"} rbx_dom_weak = "3.0.0"
rbx_reflection_database = { version = "0.2.12", registry = "strafesnet"} rbx_reflection_database = "1.0.3"
rbx_xml = { version = "0.13.3", registry = "strafesnet"} rbx_xml = "1.0.0"
serde = { version = "1.0.215", features = ["derive"] } serde = { version = "1.0.215", features = ["derive"] }
serde_json = "1.0.133" serde_json = "1.0.133"
siphasher = "1.0.1" siphasher = "1.0.1"
tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread", "signal"] } tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread", "signal"] }
heck = "0.5.0"
lazy-regex = "3.4.1"

View File

@@ -1,6 +1,6 @@
# Using the `rust-musl-builder` as base image, instead of # Using the `rust-musl-builder` as base image, instead of
# the official Rust toolchain # the official Rust toolchain
FROM docker.io/clux/muslrust:stable AS chef FROM registry.itzana.me/docker-proxy/clux/muslrust:1.86.0-stable AS chef
USER root USER root
RUN cargo install cargo-chef RUN cargo install cargo-chef
WORKDIR /app WORKDIR /app
@@ -17,7 +17,7 @@ RUN cargo chef cook --release --target x86_64-unknown-linux-musl --recipe-path r
COPY . . COPY . .
RUN cargo build --release --target x86_64-unknown-linux-musl --bin maps-validation RUN cargo build --release --target x86_64-unknown-linux-musl --bin maps-validation
FROM docker.io/alpine:latest AS runtime FROM registry.itzana.me/docker-proxy/alpine:3.21 AS runtime
RUN addgroup -S myuser && adduser -S myuser -G myuser RUN addgroup -S myuser && adduser -S myuser -G myuser
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/maps-validation /usr/local/bin/ COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/maps-validation /usr/local/bin/
USER myuser USER myuser

View File

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

View File

@@ -44,4 +44,8 @@ impl Context{
.body(body) .body(body)
.send().await .send().await
} }
pub async fn delete(&self,url:impl reqwest::IntoUrl)->Result<reqwest::Response,reqwest::Error>{
self.client.delete(url)
.send().await
}
} }

View File

@@ -16,7 +16,7 @@ impl Context{
).await.map_err(Error::Response)? ).await.map_err(Error::Response)?
.json().await.map_err(Error::ReqwestJson) .json().await.map_err(Error::ReqwestJson)
} }
pub async fn get_scripts<'a>(&self,config:GetScriptsRequest<'a>)->Result<Vec<ScriptResponse>,Error>{ pub async fn get_scripts(&self,config:GetScriptsRequest<'_>)->Result<Vec<ScriptResponse>,Error>{
let url_raw=format!("{}/scripts",self.0.base_url); let url_raw=format!("{}/scripts",self.0.base_url);
let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?; let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
@@ -46,7 +46,7 @@ impl Context{
).await.map_err(Error::Response)? ).await.map_err(Error::Response)?
.json().await.map_err(Error::ReqwestJson) .json().await.map_err(Error::ReqwestJson)
} }
pub async fn get_script_from_hash<'a>(&self,config:HashRequest<'a>)->Result<Option<ScriptResponse>,SingleItemError>{ pub async fn get_script_from_hash(&self,config:HashRequest<'_>)->Result<Option<ScriptResponse>,SingleItemError>{
let scripts=self.get_scripts(GetScriptsRequest{ let scripts=self.get_scripts(GetScriptsRequest{
Page:1, Page:1,
Limit:2, Limit:2,
@@ -61,7 +61,7 @@ impl Context{
} }
Ok(scripts.into_iter().next()) Ok(scripts.into_iter().next())
} }
pub async fn create_script<'a>(&self,config:CreateScriptRequest<'a>)->Result<ScriptIDResponse,Error>{ pub async fn create_script(&self,config:CreateScriptRequest<'_>)->Result<ScriptIDResponse,Error>{
let url_raw=format!("{}/scripts",self.0.base_url); let url_raw=format!("{}/scripts",self.0.base_url);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?; let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
@@ -72,7 +72,17 @@ impl Context{
).await.map_err(Error::Response)? ).await.map_err(Error::Response)?
.json().await.map_err(Error::ReqwestJson) .json().await.map_err(Error::ReqwestJson)
} }
pub async fn get_script_policies<'a>(&self,config:GetScriptPoliciesRequest<'a>)->Result<Vec<ScriptPolicyResponse>,Error>{ pub async fn delete_script(&self,config:GetScriptRequest)->Result<(),Error>{
let url_raw=format!("{}/scripts/{}",self.0.base_url,config.ScriptID.0);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
response_ok(
self.0.delete(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?;
Ok(())
}
pub async fn get_script_policies(&self,config:GetScriptPoliciesRequest<'_>)->Result<Vec<ScriptPolicyResponse>,Error>{
let url_raw=format!("{}/script-policy",self.0.base_url); let url_raw=format!("{}/script-policy",self.0.base_url);
let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?; let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
@@ -96,7 +106,7 @@ impl Context{
).await.map_err(Error::Response)? ).await.map_err(Error::Response)?
.json().await.map_err(Error::ReqwestJson) .json().await.map_err(Error::ReqwestJson)
} }
pub async fn get_script_policy_from_hash<'a>(&self,config:HashRequest<'a>)->Result<Option<ScriptPolicyResponse>,SingleItemError>{ pub async fn get_script_policy_from_hash(&self,config:HashRequest<'_>)->Result<Option<ScriptPolicyResponse>,SingleItemError>{
let policies=self.get_script_policies(GetScriptPoliciesRequest{ let policies=self.get_script_policies(GetScriptPoliciesRequest{
Page:1, Page:1,
Limit:2, Limit:2,
@@ -130,6 +140,16 @@ impl Context{
self.0.post(url,body).await.map_err(Error::Reqwest)? self.0.post(url,body).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?; ).await.map_err(Error::Response)?;
Ok(())
}
pub async fn delete_script_policy(&self,config:GetScriptPolicyRequest)->Result<(),Error>{
let url_raw=format!("{}/script-policy/{}",self.0.base_url,config.ScriptPolicyID.0);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
response_ok(
self.0.delete(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?;
Ok(()) Ok(())
} }
} }

View File

@@ -22,7 +22,7 @@ macro_rules! query_pairs{
macro_rules! action{ macro_rules! action{
($system:expr,$fname:ident,$config:ident,$config_type:ident,$action:expr,$config_submission_id:expr,$(($param:expr,$value:expr))*)=>{ ($system:expr,$fname:ident,$config:ident,$config_type:ident,$action:expr,$config_submission_id:expr,$(($param:expr,$value:expr))*)=>{
pub async fn $fname(&self,$config:$config_type)->Result<(),Error>{ pub async fn $fname(&self,$config:$config_type)->Result<(),Error>{
let url_raw=format!(concat!("{}/",$system,"/{}/status/",$action),self.0.base_url,$config_submission_id); let url_raw=format!(concat!("{}/",$system,"/{}/",$action),self.0.base_url,$config_submission_id);
let url=query_pairs!(reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?,$(($param,$value))*); let url=query_pairs!(reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?,$(($param,$value))*);
response_ok( response_ok(
@@ -46,7 +46,7 @@ impl Context{
).await.map_err(Error::Response)? ).await.map_err(Error::Response)?
.json().await.map_err(Error::ReqwestJson) .json().await.map_err(Error::ReqwestJson)
} }
pub async fn get_scripts<'a>(&self,config:GetScriptsRequest<'a>)->Result<Vec<ScriptResponse>,Error>{ pub async fn get_scripts(&self,config:GetScriptsRequest<'_>)->Result<Vec<ScriptResponse>,Error>{
let url_raw=format!("{}/scripts",self.0.base_url); let url_raw=format!("{}/scripts",self.0.base_url);
let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?; let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
@@ -76,7 +76,7 @@ impl Context{
).await.map_err(Error::Response)? ).await.map_err(Error::Response)?
.json().await.map_err(Error::ReqwestJson) .json().await.map_err(Error::ReqwestJson)
} }
pub async fn get_script_from_hash<'a>(&self,config:HashRequest<'a>)->Result<Option<ScriptResponse>,SingleItemError>{ pub async fn get_script_from_hash(&self,config:HashRequest<'_>)->Result<Option<ScriptResponse>,SingleItemError>{
let scripts=self.get_scripts(GetScriptsRequest{ let scripts=self.get_scripts(GetScriptsRequest{
Page:1, Page:1,
Limit:2, Limit:2,
@@ -91,7 +91,7 @@ impl Context{
} }
Ok(scripts.into_iter().next()) Ok(scripts.into_iter().next())
} }
pub async fn create_script<'a>(&self,config:CreateScriptRequest<'a>)->Result<ScriptIDResponse,Error>{ pub async fn create_script(&self,config:CreateScriptRequest<'_>)->Result<ScriptIDResponse,Error>{
let url_raw=format!("{}/scripts",self.0.base_url); let url_raw=format!("{}/scripts",self.0.base_url);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?; let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
@@ -102,7 +102,7 @@ impl Context{
).await.map_err(Error::Response)? ).await.map_err(Error::Response)?
.json().await.map_err(Error::ReqwestJson) .json().await.map_err(Error::ReqwestJson)
} }
pub async fn get_script_policies<'a>(&self,config:GetScriptPoliciesRequest<'a>)->Result<Vec<ScriptPolicyResponse>,Error>{ pub async fn get_script_policies(&self,config:GetScriptPoliciesRequest<'_>)->Result<Vec<ScriptPolicyResponse>,Error>{
let url_raw=format!("{}/script-policy",self.0.base_url); let url_raw=format!("{}/script-policy",self.0.base_url);
let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?; let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
@@ -126,7 +126,7 @@ impl Context{
).await.map_err(Error::Response)? ).await.map_err(Error::Response)?
.json().await.map_err(Error::ReqwestJson) .json().await.map_err(Error::ReqwestJson)
} }
pub async fn get_script_policy_from_hash<'a>(&self,config:HashRequest<'a>)->Result<Option<ScriptPolicyResponse>,SingleItemError>{ pub async fn get_script_policy_from_hash(&self,config:HashRequest<'_>)->Result<Option<ScriptPolicyResponse>,SingleItemError>{
let policies=self.get_script_policies(GetScriptPoliciesRequest{ let policies=self.get_script_policies(GetScriptPoliciesRequest{
Page:1, Page:1,
Limit:2, Limit:2,
@@ -150,7 +150,7 @@ impl Context{
).await.map_err(Error::Response)? ).await.map_err(Error::Response)?
.json().await.map_err(Error::ReqwestJson) .json().await.map_err(Error::ReqwestJson)
} }
pub async fn create_submission<'a>(&self,config:CreateSubmissionRequest<'a>)->Result<SubmissionIDResponse,Error>{ pub async fn create_submission(&self,config:CreateSubmissionRequest<'_>)->Result<SubmissionIDResponse,Error>{
let url_raw=format!("{}/submissions",self.0.base_url); let url_raw=format!("{}/submissions",self.0.base_url);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?; let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
@@ -162,18 +162,27 @@ impl Context{
.json().await.map_err(Error::ReqwestJson) .json().await.map_err(Error::ReqwestJson)
} }
// simple submission endpoints // simple submission endpoints
action!("submissions",action_submission_validated,config,SubmissionID,"validator-validated",config.0,); action!("submissions",action_submission_request_changes,config,ActionSubmissionRequestChangesRequest,"status/validator-request-changes",config.SubmissionID,
("ErrorMessage",config.ErrorMessage.as_str())
);
action!("submissions",action_submission_submitted,config,ActionSubmissionSubmittedRequest,"status/validator-submitted",config.SubmissionID,
("ModelVersion",config.ModelVersion.to_string().as_str())
("DisplayName",config.DisplayName.as_str())
("Creator",config.Creator.as_str())
("GameID",config.GameID.to_string().as_str())
);
action!("submissions",action_submission_validated,config,SubmissionID,"status/validator-validated",config.0,);
action!("submissions",update_submission_validated_model,config,UpdateSubmissionModelRequest,"validated-model",config.SubmissionID, action!("submissions",update_submission_validated_model,config,UpdateSubmissionModelRequest,"validated-model",config.SubmissionID,
("ValidatedModelID",config.ModelID.to_string().as_str()) ("ValidatedModelID",config.ModelID.to_string().as_str())
("ValidatedModelVersion",config.ModelVersion.to_string().as_str()) ("ValidatedModelVersion",config.ModelVersion.to_string().as_str())
); );
action!("submissions",action_submission_uploaded,config,ActionSubmissionUploadedRequest,"validator-uploaded",config.SubmissionID, action!("submissions",action_submission_uploaded,config,ActionSubmissionUploadedRequest,"status/validator-uploaded",config.SubmissionID,
("UploadedAssetID",config.UploadedAssetID.to_string().as_str()) ("UploadedAssetID",config.UploadedAssetID.to_string().as_str())
); );
action!("submissions",action_submission_accepted,config,ActionSubmissionAcceptedRequest,"validator-failed",config.SubmissionID, action!("submissions",action_submission_accepted,config,ActionSubmissionAcceptedRequest,"status/validator-failed",config.SubmissionID,
("StatusMessage",config.StatusMessage.as_str()) ("ErrorMessage",config.ErrorMessage.as_str())
); );
pub async fn create_mapfix<'a>(&self,config:CreateMapfixRequest<'a>)->Result<MapfixIDResponse,Error>{ pub async fn create_mapfix(&self,config:CreateMapfixRequest<'_>)->Result<MapfixIDResponse,Error>{
let url_raw=format!("{}/mapfixes",self.0.base_url); let url_raw=format!("{}/mapfixes",self.0.base_url);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?; let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
@@ -185,17 +194,26 @@ impl Context{
.json().await.map_err(Error::ReqwestJson) .json().await.map_err(Error::ReqwestJson)
} }
// simple mapfixes endpoints // simple mapfixes endpoints
action!("mapfixes",action_mapfix_validated,config,MapfixID,"validator-validated",config.0,); action!("mapfixes",action_mapfix_request_changes,config,ActionMapfixRequestChangesRequest,"status/validator-request-changes",config.MapfixID,
("ErrorMessage",config.ErrorMessage.as_str())
);
action!("mapfixes",action_mapfix_submitted,config,ActionMapfixSubmittedRequest,"status/validator-submitted",config.MapfixID,
("ModelVersion",config.ModelVersion.to_string().as_str())
("DisplayName",config.DisplayName.as_str())
("Creator",config.Creator.as_str())
("GameID",config.GameID.to_string().as_str())
);
action!("mapfixes",action_mapfix_validated,config,MapfixID,"status/validator-validated",config.0,);
action!("mapfixes",update_mapfix_validated_model,config,UpdateMapfixModelRequest,"validated-model",config.MapfixID, action!("mapfixes",update_mapfix_validated_model,config,UpdateMapfixModelRequest,"validated-model",config.MapfixID,
("ValidatedModelID",config.ModelID.to_string().as_str()) ("ValidatedModelID",config.ModelID.to_string().as_str())
("ValidatedModelVersion",config.ModelVersion.to_string().as_str()) ("ValidatedModelVersion",config.ModelVersion.to_string().as_str())
); );
action!("mapfixes",action_mapfix_uploaded,config,ActionMapfixUploadedRequest,"validator-uploaded",config.MapfixID,); action!("mapfixes",action_mapfix_uploaded,config,ActionMapfixUploadedRequest,"status/validator-uploaded",config.MapfixID,);
action!("mapfixes",action_mapfix_accepted,config,ActionMapfixAcceptedRequest,"validator-failed",config.MapfixID, action!("mapfixes",action_mapfix_accepted,config,ActionMapfixAcceptedRequest,"status/validator-failed",config.MapfixID,
("StatusMessage",config.StatusMessage.as_str()) ("ErrorMessage",config.ErrorMessage.as_str())
); );
// simple operation endpoint // simple operation endpoint
action!("operations",action_operation_failed,config,ActionOperationFailedRequest,"operation-failed",config.OperationID, action!("operations",action_operation_failed,config,ActionOperationFailedRequest,"status/operation-failed",config.OperationID,
("StatusMessage",config.StatusMessage.as_str()) ("StatusMessage",config.StatusMessage.as_str())
); );
} }

View File

@@ -27,8 +27,7 @@ impl std::error::Error for SingleItemError{}
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub struct StatusCodeWithUrlAndBody{ pub struct UrlAndBody{
pub status_code:reqwest::StatusCode,
pub url:url::Url, pub url:url::Url,
pub body:String, pub body:String,
} }
@@ -36,7 +35,10 @@ pub struct StatusCodeWithUrlAndBody{
#[derive(Debug)] #[derive(Debug)]
pub enum ResponseError{ pub enum ResponseError{
Reqwest(reqwest::Error), Reqwest(reqwest::Error),
StatusCodeWithUrlAndBody(StatusCodeWithUrlAndBody), Details{
status_code:reqwest::StatusCode,
url_and_body:Box<UrlAndBody>,
},
} }
impl std::fmt::Display for ResponseError{ impl std::fmt::Display for ResponseError{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -53,11 +55,10 @@ pub async fn response_ok(response:reqwest::Response)->Result<reqwest::Response,R
let url=response.url().to_owned(); let url=response.url().to_owned();
let bytes=response.bytes().await.map_err(ResponseError::Reqwest)?; let bytes=response.bytes().await.map_err(ResponseError::Reqwest)?;
let body=String::from_utf8_lossy(&bytes).to_string(); let body=String::from_utf8_lossy(&bytes).to_string();
Err(ResponseError::StatusCodeWithUrlAndBody(StatusCodeWithUrlAndBody{ Err(ResponseError::Details{
status_code, status_code,
url, url_and_body:Box::new(UrlAndBody{url,body})
body, })
}))
} }
} }
@@ -73,6 +74,7 @@ pub struct CreateMapfixRequest<'a>{
pub AssetID:u64, pub AssetID:u64,
pub AssetVersion:u64, pub AssetVersion:u64,
pub TargetAssetID:u64, pub TargetAssetID:u64,
pub Description:&'a str,
} }
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)] #[derive(Clone,Debug,serde::Deserialize)]
@@ -90,6 +92,8 @@ pub struct CreateSubmissionRequest<'a>{
pub GameID:i32, pub GameID:i32,
pub AssetID:u64, pub AssetID:u64,
pub AssetVersion:u64, pub AssetVersion:u64,
pub Status:u32,
pub Roles:u32,
} }
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)] #[derive(Clone,Debug,serde::Deserialize)]
@@ -169,6 +173,10 @@ pub enum Policy{
Replace=4, Replace=4,
} }
#[allow(nonstandard_style)]
pub struct GetScriptPolicyRequest{
pub ScriptPolicyID:ScriptPolicyID,
}
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(Clone,Debug,serde::Serialize)] #[derive(Clone,Debug,serde::Serialize)]
pub struct GetScriptPoliciesRequest<'a>{ pub struct GetScriptPoliciesRequest<'a>{
@@ -222,6 +230,23 @@ pub struct UpdateSubmissionModelRequest{
pub ModelVersion:u64, pub ModelVersion:u64,
} }
#[allow(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct ActionSubmissionSubmittedRequest{
pub SubmissionID:i64,
pub ModelVersion:u64,
pub DisplayName:String,
pub Creator:String,
pub GameID:u32,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct ActionSubmissionRequestChangesRequest{
pub SubmissionID:i64,
pub ErrorMessage:String,
}
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
pub struct ActionSubmissionUploadedRequest{ pub struct ActionSubmissionUploadedRequest{
@@ -233,7 +258,7 @@ pub struct ActionSubmissionUploadedRequest{
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
pub struct ActionSubmissionAcceptedRequest{ pub struct ActionSubmissionAcceptedRequest{
pub SubmissionID:i64, pub SubmissionID:i64,
pub StatusMessage:String, pub ErrorMessage:String,
} }
#[derive(Clone,Copy,Debug,serde::Deserialize)] #[derive(Clone,Copy,Debug,serde::Deserialize)]
@@ -247,6 +272,23 @@ pub struct UpdateMapfixModelRequest{
pub ModelVersion:u64, pub ModelVersion:u64,
} }
#[allow(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct ActionMapfixSubmittedRequest{
pub MapfixID:i64,
pub ModelVersion:u64,
pub DisplayName:String,
pub Creator:String,
pub GameID:u32,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct ActionMapfixRequestChangesRequest{
pub MapfixID:i64,
pub ErrorMessage:String,
}
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
pub struct ActionMapfixUploadedRequest{ pub struct ActionMapfixUploadedRequest{
@@ -257,7 +299,7 @@ pub struct ActionMapfixUploadedRequest{
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
pub struct ActionMapfixAcceptedRequest{ pub struct ActionMapfixAcceptedRequest{
pub MapfixID:i64, pub MapfixID:i64,
pub StatusMessage:String, pub ErrorMessage:String,
} }
#[derive(Clone,Copy,Debug,serde::Deserialize)] #[derive(Clone,Copy,Debug,serde::Deserialize)]

View File

@@ -0,0 +1,9 @@
[package]
name = "cli"
version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4.5.39", features = ["derive"] }
maps-validation = { path = ".." }
rbx_binary = "1.0.0"

View File

@@ -0,0 +1,73 @@
use clap::{Args,Parser,Subcommand};
use std::path::{Path,PathBuf};
#[derive(Parser)]
#[command(author,version,about,long_about=None)]
#[command(propagate_version=true)]
struct Cli{
#[command(subcommand)]
command:Commands,
}
#[derive(Subcommand)]
enum Commands{
Check(CheckCommand),
}
#[derive(Args)]
struct CheckCommand{
files:Vec<PathBuf>,
}
fn main(){
let cli=Cli::parse();
match cli.command{
Commands::Check(command)=>command.run().unwrap(),
}
}
#[allow(dead_code)]
#[derive(Debug)]
enum CheckError{
Io(std::io::Error),
Binary(maps_validation::rbx_util::ReadDomError),
CheckDom(maps_validation::rbx_util::GetRootInstanceError),
}
fn check(path:&Path)->Result<(),CheckError>{
let name=path.file_name().unwrap_or_default().to_str().unwrap_or_default();
let file=std::fs::read(path).map_err(CheckError::Io)?;
let dom=maps_validation::rbx_util::read_dom(file.as_slice()).map_err(CheckError::Binary)?;
let check=maps_validation::message_handler::MessageHandler::check_dom(&dom).map_err(CheckError::CheckDom)?;
match check.result(){
Ok(_map_info)=>(),//println!("good {}",map_info.display_name),
Err(Ok(check_list))=>println!("bad {name} Error: {}",check_list.summary()),
Err(Err(e))=>println!("ugly {name} Error: {e}"),
}
Ok(())
}
impl CheckCommand{
fn run(self)->Result<(),()>{
let mut handles=Vec::new();
for path in self.files{
if path.is_file(){
handles.push(std::thread::spawn(move||{
if let Err(e)=check(path.as_path()){
let name=path.file_name().unwrap_or_default().to_str().unwrap_or_default();
println!("ugly {name} Error: {e:?}");
}
}));
}
}
for handle in handles{
handle.join().unwrap();
}
Ok(())
}
}

855
validation/src/check.rs Normal file
View File

@@ -0,0 +1,855 @@
use std::collections::{HashSet,HashMap};
use crate::download::download_asset_version;
use crate::rbx_util::{class_is_a,get_mapinfo,get_root_instance,read_dom,ReadDomError,GameID,ParseGameIDError,MapInfo,GetRootInstanceError,StringValueError};
use heck::{ToSnakeCase,ToTitleCase};
#[allow(dead_code)]
#[derive(Debug)]
pub enum Error{
ModelInfoDownload(rbx_asset::cloud::GetError),
CreatorTypeMustBeUser,
Download(crate::download::Error),
ModelFileDecode(ReadDomError),
GetRootInstance(GetRootInstanceError),
ToJsonValue(serde_json::Error),
}
impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for Error{}
#[allow(nonstandard_style)]
pub struct CheckRequest{
pub ModelID:u64,
}
impl From<crate::nats_types::CheckMapfixRequest> for CheckRequest{
fn from(value:crate::nats_types::CheckMapfixRequest)->Self{
Self{
ModelID:value.ModelID,
}
}
}
impl From<crate::nats_types::CheckSubmissionRequest> for CheckRequest{
fn from(value:crate::nats_types::CheckSubmissionRequest)->Self{
Self{
ModelID:value.ModelID,
}
}
}
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
struct ModeID(u64);
impl ModeID{
const MAIN:Self=Self(0);
const BONUS:Self=Self(1);
}
enum Zone{
Start,
Finish,
Anticheat,
}
struct ModeElement{
zone:Zone,
mode_id:ModeID,
}
#[allow(dead_code)]
pub enum IDParseError{
NoCaptures,
ParseInt(core::num::ParseIntError),
}
// Parse a Zone from a part name
impl std::str::FromStr for ModeElement{
type Err=IDParseError;
fn from_str(s:&str)->Result<Self,Self::Err>{
match s{
"MapStart"=>Ok(Self{zone:Zone::Start,mode_id:ModeID::MAIN}),
"MapFinish"=>Ok(Self{zone:Zone::Finish,mode_id:ModeID::MAIN}),
"MapAnticheat"=>Ok(Self{zone:Zone::Anticheat,mode_id:ModeID::MAIN}),
"BonusStart"=>Ok(Self{zone:Zone::Start,mode_id:ModeID::BONUS}),
"BonusFinish"=>Ok(Self{zone:Zone::Finish,mode_id:ModeID::BONUS}),
"BonusAnticheat"=>Ok(Self{zone:Zone::Anticheat,mode_id:ModeID::BONUS}),
other=>{
let everything_pattern=lazy_regex::lazy_regex!(r"^Bonus(\d+)Start$|^BonusStart(\d+)$|^Bonus(\d+)Finish$|^BonusFinish(\d+)$|^Bonus(\d+)Anticheat$|^BonusAnticheat(\d+)$");
if let Some(captures)=everything_pattern.captures(other){
if let Some(mode_id)=captures.get(1).or(captures.get(2)){
return Ok(Self{
zone:Zone::Start,
mode_id:ModeID(mode_id.as_str().parse().map_err(IDParseError::ParseInt)?),
});
}
if let Some(mode_id)=captures.get(3).or(captures.get(4)){
return Ok(Self{
zone:Zone::Finish,
mode_id:ModeID(mode_id.as_str().parse().map_err(IDParseError::ParseInt)?),
});
}
if let Some(mode_id)=captures.get(5).or(captures.get(6)){
return Ok(Self{
zone:Zone::Anticheat,
mode_id:ModeID(mode_id.as_str().parse().map_err(IDParseError::ParseInt)?),
});
}
}
Err(IDParseError::NoCaptures)
}
}
}
}
impl std::fmt::Display for ModeElement{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
match self{
ModeElement{zone:Zone::Start,mode_id:ModeID::MAIN}=>write!(f,"MapStart"),
ModeElement{zone:Zone::Start,mode_id:ModeID::BONUS}=>write!(f,"BonusStart"),
ModeElement{zone:Zone::Start,mode_id:ModeID(mode_id)}=>write!(f,"Bonus{mode_id}Start"),
ModeElement{zone:Zone::Finish,mode_id:ModeID::MAIN}=>write!(f,"MapFinish"),
ModeElement{zone:Zone::Finish,mode_id:ModeID::BONUS}=>write!(f,"BonusFinish"),
ModeElement{zone:Zone::Finish,mode_id:ModeID(mode_id)}=>write!(f,"Bonus{mode_id}Finish"),
ModeElement{zone:Zone::Anticheat,mode_id:ModeID::MAIN}=>write!(f,"MapAnticheat"),
ModeElement{zone:Zone::Anticheat,mode_id:ModeID::BONUS}=>write!(f,"BonusAnticheat"),
ModeElement{zone:Zone::Anticheat,mode_id:ModeID(mode_id)}=>write!(f,"Bonus{mode_id}Anticheat"),
}
}
}
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
struct StageID(u64);
impl StageID{
const FIRST:Self=Self(1);
}
enum StageElementBehaviour{
Teleport,
Spawn,
}
struct StageElement{
stage_id:StageID,
behaviour:StageElementBehaviour,
}
// Parse a SpawnTeleport from a part name
impl std::str::FromStr for StageElement{
type Err=IDParseError;
fn from_str(s:&str)->Result<Self,Self::Err>{
// Trigger ForceTrigger Teleport ForceTeleport SpawnAt ForceSpawnAt
let bonus_start_pattern=lazy_regex::lazy_regex!(r"^(?:Force)?(Teleport|SpawnAt|Trigger)(\d+)$");
if let Some(captures)=bonus_start_pattern.captures(s){
return Ok(StageElement{
behaviour:StageElementBehaviour::Teleport,
stage_id:StageID(captures[1].parse().map_err(IDParseError::ParseInt)?),
});
}
// Spawn
let bonus_finish_pattern=lazy_regex::lazy_regex!(r"^Spawn(\d+)$");
if let Some(captures)=bonus_finish_pattern.captures(s){
return Ok(StageElement{
behaviour:StageElementBehaviour::Spawn,
stage_id:StageID(captures[1].parse().map_err(IDParseError::ParseInt)?),
});
}
Err(IDParseError::NoCaptures)
}
}
impl std::fmt::Display for StageElement{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
match self{
StageElement{behaviour:StageElementBehaviour::Spawn,stage_id:StageID(stage_id)}=>write!(f,"Spawn{stage_id}"),
StageElement{behaviour:StageElementBehaviour::Teleport,stage_id:StageID(stage_id)}=>write!(f,"Teleport{stage_id}"),
}
}
}
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
struct WormholeID(u64);
enum WormholeBehaviour{
In,
Out,
}
struct WormholeElement{
behaviour:WormholeBehaviour,
wormhole_id:WormholeID,
}
// Parse a Wormhole from a part name
impl std::str::FromStr for WormholeElement{
type Err=IDParseError;
fn from_str(s:&str)->Result<Self,Self::Err>{
let bonus_start_pattern=lazy_regex::lazy_regex!(r"^WormholeIn(\d+)$");
if let Some(captures)=bonus_start_pattern.captures(s){
return Ok(Self{
behaviour:WormholeBehaviour::In,
wormhole_id:WormholeID(captures[1].parse().map_err(IDParseError::ParseInt)?),
});
}
let bonus_finish_pattern=lazy_regex::lazy_regex!(r"^WormholeOut(\d+)$");
if let Some(captures)=bonus_finish_pattern.captures(s){
return Ok(Self{
behaviour:WormholeBehaviour::Out,
wormhole_id:WormholeID(captures[1].parse().map_err(IDParseError::ParseInt)?),
});
}
Err(IDParseError::NoCaptures)
}
}
impl std::fmt::Display for WormholeElement{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
match self{
WormholeElement{behaviour:WormholeBehaviour::In,wormhole_id:WormholeID(wormhole_id)}=>write!(f,"WormholeIn{wormhole_id}"),
WormholeElement{behaviour:WormholeBehaviour::Out,wormhole_id:WormholeID(wormhole_id)}=>write!(f,"WormholeOut{wormhole_id}"),
}
}
}
/// Count various map elements
#[derive(Default)]
struct Counts<'a>{
mode_start_counts:HashMap<ModeID,Vec<&'a str>>,
mode_finish_counts:HashMap<ModeID,Vec<&'a str>>,
mode_anticheat_counts:HashMap<ModeID,Vec<&'a str>>,
teleport_counts:HashMap<StageID,Vec<&'a str>>,
spawn_counts:HashMap<StageID,u64>,
wormhole_in_counts:HashMap<WormholeID,u64>,
wormhole_out_counts:HashMap<WormholeID,u64>,
}
pub struct ModelInfo<'a>{
model_class:&'a str,
model_name:&'a str,
map_info:MapInfo<'a>,
counts:Counts<'a>,
}
pub fn get_model_info<'a>(dom:&'a rbx_dom_weak::WeakDom,model_instance:&'a rbx_dom_weak::Instance)->ModelInfo<'a>{
// extract model info
let map_info=get_mapinfo(dom,model_instance);
// count objects (default count is 0)
let mut counts=Counts::default();
for instance in dom.descendants_of(model_instance.referent()){
if class_is_a(instance.class.as_str(),"BasePart"){
// Zones
match instance.name.parse(){
Ok(ModeElement{zone:Zone::Start,mode_id})=>counts.mode_start_counts.entry(mode_id).or_default().push(instance.name.as_str()),
Ok(ModeElement{zone:Zone::Finish,mode_id})=>counts.mode_finish_counts.entry(mode_id).or_default().push(instance.name.as_str()),
Ok(ModeElement{zone:Zone::Anticheat,mode_id})=>counts.mode_anticheat_counts.entry(mode_id).or_default().push(instance.name.as_str()),
Err(_)=>(),
}
// Spawns & Teleports
match instance.name.parse(){
Ok(StageElement{behaviour:StageElementBehaviour::Teleport,stage_id})=>counts.teleport_counts.entry(stage_id).or_default().push(instance.name.as_str()),
Ok(StageElement{behaviour:StageElementBehaviour::Spawn,stage_id})=>*counts.spawn_counts.entry(stage_id).or_insert(0)+=1,
Err(_)=>(),
}
// Wormholes
match instance.name.parse(){
Ok(WormholeElement{behaviour:WormholeBehaviour::In,wormhole_id})=>*counts.wormhole_in_counts.entry(wormhole_id).or_insert(0)+=1,
Ok(WormholeElement{behaviour:WormholeBehaviour::Out,wormhole_id})=>*counts.wormhole_out_counts.entry(wormhole_id).or_insert(0)+=1,
Err(_)=>(),
}
}
}
ModelInfo{
model_class:model_instance.class.as_str(),
model_name:model_instance.name.as_str(),
map_info,
counts,
}
}
// check if an observed string matches an expected string
pub struct StringCheck<'a,T,Str>(Result<T,StringCheckContext<'a,Str>>);
pub struct StringCheckContext<'a,Str>{
observed:&'a str,
expected:Str,
}
impl<'a,Str> StringCheckContext<'a,Str>
where
&'a str:PartialEq<Str>,
{
/// Compute the StringCheck, passing through the provided value on success.
fn check<T>(self,value:T)->StringCheck<'a,T,Str>{
if self.observed==self.expected{
StringCheck(Ok(value))
}else{
StringCheck(Err(self))
}
}
}
impl<Str:std::fmt::Display> std::fmt::Display for StringCheckContext<'_,Str>{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"expected: {}, observed: {}",self.expected,self.observed)
}
}
// check if a string is empty
pub struct StringEmpty;
impl std::fmt::Display for StringEmpty{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"Empty string")
}
}
fn check_empty(value:&str)->Result<&str,StringEmpty>{
(!value.is_empty()).then_some(value).ok_or(StringEmpty)
}
// check for duplicate objects
pub struct DuplicateCheckContext<ID,T>(HashMap<ID,T>);
pub struct DuplicateCheck<ID,T>(Result<(),DuplicateCheckContext<ID,T>>);
impl<ID,T> DuplicateCheckContext<ID,T>{
/// Compute the DuplicateCheck using the contents predicate.
fn check(self,f:impl Fn(&T)->bool)->DuplicateCheck<ID,T>{
let Self(mut set)=self;
// remove correct entries
set.retain(|_,c|f(c));
// if any entries remain, they are incorrect
if set.is_empty(){
DuplicateCheck(Ok(()))
}else{
DuplicateCheck(Err(Self(set)))
}
}
}
// Check that there are no items which do not have a matching item in a reference set
pub struct SetDifferenceCheckContextAllowNone<ID,T>{
extra:HashMap<ID,T>,
}
// Check that there is at least one matching item for each item in a reference set, and no extra items
pub struct SetDifferenceCheckContextAtLeastOne<ID,T>{
extra:HashMap<ID,T>,
missing:HashSet<ID>,
}
pub struct SetDifferenceCheck<Context>(Result<(),Context>);
impl<ID,T> SetDifferenceCheckContextAllowNone<ID,T>{
fn new(initial_set:HashMap<ID,T>)->Self{
Self{
extra:initial_set,
}
}
}
impl<ID:Eq+std::hash::Hash,T> SetDifferenceCheckContextAllowNone<ID,T>{
/// Compute the SetDifferenceCheck result for the specified reference set.
fn check<U>(mut self,reference_set:&HashMap<ID,U>)->SetDifferenceCheck<Self>{
// remove correct entries
for id in reference_set.keys(){
self.extra.remove(id);
}
// if any entries remain, they are incorrect
if self.extra.is_empty(){
SetDifferenceCheck(Ok(()))
}else{
SetDifferenceCheck(Err(self))
}
}
}
impl<ID,T> SetDifferenceCheckContextAtLeastOne<ID,T>{
fn new(initial_set:HashMap<ID,T>)->Self{
Self{
extra:initial_set,
missing:HashSet::new(),
}
}
}
impl<ID:Copy+Eq+std::hash::Hash,T> SetDifferenceCheckContextAtLeastOne<ID,T>{
/// Compute the SetDifferenceCheck result for the specified reference set.
fn check<U>(mut self,reference_set:&HashMap<ID,U>)->SetDifferenceCheck<Self>{
// remove correct entries
for id in reference_set.keys(){
if self.extra.remove(id).is_none(){
// the set did not contain a required item. This is a fail
self.missing.insert(*id);
}
}
// if any entries remain, they are incorrect
if self.extra.is_empty()&&self.missing.is_empty(){
SetDifferenceCheck(Ok(()))
}else{
SetDifferenceCheck(Err(self))
}
}
}
/// Info lifted out of a fully compliant map
#[derive(Debug)]
pub struct MapInfoOwned{
pub display_name:String,
pub creator:String,
pub game_id:GameID,
}
// Named dummy types for readability
struct Exists;
struct Absent;
/// The result of every map check.
pub struct MapCheck<'a>{
// === METADATA CHECKS ===
// The root must be of class Model
model_class:StringCheck<'a,(),&'static str>,
// Model's name must be in snake case
model_name:StringCheck<'a,(),String>,
// Map must have a StringValue named DisplayName.
// Value must not be empty, must be in title case.
display_name:Result<Result<StringCheck<'a,&'a str,String>,StringEmpty>,StringValueError>,
// Map must have a StringValue named Creator.
// Value must not be empty.
creator:Result<Result<&'a str,StringEmpty>,StringValueError>,
// The prefix of the model's name must match the game it was submitted for.
// bhop_ for bhop, and surf_ for surf
game_id:Result<GameID,ParseGameIDError>,
// === MODE CHECKS ===
// MapStart must exist
mapstart:Result<Exists,Absent>,
// No duplicate map starts (including bonuses)
mode_start_counts:DuplicateCheck<ModeID,Vec<&'a str>>,
// At least one finish zone for each start zone, and no finishes with no start
mode_finish_counts:SetDifferenceCheck<SetDifferenceCheckContextAtLeastOne<ModeID,Vec<&'a str>>>,
// Check for dangling MapAnticheat zones (no associated MapStart)
mode_anticheat_counts:SetDifferenceCheck<SetDifferenceCheckContextAllowNone<ModeID,Vec<&'a str>>>,
// Spawn1 must exist
spawn1:Result<Exists,Absent>,
// Check for dangling Teleport# (no associated Spawn#)
teleport_counts:SetDifferenceCheck<SetDifferenceCheckContextAllowNone<StageID,Vec<&'a str>>>,
// No duplicate Spawn#
spawn_counts:DuplicateCheck<StageID,u64>,
// Check for dangling WormholeIn# (no associated WormholeOut#)
wormhole_in_counts:SetDifferenceCheck<SetDifferenceCheckContextAtLeastOne<WormholeID,u64>>,
// No duplicate WormholeOut# (duplicate WormholeIn# ok)
// No dangling WormholeOut#
wormhole_out_counts:DuplicateCheck<WormholeID,u64>,
}
impl<'a> ModelInfo<'a>{
fn check(self)->MapCheck<'a>{
// Check class is exactly "Model"
let model_class=StringCheckContext{
observed:self.model_class,
expected:"Model",
}.check(());
// Check model name is snake case
let model_name=StringCheckContext{
observed:self.model_name,
expected:self.model_name.to_snake_case(),
}.check(());
// Check display name is not empty and has title case
let display_name=self.map_info.display_name.map(|display_name|{
check_empty(display_name).map(|display_name|StringCheckContext{
observed:display_name,
expected:display_name.to_title_case(),
}.check(display_name))
});
// Check Creator is not empty
let creator=self.map_info.creator.map(check_empty);
// Check GameID (model name was prefixed with bhop_ surf_ etc)
let game_id=self.map_info.game_id;
// MapStart must exist
let mapstart=if self.counts.mode_start_counts.contains_key(&ModeID::MAIN){
Ok(Exists)
}else{
Err(Absent)
};
// Spawn1 must exist
let spawn1=if self.counts.spawn_counts.contains_key(&StageID::FIRST){
Ok(Exists)
}else{
Err(Absent)
};
// Check that at least one finish zone exists for each start zone.
// This also checks that there are no finish zones without a corresponding start zone.
let mode_finish_counts=SetDifferenceCheckContextAtLeastOne::new(self.counts.mode_finish_counts)
.check(&self.counts.mode_start_counts);
// Check that there are no anticheat zones without a corresponding start zone.
// Modes are allowed to have 0 anticheat zones.
let mode_anticheat_counts=SetDifferenceCheckContextAllowNone::new(self.counts.mode_anticheat_counts)
.check(&self.counts.mode_start_counts);
// There must be exactly one start zone for every mode in the map.
let mode_start_counts=DuplicateCheckContext(self.counts.mode_start_counts).check(|c|1<c.len());
// Check that there are no Teleports without a corresponding Spawn.
// Spawns are allowed to have 0 Teleports.
let teleport_counts=SetDifferenceCheckContextAllowNone::new(self.counts.teleport_counts)
.check(&self.counts.spawn_counts);
// There must be exactly one of any perticular spawn id in the map.
let spawn_counts=DuplicateCheckContext(self.counts.spawn_counts).check(|&c|1<c);
// Check that at least one WormholeIn exists for each WormholeOut.
// This also checks that there are no WormholeIn without a corresponding WormholeOut.
let wormhole_in_counts=SetDifferenceCheckContextAtLeastOne::new(self.counts.wormhole_in_counts)
.check(&self.counts.wormhole_out_counts);
// There must be exactly one of any perticular wormhole out id in the map.
let wormhole_out_counts=DuplicateCheckContext(self.counts.wormhole_out_counts).check(|&c|1<c);
MapCheck{
model_class,
model_name,
display_name,
creator,
game_id,
mapstart,
mode_start_counts,
mode_finish_counts,
mode_anticheat_counts,
spawn1,
teleport_counts,
spawn_counts,
wormhole_in_counts,
wormhole_out_counts,
}
}
}
impl MapCheck<'_>{
pub fn result(self)->Result<MapInfoOwned,Result<MapCheckList,serde_json::Error>>{
match self{
MapCheck{
model_class:StringCheck(Ok(())),
model_name:StringCheck(Ok(())),
display_name:Ok(Ok(StringCheck(Ok(display_name)))),
creator:Ok(Ok(creator)),
game_id:Ok(game_id),
mapstart:Ok(Exists),
mode_start_counts:DuplicateCheck(Ok(())),
mode_finish_counts:SetDifferenceCheck(Ok(())),
mode_anticheat_counts:SetDifferenceCheck(Ok(())),
spawn1:Ok(Exists),
teleport_counts:SetDifferenceCheck(Ok(())),
spawn_counts:DuplicateCheck(Ok(())),
wormhole_in_counts:SetDifferenceCheck(Ok(())),
wormhole_out_counts:DuplicateCheck(Ok(())),
}=>{
Ok(MapInfoOwned{
display_name:display_name.to_owned(),
creator:creator.to_owned(),
game_id,
})
},
other=>Err(other.itemize()),
}
}
}
struct Separated<F>{
f:F,
separator:&'static str,
}
impl<F> Separated<F>{
fn new(separator:&'static str,f:F)->Self{
Self{separator,f}
}
}
impl<F,I,D> std::fmt::Display for Separated<F>
where
D:std::fmt::Display,
I:IntoIterator<Item=D>,
F:Fn()->I,
{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
let mut it=(self.f)().into_iter();
if let Some(first)=it.next(){
write!(f,"{first}")?;
for item in it{
write!(f,"{}{item}",self.separator)?;
}
}
Ok(())
}
}
struct Duplicates<D>{
display:D,
duplicates:usize,
}
impl<D> Duplicates<D>{
fn new(display:D,duplicates:usize)->Self{
Self{
display,
duplicates,
}
}
}
impl<D:std::fmt::Display> std::fmt::Display for Duplicates<D>{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{} ({} duplicates)",self.display,self.duplicates)
}
}
#[derive(serde::Serialize)]
pub struct CheckSummary{
name:&'static str,
summary:String,
pub passed:bool,
details:serde_json::Value,
}
impl CheckSummary{
const fn passed(name:&'static str)->Self{
Self{
name,
summary:String::new(),
passed:true,
details:serde_json::Value::Null,
}
}
}
macro_rules! summary{
($name:literal,$summary:expr,$details:expr)=>{
CheckSummary{
name:$name,
summary:$summary,
passed:false,
details:serde_json::to_value($details)?,
}
};
}
macro_rules! summary_format{
($name:literal,$fmt:literal,$details:expr)=>{
CheckSummary{
name:$name,
summary:format!($fmt),
passed:false,
details:serde_json::to_value($details)?,
}
};
}
// Generate an error message for each observed issue separated by newlines.
// This defines MapCheck.to_string() which is used in MapCheck.result()
impl MapCheck<'_>{
fn itemize(&self)->Result<MapCheckList,serde_json::Error>{
let model_class=match &self.model_class{
StringCheck(Ok(()))=>CheckSummary::passed("ModelClass"),
StringCheck(Err(context))=>summary_format!("ModelClass","Invalid model class: {context}",()),
};
let model_name=match &self.model_name{
StringCheck(Ok(()))=>CheckSummary::passed("ModelName"),
StringCheck(Err(context))=>summary_format!("ModelName","Model name must have snake_case: {context}",()),
};
let display_name=match &self.display_name{
Ok(Ok(StringCheck(Ok(_))))=>CheckSummary::passed("DisplayName"),
Ok(Ok(StringCheck(Err(context))))=>summary_format!("DisplayName","DisplayName must have Title Case: {context}",()),
Ok(Err(context))=>summary_format!("DisplayName","Invalid DisplayName: {context}",()),
Err(StringValueError::ObjectNotFound)=>summary!("DisplayName","Missing DisplayName StringValue".to_owned(),()),
Err(StringValueError::ValueNotSet)=>summary!("DisplayName","DisplayName Value not set".to_owned(),()),
Err(StringValueError::NonStringValue)=>summary!("DisplayName","DisplayName Value is not a String".to_owned(),()),
};
let creator=match &self.creator{
Ok(Ok(_))=>CheckSummary::passed("Creator"),
Ok(Err(context))=>summary_format!("Creator","Invalid Creator: {context}",()),
Err(StringValueError::ObjectNotFound)=>summary!("Creator","Missing Creator StringValue".to_owned(),()),
Err(StringValueError::ValueNotSet)=>summary!("Creator","Creator Value not set".to_owned(),()),
Err(StringValueError::NonStringValue)=>summary!("Creator","Creator Value is not a String".to_owned(),()),
};
let game_id=match &self.game_id{
Ok(_)=>CheckSummary::passed("GameID"),
Err(ParseGameIDError)=>summary!("GameID","Model name must be prefixed with bhop_ surf_ or flytrials_".to_owned(),()),
};
let mapstart=match &self.mapstart{
Ok(Exists)=>CheckSummary::passed("MapStart"),
Err(Absent)=>summary_format!("MapStart","Model has no MapStart",()),
};
let duplicate_start=match &self.mode_start_counts{
DuplicateCheck(Ok(()))=>CheckSummary::passed("DuplicateStart"),
DuplicateCheck(Err(DuplicateCheckContext(context)))=>{
let context=Separated::new(", ",||context.iter().map(|(&mode_id,names)|
Duplicates::new(ModeElement{zone:Zone::Start,mode_id},names.len())
));
summary_format!("DuplicateStart","Duplicate start zones: {context}",())
}
};
let (extra_finish,missing_finish)=match &self.mode_finish_counts{
SetDifferenceCheck(Ok(()))=>(CheckSummary::passed("ExtraFinish"),CheckSummary::passed("MissingFinish")),
SetDifferenceCheck(Err(context))=>(
if context.extra.is_empty(){
CheckSummary::passed("ExtraFinish")
}else{
let plural=if context.extra.len()==1{"zone"}else{"zones"};
let context=Separated::new(", ",||context.extra.iter().map(|(&mode_id,_names)|
ModeElement{zone:Zone::Finish,mode_id}
));
summary_format!("ExtraFinish","No matching start zone for finish {plural}: {context}",())
},
if context.missing.is_empty(){
CheckSummary::passed("MissingFinish")
}else{
let plural=if context.missing.len()==1{"zone"}else{"zones"};
let context=Separated::new(", ",||context.missing.iter().map(|&mode_id|
ModeElement{zone:Zone::Finish,mode_id}
));
summary_format!("MissingFinish","Missing finish {plural}: {context}",())
}
),
};
let dangling_anticheat=match &self.mode_anticheat_counts{
SetDifferenceCheck(Ok(()))=>CheckSummary::passed("DanglingAnticheat"),
SetDifferenceCheck(Err(context))=>{
if context.extra.is_empty(){
CheckSummary::passed("DanglingAnticheat")
}else{
let plural=if context.extra.len()==1{"zone"}else{"zones"};
let context=Separated::new(", ",||context.extra.iter().map(|(&mode_id,_names)|
ModeElement{zone:Zone::Anticheat,mode_id}
));
summary_format!("DanglingAnticheat","No matching start zone for anticheat {plural}: {context}",())
}
}
};
let spawn1=match &self.spawn1{
Ok(Exists)=>CheckSummary::passed("Spawn1"),
Err(Absent)=>summary_format!("Spawn1","Model has no Spawn1",()),
};
let dangling_teleport=match &self.teleport_counts{
SetDifferenceCheck(Ok(()))=>CheckSummary::passed("DanglingTeleport"),
SetDifferenceCheck(Err(context))=>{
let unique_names:HashSet<_>=context.extra.values().flat_map(|names|names.iter().copied()).collect();
let plural=if unique_names.len()==1{"object"}else{"objects"};
let context=Separated::new(", ",||&unique_names);
summary_format!("DanglingTeleport","No matching Spawn for {plural}: {context}",())
}
};
let duplicate_spawns=match &self.spawn_counts{
DuplicateCheck(Ok(()))=>CheckSummary::passed("DuplicateSpawn"),
DuplicateCheck(Err(DuplicateCheckContext(context)))=>{
let context=Separated::new(", ",||context.iter().map(|(&stage_id,&names)|
Duplicates::new(StageElement{behaviour:StageElementBehaviour::Spawn,stage_id},names as usize)
));
summary_format!("DuplicateSpawn","Duplicate Spawn: {context}",())
}
};
let (extra_wormhole_in,missing_wormhole_in)=match &self.wormhole_in_counts{
SetDifferenceCheck(Ok(()))=>(CheckSummary::passed("ExtraWormholeIn"),CheckSummary::passed("MissingWormholeIn")),
SetDifferenceCheck(Err(context))=>(
if context.extra.is_empty(){
CheckSummary::passed("ExtraWormholeIn")
}else{
let context=Separated::new(", ",||context.extra.iter().map(|(&wormhole_id,_names)|
WormholeElement{behaviour:WormholeBehaviour::In,wormhole_id}
));
summary_format!("ExtraWormholeIn","WormholeIn with no matching WormholeOut: {context}",())
},
if context.missing.is_empty(){
CheckSummary::passed("MissingWormholeIn")
}else{
// This counts WormholeIn objects, but
// flipped logic is easier to understand
let context=Separated::new(", ",||context.missing.iter().map(|&wormhole_id|
WormholeElement{behaviour:WormholeBehaviour::Out,wormhole_id}
));
summary_format!("MissingWormholeIn","WormholeOut with no matching WormholeIn: {context}",())
}
)
};
let duplicate_wormhole_out=match &self.wormhole_out_counts{
DuplicateCheck(Ok(()))=>CheckSummary::passed("DuplicateWormholeOut"),
DuplicateCheck(Err(DuplicateCheckContext(context)))=>{
let context=Separated::new(", ",||context.iter().map(|(&wormhole_id,&names)|
Duplicates::new(WormholeElement{behaviour:WormholeBehaviour::Out,wormhole_id},names as usize)
));
summary_format!("DuplicateWormholeOut","Duplicate WormholeOut: {context}",())
}
};
Ok(MapCheckList{checks:Box::new([
model_class,
model_name,
display_name,
creator,
game_id,
mapstart,
duplicate_start,
extra_finish,
missing_finish,
dangling_anticheat,
spawn1,
dangling_teleport,
duplicate_spawns,
extra_wormhole_in,
missing_wormhole_in,
duplicate_wormhole_out,
])})
}
}
#[derive(serde::Serialize)]
pub struct MapCheckList{
pub checks:Box<[CheckSummary;16]>,
}
impl MapCheckList{
pub fn summary(&self)->String{
Separated::new("; ",||self.checks.iter().filter_map(|check|
(!check.passed).then_some(check.summary.as_str())
)).to_string()
}
}
#[derive(Debug)]
pub struct Summary{
pub summary:String,
pub json:serde_json::Value,
}
pub struct CheckReportAndVersion{
pub status:Result<MapInfoOwned,Summary>,
pub version:u64,
}
impl crate::message_handler::MessageHandler{
pub async fn check_inner(&self,check_info:CheckRequest)->Result<CheckReportAndVersion,Error>{
// discover asset creator and latest version
let info=self.cloud_context.get_asset_info(
rbx_asset::cloud::GetAssetLatestRequest{asset_id:check_info.ModelID}
).await.map_err(Error::ModelInfoDownload)?;
// reject models created by a group
let rbx_asset::cloud::Creator::userId(_user_id)=info.creationContext.creator else{
return Err(Error::CreatorTypeMustBeUser);
};
// parse model version string
let version=info.revisionId;
let maybe_gzip=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{
asset_id:check_info.ModelID,
version,
}).await.map_err(Error::Download)?;
// decode dom (slow!)
let dom=maybe_gzip.read_with(read_dom,read_dom).map_err(Error::ModelFileDecode)?;
let map_check=Self::check_dom(&dom).map_err(Error::GetRootInstance)?;
// check the report, generate an error message if it fails the check
let status=match map_check.result(){
Ok(map_info)=>Ok(map_info),
Err(Ok(summary))=>Err(Summary{
summary:summary.summary(),
json:serde_json::to_value(&summary).map_err(Error::ToJsonValue)?,
}),
Err(Err(e))=>return Err(Error::ToJsonValue(e)),
};
Ok(CheckReportAndVersion{status,version})
}
pub fn check_dom(dom:&rbx_dom_weak::WeakDom)->Result<MapCheck,GetRootInstanceError>{
// extract the root instance
let model_instance=get_root_instance(&dom)?;
// extract information from the model
let model_info=get_model_info(&dom,model_instance);
// convert the model information into a structured report
let map_check=model_info.check();
Ok(map_check)
}
}

View File

@@ -0,0 +1,56 @@
use crate::check::CheckReportAndVersion;
use crate::nats_types::CheckMapfixRequest;
#[allow(dead_code)]
#[derive(Debug)]
pub enum Error{
Check(crate::check::Error),
ApiActionMapfixCheck(submissions_api::Error),
}
impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for Error{}
impl crate::message_handler::MessageHandler{
pub async fn check_mapfix(&self,check_info:CheckMapfixRequest)->Result<(),Error>{
let mapfix_id=check_info.MapfixID;
let check_result=self.check_inner(check_info.into()).await;
// update the mapfix depending on the result
match check_result{
Ok(CheckReportAndVersion{status:Ok(map_info),version})=>self.api.action_mapfix_submitted(
submissions_api::types::ActionMapfixSubmittedRequest{
MapfixID:mapfix_id,
ModelVersion:version,
DisplayName:map_info.display_name,
Creator:map_info.creator,
GameID:map_info.game_id as u32,
}
).await.map_err(Error::ApiActionMapfixCheck)?,
// update the mapfix model status to request changes
Ok(CheckReportAndVersion{status:Err(report),..})=>self.api.action_mapfix_request_changes(
submissions_api::types::ActionMapfixRequestChangesRequest{
MapfixID:mapfix_id,
ErrorMessage:report.summary,
}
).await.map_err(Error::ApiActionMapfixCheck)?,
// update the mapfix model status to request changes
Err(e)=>{
// log error
println!("[check_mapfix] Error: {e}");
self.api.action_mapfix_request_changes(
submissions_api::types::ActionMapfixRequestChangesRequest{
MapfixID:mapfix_id,
ErrorMessage:e.to_string(),
}
).await.map_err(Error::ApiActionMapfixCheck)?;
},
}
Ok(())
}
}

View File

@@ -0,0 +1,57 @@
use crate::check::CheckReportAndVersion;
use crate::nats_types::CheckSubmissionRequest;
#[allow(dead_code)]
#[derive(Debug)]
pub enum Error{
Check(crate::check::Error),
ApiActionSubmissionCheck(submissions_api::Error),
}
impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for Error{}
impl crate::message_handler::MessageHandler{
pub async fn check_submission(&self,check_info:CheckSubmissionRequest)->Result<(),Error>{
let submission_id=check_info.SubmissionID;
let check_result=self.check_inner(check_info.into()).await;
// update the submission depending on the result
match check_result{
// update the submission model status to submitted
Ok(CheckReportAndVersion{status:Ok(map_info),version})=>self.api.action_submission_submitted(
submissions_api::types::ActionSubmissionSubmittedRequest{
SubmissionID:submission_id,
ModelVersion:version,
DisplayName:map_info.display_name,
Creator:map_info.creator,
GameID:map_info.game_id as u32,
}
).await.map_err(Error::ApiActionSubmissionCheck)?,
// update the submission model status to request changes
Ok(CheckReportAndVersion{status:Err(report),..})=>self.api.action_submission_request_changes(
submissions_api::types::ActionSubmissionRequestChangesRequest{
SubmissionID:submission_id,
ErrorMessage:report.summary,
}
).await.map_err(Error::ApiActionSubmissionCheck)?,
// update the submission model status to request changes
Err(e)=>{
// log error
println!("[check_submission] Error: {e}");
self.api.action_submission_request_changes(
submissions_api::types::ActionSubmissionRequestChangesRequest{
SubmissionID:submission_id,
ErrorMessage:e.to_string(),
}
).await.map_err(Error::ApiActionSubmissionCheck)?;
},
}
Ok(())
}
}

View File

@@ -1,18 +1,14 @@
use crate::rbx_util::{get_mapinfo,read_dom,MapInfo,ReadDomError,GetMapInfoError,ParseGameIDError}; use crate::download::download_asset_version;
use crate::rbx_util::{get_root_instance,get_mapinfo,read_dom,MapInfo,ReadDomError,GetRootInstanceError,GameID};
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum Error{ pub enum Error{
ModelVersionsPage(rbx_asset::cookie::PageError), CreatorTypeMustBeUser,
EmptyVersionsPage, ModelInfoDownload(rbx_asset::cloud::GetError),
CreatorTypeMustBeUser(rbx_asset::cookie::CreatorType), Download(crate::download::Error),
ModelDetails(rbx_asset::cookie::GetError),
ModelInfoDownload(rbx_asset::cookie::GetAssetV2Error),
ModelFileDownload(rbx_asset::cookie::GetError),
NoLocations,
ModelFileDecode(ReadDomError), ModelFileDecode(ReadDomError),
GetMapInfo(GetMapInfoError), GetRootInstance(GetRootInstanceError),
ParseGameID(ParseGameIDError),
} }
impl std::fmt::Display for Error{ impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
@@ -27,57 +23,50 @@ pub struct CreateRequest{
} }
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
pub struct CreateResult{ pub struct CreateResult{
pub AssetOwner:i64, pub AssetOwner:u64,
pub DisplayName:String, pub DisplayName:Option<String>,
pub Creator:String, pub Creator:Option<String>,
pub GameID:i32, pub GameID:Option<GameID>,
pub AssetVersion:u64, pub AssetVersion:u64,
} }
impl crate::message_handler::MessageHandler{ impl crate::message_handler::MessageHandler{
pub async fn create_inner(&self,create_info:CreateRequest)->Result<CreateResult,Error>{ pub async fn create_inner(&self,create_info:CreateRequest)->Result<CreateResult,Error>{
// discover asset creator // discover asset creator and latest version
let creator_fut=async{ let info=self.cloud_context.get_asset_info(
self.cookie_context.get_asset_details( rbx_asset::cloud::GetAssetLatestRequest{asset_id:create_info.ModelID}
rbx_asset::cookie::GetAssetDetailsRequest{asset_id:create_info.ModelID} ).await.map_err(Error::ModelInfoDownload)?;
).await.map_err(Error::ModelDetails)
// reject models created by a group
let rbx_asset::cloud::Creator::userId(user_id)=info.creationContext.creator else{
return Err(Error::CreatorTypeMustBeUser);
}; };
let asset_version=info.revisionId;
// download the map model // download the map model
let asset_fut=async{ let maybe_gzip=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{
let asset_info=self.cookie_context.get_asset_v2(rbx_asset::cookie::GetAssetRequest{ asset_id:create_info.ModelID,
asset_id:create_info.ModelID, version:asset_version,
version:None, }).await.map_err(Error::Download)?;
}).await.map_err(Error::ModelInfoDownload)?;
let location=asset_info.info.locations.first().ok_or(Error::NoLocations)?;
let data=self.cookie_context.get_asset_v2_download(location).await.map_err(Error::ModelFileDownload)?;
Ok((asset_info.version,data))
};
let (details,(asset_version,model_data))=tokio::try_join!(creator_fut,asset_fut)?;
if details.Creator.CreatorType!=rbx_asset::cookie::CreatorType::User{
return Err(Error::CreatorTypeMustBeUser(details.Creator.CreatorType));
}
// decode dom (slow!) // decode dom (slow!)
let dom=read_dom(&mut std::io::Cursor::new(model_data)).map_err(Error::ModelFileDecode)?; let dom=maybe_gzip.read_with(read_dom,read_dom).map_err(Error::ModelFileDecode)?;
// extract the root instance
let model_instance=get_root_instance(&dom).map_err(Error::GetRootInstance)?;
// parse create fields out of asset // parse create fields out of asset
let MapInfo{ let MapInfo{
display_name, display_name,
creator, creator,
game_id, game_id,
}=get_mapinfo(&dom).map_err(Error::GetMapInfo)?; }=get_mapinfo(&dom,model_instance);
let game_id=game_id.map_err(Error::ParseGameID)?;
Ok(CreateResult{ Ok(CreateResult{
AssetOwner:details.Creator.Id as i64, AssetOwner:user_id,
DisplayName:display_name.unwrap_or_default().to_owned(), DisplayName:display_name.ok().map(ToOwned::to_owned),
Creator:creator.unwrap_or_default().to_owned(), Creator:creator.ok().map(ToOwned::to_owned),
GameID:game_id as i32, GameID:game_id.ok(),
AssetVersion:asset_version, AssetVersion:asset_version,
}) })
} }

View File

@@ -24,13 +24,15 @@ impl crate::message_handler::MessageHandler{
// call create on api // call create on api
self.api.create_mapfix(submissions_api::types::CreateMapfixRequest{ self.api.create_mapfix(submissions_api::types::CreateMapfixRequest{
OperationID:create_info.OperationID, OperationID:create_info.OperationID,
AssetOwner:create_request.AssetOwner, AssetOwner:create_request.AssetOwner as i64,
DisplayName:create_request.DisplayName.as_str(), DisplayName:create_request.DisplayName.as_deref().unwrap_or_default(),
Creator:create_request.Creator.as_str(), Creator:create_request.Creator.as_deref().unwrap_or_default(),
GameID:create_request.GameID, // not great TODO: make this great
GameID:create_request.GameID.unwrap_or(crate::rbx_util::GameID::Bhop) as i32,
AssetID:create_info.ModelID, AssetID:create_info.ModelID,
AssetVersion:create_request.AssetVersion, AssetVersion:create_request.AssetVersion,
TargetAssetID:create_info.TargetAssetID, TargetAssetID:create_info.TargetAssetID,
Description:create_info.Description.as_str(),
}).await.map_err(Error::ApiActionMapfixCreate)?; }).await.map_err(Error::ApiActionMapfixCreate)?;
Ok(()) Ok(())
@@ -41,9 +43,12 @@ impl crate::message_handler::MessageHandler{
let create_result=self.create_mapfix_inner(create_info).await; let create_result=self.create_mapfix_inner(create_info).await;
if let Err(e)=create_result{ if let Err(e)=create_result{
// log error
println!("[create_mapfix] Error: {e}");
self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{ self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{
OperationID:operation_id, OperationID:operation_id,
StatusMessage:format!("{e}"), StatusMessage:e.to_string(),
}).await?; }).await?;
} }

View File

@@ -1,5 +1,6 @@
use crate::nats_types::CreateSubmissionRequest; use crate::nats_types::CreateSubmissionRequest;
use crate::create::CreateRequest; use crate::create::CreateRequest;
use crate::rbx_util::GameID;
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
@@ -19,15 +20,33 @@ impl crate::message_handler::MessageHandler{
let create_request=self.create_inner(CreateRequest{ let create_request=self.create_inner(CreateRequest{
ModelID:create_info.ModelID, ModelID:create_info.ModelID,
}).await.map_err(Error::Create)?; }).await.map_err(Error::Create)?;
// grab values from submission form, otherwise try to fill blanks from map data
let display_name=if create_info.DisplayName.is_empty(){
create_request.DisplayName.as_deref().unwrap_or_default()
}else{
create_info.DisplayName.as_str()
};
let creator=if create_info.Creator.is_empty(){
create_request.Creator.as_deref().unwrap_or_default()
}else{
create_info.Creator.as_str()
};
let game_id=create_info.GameID.try_into().ok().or(create_request.GameID).unwrap_or(GameID::Bhop);
// call create on api // call create on api
self.api.create_submission(submissions_api::types::CreateSubmissionRequest{ self.api.create_submission(submissions_api::types::CreateSubmissionRequest{
OperationID:create_info.OperationID, OperationID:create_info.OperationID,
AssetOwner:create_request.AssetOwner, AssetOwner:create_request.AssetOwner as i64,
DisplayName:create_request.DisplayName.as_str(), DisplayName:display_name,
Creator:create_request.Creator.as_str(), Creator:creator,
GameID:create_request.GameID, GameID:game_id as i32,
AssetID:create_info.ModelID, AssetID:create_info.ModelID,
AssetVersion:create_request.AssetVersion, AssetVersion:create_request.AssetVersion,
Status:create_info.Status,
Roles:create_info.Roles,
}).await.map_err(Error::ApiActionSubmissionCreate)?; }).await.map_err(Error::ApiActionSubmissionCreate)?;
Ok(()) Ok(())
@@ -38,9 +57,12 @@ impl crate::message_handler::MessageHandler{
let create_result=self.create_submission_inner(create_info).await; let create_result=self.create_submission_inner(create_info).await;
if let Err(e)=create_result{ if let Err(e)=create_result{
// log error
println!("[create_submission] Error: {e}");
self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{ self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{
OperationID:operation_id, OperationID:operation_id,
StatusMessage:format!("{e}"), StatusMessage:e.to_string(),
}).await?; }).await?;
} }

View File

@@ -0,0 +1,26 @@
#[allow(dead_code)]
#[derive(Debug)]
pub enum Error{
ModelLocationDownload(rbx_asset::cloud::GetError),
NonFreeModel,
ModelFileDownload(rbx_asset::cloud::GetError),
}
impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for Error{}
pub async fn download_asset_version(cloud_context:&rbx_asset::cloud::Context,request:rbx_asset::cloud::GetAssetVersionRequest)->Result<rbx_asset::types::MaybeGzippedBytes,Error>{
// download the location of the map model
let location=cloud_context.get_asset_version_location(request).await.map_err(Error::ModelLocationDownload)?;
// if the location does not exist, you are not allowed to download it
let location=location.location.ok_or(Error::NonFreeModel)?;
// download the map model
let maybe_gzip=cloud_context.get_asset(&location).await.map_err(Error::ModelFileDownload)?;
Ok(maybe_gzip)
}

16
validation/src/lib.rs Normal file
View File

@@ -0,0 +1,16 @@
pub mod rbx_util;
pub mod message_handler;
pub mod nats_types;
pub mod types;
pub mod download;
pub mod check;
pub mod check_mapfix;
pub mod check_submission;
pub mod create;
pub mod create_mapfix;
pub mod create_submission;
pub mod upload_mapfix;
pub mod upload_submission;
pub mod validator;
pub mod validate_mapfix;
pub mod validate_submission;

View File

@@ -4,6 +4,10 @@ mod rbx_util;
mod message_handler; mod message_handler;
mod nats_types; mod nats_types;
mod types; mod types;
mod download;
mod check;
mod check_mapfix;
mod check_submission;
mod create; mod create;
mod create_mapfix; mod create_mapfix;
mod create_submission; mod create_submission;
@@ -20,6 +24,7 @@ pub enum StartupError{
NatsConnect(async_nats::ConnectError), NatsConnect(async_nats::ConnectError),
NatsGetStream(async_nats::jetstream::context::GetStreamError), NatsGetStream(async_nats::jetstream::context::GetStreamError),
NatsConsumer(async_nats::jetstream::stream::ConsumerError), NatsConsumer(async_nats::jetstream::stream::ConsumerError),
NatsConsumerUpdate(async_nats::jetstream::stream::ConsumerUpdateError),
NatsStream(async_nats::jetstream::consumer::StreamError), NatsStream(async_nats::jetstream::consumer::StreamError),
} }
impl std::fmt::Display for StartupError{ impl std::fmt::Display for StartupError{
@@ -38,12 +43,15 @@ async fn main()->Result<(),StartupError>{
"None"=>None, "None"=>None,
_=>Some(s.parse().expect("ROBLOX_GROUP_ID int parse")), _=>Some(s.parse().expect("ROBLOX_GROUP_ID int parse")),
}, },
Err(e)=>Err(e).expect("ROBLOX_GROUP_ID env required"), Err(e)=>panic!("{e}: ROBLOX_GROUP_ID env required"),
}; };
// talk to roblox through STRAFESNET_CI2 account // create / upload models through STRAFESNET_CI2 account
let cookie=std::env::var("RBXCOOKIE").expect("RBXCOOKIE env required"); let cookie=std::env::var("RBXCOOKIE").expect("RBXCOOKIE env required");
let cookie_context=rbx_asset::cookie::CookieContext::new(rbx_asset::cookie::Cookie::new(cookie)); let cookie_context=rbx_asset::cookie::Context::new(rbx_asset::cookie::Cookie::new(cookie));
// download models through cloud api
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));
// maps-service api // maps-service api
let api_host_internal=std::env::var("API_HOST_INTERNAL").expect("API_HOST_INTERNAL env required"); let api_host_internal=std::env::var("API_HOST_INTERNAL").expect("API_HOST_INTERNAL env required");
@@ -52,20 +60,35 @@ async fn main()->Result<(),StartupError>{
// nats // nats
let nats_host=std::env::var("NATS_HOST").expect("NATS_HOST env required"); let nats_host=std::env::var("NATS_HOST").expect("NATS_HOST env required");
let nats_fut=async{ let nats_fut=async{
const STREAM_NAME:&str="maptest";
const DURABLE_NAME:&str="validation";
const FILTER_SUBJECT:&str="maptest.>";
let nats_config=async_nats::jetstream::consumer::pull::Config{
name:Some(DURABLE_NAME.to_owned()),
durable_name:Some(DURABLE_NAME.to_owned()),
filter_subject:FILTER_SUBJECT.to_owned(),
..Default::default()
};
let nasty=async_nats::connect(nats_host).await.map_err(StartupError::NatsConnect)?; let nasty=async_nats::connect(nats_host).await.map_err(StartupError::NatsConnect)?;
// use nats jetstream // use nats jetstream
async_nats::jetstream::new(nasty) let stream=async_nats::jetstream::new(nasty)
.get_stream("maptest").await.map_err(StartupError::NatsGetStream)? .get_stream(STREAM_NAME).await.map_err(StartupError::NatsGetStream)?;
.get_or_create_consumer("validation",async_nats::jetstream::consumer::pull::Config{
name:Some("validation".to_owned()), let consumer=stream.get_or_create_consumer(DURABLE_NAME,nats_config.clone()).await.map_err(StartupError::NatsConsumer)?;
durable_name:Some("validation".to_owned()),
filter_subject:"maptest.>".to_owned(), // check if config matches expected config
..Default::default() if consumer.cached_info().config.filter_subject!=FILTER_SUBJECT{
}).await.map_err(StartupError::NatsConsumer)? stream.update_consumer(nats_config).await.map_err(StartupError::NatsConsumerUpdate)?;
.messages().await.map_err(StartupError::NatsStream) }
// only need messages
consumer.messages().await.map_err(StartupError::NatsStream)
}; };
let message_handler=message_handler::MessageHandler::new(cookie_context,group_id,api); let message_handler=message_handler::MessageHandler::new(cloud_context,cookie_context,group_id,api);
// Create a signal listener for SIGTERM // Create a signal listener for SIGTERM
let mut sig_term=tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()).expect("Failed to create SIGTERM signal listener"); let mut sig_term=tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()).expect("Failed to create SIGTERM signal listener");

View File

@@ -7,6 +7,8 @@ pub enum HandleMessageError{
UnknownSubject(String), UnknownSubject(String),
CreateMapfix(submissions_api::Error), CreateMapfix(submissions_api::Error),
CreateSubmission(submissions_api::Error), CreateSubmission(submissions_api::Error),
CheckMapfix(crate::check_mapfix::Error),
CheckSubmission(crate::check_submission::Error),
UploadMapfix(crate::upload_mapfix::Error), UploadMapfix(crate::upload_mapfix::Error),
UploadSubmission(crate::upload_submission::Error), UploadSubmission(crate::upload_submission::Error),
ValidateMapfix(crate::validate_mapfix::Error), ValidateMapfix(crate::validate_mapfix::Error),
@@ -26,18 +28,21 @@ fn from_slice<'a,T:serde::de::Deserialize<'a>>(slice:&'a [u8])->Result<T,HandleM
} }
pub struct MessageHandler{ pub struct MessageHandler{
pub(crate) cookie_context:rbx_asset::cookie::CookieContext, pub(crate) cloud_context:rbx_asset::cloud::Context,
pub(crate) cookie_context:rbx_asset::cookie::Context,
pub(crate) group_id:Option<u64>, pub(crate) group_id:Option<u64>,
pub(crate) api:submissions_api::internal::Context, pub(crate) api:submissions_api::internal::Context,
} }
impl MessageHandler{ impl MessageHandler{
pub fn new( pub fn new(
cookie_context:rbx_asset::cookie::CookieContext, cloud_context:rbx_asset::cloud::Context,
cookie_context:rbx_asset::cookie::Context,
group_id:Option<u64>, group_id:Option<u64>,
api:submissions_api::internal::Context, api:submissions_api::internal::Context,
)->Self{ )->Self{
Self{ Self{
cloud_context,
cookie_context, cookie_context,
group_id, group_id,
api, api,
@@ -49,6 +54,8 @@ impl MessageHandler{
match message.subject.as_str(){ match message.subject.as_str(){
"maptest.mapfixes.create"=>self.create_mapfix(from_slice(&message.payload)?).await.map_err(HandleMessageError::CreateMapfix), "maptest.mapfixes.create"=>self.create_mapfix(from_slice(&message.payload)?).await.map_err(HandleMessageError::CreateMapfix),
"maptest.submissions.create"=>self.create_submission(from_slice(&message.payload)?).await.map_err(HandleMessageError::CreateSubmission), "maptest.submissions.create"=>self.create_submission(from_slice(&message.payload)?).await.map_err(HandleMessageError::CreateSubmission),
"maptest.mapfixes.check"=>self.check_mapfix(from_slice(&message.payload)?).await.map_err(HandleMessageError::CheckMapfix),
"maptest.submissions.check"=>self.check_submission(from_slice(&message.payload)?).await.map_err(HandleMessageError::CheckSubmission),
"maptest.mapfixes.upload"=>self.upload_mapfix(from_slice(&message.payload)?).await.map_err(HandleMessageError::UploadMapfix), "maptest.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.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), "maptest.mapfixes.validate"=>self.validate_mapfix(from_slice(&message.payload)?).await.map_err(HandleMessageError::ValidateMapfix),

View File

@@ -10,6 +10,12 @@ pub struct CreateSubmissionRequest{
// operation_id is passed back in the response message // operation_id is passed back in the response message
pub OperationID:i32, pub OperationID:i32,
pub ModelID:u64, pub ModelID:u64,
pub DisplayName:String,
pub Creator:String,
pub GameID:u32,
// initial status is passed back on create
pub Status:u32,
pub Roles:u32,
} }
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
@@ -18,6 +24,21 @@ pub struct CreateMapfixRequest{
pub OperationID:i32, pub OperationID:i32,
pub ModelID:u64, pub ModelID:u64,
pub TargetAssetID:u64, pub TargetAssetID:u64,
pub Description:String,
}
#[allow(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct CheckSubmissionRequest{
pub SubmissionID:i64,
pub ModelID:u64,
}
#[allow(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct CheckMapfixRequest{
pub MapfixID:i64,
pub ModelID:u64,
} }
#[allow(nonstandard_style)] #[allow(nonstandard_style)]

View File

@@ -5,8 +5,7 @@ pub enum ReadDomError{
Binary(rbx_binary::DecodeError), Binary(rbx_binary::DecodeError),
Xml(rbx_xml::DecodeError), Xml(rbx_xml::DecodeError),
Read(std::io::Error), Read(std::io::Error),
Seek(std::io::Error), UnknownFormat(Vec<u8>),
UnknownFormat([u8;8]),
} }
impl std::fmt::Display for ReadDomError{ impl std::fmt::Display for ReadDomError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
@@ -15,46 +14,33 @@ impl std::fmt::Display for ReadDomError{
} }
impl std::error::Error for ReadDomError{} impl std::error::Error for ReadDomError{}
pub fn read_dom<R:std::io::Read+std::io::Seek>(input:&mut R)->Result<rbx_dom_weak::WeakDom,ReadDomError>{ pub fn read_dom<R:std::io::Read>(input:R)->Result<rbx_dom_weak::WeakDom,ReadDomError>{
let mut first_8=[0u8;8]; let mut buf=std::io::BufReader::new(input);
std::io::Read::read_exact(input,&mut first_8).map_err(ReadDomError::Read)?; let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadDomError::Read)?;
std::io::Seek::rewind(input).map_err(ReadDomError::Seek)?; match peek.get(0..8){
match &first_8[0..4]{ Some(b"<roblox!")=>rbx_binary::from_reader(buf).map_err(ReadDomError::Binary),
b"<rob"=>{ Some(b"<roblox ")=>rbx_xml::from_reader_default(buf).map_err(ReadDomError::Xml),
match &first_8[4..8]{ _=>Err(ReadDomError::UnknownFormat(peek.to_owned())),
b"lox!"=>rbx_binary::from_reader(input).map_err(ReadDomError::Binary),
b"lox "=>rbx_xml::from_reader(input,rbx_xml::DecodeOptions::default()).map_err(ReadDomError::Xml),
_=>Err(ReadDomError::UnknownFormat(first_8)),
}
},
_=>Err(ReadDomError::UnknownFormat(first_8)),
} }
} }
pub fn static_ustr(s:&'static str)->rbx_dom_weak::Ustr{
rbx_dom_weak::ustr(s)
}
pub fn class_is_a(class:&str,superclass:&str)->bool{ pub fn class_is_a(class:&str,superclass:&str)->bool{
if class==superclass{ let db=rbx_reflection_database::get();
return true let (Some(class),Some(superclass))=(db.classes.get(class),db.classes.get(superclass))else{
} return false;
let class_descriptor=rbx_reflection_database::get().classes.get(class); };
if let Some(descriptor)=&class_descriptor{ db.has_superclass(class,superclass)
if let Some(class_super)=&descriptor.superclass{
return class_is_a(&class_super,superclass)
}
}
false
} }
pub fn find_first_child_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,name:&str,class:&str)->Option<&'a rbx_dom_weak::Instance>{ fn find_first_child_name_and_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,name:&str,class:&str)->Option<&'a rbx_dom_weak::Instance>{
for &referent in instance.children(){ instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.name==name&&inst.class==class)
if let Some(c)=dom.get_by_ref(referent){
if c.name==name&&class_is_a(c.class.as_str(),class) {
return Some(c);
}
}
}
None
} }
#[derive(Debug)]
pub enum GameID{ pub enum GameID{
Bhop=1, Bhop=1,
Surf=2, Surf=2,
@@ -74,7 +60,19 @@ impl std::str::FromStr for GameID{
if s.starts_with("flytrials_"){ if s.starts_with("flytrials_"){
return Ok(GameID::FlyTrials); return Ok(GameID::FlyTrials);
} }
return Err(ParseGameIDError); Err(ParseGameIDError)
}
}
pub struct GameIDError;
impl TryFrom<u32> for GameID{
type Error=GameIDError;
fn try_from(value:u32)->Result<Self,Self::Error>{
match value{
1=>Ok(GameID::Bhop),
2=>Ok(GameID::Surf),
5=>Ok(GameID::FlyTrials),
_=>Err(GameIDError)
}
} }
} }
@@ -84,6 +82,7 @@ pub struct MapInfo<'a>{
pub game_id:Result<GameID,ParseGameIDError>, pub game_id:Result<GameID,ParseGameIDError>,
} }
#[derive(Debug)]
pub enum StringValueError{ pub enum StringValueError{
ObjectNotFound, ObjectNotFound,
ValueNotSet, ValueNotSet,
@@ -92,7 +91,7 @@ pub enum StringValueError{
fn string_value(instance:Option<&rbx_dom_weak::Instance>)->Result<&str,StringValueError>{ fn string_value(instance:Option<&rbx_dom_weak::Instance>)->Result<&str,StringValueError>{
let instance=instance.ok_or(StringValueError::ObjectNotFound)?; let instance=instance.ok_or(StringValueError::ObjectNotFound)?;
let value=instance.properties.get("Value").ok_or(StringValueError::ValueNotSet)?; let value=instance.properties.get(&static_ustr("Value")).ok_or(StringValueError::ValueNotSet)?;
match value{ match value{
rbx_dom_weak::types::Variant::String(value)=>Ok(value), rbx_dom_weak::types::Variant::String(value)=>Ok(value),
_=>Err(StringValueError::NonStringValue), _=>Err(StringValueError::NonStringValue),
@@ -100,20 +99,24 @@ fn string_value(instance:Option<&rbx_dom_weak::Instance>)->Result<&str,StringVal
} }
#[derive(Debug)] #[derive(Debug)]
pub enum GetMapInfoError{ pub enum GetRootInstanceError{
ModelFileRootMustHaveOneChild, ModelFileRootMustHaveOneChild,
ModelFileChildRefIsNil, ModelFileChildRefIsNil,
} }
pub fn get_mapinfo(dom:&rbx_dom_weak::WeakDom)->Result<MapInfo,GetMapInfoError>{ pub fn get_root_instance(dom:&rbx_dom_weak::WeakDom)->Result<&rbx_dom_weak::Instance,GetRootInstanceError>{
let &[map_ref]=dom.root().children()else{ let &[map_ref]=dom.root().children()else{
return Err(GetMapInfoError::ModelFileRootMustHaveOneChild); return Err(GetRootInstanceError::ModelFileRootMustHaveOneChild);
}; };
let model_instance=dom.get_by_ref(map_ref).ok_or(GetMapInfoError::ModelFileChildRefIsNil)?; let model_instance=dom.get_by_ref(map_ref).ok_or(GetRootInstanceError::ModelFileChildRefIsNil)?;
Ok(MapInfo{ Ok(model_instance)
display_name:string_value(find_first_child_class(dom,model_instance,"DisplayName","StringValue")), }
creator:string_value(find_first_child_class(dom,model_instance,"Creator","StringValue")),
game_id:model_instance.name.parse(), pub fn get_mapinfo<'a>(dom:&'a rbx_dom_weak::WeakDom,model_instance:&rbx_dom_weak::Instance)->MapInfo<'a>{
}) MapInfo{
display_name:string_value(find_first_child_name_and_class(dom,model_instance,"DisplayName","StringValue")),
creator:string_value(find_first_child_name_and_class(dom,model_instance,"Creator","StringValue")),
game_id:model_instance.name.parse(),
}
} }

View File

@@ -1,9 +1,11 @@
use crate::download::download_asset_version;
use crate::nats_types::UploadMapfixRequest; use crate::nats_types::UploadMapfixRequest;
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum Error{ pub enum Error{
Get(rbx_asset::cookie::GetError), Download(crate::download::Error),
IO(std::io::Error),
Json(serde_json::Error), Json(serde_json::Error),
Upload(rbx_asset::cookie::UploadError), Upload(rbx_asset::cookie::UploadError),
ApiActionMapfixUploaded(submissions_api::Error), ApiActionMapfixUploaded(submissions_api::Error),
@@ -17,11 +19,14 @@ impl std::error::Error for Error{}
impl crate::message_handler::MessageHandler{ impl crate::message_handler::MessageHandler{
pub async fn upload_mapfix(&self,upload_info:UploadMapfixRequest)->Result<(),Error>{ pub async fn upload_mapfix(&self,upload_info:UploadMapfixRequest)->Result<(),Error>{
// download the map model version // download the map model
let model_data=self.cookie_context.get_asset(rbx_asset::cookie::GetAssetRequest{ let maybe_gzip=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{
asset_id:upload_info.ModelID, asset_id:upload_info.ModelID,
version:Some(upload_info.ModelVersion), version:upload_info.ModelVersion,
}).await.map_err(Error::Get)?; }).await.map_err(Error::Download)?;
// transparently handle gzipped models
let model_data=maybe_gzip.to_vec().map_err(Error::IO)?;
// upload the map to the strafesnet group // upload the map to the strafesnet group
let _upload_response=self.cookie_context.upload(rbx_asset::cookie::UploadRequest{ let _upload_response=self.cookie_context.upload(rbx_asset::cookie::UploadRequest{

View File

@@ -1,9 +1,11 @@
use crate::download::download_asset_version;
use crate::nats_types::UploadSubmissionRequest; use crate::nats_types::UploadSubmissionRequest;
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum Error{ pub enum Error{
Get(rbx_asset::cookie::GetError), Download(crate::download::Error),
IO(std::io::Error),
Json(serde_json::Error), Json(serde_json::Error),
Create(rbx_asset::cookie::CreateError), Create(rbx_asset::cookie::CreateError),
SystemTime(std::time::SystemTimeError), SystemTime(std::time::SystemTimeError),
@@ -18,11 +20,14 @@ impl std::error::Error for Error{}
impl crate::message_handler::MessageHandler{ impl crate::message_handler::MessageHandler{
pub async fn upload_submission(&self,upload_info:UploadSubmissionRequest)->Result<(),Error>{ pub async fn upload_submission(&self,upload_info:UploadSubmissionRequest)->Result<(),Error>{
// download the map model version // download the map model
let model_data=self.cookie_context.get_asset(rbx_asset::cookie::GetAssetRequest{ let maybe_gzip=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{
asset_id:upload_info.ModelID, asset_id:upload_info.ModelID,
version:Some(upload_info.ModelVersion), version:upload_info.ModelVersion,
}).await.map_err(Error::Get)?; }).await.map_err(Error::Download)?;
// transparently handle gzipped models
let model_data=maybe_gzip.to_vec().map_err(Error::IO)?;
// upload the map to the strafesnet group // upload the map to the strafesnet group
let upload_response=self.cookie_context.create(rbx_asset::cookie::CreateRequest{ let upload_response=self.cookie_context.create(rbx_asset::cookie::CreateRequest{

View File

@@ -26,10 +26,13 @@ impl crate::message_handler::MessageHandler{
).await.map_err(Error::ApiActionMapfixValidate)?; ).await.map_err(Error::ApiActionMapfixValidate)?;
}, },
Err(e)=>{ Err(e)=>{
// log error
println!("[validate_mapfix] Error: {e}");
// update the mapfix model status to accepted // update the mapfix model status to accepted
self.api.action_mapfix_accepted(submissions_api::types::ActionMapfixAcceptedRequest{ self.api.action_mapfix_accepted(submissions_api::types::ActionMapfixAcceptedRequest{
MapfixID:mapfix_id, MapfixID:mapfix_id,
StatusMessage:format!("{e}"), ErrorMessage:e.to_string(),
}).await.map_err(Error::ApiActionMapfixValidate)?; }).await.map_err(Error::ApiActionMapfixValidate)?;
}, },
} }

View File

@@ -26,10 +26,13 @@ impl crate::message_handler::MessageHandler{
).await.map_err(Error::ApiActionSubmissionValidate)?; ).await.map_err(Error::ApiActionSubmissionValidate)?;
}, },
Err(e)=>{ Err(e)=>{
// log error
println!("[validate_submission] Error: {e}");
// update the submission model status to accepted // update the submission model status to accepted
self.api.action_submission_accepted(submissions_api::types::ActionSubmissionAcceptedRequest{ self.api.action_submission_accepted(submissions_api::types::ActionSubmissionAcceptedRequest{
SubmissionID:submission_id, SubmissionID:submission_id,
StatusMessage:format!("{e}"), ErrorMessage:e.to_string(),
}).await.map_err(Error::ApiActionSubmissionValidate)?; }).await.map_err(Error::ApiActionSubmissionValidate)?;
}, },
} }

View File

@@ -1,7 +1,8 @@
use futures::TryStreamExt; use futures::TryStreamExt;
use submissions_api::types::ResourceType; use submissions_api::types::ResourceType;
use crate::rbx_util::{class_is_a,read_dom,ReadDomError}; use crate::download::download_asset_version;
use crate::rbx_util::{read_dom,static_ustr,ReadDomError};
use crate::types::ResourceID; use crate::types::ResourceID;
const SCRIPT_CONCURRENCY:usize=16; const SCRIPT_CONCURRENCY:usize=16;
@@ -20,7 +21,7 @@ struct NamePolicy{
} }
fn source_has_illegal_keywords(source:&str)->bool{ fn source_has_illegal_keywords(source:&str)->bool{
source.find("getfenv").is_some()||source.find("require").is_some() source.contains("getfenv")||source.contains("require")
} }
fn hash_source(source:&str)->String{ fn hash_source(source:&str)->String{
@@ -32,11 +33,11 @@ fn hash_source(source:&str)->String{
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum ValidateError{ pub enum Error{
ScriptFlaggedIllegalKeyword(String), ScriptFlaggedIllegalKeyword(String),
ScriptBlocked(Option<submissions_api::types::ScriptID>), ScriptBlocked(Option<submissions_api::types::ScriptID>),
ScriptNotYetReviewed(Option<submissions_api::types::ScriptID>), ScriptNotYetReviewed(Option<submissions_api::types::ScriptID>),
ModelFileDownload(rbx_asset::cookie::GetError), Download(crate::download::Error),
ModelFileDecode(ReadDomError), ModelFileDecode(ReadDomError),
ApiGetScriptPolicyFromHash(submissions_api::types::SingleItemError), ApiGetScriptPolicyFromHash(submissions_api::types::SingleItemError),
ApiGetScript(submissions_api::Error), ApiGetScript(submissions_api::Error),
@@ -51,12 +52,12 @@ pub enum ValidateError{
AssetUpload(rbx_asset::cookie::UploadError), AssetUpload(rbx_asset::cookie::UploadError),
AssetCreate(rbx_asset::cookie::CreateError), AssetCreate(rbx_asset::cookie::CreateError),
} }
impl std::fmt::Display for ValidateError{ impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}") write!(f,"{self:?}")
} }
} }
impl std::error::Error for ValidateError{} impl std::error::Error for Error{}
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
pub struct ValidateRequest{ pub struct ValidateRequest{
@@ -88,30 +89,33 @@ impl From<crate::nats_types::ValidateSubmissionRequest> for ValidateRequest{
} }
impl crate::message_handler::MessageHandler{ impl crate::message_handler::MessageHandler{
pub async fn validate_inner(&self,validate_info:ValidateRequest)->Result<(),ValidateError>{ pub async fn validate_inner(&self,validate_info:ValidateRequest)->Result<(),Error>{
// download map // download the map model
let data=self.cookie_context.get_asset(rbx_asset::cookie::GetAssetRequest{ let maybe_gzip=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{
asset_id:validate_info.ModelID, asset_id:validate_info.ModelID,
version:Some(validate_info.ModelVersion), version:validate_info.ModelVersion,
}).await.map_err(ValidateError::ModelFileDownload)?; }).await.map_err(Error::Download)?;
// decode dom (slow!) // decode dom (slow!)
let mut dom=read_dom(&mut std::io::Cursor::new(data)).map_err(ValidateError::ModelFileDecode)?; let mut dom=maybe_gzip.read_with(read_dom,read_dom).map_err(Error::ModelFileDecode)?;
/* VALIDATE MAP */ /* VALIDATE MAP */
// stupid ustr thing
let source_property=static_ustr("Source");
// collect unique scripts // collect unique scripts
let script_refs=get_script_refs(&dom); let script_refs=get_script_refs(&dom);
let mut script_map=std::collections::HashMap::<String,NamePolicy>::new(); let mut script_map=std::collections::HashMap::<String,NamePolicy>::new();
for &script_ref in &script_refs{ for &script_ref in &script_refs{
if let Some(script)=dom.get_by_ref(script_ref){ if let Some(script)=dom.get_by_ref(script_ref){
if let Some(rbx_dom_weak::types::Variant::String(source))=script.properties.get("Source"){ if let Some(rbx_dom_weak::types::Variant::String(source))=script.properties.get(&source_property){
// check the source for illegal keywords // check the source for illegal keywords
if source_has_illegal_keywords(source){ if source_has_illegal_keywords(source){
// immediately abort // immediately abort
// grab path to offending script // grab path to offending script
let path=get_partial_path(&dom,script); let path=get_partial_path(&dom,script);
return Err(ValidateError::ScriptFlaggedIllegalKeyword(path)); return Err(Error::ScriptFlaggedIllegalKeyword(path));
} }
// associate a name and policy with the source code // associate a name and policy with the source code
// policy will be fetched from the database to replace the default policy // policy will be fetched from the database to replace the default policy
@@ -132,7 +136,7 @@ impl crate::message_handler::MessageHandler{
// fetch the script policy // fetch the script policy
let script_policy=self.api.get_script_policy_from_hash(submissions_api::types::HashRequest{ let script_policy=self.api.get_script_policy_from_hash(submissions_api::types::HashRequest{
hash:hash.as_str(), hash:hash.as_str(),
}).await.map_err(ValidateError::ApiGetScriptPolicyFromHash)?; }).await.map_err(Error::ApiGetScriptPolicyFromHash)?;
// write the policy to the script_map, fetching the replacement code if necessary // write the policy to the script_map, fetching the replacement code if necessary
if let Some(script_policy)=script_policy{ if let Some(script_policy)=script_policy{
@@ -144,7 +148,7 @@ impl crate::message_handler::MessageHandler{
submissions_api::types::Policy::Replace=>{ submissions_api::types::Policy::Replace=>{
let script=self.api.get_script(submissions_api::types::GetScriptRequest{ let script=self.api.get_script(submissions_api::types::GetScriptRequest{
ScriptID:script_policy.ToScriptID, ScriptID:script_policy.ToScriptID,
}).await.map_err(ValidateError::ApiGetScript)?; }).await.map_err(Error::ApiGetScript)?;
Policy::Replace(script.Source) Policy::Replace(script.Source)
}, },
}; };
@@ -160,14 +164,14 @@ impl crate::message_handler::MessageHandler{
Source:source.as_str(), Source:source.as_str(),
ResourceType:resource_type, ResourceType:resource_type,
ResourceID:Some(resource_id), ResourceID:Some(resource_id),
}).await.map_err(ValidateError::ApiCreateScript)?; }).await.map_err(Error::ApiCreateScript)?;
// create a None policy (pending review by yours truly) // create a None policy (pending review by yours truly)
self.api.create_script_policy(submissions_api::types::CreateScriptPolicyRequest{ self.api.create_script_policy(submissions_api::types::CreateScriptPolicyRequest{
ToScriptID:script.ScriptID, ToScriptID:script.ScriptID,
FromScriptID:script.ScriptID, FromScriptID:script.ScriptID,
Policy:submissions_api::types::Policy::None, Policy:submissions_api::types::Policy::None,
}).await.map_err(ValidateError::ApiCreateScriptPolicy)?; }).await.map_err(Error::ApiCreateScriptPolicy)?;
} }
Ok(()) Ok(())
@@ -175,17 +179,17 @@ impl crate::message_handler::MessageHandler{
.await?; .await?;
// make the replacements // make the replacements
let mut modified=true; let mut modified=false;
for &script_ref in &script_refs{ for &script_ref in &script_refs{
if let Some(script)=dom.get_by_ref_mut(script_ref){ if let Some(script)=dom.get_by_ref_mut(script_ref){
if let Some(rbx_dom_weak::types::Variant::String(source))=script.properties.get_mut("Source"){ if let Some(rbx_dom_weak::types::Variant::String(source))=script.properties.get_mut(&source_property){
match script_map.get(source.as_str()).map(|p|&p.policy){ match script_map.get(source.as_str()).map(|p|&p.policy){
Some(Policy::Blocked)=>{ Some(Policy::Blocked)=>{
let hash=hash_source(source.as_str()); let hash=hash_source(source.as_str());
let script=self.api.get_script_from_hash(submissions_api::types::HashRequest{ let script=self.api.get_script_from_hash(submissions_api::types::HashRequest{
hash:hash.as_str(), hash:hash.as_str(),
}).await.map_err(ValidateError::ApiGetScriptFromHash)?; }).await.map_err(Error::ApiGetScriptFromHash)?;
return Err(ValidateError::ScriptBlocked(script.map(|s|s.ID))); return Err(Error::ScriptBlocked(script.map(|s|s.ID)));
}, },
None None
|Some(Policy::None) |Some(Policy::None)
@@ -193,8 +197,8 @@ impl crate::message_handler::MessageHandler{
let hash=hash_source(source.as_str()); let hash=hash_source(source.as_str());
let script=self.api.get_script_from_hash(submissions_api::types::HashRequest{ let script=self.api.get_script_from_hash(submissions_api::types::HashRequest{
hash:hash.as_str(), hash:hash.as_str(),
}).await.map_err(ValidateError::ApiGetScriptFromHash)?; }).await.map_err(Error::ApiGetScriptFromHash)?;
return Err(ValidateError::ScriptNotYetReviewed(script.map(|s|s.ID))); return Err(Error::ScriptNotYetReviewed(script.map(|s|s.ID)));
}, },
Some(Policy::Allowed)=>(), Some(Policy::Allowed)=>(),
Some(Policy::Delete)=>{ Some(Policy::Delete)=>{
@@ -211,19 +215,17 @@ impl crate::message_handler::MessageHandler{
} }
} }
println!("[Validator] Forcing model upload! modified=true");
// if the model was validated, the submission must be changed to use the modified model // if the model was validated, the submission must be changed to use the modified model
if modified{ let (validated_model_id,validated_model_version)=if modified{
// serialize model (slow!) // serialize model (slow!)
let mut data=Vec::new(); let mut data=Vec::new();
let &[map_ref]=dom.root().children()else{ let &[map_ref]=dom.root().children()else{
return Err(ValidateError::ModelFileRootMustHaveOneChild); return Err(Error::ModelFileRootMustHaveOneChild);
}; };
rbx_binary::to_writer(&mut data,&dom,&[map_ref]).map_err(ValidateError::ModelFileEncode)?; rbx_binary::to_writer(&mut data,&dom,&[map_ref]).map_err(Error::ModelFileEncode)?;
// upload a model lol // upload a model lol
let model_id=if let Some(model_id)=validate_info.ValidatedModelID{ if let Some(model_id)=validate_info.ValidatedModelID{
// upload to existing id // upload to existing id
let response=self.cookie_context.upload(rbx_asset::cookie::UploadRequest{ let response=self.cookie_context.upload(rbx_asset::cookie::UploadRequest{
assetid:model_id, assetid:model_id,
@@ -232,13 +234,13 @@ impl crate::message_handler::MessageHandler{
ispublic:None, ispublic:None,
allowComments:None, allowComments:None,
groupId:None, groupId:None,
},data).await.map_err(ValidateError::AssetUpload)?; },data).await.map_err(Error::AssetUpload)?;
response.AssetId (response.AssetId,response.AssetVersion)
}else{ }else{
// grab the map instance from the map re // grab the map instance from the map ref
let Some(map_instance)=dom.get_by_ref(map_ref)else{ let Some(map_instance)=dom.get_by_ref(map_ref)else{
return Err(ValidateError::ModelFileChildRefIsNil); return Err(Error::ModelFileChildRefIsNil);
}; };
// create new model // create new model
let response=self.cookie_context.create(rbx_asset::cookie::CreateRequest{ let response=self.cookie_context.create(rbx_asset::cookie::CreateRequest{
@@ -247,46 +249,37 @@ impl crate::message_handler::MessageHandler{
ispublic:true, ispublic:true,
allowComments:true, allowComments:true,
groupId:None, groupId:None,
},data).await.map_err(ValidateError::AssetCreate)?; },data).await.map_err(Error::AssetCreate)?;
response.AssetId (response.AssetId,response.AssetVersion)
};
match validate_info.ResourceID{
ResourceID::Mapfix(mapfix_id)=>{
// update the mapfix to use the validated model
self.api.update_mapfix_validated_model(submissions_api::types::UpdateMapfixModelRequest{
MapfixID:mapfix_id,
ModelID:model_id,
ModelVersion:1, //TODO
}).await.map_err(ValidateError::ApiUpdateMapfixModel)?;
},
ResourceID::Submission(submission_id)=>{
// update the submission to use the validated model
self.api.update_submission_validated_model(submissions_api::types::UpdateSubmissionModelRequest{
SubmissionID:submission_id,
ModelID:model_id,
ModelVersion:1, //TODO
}).await.map_err(ValidateError::ApiUpdateSubmissionModel)?;
},
} }
}else{
(validate_info.ModelID,validate_info.ModelVersion)
};
match validate_info.ResourceID{
ResourceID::Mapfix(mapfix_id)=>{
// update the mapfix to use the validated model
self.api.update_mapfix_validated_model(submissions_api::types::UpdateMapfixModelRequest{
MapfixID:mapfix_id,
ModelID:validated_model_id,
ModelVersion:validated_model_version,
}).await.map_err(Error::ApiUpdateMapfixModel)?;
},
ResourceID::Submission(submission_id)=>{
// update the submission to use the validated model
self.api.update_submission_validated_model(submissions_api::types::UpdateSubmissionModelRequest{
SubmissionID:submission_id,
ModelID:validated_model_id,
ModelVersion:validated_model_version,
}).await.map_err(Error::ApiUpdateSubmissionModel)?;
},
} }
Ok(()) Ok(())
} }
} }
fn recursive_collect_superclass(objects:&mut std::vec::Vec<rbx_dom_weak::types::Ref>,dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,superclass:&str){
for &referent in instance.children(){
if let Some(c)=dom.get_by_ref(referent){
if class_is_a(c.class.as_str(),superclass){
objects.push(c.referent());//copy ref
}
recursive_collect_superclass(objects,dom,c,superclass);
}
}
}
fn get_partial_path(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->String{ fn get_partial_path(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->String{
let mut names:Vec<_>=core::iter::successors( let mut names:Vec<_>=core::iter::successors(
Some(instance), Some(instance),
@@ -301,7 +294,11 @@ fn get_partial_path(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)
} }
fn get_script_refs(dom:&rbx_dom_weak::WeakDom)->Vec<rbx_dom_weak::types::Ref>{ fn get_script_refs(dom:&rbx_dom_weak::WeakDom)->Vec<rbx_dom_weak::types::Ref>{
let mut scripts=std::vec::Vec::new(); let db=rbx_reflection_database::get();
recursive_collect_superclass(&mut scripts,dom,dom.root(),"LuaSourceContainer"); let superclass=&db.classes["LuaSourceContainer"];
scripts dom.descendants().filter_map(|inst|{
let class=db.classes.get(inst.class.as_str())?;
db.has_superclass(class,superclass)
.then_some(inst.referent())
}).collect()
} }

View File

@@ -1,4 +1,4 @@
FROM oven/bun:latest FROM registry.itzana.me/docker-proxy/oven/bun:1.2.8
WORKDIR /app WORKDIR /app
@@ -10,4 +10,4 @@ ENV NEXT_TELEMETRY_DISABLED=1
RUN bun install RUN bun install
RUN bun run build RUN bun run build
ENTRYPOINT ["bun", "run", "start"] ENTRYPOINT ["bun", "run", "start"]

BIN
web/public/errors/404.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
web/public/errors/500.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -1,6 +1,11 @@
"use client"
import Link from "next/link" import Link from "next/link"
import Image from "next/image";
import "./styles/header.scss" import "./styles/header.scss"
import { UserInfo } from "@/app/ts/User";
import { useState, useEffect } from "react";
interface HeaderButton { interface HeaderButton {
name: string, name: string,
@@ -15,6 +20,25 @@ function HeaderButton(header: HeaderButton) {
} }
export default function Header() { export default function Header() {
const handleLoginClick = () => {
window.location.href = "/auth/oauth2/login?redirect=" + window.location.href;
};
const [valid, setValid] = useState<boolean>(false)
const [user, setUser] = useState<UserInfo | null>(null)
useEffect(() => {
async function getLoginInfo() {
const [validateData, userData] = await Promise.all([
fetch("/api/session/validate").then(validateResponse => validateResponse.json()),
fetch("/api/session/user").then(userResponse => userResponse.json())
]);
setValid(validateData)
setUser(userData)
}
getLoginInfo()
}, [])
return ( return (
<header className="header-bar"> <header className="header-bar">
<nav className="left"> <nav className="left">
@@ -24,6 +48,16 @@ export default function Header() {
</nav> </nav>
<nav className="right"> <nav className="right">
<HeaderButton name="Submit" href="/submit"/> <HeaderButton name="Submit" href="/submit"/>
{valid && user ? (
<div className="author">
<Link href="/auth">
<Image className="avatar" width={28} height={28} priority={true} src={user.AvatarURL} alt={user.Username}/>
<button>{user.Username}</button>
</Link>
</div>
) : (
<button onClick={handleLoginClick}>Login</button>
)}
</nav> </nav>
</header> </header>
) )

View File

@@ -12,7 +12,7 @@ interface SubmissionCardProps {
id: number; id: number;
} }
export default function SubmissionCard(props: SubmissionCardProps) { export function SubmissionCard(props: SubmissionCardProps) {
return ( return (
<Link href={`/submissions/${props.id}`}> <Link href={`/submissions/${props.id}`}>
<div className="submissionCard"> <div className="submissionCard">
@@ -40,3 +40,32 @@ export default function SubmissionCard(props: SubmissionCardProps) {
</Link> </Link>
); );
} }
export function MapfixCard(props: SubmissionCardProps) {
return (
<Link href={`/mapfixes/${props.id}`}>
<div className="MapfixCard">
<div className="content">
<div className="map-image">
{/* TODO: Grab image of model */}
<Image width={230} height={230} layout="fixed" priority={true} src={`/thumbnails/asset/${props.assetId}`} alt={props.displayName} />
</div>
<div className="details">
<div className="header">
<span className="displayName">{props.displayName}</span>
<div className="rating">
<Rating value={props.rating} readOnly size="small" />
</div>
</div>
<div className="footer">
<div className="author">
<Image className="avatar" width={28} height={28} priority={true} src={`/thumbnails/user/${props.authorId}`} alt={props.author}/>
<span>{props.author}</span>
</div>
</div>
</div>
</div>
</div>
</Link>
);
}

View File

@@ -1,25 +1,8 @@
"use client" "use client"
import { redirect } from "next/navigation";
import { useEffect } from "react";
import Header from "./header"; import Header from "./header";
async function login_check() {
const response = await fetch("/api/session/validate")
if (response.ok) {
const logged_in = await response.json()
if (!logged_in) {
redirect("https://auth.staging.strafes.net/oauth2/login?redirect=" + window.location.href)
}
} else {
console.error("No response from /api/session/validate")
}
}
export default function Webpage({children}: Readonly<{children?: React.ReactNode}>) { export default function Webpage({children}: Readonly<{children?: React.ReactNode}>) {
useEffect(() => { login_check() }, [])
return <> return <>
<Header/> <Header/>
{children} {children}

View File

@@ -0,0 +1,54 @@
@use "../../globals.scss";
::placeholder {
color: var(--placeholder-text)
}
.form-spacer {
margin-bottom: 20px;
&:last-of-type {
margin-top: 15px;
}
}
#target-asset-radio {
color: var(--text-color);
font-size: globals.$form-label-fontsize;
}
.form-field {
width: 850px;
& label, & input {
color: var(--text-color);
}
& fieldset {
border-color: rgb(100,100,100);
}
& span {
color: white;
}
}
main {
display: grid;
justify-content: center;
align-items: center;
margin-inline: auto;
width: 700px;
}
header h1 {
text-align: center;
color: var(--text-color);
}
form {
display: grid;
gap: 25px;
fieldset {
border: blue
}
}

View File

@@ -0,0 +1,65 @@
import { FormControl, Select, InputLabel, MenuItem } from "@mui/material";
import { styled } from '@mui/material/styles';
import InputBase from '@mui/material/InputBase';
import React from "react";
import { SelectChangeEvent } from "@mui/material";
// TODO: Properly style everything instead of pasting 🤚
type GameSelectionProps = {
game: number;
setGame: React.Dispatch<React.SetStateAction<number>>;
};
const BootstrapInput = styled(InputBase)(({ theme }) => ({
'label + &': {
marginTop: theme.spacing(3),
},
'& .MuiInputBase-input': {
backgroundColor: '#0000',
color: '#FFF',
border: '1px solid rgba(175, 175, 175, 0.66)',
fontSize: 16,
padding: '10px 26px 10px 12px',
transition: theme.transitions.create(['border-color', 'box-shadow']),
fontFamily: [
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Roboto',
'"Helvetica Neue"',
'Arial',
'sans-serif',
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
].join(','),
'&:focus': {
borderRadius: 4,
borderColor: '#80bdff',
boxShadow: '0 0 0 0.2rem rgba(0,123,255,.25)',
},
},
}));
export default function GameSelection({ game, setGame }: GameSelectionProps) {
const handleChange = (event: SelectChangeEvent) => {
setGame(Number(event.target.value)); // TODO: Change later!! there's 100% a proper way of doing this
};
return (
<FormControl>
<InputLabel sx={{ color: "#646464" }}>Game</InputLabel>
<Select
value={String(game)}
label="Game"
onChange={handleChange}
input={<BootstrapInput />}
>
<MenuItem value={1}>Bhop</MenuItem>
<MenuItem value={2}>Surf</MenuItem>
<MenuItem value={3}>Fly Trials</MenuItem>
</Select>
</FormControl>
);
}

View File

@@ -0,0 +1,90 @@
"use client"
import { Button, TextField } from "@mui/material"
import GameSelection from "./_game";
import SendIcon from '@mui/icons-material/Send';
import Webpage from "@/app/_components/webpage"
import React, { useState } from "react";
import "./(styles)/page.scss"
interface SubmissionPayload {
AssetID: number;
DisplayName: string;
Creator: string;
GameID: number;
}
interface IdResponse {
OperationID: number;
}
export default function SubmissionInfoPage() {
const [game, setGame] = useState(1);
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const form = event.currentTarget;
const formData = new FormData(form);
const payload: SubmissionPayload = {
DisplayName: (formData.get("display-name") as string) ?? "unknown", // TEMPORARY! TODO: Change
Creator: (formData.get("creator") as string) ?? "unknown", // TEMPORARY! TODO: Change
GameID: game,
AssetID: Number((formData.get("asset-id") as string) ?? "-1"),
};
console.log(payload)
console.log(JSON.stringify(payload))
try {
// Send the POST request
const response = await fetch("/api/submissions-admin", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
// Check if the HTTP request was successful
if (!response.ok) {
const errorDetails = await response.text();
// Throw an error with detailed information
throw new Error(`HTTP error! status: ${response.status}, details: ${errorDetails}`);
}
// Allow any HTTP status
const id_response:IdResponse = await response.json();
// navigate to newly created submission
window.location.assign(`/operations/${id_response.OperationID}`)
} catch (error) {
console.error("Error submitting data:", error);
}
};
return (
<Webpage>
<main>
<header>
<h1>Submit New Map on Behalf of Another User (Admin)</h1>
<span className="spacer form-spacer"></span>
</header>
<form onSubmit={handleSubmit}>
<TextField className="form-field" id="asset-id" name="asset-id" label="Asset ID (required)" variant="outlined"/>
<TextField className="form-field" id="display-name" name="display-name" label="Display Name" variant="outlined"/>
<TextField className="form-field" id="creator" name="creator" label="Creator" variant="outlined"/>
<GameSelection game={game} setGame={setGame} />
<span className="spacer form-spacer"></span>
<Button type="submit" variant="contained" startIcon={<SendIcon/>} sx={{
width: "400px",
height: "50px",
marginInline: "auto"
}}>Create Submission in ChangesRequested Status (Ready to Force-Submit)</Button>
</form>
</main>
</Webpage>
)
}

BIN
web/src/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,35 @@
import path from 'path';
import { promises as fs } from 'fs';
import { NextResponse } from 'next/server';
export async function errorImageResponse(
statusCode: number = 500,
options?: { message?: string }
): Promise<NextResponse> {
const file = `${statusCode}.png`;
const filePath = path.join(process.cwd(), 'public/errors', file);
const headers: Record<string, string> = {
'Content-Type': 'image/png',
};
if (options?.message) {
headers['X-Error-Message'] = encodeURIComponent(options.message);
}
try {
const buffer = await fs.readFile(filePath);
headers['Content-Length'] = buffer.length.toString();
return new NextResponse(buffer, {
status: statusCode,
headers,
});
} catch {
const fallback = path.join(process.cwd(), 'public/errors', '500.png');
const buffer = await fs.readFile(fallback);
headers['Content-Length'] = buffer.length.toString();
return new NextResponse(buffer, {
status: 500,
headers,
});
}
}

View File

@@ -12,7 +12,9 @@ interface CreatorAndReviewStatus {
asset_id: MapfixInfo["AssetID"], asset_id: MapfixInfo["AssetID"],
creator: MapfixInfo["DisplayName"], creator: MapfixInfo["DisplayName"],
review: MapfixInfo["StatusID"], review: MapfixInfo["StatusID"],
status_message: MapfixInfo["StatusMessage"], submitter: MapfixInfo["Submitter"],
target_asset_id: MapfixInfo["TargetAssetID"],
description: MapfixInfo["Description"],
comments: Comment[], comments: Comment[],
name: string name: string
} }
@@ -50,7 +52,7 @@ function LeaveAComment() {
) )
} }
export default function Comments(stats: CommentersProps) { export function Comments(stats: CommentersProps) {
return (<> return (<>
<section className="comments"> <section className="comments">
{stats.comments_data.comments.length===0 {stats.comments_data.comments.length===0
@@ -64,5 +66,6 @@ export default function Comments(stats: CommentersProps) {
} }
export { export {
type CreatorAndReviewStatus type CreatorAndReviewStatus,
type Comment,
} }

Some files were not shown because too many files have changed in this diff Show More