215 Commits

Author SHA1 Message Date
84299675ff later
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-31 18:24:02 -07:00
dd698528be validator: refactor everything 2025-03-31 18:24:02 -07:00
01785bb190 validator: move files 2025-03-31 18:14:08 -07:00
8366b84d90 submissions: tweak script data model
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-31 18:13:31 -07:00
746c7aa9b7 openapi: generate 2025-03-31 18:09:50 -07:00
930eb47096 openapi: tweak script data model 2025-03-31 18:09:25 -07:00
9671c357f4 submissions: ruin script data model 2025-03-31 18:06:39 -07:00
3404251c14 nats: rename events
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-31 16:51:36 -07:00
ffcba57408 validation: tweak validator-uploaded endpoint 2025-03-31 16:27:42 -07:00
a60ccd22f0 submissions: remove prints 2025-03-31 16:27:42 -07:00
f7d7a0891d submissions: tweak validator-uploaded endpoint 2025-03-31 16:27:42 -07:00
e5e85db1fd openapi-internal: generate 2025-03-31 16:27:42 -07:00
0b64440975 openapi-internal: tweak validator-uploaded endpoint 2025-03-31 16:27:42 -07:00
0e29ca98dd web: remove TargetAssetID 2025-03-31 16:27:42 -07:00
9740cbe91a openapi: generate 2025-03-31 16:27:42 -07:00
2d2691b551 openapi: tweak Submission fields 2025-03-31 16:27:42 -07:00
dfc2a605f4 submissions: prepare for separate mapfixes 2025-03-31 16:27:42 -07:00
88c3866654 Revert "submissions: add AcceptedBy, UploadedBy fields to model"
This reverts commit 4c17a3c9e9.
2025-03-31 14:39:06 -07:00
92226e768d submissions: allow map council to upload maps
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2025-03-28 23:57:34 -07:00
4515eb6da2 submissions: typo in error variable names
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-28 23:55:07 -07:00
f2d8c49647 submissions: move pipeline restriction to accept rather than create
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2025-03-28 19:10:29 -07:00
2c75cfa67f submissions: remove StatusUploaded from ActiveSubmissionStatuses 2025-03-28 19:00:26 -07:00
f3689f4916 rename part 2: rename all occurrences of "publish" to "upload"
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2025-03-28 15:56:47 -07:00
e855ace229 rename part 1: move files 2025-03-28 15:56:47 -07:00
6e21447d4b validation: update deps 2025-03-28 15:56:47 -07:00
49fea314ec submissions: log accepter and uploader
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-28 22:34:51 +00:00
4c17a3c9e9 submissions: add AcceptedBy, UploadedBy fields to model 2025-03-28 22:34:51 +00:00
a7784bdaf5 web: fix api types
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is running
2025-03-28 18:26:59 -04:00
f0e18a5963 web: Validate button calls retry-validate endpoint
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2025-03-28 15:07:07 -07:00
661fa17fa7 submissions: implement ActionSubmissionRetryValidate 2025-03-28 15:01:22 -07:00
cc1d5f4bda submissions: remove Accepted from valid src status in ActionSubmissionTriggerValidate 2025-03-28 15:01:09 -07:00
e7a66ebe0d openapi: generate 2025-03-28 14:48:48 -07:00
977f3902b7 openapi: split trigger-validate into two cases 2025-03-28 14:48:27 -07:00
af9f413b49 validation: refactor get_partial_path
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-27 22:53:39 +00:00
b02b3d205e Switch to using /api/session/validate for determining if the user is not logged in (#34)
All checks were successful
continuous-integration/drone/push Build is passing
My apologies for being stupid not knowing the NextJS framework fully, as I have little experience with it and its non intuitive SSR and CSR workflow

Code successfully built locally running `bun run build`

Reviewed-on: #34
Co-authored-by: rhpidfyre <brandon@rhpidfyre.io>
Co-committed-by: rhpidfyre <brandon@rhpidfyre.io>
2025-03-27 21:47:03 +00:00
2f2241612a openapi: generate
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2025-03-26 20:53:25 -07:00
a7c72163eb openapi: user session is required for SessionValidate 2025-03-26 20:53:02 -07:00
c8077482f3 submissions: do not validate session in HandleCookieAuth 2025-03-26 20:53:02 -07:00
79c21b62d8 openapi: generate
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2025-03-26 20:17:03 -07:00
032f0e8739 openapi: opt out of security for get requests 2025-03-26 20:16:44 -07:00
251a24efae web: Revert auth redirect
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2025-03-26 17:23:21 -07:00
a9afdf38cf web: auth redirect fix
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2025-03-26 16:27:32 -07:00
d3edb6b3da validation: include script path in ScriptFlaggedIllegalKeyword
Some checks failed
continuous-integration/drone/push Build is running
continuous-integration/drone/pr Build is failing
2025-03-26 16:23:44 -07:00
188fbd2a6d submissions: rename VersionID to ValidatedModelVersion
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2025-03-26 15:41:46 -07:00
1468a9edc2 openapi: generate 2025-03-26 15:41:19 -07:00
1053719eab openapi: rename field 2025-03-26 15:40:57 -07:00
2867da4b21 submissions: detect sentinel value
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2025-03-26 15:33:47 -07:00
85a144e276 submissions-api: v0.6.1
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-26 14:58:28 -07:00
4227f18992 validator: name model correctly
Some checks failed
continuous-integration/drone/push Build is failing
2025-03-26 21:30:12 +00:00
123bc8af47 validator: write and use tragic script name function 2025-03-26 21:30:12 +00:00
cd82954b73 validator: refactor errors to improve information and clarity 2025-03-26 21:30:12 +00:00
ce08b57e18 submissions-api: include get_scripts & get_script_from_hash in internal api 2025-03-26 21:30:12 +00:00
1ca0348924 submissions-api: derive Clone, Debug on many types 2025-03-26 21:30:12 +00:00
936a1f93aa web: use --turbopack for dev
Some checks are pending
continuous-integration/drone/push Build is running
2025-03-26 21:29:07 +00:00
d5d0e5ffc9 web: redirect if the user is not logged in based on session_id cookie's presence 2025-03-26 21:29:07 +00:00
039309c75a submissions: include status message
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2025-03-26 13:08:56 -07:00
7cc0b5da7f openapi: generate 2025-03-26 13:08:41 -07:00
f0c44fb4a8 openapi: include status message 2025-03-26 13:08:22 -07:00
4fec1bba47 validation: do not implicitly append url
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2025-03-26 12:53:35 -07:00
5ae287f3f2 docker: fix API_HOST
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2025-03-26 12:46:54 -07:00
bf6c8af21a docker: add group id env var
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-26 12:34:27 -07:00
65e63431a3 docker: use staging auth image
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-26 12:26:37 -07:00
a8dc6cd35a submissions: introduce new role SubmissionRelease
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2025-03-26 12:07:06 -07:00
539e09fe06 validator: correct enum item name 2025-03-26 12:07:06 -07:00
87fd7adb93 submissions: rename SubmissionPublish to SubmissionUpload 2025-03-26 12:07:06 -07:00
7d57d1ac4d submissions: improve error granularity 2025-03-26 12:07:06 -07:00
636bb1fb94 submissions: fix roles bug
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2025-03-25 19:42:39 -07:00
295ec3cd8b submissions: refactor UserInfoHandle.GetRoles
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-25 19:32:48 -07:00
6af006f802 fix docker compose
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2025-03-25 19:27:26 -07:00
d16bb8ad02 submissions: refactor roles again
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2025-03-25 18:07:37 -07:00
1af7d7e941 submissions: implement session endpoints
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2025-03-25 17:53:50 -07:00
1feca92f7d submissions: add UserInfoHandle.Validate 2025-03-25 17:44:49 -07:00
7213948a26 submissions: add UserInfoHandle.GetUserInfo function 2025-03-25 17:44:49 -07:00
783d0e843c submissions: refactor roles 2025-03-25 17:44:13 -07:00
977d1d20c2 submissions: rename UserInfo to UserInfoHandle 2025-03-25 17:44:13 -07:00
d7634de9ec openapi: generate 2025-03-25 17:44:13 -07:00
8da1c9346b openapi: add session endpoints 2025-03-25 17:44:08 -07:00
894851c0e8 openapi: fix operation summary
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2025-03-25 16:35:50 -07:00
3da4023466 web: throw error on failure status (#16)
All checks were successful
continuous-integration/drone/push Build is passing
Thanks to ai for knowing javascript

Co-authored-by: rhpidfyre <brandon@rhpidfyre.io>
Reviewed-on: #16
Co-authored-by: Quaternions <krakow20@gmail.com>
Co-committed-by: Quaternions <krakow20@gmail.com>
2025-03-24 23:03:53 +00:00
08a4e913a9 Enable auto deploy
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-23 19:13:36 -04:00
6748cb4324 submissions: submitter cannot accept their own submission
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-19 18:12:18 -07:00
73e5c76e75 submissions: reject reset unless validator is stale
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-19 18:05:50 -07:00
b4be174d98 web: implement reset from softlock 2025-03-19 17:49:26 -07:00
f52e0a91a2 openapi: generate 2025-03-19 17:43:17 -07:00
0b1e7085e3 openapi: implement reset from softlock 2025-03-19 17:42:38 -07:00
31f1db6446 submissions: implement reset from softlock 2025-03-19 17:38:40 -07:00
b377405762 web: display validation error
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-18 16:45:41 -07:00
b496f8c0d8 submissions-api: v0.6.0
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-18 16:13:10 -07:00
0c247fbb43 submissions-api: add status message to validation failure 2025-03-18 16:12:42 -07:00
483ffd1d66 submissions: add StatusMessage to submissions 2025-03-18 16:06:47 -07:00
ff01abdd63 openapi: generate 2025-03-18 16:05:02 -07:00
0271ba4d28 openapi: add status message to validation failure 2025-03-18 16:04:28 -07:00
c6b31b7c73 submissions: tweak group roles to allow developers proper staging permissions
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-18 15:12:10 -07:00
80e7d735be openapi: generate
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-18 14:47:19 -07:00
e66513e88d Revert "openapi: no security for get submission requests"
This reverts commit 11e801443f.
2025-03-18 14:47:05 -07:00
355161c3b1 submissions: publish validated model
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-18 14:26:19 -07:00
e5a1dcf144 submissions-api: v0.5.0
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-18 14:20:21 -07:00
99e320d17f submissions-api: validated-model 2025-03-18 14:19:12 -07:00
57d714fdd7 submissions: implement internal validated model 2025-03-18 14:17:18 -07:00
d77bf02185 openapi: generate 2025-03-18 14:16:48 -07:00
47129e2d1f openapi: internal operation updates validated model 2025-03-18 14:16:16 -07:00
b542dba739 submissions: add ValidatedModelID to submissions model 2025-03-18 14:16:16 -07:00
f5c4868dc4 validation: v0.1.1
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-18 13:33:01 -07:00
1341f87bf8 submissions-api: v0.4.0 2025-03-18 13:33:01 -07:00
57544f3f64 submissions-api: external is default usage 2025-03-18 13:32:28 -07:00
ecb88c14a4 validation: explicitly append url (TODO: update deployment env var) 2025-03-18 13:23:48 -07:00
e1645e7c46 validation: use cargo workspace
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-18 12:37:20 -07:00
49e767f027 web: note when submissions list is loaded but empty 2025-03-18 12:32:48 -07:00
91a72ccf8b openapi: generate
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-18 12:08:56 -07:00
11e801443f openapi: no security for get submission requests 2025-03-18 12:08:56 -07:00
8338a71470 submissions: modernize loops 2025-03-18 12:08:56 -07:00
59e5e529c6 Strip /api prefix
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-17 20:25:02 -04:00
a82a78c938 middleware: oops, thats the wrong path
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-16 20:38:49 -04:00
b6c7c76900 document the middleware
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-16 16:42:44 -04:00
75e8d2b7b2 middleware 2025-03-16 16:33:16 -04:00
8dbdfbdb3f document environment variables
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-16 12:11:07 -07:00
28990e2dbe submissions: implement sort functionality for listSubmissions
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2025-01-13 20:34:04 -08:00
a39e2892ef openapi: generate 2025-01-13 05:01:04 -08:00
8e223d432e openapi: add sort parameter to listSubmissions 2025-01-13 05:00:51 -08:00
ic3w0lf
040488d85f small changes
All checks were successful
continuous-integration/drone/push Build is passing
2025-01-13 04:31:33 -07:00
e43f4bd0f0 openapi-internal: remove unused endpoint
All checks were successful
continuous-integration/drone/push Build is passing
2025-01-02 18:33:38 -08:00
ca1e007b07 docker: update compose
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-30 20:11:09 -08:00
952ceab014 submissions: ReleaseSubmissions operation 2024-12-30 20:11:09 -08:00
952b77b3db submissions: connect to maps grpc 2024-12-30 20:11:09 -08:00
0794e7ba46 openapi: generate 2024-12-30 19:16:58 -08:00
bc8b7b68d2 openapi: add release-submissions endpoint 2024-12-30 19:14:49 -08:00
c04ba33f9c submissions: reject duplicate submissions
All checks were successful
continuous-integration/drone/push Build is passing
closes #6
2024-12-28 17:20:27 -08:00
c95d10a0d4 web: submit GameID 2024-12-27 18:50:36 -08:00
94abe3137b web: submit TargetAssetID 2024-12-27 18:50:24 -08:00
78db4eeba7 web: display model id
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-27 18:31:49 -08:00
56ff5670dd web: fix status codes
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-27 18:24:54 -08:00
d584ee2c03 web: submit page navigates to newly created submission
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-27 18:14:02 -08:00
f629ac2998 web: submission page reload after action request completes 2024-12-27 18:00:26 -08:00
07ef22bc02 submissions: limit active submissions to 20
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-27 17:42:29 -08:00
8bf2c92df3 submissions: refactor auth to only make requests when needed
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-27 17:25:18 -08:00
0d549a46d4 TEMP: validation: force model upload to prevent model validation bait and switch
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-27 16:48:41 -08:00
1b58bfd096 web: describe when each button should be visible
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-26 18:28:26 -08:00
cd57ead995 web: remove maptest button 2024-12-26 18:18:59 -08:00
c085ea9b7d submissions: implement ScriptWrite permission
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-26 17:54:42 -08:00
25dbc038ca submissions-api: incorrectly named field
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-26 17:46:45 -08:00
f038b9cda6 submissions-api: wrong url
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-26 17:41:05 -08:00
8b3aa158c9 submissions-api: lazily export other error to avoid importing reqwest elsewhere
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-21 22:39:59 -08:00
a45b4f2f0c validation: flag illegal keywords
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-21 21:08:03 -08:00
ca846972c1 submissions-api: openapi expects optional fields to be omitted
All checks were successful
continuous-integration/drone/push Build is passing
The default serde configuration is to serialize optional values as "null"
2024-12-19 17:48:58 -08:00
a511246d78 bruh 2024-12-19 17:23:46 -08:00
f04ab4f653 submissions: postgres does not support unsigned integers, so let's pretend they are signed 2024-12-19 17:23:46 -08:00
b3ffbe4b50 submissions-api: fix cookie 2024-12-19 16:27:11 -08:00
a7e9dbb94d web: fix up
All checks were successful
continuous-integration/drone/push Build is passing
When possible you should not use inline styling and instead use SCSS files for following convention and keeping consistency, Grid is also a deprecated React component in Material UI
You should also separate components that are client only to its own .tsx module rather than having it be mixed with components that aren't required for being client only
2024-12-19 02:30:45 -05:00
ic3w0lf
b0b16c91dc compilable:)
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-18 22:55:12 -07:00
ic3w0lf
9bd3eb69f9 Huge mess
Some checks failed
continuous-integration/drone/push Build is failing
2024-12-18 22:12:15 -07:00
02d77ab421 submissions-api: v0.3.0 refactor
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-18 19:12:06 -08:00
8dbb4517fa submissions-api: silence lint 2024-12-18 19:12:06 -08:00
b782b1ae64 submissions-api: add eq to select types 2024-12-18 18:57:52 -08:00
246b8a7dc8 validation: update api 2024-12-18 17:01:12 -08:00
621edbdbe0 submissions: normalize get from hash as list requests 2024-12-18 15:46:37 -08:00
516bd7a439 openapi: generate 2024-12-18 15:06:42 -08:00
6a8805b91a openapi: normalize get policy from hash as list request 2024-12-18 15:06:42 -08:00
518327820d submissions-api: reintroduce external api
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-18 14:28:11 -08:00
964fc24e26 submissions-api: optional cookie 2024-12-18 14:28:11 -08:00
a94ae5d61e submissions: flatten list query params
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-17 21:39:04 -08:00
76d36bea5c openapi: generate 2024-12-17 21:36:40 -08:00
88dfc92bc6 openapi: flatten list query parameters 2024-12-17 21:36:22 -08:00
e905d96917 submissions: fix list requests
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-17 21:03:39 -08:00
b238e4c21d submissions: update openapi
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-17 20:51:34 -08:00
1d3e553390 openapi: generate 2024-12-17 20:50:25 -08:00
6545fa703d openapi: make pagination match game-rpc 2024-12-17 20:50:02 -08:00
a28ec58ce8 openapi: generate
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-17 20:42:31 -08:00
fe0a1e0e0f openapi: remove required ID field on Filter schemas 2024-12-17 20:42:15 -08:00
9070d77f41 validation: set status on failure
Some checks are pending
continuous-integration/drone/push Build is running
2024-12-17 20:32:10 -08:00
0dc39121c8 submissions-api: need stupid dependency to do this 2024-12-17 20:32:10 -08:00
3ea881e724 docker: .dockerignore 2024-12-17 20:32:10 -08:00
6064a1e48f submissions-api: hardcode header to application/json 2024-12-17 20:32:10 -08:00
e7234a614d submissions-api: use goofy function to make errors include more information 2024-12-17 20:08:14 -08:00
299f994f32 openapi: generate 2024-12-17 20:08:14 -08:00
49db6e35ce openapi: no minimum length for script names 2024-12-17 20:08:14 -08:00
185a1d147f sumbissions: return correct http error code 2024-12-17 20:08:14 -08:00
b5bb79c6ef docker: internal only + path copy 2024-12-17 20:08:14 -08:00
f7101e2b84 validation: api is internal only 2024-12-17 20:08:14 -08:00
f3af65aa13 validation: use path 2024-12-17 18:29:47 -08:00
833ed66844 validation: subsume submissions-api 2024-12-17 18:29:14 -08:00
67651633d8 submissions: UpdateSubmissionModel internal endpoint
All checks were successful
continuous-integration/drone/push Build is passing
not quite duplicate code, hooray
2024-12-17 18:26:32 -08:00
7a7e158ec3 submissions: legendary code duplication 2024-12-17 18:23:18 -08:00
7ad4ffc7e0 openapi: generate 2024-12-17 18:23:18 -08:00
e46f9fc6ea openapi: legendary levels of duplicate code 2024-12-17 18:23:18 -08:00
2ad219cf77 submissions: tweak comments 2024-12-17 18:23:00 -08:00
9bdf98635e submissions: comment on unclear status name 2024-12-17 18:10:40 -08:00
3a6dd311bf submissions: wrong query
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-17 17:12:06 -08:00
298a68fa97 submissions: fix unhandled error path causing silent failure 2024-12-17 16:15:33 -08:00
6bab1e1b6b submissions: centralize hashing and formatting
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-17 15:57:39 -08:00
8c45736cf4 validation: fix hash formatting 2024-12-17 15:57:39 -08:00
db52b1dcd4 scripts: name property
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-17 15:45:09 -08:00
f4abc30c21 submissions: return 404 when ErrNotExist
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-17 15:45:06 -08:00
332578ec94 validation: upload new scripts 2024-12-17 15:45:06 -08:00
64e9e2b263 docker: use staging cookie and group
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-17 15:45:00 -08:00
ffadaa44be web: review buttons are no longer hard-coded for submission id 1
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-17 18:31:59 -05:00
9a7270d2f9 submissions: chatgpt solution #2
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-15 03:20:52 -08:00
cb736628d7 validation: plumb group id into publish functions
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-15 02:31:28 -08:00
ec414a0f42 submissions-api: v0.2.2 wrong url in action_submission_uploaded
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-15 02:10:31 -08:00
2342981643 submissions: fix null pointer deref
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-15 01:50:41 -08:00
3cfcbff253 submissions: chatgpt solution 2024-12-15 01:47:43 -08:00
ce59d7c947 submissions-api: v0.2.1 feature flag for external 2024-12-15 01:41:13 -08:00
ed68af80b0 docker: rename stuff for no reason 2024-12-15 01:41:13 -08:00
11846c32e6 docker: use env file because env var is broken 2024-12-15 01:05:51 -08:00
ecbd102aef docker: split internal & external api 2024-12-15 01:05:51 -08:00
cf1fdb4099 submissions-api-rs: v0.2.0 split internal & external 2024-12-15 01:05:51 -08:00
33d272ab04 nats: edit PublishNewRequest message 2024-12-15 01:05:51 -08:00
75d8cafc7b web: change buttons 2024-12-15 01:05:51 -08:00
7d2147779a submissions-api: v0.1.1 2024-12-15 01:05:51 -08:00
7e940cdfb1 submissions: update ActionSubmissionUploaded 2024-12-15 01:05:51 -08:00
47c30ad2db openapi-internal: optionally change TargetAssetID on upload 2024-12-15 01:05:51 -08:00
29b77f14de roles: potential future roles 2024-12-15 01:05:51 -08:00
9e022ca265 submissions: refactor publishing model 2024-12-15 01:05:51 -08:00
95675c51e6 openapi: generate 2024-12-15 00:07:09 -08:00
7a30dc4ec3 openapi: public endpoints use cookieAuth by default 2024-12-15 00:07:01 -08:00
cd9bb17370 openapi: move internal functions to separate api spec 2024-12-15 00:07:01 -08:00
113 changed files with 17352 additions and 7817 deletions

View File

@@ -67,7 +67,10 @@ steps:
- name: deploy - name: deploy
image: argoproj/argocd:latest image: argoproj/argocd:latest
commands: commands:
- echo "Deploy!" # Not going to do actually do this until - argocd login --grpc-web cd.stricity.com --username $USERNAME --password $PASSWORD
- argocd app --grpc-web set ${DRONE_BRANCH}-maps-service --kustomize-image registry.itzana.me/strafesnet/maptest-api:${DRONE_BRANCH}-${DRONE_BUILD_NUMBER}
- argocd app --grpc-web set ${DRONE_BRANCH}-maps-service --kustomize-image registry.itzana.me/strafesnet/maptest-frontend:${DRONE_BRANCH}-${DRONE_BUILD_NUMBER}
- argocd app --grpc-web set ${DRONE_BRANCH}-maps-service --kustomize-image registry.itzana.me/strafesnet/maptest-validator:${DRONE_BRANCH}-${DRONE_BUILD_NUMBER}
environment: environment:
USERNAME: USERNAME:
from_secret: ARGO_USER from_secret: ARGO_USER
@@ -83,6 +86,6 @@ steps:
- staging - staging
--- ---
kind: signature kind: signature
hmac: 9958fd5b01af1ebcc75f7277fe71eb5336b899445c359cecf1b14e83b3d05059 hmac: 1162b329a9cad12b4c5db0ccf8b8998072b0de9279326f76a493fd0af6794095
... ...

3
.gitignore vendored
View File

@@ -1 +1,2 @@
.idea .idea
/target

File diff suppressed because it is too large Load Diff

6
Cargo.toml Normal file
View File

@@ -0,0 +1,6 @@
[workspace]
members = [
"validation",
"validation/api",
]
resolver = "2"

View File

@@ -1,5 +1,5 @@
maps-service: submissions:
DOCKER_BUILDKIT=1 docker build . -f Containerfile -t maps-service DOCKER_BUILDKIT=1 docker build . -f Containerfile -t maps-service-submissions
web: web:
docker build web -f web/Containerfile -t maps-service-web docker build web -f web/Containerfile -t maps-service-web
@@ -7,6 +7,6 @@ web:
validation: validation:
docker build validation -f validation/Containerfile -t maps-service-validation docker build validation -f validation/Containerfile -t maps-service-validation
all: maps-service web validation all: submissions web validation
.PHONY: maps-service web validation .PHONY: submissions web validation

View File

@@ -26,6 +26,12 @@ Prerequisite: golang installed
Prerequisite: bun installed Prerequisite: bun installed
The environment variable `API_HOST` will need to be set for the middleware.
Example `.env` in web's root:
```
API_HOST="http://localhost:8082/v1/"
```
1. `cd web` 1. `cd web`
2. `bun install` 2. `bun install`
@@ -43,6 +49,12 @@ Prerequisite: rust installed
1. `cd validation` 1. `cd validation`
2. `cargo run --release` 2. `cargo run --release`
Environment Variables:
- ROBLOX_GROUP_ID
- RBXCOOKIE
- API_HOST_INTERNAL
- NATS_HOST
#### License #### License
<sup> <sup>

View File

@@ -11,15 +11,17 @@ services:
networks: networks:
- maps-service-network - maps-service-network
mapsservice: submissions:
image: image:
maps-service maps-service-submissions
container_name: mapsservice container_name: submissions
command: [ command: [
# debug # debug
"--debug","serve", "--debug","serve",
# http service port # http service port
"--port","8082", "--port","8082",
# internal http endpoints
"--port-internal","8083",
# postgres # postgres
"--pg-host","10.0.0.29", "--pg-host","10.0.0.29",
"--pg-port","5432", "--pg-port","5432",
@@ -28,7 +30,8 @@ services:
"--pg-password","happypostgresuser", "--pg-password","happypostgresuser",
# other hosts # other hosts
"--nats-host","nats:4222", "--nats-host","nats:4222",
"--auth-rpc-host","authrpc:8081" "--auth-rpc-host","authrpc:8081",
"--data-rpc-host","dataservice:9000",
] ]
depends_on: depends_on:
- authrpc - authrpc
@@ -45,21 +48,24 @@ services:
- maps-service-network - maps-service-network
ports: ports:
- "3000:3000" - "3000:3000"
environment:
- API_HOST=http://submissions:8082/v1
validation: validation:
image: image:
maps-service-validation maps-service-validation
container_name: validation container_name: validation
env_file:
- ../auth-compose/strafesnet_staging.env
environment: environment:
- RBXCOOKIE - ROBLOX_GROUP_ID=17032139 # "None" is special case string value
- API_HOST=http://mapsservice:8082 - API_HOST_INTERNAL=http://submissions:8083/v1
- NATS_HOST=nats:4222 - NATS_HOST=nats:4222
- DATA_HOST=http://dataservice:9000
depends_on: depends_on:
- nats - nats
# note: this races the mapsservice which creates a nats stream # note: this races the submissions which creates a nats stream
# the validation will panic if the nats stream is not created # the validation will panic if the nats stream is not created
- mapsservice - submissions
- dataservice - dataservice
networks: networks:
- maps-service-network - maps-service-network
@@ -87,11 +93,12 @@ services:
- maps-service-network - maps-service-network
authrpc: authrpc:
image: registry.itzana.me/strafesnet/auth-service:master image: registry.itzana.me/strafesnet/auth-service:staging
container_name: authrpc container_name: authrpc
command: ["serve", "rpc"] command: ["serve", "rpc"]
environment: environment:
- REDIS_ADDR=authredis:6379 - REDIS_ADDR=authredis:6379
- RBX_GROUP_ID=17032139
env_file: env_file:
- ../auth-compose/auth-service.env - ../auth-compose/auth-service.env
depends_on: depends_on:
@@ -102,7 +109,7 @@ services:
driver: "none" driver: "none"
auth-web: auth-web:
image: registry.itzana.me/strafesnet/auth-service:master image: registry.itzana.me/strafesnet/auth-service:staging
command: ["serve", "web"] command: ["serve", "web"]
environment: environment:
- REDIS_ADDR=authredis:6379 - REDIS_ADDR=authredis:6379

View File

@@ -1,3 +1,4 @@
package main package main
//go:generate go run github.com/ogen-go/ogen/cmd/ogen@latest --target pkg/api --clean openapi.yaml //go:generate go run github.com/ogen-go/ogen/cmd/ogen@latest --target pkg/api --clean openapi.yaml
//go:generate go run github.com/ogen-go/ogen/cmd/ogen@latest --target pkg/internal --clean openapi-internal.yaml

398
openapi-internal.yaml Normal file
View File

@@ -0,0 +1,398 @@
openapi: 3.1.0
info:
title: StrafesNET Internal - OpenAPI 3.1
description: Internal operations inaccessible from the public internet.
version: 0.1.0
tags:
- name: Submissions
description: Submission operations
paths:
/submissions/{SubmissionID}/validated-model:
post:
summary: Update validated model
operationId: updateSubmissionValidatedModel
tags:
- Submissions
parameters:
- $ref: '#/components/parameters/SubmissionID'
- name: ValidatedModelID
in: query
required: true
schema:
type: integer
format: int64
- name: ValidatedModelVersion
in: query
required: true
schema:
type: integer
format: int64
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/validator-validated:
post:
summary: (Internal endpoint) Role Validator changes status from Validating -> Validated
operationId: actionSubmissionValidated
tags:
- Submissions
parameters:
- $ref: '#/components/parameters/SubmissionID'
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/validator-failed:
post:
summary: (Internal endpoint) Role Validator changes status from Validating -> Accepted
operationId: actionSubmissionAccepted
tags:
- Submissions
parameters:
- $ref: '#/components/parameters/SubmissionID'
- name: StatusMessage
in: query
required: true
schema:
type: string
minLength: 0
maxLength: 4096
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/validator-uploaded:
post:
summary: (Internal endpoint) Role Validator changes status from Uploading -> Uploaded
operationId: actionSubmissionUploaded
tags:
- Submissions
parameters:
- $ref: '#/components/parameters/SubmissionID'
- name: UploadedAssetID
in: query
required: true
schema:
type: integer
format: int64
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/script-policy:
get:
summary: Get list of script policies
operationId: listScriptPolicy
tags:
- ScriptPolicy
parameters:
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/Limit"
- name: FromScriptHash
in: query
schema:
type: string
minLength: 16
maxLength: 16
- name: ToScriptID
in: query
schema:
type: integer
format: int64
- name: Policy
in: query
schema:
type: integer
format: int32
responses:
"200":
description: Successful response
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/ScriptPolicy"
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
post:
summary: Create a new script policy
operationId: createScriptPolicy
tags:
- ScriptPolicy
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ScriptPolicyCreate'
responses:
"201":
description: Successful response
content:
application/json:
schema:
$ref: "#/components/schemas/Id"
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/scripts:
get:
summary: Get list of scripts
operationId: listScripts
tags:
- Script
parameters:
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/Limit"
- name: Hash
in: query
schema:
type: string
minLength: 16
maxLength: 16
- name: Name
in: query
schema:
type: string
maxLength: 128
- name: Source
in: query
schema:
type: string
maxLength: 1048576
- name: SubmissionID
in: query
schema:
type: integer
format: int64
responses:
"200":
description: Successful response
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Script"
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
post:
summary: Create a new script
operationId: createScript
tags:
- Scripts
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ScriptCreate'
responses:
"201":
description: Successful response
content:
application/json:
schema:
$ref: "#/components/schemas/Id"
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/scripts/{ScriptID}:
get:
summary: Get the specified script by ID
operationId: getScript
tags:
- Scripts
parameters:
- $ref: '#/components/parameters/ScriptID'
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: "#/components/schemas/Script"
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
components:
parameters:
SubmissionID:
name: SubmissionID
in: path
required: true
description: The unique identifier for a submission.
schema:
type: integer
format: int64
ScriptID:
name: ScriptID
in: path
required: true
description: The unique identifier for a script.
schema:
type: integer
format: int64
Page:
name: Page
in: query
required: true
schema:
type: integer
format: int32
minimum: 1
Limit:
name: Limit
in: query
required: true
schema:
type: integer
format: int32
minimum: 1
maximum: 100
schemas:
Id:
required:
- ID
type: object
properties:
ID:
type: integer
format: int64
Script:
required:
- ID
- Name
- Hash
- Source
- ResourceType
- ResourceID
type: object
properties:
ID:
type: integer
format: int64
Name:
type: string
maxLength: 128
Hash:
type: string
minLength: 16
maxLength: 16
Source:
type: string
maxLength: 1048576
ResourceType:
type: integer
format: int32
ResourceID:
type: integer
format: int64
ScriptCreate:
required:
- Name
- Source
- ResourceType
# - ResourceID
type: object
properties:
Name:
type: string
maxLength: 128
Source:
type: string
maxLength: 1048576
ResourceType:
type: integer
format: int32
ResourceID:
type: integer
format: int64
ScriptPolicy:
required:
- ID
- FromScriptHash
- ToScriptID
- Policy
type: object
properties:
ID:
type: integer
format: int64
FromScriptHash:
type: string
minLength: 16
maxLength: 16
ToScriptID:
type: integer
format: int64
Policy:
type: integer
format: int32
ScriptPolicyCreate:
required:
- FromScriptID
- ToScriptID
- Policy
type: object
properties:
FromScriptID:
type: integer
format: int64
ToScriptID:
type: integer
format: int64
Policy:
type: integer
format: int32
Error:
description: Represents error object
type: object
properties:
code:
type: integer
format: int64
message:
type: string
required:
- code
- message

View File

@@ -6,34 +6,104 @@ info:
servers: servers:
- url: https://submissions.strafes.net/v1 - url: https://submissions.strafes.net/v1
tags: tags:
- name: Session
description: Session operations
- name: Submissions - name: Submissions
description: Submission operations description: Submission operations
- name: Scripts - name: Scripts
description: Script operations description: Script operations
- name: ScriptPolicy - name: ScriptPolicy
description: Script policy operations description: Script policy operations
security:
- cookieAuth: []
paths: paths:
/session/user:
get:
summary: Get information about the currently logged in user
operationId: sessionUser
tags:
- Session
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: "#/components/schemas/User"
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/session/roles:
get:
summary: Get list of roles for the current session
operationId: sessionRoles
tags:
- Session
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: "#/components/schemas/Roles"
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/session/validate:
get:
summary: Ask if the current session is valid
operationId: sessionValidate
tags:
- Session
responses:
"200":
description: Successful response
content:
application/json:
schema:
type: boolean
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/submissions: /submissions:
get: get:
summary: Get list of submissions summary: Get list of submissions
operationId: listSubmissions operationId: listSubmissions
tags: tags:
- Submissions - Submissions
requestBody: security: []
required: true parameters:
content: - $ref: "#/components/parameters/Page"
application/json: - $ref: "#/components/parameters/Limit"
schema: - name: DisplayName
required: in: query
- Page schema:
type: object type: string
properties: maxLength: 128
Page: - name: Creator
$ref: "#/components/schemas/Pagination" in: query
Filter: schema:
$ref: "#/components/schemas/SubmissionFilter" type: string
security: maxLength: 128
- cookieAuth: [] - name: GameID
in: query
schema:
type: integer
format: int32
- name: Sort
in: query
schema:
type: integer
format: int32
responses: responses:
"200": "200":
description: Successful response description: Successful response
@@ -60,8 +130,6 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/SubmissionCreate' $ref: '#/components/schemas/SubmissionCreate'
security:
- cookieAuth: []
responses: responses:
"201": "201":
description: Successful response description: Successful response
@@ -81,10 +149,9 @@ paths:
operationId: getSubmission operationId: getSubmission
tags: tags:
- Submissions - Submissions
security: []
parameters: parameters:
- $ref: '#/components/parameters/SubmissionID' - $ref: '#/components/parameters/SubmissionID'
security:
- cookieAuth: []
responses: responses:
"200": "200":
description: Successful response description: Successful response
@@ -118,8 +185,6 @@ paths:
schema: schema:
type: integer type: integer
format: int64 format: int64
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@@ -131,14 +196,12 @@ paths:
$ref: "#/components/schemas/Error" $ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/completed: /submissions/{SubmissionID}/completed:
post: post:
summary: Retrieve map with ID summary: Called by maptest when a player completes the map
operationId: setSubmissionCompleted operationId: setSubmissionCompleted
tags: tags:
- Submissions - Submissions
parameters: parameters:
- $ref: '#/components/parameters/SubmissionID' - $ref: '#/components/parameters/SubmissionID'
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@@ -156,8 +219,6 @@ paths:
- Submissions - Submissions
parameters: parameters:
- $ref: '#/components/parameters/SubmissionID' - $ref: '#/components/parameters/SubmissionID'
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@@ -175,8 +236,6 @@ paths:
- Submissions - Submissions
parameters: parameters:
- $ref: '#/components/parameters/SubmissionID' - $ref: '#/components/parameters/SubmissionID'
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@@ -188,14 +247,46 @@ paths:
$ref: "#/components/schemas/Error" $ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/trigger-validate: /submissions/{SubmissionID}/status/trigger-validate:
post: post:
summary: Role Reviewer triggers validation and changes status from Submitted|Accepted -> Validating summary: Role Reviewer triggers validation and changes status from Submitted -> Validating
operationId: actionSubmissionTriggerValidate operationId: actionSubmissionTriggerValidate
tags: tags:
- Submissions - Submissions
parameters: parameters:
- $ref: '#/components/parameters/SubmissionID' - $ref: '#/components/parameters/SubmissionID'
security: responses:
- cookieAuth: [] "204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/retry-validate:
post:
summary: Role Reviewer re-runs validation and changes status from Accepted -> Validating
operationId: actionSubmissionRetryValidate
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-validating:
post:
summary: Role Reviewer manually resets validating softlock and changes status from Validating -> Accepted
operationId: actionSubmissionAccepted
tags:
- Submissions
parameters:
- $ref: '#/components/parameters/SubmissionID'
responses: responses:
"204": "204":
description: Successful response description: Successful response
@@ -213,8 +304,6 @@ paths:
- Submissions - Submissions
parameters: parameters:
- $ref: '#/components/parameters/SubmissionID' - $ref: '#/components/parameters/SubmissionID'
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@@ -232,8 +321,6 @@ paths:
- Submissions - Submissions
parameters: parameters:
- $ref: '#/components/parameters/SubmissionID' - $ref: '#/components/parameters/SubmissionID'
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@@ -243,10 +330,10 @@ paths:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Error" $ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/validator-validated: /submissions/{SubmissionID}/status/trigger-upload:
post: post:
summary: (Internal endpoint) Role Validator changes status from Validating -> Validated summary: Role Admin changes status from Validated -> Uploading
operationId: actionSubmissionValidate operationId: actionSubmissionTriggerUpload
tags: tags:
- Submissions - Submissions
parameters: parameters:
@@ -260,10 +347,10 @@ paths:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Error" $ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/validator-published: /submissions/{SubmissionID}/status/reset-uploading:
post: post:
summary: (Internal endpoint) Role Validator changes status from Publishing -> Published summary: Role Admin manually resets uploading softlock and changes status from Uploading -> Validated
operationId: actionSubmissionPublish operationId: actionSubmissionValidated
tags: tags:
- Submissions - Submissions
parameters: parameters:
@@ -277,18 +364,24 @@ paths:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Error" $ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/trigger-publish: /release-submissions:
post: post:
summary: Role Admin changes status from Validated -> Publishing summary: Release a set of uploaded maps
operationId: actionSubmissionTriggerPublish operationId: releaseSubmissions
tags: tags:
- Submissions - Submissions
parameters: requestBody:
- $ref: '#/components/parameters/SubmissionID' required: true
security: content:
- cookieAuth: [] application/json:
schema:
type: array
minItems: 1
maxItems: 255
items:
$ref: '#/components/schemas/ReleaseInfo'
responses: responses:
"204": "201":
description: Successful response description: Successful response
default: default:
description: General Error description: General Error
@@ -302,21 +395,26 @@ paths:
operationId: listScriptPolicy operationId: listScriptPolicy
tags: tags:
- ScriptPolicy - ScriptPolicy
requestBody: security: []
required: true parameters:
content: - $ref: "#/components/parameters/Page"
application/json: - $ref: "#/components/parameters/Limit"
schema: - name: FromScriptHash
required: in: query
- Page schema:
type: object type: string
properties: minLength: 16
Page: maxLength: 16
$ref: "#/components/schemas/Pagination" - name: ToScriptID
Filter: in: query
$ref: "#/components/schemas/ScriptPolicyFilter" schema:
security: type: integer
- cookieAuth: [] format: int64
- name: Policy
in: query
schema:
type: integer
format: int32
responses: responses:
"200": "200":
description: Successful response description: Successful response
@@ -343,8 +441,6 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ScriptPolicyCreate' $ref: '#/components/schemas/ScriptPolicyCreate'
security:
- cookieAuth: []
responses: responses:
"201": "201":
description: Successful response description: Successful response
@@ -358,45 +454,15 @@ paths:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Error" $ref: "#/components/schemas/Error"
/script-policy/hash/{FromScriptHash}: /script-policy/{ScriptPolicyID}:
get:
summary: Get the policy for the given hash of script source code
operationId: getScriptPolicyFromHash
tags:
- ScriptPolicy
parameters:
- name: FromScriptHash
in: path
required: true
schema:
type: string
minLength: 16
maxLength: 16
security:
- cookieAuth: []
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: "#/components/schemas/ScriptPolicy"
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/script-policy/id/{ScriptPolicyID}:
get: get:
summary: Get the specified script policy by ID summary: Get the specified script policy by ID
operationId: getScriptPolicy operationId: getScriptPolicy
tags: tags:
- ScriptPolicy - ScriptPolicy
security: []
parameters: parameters:
- $ref: '#/components/parameters/ScriptPolicyID' - $ref: '#/components/parameters/ScriptPolicyID'
security:
- cookieAuth: []
responses: responses:
"200": "200":
description: Successful response description: Successful response
@@ -423,8 +489,6 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ScriptPolicyUpdate' $ref: '#/components/schemas/ScriptPolicyUpdate'
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@@ -441,8 +505,6 @@ paths:
- ScriptPolicy - ScriptPolicy
parameters: parameters:
- $ref: '#/components/parameters/ScriptPolicyID' - $ref: '#/components/parameters/ScriptPolicyID'
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@@ -453,6 +515,51 @@ paths:
schema: schema:
$ref: "#/components/schemas/Error" $ref: "#/components/schemas/Error"
/scripts: /scripts:
get:
summary: Get list of scripts
operationId: listScripts
tags:
- Script
security: []
parameters:
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/Limit"
- name: Hash
in: query
schema:
type: string
minLength: 16
maxLength: 16
- name: Name
in: query
schema:
type: string
maxLength: 128
- name: Source
in: query
schema:
type: string
maxLength: 1048576
- name: SubmissionID
in: query
schema:
type: integer
format: int64
responses:
"200":
description: Successful response
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Script"
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
post: post:
summary: Create a new script summary: Create a new script
operationId: createScript operationId: createScript
@@ -464,8 +571,6 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ScriptCreate' $ref: '#/components/schemas/ScriptCreate'
security:
- cookieAuth: []
responses: responses:
"201": "201":
description: Successful response description: Successful response
@@ -485,10 +590,9 @@ paths:
operationId: getScript operationId: getScript
tags: tags:
- Scripts - Scripts
security: []
parameters: parameters:
- $ref: '#/components/parameters/ScriptID' - $ref: '#/components/parameters/ScriptID'
security:
- cookieAuth: []
responses: responses:
"200": "200":
description: Successful response description: Successful response
@@ -515,8 +619,6 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ScriptUpdate' $ref: '#/components/schemas/ScriptUpdate'
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@@ -533,8 +635,6 @@ paths:
- Scripts - Scripts
parameters: parameters:
- $ref: '#/components/parameters/ScriptID' - $ref: '#/components/parameters/ScriptID'
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@@ -575,6 +675,23 @@ components:
schema: schema:
type: integer type: integer
format: int64 format: int64
Page:
name: Page
in: query
required: true
schema:
type: integer
format: int32
minimum: 1
Limit:
name: Limit
in: query
required: true
schema:
type: integer
format: int32
minimum: 1
maximum: 100
schemas: schemas:
Id: Id:
required: required:
@@ -584,6 +701,30 @@ components:
ID: ID:
type: integer type: integer
format: int64 format: int64
Roles:
required:
- Roles
type: object
properties:
Roles:
type: integer
format: int32
User:
required:
- UserID
- Username
- AvatarURL
type: object
properties:
UserID:
type: integer
format: int64
Username:
type: string
maxLength: 128
AvatarURL:
type: string
maxLength: 256
Submission: Submission:
required: required:
- ID - ID
@@ -596,9 +737,9 @@ components:
- AssetID - AssetID
- AssetVersion - AssetVersion
- Completed - Completed
- SubmissionType # - UploadedAssetID
# - TargetAssetID
- StatusID - StatusID
- StatusMessage
type: object type: object
properties: properties:
ID: ID:
@@ -630,32 +771,15 @@ components:
format: int64 format: int64
Completed: Completed:
type: boolean type: boolean
SubmissionType: UploadedAssetID:
type: integer
format: int32
TargetAssetID:
type: integer type: integer
format: int64 format: int64
StatusID: StatusID:
type: integer type: integer
format: int32 format: int32
SubmissionFilter: StatusMessage:
required:
- ID
type: object
properties:
ID:
type: integer
format: int64
DisplayName:
type: string type: string
maxLength: 128 maxLength: 256
Creator:
type: string
maxLength: 128
GameID:
type: integer
format: int32
SubmissionCreate: SubmissionCreate:
required: required:
- DisplayName - DisplayName
@@ -663,7 +787,6 @@ components:
- GameID - GameID
- AssetID - AssetID
- AssetVersion - AssetVersion
# - TargetAssetID
type: object type: object
properties: properties:
DisplayName: DisplayName:
@@ -681,20 +804,34 @@ components:
AssetVersion: AssetVersion:
type: integer type: integer
format: int64 format: int64
TargetAssetID: ReleaseInfo:
required:
- SubmissionID
- Date
type: object
properties:
SubmissionID:
type: integer type: integer
format: int64 format: int64
Date:
type: string
format: date-time
Script: Script:
required: required:
- ID - ID
- Name
- Hash - Hash
- Source - Source
- SubmissionID - ResourceType
- ResourceID
type: object type: object
properties: properties:
ID: ID:
type: integer type: integer
format: int64 format: int64
Name:
type: string
maxLength: 128
Hash: Hash:
type: string type: string
minLength: 16 minLength: 16
@@ -702,19 +839,30 @@ components:
Source: Source:
type: string type: string
maxLength: 1048576 maxLength: 1048576
SubmissionID: ResourceType:
type: integer
format: int32
ResourceID:
type: integer type: integer
format: int64 format: int64
ScriptCreate: ScriptCreate:
required: required:
- Name
- Source - Source
# - SubmissionID - ResourceType
# - ResourceID
type: object type: object
properties: properties:
Name:
type: string
maxLength: 128
Source: Source:
type: string type: string
maxLength: 1048576 maxLength: 1048576
SubmissionID: ResourceType:
type: integer
format: int32
ResourceID:
type: integer type: integer
format: int64 format: int64
ScriptUpdate: ScriptUpdate:
@@ -725,10 +873,16 @@ components:
ID: ID:
type: integer type: integer
format: int64 format: int64
Name:
type: string
maxLength: 128
Source: Source:
type: string type: string
maxLength: 1048576 maxLength: 1048576
SubmissionID: ResourceType:
type: integer
format: int32
ResourceID:
type: integer type: integer
format: int64 format: int64
ScriptPolicy: ScriptPolicy:
@@ -752,22 +906,6 @@ components:
Policy: Policy:
type: integer type: integer
format: int32 format: int32
ScriptPolicyFilter:
type: object
properties:
ID:
type: integer
format: int64
FromScriptHash:
type: string
minLength: 16
maxLength: 16
ToScriptID:
type: integer
format: int64
Policy:
type: integer
format: int32
ScriptPolicyCreate: ScriptPolicyCreate:
required: required:
- FromScriptID - FromScriptID
@@ -801,21 +939,6 @@ components:
Policy: Policy:
type: integer type: integer
format: int32 format: int32
Pagination:
type: object
required:
- Page
- Limit
properties:
Page:
type: integer
format: int32
minimum: 1
Limit:
type: integer
format: int32
minimum: 1
maximum: 100
Error: Error:
description: Represents error object description: Represents error object
type: object type: object

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -6,14 +6,15 @@ package api
type OperationName = string type OperationName = string
const ( const (
ActionSubmissionPublishOperation OperationName = "ActionSubmissionPublish" ActionSubmissionAcceptedOperation OperationName = "ActionSubmissionAccepted"
ActionSubmissionRejectOperation OperationName = "ActionSubmissionReject" ActionSubmissionRejectOperation OperationName = "ActionSubmissionReject"
ActionSubmissionRequestChangesOperation OperationName = "ActionSubmissionRequestChanges" ActionSubmissionRequestChangesOperation OperationName = "ActionSubmissionRequestChanges"
ActionSubmissionRetryValidateOperation OperationName = "ActionSubmissionRetryValidate"
ActionSubmissionRevokeOperation OperationName = "ActionSubmissionRevoke" ActionSubmissionRevokeOperation OperationName = "ActionSubmissionRevoke"
ActionSubmissionSubmitOperation OperationName = "ActionSubmissionSubmit" ActionSubmissionSubmitOperation OperationName = "ActionSubmissionSubmit"
ActionSubmissionTriggerPublishOperation OperationName = "ActionSubmissionTriggerPublish" ActionSubmissionTriggerUploadOperation OperationName = "ActionSubmissionTriggerUpload"
ActionSubmissionTriggerValidateOperation OperationName = "ActionSubmissionTriggerValidate" ActionSubmissionTriggerValidateOperation OperationName = "ActionSubmissionTriggerValidate"
ActionSubmissionValidateOperation OperationName = "ActionSubmissionValidate" ActionSubmissionValidatedOperation OperationName = "ActionSubmissionValidated"
CreateScriptOperation OperationName = "CreateScript" CreateScriptOperation OperationName = "CreateScript"
CreateScriptPolicyOperation OperationName = "CreateScriptPolicy" CreateScriptPolicyOperation OperationName = "CreateScriptPolicy"
CreateSubmissionOperation OperationName = "CreateSubmission" CreateSubmissionOperation OperationName = "CreateSubmission"
@@ -21,10 +22,14 @@ const (
DeleteScriptPolicyOperation OperationName = "DeleteScriptPolicy" DeleteScriptPolicyOperation OperationName = "DeleteScriptPolicy"
GetScriptOperation OperationName = "GetScript" GetScriptOperation OperationName = "GetScript"
GetScriptPolicyOperation OperationName = "GetScriptPolicy" GetScriptPolicyOperation OperationName = "GetScriptPolicy"
GetScriptPolicyFromHashOperation OperationName = "GetScriptPolicyFromHash"
GetSubmissionOperation OperationName = "GetSubmission" GetSubmissionOperation OperationName = "GetSubmission"
ListScriptPolicyOperation OperationName = "ListScriptPolicy" ListScriptPolicyOperation OperationName = "ListScriptPolicy"
ListScriptsOperation OperationName = "ListScripts"
ListSubmissionsOperation OperationName = "ListSubmissions" ListSubmissionsOperation OperationName = "ListSubmissions"
ReleaseSubmissionsOperation OperationName = "ReleaseSubmissions"
SessionRolesOperation OperationName = "SessionRoles"
SessionUserOperation OperationName = "SessionUser"
SessionValidateOperation OperationName = "SessionValidate"
SetSubmissionCompletedOperation OperationName = "SetSubmissionCompleted" SetSubmissionCompletedOperation OperationName = "SetSubmissionCompleted"
UpdateScriptOperation OperationName = "UpdateScript" UpdateScriptOperation OperationName = "UpdateScript"
UpdateScriptPolicyOperation OperationName = "UpdateScriptPolicy" UpdateScriptPolicyOperation OperationName = "UpdateScriptPolicy"

File diff suppressed because it is too large Load Diff

View File

@@ -220,8 +220,8 @@ func (s *Server) decodeCreateSubmissionRequest(r *http.Request) (
} }
} }
func (s *Server) decodeListScriptPolicyRequest(r *http.Request) ( func (s *Server) decodeReleaseSubmissionsRequest(r *http.Request) (
req *ListScriptPolicyReq, req []ReleaseInfo,
close func() error, close func() error,
rerr error, rerr error,
) { ) {
@@ -260,9 +260,17 @@ func (s *Server) decodeListScriptPolicyRequest(r *http.Request) (
d := jx.DecodeBytes(buf) d := jx.DecodeBytes(buf)
var request ListScriptPolicyReq var request []ReleaseInfo
if err := func() error { if err := func() error {
if err := request.Decode(d); err != nil { request = make([]ReleaseInfo, 0)
if err := d.Arr(func(d *jx.Decoder) error {
var elem ReleaseInfo
if err := elem.Decode(d); err != nil {
return err
}
request = append(request, elem)
return nil
}); err != nil {
return err return err
} }
if err := d.Skip(); err != io.EOF { if err := d.Skip(); err != io.EOF {
@@ -278,85 +286,22 @@ func (s *Server) decodeListScriptPolicyRequest(r *http.Request) (
return req, close, err return req, close, err
} }
if err := func() error { if err := func() error {
if err := request.Validate(); err != nil { if request == nil {
return err return errors.New("nil is invalid value")
}
if err := (validate.Array{
MinLength: 1,
MinLengthSet: true,
MaxLength: 255,
MaxLengthSet: true,
}).ValidateLength(len(request)); err != nil {
return errors.Wrap(err, "array")
} }
return nil return nil
}(); err != nil { }(); err != nil {
return req, close, errors.Wrap(err, "validate") return req, close, errors.Wrap(err, "validate")
} }
return &request, close, nil return request, close, nil
default:
return req, close, validate.InvalidContentType(ct)
}
}
func (s *Server) decodeListSubmissionsRequest(r *http.Request) (
req *ListSubmissionsReq,
close func() error,
rerr error,
) {
var closers []func() error
close = func() error {
var merr error
// Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- {
c := closers[i]
merr = multierr.Append(merr, c())
}
return merr
}
defer func() {
if rerr != nil {
rerr = multierr.Append(rerr, close())
}
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
if r.ContentLength == 0 {
return req, close, validate.ErrBodyRequired
}
buf, err := io.ReadAll(r.Body)
if err != nil {
return req, close, err
}
if len(buf) == 0 {
return req, close, validate.ErrBodyRequired
}
d := jx.DecodeBytes(buf)
var request ListSubmissionsReq
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: default:
return req, close, validate.InvalidContentType(ct) return req, close, validate.InvalidContentType(ct)
} }

View File

@@ -53,28 +53,18 @@ func encodeCreateSubmissionRequest(
return nil return nil
} }
func encodeListScriptPolicyRequest( func encodeReleaseSubmissionsRequest(
req *ListScriptPolicyReq, req []ReleaseInfo,
r *http.Request, r *http.Request,
) error { ) error {
const contentType = "application/json" const contentType = "application/json"
e := new(jx.Encoder) e := new(jx.Encoder)
{ {
req.Encode(e) e.ArrStart()
} for _, elem := range req {
encoded := e.Bytes() elem.Encode(e)
ht.SetBody(r, bytes.NewReader(encoded), contentType) }
return nil e.ArrEnd()
}
func encodeListSubmissionsRequest(
req *ListSubmissionsReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
} }
encoded := e.Bytes() encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType) ht.SetBody(r, bytes.NewReader(encoded), contentType)

View File

@@ -15,11 +15,11 @@ import (
"github.com/ogen-go/ogen/validate" "github.com/ogen-go/ogen/validate"
) )
func decodeActionSubmissionPublishResponse(resp *http.Response) (res *ActionSubmissionPublishNoContent, _ error) { func decodeActionSubmissionAcceptedResponse(resp *http.Response) (res *ActionSubmissionAcceptedNoContent, _ error) {
switch resp.StatusCode { switch resp.StatusCode {
case 204: case 204:
// Code 204. // Code 204.
return &ActionSubmissionPublishNoContent{}, nil return &ActionSubmissionAcceptedNoContent{}, nil
} }
// Convenient error response. // Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) { defRes, err := func() (res *ErrorStatusCode, err error) {
@@ -168,6 +168,57 @@ func decodeActionSubmissionRequestChangesResponse(resp *http.Response) (res *Act
return res, errors.Wrap(defRes, "error") return res, errors.Wrap(defRes, "error")
} }
func decodeActionSubmissionRetryValidateResponse(resp *http.Response) (res *ActionSubmissionRetryValidateNoContent, _ error) {
switch resp.StatusCode {
case 204:
// Code 204.
return &ActionSubmissionRetryValidateNoContent{}, 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
}
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 decodeActionSubmissionRevokeResponse(resp *http.Response) (res *ActionSubmissionRevokeNoContent, _ error) { func decodeActionSubmissionRevokeResponse(resp *http.Response) (res *ActionSubmissionRevokeNoContent, _ error) {
switch resp.StatusCode { switch resp.StatusCode {
case 204: case 204:
@@ -270,11 +321,11 @@ func decodeActionSubmissionSubmitResponse(resp *http.Response) (res *ActionSubmi
return res, errors.Wrap(defRes, "error") return res, errors.Wrap(defRes, "error")
} }
func decodeActionSubmissionTriggerPublishResponse(resp *http.Response) (res *ActionSubmissionTriggerPublishNoContent, _ error) { func decodeActionSubmissionTriggerUploadResponse(resp *http.Response) (res *ActionSubmissionTriggerUploadNoContent, _ error) {
switch resp.StatusCode { switch resp.StatusCode {
case 204: case 204:
// Code 204. // Code 204.
return &ActionSubmissionTriggerPublishNoContent{}, nil return &ActionSubmissionTriggerUploadNoContent{}, nil
} }
// Convenient error response. // Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) { defRes, err := func() (res *ErrorStatusCode, err error) {
@@ -372,11 +423,11 @@ func decodeActionSubmissionTriggerValidateResponse(resp *http.Response) (res *Ac
return res, errors.Wrap(defRes, "error") return res, errors.Wrap(defRes, "error")
} }
func decodeActionSubmissionValidateResponse(resp *http.Response) (res *ActionSubmissionValidateNoContent, _ error) { func decodeActionSubmissionValidatedResponse(resp *http.Response) (res *ActionSubmissionValidatedNoContent, _ error) {
switch resp.StatusCode { switch resp.StatusCode {
case 204: case 204:
// Code 204. // Code 204.
return &ActionSubmissionValidateNoContent{}, nil return &ActionSubmissionValidatedNoContent{}, nil
} }
// Convenient error response. // Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) { defRes, err := func() (res *ErrorStatusCode, err error) {
@@ -958,98 +1009,6 @@ func decodeGetScriptPolicyResponse(resp *http.Response) (res *ScriptPolicy, _ er
return res, errors.Wrap(defRes, "error") return res, errors.Wrap(defRes, "error")
} }
func decodeGetScriptPolicyFromHashResponse(resp *http.Response) (res *ScriptPolicy, _ error) {
switch resp.StatusCode {
case 200:
// Code 200.
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response ScriptPolicy
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
// Validate response.
if err := func() error {
if err := response.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return &response, nil
default:
return res, validate.InvalidContentType(ct)
}
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeGetSubmissionResponse(resp *http.Response) (res *Submission, _ error) { func decodeGetSubmissionResponse(resp *http.Response) (res *Submission, _ error) {
switch resp.StatusCode { switch resp.StatusCode {
case 200: case 200:
@@ -1259,6 +1218,123 @@ func decodeListScriptPolicyResponse(resp *http.Response) (res []ScriptPolicy, _
return res, errors.Wrap(defRes, "error") return res, errors.Wrap(defRes, "error")
} }
func decodeListScriptsResponse(resp *http.Response) (res []Script, _ error) {
switch resp.StatusCode {
case 200:
// Code 200.
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response []Script
if err := func() error {
response = make([]Script, 0)
if err := d.Arr(func(d *jx.Decoder) error {
var elem Script
if err := elem.Decode(d); err != nil {
return err
}
response = append(response, elem)
return nil
}); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
// Validate response.
if err := func() error {
if response == nil {
return errors.New("nil is invalid value")
}
var failures []validate.FieldError
for i, elem := range response {
if err := func() error {
if err := elem.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: fmt.Sprintf("[%d]", i),
Error: err,
})
}
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return response, nil
default:
return res, validate.InvalidContentType(ct)
}
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeListSubmissionsResponse(resp *http.Response) (res []Submission, _ error) { func decodeListSubmissionsResponse(resp *http.Response) (res []Submission, _ error) {
switch resp.StatusCode { switch resp.StatusCode {
case 200: case 200:
@@ -1376,6 +1452,317 @@ func decodeListSubmissionsResponse(resp *http.Response) (res []Submission, _ err
return res, errors.Wrap(defRes, "error") return res, errors.Wrap(defRes, "error")
} }
func decodeReleaseSubmissionsResponse(resp *http.Response) (res *ReleaseSubmissionsCreated, _ error) {
switch resp.StatusCode {
case 201:
// Code 201.
return &ReleaseSubmissionsCreated{}, 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
}
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 decodeSessionRolesResponse(resp *http.Response) (res *Roles, _ error) {
switch resp.StatusCode {
case 200:
// Code 200.
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Roles
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
return &response, nil
default:
return res, validate.InvalidContentType(ct)
}
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeSessionUserResponse(resp *http.Response) (res *User, _ error) {
switch resp.StatusCode {
case 200:
// Code 200.
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response User
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
// Validate response.
if err := func() error {
if err := response.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return &response, nil
default:
return res, validate.InvalidContentType(ct)
}
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeSessionValidateResponse(resp *http.Response) (res bool, _ error) {
switch resp.StatusCode {
case 200:
// Code 200.
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response bool
if err := func() error {
v, err := d.Bool()
response = bool(v)
if err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
return response, nil
default:
return res, validate.InvalidContentType(ct)
}
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeSetSubmissionCompletedResponse(resp *http.Response) (res *SetSubmissionCompletedNoContent, _ error) { func decodeSetSubmissionCompletedResponse(resp *http.Response) (res *SetSubmissionCompletedNoContent, _ error) {
switch resp.StatusCode { switch resp.StatusCode {
case 204: case 204:

View File

@@ -13,7 +13,7 @@ import (
ht "github.com/ogen-go/ogen/http" ht "github.com/ogen-go/ogen/http"
) )
func encodeActionSubmissionPublishResponse(response *ActionSubmissionPublishNoContent, w http.ResponseWriter, span trace.Span) error { func encodeActionSubmissionAcceptedResponse(response *ActionSubmissionAcceptedNoContent, 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 +34,13 @@ func encodeActionSubmissionRequestChangesResponse(response *ActionSubmissionRequ
return nil return nil
} }
func encodeActionSubmissionRetryValidateResponse(response *ActionSubmissionRetryValidateNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeActionSubmissionRevokeResponse(response *ActionSubmissionRevokeNoContent, w http.ResponseWriter, span trace.Span) error { func encodeActionSubmissionRevokeResponse(response *ActionSubmissionRevokeNoContent, 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 +55,7 @@ func encodeActionSubmissionSubmitResponse(response *ActionSubmissionSubmitNoCont
return nil return nil
} }
func encodeActionSubmissionTriggerPublishResponse(response *ActionSubmissionTriggerPublishNoContent, w http.ResponseWriter, span trace.Span) error { func encodeActionSubmissionTriggerUploadResponse(response *ActionSubmissionTriggerUploadNoContent, 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))
@@ -62,7 +69,7 @@ func encodeActionSubmissionTriggerValidateResponse(response *ActionSubmissionTri
return nil return nil
} }
func encodeActionSubmissionValidateResponse(response *ActionSubmissionValidateNoContent, w http.ResponseWriter, span trace.Span) error { func encodeActionSubmissionValidatedResponse(response *ActionSubmissionValidatedNoContent, 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,20 +160,6 @@ func encodeGetScriptPolicyResponse(response *ScriptPolicy, w http.ResponseWriter
return nil return nil
} }
func encodeGetScriptPolicyFromHashResponse(response *ScriptPolicy, 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 encodeGetSubmissionResponse(response *Submission, w http.ResponseWriter, span trace.Span) error { func encodeGetSubmissionResponse(response *Submission, 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)
@@ -199,6 +192,24 @@ func encodeListScriptPolicyResponse(response []ScriptPolicy, w http.ResponseWrit
return nil return nil
} }
func encodeListScriptsResponse(response []Script, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200)
span.SetStatus(codes.Ok, http.StatusText(200))
e := new(jx.Encoder)
e.ArrStart()
for _, elem := range response {
elem.Encode(e)
}
e.ArrEnd()
if _, err := e.WriteTo(w); err != nil {
return errors.Wrap(err, "write")
}
return nil
}
func encodeListSubmissionsResponse(response []Submission, w http.ResponseWriter, span trace.Span) error { func encodeListSubmissionsResponse(response []Submission, 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)
@@ -217,6 +228,55 @@ func encodeListSubmissionsResponse(response []Submission, w http.ResponseWriter,
return nil return nil
} }
func encodeReleaseSubmissionsResponse(response *ReleaseSubmissionsCreated, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(201)
span.SetStatus(codes.Ok, http.StatusText(201))
return nil
}
func encodeSessionRolesResponse(response *Roles, 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 encodeSessionUserResponse(response *User, 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 encodeSessionValidateResponse(response bool, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200)
span.SetStatus(codes.Ok, http.StatusText(200))
e := new(jx.Encoder)
e.Bool(response)
if _, err := e.WriteTo(w); err != nil {
return errors.Wrap(err, "write")
}
return nil
}
func encodeSetSubmissionCompletedResponse(response *SetSubmissionCompletedNoContent, w http.ResponseWriter, span trace.Span) error { func encodeSetSubmissionCompletedResponse(response *SetSubmissionCompletedNoContent, 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))

File diff suppressed because it is too large Load Diff

View File

@@ -4,14 +4,15 @@ package api
import ( import (
"fmt" "fmt"
"time"
) )
func (s *ErrorStatusCode) Error() string { func (s *ErrorStatusCode) Error() string {
return fmt.Sprintf("code %d: %+v", s.StatusCode, s.Response) return fmt.Sprintf("code %d: %+v", s.StatusCode, s.Response)
} }
// ActionSubmissionPublishNoContent is response for ActionSubmissionPublish operation. // ActionSubmissionAcceptedNoContent is response for ActionSubmissionAccepted operation.
type ActionSubmissionPublishNoContent struct{} type ActionSubmissionAcceptedNoContent struct{}
// ActionSubmissionRejectNoContent is response for ActionSubmissionReject operation. // ActionSubmissionRejectNoContent is response for ActionSubmissionReject operation.
type ActionSubmissionRejectNoContent struct{} type ActionSubmissionRejectNoContent struct{}
@@ -19,20 +20,23 @@ type ActionSubmissionRejectNoContent struct{}
// ActionSubmissionRequestChangesNoContent is response for ActionSubmissionRequestChanges operation. // ActionSubmissionRequestChangesNoContent is response for ActionSubmissionRequestChanges operation.
type ActionSubmissionRequestChangesNoContent struct{} type ActionSubmissionRequestChangesNoContent struct{}
// ActionSubmissionRetryValidateNoContent is response for ActionSubmissionRetryValidate operation.
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. // ActionSubmissionSubmitNoContent is response for ActionSubmissionSubmit operation.
type ActionSubmissionSubmitNoContent struct{} type ActionSubmissionSubmitNoContent struct{}
// ActionSubmissionTriggerPublishNoContent is response for ActionSubmissionTriggerPublish operation. // ActionSubmissionTriggerUploadNoContent is response for ActionSubmissionTriggerUpload operation.
type ActionSubmissionTriggerPublishNoContent struct{} type ActionSubmissionTriggerUploadNoContent struct{}
// ActionSubmissionTriggerValidateNoContent is response for ActionSubmissionTriggerValidate operation. // ActionSubmissionTriggerValidateNoContent is response for ActionSubmissionTriggerValidate operation.
type ActionSubmissionTriggerValidateNoContent struct{} type ActionSubmissionTriggerValidateNoContent struct{}
// ActionSubmissionValidateNoContent is response for ActionSubmissionValidate operation. // ActionSubmissionValidatedNoContent is response for ActionSubmissionValidated operation.
type ActionSubmissionValidateNoContent struct{} type ActionSubmissionValidatedNoContent struct{}
type CookieAuth struct { type CookieAuth struct {
APIKey string APIKey string
@@ -122,56 +126,6 @@ func (s *ID) SetID(val int64) {
s.ID = val s.ID = val
} }
type ListScriptPolicyReq struct {
Page Pagination `json:"Page"`
Filter OptScriptPolicyFilter `json:"Filter"`
}
// GetPage returns the value of Page.
func (s *ListScriptPolicyReq) GetPage() Pagination {
return s.Page
}
// GetFilter returns the value of Filter.
func (s *ListScriptPolicyReq) GetFilter() OptScriptPolicyFilter {
return s.Filter
}
// SetPage sets the value of Page.
func (s *ListScriptPolicyReq) SetPage(val Pagination) {
s.Page = val
}
// SetFilter sets the value of Filter.
func (s *ListScriptPolicyReq) SetFilter(val OptScriptPolicyFilter) {
s.Filter = val
}
type ListSubmissionsReq struct {
Page Pagination `json:"Page"`
Filter OptSubmissionFilter `json:"Filter"`
}
// GetPage returns the value of Page.
func (s *ListSubmissionsReq) GetPage() Pagination {
return s.Page
}
// GetFilter returns the value of Filter.
func (s *ListSubmissionsReq) GetFilter() OptSubmissionFilter {
return s.Filter
}
// SetPage sets the value of Page.
func (s *ListSubmissionsReq) SetPage(val Pagination) {
s.Page = val
}
// SetFilter sets the value of Filter.
func (s *ListSubmissionsReq) SetFilter(val OptSubmissionFilter) {
s.Filter = val
}
// NewOptInt32 returns new OptInt32 with value set to v. // NewOptInt32 returns new OptInt32 with value set to v.
func NewOptInt32(v int32) OptInt32 { func NewOptInt32(v int32) OptInt32 {
return OptInt32{ return OptInt32{
@@ -264,52 +218,6 @@ func (o OptInt64) Or(d int64) int64 {
return d return d
} }
// NewOptScriptPolicyFilter returns new OptScriptPolicyFilter with value set to v.
func NewOptScriptPolicyFilter(v ScriptPolicyFilter) OptScriptPolicyFilter {
return OptScriptPolicyFilter{
Value: v,
Set: true,
}
}
// OptScriptPolicyFilter is optional ScriptPolicyFilter.
type OptScriptPolicyFilter struct {
Value ScriptPolicyFilter
Set bool
}
// IsSet returns true if OptScriptPolicyFilter was set.
func (o OptScriptPolicyFilter) IsSet() bool { return o.Set }
// Reset unsets value.
func (o *OptScriptPolicyFilter) Reset() {
var v ScriptPolicyFilter
o.Value = v
o.Set = false
}
// SetTo sets value to v.
func (o *OptScriptPolicyFilter) SetTo(v ScriptPolicyFilter) {
o.Set = true
o.Value = v
}
// Get returns value and boolean that denotes whether value was set.
func (o OptScriptPolicyFilter) Get() (v ScriptPolicyFilter, ok bool) {
if !o.Set {
return v, false
}
return o.Value, true
}
// Or returns value if set, or given parameter if does not.
func (o OptScriptPolicyFilter) Or(d ScriptPolicyFilter) ScriptPolicyFilter {
if v, ok := o.Get(); ok {
return v
}
return d
}
// NewOptString returns new OptString with value set to v. // NewOptString returns new OptString with value set to v.
func NewOptString(v string) OptString { func NewOptString(v string) OptString {
return OptString{ return OptString{
@@ -356,84 +264,58 @@ func (o OptString) Or(d string) string {
return d return d
} }
// NewOptSubmissionFilter returns new OptSubmissionFilter with value set to v. // Ref: #/components/schemas/ReleaseInfo
func NewOptSubmissionFilter(v SubmissionFilter) OptSubmissionFilter { type ReleaseInfo struct {
return OptSubmissionFilter{ SubmissionID int64 `json:"SubmissionID"`
Value: v, Date time.Time `json:"Date"`
Set: true,
}
} }
// OptSubmissionFilter is optional SubmissionFilter. // GetSubmissionID returns the value of SubmissionID.
type OptSubmissionFilter struct { func (s *ReleaseInfo) GetSubmissionID() int64 {
Value SubmissionFilter return s.SubmissionID
Set bool
} }
// IsSet returns true if OptSubmissionFilter was set. // GetDate returns the value of Date.
func (o OptSubmissionFilter) IsSet() bool { return o.Set } func (s *ReleaseInfo) GetDate() time.Time {
return s.Date
// Reset unsets value.
func (o *OptSubmissionFilter) Reset() {
var v SubmissionFilter
o.Value = v
o.Set = false
} }
// SetTo sets value to v. // SetSubmissionID sets the value of SubmissionID.
func (o *OptSubmissionFilter) SetTo(v SubmissionFilter) { func (s *ReleaseInfo) SetSubmissionID(val int64) {
o.Set = true s.SubmissionID = val
o.Value = v
} }
// Get returns value and boolean that denotes whether value was set. // SetDate sets the value of Date.
func (o OptSubmissionFilter) Get() (v SubmissionFilter, ok bool) { func (s *ReleaseInfo) SetDate(val time.Time) {
if !o.Set { s.Date = val
return v, false
}
return o.Value, true
} }
// Or returns value if set, or given parameter if does not. // ReleaseSubmissionsCreated is response for ReleaseSubmissions operation.
func (o OptSubmissionFilter) Or(d SubmissionFilter) SubmissionFilter { type ReleaseSubmissionsCreated struct{}
if v, ok := o.Get(); ok {
return v // Ref: #/components/schemas/Roles
} type Roles struct {
return d Roles int32 `json:"Roles"`
} }
// Ref: #/components/schemas/Pagination // GetRoles returns the value of Roles.
type Pagination struct { func (s *Roles) GetRoles() int32 {
Page int32 `json:"Page"` return s.Roles
Limit int32 `json:"Limit"`
} }
// GetPage returns the value of Page. // SetRoles sets the value of Roles.
func (s *Pagination) GetPage() int32 { func (s *Roles) SetRoles(val int32) {
return s.Page s.Roles = val
}
// GetLimit returns the value of Limit.
func (s *Pagination) GetLimit() int32 {
return s.Limit
}
// SetPage sets the value of Page.
func (s *Pagination) SetPage(val int32) {
s.Page = val
}
// SetLimit sets the value of Limit.
func (s *Pagination) SetLimit(val int32) {
s.Limit = val
} }
// Ref: #/components/schemas/Script // Ref: #/components/schemas/Script
type Script struct { type Script struct {
ID int64 `json:"ID"` ID int64 `json:"ID"`
Name string `json:"Name"`
Hash string `json:"Hash"` Hash string `json:"Hash"`
Source string `json:"Source"` Source string `json:"Source"`
SubmissionID int64 `json:"SubmissionID"` ResourceType int32 `json:"ResourceType"`
ResourceID int64 `json:"ResourceID"`
} }
// GetID returns the value of ID. // GetID returns the value of ID.
@@ -441,6 +323,11 @@ func (s *Script) GetID() int64 {
return s.ID return s.ID
} }
// GetName returns the value of Name.
func (s *Script) GetName() string {
return s.Name
}
// GetHash returns the value of Hash. // GetHash returns the value of Hash.
func (s *Script) GetHash() string { func (s *Script) GetHash() string {
return s.Hash return s.Hash
@@ -451,9 +338,14 @@ func (s *Script) GetSource() string {
return s.Source return s.Source
} }
// GetSubmissionID returns the value of SubmissionID. // GetResourceType returns the value of ResourceType.
func (s *Script) GetSubmissionID() int64 { func (s *Script) GetResourceType() int32 {
return s.SubmissionID return s.ResourceType
}
// GetResourceID returns the value of ResourceID.
func (s *Script) GetResourceID() int64 {
return s.ResourceID
} }
// SetID sets the value of ID. // SetID sets the value of ID.
@@ -461,6 +353,11 @@ func (s *Script) SetID(val int64) {
s.ID = val s.ID = val
} }
// SetName sets the value of Name.
func (s *Script) SetName(val string) {
s.Name = val
}
// SetHash sets the value of Hash. // SetHash sets the value of Hash.
func (s *Script) SetHash(val string) { func (s *Script) SetHash(val string) {
s.Hash = val s.Hash = val
@@ -471,15 +368,27 @@ func (s *Script) SetSource(val string) {
s.Source = val s.Source = val
} }
// SetSubmissionID sets the value of SubmissionID. // SetResourceType sets the value of ResourceType.
func (s *Script) SetSubmissionID(val int64) { func (s *Script) SetResourceType(val int32) {
s.SubmissionID = val s.ResourceType = val
}
// SetResourceID sets the value of ResourceID.
func (s *Script) SetResourceID(val int64) {
s.ResourceID = val
} }
// Ref: #/components/schemas/ScriptCreate // Ref: #/components/schemas/ScriptCreate
type ScriptCreate struct { type ScriptCreate struct {
Name string `json:"Name"`
Source string `json:"Source"` Source string `json:"Source"`
SubmissionID OptInt64 `json:"SubmissionID"` ResourceType int32 `json:"ResourceType"`
ResourceID OptInt64 `json:"ResourceID"`
}
// GetName returns the value of Name.
func (s *ScriptCreate) GetName() string {
return s.Name
} }
// GetSource returns the value of Source. // GetSource returns the value of Source.
@@ -487,9 +396,19 @@ func (s *ScriptCreate) GetSource() string {
return s.Source return s.Source
} }
// GetSubmissionID returns the value of SubmissionID. // GetResourceType returns the value of ResourceType.
func (s *ScriptCreate) GetSubmissionID() OptInt64 { func (s *ScriptCreate) GetResourceType() int32 {
return s.SubmissionID return s.ResourceType
}
// GetResourceID returns the value of ResourceID.
func (s *ScriptCreate) GetResourceID() OptInt64 {
return s.ResourceID
}
// SetName sets the value of Name.
func (s *ScriptCreate) SetName(val string) {
s.Name = val
} }
// SetSource sets the value of Source. // SetSource sets the value of Source.
@@ -497,9 +416,14 @@ func (s *ScriptCreate) SetSource(val string) {
s.Source = val s.Source = val
} }
// SetSubmissionID sets the value of SubmissionID. // SetResourceType sets the value of ResourceType.
func (s *ScriptCreate) SetSubmissionID(val OptInt64) { func (s *ScriptCreate) SetResourceType(val int32) {
s.SubmissionID = val s.ResourceType = val
}
// SetResourceID sets the value of ResourceID.
func (s *ScriptCreate) SetResourceID(val OptInt64) {
s.ResourceID = val
} }
// Ref: #/components/schemas/ScriptPolicy // Ref: #/components/schemas/ScriptPolicy
@@ -587,54 +511,6 @@ func (s *ScriptPolicyCreate) SetPolicy(val int32) {
s.Policy = val s.Policy = val
} }
// Ref: #/components/schemas/ScriptPolicyFilter
type ScriptPolicyFilter struct {
ID OptInt64 `json:"ID"`
FromScriptHash OptString `json:"FromScriptHash"`
ToScriptID OptInt64 `json:"ToScriptID"`
Policy OptInt32 `json:"Policy"`
}
// GetID returns the value of ID.
func (s *ScriptPolicyFilter) GetID() OptInt64 {
return s.ID
}
// GetFromScriptHash returns the value of FromScriptHash.
func (s *ScriptPolicyFilter) GetFromScriptHash() OptString {
return s.FromScriptHash
}
// GetToScriptID returns the value of ToScriptID.
func (s *ScriptPolicyFilter) GetToScriptID() OptInt64 {
return s.ToScriptID
}
// GetPolicy returns the value of Policy.
func (s *ScriptPolicyFilter) GetPolicy() OptInt32 {
return s.Policy
}
// SetID sets the value of ID.
func (s *ScriptPolicyFilter) SetID(val OptInt64) {
s.ID = val
}
// SetFromScriptHash sets the value of FromScriptHash.
func (s *ScriptPolicyFilter) SetFromScriptHash(val OptString) {
s.FromScriptHash = val
}
// SetToScriptID sets the value of ToScriptID.
func (s *ScriptPolicyFilter) SetToScriptID(val OptInt64) {
s.ToScriptID = val
}
// SetPolicy sets the value of Policy.
func (s *ScriptPolicyFilter) SetPolicy(val OptInt32) {
s.Policy = val
}
// Ref: #/components/schemas/ScriptPolicyUpdate // Ref: #/components/schemas/ScriptPolicyUpdate
type ScriptPolicyUpdate struct { type ScriptPolicyUpdate struct {
ID int64 `json:"ID"` ID int64 `json:"ID"`
@@ -686,8 +562,10 @@ func (s *ScriptPolicyUpdate) SetPolicy(val OptInt32) {
// Ref: #/components/schemas/ScriptUpdate // Ref: #/components/schemas/ScriptUpdate
type ScriptUpdate struct { type ScriptUpdate struct {
ID int64 `json:"ID"` ID int64 `json:"ID"`
Name OptString `json:"Name"`
Source OptString `json:"Source"` Source OptString `json:"Source"`
SubmissionID OptInt64 `json:"SubmissionID"` ResourceType OptInt32 `json:"ResourceType"`
ResourceID OptInt64 `json:"ResourceID"`
} }
// GetID returns the value of ID. // GetID returns the value of ID.
@@ -695,14 +573,24 @@ func (s *ScriptUpdate) GetID() int64 {
return s.ID return s.ID
} }
// GetName returns the value of Name.
func (s *ScriptUpdate) GetName() OptString {
return s.Name
}
// GetSource returns the value of Source. // GetSource returns the value of Source.
func (s *ScriptUpdate) GetSource() OptString { func (s *ScriptUpdate) GetSource() OptString {
return s.Source return s.Source
} }
// GetSubmissionID returns the value of SubmissionID. // GetResourceType returns the value of ResourceType.
func (s *ScriptUpdate) GetSubmissionID() OptInt64 { func (s *ScriptUpdate) GetResourceType() OptInt32 {
return s.SubmissionID return s.ResourceType
}
// GetResourceID returns the value of ResourceID.
func (s *ScriptUpdate) GetResourceID() OptInt64 {
return s.ResourceID
} }
// SetID sets the value of ID. // SetID sets the value of ID.
@@ -710,14 +598,24 @@ func (s *ScriptUpdate) SetID(val int64) {
s.ID = val s.ID = val
} }
// SetName sets the value of Name.
func (s *ScriptUpdate) SetName(val OptString) {
s.Name = val
}
// SetSource sets the value of Source. // SetSource sets the value of Source.
func (s *ScriptUpdate) SetSource(val OptString) { func (s *ScriptUpdate) SetSource(val OptString) {
s.Source = val s.Source = val
} }
// SetSubmissionID sets the value of SubmissionID. // SetResourceType sets the value of ResourceType.
func (s *ScriptUpdate) SetSubmissionID(val OptInt64) { func (s *ScriptUpdate) SetResourceType(val OptInt32) {
s.SubmissionID = val s.ResourceType = val
}
// SetResourceID sets the value of ResourceID.
func (s *ScriptUpdate) SetResourceID(val OptInt64) {
s.ResourceID = val
} }
// SetSubmissionCompletedNoContent is response for SetSubmissionCompleted operation. // SetSubmissionCompletedNoContent is response for SetSubmissionCompleted operation.
@@ -725,19 +623,19 @@ type SetSubmissionCompletedNoContent struct{}
// Ref: #/components/schemas/Submission // Ref: #/components/schemas/Submission
type Submission struct { type Submission struct {
ID int64 `json:"ID"` ID int64 `json:"ID"`
DisplayName string `json:"DisplayName"` DisplayName string `json:"DisplayName"`
Creator string `json:"Creator"` Creator string `json:"Creator"`
GameID int32 `json:"GameID"` GameID int32 `json:"GameID"`
CreatedAt int64 `json:"CreatedAt"` CreatedAt int64 `json:"CreatedAt"`
UpdatedAt int64 `json:"UpdatedAt"` UpdatedAt int64 `json:"UpdatedAt"`
Submitter int64 `json:"Submitter"` Submitter int64 `json:"Submitter"`
AssetID int64 `json:"AssetID"` AssetID int64 `json:"AssetID"`
AssetVersion int64 `json:"AssetVersion"` AssetVersion int64 `json:"AssetVersion"`
Completed bool `json:"Completed"` Completed bool `json:"Completed"`
SubmissionType int32 `json:"SubmissionType"` UploadedAssetID OptInt64 `json:"UploadedAssetID"`
TargetAssetID OptInt64 `json:"TargetAssetID"` StatusID int32 `json:"StatusID"`
StatusID int32 `json:"StatusID"` StatusMessage string `json:"StatusMessage"`
} }
// GetID returns the value of ID. // GetID returns the value of ID.
@@ -790,14 +688,9 @@ func (s *Submission) GetCompleted() bool {
return s.Completed return s.Completed
} }
// GetSubmissionType returns the value of SubmissionType. // GetUploadedAssetID returns the value of UploadedAssetID.
func (s *Submission) GetSubmissionType() int32 { func (s *Submission) GetUploadedAssetID() OptInt64 {
return s.SubmissionType return s.UploadedAssetID
}
// GetTargetAssetID returns the value of TargetAssetID.
func (s *Submission) GetTargetAssetID() OptInt64 {
return s.TargetAssetID
} }
// GetStatusID returns the value of StatusID. // GetStatusID returns the value of StatusID.
@@ -805,6 +698,11 @@ 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
@@ -855,14 +753,9 @@ func (s *Submission) SetCompleted(val bool) {
s.Completed = val s.Completed = val
} }
// SetSubmissionType sets the value of SubmissionType. // SetUploadedAssetID sets the value of UploadedAssetID.
func (s *Submission) SetSubmissionType(val int32) { func (s *Submission) SetUploadedAssetID(val OptInt64) {
s.SubmissionType = val s.UploadedAssetID = val
}
// SetTargetAssetID sets the value of TargetAssetID.
func (s *Submission) SetTargetAssetID(val OptInt64) {
s.TargetAssetID = val
} }
// SetStatusID sets the value of StatusID. // SetStatusID sets the value of StatusID.
@@ -870,14 +763,18 @@ 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/SubmissionCreate // Ref: #/components/schemas/SubmissionCreate
type SubmissionCreate struct { type SubmissionCreate struct {
DisplayName string `json:"DisplayName"` DisplayName string `json:"DisplayName"`
Creator string `json:"Creator"` Creator string `json:"Creator"`
GameID int32 `json:"GameID"` GameID int32 `json:"GameID"`
AssetID int64 `json:"AssetID"` AssetID int64 `json:"AssetID"`
AssetVersion int64 `json:"AssetVersion"` AssetVersion int64 `json:"AssetVersion"`
TargetAssetID OptInt64 `json:"TargetAssetID"`
} }
// GetDisplayName returns the value of DisplayName. // GetDisplayName returns the value of DisplayName.
@@ -905,11 +802,6 @@ func (s *SubmissionCreate) GetAssetVersion() int64 {
return s.AssetVersion return s.AssetVersion
} }
// GetTargetAssetID returns the value of TargetAssetID.
func (s *SubmissionCreate) GetTargetAssetID() OptInt64 {
return s.TargetAssetID
}
// SetDisplayName sets the value of DisplayName. // SetDisplayName sets the value of DisplayName.
func (s *SubmissionCreate) SetDisplayName(val string) { func (s *SubmissionCreate) SetDisplayName(val string) {
s.DisplayName = val s.DisplayName = val
@@ -935,59 +827,6 @@ func (s *SubmissionCreate) SetAssetVersion(val int64) {
s.AssetVersion = val s.AssetVersion = val
} }
// SetTargetAssetID sets the value of TargetAssetID.
func (s *SubmissionCreate) SetTargetAssetID(val OptInt64) {
s.TargetAssetID = val
}
// Ref: #/components/schemas/SubmissionFilter
type SubmissionFilter struct {
ID int64 `json:"ID"`
DisplayName OptString `json:"DisplayName"`
Creator OptString `json:"Creator"`
GameID OptInt32 `json:"GameID"`
}
// GetID returns the value of ID.
func (s *SubmissionFilter) GetID() int64 {
return s.ID
}
// GetDisplayName returns the value of DisplayName.
func (s *SubmissionFilter) GetDisplayName() OptString {
return s.DisplayName
}
// GetCreator returns the value of Creator.
func (s *SubmissionFilter) GetCreator() OptString {
return s.Creator
}
// GetGameID returns the value of GameID.
func (s *SubmissionFilter) GetGameID() OptInt32 {
return s.GameID
}
// SetID sets the value of ID.
func (s *SubmissionFilter) SetID(val int64) {
s.ID = val
}
// SetDisplayName sets the value of DisplayName.
func (s *SubmissionFilter) SetDisplayName(val OptString) {
s.DisplayName = val
}
// SetCreator sets the value of Creator.
func (s *SubmissionFilter) SetCreator(val OptString) {
s.Creator = val
}
// SetGameID sets the value of GameID.
func (s *SubmissionFilter) SetGameID(val OptInt32) {
s.GameID = val
}
// UpdateScriptNoContent is response for UpdateScript operation. // UpdateScriptNoContent is response for UpdateScript operation.
type UpdateScriptNoContent struct{} type UpdateScriptNoContent struct{}
@@ -996,3 +835,40 @@ type UpdateScriptPolicyNoContent struct{}
// UpdateSubmissionModelNoContent is response for UpdateSubmissionModel operation. // UpdateSubmissionModelNoContent is response for UpdateSubmissionModel operation.
type UpdateSubmissionModelNoContent struct{} type UpdateSubmissionModelNoContent struct{}
// Ref: #/components/schemas/User
type User struct {
UserID int64 `json:"UserID"`
Username string `json:"Username"`
AvatarURL string `json:"AvatarURL"`
}
// GetUserID returns the value of UserID.
func (s *User) GetUserID() int64 {
return s.UserID
}
// GetUsername returns the value of Username.
func (s *User) GetUsername() string {
return s.Username
}
// GetAvatarURL returns the value of AvatarURL.
func (s *User) GetAvatarURL() string {
return s.AvatarURL
}
// SetUserID sets the value of UserID.
func (s *User) SetUserID(val int64) {
s.UserID = val
}
// SetUsername sets the value of Username.
func (s *User) SetUsername(val string) {
s.Username = val
}
// SetAvatarURL sets the value of AvatarURL.
func (s *User) SetAvatarURL(val string) {
s.AvatarURL = val
}

View File

@@ -8,12 +8,12 @@ import (
// Handler handles operations described by OpenAPI v3 specification. // Handler handles operations described by OpenAPI v3 specification.
type Handler interface { type Handler interface {
// ActionSubmissionPublish implements actionSubmissionPublish operation. // ActionSubmissionAccepted implements actionSubmissionAccepted operation.
// //
// (Internal endpoint) Role Validator changes status from Publishing -> Published. // Role Reviewer manually resets validating softlock and changes status from Validating -> Accepted.
// //
// POST /submissions/{SubmissionID}/status/validator-published // POST /submissions/{SubmissionID}/status/reset-validating
ActionSubmissionPublish(ctx context.Context, params ActionSubmissionPublishParams) error ActionSubmissionAccepted(ctx context.Context, params ActionSubmissionAcceptedParams) error
// ActionSubmissionReject implements actionSubmissionReject operation. // ActionSubmissionReject implements actionSubmissionReject operation.
// //
// Role Reviewer changes status from Submitted -> Rejected. // Role Reviewer changes status from Submitted -> Rejected.
@@ -26,6 +26,12 @@ 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
// ActionSubmissionRetryValidate implements actionSubmissionRetryValidate operation.
//
// Role Reviewer re-runs validation and changes status from Accepted -> Validating.
//
// POST /submissions/{SubmissionID}/status/retry-validate
ActionSubmissionRetryValidate(ctx context.Context, params ActionSubmissionRetryValidateParams) error
// ActionSubmissionRevoke implements actionSubmissionRevoke operation. // ActionSubmissionRevoke implements actionSubmissionRevoke operation.
// //
// Role Submitter changes status from Submitted|ChangesRequested -> UnderConstruction. // Role Submitter changes status from Submitted|ChangesRequested -> UnderConstruction.
@@ -38,24 +44,24 @@ type Handler interface {
// //
// POST /submissions/{SubmissionID}/status/submit // POST /submissions/{SubmissionID}/status/submit
ActionSubmissionSubmit(ctx context.Context, params ActionSubmissionSubmitParams) error ActionSubmissionSubmit(ctx context.Context, params ActionSubmissionSubmitParams) error
// ActionSubmissionTriggerPublish implements actionSubmissionTriggerPublish operation. // ActionSubmissionTriggerUpload implements actionSubmissionTriggerUpload operation.
// //
// Role Admin changes status from Validated -> Publishing. // Role Admin changes status from Validated -> Uploading.
// //
// POST /submissions/{SubmissionID}/status/trigger-publish // POST /submissions/{SubmissionID}/status/trigger-upload
ActionSubmissionTriggerPublish(ctx context.Context, params ActionSubmissionTriggerPublishParams) error ActionSubmissionTriggerUpload(ctx context.Context, params ActionSubmissionTriggerUploadParams) error
// ActionSubmissionTriggerValidate implements actionSubmissionTriggerValidate operation. // ActionSubmissionTriggerValidate implements actionSubmissionTriggerValidate operation.
// //
// Role Reviewer triggers validation and changes status from Submitted|Accepted -> Validating. // Role Reviewer triggers validation and changes status from Submitted -> Validating.
// //
// POST /submissions/{SubmissionID}/status/trigger-validate // POST /submissions/{SubmissionID}/status/trigger-validate
ActionSubmissionTriggerValidate(ctx context.Context, params ActionSubmissionTriggerValidateParams) error ActionSubmissionTriggerValidate(ctx context.Context, params ActionSubmissionTriggerValidateParams) error
// ActionSubmissionValidate implements actionSubmissionValidate operation. // ActionSubmissionValidated implements actionSubmissionValidated operation.
// //
// (Internal endpoint) Role Validator changes status from Validating -> Validated. // Role Admin manually resets uploading softlock and changes status from Uploading -> Validated.
// //
// POST /submissions/{SubmissionID}/status/validator-validated // POST /submissions/{SubmissionID}/status/reset-uploading
ActionSubmissionValidate(ctx context.Context, params ActionSubmissionValidateParams) error ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error
// CreateScript implements createScript operation. // CreateScript implements createScript operation.
// //
// Create a new script. // Create a new script.
@@ -84,7 +90,7 @@ type Handler interface {
// //
// Delete the specified script policy by ID. // Delete the specified script policy by ID.
// //
// DELETE /script-policy/id/{ScriptPolicyID} // DELETE /script-policy/{ScriptPolicyID}
DeleteScriptPolicy(ctx context.Context, params DeleteScriptPolicyParams) error DeleteScriptPolicy(ctx context.Context, params DeleteScriptPolicyParams) error
// GetScript implements getScript operation. // GetScript implements getScript operation.
// //
@@ -96,14 +102,8 @@ type Handler interface {
// //
// Get the specified script policy by ID. // Get the specified script policy by ID.
// //
// GET /script-policy/id/{ScriptPolicyID} // GET /script-policy/{ScriptPolicyID}
GetScriptPolicy(ctx context.Context, params GetScriptPolicyParams) (*ScriptPolicy, error) GetScriptPolicy(ctx context.Context, params GetScriptPolicyParams) (*ScriptPolicy, error)
// GetScriptPolicyFromHash implements getScriptPolicyFromHash operation.
//
// Get the policy for the given hash of script source code.
//
// GET /script-policy/hash/{FromScriptHash}
GetScriptPolicyFromHash(ctx context.Context, params GetScriptPolicyFromHashParams) (*ScriptPolicy, error)
// GetSubmission implements getSubmission operation. // GetSubmission implements getSubmission operation.
// //
// Retrieve map with ID. // Retrieve map with ID.
@@ -115,16 +115,46 @@ type Handler interface {
// Get list of script policies. // Get list of script policies.
// //
// GET /script-policy // GET /script-policy
ListScriptPolicy(ctx context.Context, req *ListScriptPolicyReq) ([]ScriptPolicy, error) ListScriptPolicy(ctx context.Context, params ListScriptPolicyParams) ([]ScriptPolicy, error)
// ListScripts implements listScripts operation.
//
// Get list of scripts.
//
// GET /scripts
ListScripts(ctx context.Context, params ListScriptsParams) ([]Script, 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, req *ListSubmissionsReq) ([]Submission, error) ListSubmissions(ctx context.Context, params ListSubmissionsParams) ([]Submission, error)
// ReleaseSubmissions implements releaseSubmissions operation.
//
// Release a set of uploaded maps.
//
// POST /release-submissions
ReleaseSubmissions(ctx context.Context, req []ReleaseInfo) error
// SessionRoles implements sessionRoles operation.
//
// Get list of roles for the current session.
//
// GET /session/roles
SessionRoles(ctx context.Context) (*Roles, error)
// SessionUser implements sessionUser operation.
//
// Get information about the currently logged in user.
//
// GET /session/user
SessionUser(ctx context.Context) (*User, error)
// SessionValidate implements sessionValidate operation.
//
// Ask if the current session is valid.
//
// GET /session/validate
SessionValidate(ctx context.Context) (bool, error)
// SetSubmissionCompleted implements setSubmissionCompleted operation. // SetSubmissionCompleted implements setSubmissionCompleted operation.
// //
// Retrieve map with ID. // Called by maptest when a player completes the map.
// //
// POST /submissions/{SubmissionID}/completed // POST /submissions/{SubmissionID}/completed
SetSubmissionCompleted(ctx context.Context, params SetSubmissionCompletedParams) error SetSubmissionCompleted(ctx context.Context, params SetSubmissionCompletedParams) error
@@ -138,7 +168,7 @@ type Handler interface {
// //
// Update the specified script policy by ID. // Update the specified script policy by ID.
// //
// POST /script-policy/id/{ScriptPolicyID} // POST /script-policy/{ScriptPolicyID}
UpdateScriptPolicy(ctx context.Context, req *ScriptPolicyUpdate, params UpdateScriptPolicyParams) error UpdateScriptPolicy(ctx context.Context, req *ScriptPolicyUpdate, params UpdateScriptPolicyParams) error
// UpdateSubmissionModel implements updateSubmissionModel operation. // UpdateSubmissionModel implements updateSubmissionModel operation.
// //

View File

@@ -13,12 +13,12 @@ type UnimplementedHandler struct{}
var _ Handler = UnimplementedHandler{} var _ Handler = UnimplementedHandler{}
// ActionSubmissionPublish implements actionSubmissionPublish operation. // ActionSubmissionAccepted implements actionSubmissionAccepted operation.
// //
// (Internal endpoint) Role Validator changes status from Publishing -> Published. // Role Reviewer manually resets validating softlock and changes status from Validating -> Accepted.
// //
// POST /submissions/{SubmissionID}/status/validator-published // POST /submissions/{SubmissionID}/status/reset-validating
func (UnimplementedHandler) ActionSubmissionPublish(ctx context.Context, params ActionSubmissionPublishParams) error { func (UnimplementedHandler) ActionSubmissionAccepted(ctx context.Context, params ActionSubmissionAcceptedParams) error {
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
@@ -40,6 +40,15 @@ func (UnimplementedHandler) ActionSubmissionRequestChanges(ctx context.Context,
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
// ActionSubmissionRetryValidate implements actionSubmissionRetryValidate operation.
//
// Role Reviewer re-runs validation and changes status from Accepted -> Validating.
//
// POST /submissions/{SubmissionID}/status/retry-validate
func (UnimplementedHandler) ActionSubmissionRetryValidate(ctx context.Context, params ActionSubmissionRetryValidateParams) error {
return ht.ErrNotImplemented
}
// ActionSubmissionRevoke implements actionSubmissionRevoke operation. // ActionSubmissionRevoke implements actionSubmissionRevoke operation.
// //
// Role Submitter changes status from Submitted|ChangesRequested -> UnderConstruction. // Role Submitter changes status from Submitted|ChangesRequested -> UnderConstruction.
@@ -58,30 +67,30 @@ func (UnimplementedHandler) ActionSubmissionSubmit(ctx context.Context, params A
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
// ActionSubmissionTriggerPublish implements actionSubmissionTriggerPublish operation. // ActionSubmissionTriggerUpload implements actionSubmissionTriggerUpload operation.
// //
// Role Admin changes status from Validated -> Publishing. // Role Admin changes status from Validated -> Uploading.
// //
// POST /submissions/{SubmissionID}/status/trigger-publish // POST /submissions/{SubmissionID}/status/trigger-upload
func (UnimplementedHandler) ActionSubmissionTriggerPublish(ctx context.Context, params ActionSubmissionTriggerPublishParams) error { func (UnimplementedHandler) ActionSubmissionTriggerUpload(ctx context.Context, params ActionSubmissionTriggerUploadParams) error {
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
// ActionSubmissionTriggerValidate implements actionSubmissionTriggerValidate operation. // ActionSubmissionTriggerValidate implements actionSubmissionTriggerValidate operation.
// //
// Role Reviewer triggers validation and changes status from Submitted|Accepted -> Validating. // Role Reviewer triggers validation and changes status from Submitted -> Validating.
// //
// POST /submissions/{SubmissionID}/status/trigger-validate // POST /submissions/{SubmissionID}/status/trigger-validate
func (UnimplementedHandler) ActionSubmissionTriggerValidate(ctx context.Context, params ActionSubmissionTriggerValidateParams) error { func (UnimplementedHandler) ActionSubmissionTriggerValidate(ctx context.Context, params ActionSubmissionTriggerValidateParams) error {
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
// ActionSubmissionValidate implements actionSubmissionValidate operation. // ActionSubmissionValidated implements actionSubmissionValidated operation.
// //
// (Internal endpoint) Role Validator changes status from Validating -> Validated. // Role Admin manually resets uploading softlock and changes status from Uploading -> Validated.
// //
// POST /submissions/{SubmissionID}/status/validator-validated // POST /submissions/{SubmissionID}/status/reset-uploading
func (UnimplementedHandler) ActionSubmissionValidate(ctx context.Context, params ActionSubmissionValidateParams) error { func (UnimplementedHandler) ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error {
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
@@ -125,7 +134,7 @@ func (UnimplementedHandler) DeleteScript(ctx context.Context, params DeleteScrip
// //
// Delete the specified script policy by ID. // Delete the specified script policy by ID.
// //
// DELETE /script-policy/id/{ScriptPolicyID} // DELETE /script-policy/{ScriptPolicyID}
func (UnimplementedHandler) DeleteScriptPolicy(ctx context.Context, params DeleteScriptPolicyParams) error { func (UnimplementedHandler) DeleteScriptPolicy(ctx context.Context, params DeleteScriptPolicyParams) error {
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
@@ -143,20 +152,11 @@ func (UnimplementedHandler) GetScript(ctx context.Context, params GetScriptParam
// //
// Get the specified script policy by ID. // Get the specified script policy by ID.
// //
// GET /script-policy/id/{ScriptPolicyID} // GET /script-policy/{ScriptPolicyID}
func (UnimplementedHandler) GetScriptPolicy(ctx context.Context, params GetScriptPolicyParams) (r *ScriptPolicy, _ error) { func (UnimplementedHandler) GetScriptPolicy(ctx context.Context, params GetScriptPolicyParams) (r *ScriptPolicy, _ error) {
return r, ht.ErrNotImplemented return r, ht.ErrNotImplemented
} }
// GetScriptPolicyFromHash implements getScriptPolicyFromHash operation.
//
// Get the policy for the given hash of script source code.
//
// GET /script-policy/hash/{FromScriptHash}
func (UnimplementedHandler) GetScriptPolicyFromHash(ctx context.Context, params GetScriptPolicyFromHashParams) (r *ScriptPolicy, _ error) {
return r, ht.ErrNotImplemented
}
// GetSubmission implements getSubmission operation. // GetSubmission implements getSubmission operation.
// //
// Retrieve map with ID. // Retrieve map with ID.
@@ -171,7 +171,16 @@ func (UnimplementedHandler) GetSubmission(ctx context.Context, params GetSubmiss
// Get list of script policies. // Get list of script policies.
// //
// GET /script-policy // GET /script-policy
func (UnimplementedHandler) ListScriptPolicy(ctx context.Context, req *ListScriptPolicyReq) (r []ScriptPolicy, _ error) { func (UnimplementedHandler) ListScriptPolicy(ctx context.Context, params ListScriptPolicyParams) (r []ScriptPolicy, _ error) {
return r, ht.ErrNotImplemented
}
// ListScripts implements listScripts operation.
//
// Get list of scripts.
//
// GET /scripts
func (UnimplementedHandler) ListScripts(ctx context.Context, params ListScriptsParams) (r []Script, _ error) {
return r, ht.ErrNotImplemented return r, ht.ErrNotImplemented
} }
@@ -180,13 +189,49 @@ func (UnimplementedHandler) ListScriptPolicy(ctx context.Context, req *ListScrip
// Get list of submissions. // Get list of submissions.
// //
// GET /submissions // GET /submissions
func (UnimplementedHandler) ListSubmissions(ctx context.Context, req *ListSubmissionsReq) (r []Submission, _ error) { func (UnimplementedHandler) ListSubmissions(ctx context.Context, params ListSubmissionsParams) (r []Submission, _ error) {
return r, ht.ErrNotImplemented
}
// ReleaseSubmissions implements releaseSubmissions operation.
//
// Release a set of uploaded maps.
//
// POST /release-submissions
func (UnimplementedHandler) ReleaseSubmissions(ctx context.Context, req []ReleaseInfo) error {
return ht.ErrNotImplemented
}
// SessionRoles implements sessionRoles operation.
//
// Get list of roles for the current session.
//
// GET /session/roles
func (UnimplementedHandler) SessionRoles(ctx context.Context) (r *Roles, _ error) {
return r, ht.ErrNotImplemented
}
// SessionUser implements sessionUser operation.
//
// Get information about the currently logged in user.
//
// GET /session/user
func (UnimplementedHandler) SessionUser(ctx context.Context) (r *User, _ error) {
return r, ht.ErrNotImplemented
}
// SessionValidate implements sessionValidate operation.
//
// Ask if the current session is valid.
//
// GET /session/validate
func (UnimplementedHandler) SessionValidate(ctx context.Context) (r bool, _ error) {
return r, ht.ErrNotImplemented return r, ht.ErrNotImplemented
} }
// SetSubmissionCompleted implements setSubmissionCompleted operation. // SetSubmissionCompleted implements setSubmissionCompleted operation.
// //
// Retrieve map with ID. // Called by maptest when a player completes the map.
// //
// POST /submissions/{SubmissionID}/completed // POST /submissions/{SubmissionID}/completed
func (UnimplementedHandler) SetSubmissionCompleted(ctx context.Context, params SetSubmissionCompletedParams) error { func (UnimplementedHandler) SetSubmissionCompleted(ctx context.Context, params SetSubmissionCompletedParams) error {
@@ -206,7 +251,7 @@ func (UnimplementedHandler) UpdateScript(ctx context.Context, req *ScriptUpdate,
// //
// Update the specified script policy by ID. // Update the specified script policy by ID.
// //
// POST /script-policy/id/{ScriptPolicyID} // POST /script-policy/{ScriptPolicyID}
func (UnimplementedHandler) UpdateScriptPolicy(ctx context.Context, req *ScriptPolicyUpdate, params UpdateScriptPolicyParams) error { func (UnimplementedHandler) UpdateScriptPolicy(ctx context.Context, req *ScriptPolicyUpdate, params UpdateScriptPolicyParams) error {
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }

View File

@@ -8,146 +8,31 @@ import (
"github.com/ogen-go/ogen/validate" "github.com/ogen-go/ogen/validate"
) )
func (s *ListScriptPolicyReq) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if err := s.Page.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Page",
Error: err,
})
}
if err := func() error {
if value, ok := s.Filter.Get(); ok {
if err := func() error {
if err := value.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return err
}
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Filter",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *ListSubmissionsReq) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if err := s.Page.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Page",
Error: err,
})
}
if err := func() error {
if value, ok := s.Filter.Get(); ok {
if err := func() error {
if err := value.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return err
}
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Filter",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *Pagination) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if err := (validate.Int{
MinSet: true,
Min: 1,
MaxSet: false,
Max: 0,
MinExclusive: false,
MaxExclusive: false,
MultipleOfSet: false,
MultipleOf: 0,
}).Validate(int64(s.Page)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Page",
Error: err,
})
}
if err := func() error {
if err := (validate.Int{
MinSet: true,
Min: 1,
MaxSet: true,
Max: 100,
MinExclusive: false,
MaxExclusive: false,
MultipleOfSet: false,
MultipleOf: 0,
}).Validate(int64(s.Limit)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Limit",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *Script) Validate() error { func (s *Script) 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.String{
MinLength: 0,
MinLengthSet: false,
MaxLength: 128,
MaxLengthSet: true,
Email: false,
Hostname: false,
Regex: nil,
}).Validate(string(s.Name)); err != nil {
return errors.Wrap(err, "string")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Name",
Error: err,
})
}
if err := func() error { if err := func() error {
if err := (validate.String{ if err := (validate.String{
MinLength: 16, MinLength: 16,
@@ -198,6 +83,25 @@ func (s *ScriptCreate) Validate() error {
} }
var failures []validate.FieldError var failures []validate.FieldError
if err := func() error {
if err := (validate.String{
MinLength: 0,
MinLengthSet: false,
MaxLength: 128,
MaxLengthSet: true,
Email: false,
Hostname: false,
Regex: nil,
}).Validate(string(s.Name)); err != nil {
return errors.Wrap(err, "string")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Name",
Error: err,
})
}
if err := func() error { if err := func() error {
if err := (validate.String{ if err := (validate.String{
MinLength: 0, MinLength: 0,
@@ -254,19 +158,19 @@ func (s *ScriptPolicy) Validate() error {
return nil return nil
} }
func (s *ScriptPolicyFilter) Validate() error { func (s *ScriptUpdate) 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 := func() error {
if value, ok := s.FromScriptHash.Get(); ok { if value, ok := s.Name.Get(); ok {
if err := func() error { if err := func() error {
if err := (validate.String{ if err := (validate.String{
MinLength: 16, MinLength: 0,
MinLengthSet: true, MinLengthSet: false,
MaxLength: 16, MaxLength: 128,
MaxLengthSet: true, MaxLengthSet: true,
Email: false, Email: false,
Hostname: false, Hostname: false,
@@ -282,22 +186,10 @@ func (s *ScriptPolicyFilter) Validate() error {
return nil return nil
}(); err != nil { }(); err != nil {
failures = append(failures, validate.FieldError{ failures = append(failures, validate.FieldError{
Name: "FromScriptHash", Name: "Name",
Error: err, Error: err,
}) })
} }
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *ScriptUpdate) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error { if err := func() error {
if value, ok := s.Source.Get(); ok { if value, ok := s.Source.Get(); ok {
if err := func() error { if err := func() error {
@@ -374,6 +266,25 @@ func (s *Submission) Validate() error {
Error: err, Error: err,
}) })
} }
if err := func() error {
if err := (validate.String{
MinLength: 0,
MinLengthSet: false,
MaxLength: 256,
MaxLengthSet: true,
Email: false,
Hostname: false,
Regex: nil,
}).Validate(string(s.StatusMessage)); err != nil {
return errors.Wrap(err, "string")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "StatusMessage",
Error: err,
})
}
if len(failures) > 0 { if len(failures) > 0 {
return &validate.Error{Fields: failures} return &validate.Error{Fields: failures}
} }
@@ -430,61 +341,47 @@ func (s *SubmissionCreate) Validate() error {
return nil return nil
} }
func (s *SubmissionFilter) Validate() error { func (s *User) 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 := func() error {
if value, ok := s.DisplayName.Get(); ok { if err := (validate.String{
if err := func() error { MinLength: 0,
if err := (validate.String{ MinLengthSet: false,
MinLength: 0, MaxLength: 128,
MinLengthSet: false, MaxLengthSet: true,
MaxLength: 128, Email: false,
MaxLengthSet: true, Hostname: false,
Email: false, Regex: nil,
Hostname: false, }).Validate(string(s.Username)); err != nil {
Regex: nil, return errors.Wrap(err, "string")
}).Validate(string(value)); err != nil {
return errors.Wrap(err, "string")
}
return nil
}(); err != nil {
return err
}
} }
return nil return nil
}(); err != nil { }(); err != nil {
failures = append(failures, validate.FieldError{ failures = append(failures, validate.FieldError{
Name: "DisplayName", Name: "Username",
Error: err, Error: err,
}) })
} }
if err := func() error { if err := func() error {
if value, ok := s.Creator.Get(); ok { if err := (validate.String{
if err := func() error { MinLength: 0,
if err := (validate.String{ MinLengthSet: false,
MinLength: 0, MaxLength: 256,
MinLengthSet: false, MaxLengthSet: true,
MaxLength: 128, Email: false,
MaxLengthSet: true, Hostname: false,
Email: false, Regex: nil,
Hostname: false, }).Validate(string(s.AvatarURL)); err != nil {
Regex: nil, return errors.Wrap(err, "string")
}).Validate(string(value)); err != nil {
return errors.Wrap(err, "string")
}
return nil
}(); err != nil {
return err
}
} }
return nil return nil
}(); err != nil { }(); err != nil {
failures = append(failures, validate.FieldError{ failures = append(failures, validate.FieldError{
Name: "Creator", Name: "AvatarURL",
Error: err, Error: err,
}) })
} }

View File

@@ -2,16 +2,20 @@ package cmds
import ( import (
"fmt" "fmt"
"net/http"
"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/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"
"git.itzana.me/strafesnet/maps-service/pkg/service" "git.itzana.me/strafesnet/maps-service/pkg/service"
"git.itzana.me/strafesnet/maps-service/pkg/service_internal"
"github.com/nats-io/nats.go" "github.com/nats-io/nats.go"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"net/http"
) )
func NewServeCommand() *cli.Command { func NewServeCommand() *cli.Command {
@@ -62,12 +66,24 @@ func NewServeCommand() *cli.Command {
Value: 8080, Value: 8080,
EnvVars: []string{"PORT"}, EnvVars: []string{"PORT"},
}, },
&cli.IntFlag{
Name: "port-internal",
Usage: "Port to listen on for internal api",
Value: 8081,
EnvVars: []string{"PORT_INTERNAL"},
},
&cli.StringFlag{ &cli.StringFlag{
Name: "auth-rpc-host", Name: "auth-rpc-host",
Usage: "Host of auth rpc", Usage: "Host of auth rpc",
EnvVars: []string{"AUTH_RPC_HOST"}, EnvVars: []string{"AUTH_RPC_HOST"},
Value: "auth-service:8090", Value: "auth-service:8090",
}, },
&cli.StringFlag{
Name: "data-rpc-host",
Usage: "Host of data rpc",
EnvVars: []string{"DATA_RPC_HOST"},
Value: "data-service:9000",
},
&cli.StringFlag{ &cli.StringFlag{
Name: "nats-host", Name: "nats-host",
Usage: "Host of nats", Usage: "Host of nats",
@@ -101,12 +117,18 @@ func serve(ctx *cli.Context) error {
log.WithError(err).Fatal("failed to add stream") log.WithError(err).Fatal("failed to add stream")
} }
// connect to main game database
conn, err := grpc.Dial(ctx.String("data-rpc-host"), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
}
svc := &service.Service{ svc := &service.Service{
DB: db, DB: db,
Nats: js, Nats: js,
Client: maps.NewMapsServiceClient(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()))
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@@ -119,5 +141,37 @@ func serve(ctx *cli.Context) error {
log.WithError(err).Fatal("failed to initialize api server") log.WithError(err).Fatal("failed to initialize api server")
} }
return http.ListenAndServe(fmt.Sprintf(":%d", ctx.Int("port")), srv) svc2 := &service_internal.Service{
DB: db,
Nats: js,
}
srv2, err := internal.NewServer(svc2, internal.WithPathPrefix("/v1"))
if err != nil {
log.WithError(err).Fatal("failed to initialize api server")
}
// Channel to collect errors
errChan := make(chan error, 2)
// First server
go func(errChan chan error) {
errChan <- http.ListenAndServe(fmt.Sprintf(":%d", ctx.Int("port-internal")), srv2)
}(errChan)
// Second server
go func(errChan chan error) {
errChan <- http.ListenAndServe(fmt.Sprintf(":%d", ctx.Int("port")), srv)
}(errChan)
// Wait for the first error or completion of both tasks
for i := 0; i < 2; i++ {
err := <-errChan
if err != nil {
fmt.Println("Exiting due to:", err)
return err
}
}
log.Println("Both servers have stopped.")
return nil
} }

View File

@@ -9,6 +9,16 @@ import (
var ( var (
ErrNotExist = errors.New("resource does not exist") ErrNotExist = errors.New("resource does not exist")
ErroNoRowsAffected = errors.New("query did not affect any rows") ErroNoRowsAffected = errors.New("query did not affect any rows")
ErrInvalidListSort = errors.New("invalid list sort parameter [1,2,3,4]")
)
type ListSort uint32
const (
ListSortDisabled ListSort = 0
ListSortDisplayNameAscending ListSort = 1
ListSortDisplayNameDescending ListSort = 2
ListSortDateAscending ListSort = 3
ListSortDateDescending ListSort = 4
) )
type Datastore interface { type Datastore interface {
@@ -22,10 +32,10 @@ type Submissions interface {
GetList(ctx context.Context, id []int64) ([]model.Submission, error) GetList(ctx context.Context, id []int64) ([]model.Submission, error)
Create(ctx context.Context, smap model.Submission) (model.Submission, error) Create(ctx context.Context, smap model.Submission) (model.Submission, error)
Update(ctx context.Context, id int64, values OptionalMap) error Update(ctx context.Context, id int64, values OptionalMap) error
IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.Status, values OptionalMap) error IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.SubmissionStatus, values OptionalMap) error
IfStatusThenUpdateAndGet(ctx context.Context, id int64, statuses []model.Status, 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) ([]model.Submission, error) List(ctx context.Context, filters OptionalMap, page model.Page, sort ListSort) ([]model.Submission, error)
} }
type Scripts interface { type Scripts interface {
@@ -33,6 +43,7 @@ type Scripts interface {
Create(ctx context.Context, smap model.Script) (model.Script, error) Create(ctx context.Context, smap model.Script) (model.Script, error)
Update(ctx context.Context, id int64, values OptionalMap) error Update(ctx context.Context, id int64, values OptionalMap) error
Delete(ctx context.Context, id int64) error Delete(ctx context.Context, id int64) error
List(ctx context.Context, filters OptionalMap, page model.Page) ([]model.Script, error)
} }
type ScriptPolicy interface { type ScriptPolicy interface {

View File

@@ -19,16 +19,18 @@ func (env *ScriptPolicy) Get(ctx context.Context, id int64) (model.ScriptPolicy,
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return mdl, datastore.ErrNotExist return mdl, datastore.ErrNotExist
} }
return mdl, err
} }
return mdl, nil return mdl, nil
} }
func (env *ScriptPolicy) GetFromHash(ctx context.Context, hash uint64) (model.ScriptPolicy, error) { func (env *ScriptPolicy) GetFromHash(ctx context.Context, hash uint64) (model.ScriptPolicy, error) {
var mdl model.ScriptPolicy var mdl model.ScriptPolicy
if err := env.db.Model(&model.ScriptPolicy{}).Where("hash = ?", hash).Error; err != nil { if err := env.db.Take(&mdl,"from_script_hash = ?", hash).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return mdl, datastore.ErrNotExist return mdl, datastore.ErrNotExist
} }
return mdl, err
} }
return mdl, nil return mdl, nil
} }

View File

@@ -19,6 +19,7 @@ func (env *Scripts) Get(ctx context.Context, id int64) (model.Script, error) {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return mdl, datastore.ErrNotExist return mdl, datastore.ErrNotExist
} }
return mdl, err
} }
return mdl, nil return mdl, nil
} }
@@ -52,7 +53,7 @@ func (env *Scripts) Update(ctx context.Context, id int64, values datastore.Optio
} }
// the update can only occur if the status matches one of the provided values. // the update can only occur if the status matches one of the provided values.
func (env *Scripts) IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.Status, values datastore.OptionalMap) error { func (env *Scripts) IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.SubmissionStatus, values datastore.OptionalMap) error {
if err := env.db.Model(&model.Script{}).Where("id = ?", id).Where("status IN ?", statuses).Updates(values.Map()).Error; err != nil { if err := env.db.Model(&model.Script{}).Where("id = ?", id).Where("status IN ?", statuses).Updates(values.Map()).Error; err != nil {
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {
return datastore.ErrNotExist return datastore.ErrNotExist

View File

@@ -20,6 +20,7 @@ func (env *Submissions) Get(ctx context.Context, id int64) (model.Submission, er
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return submission, datastore.ErrNotExist return submission, datastore.ErrNotExist
} }
return submission, err
} }
return submission, nil return submission, nil
} }
@@ -53,7 +54,7 @@ func (env *Submissions) Update(ctx context.Context, id int64, values datastore.O
} }
// the update can only occur if the status matches one of the provided values. // the update can only occur if the status matches one of the provided values.
func (env *Submissions) IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.Status, values datastore.OptionalMap) error { func (env *Submissions) IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.SubmissionStatus, values datastore.OptionalMap) error {
if err := env.db.Model(&model.Submission{}).Where("id = ?", id).Where("status_id IN ?", statuses).Updates(values.Map()).Error; err != nil { if err := env.db.Model(&model.Submission{}).Where("id = ?", id).Where("status_id IN ?", statuses).Updates(values.Map()).Error; err != nil {
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {
return datastore.ErrNotExist return datastore.ErrNotExist
@@ -66,7 +67,7 @@ func (env *Submissions) IfStatusThenUpdate(ctx context.Context, id int64, status
// the update can only occur if the status matches one of the provided values. // the update can only occur if the status matches one of the provided values.
// returns the updated value // returns the updated value
func (env *Submissions) IfStatusThenUpdateAndGet(ctx context.Context, id int64, statuses []model.Status, values datastore.OptionalMap) (model.Submission, error) { func (env *Submissions) IfStatusThenUpdateAndGet(ctx context.Context, id int64, statuses []model.SubmissionStatus, values datastore.OptionalMap) (model.Submission, error) {
var submission model.Submission var submission model.Submission
result := env.db.Model(&submission). result := env.db.Model(&submission).
Clauses(clause.Returning{}). Clauses(clause.Returning{}).
@@ -98,9 +99,32 @@ func (env *Submissions) Delete(ctx context.Context, id int64) error {
return nil return nil
} }
func (env *Submissions) List(ctx context.Context, filters datastore.OptionalMap, page model.Page) ([]model.Submission, error) { func (env *Submissions) List(ctx context.Context, filters datastore.OptionalMap, page model.Page, sort datastore.ListSort) ([]model.Submission, error) {
var maps []model.Submission var maps []model.Submission
if err := env.db.Where(filters.Map()).Offset(int((page.Number - 1) * page.Size)).Limit(int(page.Size)).Find(&maps).Error; err != nil {
db := env.db
switch sort {
case datastore.ListSortDisabled:
// No sort
break
case datastore.ListSortDisplayNameAscending:
db=db.Order("display_name ASC")
break
case datastore.ListSortDisplayNameDescending:
db=db.Order("display_name DESC")
break
case datastore.ListSortDateAscending:
db=db.Order("created_at ASC")
break
case datastore.ListSortDateDescending:
db=db.Order("created_at DESC")
break
default:
return nil, datastore.ErrInvalidListSort
}
if err := db.Where(filters.Map()).Offset(int((page.Number - 1) * page.Size)).Limit(int(page.Size)).Find(&maps).Error; err != nil {
return nil, err return nil, err
} }

283
pkg/internal/oas_cfg_gen.go Normal file
View File

@@ -0,0 +1,283 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"net/http"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
ht "github.com/ogen-go/ogen/http"
"github.com/ogen-go/ogen/middleware"
"github.com/ogen-go/ogen/ogenerrors"
"github.com/ogen-go/ogen/otelogen"
)
var (
// Allocate option closure once.
clientSpanKind = trace.WithSpanKind(trace.SpanKindClient)
// Allocate option closure once.
serverSpanKind = trace.WithSpanKind(trace.SpanKindServer)
)
type (
optionFunc[C any] func(*C)
otelOptionFunc func(*otelConfig)
)
type otelConfig struct {
TracerProvider trace.TracerProvider
Tracer trace.Tracer
MeterProvider metric.MeterProvider
Meter metric.Meter
}
func (cfg *otelConfig) initOTEL() {
if cfg.TracerProvider == nil {
cfg.TracerProvider = otel.GetTracerProvider()
}
if cfg.MeterProvider == nil {
cfg.MeterProvider = otel.GetMeterProvider()
}
cfg.Tracer = cfg.TracerProvider.Tracer(otelogen.Name,
trace.WithInstrumentationVersion(otelogen.SemVersion()),
)
cfg.Meter = cfg.MeterProvider.Meter(otelogen.Name,
metric.WithInstrumentationVersion(otelogen.SemVersion()),
)
}
// ErrorHandler is error handler.
type ErrorHandler = ogenerrors.ErrorHandler
type serverConfig struct {
otelConfig
NotFound http.HandlerFunc
MethodNotAllowed func(w http.ResponseWriter, r *http.Request, allowed string)
ErrorHandler ErrorHandler
Prefix string
Middleware Middleware
MaxMultipartMemory int64
}
// ServerOption is server config option.
type ServerOption interface {
applyServer(*serverConfig)
}
var _ ServerOption = (optionFunc[serverConfig])(nil)
func (o optionFunc[C]) applyServer(c *C) {
o(c)
}
var _ ServerOption = (otelOptionFunc)(nil)
func (o otelOptionFunc) applyServer(c *serverConfig) {
o(&c.otelConfig)
}
func newServerConfig(opts ...ServerOption) serverConfig {
cfg := serverConfig{
NotFound: http.NotFound,
MethodNotAllowed: func(w http.ResponseWriter, r *http.Request, allowed string) {
status := http.StatusMethodNotAllowed
if r.Method == "OPTIONS" {
w.Header().Set("Access-Control-Allow-Methods", allowed)
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
status = http.StatusNoContent
} else {
w.Header().Set("Allow", allowed)
}
w.WriteHeader(status)
},
ErrorHandler: ogenerrors.DefaultErrorHandler,
Middleware: nil,
MaxMultipartMemory: 32 << 20, // 32 MB
}
for _, opt := range opts {
opt.applyServer(&cfg)
}
cfg.initOTEL()
return cfg
}
type baseServer struct {
cfg serverConfig
requests metric.Int64Counter
errors metric.Int64Counter
duration metric.Float64Histogram
}
func (s baseServer) notFound(w http.ResponseWriter, r *http.Request) {
s.cfg.NotFound(w, r)
}
func (s baseServer) notAllowed(w http.ResponseWriter, r *http.Request, allowed string) {
s.cfg.MethodNotAllowed(w, r, allowed)
}
func (cfg serverConfig) baseServer() (s baseServer, err error) {
s = baseServer{cfg: cfg}
if s.requests, err = otelogen.ServerRequestCountCounter(s.cfg.Meter); err != nil {
return s, err
}
if s.errors, err = otelogen.ServerErrorsCountCounter(s.cfg.Meter); err != nil {
return s, err
}
if s.duration, err = otelogen.ServerDurationHistogram(s.cfg.Meter); err != nil {
return s, err
}
return s, nil
}
type clientConfig struct {
otelConfig
Client ht.Client
}
// ClientOption is client config option.
type ClientOption interface {
applyClient(*clientConfig)
}
var _ ClientOption = (optionFunc[clientConfig])(nil)
func (o optionFunc[C]) applyClient(c *C) {
o(c)
}
var _ ClientOption = (otelOptionFunc)(nil)
func (o otelOptionFunc) applyClient(c *clientConfig) {
o(&c.otelConfig)
}
func newClientConfig(opts ...ClientOption) clientConfig {
cfg := clientConfig{
Client: http.DefaultClient,
}
for _, opt := range opts {
opt.applyClient(&cfg)
}
cfg.initOTEL()
return cfg
}
type baseClient struct {
cfg clientConfig
requests metric.Int64Counter
errors metric.Int64Counter
duration metric.Float64Histogram
}
func (cfg clientConfig) baseClient() (c baseClient, err error) {
c = baseClient{cfg: cfg}
if c.requests, err = otelogen.ClientRequestCountCounter(c.cfg.Meter); err != nil {
return c, err
}
if c.errors, err = otelogen.ClientErrorsCountCounter(c.cfg.Meter); err != nil {
return c, err
}
if c.duration, err = otelogen.ClientDurationHistogram(c.cfg.Meter); err != nil {
return c, err
}
return c, nil
}
// Option is config option.
type Option interface {
ServerOption
ClientOption
}
// WithTracerProvider specifies a tracer provider to use for creating a tracer.
//
// If none is specified, the global provider is used.
func WithTracerProvider(provider trace.TracerProvider) Option {
return otelOptionFunc(func(cfg *otelConfig) {
if provider != nil {
cfg.TracerProvider = provider
}
})
}
// WithMeterProvider specifies a meter provider to use for creating a meter.
//
// If none is specified, the otel.GetMeterProvider() is used.
func WithMeterProvider(provider metric.MeterProvider) Option {
return otelOptionFunc(func(cfg *otelConfig) {
if provider != nil {
cfg.MeterProvider = provider
}
})
}
// WithClient specifies http client to use.
func WithClient(client ht.Client) ClientOption {
return optionFunc[clientConfig](func(cfg *clientConfig) {
if client != nil {
cfg.Client = client
}
})
}
// WithNotFound specifies Not Found handler to use.
func WithNotFound(notFound http.HandlerFunc) ServerOption {
return optionFunc[serverConfig](func(cfg *serverConfig) {
if notFound != nil {
cfg.NotFound = notFound
}
})
}
// WithMethodNotAllowed specifies Method Not Allowed handler to use.
func WithMethodNotAllowed(methodNotAllowed func(w http.ResponseWriter, r *http.Request, allowed string)) ServerOption {
return optionFunc[serverConfig](func(cfg *serverConfig) {
if methodNotAllowed != nil {
cfg.MethodNotAllowed = methodNotAllowed
}
})
}
// WithErrorHandler specifies error handler to use.
func WithErrorHandler(h ErrorHandler) ServerOption {
return optionFunc[serverConfig](func(cfg *serverConfig) {
if h != nil {
cfg.ErrorHandler = h
}
})
}
// WithPathPrefix specifies server path prefix.
func WithPathPrefix(prefix string) ServerOption {
return optionFunc[serverConfig](func(cfg *serverConfig) {
cfg.Prefix = prefix
})
}
// WithMiddleware specifies middlewares to use.
func WithMiddleware(m ...Middleware) ServerOption {
return optionFunc[serverConfig](func(cfg *serverConfig) {
switch len(m) {
case 0:
cfg.Middleware = nil
case 1:
cfg.Middleware = m[0]
default:
cfg.Middleware = middleware.ChainMiddlewares(m...)
}
})
}
// WithMaxMultipartMemory specifies limit of memory for storing file parts.
// File parts which can't be stored in memory will be stored on disk in temporary files.
func WithMaxMultipartMemory(max int64) ServerOption {
return optionFunc[serverConfig](func(cfg *serverConfig) {
if max > 0 {
cfg.MaxMultipartMemory = max
}
})
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,862 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"math/bits"
"strconv"
"github.com/go-faster/errors"
"github.com/go-faster/jx"
"github.com/ogen-go/ogen/validate"
)
// Encode implements json.Marshaler.
func (s *Error) Encode(e *jx.Encoder) {
e.ObjStart()
s.encodeFields(e)
e.ObjEnd()
}
// encodeFields encodes fields.
func (s *Error) encodeFields(e *jx.Encoder) {
{
e.FieldStart("code")
e.Int64(s.Code)
}
{
e.FieldStart("message")
e.Str(s.Message)
}
}
var jsonFieldsNameOfError = [2]string{
0: "code",
1: "message",
}
// Decode decodes Error from json.
func (s *Error) Decode(d *jx.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode Error to nil")
}
var requiredBitSet [1]uint8
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
switch string(k) {
case "code":
requiredBitSet[0] |= 1 << 0
if err := func() error {
v, err := d.Int64()
s.Code = int64(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"code\"")
}
case "message":
requiredBitSet[0] |= 1 << 1
if err := func() error {
v, err := d.Str()
s.Message = string(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"message\"")
}
default:
return d.Skip()
}
return nil
}); err != nil {
return errors.Wrap(err, "decode Error")
}
// 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(jsonFieldsNameOfError) {
name = jsonFieldsNameOfError[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 *Error) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *Error) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}
// Encode implements json.Marshaler.
func (s *ID) Encode(e *jx.Encoder) {
e.ObjStart()
s.encodeFields(e)
e.ObjEnd()
}
// encodeFields encodes fields.
func (s *ID) encodeFields(e *jx.Encoder) {
{
e.FieldStart("ID")
e.Int64(s.ID)
}
}
var jsonFieldsNameOfID = [1]string{
0: "ID",
}
// Decode decodes ID from json.
func (s *ID) Decode(d *jx.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode ID to nil")
}
var requiredBitSet [1]uint8
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
switch string(k) {
case "ID":
requiredBitSet[0] |= 1 << 0
if err := func() error {
v, err := d.Int64()
s.ID = int64(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"ID\"")
}
default:
return d.Skip()
}
return nil
}); err != nil {
return errors.Wrap(err, "decode ID")
}
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b00000001,
} {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR.
//
// If XOR result is not zero, result is not equal to expected, so some fields are missed.
// Bits of fields which would be set are actually bits of missed fields.
missed := bits.OnesCount8(result)
for bitN := 0; bitN < missed; bitN++ {
bitIdx := bits.TrailingZeros8(result)
fieldIdx := i*8 + bitIdx
var name string
if fieldIdx < len(jsonFieldsNameOfID) {
name = jsonFieldsNameOfID[fieldIdx]
} else {
name = strconv.Itoa(fieldIdx)
}
failures = append(failures, validate.FieldError{
Name: name,
Error: validate.ErrFieldRequired,
})
// Reset bit.
result &^= 1 << bitIdx
}
}
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
// MarshalJSON implements stdjson.Marshaler.
func (s *ID) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *ID) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}
// Encode encodes int64 as json.
func (o OptInt64) Encode(e *jx.Encoder) {
if !o.Set {
return
}
e.Int64(int64(o.Value))
}
// Decode decodes int64 from json.
func (o *OptInt64) Decode(d *jx.Decoder) error {
if o == nil {
return errors.New("invalid: unable to decode OptInt64 to nil")
}
o.Set = true
v, err := d.Int64()
if err != nil {
return err
}
o.Value = int64(v)
return nil
}
// MarshalJSON implements stdjson.Marshaler.
func (s OptInt64) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *OptInt64) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}
// Encode implements json.Marshaler.
func (s *Script) Encode(e *jx.Encoder) {
e.ObjStart()
s.encodeFields(e)
e.ObjEnd()
}
// encodeFields encodes fields.
func (s *Script) encodeFields(e *jx.Encoder) {
{
e.FieldStart("ID")
e.Int64(s.ID)
}
{
e.FieldStart("Name")
e.Str(s.Name)
}
{
e.FieldStart("Hash")
e.Str(s.Hash)
}
{
e.FieldStart("Source")
e.Str(s.Source)
}
{
e.FieldStart("ResourceType")
e.Int32(s.ResourceType)
}
{
e.FieldStart("ResourceID")
e.Int64(s.ResourceID)
}
}
var jsonFieldsNameOfScript = [6]string{
0: "ID",
1: "Name",
2: "Hash",
3: "Source",
4: "ResourceType",
5: "ResourceID",
}
// Decode decodes Script from json.
func (s *Script) Decode(d *jx.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode Script 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 "Name":
requiredBitSet[0] |= 1 << 1
if err := func() error {
v, err := d.Str()
s.Name = string(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Name\"")
}
case "Hash":
requiredBitSet[0] |= 1 << 2
if err := func() error {
v, err := d.Str()
s.Hash = string(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Hash\"")
}
case "Source":
requiredBitSet[0] |= 1 << 3
if err := func() error {
v, err := d.Str()
s.Source = string(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Source\"")
}
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\"")
}
default:
return d.Skip()
}
return nil
}); err != nil {
return errors.Wrap(err, "decode Script")
}
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b00111111,
} {
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(jsonFieldsNameOfScript) {
name = jsonFieldsNameOfScript[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 *Script) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *Script) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}
// Encode implements json.Marshaler.
func (s *ScriptCreate) Encode(e *jx.Encoder) {
e.ObjStart()
s.encodeFields(e)
e.ObjEnd()
}
// encodeFields encodes fields.
func (s *ScriptCreate) encodeFields(e *jx.Encoder) {
{
e.FieldStart("Name")
e.Str(s.Name)
}
{
e.FieldStart("Source")
e.Str(s.Source)
}
{
e.FieldStart("ResourceType")
e.Int32(s.ResourceType)
}
{
if s.ResourceID.Set {
e.FieldStart("ResourceID")
s.ResourceID.Encode(e)
}
}
}
var jsonFieldsNameOfScriptCreate = [4]string{
0: "Name",
1: "Source",
2: "ResourceType",
3: "ResourceID",
}
// Decode decodes ScriptCreate from json.
func (s *ScriptCreate) Decode(d *jx.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode ScriptCreate to nil")
}
var requiredBitSet [1]uint8
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
switch string(k) {
case "Name":
requiredBitSet[0] |= 1 << 0
if err := func() error {
v, err := d.Str()
s.Name = string(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Name\"")
}
case "Source":
requiredBitSet[0] |= 1 << 1
if err := func() error {
v, err := d.Str()
s.Source = string(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Source\"")
}
case "ResourceType":
requiredBitSet[0] |= 1 << 2
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":
if err := func() error {
s.ResourceID.Reset()
if err := s.ResourceID.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"ResourceID\"")
}
default:
return d.Skip()
}
return nil
}); err != nil {
return errors.Wrap(err, "decode ScriptCreate")
}
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b00000111,
} {
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(jsonFieldsNameOfScriptCreate) {
name = jsonFieldsNameOfScriptCreate[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 *ScriptCreate) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *ScriptCreate) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}
// Encode implements json.Marshaler.
func (s *ScriptPolicy) Encode(e *jx.Encoder) {
e.ObjStart()
s.encodeFields(e)
e.ObjEnd()
}
// encodeFields encodes fields.
func (s *ScriptPolicy) encodeFields(e *jx.Encoder) {
{
e.FieldStart("ID")
e.Int64(s.ID)
}
{
e.FieldStart("FromScriptHash")
e.Str(s.FromScriptHash)
}
{
e.FieldStart("ToScriptID")
e.Int64(s.ToScriptID)
}
{
e.FieldStart("Policy")
e.Int32(s.Policy)
}
}
var jsonFieldsNameOfScriptPolicy = [4]string{
0: "ID",
1: "FromScriptHash",
2: "ToScriptID",
3: "Policy",
}
// Decode decodes ScriptPolicy from json.
func (s *ScriptPolicy) Decode(d *jx.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode ScriptPolicy 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 "FromScriptHash":
requiredBitSet[0] |= 1 << 1
if err := func() error {
v, err := d.Str()
s.FromScriptHash = string(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"FromScriptHash\"")
}
case "ToScriptID":
requiredBitSet[0] |= 1 << 2
if err := func() error {
v, err := d.Int64()
s.ToScriptID = int64(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"ToScriptID\"")
}
case "Policy":
requiredBitSet[0] |= 1 << 3
if err := func() error {
v, err := d.Int32()
s.Policy = int32(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Policy\"")
}
default:
return d.Skip()
}
return nil
}); err != nil {
return errors.Wrap(err, "decode ScriptPolicy")
}
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b00001111,
} {
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(jsonFieldsNameOfScriptPolicy) {
name = jsonFieldsNameOfScriptPolicy[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 *ScriptPolicy) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *ScriptPolicy) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}
// Encode implements json.Marshaler.
func (s *ScriptPolicyCreate) Encode(e *jx.Encoder) {
e.ObjStart()
s.encodeFields(e)
e.ObjEnd()
}
// encodeFields encodes fields.
func (s *ScriptPolicyCreate) encodeFields(e *jx.Encoder) {
{
e.FieldStart("FromScriptID")
e.Int64(s.FromScriptID)
}
{
e.FieldStart("ToScriptID")
e.Int64(s.ToScriptID)
}
{
e.FieldStart("Policy")
e.Int32(s.Policy)
}
}
var jsonFieldsNameOfScriptPolicyCreate = [3]string{
0: "FromScriptID",
1: "ToScriptID",
2: "Policy",
}
// Decode decodes ScriptPolicyCreate from json.
func (s *ScriptPolicyCreate) Decode(d *jx.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode ScriptPolicyCreate to nil")
}
var requiredBitSet [1]uint8
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
switch string(k) {
case "FromScriptID":
requiredBitSet[0] |= 1 << 0
if err := func() error {
v, err := d.Int64()
s.FromScriptID = int64(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"FromScriptID\"")
}
case "ToScriptID":
requiredBitSet[0] |= 1 << 1
if err := func() error {
v, err := d.Int64()
s.ToScriptID = int64(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"ToScriptID\"")
}
case "Policy":
requiredBitSet[0] |= 1 << 2
if err := func() error {
v, err := d.Int32()
s.Policy = int32(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Policy\"")
}
default:
return d.Skip()
}
return nil
}); err != nil {
return errors.Wrap(err, "decode ScriptPolicyCreate")
}
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b00000111,
} {
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(jsonFieldsNameOfScriptPolicyCreate) {
name = jsonFieldsNameOfScriptPolicyCreate[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 *ScriptPolicyCreate) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *ScriptPolicyCreate) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}

View File

@@ -0,0 +1,42 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"context"
"go.opentelemetry.io/otel/attribute"
)
// Labeler is used to allow adding custom attributes to the server request metrics.
type Labeler struct {
attrs []attribute.KeyValue
}
// Add attributes to the Labeler.
func (l *Labeler) Add(attrs ...attribute.KeyValue) {
l.attrs = append(l.attrs, attrs...)
}
// AttributeSet returns the attributes added to the Labeler as an attribute.Set.
func (l *Labeler) AttributeSet() attribute.Set {
return attribute.NewSet(l.attrs...)
}
type labelerContextKey struct{}
// LabelerFromContext retrieves the Labeler from the provided context, if present.
//
// If no Labeler was found in the provided context a new, empty Labeler is returned and the second
// return value is false. In this case it is safe to use the Labeler but any attributes added to
// it will not be used.
func LabelerFromContext(ctx context.Context) (*Labeler, bool) {
if l, ok := ctx.Value(labelerContextKey{}).(*Labeler); ok {
return l, true
}
return &Labeler{}, false
}
func contextWithLabeler(ctx context.Context, l *Labeler) context.Context {
return context.WithValue(ctx, labelerContextKey{}, l)
}

View File

@@ -0,0 +1,10 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"github.com/ogen-go/ogen/middleware"
)
// Middleware is middleware type.
type Middleware = middleware.Middleware

View File

@@ -0,0 +1,18 @@
// Code generated by ogen, DO NOT EDIT.
package api
// OperationName is the ogen operation name
type OperationName = string
const (
ActionSubmissionAcceptedOperation OperationName = "ActionSubmissionAccepted"
ActionSubmissionUploadedOperation OperationName = "ActionSubmissionUploaded"
ActionSubmissionValidatedOperation OperationName = "ActionSubmissionValidated"
CreateScriptOperation OperationName = "CreateScript"
CreateScriptPolicyOperation OperationName = "CreateScriptPolicy"
GetScriptOperation OperationName = "GetScript"
ListScriptPolicyOperation OperationName = "ListScriptPolicy"
ListScriptsOperation OperationName = "ListScripts"
UpdateSubmissionValidatedModelOperation OperationName = "UpdateSubmissionValidatedModel"
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,150 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"io"
"mime"
"net/http"
"github.com/go-faster/errors"
"github.com/go-faster/jx"
"go.uber.org/multierr"
"github.com/ogen-go/ogen/ogenerrors"
"github.com/ogen-go/ogen/validate"
)
func (s *Server) decodeCreateScriptRequest(r *http.Request) (
req *ScriptCreate,
close func() error,
rerr error,
) {
var closers []func() error
close = func() error {
var merr error
// Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- {
c := closers[i]
merr = multierr.Append(merr, c())
}
return merr
}
defer func() {
if rerr != nil {
rerr = multierr.Append(rerr, close())
}
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
if r.ContentLength == 0 {
return req, close, validate.ErrBodyRequired
}
buf, err := io.ReadAll(r.Body)
if err != nil {
return req, close, err
}
if len(buf) == 0 {
return req, close, validate.ErrBodyRequired
}
d := jx.DecodeBytes(buf)
var request ScriptCreate
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) decodeCreateScriptPolicyRequest(r *http.Request) (
req *ScriptPolicyCreate,
close func() error,
rerr error,
) {
var closers []func() error
close = func() error {
var merr error
// Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- {
c := closers[i]
merr = multierr.Append(merr, c())
}
return merr
}
defer func() {
if rerr != nil {
rerr = multierr.Append(rerr, close())
}
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
if r.ContentLength == 0 {
return req, close, validate.ErrBodyRequired
}
buf, err := io.ReadAll(r.Body)
if err != nil {
return req, close, err
}
if len(buf) == 0 {
return req, close, validate.ErrBodyRequired
}
d := jx.DecodeBytes(buf)
var request ScriptPolicyCreate
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
}
return &request, close, nil
default:
return req, close, validate.InvalidContentType(ct)
}
}

View File

@@ -0,0 +1,40 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"bytes"
"net/http"
"github.com/go-faster/jx"
ht "github.com/ogen-go/ogen/http"
)
func encodeCreateScriptRequest(
req *ScriptCreate,
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 encodeCreateScriptPolicyRequest(
req *ScriptPolicyCreate,
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
}

View File

@@ -0,0 +1,712 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"fmt"
"io"
"mime"
"net/http"
"github.com/go-faster/errors"
"github.com/go-faster/jx"
"github.com/ogen-go/ogen/ogenerrors"
"github.com/ogen-go/ogen/validate"
)
func decodeActionSubmissionAcceptedResponse(resp *http.Response) (res *ActionSubmissionAcceptedNoContent, _ error) {
switch resp.StatusCode {
case 204:
// Code 204.
return &ActionSubmissionAcceptedNoContent{}, 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
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeActionSubmissionUploadedResponse(resp *http.Response) (res *ActionSubmissionUploadedNoContent, _ error) {
switch resp.StatusCode {
case 204:
// Code 204.
return &ActionSubmissionUploadedNoContent{}, 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
}
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 decodeActionSubmissionValidatedResponse(resp *http.Response) (res *ActionSubmissionValidatedNoContent, _ error) {
switch resp.StatusCode {
case 204:
// Code 204.
return &ActionSubmissionValidatedNoContent{}, 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
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeCreateScriptResponse(resp *http.Response) (res *ID, _ error) {
switch resp.StatusCode {
case 201:
// Code 201.
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response ID
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
return &response, nil
default:
return res, validate.InvalidContentType(ct)
}
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeCreateScriptPolicyResponse(resp *http.Response) (res *ID, _ error) {
switch resp.StatusCode {
case 201:
// Code 201.
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response ID
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
return &response, nil
default:
return res, validate.InvalidContentType(ct)
}
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeGetScriptResponse(resp *http.Response) (res *Script, _ error) {
switch resp.StatusCode {
case 200:
// Code 200.
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Script
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
// Validate response.
if err := func() error {
if err := response.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return &response, nil
default:
return res, validate.InvalidContentType(ct)
}
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeListScriptPolicyResponse(resp *http.Response) (res []ScriptPolicy, _ error) {
switch resp.StatusCode {
case 200:
// Code 200.
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response []ScriptPolicy
if err := func() error {
response = make([]ScriptPolicy, 0)
if err := d.Arr(func(d *jx.Decoder) error {
var elem ScriptPolicy
if err := elem.Decode(d); err != nil {
return err
}
response = append(response, elem)
return nil
}); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
// Validate response.
if err := func() error {
if response == nil {
return errors.New("nil is invalid value")
}
var failures []validate.FieldError
for i, elem := range response {
if err := func() error {
if err := elem.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: fmt.Sprintf("[%d]", i),
Error: err,
})
}
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return response, nil
default:
return res, validate.InvalidContentType(ct)
}
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeListScriptsResponse(resp *http.Response) (res []Script, _ error) {
switch resp.StatusCode {
case 200:
// Code 200.
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response []Script
if err := func() error {
response = make([]Script, 0)
if err := d.Arr(func(d *jx.Decoder) error {
var elem Script
if err := elem.Decode(d); err != nil {
return err
}
response = append(response, elem)
return nil
}); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
// Validate response.
if err := func() error {
if response == nil {
return errors.New("nil is invalid value")
}
var failures []validate.FieldError
for i, elem := range response {
if err := func() error {
if err := elem.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: fmt.Sprintf("[%d]", i),
Error: err,
})
}
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return response, nil
default:
return res, validate.InvalidContentType(ct)
}
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeUpdateSubmissionValidatedModelResponse(resp *http.Response) (res *UpdateSubmissionValidatedModelNoContent, _ error) {
switch resp.StatusCode {
case 204:
// Code 204.
return &UpdateSubmissionValidatedModelNoContent{}, 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
}
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")
}

View File

@@ -0,0 +1,147 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"net/http"
"github.com/go-faster/errors"
"github.com/go-faster/jx"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
ht "github.com/ogen-go/ogen/http"
)
func encodeActionSubmissionAcceptedResponse(response *ActionSubmissionAcceptedNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeActionSubmissionUploadedResponse(response *ActionSubmissionUploadedNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeActionSubmissionValidatedResponse(response *ActionSubmissionValidatedNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeCreateScriptResponse(response *ID, 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 encodeCreateScriptPolicyResponse(response *ID, 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 encodeGetScriptResponse(response *Script, 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 encodeListScriptPolicyResponse(response []ScriptPolicy, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200)
span.SetStatus(codes.Ok, http.StatusText(200))
e := new(jx.Encoder)
e.ArrStart()
for _, elem := range response {
elem.Encode(e)
}
e.ArrEnd()
if _, err := e.WriteTo(w); err != nil {
return errors.Wrap(err, "write")
}
return nil
}
func encodeListScriptsResponse(response []Script, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200)
span.SetStatus(codes.Ok, http.StatusText(200))
e := new(jx.Encoder)
e.ArrStart()
for _, elem := range response {
elem.Encode(e)
}
e.ArrEnd()
if _, err := e.WriteTo(w); err != nil {
return errors.Wrap(err, "write")
}
return nil
}
func encodeUpdateSubmissionValidatedModelResponse(response *UpdateSubmissionValidatedModelNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeErrorResponse(response *ErrorStatusCode, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
code := response.StatusCode
if code == 0 {
// Set default status code.
code = http.StatusOK
}
w.WriteHeader(code)
if st := http.StatusText(code); code >= http.StatusBadRequest {
span.SetStatus(codes.Error, st)
} else {
span.SetStatus(codes.Ok, st)
}
e := new(jx.Encoder)
response.Response.Encode(e)
if _, err := e.WriteTo(w); err != nil {
return errors.Wrap(err, "write")
}
if code >= http.StatusInternalServerError {
return errors.Wrapf(ht.ErrInternalServerErrorResponse, "code: %d, message: %s", code, http.StatusText(code))
}
return nil
}

View File

@@ -0,0 +1,651 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"net/http"
"net/url"
"strings"
"github.com/ogen-go/ogen/uri"
)
func (s *Server) cutPrefix(path string) (string, bool) {
prefix := s.cfg.Prefix
if prefix == "" {
return path, true
}
if !strings.HasPrefix(path, prefix) {
// Prefix doesn't match.
return "", false
}
// Cut prefix from the path.
return strings.TrimPrefix(path, prefix), true
}
// ServeHTTP serves http request as defined by OpenAPI v3 specification,
// calling handler that matches the path or returning not found error.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
elem := r.URL.Path
elemIsEscaped := false
if rawPath := r.URL.RawPath; rawPath != "" {
if normalized, ok := uri.NormalizeEscapedPath(rawPath); ok {
elem = normalized
elemIsEscaped = strings.ContainsRune(elem, '%')
}
}
elem, ok := s.cutPrefix(elem)
if !ok || len(elem) == 0 {
s.notFound(w, r)
return
}
args := [1]string{}
// Static code generated router with unwrapped path search.
switch {
default:
if len(elem) == 0 {
break
}
switch elem[0] {
case '/': // Prefix: "/s"
if l := len("/s"); len(elem) >= l && elem[0:l] == "/s" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 'c': // Prefix: "cript"
if l := len("cript"); len(elem) >= l && elem[0:l] == "cript" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case '-': // Prefix: "-policy"
if l := len("-policy"); len(elem) >= l && elem[0:l] == "-policy" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "GET":
s.handleListScriptPolicyRequest([0]string{}, elemIsEscaped, w, r)
case "POST":
s.handleCreateScriptPolicyRequest([0]string{}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "GET,POST")
}
return
}
case 's': // Prefix: "s"
if l := len("s"); len(elem) >= l && elem[0:l] == "s" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
switch r.Method {
case "GET":
s.handleListScriptsRequest([0]string{}, elemIsEscaped, w, r)
case "POST":
s.handleCreateScriptRequest([0]string{}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "GET,POST")
}
return
}
switch elem[0] {
case '/': // Prefix: "/"
if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
elem = elem[l:]
} else {
break
}
// Param: "ScriptID"
// Leaf parameter, slashes are prohibited
idx := strings.IndexByte(elem, '/')
if idx >= 0 {
break
}
args[0] = elem
elem = ""
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "GET":
s.handleGetScriptRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "GET")
}
return
}
}
}
case 'u': // Prefix: "ubmissions/"
if l := len("ubmissions/"); len(elem) >= l && elem[0:l] == "ubmissions/" {
elem = elem[l:]
} else {
break
}
// Param: "SubmissionID"
// Match until "/"
idx := strings.IndexByte(elem, '/')
if idx < 0 {
idx = len(elem)
}
args[0] = elem[:idx]
elem = elem[idx:]
if len(elem) == 0 {
break
}
switch elem[0] {
case '/': // Prefix: "/"
if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 's': // Prefix: "status/validator-"
if l := len("status/validator-"); len(elem) >= l && elem[0:l] == "status/validator-" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 'f': // Prefix: "failed"
if l := len("failed"); len(elem) >= l && elem[0:l] == "failed" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionSubmissionAcceptedRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
case 'u': // Prefix: "uploaded"
if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionSubmissionUploadedRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
case 'v': // Prefix: "validated"
if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionSubmissionValidatedRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
}
case 'v': // Prefix: "validated-model"
if l := len("validated-model"); len(elem) >= l && elem[0:l] == "validated-model" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleUpdateSubmissionValidatedModelRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
}
}
}
}
}
s.notFound(w, r)
}
// Route is route object.
type Route struct {
name string
summary string
operationID string
pathPattern string
count int
args [1]string
}
// Name returns ogen operation name.
//
// It is guaranteed to be unique and not empty.
func (r Route) Name() string {
return r.name
}
// Summary returns OpenAPI summary.
func (r Route) Summary() string {
return r.summary
}
// OperationID returns OpenAPI operationId.
func (r Route) OperationID() string {
return r.operationID
}
// PathPattern returns OpenAPI path.
func (r Route) PathPattern() string {
return r.pathPattern
}
// Args returns parsed arguments.
func (r Route) Args() []string {
return r.args[:r.count]
}
// FindRoute finds Route for given method and path.
//
// Note: this method does not unescape path or handle reserved characters in path properly. Use FindPath instead.
func (s *Server) FindRoute(method, path string) (Route, bool) {
return s.FindPath(method, &url.URL{Path: path})
}
// FindPath finds Route for given method and URL.
func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
var (
elem = u.Path
args = r.args
)
if rawPath := u.RawPath; rawPath != "" {
if normalized, ok := uri.NormalizeEscapedPath(rawPath); ok {
elem = normalized
}
defer func() {
for i, arg := range r.args[:r.count] {
if unescaped, err := url.PathUnescape(arg); err == nil {
r.args[i] = unescaped
}
}
}()
}
elem, ok := s.cutPrefix(elem)
if !ok {
return r, false
}
// Static code generated router with unwrapped path search.
switch {
default:
if len(elem) == 0 {
break
}
switch elem[0] {
case '/': // Prefix: "/s"
if l := len("/s"); len(elem) >= l && elem[0:l] == "/s" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 'c': // Prefix: "cript"
if l := len("cript"); len(elem) >= l && elem[0:l] == "cript" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case '-': // Prefix: "-policy"
if l := len("-policy"); len(elem) >= l && elem[0:l] == "-policy" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "GET":
r.name = ListScriptPolicyOperation
r.summary = "Get list of script policies"
r.operationID = "listScriptPolicy"
r.pathPattern = "/script-policy"
r.args = args
r.count = 0
return r, true
case "POST":
r.name = CreateScriptPolicyOperation
r.summary = "Create a new script policy"
r.operationID = "createScriptPolicy"
r.pathPattern = "/script-policy"
r.args = args
r.count = 0
return r, true
default:
return
}
}
case 's': // Prefix: "s"
if l := len("s"); len(elem) >= l && elem[0:l] == "s" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
switch method {
case "GET":
r.name = ListScriptsOperation
r.summary = "Get list of scripts"
r.operationID = "listScripts"
r.pathPattern = "/scripts"
r.args = args
r.count = 0
return r, true
case "POST":
r.name = CreateScriptOperation
r.summary = "Create a new script"
r.operationID = "createScript"
r.pathPattern = "/scripts"
r.args = args
r.count = 0
return r, true
default:
return
}
}
switch elem[0] {
case '/': // Prefix: "/"
if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
elem = elem[l:]
} else {
break
}
// Param: "ScriptID"
// Leaf parameter, slashes are prohibited
idx := strings.IndexByte(elem, '/')
if idx >= 0 {
break
}
args[0] = elem
elem = ""
if len(elem) == 0 {
// Leaf node.
switch method {
case "GET":
r.name = GetScriptOperation
r.summary = "Get the specified script by ID"
r.operationID = "getScript"
r.pathPattern = "/scripts/{ScriptID}"
r.args = args
r.count = 1
return r, true
default:
return
}
}
}
}
case 'u': // Prefix: "ubmissions/"
if l := len("ubmissions/"); len(elem) >= l && elem[0:l] == "ubmissions/" {
elem = elem[l:]
} else {
break
}
// Param: "SubmissionID"
// Match until "/"
idx := strings.IndexByte(elem, '/')
if idx < 0 {
idx = len(elem)
}
args[0] = elem[:idx]
elem = elem[idx:]
if len(elem) == 0 {
break
}
switch elem[0] {
case '/': // Prefix: "/"
if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 's': // Prefix: "status/validator-"
if l := len("status/validator-"); len(elem) >= l && elem[0:l] == "status/validator-" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 'f': // Prefix: "failed"
if l := len("failed"); len(elem) >= l && elem[0:l] == "failed" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionSubmissionAcceptedOperation
r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Accepted"
r.operationID = "actionSubmissionAccepted"
r.pathPattern = "/submissions/{SubmissionID}/status/validator-failed"
r.args = args
r.count = 1
return r, true
default:
return
}
}
case 'u': // Prefix: "uploaded"
if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionSubmissionUploadedOperation
r.summary = "(Internal endpoint) Role Validator changes status from Uploading -> Uploaded"
r.operationID = "actionSubmissionUploaded"
r.pathPattern = "/submissions/{SubmissionID}/status/validator-uploaded"
r.args = args
r.count = 1
return r, true
default:
return
}
}
case 'v': // Prefix: "validated"
if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionSubmissionValidatedOperation
r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Validated"
r.operationID = "actionSubmissionValidated"
r.pathPattern = "/submissions/{SubmissionID}/status/validator-validated"
r.args = args
r.count = 1
return r, true
default:
return
}
}
}
case 'v': // Prefix: "validated-model"
if l := len("validated-model"); len(elem) >= l && elem[0:l] == "validated-model" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = UpdateSubmissionValidatedModelOperation
r.summary = "Update validated model"
r.operationID = "updateSubmissionValidatedModel"
r.pathPattern = "/submissions/{SubmissionID}/validated-model"
r.args = args
r.count = 1
return r, true
default:
return
}
}
}
}
}
}
}
return r, false
}

View File

@@ -0,0 +1,432 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"fmt"
)
func (s *ErrorStatusCode) Error() string {
return fmt.Sprintf("code %d: %+v", s.StatusCode, s.Response)
}
// ActionSubmissionAcceptedNoContent is response for ActionSubmissionAccepted operation.
type ActionSubmissionAcceptedNoContent struct{}
// ActionSubmissionUploadedNoContent is response for ActionSubmissionUploaded operation.
type ActionSubmissionUploadedNoContent struct{}
// ActionSubmissionValidatedNoContent is response for ActionSubmissionValidated operation.
type ActionSubmissionValidatedNoContent struct{}
// Represents error object.
// Ref: #/components/schemas/Error
type Error struct {
Code int64 `json:"code"`
Message string `json:"message"`
}
// GetCode returns the value of Code.
func (s *Error) GetCode() int64 {
return s.Code
}
// GetMessage returns the value of Message.
func (s *Error) GetMessage() string {
return s.Message
}
// SetCode sets the value of Code.
func (s *Error) SetCode(val int64) {
s.Code = val
}
// SetMessage sets the value of Message.
func (s *Error) SetMessage(val string) {
s.Message = val
}
// ErrorStatusCode wraps Error with StatusCode.
type ErrorStatusCode struct {
StatusCode int
Response Error
}
// GetStatusCode returns the value of StatusCode.
func (s *ErrorStatusCode) GetStatusCode() int {
return s.StatusCode
}
// GetResponse returns the value of Response.
func (s *ErrorStatusCode) GetResponse() Error {
return s.Response
}
// SetStatusCode sets the value of StatusCode.
func (s *ErrorStatusCode) SetStatusCode(val int) {
s.StatusCode = val
}
// SetResponse sets the value of Response.
func (s *ErrorStatusCode) SetResponse(val Error) {
s.Response = val
}
// Ref: #/components/schemas/Id
type ID struct {
ID int64 `json:"ID"`
}
// GetID returns the value of ID.
func (s *ID) GetID() int64 {
return s.ID
}
// SetID sets the value of ID.
func (s *ID) SetID(val int64) {
s.ID = val
}
// NewOptInt32 returns new OptInt32 with value set to v.
func NewOptInt32(v int32) OptInt32 {
return OptInt32{
Value: v,
Set: true,
}
}
// OptInt32 is optional int32.
type OptInt32 struct {
Value int32
Set bool
}
// IsSet returns true if OptInt32 was set.
func (o OptInt32) IsSet() bool { return o.Set }
// Reset unsets value.
func (o *OptInt32) Reset() {
var v int32
o.Value = v
o.Set = false
}
// SetTo sets value to v.
func (o *OptInt32) SetTo(v int32) {
o.Set = true
o.Value = v
}
// Get returns value and boolean that denotes whether value was set.
func (o OptInt32) Get() (v int32, ok bool) {
if !o.Set {
return v, false
}
return o.Value, true
}
// Or returns value if set, or given parameter if does not.
func (o OptInt32) Or(d int32) int32 {
if v, ok := o.Get(); ok {
return v
}
return d
}
// NewOptInt64 returns new OptInt64 with value set to v.
func NewOptInt64(v int64) OptInt64 {
return OptInt64{
Value: v,
Set: true,
}
}
// OptInt64 is optional int64.
type OptInt64 struct {
Value int64
Set bool
}
// IsSet returns true if OptInt64 was set.
func (o OptInt64) IsSet() bool { return o.Set }
// Reset unsets value.
func (o *OptInt64) Reset() {
var v int64
o.Value = v
o.Set = false
}
// SetTo sets value to v.
func (o *OptInt64) SetTo(v int64) {
o.Set = true
o.Value = v
}
// Get returns value and boolean that denotes whether value was set.
func (o OptInt64) Get() (v int64, ok bool) {
if !o.Set {
return v, false
}
return o.Value, true
}
// Or returns value if set, or given parameter if does not.
func (o OptInt64) Or(d int64) int64 {
if v, ok := o.Get(); ok {
return v
}
return d
}
// NewOptString returns new OptString with value set to v.
func NewOptString(v string) OptString {
return OptString{
Value: v,
Set: true,
}
}
// OptString is optional string.
type OptString struct {
Value string
Set bool
}
// IsSet returns true if OptString was set.
func (o OptString) IsSet() bool { return o.Set }
// Reset unsets value.
func (o *OptString) Reset() {
var v string
o.Value = v
o.Set = false
}
// SetTo sets value to v.
func (o *OptString) SetTo(v string) {
o.Set = true
o.Value = v
}
// Get returns value and boolean that denotes whether value was set.
func (o OptString) Get() (v string, ok bool) {
if !o.Set {
return v, false
}
return o.Value, true
}
// Or returns value if set, or given parameter if does not.
func (o OptString) Or(d string) string {
if v, ok := o.Get(); ok {
return v
}
return d
}
// Ref: #/components/schemas/Script
type Script struct {
ID int64 `json:"ID"`
Name string `json:"Name"`
Hash string `json:"Hash"`
Source string `json:"Source"`
ResourceType int32 `json:"ResourceType"`
ResourceID int64 `json:"ResourceID"`
}
// GetID returns the value of ID.
func (s *Script) GetID() int64 {
return s.ID
}
// GetName returns the value of Name.
func (s *Script) GetName() string {
return s.Name
}
// GetHash returns the value of Hash.
func (s *Script) GetHash() string {
return s.Hash
}
// GetSource returns the value of Source.
func (s *Script) GetSource() string {
return s.Source
}
// GetResourceType returns the value of ResourceType.
func (s *Script) GetResourceType() int32 {
return s.ResourceType
}
// GetResourceID returns the value of ResourceID.
func (s *Script) GetResourceID() int64 {
return s.ResourceID
}
// SetID sets the value of ID.
func (s *Script) SetID(val int64) {
s.ID = val
}
// SetName sets the value of Name.
func (s *Script) SetName(val string) {
s.Name = val
}
// SetHash sets the value of Hash.
func (s *Script) SetHash(val string) {
s.Hash = val
}
// SetSource sets the value of Source.
func (s *Script) SetSource(val string) {
s.Source = val
}
// SetResourceType sets the value of ResourceType.
func (s *Script) SetResourceType(val int32) {
s.ResourceType = val
}
// SetResourceID sets the value of ResourceID.
func (s *Script) SetResourceID(val int64) {
s.ResourceID = val
}
// Ref: #/components/schemas/ScriptCreate
type ScriptCreate struct {
Name string `json:"Name"`
Source string `json:"Source"`
ResourceType int32 `json:"ResourceType"`
ResourceID OptInt64 `json:"ResourceID"`
}
// GetName returns the value of Name.
func (s *ScriptCreate) GetName() string {
return s.Name
}
// GetSource returns the value of Source.
func (s *ScriptCreate) GetSource() string {
return s.Source
}
// GetResourceType returns the value of ResourceType.
func (s *ScriptCreate) GetResourceType() int32 {
return s.ResourceType
}
// GetResourceID returns the value of ResourceID.
func (s *ScriptCreate) GetResourceID() OptInt64 {
return s.ResourceID
}
// SetName sets the value of Name.
func (s *ScriptCreate) SetName(val string) {
s.Name = val
}
// SetSource sets the value of Source.
func (s *ScriptCreate) SetSource(val string) {
s.Source = val
}
// SetResourceType sets the value of ResourceType.
func (s *ScriptCreate) SetResourceType(val int32) {
s.ResourceType = val
}
// SetResourceID sets the value of ResourceID.
func (s *ScriptCreate) SetResourceID(val OptInt64) {
s.ResourceID = val
}
// Ref: #/components/schemas/ScriptPolicy
type ScriptPolicy struct {
ID int64 `json:"ID"`
FromScriptHash string `json:"FromScriptHash"`
ToScriptID int64 `json:"ToScriptID"`
Policy int32 `json:"Policy"`
}
// GetID returns the value of ID.
func (s *ScriptPolicy) GetID() int64 {
return s.ID
}
// GetFromScriptHash returns the value of FromScriptHash.
func (s *ScriptPolicy) GetFromScriptHash() string {
return s.FromScriptHash
}
// GetToScriptID returns the value of ToScriptID.
func (s *ScriptPolicy) GetToScriptID() int64 {
return s.ToScriptID
}
// GetPolicy returns the value of Policy.
func (s *ScriptPolicy) GetPolicy() int32 {
return s.Policy
}
// SetID sets the value of ID.
func (s *ScriptPolicy) SetID(val int64) {
s.ID = val
}
// SetFromScriptHash sets the value of FromScriptHash.
func (s *ScriptPolicy) SetFromScriptHash(val string) {
s.FromScriptHash = val
}
// SetToScriptID sets the value of ToScriptID.
func (s *ScriptPolicy) SetToScriptID(val int64) {
s.ToScriptID = val
}
// SetPolicy sets the value of Policy.
func (s *ScriptPolicy) SetPolicy(val int32) {
s.Policy = val
}
// Ref: #/components/schemas/ScriptPolicyCreate
type ScriptPolicyCreate struct {
FromScriptID int64 `json:"FromScriptID"`
ToScriptID int64 `json:"ToScriptID"`
Policy int32 `json:"Policy"`
}
// GetFromScriptID returns the value of FromScriptID.
func (s *ScriptPolicyCreate) GetFromScriptID() int64 {
return s.FromScriptID
}
// GetToScriptID returns the value of ToScriptID.
func (s *ScriptPolicyCreate) GetToScriptID() int64 {
return s.ToScriptID
}
// GetPolicy returns the value of Policy.
func (s *ScriptPolicyCreate) GetPolicy() int32 {
return s.Policy
}
// SetFromScriptID sets the value of FromScriptID.
func (s *ScriptPolicyCreate) SetFromScriptID(val int64) {
s.FromScriptID = val
}
// SetToScriptID sets the value of ToScriptID.
func (s *ScriptPolicyCreate) SetToScriptID(val int64) {
s.ToScriptID = val
}
// SetPolicy sets the value of Policy.
func (s *ScriptPolicyCreate) SetPolicy(val int32) {
s.Policy = val
}
// UpdateSubmissionValidatedModelNoContent is response for UpdateSubmissionValidatedModel operation.
type UpdateSubmissionValidatedModelNoContent struct{}

View File

@@ -0,0 +1,88 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"context"
)
// Handler handles operations described by OpenAPI v3 specification.
type Handler interface {
// ActionSubmissionAccepted implements actionSubmissionAccepted operation.
//
// (Internal endpoint) Role Validator changes status from Validating -> Accepted.
//
// POST /submissions/{SubmissionID}/status/validator-failed
ActionSubmissionAccepted(ctx context.Context, params ActionSubmissionAcceptedParams) error
// ActionSubmissionUploaded implements actionSubmissionUploaded operation.
//
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.
//
// POST /submissions/{SubmissionID}/status/validator-uploaded
ActionSubmissionUploaded(ctx context.Context, params ActionSubmissionUploadedParams) error
// ActionSubmissionValidated implements actionSubmissionValidated operation.
//
// (Internal endpoint) Role Validator changes status from Validating -> Validated.
//
// POST /submissions/{SubmissionID}/status/validator-validated
ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error
// CreateScript implements createScript operation.
//
// Create a new script.
//
// POST /scripts
CreateScript(ctx context.Context, req *ScriptCreate) (*ID, error)
// CreateScriptPolicy implements createScriptPolicy operation.
//
// Create a new script policy.
//
// POST /script-policy
CreateScriptPolicy(ctx context.Context, req *ScriptPolicyCreate) (*ID, error)
// GetScript implements getScript operation.
//
// Get the specified script by ID.
//
// GET /scripts/{ScriptID}
GetScript(ctx context.Context, params GetScriptParams) (*Script, error)
// ListScriptPolicy implements listScriptPolicy operation.
//
// Get list of script policies.
//
// GET /script-policy
ListScriptPolicy(ctx context.Context, params ListScriptPolicyParams) ([]ScriptPolicy, error)
// ListScripts implements listScripts operation.
//
// Get list of scripts.
//
// GET /scripts
ListScripts(ctx context.Context, params ListScriptsParams) ([]Script, error)
// UpdateSubmissionValidatedModel implements updateSubmissionValidatedModel operation.
//
// Update validated model.
//
// POST /submissions/{SubmissionID}/validated-model
UpdateSubmissionValidatedModel(ctx context.Context, params UpdateSubmissionValidatedModelParams) error
// NewError creates *ErrorStatusCode from error returned by handler.
//
// Used for common default response.
NewError(ctx context.Context, err error) *ErrorStatusCode
}
// Server implements http server based on OpenAPI v3 specification and
// calls Handler to handle requests.
type Server struct {
h Handler
baseServer
}
// NewServer creates new Server.
func NewServer(h Handler, opts ...ServerOption) (*Server, error) {
s, err := newServerConfig(opts...).baseServer()
if err != nil {
return nil, err
}
return &Server{
h: h,
baseServer: s,
}, nil
}

View File

@@ -0,0 +1,103 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"context"
ht "github.com/ogen-go/ogen/http"
)
// UnimplementedHandler is no-op Handler which returns http.ErrNotImplemented.
type UnimplementedHandler struct{}
var _ Handler = UnimplementedHandler{}
// ActionSubmissionAccepted implements actionSubmissionAccepted operation.
//
// (Internal endpoint) Role Validator changes status from Validating -> Accepted.
//
// POST /submissions/{SubmissionID}/status/validator-failed
func (UnimplementedHandler) ActionSubmissionAccepted(ctx context.Context, params ActionSubmissionAcceptedParams) error {
return ht.ErrNotImplemented
}
// ActionSubmissionUploaded implements actionSubmissionUploaded operation.
//
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.
//
// POST /submissions/{SubmissionID}/status/validator-uploaded
func (UnimplementedHandler) ActionSubmissionUploaded(ctx context.Context, params ActionSubmissionUploadedParams) error {
return ht.ErrNotImplemented
}
// ActionSubmissionValidated implements actionSubmissionValidated operation.
//
// (Internal endpoint) Role Validator changes status from Validating -> Validated.
//
// POST /submissions/{SubmissionID}/status/validator-validated
func (UnimplementedHandler) ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error {
return ht.ErrNotImplemented
}
// CreateScript implements createScript operation.
//
// Create a new script.
//
// POST /scripts
func (UnimplementedHandler) CreateScript(ctx context.Context, req *ScriptCreate) (r *ID, _ error) {
return r, ht.ErrNotImplemented
}
// CreateScriptPolicy implements createScriptPolicy operation.
//
// Create a new script policy.
//
// POST /script-policy
func (UnimplementedHandler) CreateScriptPolicy(ctx context.Context, req *ScriptPolicyCreate) (r *ID, _ error) {
return r, ht.ErrNotImplemented
}
// GetScript implements getScript operation.
//
// Get the specified script by ID.
//
// GET /scripts/{ScriptID}
func (UnimplementedHandler) GetScript(ctx context.Context, params GetScriptParams) (r *Script, _ error) {
return r, ht.ErrNotImplemented
}
// ListScriptPolicy implements listScriptPolicy operation.
//
// Get list of script policies.
//
// GET /script-policy
func (UnimplementedHandler) ListScriptPolicy(ctx context.Context, params ListScriptPolicyParams) (r []ScriptPolicy, _ error) {
return r, ht.ErrNotImplemented
}
// ListScripts implements listScripts operation.
//
// Get list of scripts.
//
// GET /scripts
func (UnimplementedHandler) ListScripts(ctx context.Context, params ListScriptsParams) (r []Script, _ error) {
return r, ht.ErrNotImplemented
}
// UpdateSubmissionValidatedModel implements updateSubmissionValidatedModel operation.
//
// Update validated model.
//
// POST /submissions/{SubmissionID}/validated-model
func (UnimplementedHandler) UpdateSubmissionValidatedModel(ctx context.Context, params UpdateSubmissionValidatedModelParams) error {
return ht.ErrNotImplemented
}
// NewError creates *ErrorStatusCode from error returned by handler.
//
// Used for common default response.
func (UnimplementedHandler) NewError(ctx context.Context, err error) (r *ErrorStatusCode) {
r = new(ErrorStatusCode)
return r
}

View File

@@ -0,0 +1,159 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"github.com/go-faster/errors"
"github.com/ogen-go/ogen/validate"
)
func (s *Script) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if err := (validate.String{
MinLength: 0,
MinLengthSet: false,
MaxLength: 128,
MaxLengthSet: true,
Email: false,
Hostname: false,
Regex: nil,
}).Validate(string(s.Name)); err != nil {
return errors.Wrap(err, "string")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Name",
Error: err,
})
}
if err := func() error {
if err := (validate.String{
MinLength: 16,
MinLengthSet: true,
MaxLength: 16,
MaxLengthSet: true,
Email: false,
Hostname: false,
Regex: nil,
}).Validate(string(s.Hash)); err != nil {
return errors.Wrap(err, "string")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Hash",
Error: err,
})
}
if err := func() error {
if err := (validate.String{
MinLength: 0,
MinLengthSet: false,
MaxLength: 1048576,
MaxLengthSet: true,
Email: false,
Hostname: false,
Regex: nil,
}).Validate(string(s.Source)); err != nil {
return errors.Wrap(err, "string")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Source",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *ScriptCreate) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if err := (validate.String{
MinLength: 0,
MinLengthSet: false,
MaxLength: 128,
MaxLengthSet: true,
Email: false,
Hostname: false,
Regex: nil,
}).Validate(string(s.Name)); err != nil {
return errors.Wrap(err, "string")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Name",
Error: err,
})
}
if err := func() error {
if err := (validate.String{
MinLength: 0,
MinLengthSet: false,
MaxLength: 1048576,
MaxLengthSet: true,
Email: false,
Hostname: false,
Regex: nil,
}).Validate(string(s.Source)); err != nil {
return errors.Wrap(err, "string")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Source",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *ScriptPolicy) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if err := (validate.String{
MinLength: 16,
MinLengthSet: true,
MaxLength: 16,
MaxLengthSet: true,
Email: false,
Hostname: false,
Regex: nil,
}).Validate(string(s.FromScriptHash)); err != nil {
return errors.Wrap(err, "string")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "FromScriptHash",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}

View File

@@ -8,25 +8,22 @@ package model
type ValidateRequest struct { type ValidateRequest struct {
// submission_id is passed back in the response message // submission_id is passed back in the response message
SubmissionID int64 SubmissionID int64
ModelID uint64 ModelID int64
ModelVersion uint64 ModelVersion int64
ValidatedModelID uint64 // optional value ValidatedModelID *int64 // optional value
} }
// Create a new map // Create a new map
type PublishNewRequest struct { type UploadNewRequest struct {
SubmissionID int64 SubmissionID int64
ModelID uint64 ModelID int64
ModelVersion uint64 ModelVersion int64
Creator string ModelName string
DisplayName string
GameID uint32
//games HashSet<GameID>
} }
type PublishFixRequest struct { type UploadFixRequest struct {
SubmissionID int64 SubmissionID int64
ModelID uint64 ModelID int64
ModelVersion uint64 ModelVersion int64
TargetAssetID uint64 TargetAssetID int64
} }

View File

@@ -17,7 +17,7 @@ type ScriptPolicy struct {
// Hash of the source code that leads to this policy. // Hash of the source code that leads to this policy.
// If this is a replacement mapping, the original source may not be pointed to by any policy. // If this is a replacement mapping, the original source may not be pointed to by any policy.
// The original source should still exist in the scripts table, which can be located by the same hash. // The original source should still exist in the scripts table, which can be located by the same hash.
FromScriptHash uint64 FromScriptHash int64 // postgres does not support unsigned integers, so we have to pretend
// The ID of the replacement source (ScriptPolicyReplace) // The ID of the replacement source (ScriptPolicyReplace)
// or verbatim source (ScriptPolicyAllowed) // or verbatim source (ScriptPolicyAllowed)
// or 0 (other) // or 0 (other)

View File

@@ -1,12 +1,42 @@
package model package model
import "time" import (
"fmt"
"strconv"
"time"
"github.com/dchest/siphash"
)
// compute the hash of a source code string
func HashSource(source string) uint64{
return siphash.Hash(0, 0, []byte(source))
}
// format a hash value as a hexidecimal string
func HashFormat(hash uint64) string{
return fmt.Sprintf("%016x", hash)
}
// parse a hexidecimal hash string
func HashParse(hash string) (uint64, error){
return strconv.ParseUint(hash, 16, 64)
}
type ResourceType int32
const (
ResourceUnknown ResourceType = 0
ResourceMapfix ResourceType = 1
ResourceSubmission ResourceType = 2
)
type Script struct { type Script struct {
ID int64 `gorm:"primaryKey"` ID int64 `gorm:"primaryKey"`
Hash uint64 Name string
Hash int64 // postgres does not support unsigned integers, so we have to pretend
Source string Source string
SubmissionID int64 // which submission did this script first appear in ResourceType ResourceType // is this a submission or is it a mapfix
ResourceID int64 // which submission / mapfix did this script first appear in
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
} }

View File

@@ -2,20 +2,24 @@ package model
import "time" import "time"
type Status int32 type SubmissionStatus int32
const ( const (
StatusPublished Status = 8 // Phase: Final SubmissionStatus
StatusRejected Status = 7 SubmissionStatusReleased SubmissionStatus = 9
SubmissionStatusRejected SubmissionStatus = 8
StatusPublishing Status = 6 // Phase: Testing
StatusValidated Status = 5 SubmissionStatusUploaded SubmissionStatus = 7 // uploaded to the group, but pending release
StatusValidating Status = 4 SubmissionStatusUploading SubmissionStatus = 6
StatusAccepted Status = 3 SubmissionStatusValidated SubmissionStatus = 5
SubmissionStatusValidating SubmissionStatus = 4
SubmissionStatusAccepted SubmissionStatus = 3 // pending script review, can re-trigger validation
StatusChangesRequested Status = 2 // Phase: Creation
StatusSubmitted Status = 1 SubmissionStatusChangesRequested SubmissionStatus = 2
StatusUnderConstruction Status = 0 SubmissionStatusSubmitted SubmissionStatus = 1
SubmissionStatusUnderConstruction SubmissionStatus = 0
) )
type Submission struct { type Submission struct {
@@ -25,10 +29,13 @@ type Submission struct {
GameID int32 GameID int32
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
Submitter uint64 // UserID Submitter int64 // UserID
AssetID uint64 AssetID int64
AssetVersion uint64 AssetVersion int64
ValidatedAssetID int64
ValidatedAssetVersion int64
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 uint64 // where to upload map fix. if the TargetAssetID is 0, it's a new map. UploadedAssetID int64 // where to upload map fix. if the TargetAssetID is 0, it's a new map.
StatusID Status StatusID SubmissionStatus
StatusMessage string
} }

View File

@@ -2,8 +2,6 @@ package service
import ( import (
"context" "context"
"fmt"
"strconv"
"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"
@@ -16,12 +14,16 @@ import (
// //
// POST /script-policy // POST /script-policy
func (svc *Service) CreateScriptPolicy(ctx context.Context, req *api.ScriptPolicyCreate) (*api.ID, error) { func (svc *Service) CreateScriptPolicy(ctx context.Context, req *api.ScriptPolicyCreate) (*api.ID, error) {
userInfo, ok := ctx.Value("UserInfo").(UserInfo) userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return nil, ErrUserInfo return nil, ErrUserInfo
} }
if !userInfo.Roles.ScriptWrite { has_role, err := userInfo.HasRoleScriptWrite()
if err != nil {
return nil, err
}
if !has_role {
return nil, ErrPermissionDenied return nil, ErrPermissionDenied
} }
@@ -53,30 +55,38 @@ func (svc *Service) CreateScriptPolicy(ctx context.Context, req *api.ScriptPolic
// Get list of script policies. // Get list of script policies.
// //
// GET /script-policy // GET /script-policy
func (svc *Service) ListScriptPolicy(ctx context.Context, request *api.ListScriptPolicyReq) ([]api.ScriptPolicy, error) { func (svc *Service) ListScriptPolicy(ctx context.Context, params api.ListScriptPolicyParams) ([]api.ScriptPolicy, error) {
filter := datastore.Optional() filter := datastore.Optional()
//fmt.Println(request)
if request.Filter.IsSet() { if params.FromScriptHash.IsSet(){
filter.AddNotNil("from_script_hash", request.Filter.Value.FromScriptHash) hash, err := model.HashParse(params.FromScriptHash.Value)
filter.AddNotNil("to_script_id", request.Filter.Value.ToScriptID) if err != nil {
filter.AddNotNil("policy", request.Filter.Value.Policy) return nil, err
}
filter.AddNotNil("from_script_hash", int64(hash)) // No type safety!
}
if params.ToScriptID.IsSet(){
filter.AddNotNil("to_script_id", params.ToScriptID.Value)
}
if params.Policy.IsSet(){
filter.AddNotNil("policy", params.Policy.Value)
} }
items, err := svc.DB.ScriptPolicy().List(ctx, filter, model.Page{ items, err := svc.DB.ScriptPolicy().List(ctx, filter, model.Page{
Number: request.Page.GetPage(), Number: params.Page,
Size: request.Page.GetLimit(), Size: params.Limit,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
var resp []api.ScriptPolicy var resp []api.ScriptPolicy
for i := 0; i < len(items); i++ { for _, item := range items {
resp = append(resp, api.ScriptPolicy{ resp = append(resp, api.ScriptPolicy{
ID: items[i].ID, ID: item.ID,
FromScriptHash: fmt.Sprintf("%x", items[i].FromScriptHash), FromScriptHash: model.HashFormat(uint64(item.FromScriptHash)),
ToScriptID: items[i].ToScriptID, ToScriptID: item.ToScriptID,
Policy: int32(items[i].Policy), Policy: int32(item.Policy),
}) })
} }
@@ -87,14 +97,18 @@ func (svc *Service) ListScriptPolicy(ctx context.Context, request *api.ListScrip
// //
// Delete the specified script policy by ID. // Delete the specified script policy by ID.
// //
// DELETE /script-policy/id/{ScriptPolicyID} // DELETE /script-policy/{ScriptPolicyID}
func (svc *Service) DeleteScriptPolicy(ctx context.Context, params api.DeleteScriptPolicyParams) error { func (svc *Service) DeleteScriptPolicy(ctx context.Context, params api.DeleteScriptPolicyParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfo) userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return ErrUserInfo return ErrUserInfo
} }
if !userInfo.Roles.ScriptWrite { has_role, err := userInfo.HasRoleScriptWrite()
if err != nil {
return err
}
if !has_role {
return ErrPermissionDenied return ErrPermissionDenied
} }
@@ -105,9 +119,9 @@ func (svc *Service) DeleteScriptPolicy(ctx context.Context, params api.DeleteScr
// //
// Get the specified script policy by ID. // Get the specified script policy by ID.
// //
// GET /script-policy/id/{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").(UserInfo) _, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return nil, ErrUserInfo return nil, ErrUserInfo
} }
@@ -121,39 +135,7 @@ func (svc *Service) GetScriptPolicy(ctx context.Context, params api.GetScriptPol
return &api.ScriptPolicy{ return &api.ScriptPolicy{
ID: policy.ID, ID: policy.ID,
FromScriptHash: fmt.Sprintf("%x", policy.FromScriptHash), FromScriptHash: model.HashFormat(uint64(policy.FromScriptHash)),
ToScriptID: policy.ToScriptID,
Policy: int32(policy.Policy),
}, nil
}
// GetScriptPolicyFromHash implements getScriptPolicyFromHash operation.
//
// Get the policy for the given hash of script source code.
//
// GET /script-policy/hash/{FromScriptHash}
func (svc *Service) GetScriptPolicyFromHash(ctx context.Context, params api.GetScriptPolicyFromHashParams) (*api.ScriptPolicy, error) {
_, ok := ctx.Value("UserInfo").(UserInfo)
if !ok {
return nil, ErrUserInfo
}
// Read permission for script policy only requires you to be logged in
// parse hash from hex
hash, err := strconv.ParseUint(params.FromScriptHash, 16, 64)
if err != nil {
return nil, err
}
policy, err := svc.DB.ScriptPolicy().GetFromHash(ctx, hash)
if err != nil {
return nil, err
}
return &api.ScriptPolicy{
ID: policy.ID,
FromScriptHash: fmt.Sprintf("%x", policy.FromScriptHash),
ToScriptID: policy.ToScriptID, ToScriptID: policy.ToScriptID,
Policy: int32(policy.Policy), Policy: int32(policy.Policy),
}, nil }, nil
@@ -163,14 +145,18 @@ func (svc *Service) GetScriptPolicyFromHash(ctx context.Context, params api.GetS
// //
// Update the specified script policy by ID. // Update the specified script policy by ID.
// //
// PATCH /script-policy/id/{ScriptPolicyID} // POST /script-policy/{ScriptPolicyID}
func (svc *Service) UpdateScriptPolicy(ctx context.Context, req *api.ScriptPolicyUpdate, params api.UpdateScriptPolicyParams) error { func (svc *Service) UpdateScriptPolicy(ctx context.Context, req *api.ScriptPolicyUpdate, params api.UpdateScriptPolicyParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfo) userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return ErrUserInfo return ErrUserInfo
} }
if !userInfo.Roles.ScriptWrite { has_role, err := userInfo.HasRoleScriptWrite()
if err != nil {
return err
}
if !has_role {
return ErrPermissionDenied return ErrPermissionDenied
} }

View File

@@ -2,12 +2,10 @@ package service
import ( import (
"context" "context"
"fmt"
"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"
"git.itzana.me/strafesnet/maps-service/pkg/model" "git.itzana.me/strafesnet/maps-service/pkg/model"
"github.com/dchest/siphash"
) )
// CreateScript implements createScript operation. // CreateScript implements createScript operation.
@@ -16,20 +14,26 @@ import (
// //
// POST /scripts // POST /scripts
func (svc *Service) CreateScript(ctx context.Context, req *api.ScriptCreate) (*api.ID, error) { func (svc *Service) CreateScript(ctx context.Context, req *api.ScriptCreate) (*api.ID, error) {
userInfo, ok := ctx.Value("UserInfo").(UserInfo) userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return nil, ErrUserInfo return nil, ErrUserInfo
} }
if !userInfo.Roles.ScriptWrite { has_role, err := userInfo.HasRoleScriptWrite()
if err != nil {
return nil, err
}
if !has_role {
return nil, ErrPermissionDenied return nil, ErrPermissionDenied
} }
script, err := svc.DB.Scripts().Create(ctx, model.Script{ script, err := svc.DB.Scripts().Create(ctx, model.Script{
ID: 0, ID: 0,
Hash: siphash.Hash(0, 0, []byte(req.Source)), Name: req.Name,
Hash: int64(model.HashSource(req.Source)),
Source: req.Source, Source: req.Source,
SubmissionID: req.SubmissionID.Or(0), ResourceType: model.ResourceType(req.ResourceType),
ResourceID: req.ResourceID.Or(0),
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@@ -40,18 +44,69 @@ func (svc *Service) CreateScript(ctx context.Context, req *api.ScriptCreate) (*a
}, nil }, nil
} }
// ListScripts implements listScripts operation.
//
// Get list of scripts.
//
// GET /scripts
func (svc *Service) ListScripts(ctx context.Context, params api.ListScriptsParams) ([]api.Script, error) {
filter := datastore.Optional()
if params.Hash.IsSet(){
hash, err := model.HashParse(params.Hash.Value)
if err != nil {
return nil, err
}
filter.AddNotNil("hash", int64(hash)) // No type safety!
}
if params.Name.IsSet(){
filter.AddNotNil("name", params.Name.Value)
}
if params.Source.IsSet(){
filter.AddNotNil("source", params.Source.Value)
}
if params.SubmissionID.IsSet(){
filter.AddNotNil("submission_id", params.SubmissionID.Value)
}
items, err := svc.DB.Scripts().List(ctx, filter, model.Page{
Number: params.Page,
Size: params.Limit,
})
if err != nil {
return nil, err
}
var resp []api.Script
for _, item := range items {
resp = append(resp, api.Script{
ID: item.ID,
Hash: model.HashFormat(uint64(item.Hash)),
Source: item.Source,
ResourceType: int32(item.ResourceType),
ResourceID: item.ResourceID,
})
}
return resp, nil
}
// DeleteScript implements deleteScript operation. // DeleteScript implements deleteScript operation.
// //
// Delete the specified script by ID. // Delete the specified script by ID.
// //
// DELETE /scripts/{ScriptID} // DELETE /scripts/{ScriptID}
func (svc *Service) DeleteScript(ctx context.Context, params api.DeleteScriptParams) error { func (svc *Service) DeleteScript(ctx context.Context, params api.DeleteScriptParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfo) userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return ErrUserInfo return ErrUserInfo
} }
if !userInfo.Roles.ScriptWrite { has_role, err := userInfo.HasRoleScriptWrite()
if err != nil {
return err
}
if !has_role {
return ErrPermissionDenied return ErrPermissionDenied
} }
@@ -64,7 +119,7 @@ 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").(UserInfo) _, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return nil, ErrUserInfo return nil, ErrUserInfo
} }
@@ -78,9 +133,11 @@ func (svc *Service) GetScript(ctx context.Context, params api.GetScriptParams) (
return &api.Script{ return &api.Script{
ID: script.ID, ID: script.ID,
Hash: fmt.Sprintf("%x", script.Hash), Name: script.Name,
Hash: model.HashFormat(uint64(script.Hash)),
Source: script.Source, Source: script.Source,
SubmissionID: script.SubmissionID, ResourceType: int32(script.ResourceType),
ResourceID: script.ResourceID,
}, nil }, nil
} }
@@ -90,22 +147,32 @@ func (svc *Service) GetScript(ctx context.Context, params api.GetScriptParams) (
// //
// PATCH /scripts/{ScriptID} // PATCH /scripts/{ScriptID}
func (svc *Service) UpdateScript(ctx context.Context, req *api.ScriptUpdate, params api.UpdateScriptParams) error { func (svc *Service) UpdateScript(ctx context.Context, req *api.ScriptUpdate, params api.UpdateScriptParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfo) userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return ErrUserInfo return ErrUserInfo
} }
if !userInfo.Roles.ScriptWrite { has_role, err := userInfo.HasRoleScriptWrite()
if err != nil {
return err
}
if !has_role {
return ErrPermissionDenied return ErrPermissionDenied
} }
pmap := datastore.Optional() pmap := datastore.Optional()
if Name, ok := req.Name.Get(); ok {
pmap.Add("name", Name)
}
if source, ok := req.Source.Get(); ok { if source, ok := req.Source.Get(); ok {
pmap.Add("source", source) pmap.Add("source", source)
pmap.Add("hash", siphash.Hash(0, 0, []byte(source))) pmap.Add("hash", int64(model.HashSource(source))) // No type safety!
} }
if SubmissionID, ok := req.SubmissionID.Get(); ok { if ResourceType, ok := req.ResourceType.Get(); ok {
pmap.Add("submission_id", SubmissionID) pmap.Add("resource_type", ResourceType)
}
if ResourceID, ok := req.ResourceID.Get(); ok {
pmap.Add("resource_id", ResourceID)
} }
return svc.DB.Scripts().Update(ctx, req.ID, pmap) return svc.DB.Scripts().Update(ctx, req.ID, pmap)
} }

View File

@@ -14,30 +14,145 @@ var (
ErrInvalidSession = errors.New("Session invalid") ErrInvalidSession = errors.New("Session invalid")
) )
// Submissions roles bitflag
type Roles int32
var ( var (
// has SubmissionPublish RolesSubmissionRelease Roles = 1<<4
RoleMapAdmin int32 = 128 RolesScriptWrite Roles = 1<<3
// has SubmissionReview RolesMapUpload Roles = 1<<2
RoleMapCouncil int32 = 64 RolesMapReview Roles = 1<<1
RolesMapDownload Roles = 1<<0
RolesEmpty Roles = 0
) )
type Roles struct { // StrafesNET group roles
// human roles type GroupRole int32
SubmissionPublish bool var (
SubmissionReview bool // has ScriptWrite
ScriptWrite bool RoleQuat GroupRole = 255
// automated roles RoleItzaname GroupRole = 254
Maptest bool RoleStagingDeveloper GroupRole = 240
Validator bool RolesAll Roles = RolesScriptWrite|RolesSubmissionRelease|RolesMapUpload|RolesMapReview|RolesMapDownload
} // has SubmissionUpload
RoleMapAdmin GroupRole = 128
RolesMapAdmin Roles = RolesSubmissionRelease|RolesMapUpload|RolesMapReview|RolesMapDownload
// has SubmissionReview
RoleMapCouncil GroupRole = 64
RolesMapCouncil Roles = RolesMapReview|RolesMapUpload|RolesMapDownload
// access to downloading maps
RoleMapAccess GroupRole = 32
RolesMapAccess Roles = RolesMapDownload
)
type UserInfoHandle struct {
// Would love to know a better way to do this
svc *SecurityHandler
ctx *context.Context
sessionId string
}
type UserInfo struct { type UserInfo struct {
Roles Roles UserID uint64
UserID uint64 Username string
AvatarURL string
} }
func (usr UserInfo) IsSubmitter(submitter uint64) bool { func (usr UserInfoHandle) GetUserInfo() (userInfo UserInfo, err error) {
return usr.UserID == submitter session, err := usr.svc.Client.GetSessionUser(*usr.ctx, &auth.IdMessage{
SessionID: usr.sessionId,
})
if err != nil {
return userInfo, err
}
userInfo.UserID = session.UserID
userInfo.Username = session.Username
userInfo.AvatarURL = session.AvatarURL
return userInfo, nil
}
func (usr UserInfoHandle) GetUserID() (uint64, error) {
session, err := usr.svc.Client.GetSessionUser(*usr.ctx, &auth.IdMessage{
SessionID: usr.sessionId,
})
if err != nil {
return 0, err
}
return session.UserID, nil
}
func (usr UserInfoHandle) Validate() (bool, error) {
validate, err := usr.svc.Client.ValidateSession(*usr.ctx, &auth.IdMessage{
SessionID: usr.sessionId,
})
if err != nil {
return false, err
}
return validate.Valid, nil
}
func (usr UserInfoHandle) IsSubmitter(submitter uint64) (bool, error) {
userId, err := usr.GetUserID()
if err != nil {
return false, err
}
return userId == submitter, nil
}
func (usr UserInfoHandle) hasRoles(wantRoles Roles) (bool, error) {
haveroles, err := usr.GetRoles()
if err != nil {
return false, err
}
return haveroles & wantRoles == wantRoles, nil
}
func (usr UserInfoHandle) GetRoles() (Roles, error) {
roles, err := usr.svc.Client.GetGroupRole(*usr.ctx, &auth.IdMessage{
SessionID: usr.sessionId,
})
if err != nil {
return RolesEmpty, err
}
// map roles into bitflag
rolesBitflag := RolesEmpty;
for _, r := range roles.Roles {
switch GroupRole(r.Rank){
case RoleQuat, RoleItzaname, RoleStagingDeveloper:
rolesBitflag|=RolesAll
case RoleMapAdmin:
rolesBitflag|=RolesMapAdmin
case RoleMapCouncil:
rolesBitflag|=RolesMapCouncil
case RoleMapAccess:
rolesBitflag|=RolesMapAccess
}
}
return rolesBitflag, nil
}
// RoleThumbnail
func (usr UserInfoHandle) HasRoleMapfixUpload() (bool, error) {
return usr.hasRoles(RolesMapUpload)
}
func (usr UserInfoHandle) HasRoleMapfixReview() (bool, error) {
return usr.hasRoles(RolesMapReview)
}
func (usr UserInfoHandle) HasRoleMapDownload() (bool, error) {
return usr.hasRoles(RolesMapDownload)
}
func (usr UserInfoHandle) HasRoleSubmissionRelease() (bool, error) {
return usr.hasRoles(RolesSubmissionRelease)
}
func (usr UserInfoHandle) HasRoleSubmissionUpload() (bool, error) {
return usr.hasRoles(RolesMapUpload)
}
func (usr UserInfoHandle) HasRoleSubmissionReview() (bool, error) {
return usr.hasRoles(RolesMapReview)
}
func (usr UserInfoHandle) HasRoleScriptWrite() (bool, error) {
return usr.hasRoles(RolesScriptWrite)
}
/// Not implemented
func (usr UserInfoHandle) HasRoleMaptest() (bool, error) {
println("HasRoleMaptest is not implemented!")
return false, nil
} }
type SecurityHandler struct { type SecurityHandler struct {
@@ -50,45 +165,10 @@ func (svc SecurityHandler) HandleCookieAuth(ctx context.Context, operationName a
return nil, ErrMissingSessionID return nil, ErrMissingSessionID
} }
session, err := svc.Client.GetSessionUser(ctx, &auth.IdMessage{ newCtx := context.WithValue(ctx, "UserInfo", UserInfoHandle{
SessionID: sessionId, svc: &svc,
}) ctx: &ctx,
if err != nil { sessionId: sessionId,
return nil, err
}
role, err := svc.Client.GetGroupRole(ctx, &auth.IdMessage{
SessionID: sessionId,
})
if err != nil {
return nil, err
}
validate, err := svc.Client.ValidateSession(ctx, &auth.IdMessage{
SessionID: sessionId,
})
if err != nil {
return nil, err
}
if !validate.Valid {
return nil, ErrInvalidSession
}
roles := Roles{}
// fix this when roblox udpates group roles
for _, r := range role.Roles {
if RoleMapAdmin <= r.Rank {
roles.SubmissionPublish = true
}
if RoleMapCouncil <= r.Rank {
roles.SubmissionReview = true
}
}
newCtx := context.WithValue(ctx, "UserInfo", UserInfo{
Roles: roles,
UserID: session.UserID,
}) })
return newCtx, nil return newCtx, nil

View File

@@ -3,6 +3,9 @@ package service
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"git.itzana.me/strafesnet/go-grpc/maps"
"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"
@@ -13,11 +16,20 @@ var (
ErrPermissionDenied = errors.New("Permission denied") ErrPermissionDenied = errors.New("Permission denied")
// ErrUserInfo user info is missing for some reason // ErrUserInfo user info is missing for some reason
ErrUserInfo = errors.New("Missing user info") ErrUserInfo = errors.New("Missing user info")
ErrDelayReset = errors.New("Please give the validator at least 10 seconds to operate before attempting to reset the status")
ErrPermissionDeniedNotSubmitter = fmt.Errorf("%w: You must be the submitter to perform this action", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleSubmissionRelease = fmt.Errorf("%w: Need Role SubmissionRelease", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleMapUpload = fmt.Errorf("%w: Need Role MapUpload", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleMapReview = fmt.Errorf("%w: Need Role MapReview", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleMapDownload = fmt.Errorf("%w: Need Role MapDownload", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleScriptWrite = fmt.Errorf("%w: Need Role ScriptWrite", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleMaptest = fmt.Errorf("%w: Need Role Maptest", ErrPermissionDenied)
) )
type Service struct { type Service struct {
DB datastore.Datastore DB datastore.Datastore
Nats nats.JetStreamContext Nats nats.JetStreamContext
Client maps.MapsServiceClient
} }
// NewError creates *ErrorStatusCode from error returned by handler. // NewError creates *ErrorStatusCode from error returned by handler.
@@ -25,6 +37,9 @@ type Service struct {
// Used for common default response. // Used for common default response.
func (svc *Service) NewError(ctx context.Context, err error) *api.ErrorStatusCode { func (svc *Service) NewError(ctx context.Context, err error) *api.ErrorStatusCode {
status := 500 status := 500
if errors.Is(err, datastore.ErrNotExist) {
status = 404
}
if errors.Is(err, ErrPermissionDenied) { if errors.Is(err, ErrPermissionDenied) {
status = 403 status = 403
} }

68
pkg/service/session.go Normal file
View File

@@ -0,0 +1,68 @@
package service
import (
"context"
"git.itzana.me/strafesnet/maps-service/pkg/api"
)
// SessionRoles implements getSessionRoles operation.
//
// Get bitflags of permissions the currently logged in user has.
//
// GET /session/roles
func (svc *Service) SessionRoles(ctx context.Context) (*api.Roles, error) {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return nil, ErrUserInfo
}
roles, err := userInfo.GetRoles();
if err != nil {
return nil, err
}
return &api.Roles{Roles: int32(roles)}, nil
}
// SessionUser implements sessionUser operation.
//
// Get information about the currently logged in user.
//
// GET /session/roles
func (svc *Service) SessionUser(ctx context.Context) (*api.User, error) {
userInfoHandle, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return nil, ErrUserInfo
}
userInfo, err := userInfoHandle.GetUserInfo();
if err != nil {
return nil, err
}
return &api.User{
UserID:int64(userInfo.UserID),
Username:userInfo.Username,
AvatarURL:userInfo.AvatarURL,
}, nil
}
// SessionUser implements sessionUser operation.
//
// Get information about the currently logged in user.
//
// GET /session/roles
func (svc *Service) SessionValidate(ctx context.Context) (bool, error) {
userInfoHandle, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return false, ErrUserInfo
}
valid, err := userInfoHandle.Validate();
if err != nil {
return false, err
}
return valid, nil
}

View File

@@ -3,30 +3,109 @@ package service
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt"
"time"
"git.itzana.me/strafesnet/go-grpc/maps"
"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"
"git.itzana.me/strafesnet/maps-service/pkg/model" "git.itzana.me/strafesnet/maps-service/pkg/model"
) )
var(
CreationPhaseSubmissionsLimit = 20
CreationPhaseSubmissionStatuses = []model.SubmissionStatus{
model.SubmissionStatusChangesRequested,
model.SubmissionStatusSubmitted,
model.SubmissionStatusUnderConstruction,
}
// prevent two mapfixes with same asset id
ActiveSubmissionStatuses = []model.SubmissionStatus{
model.SubmissionStatusUploading,
model.SubmissionStatusValidated,
model.SubmissionStatusValidating,
model.SubmissionStatusAccepted,
model.SubmissionStatusChangesRequested,
model.SubmissionStatusSubmitted,
model.SubmissionStatusUnderConstruction,
}
// limit mapfixes in the pipeline to one per target map
ActiveAcceptedSubmissionStatuses = []model.SubmissionStatus{
model.SubmissionStatusUploading,
model.SubmissionStatusValidated,
model.SubmissionStatusValidating,
model.SubmissionStatusAccepted,
}
)
var (
ErrCreationPhaseSubmissionsLimit = errors.New("Active submissions limited to 20")
ErrActiveSubmissionSameAssetID = errors.New("There is an active submission with the same AssetID")
ErrUploadedAssetIDAlreadyExists = errors.New("The submission UploadedAssetID is already set")
ErrReleaseInvalidStatus = errors.New("Only submissions with Uploaded status 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)
)
// POST /submissions // POST /submissions
func (svc *Service) CreateSubmission(ctx context.Context, request *api.SubmissionCreate) (*api.ID, error) { func (svc *Service) CreateSubmission(ctx context.Context, request *api.SubmissionCreate) (*api.ID, error) {
userInfo, ok := ctx.Value("UserInfo").(UserInfo) userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return nil, ErrUserInfo return nil, ErrUserInfo
} }
userId, err := userInfo.GetUserID()
if err != nil {
return nil, err
}
// Check if user's submissions in the creation phase exceeds the limit
{
filter := datastore.Optional()
filter.Add("submitter", int64(userId))
filter.Add("status_id", CreationPhaseSubmissionStatuses)
creation_submissions, err := svc.DB.Submissions().List(ctx, filter, model.Page{
Number: 1,
Size: int32(CreationPhaseSubmissionsLimit),
},datastore.ListSortDisabled)
if err != nil {
return nil, err
}
if CreationPhaseSubmissionsLimit <= len(creation_submissions) {
return nil, ErrCreationPhaseSubmissionsLimit
}
}
// Check if an active submission with the same asset id exists
{
filter := datastore.Optional()
filter.Add("asset_id", request.AssetID)
filter.Add("asset_version", request.AssetVersion)
filter.Add("status_id", ActiveSubmissionStatuses)
active_submissions, err := svc.DB.Submissions().List(ctx, filter, model.Page{
Number: 1,
Size: 1,
},datastore.ListSortDisabled)
if err != nil {
return nil, err
}
if len(active_submissions) != 0{
return nil, ErrActiveSubmissionSameAssetID
}
}
submission, err := svc.DB.Submissions().Create(ctx, model.Submission{ submission, err := svc.DB.Submissions().Create(ctx, model.Submission{
ID: 0, ID: 0,
DisplayName: request.DisplayName, DisplayName: request.DisplayName,
Creator: request.Creator, Creator: request.Creator,
GameID: request.GameID, GameID: request.GameID,
Submitter: userInfo.UserID, Submitter: int64(userId),
AssetID: uint64(request.AssetID), AssetID: request.AssetID,
AssetVersion: uint64(request.AssetVersion), AssetVersion: request.AssetVersion,
Completed: false, Completed: false,
TargetAssetID: uint64(request.TargetAssetID.Value), StatusID: model.SubmissionStatusUnderConstruction,
StatusID: model.StatusUnderConstruction,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@@ -57,8 +136,9 @@ func (svc *Service) GetSubmission(ctx context.Context, params api.GetSubmissionP
AssetID: int64(submission.AssetID), AssetID: int64(submission.AssetID),
AssetVersion: int64(submission.AssetVersion), AssetVersion: int64(submission.AssetVersion),
Completed: submission.Completed, Completed: submission.Completed,
TargetAssetID: api.NewOptInt64(int64(submission.TargetAssetID)), UploadedAssetID: api.NewOptInt64(int64(submission.UploadedAssetID)),
StatusID: int32(submission.StatusID), StatusID: int32(submission.StatusID),
StatusMessage: submission.StatusMessage,
}, nil }, nil
} }
@@ -67,38 +147,44 @@ 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, request *api.ListSubmissionsReq) ([]api.Submission, error) { func (svc *Service) ListSubmissions(ctx context.Context, params api.ListSubmissionsParams) ([]api.Submission, error) {
filter := datastore.Optional() filter := datastore.Optional()
//fmt.Println(request)
if request.Filter.IsSet() { if params.DisplayName.IsSet(){
filter.AddNotNil("display_name", request.Filter.Value.DisplayName) filter.Add("display_name", params.DisplayName.Value)
filter.AddNotNil("creator", request.Filter.Value.Creator) }
filter.AddNotNil("game_id", request.Filter.Value.GameID) if params.Creator.IsSet(){
filter.Add("creator", params.Creator.Value)
}
if params.GameID.IsSet(){
filter.Add("game_id", params.GameID.Value)
} }
sort := datastore.ListSort(params.Sort.Or(int32(datastore.ListSortDisabled)))
items, err := svc.DB.Submissions().List(ctx, filter, model.Page{ items, err := svc.DB.Submissions().List(ctx, filter, model.Page{
Number: request.Page.GetPage(), Number: params.Page,
Size: request.Page.GetLimit(), Size: params.Limit,
}) },sort)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var resp []api.Submission var resp []api.Submission
for i := 0; i < len(items); i++ { for _, item := range items {
resp = append(resp, api.Submission{ resp = append(resp, api.Submission{
ID: items[i].ID, ID: item.ID,
DisplayName: items[i].DisplayName, DisplayName: item.DisplayName,
Creator: items[i].Creator, Creator: item.Creator,
GameID: items[i].GameID, GameID: item.GameID,
CreatedAt: items[i].CreatedAt.Unix(), CreatedAt: item.CreatedAt.Unix(),
UpdatedAt: items[i].UpdatedAt.Unix(), UpdatedAt: item.UpdatedAt.Unix(),
Submitter: int64(items[i].Submitter), Submitter: int64(item.Submitter),
AssetID: int64(items[i].AssetID), AssetID: int64(item.AssetID),
AssetVersion: int64(items[i].AssetVersion), AssetVersion: int64(item.AssetVersion),
Completed: items[i].Completed, Completed: item.Completed,
TargetAssetID: api.NewOptInt64(int64(items[i].TargetAssetID)), UploadedAssetID: api.NewOptInt64(int64(item.UploadedAssetID)),
StatusID: int32(items[i].StatusID), StatusID: int32(item.StatusID),
}) })
} }
@@ -111,29 +197,32 @@ func (svc *Service) ListSubmissions(ctx context.Context, request *api.ListSubmis
// //
// POST /submissions/{SubmissionID}/completed // POST /submissions/{SubmissionID}/completed
func (svc *Service) SetSubmissionCompleted(ctx context.Context, params api.SetSubmissionCompletedParams) error { func (svc *Service) SetSubmissionCompleted(ctx context.Context, params api.SetSubmissionCompletedParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfo) userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return ErrUserInfo return ErrUserInfo
} }
has_role, err := userInfo.HasRoleMaptest()
if err != nil {
return err
}
// check if caller has MaptestGame role (request must originate from a maptest roblox game) // check if caller has MaptestGame role (request must originate from a maptest roblox game)
if !userInfo.Roles.Maptest { if !has_role {
return ErrPermissionDenied return ErrPermissionDeniedNeedRoleMaptest
} }
pmap := datastore.Optional() pmap := datastore.Optional()
pmap.Add("completed", true) pmap.Add("completed", true)
err := svc.DB.Submissions().Update(ctx, params.SubmissionID, pmap) return svc.DB.Submissions().Update(ctx, params.SubmissionID, pmap)
return err
} }
// PatchSubmissionModel implements patchSubmissionModel operation. // UpdateSubmissionModel implements patchSubmissionModel operation.
// //
// Update model following role restrictions. // Update model following role restrictions.
// //
// POST /submissions/{SubmissionID}/model // POST /submissions/{SubmissionID}/model
func (svc *Service) UpdateSubmissionModel(ctx context.Context, params api.UpdateSubmissionModelParams) error { func (svc *Service) UpdateSubmissionModel(ctx context.Context, params api.UpdateSubmissionModelParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfo) userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return ErrUserInfo return ErrUserInfo
} }
@@ -144,9 +233,13 @@ func (svc *Service) UpdateSubmissionModel(ctx context.Context, params api.Update
return err return err
} }
has_role, err := userInfo.IsSubmitter(uint64(submission.Submitter))
if err != nil {
return err
}
// check if caller is the submitter // check if caller is the submitter
if !userInfo.IsSubmitter(submission.Submitter) { if !has_role {
return ErrPermissionDenied return ErrPermissionDeniedNotSubmitter
} }
// check if Status is ChangesRequested|Submitted|UnderConstruction // check if Status is ChangesRequested|Submitted|UnderConstruction
@@ -155,21 +248,7 @@ func (svc *Service) UpdateSubmissionModel(ctx context.Context, params api.Update
pmap.AddNotNil("asset_version", params.VersionID) pmap.AddNotNil("asset_version", params.VersionID)
//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.Status{model.StatusChangesRequested, model.StatusSubmitted, model.StatusUnderConstruction}, pmap) return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusChangesRequested, model.SubmissionStatusSubmitted, model.SubmissionStatusUnderConstruction}, pmap)
}
// ActionSubmissionPublish invokes actionSubmissionPublish operation.
//
// Role Validator changes status from Publishing -> Published.
//
// POST /submissions/{SubmissionID}/status/publish
func (svc *Service) ActionSubmissionPublish(ctx context.Context, params api.ActionSubmissionPublishParams) error {
println("[ActionSubmissionPublish] Implicit Validator permission granted!")
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.StatusPublished)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusPublishing}, smap)
} }
// ActionSubmissionReject invokes actionSubmissionReject operation. // ActionSubmissionReject invokes actionSubmissionReject operation.
@@ -178,20 +257,24 @@ func (svc *Service) ActionSubmissionPublish(ctx context.Context, params api.Acti
// //
// POST /submissions/{SubmissionID}/status/reject // POST /submissions/{SubmissionID}/status/reject
func (svc *Service) ActionSubmissionReject(ctx context.Context, params api.ActionSubmissionRejectParams) error { func (svc *Service) ActionSubmissionReject(ctx context.Context, params api.ActionSubmissionRejectParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfo) userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return ErrUserInfo return ErrUserInfo
} }
has_role, err := userInfo.HasRoleSubmissionReview()
if err != nil {
return err
}
// check if caller has required role // check if caller has required role
if !userInfo.Roles.SubmissionReview { if !has_role {
return ErrPermissionDenied return ErrPermissionDeniedNeedRoleMapReview
} }
// transaction // transaction
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.StatusRejected) smap.Add("status_id", model.SubmissionStatusRejected)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusSubmitted}, smap) return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitted}, smap)
} }
// ActionSubmissionRequestChanges invokes actionSubmissionRequestChanges operation. // ActionSubmissionRequestChanges invokes actionSubmissionRequestChanges operation.
@@ -200,20 +283,24 @@ func (svc *Service) ActionSubmissionReject(ctx context.Context, params api.Actio
// //
// POST /submissions/{SubmissionID}/status/request-changes // POST /submissions/{SubmissionID}/status/request-changes
func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params api.ActionSubmissionRequestChangesParams) error { func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params api.ActionSubmissionRequestChangesParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfo) userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return ErrUserInfo return ErrUserInfo
} }
has_role, err := userInfo.HasRoleSubmissionReview()
if err != nil {
return err
}
// check if caller has required role // check if caller has required role
if !userInfo.Roles.SubmissionReview { if !has_role {
return ErrPermissionDenied return ErrPermissionDeniedNeedRoleMapReview
} }
// transaction // transaction
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.StatusChangesRequested) smap.Add("status_id", model.SubmissionStatusChangesRequested)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusValidated, model.StatusAccepted, model.StatusSubmitted}, smap) return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidated, model.SubmissionStatusAccepted, model.SubmissionStatusSubmitted}, smap)
} }
// ActionSubmissionRevoke invokes actionSubmissionRevoke operation. // ActionSubmissionRevoke invokes actionSubmissionRevoke operation.
@@ -222,7 +309,7 @@ func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params a
// //
// POST /submissions/{SubmissionID}/status/revoke // POST /submissions/{SubmissionID}/status/revoke
func (svc *Service) ActionSubmissionRevoke(ctx context.Context, params api.ActionSubmissionRevokeParams) error { func (svc *Service) ActionSubmissionRevoke(ctx context.Context, params api.ActionSubmissionRevokeParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfo) userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return ErrUserInfo return ErrUserInfo
} }
@@ -233,15 +320,19 @@ func (svc *Service) ActionSubmissionRevoke(ctx context.Context, params api.Actio
return err return err
} }
has_role, err := userInfo.IsSubmitter(uint64(submission.Submitter))
if err != nil {
return err
}
// check if caller is the submitter // check if caller is the submitter
if !userInfo.IsSubmitter(submission.Submitter) { if !has_role {
return ErrPermissionDenied return ErrPermissionDeniedNotSubmitter
} }
// transaction // transaction
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.StatusUnderConstruction) smap.Add("status_id", model.SubmissionStatusUnderConstruction)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusSubmitted, model.StatusChangesRequested}, smap) return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitted, model.SubmissionStatusChangesRequested}, smap)
} }
// ActionSubmissionSubmit invokes actionSubmissionSubmit operation. // ActionSubmissionSubmit invokes actionSubmissionSubmit operation.
@@ -250,7 +341,7 @@ func (svc *Service) ActionSubmissionRevoke(ctx context.Context, params api.Actio
// //
// POST /submissions/{SubmissionID}/status/submit // POST /submissions/{SubmissionID}/status/submit
func (svc *Service) ActionSubmissionSubmit(ctx context.Context, params api.ActionSubmissionSubmitParams) error { func (svc *Service) ActionSubmissionSubmit(ctx context.Context, params api.ActionSubmissionSubmitParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfo) userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return ErrUserInfo return ErrUserInfo
} }
@@ -261,99 +352,149 @@ func (svc *Service) ActionSubmissionSubmit(ctx context.Context, params api.Actio
return err return err
} }
has_role, err := userInfo.IsSubmitter(uint64(submission.Submitter))
if err != nil {
return err
}
// check if caller is the submitter // check if caller is the submitter
if !userInfo.IsSubmitter(submission.Submitter) { if !has_role {
return ErrPermissionDenied return ErrPermissionDeniedNotSubmitter
} }
// transaction // transaction
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.StatusSubmitted) smap.Add("status_id", model.SubmissionStatusSubmitted)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusUnderConstruction, model.StatusChangesRequested}, smap) return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUnderConstruction, model.SubmissionStatusChangesRequested}, smap)
} }
// ActionSubmissionTriggerPublish invokes actionSubmissionTriggerPublish operation. // ActionSubmissionTriggerUpload invokes actionSubmissionTriggerUpload operation.
// //
// Role Admin changes status from Validated -> Publishing. // Role Admin changes status from Validated -> Uploading.
// //
// POST /submissions/{SubmissionID}/status/trigger-publish // POST /submissions/{SubmissionID}/status/trigger-upload
func (svc *Service) ActionSubmissionTriggerPublish(ctx context.Context, params api.ActionSubmissionTriggerPublishParams) error { func (svc *Service) ActionSubmissionTriggerUpload(ctx context.Context, params api.ActionSubmissionTriggerUploadParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfo) userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return ErrUserInfo return ErrUserInfo
} }
has_role, err := userInfo.HasRoleSubmissionUpload()
if err != nil {
return err
}
// check if caller has required role // check if caller has required role
if !userInfo.Roles.SubmissionPublish { if !has_role {
return ErrPermissionDenied return ErrPermissionDeniedNeedRoleMapUpload
} }
// transaction // transaction
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.StatusPublishing) smap.Add("status_id", model.SubmissionStatusUploading)
submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.Status{model.StatusValidated}, 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
} }
// sentinel value because we are not using rust // sentinel value because we are not using rust
if submission.TargetAssetID == 0 { if submission.UploadedAssetID == 0 {
// this is a new map // this is a new map
publish_new_request := model.PublishNewRequest{ upload_new_request := model.UploadNewRequest{
SubmissionID: submission.ID, SubmissionID: submission.ID,
ModelID: submission.AssetID, ModelID: submission.ValidatedAssetID,
ModelVersion: submission.AssetVersion, ModelVersion: submission.ValidatedAssetVersion,
Creator: submission.Creator, // upload as displayname, whatever
DisplayName: submission.DisplayName, ModelName: submission.DisplayName,
GameID: uint32(submission.GameID),
} }
j, err := json.Marshal(publish_new_request) j, err := json.Marshal(upload_new_request)
if err != nil { if err != nil {
return err return err
} }
svc.Nats.Publish("maptest.submissions.publishnew", []byte(j)) svc.Nats.Publish("maptest.submissions.upload", []byte(j))
} else { } else {
// this is a map fix // refuse to operate
publish_fix_request := model.PublishFixRequest{ return ErrUploadedAssetIDAlreadyExists
SubmissionID: submission.ID,
ModelID: submission.AssetID,
ModelVersion: submission.AssetVersion,
TargetAssetID: submission.TargetAssetID,
}
j, err := json.Marshal(publish_fix_request)
if err != nil {
return err
}
svc.Nats.Publish("maptest.submissions.publishfix", []byte(j))
} }
return nil return nil
} }
// ActionSubmissionTriggerValidate invokes actionSubmissionTriggerValidate operation. // ActionSubmissionValidate invokes actionSubmissionValidate operation.
// //
// Role Reviewer triggers validation and changes status from Submitted|Accepted -> Validating. // Role SubmissionRelease changes status from Uploading -> Validated.
// //
// POST /submissions/{SubmissionID}/status/trigger-validate // POST /submissions/{SubmissionID}/status/reset-uploading
func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params api.ActionSubmissionTriggerValidateParams) error { func (svc *Service) ActionSubmissionValidated(ctx context.Context, params api.ActionSubmissionValidatedParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfo) userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok { if !ok {
return ErrUserInfo return ErrUserInfo
} }
has_role, err := userInfo.HasRoleSubmissionUpload()
if err != nil {
return err
}
// check if caller has required role // check if caller has required role
if !userInfo.Roles.SubmissionReview { if !has_role {
return ErrPermissionDenied return ErrPermissionDeniedNeedRoleMapUpload
}
// 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
} }
// transaction // transaction
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.StatusValidating) smap.Add("status_id", model.SubmissionStatusValidated)
submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.Status{model.StatusSubmitted, model.StatusAccepted}, smap) return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUploading}, smap)
}
// ActionSubmissionTriggerValidate invokes actionSubmissionTriggerValidate operation.
//
// Role Reviewer triggers validation and changes status from Submitted -> Validating.
//
// POST /submissions/{SubmissionID}/status/trigger-validate
func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params api.ActionSubmissionTriggerValidateParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return ErrUserInfo
}
has_role, err := userInfo.HasRoleSubmissionReview()
if err != nil {
return err
}
// check if caller has required role
if !has_role {
return ErrPermissionDeniedNeedRoleMapReview
}
// 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
}
has_role, err = userInfo.IsSubmitter(uint64(submission.Submitter))
if err != nil {
return err
}
// check if caller is NOT the submitter
if has_role {
return ErrAcceptOwnSubmission
}
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.SubmissionStatusValidating)
submission, err = svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitted}, smap)
if err != nil { if err != nil {
return err return err
} }
@@ -362,7 +503,12 @@ func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params
SubmissionID: submission.ID, SubmissionID: submission.ID,
ModelID: submission.AssetID, ModelID: submission.AssetID,
ModelVersion: submission.AssetVersion, ModelVersion: submission.AssetVersion,
ValidatedModelID: 0, //TODO: reuse velidation models ValidatedModelID: nil,
}
// sentinel values because we're not using rust
if submission.ValidatedAssetID != 0 {
validate_request.ValidatedModelID = &submission.ValidatedAssetID
} }
j, err := json.Marshal(validate_request) j, err := json.Marshal(validate_request)
@@ -375,16 +521,156 @@ func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params
return nil return nil
} }
// ActionSubmissionValidate invokes actionSubmissionValidate operation. // ActionSubmissionRetryValidate invokes actionSubmissionRetryValidate operation.
// //
// Role Validator changes status from Validating -> Validated. // Role Reviewer re-runs validation and changes status from Accepted -> Validating.
// //
// POST /submissions/{SubmissionID}/status/validate // POST /submissions/{SubmissionID}/status/retry-validate
func (svc *Service) ActionSubmissionValidate(ctx context.Context, params api.ActionSubmissionValidateParams) error { func (svc *Service) ActionSubmissionRetryValidate(ctx context.Context, params api.ActionSubmissionRetryValidateParams) error {
println("[ActionSubmissionValidate] Implicit Validator permission granted!") userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return ErrUserInfo
}
has_role, err := userInfo.HasRoleSubmissionReview()
if err != nil {
return err
}
// check if caller has required role
if !has_role {
return ErrPermissionDeniedNeedRoleMapReview
}
// transaction // transaction
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.StatusValidated) smap.Add("status_id", model.SubmissionStatusValidating)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusValidating}, smap) submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusAccepted}, smap)
if err != nil {
return err
}
validate_request := model.ValidateRequest{
SubmissionID: submission.ID,
ModelID: submission.AssetID,
ModelVersion: submission.AssetVersion,
ValidatedModelID: nil,
}
// sentinel values because we're not using rust
if submission.ValidatedAssetID != 0 {
validate_request.ValidatedModelID = &submission.ValidatedAssetID
}
j, err := json.Marshal(validate_request)
if err != nil {
return err
}
svc.Nats.Publish("maptest.submissions.validate", []byte(j))
return nil
}
// ActionSubmissionAccepted implements actionSubmissionAccepted operation.
//
// Role SubmissionReview changes status from Validating -> Accepted.
//
// POST /submissions/{SubmissionID}/status/reset-validating
func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params api.ActionSubmissionAcceptedParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return ErrUserInfo
}
has_role, err := userInfo.HasRoleSubmissionReview()
if err != nil {
return err
}
// check if caller has required role
if !has_role {
return ErrPermissionDeniedNeedRoleMapReview
}
// 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
}
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.SubmissionStatusAccepted)
smap.Add("status_message", "Manually forced reset")
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap)
}
// ReleaseSubmissions invokes releaseSubmissions operation.
//
// Release a set of uploaded maps.
//
// POST /release-submissions
func (svc *Service) ReleaseSubmissions(ctx context.Context, request []api.ReleaseInfo) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return ErrUserInfo
}
has_role, err := userInfo.HasRoleSubmissionRelease()
if err != nil {
return err
}
// check if caller has required role
if !has_role {
return ErrPermissionDeniedNeedRoleSubmissionRelease
}
idList := make([]int64, len(request))
for i, releaseInfo := range request {
idList[i] = releaseInfo.SubmissionID
}
// fetch submissions
submissions, err := svc.DB.Submissions().GetList(ctx, idList)
if err != nil {
return err
}
// check each submission to make sure it is ready to release
for _,submission := range submissions{
if submission.StatusID != model.SubmissionStatusUploaded{
return ErrReleaseInvalidStatus
}
if submission.UploadedAssetID == 0{
return ErrReleaseNoUploadedAssetID
}
}
for i,submission := range submissions{
date := request[i].Date.Unix()
// create each map with go-grpc
_, err := svc.Client.Create(ctx, &maps.MapRequest{
ID: submission.UploadedAssetID,
DisplayName: &submission.DisplayName,
Creator: &submission.Creator,
GameID: &submission.GameID,
Date: &date,
})
if err != nil {
return err
}
// update each status to Released
smap := datastore.Optional()
smap.Add("status_id", model.SubmissionStatusReleased)
err = svc.DB.Submissions().IfStatusThenUpdate(ctx, submission.ID, []model.SubmissionStatus{model.SubmissionStatusUploaded}, smap)
if err != nil {
return err
}
}
return nil
} }

