Compare commits
621 Commits
web-docker
...
compose-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
0b71386bf8
|
|||
| 08753d36b4 | |||
|
5dce0f2017
|
|||
|
d196da949c
|
|||
|
b0c723be1b
|
|||
| 48314f5d18 | |||
|
1e4e513dc1
|
|||
|
f9455e7317
|
|||
|
69ebef5fb8
|
|||
| a9258136da | |||
|
ac05f4acdc
|
|||
|
061b7b2a01
|
|||
|
b4a0041fb5
|
|||
| 692c915af8 | |||
|
d35c331b76
|
|||
|
9ce8b75f0f
|
|||
|
f5d41ab672
|
|||
|
2578a74ddb
|
|||
| 45a58145ed | |||
|
a4cefd263d
|
|||
| 1b18649cb6 | |||
|
bdfd1a0b23
|
|||
|
859004f264
|
|||
|
89ef254b72
|
|||
|
36f9e2db5f
|
|||
|
ce4e37dc64
|
|||
|
f417111bcf
|
|||
|
1826a51ebd
|
|||
| 76903656c2 | |||
|
3e353b2ec6
|
|||
|
749ea7e57d
|
|||
|
03ec0b0183
|
|||
|
cc93776c25
|
|||
|
a8ad9f7de0
|
|||
|
d5d794703b
|
|||
|
70bbba6003
|
|||
|
39ba12edd9
|
|||
|
1cfdb3668a
|
|||
| 740e6368b1 | |||
|
746ac8e8a7
|
|||
|
bdfe16ed39
|
|||
|
3c21d8948a
|
|||
|
eaa8f704ea
|
|||
|
d50ae0e664
|
|||
|
f95d8b1665
|
|||
|
524c56b6b5
|
|||
|
e05f69ef7d
|
|||
|
a45fbb370c
|
|||
|
27a646c006
|
|||
|
dde6f3ebdb
|
|||
|
3d08b144b1
|
|||
|
63b701eb72
|
|||
| 07e08af5ed | |||
| 391a0fe6f9 | |||
| 0e06d00c21 | |||
|
cd0bfbaeb2
|
|||
|
70cb80ab9b
|
|||
|
6d0af22485
|
|||
|
c08fdddf36
|
|||
|
727b823fef
|
|||
| efefaa5e62 | |||
| c6ce9b8f82 | |||
| ca008f8fcf | |||
| e9affea859 | |||
| a31a92f131 | |||
|
a5187be8a6
|
|||
| 825b2aa91a | |||
| 6f9cd952d4 | |||
|
abb3cf3076
|
|||
|
40b0af0063
|
|||
| 976adf2b66 | |||
| 53cc4b9e9e | |||
|
51f62f039b
|
|||
|
42cc783887
|
|||
| ed7109270f | |||
| abd233ce65 | |||
| 215c39000b | |||
| c4d97b6537 | |||
| 0834400c05 | |||
| 463d14d2b5 | |||
| 6a52166901 | |||
| d7c2ad3dde | |||
| f54bf1dc34 | |||
| 644c04c133 | |||
| 8006e3efbc | |||
| b60d2b6186 | |||
| 8f2a0b53e4 | |||
| 70dd8502f4 | |||
| 5b977289e7 | |||
| 7b3af95f3d | |||
|
4d78a9b2c5
|
|||
|
ec59a83379
|
|||
| 54bf3f55a0 | |||
| 14f404ffe3 | |||
|
0e1d2fe50a
|
|||
|
ada8c322da
|
|||
|
84d2bfef20
|
|||
| 170e7c64b6 | |||
| b443866dd6 | |||
|
ebe37ad6a2
|
|||
|
131dad7ae0
|
|||
| 127402fa77 | |||
|
40f83a4e30
|
|||
|
b6d4ce4f80
|
|||
| 07391a84cb | |||
|
|
3f848a35c8 | ||
| e5e2387502 | |||
|
90d13d28ae
|
|||
| 513b9722b1 | |||
|
3da8e414e6
|
|||
|
2acc30e18c
|
|||
|
a990ed458c
|
|||
|
4055ef550e
|
|||
|
555844e6ee
|
|||
|
80f30d20fa
|
|||
|
489a8c9c10
|
|||
|
534598ba70
|
|||
|
fdc0240698
|
|||
|
b0829bc1fc
|
|||
|
845f8e69d9
|
|||
|
0d8937e896
|
|||
| 2927afd848 | |||
| 8f6c543f81 | |||
|
7c95f8ddd0
|
|||
|
24964407bd
|
|||
|
|
8d5bd9e523 | ||
|
|
e1fc637619 | ||
|
|
762ee874a0 | ||
| a1c84ff225 | |||
|
cea6242dd7
|
|||
| fefe116611 | |||
|
0ada77421f
|
|||
|
fa2d611534
|
|||
| 81539a606c | |||
| 32095296c2 | |||
|
8ea5ee2d41
|
|||
|
954dbaeac6
|
|||
| 5b7efa2426 | |||
|
|
740e3c8932 | ||
|
4f31f8c75a
|
|||
|
d39a8c0208
|
|||
|
636282b993
|
|||
|
e3667fec0c
|
|||
|
0dff464561
|
|||
|
e30fb5f916
|
|||
|
9dd7156f38
|
|||
|
fc9aae4235
|
|||
|
a11a0d2fd5
|
|||
|
9dad1a6b4d
|
|||
|
a95e6b7a9a
|
|||
|
|
4c1aef9113 | ||
|
|
c98d170423 | ||
|
6d14047f57
|
|||
|
41663624d3
|
|||
|
49b9b41085
|
|||
|
3614018794
|
|||
|
872b98aa74
|
|||
|
d5c8477869
|
|||
|
b600ca582b
|
|||
|
adbcbed9ac
|
|||
|
8f8d685f71
|
|||
|
a669de3c0b
|
|||
|
649b941d5f
|
|||
|
1b4456f30a
|
|||
|
d34a5c7091
|
|||
|
2f36877cb6
|
|||
|
3a124b8190
|
|||
|
6cc6da4879
|
|||
|
123b0c9a81
|
|||
|
54b0abbbf3
|
|||
|
1b0384da11
|
|||
|
e0cebfd80e
|
|||
|
5ba52ecb57
|
|||
|
9e42050a65
|
|||
|
c817bfc8c8
|
|||
|
8f97ca6690
|
|||
|
f220cb62bc
|
|||
|
f090fd7d68
|
|||
|
404e1281ff
|
|||
|
e4f710c83f
|
|||
|
a942c81ea8
|
|||
|
109b24061a
|
|||
|
ddef30984f
|
|||
|
9331f37d70
|
|||
|
c4f910c1f0
|
|||
|
343a4011dd
|
|||
|
c63997d161
|
|||
|
ea58fcedc9
|
|||
|
50e3fb283c
|
|||
|
aa513a7973
|
|||
|
eff9097456
|
|||
|
668c5fef51
|
|||
|
57db5f738e
|
|||
|
3789755a19
|
|||
|
ee6c37ab9d
|
|||
|
12bfbfb0a0
|
|||
|
c57a53692d
|
|||
|
ccf07c5931
|
|||
|
6efab4f411
|
|||
|
34d1db02a5
|
|||
|
d86ed0cdf5
|
|||
|
d19763349e
|
|||
|
5846e92924
|
|||
|
34b8d7475d
|
|||
|
a5daa2df4a
|
|||
|
1b73af9fe2
|
|||
|
8433030562
|
|||
|
8372665fd3
|
|||
|
d24b342738
|
|||
|
796f31aadf
|
|||
|
44f8736838
|
|||
|
b7e5d82c13
|
|||
|
169007f16e
|
|||
|
2519c9faa1
|
|||
|
1ff6bdbd4c
|
|||
|
d1ca9bdab9
|
|||
| c76ff3b687 | |||
| a42501d254 | |||
| f915c51ba4 | |||
| ff9da333eb | |||
| 1dabd216aa | |||
| cc7e890580 | |||
| 99d1b38535 | |||
| 12ca1b7dab | |||
|
fa1b44f172
|
|||
|
03519e9337
|
|||
|
60b6d30379
|
|||
|
19b8f7b7a2
|
|||
|
4f586c6176
|
|||
|
d1a70509b7
|
|||
|
95bfb87c6e
|
|||
|
de0cf37918
|
|||
|
f1fd826c62
|
|||
|
1380a00872
|
|||
|
174a210f81
|
|||
|
67a03f394f
|
|||
|
6eebe404d5
|
|||
|
1d409218a5
|
|||
|
e2c72c90c7
|
|||
|
7334e88b55
|
|||
|
b93c813dec
|
|||
|
926a90329b
|
|||
|
18abbd92ce
|
|||
|
c923a8a076
|
|||
|
d6da6f003e
|
|||
|
0dc7aec395
|
|||
|
c85cb63639
|
|||
|
6c865e8841
|
|||
|
99a082afb5
|
|||
|
434cd295f5
|
|||
|
bfc2a2cbca
|
|||
|
c24db2c3a0
|
|||
|
68f2311658
|
|||
|
163412a253
|
|||
|
044033cabf
|
|||
|
219a15f656
|
|||
|
383bc783a4
|
|||
|
24a5baae77
|
|||
|
4ba3b5cd01
|
|||
|
f610fc1c0f
|
|||
|
e67d679901
|
|||
|
3c3d09c4a7
|
|||
|
d02e3776f3
|
|||
|
77222c84db
|
|||
|
412f34817c
|
|||
|
cac288d73b
|
|||
|
29e414d6e7
|
|||
|
c9ba2e3e6e
|
|||
|
0666685a49
|
|||
|
ff9237e453
|
|||
|
9b5f7e0b0c
|
|||
|
e28c7e8149
|
|||
|
220ea84e22
|
|||
|
7648f407c5
|
|||
|
e0266c5d24
|
|||
|
9ab2e23fa9
|
|||
|
6b2f5e29e7
|
|||
|
d42e89fcb4
|
|||
|
7e881e6ac5
|
|||
|
2d57b945f2
|
|||
|
005e99424e
|
|||
|
a330b1c43b
|
|||
|
d2662eb833
|
|||
|
3ba599114d
|
|||
| d53f61fb5b | |||
|
5d259e20f2
|
|||
|
21b6903943
|
|||
|
14c7979310
|
|||
|
e376e02dc1
|
|||
|
4e7ee9dc5a
|
|||
|
ceaec14242
|
|||
|
9372caa157
|
|||
|
f73c274367
|
|||
|
c50a28443e
|
|||
|
c7150f1e23
|
|||
|
f16a817da2
|
|||
|
e858d252ab
|
|||
|
66e0d22ccd
|
|||
|
986ecfc7ad
|
|||
|
66890ccd44
|
|||
| ec15c1f2e5 | |||
|
8be9475ee5
|
|||
|
0cb2bec6e0
|
|||
| cf1906acaa | |||
|
7e93807298
|
|||
|
ee5b3331b4
|
|||
|
29c0acf3b2
|
|||
|
a844c4e90a
|
|||
|
5ed15a6847
|
|||
|
1ff1cae709
|
|||
|
|
c6ebe5a360 | ||
|
15dd6b4178
|
|||
|
ca1676db00
|
|||
|
56681f8862
|
|||
|
fe2c20bd72
|
|||
|
027a55661b
|
|||
|
a3d644f572
|
|||
|
d0634fc141
|
|||
| 719ef95b6d | |||
| c9041168e5 | |||
| 1e012af52e | |||
| 54b4cf2d13 | |||
|
|
91ac3a5e36 | ||
|
|
fc5519e744 | ||
| 170e194ac9 | |||
|
|
739c9354a6 | ||
| 73f559f049 | |||
|
|
3f377f4605 | ||
|
|
edc1ed5459 | ||
|
c9212a5ec8
|
|||
|
|
adaa088efe | ||
|
e85e3f130f
|
|||
| 0462788c53 | |||
| 2c31a9585b | |||
| 3699ce5cbb | |||
| 8776936e96 | |||
| e466af7d27 | |||
| abed5c6227 | |||
| a639b81988 | |||
| 5aa27c08a5 | |||
| 577ab5cdd0 | |||
| a72be13843 | |||
| d4e8edbb6e | |||
| 19c4e36990 | |||
| 56dec20189 | |||
| 34d37d8c1c | |||
| 508d41506a | |||
| 493c6d084a | |||
| 722ac5178f | |||
| df39101102 | |||
| e877ba4788 | |||
|
|
8a28d6cfcf | ||
|
e9f79241f1
|
|||
|
bfd287f3cc
|
|||
|
082c573ffb
|
|||
|
3bda4803aa
|
|||
|
c401d24366
|
|||
|
a119c4292e
|
|||
|
4cf7889db9
|
|||
|
146d627534
|
|||
|
97180ab263
|
|||
|
37560ac5d2
|
|||
|
de8f869b5b
|
|||
|
b6ae600a93
|
|||
|
96ace736f4
|
|||
|
9dd7a41d8f
|
|||
|
cc7df064be
|
|||
|
732598266c
|
|||
|
6d420c3a82
|
|||
|
2e65d071e0
|
|||
|
e36b49a31e
|
|||
|
1d7f6ea79a
|
|||
|
b0f1e42a06
|
|||
|
8925d71bcd
|
|||
|
8de5bcba68
|
|||
|
a048d713da
|
|||
|
581c65594d
|
|||
|
4e22933e34
|
|||
|
758c2254eb
|
|||
|
ade54ee662
|
|||
|
01785bb190
|
|||
|
8366b84d90
|
|||
|
746c7aa9b7
|
|||
|
930eb47096
|
|||
|
9671c357f4
|
|||
|
3404251c14
|
|||
|
ffcba57408
|
|||
|
a60ccd22f0
|
|||
|
f7d7a0891d
|
|||
|
e5e85db1fd
|
|||
|
0b64440975
|
|||
|
0e29ca98dd
|
|||
|
9740cbe91a
|
|||
|
2d2691b551
|
|||
|
dfc2a605f4
|
|||
|
88c3866654
|
|||
|
92226e768d
|
|||
|
4515eb6da2
|
|||
|
f2d8c49647
|
|||
|
2c75cfa67f
|
|||
|
f3689f4916
|
|||
|
e855ace229
|
|||
|
6e21447d4b
|
|||
| 49fea314ec | |||
| 4c17a3c9e9 | |||
| a7784bdaf5 | |||
|
f0e18a5963
|
|||
|
661fa17fa7
|
|||
|
cc1d5f4bda
|
|||
|
e7a66ebe0d
|
|||
|
977f3902b7
|
|||
| af9f413b49 | |||
| b02b3d205e | |||
|
2f2241612a
|
|||
|
a7c72163eb
|
|||
|
c8077482f3
|
|||
|
79c21b62d8
|
|||
|
032f0e8739
|
|||
|
251a24efae
|
|||
|
a9afdf38cf
|
|||
|
d3edb6b3da
|
|||
|
188fbd2a6d
|
|||
|
1468a9edc2
|
|||
|
1053719eab
|
|||
|
2867da4b21
|
|||
|
85a144e276
|
|||
| 4227f18992 | |||
| 123bc8af47 | |||
| cd82954b73 | |||
| ce08b57e18 | |||
| 1ca0348924 | |||
| 936a1f93aa | |||
| d5d0e5ffc9 | |||
|
039309c75a
|
|||
|
7cc0b5da7f
|
|||
|
f0c44fb4a8
|
|||
|
4fec1bba47
|
|||
|
5ae287f3f2
|
|||
|
bf6c8af21a
|
|||
|
65e63431a3
|
|||
|
a8dc6cd35a
|
|||
|
539e09fe06
|
|||
|
87fd7adb93
|
|||
|
7d57d1ac4d
|
|||
|
636bb1fb94
|
|||
|
295ec3cd8b
|
|||
|
6af006f802
|
|||
|
d16bb8ad02
|
|||
|
1af7d7e941
|
|||
|
1feca92f7d
|
|||
|
7213948a26
|
|||
|
783d0e843c
|
|||
|
977d1d20c2
|
|||
|
d7634de9ec
|
|||
|
8da1c9346b
|
|||
|
894851c0e8
|
|||
| 3da4023466 | |||
| 08a4e913a9 | |||
| 6748cb4324 | |||
| 73e5c76e75 | |||
| b4be174d98 | |||
| f52e0a91a2 | |||
| 0b1e7085e3 | |||
| 31f1db6446 | |||
| b377405762 | |||
| b496f8c0d8 | |||
| 0c247fbb43 | |||
| 483ffd1d66 | |||
| ff01abdd63 | |||
| 0271ba4d28 | |||
| c6b31b7c73 | |||
| 80e7d735be | |||
| e66513e88d | |||
| 355161c3b1 | |||
| e5a1dcf144 | |||
| 99e320d17f | |||
| 57d714fdd7 | |||
| d77bf02185 | |||
| 47129e2d1f | |||
| b542dba739 | |||
| f5c4868dc4 | |||
| 1341f87bf8 | |||
| 57544f3f64 | |||
| ecb88c14a4 | |||
| e1645e7c46 | |||
| 49e767f027 | |||
| 91a72ccf8b | |||
| 11e801443f | |||
| 8338a71470 | |||
| 59e5e529c6 | |||
| a82a78c938 | |||
| b6c7c76900 | |||
| 75e8d2b7b2 | |||
| 8dbdfbdb3f | |||
| 28990e2dbe | |||
| a39e2892ef | |||
| 8e223d432e | |||
|
|
040488d85f | ||
| e43f4bd0f0 | |||
| ca1e007b07 | |||
| 952ceab014 | |||
| 952b77b3db | |||
| 0794e7ba46 | |||
| bc8b7b68d2 | |||
| c04ba33f9c | |||
| c95d10a0d4 | |||
| 94abe3137b | |||
| 78db4eeba7 | |||
| 56ff5670dd | |||
| d584ee2c03 | |||
| f629ac2998 | |||
| 07ef22bc02 | |||
| 8bf2c92df3 | |||
| 0d549a46d4 | |||
| 1b58bfd096 | |||
| cd57ead995 | |||
| c085ea9b7d | |||
| 25dbc038ca | |||
| f038b9cda6 | |||
| 8b3aa158c9 | |||
| a45b4f2f0c | |||
| ca846972c1 | |||
| a511246d78 | |||
| f04ab4f653 | |||
| b3ffbe4b50 | |||
| a7e9dbb94d | |||
|
|
b0b16c91dc | ||
|
|
9bd3eb69f9 | ||
| 02d77ab421 | |||
| 8dbb4517fa | |||
| b782b1ae64 | |||
| 246b8a7dc8 | |||
| 621edbdbe0 | |||
| 516bd7a439 | |||
| 6a8805b91a | |||
| 518327820d | |||
| 964fc24e26 | |||
| a94ae5d61e | |||
| 76d36bea5c | |||
| 88dfc92bc6 | |||
| e905d96917 | |||
| b238e4c21d | |||
| 1d3e553390 | |||
| 6545fa703d | |||
| a28ec58ce8 | |||
| fe0a1e0e0f | |||
| 9070d77f41 | |||
| 0dc39121c8 | |||
| 3ea881e724 | |||
| 6064a1e48f | |||
| e7234a614d | |||
| 299f994f32 | |||
| 49db6e35ce | |||
| 185a1d147f | |||
| b5bb79c6ef | |||
| f7101e2b84 | |||
| f3af65aa13 | |||
| 833ed66844 | |||
| 67651633d8 | |||
| 7a7e158ec3 | |||
| 7ad4ffc7e0 | |||
| e46f9fc6ea | |||
| 2ad219cf77 | |||
| 9bdf98635e | |||
| 3a6dd311bf | |||
| 298a68fa97 | |||
| 6bab1e1b6b | |||
| 8c45736cf4 | |||
| db52b1dcd4 | |||
| f4abc30c21 | |||
| 332578ec94 | |||
| 64e9e2b263 | |||
| ffadaa44be | |||
| 9a7270d2f9 | |||
| cb736628d7 | |||
| ec414a0f42 | |||
| 2342981643 | |||
| 3cfcbff253 | |||
| ce59d7c947 | |||
| ed68af80b0 | |||
| 11846c32e6 | |||
| ecbd102aef | |||
| cf1fdb4099 | |||
| 33d272ab04 | |||
| 75d8cafc7b | |||
| 7d2147779a | |||
| 7e940cdfb1 | |||
| 47c30ad2db | |||
| 29b77f14de | |||
| 9e022ca265 | |||
| 95675c51e6 | |||
| 7a30dc4ec3 | |||
| cd9bb17370 | |||
| 4ce5d5e535 | |||
| 1450c0f3a2 | |||
| 76abcf0a34 | |||
| d4303612ac | |||
| 5e5caae6c3 | |||
| 673152bc0d | |||
| 203ae67384 | |||
| e31dec6424 | |||
| 00fdbd9611 | |||
| 346f49610d | |||
| ae6e968135 | |||
| dff37906c6 | |||
| 50911af656 | |||
| 1243203085 | |||
| 7ae2775c3f | |||
| fcd74b54cc | |||
| b090a80f03 | |||
| 3a6a62fb7c | |||
| 3739ff011e | |||
| 046d95c5b3 | |||
| 090c794c24 | |||
| 4373ca4ba9 | |||
| f77dd14ac9 | |||
| 727e358cf9 | |||
| 8250686477 | |||
| 1f96b5facb | |||
| 22086e772c | |||
| c99608aaff |
154
.drone.yml
Normal file
154
.drone.yml
Normal file
@@ -0,0 +1,154 @@
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: build-backend
|
||||
image: golang:1.24.0
|
||||
environment:
|
||||
GIT_USER:
|
||||
from_secret: GIT_USER
|
||||
GIT_PASS:
|
||||
from_secret: GIT_PASS
|
||||
commands:
|
||||
- echo "machine git.itzana.me login $${GIT_USER} password $${GIT_PASS}" > ~/.netrc
|
||||
- chmod 600 ~/.netrc
|
||||
- make build-backend
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- staging
|
||||
|
||||
- name: build-validator
|
||||
image: clux/muslrust:1.86.0-stable
|
||||
commands:
|
||||
- make build-validator
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- staging
|
||||
|
||||
- name: build-frontend
|
||||
image: oven/bun:1.2.8
|
||||
commands:
|
||||
- apt-get update
|
||||
- apt-get install make
|
||||
- make build-frontend
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- staging
|
||||
event:
|
||||
- pull_request
|
||||
|
||||
- name: image-backend
|
||||
image: plugins/docker
|
||||
settings:
|
||||
registry: registry.itzana.me
|
||||
repo: registry.itzana.me/strafesnet/maptest-api
|
||||
tags:
|
||||
- ${DRONE_BRANCH}-${DRONE_BUILD_NUMBER}
|
||||
- ${DRONE_BRANCH}
|
||||
username:
|
||||
from_secret: REGISTRY_USER
|
||||
password:
|
||||
from_secret: REGISTRY_PASS
|
||||
dockerfile: Dockerfile
|
||||
context: .
|
||||
depends_on:
|
||||
- build-backend
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- staging
|
||||
event:
|
||||
- push
|
||||
|
||||
- name: image-frontend
|
||||
image: plugins/docker
|
||||
settings:
|
||||
registry: registry.itzana.me
|
||||
repo: registry.itzana.me/strafesnet/maptest-frontend
|
||||
tags:
|
||||
- ${DRONE_BRANCH}-${DRONE_BUILD_NUMBER}
|
||||
- ${DRONE_BRANCH}
|
||||
username:
|
||||
from_secret: REGISTRY_USER
|
||||
password:
|
||||
from_secret: REGISTRY_PASS
|
||||
dockerfile: web/Containerfile
|
||||
context: web
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- staging
|
||||
event:
|
||||
- push
|
||||
|
||||
- name: image-validator
|
||||
image: plugins/docker
|
||||
settings:
|
||||
registry: registry.itzana.me
|
||||
repo: registry.itzana.me/strafesnet/maptest-validator
|
||||
tags:
|
||||
- ${DRONE_BRANCH}-${DRONE_BUILD_NUMBER}
|
||||
- ${DRONE_BRANCH}
|
||||
username:
|
||||
from_secret: REGISTRY_USER
|
||||
password:
|
||||
from_secret: REGISTRY_PASS
|
||||
dockerfile: validation/Containerfile
|
||||
context: .
|
||||
depends_on:
|
||||
- build-validator
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- staging
|
||||
event:
|
||||
- push
|
||||
|
||||
- name: deploy
|
||||
image: argoproj/argocd:latest
|
||||
commands:
|
||||
- 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:
|
||||
USERNAME:
|
||||
from_secret: ARGO_USER
|
||||
PASSWORD:
|
||||
from_secret: ARGO_PASS
|
||||
depends_on:
|
||||
- image-backend
|
||||
- image-frontend
|
||||
- image-validator
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- staging
|
||||
event:
|
||||
- push
|
||||
|
||||
# pr dry run
|
||||
- name: build-pr
|
||||
image: alpine
|
||||
commands:
|
||||
- echo "Success!"
|
||||
depends_on:
|
||||
- build-backend
|
||||
- build-validator
|
||||
- build-frontend
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
---
|
||||
kind: signature
|
||||
hmac: cc7f2f8dac4285b5fa1df163bd92115f1a51a92050687cd08169e17803a2de4c
|
||||
|
||||
...
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1 +1,3 @@
|
||||
.idea
|
||||
build
|
||||
.idea
|
||||
/target
|
||||
|
||||
1470
validation/Cargo.lock → Cargo.lock
generated
1470
validation/Cargo.lock → Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
6
Cargo.toml
Normal file
6
Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"validation",
|
||||
"validation/api",
|
||||
]
|
||||
resolver = "2"
|
||||
@@ -1,36 +0,0 @@
|
||||
# Stage 1: Build
|
||||
FROM docker.io/golang:1.23 AS builder
|
||||
|
||||
# Set the working directory in the container
|
||||
WORKDIR /app
|
||||
|
||||
# Copy go.mod and go.sum files
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
# Download dependencies
|
||||
RUN --mount=type=secret,id=netrc,dst=/root/.netrc go mod download
|
||||
|
||||
# Copy the entire project
|
||||
COPY . .
|
||||
|
||||
# Build the Go application
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -o service ./cmd/maps-service/service.go
|
||||
|
||||
# Stage 2: Run
|
||||
FROM alpine
|
||||
|
||||
# Set up a non-root user for security
|
||||
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
|
||||
USER appuser
|
||||
|
||||
# Set the working directory in the container
|
||||
WORKDIR /home/appuser
|
||||
|
||||
# Copy the built application from the builder stage
|
||||
COPY --from=builder /app/service .
|
||||
|
||||
# Expose application port (adjust if needed)
|
||||
EXPOSE 8081
|
||||
|
||||
# Command to run the application
|
||||
ENTRYPOINT ["./service"]
|
||||
3
Dockerfile
Normal file
3
Dockerfile
Normal file
@@ -0,0 +1,3 @@
|
||||
FROM alpine
|
||||
COPY build/server /
|
||||
ENTRYPOINT ["/server"]
|
||||
42
Makefile
Normal file
42
Makefile
Normal file
@@ -0,0 +1,42 @@
|
||||
clean:
|
||||
rm -rf build
|
||||
rm -rf web/build
|
||||
|
||||
# build
|
||||
build-backend:
|
||||
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o build/server cmd/maps-service/service.go
|
||||
|
||||
build-validator:
|
||||
cargo build --release --target x86_64-unknown-linux-musl --bin maps-validation
|
||||
|
||||
build-frontend:
|
||||
rm -rf web/build
|
||||
cd web && bun install --frozen-lockfile
|
||||
cd web && bun run build
|
||||
|
||||
build: build-backend build-validator build-frontend
|
||||
|
||||
# image
|
||||
image-backend:
|
||||
docker build . -t maptest-api
|
||||
|
||||
image-validator:
|
||||
docker build . -f validation/Containerfile -t maptest-validator
|
||||
|
||||
image-frontend:
|
||||
docker build web -f web/Containerfile -t maptest-frontend
|
||||
|
||||
# docker
|
||||
docker-backend:
|
||||
make build-backend
|
||||
make image-backend
|
||||
docker-validator:
|
||||
make build-validator
|
||||
make image-validator
|
||||
docker-frontend:
|
||||
make build-frontend
|
||||
make image-frontend
|
||||
|
||||
docker: docker-backend docker-validator docker-frontend
|
||||
|
||||
.PHONY: clean build-backend build-validator build-frontend build image-backend image-validator image-frontend docker-backend docker-validator docker-frontend docker
|
||||
13
README.md
13
README.md
@@ -26,6 +26,13 @@ Prerequisite: golang installed
|
||||
|
||||
Prerequisite: bun installed
|
||||
|
||||
The environment variables `API_HOST` and `AUTH_HOST` will need to be set for the middleware.
|
||||
Example `.env` in web's root:
|
||||
```
|
||||
API_HOST="http://localhost:8082/"
|
||||
AUTH_HOST="http://localhost:8083/"
|
||||
```
|
||||
|
||||
1. `cd web`
|
||||
2. `bun install`
|
||||
|
||||
@@ -43,6 +50,12 @@ Prerequisite: rust installed
|
||||
1. `cd validation`
|
||||
2. `cargo run --release`
|
||||
|
||||
Environment Variables:
|
||||
- ROBLOX_GROUP_ID
|
||||
- RBXCOOKIE
|
||||
- API_HOST_INTERNAL
|
||||
- NATS_HOST
|
||||
|
||||
#### License
|
||||
|
||||
<sup>
|
||||
|
||||
@@ -7,6 +7,16 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// @title StrafesNET Maps API
|
||||
// @version 1.0
|
||||
// @description Obtain an api key at https://dev.strafes.net
|
||||
// @description Requires Maps:Read permission
|
||||
// @BasePath /public-api/v1
|
||||
|
||||
// @securityDefinitions.apikey ApiKeyAuth
|
||||
// @in header
|
||||
// @name X-API-Key
|
||||
|
||||
func main() {
|
||||
log.SetLevel(log.InfoLevel)
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
|
||||
131
compose.yaml
131
compose.yaml
@@ -3,32 +3,25 @@ networks:
|
||||
maps-service-network:
|
||||
driver: bridge
|
||||
|
||||
secrets:
|
||||
netrc:
|
||||
file: /home/quat/.netrc
|
||||
|
||||
services:
|
||||
nats:
|
||||
image: docker.io/nats:latest
|
||||
container_name: nats
|
||||
command: ["-js"]
|
||||
command: ["-js"] #"-DVV"
|
||||
networks:
|
||||
- maps-service-network
|
||||
ports:
|
||||
- "4222:4222"
|
||||
|
||||
maps-service:
|
||||
build:
|
||||
secrets:
|
||||
- netrc
|
||||
context: .
|
||||
dockerfile: Containerfile
|
||||
container_name: maps-service
|
||||
submissions:
|
||||
image:
|
||||
maptest-api
|
||||
container_name: submissions
|
||||
command: [
|
||||
# debug
|
||||
"--debug","serve",
|
||||
# http service port
|
||||
"--port","8081",
|
||||
"--port","8082",
|
||||
# internal http endpoints
|
||||
"--port-internal","8083",
|
||||
# postgres
|
||||
"--pg-host","10.0.0.29",
|
||||
"--pg-port","5432",
|
||||
@@ -37,38 +30,120 @@ services:
|
||||
"--pg-password","happypostgresuser",
|
||||
# other hosts
|
||||
"--nats-host","nats:4222",
|
||||
"--auth-rpc-host","localhost:8090"
|
||||
"--auth-rpc-host","authrpc:8081",
|
||||
"--data-rpc-host","dataservice:9000",
|
||||
]
|
||||
env_file:
|
||||
- ../auth-compose/strafesnet_staging.env
|
||||
depends_on:
|
||||
- authrpc
|
||||
- nats
|
||||
networks:
|
||||
- maps-service-network
|
||||
ports:
|
||||
- "8081:8081"
|
||||
- "8082:8082"
|
||||
|
||||
web:
|
||||
build:
|
||||
context: web
|
||||
dockerfile: Containerfile
|
||||
image:
|
||||
maptest-frontend
|
||||
networks:
|
||||
- maps-service-network
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- API_HOST=http://submissions:8082/v1
|
||||
- AUTH_HOST=http://localhost:8080/
|
||||
|
||||
validation:
|
||||
build:
|
||||
context: validation
|
||||
dockerfile: Containerfile
|
||||
image:
|
||||
maptest-validator
|
||||
container_name: validation
|
||||
env_file:
|
||||
- ../auth-compose/strafesnet_staging.env
|
||||
environment:
|
||||
- RBXCOOKIE=RBXCOOKIE
|
||||
- API_HOST=http://localhost:8081
|
||||
- ROBLOX_GROUP_ID=17032139 # "None" is special case string value
|
||||
- API_HOST_INTERNAL=http://submissions:8083/v1
|
||||
- NATS_HOST=nats:4222
|
||||
- DATA_HOST=http://localhost:9000
|
||||
depends_on:
|
||||
- nats
|
||||
# note: this races the maps-service 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
|
||||
- maps-service
|
||||
- submissions
|
||||
- dataservice
|
||||
networks:
|
||||
- maps-service-network
|
||||
|
||||
public_api:
|
||||
image:
|
||||
maptest-api
|
||||
container_name: public_api
|
||||
command: [
|
||||
# debug
|
||||
"--debug","api",
|
||||
# http service port
|
||||
"--port","8084",
|
||||
"--dev-rpc-host","dev-service:8081",
|
||||
"--maps-rpc-host","maptest-api:8081",
|
||||
]
|
||||
depends_on:
|
||||
- submissions
|
||||
- dev_service
|
||||
networks:
|
||||
- maps-service-network
|
||||
ports:
|
||||
- "8084:8084"
|
||||
|
||||
dataservice:
|
||||
image: registry.itzana.me/strafesnet/data-service:master
|
||||
container_name: dataservice
|
||||
environment:
|
||||
- DEBUG=true
|
||||
- PG_HOST=10.0.0.29
|
||||
- PG_PORT=5432
|
||||
- PG_USER=quat
|
||||
- PG_DB=data
|
||||
- PG_PASS=happypostgresuser
|
||||
networks:
|
||||
- maps-service-network
|
||||
|
||||
authredis:
|
||||
image: docker.io/redis:latest
|
||||
container_name: authredis
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
command: ["redis-server", "--appendonly", "yes"]
|
||||
networks:
|
||||
- maps-service-network
|
||||
|
||||
authrpc:
|
||||
image: registry.itzana.me/strafesnet/auth-service:staging
|
||||
container_name: authrpc
|
||||
command: ["serve", "rpc"]
|
||||
environment:
|
||||
- REDIS_ADDR=authredis:6379
|
||||
- RBX_GROUP_ID=17032139
|
||||
env_file:
|
||||
- ../auth-compose/auth-service.env
|
||||
depends_on:
|
||||
- authredis
|
||||
networks:
|
||||
- maps-service-network
|
||||
logging:
|
||||
driver: "none"
|
||||
|
||||
auth-web:
|
||||
image: registry.itzana.me/strafesnet/auth-service:staging
|
||||
command: ["serve", "web"]
|
||||
environment:
|
||||
- REDIS_ADDR=authredis:6379
|
||||
env_file:
|
||||
- ../auth-compose/auth-service.env
|
||||
depends_on:
|
||||
- authredis
|
||||
networks:
|
||||
- maps-service-network
|
||||
ports:
|
||||
- "8080:8080"
|
||||
|
||||
volumes:
|
||||
redis-data:
|
||||
|
||||
242
docs/docs.go
Normal file
242
docs/docs.go
Normal file
@@ -0,0 +1,242 @@
|
||||
// Package docs Code generated by swaggo/swag. DO NOT EDIT
|
||||
package docs
|
||||
|
||||
import "github.com/swaggo/swag"
|
||||
|
||||
const docTemplate = `{
|
||||
"schemes": {{ marshal .Schemes }},
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "{{escape .Description}}",
|
||||
"title": "{{.Title}}",
|
||||
"contact": {},
|
||||
"version": "{{.Version}}"
|
||||
},
|
||||
"host": "{{.Host}}",
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {
|
||||
"/map": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Get a list of maps",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"maps"
|
||||
],
|
||||
"summary": "List maps",
|
||||
"parameters": [
|
||||
{
|
||||
"maximum": 100,
|
||||
"minimum": 1,
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
"description": "Page size (max 100)",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"minimum": 1,
|
||||
"type": "integer",
|
||||
"default": 1,
|
||||
"description": "Page number",
|
||||
"name": "page_number",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "game_id",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/PagedResponse-Map"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "General error response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/map/{id}": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Get a specific map by its ID",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"maps"
|
||||
],
|
||||
"summary": "Get map by ID",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Map ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Response-Map"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Map not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Error"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "General error response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"Error": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Map": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"asset_version": {
|
||||
"type": "integer"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"creator": {
|
||||
"type": "string"
|
||||
},
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"game_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"load_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"modes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"submitter": {
|
||||
"type": "integer"
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "integer"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PagedResponse-Map": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"description": "Data contains the actual response payload",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Map"
|
||||
}
|
||||
},
|
||||
"pagination": {
|
||||
"description": "Pagination contains information about paging",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Pagination"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Pagination": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"description": "Current page number",
|
||||
"type": "integer"
|
||||
},
|
||||
"page_size": {
|
||||
"description": "Number of items per page",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Response-Map": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"description": "Data contains the actual response payload",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Map"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
"ApiKeyAuth": {
|
||||
"type": "apiKey",
|
||||
"name": "X-API-Key",
|
||||
"in": "header"
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = &swag.Spec{
|
||||
Version: "1.0",
|
||||
Host: "",
|
||||
BasePath: "/public-api/v1",
|
||||
Schemes: []string{},
|
||||
Title: "StrafesNET Maps API",
|
||||
Description: "Obtain an api key at https://dev.strafes.net\nRequires Data:Read permission",
|
||||
InfoInstanceName: "swagger",
|
||||
SwaggerTemplate: docTemplate,
|
||||
LeftDelim: "{{",
|
||||
RightDelim: "}}",
|
||||
}
|
||||
|
||||
func init() {
|
||||
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
||||
}
|
||||
217
docs/swagger.json
Normal file
217
docs/swagger.json
Normal file
@@ -0,0 +1,217 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "Obtain an api key at https://dev.strafes.net\nRequires Data:Read permission",
|
||||
"title": "StrafesNET Maps API",
|
||||
"contact": {},
|
||||
"version": "1.0"
|
||||
},
|
||||
"basePath": "/public-api/v1",
|
||||
"paths": {
|
||||
"/map": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Get a list of maps",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"maps"
|
||||
],
|
||||
"summary": "List maps",
|
||||
"parameters": [
|
||||
{
|
||||
"maximum": 100,
|
||||
"minimum": 1,
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
"description": "Page size (max 100)",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"minimum": 1,
|
||||
"type": "integer",
|
||||
"default": 1,
|
||||
"description": "Page number",
|
||||
"name": "page_number",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "game_id",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/PagedResponse-Map"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "General error response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/map/{id}": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Get a specific map by its ID",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"maps"
|
||||
],
|
||||
"summary": "Get map by ID",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Map ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Response-Map"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Map not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Error"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "General error response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"Error": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Map": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"asset_version": {
|
||||
"type": "integer"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"creator": {
|
||||
"type": "string"
|
||||
},
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"game_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"load_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"modes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"submitter": {
|
||||
"type": "integer"
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "integer"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PagedResponse-Map": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"description": "Data contains the actual response payload",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Map"
|
||||
}
|
||||
},
|
||||
"pagination": {
|
||||
"description": "Pagination contains information about paging",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Pagination"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Pagination": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"description": "Current page number",
|
||||
"type": "integer"
|
||||
},
|
||||
"page_size": {
|
||||
"description": "Number of items per page",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Response-Map": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"description": "Data contains the actual response payload",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Map"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
"ApiKeyAuth": {
|
||||
"type": "apiKey",
|
||||
"name": "X-API-Key",
|
||||
"in": "header"
|
||||
}
|
||||
}
|
||||
}
|
||||
141
docs/swagger.yaml
Normal file
141
docs/swagger.yaml
Normal file
@@ -0,0 +1,141 @@
|
||||
basePath: /public-api/v1
|
||||
definitions:
|
||||
Error:
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
type: object
|
||||
Map:
|
||||
properties:
|
||||
asset_version:
|
||||
type: integer
|
||||
created_at:
|
||||
type: string
|
||||
creator:
|
||||
type: string
|
||||
date:
|
||||
type: string
|
||||
display_name:
|
||||
type: string
|
||||
game_id:
|
||||
type: integer
|
||||
id:
|
||||
type: integer
|
||||
load_count:
|
||||
type: integer
|
||||
modes:
|
||||
type: integer
|
||||
submitter:
|
||||
type: integer
|
||||
thumbnail:
|
||||
type: integer
|
||||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
PagedResponse-Map:
|
||||
properties:
|
||||
data:
|
||||
description: Data contains the actual response payload
|
||||
items:
|
||||
$ref: '#/definitions/Map'
|
||||
type: array
|
||||
pagination:
|
||||
allOf:
|
||||
- $ref: '#/definitions/Pagination'
|
||||
description: Pagination contains information about paging
|
||||
type: object
|
||||
Pagination:
|
||||
properties:
|
||||
page:
|
||||
description: Current page number
|
||||
type: integer
|
||||
page_size:
|
||||
description: Number of items per page
|
||||
type: integer
|
||||
type: object
|
||||
Response-Map:
|
||||
properties:
|
||||
data:
|
||||
allOf:
|
||||
- $ref: '#/definitions/Map'
|
||||
description: Data contains the actual response payload
|
||||
type: object
|
||||
info:
|
||||
contact: {}
|
||||
description: |-
|
||||
Obtain an api key at https://dev.strafes.net
|
||||
Requires Data:Read permission
|
||||
title: StrafesNET Maps API
|
||||
version: "1.0"
|
||||
paths:
|
||||
/map:
|
||||
get:
|
||||
description: Get a list of maps
|
||||
parameters:
|
||||
- default: 10
|
||||
description: Page size (max 100)
|
||||
in: query
|
||||
maximum: 100
|
||||
minimum: 1
|
||||
name: page_size
|
||||
type: integer
|
||||
- default: 1
|
||||
description: Page number
|
||||
in: query
|
||||
minimum: 1
|
||||
name: page_number
|
||||
type: integer
|
||||
- in: query
|
||||
name: game_id
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/PagedResponse-Map'
|
||||
default:
|
||||
description: General error response
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: List maps
|
||||
tags:
|
||||
- maps
|
||||
/map/{id}:
|
||||
get:
|
||||
description: Get a specific map by its ID
|
||||
parameters:
|
||||
- description: Map ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/Response-Map'
|
||||
"404":
|
||||
description: Map not found
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
default:
|
||||
description: General error response
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Get map by ID
|
||||
tags:
|
||||
- maps
|
||||
securityDefinitions:
|
||||
ApiKeyAuth:
|
||||
in: header
|
||||
name: X-API-Key
|
||||
type: apiKey
|
||||
swagger: "2.0"
|
||||
@@ -1,3 +1,4 @@
|
||||
package main
|
||||
|
||||
//go:generate swag init -g ./cmd/maps-service/service.go
|
||||
//go:generate go run github.com/ogen-go/ogen/cmd/ogen@latest --target pkg/api --clean openapi.yaml
|
||||
|
||||
68
go.mod
68
go.mod
@@ -1,44 +1,80 @@
|
||||
module git.itzana.me/strafesnet/maps-service
|
||||
|
||||
go 1.22
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.23.3
|
||||
toolchain go1.24.5
|
||||
|
||||
require (
|
||||
git.itzana.me/strafesnet/go-grpc v0.0.0-20241129081229-9e166b3d11f7
|
||||
git.itzana.me/StrafesNET/dev-service v0.0.0-20250628052121-92af8193b5ed
|
||||
git.itzana.me/strafesnet/go-grpc v0.0.0-20250807005013-301d35b914ef
|
||||
git.itzana.me/strafesnet/utils v0.0.0-20220716194944-d8ca164052f9
|
||||
github.com/dchest/siphash v1.2.3
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/go-faster/errors v0.7.1
|
||||
github.com/go-faster/jx v1.1.0
|
||||
github.com/nats-io/nats.go v1.37.0
|
||||
github.com/ogen-go/ogen v1.2.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/urfave/cli/v2 v2.27.5
|
||||
github.com/swaggo/files v1.0.1
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/swag v1.16.6
|
||||
github.com/urfave/cli/v2 v2.27.6
|
||||
go.opentelemetry.io/otel v1.32.0
|
||||
go.opentelemetry.io/otel/metric v1.32.0
|
||||
go.opentelemetry.io/otel/trace v1.32.0
|
||||
google.golang.org/grpc v1.48.0
|
||||
gorm.io/driver/postgres v1.5.10
|
||||
gorm.io/gorm v1.25.10
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/gorm v1.25.12
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.6 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/nats-io/nkeys v0.4.7 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -53,12 +89,12 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
go.uber.org/multierr v1.11.0
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
181
go.sum
181
go.sum
@@ -1,14 +1,30 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
git.itzana.me/strafesnet/go-grpc v0.0.0-20241129081229-9e166b3d11f7 h1:5XzWd3ZZjSw1M60IfHuILty2vRPBYiqM0FZ+E7uHCi8=
|
||||
git.itzana.me/strafesnet/go-grpc v0.0.0-20241129081229-9e166b3d11f7/go.mod h1:X7XTRUScRkBWq8q8bplbeso105RPDlnY7J6Wy1IwBMs=
|
||||
git.itzana.me/StrafesNET/dev-service v0.0.0-20250628052121-92af8193b5ed h1:eGWIQx2AOrSsLC2dieuSs8MCliRE60tvpZnmxsTBtKc=
|
||||
git.itzana.me/StrafesNET/dev-service v0.0.0-20250628052121-92af8193b5ed/go.mod h1:KJal0K++M6HEzSry6JJ2iDPZtOQn5zSstNlDbU3X4Jg=
|
||||
git.itzana.me/strafesnet/go-grpc v0.0.0-20250807005013-301d35b914ef h1:SJi4V4+xzScFnbMRN1gkZxcqR1xKfiT7CaXanLltEzw=
|
||||
git.itzana.me/strafesnet/go-grpc v0.0.0-20250807005013-301d35b914ef/go.mod h1:X7XTRUScRkBWq8q8bplbeso105RPDlnY7J6Wy1IwBMs=
|
||||
git.itzana.me/strafesnet/utils v0.0.0-20220716194944-d8ca164052f9 h1:7lU6jyR7S7Rhh1dnUp7GyIRHUTBXZagw8F4n4hOyxLw=
|
||||
git.itzana.me/strafesnet/utils v0.0.0-20220716194944-d8ca164052f9/go.mod h1:uyYerSieEt4v0MJCdPLppG0LtJ4Yj035vuTetWGsxjY=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
@@ -17,6 +33,7 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -32,8 +49,16 @@ github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
|
||||
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
|
||||
github.com/go-faster/jx v1.1.0 h1:ZsW3wD+snOdmTDy9eIVgQdjUpXRRV4rqW8NS3t+20bg=
|
||||
@@ -45,6 +70,26 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
|
||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
|
||||
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -70,6 +115,7 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
@@ -77,42 +123,67 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
||||
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
||||
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
|
||||
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE=
|
||||
github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
|
||||
github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
|
||||
github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/ogen-go/ogen v1.2.1 h1:C5A0lvUMu2wl+eWIxnpXMWnuOJ26a2FyzR1CIC2qG0M=
|
||||
github.com/ogen-go/ogen v1.2.1/go.mod h1:P2zQdEu8UqaVRfD5GEFvl+9q63VjMLvDquq1wVbyInM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
@@ -121,16 +192,35 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
|
||||
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
||||
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
|
||||
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
|
||||
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
|
||||
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
|
||||
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
|
||||
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
|
||||
@@ -144,55 +234,85 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
|
||||
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
@@ -222,9 +342,11 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
@@ -232,12 +354,15 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/postgres v1.5.10 h1:7Lggqempgy496c0WfHXsYWxk3Th+ZcW66/21QhVFdeE=
|
||||
gorm.io/driver/postgres v1.5.10/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
gorm.io/gorm v1.21.11/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
||||
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
|
||||
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
1557
openapi.yaml
1557
openapi.yaml
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
File diff suppressed because it is too large
Load Diff
@@ -6,26 +6,59 @@ package api
|
||||
type OperationName = string
|
||||
|
||||
const (
|
||||
ActionSubmissionPublishOperation OperationName = "ActionSubmissionPublish"
|
||||
ActionSubmissionRejectOperation OperationName = "ActionSubmissionReject"
|
||||
ActionSubmissionRequestChangesOperation OperationName = "ActionSubmissionRequestChanges"
|
||||
ActionSubmissionRevokeOperation OperationName = "ActionSubmissionRevoke"
|
||||
ActionSubmissionSubmitOperation OperationName = "ActionSubmissionSubmit"
|
||||
ActionSubmissionTriggerPublishOperation OperationName = "ActionSubmissionTriggerPublish"
|
||||
ActionSubmissionTriggerValidateOperation OperationName = "ActionSubmissionTriggerValidate"
|
||||
ActionSubmissionValidateOperation OperationName = "ActionSubmissionValidate"
|
||||
CreateScriptOperation OperationName = "CreateScript"
|
||||
CreateScriptPolicyOperation OperationName = "CreateScriptPolicy"
|
||||
CreateSubmissionOperation OperationName = "CreateSubmission"
|
||||
DeleteScriptOperation OperationName = "DeleteScript"
|
||||
DeleteScriptPolicyOperation OperationName = "DeleteScriptPolicy"
|
||||
GetScriptOperation OperationName = "GetScript"
|
||||
GetScriptPolicyOperation OperationName = "GetScriptPolicy"
|
||||
GetScriptPolicyFromHashOperation OperationName = "GetScriptPolicyFromHash"
|
||||
GetSubmissionOperation OperationName = "GetSubmission"
|
||||
ListSubmissionsOperation OperationName = "ListSubmissions"
|
||||
SetSubmissionCompletedOperation OperationName = "SetSubmissionCompleted"
|
||||
UpdateScriptOperation OperationName = "UpdateScript"
|
||||
UpdateScriptPolicyOperation OperationName = "UpdateScriptPolicy"
|
||||
UpdateSubmissionModelOperation OperationName = "UpdateSubmissionModel"
|
||||
ActionMapfixAcceptedOperation OperationName = "ActionMapfixAccepted"
|
||||
ActionMapfixRejectOperation OperationName = "ActionMapfixReject"
|
||||
ActionMapfixRequestChangesOperation OperationName = "ActionMapfixRequestChanges"
|
||||
ActionMapfixResetSubmittingOperation OperationName = "ActionMapfixResetSubmitting"
|
||||
ActionMapfixRetryValidateOperation OperationName = "ActionMapfixRetryValidate"
|
||||
ActionMapfixRevokeOperation OperationName = "ActionMapfixRevoke"
|
||||
ActionMapfixTriggerSubmitOperation OperationName = "ActionMapfixTriggerSubmit"
|
||||
ActionMapfixTriggerSubmitUncheckedOperation OperationName = "ActionMapfixTriggerSubmitUnchecked"
|
||||
ActionMapfixTriggerUploadOperation OperationName = "ActionMapfixTriggerUpload"
|
||||
ActionMapfixTriggerValidateOperation OperationName = "ActionMapfixTriggerValidate"
|
||||
ActionMapfixValidatedOperation OperationName = "ActionMapfixValidated"
|
||||
ActionSubmissionAcceptedOperation OperationName = "ActionSubmissionAccepted"
|
||||
ActionSubmissionRejectOperation OperationName = "ActionSubmissionReject"
|
||||
ActionSubmissionRequestChangesOperation OperationName = "ActionSubmissionRequestChanges"
|
||||
ActionSubmissionResetSubmittingOperation OperationName = "ActionSubmissionResetSubmitting"
|
||||
ActionSubmissionRetryValidateOperation OperationName = "ActionSubmissionRetryValidate"
|
||||
ActionSubmissionRevokeOperation OperationName = "ActionSubmissionRevoke"
|
||||
ActionSubmissionTriggerSubmitOperation OperationName = "ActionSubmissionTriggerSubmit"
|
||||
ActionSubmissionTriggerSubmitUncheckedOperation OperationName = "ActionSubmissionTriggerSubmitUnchecked"
|
||||
ActionSubmissionTriggerUploadOperation OperationName = "ActionSubmissionTriggerUpload"
|
||||
ActionSubmissionTriggerValidateOperation OperationName = "ActionSubmissionTriggerValidate"
|
||||
ActionSubmissionValidatedOperation OperationName = "ActionSubmissionValidated"
|
||||
CreateMapfixOperation OperationName = "CreateMapfix"
|
||||
CreateMapfixAuditCommentOperation OperationName = "CreateMapfixAuditComment"
|
||||
CreateScriptOperation OperationName = "CreateScript"
|
||||
CreateScriptPolicyOperation OperationName = "CreateScriptPolicy"
|
||||
CreateSubmissionOperation OperationName = "CreateSubmission"
|
||||
CreateSubmissionAdminOperation OperationName = "CreateSubmissionAdmin"
|
||||
CreateSubmissionAuditCommentOperation OperationName = "CreateSubmissionAuditComment"
|
||||
DeleteScriptOperation OperationName = "DeleteScript"
|
||||
DeleteScriptPolicyOperation OperationName = "DeleteScriptPolicy"
|
||||
DownloadMapAssetOperation OperationName = "DownloadMapAsset"
|
||||
GetMapOperation OperationName = "GetMap"
|
||||
GetMapfixOperation OperationName = "GetMapfix"
|
||||
GetOperationOperation OperationName = "GetOperation"
|
||||
GetScriptOperation OperationName = "GetScript"
|
||||
GetScriptPolicyOperation OperationName = "GetScriptPolicy"
|
||||
GetSubmissionOperation OperationName = "GetSubmission"
|
||||
ListMapfixAuditEventsOperation OperationName = "ListMapfixAuditEvents"
|
||||
ListMapfixesOperation OperationName = "ListMapfixes"
|
||||
ListMapsOperation OperationName = "ListMaps"
|
||||
ListScriptPolicyOperation OperationName = "ListScriptPolicy"
|
||||
ListScriptsOperation OperationName = "ListScripts"
|
||||
ListSubmissionAuditEventsOperation OperationName = "ListSubmissionAuditEvents"
|
||||
ListSubmissionsOperation OperationName = "ListSubmissions"
|
||||
ReleaseSubmissionsOperation OperationName = "ReleaseSubmissions"
|
||||
SessionRolesOperation OperationName = "SessionRoles"
|
||||
SessionUserOperation OperationName = "SessionUser"
|
||||
SessionValidateOperation OperationName = "SessionValidate"
|
||||
SetMapfixCompletedOperation OperationName = "SetMapfixCompleted"
|
||||
SetSubmissionCompletedOperation OperationName = "SetSubmissionCompleted"
|
||||
UpdateMapfixModelOperation OperationName = "UpdateMapfixModel"
|
||||
UpdateScriptOperation OperationName = "UpdateScript"
|
||||
UpdateScriptPolicyOperation OperationName = "UpdateScriptPolicy"
|
||||
UpdateSubmissionModelOperation OperationName = "UpdateSubmissionModel"
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,18 +3,123 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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) decodeCreateMapfixRequest(r *http.Request) (
|
||||
req *MapfixTriggerCreate,
|
||||
close func() error,
|
||||
rerr error,
|
||||
) {
|
||||
var closers []func() error
|
||||
close = func() error {
|
||||
var merr error
|
||||
// Close in reverse order, to match defer behavior.
|
||||
for i := len(closers) - 1; i >= 0; i-- {
|
||||
c := closers[i]
|
||||
merr = errors.Join(merr, c())
|
||||
}
|
||||
return merr
|
||||
}
|
||||
defer func() {
|
||||
if rerr != nil {
|
||||
rerr = errors.Join(rerr, close())
|
||||
}
|
||||
}()
|
||||
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return req, close, errors.Wrap(err, "parse media type")
|
||||
}
|
||||
switch {
|
||||
case ct == "application/json":
|
||||
if r.ContentLength == 0 {
|
||||
return req, close, validate.ErrBodyRequired
|
||||
}
|
||||
buf, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return req, close, err
|
||||
}
|
||||
|
||||
if len(buf) == 0 {
|
||||
return req, close, validate.ErrBodyRequired
|
||||
}
|
||||
|
||||
d := jx.DecodeBytes(buf)
|
||||
|
||||
var request MapfixTriggerCreate
|
||||
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) decodeCreateMapfixAuditCommentRequest(r *http.Request) (
|
||||
req CreateMapfixAuditCommentReq,
|
||||
close func() error,
|
||||
rerr error,
|
||||
) {
|
||||
var closers []func() error
|
||||
close = func() error {
|
||||
var merr error
|
||||
// Close in reverse order, to match defer behavior.
|
||||
for i := len(closers) - 1; i >= 0; i-- {
|
||||
c := closers[i]
|
||||
merr = errors.Join(merr, c())
|
||||
}
|
||||
return merr
|
||||
}
|
||||
defer func() {
|
||||
if rerr != nil {
|
||||
rerr = errors.Join(rerr, close())
|
||||
}
|
||||
}()
|
||||
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return req, close, errors.Wrap(err, "parse media type")
|
||||
}
|
||||
switch {
|
||||
case ct == "text/plain":
|
||||
reader := r.Body
|
||||
request := CreateMapfixAuditCommentReq{Data: reader}
|
||||
return request, close, nil
|
||||
default:
|
||||
return req, close, validate.InvalidContentType(ct)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) decodeCreateScriptRequest(r *http.Request) (
|
||||
req *ScriptCreate,
|
||||
close func() error,
|
||||
@@ -26,13 +131,13 @@ func (s *Server) decodeCreateScriptRequest(r *http.Request) (
|
||||
// Close in reverse order, to match defer behavior.
|
||||
for i := len(closers) - 1; i >= 0; i-- {
|
||||
c := closers[i]
|
||||
merr = multierr.Append(merr, c())
|
||||
merr = errors.Join(merr, c())
|
||||
}
|
||||
return merr
|
||||
}
|
||||
defer func() {
|
||||
if rerr != nil {
|
||||
rerr = multierr.Append(rerr, close())
|
||||
rerr = errors.Join(rerr, close())
|
||||
}
|
||||
}()
|
||||
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||
@@ -97,13 +202,13 @@ func (s *Server) decodeCreateScriptPolicyRequest(r *http.Request) (
|
||||
// Close in reverse order, to match defer behavior.
|
||||
for i := len(closers) - 1; i >= 0; i-- {
|
||||
c := closers[i]
|
||||
merr = multierr.Append(merr, c())
|
||||
merr = errors.Join(merr, c())
|
||||
}
|
||||
return merr
|
||||
}
|
||||
defer func() {
|
||||
if rerr != nil {
|
||||
rerr = multierr.Append(rerr, close())
|
||||
rerr = errors.Join(rerr, close())
|
||||
}
|
||||
}()
|
||||
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||
@@ -143,6 +248,14 @@ func (s *Server) decodeCreateScriptPolicyRequest(r *http.Request) (
|
||||
}
|
||||
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)
|
||||
@@ -150,7 +263,7 @@ func (s *Server) decodeCreateScriptPolicyRequest(r *http.Request) (
|
||||
}
|
||||
|
||||
func (s *Server) decodeCreateSubmissionRequest(r *http.Request) (
|
||||
req *SubmissionCreate,
|
||||
req *SubmissionTriggerCreate,
|
||||
close func() error,
|
||||
rerr error,
|
||||
) {
|
||||
@@ -160,13 +273,13 @@ func (s *Server) decodeCreateSubmissionRequest(r *http.Request) (
|
||||
// Close in reverse order, to match defer behavior.
|
||||
for i := len(closers) - 1; i >= 0; i-- {
|
||||
c := closers[i]
|
||||
merr = multierr.Append(merr, c())
|
||||
merr = errors.Join(merr, c())
|
||||
}
|
||||
return merr
|
||||
}
|
||||
defer func() {
|
||||
if rerr != nil {
|
||||
rerr = multierr.Append(rerr, close())
|
||||
rerr = errors.Join(rerr, close())
|
||||
}
|
||||
}()
|
||||
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||
@@ -189,7 +302,7 @@ func (s *Server) decodeCreateSubmissionRequest(r *http.Request) (
|
||||
|
||||
d := jx.DecodeBytes(buf)
|
||||
|
||||
var request SubmissionCreate
|
||||
var request SubmissionTriggerCreate
|
||||
if err := func() error {
|
||||
if err := request.Decode(d); err != nil {
|
||||
return err
|
||||
@@ -220,6 +333,215 @@ func (s *Server) decodeCreateSubmissionRequest(r *http.Request) (
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) decodeCreateSubmissionAdminRequest(r *http.Request) (
|
||||
req *SubmissionTriggerCreate,
|
||||
close func() error,
|
||||
rerr error,
|
||||
) {
|
||||
var closers []func() error
|
||||
close = func() error {
|
||||
var merr error
|
||||
// Close in reverse order, to match defer behavior.
|
||||
for i := len(closers) - 1; i >= 0; i-- {
|
||||
c := closers[i]
|
||||
merr = errors.Join(merr, c())
|
||||
}
|
||||
return merr
|
||||
}
|
||||
defer func() {
|
||||
if rerr != nil {
|
||||
rerr = errors.Join(rerr, close())
|
||||
}
|
||||
}()
|
||||
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return req, close, errors.Wrap(err, "parse media type")
|
||||
}
|
||||
switch {
|
||||
case ct == "application/json":
|
||||
if r.ContentLength == 0 {
|
||||
return req, close, validate.ErrBodyRequired
|
||||
}
|
||||
buf, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return req, close, err
|
||||
}
|
||||
|
||||
if len(buf) == 0 {
|
||||
return req, close, validate.ErrBodyRequired
|
||||
}
|
||||
|
||||
d := jx.DecodeBytes(buf)
|
||||
|
||||
var request SubmissionTriggerCreate
|
||||
if err := func() error {
|
||||
if err := request.Decode(d); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Skip(); err != io.EOF {
|
||||
return errors.New("unexpected trailing data")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
err = &ogenerrors.DecodeBodyError{
|
||||
ContentType: ct,
|
||||
Body: buf,
|
||||
Err: err,
|
||||
}
|
||||
return req, close, err
|
||||
}
|
||||
if err := func() error {
|
||||
if err := request.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return req, close, errors.Wrap(err, "validate")
|
||||
}
|
||||
return &request, close, nil
|
||||
default:
|
||||
return req, close, validate.InvalidContentType(ct)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) decodeCreateSubmissionAuditCommentRequest(r *http.Request) (
|
||||
req CreateSubmissionAuditCommentReq,
|
||||
close func() error,
|
||||
rerr error,
|
||||
) {
|
||||
var closers []func() error
|
||||
close = func() error {
|
||||
var merr error
|
||||
// Close in reverse order, to match defer behavior.
|
||||
for i := len(closers) - 1; i >= 0; i-- {
|
||||
c := closers[i]
|
||||
merr = errors.Join(merr, c())
|
||||
}
|
||||
return merr
|
||||
}
|
||||
defer func() {
|
||||
if rerr != nil {
|
||||
rerr = errors.Join(rerr, close())
|
||||
}
|
||||
}()
|
||||
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return req, close, errors.Wrap(err, "parse media type")
|
||||
}
|
||||
switch {
|
||||
case ct == "text/plain":
|
||||
reader := r.Body
|
||||
request := CreateSubmissionAuditCommentReq{Data: reader}
|
||||
return request, close, nil
|
||||
default:
|
||||
return req, close, validate.InvalidContentType(ct)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) decodeReleaseSubmissionsRequest(r *http.Request) (
|
||||
req []ReleaseInfo,
|
||||
close func() error,
|
||||
rerr error,
|
||||
) {
|
||||
var closers []func() error
|
||||
close = func() error {
|
||||
var merr error
|
||||
// Close in reverse order, to match defer behavior.
|
||||
for i := len(closers) - 1; i >= 0; i-- {
|
||||
c := closers[i]
|
||||
merr = errors.Join(merr, c())
|
||||
}
|
||||
return merr
|
||||
}
|
||||
defer func() {
|
||||
if rerr != nil {
|
||||
rerr = errors.Join(rerr, close())
|
||||
}
|
||||
}()
|
||||
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return req, close, errors.Wrap(err, "parse media type")
|
||||
}
|
||||
switch {
|
||||
case ct == "application/json":
|
||||
if r.ContentLength == 0 {
|
||||
return req, close, validate.ErrBodyRequired
|
||||
}
|
||||
buf, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return req, close, err
|
||||
}
|
||||
|
||||
if len(buf) == 0 {
|
||||
return req, close, validate.ErrBodyRequired
|
||||
}
|
||||
|
||||
d := jx.DecodeBytes(buf)
|
||||
|
||||
var request []ReleaseInfo
|
||||
if err := func() error {
|
||||
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
|
||||
}
|
||||
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 request == nil {
|
||||
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")
|
||||
}
|
||||
var failures []validate.FieldError
|
||||
for i, elem := range request {
|
||||
if err := func() error {
|
||||
if err := elem.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: fmt.Sprintf("[%d]", i),
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return req, close, errors.Wrap(err, "validate")
|
||||
}
|
||||
return request, close, nil
|
||||
default:
|
||||
return req, close, validate.InvalidContentType(ct)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) decodeUpdateScriptRequest(r *http.Request) (
|
||||
req *ScriptUpdate,
|
||||
close func() error,
|
||||
@@ -231,13 +553,13 @@ func (s *Server) decodeUpdateScriptRequest(r *http.Request) (
|
||||
// Close in reverse order, to match defer behavior.
|
||||
for i := len(closers) - 1; i >= 0; i-- {
|
||||
c := closers[i]
|
||||
merr = multierr.Append(merr, c())
|
||||
merr = errors.Join(merr, c())
|
||||
}
|
||||
return merr
|
||||
}
|
||||
defer func() {
|
||||
if rerr != nil {
|
||||
rerr = multierr.Append(rerr, close())
|
||||
rerr = errors.Join(rerr, close())
|
||||
}
|
||||
}()
|
||||
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||
@@ -302,13 +624,13 @@ func (s *Server) decodeUpdateScriptPolicyRequest(r *http.Request) (
|
||||
// Close in reverse order, to match defer behavior.
|
||||
for i := len(closers) - 1; i >= 0; i-- {
|
||||
c := closers[i]
|
||||
merr = multierr.Append(merr, c())
|
||||
merr = errors.Join(merr, c())
|
||||
}
|
||||
return merr
|
||||
}
|
||||
defer func() {
|
||||
if rerr != nil {
|
||||
rerr = multierr.Append(rerr, close())
|
||||
rerr = errors.Join(rerr, close())
|
||||
}
|
||||
}()
|
||||
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||
@@ -348,6 +670,14 @@ func (s *Server) decodeUpdateScriptPolicyRequest(r *http.Request) (
|
||||
}
|
||||
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)
|
||||
|
||||
@@ -11,6 +11,30 @@ import (
|
||||
ht "github.com/ogen-go/ogen/http"
|
||||
)
|
||||
|
||||
func encodeCreateMapfixRequest(
|
||||
req *MapfixTriggerCreate,
|
||||
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 encodeCreateMapfixAuditCommentRequest(
|
||||
req CreateMapfixAuditCommentReq,
|
||||
r *http.Request,
|
||||
) error {
|
||||
const contentType = "text/plain"
|
||||
body := req
|
||||
ht.SetBody(r, body, contentType)
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeCreateScriptRequest(
|
||||
req *ScriptCreate,
|
||||
r *http.Request,
|
||||
@@ -40,7 +64,7 @@ func encodeCreateScriptPolicyRequest(
|
||||
}
|
||||
|
||||
func encodeCreateSubmissionRequest(
|
||||
req *SubmissionCreate,
|
||||
req *SubmissionTriggerCreate,
|
||||
r *http.Request,
|
||||
) error {
|
||||
const contentType = "application/json"
|
||||
@@ -53,6 +77,48 @@ func encodeCreateSubmissionRequest(
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeCreateSubmissionAdminRequest(
|
||||
req *SubmissionTriggerCreate,
|
||||
r *http.Request,
|
||||
) error {
|
||||
const contentType = "application/json"
|
||||
e := new(jx.Encoder)
|
||||
{
|
||||
req.Encode(e)
|
||||
}
|
||||
encoded := e.Bytes()
|
||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeCreateSubmissionAuditCommentRequest(
|
||||
req CreateSubmissionAuditCommentReq,
|
||||
r *http.Request,
|
||||
) error {
|
||||
const contentType = "text/plain"
|
||||
body := req
|
||||
ht.SetBody(r, body, contentType)
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeReleaseSubmissionsRequest(
|
||||
req []ReleaseInfo,
|
||||
r *http.Request,
|
||||
) error {
|
||||
const contentType = "application/json"
|
||||
e := new(jx.Encoder)
|
||||
{
|
||||
e.ArrStart()
|
||||
for _, elem := range req {
|
||||
elem.Encode(e)
|
||||
}
|
||||
e.ArrEnd()
|
||||
}
|
||||
encoded := e.Bytes()
|
||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeUpdateScriptRequest(
|
||||
req *ScriptUpdate,
|
||||
r *http.Request,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
@@ -13,7 +14,84 @@ import (
|
||||
ht "github.com/ogen-go/ogen/http"
|
||||
)
|
||||
|
||||
func encodeActionSubmissionPublishResponse(response *ActionSubmissionPublishNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
func encodeActionMapfixAcceptedResponse(response *ActionMapfixAcceptedNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeActionMapfixRejectResponse(response *ActionMapfixRejectNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeActionMapfixRequestChangesResponse(response *ActionMapfixRequestChangesNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeActionMapfixResetSubmittingResponse(response *ActionMapfixResetSubmittingNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeActionMapfixRetryValidateResponse(response *ActionMapfixRetryValidateNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeActionMapfixRevokeResponse(response *ActionMapfixRevokeNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeActionMapfixTriggerSubmitResponse(response *ActionMapfixTriggerSubmitNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeActionMapfixTriggerSubmitUncheckedResponse(response *ActionMapfixTriggerSubmitUncheckedNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeActionMapfixTriggerUploadResponse(response *ActionMapfixTriggerUploadNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeActionMapfixTriggerValidateResponse(response *ActionMapfixTriggerValidateNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeActionMapfixValidatedResponse(response *ActionMapfixValidatedNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeActionSubmissionAcceptedResponse(response *ActionSubmissionAcceptedNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
@@ -34,6 +112,20 @@ func encodeActionSubmissionRequestChangesResponse(response *ActionSubmissionRequ
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeActionSubmissionResetSubmittingResponse(response *ActionSubmissionResetSubmittingNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeActionSubmissionRetryValidateResponse(response *ActionSubmissionRetryValidateNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeActionSubmissionRevokeResponse(response *ActionSubmissionRevokeNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
@@ -41,14 +133,21 @@ func encodeActionSubmissionRevokeResponse(response *ActionSubmissionRevokeNoCont
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeActionSubmissionSubmitResponse(response *ActionSubmissionSubmitNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
func encodeActionSubmissionTriggerSubmitResponse(response *ActionSubmissionTriggerSubmitNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeActionSubmissionTriggerPublishResponse(response *ActionSubmissionTriggerPublishNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
func encodeActionSubmissionTriggerSubmitUncheckedResponse(response *ActionSubmissionTriggerSubmitUncheckedNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeActionSubmissionTriggerUploadResponse(response *ActionSubmissionTriggerUploadNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
@@ -62,14 +161,14 @@ func encodeActionSubmissionTriggerValidateResponse(response *ActionSubmissionTri
|
||||
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)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeCreateScriptResponse(response *ID, w http.ResponseWriter, span trace.Span) error {
|
||||
func encodeCreateMapfixResponse(response *OperationID, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(201)
|
||||
span.SetStatus(codes.Ok, http.StatusText(201))
|
||||
@@ -83,7 +182,14 @@ func encodeCreateScriptResponse(response *ID, w http.ResponseWriter, span trace.
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeCreateScriptPolicyResponse(response *ID, w http.ResponseWriter, span trace.Span) error {
|
||||
func encodeCreateMapfixAuditCommentResponse(response *CreateMapfixAuditCommentNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeCreateScriptResponse(response *ScriptID, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(201)
|
||||
span.SetStatus(codes.Ok, http.StatusText(201))
|
||||
@@ -97,7 +203,7 @@ func encodeCreateScriptPolicyResponse(response *ID, w http.ResponseWriter, span
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeCreateSubmissionResponse(response *ID, w http.ResponseWriter, span trace.Span) error {
|
||||
func encodeCreateScriptPolicyResponse(response *ScriptPolicyID, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(201)
|
||||
span.SetStatus(codes.Ok, http.StatusText(201))
|
||||
@@ -111,6 +217,41 @@ func encodeCreateSubmissionResponse(response *ID, w http.ResponseWriter, span tr
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeCreateSubmissionResponse(response *OperationID, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(201)
|
||||
span.SetStatus(codes.Ok, http.StatusText(201))
|
||||
|
||||
e := new(jx.Encoder)
|
||||
response.Encode(e)
|
||||
if _, err := e.WriteTo(w); err != nil {
|
||||
return errors.Wrap(err, "write")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeCreateSubmissionAdminResponse(response *OperationID, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(201)
|
||||
span.SetStatus(codes.Ok, http.StatusText(201))
|
||||
|
||||
e := new(jx.Encoder)
|
||||
response.Encode(e)
|
||||
if _, err := e.WriteTo(w); err != nil {
|
||||
return errors.Wrap(err, "write")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeCreateSubmissionAuditCommentResponse(response *CreateSubmissionAuditCommentNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeDeleteScriptResponse(response *DeleteScriptNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
@@ -125,6 +266,64 @@ func encodeDeleteScriptPolicyResponse(response *DeleteScriptPolicyNoContent, w h
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeDownloadMapAssetResponse(response DownloadMapAssetOK, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.WriteHeader(200)
|
||||
span.SetStatus(codes.Ok, http.StatusText(200))
|
||||
|
||||
writer := w
|
||||
if closer, ok := response.Data.(io.Closer); ok {
|
||||
defer closer.Close()
|
||||
}
|
||||
if _, err := io.Copy(writer, response); err != nil {
|
||||
return errors.Wrap(err, "write")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeGetMapResponse(response *Map, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(200)
|
||||
span.SetStatus(codes.Ok, http.StatusText(200))
|
||||
|
||||
e := new(jx.Encoder)
|
||||
response.Encode(e)
|
||||
if _, err := e.WriteTo(w); err != nil {
|
||||
return errors.Wrap(err, "write")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeGetMapfixResponse(response *Mapfix, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(200)
|
||||
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 encodeGetOperationResponse(response *Operation, 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 encodeGetScriptResponse(response *Script, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(200)
|
||||
@@ -153,20 +352,6 @@ func encodeGetScriptPolicyResponse(response *ScriptPolicy, w http.ResponseWriter
|
||||
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 {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(200)
|
||||
@@ -181,7 +366,7 @@ func encodeGetSubmissionResponse(response *Submission, w http.ResponseWriter, sp
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeListSubmissionsResponse(response []Submission, w http.ResponseWriter, span trace.Span) error {
|
||||
func encodeListMapfixAuditEventsResponse(response []AuditEvent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(200)
|
||||
span.SetStatus(codes.Ok, http.StatusText(200))
|
||||
@@ -199,6 +384,162 @@ func encodeListSubmissionsResponse(response []Submission, w http.ResponseWriter,
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeListMapfixesResponse(response *Mapfixes, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(200)
|
||||
span.SetStatus(codes.Ok, http.StatusText(200))
|
||||
|
||||
e := new(jx.Encoder)
|
||||
response.Encode(e)
|
||||
if _, err := e.WriteTo(w); err != nil {
|
||||
return errors.Wrap(err, "write")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeListMapsResponse(response []Map, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(200)
|
||||
span.SetStatus(codes.Ok, http.StatusText(200))
|
||||
|
||||
e := new(jx.Encoder)
|
||||
e.ArrStart()
|
||||
for _, elem := range response {
|
||||
elem.Encode(e)
|
||||
}
|
||||
e.ArrEnd()
|
||||
if _, err := e.WriteTo(w); err != nil {
|
||||
return errors.Wrap(err, "write")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeListScriptPolicyResponse(response []ScriptPolicy, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(200)
|
||||
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 encodeListSubmissionAuditEventsResponse(response []AuditEvent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(200)
|
||||
span.SetStatus(codes.Ok, http.StatusText(200))
|
||||
|
||||
e := new(jx.Encoder)
|
||||
e.ArrStart()
|
||||
for _, elem := range response {
|
||||
elem.Encode(e)
|
||||
}
|
||||
e.ArrEnd()
|
||||
if _, err := e.WriteTo(w); err != nil {
|
||||
return errors.Wrap(err, "write")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeListSubmissionsResponse(response *Submissions, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(200)
|
||||
span.SetStatus(codes.Ok, http.StatusText(200))
|
||||
|
||||
e := new(jx.Encoder)
|
||||
response.Encode(e)
|
||||
if _, err := e.WriteTo(w); err != nil {
|
||||
return errors.Wrap(err, "write")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeReleaseSubmissionsResponse(response *ReleaseSubmissionsCreated, w http.ResponseWriter, span trace.Span) error {
|
||||
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 encodeSetMapfixCompletedResponse(response *SetMapfixCompletedNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeSetSubmissionCompletedResponse(response *SetSubmissionCompletedNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
@@ -206,6 +547,13 @@ func encodeSetSubmissionCompletedResponse(response *SetSubmissionCompletedNoCont
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeUpdateMapfixModelResponse(response *UpdateMapfixModelNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeUpdateScriptResponse(response *UpdateScriptNoContent, w http.ResponseWriter, span trace.Span) error {
|
||||
w.WriteHeader(204)
|
||||
span.SetStatus(codes.Ok, http.StatusText(204))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -33,6 +33,52 @@ func findAuthorization(h http.Header, prefix string) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
var operationRolesCookieAuth = map[string][]string{
|
||||
ActionMapfixAcceptedOperation: []string{},
|
||||
ActionMapfixRejectOperation: []string{},
|
||||
ActionMapfixRequestChangesOperation: []string{},
|
||||
ActionMapfixResetSubmittingOperation: []string{},
|
||||
ActionMapfixRetryValidateOperation: []string{},
|
||||
ActionMapfixRevokeOperation: []string{},
|
||||
ActionMapfixTriggerSubmitOperation: []string{},
|
||||
ActionMapfixTriggerSubmitUncheckedOperation: []string{},
|
||||
ActionMapfixTriggerUploadOperation: []string{},
|
||||
ActionMapfixTriggerValidateOperation: []string{},
|
||||
ActionMapfixValidatedOperation: []string{},
|
||||
ActionSubmissionAcceptedOperation: []string{},
|
||||
ActionSubmissionRejectOperation: []string{},
|
||||
ActionSubmissionRequestChangesOperation: []string{},
|
||||
ActionSubmissionResetSubmittingOperation: []string{},
|
||||
ActionSubmissionRetryValidateOperation: []string{},
|
||||
ActionSubmissionRevokeOperation: []string{},
|
||||
ActionSubmissionTriggerSubmitOperation: []string{},
|
||||
ActionSubmissionTriggerSubmitUncheckedOperation: []string{},
|
||||
ActionSubmissionTriggerUploadOperation: []string{},
|
||||
ActionSubmissionTriggerValidateOperation: []string{},
|
||||
ActionSubmissionValidatedOperation: []string{},
|
||||
CreateMapfixOperation: []string{},
|
||||
CreateMapfixAuditCommentOperation: []string{},
|
||||
CreateScriptOperation: []string{},
|
||||
CreateScriptPolicyOperation: []string{},
|
||||
CreateSubmissionOperation: []string{},
|
||||
CreateSubmissionAdminOperation: []string{},
|
||||
CreateSubmissionAuditCommentOperation: []string{},
|
||||
DeleteScriptOperation: []string{},
|
||||
DeleteScriptPolicyOperation: []string{},
|
||||
DownloadMapAssetOperation: []string{},
|
||||
GetOperationOperation: []string{},
|
||||
ReleaseSubmissionsOperation: []string{},
|
||||
SessionRolesOperation: []string{},
|
||||
SessionUserOperation: []string{},
|
||||
SessionValidateOperation: []string{},
|
||||
SetMapfixCompletedOperation: []string{},
|
||||
SetSubmissionCompletedOperation: []string{},
|
||||
UpdateMapfixModelOperation: []string{},
|
||||
UpdateScriptOperation: []string{},
|
||||
UpdateScriptPolicyOperation: []string{},
|
||||
UpdateSubmissionModelOperation: []string{},
|
||||
}
|
||||
|
||||
func (s *Server) securityCookieAuth(ctx context.Context, operationName OperationName, req *http.Request) (context.Context, bool, error) {
|
||||
var t CookieAuth
|
||||
const parameterName = "session_id"
|
||||
@@ -46,6 +92,7 @@ func (s *Server) securityCookieAuth(ctx context.Context, operationName Operation
|
||||
return nil, false, errors.Wrap(err, "get cookie value")
|
||||
}
|
||||
t.APIKey = value
|
||||
t.Roles = operationRolesCookieAuth[operationName]
|
||||
rctx, err := s.sec.HandleCookieAuth(ctx, operationName, t)
|
||||
if errors.Is(err, ogenerrors.ErrSkipServerSecurity) {
|
||||
return nil, false, nil
|
||||
|
||||
@@ -8,12 +8,79 @@ import (
|
||||
|
||||
// Handler handles operations described by OpenAPI v3 specification.
|
||||
type Handler interface {
|
||||
// ActionSubmissionPublish implements actionSubmissionPublish operation.
|
||||
// ActionMapfixAccepted implements actionMapfixAccepted 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
|
||||
ActionSubmissionPublish(ctx context.Context, params ActionSubmissionPublishParams) error
|
||||
// POST /mapfixes/{MapfixID}/status/reset-validating
|
||||
ActionMapfixAccepted(ctx context.Context, params ActionMapfixAcceptedParams) error
|
||||
// ActionMapfixReject implements actionMapfixReject operation.
|
||||
//
|
||||
// Role Reviewer changes status from Submitted -> Rejected.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/reject
|
||||
ActionMapfixReject(ctx context.Context, params ActionMapfixRejectParams) error
|
||||
// ActionMapfixRequestChanges implements actionMapfixRequestChanges operation.
|
||||
//
|
||||
// Role Reviewer changes status from Validated|Accepted|Submitted -> ChangesRequested.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/request-changes
|
||||
ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error
|
||||
// ActionMapfixResetSubmitting implements actionMapfixResetSubmitting operation.
|
||||
//
|
||||
// Role Submitter manually resets submitting softlock and changes status from Submitting ->
|
||||
// UnderConstruction.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/reset-submitting
|
||||
ActionMapfixResetSubmitting(ctx context.Context, params ActionMapfixResetSubmittingParams) error
|
||||
// ActionMapfixRetryValidate implements actionMapfixRetryValidate operation.
|
||||
//
|
||||
// Role Reviewer re-runs validation and changes status from Accepted -> Validating.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/retry-validate
|
||||
ActionMapfixRetryValidate(ctx context.Context, params ActionMapfixRetryValidateParams) error
|
||||
// ActionMapfixRevoke implements actionMapfixRevoke operation.
|
||||
//
|
||||
// Role Submitter changes status from Submitted|ChangesRequested -> UnderConstruction.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/revoke
|
||||
ActionMapfixRevoke(ctx context.Context, params ActionMapfixRevokeParams) error
|
||||
// ActionMapfixTriggerSubmit implements actionMapfixTriggerSubmit operation.
|
||||
//
|
||||
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/trigger-submit
|
||||
ActionMapfixTriggerSubmit(ctx context.Context, params ActionMapfixTriggerSubmitParams) error
|
||||
// ActionMapfixTriggerSubmitUnchecked implements actionMapfixTriggerSubmitUnchecked operation.
|
||||
//
|
||||
// Role Reviewer changes status from ChangesRequested -> Submitting.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/trigger-submit-unchecked
|
||||
ActionMapfixTriggerSubmitUnchecked(ctx context.Context, params ActionMapfixTriggerSubmitUncheckedParams) error
|
||||
// ActionMapfixTriggerUpload implements actionMapfixTriggerUpload operation.
|
||||
//
|
||||
// Role Admin changes status from Validated -> Uploading.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/trigger-upload
|
||||
ActionMapfixTriggerUpload(ctx context.Context, params ActionMapfixTriggerUploadParams) error
|
||||
// ActionMapfixTriggerValidate implements actionMapfixTriggerValidate operation.
|
||||
//
|
||||
// Role Reviewer triggers validation and changes status from Submitted -> Validating.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/trigger-validate
|
||||
ActionMapfixTriggerValidate(ctx context.Context, params ActionMapfixTriggerValidateParams) error
|
||||
// ActionMapfixValidated implements actionMapfixValidated operation.
|
||||
//
|
||||
// Role Admin manually resets uploading softlock and changes status from Uploading -> Validated.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/reset-uploading
|
||||
ActionMapfixValidated(ctx context.Context, params ActionMapfixValidatedParams) error
|
||||
// ActionSubmissionAccepted implements actionSubmissionAccepted operation.
|
||||
//
|
||||
// Role Reviewer manually resets validating softlock and changes status from Validating -> Accepted.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/reset-validating
|
||||
ActionSubmissionAccepted(ctx context.Context, params ActionSubmissionAcceptedParams) error
|
||||
// ActionSubmissionReject implements actionSubmissionReject operation.
|
||||
//
|
||||
// Role Reviewer changes status from Submitted -> Rejected.
|
||||
@@ -26,54 +93,97 @@ type Handler interface {
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/request-changes
|
||||
ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error
|
||||
// ActionSubmissionResetSubmitting implements actionSubmissionResetSubmitting operation.
|
||||
//
|
||||
// Role Submitter manually resets submitting softlock and changes status from Submitting ->
|
||||
// UnderConstruction.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/reset-submitting
|
||||
ActionSubmissionResetSubmitting(ctx context.Context, params ActionSubmissionResetSubmittingParams) error
|
||||
// ActionSubmissionRetryValidate implements actionSubmissionRetryValidate operation.
|
||||
//
|
||||
// Role Reviewer re-runs validation and changes status from Accepted -> Validating.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/retry-validate
|
||||
ActionSubmissionRetryValidate(ctx context.Context, params ActionSubmissionRetryValidateParams) error
|
||||
// ActionSubmissionRevoke implements actionSubmissionRevoke operation.
|
||||
//
|
||||
// Role Submitter changes status from Submitted|ChangesRequested -> UnderConstruction.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/revoke
|
||||
ActionSubmissionRevoke(ctx context.Context, params ActionSubmissionRevokeParams) error
|
||||
// ActionSubmissionSubmit implements actionSubmissionSubmit operation.
|
||||
// ActionSubmissionTriggerSubmit implements actionSubmissionTriggerSubmit operation.
|
||||
//
|
||||
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted.
|
||||
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/submit
|
||||
ActionSubmissionSubmit(ctx context.Context, params ActionSubmissionSubmitParams) error
|
||||
// ActionSubmissionTriggerPublish implements actionSubmissionTriggerPublish operation.
|
||||
// POST /submissions/{SubmissionID}/status/trigger-submit
|
||||
ActionSubmissionTriggerSubmit(ctx context.Context, params ActionSubmissionTriggerSubmitParams) error
|
||||
// ActionSubmissionTriggerSubmitUnchecked implements actionSubmissionTriggerSubmitUnchecked operation.
|
||||
//
|
||||
// Role Admin changes status from Validated -> Publishing.
|
||||
// Role Reviewer changes status from ChangesRequested -> Submitting.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/trigger-publish
|
||||
ActionSubmissionTriggerPublish(ctx context.Context, params ActionSubmissionTriggerPublishParams) error
|
||||
// POST /submissions/{SubmissionID}/status/trigger-submit-unchecked
|
||||
ActionSubmissionTriggerSubmitUnchecked(ctx context.Context, params ActionSubmissionTriggerSubmitUncheckedParams) error
|
||||
// ActionSubmissionTriggerUpload implements actionSubmissionTriggerUpload operation.
|
||||
//
|
||||
// Role Admin changes status from Validated -> Uploading.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/trigger-upload
|
||||
ActionSubmissionTriggerUpload(ctx context.Context, params ActionSubmissionTriggerUploadParams) error
|
||||
// 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
|
||||
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
|
||||
ActionSubmissionValidate(ctx context.Context, params ActionSubmissionValidateParams) error
|
||||
// POST /submissions/{SubmissionID}/status/reset-uploading
|
||||
ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error
|
||||
// CreateMapfix implements createMapfix operation.
|
||||
//
|
||||
// Trigger the validator to create a mapfix.
|
||||
//
|
||||
// POST /mapfixes
|
||||
CreateMapfix(ctx context.Context, req *MapfixTriggerCreate) (*OperationID, error)
|
||||
// CreateMapfixAuditComment implements createMapfixAuditComment operation.
|
||||
//
|
||||
// Post a comment to the audit log.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/comment
|
||||
CreateMapfixAuditComment(ctx context.Context, req CreateMapfixAuditCommentReq, params CreateMapfixAuditCommentParams) error
|
||||
// CreateScript implements createScript operation.
|
||||
//
|
||||
// Create a new script.
|
||||
//
|
||||
// POST /scripts
|
||||
CreateScript(ctx context.Context, req *ScriptCreate) (*ID, error)
|
||||
CreateScript(ctx context.Context, req *ScriptCreate) (*ScriptID, error)
|
||||
// CreateScriptPolicy implements createScriptPolicy operation.
|
||||
//
|
||||
// Create a new script policy.
|
||||
//
|
||||
// POST /script-policy
|
||||
CreateScriptPolicy(ctx context.Context, req *ScriptPolicyCreate) (*ID, error)
|
||||
CreateScriptPolicy(ctx context.Context, req *ScriptPolicyCreate) (*ScriptPolicyID, error)
|
||||
// CreateSubmission implements createSubmission operation.
|
||||
//
|
||||
// Create new submission.
|
||||
// Trigger the validator to create a new submission.
|
||||
//
|
||||
// POST /submissions
|
||||
CreateSubmission(ctx context.Context, req *SubmissionCreate) (*ID, error)
|
||||
CreateSubmission(ctx context.Context, req *SubmissionTriggerCreate) (*OperationID, error)
|
||||
// CreateSubmissionAdmin implements createSubmissionAdmin operation.
|
||||
//
|
||||
// Trigger the validator to create a new submission.
|
||||
//
|
||||
// POST /submissions-admin
|
||||
CreateSubmissionAdmin(ctx context.Context, req *SubmissionTriggerCreate) (*OperationID, error)
|
||||
// CreateSubmissionAuditComment implements createSubmissionAuditComment operation.
|
||||
//
|
||||
// Post a comment to the audit log.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/comment
|
||||
CreateSubmissionAuditComment(ctx context.Context, req CreateSubmissionAuditCommentReq, params CreateSubmissionAuditCommentParams) error
|
||||
// DeleteScript implements deleteScript operation.
|
||||
//
|
||||
// Delete the specified script by ID.
|
||||
@@ -84,8 +194,32 @@ type Handler interface {
|
||||
//
|
||||
// Delete the specified script policy by ID.
|
||||
//
|
||||
// DELETE /script-policy/id/{ScriptPolicyID}
|
||||
// DELETE /script-policy/{ScriptPolicyID}
|
||||
DeleteScriptPolicy(ctx context.Context, params DeleteScriptPolicyParams) error
|
||||
// DownloadMapAsset implements downloadMapAsset operation.
|
||||
//
|
||||
// Download the map asset.
|
||||
//
|
||||
// GET /maps/{MapID}/download
|
||||
DownloadMapAsset(ctx context.Context, params DownloadMapAssetParams) (DownloadMapAssetOK, error)
|
||||
// GetMap implements getMap operation.
|
||||
//
|
||||
// Retrieve map with ID.
|
||||
//
|
||||
// GET /maps/{MapID}
|
||||
GetMap(ctx context.Context, params GetMapParams) (*Map, error)
|
||||
// GetMapfix implements getMapfix operation.
|
||||
//
|
||||
// Retrieve map with ID.
|
||||
//
|
||||
// GET /mapfixes/{MapfixID}
|
||||
GetMapfix(ctx context.Context, params GetMapfixParams) (*Mapfix, error)
|
||||
// GetOperation implements getOperation operation.
|
||||
//
|
||||
// Retrieve operation with ID.
|
||||
//
|
||||
// GET /operations/{OperationID}
|
||||
GetOperation(ctx context.Context, params GetOperationParams) (*Operation, error)
|
||||
// GetScript implements getScript operation.
|
||||
//
|
||||
// Get the specified script by ID.
|
||||
@@ -96,32 +230,98 @@ type Handler interface {
|
||||
//
|
||||
// Get the specified script policy by ID.
|
||||
//
|
||||
// GET /script-policy/id/{ScriptPolicyID}
|
||||
// GET /script-policy/{ScriptPolicyID}
|
||||
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.
|
||||
//
|
||||
// Retrieve map with ID.
|
||||
//
|
||||
// GET /submissions/{SubmissionID}
|
||||
GetSubmission(ctx context.Context, params GetSubmissionParams) (*Submission, error)
|
||||
// ListMapfixAuditEvents implements listMapfixAuditEvents operation.
|
||||
//
|
||||
// Retrieve a list of audit events.
|
||||
//
|
||||
// GET /mapfixes/{MapfixID}/audit-events
|
||||
ListMapfixAuditEvents(ctx context.Context, params ListMapfixAuditEventsParams) ([]AuditEvent, error)
|
||||
// ListMapfixes implements listMapfixes operation.
|
||||
//
|
||||
// Get list of mapfixes.
|
||||
//
|
||||
// GET /mapfixes
|
||||
ListMapfixes(ctx context.Context, params ListMapfixesParams) (*Mapfixes, error)
|
||||
// ListMaps implements listMaps operation.
|
||||
//
|
||||
// Get list of maps.
|
||||
//
|
||||
// GET /maps
|
||||
ListMaps(ctx context.Context, params ListMapsParams) ([]Map, error)
|
||||
// ListScriptPolicy implements listScriptPolicy operation.
|
||||
//
|
||||
// Get list of script policies.
|
||||
//
|
||||
// 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)
|
||||
// ListSubmissionAuditEvents implements listSubmissionAuditEvents operation.
|
||||
//
|
||||
// Retrieve a list of audit events.
|
||||
//
|
||||
// GET /submissions/{SubmissionID}/audit-events
|
||||
ListSubmissionAuditEvents(ctx context.Context, params ListSubmissionAuditEventsParams) ([]AuditEvent, error)
|
||||
// ListSubmissions implements listSubmissions operation.
|
||||
//
|
||||
// Get list of submissions.
|
||||
//
|
||||
// GET /submissions
|
||||
ListSubmissions(ctx context.Context, params ListSubmissionsParams) ([]Submission, error)
|
||||
ListSubmissions(ctx context.Context, params ListSubmissionsParams) (*Submissions, 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)
|
||||
// SetMapfixCompleted implements setMapfixCompleted operation.
|
||||
//
|
||||
// Called by maptest when a player completes the map.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/completed
|
||||
SetMapfixCompleted(ctx context.Context, params SetMapfixCompletedParams) error
|
||||
// SetSubmissionCompleted implements setSubmissionCompleted operation.
|
||||
//
|
||||
// Retrieve map with ID.
|
||||
// Called by maptest when a player completes the map.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/completed
|
||||
SetSubmissionCompleted(ctx context.Context, params SetSubmissionCompletedParams) error
|
||||
// UpdateMapfixModel implements updateMapfixModel operation.
|
||||
//
|
||||
// Update model following role restrictions.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/model
|
||||
UpdateMapfixModel(ctx context.Context, params UpdateMapfixModelParams) error
|
||||
// UpdateScript implements updateScript operation.
|
||||
//
|
||||
// Update the specified script by ID.
|
||||
@@ -132,7 +332,7 @@ type Handler interface {
|
||||
//
|
||||
// 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
|
||||
// UpdateSubmissionModel implements updateSubmissionModel operation.
|
||||
//
|
||||
|
||||
@@ -13,12 +13,112 @@ type UnimplementedHandler struct{}
|
||||
|
||||
var _ Handler = UnimplementedHandler{}
|
||||
|
||||
// ActionSubmissionPublish implements actionSubmissionPublish operation.
|
||||
// ActionMapfixAccepted implements actionMapfixAccepted 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
|
||||
func (UnimplementedHandler) ActionSubmissionPublish(ctx context.Context, params ActionSubmissionPublishParams) error {
|
||||
// POST /mapfixes/{MapfixID}/status/reset-validating
|
||||
func (UnimplementedHandler) ActionMapfixAccepted(ctx context.Context, params ActionMapfixAcceptedParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ActionMapfixReject implements actionMapfixReject operation.
|
||||
//
|
||||
// Role Reviewer changes status from Submitted -> Rejected.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/reject
|
||||
func (UnimplementedHandler) ActionMapfixReject(ctx context.Context, params ActionMapfixRejectParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ActionMapfixRequestChanges implements actionMapfixRequestChanges operation.
|
||||
//
|
||||
// Role Reviewer changes status from Validated|Accepted|Submitted -> ChangesRequested.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/request-changes
|
||||
func (UnimplementedHandler) ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ActionMapfixResetSubmitting implements actionMapfixResetSubmitting operation.
|
||||
//
|
||||
// Role Submitter manually resets submitting softlock and changes status from Submitting ->
|
||||
// UnderConstruction.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/reset-submitting
|
||||
func (UnimplementedHandler) ActionMapfixResetSubmitting(ctx context.Context, params ActionMapfixResetSubmittingParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ActionMapfixRetryValidate implements actionMapfixRetryValidate operation.
|
||||
//
|
||||
// Role Reviewer re-runs validation and changes status from Accepted -> Validating.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/retry-validate
|
||||
func (UnimplementedHandler) ActionMapfixRetryValidate(ctx context.Context, params ActionMapfixRetryValidateParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ActionMapfixRevoke implements actionMapfixRevoke operation.
|
||||
//
|
||||
// Role Submitter changes status from Submitted|ChangesRequested -> UnderConstruction.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/revoke
|
||||
func (UnimplementedHandler) ActionMapfixRevoke(ctx context.Context, params ActionMapfixRevokeParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ActionMapfixTriggerSubmit implements actionMapfixTriggerSubmit operation.
|
||||
//
|
||||
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/trigger-submit
|
||||
func (UnimplementedHandler) ActionMapfixTriggerSubmit(ctx context.Context, params ActionMapfixTriggerSubmitParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ActionMapfixTriggerSubmitUnchecked implements actionMapfixTriggerSubmitUnchecked operation.
|
||||
//
|
||||
// Role Reviewer changes status from ChangesRequested -> Submitting.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/trigger-submit-unchecked
|
||||
func (UnimplementedHandler) ActionMapfixTriggerSubmitUnchecked(ctx context.Context, params ActionMapfixTriggerSubmitUncheckedParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ActionMapfixTriggerUpload implements actionMapfixTriggerUpload operation.
|
||||
//
|
||||
// Role Admin changes status from Validated -> Uploading.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/trigger-upload
|
||||
func (UnimplementedHandler) ActionMapfixTriggerUpload(ctx context.Context, params ActionMapfixTriggerUploadParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ActionMapfixTriggerValidate implements actionMapfixTriggerValidate operation.
|
||||
//
|
||||
// Role Reviewer triggers validation and changes status from Submitted -> Validating.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/trigger-validate
|
||||
func (UnimplementedHandler) ActionMapfixTriggerValidate(ctx context.Context, params ActionMapfixTriggerValidateParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ActionMapfixValidated implements actionMapfixValidated operation.
|
||||
//
|
||||
// Role Admin manually resets uploading softlock and changes status from Uploading -> Validated.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/reset-uploading
|
||||
func (UnimplementedHandler) ActionMapfixValidated(ctx context.Context, params ActionMapfixValidatedParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ActionSubmissionAccepted implements actionSubmissionAccepted operation.
|
||||
//
|
||||
// Role Reviewer manually resets validating softlock and changes status from Validating -> Accepted.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/reset-validating
|
||||
func (UnimplementedHandler) ActionSubmissionAccepted(ctx context.Context, params ActionSubmissionAcceptedParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
@@ -40,6 +140,25 @@ func (UnimplementedHandler) ActionSubmissionRequestChanges(ctx context.Context,
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ActionSubmissionResetSubmitting implements actionSubmissionResetSubmitting operation.
|
||||
//
|
||||
// Role Submitter manually resets submitting softlock and changes status from Submitting ->
|
||||
// UnderConstruction.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/reset-submitting
|
||||
func (UnimplementedHandler) ActionSubmissionResetSubmitting(ctx context.Context, params ActionSubmissionResetSubmittingParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ActionSubmissionRetryValidate implements actionSubmissionRetryValidate operation.
|
||||
//
|
||||
// Role Reviewer re-runs validation and changes status from Accepted -> Validating.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/retry-validate
|
||||
func (UnimplementedHandler) ActionSubmissionRetryValidate(ctx context.Context, params ActionSubmissionRetryValidateParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ActionSubmissionRevoke implements actionSubmissionRevoke operation.
|
||||
//
|
||||
// Role Submitter changes status from Submitted|ChangesRequested -> UnderConstruction.
|
||||
@@ -49,39 +168,66 @@ func (UnimplementedHandler) ActionSubmissionRevoke(ctx context.Context, params A
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ActionSubmissionSubmit implements actionSubmissionSubmit operation.
|
||||
// ActionSubmissionTriggerSubmit implements actionSubmissionTriggerSubmit operation.
|
||||
//
|
||||
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted.
|
||||
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/submit
|
||||
func (UnimplementedHandler) ActionSubmissionSubmit(ctx context.Context, params ActionSubmissionSubmitParams) error {
|
||||
// POST /submissions/{SubmissionID}/status/trigger-submit
|
||||
func (UnimplementedHandler) ActionSubmissionTriggerSubmit(ctx context.Context, params ActionSubmissionTriggerSubmitParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ActionSubmissionTriggerPublish implements actionSubmissionTriggerPublish operation.
|
||||
// ActionSubmissionTriggerSubmitUnchecked implements actionSubmissionTriggerSubmitUnchecked operation.
|
||||
//
|
||||
// Role Admin changes status from Validated -> Publishing.
|
||||
// Role Reviewer changes status from ChangesRequested -> Submitting.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/trigger-publish
|
||||
func (UnimplementedHandler) ActionSubmissionTriggerPublish(ctx context.Context, params ActionSubmissionTriggerPublishParams) error {
|
||||
// POST /submissions/{SubmissionID}/status/trigger-submit-unchecked
|
||||
func (UnimplementedHandler) ActionSubmissionTriggerSubmitUnchecked(ctx context.Context, params ActionSubmissionTriggerSubmitUncheckedParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ActionSubmissionTriggerUpload implements actionSubmissionTriggerUpload operation.
|
||||
//
|
||||
// Role Admin changes status from Validated -> Uploading.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/trigger-upload
|
||||
func (UnimplementedHandler) ActionSubmissionTriggerUpload(ctx context.Context, params ActionSubmissionTriggerUploadParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// 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
|
||||
func (UnimplementedHandler) ActionSubmissionTriggerValidate(ctx context.Context, params ActionSubmissionTriggerValidateParams) error {
|
||||
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
|
||||
func (UnimplementedHandler) ActionSubmissionValidate(ctx context.Context, params ActionSubmissionValidateParams) error {
|
||||
// POST /submissions/{SubmissionID}/status/reset-uploading
|
||||
func (UnimplementedHandler) ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// CreateMapfix implements createMapfix operation.
|
||||
//
|
||||
// Trigger the validator to create a mapfix.
|
||||
//
|
||||
// POST /mapfixes
|
||||
func (UnimplementedHandler) CreateMapfix(ctx context.Context, req *MapfixTriggerCreate) (r *OperationID, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// CreateMapfixAuditComment implements createMapfixAuditComment operation.
|
||||
//
|
||||
// Post a comment to the audit log.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/comment
|
||||
func (UnimplementedHandler) CreateMapfixAuditComment(ctx context.Context, req CreateMapfixAuditCommentReq, params CreateMapfixAuditCommentParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
@@ -90,7 +236,7 @@ func (UnimplementedHandler) ActionSubmissionValidate(ctx context.Context, params
|
||||
// Create a new script.
|
||||
//
|
||||
// POST /scripts
|
||||
func (UnimplementedHandler) CreateScript(ctx context.Context, req *ScriptCreate) (r *ID, _ error) {
|
||||
func (UnimplementedHandler) CreateScript(ctx context.Context, req *ScriptCreate) (r *ScriptID, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
@@ -99,19 +245,37 @@ func (UnimplementedHandler) CreateScript(ctx context.Context, req *ScriptCreate)
|
||||
// Create a new script policy.
|
||||
//
|
||||
// POST /script-policy
|
||||
func (UnimplementedHandler) CreateScriptPolicy(ctx context.Context, req *ScriptPolicyCreate) (r *ID, _ error) {
|
||||
func (UnimplementedHandler) CreateScriptPolicy(ctx context.Context, req *ScriptPolicyCreate) (r *ScriptPolicyID, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// CreateSubmission implements createSubmission operation.
|
||||
//
|
||||
// Create new submission.
|
||||
// Trigger the validator to create a new submission.
|
||||
//
|
||||
// POST /submissions
|
||||
func (UnimplementedHandler) CreateSubmission(ctx context.Context, req *SubmissionCreate) (r *ID, _ error) {
|
||||
func (UnimplementedHandler) CreateSubmission(ctx context.Context, req *SubmissionTriggerCreate) (r *OperationID, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// CreateSubmissionAdmin implements createSubmissionAdmin operation.
|
||||
//
|
||||
// Trigger the validator to create a new submission.
|
||||
//
|
||||
// POST /submissions-admin
|
||||
func (UnimplementedHandler) CreateSubmissionAdmin(ctx context.Context, req *SubmissionTriggerCreate) (r *OperationID, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// CreateSubmissionAuditComment implements createSubmissionAuditComment operation.
|
||||
//
|
||||
// Post a comment to the audit log.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/comment
|
||||
func (UnimplementedHandler) CreateSubmissionAuditComment(ctx context.Context, req CreateSubmissionAuditCommentReq, params CreateSubmissionAuditCommentParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// DeleteScript implements deleteScript operation.
|
||||
//
|
||||
// Delete the specified script by ID.
|
||||
@@ -125,11 +289,47 @@ func (UnimplementedHandler) DeleteScript(ctx context.Context, params DeleteScrip
|
||||
//
|
||||
// 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 {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// DownloadMapAsset implements downloadMapAsset operation.
|
||||
//
|
||||
// Download the map asset.
|
||||
//
|
||||
// GET /maps/{MapID}/download
|
||||
func (UnimplementedHandler) DownloadMapAsset(ctx context.Context, params DownloadMapAssetParams) (r DownloadMapAssetOK, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// GetMap implements getMap operation.
|
||||
//
|
||||
// Retrieve map with ID.
|
||||
//
|
||||
// GET /maps/{MapID}
|
||||
func (UnimplementedHandler) GetMap(ctx context.Context, params GetMapParams) (r *Map, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// GetMapfix implements getMapfix operation.
|
||||
//
|
||||
// Retrieve map with ID.
|
||||
//
|
||||
// GET /mapfixes/{MapfixID}
|
||||
func (UnimplementedHandler) GetMapfix(ctx context.Context, params GetMapfixParams) (r *Mapfix, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// GetOperation implements getOperation operation.
|
||||
//
|
||||
// Retrieve operation with ID.
|
||||
//
|
||||
// GET /operations/{OperationID}
|
||||
func (UnimplementedHandler) GetOperation(ctx context.Context, params GetOperationParams) (r *Operation, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// GetScript implements getScript operation.
|
||||
//
|
||||
// Get the specified script by ID.
|
||||
@@ -143,20 +343,11 @@ func (UnimplementedHandler) GetScript(ctx context.Context, params GetScriptParam
|
||||
//
|
||||
// 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) {
|
||||
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.
|
||||
//
|
||||
// Retrieve map with ID.
|
||||
@@ -166,24 +357,132 @@ func (UnimplementedHandler) GetSubmission(ctx context.Context, params GetSubmiss
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ListMapfixAuditEvents implements listMapfixAuditEvents operation.
|
||||
//
|
||||
// Retrieve a list of audit events.
|
||||
//
|
||||
// GET /mapfixes/{MapfixID}/audit-events
|
||||
func (UnimplementedHandler) ListMapfixAuditEvents(ctx context.Context, params ListMapfixAuditEventsParams) (r []AuditEvent, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ListMapfixes implements listMapfixes operation.
|
||||
//
|
||||
// Get list of mapfixes.
|
||||
//
|
||||
// GET /mapfixes
|
||||
func (UnimplementedHandler) ListMapfixes(ctx context.Context, params ListMapfixesParams) (r *Mapfixes, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ListMaps implements listMaps operation.
|
||||
//
|
||||
// Get list of maps.
|
||||
//
|
||||
// GET /maps
|
||||
func (UnimplementedHandler) ListMaps(ctx context.Context, params ListMapsParams) (r []Map, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ListScriptPolicy implements listScriptPolicy operation.
|
||||
//
|
||||
// Get list of script policies.
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
// ListSubmissionAuditEvents implements listSubmissionAuditEvents operation.
|
||||
//
|
||||
// Retrieve a list of audit events.
|
||||
//
|
||||
// GET /submissions/{SubmissionID}/audit-events
|
||||
func (UnimplementedHandler) ListSubmissionAuditEvents(ctx context.Context, params ListSubmissionAuditEventsParams) (r []AuditEvent, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ListSubmissions implements listSubmissions operation.
|
||||
//
|
||||
// Get list of submissions.
|
||||
//
|
||||
// GET /submissions
|
||||
func (UnimplementedHandler) ListSubmissions(ctx context.Context, params ListSubmissionsParams) (r []Submission, _ error) {
|
||||
func (UnimplementedHandler) ListSubmissions(ctx context.Context, params ListSubmissionsParams) (r *Submissions, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// SetMapfixCompleted implements setMapfixCompleted operation.
|
||||
//
|
||||
// Called by maptest when a player completes the map.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/completed
|
||||
func (UnimplementedHandler) SetMapfixCompleted(ctx context.Context, params SetMapfixCompletedParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// SetSubmissionCompleted implements setSubmissionCompleted operation.
|
||||
//
|
||||
// Retrieve map with ID.
|
||||
// Called by maptest when a player completes the map.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/completed
|
||||
func (UnimplementedHandler) SetSubmissionCompleted(ctx context.Context, params SetSubmissionCompletedParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// UpdateMapfixModel implements updateMapfixModel operation.
|
||||
//
|
||||
// Update model following role restrictions.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/model
|
||||
func (UnimplementedHandler) UpdateMapfixModel(ctx context.Context, params UpdateMapfixModelParams) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// UpdateScript implements updateScript operation.
|
||||
//
|
||||
// Update the specified script by ID.
|
||||
@@ -197,7 +496,7 @@ func (UnimplementedHandler) UpdateScript(ctx context.Context, req *ScriptUpdate,
|
||||
//
|
||||
// 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 {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
@@ -1,305 +0,0 @@
|
||||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"math/bits"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
|
||||
"github.com/ogen-go/ogen/conv"
|
||||
"github.com/ogen-go/ogen/uri"
|
||||
"github.com/ogen-go/ogen/validate"
|
||||
)
|
||||
|
||||
// EncodeURI encodes Pagination as URI form.
|
||||
func (s *Pagination) EncodeURI(e uri.Encoder) error {
|
||||
if err := e.EncodeField("Page", func(e uri.Encoder) error {
|
||||
return e.EncodeValue(conv.Int32ToString(s.Page))
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "encode field \"Page\"")
|
||||
}
|
||||
if err := e.EncodeField("Limit", func(e uri.Encoder) error {
|
||||
return e.EncodeValue(conv.Int32ToString(s.Limit))
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "encode field \"Limit\"")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var uriFieldsNameOfPagination = [2]string{
|
||||
0: "Page",
|
||||
1: "Limit",
|
||||
}
|
||||
|
||||
// DecodeURI decodes Pagination from URI form.
|
||||
func (s *Pagination) DecodeURI(d uri.Decoder) error {
|
||||
if s == nil {
|
||||
return errors.New("invalid: unable to decode Pagination to nil")
|
||||
}
|
||||
var requiredBitSet [1]uint8
|
||||
|
||||
if err := d.DecodeFields(func(k string, d uri.Decoder) error {
|
||||
switch k {
|
||||
case "Page":
|
||||
requiredBitSet[0] |= 1 << 0
|
||||
if err := func() error {
|
||||
val, err := d.DecodeValue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := conv.ToInt32(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Page = c
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"Page\"")
|
||||
}
|
||||
case "Limit":
|
||||
requiredBitSet[0] |= 1 << 1
|
||||
if err := func() error {
|
||||
val, err := d.DecodeValue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := conv.ToInt32(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Limit = c
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"Limit\"")
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "decode Pagination")
|
||||
}
|
||||
// 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(uriFieldsNameOfPagination) {
|
||||
name = uriFieldsNameOfPagination[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
|
||||
}
|
||||
|
||||
// EncodeURI encodes SubmissionFilter as URI form.
|
||||
func (s *SubmissionFilter) EncodeURI(e uri.Encoder) error {
|
||||
if err := e.EncodeField("ID", func(e uri.Encoder) error {
|
||||
return e.EncodeValue(conv.Int64ToString(s.ID))
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "encode field \"ID\"")
|
||||
}
|
||||
if err := e.EncodeField("DisplayName", func(e uri.Encoder) error {
|
||||
if val, ok := s.DisplayName.Get(); ok {
|
||||
return e.EncodeValue(conv.StringToString(val))
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "encode field \"DisplayName\"")
|
||||
}
|
||||
if err := e.EncodeField("Creator", func(e uri.Encoder) error {
|
||||
if val, ok := s.Creator.Get(); ok {
|
||||
return e.EncodeValue(conv.StringToString(val))
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "encode field \"Creator\"")
|
||||
}
|
||||
if err := e.EncodeField("GameID", func(e uri.Encoder) error {
|
||||
if val, ok := s.GameID.Get(); ok {
|
||||
return e.EncodeValue(conv.Int32ToString(val))
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "encode field \"GameID\"")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var uriFieldsNameOfSubmissionFilter = [4]string{
|
||||
0: "ID",
|
||||
1: "DisplayName",
|
||||
2: "Creator",
|
||||
3: "GameID",
|
||||
}
|
||||
|
||||
// DecodeURI decodes SubmissionFilter from URI form.
|
||||
func (s *SubmissionFilter) DecodeURI(d uri.Decoder) error {
|
||||
if s == nil {
|
||||
return errors.New("invalid: unable to decode SubmissionFilter to nil")
|
||||
}
|
||||
var requiredBitSet [1]uint8
|
||||
|
||||
if err := d.DecodeFields(func(k string, d uri.Decoder) error {
|
||||
switch k {
|
||||
case "ID":
|
||||
requiredBitSet[0] |= 1 << 0
|
||||
if err := func() error {
|
||||
val, err := d.DecodeValue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := conv.ToInt64(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.ID = c
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"ID\"")
|
||||
}
|
||||
case "DisplayName":
|
||||
if err := func() error {
|
||||
var sDotDisplayNameVal string
|
||||
if err := func() error {
|
||||
val, err := d.DecodeValue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := conv.ToString(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sDotDisplayNameVal = c
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
s.DisplayName.SetTo(sDotDisplayNameVal)
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"DisplayName\"")
|
||||
}
|
||||
case "Creator":
|
||||
if err := func() error {
|
||||
var sDotCreatorVal string
|
||||
if err := func() error {
|
||||
val, err := d.DecodeValue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := conv.ToString(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sDotCreatorVal = c
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Creator.SetTo(sDotCreatorVal)
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"Creator\"")
|
||||
}
|
||||
case "GameID":
|
||||
if err := func() error {
|
||||
var sDotGameIDVal int32
|
||||
if err := func() error {
|
||||
val, err := d.DecodeValue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := conv.ToInt32(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sDotGameIDVal = c
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
s.GameID.SetTo(sDotGameIDVal)
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"GameID\"")
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "decode SubmissionFilter")
|
||||
}
|
||||
// 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(uriFieldsNameOfSubmissionFilter) {
|
||||
name = uriFieldsNameOfSubmissionFilter[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
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
57
pkg/cmds/api.go
Normal file
57
pkg/cmds/api.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package cmds
|
||||
|
||||
import (
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/public_api"
|
||||
"github.com/urfave/cli/v2"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
func NewApiCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "api",
|
||||
Usage: "Run api service",
|
||||
Action: runAPI,
|
||||
Flags: []cli.Flag{
|
||||
&cli.IntFlag{
|
||||
Name: "port",
|
||||
Usage: "Listen port",
|
||||
EnvVars: []string{"PORT"},
|
||||
Value: 8080,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "dev-rpc-host",
|
||||
Usage: "Host of dev rpc",
|
||||
EnvVars: []string{"DEV_RPC_HOST"},
|
||||
Value: "dev-service:8081",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "maps-rpc-host",
|
||||
Usage: "Host of maps rpc",
|
||||
EnvVars: []string{"MAPS_RPC_HOST"},
|
||||
Value: "maptest-api:8081",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func runAPI(ctx *cli.Context) error {
|
||||
// Dev service client
|
||||
devConn, err := grpc.Dial(ctx.String("dev-rpc-host"), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Data service client
|
||||
mapsConn, err := grpc.Dial(ctx.String("maps-rpc-host"), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return api.NewRouter(
|
||||
api.WithContext(ctx),
|
||||
api.WithPort(ctx.Int("port")),
|
||||
api.WithDevClient(devConn),
|
||||
api.WithMapsClient(mapsConn),
|
||||
)
|
||||
}
|
||||
@@ -2,16 +2,26 @@ package cmds
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"git.itzana.me/strafesnet/go-grpc/auth"
|
||||
"git.itzana.me/strafesnet/go-grpc/maps"
|
||||
"git.itzana.me/strafesnet/go-grpc/maps_extended"
|
||||
"git.itzana.me/strafesnet/go-grpc/users"
|
||||
"git.itzana.me/strafesnet/go-grpc/validator"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/api"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/controller"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/datastore/gormstore"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/roblox"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/service"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/validator_controller"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/web_api"
|
||||
"github.com/nats-io/nats.go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func NewServeCommand() *cli.Command {
|
||||
@@ -62,18 +72,36 @@ func NewServeCommand() *cli.Command {
|
||||
Value: 8080,
|
||||
EnvVars: []string{"PORT"},
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "port-internal",
|
||||
Usage: "Port to listen on for internal api",
|
||||
Value: 8081,
|
||||
EnvVars: []string{"PORT_INTERNAL"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "auth-rpc-host",
|
||||
Usage: "Host of auth rpc",
|
||||
EnvVars: []string{"AUTH_RPC_HOST"},
|
||||
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{
|
||||
Name: "nats-host",
|
||||
Usage: "Host of nats",
|
||||
EnvVars: []string{"NATS_HOST"},
|
||||
Value: "nats:4222",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "rbx-api-key",
|
||||
Usage: "API Key for downloading asset locations",
|
||||
EnvVars: []string{"RBX_API_KEY"},
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -101,23 +129,83 @@ func serve(ctx *cli.Context) error {
|
||||
log.WithError(err).Fatal("failed to add stream")
|
||||
}
|
||||
|
||||
svc := &service.Service{
|
||||
DB: db,
|
||||
Nats: js,
|
||||
}
|
||||
|
||||
conn, err := grpc.Dial(ctx.String("auth-rpc-host"), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
// connect to main game database
|
||||
conn, err := grpc.Dial(ctx.String("data-rpc-host"), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
sec := service.SecurityHandler{
|
||||
svc_inner := service.NewService(
|
||||
db,
|
||||
js,
|
||||
maps.NewMapsServiceClient(conn),
|
||||
users.NewUsersServiceClient(conn),
|
||||
)
|
||||
|
||||
svc_external := web_api.NewService(
|
||||
&svc_inner,
|
||||
roblox.Client{
|
||||
HttpClient: http.DefaultClient,
|
||||
ApiKey: ctx.String("rbx-api-key"),
|
||||
},
|
||||
)
|
||||
|
||||
conn, err = grpc.Dial(ctx.String("auth-rpc-host"), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
sec := web_api.SecurityHandler{
|
||||
Client: auth.NewAuthServiceClient(conn),
|
||||
}
|
||||
|
||||
srv, err := api.NewServer(svc, sec, api.WithPathPrefix("/v1"))
|
||||
srv_external, err := api.NewServer(&svc_external, sec, api.WithPathPrefix("/v1"))
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("failed to initialize api server")
|
||||
}
|
||||
|
||||
return http.ListenAndServe(fmt.Sprintf(":%d", ctx.Int("port")), srv)
|
||||
grpcServer := grpc.NewServer()
|
||||
|
||||
maps_controller := controller.NewMapsController(&svc_inner)
|
||||
maps_extended.RegisterMapsServiceServer(grpcServer,&maps_controller)
|
||||
|
||||
mapfix_controller := validator_controller.NewMapfixesController(&svc_inner)
|
||||
operation_controller := validator_controller.NewOperationsController(&svc_inner)
|
||||
script_controller := validator_controller.NewScriptsController(&svc_inner)
|
||||
script_policy_controller := validator_controller.NewScriptPolicyController(&svc_inner)
|
||||
submission_controller := validator_controller.NewSubmissionsController(&svc_inner)
|
||||
|
||||
validator.RegisterValidatorMapfixServiceServer(grpcServer,&mapfix_controller)
|
||||
validator.RegisterValidatorOperationServiceServer(grpcServer,&operation_controller)
|
||||
validator.RegisterValidatorScriptServiceServer(grpcServer,&script_controller)
|
||||
validator.RegisterValidatorScriptPolicyServiceServer(grpcServer,&script_policy_controller)
|
||||
validator.RegisterValidatorSubmissionServiceServer(grpcServer,&submission_controller)
|
||||
|
||||
port := ctx.Int("port-internal")
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||
if err != nil {
|
||||
log.WithField("error", err).Fatalln("failed to net.Listen")
|
||||
}
|
||||
// Channel to collect errors
|
||||
errChan := make(chan error, 2)
|
||||
|
||||
// First server
|
||||
go func(errChan chan error) {
|
||||
errChan <- grpcServer.Serve(lis)
|
||||
}(errChan)
|
||||
|
||||
// Second server
|
||||
go func(errChan chan error) {
|
||||
errChan <- http.ListenAndServe(fmt.Sprintf(":%d", ctx.Int("port")), srv_external)
|
||||
}(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
|
||||
}
|
||||
|
||||
197
pkg/controller/maps.go
Normal file
197
pkg/controller/maps.go
Normal file
@@ -0,0 +1,197 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.itzana.me/strafesnet/go-grpc/maps_extended"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/service"
|
||||
)
|
||||
|
||||
var (
|
||||
PageError = errors.New("Pagination required")
|
||||
)
|
||||
|
||||
type Maps struct {
|
||||
*maps_extended.UnimplementedMapsServiceServer
|
||||
inner *service.Service
|
||||
}
|
||||
|
||||
func NewMapsController(
|
||||
inner *service.Service,
|
||||
) Maps {
|
||||
return Maps{
|
||||
inner: inner,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *Maps) Create(ctx context.Context, request *maps_extended.MapCreate) (*maps_extended.MapId, error) {
|
||||
id, err := svc.inner.CreateMap(ctx, model.Map{
|
||||
ID: request.ID,
|
||||
DisplayName: request.DisplayName,
|
||||
Creator: request.Creator,
|
||||
GameID: request.GameID,
|
||||
Submitter: request.Submitter,
|
||||
Date: time.Unix(request.Date, 0),
|
||||
Thumbnail: request.Thumbnail,
|
||||
AssetVersion: request.AssetVersion,
|
||||
LoadCount: 0,
|
||||
Modes: request.Modes,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &maps_extended.MapId{
|
||||
ID: id,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (svc *Maps) Delete(ctx context.Context, request *maps_extended.MapId) (*maps_extended.NullResponse, error) {
|
||||
err := svc.inner.DeleteMap(ctx, request.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &maps_extended.NullResponse{}, nil
|
||||
}
|
||||
func (svc *Maps) Get(ctx context.Context, request *maps_extended.MapId) (*maps_extended.MapResponse, error) {
|
||||
item, err := svc.inner.GetMap(ctx, request.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &maps_extended.MapResponse{
|
||||
ID: item.ID,
|
||||
DisplayName: item.DisplayName,
|
||||
Creator: item.Creator,
|
||||
GameID: uint32(item.GameID),
|
||||
Date: item.Date.Unix(),
|
||||
CreatedAt: item.CreatedAt.Unix(),
|
||||
UpdatedAt: item.UpdatedAt.Unix(),
|
||||
Submitter: uint64(item.Submitter),
|
||||
Thumbnail: uint64(item.Thumbnail),
|
||||
AssetVersion: uint64(item.AssetVersion),
|
||||
LoadCount: item.LoadCount,
|
||||
Modes: item.Modes,
|
||||
}, nil
|
||||
}
|
||||
func (svc *Maps) GetList(ctx context.Context, request *maps_extended.MapIdList) (*maps_extended.MapList, error) {
|
||||
items, err := svc.inner.GetMapList(ctx, request.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := maps_extended.MapList{}
|
||||
resp.Maps = make([]*maps_extended.MapResponse, len(items))
|
||||
for i, item := range items {
|
||||
resp.Maps[i] = &maps_extended.MapResponse{
|
||||
ID: item.ID,
|
||||
DisplayName: item.DisplayName,
|
||||
Creator: item.Creator,
|
||||
GameID: uint32(item.GameID),
|
||||
Date: item.Date.Unix(),
|
||||
CreatedAt: item.CreatedAt.Unix(),
|
||||
UpdatedAt: item.UpdatedAt.Unix(),
|
||||
Submitter: uint64(item.Submitter),
|
||||
Thumbnail: uint64(item.Thumbnail),
|
||||
AssetVersion: uint64(item.AssetVersion),
|
||||
LoadCount: item.LoadCount,
|
||||
Modes: item.Modes,
|
||||
}
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
func (svc *Maps) List(ctx context.Context, request *maps_extended.ListRequest) (*maps_extended.MapList, error) {
|
||||
if request.Page == nil {
|
||||
return nil, PageError
|
||||
}
|
||||
|
||||
filter := service.NewMapFilter()
|
||||
if request.Filter != nil {
|
||||
if request.Filter.DisplayName != nil {
|
||||
filter.SetDisplayName(*request.Filter.DisplayName)
|
||||
}
|
||||
if request.Filter.Creator != nil {
|
||||
filter.SetCreator(*request.Filter.Creator)
|
||||
}
|
||||
if request.Filter.GameID != nil {
|
||||
filter.SetGameID(*request.Filter.GameID)
|
||||
}
|
||||
if request.Filter.Submitter != nil {
|
||||
filter.SetSubmitter(*request.Filter.Submitter)
|
||||
}
|
||||
}
|
||||
|
||||
items, err := svc.inner.ListMaps(ctx, filter, model.Page{
|
||||
Number: int32(request.Page.Number),
|
||||
Size: int32(request.Page.Size),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := maps_extended.MapList{}
|
||||
resp.Maps = make([]*maps_extended.MapResponse, len(items))
|
||||
for i, item := range items {
|
||||
resp.Maps[i] = &maps_extended.MapResponse{
|
||||
ID: item.ID,
|
||||
DisplayName: item.DisplayName,
|
||||
Creator: item.Creator,
|
||||
GameID: uint32(item.GameID),
|
||||
Date: item.Date.Unix(),
|
||||
CreatedAt: item.CreatedAt.Unix(),
|
||||
UpdatedAt: item.UpdatedAt.Unix(),
|
||||
Submitter: uint64(item.Submitter),
|
||||
Thumbnail: uint64(item.Thumbnail),
|
||||
AssetVersion: uint64(item.AssetVersion),
|
||||
LoadCount: item.LoadCount,
|
||||
Modes: item.Modes,
|
||||
}
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
func (svc *Maps) Update(ctx context.Context, request *maps_extended.MapUpdate) (*maps_extended.NullResponse, error) {
|
||||
update := service.NewMapUpdate()
|
||||
if request.DisplayName != nil {
|
||||
update.SetDisplayName(*request.DisplayName)
|
||||
}
|
||||
if request.Creator != nil {
|
||||
update.SetCreator(*request.Creator)
|
||||
}
|
||||
if request.GameID != nil {
|
||||
update.SetGameID(*request.GameID)
|
||||
}
|
||||
if request.Date != nil {
|
||||
update.SetDate(*request.Date)
|
||||
}
|
||||
if request.Submitter != nil {
|
||||
update.SetSubmitter(*request.Submitter)
|
||||
}
|
||||
if request.Thumbnail != nil {
|
||||
update.SetThumbnail(*request.Thumbnail)
|
||||
}
|
||||
if request.AssetVersion != nil {
|
||||
update.SetAssetVersion(*request.AssetVersion)
|
||||
}
|
||||
if request.Modes != nil {
|
||||
update.SetModes(*request.Modes)
|
||||
}
|
||||
|
||||
err := svc.inner.UpdateMap(ctx, request.ID, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &maps_extended.NullResponse{}, nil
|
||||
}
|
||||
|
||||
func (svc *Maps) IncrementLoadCount(ctx context.Context, request *maps_extended.MapId) (*maps_extended.NullResponse, error) {
|
||||
err := svc.inner.IncrementMapLoadCount(ctx, request.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &maps_extended.NullResponse{}, nil
|
||||
}
|
||||
@@ -3,29 +3,84 @@ package datastore
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotExist = errors.New("resource does not exist")
|
||||
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 {
|
||||
AuditEvents() AuditEvents
|
||||
Maps() Maps
|
||||
Mapfixes() Mapfixes
|
||||
Operations() Operations
|
||||
Submissions() Submissions
|
||||
Scripts() Scripts
|
||||
ScriptPolicy() ScriptPolicy
|
||||
}
|
||||
|
||||
type AuditEvents interface {
|
||||
Get(ctx context.Context, id int64) (model.AuditEvent, error)
|
||||
Create(ctx context.Context, smap model.AuditEvent) (model.AuditEvent, error)
|
||||
Update(ctx context.Context, id int64, values OptionalMap) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
List(ctx context.Context, filters OptionalMap, page model.Page) ([]model.AuditEvent, error)
|
||||
}
|
||||
|
||||
type Maps interface {
|
||||
Get(ctx context.Context, id int64) (model.Map, error)
|
||||
GetList(ctx context.Context, id []int64) ([]model.Map, error)
|
||||
Create(ctx context.Context, smap model.Map) (model.Map, error)
|
||||
Update(ctx context.Context, id int64, values OptionalMap) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
List(ctx context.Context, filters OptionalMap, page model.Page) ([]model.Map, error)
|
||||
IncrementLoadCount(ctx context.Context, id int64) error
|
||||
}
|
||||
|
||||
type Mapfixes interface {
|
||||
Get(ctx context.Context, id int64) (model.Mapfix, error)
|
||||
GetList(ctx context.Context, id []int64) ([]model.Mapfix, error)
|
||||
Create(ctx context.Context, smap model.Mapfix) (model.Mapfix, error)
|
||||
Update(ctx context.Context, id int64, values OptionalMap) error
|
||||
IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.MapfixStatus, values OptionalMap) error
|
||||
IfStatusThenUpdateAndGet(ctx context.Context, id int64, statuses []model.MapfixStatus, values OptionalMap) (model.Mapfix, error)
|
||||
Delete(ctx context.Context, id int64) error
|
||||
List(ctx context.Context, filters OptionalMap, page model.Page, sort ListSort) ([]model.Mapfix, error)
|
||||
ListWithTotal(ctx context.Context, filters OptionalMap, page model.Page, sort ListSort) (int64, []model.Mapfix, error)
|
||||
}
|
||||
|
||||
type Operations interface {
|
||||
Get(ctx context.Context, id int32) (model.Operation, error)
|
||||
Create(ctx context.Context, smap model.Operation) (model.Operation, error)
|
||||
Update(ctx context.Context, id int32, values OptionalMap) error
|
||||
Delete(ctx context.Context, id int32) error
|
||||
CountSince(ctx context.Context, owner int64, since time.Time) (int64, error)
|
||||
}
|
||||
|
||||
type Submissions interface {
|
||||
Get(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)
|
||||
Update(ctx context.Context, id int64, values OptionalMap) error
|
||||
IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.Status, values OptionalMap) error
|
||||
IfStatusThenUpdateAndGet(ctx context.Context, id int64, statuses []model.Status, values OptionalMap) (model.Submission, error)
|
||||
IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.SubmissionStatus, values OptionalMap) error
|
||||
IfStatusThenUpdateAndGet(ctx context.Context, id int64, statuses []model.SubmissionStatus, values OptionalMap) (model.Submission, 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)
|
||||
ListWithTotal(ctx context.Context, filters OptionalMap, page model.Page, sort ListSort) (int64, []model.Submission, error)
|
||||
}
|
||||
|
||||
type Scripts interface {
|
||||
@@ -33,6 +88,7 @@ type Scripts interface {
|
||||
Create(ctx context.Context, smap model.Script) (model.Script, error)
|
||||
Update(ctx context.Context, id int64, values OptionalMap) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
List(ctx context.Context, filters OptionalMap, page model.Page) ([]model.Script, error)
|
||||
}
|
||||
|
||||
type ScriptPolicy interface {
|
||||
@@ -41,4 +97,5 @@ type ScriptPolicy interface {
|
||||
Create(ctx context.Context, smap model.ScriptPolicy) (model.ScriptPolicy, error)
|
||||
Update(ctx context.Context, id int64, values OptionalMap) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
List(ctx context.Context, filters OptionalMap, page model.Page) ([]model.ScriptPolicy, error)
|
||||
}
|
||||
|
||||
64
pkg/datastore/gormstore/audit_events.go
Normal file
64
pkg/datastore/gormstore/audit_events.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package gormstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AuditEvents struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func (env *AuditEvents) Get(ctx context.Context, id int64) (model.AuditEvent, error) {
|
||||
var mdl model.AuditEvent
|
||||
if err := env.db.First(&mdl, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return mdl, datastore.ErrNotExist
|
||||
}
|
||||
return mdl, err
|
||||
}
|
||||
return mdl, nil
|
||||
}
|
||||
|
||||
func (env *AuditEvents) Create(ctx context.Context, smap model.AuditEvent) (model.AuditEvent, error) {
|
||||
if err := env.db.Create(&smap).Error; err != nil {
|
||||
return smap, err
|
||||
}
|
||||
|
||||
return smap, nil
|
||||
}
|
||||
|
||||
func (env *AuditEvents) Update(ctx context.Context, id int64, values datastore.OptionalMap) error {
|
||||
if err := env.db.Model(&model.AuditEvent{}).Where("id = ?", id).Updates(values.Map()).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return datastore.ErrNotExist
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *AuditEvents) Delete(ctx context.Context, id int64) error {
|
||||
if err := env.db.Delete(&model.AuditEvent{}, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return datastore.ErrNotExist
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *AuditEvents) List(ctx context.Context, filters datastore.OptionalMap, page model.Page) ([]model.AuditEvent, error) {
|
||||
var events []model.AuditEvent
|
||||
if err := env.db.Where(filters.Map()).Order("id ASC").Offset(int((page.Number - 1) * page.Size)).Limit(int(page.Size)).Find(&events).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return events, nil
|
||||
}
|
||||
@@ -31,6 +31,10 @@ func New(ctx *cli.Context) (datastore.Datastore, error) {
|
||||
|
||||
if ctx.Bool("migrate") {
|
||||
if err := db.AutoMigrate(
|
||||
&model.AuditEvent{},
|
||||
&model.Map{},
|
||||
&model.Mapfix{},
|
||||
&model.Operation{},
|
||||
&model.Submission{},
|
||||
&model.Script{},
|
||||
&model.ScriptPolicy{},
|
||||
|
||||
@@ -9,6 +9,22 @@ type Gormstore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func (g Gormstore) AuditEvents() datastore.AuditEvents {
|
||||
return &AuditEvents{db: g.db}
|
||||
}
|
||||
|
||||
func (g Gormstore) Maps() datastore.Maps {
|
||||
return &Maps{db: g.db}
|
||||
}
|
||||
|
||||
func (g Gormstore) Mapfixes() datastore.Mapfixes {
|
||||
return &Mapfixes{db: g.db}
|
||||
}
|
||||
|
||||
func (g Gormstore) Operations() datastore.Operations {
|
||||
return &Operations{db: g.db}
|
||||
}
|
||||
|
||||
func (g Gormstore) Submissions() datastore.Submissions {
|
||||
return &Submissions{db: g.db}
|
||||
}
|
||||
|
||||
153
pkg/datastore/gormstore/mapfixes.go
Normal file
153
pkg/datastore/gormstore/mapfixes.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package gormstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type Mapfixes struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func (env *Mapfixes) Get(ctx context.Context, id int64) (model.Mapfix, error) {
|
||||
var mapfix model.Mapfix
|
||||
if err := env.db.First(&mapfix, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return mapfix, datastore.ErrNotExist
|
||||
}
|
||||
return mapfix, err
|
||||
}
|
||||
return mapfix, nil
|
||||
}
|
||||
|
||||
func (env *Mapfixes) GetList(ctx context.Context, id []int64) ([]model.Mapfix, error) {
|
||||
var mapList []model.Mapfix
|
||||
if err := env.db.Find(&mapList, "id IN ?", id).Error; err != nil {
|
||||
return mapList, err
|
||||
}
|
||||
|
||||
return mapList, nil
|
||||
}
|
||||
|
||||
func (env *Mapfixes) Create(ctx context.Context, smap model.Mapfix) (model.Mapfix, error) {
|
||||
if err := env.db.Create(&smap).Error; err != nil {
|
||||
return smap, err
|
||||
}
|
||||
|
||||
return smap, nil
|
||||
}
|
||||
|
||||
func (env *Mapfixes) Update(ctx context.Context, id int64, values datastore.OptionalMap) error {
|
||||
if err := env.db.Model(&model.Mapfix{}).Where("id = ?", id).Updates(values.Map()).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return datastore.ErrNotExist
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// the update can only occur if the status matches one of the provided values.
|
||||
func (env *Mapfixes) IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.MapfixStatus, values datastore.OptionalMap) error {
|
||||
result := env.db.Model(&model.Mapfix{}).Where("id = ?", id).Where("status_id IN ?", statuses).Updates(values.Map())
|
||||
if result.Error != nil {
|
||||
if result.Error == gorm.ErrRecordNotFound {
|
||||
return datastore.ErrNotExist
|
||||
}
|
||||
return result.Error
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
return datastore.ErroNoRowsAffected
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// the update can only occur if the status matches one of the provided values.
|
||||
// returns the updated value
|
||||
func (env *Mapfixes) IfStatusThenUpdateAndGet(ctx context.Context, id int64, statuses []model.MapfixStatus, values datastore.OptionalMap) (model.Mapfix, error) {
|
||||
var mapfix model.Mapfix
|
||||
result := env.db.Model(&mapfix).
|
||||
Clauses(clause.Returning{}).
|
||||
Where("id = ?", id).
|
||||
Where("status_id IN ?", statuses).
|
||||
Updates(values.Map())
|
||||
if result.Error != nil {
|
||||
if result.Error == gorm.ErrRecordNotFound {
|
||||
return mapfix, datastore.ErrNotExist
|
||||
}
|
||||
return mapfix, result.Error
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
return mapfix, datastore.ErroNoRowsAffected
|
||||
}
|
||||
|
||||
return mapfix, nil
|
||||
}
|
||||
|
||||
func (env *Mapfixes) Delete(ctx context.Context, id int64) error {
|
||||
if err := env.db.Delete(&model.Mapfix{}, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return datastore.ErrNotExist
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *Mapfixes) List(ctx context.Context, filters datastore.OptionalMap, page model.Page, sort datastore.ListSort) ([]model.Mapfix, error) {
|
||||
var maps []model.Mapfix
|
||||
|
||||
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 maps, nil
|
||||
}
|
||||
|
||||
func (env *Mapfixes) ListWithTotal(ctx context.Context, filters datastore.OptionalMap, page model.Page, sort datastore.ListSort) (int64, []model.Mapfix, error) {
|
||||
// grab page items
|
||||
maps, err := env.List(ctx, filters, page, sort)
|
||||
if err != nil{
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
// count total with filters
|
||||
var total int64
|
||||
if err := env.db.Model(&model.Mapfix{}).Where(filters.Map()).Count(&total).Error; err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
return total, maps, nil
|
||||
}
|
||||
84
pkg/datastore/gormstore/maps.go
Normal file
84
pkg/datastore/gormstore/maps.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package gormstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Maps struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func (env *Maps) Get(ctx context.Context, id int64) (model.Map, error) {
|
||||
var mdl model.Map
|
||||
if err := env.db.First(&mdl, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return mdl, datastore.ErrNotExist
|
||||
}
|
||||
return mdl, err
|
||||
}
|
||||
return mdl, nil
|
||||
}
|
||||
|
||||
func (env *Maps) GetList(ctx context.Context, id []int64) ([]model.Map, error) {
|
||||
var mapList []model.Map
|
||||
if err := env.db.Find(&mapList, "id IN ?", id).Error; err != nil {
|
||||
return mapList, err
|
||||
}
|
||||
|
||||
return mapList, nil
|
||||
}
|
||||
|
||||
func (env *Maps) Create(ctx context.Context, smap model.Map) (model.Map, error) {
|
||||
if err := env.db.Create(&smap).Error; err != nil {
|
||||
return smap, err
|
||||
}
|
||||
|
||||
return smap, nil
|
||||
}
|
||||
|
||||
func (env *Maps) Update(ctx context.Context, id int64, values datastore.OptionalMap) error {
|
||||
if err := env.db.Model(&model.Map{}).Where("id = ?", id).Updates(values.Map()).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return datastore.ErrNotExist
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *Maps) IncrementLoadCount(ctx context.Context, id int64) error {
|
||||
if err := env.db.Model(&model.Map{}).Where("id = ?", id).UpdateColumn("load_count", gorm.Expr("load_count + ?", 1)).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return datastore.ErrNotExist
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *Maps) Delete(ctx context.Context, id int64) error {
|
||||
if err := env.db.Delete(&model.Map{}, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return datastore.ErrNotExist
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *Maps) List(ctx context.Context, filters datastore.OptionalMap, page model.Page) ([]model.Map, error) {
|
||||
var events []model.Map
|
||||
if err := env.db.Where(filters.Map()).Offset(int((page.Number - 1) * page.Size)).Limit(int(page.Size)).Find(&events).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return events, nil
|
||||
}
|
||||
65
pkg/datastore/gormstore/operations.go
Normal file
65
pkg/datastore/gormstore/operations.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package gormstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Operations struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func (env *Operations) Get(ctx context.Context, id int32) (model.Operation, error) {
|
||||
var operation model.Operation
|
||||
if err := env.db.First(&operation, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return operation, datastore.ErrNotExist
|
||||
}
|
||||
return operation, err
|
||||
}
|
||||
return operation, nil
|
||||
}
|
||||
|
||||
func (env *Operations) Create(ctx context.Context, smap model.Operation) (model.Operation, error) {
|
||||
if err := env.db.Create(&smap).Error; err != nil {
|
||||
return smap, err
|
||||
}
|
||||
|
||||
return smap, nil
|
||||
}
|
||||
|
||||
func (env *Operations) Update(ctx context.Context, id int32, values datastore.OptionalMap) error {
|
||||
if err := env.db.Model(&model.Operation{}).Where("id = ?", id).Updates(values.Map()).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return datastore.ErrNotExist
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *Operations) Delete(ctx context.Context, id int32) error {
|
||||
if err := env.db.Delete(&model.Operation{}, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return datastore.ErrNotExist
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *Operations) CountSince(ctx context.Context, owner int64, since time.Time) (int64, error) {
|
||||
var count int64
|
||||
if err := env.db.Model(&model.Operation{}).Where("owner = ? AND created_at > ?",owner,since).Count(&count).Error; err != nil {
|
||||
return count, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
@@ -19,16 +19,18 @@ func (env *ScriptPolicy) Get(ctx context.Context, id int64) (model.ScriptPolicy,
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return mdl, datastore.ErrNotExist
|
||||
}
|
||||
return mdl, err
|
||||
}
|
||||
return mdl, nil
|
||||
}
|
||||
|
||||
func (env *ScriptPolicy) GetFromHash(ctx context.Context, hash uint64) (model.ScriptPolicy, error) {
|
||||
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) {
|
||||
return mdl, datastore.ErrNotExist
|
||||
}
|
||||
return mdl, err
|
||||
}
|
||||
return mdl, nil
|
||||
}
|
||||
@@ -62,3 +64,12 @@ func (env *ScriptPolicy) Delete(ctx context.Context, id int64) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *ScriptPolicy) List(ctx context.Context, filters datastore.OptionalMap, page model.Page) ([]model.ScriptPolicy, error) {
|
||||
var maps []model.ScriptPolicy
|
||||
if err := env.db.Where(filters.Map()).Offset(int((page.Number - 1) * page.Size)).Limit(int(page.Size)).Find(&maps).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return maps, nil
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ func (env *Scripts) Get(ctx context.Context, id int64) (model.Script, error) {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return mdl, datastore.ErrNotExist
|
||||
}
|
||||
return mdl, err
|
||||
}
|
||||
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.
|
||||
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 == gorm.ErrRecordNotFound {
|
||||
return datastore.ErrNotExist
|
||||
|
||||
@@ -20,6 +20,7 @@ func (env *Submissions) Get(ctx context.Context, id int64) (model.Submission, er
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return submission, datastore.ErrNotExist
|
||||
}
|
||||
return submission, err
|
||||
}
|
||||
return submission, nil
|
||||
}
|
||||
@@ -53,12 +54,17 @@ 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.
|
||||
func (env *Submissions) IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.Status, 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 == gorm.ErrRecordNotFound {
|
||||
func (env *Submissions) IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.SubmissionStatus, values datastore.OptionalMap) error {
|
||||
result := env.db.Model(&model.Submission{}).Where("id = ?", id).Where("status_id IN ?", statuses).Updates(values.Map())
|
||||
if result.Error != nil {
|
||||
if result.Error == gorm.ErrRecordNotFound {
|
||||
return datastore.ErrNotExist
|
||||
}
|
||||
return err
|
||||
return result.Error
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
return datastore.ErroNoRowsAffected
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -66,7 +72,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.
|
||||
// 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
|
||||
result := env.db.Model(&submission).
|
||||
Clauses(clause.Returning{}).
|
||||
@@ -98,11 +104,50 @@ func (env *Submissions) Delete(ctx context.Context, id int64) error {
|
||||
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
|
||||
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 maps, nil
|
||||
}
|
||||
|
||||
func (env *Submissions) ListWithTotal(ctx context.Context, filters datastore.OptionalMap, page model.Page, sort datastore.ListSort) (int64, []model.Submission, error) {
|
||||
// grab page items
|
||||
maps, err := env.List(ctx, filters, page, sort)
|
||||
if err != nil{
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
// count total with filters
|
||||
var total int64
|
||||
if err := env.db.Model(&model.Submission{}).Where(filters.Map()).Count(&total).Error; err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
return total, maps, nil
|
||||
}
|
||||
|
||||
75
pkg/model/audit_event.go
Normal file
75
pkg/model/audit_event.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
const ValidatorUserID uint64 = uint64(math.MaxInt64)
|
||||
|
||||
type AuditEventType int32
|
||||
|
||||
// User clicked "Submit", "Accept" etc
|
||||
const AuditEventTypeAction AuditEventType = 0
|
||||
type AuditEventDataAction struct {
|
||||
TargetStatus uint32 `json:"target_status"`
|
||||
}
|
||||
|
||||
// User wrote a comment
|
||||
const AuditEventTypeComment AuditEventType = 1
|
||||
type AuditEventDataComment struct {
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
|
||||
// User changed Model
|
||||
const AuditEventTypeChangeModel AuditEventType = 2
|
||||
type AuditEventDataChangeModel struct {
|
||||
OldModelID uint64 `json:"old_model_id"`
|
||||
OldModelVersion uint64 `json:"old_model_version"`
|
||||
NewModelID uint64 `json:"new_model_id"`
|
||||
NewModelVersion uint64 `json:"new_model_version"`
|
||||
}
|
||||
// Validator validates model
|
||||
const AuditEventTypeChangeValidatedModel AuditEventType = 3
|
||||
type AuditEventDataChangeValidatedModel struct {
|
||||
ValidatedModelID uint64 `json:"validated_model_id"`
|
||||
ValidatedModelVersion uint64 `json:"validated_model_version"`
|
||||
}
|
||||
|
||||
// User changed DisplayName / Creator
|
||||
const AuditEventTypeChangeDisplayName AuditEventType = 4
|
||||
const AuditEventTypeChangeCreator AuditEventType = 5
|
||||
type AuditEventDataChangeName struct {
|
||||
OldName string `json:"old_name"`
|
||||
NewName string `json:"new_name"`
|
||||
}
|
||||
|
||||
// Validator had an error
|
||||
const AuditEventTypeError AuditEventType = 6
|
||||
type AuditEventDataError struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type Check struct {
|
||||
Name string `json:"name"`
|
||||
Summary string `json:"summary"`
|
||||
Passed bool `json:"passed"`
|
||||
}
|
||||
|
||||
// Validator map checks details
|
||||
const AuditEventTypeCheckList AuditEventType = 7
|
||||
|
||||
type AuditEventDataCheckList struct {
|
||||
CheckList []Check `json:"check_list"`
|
||||
}
|
||||
|
||||
type AuditEvent struct {
|
||||
ID int64 `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
User uint64
|
||||
ResourceType ResourceType // is this a submission or is it a mapfix
|
||||
ResourceID int64 // submission / mapfix / map ID
|
||||
EventType AuditEventType
|
||||
EventData json.RawMessage `gorm:"type:jsonb"`
|
||||
}
|
||||
18
pkg/model/map.go
Normal file
18
pkg/model/map.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type Map struct {
|
||||
ID int64
|
||||
DisplayName string
|
||||
Creator string
|
||||
GameID uint32
|
||||
Date time.Time // Release date
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Submitter uint64 // UserID of submitter
|
||||
Thumbnail uint64 // AssetID of thumbnail
|
||||
AssetVersion uint64 // Version number for LoadAssetVersion
|
||||
LoadCount uint32 // How many times the map has been loaded
|
||||
Modes uint32 // Number of modes (always at least one)
|
||||
}
|
||||
43
pkg/model/mapfix.go
Normal file
43
pkg/model/mapfix.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type MapfixStatus int32
|
||||
|
||||
const (
|
||||
// Phase: Creation
|
||||
MapfixStatusUnderConstruction MapfixStatus = 0
|
||||
MapfixStatusChangesRequested MapfixStatus = 1
|
||||
|
||||
// Phase: Review
|
||||
MapfixStatusSubmitting MapfixStatus = 2
|
||||
MapfixStatusSubmitted MapfixStatus = 3
|
||||
|
||||
// Phase: Testing
|
||||
MapfixStatusAcceptedUnvalidated MapfixStatus = 4 // pending script review, can re-trigger validation
|
||||
MapfixStatusValidating MapfixStatus = 5
|
||||
MapfixStatusValidated MapfixStatus = 6
|
||||
MapfixStatusUploading MapfixStatus = 7
|
||||
|
||||
// Phase: Final MapfixStatus
|
||||
MapfixStatusUploaded MapfixStatus = 8 // uploaded to the group, but pending release
|
||||
MapfixStatusRejected MapfixStatus = 9
|
||||
)
|
||||
|
||||
type Mapfix struct {
|
||||
ID int64 `gorm:"primaryKey"`
|
||||
DisplayName string
|
||||
Creator string
|
||||
GameID uint32
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Submitter uint64 // UserID
|
||||
AssetID uint64
|
||||
AssetVersion uint64
|
||||
ValidatedAssetID uint64
|
||||
ValidatedAssetVersion uint64
|
||||
Completed bool // Has this version of the map been completed at least once on maptest
|
||||
TargetAssetID uint64 // where to upload map fix. if the TargetAssetID is 0, it's a new map.
|
||||
StatusID MapfixStatus
|
||||
Description string // mapfix description
|
||||
}
|
||||
@@ -5,27 +5,62 @@ package model
|
||||
|
||||
// Requests are sent from maps-service to validator
|
||||
|
||||
type ValidateRequest struct {
|
||||
type CreateSubmissionRequest struct {
|
||||
// operation_id is passed back in the response message
|
||||
OperationID int32
|
||||
ModelID uint64
|
||||
DisplayName string
|
||||
Creator string
|
||||
GameID uint32
|
||||
Status uint32
|
||||
Roles uint32
|
||||
}
|
||||
|
||||
type CreateMapfixRequest struct {
|
||||
OperationID int32
|
||||
ModelID uint64
|
||||
TargetAssetID uint64
|
||||
Description string
|
||||
}
|
||||
|
||||
|
||||
type CheckSubmissionRequest struct{
|
||||
SubmissionID int64
|
||||
ModelID uint64
|
||||
SkipChecks bool
|
||||
}
|
||||
|
||||
type CheckMapfixRequest struct{
|
||||
MapfixID int64
|
||||
ModelID uint64
|
||||
SkipChecks bool
|
||||
}
|
||||
|
||||
type ValidateSubmissionRequest struct {
|
||||
// submission_id is passed back in the response message
|
||||
SubmissionID int64
|
||||
ModelID uint64
|
||||
ModelVersion uint64
|
||||
ValidatedModelID uint64 // optional value
|
||||
SubmissionID int64
|
||||
ModelID uint64
|
||||
ModelVersion uint64
|
||||
ValidatedModelID *uint64 // optional value
|
||||
}
|
||||
|
||||
type ValidateMapfixRequest struct {
|
||||
MapfixID int64
|
||||
ModelID uint64
|
||||
ModelVersion uint64
|
||||
ValidatedModelID *uint64 // optional value
|
||||
}
|
||||
|
||||
// Create a new map
|
||||
type PublishNewRequest struct {
|
||||
SubmissionID int64
|
||||
type UploadSubmissionRequest struct {
|
||||
SubmissionID int64
|
||||
ModelID uint64
|
||||
ModelVersion uint64
|
||||
Creator string
|
||||
DisplayName string
|
||||
GameID uint32
|
||||
//games HashSet<GameID>
|
||||
ModelName string
|
||||
}
|
||||
|
||||
type PublishFixRequest struct {
|
||||
SubmissionID int64
|
||||
type UploadMapfixRequest struct {
|
||||
MapfixID int64
|
||||
ModelID uint64
|
||||
ModelVersion uint64
|
||||
TargetAssetID uint64
|
||||
|
||||
19
pkg/model/operation.go
Normal file
19
pkg/model/operation.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type OperationStatus int32
|
||||
const (
|
||||
OperationStatusCreated OperationStatus = 0
|
||||
OperationStatusCompleted OperationStatus = 1
|
||||
OperationStatusFailed OperationStatus = 2
|
||||
)
|
||||
|
||||
type Operation struct {
|
||||
ID int32 `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
Owner uint64 // UserID
|
||||
StatusID OperationStatus
|
||||
StatusMessage string
|
||||
Path string // redirect to view completed operation e.g. "/mapfixes/4"
|
||||
}
|
||||
@@ -5,10 +5,11 @@ import "time"
|
||||
type Policy int32
|
||||
|
||||
const (
|
||||
ScriptPolicyAllowed Policy = 0
|
||||
ScriptPolicyBlocked Policy = 1
|
||||
ScriptPolicyDelete Policy = 2
|
||||
ScriptPolicyReplace Policy = 3
|
||||
ScriptPolicyNone Policy = 0 // not yet reviewed
|
||||
ScriptPolicyAllowed Policy = 1
|
||||
ScriptPolicyBlocked Policy = 2
|
||||
ScriptPolicyDelete Policy = 3
|
||||
ScriptPolicyReplace Policy = 4
|
||||
)
|
||||
|
||||
type ScriptPolicy struct {
|
||||
@@ -16,7 +17,7 @@ type ScriptPolicy struct {
|
||||
// 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.
|
||||
// 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)
|
||||
// or verbatim source (ScriptPolicyAllowed)
|
||||
// or 0 (other)
|
||||
|
||||
13
pkg/model/resource.go
Normal file
13
pkg/model/resource.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package model
|
||||
|
||||
type ResourceType int32
|
||||
const (
|
||||
ResourceUnknown ResourceType = 0
|
||||
ResourceMapfix ResourceType = 1
|
||||
ResourceSubmission ResourceType = 2
|
||||
)
|
||||
|
||||
type Resource struct{
|
||||
ID int64
|
||||
Type ResourceType
|
||||
}
|
||||
33
pkg/model/roles.go
Normal file
33
pkg/model/roles.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package model
|
||||
|
||||
// Submissions roles bitflag
|
||||
type Roles int32
|
||||
var (
|
||||
RolesSubmissionUpload Roles = 1<<6
|
||||
RolesSubmissionReview Roles = 1<<5
|
||||
RolesSubmissionRelease Roles = 1<<4
|
||||
RolesScriptWrite Roles = 1<<3
|
||||
RolesMapfixUpload Roles = 1<<2
|
||||
RolesMapfixReview Roles = 1<<1
|
||||
RolesMapDownload Roles = 1<<0
|
||||
RolesEmpty Roles = 0
|
||||
)
|
||||
|
||||
// StrafesNET group roles
|
||||
type GroupRole int32
|
||||
var (
|
||||
// has ScriptWrite
|
||||
RoleQuat GroupRole = 255
|
||||
RoleItzaname GroupRole = 254
|
||||
RoleStagingDeveloper GroupRole = 240
|
||||
RolesAll Roles = ^RolesEmpty
|
||||
// has SubmissionUpload
|
||||
RoleMapAdmin GroupRole = 128
|
||||
RolesMapAdmin Roles = RolesSubmissionRelease|RolesSubmissionUpload|RolesSubmissionReview|RolesMapCouncil
|
||||
// has MapfixReview
|
||||
RoleMapCouncil GroupRole = 64
|
||||
RolesMapCouncil Roles = RolesMapfixReview|RolesMapfixUpload|RolesMapAccess
|
||||
// access to downloading maps
|
||||
RoleMapAccess GroupRole = 32
|
||||
RolesMapAccess Roles = RolesMapDownload
|
||||
)
|
||||
@@ -1,12 +1,35 @@
|
||||
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 Script struct {
|
||||
ID int64 `gorm:"primaryKey"`
|
||||
Hash uint64
|
||||
Name string
|
||||
Hash int64 // postgres does not support unsigned integers, so we have to pretend
|
||||
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
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
@@ -2,33 +2,42 @@ package model
|
||||
|
||||
import "time"
|
||||
|
||||
type Status int32
|
||||
type SubmissionStatus int32
|
||||
|
||||
const (
|
||||
StatusPublished Status = 8
|
||||
StatusRejected Status = 7
|
||||
// Phase: Creation
|
||||
SubmissionStatusUnderConstruction SubmissionStatus = 0
|
||||
SubmissionStatusChangesRequested SubmissionStatus = 1
|
||||
|
||||
StatusPublishing Status = 6
|
||||
StatusValidated Status = 5
|
||||
StatusValidating Status = 4
|
||||
StatusAccepted Status = 3
|
||||
// Phase: Review
|
||||
SubmissionStatusSubmitting SubmissionStatus = 2
|
||||
SubmissionStatusSubmitted SubmissionStatus = 3
|
||||
|
||||
StatusChangesRequested Status = 2
|
||||
StatusSubmitted Status = 1
|
||||
StatusUnderConstruction Status = 0
|
||||
// Phase: Testing
|
||||
SubmissionStatusAcceptedUnvalidated SubmissionStatus = 4 // pending script review, can re-trigger validation
|
||||
SubmissionStatusValidating SubmissionStatus = 5
|
||||
SubmissionStatusValidated SubmissionStatus = 6
|
||||
SubmissionStatusUploading SubmissionStatus = 7
|
||||
SubmissionStatusUploaded SubmissionStatus = 8 // uploaded to the group, but pending release
|
||||
|
||||
// Phase: Final SubmissionStatus
|
||||
SubmissionStatusRejected SubmissionStatus = 9
|
||||
SubmissionStatusReleased SubmissionStatus = 10
|
||||
)
|
||||
|
||||
type Submission struct {
|
||||
ID int64 `gorm:"primaryKey"`
|
||||
DisplayName string
|
||||
Creator string
|
||||
GameID int32
|
||||
GameID uint32
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Submitter uint64 // UserID
|
||||
AssetID uint64
|
||||
AssetVersion uint64
|
||||
ValidatedAssetID uint64
|
||||
ValidatedAssetVersion uint64
|
||||
Completed bool // Has this version of the map been completed at least once on maptest
|
||||
TargetAssetID uint64 // where to upload map fix. if the TargetAssetID is 0, it's a new map.
|
||||
StatusID Status
|
||||
UploadedAssetID uint64 // where to upload map fix. if the TargetAssetID is 0, it's a new map.
|
||||
StatusID SubmissionStatus
|
||||
}
|
||||
|
||||
47
pkg/public_api/dto/map.go
Normal file
47
pkg/public_api/dto/map.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"git.itzana.me/strafesnet/go-grpc/maps_extended"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MapFilter struct {
|
||||
GameID *uint32 `json:"game_id" form:"game_id"`
|
||||
} // @name MapFilter
|
||||
|
||||
type Map struct {
|
||||
ID int64 `json:"id"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Creator string `json:"creator"`
|
||||
GameID uint32 `json:"game_id"`
|
||||
Date time.Time `json:"date"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Submitter uint64 `json:"submitter"`
|
||||
Thumbnail uint64 `json:"thumbnail"`
|
||||
AssetVersion uint64 `json:"asset_version"`
|
||||
LoadCount uint32 `json:"load_count"`
|
||||
Modes uint32 `json:"modes"`
|
||||
} // @name Map
|
||||
|
||||
// FromGRPC converts a maps.MapResponse protobuf message to a Map domain object
|
||||
func (m *Map) FromGRPC(resp *maps_extended.MapResponse) *Map {
|
||||
if resp == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
m.ID = resp.ID
|
||||
m.DisplayName = resp.DisplayName
|
||||
m.Creator = resp.Creator
|
||||
m.Date = time.Unix(resp.Date, 0)
|
||||
m.GameID = resp.GameID
|
||||
m.CreatedAt = time.Unix(resp.CreatedAt, 0)
|
||||
m.UpdatedAt = time.Unix(resp.UpdatedAt, 0)
|
||||
m.Submitter = resp.Submitter
|
||||
m.Thumbnail = resp.Thumbnail
|
||||
m.AssetVersion = resp.AssetVersion
|
||||
m.LoadCount = resp.LoadCount
|
||||
m.Modes = resp.Modes
|
||||
|
||||
return m
|
||||
}
|
||||
52
pkg/public_api/dto/response.go
Normal file
52
pkg/public_api/dto/response.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package dto
|
||||
|
||||
// @Description Generic response
|
||||
type Response[T any] struct {
|
||||
// Data contains the actual response payload
|
||||
Data T `json:"data"`
|
||||
} // @name Response
|
||||
|
||||
type PagedTotalResponse[T any] struct {
|
||||
// Data contains the actual response payload
|
||||
Data []T `json:"data"`
|
||||
|
||||
// Pagination contains information about paging
|
||||
Pagination PaginationWithTotal `json:"pagination"`
|
||||
} // @name PagedTotalResponse
|
||||
|
||||
// PaginationWithTotal holds information about the current page, total items, etc.
|
||||
type PaginationWithTotal struct {
|
||||
// Current page number
|
||||
Page int `json:"page"`
|
||||
|
||||
// Number of items per page
|
||||
PageSize int `json:"page_size"`
|
||||
|
||||
// Total number of items across all pages
|
||||
TotalItems int `json:"total_items"`
|
||||
|
||||
// Total number of pages
|
||||
TotalPages int `json:"total_pages"`
|
||||
} // @name PaginationWithTotal
|
||||
|
||||
type PagedResponse[T any] struct {
|
||||
// Data contains the actual response payload
|
||||
Data []T `json:"data"`
|
||||
|
||||
// Pagination contains information about paging
|
||||
Pagination Pagination `json:"pagination"`
|
||||
} // @name PagedResponse
|
||||
|
||||
// Pagination holds information about the current page.
|
||||
type Pagination struct {
|
||||
// Current page number
|
||||
Page int `json:"page"`
|
||||
|
||||
// Number of items per page
|
||||
PageSize int `json:"page_size"`
|
||||
} // @name Pagination
|
||||
|
||||
// Error holds error responses
|
||||
type Error struct {
|
||||
Error string `json:"error"`
|
||||
} // @name Error
|
||||
98
pkg/public_api/handlers/handler.go
Normal file
98
pkg/public_api/handlers/handler.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"google.golang.org/grpc"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrMsgDataClient = "data client is required"
|
||||
)
|
||||
|
||||
// Handler is a base handler that provides common functionality for all HTTP handlers.
|
||||
type Handler struct {
|
||||
mapsClient *grpc.ClientConn
|
||||
}
|
||||
|
||||
// HandlerOption defines a functional option for configuring a Handler
|
||||
type HandlerOption func(*Handler)
|
||||
|
||||
// WithMapsClient sets the data client for the Handler
|
||||
func WithMapsClient(mapsClient *grpc.ClientConn) HandlerOption {
|
||||
return func(h *Handler) {
|
||||
h.mapsClient = mapsClient
|
||||
}
|
||||
}
|
||||
|
||||
// NewHandler creates a new Handler with the provided options.
|
||||
// It requires both a datastore and an authentication service to function properly.
|
||||
func NewHandler(options ...HandlerOption) (*Handler, error) {
|
||||
handler := &Handler{}
|
||||
|
||||
// Apply all provided options
|
||||
for _, option := range options {
|
||||
option(handler)
|
||||
}
|
||||
|
||||
// Validate required dependencies
|
||||
if err := handler.validateDependencies(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
// validateDependencies ensures all required dependencies are properly set
|
||||
func (h *Handler) validateDependencies() error {
|
||||
if h.mapsClient == nil {
|
||||
return fmt.Errorf(ErrMsgDataClient)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateRange ensures a value is within the specified range, returning defaultValue if outside
|
||||
func validateRange(value, min, max, defaultValue int) int {
|
||||
if value < min {
|
||||
return defaultValue
|
||||
}
|
||||
if value > max {
|
||||
return max
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// validateMin ensures a value is at least the minimum, returning defaultValue if below
|
||||
func validateMin(value, min, defaultValue int) int {
|
||||
if value < min {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// getPagination extracts pagination parameters from query string.
|
||||
// It applies validation rules to ensure parameters are within acceptable ranges.
|
||||
func getPagination(ctx *gin.Context, defaultPageSize, minPageSize, maxPageSize int) (pageSize, pageNumber int) {
|
||||
// Get page size from query string, parse to integer
|
||||
pageSizeStr := ctx.Query("page_size")
|
||||
if pageSizeStr != "" {
|
||||
pageSize, _ = strconv.Atoi(pageSizeStr)
|
||||
} else {
|
||||
pageSize = defaultPageSize
|
||||
}
|
||||
|
||||
// Get page number from query string, parse to integer
|
||||
pageNumberStr := ctx.Query("page_number")
|
||||
if pageNumberStr != "" {
|
||||
pageNumber, _ = strconv.Atoi(pageNumberStr)
|
||||
} else {
|
||||
pageNumber = 1 // Default to first page
|
||||
}
|
||||
|
||||
// Apply validation rules
|
||||
pageSize = validateRange(pageSize, minPageSize, maxPageSize, defaultPageSize)
|
||||
pageNumber = validateMin(pageNumber, 1, 1)
|
||||
|
||||
return pageSize, pageNumber
|
||||
}
|
||||
153
pkg/public_api/handlers/maps.go
Normal file
153
pkg/public_api/handlers/maps.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"git.itzana.me/strafesnet/go-grpc/maps_extended"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/public_api/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// MapHandler handles HTTP requests related to maps.
|
||||
type MapHandler struct {
|
||||
*Handler
|
||||
}
|
||||
|
||||
// NewMapHandler creates a new MapHandler with the provided options.
|
||||
func NewMapHandler(options ...HandlerOption) (*MapHandler, error) {
|
||||
baseHandler, err := NewHandler(options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &MapHandler{
|
||||
Handler: baseHandler,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// @Summary Get map by ID
|
||||
// @Description Get a specific map by its ID
|
||||
// @Tags maps
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param id path int true "Map ID"
|
||||
// @Success 200 {object} dto.Response[dto.Map]
|
||||
// @Failure 404 {object} dto.Error "Map not found"
|
||||
// @Failure default {object} dto.Error "General error response"
|
||||
// @Router /map/{id} [get]
|
||||
func (h *MapHandler) Get(ctx *gin.Context) {
|
||||
// Extract map ID from path parameter
|
||||
id := ctx.Param("id")
|
||||
mapID, err := strconv.ParseInt(id, 10, 64)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, dto.Error{
|
||||
Error: "Invalid map ID format",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Call the gRPC service
|
||||
mapData, err := maps_extended.NewMapsServiceClient(h.mapsClient).Get(ctx, &maps_extended.MapId{
|
||||
ID: mapID,
|
||||
})
|
||||
if err != nil {
|
||||
statusCode := http.StatusInternalServerError
|
||||
errorMessage := "Failed to get map"
|
||||
|
||||
// Check if it's a "not found" error
|
||||
if status.Code(err) == codes.NotFound {
|
||||
statusCode = http.StatusNotFound
|
||||
errorMessage = "Map not found"
|
||||
}
|
||||
|
||||
ctx.JSON(statusCode, dto.Error{
|
||||
Error: errorMessage,
|
||||
})
|
||||
log.WithError(err).Error(
|
||||
"Failed to get map",
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert gRPC MapResponse object to dto.Map object
|
||||
var mapDto dto.Map
|
||||
result := mapDto.FromGRPC(mapData)
|
||||
|
||||
// Return the map data
|
||||
ctx.JSON(http.StatusOK, dto.Response[dto.Map]{
|
||||
Data: *result,
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary List maps
|
||||
// @Description Get a list of maps
|
||||
// @Tags maps
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param page_size query int false "Page size (max 100)" default(10) minimum(1) maximum(100)
|
||||
// @Param page_number query int false "Page number" default(1) minimum(1)
|
||||
// @Param filter query dto.MapFilter false "Map filter parameters"
|
||||
// @Success 200 {object} dto.PagedResponse[dto.Map]
|
||||
// @Failure default {object} dto.Error "General error response"
|
||||
// @Router /map [get]
|
||||
func (h *MapHandler) List(ctx *gin.Context) {
|
||||
// Extract and constrain pagination parameters
|
||||
query := struct {
|
||||
PageSize int `form:"page_size,default=10" binding:"min=1,max=100"`
|
||||
PageNumber int `form:"page_number,default=1" binding:"min=1"`
|
||||
SortBy int `form:"sort_by,default=0" binding:"min=0,max=3"`
|
||||
}{}
|
||||
if err := ctx.ShouldBindQuery(&query); err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, dto.Error{
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get list filter
|
||||
var filter dto.MapFilter
|
||||
if err := ctx.ShouldBindQuery(&filter); err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, dto.Error{
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Call the gRPC service
|
||||
mapList, err := maps_extended.NewMapsServiceClient(h.mapsClient).List(ctx, &maps_extended.ListRequest{
|
||||
Filter: &maps_extended.MapFilter{
|
||||
GameID: filter.GameID,
|
||||
},
|
||||
Page: &maps_extended.Pagination{
|
||||
Size: uint32(query.PageSize),
|
||||
Number: uint32(query.PageNumber),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, dto.Error{
|
||||
Error: "Failed to list maps",
|
||||
})
|
||||
log.WithError(err).Error(
|
||||
"Failed to list maps",
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert gRPC MapResponse objects to dto.Map objects
|
||||
dtoMaps := make([]dto.Map, len(mapList.Maps))
|
||||
for i, m := range mapList.Maps {
|
||||
var mapDto dto.Map
|
||||
dtoMaps[i] = *mapDto.FromGRPC(m)
|
||||
}
|
||||
|
||||
// Return the paged response
|
||||
ctx.JSON(http.StatusOK, dto.PagedResponse[dto.Map]{
|
||||
Data: dtoMaps,
|
||||
Pagination: dto.Pagination{
|
||||
Page: query.PageNumber,
|
||||
PageSize: query.PageSize,
|
||||
},
|
||||
})
|
||||
}
|
||||
159
pkg/public_api/router.go
Normal file
159
pkg/public_api/router.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.itzana.me/StrafesNET/dev-service/pkg/api/middleware"
|
||||
"git.itzana.me/strafesnet/maps-service/docs"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/public_api/handlers"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
swaggerfiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
"github.com/urfave/cli/v2"
|
||||
"google.golang.org/grpc"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Option defines a function that configures a Router
|
||||
type Option func(*RouterConfig)
|
||||
|
||||
// RouterConfig holds all router configuration
|
||||
type RouterConfig struct {
|
||||
port int
|
||||
devClient *grpc.ClientConn
|
||||
mapsClient *grpc.ClientConn
|
||||
context *cli.Context
|
||||
shutdownTimeout time.Duration
|
||||
}
|
||||
|
||||
// WithPort sets the port for the server£
|
||||
func WithPort(port int) Option {
|
||||
return func(cfg *RouterConfig) {
|
||||
cfg.port = port
|
||||
}
|
||||
}
|
||||
|
||||
// WithContext sets the context for the server
|
||||
func WithContext(ctx *cli.Context) Option {
|
||||
return func(cfg *RouterConfig) {
|
||||
cfg.context = ctx
|
||||
}
|
||||
}
|
||||
|
||||
// WithDevClient sets the dev gRPC client
|
||||
func WithDevClient(conn *grpc.ClientConn) Option {
|
||||
return func(cfg *RouterConfig) {
|
||||
cfg.devClient = conn
|
||||
}
|
||||
}
|
||||
|
||||
// WithMapsClient sets the data gRPC client
|
||||
func WithMapsClient(conn *grpc.ClientConn) Option {
|
||||
return func(cfg *RouterConfig) {
|
||||
cfg.mapsClient = conn
|
||||
}
|
||||
}
|
||||
|
||||
// WithShutdownTimeout sets the graceful shutdown timeout
|
||||
func WithShutdownTimeout(timeout time.Duration) Option {
|
||||
return func(cfg *RouterConfig) {
|
||||
cfg.shutdownTimeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
func setupRoutes(cfg *RouterConfig) (*gin.Engine, error) {
|
||||
r := gin.Default()
|
||||
r.ForwardedByClientIP = true
|
||||
r.Use(gin.Logger())
|
||||
r.Use(gin.Recovery())
|
||||
|
||||
handlerOptions := []handlers.HandlerOption{
|
||||
handlers.WithMapsClient(cfg.mapsClient),
|
||||
}
|
||||
|
||||
// Maps handler
|
||||
mapsHandler, err := handlers.NewMapHandler(handlerOptions...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
docs.SwaggerInfo.BasePath = "/public-api/v1"
|
||||
public_api := r.Group("/public-api")
|
||||
{
|
||||
v1 := public_api.Group("/v1")
|
||||
{
|
||||
// Auth middleware
|
||||
v1.Use(middleware.ValidateRequest("Maps", "Read", cfg.devClient))
|
||||
|
||||
// Maps
|
||||
v1.GET("/map", mapsHandler.List)
|
||||
v1.GET("/map/:id", mapsHandler.Get)
|
||||
}
|
||||
|
||||
// Docs
|
||||
public_api.GET("/docs/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
|
||||
public_api.GET("/", func(ctx *gin.Context) {
|
||||
ctx.Redirect(http.StatusPermanentRedirect, "/docs/index.html")
|
||||
})
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// NewRouter creates a new router with the given options
|
||||
func NewRouter(options ...Option) error {
|
||||
// Default configuration
|
||||
cfg := &RouterConfig{
|
||||
port: 8080, // Default port
|
||||
context: nil,
|
||||
shutdownTimeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
// Apply options
|
||||
for _, option := range options {
|
||||
option(cfg)
|
||||
}
|
||||
|
||||
// Validate configuration
|
||||
if cfg.context == nil {
|
||||
return errors.New("context is required")
|
||||
}
|
||||
|
||||
if cfg.devClient == nil {
|
||||
return errors.New("dev client is required")
|
||||
}
|
||||
|
||||
routes, err := setupRoutes(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("Starting server")
|
||||
|
||||
return runServer(cfg.context.Context, fmt.Sprint(":", cfg.port), routes, cfg.shutdownTimeout)
|
||||
}
|
||||
|
||||
func runServer(ctx context.Context, addr string, r *gin.Engine, shutdownTimeout time.Duration) error {
|
||||
srv := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: r,
|
||||
}
|
||||
|
||||
// Run the server in a separate goroutine
|
||||
go func() {
|
||||
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.WithError(err).Fatal("web server exit")
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for a shutdown signal
|
||||
<-ctx.Done()
|
||||
|
||||
// Shutdown server gracefully
|
||||
ctxShutdown, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
||||
defer cancel()
|
||||
return srv.Shutdown(ctxShutdown)
|
||||
}
|
||||
95
pkg/roblox/asset_location.go
Normal file
95
pkg/roblox/asset_location.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package roblox
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type AssetMetadata struct {
|
||||
MetadataType uint32 `json:"metadataType"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// Struct equivalent to Rust's AssetLocationInfo
|
||||
type AssetLocationInfo struct {
|
||||
Location string `json:"location"`
|
||||
RequestId string `json:"requestId"`
|
||||
IsArchived bool `json:"isArchived"`
|
||||
AssetTypeId uint32 `json:"assetTypeId"`
|
||||
AssetMetadatas []AssetMetadata `json:"assetMetadatas"`
|
||||
IsRecordable bool `json:"isRecordable"`
|
||||
}
|
||||
|
||||
// Input struct for getAssetLocation
|
||||
type GetAssetLatestRequest struct {
|
||||
AssetID uint64
|
||||
}
|
||||
|
||||
// Custom error type if needed
|
||||
type GetError string
|
||||
|
||||
func (e GetError) Error() string { return string(e) }
|
||||
|
||||
// Example client with a Get method
|
||||
type Client struct {
|
||||
HttpClient *http.Client
|
||||
ApiKey string
|
||||
}
|
||||
|
||||
func (c *Client) GetAssetLocation(config GetAssetLatestRequest) (*AssetLocationInfo, error) {
|
||||
rawURL := fmt.Sprintf("https://apis.roblox.com/asset-delivery-api/v1/assetId/%d", config.AssetID)
|
||||
parsedURL, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return nil, GetError("ParseError: " + err.Error())
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", parsedURL.String(), nil)
|
||||
if err != nil {
|
||||
return nil, GetError("RequestCreationError: " + err.Error())
|
||||
}
|
||||
|
||||
req.Header.Set("x-api-key", c.ApiKey)
|
||||
|
||||
resp, err := c.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, GetError("RequestError: " + err.Error())
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, GetError(fmt.Sprintf("ResponseError: status code %d", resp.StatusCode))
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, GetError("ReadBodyError: " + err.Error())
|
||||
}
|
||||
|
||||
var info AssetLocationInfo
|
||||
if err := json.Unmarshal(body, &info); err != nil {
|
||||
return nil, GetError("JSONError: " + err.Error())
|
||||
}
|
||||
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func (c *Client) DownloadAsset(info *AssetLocationInfo) (io.Reader, error) {
|
||||
req, err := http.NewRequest("GET", info.Location, nil)
|
||||
if err != nil {
|
||||
return nil, GetError("RequestCreationError: " + err.Error())
|
||||
}
|
||||
|
||||
resp, err := c.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, GetError("RequestError: " + err.Error())
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, GetError(fmt.Sprintf("ResponseError: status code %d", resp.StatusCode))
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
}
|
||||
238
pkg/service/audit_events.go
Normal file
238
pkg/service/audit_events.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"git.itzana.me/strafesnet/go-grpc/users"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/api"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
)
|
||||
|
||||
func (svc *Service) ListAuditEvents(ctx context.Context, resource model.Resource, page model.Page) ([]api.AuditEvent, error){
|
||||
filter := datastore.Optional()
|
||||
|
||||
filter.Add("resource_type", resource.Type)
|
||||
filter.Add("resource_id", resource.ID)
|
||||
|
||||
items, err := svc.db.AuditEvents().List(ctx, filter, page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
idMap := make(map[int64]bool)
|
||||
for _, item := range items {
|
||||
idMap[int64(item.User)] = true
|
||||
}
|
||||
|
||||
var idList users.IdList
|
||||
idList.ID = make([]int64,len(idMap))
|
||||
for userId := range idMap {
|
||||
idList.ID = append(idList.ID, userId)
|
||||
}
|
||||
|
||||
userList, err := svc.users.GetList(ctx, &idList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userMap := make(map[int64]*users.UserResponse)
|
||||
for _,user := range userList.Users {
|
||||
userMap[user.ID] = user
|
||||
}
|
||||
|
||||
var resp []api.AuditEvent
|
||||
for _, item := range items {
|
||||
EventData := api.AuditEventEventData{}
|
||||
err = EventData.UnmarshalJSON(item.EventData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
username := ""
|
||||
if userMap[int64(item.User)] != nil {
|
||||
username = userMap[int64(item.User)].Username
|
||||
}
|
||||
resp = append(resp, api.AuditEvent{
|
||||
ID: item.ID,
|
||||
Date: item.CreatedAt.Unix(),
|
||||
User: int64(item.User),
|
||||
Username: username,
|
||||
ResourceType: int32(item.ResourceType),
|
||||
ResourceID: item.ResourceID,
|
||||
EventType: int32(item.EventType),
|
||||
EventData: EventData,
|
||||
})
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (svc *Service) CreateAuditEventAction(ctx context.Context, userId uint64, resource model.Resource, event_data model.AuditEventDataAction) error {
|
||||
EventData, err := json.Marshal(event_data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = svc.db.AuditEvents().Create(ctx, model.AuditEvent{
|
||||
ID: 0,
|
||||
User: userId,
|
||||
ResourceType: resource.Type,
|
||||
ResourceID: resource.ID,
|
||||
EventType: model.AuditEventTypeAction,
|
||||
EventData: EventData,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) CreateAuditEventComment(ctx context.Context, userId uint64, resource model.Resource, event_data model.AuditEventDataComment) error {
|
||||
EventData, err := json.Marshal(event_data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = svc.db.AuditEvents().Create(ctx, model.AuditEvent{
|
||||
ID: 0,
|
||||
User: userId,
|
||||
ResourceType: resource.Type,
|
||||
ResourceID: resource.ID,
|
||||
EventType: model.AuditEventTypeComment,
|
||||
EventData: EventData,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) CreateAuditEventChangeModel(ctx context.Context, userId uint64, resource model.Resource, event_data model.AuditEventDataChangeModel) error {
|
||||
EventData, err := json.Marshal(event_data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = svc.db.AuditEvents().Create(ctx, model.AuditEvent{
|
||||
ID: 0,
|
||||
User: userId,
|
||||
ResourceType: resource.Type,
|
||||
ResourceID: resource.ID,
|
||||
EventType: model.AuditEventTypeChangeModel,
|
||||
EventData: EventData,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func (svc *Service) CreateAuditEventChangeValidatedModel(ctx context.Context, userId uint64, resource model.Resource, event_data model.AuditEventDataChangeValidatedModel) error {
|
||||
EventData, err := json.Marshal(event_data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = svc.db.AuditEvents().Create(ctx, model.AuditEvent{
|
||||
ID: 0,
|
||||
User: userId,
|
||||
ResourceType: resource.Type,
|
||||
ResourceID: resource.ID,
|
||||
EventType: model.AuditEventTypeChangeValidatedModel,
|
||||
EventData: EventData,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) CreateAuditEventError(ctx context.Context, userId uint64, resource model.Resource, event_data model.AuditEventDataError) error {
|
||||
EventData, err := json.Marshal(event_data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = svc.db.AuditEvents().Create(ctx, model.AuditEvent{
|
||||
ID: 0,
|
||||
User: userId,
|
||||
ResourceType: resource.Type,
|
||||
ResourceID: resource.ID,
|
||||
EventType: model.AuditEventTypeError,
|
||||
EventData: EventData,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) CreateAuditEventCheckList(ctx context.Context, userId uint64, resource model.Resource, event_data model.AuditEventDataCheckList) error {
|
||||
EventData, err := json.Marshal(event_data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = svc.db.AuditEvents().Create(ctx, model.AuditEvent{
|
||||
ID: 0,
|
||||
User: userId,
|
||||
ResourceType: resource.Type,
|
||||
ResourceID: resource.ID,
|
||||
EventType: model.AuditEventTypeCheckList,
|
||||
EventData: EventData,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) CreateAuditEventChangeDisplayName(ctx context.Context, userId uint64, resource model.Resource, event_data model.AuditEventDataChangeName) error {
|
||||
EventData, err := json.Marshal(event_data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = svc.db.AuditEvents().Create(ctx, model.AuditEvent{
|
||||
ID: 0,
|
||||
User: userId,
|
||||
ResourceType: resource.Type,
|
||||
ResourceID: resource.ID,
|
||||
EventType: model.AuditEventTypeChangeDisplayName,
|
||||
EventData: EventData,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) CreateAuditEventChangeCreator(ctx context.Context, userId uint64, resource model.Resource, event_data model.AuditEventDataChangeName) error {
|
||||
EventData, err := json.Marshal(event_data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = svc.db.AuditEvents().Create(ctx, model.AuditEvent{
|
||||
ID: 0,
|
||||
User: userId,
|
||||
ResourceType: resource.Type,
|
||||
ResourceID: resource.ID,
|
||||
EventType: model.AuditEventTypeChangeCreator,
|
||||
EventData: EventData,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
116
pkg/service/mapfixes.go
Normal file
116
pkg/service/mapfixes.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
)
|
||||
|
||||
type MapfixUpdate datastore.OptionalMap
|
||||
|
||||
func NewMapfixUpdate() MapfixUpdate {
|
||||
update := datastore.Optional()
|
||||
return MapfixUpdate(update)
|
||||
}
|
||||
|
||||
func (update MapfixUpdate) SetDisplayName(display_name string) {
|
||||
datastore.OptionalMap(update).Add("display_name", display_name)
|
||||
}
|
||||
func (update MapfixUpdate) SetCreator(creator string) {
|
||||
datastore.OptionalMap(update).Add("creator", creator)
|
||||
}
|
||||
func (update MapfixUpdate) SetGameID(game_id uint32) {
|
||||
datastore.OptionalMap(update).Add("game_id", game_id)
|
||||
}
|
||||
func (update MapfixUpdate) SetSubmitter(submitter uint64) {
|
||||
datastore.OptionalMap(update).Add("submitter", submitter)
|
||||
}
|
||||
func (update MapfixUpdate) SetAssetID(asset_id uint64) {
|
||||
datastore.OptionalMap(update).Add("asset_id", asset_id)
|
||||
}
|
||||
func (update MapfixUpdate) SetAssetVersion(asset_version uint64) {
|
||||
datastore.OptionalMap(update).Add("asset_version", asset_version)
|
||||
}
|
||||
func (update MapfixUpdate) SetValidatedAssetID(validated_asset_id uint64) {
|
||||
datastore.OptionalMap(update).Add("validated_asset_id", validated_asset_id)
|
||||
}
|
||||
func (update MapfixUpdate) SetValidatedAssetVersion(validated_asset_version uint64) {
|
||||
datastore.OptionalMap(update).Add("validated_asset_version", validated_asset_version)
|
||||
}
|
||||
func (update MapfixUpdate) SetCompleted(completed bool) {
|
||||
datastore.OptionalMap(update).Add("completed", completed)
|
||||
}
|
||||
func (update MapfixUpdate) SetTargetAssetID(target_asset_id uint64) {
|
||||
datastore.OptionalMap(update).Add("target_asset_id", target_asset_id)
|
||||
}
|
||||
func (update MapfixUpdate) SetStatusID(status_id model.MapfixStatus) {
|
||||
datastore.OptionalMap(update).Add("status_id", status_id)
|
||||
}
|
||||
func (update MapfixUpdate) SetDescription(description string) {
|
||||
datastore.OptionalMap(update).Add("description", description)
|
||||
}
|
||||
|
||||
type MapfixFilter datastore.OptionalMap
|
||||
|
||||
func NewMapfixFilter(
|
||||
) MapfixFilter {
|
||||
filter := datastore.Optional()
|
||||
return MapfixFilter(filter)
|
||||
}
|
||||
func (update MapfixFilter) SetDisplayName(display_name string) {
|
||||
datastore.OptionalMap(update).Add("display_name", display_name)
|
||||
}
|
||||
func (update MapfixFilter) SetCreator(creator string) {
|
||||
datastore.OptionalMap(update).Add("creator", creator)
|
||||
}
|
||||
func (update MapfixFilter) SetGameID(game_id uint32) {
|
||||
datastore.OptionalMap(update).Add("game_id", game_id)
|
||||
}
|
||||
func (update MapfixFilter) SetSubmitter(submitter uint64) {
|
||||
datastore.OptionalMap(update).Add("submitter", submitter)
|
||||
}
|
||||
func (update MapfixFilter) SetAssetID(asset_id uint64) {
|
||||
datastore.OptionalMap(update).Add("asset_id", asset_id)
|
||||
}
|
||||
func (update MapfixFilter) SetAssetVersion(asset_version uint64) {
|
||||
datastore.OptionalMap(update).Add("asset_version", asset_version)
|
||||
}
|
||||
func (update MapfixFilter) SetTargetAssetID(target_asset_id uint64) {
|
||||
datastore.OptionalMap(update).Add("target_asset_id", target_asset_id)
|
||||
}
|
||||
func (update MapfixFilter) SetStatuses(statuses []model.MapfixStatus) {
|
||||
datastore.OptionalMap(update).Add("status_id", statuses)
|
||||
}
|
||||
|
||||
func (svc *Service) CreateMapfix(ctx context.Context, script model.Mapfix) (model.Mapfix, error) {
|
||||
return svc.db.Mapfixes().Create(ctx, script)
|
||||
}
|
||||
|
||||
func (svc *Service) ListMapfixes(ctx context.Context, filter MapfixFilter, page model.Page, sort datastore.ListSort) ([]model.Mapfix, error) {
|
||||
return svc.db.Mapfixes().List(ctx, datastore.OptionalMap(filter), page, sort)
|
||||
}
|
||||
|
||||
func (svc *Service) ListMapfixesWithTotal(ctx context.Context, filter MapfixFilter, page model.Page, sort datastore.ListSort) (int64, []model.Mapfix, error) {
|
||||
return svc.db.Mapfixes().ListWithTotal(ctx, datastore.OptionalMap(filter), page, sort)
|
||||
}
|
||||
|
||||
func (svc *Service) DeleteMapfix(ctx context.Context, id int64) error {
|
||||
return svc.db.Mapfixes().Delete(ctx, id)
|
||||
}
|
||||
|
||||
func (svc *Service) GetMapfix(ctx context.Context, id int64) (model.Mapfix, error) {
|
||||
return svc.db.Mapfixes().Get(ctx, id)
|
||||
}
|
||||
|
||||
func (svc *Service) UpdateMapfix(ctx context.Context, id int64, pmap MapfixUpdate) error {
|
||||
return svc.db.Mapfixes().Update(ctx, id, datastore.OptionalMap(pmap))
|
||||
}
|
||||
|
||||
func (svc *Service) UpdateMapfixIfStatus(ctx context.Context, id int64, statuses []model.MapfixStatus, pmap MapfixUpdate) error {
|
||||
return svc.db.Mapfixes().IfStatusThenUpdate(ctx, id, statuses, datastore.OptionalMap(pmap))
|
||||
}
|
||||
|
||||
func (svc *Service) UpdateAndGetMapfixIfStatus(ctx context.Context, id int64, statuses []model.MapfixStatus, pmap MapfixUpdate) (model.Mapfix, error) {
|
||||
return svc.db.Mapfixes().IfStatusThenUpdateAndGet(ctx, id, statuses, datastore.OptionalMap(pmap))
|
||||
}
|
||||
149
pkg/service/maps.go
Normal file
149
pkg/service/maps.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.itzana.me/strafesnet/go-grpc/maps"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
)
|
||||
|
||||
// Optional map used to update an object
|
||||
type MapUpdate datastore.OptionalMap
|
||||
|
||||
func NewMapUpdate() MapUpdate {
|
||||
update := datastore.Optional()
|
||||
return MapUpdate(update)
|
||||
}
|
||||
|
||||
func (update MapUpdate) SetDisplayName(display_name string) {
|
||||
datastore.OptionalMap(update).Add("display_name", display_name)
|
||||
}
|
||||
func (update MapUpdate) SetCreator(creator string) {
|
||||
datastore.OptionalMap(update).Add("creator", creator)
|
||||
}
|
||||
func (update MapUpdate) SetGameID(game_id uint32) {
|
||||
datastore.OptionalMap(update).Add("game_id", game_id)
|
||||
}
|
||||
func (update MapUpdate) SetDate(date int64) {
|
||||
datastore.OptionalMap(update).Add("date", date)
|
||||
}
|
||||
func (update MapUpdate) SetSubmitter(submitter uint64) {
|
||||
datastore.OptionalMap(update).Add("submitter", submitter)
|
||||
}
|
||||
func (update MapUpdate) SetThumbnail(thumbnail uint64) {
|
||||
datastore.OptionalMap(update).Add("thumbnail", thumbnail)
|
||||
}
|
||||
func (update MapUpdate) SetAssetVersion(asset_version uint64) {
|
||||
datastore.OptionalMap(update).Add("asset_version", asset_version)
|
||||
}
|
||||
func (update MapUpdate) SetModes(modes uint32) {
|
||||
datastore.OptionalMap(update).Add("modes", modes)
|
||||
}
|
||||
|
||||
// getters
|
||||
func (update MapUpdate) GetDisplayName() (string, bool) {
|
||||
value, ok := datastore.OptionalMap(update).Map()["display_name"].(string)
|
||||
return value, ok
|
||||
}
|
||||
func (update MapUpdate) GetGameID() (uint32, bool) {
|
||||
value, ok := datastore.OptionalMap(update).Map()["game_id"].(uint32)
|
||||
return value, ok
|
||||
}
|
||||
func (update MapUpdate) GetThumbnail() (uint64, bool) {
|
||||
value, ok := datastore.OptionalMap(update).Map()["thumbnail"].(uint64)
|
||||
return value, ok
|
||||
}
|
||||
|
||||
// Optional map used to find matching objects
|
||||
type MapFilter datastore.OptionalMap
|
||||
|
||||
func NewMapFilter(
|
||||
) MapFilter {
|
||||
filter := datastore.Optional()
|
||||
return MapFilter(filter)
|
||||
}
|
||||
func (update MapFilter) SetDisplayName(display_name string) {
|
||||
datastore.OptionalMap(update).Add("display_name", display_name)
|
||||
}
|
||||
func (update MapFilter) SetCreator(creator string) {
|
||||
datastore.OptionalMap(update).Add("creator", creator)
|
||||
}
|
||||
func (update MapFilter) SetGameID(game_id uint32) {
|
||||
datastore.OptionalMap(update).Add("game_id", game_id)
|
||||
}
|
||||
func (update MapFilter) SetSubmitter(submitter uint64) {
|
||||
datastore.OptionalMap(update).Add("submitter", submitter)
|
||||
}
|
||||
|
||||
func (svc *Service) CreateMap(ctx context.Context, item model.Map) (int64, error) {
|
||||
// 2 jobs:
|
||||
// create map on maps-service
|
||||
map_item, err := svc.db.Maps().Create(ctx, item)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// create map on data-service
|
||||
game_id := int32(item.GameID)
|
||||
_, err = svc.maps.Create(ctx, &maps.MapRequest{
|
||||
ID: item.ID,
|
||||
DisplayName: &item.DisplayName,
|
||||
GameID: &game_id,
|
||||
Thumbnail: &item.Thumbnail,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return map_item.ID, nil
|
||||
}
|
||||
|
||||
func (svc *Service) ListMaps(ctx context.Context, filter MapFilter, page model.Page) ([]model.Map, error) {
|
||||
return svc.db.Maps().List(ctx, datastore.OptionalMap(filter), page)
|
||||
}
|
||||
|
||||
func (svc *Service) GetMapList(ctx context.Context, ids []int64) ([]model.Map, error) {
|
||||
return svc.db.Maps().GetList(ctx, ids)
|
||||
}
|
||||
|
||||
func (svc *Service) DeleteMap(ctx context.Context, id int64) error {
|
||||
// Do not delete the "embedded" map, since it deletes times.
|
||||
// _, err := svc.maps.Delete(ctx, &maps.IdMessage{ID: id})
|
||||
return svc.db.Maps().Delete(ctx, id)
|
||||
}
|
||||
|
||||
func (svc *Service) GetMap(ctx context.Context, id int64) (model.Map, error) {
|
||||
return svc.db.Maps().Get(ctx, id)
|
||||
}
|
||||
|
||||
func (svc *Service) UpdateMap(ctx context.Context, id int64, pmap MapUpdate) error {
|
||||
// 2 jobs:
|
||||
// update map on maps-service
|
||||
err := svc.db.Maps().Update(ctx, id, datastore.OptionalMap(pmap))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// update map on data-service
|
||||
update := maps.MapRequest{
|
||||
ID: id,
|
||||
}
|
||||
if display_name, ok := pmap.GetDisplayName(); ok {
|
||||
update.DisplayName = &display_name
|
||||
}
|
||||
if game_id, ok := pmap.GetGameID(); ok {
|
||||
game_id_int32 := int32(game_id)
|
||||
update.GameID = &game_id_int32
|
||||
}
|
||||
if thumbnail, ok := pmap.GetThumbnail(); ok {
|
||||
update.Thumbnail = &thumbnail
|
||||
}
|
||||
_, err = svc.maps.Update(ctx, &update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) IncrementMapLoadCount(ctx context.Context, id int64) error {
|
||||
return svc.db.Maps().IncrementLoadCount(ctx, id)
|
||||
}
|
||||
114
pkg/service/nats_mapfix.go
Normal file
114
pkg/service/nats_mapfix.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
)
|
||||
|
||||
func (svc *Service) NatsCreateMapfix(
|
||||
OperationID int32,
|
||||
ModelID uint64,
|
||||
TargetAssetID uint64,
|
||||
Description string,
|
||||
) error {
|
||||
create_request := model.CreateMapfixRequest{
|
||||
OperationID: OperationID,
|
||||
ModelID: ModelID,
|
||||
TargetAssetID: TargetAssetID,
|
||||
Description: Description,
|
||||
}
|
||||
|
||||
j, err := json.Marshal(create_request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = svc.nats.Publish("maptest.mapfixes.create", []byte(j))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) NatsCheckMapfix(
|
||||
MapfixID int64,
|
||||
ModelID uint64,
|
||||
SkipChecks bool,
|
||||
) error {
|
||||
validate_request := model.CheckMapfixRequest{
|
||||
MapfixID: MapfixID,
|
||||
ModelID: ModelID,
|
||||
SkipChecks: SkipChecks,
|
||||
}
|
||||
|
||||
j, err := json.Marshal(validate_request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = svc.nats.Publish("maptest.mapfixes.check", []byte(j))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) NatsUploadMapfix(
|
||||
MapfixID int64,
|
||||
ModelID uint64,
|
||||
ModelVersion uint64,
|
||||
TargetAssetID uint64,
|
||||
) error {
|
||||
upload_fix_request := model.UploadMapfixRequest{
|
||||
MapfixID: MapfixID,
|
||||
ModelID: ModelID,
|
||||
ModelVersion: ModelVersion,
|
||||
TargetAssetID: TargetAssetID,
|
||||
}
|
||||
|
||||
j, err := json.Marshal(upload_fix_request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = svc.nats.Publish("maptest.mapfixes.upload", []byte(j))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) NatsValidateMapfix(
|
||||
MapfixID int64,
|
||||
ModelID uint64,
|
||||
ModelVersion uint64,
|
||||
ValidatedAssetID uint64,
|
||||
) error {
|
||||
validate_request := model.ValidateMapfixRequest{
|
||||
MapfixID: MapfixID,
|
||||
ModelID: ModelID,
|
||||
ModelVersion: ModelVersion,
|
||||
ValidatedModelID: nil,
|
||||
}
|
||||
|
||||
// sentinel values because we're not using rust
|
||||
if ValidatedAssetID != 0 {
|
||||
validate_request.ValidatedModelID = &ValidatedAssetID
|
||||
}
|
||||
|
||||
j, err := json.Marshal(validate_request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = svc.nats.Publish("maptest.mapfixes.validate", []byte(j))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
120
pkg/service/nats_submission.go
Normal file
120
pkg/service/nats_submission.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
)
|
||||
|
||||
func (svc *Service) NatsCreateSubmission(
|
||||
OperationID int32,
|
||||
ModelID uint64,
|
||||
DisplayName string,
|
||||
Creator string,
|
||||
GameID uint32,
|
||||
Status uint32,
|
||||
Roles uint32,
|
||||
) error {
|
||||
create_request := model.CreateSubmissionRequest{
|
||||
OperationID: OperationID,
|
||||
ModelID: ModelID,
|
||||
DisplayName: DisplayName,
|
||||
Creator: Creator,
|
||||
GameID: GameID,
|
||||
Status: Status,
|
||||
Roles: Roles,
|
||||
}
|
||||
|
||||
j, err := json.Marshal(create_request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = svc.nats.Publish("maptest.submissions.create", []byte(j))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) NatsCheckSubmission(
|
||||
SubmissionID int64,
|
||||
ModelID uint64,
|
||||
SkipChecks bool,
|
||||
) error {
|
||||
validate_request := model.CheckSubmissionRequest{
|
||||
SubmissionID: SubmissionID,
|
||||
ModelID: ModelID,
|
||||
SkipChecks: SkipChecks,
|
||||
}
|
||||
|
||||
j, err := json.Marshal(validate_request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = svc.nats.Publish("maptest.submissions.check", []byte(j))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) NatsUploadSubmission(
|
||||
SubmissionID int64,
|
||||
ModelID uint64,
|
||||
ModelVersion uint64,
|
||||
ModelName string,
|
||||
) error {
|
||||
upload_new_request := model.UploadSubmissionRequest{
|
||||
SubmissionID: SubmissionID,
|
||||
ModelID: ModelID,
|
||||
ModelVersion: ModelVersion,
|
||||
ModelName: ModelName,
|
||||
}
|
||||
|
||||
j, err := json.Marshal(upload_new_request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = svc.nats.Publish("maptest.submissions.upload", []byte(j))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) NatsValidateSubmission(
|
||||
SubmissionID int64,
|
||||
ModelID uint64,
|
||||
ModelVersion uint64,
|
||||
ValidatedModelID uint64,
|
||||
) error {
|
||||
validate_request := model.ValidateSubmissionRequest{
|
||||
SubmissionID: SubmissionID,
|
||||
ModelID: ModelID,
|
||||
ModelVersion: ModelVersion,
|
||||
ValidatedModelID: nil,
|
||||
}
|
||||
|
||||
// sentinel values because we're not using rust
|
||||
if ValidatedModelID != 0 {
|
||||
validate_request.ValidatedModelID = &ValidatedModelID
|
||||
}
|
||||
|
||||
j, err := json.Marshal(validate_request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = svc.nats.Publish("maptest.submissions.validate", []byte(j))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
55
pkg/service/operations.go
Normal file
55
pkg/service/operations.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
)
|
||||
|
||||
type OperationFailParams datastore.OptionalMap
|
||||
|
||||
func NewOperationFailParams(
|
||||
status_message string,
|
||||
) OperationFailParams {
|
||||
filter := datastore.Optional()
|
||||
filter.Add("status_id", model.OperationStatusFailed)
|
||||
filter.Add("status_message", status_message)
|
||||
return OperationFailParams(filter)
|
||||
}
|
||||
|
||||
type OperationCompleteParams datastore.OptionalMap
|
||||
|
||||
func NewOperationCompleteParams(
|
||||
path string,
|
||||
) OperationCompleteParams {
|
||||
filter := datastore.Optional()
|
||||
filter.Add("status_id", model.OperationStatusCompleted)
|
||||
filter.Add("path", path)
|
||||
return OperationCompleteParams(filter)
|
||||
}
|
||||
|
||||
func (svc *Service) CreateOperation(ctx context.Context, operation model.Operation) (model.Operation, error) {
|
||||
return svc.db.Operations().Create(ctx, operation)
|
||||
}
|
||||
|
||||
func (svc *Service) CountOperationsSince(ctx context.Context, owner int64, since time.Time) (int64, error) {
|
||||
return svc.db.Operations().CountSince(ctx, owner, since)
|
||||
}
|
||||
|
||||
func (svc *Service) DeleteOperation(ctx context.Context, id int32) error {
|
||||
return svc.db.Operations().Delete(ctx, id)
|
||||
}
|
||||
|
||||
func (svc *Service) GetOperation(ctx context.Context, id int32) (model.Operation, error) {
|
||||
return svc.db.Operations().Get(ctx, id)
|
||||
}
|
||||
|
||||
func (svc *Service) FailOperation(ctx context.Context, id int32, params OperationFailParams) error {
|
||||
return svc.db.Operations().Update(ctx, id, datastore.OptionalMap(params))
|
||||
}
|
||||
|
||||
func (svc *Service) CompleteOperation(ctx context.Context, id int32, params OperationCompleteParams) error {
|
||||
return svc.db.Operations().Update(ctx, id, datastore.OptionalMap(params))
|
||||
}
|
||||
@@ -2,155 +2,44 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/api"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
|
||||
"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) {
|
||||
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
|
||||
if !ok {
|
||||
return nil, ErrUserInfo
|
||||
}
|
||||
type ScriptPolicyFilter datastore.OptionalMap
|
||||
|
||||
if !userInfo.Roles.ScriptWrite {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
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
|
||||
func NewScriptPolicyFilter() ScriptPolicyFilter {
|
||||
filter := datastore.Optional()
|
||||
return ScriptPolicyFilter(filter)
|
||||
}
|
||||
func (filter ScriptPolicyFilter) SetFromScriptHash(from_script_hash int64) {
|
||||
// Finally, type safety!
|
||||
datastore.OptionalMap(filter).Add("from_script_hash", from_script_hash)
|
||||
}
|
||||
func (filter ScriptPolicyFilter) SetToScriptID(to_script_id int64) {
|
||||
datastore.OptionalMap(filter).Add("to_script_id", to_script_id)
|
||||
}
|
||||
func (filter ScriptPolicyFilter) SetPolicy(policy int32) {
|
||||
datastore.OptionalMap(filter).Add("policy", policy)
|
||||
}
|
||||
|
||||
// DeleteScriptPolicy implements deleteScriptPolicy operation.
|
||||
//
|
||||
// Delete the specified script policy by ID.
|
||||
//
|
||||
// DELETE /script-policy/id/{ScriptPolicyID}
|
||||
func (svc *Service) DeleteScriptPolicy(ctx context.Context, params api.DeleteScriptPolicyParams) error {
|
||||
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
|
||||
if !ok {
|
||||
return ErrUserInfo
|
||||
}
|
||||
|
||||
if !userInfo.Roles.ScriptWrite {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
|
||||
return svc.DB.ScriptPolicy().Delete(ctx, params.ScriptPolicyID)
|
||||
func (svc *Service) CreateScriptPolicy(ctx context.Context, script model.ScriptPolicy) (model.ScriptPolicy, error) {
|
||||
return svc.db.ScriptPolicy().Create(ctx, script)
|
||||
}
|
||||
|
||||
// GetScriptPolicy implements getScriptPolicy operation.
|
||||
//
|
||||
// Get the specified script policy by ID.
|
||||
//
|
||||
// GET /script-policy/id/{ScriptPolicyID}
|
||||
func (svc *Service) GetScriptPolicy(ctx context.Context, params api.GetScriptPolicyParams) (*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
|
||||
|
||||
policy, err := svc.DB.ScriptPolicy().Get(ctx, params.ScriptPolicyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &api.ScriptPolicy{
|
||||
ID: policy.ID,
|
||||
FromScriptHash: fmt.Sprintf("%x", policy.FromScriptHash),
|
||||
ToScriptID: policy.ToScriptID,
|
||||
Policy: int32(policy.Policy),
|
||||
}, nil
|
||||
func (svc *Service) ListScriptPolicies(ctx context.Context, filter ScriptPolicyFilter, page model.Page) ([]model.ScriptPolicy, error) {
|
||||
return svc.db.ScriptPolicy().List(ctx, datastore.OptionalMap(filter), page)
|
||||
}
|
||||
|
||||
// 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,
|
||||
Policy: int32(policy.Policy),
|
||||
}, nil
|
||||
func (svc *Service) DeleteScriptPolicy(ctx context.Context, id int64) error {
|
||||
return svc.db.ScriptPolicy().Delete(ctx, id)
|
||||
}
|
||||
|
||||
// UpdateScriptPolicy implements updateScriptPolicy operation.
|
||||
//
|
||||
// Update the specified script policy by ID.
|
||||
//
|
||||
// PATCH /script-policy/id/{ScriptPolicyID}
|
||||
func (svc *Service) UpdateScriptPolicy(ctx context.Context, req *api.ScriptPolicyUpdate, params api.UpdateScriptPolicyParams) error {
|
||||
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
|
||||
if !ok {
|
||||
return ErrUserInfo
|
||||
}
|
||||
|
||||
if !userInfo.Roles.ScriptWrite {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
|
||||
pmap := datastore.Optional()
|
||||
if from_script_id, ok := req.FromScriptID.Get(); ok {
|
||||
from_script, err := svc.DB.Scripts().Get(ctx, from_script_id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pmap.Add("from_script_hash", from_script.Hash)
|
||||
}
|
||||
if to_script_id, ok := req.ToScriptID.Get(); ok {
|
||||
pmap.Add("to_script_id", to_script_id)
|
||||
}
|
||||
if policy, ok := req.Policy.Get(); ok {
|
||||
pmap.Add("policy", policy)
|
||||
}
|
||||
return svc.DB.ScriptPolicy().Update(ctx, req.ID, pmap)
|
||||
func (svc *Service) GetScriptPolicy(ctx context.Context, id int64) (model.ScriptPolicy, error) {
|
||||
return svc.db.ScriptPolicy().Get(ctx, id)
|
||||
}
|
||||
|
||||
func (svc *Service) UpdateScriptPolicy(ctx context.Context, id int64, pmap ScriptPolicyFilter) error {
|
||||
return svc.db.ScriptPolicy().Update(ctx, id, datastore.OptionalMap(pmap))
|
||||
}
|
||||
|
||||
@@ -2,110 +2,49 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/api"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
"github.com/dchest/siphash"
|
||||
)
|
||||
|
||||
// CreateScript implements createScript operation.
|
||||
//
|
||||
// Create a new script.
|
||||
//
|
||||
// POST /scripts
|
||||
func (svc *Service) CreateScript(ctx context.Context, req *api.ScriptCreate) (*api.ID, error) {
|
||||
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
|
||||
if !ok {
|
||||
return nil, ErrUserInfo
|
||||
}
|
||||
type ScriptFilter datastore.OptionalMap
|
||||
|
||||
if !userInfo.Roles.ScriptWrite {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
script, err := svc.DB.Scripts().Create(ctx, model.Script{
|
||||
ID: 0,
|
||||
Hash: siphash.Hash(0, 0, []byte(req.Source)),
|
||||
Source: req.Source,
|
||||
SubmissionID: req.SubmissionID.Or(0),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &api.ID{
|
||||
ID: script.ID,
|
||||
}, nil
|
||||
func NewScriptFilter() ScriptFilter {
|
||||
filter := datastore.Optional()
|
||||
return ScriptFilter(filter)
|
||||
}
|
||||
func (filter ScriptFilter) SetName(name string) {
|
||||
datastore.OptionalMap(filter).Add("name", name)
|
||||
}
|
||||
func (filter ScriptFilter) SetSource(source string) {
|
||||
datastore.OptionalMap(filter).Add("source", source)
|
||||
}
|
||||
func (filter ScriptFilter) SetHash(hash int64) {
|
||||
datastore.OptionalMap(filter).Add("hash", hash)
|
||||
}
|
||||
func (filter ScriptFilter) SetResourceType(resource_type int32) {
|
||||
datastore.OptionalMap(filter).Add("resource_type", resource_type)
|
||||
}
|
||||
func (filter ScriptFilter) SetResourceID(resource_id int64) {
|
||||
datastore.OptionalMap(filter).Add("resource_id", resource_id)
|
||||
}
|
||||
|
||||
// DeleteScript implements deleteScript operation.
|
||||
//
|
||||
// Delete the specified script by ID.
|
||||
//
|
||||
// DELETE /scripts/{ScriptID}
|
||||
func (svc *Service) DeleteScript(ctx context.Context, params api.DeleteScriptParams) error {
|
||||
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
|
||||
if !ok {
|
||||
return ErrUserInfo
|
||||
}
|
||||
|
||||
if !userInfo.Roles.ScriptWrite {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
|
||||
return svc.DB.Scripts().Delete(ctx, params.ScriptID)
|
||||
func (svc *Service) CreateScript(ctx context.Context, script model.Script) (model.Script, error) {
|
||||
return svc.db.Scripts().Create(ctx, script)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
_, ok := ctx.Value("UserInfo").(UserInfo)
|
||||
if !ok {
|
||||
return nil, ErrUserInfo
|
||||
}
|
||||
|
||||
// Read permission for scripts only requires you to be logged in
|
||||
|
||||
script, err := svc.DB.Scripts().Get(ctx, params.ScriptID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &api.Script{
|
||||
ID: script.ID,
|
||||
Hash: fmt.Sprintf("%x", script.Hash),
|
||||
Source: script.Source,
|
||||
SubmissionID: script.SubmissionID,
|
||||
}, nil
|
||||
func (svc *Service) ListScripts(ctx context.Context, filter ScriptFilter, page model.Page) ([]model.Script, error) {
|
||||
return svc.db.Scripts().List(ctx, datastore.OptionalMap(filter), page)
|
||||
}
|
||||
|
||||
// UpdateScript implements updateScript operation.
|
||||
//
|
||||
// Update the specified script by ID.
|
||||
//
|
||||
// PATCH /scripts/{ScriptID}
|
||||
func (svc *Service) UpdateScript(ctx context.Context, req *api.ScriptUpdate, params api.UpdateScriptParams) error {
|
||||
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
|
||||
if !ok {
|
||||
return ErrUserInfo
|
||||
}
|
||||
|
||||
if !userInfo.Roles.ScriptWrite {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
|
||||
pmap := datastore.Optional()
|
||||
if source, ok := req.Source.Get(); ok {
|
||||
pmap.Add("source", source)
|
||||
pmap.Add("hash", siphash.Hash(0, 0, []byte(source)))
|
||||
}
|
||||
if SubmissionID, ok := req.SubmissionID.Get(); ok {
|
||||
pmap.Add("submission_id", SubmissionID)
|
||||
}
|
||||
return svc.DB.Scripts().Update(ctx, req.ID, pmap)
|
||||
func (svc *Service) DeleteScript(ctx context.Context, id int64) error {
|
||||
return svc.db.Scripts().Delete(ctx, id)
|
||||
}
|
||||
|
||||
func (svc *Service) GetScript(ctx context.Context, id int64) (model.Script, error) {
|
||||
return svc.db.Scripts().Get(ctx, id)
|
||||
}
|
||||
|
||||
func (svc *Service) UpdateScript(ctx context.Context, id int64, pmap ScriptFilter) error {
|
||||
return svc.db.Scripts().Update(ctx, id, datastore.OptionalMap(pmap))
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.itzana.me/strafesnet/go-grpc/auth"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/api"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrMissingSessionID there is no session id
|
||||
ErrMissingSessionID = errors.New("SessionID missing")
|
||||
// ErrInvalidSession caller does not have a valid session
|
||||
ErrInvalidSession = errors.New("Session invalid")
|
||||
)
|
||||
|
||||
var (
|
||||
// has SubmissionPublish
|
||||
RoleMapAdmin int32 = 128
|
||||
// has SubmissionReview
|
||||
RoleMapCouncil int32 = 64
|
||||
)
|
||||
|
||||
type Roles struct {
|
||||
// human roles
|
||||
SubmissionPublish bool
|
||||
SubmissionReview bool
|
||||
ScriptWrite bool
|
||||
// automated roles
|
||||
Maptest bool
|
||||
Validator bool
|
||||
}
|
||||
|
||||
type UserInfo struct {
|
||||
Roles Roles
|
||||
UserID uint64
|
||||
}
|
||||
|
||||
func (usr UserInfo) IsSubmitter(submitter uint64) bool {
|
||||
return usr.UserID == submitter
|
||||
}
|
||||
|
||||
type SecurityHandler struct {
|
||||
Client auth.AuthServiceClient
|
||||
}
|
||||
|
||||
func (svc SecurityHandler) HandleCookieAuth(ctx context.Context, operationName api.OperationName, t api.CookieAuth) (context.Context, error) {
|
||||
sessionId := t.GetAPIKey()
|
||||
if sessionId == "" {
|
||||
return nil, ErrMissingSessionID
|
||||
}
|
||||
|
||||
session, err := svc.Client.GetSessionUser(ctx, &auth.IdMessage{
|
||||
SessionID: sessionId,
|
||||
})
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
@@ -1,41 +1,29 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/api"
|
||||
"git.itzana.me/strafesnet/go-grpc/maps"
|
||||
"git.itzana.me/strafesnet/go-grpc/users"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
|
||||
"github.com/nats-io/nats.go"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrPermissionDenied caller does not have the required role
|
||||
ErrPermissionDenied = errors.New("Permission denied")
|
||||
// ErrUserInfo user info is missing for some reason
|
||||
ErrUserInfo = errors.New("Missing user info")
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
DB datastore.Datastore
|
||||
Nats nats.JetStreamContext
|
||||
db datastore.Datastore
|
||||
nats nats.JetStreamContext
|
||||
maps maps.MapsServiceClient
|
||||
users users.UsersServiceClient
|
||||
}
|
||||
|
||||
// NewError creates *ErrorStatusCode from error returned by handler.
|
||||
//
|
||||
// Used for common default response.
|
||||
func (svc *Service) NewError(ctx context.Context, err error) *api.ErrorStatusCode {
|
||||
status := 500
|
||||
if errors.Is(err, ErrPermissionDenied) {
|
||||
status = 403
|
||||
}
|
||||
if errors.Is(err, ErrUserInfo) {
|
||||
status = 401
|
||||
}
|
||||
return &api.ErrorStatusCode{
|
||||
StatusCode: status,
|
||||
Response: api.Error{
|
||||
Code: int64(status),
|
||||
Message: err.Error(),
|
||||
},
|
||||
func NewService(
|
||||
db datastore.Datastore,
|
||||
nats nats.JetStreamContext,
|
||||
maps maps.MapsServiceClient,
|
||||
users users.UsersServiceClient,
|
||||
) Service {
|
||||
return Service{
|
||||
db: db,
|
||||
nats: nats,
|
||||
maps: maps,
|
||||
users: users,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,389 +2,119 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/api"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
)
|
||||
|
||||
// POST /submissions
|
||||
func (svc *Service) CreateSubmission(ctx context.Context, request *api.SubmissionCreate) (*api.ID, error) {
|
||||
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
|
||||
if !ok {
|
||||
return nil, ErrUserInfo
|
||||
}
|
||||
type SubmissionUpdate datastore.OptionalMap
|
||||
|
||||
submission, err := svc.DB.Submissions().Create(ctx, model.Submission{
|
||||
ID: 0,
|
||||
DisplayName: request.DisplayName,
|
||||
Creator: request.Creator,
|
||||
GameID: request.GameID,
|
||||
Submitter: userInfo.UserID,
|
||||
AssetID: uint64(request.AssetID),
|
||||
AssetVersion: uint64(request.AssetVersion),
|
||||
Completed: false,
|
||||
TargetAssetID: uint64(request.TargetAssetID.Value),
|
||||
StatusID: model.StatusUnderConstruction,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &api.ID{
|
||||
ID: submission.ID,
|
||||
}, nil
|
||||
func NewSubmissionUpdate() SubmissionUpdate {
|
||||
update := datastore.Optional()
|
||||
return SubmissionUpdate(update)
|
||||
}
|
||||
|
||||
// GetSubmission implements getSubmission operation.
|
||||
//
|
||||
// Retrieve map with ID.
|
||||
//
|
||||
// GET /submissions/{SubmissionID}
|
||||
func (svc *Service) GetSubmission(ctx context.Context, params api.GetSubmissionParams) (*api.Submission, error) {
|
||||
submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &api.Submission{
|
||||
ID: submission.ID,
|
||||
DisplayName: submission.DisplayName,
|
||||
Creator: submission.Creator,
|
||||
GameID: submission.GameID,
|
||||
CreatedAt: submission.CreatedAt.Unix(),
|
||||
UpdatedAt: submission.UpdatedAt.Unix(),
|
||||
Submitter: int64(submission.Submitter),
|
||||
AssetID: int64(submission.AssetID),
|
||||
AssetVersion: int64(submission.AssetVersion),
|
||||
Completed: submission.Completed,
|
||||
TargetAssetID: api.NewOptInt64(int64(submission.TargetAssetID)),
|
||||
StatusID: int32(submission.StatusID),
|
||||
}, nil
|
||||
func (update SubmissionUpdate) SetDisplayName(display_name string) {
|
||||
datastore.OptionalMap(update).Add("display_name", display_name)
|
||||
}
|
||||
func (update SubmissionUpdate) SetCreator(creator string) {
|
||||
datastore.OptionalMap(update).Add("creator", creator)
|
||||
}
|
||||
func (update SubmissionUpdate) SetGameID(game_id uint32) {
|
||||
datastore.OptionalMap(update).Add("game_id", game_id)
|
||||
}
|
||||
func (update SubmissionUpdate) SetSubmitter(submitter uint64) {
|
||||
datastore.OptionalMap(update).Add("submitter", submitter)
|
||||
}
|
||||
func (update SubmissionUpdate) SetAssetID(asset_id uint64) {
|
||||
datastore.OptionalMap(update).Add("asset_id", asset_id)
|
||||
}
|
||||
func (update SubmissionUpdate) SetAssetVersion(asset_version uint64) {
|
||||
datastore.OptionalMap(update).Add("asset_version", asset_version)
|
||||
}
|
||||
func (update SubmissionUpdate) SetValidatedAssetID(validated_asset_id uint64) {
|
||||
datastore.OptionalMap(update).Add("validated_asset_id", validated_asset_id)
|
||||
}
|
||||
func (update SubmissionUpdate) SetValidatedAssetVersion(validated_asset_version uint64) {
|
||||
datastore.OptionalMap(update).Add("validated_asset_version", validated_asset_version)
|
||||
}
|
||||
func (update SubmissionUpdate) SetCompleted(completed bool) {
|
||||
datastore.OptionalMap(update).Add("completed", completed)
|
||||
}
|
||||
func (update SubmissionUpdate) SetUploadedAssetID(uploaded_asset_id uint64) {
|
||||
datastore.OptionalMap(update).Add("uploaded_asset_id", uploaded_asset_id)
|
||||
}
|
||||
func (update SubmissionUpdate) SetStatusID(status_id model.SubmissionStatus) {
|
||||
datastore.OptionalMap(update).Add("status_id", status_id)
|
||||
}
|
||||
func (update SubmissionUpdate) SetDescription(description string) {
|
||||
datastore.OptionalMap(update).Add("description", description)
|
||||
}
|
||||
|
||||
// ListSubmissions implements listSubmissions operation.
|
||||
//
|
||||
// Get list of submissions.
|
||||
//
|
||||
// GET /submissions
|
||||
func (svc *Service) ListSubmissions(ctx context.Context, request api.ListSubmissionsParams) ([]api.Submission, error) {
|
||||
type SubmissionFilter datastore.OptionalMap
|
||||
|
||||
func NewSubmissionFilter(
|
||||
) SubmissionFilter {
|
||||
filter := datastore.Optional()
|
||||
//fmt.Println(request)
|
||||
if request.Filter.IsSet() {
|
||||
filter.AddNotNil("display_name", request.Filter.Value.DisplayName)
|
||||
filter.AddNotNil("creator", request.Filter.Value.Creator)
|
||||
filter.AddNotNil("game_id", request.Filter.Value.GameID)
|
||||
}
|
||||
|
||||
items, err := svc.DB.Submissions().List(ctx, filter, model.Page{
|
||||
Number: request.Page.GetPage(),
|
||||
Size: request.Page.GetLimit(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp []api.Submission
|
||||
for i := 0; i < len(items); i++ {
|
||||
resp = append(resp, api.Submission{
|
||||
ID: items[i].ID,
|
||||
DisplayName: items[i].DisplayName,
|
||||
Creator: items[i].Creator,
|
||||
GameID: items[i].GameID,
|
||||
CreatedAt: items[i].CreatedAt.Unix(),
|
||||
UpdatedAt: items[i].UpdatedAt.Unix(),
|
||||
Submitter: int64(items[i].Submitter),
|
||||
AssetID: int64(items[i].AssetID),
|
||||
AssetVersion: int64(items[i].AssetVersion),
|
||||
Completed: items[i].Completed,
|
||||
TargetAssetID: api.NewOptInt64(int64(items[i].TargetAssetID)),
|
||||
StatusID: int32(items[i].StatusID),
|
||||
})
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
return SubmissionFilter(filter)
|
||||
}
|
||||
func (update SubmissionFilter) SetDisplayName(display_name string) {
|
||||
datastore.OptionalMap(update).Add("display_name", display_name)
|
||||
}
|
||||
func (update SubmissionFilter) SetCreator(creator string) {
|
||||
datastore.OptionalMap(update).Add("creator", creator)
|
||||
}
|
||||
func (update SubmissionFilter) SetGameID(game_id uint32) {
|
||||
datastore.OptionalMap(update).Add("game_id", game_id)
|
||||
}
|
||||
func (update SubmissionFilter) SetSubmitter(submitter uint64) {
|
||||
datastore.OptionalMap(update).Add("submitter", submitter)
|
||||
}
|
||||
func (update SubmissionFilter) SetAssetID(asset_id uint64) {
|
||||
datastore.OptionalMap(update).Add("asset_id", asset_id)
|
||||
}
|
||||
func (update SubmissionFilter) SetAssetVersion(asset_version uint64) {
|
||||
datastore.OptionalMap(update).Add("asset_version", asset_version)
|
||||
}
|
||||
func (update SubmissionFilter) SetUploadedAssetID(uploaded_asset_id uint64) {
|
||||
datastore.OptionalMap(update).Add("uploaded_asset_id", uploaded_asset_id)
|
||||
}
|
||||
func (update SubmissionFilter) SetStatuses(statuses []model.SubmissionStatus) {
|
||||
datastore.OptionalMap(update).Add("status_id", statuses)
|
||||
}
|
||||
|
||||
// PatchSubmissionCompleted implements patchSubmissionCompleted operation.
|
||||
//
|
||||
// Retrieve map with ID.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/completed
|
||||
func (svc *Service) SetSubmissionCompleted(ctx context.Context, params api.SetSubmissionCompletedParams) error {
|
||||
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
|
||||
if !ok {
|
||||
return ErrUserInfo
|
||||
}
|
||||
|
||||
// check if caller has MaptestGame role (request must originate from a maptest roblox game)
|
||||
if !userInfo.Roles.Maptest {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
|
||||
pmap := datastore.Optional()
|
||||
pmap.Add("completed", true)
|
||||
err := svc.DB.Submissions().Update(ctx, params.SubmissionID, pmap)
|
||||
return err
|
||||
func (svc *Service) CreateSubmission(ctx context.Context, script model.Submission) (model.Submission, error) {
|
||||
return svc.db.Submissions().Create(ctx, script)
|
||||
}
|
||||
|
||||
// PatchSubmissionModel implements patchSubmissionModel operation.
|
||||
//
|
||||
// Update model following role restrictions.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/model
|
||||
func (svc *Service) UpdateSubmissionModel(ctx context.Context, params api.UpdateSubmissionModelParams) error {
|
||||
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
|
||||
if !ok {
|
||||
return ErrUserInfo
|
||||
}
|
||||
|
||||
// read submission (this could be done with a transaction WHERE clause)
|
||||
submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if caller is the submitter
|
||||
if !userInfo.IsSubmitter(submission.Submitter) {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
|
||||
// check if Status is ChangesRequested|Submitted|UnderConstruction
|
||||
pmap := datastore.Optional()
|
||||
pmap.AddNotNil("asset_id", params.ModelID)
|
||||
pmap.AddNotNil("asset_version", params.VersionID)
|
||||
//always reset completed when model changes
|
||||
pmap.Add("completed", false)
|
||||
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusChangesRequested, model.StatusSubmitted, model.StatusUnderConstruction}, pmap)
|
||||
func (svc *Service) ListSubmissions(ctx context.Context, filter SubmissionFilter, page model.Page, sort datastore.ListSort) ([]model.Submission, error) {
|
||||
return svc.db.Submissions().List(ctx, datastore.OptionalMap(filter), page, sort)
|
||||
}
|
||||
|
||||
// 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)
|
||||
func (svc *Service) ListSubmissionsWithTotal(ctx context.Context, filter SubmissionFilter, page model.Page, sort datastore.ListSort) (int64, []model.Submission, error) {
|
||||
return svc.db.Submissions().ListWithTotal(ctx, datastore.OptionalMap(filter), page, sort)
|
||||
}
|
||||
|
||||
// ActionSubmissionReject invokes actionSubmissionReject operation.
|
||||
//
|
||||
// Role Reviewer changes status from Submitted -> Rejected.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/reject
|
||||
func (svc *Service) ActionSubmissionReject(ctx context.Context, params api.ActionSubmissionRejectParams) error {
|
||||
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
|
||||
if !ok {
|
||||
return ErrUserInfo
|
||||
}
|
||||
|
||||
// check if caller has required role
|
||||
if !userInfo.Roles.SubmissionReview {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
|
||||
// transaction
|
||||
smap := datastore.Optional()
|
||||
smap.Add("status_id", model.StatusRejected)
|
||||
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusSubmitted}, smap)
|
||||
func (svc *Service) DeleteSubmission(ctx context.Context, id int64) error {
|
||||
return svc.db.Submissions().Delete(ctx, id)
|
||||
}
|
||||
|
||||
// ActionSubmissionRequestChanges invokes actionSubmissionRequestChanges operation.
|
||||
//
|
||||
// Role Reviewer changes status from Validated|Accepted|Submitted -> ChangesRequested.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/request-changes
|
||||
func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params api.ActionSubmissionRequestChangesParams) error {
|
||||
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
|
||||
if !ok {
|
||||
return ErrUserInfo
|
||||
}
|
||||
|
||||
// check if caller has required role
|
||||
if !userInfo.Roles.SubmissionReview {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
|
||||
// transaction
|
||||
smap := datastore.Optional()
|
||||
smap.Add("status_id", model.StatusChangesRequested)
|
||||
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusValidated, model.StatusAccepted, model.StatusSubmitted}, smap)
|
||||
func (svc *Service) GetSubmission(ctx context.Context, id int64) (model.Submission, error) {
|
||||
return svc.db.Submissions().Get(ctx, id)
|
||||
}
|
||||
|
||||
// ActionSubmissionRevoke invokes actionSubmissionRevoke operation.
|
||||
//
|
||||
// Role Submitter changes status from Submitted|ChangesRequested -> UnderConstruction.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/revoke
|
||||
func (svc *Service) ActionSubmissionRevoke(ctx context.Context, params api.ActionSubmissionRevokeParams) error {
|
||||
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
|
||||
if !ok {
|
||||
return ErrUserInfo
|
||||
}
|
||||
|
||||
// read submission (this could be done with a transaction WHERE clause)
|
||||
submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if caller is the submitter
|
||||
if !userInfo.IsSubmitter(submission.Submitter) {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
|
||||
// transaction
|
||||
smap := datastore.Optional()
|
||||
smap.Add("status_id", model.StatusUnderConstruction)
|
||||
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusSubmitted, model.StatusChangesRequested}, smap)
|
||||
func (svc *Service) GetSubmissionList(ctx context.Context, ids []int64) ([]model.Submission, error) {
|
||||
return svc.db.Submissions().GetList(ctx, ids)
|
||||
}
|
||||
|
||||
// ActionSubmissionSubmit invokes actionSubmissionSubmit operation.
|
||||
//
|
||||
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/submit
|
||||
func (svc *Service) ActionSubmissionSubmit(ctx context.Context, params api.ActionSubmissionSubmitParams) error {
|
||||
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
|
||||
if !ok {
|
||||
return ErrUserInfo
|
||||
}
|
||||
|
||||
// read submission (this could be done with a transaction WHERE clause)
|
||||
submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if caller is the submitter
|
||||
if !userInfo.IsSubmitter(submission.Submitter) {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
|
||||
// transaction
|
||||
smap := datastore.Optional()
|
||||
smap.Add("status_id", model.StatusSubmitted)
|
||||
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusUnderConstruction, model.StatusChangesRequested}, smap)
|
||||
func (svc *Service) UpdateSubmission(ctx context.Context, id int64, pmap SubmissionUpdate) error {
|
||||
return svc.db.Submissions().Update(ctx, id, datastore.OptionalMap(pmap))
|
||||
}
|
||||
|
||||
// ActionSubmissionTriggerPublish invokes actionSubmissionTriggerPublish operation.
|
||||
//
|
||||
// Role Admin changes status from Validated -> Publishing.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/trigger-publish
|
||||
func (svc *Service) ActionSubmissionTriggerPublish(ctx context.Context, params api.ActionSubmissionTriggerPublishParams) error {
|
||||
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
|
||||
if !ok {
|
||||
return ErrUserInfo
|
||||
}
|
||||
|
||||
// check if caller has required role
|
||||
if !userInfo.Roles.SubmissionPublish {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
|
||||
// transaction
|
||||
smap := datastore.Optional()
|
||||
smap.Add("status_id", model.StatusPublishing)
|
||||
submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.Status{model.StatusValidated}, smap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// sentinel value because we are not using rust
|
||||
if submission.TargetAssetID == 0 {
|
||||
// this is a new map
|
||||
publish_new_request := model.PublishNewRequest{
|
||||
SubmissionID: submission.ID,
|
||||
ModelID: submission.AssetID,
|
||||
ModelVersion: submission.AssetVersion,
|
||||
Creator: submission.Creator,
|
||||
DisplayName: submission.DisplayName,
|
||||
GameID: uint32(submission.GameID),
|
||||
}
|
||||
|
||||
j, err := json.Marshal(publish_new_request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
svc.Nats.Publish("maptest.submissions.publish.new", []byte(j))
|
||||
} else {
|
||||
// this is a map fix
|
||||
publish_fix_request := model.PublishFixRequest{
|
||||
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.publish.fix", []byte(j))
|
||||
}
|
||||
|
||||
return nil
|
||||
func (svc *Service) UpdateSubmissionIfStatus(ctx context.Context, id int64, statuses []model.SubmissionStatus, pmap SubmissionUpdate) error {
|
||||
return svc.db.Submissions().IfStatusThenUpdate(ctx, id, statuses, datastore.OptionalMap(pmap))
|
||||
}
|
||||
|
||||
// ActionSubmissionTriggerValidate invokes actionSubmissionTriggerValidate operation.
|
||||
//
|
||||
// Role Reviewer triggers validation and changes status from Submitted|Accepted -> Validating.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/trigger-validate
|
||||
func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params api.ActionSubmissionTriggerValidateParams) error {
|
||||
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
|
||||
if !ok {
|
||||
return ErrUserInfo
|
||||
}
|
||||
|
||||
// check if caller has required role
|
||||
if !userInfo.Roles.SubmissionReview {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
|
||||
// transaction
|
||||
smap := datastore.Optional()
|
||||
smap.Add("status_id", model.StatusValidating)
|
||||
submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.Status{model.StatusSubmitted, model.StatusAccepted}, smap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
validate_request := model.ValidateRequest{
|
||||
SubmissionID: submission.ID,
|
||||
ModelID: submission.AssetID,
|
||||
ModelVersion: submission.AssetVersion,
|
||||
ValidatedModelID: 0, //TODO: reuse velidation models
|
||||
}
|
||||
|
||||
j, err := json.Marshal(validate_request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
svc.Nats.Publish("maptest.submissions.validate", []byte(j))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ActionSubmissionValidate invokes actionSubmissionValidate operation.
|
||||
//
|
||||
// Role Validator changes status from Validating -> Validated.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/validate
|
||||
func (svc *Service) ActionSubmissionValidate(ctx context.Context, params api.ActionSubmissionValidateParams) error {
|
||||
println("[ActionSubmissionValidate] Implicit Validator permission granted!")
|
||||
|
||||
// transaction
|
||||
smap := datastore.Optional()
|
||||
smap.Add("status_id", model.StatusValidated)
|
||||
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusValidating}, smap)
|
||||
func (svc *Service) UpdateAndGetSubmissionIfStatus(ctx context.Context, id int64, statuses []model.SubmissionStatus, pmap SubmissionUpdate) (model.Submission, error) {
|
||||
return svc.db.Submissions().IfStatusThenUpdateAndGet(ctx, id, statuses, datastore.OptionalMap(pmap))
|
||||
}
|
||||
|
||||
380
pkg/validator_controller/mapfixes.go
Normal file
380
pkg/validator_controller/mapfixes.go
Normal file
@@ -0,0 +1,380 @@
|
||||
package validator_controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.itzana.me/strafesnet/go-grpc/validator"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/service"
|
||||
)
|
||||
|
||||
type Mapfixes struct {
|
||||
*validator.UnimplementedValidatorMapfixServiceServer
|
||||
inner *service.Service
|
||||
}
|
||||
func NewMapfixesController(
|
||||
inner *service.Service,
|
||||
) Mapfixes {
|
||||
return Mapfixes{
|
||||
inner: inner,
|
||||
}
|
||||
}
|
||||
|
||||
var(
|
||||
// prevent two mapfixes with same asset id
|
||||
ActiveMapfixStatuses = []model.MapfixStatus{
|
||||
model.MapfixStatusUploading,
|
||||
model.MapfixStatusValidated,
|
||||
model.MapfixStatusValidating,
|
||||
model.MapfixStatusAcceptedUnvalidated,
|
||||
model.MapfixStatusChangesRequested,
|
||||
model.MapfixStatusSubmitted,
|
||||
model.MapfixStatusUnderConstruction,
|
||||
}
|
||||
)
|
||||
|
||||
var(
|
||||
ErrActiveMapfixSameAssetID = errors.New("There is an active mapfix with the same AssetID")
|
||||
ErrNotAssetOwner = errors.New("You can only submit an asset you own")
|
||||
)
|
||||
|
||||
// UpdateMapfixValidatedModel implements patchMapfixModel operation.
|
||||
//
|
||||
// Update model following role restrictions.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/validated-model
|
||||
func (svc *Mapfixes) SetValidatedModel(ctx context.Context, params *validator.ValidatedModelRequest) (*validator.NullResponse, error) {
|
||||
MapfixID := int64(params.ID)
|
||||
|
||||
// check if Status is ChangesRequested|Submitted|UnderConstruction
|
||||
update := service.NewMapfixUpdate()
|
||||
update.SetValidatedAssetID(params.ValidatedModelID)
|
||||
update.SetValidatedAssetVersion(params.ValidatedModelVersion)
|
||||
// DO NOT reset completed when validated model is updated
|
||||
// update.Add("completed", false)
|
||||
allow_statuses := []model.MapfixStatus{model.MapfixStatusValidating}
|
||||
err := svc.inner.UpdateMapfixIfStatus(ctx, MapfixID, allow_statuses, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
event_data := model.AuditEventDataChangeValidatedModel{
|
||||
ValidatedModelID: params.ValidatedModelID,
|
||||
ValidatedModelVersion: params.ValidatedModelVersion,
|
||||
}
|
||||
|
||||
err = svc.inner.CreateAuditEventChangeValidatedModel(
|
||||
ctx,
|
||||
model.ValidatorUserID,
|
||||
model.Resource{
|
||||
ID: MapfixID,
|
||||
Type: model.ResourceMapfix,
|
||||
},
|
||||
event_data,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.NullResponse{}, nil
|
||||
}
|
||||
|
||||
// ActionMapfixSubmitted invokes actionMapfixSubmitted operation.
|
||||
//
|
||||
// Role Validator changes status from Submitting -> Submitted.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/validator-submitted
|
||||
func (svc *Mapfixes) SetStatusSubmitted(ctx context.Context, params *validator.SubmittedRequest) (*validator.NullResponse, error) {
|
||||
MapfixID := int64(params.ID)
|
||||
// transaction
|
||||
target_status := model.MapfixStatusSubmitted
|
||||
update := service.NewMapfixUpdate()
|
||||
update.SetStatusID(target_status)
|
||||
update.SetAssetVersion(uint64(params.ModelVersion))
|
||||
update.SetDisplayName(params.DisplayName)
|
||||
update.SetCreator(params.Creator)
|
||||
update.SetGameID(uint32(params.GameID))
|
||||
allow_statuses := []model.MapfixStatus{model.MapfixStatusSubmitting}
|
||||
err := svc.inner.UpdateMapfixIfStatus(ctx, MapfixID, allow_statuses, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
event_data := model.AuditEventDataAction{
|
||||
TargetStatus: uint32(target_status),
|
||||
}
|
||||
|
||||
err = svc.inner.CreateAuditEventAction(
|
||||
ctx,
|
||||
model.ValidatorUserID,
|
||||
model.Resource{
|
||||
ID: MapfixID,
|
||||
Type: model.ResourceMapfix,
|
||||
},
|
||||
event_data,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.NullResponse{}, nil
|
||||
}
|
||||
|
||||
// ActionMapfixRequestChanges implements actionMapfixRequestChanges operation.
|
||||
//
|
||||
// (Internal endpoint) Role Validator changes status from Submitting -> RequestChanges.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/validator-request-changes
|
||||
func (svc *Mapfixes) SetStatusRequestChanges(ctx context.Context, params *validator.MapfixID) (*validator.NullResponse, error) {
|
||||
MapfixID := int64(params.ID)
|
||||
// transaction
|
||||
target_status := model.MapfixStatusChangesRequested
|
||||
update := service.NewMapfixUpdate()
|
||||
update.SetStatusID(target_status)
|
||||
allow_statuses := []model.MapfixStatus{model.MapfixStatusSubmitting}
|
||||
err := svc.inner.UpdateMapfixIfStatus(ctx, MapfixID, allow_statuses, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
event_data := model.AuditEventDataAction{
|
||||
TargetStatus: uint32(target_status),
|
||||
}
|
||||
|
||||
err = svc.inner.CreateAuditEventAction(
|
||||
ctx,
|
||||
model.ValidatorUserID,
|
||||
model.Resource{
|
||||
ID: MapfixID,
|
||||
Type: model.ResourceMapfix,
|
||||
},
|
||||
event_data,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.NullResponse{}, nil
|
||||
}
|
||||
|
||||
// ActionMapfixValidate invokes actionMapfixValidate operation.
|
||||
//
|
||||
// Role Validator changes status from Validating -> Validated.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/validator-validated
|
||||
func (svc *Mapfixes) SetStatusValidated(ctx context.Context, params *validator.MapfixID) (*validator.NullResponse, error) {
|
||||
MapfixID := int64(params.ID)
|
||||
// transaction
|
||||
update := service.NewMapfixUpdate()
|
||||
update.SetStatusID(model.MapfixStatusValidated)
|
||||
allow_statuses := []model.MapfixStatus{model.MapfixStatusValidating}
|
||||
err := svc.inner.UpdateMapfixIfStatus(ctx, MapfixID, allow_statuses, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.NullResponse{}, nil
|
||||
}
|
||||
|
||||
// ActionMapfixAccepted implements actionMapfixAccepted operation.
|
||||
//
|
||||
// (Internal endpoint) Role Validator changes status from Validating -> Accepted.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/validator-failed
|
||||
func (svc *Mapfixes) SetStatusFailed(ctx context.Context, params *validator.MapfixID) (*validator.NullResponse, error) {
|
||||
MapfixID := int64(params.ID)
|
||||
// transaction
|
||||
target_status := model.MapfixStatusAcceptedUnvalidated
|
||||
update := service.NewMapfixUpdate()
|
||||
update.SetStatusID(target_status)
|
||||
allow_statuses := []model.MapfixStatus{model.MapfixStatusValidating}
|
||||
err := svc.inner.UpdateMapfixIfStatus(ctx, MapfixID, allow_statuses, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// push an action audit event
|
||||
event_data := model.AuditEventDataAction{
|
||||
TargetStatus: uint32(target_status),
|
||||
}
|
||||
|
||||
err = svc.inner.CreateAuditEventAction(
|
||||
ctx,
|
||||
model.ValidatorUserID,
|
||||
model.Resource{
|
||||
ID: MapfixID,
|
||||
Type: model.ResourceMapfix,
|
||||
},
|
||||
event_data,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.NullResponse{}, nil
|
||||
}
|
||||
|
||||
// ActionMapfixUploaded implements actionMapfixUploaded operation.
|
||||
//
|
||||
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/status/validator-uploaded
|
||||
func (svc *Mapfixes) SetStatusUploaded(ctx context.Context, params *validator.MapfixID) (*validator.NullResponse, error) {
|
||||
MapfixID := int64(params.ID)
|
||||
// transaction
|
||||
target_status := model.MapfixStatusUploaded
|
||||
update := service.NewMapfixUpdate()
|
||||
update.SetStatusID(target_status)
|
||||
allow_statuses := []model.MapfixStatus{model.MapfixStatusUploading}
|
||||
err := svc.inner.UpdateMapfixIfStatus(ctx, MapfixID, allow_statuses, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
event_data := model.AuditEventDataAction{
|
||||
TargetStatus: uint32(target_status),
|
||||
}
|
||||
|
||||
err = svc.inner.CreateAuditEventAction(
|
||||
ctx,
|
||||
model.ValidatorUserID,
|
||||
model.Resource{
|
||||
ID: MapfixID,
|
||||
Type: model.ResourceMapfix,
|
||||
},
|
||||
event_data,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.NullResponse{}, nil
|
||||
}
|
||||
|
||||
// CreateMapfixAuditError implements createMapfixAuditError operation.
|
||||
//
|
||||
// Post an error to the audit log
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/error
|
||||
func (svc *Mapfixes) CreateAuditError(ctx context.Context, params *validator.AuditErrorRequest) (*validator.NullResponse, error) {
|
||||
MapfixID := int64(params.ID)
|
||||
event_data := model.AuditEventDataError{
|
||||
Error: params.ErrorMessage,
|
||||
}
|
||||
|
||||
err := svc.inner.CreateAuditEventError(
|
||||
ctx,
|
||||
model.ValidatorUserID,
|
||||
model.Resource{
|
||||
ID: MapfixID,
|
||||
Type: model.ResourceMapfix,
|
||||
},
|
||||
event_data,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.NullResponse{}, nil
|
||||
}
|
||||
|
||||
// CreateMapfixAuditCheckList implements createMapfixAuditCheckList operation.
|
||||
//
|
||||
// Post a checklist to the audit log
|
||||
//
|
||||
// POST /mapfixes/{MapfixID}/checklist
|
||||
func (svc *Mapfixes) CreateAuditChecklist(ctx context.Context, params *validator.AuditChecklistRequest) (*validator.NullResponse, error) {
|
||||
MapfixID := int64(params.ID)
|
||||
check_list := make([]model.Check, len(params.CheckList))
|
||||
for i, check := range params.CheckList {
|
||||
check_list[i] = model.Check{
|
||||
Name: check.Name,
|
||||
Summary: check.Summary,
|
||||
Passed: check.Passed,
|
||||
}
|
||||
}
|
||||
|
||||
event_data := model.AuditEventDataCheckList{
|
||||
CheckList: check_list,
|
||||
}
|
||||
|
||||
err := svc.inner.CreateAuditEventCheckList(
|
||||
ctx,
|
||||
model.ValidatorUserID,
|
||||
model.Resource{
|
||||
ID: MapfixID,
|
||||
Type: model.ResourceMapfix,
|
||||
},
|
||||
event_data,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.NullResponse{}, nil
|
||||
}
|
||||
|
||||
// POST /mapfixes
|
||||
func (svc *Mapfixes) Create(ctx context.Context, request *validator.MapfixCreate) (*validator.MapfixID, error) {
|
||||
var Submitter=request.AssetOwner;
|
||||
// Check if an active mapfix with the same asset id exists
|
||||
{
|
||||
filter := service.NewMapfixFilter()
|
||||
filter.SetAssetID(request.AssetID)
|
||||
filter.SetAssetVersion(request.AssetVersion)
|
||||
filter.SetStatuses(ActiveMapfixStatuses)
|
||||
active_mapfixes, err := svc.inner.ListMapfixes(ctx, filter, model.Page{
|
||||
Number: 1,
|
||||
Size: 1,
|
||||
},datastore.ListSortDisabled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(active_mapfixes) != 0{
|
||||
return nil, ErrActiveMapfixSameAssetID
|
||||
}
|
||||
}
|
||||
|
||||
OperationID := int32(request.OperationID)
|
||||
operation, err := svc.inner.GetOperation(ctx, OperationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check if user owns asset
|
||||
// TODO: allow bypass by admin
|
||||
if operation.Owner != Submitter {
|
||||
return nil, ErrNotAssetOwner
|
||||
}
|
||||
|
||||
mapfix, err := svc.inner.CreateMapfix(ctx, model.Mapfix{
|
||||
ID: 0,
|
||||
DisplayName: request.DisplayName,
|
||||
Creator: request.Creator,
|
||||
GameID: request.GameID,
|
||||
Submitter: Submitter,
|
||||
AssetID: request.AssetID,
|
||||
AssetVersion: request.AssetVersion,
|
||||
Completed: false,
|
||||
TargetAssetID: request.TargetAssetID,
|
||||
StatusID: model.MapfixStatusUnderConstruction,
|
||||
Description: request.Description,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// mark the operation as completed and provide the path
|
||||
params := service.NewOperationCompleteParams(fmt.Sprintf("/mapfixes/%d", mapfix.ID))
|
||||
err = svc.inner.CompleteOperation(ctx, OperationID, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.MapfixID{
|
||||
ID: uint64(mapfix.ID),
|
||||
}, nil
|
||||
}
|
||||
37
pkg/validator_controller/operations.go
Normal file
37
pkg/validator_controller/operations.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package validator_controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.itzana.me/strafesnet/go-grpc/validator"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/service"
|
||||
)
|
||||
|
||||
type Operations struct {
|
||||
*validator.UnimplementedValidatorOperationServiceServer
|
||||
inner *service.Service
|
||||
}
|
||||
func NewOperationsController(
|
||||
inner *service.Service,
|
||||
) Operations {
|
||||
return Operations{
|
||||
inner: inner,
|
||||
}
|
||||
}
|
||||
|
||||
// ActionOperationFailed implements actionOperationFailed operation.
|
||||
//
|
||||
// Fail the specified OperationID with a StatusMessage.
|
||||
//
|
||||
// POST /operations/{OperationID}/status/operation-failed
|
||||
func (svc *Operations) Fail(ctx context.Context, params *validator.OperationFailRequest) (*validator.NullResponse, error) {
|
||||
fail_params := service.NewOperationFailParams(
|
||||
params.StatusMessage,
|
||||
)
|
||||
err := svc.inner.FailOperation(ctx, int32(params.OperationID), fail_params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.NullResponse{}, nil
|
||||
}
|
||||
89
pkg/validator_controller/script_policy.go
Normal file
89
pkg/validator_controller/script_policy.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package validator_controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.itzana.me/strafesnet/go-grpc/validator"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/service"
|
||||
)
|
||||
|
||||
type ScriptPolicy struct {
|
||||
*validator.UnimplementedValidatorScriptPolicyServiceServer
|
||||
inner *service.Service
|
||||
}
|
||||
func NewScriptPolicyController(
|
||||
inner *service.Service,
|
||||
) ScriptPolicy {
|
||||
return ScriptPolicy{
|
||||
inner: inner,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateScriptPolicy implements createScriptPolicy operation.
|
||||
//
|
||||
// Create a new script policy.
|
||||
//
|
||||
// POST /script-policy
|
||||
func (svc *ScriptPolicy) Create(ctx context.Context, req *validator.ScriptPolicyCreate) (*validator.ScriptPolicyID, error) {
|
||||
from_script, err := svc.inner.GetScript(ctx, int64(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.inner.CreateScriptPolicy(ctx, model.ScriptPolicy{
|
||||
ID: 0,
|
||||
FromScriptHash: from_script.Hash,
|
||||
ToScriptID: int64(req.ToScriptID),
|
||||
Policy: model.Policy(req.Policy),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.ScriptPolicyID{
|
||||
ID: uint64(script.ID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListScriptPolicy implements listScriptPolicy operation.
|
||||
//
|
||||
// Get list of script policies.
|
||||
//
|
||||
// GET /script-policy
|
||||
func (svc *ScriptPolicy) List(ctx context.Context, params *validator.ScriptPolicyListRequest) (*validator.ScriptPolicyListResponse, error) {
|
||||
filter := service.NewScriptPolicyFilter()
|
||||
|
||||
if params.Filter.FromScriptHash != nil {
|
||||
filter.SetFromScriptHash(int64(*params.Filter.FromScriptHash))
|
||||
}
|
||||
if params.Filter.ToScriptID != nil {
|
||||
filter.SetToScriptID(int64(*params.Filter.ToScriptID))
|
||||
}
|
||||
if params.Filter.Policy != nil {
|
||||
filter.SetPolicy(int32(*params.Filter.Policy))
|
||||
}
|
||||
|
||||
items, err := svc.inner.ListScriptPolicies(ctx, filter, model.Page{
|
||||
Number: int32(params.Page.Number),
|
||||
Size: int32(params.Page.Size),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := validator.ScriptPolicyListResponse{}
|
||||
resp.ScriptPolicies = make([]*validator.ScriptPolicy, len(items))
|
||||
for i, item := range items {
|
||||
resp.ScriptPolicies[i] = &validator.ScriptPolicy{
|
||||
ID: uint64(item.ID),
|
||||
FromScriptHash: uint64(item.FromScriptHash),
|
||||
ToScriptID: uint64(item.ToScriptID),
|
||||
Policy: validator.Policy(int32(item.Policy)),
|
||||
}
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
119
pkg/validator_controller/scripts.go
Normal file
119
pkg/validator_controller/scripts.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package validator_controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.itzana.me/strafesnet/go-grpc/validator"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/service"
|
||||
)
|
||||
|
||||
type Scripts struct {
|
||||
*validator.UnimplementedValidatorScriptServiceServer
|
||||
inner *service.Service
|
||||
}
|
||||
func NewScriptsController(
|
||||
inner *service.Service,
|
||||
) Scripts {
|
||||
return Scripts{
|
||||
inner: inner,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateScript implements createScript operation.
|
||||
//
|
||||
// Create a new script.
|
||||
//
|
||||
// POST /scripts
|
||||
func (svc *Scripts) Create(ctx context.Context, req *validator.ScriptCreate) (*validator.ScriptID, error) {
|
||||
ResourceID := int64(0)
|
||||
if req.ResourceID != nil {
|
||||
ResourceID = int64(*req.ResourceID)
|
||||
}
|
||||
script, err := svc.inner.CreateScript(ctx, model.Script{
|
||||
ID: 0,
|
||||
Name: req.Name,
|
||||
Hash: int64(model.HashSource(req.Source)),
|
||||
Source: req.Source,
|
||||
ResourceType: model.ResourceType(req.ResourceType),
|
||||
ResourceID: ResourceID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.ScriptID{
|
||||
ID: uint64(script.ID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListScripts implements listScripts operation.
|
||||
//
|
||||
// Get list of scripts.
|
||||
//
|
||||
// GET /scripts
|
||||
func (svc *Scripts) List(ctx context.Context, params *validator.ScriptListRequest) (*validator.ScriptListResponse, error) {
|
||||
filter := service.NewScriptFilter()
|
||||
if params.Filter.Hash != nil {
|
||||
filter.SetHash(int64(*params.Filter.Hash))
|
||||
}
|
||||
if params.Filter.Name != nil {
|
||||
filter.SetName(*params.Filter.Name)
|
||||
}
|
||||
if params.Filter.Source != nil {
|
||||
filter.SetSource(*params.Filter.Source)
|
||||
}
|
||||
if params.Filter.ResourceType != nil {
|
||||
filter.SetResourceType(int32(*params.Filter.ResourceType))
|
||||
}
|
||||
if params.Filter.ResourceID != nil {
|
||||
filter.SetResourceID(int64(*params.Filter.ResourceID))
|
||||
}
|
||||
|
||||
items, err := svc.inner.ListScripts(ctx, filter, model.Page{
|
||||
Number: int32(params.Page.Number),
|
||||
Size: int32(params.Page.Size),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := validator.ScriptListResponse{}
|
||||
resp.Scripts = make([]*validator.Script, len(items))
|
||||
for i, item := range items {
|
||||
resource_id := uint64(item.ResourceID)
|
||||
resp.Scripts[i] = &validator.Script{
|
||||
ID: uint64(item.ID),
|
||||
Name: item.Name,
|
||||
Hash: uint64(item.Hash),
|
||||
Source: item.Source,
|
||||
ResourceType: validator.ResourceType(item.ResourceType),
|
||||
ResourceID: &resource_id,
|
||||
}
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// GetScript implements getScript operation.
|
||||
//
|
||||
// Get the specified script by ID.
|
||||
//
|
||||
// GET /scripts/{ScriptID}
|
||||
func (svc *Scripts) Get(ctx context.Context, params *validator.ScriptID) (*validator.Script, error) {
|
||||
ScriptID := int64(params.ID)
|
||||
script, err := svc.inner.GetScript(ctx, ScriptID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ResourceID := uint64(script.ResourceID)
|
||||
return &validator.Script{
|
||||
ID: uint64(script.ID),
|
||||
Name: script.Name,
|
||||
Hash: uint64(script.Hash),
|
||||
Source: script.Source,
|
||||
ResourceType: validator.ResourceType(script.ResourceType),
|
||||
ResourceID: &ResourceID,
|
||||
}, nil
|
||||
}
|
||||
403
pkg/validator_controller/submissions.go
Normal file
403
pkg/validator_controller/submissions.go
Normal file
@@ -0,0 +1,403 @@
|
||||
package validator_controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.itzana.me/strafesnet/go-grpc/validator"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/service"
|
||||
)
|
||||
|
||||
type Submissions struct {
|
||||
*validator.UnimplementedValidatorSubmissionServiceServer
|
||||
inner *service.Service
|
||||
}
|
||||
func NewSubmissionsController(
|
||||
inner *service.Service,
|
||||
) Submissions {
|
||||
return Submissions{
|
||||
inner: inner,
|
||||
}
|
||||
}
|
||||
|
||||
var(
|
||||
// prevent two mapfixes with same asset id
|
||||
ActiveSubmissionStatuses = []model.SubmissionStatus{
|
||||
model.SubmissionStatusUploading,
|
||||
model.SubmissionStatusValidated,
|
||||
model.SubmissionStatusValidating,
|
||||
model.SubmissionStatusAcceptedUnvalidated,
|
||||
model.SubmissionStatusChangesRequested,
|
||||
model.SubmissionStatusSubmitted,
|
||||
model.SubmissionStatusUnderConstruction,
|
||||
}
|
||||
)
|
||||
|
||||
var(
|
||||
ErrActiveSubmissionSameAssetID = errors.New("There is an active submission with the same AssetID")
|
||||
)
|
||||
|
||||
// UpdateSubmissionValidatedModel implements patchSubmissionModel operation.
|
||||
//
|
||||
// Update model following role restrictions.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/validated-model
|
||||
func (svc *Submissions) SetValidatedModel(ctx context.Context, params *validator.ValidatedModelRequest) (*validator.NullResponse, error) {
|
||||
SubmissionID := int64(params.ID)
|
||||
|
||||
// check if Status is ChangesRequested|Submitted|UnderConstruction
|
||||
update := service.NewSubmissionUpdate()
|
||||
update.SetValidatedAssetID(params.ValidatedModelID)
|
||||
update.SetValidatedAssetVersion(params.ValidatedModelVersion)
|
||||
// DO NOT reset completed when validated model is updated
|
||||
// update.Add("completed", false)
|
||||
allowed_statuses := []model.SubmissionStatus{model.SubmissionStatusValidating}
|
||||
err := svc.inner.UpdateSubmissionIfStatus(ctx, SubmissionID, allowed_statuses, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
event_data := model.AuditEventDataChangeValidatedModel{
|
||||
ValidatedModelID: params.ValidatedModelID,
|
||||
ValidatedModelVersion: params.ValidatedModelVersion,
|
||||
}
|
||||
|
||||
err = svc.inner.CreateAuditEventChangeValidatedModel(
|
||||
ctx,
|
||||
model.ValidatorUserID,
|
||||
model.Resource{
|
||||
ID: SubmissionID,
|
||||
Type: model.ResourceSubmission,
|
||||
},
|
||||
event_data,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.NullResponse{}, nil
|
||||
}
|
||||
|
||||
// ActionSubmissionSubmitted invokes actionSubmissionSubmitted operation.
|
||||
//
|
||||
// Role Validator changes status from Submitting -> Submitted.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/validator-submitted
|
||||
func (svc *Submissions) SetStatusSubmitted(ctx context.Context, params *validator.SubmittedRequest) (*validator.NullResponse, error) {
|
||||
SubmissionID := int64(params.ID)
|
||||
// transaction
|
||||
target_status := model.SubmissionStatusSubmitted
|
||||
update := service.NewSubmissionUpdate()
|
||||
update.SetStatusID(target_status)
|
||||
update.SetAssetVersion(uint64(params.ModelVersion))
|
||||
update.SetDisplayName(params.DisplayName)
|
||||
update.SetCreator(params.Creator)
|
||||
update.SetGameID(uint32(params.GameID))
|
||||
allowed_statuses := []model.SubmissionStatus{model.SubmissionStatusSubmitting}
|
||||
err := svc.inner.UpdateSubmissionIfStatus(ctx, SubmissionID, allowed_statuses, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
event_data := model.AuditEventDataAction{
|
||||
TargetStatus: uint32(target_status),
|
||||
}
|
||||
|
||||
err = svc.inner.CreateAuditEventAction(
|
||||
ctx,
|
||||
model.ValidatorUserID,
|
||||
model.Resource{
|
||||
ID: SubmissionID,
|
||||
Type: model.ResourceSubmission,
|
||||
},
|
||||
event_data,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.NullResponse{}, nil
|
||||
}
|
||||
|
||||
// ActionSubmissionRequestChanges implements actionSubmissionRequestChanges operation.
|
||||
//
|
||||
// (Internal endpoint) Role Validator changes status from Submitting -> RequestChanges.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/validator-request-changes
|
||||
func (svc *Submissions) SetStatusRequestChanges(ctx context.Context, params *validator.SubmissionID) (*validator.NullResponse, error) {
|
||||
SubmissionID := int64(params.ID)
|
||||
// transaction
|
||||
target_status := model.SubmissionStatusChangesRequested
|
||||
update := service.NewSubmissionUpdate()
|
||||
update.SetStatusID(target_status)
|
||||
allowed_statuses :=[]model.SubmissionStatus{model.SubmissionStatusSubmitting}
|
||||
err := svc.inner.UpdateSubmissionIfStatus(ctx, SubmissionID, allowed_statuses, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// push an action audit event
|
||||
event_data := model.AuditEventDataAction{
|
||||
TargetStatus: uint32(target_status),
|
||||
}
|
||||
|
||||
err = svc.inner.CreateAuditEventAction(
|
||||
ctx,
|
||||
model.ValidatorUserID,
|
||||
model.Resource{
|
||||
ID: SubmissionID,
|
||||
Type: model.ResourceSubmission,
|
||||
},
|
||||
event_data,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.NullResponse{}, nil
|
||||
}
|
||||
|
||||
// ActionSubmissionValidate invokes actionSubmissionValidate operation.
|
||||
//
|
||||
// Role Validator changes status from Validating -> Validated.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/validator-validated
|
||||
func (svc *Submissions) SetStatusValidated(ctx context.Context, params *validator.SubmissionID) (*validator.NullResponse, error) {
|
||||
SubmissionID := int64(params.ID)
|
||||
// transaction
|
||||
target_status := model.SubmissionStatusValidated
|
||||
update := service.NewSubmissionUpdate()
|
||||
update.SetStatusID(target_status)
|
||||
allowed_statuses :=[]model.SubmissionStatus{model.SubmissionStatusValidating}
|
||||
err := svc.inner.UpdateSubmissionIfStatus(ctx, SubmissionID, allowed_statuses, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
event_data := model.AuditEventDataAction{
|
||||
TargetStatus: uint32(target_status),
|
||||
}
|
||||
|
||||
err = svc.inner.CreateAuditEventAction(
|
||||
ctx,
|
||||
model.ValidatorUserID,
|
||||
model.Resource{
|
||||
ID: SubmissionID,
|
||||
Type: model.ResourceSubmission,
|
||||
},
|
||||
event_data,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.NullResponse{}, nil
|
||||
}
|
||||
|
||||
// ActionSubmissionAccepted implements actionSubmissionAccepted operation.
|
||||
//
|
||||
// (Internal endpoint) Role Validator changes status from Validating -> Accepted.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/validator-failed
|
||||
func (svc *Submissions) SetStatusFailed(ctx context.Context, params *validator.SubmissionID) (*validator.NullResponse, error) {
|
||||
SubmissionID := int64(params.ID)
|
||||
// transaction
|
||||
target_status := model.SubmissionStatusAcceptedUnvalidated
|
||||
update := service.NewSubmissionUpdate()
|
||||
update.SetStatusID(target_status)
|
||||
allowed_statuses :=[]model.SubmissionStatus{model.SubmissionStatusValidating}
|
||||
err := svc.inner.UpdateSubmissionIfStatus(ctx, SubmissionID, allowed_statuses, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// push an action audit event
|
||||
event_data := model.AuditEventDataAction{
|
||||
TargetStatus: uint32(target_status),
|
||||
}
|
||||
|
||||
err = svc.inner.CreateAuditEventAction(
|
||||
ctx,
|
||||
model.ValidatorUserID,
|
||||
model.Resource{
|
||||
ID: SubmissionID,
|
||||
Type: model.ResourceSubmission,
|
||||
},
|
||||
event_data,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.NullResponse{}, nil
|
||||
}
|
||||
|
||||
// ActionSubmissionUploaded implements actionSubmissionUploaded operation.
|
||||
//
|
||||
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/status/validator-uploaded
|
||||
func (svc *Submissions) SetStatusUploaded(ctx context.Context, params *validator.StatusUploadedRequest) (*validator.NullResponse, error) {
|
||||
SubmissionID := int64(params.ID)
|
||||
// transaction
|
||||
target_status := model.SubmissionStatusUploaded
|
||||
update := service.NewSubmissionUpdate()
|
||||
update.SetStatusID(target_status)
|
||||
update.SetUploadedAssetID(params.UploadedAssetID)
|
||||
allowed_statuses :=[]model.SubmissionStatus{model.SubmissionStatusUploading}
|
||||
err := svc.inner.UpdateSubmissionIfStatus(ctx, SubmissionID, allowed_statuses, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
event_data := model.AuditEventDataAction{
|
||||
TargetStatus: uint32(target_status),
|
||||
}
|
||||
|
||||
err = svc.inner.CreateAuditEventAction(
|
||||
ctx,
|
||||
model.ValidatorUserID,
|
||||
model.Resource{
|
||||
ID: SubmissionID,
|
||||
Type: model.ResourceSubmission,
|
||||
},
|
||||
event_data,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.NullResponse{}, nil
|
||||
}
|
||||
|
||||
// CreateSubmissionAuditError implements createSubmissionAuditError operation.
|
||||
//
|
||||
// Post an error to the audit log
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/error
|
||||
func (svc *Submissions) CreateAuditError(ctx context.Context, params *validator.AuditErrorRequest) (*validator.NullResponse, error) {
|
||||
SubmissionID := int64(params.ID)
|
||||
event_data := model.AuditEventDataError{
|
||||
Error: params.ErrorMessage,
|
||||
}
|
||||
|
||||
err := svc.inner.CreateAuditEventError(
|
||||
ctx,
|
||||
model.ValidatorUserID,
|
||||
model.Resource{
|
||||
ID: SubmissionID,
|
||||
Type: model.ResourceSubmission,
|
||||
},
|
||||
event_data,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.NullResponse{}, nil
|
||||
}
|
||||
|
||||
// CreateSubmissionAuditCheckList implements createSubmissionAuditCheckList operation.
|
||||
//
|
||||
// Post a checklist to the audit log
|
||||
//
|
||||
// POST /submissions/{SubmissionID}/checklist
|
||||
func (svc *Submissions) CreateAuditChecklist(ctx context.Context, params *validator.AuditChecklistRequest) (*validator.NullResponse, error) {
|
||||
SubmissionID := int64(params.ID)
|
||||
check_list := make([]model.Check, len(params.CheckList))
|
||||
for i, check := range params.CheckList {
|
||||
check_list[i] = model.Check{
|
||||
Name: check.Name,
|
||||
Summary: check.Summary,
|
||||
Passed: check.Passed,
|
||||
}
|
||||
}
|
||||
|
||||
event_data := model.AuditEventDataCheckList{
|
||||
CheckList: check_list,
|
||||
}
|
||||
|
||||
err := svc.inner.CreateAuditEventCheckList(
|
||||
ctx,
|
||||
model.ValidatorUserID,
|
||||
model.Resource{
|
||||
ID: SubmissionID,
|
||||
Type: model.ResourceSubmission,
|
||||
},
|
||||
event_data,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.NullResponse{}, nil
|
||||
}
|
||||
|
||||
// POST /submissions
|
||||
func (svc *Submissions) Create(ctx context.Context, request *validator.SubmissionCreate) (*validator.SubmissionID, error) {
|
||||
var Submitter=uint64(request.AssetOwner);
|
||||
var Status=model.SubmissionStatus(request.Status);
|
||||
var roles=model.Roles(request.Roles);
|
||||
|
||||
// Check if an active submission with the same asset id exists
|
||||
{
|
||||
filter := service.NewSubmissionFilter()
|
||||
filter.SetAssetID(request.AssetID)
|
||||
filter.SetAssetVersion(request.AssetVersion)
|
||||
filter.SetStatuses(ActiveSubmissionStatuses)
|
||||
active_submissions, err := svc.inner.ListSubmissions(ctx, filter, model.Page{
|
||||
Number: 1,
|
||||
Size: 1,
|
||||
},datastore.ListSortDisabled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(active_submissions) != 0{
|
||||
return nil, ErrActiveSubmissionSameAssetID
|
||||
}
|
||||
}
|
||||
|
||||
operation_id := int32(request.OperationID)
|
||||
operation, err := svc.inner.GetOperation(ctx, operation_id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check if user owns asset
|
||||
is_submitter := operation.Owner == Submitter
|
||||
// check if user is map admin
|
||||
has_submission_review := roles & model.RolesSubmissionReview == model.RolesSubmissionReview
|
||||
// if neither, u not allowed
|
||||
if !is_submitter && !has_submission_review {
|
||||
return nil, ErrNotAssetOwner
|
||||
}
|
||||
|
||||
submission, err := svc.inner.CreateSubmission(ctx, model.Submission{
|
||||
ID: 0,
|
||||
DisplayName: request.DisplayName,
|
||||
Creator: request.Creator,
|
||||
GameID: request.GameID,
|
||||
Submitter: Submitter,
|
||||
AssetID: request.AssetID,
|
||||
AssetVersion: request.AssetVersion,
|
||||
Completed: false,
|
||||
StatusID: Status,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// mark the operation as completed and provide the path
|
||||
params := service.NewOperationCompleteParams(fmt.Sprintf("/submissions/%d", submission.ID))
|
||||
err = svc.inner.CompleteOperation(ctx, operation_id, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &validator.SubmissionID{
|
||||
ID: uint64(submission.ID),
|
||||
}, nil
|
||||
}
|
||||
1068
pkg/web_api/mapfixes.go
Normal file
1068
pkg/web_api/mapfixes.go
Normal file
File diff suppressed because it is too large
Load Diff
131
pkg/web_api/maps.go
Normal file
131
pkg/web_api/maps.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package web_api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/api"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/roblox"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/service"
|
||||
)
|
||||
|
||||
// ListMaps implements listMaps operation.
|
||||
//
|
||||
// Get list of maps.
|
||||
//
|
||||
// GET /maps
|
||||
func (svc *Service) ListMaps(ctx context.Context, params api.ListMapsParams) ([]api.Map, error) {
|
||||
filter := service.NewMapFilter()
|
||||
|
||||
if display_name, display_name_ok := params.DisplayName.Get(); display_name_ok{
|
||||
filter.SetDisplayName(display_name)
|
||||
}
|
||||
if creator, creator_ok := params.Creator.Get(); creator_ok{
|
||||
filter.SetCreator(creator)
|
||||
}
|
||||
if game_id, game_id_ok := params.GameID.Get(); game_id_ok{
|
||||
filter.SetGameID(uint32(game_id))
|
||||
}
|
||||
|
||||
items, err := svc.inner.ListMaps(ctx,
|
||||
filter,
|
||||
model.Page{
|
||||
Size: params.Limit,
|
||||
Number: params.Page,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp []api.Map
|
||||
for _, item := range items {
|
||||
resp = append(resp, api.Map{
|
||||
ID: item.ID,
|
||||
DisplayName: item.DisplayName,
|
||||
Creator: item.Creator,
|
||||
GameID: int32(item.GameID),
|
||||
Date: item.Date.Unix(),
|
||||
CreatedAt: item.CreatedAt.Unix(),
|
||||
UpdatedAt: item.UpdatedAt.Unix(),
|
||||
Submitter: item.Submitter,
|
||||
Thumbnail: item.Thumbnail,
|
||||
AssetVersion: item.AssetVersion,
|
||||
LoadCount: item.LoadCount,
|
||||
Modes: item.Modes,
|
||||
})
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetMap implements getScript operation.
|
||||
//
|
||||
// Get the specified script by ID.
|
||||
//
|
||||
// GET /maps/{MapID}
|
||||
func (svc *Service) GetMap(ctx context.Context, params api.GetMapParams) (*api.Map, error) {
|
||||
mapResponse, err := svc.inner.GetMap(ctx, params.MapID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &api.Map{
|
||||
ID: mapResponse.ID,
|
||||
DisplayName: mapResponse.DisplayName,
|
||||
Creator: mapResponse.Creator,
|
||||
GameID: int32(mapResponse.GameID),
|
||||
Date: mapResponse.Date.Unix(),
|
||||
CreatedAt: mapResponse.CreatedAt.Unix(),
|
||||
UpdatedAt: mapResponse.UpdatedAt.Unix(),
|
||||
Submitter: mapResponse.Submitter,
|
||||
Thumbnail: mapResponse.Thumbnail,
|
||||
AssetVersion: mapResponse.AssetVersion,
|
||||
LoadCount: mapResponse.LoadCount,
|
||||
Modes: mapResponse.Modes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DownloadMapAsset invokes downloadMapAsset operation.
|
||||
//
|
||||
// Download the map asset.
|
||||
//
|
||||
// GET /maps/{MapID}/download
|
||||
func (svc *Service) DownloadMapAsset(ctx context.Context, params api.DownloadMapAssetParams) (ok api.DownloadMapAssetOK, err error) {
|
||||
userInfo, success := ctx.Value("UserInfo").(UserInfoHandle)
|
||||
if !success {
|
||||
return ok, ErrUserInfo
|
||||
}
|
||||
|
||||
has_role, err := userInfo.HasRoleMapDownload()
|
||||
if err != nil {
|
||||
return ok, err
|
||||
}
|
||||
|
||||
if !has_role {
|
||||
return ok, ErrPermissionDeniedNeedRoleMapDownload
|
||||
}
|
||||
|
||||
// Ensure map exists in the db!
|
||||
// This could otherwise be used to access any asset
|
||||
_, err = svc.inner.GetMap(ctx, params.MapID)
|
||||
if err != nil {
|
||||
return ok, err
|
||||
}
|
||||
|
||||
info, err := svc.roblox.GetAssetLocation(roblox.GetAssetLatestRequest{
|
||||
AssetID: uint64(params.MapID),
|
||||
})
|
||||
if err != nil{
|
||||
return ok, err
|
||||
}
|
||||
|
||||
// download the complete file
|
||||
asset, err := svc.roblox.DownloadAsset(info)
|
||||
if err != nil{
|
||||
return ok, err
|
||||
}
|
||||
|
||||
ok.Data = asset
|
||||
return ok, nil
|
||||
}
|
||||
46
pkg/web_api/operations.go
Normal file
46
pkg/web_api/operations.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package web_api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/api"
|
||||
)
|
||||
|
||||
// GetOperation implements getOperation operation.
|
||||
//
|
||||
// Get the specified operation by ID.
|
||||
//
|
||||
// GET /operations/{OperationID}
|
||||
func (svc *Service) GetOperation(ctx context.Context, params api.GetOperationParams) (*api.Operation, error) {
|
||||
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
|
||||
if !ok {
|
||||
return nil, ErrUserInfo
|
||||
}
|
||||
|
||||
// You must be the operation owner to read it
|
||||
|
||||
operation, err := svc.inner.GetOperation(ctx, params.OperationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userId, err := userInfo.GetUserID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check if caller is the submitter
|
||||
has_role := userId == operation.Owner
|
||||
if !has_role {
|
||||
return nil, ErrPermissionDeniedNotSubmitter
|
||||
}
|
||||
|
||||
return &api.Operation{
|
||||
OperationID: operation.ID,
|
||||
Date: operation.CreatedAt.Unix(),
|
||||
Owner: int64(operation.Owner),
|
||||
Status: int32(operation.StatusID),
|
||||
StatusMessage: operation.StatusMessage,
|
||||
Path: operation.Path,
|
||||
}, nil
|
||||
}
|
||||
149
pkg/web_api/script_policy.go
Normal file
149
pkg/web_api/script_policy.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package web_api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/api"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/service"
|
||||
)
|
||||
|
||||
// CreateScriptPolicy implements createScriptPolicy operation.
|
||||
//
|
||||
// Create a new script policy.
|
||||
//
|
||||
// POST /script-policy
|
||||
func (svc *Service) CreateScriptPolicy(ctx context.Context, req *api.ScriptPolicyCreate) (*api.ScriptPolicyID, error) {
|
||||
err := CheckHasRoleScriptWrite(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
from_script, err := svc.inner.GetScript(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.inner.CreateScriptPolicy(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.ScriptPolicyID{
|
||||
ScriptPolicyID: 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 := service.NewScriptPolicyFilter()
|
||||
|
||||
if hash_hex, ok := params.FromScriptHash.Get(); ok{
|
||||
hash_parsed, err := model.HashParse(hash_hex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filter.SetFromScriptHash(int64(hash_parsed))
|
||||
}
|
||||
if to_script_id, to_script_id_ok := params.ToScriptID.Get(); to_script_id_ok{
|
||||
filter.SetToScriptID(to_script_id)
|
||||
}
|
||||
if policy, policy_ok := params.Policy.Get(); policy_ok{
|
||||
filter.SetPolicy(policy)
|
||||
}
|
||||
|
||||
|
||||
items, err := svc.inner.ListScriptPolicies(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
|
||||
}
|
||||
|
||||
// DeleteScriptPolicy implements deleteScriptPolicy operation.
|
||||
//
|
||||
// Delete the specified script policy by ID.
|
||||
//
|
||||
// DELETE /script-policy/{ScriptPolicyID}
|
||||
func (svc *Service) DeleteScriptPolicy(ctx context.Context, params api.DeleteScriptPolicyParams) error {
|
||||
err := CheckHasRoleScriptWrite(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return svc.inner.DeleteScriptPolicy(ctx, params.ScriptPolicyID)
|
||||
}
|
||||
|
||||
// GetScriptPolicy implements getScriptPolicy operation.
|
||||
//
|
||||
// Get the specified script policy by ID.
|
||||
//
|
||||
// GET /script-policy/{ScriptPolicyID}
|
||||
func (svc *Service) GetScriptPolicy(ctx context.Context, params api.GetScriptPolicyParams) (*api.ScriptPolicy, error) {
|
||||
policy, err := svc.inner.GetScriptPolicy(ctx, params.ScriptPolicyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &api.ScriptPolicy{
|
||||
ID: policy.ID,
|
||||
FromScriptHash: model.HashFormat(uint64(policy.FromScriptHash)),
|
||||
ToScriptID: policy.ToScriptID,
|
||||
Policy: int32(policy.Policy),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateScriptPolicy implements updateScriptPolicy operation.
|
||||
//
|
||||
// Update the specified script policy by ID.
|
||||
//
|
||||
// POST /script-policy/{ScriptPolicyID}
|
||||
func (svc *Service) UpdateScriptPolicy(ctx context.Context, req *api.ScriptPolicyUpdate, params api.UpdateScriptPolicyParams) error {
|
||||
err := CheckHasRoleScriptWrite(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filter := service.NewScriptPolicyFilter()
|
||||
if from_script_id, ok := req.FromScriptID.Get(); ok {
|
||||
from_script, err := svc.inner.GetScript(ctx, from_script_id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filter.SetFromScriptHash(from_script.Hash)
|
||||
}
|
||||
if to_script_id, to_script_id_ok := req.ToScriptID.Get(); to_script_id_ok{
|
||||
filter.SetToScriptID(to_script_id)
|
||||
}
|
||||
if policy, policy_ok := req.Policy.Get(); policy_ok{
|
||||
filter.SetPolicy(policy)
|
||||
}
|
||||
|
||||
return svc.inner.UpdateScriptPolicy(ctx, req.ID, filter)
|
||||
}
|
||||
170
pkg/web_api/scripts.go
Normal file
170
pkg/web_api/scripts.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package web_api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/api"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/service"
|
||||
)
|
||||
|
||||
func CheckHasRoleScriptWrite(ctx context.Context) error {
|
||||
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
|
||||
if !ok {
|
||||
return ErrUserInfo
|
||||
}
|
||||
|
||||
has_role, err := userInfo.HasRoleScriptWrite()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !has_role {
|
||||
return ErrPermissionDeniedNeedRoleScriptWrite
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateScript implements createScript operation.
|
||||
//
|
||||
// Create a new script.
|
||||
//
|
||||
// POST /scripts
|
||||
func (svc *Service) CreateScript(ctx context.Context, req *api.ScriptCreate) (*api.ScriptID, error) {
|
||||
err := CheckHasRoleScriptWrite(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
script, err := svc.inner.CreateScript(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.ScriptID{
|
||||
ScriptID: 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 := service.NewScriptFilter()
|
||||
|
||||
if hash_hex, ok := params.Hash.Get(); ok{
|
||||
hash_parsed, err := model.HashParse(hash_hex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filter.SetHash(int64(hash_parsed))
|
||||
}
|
||||
if name, name_ok := params.Name.Get(); name_ok{
|
||||
filter.SetName(name)
|
||||
}
|
||||
if source, source_ok := params.Source.Get(); source_ok{
|
||||
filter.SetSource(source)
|
||||
}
|
||||
if resource_type, resource_type_ok := params.ResourceType.Get(); resource_type_ok{
|
||||
filter.SetResourceType(resource_type)
|
||||
}
|
||||
if resource_id, resource_id_ok := params.ResourceID.Get(); resource_id_ok{
|
||||
filter.SetResourceID(resource_id)
|
||||
}
|
||||
|
||||
|
||||
items, err := svc.inner.ListScripts(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,
|
||||
Name: item.Name,
|
||||
Hash: model.HashFormat(uint64(item.Hash)),
|
||||
Source: item.Source,
|
||||
ResourceType: int32(item.ResourceType),
|
||||
ResourceID: item.ResourceID,
|
||||
})
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeleteScript implements deleteScript operation.
|
||||
//
|
||||
// Delete the specified script by ID.
|
||||
//
|
||||
// DELETE /scripts/{ScriptID}
|
||||
func (svc *Service) DeleteScript(ctx context.Context, params api.DeleteScriptParams) error {
|
||||
err := CheckHasRoleScriptWrite(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return svc.inner.DeleteScript(ctx, params.ScriptID)
|
||||
}
|
||||
|
||||
// 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.inner.GetScript(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
|
||||
}
|
||||
|
||||
// UpdateScript implements updateScript operation.
|
||||
//
|
||||
// Update the specified script by ID.
|
||||
//
|
||||
// PATCH /scripts/{ScriptID}
|
||||
func (svc *Service) UpdateScript(ctx context.Context, req *api.ScriptUpdate, params api.UpdateScriptParams) error {
|
||||
err := CheckHasRoleScriptWrite(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filter := service.NewScriptFilter()
|
||||
if name, name_ok := req.Name.Get(); name_ok{
|
||||
filter.SetName(name)
|
||||
}
|
||||
if source, source_ok := req.Source.Get(); source_ok{
|
||||
filter.SetSource(source)
|
||||
filter.SetHash(int64(model.HashSource(source)))
|
||||
}
|
||||
if resource_type, resource_type_ok := req.ResourceType.Get(); resource_type_ok{
|
||||
filter.SetResourceType(resource_type)
|
||||
}
|
||||
if resource_id, resource_id_ok := req.ResourceID.Get(); resource_id_ok{
|
||||
filter.SetResourceID(resource_id)
|
||||
}
|
||||
|
||||
return svc.inner.UpdateScript(ctx, req.ID, filter)
|
||||
}
|
||||
140
pkg/web_api/security.go
Normal file
140
pkg/web_api/security.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package web_api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"git.itzana.me/strafesnet/go-grpc/auth"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/api"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrMissingSessionID there is no session id
|
||||
ErrMissingSessionID = errors.New("SessionID missing")
|
||||
// ErrInvalidSession caller does not have a valid session
|
||||
ErrInvalidSession = errors.New("Session invalid")
|
||||
)
|
||||
|
||||
type UserInfoHandle struct {
|
||||
// Would love to know a better way to do this
|
||||
svc *SecurityHandler
|
||||
ctx *context.Context
|
||||
sessionId string
|
||||
}
|
||||
type UserInfo struct {
|
||||
UserID uint64
|
||||
Username string
|
||||
AvatarURL string
|
||||
}
|
||||
|
||||
func (usr UserInfoHandle) GetUserInfo() (userInfo UserInfo, err error) {
|
||||
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) hasRoles(wantRoles model.Roles) (bool, error) {
|
||||
haveroles, err := usr.GetRoles()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return haveroles & wantRoles == wantRoles, nil
|
||||
}
|
||||
func (usr UserInfoHandle) GetRoles() (model.Roles, error) {
|
||||
roles, err := usr.svc.Client.GetGroupRole(*usr.ctx, &auth.IdMessage{
|
||||
SessionID: usr.sessionId,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return model.RolesEmpty, err
|
||||
}
|
||||
|
||||
// map roles into bitflag
|
||||
rolesBitflag := model.RolesEmpty;
|
||||
for _, r := range roles.Roles {
|
||||
switch model.GroupRole(r.Rank){
|
||||
case model.RoleQuat, model.RoleItzaname, model.RoleStagingDeveloper:
|
||||
rolesBitflag|=model.RolesAll
|
||||
case model.RoleMapAdmin:
|
||||
rolesBitflag|=model.RolesMapAdmin
|
||||
case model.RoleMapCouncil:
|
||||
rolesBitflag|=model.RolesMapCouncil
|
||||
case model.RoleMapAccess:
|
||||
rolesBitflag|=model.RolesMapAccess
|
||||
}
|
||||
}
|
||||
return rolesBitflag, nil
|
||||
}
|
||||
|
||||
// RoleThumbnail
|
||||
func (usr UserInfoHandle) HasRoleMapfixUpload() (bool, error) {
|
||||
return usr.hasRoles(model.RolesMapfixUpload)
|
||||
}
|
||||
func (usr UserInfoHandle) HasRoleMapfixReview() (bool, error) {
|
||||
return usr.hasRoles(model.RolesMapfixReview)
|
||||
}
|
||||
func (usr UserInfoHandle) HasRoleMapDownload() (bool, error) {
|
||||
return usr.hasRoles(model.RolesMapDownload)
|
||||
}
|
||||
func (usr UserInfoHandle) HasRoleSubmissionRelease() (bool, error) {
|
||||
return usr.hasRoles(model.RolesSubmissionRelease)
|
||||
}
|
||||
func (usr UserInfoHandle) HasRoleSubmissionUpload() (bool, error) {
|
||||
return usr.hasRoles(model.RolesSubmissionUpload)
|
||||
}
|
||||
func (usr UserInfoHandle) HasRoleSubmissionReview() (bool, error) {
|
||||
return usr.hasRoles(model.RolesSubmissionReview)
|
||||
}
|
||||
func (usr UserInfoHandle) HasRoleScriptWrite() (bool, error) {
|
||||
return usr.hasRoles(model.RolesScriptWrite)
|
||||
}
|
||||
/// Not implemented
|
||||
func (usr UserInfoHandle) HasRoleMaptest() (bool, error) {
|
||||
println("HasRoleMaptest is not implemented!")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
type SecurityHandler struct {
|
||||
Client auth.AuthServiceClient
|
||||
}
|
||||
|
||||
func (svc SecurityHandler) HandleCookieAuth(ctx context.Context, operationName api.OperationName, t api.CookieAuth) (context.Context, error) {
|
||||
sessionId := t.GetAPIKey()
|
||||
if sessionId == "" {
|
||||
return nil, ErrMissingSessionID
|
||||
}
|
||||
|
||||
newCtx := context.WithValue(ctx, "UserInfo", UserInfoHandle{
|
||||
svc: &svc,
|
||||
ctx: &ctx,
|
||||
sessionId: sessionId,
|
||||
})
|
||||
|
||||
return newCtx, nil
|
||||
}
|
||||
68
pkg/web_api/service.go
Normal file
68
pkg/web_api/service.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package web_api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/api"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/roblox"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/service"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrPermissionDenied caller does not have the required role
|
||||
ErrPermissionDenied = errors.New("Permission denied")
|
||||
// ErrUserInfo user info is missing for some reason
|
||||
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)
|
||||
ErrPermissionDeniedNeedRoleMapfixUpload = fmt.Errorf("%w: Need Role MapfixUpload", ErrPermissionDenied)
|
||||
ErrPermissionDeniedNeedRoleMapfixReview = fmt.Errorf("%w: Need Role MapfixReview", ErrPermissionDenied)
|
||||
ErrPermissionDeniedNeedRoleSubmissionUpload = fmt.Errorf("%w: Need Role SubmissionUpload", ErrPermissionDenied)
|
||||
ErrPermissionDeniedNeedRoleSubmissionReview = fmt.Errorf("%w: Need Role SubmissionReview", ErrPermissionDenied)
|
||||
ErrPermissionDeniedNeedRoleMapDownload = fmt.Errorf("%w: Need Role MapDownload", ErrPermissionDenied)
|
||||
ErrPermissionDeniedNeedRoleScriptWrite = fmt.Errorf("%w: Need Role ScriptWrite", ErrPermissionDenied)
|
||||
ErrPermissionDeniedNeedRoleMaptest = fmt.Errorf("%w: Need Role Maptest", ErrPermissionDenied)
|
||||
ErrNegativeID = errors.New("A negative ID was provided")
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
inner *service.Service
|
||||
roblox roblox.Client
|
||||
}
|
||||
|
||||
func NewService(
|
||||
inner *service.Service,
|
||||
roblox roblox.Client,
|
||||
) Service {
|
||||
return Service{
|
||||
inner: inner,
|
||||
roblox: roblox,
|
||||
}
|
||||
}
|
||||
|
||||
// NewError creates *ErrorStatusCode from error returned by handler.
|
||||
//
|
||||
// Used for common default response.
|
||||
func (svc *Service) NewError(ctx context.Context, err error) *api.ErrorStatusCode {
|
||||
status := 500
|
||||
if errors.Is(err, datastore.ErrNotExist) {
|
||||
status = 404
|
||||
}
|
||||
if errors.Is(err, ErrPermissionDenied) {
|
||||
status = 403
|
||||
}
|
||||
if errors.Is(err, ErrUserInfo) {
|
||||
status = 401
|
||||
}
|
||||
return &api.ErrorStatusCode{
|
||||
StatusCode: status,
|
||||
Response: api.Error{
|
||||
Code: int64(status),
|
||||
Message: err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
68
pkg/web_api/session.go
Normal file
68
pkg/web_api/session.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package web_api
|
||||
|
||||
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
|
||||
}
|
||||
1178
pkg/web_api/submissions.go
Normal file
1178
pkg/web_api/submissions.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,21 @@
|
||||
[package]
|
||||
name = "maps-validation"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
version = "0.1.1"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
api = { path = "api" }
|
||||
async-nats = "0.38.0"
|
||||
async-nats = "0.42.0"
|
||||
futures = "0.3.31"
|
||||
rbx_asset = { version = "0.2.3", registry = "strafesnet" }
|
||||
rbx_binary = { version = "0.7.4", registry = "strafesnet"}
|
||||
rbx_dom_weak = { version = "2.9.0", registry = "strafesnet"}
|
||||
rbx_reflection_database = { version = "0.2.12", registry = "strafesnet"}
|
||||
rbx_xml = { version = "0.13.3", registry = "strafesnet"}
|
||||
rust-grpc = { version = "1.0.3", registry = "strafesnet" }
|
||||
rbx_asset = { version = "0.4.9", features = ["gzip", "rustls-tls"], default-features = false, registry = "strafesnet" }
|
||||
rbx_binary = "1.0.0"
|
||||
rbx_dom_weak = "3.0.0"
|
||||
rbx_reflection_database = "1.0.3"
|
||||
rbx_xml = "1.0.0"
|
||||
serde = { version = "1.0.215", features = ["derive"] }
|
||||
serde_json = "1.0.133"
|
||||
siphasher = "1.0.1"
|
||||
tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread", "fs"] }
|
||||
tonic = "0.12.3"
|
||||
tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread", "signal"] }
|
||||
heck = "0.5.0"
|
||||
lazy-regex = "3.4.1"
|
||||
rust-grpc = { version = "1.2.1", registry = "strafesnet" }
|
||||
tonic = "0.13.1"
|
||||
|
||||
@@ -1,24 +1,3 @@
|
||||
# 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
|
||||
COPY api ./api
|
||||
# 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 maps-validation
|
||||
|
||||
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/maps-validation /usr/local/bin/
|
||||
USER myuser
|
||||
ENTRYPOINT ["/usr/local/bin/maps-validation"]
|
||||
FROM alpine:3.21 AS runtime
|
||||
COPY /target/x86_64-unknown-linux-musl/release/maps-validation /
|
||||
ENTRYPOINT ["/maps-validation"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "api"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
name = "submissions-api"
|
||||
version = "0.8.2"
|
||||
edition = "2024"
|
||||
publish = ["strafesnet"]
|
||||
repository = "https://git.itzana.me/StrafesNET/maps-service"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@@ -11,7 +11,13 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
reqwest = { version = "0", features = ["json"] }
|
||||
chrono = { version = "0.4.41", features = ["serde"] }
|
||||
reqwest = { version = "0", features = [
|
||||
"json", "rustls-tls",
|
||||
# default features
|
||||
"charset", "http2", "system-proxy"
|
||||
], default-features = false }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_repr = "0.1.19"
|
||||
url = "2"
|
||||
|
||||
46
validation/api/src/context.rs
Normal file
46
validation/api/src/context.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
pub async fn delete(&self,url:impl reqwest::IntoUrl)->Result<reqwest::Response,reqwest::Error>{
|
||||
self.client.delete(url)
|
||||
.send().await
|
||||
}
|
||||
}
|
||||
233
validation/api/src/external.rs
Normal file
233
validation/api/src/external.rs
Normal file
@@ -0,0 +1,233 @@
|
||||
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::ReqwestJson)
|
||||
}
|
||||
pub async fn get_scripts(&self,config:GetScriptsRequest<'_>)->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(resource_type)=config.ResourceType{
|
||||
query_pairs.append_pair("ResourceType",(resource_type as i32).to_string().as_str());
|
||||
}
|
||||
if let Some(ResourceID(resource_id))=config.ResourceID{
|
||||
query_pairs.append_pair("ResourceID",resource_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::ReqwestJson)
|
||||
}
|
||||
pub async fn get_script_from_hash(&self,config:HashRequest<'_>)->Result<Option<ScriptResponse>,ScriptSingleItemError>{
|
||||
let scripts=self.get_scripts(GetScriptsRequest{
|
||||
Page:1,
|
||||
Limit:2,
|
||||
Hash:Some(config.hash),
|
||||
Name:None,
|
||||
Source:None,
|
||||
ResourceType:None,
|
||||
ResourceID:None,
|
||||
}).await.map_err(SingleItemError::Other)?;
|
||||
if 1<scripts.len(){
|
||||
return Err(SingleItemError::DuplicateItems(scripts.into_iter().map(|item|item.ID).collect()));
|
||||
}
|
||||
Ok(scripts.into_iter().next())
|
||||
}
|
||||
pub async fn create_script(&self,config:CreateScriptRequest<'_>)->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::ReqwestJson)
|
||||
}
|
||||
pub async fn delete_script(&self,config:GetScriptRequest)->Result<(),Error>{
|
||||
let url_raw=format!("{}/scripts/{}",self.0.base_url,config.ScriptID.0);
|
||||
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
|
||||
|
||||
response_ok(
|
||||
self.0.delete(url).await.map_err(Error::Reqwest)?
|
||||
).await.map_err(Error::Response)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub async fn get_script_policies(&self,config:GetScriptPoliciesRequest<'_>)->Result<Vec<ScriptPolicyResponse>,Error>{
|
||||
let url_raw=format!("{}/script-policy",self.0.base_url);
|
||||
let 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::ReqwestJson)
|
||||
}
|
||||
pub async fn get_script_policy_from_hash(&self,config:HashRequest<'_>)->Result<Option<ScriptPolicyResponse>,ScriptPolicySingleItemError>{
|
||||
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(policies.into_iter().map(|item|item.ID).collect()));
|
||||
}
|
||||
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::ReqwestJson)
|
||||
}
|
||||
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(())
|
||||
}
|
||||
pub async fn delete_script_policy(&self,config:GetScriptPolicyRequest)->Result<(),Error>{
|
||||
let url_raw=format!("{}/script-policy/{}",self.0.base_url,config.ScriptPolicyID.0);
|
||||
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
|
||||
|
||||
response_ok(
|
||||
self.0.delete(url).await.map_err(Error::Reqwest)?
|
||||
).await.map_err(Error::Response)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub async fn get_submissions(&self,config:GetSubmissionsRequest<'_>)->Result<SubmissionsResponse,Error>{
|
||||
let url_raw=format!("{}/submissions",self.0.base_url);
|
||||
let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
|
||||
|
||||
{
|
||||
let mut query_pairs=url.query_pairs_mut();
|
||||
query_pairs.append_pair("Page",config.Page.to_string().as_str());
|
||||
query_pairs.append_pair("Limit",config.Limit.to_string().as_str());
|
||||
if let Some(sort)=config.Sort{
|
||||
query_pairs.append_pair("Sort",(sort as u8).to_string().as_str());
|
||||
}
|
||||
if let Some(display_name)=config.DisplayName{
|
||||
query_pairs.append_pair("DisplayName",display_name);
|
||||
}
|
||||
if let Some(creator)=config.Creator{
|
||||
query_pairs.append_pair("Creator",creator);
|
||||
}
|
||||
if let Some(game_id)=config.GameID{
|
||||
query_pairs.append_pair("GameID",(game_id as u8).to_string().as_str());
|
||||
}
|
||||
if let Some(submitter)=config.Submitter{
|
||||
query_pairs.append_pair("Submitter",submitter.to_string().as_str());
|
||||
}
|
||||
if let Some(asset_id)=config.AssetID{
|
||||
query_pairs.append_pair("AssetID",asset_id.to_string().as_str());
|
||||
}
|
||||
if let Some(uploaded_asset_id)=config.UploadedAssetID{
|
||||
query_pairs.append_pair("UploadedAssetID",uploaded_asset_id.to_string().as_str());
|
||||
}
|
||||
if let Some(status_id)=config.StatusID{
|
||||
query_pairs.append_pair("StatusID",(status_id as u8).to_string().as_str());
|
||||
}
|
||||
}
|
||||
|
||||
response_ok(
|
||||
self.0.get(url).await.map_err(Error::Reqwest)?
|
||||
).await.map_err(Error::Response)?
|
||||
.json().await.map_err(Error::ReqwestJson)
|
||||
}
|
||||
pub async fn get_maps(&self,config:GetMapsRequest<'_>)->Result<Vec<MapResponse>,Error>{
|
||||
let url_raw=format!("{}/maps",self.0.base_url);
|
||||
let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
|
||||
|
||||
{
|
||||
let mut query_pairs=url.query_pairs_mut();
|
||||
query_pairs.append_pair("Page",config.Page.to_string().as_str());
|
||||
query_pairs.append_pair("Limit",config.Limit.to_string().as_str());
|
||||
if let Some(sort)=config.Sort{
|
||||
query_pairs.append_pair("Sort",(sort as u8).to_string().as_str());
|
||||
}
|
||||
if let Some(display_name)=config.DisplayName{
|
||||
query_pairs.append_pair("DisplayName",display_name);
|
||||
}
|
||||
if let Some(creator)=config.Creator{
|
||||
query_pairs.append_pair("Creator",creator);
|
||||
}
|
||||
if let Some(game_id)=config.GameID{
|
||||
query_pairs.append_pair("GameID",(game_id as u8).to_string().as_str());
|
||||
}
|
||||
}
|
||||
|
||||
response_ok(
|
||||
self.0.get(url).await.map_err(Error::Reqwest)?
|
||||
).await.map_err(Error::Response)?
|
||||
.json().await.map_err(Error::ReqwestJson)
|
||||
}
|
||||
pub async fn release_submissions(&self,config:ReleaseRequest<'_>)->Result<(),Error>{
|
||||
let url_raw=format!("{}/release-submissions",self.0.base_url);
|
||||
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
|
||||
|
||||
let body=serde_json::to_string(config.schedule).map_err(Error::JSON)?;
|
||||
|
||||
response_ok(
|
||||
self.0.post(url,body).await.map_err(Error::Reqwest)?
|
||||
).await.map_err(Error::Response)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,130 +1,10 @@
|
||||
#[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{}
|
||||
mod context;
|
||||
pub use context::Cookie;
|
||||
|
||||
#[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{
|
||||
Allowed=0,
|
||||
Blocked=1,
|
||||
Delete=2,
|
||||
Replace=3,
|
||||
}
|
||||
|
||||
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 mod types;
|
||||
pub mod external;
|
||||
|
||||
//lazy reexports
|
||||
pub use types::Error;
|
||||
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");
|
||||
}
|
||||
pub type CookieError=reqwest::header::InvalidHeaderValue;
|
||||
|
||||
480
validation/api/src/types.rs
Normal file
480
validation/api/src/types.rs
Normal file
@@ -0,0 +1,480 @@
|
||||
#[derive(Debug)]
|
||||
pub enum Error{
|
||||
Parse(url::ParseError),
|
||||
Reqwest(reqwest::Error),
|
||||
ReqwestJson(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<Items>{
|
||||
DuplicateItems(Items),
|
||||
Other(Error),
|
||||
}
|
||||
impl<Items> std::fmt::Display for SingleItemError<Items>
|
||||
where
|
||||
Items:std::fmt::Debug
|
||||
{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl<Items> std::error::Error for SingleItemError<Items> where Items:std::fmt::Debug{}
|
||||
pub type ScriptSingleItemError=SingleItemError<Vec<ScriptID>>;
|
||||
pub type ScriptPolicySingleItemError=SingleItemError<Vec<ScriptPolicyID>>;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub struct UrlAndBody{
|
||||
pub url:url::Url,
|
||||
pub body:String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ResponseError{
|
||||
Reqwest(reqwest::Error),
|
||||
Details{
|
||||
status_code:reqwest::StatusCode,
|
||||
url_and_body:Box<UrlAndBody>,
|
||||
},
|
||||
}
|
||||
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::Details{
|
||||
status_code,
|
||||
url_and_body:Box::new(UrlAndBody{url,body})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq,Ord,PartialOrd,serde_repr::Serialize_repr,serde_repr::Deserialize_repr)]
|
||||
#[repr(u8)]
|
||||
pub enum GameID{
|
||||
Bhop=1,
|
||||
Surf=2,
|
||||
KreedzClimb=3,
|
||||
FlyTrials=5,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug,serde::Serialize)]
|
||||
pub struct CreateMapfixRequest<'a>{
|
||||
pub OperationID:OperationID,
|
||||
pub AssetOwner:i64,
|
||||
pub DisplayName:&'a str,
|
||||
pub Creator:&'a str,
|
||||
pub GameID:GameID,
|
||||
pub AssetID:u64,
|
||||
pub AssetVersion:u64,
|
||||
pub TargetAssetID:u64,
|
||||
pub Description:&'a str,
|
||||
}
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug,serde::Deserialize)]
|
||||
pub struct MapfixIDResponse{
|
||||
pub MapfixID:MapfixID,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug,serde::Serialize)]
|
||||
pub struct CreateSubmissionRequest<'a>{
|
||||
pub OperationID:OperationID,
|
||||
pub AssetOwner:i64,
|
||||
pub DisplayName:&'a str,
|
||||
pub Creator:&'a str,
|
||||
pub GameID:GameID,
|
||||
pub AssetID:u64,
|
||||
pub AssetVersion:u64,
|
||||
pub Status:u32,
|
||||
pub Roles:u32,
|
||||
}
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug,serde::Deserialize)]
|
||||
pub struct SubmissionIDResponse{
|
||||
pub SubmissionID:SubmissionID,
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq,serde::Serialize,serde::Deserialize)]
|
||||
pub struct ScriptID(pub(crate)i64);
|
||||
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq,serde::Serialize,serde::Deserialize)]
|
||||
pub struct ScriptPolicyID(pub(crate)i64);
|
||||
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,serde_repr::Serialize_repr,serde_repr::Deserialize_repr)]
|
||||
#[repr(i32)]
|
||||
pub enum ResourceType{
|
||||
Unknown=0,
|
||||
Mapfix=1,
|
||||
Submission=2,
|
||||
}
|
||||
|
||||
#[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 ResourceType:Option<ResourceType>,
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
pub ResourceID:Option<ResourceID>,
|
||||
}
|
||||
#[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 ResourceType:ResourceType,
|
||||
pub ResourceID:ResourceID,
|
||||
}
|
||||
#[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<ResourceID>,
|
||||
}
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug,serde::Deserialize)]
|
||||
pub struct ScriptIDResponse{
|
||||
pub ScriptID: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)]
|
||||
pub struct GetScriptPolicyRequest{
|
||||
pub ScriptPolicyID:ScriptPolicyID,
|
||||
}
|
||||
#[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 ScriptPolicyID: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:SubmissionID,
|
||||
pub ModelID:u64,
|
||||
pub ModelVersion:u64,
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum Sort{
|
||||
Disabled=0,
|
||||
DisplayNameAscending=1,
|
||||
DisplayNameDescending=2,
|
||||
DateAscending=3,
|
||||
DateDescending=4,
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug,serde_repr::Deserialize_repr)]
|
||||
#[repr(u8)]
|
||||
pub enum SubmissionStatus{
|
||||
// Phase: Creation
|
||||
UnderConstruction=0,
|
||||
ChangesRequested=1,
|
||||
|
||||
// Phase: Review
|
||||
Submitting=2,
|
||||
Submitted=3,
|
||||
|
||||
// Phase: Testing
|
||||
AcceptedUnvalidated=4, // pending script review, can re-trigger validation
|
||||
Validating=5,
|
||||
Validated=6,
|
||||
Uploading=7,
|
||||
Uploaded=8, // uploaded to the group, but pending release
|
||||
|
||||
// Phase: Final SubmissionStatus
|
||||
Rejected=9,
|
||||
Released=10,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct GetSubmissionsRequest<'a>{
|
||||
pub Page:u32,
|
||||
pub Limit:u32,
|
||||
pub Sort:Option<Sort>,
|
||||
pub DisplayName:Option<&'a str>,
|
||||
pub Creator:Option<&'a str>,
|
||||
pub GameID:Option<GameID>,
|
||||
pub Submitter:Option<u64>,
|
||||
pub AssetID:Option<u64>,
|
||||
pub UploadedAssetID:Option<u64>,
|
||||
pub StatusID:Option<SubmissionStatus>,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug,serde::Deserialize)]
|
||||
pub struct SubmissionResponse{
|
||||
pub ID:SubmissionID,
|
||||
pub DisplayName:String,
|
||||
pub Creator:String,
|
||||
pub GameID:GameID,
|
||||
pub CreatedAt:i64,
|
||||
pub UpdatedAt:i64,
|
||||
pub Submitter:u64,
|
||||
pub AssetID:u64,
|
||||
pub AssetVersion:u64,
|
||||
pub UploadedAssetID:u64,
|
||||
pub StatusID:SubmissionStatus,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug,serde::Deserialize)]
|
||||
pub struct SubmissionsResponse{
|
||||
pub Total:u64,
|
||||
pub Submissions:Vec<SubmissionResponse>,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct GetMapsRequest<'a>{
|
||||
pub Page:u32,
|
||||
pub Limit:u32,
|
||||
pub Sort:Option<Sort>,
|
||||
pub DisplayName:Option<&'a str>,
|
||||
pub Creator:Option<&'a str>,
|
||||
pub GameID:Option<GameID>,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug,serde::Deserialize)]
|
||||
pub struct MapResponse{
|
||||
pub ID:i64,
|
||||
pub DisplayName:String,
|
||||
pub Creator:String,
|
||||
pub GameID:GameID,
|
||||
pub Date:i64,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug,serde::Serialize)]
|
||||
pub struct Check{
|
||||
pub Name:&'static str,
|
||||
pub Summary:String,
|
||||
pub Passed:bool,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ActionSubmissionSubmittedRequest{
|
||||
pub SubmissionID:SubmissionID,
|
||||
pub ModelVersion:u64,
|
||||
pub DisplayName:String,
|
||||
pub Creator:String,
|
||||
pub GameID:GameID,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ActionSubmissionRequestChangesRequest{
|
||||
pub SubmissionID:SubmissionID,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ActionSubmissionUploadedRequest{
|
||||
pub SubmissionID:SubmissionID,
|
||||
pub UploadedAssetID:u64,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ActionSubmissionAcceptedRequest{
|
||||
pub SubmissionID:SubmissionID,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct CreateSubmissionAuditErrorRequest{
|
||||
pub SubmissionID:SubmissionID,
|
||||
pub ErrorMessage:String,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct CreateSubmissionAuditCheckListRequest<'a>{
|
||||
pub SubmissionID:SubmissionID,
|
||||
pub CheckList:&'a [Check],
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq,serde::Serialize,serde::Deserialize)]
|
||||
pub struct SubmissionID(pub(crate)i64);
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct UpdateMapfixModelRequest{
|
||||
pub MapfixID:MapfixID,
|
||||
pub ModelID:u64,
|
||||
pub ModelVersion:u64,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ActionMapfixSubmittedRequest{
|
||||
pub MapfixID:MapfixID,
|
||||
pub ModelVersion:u64,
|
||||
pub DisplayName:String,
|
||||
pub Creator:String,
|
||||
pub GameID:GameID,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ActionMapfixRequestChangesRequest{
|
||||
pub MapfixID:MapfixID,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ActionMapfixUploadedRequest{
|
||||
pub MapfixID:MapfixID,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ActionMapfixAcceptedRequest{
|
||||
pub MapfixID:MapfixID,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct CreateMapfixAuditErrorRequest{
|
||||
pub MapfixID:MapfixID,
|
||||
pub ErrorMessage:String,
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct CreateMapfixAuditCheckListRequest<'a>{
|
||||
pub MapfixID:MapfixID,
|
||||
pub CheckList:&'a [Check],
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq,serde::Serialize,serde::Deserialize)]
|
||||
pub struct MapfixID(pub(crate)i64);
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ActionOperationFailedRequest{
|
||||
pub OperationID:OperationID,
|
||||
pub StatusMessage:String,
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq,serde::Serialize,serde::Deserialize)]
|
||||
pub struct OperationID(pub(crate)i64);
|
||||
|
||||
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq,serde::Serialize,serde::Deserialize)]
|
||||
pub struct ResourceID(pub(crate)i64);
|
||||
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub enum Resource{
|
||||
Submission(SubmissionID),
|
||||
Mapfix(MapfixID),
|
||||
}
|
||||
impl Resource{
|
||||
pub fn split(self)->(ResourceType,ResourceID){
|
||||
match self{
|
||||
Resource::Mapfix(MapfixID(mapfix_id))=>(ResourceType::Mapfix,ResourceID(mapfix_id)),
|
||||
Resource::Submission(SubmissionID(submission_id))=>(ResourceType::Submission,ResourceID(submission_id)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
#[derive(Clone,Debug,serde::Serialize)]
|
||||
pub struct ReleaseInfo{
|
||||
pub SubmissionID:SubmissionID,
|
||||
pub Date:chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
pub struct ReleaseRequest<'a>{
|
||||
pub schedule:&'a [ReleaseInfo],
|
||||
}
|
||||
900
validation/src/check.rs
Normal file
900
validation/src/check.rs
Normal file
@@ -0,0 +1,900 @@
|
||||
use std::collections::{HashSet,HashMap};
|
||||
use crate::download::download_asset_version;
|
||||
use crate::rbx_util::{get_mapinfo,get_root_instance,read_dom,ReadDomError,GameID,ParseGameIDError,MapInfo,GetRootInstanceError,StringValueError};
|
||||
|
||||
use heck::{ToSnakeCase,ToTitleCase};
|
||||
use rbx_dom_weak::Instance;
|
||||
use rust_grpc::validator::Check;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error{
|
||||
ModelInfoDownload(rbx_asset::cloud::GetError),
|
||||
CreatorTypeMustBeUser,
|
||||
Download(crate::download::Error),
|
||||
ModelFileDecode(ReadDomError),
|
||||
GetRootInstance(GetRootInstanceError),
|
||||
IntoMapInfoOwned(IntoMapInfoOwnedError),
|
||||
ToJsonValue(serde_json::Error),
|
||||
}
|
||||
impl std::fmt::Display for Error{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for Error{}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
pub struct CheckRequest{
|
||||
ModelID:u64,
|
||||
SkipChecks:bool,
|
||||
}
|
||||
|
||||
impl From<crate::nats_types::CheckMapfixRequest> for CheckRequest{
|
||||
fn from(value:crate::nats_types::CheckMapfixRequest)->Self{
|
||||
Self{
|
||||
ModelID:value.ModelID,
|
||||
SkipChecks:value.SkipChecks,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<crate::nats_types::CheckSubmissionRequest> for CheckRequest{
|
||||
fn from(value:crate::nats_types::CheckSubmissionRequest)->Self{
|
||||
Self{
|
||||
ModelID:value.ModelID,
|
||||
SkipChecks:value.SkipChecks,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
|
||||
struct ModeID(u64);
|
||||
impl ModeID{
|
||||
const MAIN:Self=Self(0);
|
||||
const BONUS:Self=Self(1);
|
||||
}
|
||||
enum Zone{
|
||||
Start,
|
||||
Finish,
|
||||
Anticheat,
|
||||
}
|
||||
struct ModeElement{
|
||||
zone:Zone,
|
||||
mode_id:ModeID,
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub enum IDParseError{
|
||||
NoCaptures,
|
||||
ParseInt(core::num::ParseIntError),
|
||||
}
|
||||
// Parse a Zone from a part name
|
||||
impl std::str::FromStr for ModeElement{
|
||||
type Err=IDParseError;
|
||||
fn from_str(s:&str)->Result<Self,Self::Err>{
|
||||
match s{
|
||||
"MapStart"=>Ok(Self{zone:Zone::Start,mode_id:ModeID::MAIN}),
|
||||
"MapFinish"=>Ok(Self{zone:Zone::Finish,mode_id:ModeID::MAIN}),
|
||||
"MapAnticheat"=>Ok(Self{zone:Zone::Anticheat,mode_id:ModeID::MAIN}),
|
||||
"BonusStart"=>Ok(Self{zone:Zone::Start,mode_id:ModeID::BONUS}),
|
||||
"BonusFinish"=>Ok(Self{zone:Zone::Finish,mode_id:ModeID::BONUS}),
|
||||
"BonusAnticheat"=>Ok(Self{zone:Zone::Anticheat,mode_id:ModeID::BONUS}),
|
||||
other=>{
|
||||
let everything_pattern=lazy_regex::lazy_regex!(r"^Bonus(\d+)Start$|^BonusStart(\d+)$|^Bonus(\d+)Finish$|^BonusFinish(\d+)$|^Bonus(\d+)Anticheat$|^BonusAnticheat(\d+)$");
|
||||
if let Some(captures)=everything_pattern.captures(other){
|
||||
if let Some(mode_id)=captures.get(1).or(captures.get(2)){
|
||||
return Ok(Self{
|
||||
zone:Zone::Start,
|
||||
mode_id:ModeID(mode_id.as_str().parse().map_err(IDParseError::ParseInt)?),
|
||||
});
|
||||
}
|
||||
if let Some(mode_id)=captures.get(3).or(captures.get(4)){
|
||||
return Ok(Self{
|
||||
zone:Zone::Finish,
|
||||
mode_id:ModeID(mode_id.as_str().parse().map_err(IDParseError::ParseInt)?),
|
||||
});
|
||||
}
|
||||
if let Some(mode_id)=captures.get(5).or(captures.get(6)){
|
||||
return Ok(Self{
|
||||
zone:Zone::Anticheat,
|
||||
mode_id:ModeID(mode_id.as_str().parse().map_err(IDParseError::ParseInt)?),
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(IDParseError::NoCaptures)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for ModeElement{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
match self{
|
||||
ModeElement{zone:Zone::Start,mode_id:ModeID::MAIN}=>write!(f,"MapStart"),
|
||||
ModeElement{zone:Zone::Start,mode_id:ModeID::BONUS}=>write!(f,"BonusStart"),
|
||||
ModeElement{zone:Zone::Start,mode_id:ModeID(mode_id)}=>write!(f,"Bonus{mode_id}Start"),
|
||||
ModeElement{zone:Zone::Finish,mode_id:ModeID::MAIN}=>write!(f,"MapFinish"),
|
||||
ModeElement{zone:Zone::Finish,mode_id:ModeID::BONUS}=>write!(f,"BonusFinish"),
|
||||
ModeElement{zone:Zone::Finish,mode_id:ModeID(mode_id)}=>write!(f,"Bonus{mode_id}Finish"),
|
||||
ModeElement{zone:Zone::Anticheat,mode_id:ModeID::MAIN}=>write!(f,"MapAnticheat"),
|
||||
ModeElement{zone:Zone::Anticheat,mode_id:ModeID::BONUS}=>write!(f,"BonusAnticheat"),
|
||||
ModeElement{zone:Zone::Anticheat,mode_id:ModeID(mode_id)}=>write!(f,"Bonus{mode_id}Anticheat"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
|
||||
struct StageID(u64);
|
||||
impl StageID{
|
||||
const FIRST:Self=Self(1);
|
||||
}
|
||||
enum StageElementBehaviour{
|
||||
Teleport,
|
||||
Spawn,
|
||||
}
|
||||
struct StageElement{
|
||||
stage_id:StageID,
|
||||
behaviour:StageElementBehaviour,
|
||||
}
|
||||
// Parse a SpawnTeleport from a part name
|
||||
impl std::str::FromStr for StageElement{
|
||||
type Err=IDParseError;
|
||||
fn from_str(s:&str)->Result<Self,Self::Err>{
|
||||
// Trigger ForceTrigger Teleport ForceTeleport SpawnAt ForceSpawnAt
|
||||
let bonus_start_pattern=lazy_regex::lazy_regex!(r"^(?:Force)?(Teleport|SpawnAt|Trigger)(\d+)$");
|
||||
if let Some(captures)=bonus_start_pattern.captures(s){
|
||||
return Ok(StageElement{
|
||||
behaviour:StageElementBehaviour::Teleport,
|
||||
stage_id:StageID(captures[1].parse().map_err(IDParseError::ParseInt)?),
|
||||
});
|
||||
}
|
||||
// Spawn
|
||||
let bonus_finish_pattern=lazy_regex::lazy_regex!(r"^Spawn(\d+)$");
|
||||
if let Some(captures)=bonus_finish_pattern.captures(s){
|
||||
return Ok(StageElement{
|
||||
behaviour:StageElementBehaviour::Spawn,
|
||||
stage_id:StageID(captures[1].parse().map_err(IDParseError::ParseInt)?),
|
||||
});
|
||||
}
|
||||
Err(IDParseError::NoCaptures)
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for StageElement{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
match self{
|
||||
StageElement{behaviour:StageElementBehaviour::Spawn,stage_id:StageID(stage_id)}=>write!(f,"Spawn{stage_id}"),
|
||||
StageElement{behaviour:StageElementBehaviour::Teleport,stage_id:StageID(stage_id)}=>write!(f,"Teleport{stage_id}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
|
||||
struct WormholeID(u64);
|
||||
enum WormholeBehaviour{
|
||||
In,
|
||||
Out,
|
||||
}
|
||||
struct WormholeElement{
|
||||
behaviour:WormholeBehaviour,
|
||||
wormhole_id:WormholeID,
|
||||
}
|
||||
// Parse a Wormhole from a part name
|
||||
impl std::str::FromStr for WormholeElement{
|
||||
type Err=IDParseError;
|
||||
fn from_str(s:&str)->Result<Self,Self::Err>{
|
||||
let bonus_start_pattern=lazy_regex::lazy_regex!(r"^WormholeIn(\d+)$");
|
||||
if let Some(captures)=bonus_start_pattern.captures(s){
|
||||
return Ok(Self{
|
||||
behaviour:WormholeBehaviour::In,
|
||||
wormhole_id:WormholeID(captures[1].parse().map_err(IDParseError::ParseInt)?),
|
||||
});
|
||||
}
|
||||
let bonus_finish_pattern=lazy_regex::lazy_regex!(r"^WormholeOut(\d+)$");
|
||||
if let Some(captures)=bonus_finish_pattern.captures(s){
|
||||
return Ok(Self{
|
||||
behaviour:WormholeBehaviour::Out,
|
||||
wormhole_id:WormholeID(captures[1].parse().map_err(IDParseError::ParseInt)?),
|
||||
});
|
||||
}
|
||||
Err(IDParseError::NoCaptures)
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for WormholeElement{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
match self{
|
||||
WormholeElement{behaviour:WormholeBehaviour::In,wormhole_id:WormholeID(wormhole_id)}=>write!(f,"WormholeIn{wormhole_id}"),
|
||||
WormholeElement{behaviour:WormholeBehaviour::Out,wormhole_id:WormholeID(wormhole_id)}=>write!(f,"WormholeOut{wormhole_id}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Count various map elements
|
||||
#[derive(Default)]
|
||||
struct Counts<'a>{
|
||||
mode_start_counts:HashMap<ModeID,Vec<&'a Instance>>,
|
||||
mode_finish_counts:HashMap<ModeID,Vec<&'a Instance>>,
|
||||
mode_anticheat_counts:HashMap<ModeID,Vec<&'a Instance>>,
|
||||
teleport_counts:HashMap<StageID,Vec<&'a Instance>>,
|
||||
spawn_counts:HashMap<StageID,u64>,
|
||||
wormhole_in_counts:HashMap<WormholeID,u64>,
|
||||
wormhole_out_counts:HashMap<WormholeID,u64>,
|
||||
}
|
||||
|
||||
pub struct ModelInfo<'a>{
|
||||
model_class:&'a str,
|
||||
model_name:&'a str,
|
||||
map_info:MapInfo<'a>,
|
||||
counts:Counts<'a>,
|
||||
unanchored_parts:Vec<&'a Instance>,
|
||||
}
|
||||
|
||||
pub fn get_model_info<'a>(dom:&'a rbx_dom_weak::WeakDom,model_instance:&'a rbx_dom_weak::Instance)->ModelInfo<'a>{
|
||||
// extract model info
|
||||
let map_info=get_mapinfo(dom,model_instance);
|
||||
|
||||
// count objects (default count is 0)
|
||||
let mut counts=Counts::default();
|
||||
|
||||
// locate unanchored parts
|
||||
let mut unanchored_parts=Vec::new();
|
||||
let anchored_ustr=rbx_dom_weak::ustr("Anchored");
|
||||
|
||||
let db=rbx_reflection_database::get();
|
||||
let base_part=&db.classes["BasePart"];
|
||||
let base_parts=dom.descendants_of(model_instance.referent()).filter(|&instance|
|
||||
db.classes.get(instance.class.as_str()).is_some_and(|class|
|
||||
db.has_superclass(class,base_part)
|
||||
)
|
||||
);
|
||||
for instance in base_parts{
|
||||
// Zones
|
||||
match instance.name.parse(){
|
||||
Ok(ModeElement{zone:Zone::Start,mode_id})=>counts.mode_start_counts.entry(mode_id).or_default().push(instance),
|
||||
Ok(ModeElement{zone:Zone::Finish,mode_id})=>counts.mode_finish_counts.entry(mode_id).or_default().push(instance),
|
||||
Ok(ModeElement{zone:Zone::Anticheat,mode_id})=>counts.mode_anticheat_counts.entry(mode_id).or_default().push(instance),
|
||||
Err(_)=>(),
|
||||
}
|
||||
// Spawns & Teleports
|
||||
match instance.name.parse(){
|
||||
Ok(StageElement{behaviour:StageElementBehaviour::Teleport,stage_id})=>counts.teleport_counts.entry(stage_id).or_default().push(instance),
|
||||
Ok(StageElement{behaviour:StageElementBehaviour::Spawn,stage_id})=>*counts.spawn_counts.entry(stage_id).or_insert(0)+=1,
|
||||
Err(_)=>(),
|
||||
}
|
||||
// Wormholes
|
||||
match instance.name.parse(){
|
||||
Ok(WormholeElement{behaviour:WormholeBehaviour::In,wormhole_id})=>*counts.wormhole_in_counts.entry(wormhole_id).or_insert(0)+=1,
|
||||
Ok(WormholeElement{behaviour:WormholeBehaviour::Out,wormhole_id})=>*counts.wormhole_out_counts.entry(wormhole_id).or_insert(0)+=1,
|
||||
Err(_)=>(),
|
||||
}
|
||||
// Unanchored parts
|
||||
if let Some(rbx_dom_weak::types::Variant::Bool(false))=instance.properties.get(&anchored_ustr){
|
||||
unanchored_parts.push(instance);
|
||||
}
|
||||
}
|
||||
|
||||
ModelInfo{
|
||||
model_class:model_instance.class.as_str(),
|
||||
model_name:model_instance.name.as_str(),
|
||||
map_info,
|
||||
counts,
|
||||
unanchored_parts,
|
||||
}
|
||||
}
|
||||
|
||||
// check if an observed string matches an expected string
|
||||
pub struct StringCheck<'a,T,Str>(Result<T,StringCheckContext<'a,Str>>);
|
||||
pub struct StringCheckContext<'a,Str>{
|
||||
observed:&'a str,
|
||||
expected:Str,
|
||||
}
|
||||
impl<'a,Str> StringCheckContext<'a,Str>
|
||||
where
|
||||
&'a str:PartialEq<Str>,
|
||||
{
|
||||
/// Compute the StringCheck, passing through the provided value on success.
|
||||
fn check<T>(self,value:T)->StringCheck<'a,T,Str>{
|
||||
if self.observed==self.expected{
|
||||
StringCheck(Ok(value))
|
||||
}else{
|
||||
StringCheck(Err(self))
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Str:std::fmt::Display> std::fmt::Display for StringCheckContext<'_,Str>{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"expected: {}, observed: {}",self.expected,self.observed)
|
||||
}
|
||||
}
|
||||
|
||||
// check if a string is empty
|
||||
pub struct StringEmpty;
|
||||
impl std::fmt::Display for StringEmpty{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"Empty string")
|
||||
}
|
||||
}
|
||||
fn check_empty(value:&str)->Result<&str,StringEmpty>{
|
||||
(!value.is_empty()).then_some(value).ok_or(StringEmpty)
|
||||
}
|
||||
|
||||
// check for duplicate objects
|
||||
pub struct DuplicateCheckContext<ID,T>(HashMap<ID,T>);
|
||||
pub struct DuplicateCheck<ID,T>(Result<(),DuplicateCheckContext<ID,T>>);
|
||||
impl<ID,T> DuplicateCheckContext<ID,T>{
|
||||
/// Compute the DuplicateCheck using the contents predicate.
|
||||
fn check(self,f:impl Fn(&T)->bool)->DuplicateCheck<ID,T>{
|
||||
let Self(mut set)=self;
|
||||
// remove correct entries
|
||||
set.retain(|_,c|f(c));
|
||||
// if any entries remain, they are incorrect
|
||||
if set.is_empty(){
|
||||
DuplicateCheck(Ok(()))
|
||||
}else{
|
||||
DuplicateCheck(Err(Self(set)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that there are no items which do not have a matching item in a reference set
|
||||
pub struct SetDifferenceCheckContextAllowNone<ID,T>{
|
||||
extra:HashMap<ID,T>,
|
||||
}
|
||||
// Check that there is at least one matching item for each item in a reference set, and no extra items
|
||||
pub struct SetDifferenceCheckContextAtLeastOne<ID,T>{
|
||||
extra:HashMap<ID,T>,
|
||||
missing:HashSet<ID>,
|
||||
}
|
||||
pub struct SetDifferenceCheck<Context>(Result<(),Context>);
|
||||
impl<ID,T> SetDifferenceCheckContextAllowNone<ID,T>{
|
||||
fn new(initial_set:HashMap<ID,T>)->Self{
|
||||
Self{
|
||||
extra:initial_set,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<ID:Eq+std::hash::Hash,T> SetDifferenceCheckContextAllowNone<ID,T>{
|
||||
/// Compute the SetDifferenceCheck result for the specified reference set.
|
||||
fn check<U>(mut self,reference_set:&HashMap<ID,U>)->SetDifferenceCheck<Self>{
|
||||
// remove correct entries
|
||||
for id in reference_set.keys(){
|
||||
self.extra.remove(id);
|
||||
}
|
||||
// if any entries remain, they are incorrect
|
||||
if self.extra.is_empty(){
|
||||
SetDifferenceCheck(Ok(()))
|
||||
}else{
|
||||
SetDifferenceCheck(Err(self))
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<ID,T> SetDifferenceCheckContextAtLeastOne<ID,T>{
|
||||
fn new(initial_set:HashMap<ID,T>)->Self{
|
||||
Self{
|
||||
extra:initial_set,
|
||||
missing:HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<ID:Copy+Eq+std::hash::Hash,T> SetDifferenceCheckContextAtLeastOne<ID,T>{
|
||||
/// Compute the SetDifferenceCheck result for the specified reference set.
|
||||
fn check<U>(mut self,reference_set:&HashMap<ID,U>)->SetDifferenceCheck<Self>{
|
||||
// remove correct entries
|
||||
for id in reference_set.keys(){
|
||||
if self.extra.remove(id).is_none(){
|
||||
// the set did not contain a required item. This is a fail
|
||||
self.missing.insert(*id);
|
||||
}
|
||||
}
|
||||
// if any entries remain, they are incorrect
|
||||
if self.extra.is_empty()&&self.missing.is_empty(){
|
||||
SetDifferenceCheck(Ok(()))
|
||||
}else{
|
||||
SetDifferenceCheck(Err(self))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Info lifted out of a fully compliant map
|
||||
pub struct MapInfoOwned{
|
||||
pub display_name:String,
|
||||
pub creator:String,
|
||||
pub game_id:GameID,
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum IntoMapInfoOwnedError{
|
||||
DisplayName(StringValueError),
|
||||
Creator(StringValueError),
|
||||
GameID(ParseGameIDError),
|
||||
}
|
||||
impl TryFrom<MapInfo<'_>> for MapInfoOwned{
|
||||
type Error=IntoMapInfoOwnedError;
|
||||
fn try_from(value:MapInfo<'_>)->Result<Self,Self::Error>{
|
||||
Ok(Self{
|
||||
display_name:value.display_name.map_err(IntoMapInfoOwnedError::DisplayName)?.to_owned(),
|
||||
creator:value.creator.map_err(IntoMapInfoOwnedError::Creator)?.to_owned(),
|
||||
game_id:value.game_id.map_err(IntoMapInfoOwnedError::GameID)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Named dummy types for readability
|
||||
struct Exists;
|
||||
struct Absent;
|
||||
|
||||
/// The result of every map check.
|
||||
struct MapCheck<'a>{
|
||||
// === METADATA CHECKS ===
|
||||
// The root must be of class Model
|
||||
model_class:StringCheck<'a,(),&'static str>,
|
||||
// Model's name must be in snake case
|
||||
model_name:StringCheck<'a,(),String>,
|
||||
// Map must have a StringValue named DisplayName.
|
||||
// Value must not be empty, must be in title case.
|
||||
display_name:Result<Result<StringCheck<'a,&'a str,String>,StringEmpty>,StringValueError>,
|
||||
// Map must have a StringValue named Creator.
|
||||
// Value must not be empty.
|
||||
creator:Result<Result<&'a str,StringEmpty>,StringValueError>,
|
||||
// The prefix of the model's name must match the game it was submitted for.
|
||||
// bhop_ for bhop, and surf_ for surf
|
||||
game_id:Result<GameID,ParseGameIDError>,
|
||||
|
||||
// === MODE CHECKS ===
|
||||
// MapStart must exist
|
||||
mapstart:Result<Exists,Absent>,
|
||||
// No duplicate map starts (including bonuses)
|
||||
mode_start_counts:DuplicateCheck<ModeID,Vec<&'a Instance>>,
|
||||
// At least one finish zone for each start zone, and no finishes with no start
|
||||
mode_finish_counts:SetDifferenceCheck<SetDifferenceCheckContextAtLeastOne<ModeID,Vec<&'a Instance>>>,
|
||||
// Check for dangling MapAnticheat zones (no associated MapStart)
|
||||
mode_anticheat_counts:SetDifferenceCheck<SetDifferenceCheckContextAllowNone<ModeID,Vec<&'a Instance>>>,
|
||||
// Spawn1 must exist
|
||||
spawn1:Result<Exists,Absent>,
|
||||
// Check for dangling Teleport# (no associated Spawn#)
|
||||
teleport_counts:SetDifferenceCheck<SetDifferenceCheckContextAllowNone<StageID,Vec<&'a Instance>>>,
|
||||
// No duplicate Spawn#
|
||||
spawn_counts:DuplicateCheck<StageID,u64>,
|
||||
// Check for dangling WormholeIn# (no associated WormholeOut#)
|
||||
wormhole_in_counts:SetDifferenceCheck<SetDifferenceCheckContextAtLeastOne<WormholeID,u64>>,
|
||||
// No duplicate WormholeOut# (duplicate WormholeIn# ok)
|
||||
// No dangling WormholeOut#
|
||||
wormhole_out_counts:DuplicateCheck<WormholeID,u64>,
|
||||
|
||||
// === GENERAL CHECKS ===
|
||||
unanchored_parts:Result<(),Vec<&'a Instance>>,
|
||||
}
|
||||
|
||||
impl<'a> ModelInfo<'a>{
|
||||
fn check(self)->MapCheck<'a>{
|
||||
// Check class is exactly "Model"
|
||||
let model_class=StringCheckContext{
|
||||
observed:self.model_class,
|
||||
expected:"Model",
|
||||
}.check(());
|
||||
|
||||
// Check model name is snake case
|
||||
let model_name=StringCheckContext{
|
||||
observed:self.model_name,
|
||||
expected:self.model_name.to_snake_case(),
|
||||
}.check(());
|
||||
|
||||
// Check display name is not empty and has title case
|
||||
let display_name=self.map_info.display_name.map(|display_name|{
|
||||
check_empty(display_name).map(|display_name|StringCheckContext{
|
||||
observed:display_name,
|
||||
expected:display_name.to_title_case(),
|
||||
}.check(display_name))
|
||||
});
|
||||
|
||||
// Check Creator is not empty
|
||||
let creator=self.map_info.creator.map(check_empty);
|
||||
|
||||
// Check GameID (model name was prefixed with bhop_ surf_ etc)
|
||||
let game_id=self.map_info.game_id;
|
||||
|
||||
// MapStart must exist
|
||||
let mapstart=if self.counts.mode_start_counts.contains_key(&ModeID::MAIN){
|
||||
Ok(Exists)
|
||||
}else{
|
||||
Err(Absent)
|
||||
};
|
||||
|
||||
// Spawn1 must exist
|
||||
let spawn1=if self.counts.spawn_counts.contains_key(&StageID::FIRST){
|
||||
Ok(Exists)
|
||||
}else{
|
||||
Err(Absent)
|
||||
};
|
||||
|
||||
// Check that at least one finish zone exists for each start zone.
|
||||
// This also checks that there are no finish zones without a corresponding start zone.
|
||||
let mode_finish_counts=SetDifferenceCheckContextAtLeastOne::new(self.counts.mode_finish_counts)
|
||||
.check(&self.counts.mode_start_counts);
|
||||
|
||||
// Check that there are no anticheat zones without a corresponding start zone.
|
||||
// Modes are allowed to have 0 anticheat zones.
|
||||
let mode_anticheat_counts=SetDifferenceCheckContextAllowNone::new(self.counts.mode_anticheat_counts)
|
||||
.check(&self.counts.mode_start_counts);
|
||||
|
||||
// There must be exactly one start zone for every mode in the map.
|
||||
let mode_start_counts=DuplicateCheckContext(self.counts.mode_start_counts).check(|c|1<c.len());
|
||||
|
||||
// Check that there are no Teleports without a corresponding Spawn.
|
||||
// Spawns are allowed to have 0 Teleports.
|
||||
let teleport_counts=SetDifferenceCheckContextAllowNone::new(self.counts.teleport_counts)
|
||||
.check(&self.counts.spawn_counts);
|
||||
|
||||
// There must be exactly one of any perticular spawn id in the map.
|
||||
let spawn_counts=DuplicateCheckContext(self.counts.spawn_counts).check(|&c|1<c);
|
||||
|
||||
// Check that at least one WormholeIn exists for each WormholeOut.
|
||||
// This also checks that there are no WormholeIn without a corresponding WormholeOut.
|
||||
let wormhole_in_counts=SetDifferenceCheckContextAtLeastOne::new(self.counts.wormhole_in_counts)
|
||||
.check(&self.counts.wormhole_out_counts);
|
||||
|
||||
// There must be exactly one of any perticular wormhole out id in the map.
|
||||
let wormhole_out_counts=DuplicateCheckContext(self.counts.wormhole_out_counts).check(|&c|1<c);
|
||||
|
||||
// There must not be any unanchored parts
|
||||
let unanchored_parts=if self.unanchored_parts.is_empty(){
|
||||
Ok(())
|
||||
}else{
|
||||
Err(self.unanchored_parts)
|
||||
};
|
||||
|
||||
MapCheck{
|
||||
model_class,
|
||||
model_name,
|
||||
display_name,
|
||||
creator,
|
||||
game_id,
|
||||
mapstart,
|
||||
mode_start_counts,
|
||||
mode_finish_counts,
|
||||
mode_anticheat_counts,
|
||||
spawn1,
|
||||
teleport_counts,
|
||||
spawn_counts,
|
||||
wormhole_in_counts,
|
||||
wormhole_out_counts,
|
||||
unanchored_parts,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MapCheck<'_>{
|
||||
fn result(self)->Result<MapInfoOwned,Result<MapCheckList,serde_json::Error>>{
|
||||
match self{
|
||||
MapCheck{
|
||||
model_class:StringCheck(Ok(())),
|
||||
model_name:StringCheck(Ok(())),
|
||||
display_name:Ok(Ok(StringCheck(Ok(display_name)))),
|
||||
creator:Ok(Ok(creator)),
|
||||
game_id:Ok(game_id),
|
||||
mapstart:Ok(Exists),
|
||||
mode_start_counts:DuplicateCheck(Ok(())),
|
||||
mode_finish_counts:SetDifferenceCheck(Ok(())),
|
||||
mode_anticheat_counts:SetDifferenceCheck(Ok(())),
|
||||
spawn1:Ok(Exists),
|
||||
teleport_counts:SetDifferenceCheck(Ok(())),
|
||||
spawn_counts:DuplicateCheck(Ok(())),
|
||||
wormhole_in_counts:SetDifferenceCheck(Ok(())),
|
||||
wormhole_out_counts:DuplicateCheck(Ok(())),
|
||||
unanchored_parts:Ok(()),
|
||||
}=>{
|
||||
Ok(MapInfoOwned{
|
||||
display_name:display_name.to_owned(),
|
||||
creator:creator.to_owned(),
|
||||
game_id,
|
||||
})
|
||||
},
|
||||
other=>Err(other.itemize()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Separated<F>{
|
||||
f:F,
|
||||
separator:&'static str,
|
||||
}
|
||||
impl<F> Separated<F>{
|
||||
fn new(separator:&'static str,f:F)->Self{
|
||||
Self{separator,f}
|
||||
}
|
||||
}
|
||||
impl<F,I,D> std::fmt::Display for Separated<F>
|
||||
where
|
||||
D:std::fmt::Display,
|
||||
I:IntoIterator<Item=D>,
|
||||
F:Fn()->I,
|
||||
{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
let mut it=(self.f)().into_iter();
|
||||
if let Some(first)=it.next(){
|
||||
write!(f,"{first}")?;
|
||||
for item in it{
|
||||
write!(f,"{}{item}",self.separator)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct Duplicates<D>{
|
||||
display:D,
|
||||
duplicates:usize,
|
||||
}
|
||||
impl<D> Duplicates<D>{
|
||||
fn new(display:D,duplicates:usize)->Self{
|
||||
Self{
|
||||
display,
|
||||
duplicates,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<D:std::fmt::Display> std::fmt::Display for Duplicates<D>{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{} ({} duplicates)",self.display,self.duplicates)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
macro_rules! passed{
|
||||
($name:literal)=>{
|
||||
Check{
|
||||
name:$name.to_owned(),
|
||||
summary:String::new(),
|
||||
passed:true,
|
||||
}
|
||||
}
|
||||
}
|
||||
macro_rules! summary{
|
||||
($name:literal,$summary:expr)=>{
|
||||
Check{
|
||||
name:$name.to_owned(),
|
||||
summary:$summary,
|
||||
passed:false,
|
||||
}
|
||||
};
|
||||
}
|
||||
macro_rules! summary_format{
|
||||
($name:literal,$fmt:literal)=>{
|
||||
Check{
|
||||
name:$name.to_owned(),
|
||||
summary:format!($fmt),
|
||||
passed:false,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Generate an error message for each observed issue separated by newlines.
|
||||
// This defines MapCheck.to_string() which is used in MapCheck.result()
|
||||
impl MapCheck<'_>{
|
||||
fn itemize(&self)->Result<MapCheckList,serde_json::Error>{
|
||||
let model_class=match &self.model_class{
|
||||
StringCheck(Ok(()))=>passed!("ModelClass"),
|
||||
StringCheck(Err(context))=>summary_format!("ModelClass","Invalid model class: {context}"),
|
||||
};
|
||||
let model_name=match &self.model_name{
|
||||
StringCheck(Ok(()))=>passed!("ModelName"),
|
||||
StringCheck(Err(context))=>summary_format!("ModelName","Model name must have snake_case: {context}"),
|
||||
};
|
||||
let display_name=match &self.display_name{
|
||||
Ok(Ok(StringCheck(Ok(_))))=>passed!("DisplayName"),
|
||||
Ok(Ok(StringCheck(Err(context))))=>summary_format!("DisplayName","DisplayName must have Title Case: {context}"),
|
||||
Ok(Err(context))=>summary_format!("DisplayName","Invalid DisplayName: {context}"),
|
||||
Err(StringValueError::ObjectNotFound)=>summary!("DisplayName","Missing DisplayName StringValue".to_owned()),
|
||||
Err(StringValueError::ValueNotSet)=>summary!("DisplayName","DisplayName Value not set".to_owned()),
|
||||
Err(StringValueError::NonStringValue)=>summary!("DisplayName","DisplayName Value is not a String".to_owned()),
|
||||
};
|
||||
let creator=match &self.creator{
|
||||
Ok(Ok(_))=>passed!("Creator"),
|
||||
Ok(Err(context))=>summary_format!("Creator","Invalid Creator: {context}"),
|
||||
Err(StringValueError::ObjectNotFound)=>summary!("Creator","Missing Creator StringValue".to_owned()),
|
||||
Err(StringValueError::ValueNotSet)=>summary!("Creator","Creator Value not set".to_owned()),
|
||||
Err(StringValueError::NonStringValue)=>summary!("Creator","Creator Value is not a String".to_owned()),
|
||||
};
|
||||
let game_id=match &self.game_id{
|
||||
Ok(_)=>passed!("GameID"),
|
||||
Err(ParseGameIDError)=>summary!("GameID","Model name must be prefixed with bhop_ surf_ or flytrials_".to_owned()),
|
||||
};
|
||||
let mapstart=match &self.mapstart{
|
||||
Ok(Exists)=>passed!("MapStart"),
|
||||
Err(Absent)=>summary_format!("MapStart","Model has no MapStart"),
|
||||
};
|
||||
let duplicate_start=match &self.mode_start_counts{
|
||||
DuplicateCheck(Ok(()))=>passed!("DuplicateStart"),
|
||||
DuplicateCheck(Err(DuplicateCheckContext(context)))=>{
|
||||
let context=Separated::new(", ",||context.iter().map(|(&mode_id,instances)|
|
||||
Duplicates::new(ModeElement{zone:Zone::Start,mode_id},instances.len())
|
||||
));
|
||||
summary_format!("DuplicateStart","Duplicate start zones: {context}")
|
||||
}
|
||||
};
|
||||
let (extra_finish,missing_finish)=match &self.mode_finish_counts{
|
||||
SetDifferenceCheck(Ok(()))=>(passed!("DanglingFinish"),passed!("MissingFinish")),
|
||||
SetDifferenceCheck(Err(context))=>(
|
||||
if context.extra.is_empty(){
|
||||
passed!("DanglingFinish")
|
||||
}else{
|
||||
let plural=if context.extra.len()==1{"zone"}else{"zones"};
|
||||
let context=Separated::new(", ",||context.extra.iter().map(|(&mode_id,_instances)|
|
||||
ModeElement{zone:Zone::Finish,mode_id}
|
||||
));
|
||||
summary_format!("DanglingFinish","No matching start zone for finish {plural}: {context}")
|
||||
},
|
||||
if context.missing.is_empty(){
|
||||
passed!("MissingFinish")
|
||||
}else{
|
||||
let plural=if context.missing.len()==1{"zone"}else{"zones"};
|
||||
let context=Separated::new(", ",||context.missing.iter().map(|&mode_id|
|
||||
ModeElement{zone:Zone::Finish,mode_id}
|
||||
));
|
||||
summary_format!("MissingFinish","Missing finish {plural}: {context}")
|
||||
}
|
||||
),
|
||||
};
|
||||
let dangling_anticheat=match &self.mode_anticheat_counts{
|
||||
SetDifferenceCheck(Ok(()))=>passed!("DanglingAnticheat"),
|
||||
SetDifferenceCheck(Err(context))=>{
|
||||
if context.extra.is_empty(){
|
||||
passed!("DanglingAnticheat")
|
||||
}else{
|
||||
let plural=if context.extra.len()==1{"zone"}else{"zones"};
|
||||
let context=Separated::new(", ",||context.extra.iter().map(|(&mode_id,_instances)|
|
||||
ModeElement{zone:Zone::Anticheat,mode_id}
|
||||
));
|
||||
summary_format!("DanglingAnticheat","No matching start zone for anticheat {plural}: {context}")
|
||||
}
|
||||
}
|
||||
};
|
||||
let spawn1=match &self.spawn1{
|
||||
Ok(Exists)=>passed!("Spawn1"),
|
||||
Err(Absent)=>summary_format!("Spawn1","Model has no Spawn1"),
|
||||
};
|
||||
let dangling_teleport=match &self.teleport_counts{
|
||||
SetDifferenceCheck(Ok(()))=>passed!("DanglingTeleport"),
|
||||
SetDifferenceCheck(Err(context))=>{
|
||||
let unique_names:HashSet<_>=context.extra.values().flat_map(|instances|
|
||||
instances.iter().map(|instance|instance.name.as_str())
|
||||
).collect();
|
||||
let plural=if unique_names.len()==1{"object"}else{"objects"};
|
||||
let context=Separated::new(", ",||&unique_names);
|
||||
summary_format!("DanglingTeleport","No matching Spawn for {plural}: {context}")
|
||||
}
|
||||
};
|
||||
let duplicate_spawns=match &self.spawn_counts{
|
||||
DuplicateCheck(Ok(()))=>passed!("DuplicateSpawn"),
|
||||
DuplicateCheck(Err(DuplicateCheckContext(context)))=>{
|
||||
let context=Separated::new(", ",||context.iter().map(|(&stage_id,&instances)|
|
||||
Duplicates::new(StageElement{behaviour:StageElementBehaviour::Spawn,stage_id},instances as usize)
|
||||
));
|
||||
summary_format!("DuplicateSpawn","Duplicate Spawn: {context}")
|
||||
}
|
||||
};
|
||||
let (extra_wormhole_in,missing_wormhole_in)=match &self.wormhole_in_counts{
|
||||
SetDifferenceCheck(Ok(()))=>(passed!("ExtraWormholeIn"),passed!("MissingWormholeIn")),
|
||||
SetDifferenceCheck(Err(context))=>(
|
||||
if context.extra.is_empty(){
|
||||
passed!("ExtraWormholeIn")
|
||||
}else{
|
||||
let context=Separated::new(", ",||context.extra.iter().map(|(&wormhole_id,_instances)|
|
||||
WormholeElement{behaviour:WormholeBehaviour::In,wormhole_id}
|
||||
));
|
||||
summary_format!("ExtraWormholeIn","WormholeIn with no matching WormholeOut: {context}")
|
||||
},
|
||||
if context.missing.is_empty(){
|
||||
passed!("MissingWormholeIn")
|
||||
}else{
|
||||
// This counts WormholeIn objects, but
|
||||
// flipped logic is easier to understand
|
||||
let context=Separated::new(", ",||context.missing.iter().map(|&wormhole_id|
|
||||
WormholeElement{behaviour:WormholeBehaviour::Out,wormhole_id}
|
||||
));
|
||||
summary_format!("MissingWormholeIn","WormholeOut with no matching WormholeIn: {context}")
|
||||
}
|
||||
)
|
||||
};
|
||||
let duplicate_wormhole_out=match &self.wormhole_out_counts{
|
||||
DuplicateCheck(Ok(()))=>passed!("DuplicateWormholeOut"),
|
||||
DuplicateCheck(Err(DuplicateCheckContext(context)))=>{
|
||||
let context=Separated::new(", ",||context.iter().map(|(&wormhole_id,&instances)|
|
||||
Duplicates::new(WormholeElement{behaviour:WormholeBehaviour::Out,wormhole_id},instances as usize)
|
||||
));
|
||||
summary_format!("DuplicateWormholeOut","Duplicate WormholeOut: {context}")
|
||||
}
|
||||
};
|
||||
let unanchored_parts=match &self.unanchored_parts{
|
||||
Ok(())=>passed!("UnanchoredParts"),
|
||||
Err(unanchored_parts)=>{
|
||||
let count=unanchored_parts.len();
|
||||
let plural=if count==1{"part"}else{"parts"};
|
||||
let context=Separated::new(", ",||unanchored_parts.iter().map(|&instance|
|
||||
instance.name.as_str()
|
||||
).take(20));
|
||||
summary_format!("UnanchoredParts","{count} unanchored {plural}: {context}")
|
||||
}
|
||||
};
|
||||
Ok(MapCheckList{checks:vec![
|
||||
model_class,
|
||||
model_name,
|
||||
display_name,
|
||||
creator,
|
||||
game_id,
|
||||
mapstart,
|
||||
duplicate_start,
|
||||
extra_finish,
|
||||
missing_finish,
|
||||
dangling_anticheat,
|
||||
spawn1,
|
||||
dangling_teleport,
|
||||
duplicate_spawns,
|
||||
extra_wormhole_in,
|
||||
missing_wormhole_in,
|
||||
duplicate_wormhole_out,
|
||||
unanchored_parts,
|
||||
]})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct MapCheckList{
|
||||
pub checks:Vec<Check>,
|
||||
}
|
||||
|
||||
pub struct CheckListAndVersion{
|
||||
pub status:Result<MapInfoOwned,MapCheckList>,
|
||||
pub version:u64,
|
||||
}
|
||||
|
||||
impl crate::message_handler::MessageHandler{
|
||||
pub async fn check_inner(&self,check_info:CheckRequest)->Result<CheckListAndVersion,Error>{
|
||||
// discover asset creator and latest version
|
||||
let info=self.cloud_context.get_asset_info(
|
||||
rbx_asset::cloud::GetAssetLatestRequest{asset_id:check_info.ModelID}
|
||||
).await.map_err(Error::ModelInfoDownload)?;
|
||||
|
||||
// reject models created by a group
|
||||
let rbx_asset::cloud::Creator::userId(_user_id)=info.creationContext.creator else{
|
||||
return Err(Error::CreatorTypeMustBeUser);
|
||||
};
|
||||
|
||||
// parse model version string
|
||||
let version=info.revisionId;
|
||||
|
||||
let maybe_gzip=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{
|
||||
asset_id:check_info.ModelID,
|
||||
version,
|
||||
}).await.map_err(Error::Download)?;
|
||||
|
||||
// decode dom (slow!)
|
||||
let dom=maybe_gzip.read_with(read_dom,read_dom).map_err(Error::ModelFileDecode)?;
|
||||
|
||||
// extract the root instance
|
||||
let model_instance=get_root_instance(&dom).map_err(Error::GetRootInstance)?;
|
||||
|
||||
// skip checks
|
||||
if check_info.SkipChecks{
|
||||
// extract required fields
|
||||
let map_info=get_mapinfo(&dom,model_instance);
|
||||
let map_info_owned=map_info.try_into().map_err(Error::IntoMapInfoOwned)?;
|
||||
let status=Ok(map_info_owned);
|
||||
|
||||
// return early
|
||||
return Ok(CheckListAndVersion{status,version});
|
||||
}
|
||||
|
||||
// extract information from the model
|
||||
let model_info=get_model_info(&dom,model_instance);
|
||||
|
||||
// convert the model information into a structured report
|
||||
let map_check=model_info.check();
|
||||
|
||||
// check the report, generate an error message if it fails the check
|
||||
let status=match map_check.result(){
|
||||
Ok(map_info)=>Ok(map_info),
|
||||
Err(Ok(check_list))=>Err(check_list),
|
||||
Err(Err(e))=>return Err(Error::ToJsonValue(e)),
|
||||
};
|
||||
|
||||
Ok(CheckListAndVersion{status,version})
|
||||
}
|
||||
}
|
||||
69
validation/src/check_mapfix.rs
Normal file
69
validation/src/check_mapfix.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use crate::check::CheckListAndVersion;
|
||||
use crate::nats_types::CheckMapfixRequest;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error{
|
||||
Check(crate::check::Error),
|
||||
ApiActionMapfixCheck(tonic::Status),
|
||||
}
|
||||
impl std::fmt::Display for Error{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for Error{}
|
||||
|
||||
impl crate::message_handler::MessageHandler{
|
||||
pub async fn check_mapfix(&self,check_info:CheckMapfixRequest)->Result<(),Error>{
|
||||
let mapfix_id=check_info.MapfixID;
|
||||
let check_result=self.check_inner(check_info.into()).await;
|
||||
|
||||
// update the mapfix depending on the result
|
||||
match check_result{
|
||||
Ok(CheckListAndVersion{status:Ok(map_info),version})=>{
|
||||
self.mapfixes.set_status_submitted(
|
||||
rust_grpc::validator::SubmittedRequest{
|
||||
id:mapfix_id,
|
||||
model_version:version,
|
||||
display_name:map_info.display_name,
|
||||
creator:map_info.creator,
|
||||
game_id:map_info.game_id as u32,
|
||||
}
|
||||
).await.map_err(Error::ApiActionMapfixCheck)?;
|
||||
|
||||
// Do not proceed to request changes
|
||||
return Ok(());
|
||||
},
|
||||
// update the mapfix model status to request changes
|
||||
Ok(CheckListAndVersion{status:Err(check_list),..})=>{
|
||||
self.mapfixes.create_audit_checklist(
|
||||
rust_grpc::validator::AuditChecklistRequest{
|
||||
id:mapfix_id,
|
||||
check_list:check_list.checks,
|
||||
}
|
||||
).await.map_err(Error::ApiActionMapfixCheck)?;
|
||||
},
|
||||
// update the mapfix model status to request changes
|
||||
Err(e)=>{
|
||||
// log error
|
||||
println!("[check_mapfix] Error: {e}");
|
||||
|
||||
self.mapfixes.create_audit_error(
|
||||
rust_grpc::validator::AuditErrorRequest{
|
||||
id:mapfix_id,
|
||||
error_message:e.to_string(),
|
||||
}
|
||||
).await.map_err(Error::ApiActionMapfixCheck)?;
|
||||
},
|
||||
}
|
||||
|
||||
self.mapfixes.set_status_request_changes(
|
||||
rust_grpc::validator::MapfixId{
|
||||
id:mapfix_id,
|
||||
}
|
||||
).await.map_err(Error::ApiActionMapfixCheck)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
70
validation/src/check_submission.rs
Normal file
70
validation/src/check_submission.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use crate::check::CheckListAndVersion;
|
||||
use crate::nats_types::CheckSubmissionRequest;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error{
|
||||
Check(crate::check::Error),
|
||||
ApiActionSubmissionCheck(tonic::Status),
|
||||
}
|
||||
impl std::fmt::Display for Error{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for Error{}
|
||||
|
||||
impl crate::message_handler::MessageHandler{
|
||||
pub async fn check_submission(&self,check_info:CheckSubmissionRequest)->Result<(),Error>{
|
||||
let submission_id=check_info.SubmissionID;
|
||||
let check_result=self.check_inner(check_info.into()).await;
|
||||
|
||||
// update the submission depending on the result
|
||||
match check_result{
|
||||
// update the submission model status to submitted
|
||||
Ok(CheckListAndVersion{status:Ok(map_info),version})=>{
|
||||
self.submissions.set_status_submitted(
|
||||
rust_grpc::validator::SubmittedRequest{
|
||||
id:submission_id,
|
||||
model_version:version,
|
||||
display_name:map_info.display_name,
|
||||
creator:map_info.creator,
|
||||
game_id:map_info.game_id as u32,
|
||||
}
|
||||
).await.map_err(Error::ApiActionSubmissionCheck)?;
|
||||
|
||||
// Do not proceed to request changes
|
||||
return Ok(());
|
||||
},
|
||||
// update the submission model status to request changes
|
||||
Ok(CheckListAndVersion{status:Err(check_list),..})=>{
|
||||
self.submissions.create_audit_checklist(
|
||||
rust_grpc::validator::AuditChecklistRequest{
|
||||
id:submission_id,
|
||||
check_list:check_list.checks,
|
||||
}
|
||||
).await.map_err(Error::ApiActionSubmissionCheck)?;
|
||||
},
|
||||
// update the submission model status to request changes
|
||||
Err(e)=>{
|
||||
// log error
|
||||
println!("[check_submission] Error: {e}");
|
||||
|
||||
self.submissions.create_audit_error(
|
||||
rust_grpc::validator::AuditErrorRequest{
|
||||
id:submission_id,
|
||||
error_message:e.to_string(),
|
||||
}
|
||||
).await.map_err(Error::ApiActionSubmissionCheck)?;
|
||||
},
|
||||
}
|
||||
|
||||
self.submissions.set_status_request_changes(
|
||||
rust_grpc::validator::SubmissionId{
|
||||
id:submission_id,
|
||||
}
|
||||
).await.map_err(Error::ApiActionSubmissionCheck)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
73
validation/src/create.rs
Normal file
73
validation/src/create.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use crate::download::download_asset_version;
|
||||
use crate::rbx_util::{get_root_instance,get_mapinfo,read_dom,MapInfo,ReadDomError,GetRootInstanceError,GameID};
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error{
|
||||
CreatorTypeMustBeUser,
|
||||
ModelInfoDownload(rbx_asset::cloud::GetError),
|
||||
Download(crate::download::Error),
|
||||
ModelFileDecode(ReadDomError),
|
||||
GetRootInstance(GetRootInstanceError),
|
||||
}
|
||||
impl std::fmt::Display for Error{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for Error{}
|
||||
|
||||
#[allow(nonstandard_style)]
|
||||
pub struct CreateRequest{
|
||||
pub ModelID:u64,
|
||||
}
|
||||
#[allow(nonstandard_style)]
|
||||
pub struct CreateResult{
|
||||
pub AssetOwner:u64,
|
||||
pub DisplayName:Option<String>,
|
||||
pub Creator:Option<String>,
|
||||
pub GameID:Option<GameID>,
|
||||
pub AssetVersion:u64,
|
||||
}
|
||||
impl crate::message_handler::MessageHandler{
|
||||
pub async fn create_inner(&self,create_info:CreateRequest)->Result<CreateResult,Error>{
|
||||
// discover asset creator and latest version
|
||||
let info=self.cloud_context.get_asset_info(
|
||||
rbx_asset::cloud::GetAssetLatestRequest{asset_id:create_info.ModelID}
|
||||
).await.map_err(Error::ModelInfoDownload)?;
|
||||
|
||||
// reject models created by a group
|
||||
let rbx_asset::cloud::Creator::userId(user_id)=info.creationContext.creator else{
|
||||
return Err(Error::CreatorTypeMustBeUser);
|
||||
};
|
||||
|
||||
let asset_version=info.revisionId;
|
||||
|
||||
// download the map model
|
||||
let maybe_gzip=download_asset_version(&self.cloud_context,rbx_asset::cloud::GetAssetVersionRequest{
|
||||
asset_id:create_info.ModelID,
|
||||
version:asset_version,
|
||||
}).await.map_err(Error::Download)?;
|
||||
|
||||
// decode dom (slow!)
|
||||
let dom=maybe_gzip.read_with(read_dom,read_dom).map_err(Error::ModelFileDecode)?;
|
||||
|
||||
// extract the root instance
|
||||
let model_instance=get_root_instance(&dom).map_err(Error::GetRootInstance)?;
|
||||
|
||||
// parse create fields out of asset
|
||||
let MapInfo{
|
||||
display_name,
|
||||
creator,
|
||||
game_id,
|
||||
}=get_mapinfo(&dom,model_instance);
|
||||
|
||||
Ok(CreateResult{
|
||||
AssetOwner:user_id,
|
||||
DisplayName:display_name.ok().map(ToOwned::to_owned),
|
||||
Creator:creator.ok().map(ToOwned::to_owned),
|
||||
GameID:game_id.ok(),
|
||||
AssetVersion:asset_version,
|
||||
})
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user