View File

@@ -0,0 +1,80 @@
package service_internal
import (
"context"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
"git.itzana.me/strafesnet/maps-service/pkg/internal"
"git.itzana.me/strafesnet/maps-service/pkg/model"
)
// CreateScriptPolicy implements createScriptPolicy operation.
//
// Create a new script policy.
//
// POST /script-policy
func (svc *Service) CreateScriptPolicy(ctx context.Context, req *api.ScriptPolicyCreate) (*api.ID, error) {
from_script, err := svc.DB.Scripts().Get(ctx, req.FromScriptID)
if err != nil {
return nil, err
}
// the existence of ToScriptID does not need to be validated because it's checked by a foreign key constraint.
script, err := svc.DB.ScriptPolicy().Create(ctx, model.ScriptPolicy{
ID: 0,
FromScriptHash: from_script.Hash,
ToScriptID: req.ToScriptID,
Policy: model.Policy(req.Policy),
})
if err != nil {
return nil, err
}
return &api.ID{
ID: script.ID,
}, nil
}
// ListScriptPolicy implements listScriptPolicy operation.
//
// Get list of script policies.
//
// GET /script-policy
func (svc *Service) ListScriptPolicy(ctx context.Context, params api.ListScriptPolicyParams) ([]api.ScriptPolicy, error) {
filter := datastore.Optional()
if params.FromScriptHash.IsSet(){
hash, err := model.HashParse(params.FromScriptHash.Value)
if err != nil {
return nil, err
}
filter.AddNotNil("from_script_hash", int64(hash)) // No type safety!
}
if params.ToScriptID.IsSet(){
filter.AddNotNil("to_script_id", params.ToScriptID.Value)
}
if params.Policy.IsSet(){
filter.AddNotNil("policy", params.Policy.Value)
}
items, err := svc.DB.ScriptPolicy().List(ctx, filter, model.Page{
Number: params.Page,
Size: params.Limit,
})
if err != nil {
return nil, err
}
var resp []api.ScriptPolicy
for _, item := range items {
resp = append(resp, api.ScriptPolicy{
ID: item.ID,
FromScriptHash: model.HashFormat(uint64(item.FromScriptHash)),
ToScriptID: item.ToScriptID,
Policy: int32(item.Policy),
})
}
return resp, nil
}

View File

@@ -0,0 +1,100 @@
package service_internal
import (
"context"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
"git.itzana.me/strafesnet/maps-service/pkg/internal"
"git.itzana.me/strafesnet/maps-service/pkg/model"
)
// CreateScript implements createScript operation.
//
// Create a new script.
//
// POST /scripts
func (svc *Service) CreateScript(ctx context.Context, req *api.ScriptCreate) (*api.ID, error) {
script, err := svc.DB.Scripts().Create(ctx, model.Script{
ID: 0,
Name: req.Name,
Hash: int64(model.HashSource(req.Source)),
Source: req.Source,
ResourceType: model.ResourceType(req.ResourceType),
ResourceID: req.ResourceID.Or(0),
})
if err != nil {
return nil, err
}
return &api.ID{
ID: script.ID,
}, nil
}
// ListScripts implements listScripts operation.
//
// Get list of scripts.
//
// GET /scripts
func (svc *Service) ListScripts(ctx context.Context, params api.ListScriptsParams) ([]api.Script, error) {
filter := datastore.Optional()
if params.Hash.IsSet(){
hash, err := model.HashParse(params.Hash.Value)
if err != nil {
return nil, err
}
filter.AddNotNil("hash", int64(hash)) // No type safety!
}
if params.Name.IsSet(){
filter.AddNotNil("name", params.Name.Value)
}
if params.Source.IsSet(){
filter.AddNotNil("source", params.Source.Value)
}
if params.SubmissionID.IsSet(){
filter.AddNotNil("submission_id", params.SubmissionID.Value)
}
items, err := svc.DB.Scripts().List(ctx, filter, model.Page{
Number: params.Page,
Size: params.Limit,
})
if err != nil {
return nil, err
}
var resp []api.Script
for _, item := range items {
resp = append(resp, api.Script{
ID: item.ID,
Hash: model.HashFormat(uint64(item.Hash)),
Source: item.Source,
ResourceType: int32(item.ResourceType),
ResourceID: item.ResourceID,
})
}
return resp, nil
}
// GetScript implements getScript operation.
//
// Get the specified script by ID.
//
// GET /scripts/{ScriptID}
func (svc *Service) GetScript(ctx context.Context, params api.GetScriptParams) (*api.Script, error) {
script, err := svc.DB.Scripts().Get(ctx, params.ScriptID)
if err != nil {
return nil, err
}
return &api.Script{
ID: script.ID,
Name: script.Name,
Hash: model.HashFormat(uint64(script.Hash)),
Source: script.Source,
ResourceType: int32(script.ResourceType),
ResourceID: script.ResourceID,
}, nil
}

View File

@@ -0,0 +1,30 @@
package service_internal
import (
"context"
"errors"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
internal "git.itzana.me/strafesnet/maps-service/pkg/internal"
"github.com/nats-io/nats.go"
)
type Service struct {
DB datastore.Datastore
Nats nats.JetStreamContext
}
// yay duplicate code
func (svc *Service) NewError(ctx context.Context, err error) *internal.ErrorStatusCode {
status := 500
if errors.Is(err, datastore.ErrNotExist) {
status = 404
}
return &internal.ErrorStatusCode{
StatusCode: status,
Response: internal.Error{
Code: int64(status),
Message: err.Error(),
},
}
}

View File

@@ -0,0 +1,62 @@
package service_internal
import (
"context"
internal "git.itzana.me/strafesnet/maps-service/pkg/internal"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
"git.itzana.me/strafesnet/maps-service/pkg/model"
)
// UpdateSubmissionValidatedModel implements patchSubmissionModel operation.
//
// Update model following role restrictions.
//
// POST /submissions/{SubmissionID}/validated-model
func (svc *Service) UpdateSubmissionValidatedModel(ctx context.Context, params internal.UpdateSubmissionValidatedModelParams) error {
// check if Status is ChangesRequested|Submitted|UnderConstruction
pmap := datastore.Optional()
pmap.AddNotNil("validated_asset_id", params.ValidatedModelID)
pmap.AddNotNil("validated_asset_version", params.ValidatedModelVersion)
// DO NOT reset completed when validated model is updated
// pmap.Add("completed", false)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, pmap)
}
// ActionSubmissionValidate invokes actionSubmissionValidate operation.
//
// Role Validator changes status from Validating -> Validated.
//
// POST /submissions/{SubmissionID}/status/validator-validated
func (svc *Service) ActionSubmissionValidated(ctx context.Context, params internal.ActionSubmissionValidatedParams) error {
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.SubmissionStatusValidated)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap)
}
// ActionSubmissionAccepted implements actionSubmissionAccepted operation.
//
// (Internal endpoint) Role Validator changes status from Validating -> Accepted.
//
// POST /submissions/{SubmissionID}/status/validator-failed
func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params internal.ActionSubmissionAcceptedParams) error {
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.SubmissionStatusAccepted)
smap.Add("status_message", params.StatusMessage)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap)
}
// ActionSubmissionUploaded implements actionSubmissionUploaded operation.
//
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.
//
// POST /submissions/{SubmissionID}/status/validator-uploaded
func (svc *Service) ActionSubmissionUploaded(ctx context.Context, params internal.ActionSubmissionUploadedParams) error {
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.SubmissionStatusUploaded)
smap.Add("uploaded_asset_id", params.UploadedAssetID)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUploading}, smap)
}

View File

@@ -1,2 +0,0 @@
[registries.strafesnet]
index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"

1979
releaser/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +0,0 @@
[package]
name = "releaser"
version = "0.1.0"
edition = "2021"
[dependencies]
submissions-api = { version = "0.1.0", registry = "strafesnet" }
rbx_asset = { version = "0.2.5", registry = "strafesnet" }
rust-grpc = { version = "1.0.3", registry = "strafesnet" }
tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread", "signal"] }
tonic = "0.12.3"

View File

@@ -1,24 +0,0 @@
# Using the `rust-musl-builder` as base image, instead of
# the official Rust toolchain
FROM docker.io/clux/muslrust:stable AS chef
USER root
RUN cargo install cargo-chef
WORKDIR /app
FROM chef AS planner
COPY . .
RUN cargo chef prepare --recipe-path recipe.json
FROM chef AS builder
COPY --from=planner /app/recipe.json recipe.json
# Notice that we are specifying the --target flag!
RUN cargo chef cook --release --target x86_64-unknown-linux-musl --recipe-path recipe.json
COPY . .
RUN cargo build --release --target x86_64-unknown-linux-musl --bin releaser
FROM docker.io/alpine:latest AS runtime
RUN addgroup -S myuser && adduser -S myuser -G myuser
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/releaser /usr/local/bin/
USER myuser
ENTRYPOINT ["/usr/local/bin/releaser"]

View File

@@ -1,32 +0,0 @@
#[allow(dead_code)]
#[derive(Debug)]
pub enum StartupError{
API(submissions_api::ReqwestError),
GRPCConnect(tonic::transport::Error),
}
impl std::fmt::Display for StartupError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for StartupError{}
// annoying mile-long type
pub type MapsServiceClient=rust_grpc::maps::maps_service_client::MapsServiceClient<tonic::transport::channel::Channel>;
#[tokio::main]
async fn main()->Result<(),StartupError>{
// maps-service api
let api_host=std::env::var("API_HOST").expect("API_HOST env required");
let api=submissions_api::Context::new(api_host).map_err(StartupError::API)?;
// data-service grpc for creating map entries
let data_host=std::env::var("DATA_HOST").expect("DATA_HOST env required");
let maps_grpc=crate::MapsServiceClient::connect(data_host).await.map_err(StartupError::GRPCConnect)?;
// request maps pending release
// randomize list
// release maps
Ok(())
}

View File

@@ -1 +0,0 @@
/target

File diff suppressed because it is too large Load Diff

View File

@@ -1,131 +0,0 @@
#[derive(Debug)]
pub enum Error{
ParseError(url::ParseError),
Reqwest(reqwest::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{}
#[derive(serde::Deserialize)]
pub struct ScriptID(i64);
#[allow(nonstandard_style)]
pub struct GetScriptRequest{
pub ScriptID:ScriptID,
}
#[allow(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct ScriptResponse{
pub ID:i64,
pub Hash:String,
pub Source:String,
pub SubmissionID:i64,
}
#[derive(serde::Deserialize)]
#[repr(i32)]
pub enum Policy{
None=0, // not yet reviewed
Allowed=1,
Blocked=2,
Delete=3,
Replace=4,
}
pub struct ScriptPolicyHashRequest{
pub hash:String,
}
#[allow(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct ScriptPolicyResponse{
pub ID:i64,
pub FromScriptHash:String,
pub ToScriptID:ScriptID,
pub Policy:Policy
}
#[allow(nonstandard_style)]
pub struct UpdateSubmissionModelRequest{
pub ID:i64,
pub ModelID:u64,
pub ModelVersion:u64,
}
pub struct SubmissionID(pub i64);
#[derive(Clone)]
pub struct Context{
base_url:String,
client:reqwest::Client,
}
pub type ReqwestError=reqwest::Error;
// there are lots of action endpoints and they all follow the same pattern
macro_rules! action{
($fname:ident,$action:expr)=>{
pub async fn $fname(&self,config:SubmissionID)->Result<(),Error>{
let url_raw=format!(concat!("{}/submissions/{}/status/",$action),self.base_url,config.0);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::ParseError)?;
self.post(url).await.map_err(Error::Reqwest)?
.error_for_status().map_err(Error::Reqwest)?;
Ok(())
}
};
}
impl Context{
pub fn new(mut base_url:String)->reqwest::Result<Self>{
base_url+="/v1";
Ok(Self{
base_url,
client:reqwest::Client::new(),
})
}
async fn get(&self,url:impl reqwest::IntoUrl)->Result<reqwest::Response,reqwest::Error>{
self.client.get(url)
.send().await
}
async fn post(&self,url:impl reqwest::IntoUrl)->Result<reqwest::Response,reqwest::Error>{
self.client.post(url)
.send().await
}
pub async fn get_script(&self,config:GetScriptRequest)->Result<ScriptResponse,Error>{
let url_raw=format!("{}/scripts/{}",self.base_url,config.ScriptID.0);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::ParseError)?;
self.get(url).await.map_err(Error::Reqwest)?
.error_for_status().map_err(Error::Reqwest)?
.json().await.map_err(Error::Reqwest)
}
pub async fn get_script_policy_from_hash(&self,config:ScriptPolicyHashRequest)->Result<ScriptPolicyResponse,Error>{
let url_raw=format!("{}/script-policy/hash/{}",self.base_url,config.hash);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::ParseError)?;
self.get(url).await.map_err(Error::Reqwest)?
.error_for_status().map_err(Error::Reqwest)?
.json().await.map_err(Error::Reqwest)
}
pub async fn update_submission_model(&self,config:UpdateSubmissionModelRequest)->Result<(),Error>{
let url_raw=format!("{}/submissions/{}/model",self.base_url,config.ID);
let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::ParseError)?;
{
url.query_pairs_mut()
.append_pair("ModelID",config.ModelID.to_string().as_str())
.append_pair("ModelVersion",config.ModelVersion.to_string().as_str());
}
self.post(url).await.map_err(Error::Reqwest)?
.error_for_status().map_err(Error::Reqwest)?;
Ok(())
}
action!(action_submission_validate,"validator-validated");
action!(action_submission_publish,"validator-published");
}

View File

@@ -1 +0,0 @@
/target

View File

@@ -1,11 +1,11 @@
[package] [package]
name = "maps-validation" name = "maps-validation"
version = "0.1.0" version = "0.1.1"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
submissions-api = { version = "0.1.0", registry = "strafesnet" } submissions-api = { path = "api", features = ["internal"], default-features = false, registry = "strafesnet" }
async-nats = "0.38.0" async-nats = "0.40.0"
futures = "0.3.31" futures = "0.3.31"
rbx_asset = { version = "0.2.5", registry = "strafesnet" } rbx_asset = { version = "0.2.5", registry = "strafesnet" }
rbx_binary = { version = "0.7.4", registry = "strafesnet"} rbx_binary = { version = "0.7.4", registry = "strafesnet"}

View File

@@ -11,7 +11,7 @@ RUN cargo chef prepare --recipe-path recipe.json
FROM chef AS builder FROM chef AS builder
COPY --from=planner /app/recipe.json recipe.json COPY --from=planner /app/recipe.json recipe.json
COPY api ./api
# Notice that we are specifying the --target flag! # Notice that we are specifying the --target flag!
RUN cargo chef cook --release --target x86_64-unknown-linux-musl --recipe-path recipe.json RUN cargo chef cook --release --target x86_64-unknown-linux-musl --recipe-path recipe.json
COPY . . COPY . .

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "submissions-api" name = "submissions-api"
version = "0.1.0" version = "0.6.1"
edition = "2021" edition = "2021"
publish = ["strafesnet"] publish = ["strafesnet"]
repository = "https://git.itzana.me/StrafesNET/maps-service" repository = "https://git.itzana.me/StrafesNET/maps-service"
@@ -14,4 +14,10 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
reqwest = { version = "0", features = ["json"] } reqwest = { version = "0", features = ["json"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
serde_repr = "0.1.19"
url = "2" url = "2"
[features]
default = ["external"]
internal = []
external = []

View File

@@ -0,0 +1,47 @@
pub struct Cookie(reqwest::header::HeaderValue);
impl Cookie{
/// cookie is prepended with "session_id=" by this function
pub fn new(cookie:&str)->Result<Self,reqwest::header::InvalidHeaderValue>{
Ok(Self(reqwest::header::HeaderValue::from_str(&format!("session_id={}",cookie))?))
}
}
#[derive(Clone)]
pub struct Context{
pub base_url:String,
client:reqwest::Client,
}
impl Context{
pub fn new(base_url:String,cookie:Option<Cookie>)->reqwest::Result<Self>{
Ok(Self{
base_url,
client:{
let mut builder=reqwest::ClientBuilder::new();
if let Some(mut cookie)=cookie{
cookie.0.set_sensitive(true);
let mut headers=reqwest::header::HeaderMap::new();
headers.insert("Cookie",cookie.0);
builder=builder.default_headers(headers);
}
builder.build()?
},
})
}
pub async fn get(&self,url:impl reqwest::IntoUrl)->Result<reqwest::Response,reqwest::Error>{
self.client.get(url)
.send().await
}
#[cfg(feature="internal")]
pub async fn post_empty_body(&self,url:impl reqwest::IntoUrl)->Result<reqwest::Response,reqwest::Error>{
self.client.post(url)
.send().await
}
pub async fn post(&self,url:impl reqwest::IntoUrl,body:impl Into<reqwest::Body>)->Result<reqwest::Response,reqwest::Error>{
self.client.post(url)
.header("Content-Type","application/json")
.body(body)
.send().await
}
}

View File

@@ -0,0 +1,131 @@
use crate::types::*;
#[derive(Clone)]
pub struct Context(crate::context::Context);
impl Context{
pub fn new(base_url:String,cookie:crate::context::Cookie)->reqwest::Result<Self>{
Ok(Self(crate::context::Context::new(base_url,Some(cookie))?))
}
pub async fn get_script(&self,config:GetScriptRequest)->Result<ScriptResponse,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.get(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
}
pub async fn get_scripts<'a>(&self,config:GetScriptsRequest<'a>)->Result<Vec<ScriptResponse>,Error>{
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 query_pairs=url.query_pairs_mut();
query_pairs.append_pair("Page",config.Page.to_string().as_str());
query_pairs.append_pair("Limit",config.Limit.to_string().as_str());
if let Some(name)=config.Name{
query_pairs.append_pair("Name",name);
}
if let Some(hash)=config.Hash{
query_pairs.append_pair("Hash",hash);
}
if let Some(source)=config.Source{
query_pairs.append_pair("Source",source);
}
if let Some(submission_id)=config.SubmissionID{
query_pairs.append_pair("SubmissionID",submission_id.to_string().as_str());
}
}
response_ok(
self.0.get(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
}
pub async fn get_script_from_hash<'a>(&self,config:HashRequest<'a>)->Result<Option<ScriptResponse>,SingleItemError>{
let scripts=self.get_scripts(GetScriptsRequest{
Page:1,
Limit:2,
Hash:Some(config.hash),
Name:None,
Source:None,
SubmissionID:None,
}).await.map_err(SingleItemError::Other)?;
if 1<scripts.len(){
return Err(SingleItemError::DuplicateItems);
}
Ok(scripts.into_iter().next())
}
pub async fn create_script<'a>(&self,config:CreateScriptRequest<'a>)->Result<ScriptIDResponse,Error>{
let url_raw=format!("{}/scripts",self.0.base_url);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
let body=serde_json::to_string(&config).map_err(Error::JSON)?;
response_ok(
self.0.post(url,body).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
}
pub async fn get_script_policies<'a>(&self,config:GetScriptPoliciesRequest<'a>)->Result<Vec<ScriptPolicyResponse>,Error>{
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 query_pairs=url.query_pairs_mut();
query_pairs.append_pair("Page",config.Page.to_string().as_str());
query_pairs.append_pair("Limit",config.Limit.to_string().as_str());
if let Some(hash)=config.FromScriptHash{
query_pairs.append_pair("FromScriptHash",hash);
}
if let Some(script_id)=config.ToScriptID{
query_pairs.append_pair("ToScriptID",script_id.0.to_string().as_str());
}
if let Some(policy)=config.Policy{
query_pairs.append_pair("Policy",(policy as i32).to_string().as_str());
}
}
response_ok(
self.0.get(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
}
pub async fn get_script_policy_from_hash<'a>(&self,config:HashRequest<'a>)->Result<Option<ScriptPolicyResponse>,SingleItemError>{
let policies=self.get_script_policies(GetScriptPoliciesRequest{
Page:1,
Limit:2,
FromScriptHash:Some(config.hash),
ToScriptID:None,
Policy:None,
}).await.map_err(SingleItemError::Other)?;
if 1<policies.len(){
return Err(SingleItemError::DuplicateItems);
}
Ok(policies.into_iter().next())
}
pub async fn create_script_policy(&self,config:CreateScriptPolicyRequest)->Result<ScriptPolicyIDResponse,Error>{
let url_raw=format!("{}/script-policy",self.0.base_url);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
let body=serde_json::to_string(&config).map_err(Error::JSON)?;
response_ok(
self.0.post(url,body).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
}
pub async fn update_script_policy(&self,config:UpdateScriptPolicyRequest)->Result<(),Error>{
let url_raw=format!("{}/script-policy/{}",self.0.base_url,config.ID.0);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
let body=serde_json::to_string(&config).map_err(Error::JSON)?;
response_ok(
self.0.post(url,body).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?;
Ok(())
}
}

View File

@@ -0,0 +1,180 @@
use crate::types::*;
#[derive(Clone)]
pub struct Context(crate::context::Context);
// there are lots of action endpoints and they all follow the same pattern
macro_rules! action{
($fname:ident,$action:expr)=>{
pub async fn $fname(&self,config:SubmissionID)->Result<(),Error>{
let url_raw=format!(concat!("{}/submissions/{}/status/",$action),self.0.base_url,config.0);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
response_ok(
self.0.post_empty_body(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?;
Ok(())
}
};
}
impl Context{
pub fn new(base_url:String)->reqwest::Result<Self>{
Ok(Self(crate::context::Context::new(base_url,None)?))
}
pub async fn get_script(&self,config:GetScriptRequest)->Result<ScriptResponse,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.get(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
}
pub async fn get_scripts<'a>(&self,config:GetScriptsRequest<'a>)->Result<Vec<ScriptResponse>,Error>{
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 query_pairs=url.query_pairs_mut();
query_pairs.append_pair("Page",config.Page.to_string().as_str());
query_pairs.append_pair("Limit",config.Limit.to_string().as_str());
if let Some(name)=config.Name{
query_pairs.append_pair("Name",name);
}
if let Some(hash)=config.Hash{
query_pairs.append_pair("Hash",hash);
}
if let Some(source)=config.Source{
query_pairs.append_pair("Source",source);
}
if let Some(submission_id)=config.SubmissionID{
query_pairs.append_pair("SubmissionID",submission_id.to_string().as_str());
}
}
response_ok(
self.0.get(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
}
pub async fn get_script_from_hash<'a>(&self,config:HashRequest<'a>)->Result<Option<ScriptResponse>,SingleItemError>{
let scripts=self.get_scripts(GetScriptsRequest{
Page:1,
Limit:2,
Hash:Some(config.hash),
Name:None,
Source:None,
SubmissionID:None,
}).await.map_err(SingleItemError::Other)?;
if 1<scripts.len(){
return Err(SingleItemError::DuplicateItems);
}
Ok(scripts.into_iter().next())
}
pub async fn create_script<'a>(&self,config:CreateScriptRequest<'a>)->Result<ScriptIDResponse,Error>{
let url_raw=format!("{}/scripts",self.0.base_url);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
let body=serde_json::to_string(&config).map_err(Error::JSON)?;
response_ok(
self.0.post(url,body).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
}
pub async fn get_script_policies<'a>(&self,config:GetScriptPoliciesRequest<'a>)->Result<Vec<ScriptPolicyResponse>,Error>{
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 query_pairs=url.query_pairs_mut();
query_pairs.append_pair("Page",config.Page.to_string().as_str());
query_pairs.append_pair("Limit",config.Limit.to_string().as_str());
if let Some(hash)=config.FromScriptHash{
query_pairs.append_pair("FromScriptHash",hash);
}
if let Some(script_id)=config.ToScriptID{
query_pairs.append_pair("ToScriptID",script_id.0.to_string().as_str());
}
if let Some(policy)=config.Policy{
query_pairs.append_pair("Policy",(policy as i32).to_string().as_str());
}
}
response_ok(
self.0.get(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
}
pub async fn get_script_policy_from_hash<'a>(&self,config:HashRequest<'a>)->Result<Option<ScriptPolicyResponse>,SingleItemError>{
let policies=self.get_script_policies(GetScriptPoliciesRequest{
Page:1,
Limit:2,
FromScriptHash:Some(config.hash),
ToScriptID:None,
Policy:None,
}).await.map_err(SingleItemError::Other)?;
if 1<policies.len(){
return Err(SingleItemError::DuplicateItems);
}
Ok(policies.into_iter().next())
}
pub async fn create_script_policy(&self,config:CreateScriptPolicyRequest)->Result<ScriptPolicyIDResponse,Error>{
let url_raw=format!("{}/script-policy",self.0.base_url);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
let body=serde_json::to_string(&config).map_err(Error::JSON)?;
response_ok(
self.0.post(url,body).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
}
pub async fn update_submission_validated_model(&self,config:UpdateSubmissionModelRequest)->Result<(),Error>{
let url_raw=format!("{}/submissions/{}/validated-model",self.0.base_url,config.SubmissionID);
let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
{
url.query_pairs_mut()
.append_pair("ValidatedModelID",config.ModelID.to_string().as_str())
.append_pair("ValidatedModelVersion",config.ModelVersion.to_string().as_str());
}
response_ok(
self.0.post_empty_body(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?;
Ok(())
}
pub async fn action_submission_uploaded(&self,config:ActionSubmissionUploadedRequest)->Result<(),Error>{
let url_raw=format!("{}/submissions/{}/status/validator-uploaded",self.0.base_url,config.SubmissionID);
let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
{
url.query_pairs_mut()
.append_pair("UploadedAssetID",config.UploadedAssetID.to_string().as_str());
}
response_ok(
self.0.post_empty_body(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?;
Ok(())
}
pub async fn action_submission_accepted(&self,config:ActionSubmissionAcceptedRequest)->Result<(),Error>{
let url_raw=format!("{}/submissions/{}/status/validator-failed",self.0.base_url,config.SubmissionID);
let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
{
url.query_pairs_mut()
.append_pair("StatusMessage",config.StatusMessage.as_str());
}
response_ok(
self.0.post_empty_body(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?;
Ok(())
}
action!(action_submission_validated,"validator-validated");
}

15
validation/api/src/lib.rs Normal file
View File

@@ -0,0 +1,15 @@
mod context;
pub use context::Cookie;
pub mod types;
#[cfg(feature="internal")]
pub mod internal;
#[cfg(feature="external")]
pub mod external;
//lazy reexports
pub use types::Error;
pub type ReqwestError=reqwest::Error;
pub type CookieError=reqwest::header::InvalidHeaderValue;

222
validation/api/src/types.rs Normal file
View File

@@ -0,0 +1,222 @@
#[derive(Debug)]
pub enum Error{
Parse(url::ParseError),
Reqwest(reqwest::Error),
Response(ResponseError),
JSON(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{}
#[derive(Debug)]
pub enum SingleItemError{
DuplicateItems,
Other(Error),
}
impl std::fmt::Display for SingleItemError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for SingleItemError{}
#[allow(dead_code)]
#[derive(Debug)]
pub struct StatusCodeWithUrlAndBody{
pub status_code:reqwest::StatusCode,
pub url:url::Url,
pub body:String,
}
#[derive(Debug)]
pub enum ResponseError{
Reqwest(reqwest::Error),
StatusCodeWithUrlAndBody(StatusCodeWithUrlAndBody),
}
impl std::fmt::Display for ResponseError{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f,"{self:?}")
}
}
impl std::error::Error for ResponseError{}
// lazy function to draw out meaningful info from http response on failure
pub async fn response_ok(response:reqwest::Response)->Result<reqwest::Response,ResponseError>{
let status_code=response.status();
if status_code.is_success(){
Ok(response)
}else{
let url=response.url().to_owned();
let bytes=response.bytes().await.map_err(ResponseError::Reqwest)?;
let body=String::from_utf8_lossy(&bytes).to_string();
Err(ResponseError::StatusCodeWithUrlAndBody(StatusCodeWithUrlAndBody{
status_code,
url,
body,
}))
}
}
#[derive(Clone,Copy,Debug,PartialEq,Eq,serde::Serialize,serde::Deserialize)]
pub struct ScriptID(pub(crate)i64);
#[derive(Clone,Copy,Debug,serde::Serialize,serde::Deserialize)]
pub struct ScriptPolicyID(pub(crate)i64);
#[allow(nonstandard_style)]
pub struct GetScriptRequest{
pub ScriptID:ScriptID,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug,serde::Serialize)]
pub struct GetScriptsRequest<'a>{
pub Page:u32,
pub Limit:u32,
#[serde(skip_serializing_if="Option::is_none")]
pub Name:Option<&'a str>,
#[serde(skip_serializing_if="Option::is_none")]
pub Hash:Option<&'a str>,
#[serde(skip_serializing_if="Option::is_none")]
pub Source:Option<&'a str>,
#[serde(skip_serializing_if="Option::is_none")]
pub SubmissionID:Option<i64>,
}
#[derive(Clone,Copy,Debug)]
pub struct HashRequest<'a>{
pub hash:&'a str,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)]
pub struct ScriptResponse{
pub ID:ScriptID,
pub Name:String,
pub Hash:String,
pub Source:String,
pub SubmissionID:i64,
}
#[derive(Clone,Debug,serde::Serialize)]
pub enum ResourceType{
Unknown=0,
Mapfix=1,
Submission=2,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug,serde::Serialize)]
pub struct CreateScriptRequest<'a>{
pub Name:&'a str,
pub Source:&'a str,
pub ResourceType:ResourceType,
#[serde(skip_serializing_if="Option::is_none")]
pub ResourceID:Option<i64>,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)]
pub struct ScriptIDResponse{
pub ID:ScriptID,
}
#[derive(Clone,Copy,Debug,PartialEq,Eq,serde_repr::Serialize_repr,serde_repr::Deserialize_repr)]
#[repr(i32)]
pub enum Policy{
None=0, // not yet reviewed
Allowed=1,
Blocked=2,
Delete=3,
Replace=4,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug,serde::Serialize)]
pub struct GetScriptPoliciesRequest<'a>{
pub Page:u32,
pub Limit:u32,
#[serde(skip_serializing_if="Option::is_none")]
pub FromScriptHash:Option<&'a str>,
#[serde(skip_serializing_if="Option::is_none")]
pub ToScriptID:Option<ScriptID>,
#[serde(skip_serializing_if="Option::is_none")]
pub Policy:Option<Policy>,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)]
pub struct ScriptPolicyResponse{
pub ID:ScriptPolicyID,
pub FromScriptHash:String,
pub ToScriptID:ScriptID,
pub Policy:Policy
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug,serde::Serialize)]
pub struct CreateScriptPolicyRequest{
pub FromScriptID:ScriptID,
pub ToScriptID:ScriptID,
pub Policy:Policy,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)]
pub struct ScriptPolicyIDResponse{
pub ID:ScriptPolicyID,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug,serde::Serialize)]
pub struct UpdateScriptPolicyRequest{
pub ID:ScriptPolicyID,
#[serde(skip_serializing_if="Option::is_none")]
pub FromScriptID:Option<ScriptID>,
#[serde(skip_serializing_if="Option::is_none")]
pub ToScriptID:Option<ScriptID>,
#[serde(skip_serializing_if="Option::is_none")]
pub Policy:Option<Policy>,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct UpdateSubmissionModelRequest{
pub SubmissionID:i64,
pub ModelID:u64,
pub ModelVersion:u64,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct ActionSubmissionUploadedRequest{
pub SubmissionID:i64,
pub UploadedAssetID:u64,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct ActionSubmissionAcceptedRequest{
pub SubmissionID:i64,
pub StatusMessage:String,
}
#[derive(Clone,Copy,Debug)]
pub struct SubmissionID(pub i64);
#[allow(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct UpdateMapfixModelRequest{
pub MapfixID:i64,
pub ModelID:u64,
pub ModelVersion:u64,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct ActionMapfixUploadedRequest{
pub MapfixID:i64,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct ActionMapfixAcceptedRequest{
pub MapfixID:i64,
pub StatusMessage:String,
}
#[derive(Clone,Copy,Debug)]
pub struct MapfixID(pub i64);

View File

@@ -1,10 +1,14 @@
use futures::StreamExt; use futures::StreamExt;
mod nats_types;
mod validator;
mod publish_new;
mod publish_fix;
mod message_handler; mod message_handler;
mod nats_types;
mod types;
mod uploader;
mod upload_mapfix;
mod upload_submission;
mod validator;
mod validate_mapfix;
mod validate_submission;
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
@@ -22,19 +26,25 @@ impl std::fmt::Display for StartupError{
} }
impl std::error::Error for StartupError{} impl std::error::Error for StartupError{}
pub const GROUP_STRAFESNET:u64=6980477;
pub const PARALLEL_REQUESTS:usize=16; pub const PARALLEL_REQUESTS:usize=16;
#[tokio::main] #[tokio::main]
async fn main()->Result<(),StartupError>{ async fn main()->Result<(),StartupError>{
let group_id:Option<u64>=match std::env::var("ROBLOX_GROUP_ID"){
Ok(s)=>match s.as_str(){
"None"=>None,
_=>Some(s.parse().expect("ROBLOX_GROUP_ID int parse")),
},
Err(e)=>Err(e).expect("ROBLOX_GROUP_ID env required"),
};
// talk to roblox through STRAFESNET_CI2 account // talk to roblox 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::CookieContext::new(rbx_asset::cookie::Cookie::new(cookie));
// maps-service api // maps-service api
let api_host=std::env::var("API_HOST").expect("API_HOST env required"); let api_host_internal=std::env::var("API_HOST_INTERNAL").expect("API_HOST_INTERNAL env required");
let api=submissions_api::Context::new(api_host).map_err(StartupError::API)?; let api=submissions_api::internal::Context::new(api_host_internal).map_err(StartupError::API)?;
// 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");
@@ -46,13 +56,13 @@ async fn main()->Result<(),StartupError>{
.get_or_create_consumer("validation",async_nats::jetstream::consumer::pull::Config{ .get_or_create_consumer("validation",async_nats::jetstream::consumer::pull::Config{
name:Some("validation".to_owned()), name:Some("validation".to_owned()),
durable_name:Some("validation".to_owned()), durable_name:Some("validation".to_owned()),
filter_subject:"maptest.submissions.>".to_owned(), filter_subject:"maptest.>".to_owned(),
..Default::default() ..Default::default()
}).await.map_err(StartupError::NatsConsumer)? }).await.map_err(StartupError::NatsConsumer)?
.messages().await.map_err(StartupError::NatsStream) .messages().await.map_err(StartupError::NatsStream)
}; };
let message_handler=message_handler::MessageHandler::new(cookie_context,api); let message_handler=message_handler::MessageHandler::new(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

@@ -3,10 +3,12 @@
pub enum HandleMessageError{ pub enum HandleMessageError{
Messages(async_nats::jetstream::consumer::pull::MessagesError), Messages(async_nats::jetstream::consumer::pull::MessagesError),
DoubleAck(async_nats::Error), DoubleAck(async_nats::Error),
Json(serde_json::Error),
UnknownSubject(String), UnknownSubject(String),
PublishNew(crate::publish_new::PublishError), UploadMapfix(crate::upload_mapfix::UploadError),
PublishFix(crate::publish_fix::PublishError), UploadSubmission(crate::upload_submission::UploadError),
Validation(crate::validator::ValidateError), ValidateMapfix(crate::validate_mapfix::ValidateMapfixError),
ValidateSubmission(crate::validate_submission::ValidateSubmissionError),
} }
impl std::fmt::Display for HandleMessageError{ impl std::fmt::Display for HandleMessageError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
@@ -17,30 +19,38 @@ impl std::error::Error for HandleMessageError{}
pub type MessageResult=Result<async_nats::jetstream::Message,async_nats::jetstream::consumer::pull::MessagesError>; pub type MessageResult=Result<async_nats::jetstream::Message,async_nats::jetstream::consumer::pull::MessagesError>;
fn from_slice<'a,T:serde::de::Deserialize<'a>>(slice:&'a [u8])->Result<T,HandleMessageError>{
serde_json::from_slice(slice).map_err(HandleMessageError::Json)
}
pub struct MessageHandler{ pub struct MessageHandler{
publish_new:crate::publish_new::Publisher, upload_mapfix:crate::upload_mapfix::Uploader,
publish_fix:crate::publish_fix::Publisher, upload_submission:crate::upload_submission::Uploader,
validator:crate::validator::Validator, validate_mapfix:crate::validate_mapfix::Validator,
validate_submission:crate::validate_submission::Validator,
} }
impl MessageHandler{ impl MessageHandler{
pub fn new( pub fn new(
cookie_context:rbx_asset::cookie::CookieContext, cookie_context:rbx_asset::cookie::CookieContext,
api:submissions_api::Context, group_id:Option<u64>,
api:submissions_api::internal::Context,
)->Self{ )->Self{
Self{ Self{
publish_new:crate::publish_new::Publisher::new(cookie_context.clone(),api.clone()), upload_mapfix:crate::upload_mapfix::Uploader::new(crate::uploader::Uploader::new(cookie_context.clone(),group_id,api.clone())),
publish_fix:crate::publish_fix::Publisher::new(cookie_context.clone(),api.clone()), upload_submission:crate::upload_submission::Uploader::new(crate::uploader::Uploader::new(cookie_context.clone(),group_id,api.clone())),
validator:crate::validator::Validator::new(cookie_context,api), validate_mapfix:crate::validate_mapfix::Validator::new(crate::validator::Validator::new(cookie_context.clone(),api.clone())),
validate_submission:crate::validate_submission::Validator::new(crate::validator::Validator::new(cookie_context,api)),
} }
} }
pub async fn handle_message_result(&self,message_result:MessageResult)->Result<(),HandleMessageError>{ pub async fn handle_message_result(&self,message_result:MessageResult)->Result<(),HandleMessageError>{
let message=message_result.map_err(HandleMessageError::Messages)?; let message=message_result.map_err(HandleMessageError::Messages)?;
message.double_ack().await.map_err(HandleMessageError::DoubleAck)?; message.double_ack().await.map_err(HandleMessageError::DoubleAck)?;
match message.subject.as_str(){ match message.subject.as_str(){
"maptest.submissions.publishnew"=>self.publish_new.publish(message).await.map_err(HandleMessageError::PublishNew), "maptest.mapfixes.upload"=>self.upload_mapfix.upload(from_slice(&message.payload)?).await.map_err(HandleMessageError::UploadMapfix),
"maptest.submissions.publishfix"=>self.publish_fix.publish(message).await.map_err(HandleMessageError::PublishFix), "maptest.submissions.upload"=>self.upload_submission.upload(from_slice(&message.payload)?).await.map_err(HandleMessageError::UploadSubmission),
"maptest.submissions.validate"=>self.validator.validate(message).await.map_err(HandleMessageError::Validation), "maptest.mapfixes.validate"=>self.validate_mapfix.validate(from_slice(&message.payload)?).await.map_err(HandleMessageError::ValidateMapfix),
"maptest.submissions.validate"=>self.validate_submission.validate(from_slice(&message.payload)?).await.map_err(HandleMessageError::ValidateSubmission),
other=>Err(HandleMessageError::UnknownSubject(other.to_owned())) other=>Err(HandleMessageError::UnknownSubject(other.to_owned()))
} }
} }

View File

@@ -6,7 +6,7 @@
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
pub struct ValidateRequest{ pub struct ValidateSubmissionRequest{
// submission_id is passed back in the response message // submission_id is passed back in the response message
pub SubmissionID:i64, pub SubmissionID:i64,
pub ModelID:u64, pub ModelID:u64,
@@ -14,23 +14,30 @@ pub struct ValidateRequest{
pub ValidatedModelID:Option<u64>, pub ValidatedModelID:Option<u64>,
} }
#[allow(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct ValidateMapfixRequest{
// submission_id is passed back in the response message
pub MapfixID:i64,
pub ModelID:u64,
pub ModelVersion:u64,
pub ValidatedModelID:Option<u64>,
}
// Create a new map // Create a new map
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
pub struct PublishNewRequest{ pub struct UploadSubmissionRequest{
pub SubmissionID:i64, pub SubmissionID:i64,
pub ModelID:u64, pub ModelID:u64,
pub ModelVersion:u64, pub ModelVersion:u64,
pub Creator:String, pub ModelName:String,
pub DisplayName:String,
pub GameID:u32,
//games:HashSet<GameID>,
} }
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
pub struct PublishFixRequest{ pub struct UploadMapfixRequest{
pub SubmissionID:i64, pub MapfixID:i64,
pub ModelID:u64, pub ModelID:u64,
pub ModelVersion:u64, pub ModelVersion:u64,
pub TargetAssetID:u64, pub TargetAssetID:u64,

View File

@@ -1,62 +0,0 @@
use crate::nats_types::PublishFixRequest;
#[allow(dead_code)]
#[derive(Debug)]
pub enum PublishError{
Get(rbx_asset::cookie::GetError),
Json(serde_json::Error),
Upload(rbx_asset::cookie::UploadError),
ApiActionSubmissionPublish(submissions_api::Error),
}
impl std::fmt::Display for PublishError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for PublishError{}
pub struct Publisher{
roblox_cookie:rbx_asset::cookie::CookieContext,
api:submissions_api::Context,
}
impl Publisher{
pub const fn new(
roblox_cookie:rbx_asset::cookie::CookieContext,
api:submissions_api::Context,
)->Self{
Self{
roblox_cookie,
api,
}
}
pub async fn publish(&self,message:async_nats::jetstream::Message)->Result<(),PublishError>{
println!("publish_fix {:?}",message.message.payload);
// decode json
let publish_info:PublishFixRequest=serde_json::from_slice(&message.payload).map_err(PublishError::Json)?;
// download the map model version
let model_data=self.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{
asset_id:publish_info.ModelID,
version:Some(publish_info.ModelVersion),
}).await.map_err(PublishError::Get)?;
// upload the map to the strafesnet group
let _upload_response=self.roblox_cookie.upload(rbx_asset::cookie::UploadRequest{
assetid:publish_info.TargetAssetID,
groupId:Some(crate::GROUP_STRAFESNET),
name:None,
description:None,
ispublic:None,
allowComments:None,
},model_data).await.map_err(PublishError::Upload)?;
// that's it, the database entry does not need to be changed.
// mark submission as published
self.api.action_submission_publish(
submissions_api::SubmissionID(publish_info.SubmissionID)
).await.map_err(PublishError::ApiActionSubmissionPublish)?;
Ok(())
}
}

View File

@@ -1,60 +0,0 @@
use crate::nats_types::PublishNewRequest;
#[allow(dead_code)]
#[derive(Debug)]
pub enum PublishError{
Get(rbx_asset::cookie::GetError),
Json(serde_json::Error),
Create(rbx_asset::cookie::CreateError),
SystemTime(std::time::SystemTimeError),
ApiActionSubmissionPublish(submissions_api::Error),
}
impl std::fmt::Display for PublishError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for PublishError{}
pub struct Publisher{
roblox_cookie:rbx_asset::cookie::CookieContext,
api:submissions_api::Context,
}
impl Publisher{
pub const fn new(
roblox_cookie:rbx_asset::cookie::CookieContext,
api:submissions_api::Context,
)->Self{
Self{
roblox_cookie,
api,
}
}
pub async fn publish(&self,message:async_nats::jetstream::Message)->Result<(),PublishError>{
println!("publish_new {:?}",message.message.payload);
// decode json
let publish_info:PublishNewRequest=serde_json::from_slice(&message.payload).map_err(PublishError::Json)?;
// download the map model version
let model_data=self.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{
asset_id:publish_info.ModelID,
version:Some(publish_info.ModelVersion),
}).await.map_err(PublishError::Get)?;
// upload the map to the strafesnet group
let upload_response=self.roblox_cookie.create(rbx_asset::cookie::CreateRequest{
name:publish_info.DisplayName.clone(),
description:"".to_owned(),
ispublic:false,
allowComments:false,
groupId:Some(crate::GROUP_STRAFESNET),
},model_data).await.map_err(PublishError::Create)?;
// mark submission as published
self.api.action_submission_publish(
submissions_api::SubmissionID(publish_info.SubmissionID)
).await.map_err(PublishError::ApiActionSubmissionPublish)?;
Ok(())
}
}

4
validation/src/types.rs Normal file
View File

@@ -0,0 +1,4 @@
pub enum ResourceID{
Mapfix(i64),
Submission(i64),
}

View File

@@ -0,0 +1,50 @@
use crate::nats_types::UploadMapfixRequest;
#[allow(dead_code)]
#[derive(Debug)]
pub enum UploadError{
Get(rbx_asset::cookie::GetError),
Json(serde_json::Error),
Upload(rbx_asset::cookie::UploadError),
ApiActionMapfixUploaded(submissions_api::Error),
}
impl std::fmt::Display for UploadError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for UploadError{}
pub struct Uploader(crate::uploader::Uploader);
impl Uploader{
pub const fn new(inner:crate::uploader::Uploader)->Self{
Self(inner)
}
pub async fn upload(&self,upload_info:UploadMapfixRequest)->Result<(),UploadError>{
let Self(uploader)=self;
// download the map model version
let model_data=uploader.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{
asset_id:upload_info.ModelID,
version:Some(upload_info.ModelVersion),
}).await.map_err(UploadError::Get)?;
// upload the map to the strafesnet group
let _upload_response=uploader.roblox_cookie.upload(rbx_asset::cookie::UploadRequest{
assetid:upload_info.TargetAssetID,
groupId:uploader.group_id,
name:None,
description:None,
ispublic:None,
allowComments:None,
},model_data).await.map_err(UploadError::Upload)?;
// that's it, the database entry does not need to be changed.
// mark mapfix as uploaded, TargetAssetID is unchanged
uploader.api.action_mapfix_uploaded(submissions_api::types::ActionMapfixUploadedRequest{
MapfixID:upload_info.MapfixID,
}).await.map_err(UploadError::ApiActionMapfixUploaded)?;
Ok(())
}
}

View File

@@ -0,0 +1,49 @@
use crate::nats_types::UploadSubmissionRequest;
#[allow(dead_code)]
#[derive(Debug)]
pub enum UploadError{
Get(rbx_asset::cookie::GetError),
Json(serde_json::Error),
Create(rbx_asset::cookie::CreateError),
SystemTime(std::time::SystemTimeError),
ApiActionSubmissionUploaded(submissions_api::Error),
}
impl std::fmt::Display for UploadError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for UploadError{}
pub struct Uploader(crate::uploader::Uploader);
impl Uploader{
pub const fn new(inner:crate::uploader::Uploader)->Self{
Self(inner)
}
pub async fn upload(&self,upload_info:UploadSubmissionRequest)->Result<(),UploadError>{
let Self(uploader)=self;
// download the map model version
let model_data=uploader.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{
asset_id:upload_info.ModelID,
version:Some(upload_info.ModelVersion),
}).await.map_err(UploadError::Get)?;
// upload the map to the strafesnet group
let upload_response=uploader.roblox_cookie.create(rbx_asset::cookie::CreateRequest{
name:upload_info.ModelName.clone(),
description:"".to_owned(),
ispublic:false,
allowComments:false,
groupId:uploader.group_id,
},model_data).await.map_err(UploadError::Create)?;
// note the asset id of the created model for later release, and mark the submission as uploaded
uploader.api.action_submission_uploaded(submissions_api::types::ActionSubmissionUploadedRequest{
SubmissionID:upload_info.SubmissionID,
UploadedAssetID:upload_response.AssetId,
}).await.map_err(UploadError::ApiActionSubmissionUploaded)?;
Ok(())
}
}

View File

@@ -0,0 +1,18 @@
pub struct Uploader{
pub(crate) roblox_cookie:rbx_asset::cookie::CookieContext,
pub(crate) group_id:Option<u64>,
pub(crate) api:submissions_api::internal::Context,
}
impl Uploader{
pub const fn new(
roblox_cookie:rbx_asset::cookie::CookieContext,
group_id:Option<u64>,
api:submissions_api::internal::Context,
)->Self{
Self{
roblox_cookie,
group_id,
api,
}
}
}

View File

@@ -0,0 +1,45 @@
use crate::nats_types::ValidateMapfixRequest;
#[allow(dead_code)]
#[derive(Debug)]
pub enum ValidateMapfixError{
ApiActionMapfixValidate(submissions_api::Error),
}
impl std::fmt::Display for ValidateMapfixError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for ValidateMapfixError{}
pub struct Validator(crate::validator::Validator);
impl Validator{
pub const fn new(inner:crate::validator::Validator)->Self{
Self(inner)
}
pub async fn validate(&self,validate_info:ValidateMapfixRequest)->Result<(),ValidateMapfixError>{
let Self(validator)=self;
let mapfix_id=validate_info.MapfixID;
let validate_result=validator.validate(validate_info.into()).await;
// update the mapfix depending on the result
match &validate_result{
Ok(())=>{
// update the mapfix model status to validated
validator.api.action_mapfix_validated(
submissions_api::types::MapfixID(mapfix_id)
).await.map_err(ValidateMapfixError::ApiActionMapfixValidate)?;
},
Err(e)=>{
// update the mapfix model status to accepted
validator.api.action_mapfix_accepted(submissions_api::types::ActionMapfixAcceptedRequest{
MapfixID:mapfix_id,
StatusMessage:format!("{e}"),
}).await.map_err(ValidateMapfixError::ApiActionMapfixValidate)?;
},
}
Ok(())
}
}

View File

@@ -0,0 +1,45 @@
use crate::nats_types::ValidateSubmissionRequest;
#[allow(dead_code)]
#[derive(Debug)]
pub enum ValidateSubmissionError{
ApiActionSubmissionValidate(submissions_api::Error),
}
impl std::fmt::Display for ValidateSubmissionError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for ValidateSubmissionError{}
pub struct Validator(crate::validator::Validator);
impl Validator{
pub const fn new(inner:crate::validator::Validator)->Self{
Self(inner)
}
pub async fn validate(&self,validate_info:ValidateSubmissionRequest)->Result<(),ValidateSubmissionError>{
let Self(validator)=self;
let submission_id=validate_info.SubmissionID;
let validate_result=validator.validate(validate_info.into()).await;
// update the submission depending on the result
match &validate_result{
Ok(())=>{
// update the submission model status to validated
validator.api.action_submission_validated(
submissions_api::types::SubmissionID(submission_id)
).await.map_err(ValidateSubmissionError::ApiActionSubmissionValidate)?;
},
Err(e)=>{
// update the submission model status to accepted
validator.api.action_submission_accepted(submissions_api::types::ActionSubmissionAcceptedRequest{
SubmissionID:submission_id,
StatusMessage:format!("{e}"),
}).await.map_err(ValidateSubmissionError::ApiActionSubmissionValidate)?;
},
}
Ok(())
}
}

View File

@@ -1,6 +1,7 @@
use futures::TryStreamExt; use futures::TryStreamExt;
use submissions_api::types::ResourceType;
use crate::nats_types::ValidateRequest; use crate::types::ResourceID;
const SCRIPT_CONCURRENCY:usize=16; const SCRIPT_CONCURRENCY:usize=16;
@@ -12,21 +13,42 @@ enum Policy{
Replace(String), Replace(String),
} }
#[allow(dead_code)] struct NamePolicy{
name:String,
policy:Policy,
}
fn source_has_illegal_keywords(source:&str)->bool{
source.find("getfenv").is_some()||source.find("require").is_some()
}
fn hash_source(source:&str)->String{
let mut hasher=siphasher::sip::SipHasher::new();
std::hash::Hasher::write(&mut hasher,source.as_bytes());
let hash=std::hash::Hasher::finish(&hasher);
format!("{:016x}",hash)
}
// #[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum ValidateError{ pub enum ValidateError{
Blocked, ScriptFlaggedIllegalKeyword(String),
NotAllowed, ScriptBlocked(Option<submissions_api::types::ScriptID>),
Get(rbx_asset::cookie::GetError), ScriptNotYetReviewed(Option<submissions_api::types::ScriptID>),
Json(serde_json::Error), ModelFileDownload(rbx_asset::cookie::GetError),
ReadDom(ReadDomError), ModelFileDecode(ReadDomError),
ApiGetScriptPolicy(submissions_api::Error), ApiGetScriptPolicyFromHash(submissions_api::types::SingleItemError),
ApiGetScript(submissions_api::Error), ApiGetScript(submissions_api::Error),
ApiCreateScript(submissions_api::Error),
ApiCreateScriptPolicy(submissions_api::Error),
ApiGetScriptFromHash(submissions_api::types::SingleItemError),
ApiUpdateMapfixModel(submissions_api::Error),
ApiUpdateSubmissionModel(submissions_api::Error), ApiUpdateSubmissionModel(submissions_api::Error),
ApiActionSubmissionValidate(submissions_api::Error), ModelFileRootMustHaveOneChild,
WriteDom(rbx_binary::EncodeError), ModelFileChildRefIsNil,
Upload(rbx_asset::cookie::UploadError), ModelFileEncode(rbx_binary::EncodeError),
Create(rbx_asset::cookie::CreateError), AssetUpload(rbx_asset::cookie::UploadError),
AssetCreate(rbx_asset::cookie::CreateError),
} }
impl std::fmt::Display for ValidateError{ impl std::fmt::Display for ValidateError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
@@ -35,89 +57,158 @@ impl std::fmt::Display for ValidateError{
} }
impl std::error::Error for ValidateError{} impl std::error::Error for ValidateError{}
#[allow(nonstandard_style)]
pub struct ValidateRequest{
pub ModelID:u64,
pub ModelVersion:u64,
pub ValidatedModelID:Option<u64>,
pub ResourceID:ResourceID,
}
impl From<crate::nats_types::ValidateMapfixRequest> for ValidateRequest{
fn from(value:crate::nats_types::ValidateMapfixRequest)->Self{
Self{
ModelID:value.ModelID,
ModelVersion:value.ModelVersion,
ValidatedModelID:value.ValidatedModelID,
ResourceID:ResourceID::Mapfix(value.MapfixID),
}
}
}
impl From<crate::nats_types::ValidateSubmissionRequest> for ValidateRequest{
fn from(value:crate::nats_types::ValidateSubmissionRequest)->Self{
Self{
ModelID:value.ModelID,
ModelVersion:value.ModelVersion,
ValidatedModelID:value.ValidatedModelID,
ResourceID:ResourceID::Submission(value.SubmissionID),
}
}
}
pub struct Validator{ pub struct Validator{
roblox_cookie:rbx_asset::cookie::CookieContext, pub(crate) roblox_cookie:rbx_asset::cookie::CookieContext,
api:submissions_api::Context, pub(crate) api:submissions_api::internal::Context,
} }
impl Validator{ impl Validator{
pub const fn new( pub const fn new(
roblox_cookie:rbx_asset::cookie::CookieContext, roblox_cookie:rbx_asset::cookie::CookieContext,
api:submissions_api::Context, api:submissions_api::internal::Context,
)->Self{ )->Self{
Self{ Self{
roblox_cookie, roblox_cookie,
api, api,
} }
} }
pub async fn validate(&self,message:async_nats::jetstream::Message)->Result<(),ValidateError>{ pub async fn validate(&self,validate_info:ValidateRequest)->Result<(),ValidateError>{
println!("validate {:?}",message.message.payload);
// decode json
let validate_info:ValidateRequest=serde_json::from_slice(&message.payload).map_err(ValidateError::Json)?;
// download map // download map
let data=self.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{ let data=self.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{
asset_id:validate_info.ModelID, asset_id:validate_info.ModelID,
version:Some(validate_info.ModelVersion), version:Some(validate_info.ModelVersion),
}).await.map_err(ValidateError::Get)?; }).await.map_err(ValidateError::ModelFileDownload)?;
// decode dom (slow!) // decode dom (slow!)
let mut dom=read_dom(&mut std::io::Cursor::new(data)).map_err(ValidateError::ReadDom)?; let mut dom=read_dom(&mut std::io::Cursor::new(data)).map_err(ValidateError::ModelFileDecode)?;
/* VALIDATE MAP */ /* VALIDATE MAP */
// 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,Policy>::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"){
script_map.insert(source.clone(),Policy::None); // check the source for illegal keywords
if source_has_illegal_keywords(source){
// immediately abort
// grab path to offending script
let path=get_partial_path(&dom,script);
return Err(ValidateError::ScriptFlaggedIllegalKeyword(path));
}
// associate a name and policy with the source code
// policy will be fetched from the database to replace the default policy
script_map.insert(source.clone(),NamePolicy{
name:get_partial_path(&dom,script),
policy:Policy::None,
});
} }
} }
} }
// send all script hashes to REST endpoint and retrieve the replacements // send all script hashes to REST endpoint and retrieve the replacements
futures::stream::iter(script_map.iter_mut().map(Ok)) futures::stream::iter(script_map.iter_mut().map(Ok))
.try_for_each_concurrent(Some(SCRIPT_CONCURRENCY),|(source,replacement)|async{ .try_for_each_concurrent(Some(SCRIPT_CONCURRENCY),|(source,NamePolicy{policy,name})|async{
// get the hash // get the hash
let mut hasher=siphasher::sip::SipHasher::new(); let hash=hash_source(source.as_str());
std::hash::Hasher::write(&mut hasher,source.as_bytes());
let hash=std::hash::Hasher::finish(&hasher);
// fetch the script policy // fetch the script policy
let script_policy=self.api.get_script_policy_from_hash(submissions_api::ScriptPolicyHashRequest{ let script_policy=self.api.get_script_policy_from_hash(submissions_api::types::HashRequest{
hash:format!("{:x}",hash), hash:hash.as_str(),
}).await.map_err(ValidateError::ApiGetScriptPolicy)?; }).await.map_err(ValidateError::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
*replacement=match script_policy.Policy{ if let Some(script_policy)=script_policy{
submissions_api::Policy::None=>Policy::None, *policy=match script_policy.Policy{
submissions_api::Policy::Allowed=>Policy::Allowed, submissions_api::types::Policy::None=>Policy::None,
submissions_api::Policy::Blocked=>Policy::Blocked, submissions_api::types::Policy::Allowed=>Policy::Allowed,
submissions_api::Policy::Delete=>Policy::Delete, submissions_api::types::Policy::Blocked=>Policy::Blocked,
submissions_api::Policy::Replace=>{ submissions_api::types::Policy::Delete=>Policy::Delete,
let script=self.api.get_script(submissions_api::GetScriptRequest{ submissions_api::types::Policy::Replace=>{
ScriptID:script_policy.ToScriptID, let script=self.api.get_script(submissions_api::types::GetScriptRequest{
}).await.map_err(ValidateError::ApiGetScript)?; ScriptID:script_policy.ToScriptID,
Policy::Replace(script.Source) }).await.map_err(ValidateError::ApiGetScript)?;
}, Policy::Replace(script.Source)
}; },
};
}else{
let (resource_type,resource_id)=match validate_info.ResourceID{
ResourceID::Mapfix(mapfix_id)=>(ResourceType::Mapfix,mapfix_id),
ResourceID::Submission(submission_id)=>(ResourceType::Submission,submission_id),
};
// upload the script
let script=self.api.create_script(submissions_api::types::CreateScriptRequest{
Name:name.as_str(),
Source:source.as_str(),
ResourceType:resource_type,
ResourceID:Some(resource_id),
}).await.map_err(ValidateError::ApiCreateScript)?;
// create a None policy (pending review by yours truly)
self.api.create_script_policy(submissions_api::types::CreateScriptPolicyRequest{
ToScriptID:script.ID,
FromScriptID:script.ID,
Policy:submissions_api::types::Policy::None,
}).await.map_err(ValidateError::ApiCreateScriptPolicy)?;
}
Ok(()) Ok(())
}) })
.await?; .await?;
// make the replacements // make the replacements
let mut modified=false; let mut modified=true;
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"){
match script_map.get(source.as_str()){ match script_map.get(source.as_str()).map(|p|&p.policy){
Some(Policy::Blocked)=>return Err(ValidateError::Blocked), Some(Policy::Blocked)=>{
let hash=hash_source(source.as_str());
let script=self.api.get_script_from_hash(submissions_api::types::HashRequest{
hash:hash.as_str(),
}).await.map_err(ValidateError::ApiGetScriptFromHash)?;
return Err(ValidateError::ScriptBlocked(script.map(|s|s.ID)));
},
None None
|Some(Policy::None) |Some(Policy::None)
=>return Err(ValidateError::NotAllowed), =>{
let hash=hash_source(source.as_str());
let script=self.api.get_script_from_hash(submissions_api::types::HashRequest{
hash:hash.as_str(),
}).await.map_err(ValidateError::ApiGetScriptFromHash)?;
return Err(ValidateError::ScriptNotYetReviewed(script.map(|s|s.ID)));
},
Some(Policy::Allowed)=>(), Some(Policy::Allowed)=>(),
Some(Policy::Delete)=>{ Some(Policy::Delete)=>{
modified=true; modified=true;
@@ -133,11 +224,16 @@ impl Validator{
} }
} }
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{ if modified{
// serialize model (slow!) // serialize model (slow!)
let mut data=Vec::new(); let mut data=Vec::new();
rbx_binary::to_writer(&mut data,&dom,dom.root().children()).map_err(ValidateError::WriteDom)?; let &[map_ref]=dom.root().children()else{
return Err(ValidateError::ModelFileRootMustHaveOneChild);
};
rbx_binary::to_writer(&mut data,&dom,&[map_ref]).map_err(ValidateError::ModelFileEncode)?;
// upload a model lol // upload a model lol
let model_id=if let Some(model_id)=validate_info.ValidatedModelID{ let model_id=if let Some(model_id)=validate_info.ValidatedModelID{
@@ -149,34 +245,45 @@ impl Validator{
ispublic:None, ispublic:None,
allowComments:None, allowComments:None,
groupId:None, groupId:None,
},data).await.map_err(ValidateError::Upload)?; },data).await.map_err(ValidateError::AssetUpload)?;
response.AssetId response.AssetId
}else{ }else{
// grab the map instance from the map re
let Some(map_instance)=dom.get_by_ref(map_ref)else{
return Err(ValidateError::ModelFileChildRefIsNil);
};
// create new model // create new model
let response=self.roblox_cookie.create(rbx_asset::cookie::CreateRequest{ let response=self.roblox_cookie.create(rbx_asset::cookie::CreateRequest{
name:dom.root().name.clone(), name:map_instance.name.clone(),
description:"".to_owned(), description:"".to_owned(),
ispublic:true, ispublic:true,
allowComments:true, allowComments:true,
groupId:None, groupId:None,
},data).await.map_err(ValidateError::Create)?; },data).await.map_err(ValidateError::AssetCreate)?;
response.AssetId response.AssetId
}; };
// update the submission to use the validated model match validate_info.ResourceID{
self.api.update_submission_model(submissions_api::UpdateSubmissionModelRequest{ ResourceID::Mapfix(mapfix_id)=>{
ID:validate_info.SubmissionID, // update the mapfix to use the validated model
ModelID:model_id, self.api.update_mapfix_validated_model(submissions_api::types::UpdateMapfixModelRequest{
ModelVersion:1, //TODO MapfixID:mapfix_id,
}).await.map_err(ValidateError::ApiUpdateSubmissionModel)?; ModelID:model_id,
}; ModelVersion:1, //TODO
}).await.map_err(ValidateError::ApiUpdateMapfixModel)?;
// update the submission model status to validated },
self.api.action_submission_validate( ResourceID::Submission(submission_id)=>{
submissions_api::SubmissionID(validate_info.SubmissionID) // update the submission to use the validated model
).await.map_err(ValidateError::ApiActionSubmissionValidate)?; self.api.update_submission_validated_model(submissions_api::types::UpdateSubmissionModelRequest{
SubmissionID:submission_id,
ModelID:model_id,
ModelVersion:1, //TODO
}).await.map_err(ValidateError::ApiUpdateSubmissionModel)?;
},
}
}
Ok(()) Ok(())
} }
@@ -238,6 +345,19 @@ fn recursive_collect_superclass(objects:&mut std::vec::Vec<rbx_dom_weak::types::
} }
} }
fn get_partial_path(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->String{
let mut names:Vec<_>=core::iter::successors(
Some(instance),
|i|dom.get_by_ref(i.parent())
).map(
|i|i.name.as_str()
).collect();
// discard the name of the root object
names.pop();
names.reverse();
names.join(".")
}
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 mut scripts=std::vec::Vec::new();
recursive_collect_superclass(&mut scripts,dom,dom.root(),"LuaSourceContainer"); recursive_collect_superclass(&mut scripts,dom,dom.root(),"LuaSourceContainer");

View File

@@ -3,14 +3,17 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
distDir: "build", distDir: "build",
output: "standalone", output: "standalone",
rewrites: async () => { images: {
return [ remotePatterns: [
{ {
source: "/v1/submissions/:submissionid/status/:statustype", protocol: 'https',
destination: "http://mapsservice:8082/v1/submissions/:submissionid/status/:statustype" hostname: 'api.ic3.space',
} pathname: '/strafe/map-images/**',
] port: '',
} search: '',
},
],
},
}; };
export default nextConfig; export default nextConfig;

View File

@@ -3,7 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev -p 3000", "dev": "next dev -p 3000 --turbopack",
"build": "next build", "build": "next build",
"start": "next start -p 3000", "start": "next start -p 3000",
"lint": "next lint" "lint": "next lint"

View File

@@ -18,10 +18,10 @@ export default function Header() {
return ( return (
<header className="header-bar"> <header className="header-bar">
<nav className="left"> <nav className="left">
<HeaderButton name="Maps" href=""/> <HeaderButton name="Submissions" href="/submissions"/>
</nav> </nav>
<nav className="right"> <nav className="right">
<HeaderButton name="Home" href=""/> <HeaderButton name="Submit" href="/submit"/>
<HeaderButton name="Need" href=""/> <HeaderButton name="Need" href=""/>
<HeaderButton name="Menu" href=""/> <HeaderButton name="Menu" href=""/>
<HeaderButton name="Items" href=""/> <HeaderButton name="Items" href=""/>

View File

@@ -2,8 +2,8 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
width: 100vw; width: 100%;
height: 60px; height: var(--header-height);
background: var(--header-grad-left); background: var(--header-grad-left);
background: linear-gradient(180deg, var(--header-grad-left) 0%, var(--header-grad-right) 100%); background: linear-gradient(180deg, var(--header-grad-left) 0%, var(--header-grad-right) 100%);
@@ -24,7 +24,7 @@
.right { .right {
display: flex; display: flex;
gap: 7px; gap: 7px;
margin-right: 50px; margin-right: 35px;
button { button {
font-size: 1rem; font-size: 1rem;

View File

@@ -1,8 +1,27 @@
"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}>) {
return (<> useEffect(() => { login_check() }, [])
<Header/>
{children} return <>
</>) <Header/>
} {children}
</>
}

View File

@@ -10,6 +10,8 @@ $form-label-fontsize: 1.3rem;
:root { :root {
color-scheme: light dark; color-scheme: light dark;
--header-height: 45px;
--page: white; --page: white;
--header-grad-left: #363b40; --header-grad-left: #363b40;
--header-grad-right: #353a40; --header-grad-right: #353a40;

View File

@@ -0,0 +1,75 @@
@forward "./page/card.scss";
@use "../../globals.scss";
a {
color:rgb(255, 255, 255);
&:visited, &:hover, &:focus {
text-decoration: none;
color: rgb(255, 255, 255);
}
&:active {
color: rgb(192, 192, 192)
}
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
grid-template-rows: repeat(3, 1fr);
gap: 16px;
max-width: 100%;
margin: 0 auto;
overflow-x: hidden;
box-sizing: border-box;
}
@media (max-width: 768px) {
.grid {
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
margin: 0.3rem;
}
.pagination button {
padding: 0.25rem 0.5rem;
font-size: 1.15rem;
border: none;
border-radius: 0.35rem;
background-color: #33333350;
color: #fff;
cursor: pointer;
}
.pagination button:disabled {
background-color: #5555559a;
cursor: not-allowed;
}
.pagination-dots {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
justify-content: center;
width: 100%;
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #bbb;
cursor: pointer;
}
.dot.active {
background-color: #333;
}

View File

@@ -0,0 +1,87 @@
@use "../../../globals.scss";
.submissionCard {
display: flex;
background-color: #2020207c;
border: 1px solid #97979783;
border-radius: 10px;
box-sizing: border-box;
flex-direction: column;
justify-content: space-between;
height: 100%;
min-width: 180px;
max-width: 340px;
}
.content {
display: flex;
flex-grow: 1;
flex-direction: column;
justify-content: space-between;
}
.details {
padding: 2px 4px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin: 3px 0px;
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
margin: 3px 0px;
}
.map-image {
border-radius: 10px 10px 0 0;
overflow: hidden;
> img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.displayName {
font-size: 1rem;
font-weight: bold;
overflow: hidden;
max-width: 70%;
text-overflow: ellipsis;
white-space: nowrap;
}
.rating {
flex-shrink: 0;
}
.author {
display: flex;
align-items: center;
gap: 0.5rem;
flex-grow: 1;
min-width: 0;
}
.author span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.rating {
margin-left: auto;
flex-shrink: 0;
}
.avatar {
border-radius: 50%;
object-fit: cover;
}

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