forked from sh-edraft.de/sh_discord_bot
Compare commits
1359 Commits
Author | SHA1 | Date | |
---|---|---|---|
9bed89ed60 | |||
35fdeb168f | |||
e785bddf2e | |||
b361a7b685 | |||
bdd0da38ab | |||
179c8a954b | |||
248d47c280 | |||
414720f53b | |||
bb0ff1c560 | |||
521a063fca | |||
dc5ae365bb | |||
3c21b68b0c | |||
df316fd53b | |||
0a18f26a51 | |||
995c498e54 | |||
9353fa749c | |||
dd4b9182f3 | |||
1fc8d441ad | |||
219fffc344 | |||
fbdac4f57a | |||
dfee6b0568 | |||
9eb09fa3b2 | |||
60d81ce18b | |||
a24fefd3e2 | |||
8cd5a0d040 | |||
1b8a67e81b | |||
8318dfe988 | |||
7185b087c9 | |||
07fc512633 | |||
ca5db6d397 | |||
7888783b6a | |||
31c62b4108 | |||
b0990b626c | |||
df9c889c7e | |||
4d386759c0 | |||
3683f5f3d1 | |||
77f759a9ca | |||
f209e45905 | |||
a917803eff | |||
acfa359eeb | |||
baf7b8d7de | |||
ee69ae58d1 | |||
bc528f1474 | |||
d67ba924dc | |||
e054c04c96 | |||
c846614274 | |||
83764b3cee | |||
4553490266 | |||
35aa2574f8 | |||
ccae285b81 | |||
887a02a7af | |||
f484c553b3 | |||
c5eca69db1 | |||
d1a04c537c | |||
72715aac62 | |||
e516def7ef | |||
f1f5954dfc | |||
266dacb301 | |||
280cd9827d | |||
36998470e8 | |||
53c6bf4208 | |||
4e80e3ccb7 | |||
ba173a6743 | |||
84ff2a8a9b | |||
13bc38fea8 | |||
dfdf0555d7 | |||
39299eb11b | |||
dfb6751bf9 | |||
4a75635a55 | |||
f6b2739394 | |||
36d2605655 | |||
af891fa588 | |||
af348b85a4 | |||
7ed621a25b | |||
013f953a7f | |||
4163532696 | |||
b6a1172cc1 | |||
20bdb6bd98 | |||
3f13f65932 | |||
711e184895 | |||
7e3975f4be | |||
d4f4f39522 | |||
af9c55e4b1 | |||
e3172d6f24 | |||
0706579f62 | |||
ad3fc94923 | |||
69882af0bd | |||
560c7650bd | |||
b0fb12f841 | |||
18386339b7 | |||
4d980331e2 | |||
e6667c78c8 | |||
9976c59302 | |||
293c8cd81c | |||
8f4ea56184 | |||
898e005978 | |||
db3e6183ba | |||
0a47393510 | |||
6c5b514b41 | |||
84f994fd58 | |||
3d21123786 | |||
36887f1bdf | |||
0e4419312b | |||
94732b7227 | |||
4233e089f8 | |||
db61a764eb | |||
919eef79f6 | |||
0aa690b984 | |||
b6b9bfabf5 | |||
d927ab8fb7 | |||
a2dd447dbd | |||
8a76b46165 | |||
af3084ad36 | |||
285b8bdbe4 | |||
e2da4f09ee | |||
4ed99da689 | |||
bc94d31a8d | |||
0d3db75190 | |||
090f217f93 | |||
b98828fce3 | |||
5f8ae787f0 | |||
0c807a7de7 | |||
2dc60acaa6 | |||
29ea96a5e5 | |||
026331b397 | |||
bfe74ad1c5 | |||
7c8c2bef70 | |||
4ccb57e6a3 | |||
c8d3bf780d | |||
8788b727c5 | |||
5e9280d972 | |||
bd856d0143 | |||
da57063b68 | |||
171aa63df9 | |||
74dba4b981 | |||
a3ebd07093 | |||
2de5afd648 | |||
e8cc42e155 | |||
20e20969e4 | |||
802d5478d1 | |||
05f718f3ae | |||
7682b966a8 | |||
7cb4f03554 | |||
b1b74b2551 | |||
bbad4100dc | |||
25df0e4876 | |||
4ba40b826a | |||
06a0eba5c5 | |||
fe5b0207c0 | |||
e01c738cf0 | |||
d2d59bdad7 | |||
dd3bfa68c6 | |||
4e12ba5ffe | |||
35a8b8f592 | |||
5c8feed8aa | |||
e018fdcbdf | |||
39b9def76c | |||
2801c617f6 | |||
5461a6d8dc | |||
d93c3ad6c7 | |||
aec7dac4c7 | |||
407ec08463 | |||
4628f31993 | |||
1f2fbc362f | |||
666b20d3a9 | |||
5de6710261 | |||
e99e272029 | |||
650f612a6b | |||
38093ab817 | |||
d2c37a0098 | |||
804aa0b9b8 | |||
692cf8de31 | |||
b15c3b7fa3 | |||
49121fd179 | |||
9bad75e7c2 | |||
7358b67072 | |||
1b0ba01258 | |||
6fc5ee3ed3 | |||
099707446d | |||
d6b7fd73df | |||
6bfb0ddbf9 | |||
e1b76fa628 | |||
e6f98def6a | |||
590479eee2 | |||
d25305be4a | |||
2889a97f17 | |||
b540821a32 | |||
5b43b72838 | |||
46cd33d194 | |||
1287829bdf | |||
c91c8b2bcd | |||
a5b4c4cd66 | |||
1688347367 | |||
6e79811bc9 | |||
e1258151de | |||
a7c833b9db | |||
2f161d2f43 | |||
0a5f23f1af | |||
23a3dbbc5e | |||
8975665407 | |||
90de90684c | |||
d1ecfe9603 | |||
f7f25e9428 | |||
6ae7ab5c5a | |||
315c90cacc | |||
2caa910613 | |||
8e55ebc4b7 | |||
665e524aa9 | |||
ae021ac7ea | |||
dcbb481b10 | |||
e6165caed9 | |||
58fdc9f48f | |||
1c02608410 | |||
5e0b322273 | |||
4f336bed05 | |||
99d476df86 | |||
81062e74d7 | |||
acae8ac891 | |||
e3dc0f725e | |||
40a7919a3f | |||
14d0e38c5d | |||
62649226ba | |||
0a04ad74d8 | |||
467050d87e | |||
e8f2a01851 | |||
f972055ebf | |||
9bbfe498cc | |||
b21c9f894f | |||
7a0f29f557 | |||
ed72620d33 | |||
b8356917b3 | |||
84fedfaa0b | |||
2d358188af | |||
79278d8f4b | |||
b943b9a7ed | |||
1c20bbed66 | |||
33006e3d01 | |||
90ae55c0d4 | |||
626d0891f9 | |||
09cccd1b89 | |||
940a2ed065 | |||
c2fa1a1c00 | |||
1f1b1f7b65 | |||
4b52995143 | |||
6a6d19686c | |||
798162fb62 | |||
74c71abea5 | |||
0e679eda32 | |||
403c169b71 | |||
a113e21e19 | |||
71c091c226 | |||
b8d27cc682 | |||
c9b967b9d0 | |||
410049be6e | |||
be6361f619 | |||
3a7daf8b42 | |||
f435d3dd48 | |||
eb32bec43c | |||
232429a77d | |||
0946734633 | |||
ea1472a9eb | |||
f710f6ad6c | |||
030fedfcfb | |||
bd1d230f67 | |||
dcafa63d74 | |||
3a42b42dbf | |||
4747f23202 | |||
5057cbed16 | |||
7aff767a4b | |||
61bdc8a52a | |||
5afd0fafa8 | |||
eeda0405f3 | |||
0e2b7d03fc | |||
c7f5ab0161 | |||
01e8e4256d | |||
75adc2285e | |||
9e12d84ba0 | |||
d3b503d3ef | |||
dd86c3a657 | |||
aba6e48e2b | |||
73848c3141 | |||
33d6015088 | |||
7e962e05f6 | |||
c73c6876b2 | |||
8e949c3e1a | |||
472a76d563 | |||
f5d88ec94c | |||
2182c021b9 | |||
a9c9880fd4 | |||
d91c76467d | |||
2f10ace27f | |||
3b79a61bb6 | |||
ef0fab1178 | |||
189b6370a9 | |||
0ed93c56d0 | |||
4d18dd3845 | |||
7b7cbb20db | |||
84c2f5c2c4 | |||
c85c6df784 | |||
d5d898fa07 | |||
ef5ebabf81 | |||
f89b4c4ef5 | |||
8b277a2d19 | |||
a84e77e055 | |||
892c983e1e | |||
1d3809c986 | |||
3117f617d9 | |||
7e6053768f | |||
3d9cd0a2fc | |||
8d90768594 | |||
7e8a9482d4 | |||
bf7d29e6ab | |||
608001e0e1 | |||
0dd9558f33 | |||
7acd850e68 | |||
ecd3ea96b1 | |||
d869bcfd3a | |||
818e021761 | |||
b286322247 | |||
661b057e85 | |||
9be4e344f6 | |||
ebc782e266 | |||
887a46fa7f | |||
0cc8d6f2c7 | |||
3546d38f75 | |||
376cb76036 | |||
8e8da46a54 | |||
94e003312d | |||
fe42d46a38 | |||
4c0a4bc1ae | |||
4161d3a38a | |||
4a763e4e03 | |||
9783424066 | |||
290b5f38a7 | |||
5bfd04722c | |||
12f956f4c3 | |||
0b767fcb68 | |||
a303108da2 | |||
5e9cca5b1d | |||
d1c79c95b2 | |||
378d2c3dc9 | |||
a8ea9f5e49 | |||
957a54ccf3 | |||
0037a30c11 | |||
77e079d91c | |||
25137c6923 | |||
0a0401dd87 | |||
74f3ee2f08 | |||
69fc75fc97 | |||
5a85232374 | |||
5892b209d3 | |||
6715ecacd6 | |||
d72715d51b | |||
a7f9fa5818 | |||
c45916aaee | |||
8bbd57e82f | |||
987a1a664b | |||
99bfa9874a | |||
82058bab0e | |||
a566fca01f | |||
e52f5e4186 | |||
c90ede69dd | |||
85ba012b6f | |||
4cc094576d | |||
74ddc238be | |||
b53ddb1351 | |||
ca5a6c81b8 | |||
7b7bbaea50 | |||
d4b40eb52e | |||
ed524c2f64 | |||
8e3c8459f8 | |||
e661ee1489 | |||
9eaeb2df42 | |||
481c0f881a | |||
a065c703eb | |||
f72ea68f66 | |||
9226a1188e | |||
c06cb02cd6 | |||
20747001b0 | |||
b4be6b4ac2 | |||
bd3dd39dbc | |||
9e0905d49e | |||
3c0719f50e | |||
77d3668a06 | |||
e914c7d02b | |||
b40f521212 | |||
d40ca88cd5 | |||
1e63baed97 | |||
cdfd151851 | |||
12fbc972d9 | |||
dca939f025 | |||
2e0c1babe4 | |||
ed9b893c12 | |||
731f80bd5f | |||
85852bf2f0 | |||
2c3ef97a14 | |||
9ec65689ba | |||
44f5de32a5 | |||
c016d5fc84 | |||
306ce2664c | |||
3ce5471ef4 | |||
4cdd6c05bf | |||
d364ea1b4c | |||
6fa08ea828 | |||
296c6e8044 | |||
0541f3dfde | |||
052e0f976a | |||
f41dfc9be2 | |||
74437fdc20 | |||
df2b660b75 | |||
12f231aa41 | |||
2f3ae229c9 | |||
5bbed854a4 | |||
2321c12bc9 | |||
1cdfd1291f | |||
223946ad76 | |||
4ea1b5b94c | |||
919970d199 | |||
19ed164845 | |||
92001a45be | |||
78b761a672 | |||
7bfa39f459 | |||
d586cca672 | |||
efce172c01 | |||
0974325148 | |||
206754919f | |||
b49512e28d | |||
fa71759a20 | |||
9338e35504 | |||
2956dcb9c2 | |||
762bebf416 | |||
f3b5cef253 | |||
6869c5a671 | |||
352b44be0f | |||
5912708d3a | |||
45a3127696 | |||
10c20621a8 | |||
49e0e44461 | |||
ec6b080ba6 | |||
28944755ac | |||
7318239b2d | |||
cf66d246a9 | |||
41fe6faa52 | |||
685c96bb38 | |||
2c3e5268e4 | |||
2d5ce58612 | |||
fe3d67eff5 | |||
4cea31fce5 | |||
aa2c80ec12 | |||
e283a18def | |||
f5b2bec356 | |||
c2b32996fd | |||
195566a2b6 | |||
073c318671 | |||
caeec47b7c | |||
44f6b36347 | |||
4e32414f48 | |||
573c9c999e | |||
825fcff82d | |||
a95bf7bc6c | |||
0bc3bff58e | |||
c3b86fab6b | |||
906602134e | |||
30791f7529 | |||
456d939b47 | |||
1f47636e8d | |||
f4acc2669d | |||
46b5757fd1 | |||
1cd75cd78f | |||
f7f3fea7a7 | |||
5dd2000f10 | |||
1b2bb85b37 | |||
113b188a40 | |||
e549341196 | |||
8a0d939147 | |||
4b57d7f102 | |||
02e0c72a80 | |||
bfc9979961 | |||
8d76f79732 | |||
4add293186 | |||
1fc5ef76a6 | |||
e6c9959381 | |||
9db00516c3 | |||
44e225c273 | |||
99e75ba325 | |||
3a078271ff | |||
1f9991eeda | |||
bfe72668dc | |||
51d95c81c1 | |||
6c39cd1ae1 | |||
5d36f1188a | |||
4c42949516 | |||
8922524f44 | |||
eac367c611 | |||
f02acd7f94 | |||
05ddfb3de3 | |||
5c2c89ca45 | |||
ec1ce4adef | |||
71f1f972c9 | |||
23757bc841 | |||
9671090385 | |||
a03ddf3fc2 | |||
da3538a836 | |||
c782c11b6d | |||
05a2ea9b18 | |||
a482c72a56 | |||
22bdf13835 | |||
0a3affc5d0 | |||
d642322985 | |||
9f63a9c6dd | |||
11a4874bfb | |||
d4dd55944a | |||
23d6216029 | |||
f7297ddf78 | |||
eb3715d00b | |||
aae6472e11 | |||
a71e3e4720 | |||
189128f0d3 | |||
3fb951a748 | |||
2293849d94 | |||
cd5b3b6523 | |||
c8a2ed290b | |||
184d241695 | |||
cab65477b0 | |||
3507623c92 | |||
b99dd1bded | |||
a0d38bec49 | |||
f312a2d776 | |||
bfb816dd17 | |||
d6854d44b7 | |||
fdbba1b89c | |||
7f197a0ea7 | |||
2c9569b75f | |||
607b7d8aee | |||
00db6ac10f | |||
f034413e35 | |||
ff16eae477 | |||
dbd82930d4 | |||
2e9cf0bd97 | |||
8a2edc7228 | |||
6d14ba9d79 | |||
ba5f83f3d0 | |||
9f614e8a31 | |||
6e0d4a5144 | |||
bd8bd40863 | |||
c7a1069c0c | |||
dc0c6ca6a0 | |||
ff009ffb61 | |||
074b03eecf | |||
9201cbf357 | |||
5ea698ef97 | |||
400e54a501 | |||
3c0233e8b3 | |||
8d2ae38d85 | |||
2b866b5ab1 | |||
4da87ae3cb | |||
1ebad89c97 | |||
b8320c83fe | |||
0ee26ccf3d | |||
31ca9cd8f4 | |||
089de53136 | |||
280b22af55 | |||
ebdf375283 | |||
c058312af7 | |||
2befa921ea | |||
1792359e68 | |||
faaf14d63e | |||
49d1ec75c8 | |||
629556b5fc | |||
9ece541e52 | |||
fd51db8cac | |||
2005d93f44 | |||
010dafc655 | |||
bea9dd1590 | |||
ef0da25f11 | |||
c01a6b97fe | |||
059bd3aaf1 | |||
a9d180fdf1 | |||
c71b1092f2 | |||
c1e8274f46 | |||
583218b215 | |||
e05e67785b | |||
72d3ccded3 | |||
f30bd119df | |||
858fc062b3 | |||
6d622a70d3 | |||
4a0a6a4631 | |||
17c2acd77c | |||
27363522d3 | |||
1960b18058 | |||
91034cee09 | |||
a4d3cffa93 | |||
809c6d4cb9 | |||
2fecbf2c31 | |||
c439d5925f | |||
1777a32899 | |||
3e5301b2d8 | |||
653562b908 | |||
8025c31339 | |||
ccf41bec79 | |||
f75d8f2068 | |||
da02aca7e4 | |||
3762f16c7e | |||
b3099121ab | |||
6a750ee31b | |||
3c371ec074 | |||
d818eafb9d | |||
b4a5fdb258 | |||
cf69436168 | |||
ccf171e876 | |||
5a312bf660 | |||
0003255d7c | |||
27a4c140e4 | |||
3183461195 | |||
cb4f8d870c | |||
7c447bb0f2 | |||
4debe4e2ba | |||
3cd3df3e5f | |||
b4a631d4e4 | |||
7ba0247519 | |||
209e75fdcc | |||
3bca3f86eb | |||
657a8fa586 | |||
3c8a092f40 | |||
61bf508cea | |||
75ad07477a | |||
132d41f0a2 | |||
707a835672 | |||
f56dbf8e2a | |||
c311e534d7 | |||
87b277515c | |||
e6c614dfdc | |||
24d5bbf4d8 | |||
52035af0cc | |||
fdc9a118c8 | |||
ba1f4ee955 | |||
35d161c080 | |||
818163cbed | |||
468ebb5f79 | |||
085726bf60 | |||
223abbe66f | |||
4c5a8baed5 | |||
59162408e5 | |||
634b81d23b | |||
c0cd5bb70e | |||
cc0a0a5c69 | |||
4420c0e11c | |||
7be40ed236 | |||
ff415c354d | |||
283eaabef6 | |||
b7e72888f7 | |||
b29227e8d5 | |||
9f57182fc1 | |||
699377be54 | |||
efba1a4ce8 | |||
68fa1b8c2d | |||
8b79d69e41 | |||
c1b75dff78 | |||
5f284597aa | |||
e1b1a68b07 | |||
b1a0115e8b | |||
ad00dce5d9 | |||
e3c47ce5b1 | |||
68026e4b47 | |||
ee503f76f2 | |||
a1f7b8b2dc | |||
b8e4146b33 | |||
51f0ee5744 | |||
69ce659328 | |||
325a17b5a8 | |||
5df0501505 | |||
c3ef7a746f | |||
bca33c6e56 | |||
d24d3fa4de | |||
1755efb5d9 | |||
54b0086a14 | |||
f404287cc1 | |||
7966ca16e5 | |||
c0e5f0d4b0 | |||
80769a1bf0 | |||
bc92dc4536 | |||
16ef29999c | |||
33279b7053 | |||
04fcfddbd9 | |||
21f0f32322 | |||
d1420d18c0 | |||
22d26bf032 | |||
a701807831 | |||
3324cf3ac3 | |||
1664c67763 | |||
bddcd3929a | |||
23e17c1f38 | |||
883811d156 | |||
4d0e8898fe | |||
505c87b996 | |||
18fe6ecb86 | |||
f02268c1a1 | |||
1b328dc20e | |||
34c362a391 | |||
15dfc3f47c | |||
91b2cf7546 | |||
a8dad6b223 | |||
124209c371 | |||
d24c8e40de | |||
54004d10a5 | |||
a510cf731f | |||
23b7e4f59c | |||
24f90657e1 | |||
454fabb3e3 | |||
a588f8bf72 | |||
d75503569e | |||
5b265488df | |||
2d995544c3 | |||
e5fb9fd504 | |||
a80bbc33cf | |||
6915515932 | |||
42b71525bd | |||
2674af64e9 | |||
1eb625fe7a | |||
19f47a01e5 | |||
8ff21debf0 | |||
89bcb655d2 | |||
b9d33c5fd0 | |||
9ea1b14852 | |||
2763f254af | |||
f7dc8d135a | |||
d42b76ba87 | |||
0f26db3d74 | |||
d9d3f8c8b0 | |||
23ee963d65 | |||
f3024d2ea5 | |||
|
e463b19a73 | ||
15214b1c99 | |||
|
dfcd958965 | ||
9274be6bb7 | |||
ac1affa937 | |||
4e722d9c47 | |||
6585576266 | |||
8937243725 | |||
c841f39a03 | |||
a3fa7cb7b9 | |||
f38b517b98 | |||
7d450a12ac | |||
0ea1edff4d | |||
70775111c5 | |||
f42f2086e7 | |||
1f3b12501b | |||
c0882a8ad3 | |||
75ab159539 | |||
ec9bc80392 | |||
02d04725bd | |||
10d79f8c2c | |||
a2dcbbc465 | |||
81fe7433bd | |||
ed5564dc7c | |||
e0ca057399 | |||
8f95e196ec | |||
16b2deec6b | |||
85e664e642 | |||
64985f5983 | |||
|
02ed6eb75b | ||
|
b615f2608f | ||
|
bf776177d4 | ||
|
a2a0056873 | ||
|
eb18fdea46 | ||
|
6945cd2eac | ||
ef040f2b91 | |||
ba4ef693da | |||
07f2c4921c | |||
96339ed168 | |||
9667f40730 | |||
5a9ad77761 | |||
e4d1e229eb | |||
299fc65d1f | |||
aa38623633 | |||
fd9d938b8a | |||
efbe6efc20 | |||
da3680a83e | |||
610ce42fa2 | |||
6149825101 | |||
7193e58ba1 | |||
38417bd712 | |||
43b6df2ba3 | |||
7d67b08ce6 | |||
afff27b273 | |||
de8262dae1 | |||
b96288f4a3 | |||
1ca6debc59 | |||
dfe4f28e24 | |||
fea259fa3b | |||
85452c9a74 | |||
f44f77d7e5 | |||
b15bcec20a | |||
ee351ee749 | |||
f847841582 | |||
e2ef4f3bde | |||
3456c5f021 | |||
065e8b0645 | |||
f04a6066a1 | |||
c0d1288aef | |||
c4507d3aa1 | |||
1039a58d4f | |||
ed4d41d424 | |||
645e84a8fa | |||
b9087dccb3 | |||
4bb486f1d5 | |||
8efd0fc993 | |||
6c0b48a941 | |||
77946cb3a8 | |||
5953c82a9b | |||
6bf73fcf91 | |||
3844240930 | |||
efa18944fe | |||
f435d779a7 | |||
cd34d18205 | |||
9e2a0441d3 | |||
898e27550e | |||
60990db88d | |||
ce1cdd4017 | |||
9a8f8a13f6 | |||
667bb708d9 | |||
54cb6cf8a0 | |||
aa16847fbf | |||
2ef4d079ed | |||
bd4e088032 | |||
1e74584e71 | |||
0b5acdea58 | |||
91bbb2f2e9 | |||
dd64435c65 | |||
84937dde0a | |||
ca5744ea39 | |||
f68bd875b5 | |||
429f8b2819 | |||
7fb8b521e4 | |||
936b8e9cb6 | |||
1fa6f44f76 | |||
f04d40ce75 | |||
45a6b49cf5 | |||
4822348e01 | |||
1d1fc8a876 | |||
a6df06f13a | |||
7f14aff1bd | |||
d03ea1d970 | |||
f144564806 | |||
f7c27b77ee | |||
6aca981fd1 | |||
4266db7e35 | |||
667b9fa87e | |||
79837afdfb | |||
8273b2b98e | |||
eaa22efd08 | |||
b33c5dc9bf | |||
e9559dec08 | |||
5d470be583 | |||
1dfe7edcba | |||
af8c2dea60 | |||
23e238b7d5 | |||
fde318b85d | |||
93c60b9176 | |||
12f8f669ed | |||
36fd3c73b9 | |||
3f7cfc47af | |||
eb58c34c4d | |||
e3c0a0dea3 | |||
dbfae8d8a4 | |||
e1c89814da | |||
53cdaf3fa0 | |||
b11ce18ac9 | |||
16066864ed | |||
807827e30f | |||
c75cc54d16 | |||
6a1ad1ec9f | |||
8256ebed71 | |||
eb7fce140a | |||
ebcf876457 | |||
5efb1da0b8 | |||
b9e66bba9d | |||
b8484185e9 | |||
552e686aeb | |||
efb772094b | |||
95b9eea236 | |||
b95a951a1b | |||
1b5f87f869 | |||
ed83826234 | |||
96b54fdbc9 | |||
9658a5c238 | |||
6dd9b9c302 | |||
29ee85bd93 | |||
e969926769 | |||
d596d7465e | |||
ea266cc5be | |||
e2b2fb5abb | |||
37ec0cf0c7 | |||
460bb42028 | |||
0379c3ed9b | |||
fecb82bf36 | |||
2401e58827 | |||
5d3ceff3bf | |||
e481958bad | |||
0c76269e40 | |||
17b336623d | |||
00b4e2ab66 | |||
1b15e54199 | |||
c9aeb684d6 | |||
3811cf7d74 | |||
2a0e3d77b7 | |||
59fc1e1442 | |||
84f8690bdf | |||
d91ff392ca | |||
1ff70af72b | |||
a216506a37 | |||
3d01c9f798 | |||
624625d4b5 | |||
f9593b5f44 | |||
34ebb48c83 | |||
849a92125a | |||
5d6c55fc86 | |||
3cf0fe3479 | |||
b80958e3ab | |||
b89fa12ec6 | |||
706b6732eb | |||
053c190c78 | |||
c443d108dc | |||
09fbc27981 | |||
242ffd1550 | |||
fb4be18ef2 | |||
89ee16d1b5 | |||
8b7efed257 | |||
d10c33c6c2 | |||
3abaee3e71 | |||
4c577f6a7b | |||
f50a763539 | |||
9ff9cc672e | |||
9d4cf5a769 | |||
df1d62f569 | |||
4201510e9f | |||
394428a81a | |||
4733f84ec6 | |||
df42acec26 | |||
44204f5b94 | |||
ce85bb332a | |||
7a836a7f59 | |||
dacb429d9b | |||
77e18027a0 | |||
b13695b018 | |||
75500076a7 | |||
5455a6b359 | |||
9c466733fb | |||
bd62618fdf | |||
a78d5f0fcb | |||
661630bb37 | |||
15f041a2da | |||
1ac5d982ed | |||
1337ef35dd | |||
986d7c4562 | |||
7fcb4084d2 | |||
fbac0d3d02 | |||
9626de2b27 | |||
1d74f5e67c | |||
6949db10f8 | |||
ba5d897662 | |||
47415af868 | |||
6353c7ca86 | |||
440689653d | |||
844a818aa0 | |||
33fb973f21 | |||
7646335d03 | |||
04c905d287 | |||
c7d8508173 | |||
f3eff97780 | |||
476db0ed33 | |||
9c369b911a | |||
6dfd476bce | |||
f669410b2a | |||
a46122243f | |||
3b7345b404 | |||
59d38f8f2a | |||
612430d3e0 | |||
e01290db9b | |||
6273ce9cba | |||
5c923d8bd8 | |||
6c6169f7ee | |||
ffd5105154 | |||
9040ab6fca | |||
9d89135b4c | |||
71899346b2 | |||
d197a6e158 | |||
40e53de3f2 | |||
1b9553e63b | |||
5447d502cc | |||
5a4c2901f5 | |||
bcf71a26f0 | |||
b25b75e382 | |||
f21b4f9881 | |||
8705904882 | |||
cf610b770b | |||
ec30069ff5 | |||
7026b3abac | |||
31464df3f6 | |||
eb9f5b83d5 | |||
fdd8357729 | |||
7c744f0e65 | |||
4a0f5c28c1 | |||
d2f99003ff | |||
9dd3fd4b8e | |||
d18500b96c | |||
91fdf34d32 | |||
12369cdbe3 | |||
25c698273a | |||
2868b1afe2 | |||
0d1c15b31d | |||
840da350e4 | |||
bd94c42eae | |||
c7a925b997 | |||
7fb6d22c3f | |||
c5b5297058 | |||
9ed66c2560 | |||
6e6157ccf2 | |||
f136d6164e | |||
9b5033b80e | |||
f5a71a8450 | |||
d3279eb7c7 | |||
ec7aeb8712 | |||
fd609eb923 | |||
a7dbc75d2e | |||
b0459567f4 | |||
25b7b18013 | |||
87350cba1a | |||
864d181de0 | |||
90011be760 | |||
47dd6fdc2d | |||
8445c23e7f | |||
e6fc41090a | |||
7c79c6f992 | |||
7b8dca64bf | |||
549b05087f | |||
83d18da58f | |||
18e4465b17 | |||
2e20bb12de | |||
4f4e80fb6b | |||
fd1245fb4f | |||
fdb358c45e | |||
c438a91b87 | |||
a46fbcd9fc | |||
91285540c6 | |||
ab2145d5df | |||
63fe566044 | |||
d45d787cea | |||
442170eca9 | |||
2c7f4647af | |||
53604706c2 | |||
42d8a16d05 | |||
d933cae73c | |||
ce73145dbb | |||
ab7fdec499 | |||
d2968e0652 | |||
cebdc1b860 | |||
87435614db | |||
e9386633d8 | |||
49d9509255 | |||
905182931c | |||
026d989789 | |||
d38fa77757 | |||
5cdf2834cc | |||
7173dee28d | |||
e754a10241 | |||
|
8fa6458d6e | ||
e269442603 | |||
d36a29042e | |||
c09841a3da | |||
e098416a29 | |||
9017535bfb | |||
b2087042bc | |||
8fafc94118 | |||
c608b232b0 | |||
6ff53fb31c | |||
be96644e5c | |||
2646dccb91 | |||
1a5917ac6b | |||
588a8e140f | |||
c70149e9c8 | |||
50fbc3eb8c | |||
b4fce51b57 | |||
3c1f017e3a | |||
25cc98732d | |||
ef41ce03d3 | |||
44f22e6aee | |||
10a502fa4b | |||
2a4933f971 | |||
b30d49f5d7 | |||
7eca399b9d | |||
766e9b1235 | |||
26f0938566 | |||
97fab6adca | |||
97279c2599 | |||
8fe38c451e | |||
b0b228eef6 | |||
7bb407f2d7 | |||
bf4468bfa6 | |||
762e85d062 | |||
36a8d14720 | |||
2613fcc999 | |||
356dc1848c | |||
55c43efbdc | |||
e47318d8e9 | |||
f551fdf87b | |||
6f3877700e | |||
2483faef01 | |||
82365a24ba | |||
a29e33a3f9 | |||
273548411b | |||
a8f481ff4a | |||
7fe0f19adf | |||
b52cdf0f34 | |||
1c9c265ba8 | |||
b5080d8c04 | |||
bf107f9a18 | |||
8cd67cd68c | |||
5c7db72723 | |||
86433e841b | |||
61b2432d0b | |||
ddb9d0e647 | |||
a57c8da72a | |||
c6a5d49942 | |||
27ccdbd980 | |||
a5ef2551e8 | |||
c632ad51d4 | |||
4f433c3840 | |||
455c1e7023 | |||
9ade299be7 | |||
09dcc8a9d3 | |||
bd79831892 | |||
c7967c6904 | |||
a43676e9bf | |||
fa7e41469b | |||
c9ce81701f | |||
c407c59c1a | |||
7e5706137e | |||
144c1d7e79 | |||
054266bfa0 | |||
eaae058754 | |||
95a64732f3 | |||
57d59e91ac | |||
e3d3d200d6 | |||
7da60e6f0a | |||
95e33109fe | |||
9e937f17b7 | |||
5dca2e5fed | |||
f24fd5e880 | |||
cfd96126df | |||
9099ebe4f3 | |||
c666ceb51e | |||
e5eb50a3cb | |||
f0f23163e4 | |||
faaadb0009 | |||
fe32604a14 | |||
9b39d9baa5 | |||
7f2cdc5b1c | |||
4ffbbd5b89 | |||
898b24c8ee | |||
ccbe60b747 | |||
22456946b8 | |||
e4fcc7931e | |||
e0af7421a1 | |||
c2ed8b9bc5 | |||
e27d2ae682 | |||
b8fcaa48a5 | |||
6a18b15447 | |||
b2397fcc2f | |||
7ed30c40be | |||
63000636a8 | |||
3d27295b14 | |||
1b587b049a | |||
dc90258a28 | |||
23fde793db | |||
6640d70440 | |||
6e39154c75 | |||
164671a07c | |||
8aee72856c | |||
8118d4edc3 | |||
861a847088 | |||
666c527730 | |||
e6e26b77f9 | |||
5a3eb57c0b | |||
e1dbab3f4f | |||
3516a164da | |||
e1304cc602 | |||
88f00c6046 | |||
09a6062992 | |||
c92403f274 | |||
422c9bbb25 | |||
e45d156e74 | |||
8e32362333 | |||
801d4bf16b | |||
f7fd15b4bd | |||
ac5271de6d | |||
1f2087f50f | |||
1c5b776efb | |||
368a00434b | |||
242af637f7 | |||
6d8aa26883 | |||
bf2a412dec | |||
811e9991d0 | |||
5cbd6ec00b | |||
b9c793ebb7 | |||
76c14a79ed | |||
877efe086b | |||
3b2ab6c8a0 | |||
7c0f30ff95 | |||
8719452dad | |||
24a3a48478 | |||
ea026b351e | |||
109d3f28a2 | |||
af5b9a2341 | |||
25a6758c40 | |||
a685ab9e06 | |||
7b8f42ad59 | |||
a691808068 | |||
ecd648a9b2 | |||
f891587ce3 | |||
ffe7b5e109 | |||
602b1f3f98 | |||
17a14cedb6 | |||
e31c5472af | |||
46ed616560 | |||
47f294a982 | |||
0fcf2c7b45 | |||
fd2d30ee7f | |||
e9b74140b5 | |||
27442fe7c7 | |||
b748435f98 | |||
d60baa0dce | |||
48507962f8 | |||
7d4f3e9541 | |||
3b9ef0048e | |||
523e019aa0 | |||
d0d053152d | |||
8231d20e3b | |||
f35dd0b15d | |||
d778bd2719 | |||
0c42c6554c | |||
fbc7b58780 | |||
840193d5a8 | |||
4869039503 | |||
d0ded956cb | |||
47a73a4298 | |||
a082b879ca | |||
a51efa641d | |||
f553779797 | |||
dee8e4fe74 | |||
7760ee5725 | |||
1055d5c2e1 | |||
c094a3efae | |||
2a97438417 | |||
a69c223a33 | |||
1baa8cee60 | |||
3d17bb7703 | |||
d7a0706e0c | |||
dfcc516389 | |||
1857473ccc | |||
90bfee23b4 | |||
f634ad552e | |||
29fa35dffe | |||
c5f371e0d5 | |||
f319e89473 | |||
d5ad5ded88 | |||
dbbd2b54c4 | |||
1a3126dfc5 | |||
ba881aefa8 | |||
3fe8e1503c | |||
651482a1b9 | |||
8c3cd1fae7 | |||
ee49bde961 | |||
2b08c1d71e | |||
ead3f69a69 | |||
029b46d7de | |||
9da95f4dfb | |||
8e56ff6a8e | |||
21fcfaaa7f | |||
119f4e9d04 | |||
5cd91d8341 | |||
e0844a7f92 | |||
6f95d0a055 | |||
1090e502c2 | |||
1defe29406 | |||
126637306d | |||
2457107c63 | |||
411e44a681 | |||
91b054fdca | |||
e8c491a478 | |||
aab3d62365 | |||
0927288bb5 | |||
0019f868ad | |||
d05dec3605 | |||
30dbce3f4b | |||
56db9f42ad | |||
85f5717624 | |||
0c71e4934e | |||
f8992638d4 | |||
77e4491e90 | |||
523eb23f09 | |||
6865953433 | |||
d3dd825358 | |||
abac507e87 | |||
359a614a1a | |||
f2e6be530b | |||
2df4f90802 | |||
869439d3b8 | |||
36a8a5be3c | |||
8966bded81 | |||
c7c60edbd7 | |||
17befd9f05 | |||
eb3bdac162 | |||
b75777a339 | |||
5795028fdb | |||
8afc414d8b | |||
a23815998c | |||
97e4f8ec29 | |||
21c34436b9 | |||
b35b023b02 | |||
9daa0b1c54 | |||
d0ed413150 | |||
fa98f08161 | |||
f150df2dfd | |||
901aa4e1a5 | |||
7c0e34e1f8 | |||
bf5003f100 | |||
9f4c6d45c6 | |||
04e8e64003 | |||
1c3ec3c7e7 | |||
1a8c74d9e0 | |||
676d718fc2 | |||
11791ccf98 | |||
05fbe72304 | |||
b8874a6eab | |||
03497b6d5a | |||
5b1a98ad5c | |||
63ff8f54fa | |||
cb1ebeeb0c | |||
54db704044 | |||
d5a1c15db3 | |||
fa50123d3b | |||
e2749f6a81 | |||
12cd5c9045 | |||
6e5e5d4550 | |||
890cb6f6b5 | |||
ac238b360a | |||
8b55db172a | |||
7496a17cfa | |||
0dbf33a39f | |||
a939c741cb | |||
a4dc66396e | |||
a8b6989d27 | |||
51fec48cda | |||
4b8af2aef4 | |||
b936e569aa | |||
709767c3a0 | |||
40beb3711a | |||
4cd41c4b26 |
17
.gitea/issue_template.md
Normal file
17
.gitea/issue_template.md
Normal file
@ -0,0 +1,17 @@
|
||||
#### Beschreibung
|
||||
|
||||
Als Produktmanager muss ich nun dieses Ticket ausfüllen.
|
||||
|
||||
#### Aktuelles Verhalten
|
||||
|
||||
* Was macht die Software aktuell?
|
||||
|
||||
#### Gewünschtes Verhalten
|
||||
|
||||
* Was soll die Software anders machen?
|
||||
|
||||
#### Akzeptanzkriterien
|
||||
|
||||
* Was muss erfüllt sein, damit das Ticket als abgeschlossen angesehen werden kann?
|
||||
|
||||
#### Anmerkungen
|
7
.gitea/pull_request_template.md
Normal file
7
.gitea/pull_request_template.md
Normal file
@ -0,0 +1,7 @@
|
||||
#### Ticket Referenz:
|
||||
|
||||
#1
|
||||
|
||||
#### Gibt es etwas beim Review zu beachten?
|
||||
|
||||
Nein
|
129
.gitea/workflows/deploy_dev.yaml
Normal file
129
.gitea/workflows/deploy_dev.yaml
Normal file
@ -0,0 +1,129 @@
|
||||
name: Deploy dev on push
|
||||
run-name: Deploy dev on push
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
pre-build:
|
||||
runs-on: [ dobby.sh-edraft.de, ubuntu-latest ]
|
||||
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
|
||||
steps:
|
||||
- name: Shutdown stack
|
||||
run: docker stack rm sdb_dev
|
||||
|
||||
build-bot:
|
||||
needs: pre-build
|
||||
runs-on: [ dobby.sh-edraft.de, ubuntu-latest ]
|
||||
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
|
||||
steps:
|
||||
- name: Setup docker
|
||||
uses: https://github.com/papodaca/install-docker-action@main
|
||||
- run: docker -v
|
||||
|
||||
- name: Clone Repository
|
||||
uses: https://github.com/actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||
submodules: true
|
||||
|
||||
- name: Prepare bot build
|
||||
run: |
|
||||
python3.10 -m pip install --extra-index-url https://pip.sh-edraft.de cpl-cli
|
||||
cd bot
|
||||
cpl i
|
||||
|
||||
- name: Build docker bot
|
||||
run: |
|
||||
cd bot
|
||||
docker image prune -f
|
||||
cpl build
|
||||
docker build -t git.sh-edraft.de/sh-edraft.de/sdb-bot:$(cpl gv)-dev .
|
||||
|
||||
- name: Login to registry git.sh-edraft.de
|
||||
uses: https://github.com/docker/login-action@v1
|
||||
with:
|
||||
registry: git.sh-edraft.de
|
||||
username: ${{ secrets.CI_USERNAME }}
|
||||
password: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||
|
||||
- name: Push image
|
||||
run: |
|
||||
cd bot
|
||||
docker push git.sh-edraft.de/sh-edraft.de/sdb-bot:$(cpl gv)-dev
|
||||
|
||||
build-web:
|
||||
needs: pre-build
|
||||
runs-on: [ dobby.sh-edraft.de, ubuntu-latest ]
|
||||
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
|
||||
steps:
|
||||
- name: Setup docker
|
||||
uses: https://github.com/papodaca/install-docker-action@main
|
||||
- run: docker -v
|
||||
|
||||
- name: Clone Repository
|
||||
uses: https://github.com/actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||
submodules: true
|
||||
|
||||
- name: Setup node
|
||||
uses: https://github.com/actions/setup-node@v3
|
||||
|
||||
- name: Prepare web build
|
||||
run: |
|
||||
cd web
|
||||
npm install -g ts-node
|
||||
npm ci
|
||||
|
||||
- name: Build docker web
|
||||
run: |
|
||||
cd web
|
||||
docker image prune -f
|
||||
cp src/favicon.dev.ico src/favicon.ico
|
||||
npm run build
|
||||
docker build -t git.sh-edraft.de/sh-edraft.de/sdb-web:$(npm run -s gv)-dev .
|
||||
|
||||
- name: Login to registry git.sh-edraft.de
|
||||
uses: https://github.com/docker/login-action@v1
|
||||
with:
|
||||
registry: git.sh-edraft.de
|
||||
username: ${{ secrets.CI_USERNAME }}
|
||||
password: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||
|
||||
- name: Push image
|
||||
run: |
|
||||
cd web
|
||||
docker push git.sh-edraft.de/sh-edraft.de/sdb-web:$(npm run -s gv)-dev
|
||||
|
||||
deploy:
|
||||
needs: [ build-bot, build-web ]
|
||||
runs-on: [ dobby.sh-edraft.de, ubuntu-latest ]
|
||||
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: https://github.com/actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||
submodules: true
|
||||
|
||||
- name: Install cpl
|
||||
run: python3.10 -m pip install --extra-index-url https://pip.sh-edraft.de cpl-cli
|
||||
|
||||
- name: Set version
|
||||
run: |
|
||||
cd bot/docker
|
||||
chmod +x ./set-docker-compose-image-version.sh
|
||||
./set-docker-compose-image-version.sh git.sh-edraft.de/sh-edraft.de/sdb-bot:$(cd ../; cpl gv)-dev git.sh-edraft.de/sh-edraft.de/sdb-web:$(cd ../../web; npm run -s gv;)-dev
|
||||
|
||||
- name: Deploy Stack to sh-edraft.de
|
||||
uses: https://github.com/kgierke/portainer-stack-deployment@v1
|
||||
with:
|
||||
portainer-url: "https://docker.sh-edraft.de"
|
||||
portainer-username: "gitea_job"
|
||||
portainer-password: "${{ secrets.docker_job }}"
|
||||
portainer-endpoint: 2
|
||||
name: sdb_dev
|
||||
file: bot/docker/docker-compose.dev.yml
|
||||
variables: '{}'
|
128
.gitea/workflows/deploy_prod.yaml
Normal file
128
.gitea/workflows/deploy_prod.yaml
Normal file
@ -0,0 +1,128 @@
|
||||
name: Deploy prod on push
|
||||
run-name: Deploy prod on push
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
pre-build:
|
||||
runs-on: [ dobby.sh-edraft.de, ubuntu-latest ]
|
||||
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
|
||||
steps:
|
||||
- name: Shutdown stack
|
||||
run: docker stack rm sdb_prod
|
||||
|
||||
build-bot:
|
||||
needs: pre-build
|
||||
runs-on: [ dobby.sh-edraft.de, ubuntu-latest ]
|
||||
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
|
||||
steps:
|
||||
- name: Setup docker
|
||||
uses: https://github.com/papodaca/install-docker-action@main
|
||||
- run: docker -v
|
||||
|
||||
- name: Clone Repository
|
||||
uses: https://github.com/actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||
submodules: true
|
||||
|
||||
- name: Prepare bot build
|
||||
run: |
|
||||
python3.10 -m pip install --extra-index-url https://pip.sh-edraft.de cpl-cli
|
||||
cd bot
|
||||
cpl i
|
||||
|
||||
- name: Build docker bot
|
||||
run: |
|
||||
cd bot
|
||||
docker image prune -f
|
||||
cpl build
|
||||
docker build -t git.sh-edraft.de/sh-edraft.de/sdb-bot:$(cpl gv) .
|
||||
|
||||
- name: Login to registry git.sh-edraft.de
|
||||
uses: https://github.com/docker/login-action@v1
|
||||
with:
|
||||
registry: git.sh-edraft.de
|
||||
username: ${{ secrets.CI_USERNAME }}
|
||||
password: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||
|
||||
- name: Push image
|
||||
run: |
|
||||
cd bot
|
||||
docker push git.sh-edraft.de/sh-edraft.de/sdb-bot:$(cpl gv)
|
||||
|
||||
build-web:
|
||||
needs: pre-build
|
||||
runs-on: [ dobby.sh-edraft.de, ubuntu-latest ]
|
||||
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
|
||||
steps:
|
||||
- name: Setup docker
|
||||
uses: https://github.com/papodaca/install-docker-action@main
|
||||
- run: docker -v
|
||||
|
||||
- name: Clone Repository
|
||||
uses: https://github.com/actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||
submodules: true
|
||||
|
||||
- name: Setup node
|
||||
uses: https://github.com/actions/setup-node@v3
|
||||
|
||||
- name: Prepare web build
|
||||
run: |
|
||||
cd web
|
||||
npm install -g ts-node
|
||||
npm ci
|
||||
|
||||
- name: Build docker web
|
||||
run: |
|
||||
cd web
|
||||
docker image prune -f
|
||||
npm run build
|
||||
docker build -t git.sh-edraft.de/sh-edraft.de/sdb-web:$(npm run -s gv) .
|
||||
|
||||
- name: Login to registry git.sh-edraft.de
|
||||
uses: https://github.com/docker/login-action@v1
|
||||
with:
|
||||
registry: git.sh-edraft.de
|
||||
username: ${{ secrets.CI_USERNAME }}
|
||||
password: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||
|
||||
- name: Push image
|
||||
run: |
|
||||
cd web
|
||||
docker push git.sh-edraft.de/sh-edraft.de/sdb-web:$(npm run -s gv)
|
||||
|
||||
deploy:
|
||||
needs: [ build-bot, build-web ]
|
||||
runs-on: [ dobby.sh-edraft.de, ubuntu-latest ]
|
||||
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: https://github.com/actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||
submodules: true
|
||||
|
||||
- name: Install cpl
|
||||
run: python3.10 -m pip install --extra-index-url https://pip.sh-edraft.de cpl-cli
|
||||
|
||||
- name: Set version
|
||||
run: |
|
||||
cd bot/docker
|
||||
chmod +x ./set-docker-compose-image-version.sh
|
||||
./set-docker-compose-image-version.sh git.sh-edraft.de/sh-edraft.de/sdb-bot:$(cd ../; cpl gv) git.sh-edraft.de/sh-edraft.de/sdb-web:$(cd ../../web; npm run -s gv;)
|
||||
|
||||
- name: Deploy Stack to sh-edraft.de
|
||||
uses: https://github.com/kgierke/portainer-stack-deployment@v1
|
||||
with:
|
||||
portainer-url: "https://docker.sh-edraft.de"
|
||||
portainer-username: "gitea_job"
|
||||
portainer-password: "${{ secrets.docker_job }}"
|
||||
portainer-endpoint: 2
|
||||
name: sdb_prod
|
||||
file: bot/docker/docker-compose.yml
|
||||
variables: '{}'
|
129
.gitea/workflows/deploy_staging.yaml
Normal file
129
.gitea/workflows/deploy_staging.yaml
Normal file
@ -0,0 +1,129 @@
|
||||
name: Deploy staging on push
|
||||
run-name: Deploy staging on push
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- staging
|
||||
|
||||
jobs:
|
||||
pre-build:
|
||||
runs-on: [ dobby.sh-edraft.de, ubuntu-latest ]
|
||||
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
|
||||
steps:
|
||||
- name: Shutdown stack
|
||||
run: docker stack rm sdb_staging
|
||||
|
||||
build-bot:
|
||||
needs: pre-build
|
||||
runs-on: [ dobby.sh-edraft.de, ubuntu-latest ]
|
||||
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
|
||||
steps:
|
||||
- name: Setup docker
|
||||
uses: https://github.com/papodaca/install-docker-action@main
|
||||
- run: docker -v
|
||||
|
||||
- name: Clone Repository
|
||||
uses: https://github.com/actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||
submodules: true
|
||||
|
||||
- name: Prepare bot build
|
||||
run: |
|
||||
python3.10 -m pip install --extra-index-url https://pip.sh-edraft.de cpl-cli
|
||||
cd bot
|
||||
cpl i
|
||||
|
||||
- name: Build docker bot
|
||||
run: |
|
||||
cd bot
|
||||
docker image prune -f
|
||||
cpl build
|
||||
docker build -t git.sh-edraft.de/sh-edraft.de/sdb-bot:$(cpl gv)-staging .
|
||||
|
||||
- name: Login to registry git.sh-edraft.de
|
||||
uses: https://github.com/docker/login-action@v1
|
||||
with:
|
||||
registry: git.sh-edraft.de
|
||||
username: ${{ secrets.CI_USERNAME }}
|
||||
password: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||
|
||||
- name: Push image
|
||||
run: |
|
||||
cd bot
|
||||
docker push git.sh-edraft.de/sh-edraft.de/sdb-bot:$(cpl gv)-staging
|
||||
|
||||
build-web:
|
||||
needs: pre-build
|
||||
runs-on: [ dobby.sh-edraft.de, ubuntu-latest ]
|
||||
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
|
||||
steps:
|
||||
- name: Setup docker
|
||||
uses: https://github.com/papodaca/install-docker-action@main
|
||||
- run: docker -v
|
||||
|
||||
- name: Clone Repository
|
||||
uses: https://github.com/actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||
submodules: true
|
||||
|
||||
- name: Setup node
|
||||
uses: https://github.com/actions/setup-node@v3
|
||||
|
||||
- name: Prepare web build
|
||||
run: |
|
||||
cd web
|
||||
npm install -g ts-node
|
||||
npm ci
|
||||
|
||||
- name: Build docker web
|
||||
run: |
|
||||
cd web
|
||||
docker image prune -f
|
||||
cp src/favicon.staging.ico src/favicon.ico
|
||||
npm run build
|
||||
docker build -t git.sh-edraft.de/sh-edraft.de/sdb-web:$(npm run -s gv)-staging .
|
||||
|
||||
- name: Login to registry git.sh-edraft.de
|
||||
uses: https://github.com/docker/login-action@v1
|
||||
with:
|
||||
registry: git.sh-edraft.de
|
||||
username: ${{ secrets.CI_USERNAME }}
|
||||
password: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||
|
||||
- name: Push image
|
||||
run: |
|
||||
cd web
|
||||
docker push git.sh-edraft.de/sh-edraft.de/sdb-web:$(npm run -s gv)-staging
|
||||
|
||||
deploy:
|
||||
needs: [ build-bot, build-web ]
|
||||
runs-on: [ dobby.sh-edraft.de, ubuntu-latest ]
|
||||
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: https://github.com/actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||
submodules: true
|
||||
|
||||
- name: Install cpl
|
||||
run: python3.10 -m pip install --extra-index-url https://pip.sh-edraft.de cpl-cli
|
||||
|
||||
- name: Set version
|
||||
run: |
|
||||
cd bot/docker
|
||||
chmod +x ./set-docker-compose-image-version.sh
|
||||
./set-docker-compose-image-version.sh git.sh-edraft.de/sh-edraft.de/sdb-bot:$(cd ../; cpl gv)-staging git.sh-edraft.de/sh-edraft.de/sdb-web:$(cd ../../web; npm run -s gv;)-staging
|
||||
|
||||
- name: Deploy Stack to sh-edraft.de
|
||||
uses: https://github.com/kgierke/portainer-stack-deployment@v1
|
||||
with:
|
||||
portainer-url: "https://docker.sh-edraft.de"
|
||||
portainer-username: "gitea_job"
|
||||
portainer-password: "${{ secrets.docker_job }}"
|
||||
portainer-endpoint: 2
|
||||
name: sdb_staging
|
||||
file: bot/docker/docker-compose.staging.yml
|
||||
variables: '{}'
|
9
.gitmodules
vendored
Normal file
9
.gitmodules
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
[submodule "bot/src/bot/config"]
|
||||
path = bot/src/bot/config
|
||||
url = https://git.sh-edraft.de/sh-edraft.de/sh_discord_bot.config.git
|
||||
[submodule "bot/src/bot_api/config"]
|
||||
path = bot/src/bot_api/config
|
||||
url = https://git.sh-edraft.de/sh-edraft.de/sh_discord_bot.api.config.git
|
||||
[submodule "bot/docker"]
|
||||
path = bot/docker
|
||||
url = https://git.sh-edraft.de/sh-edraft.de/sh_discord_bot.docker.git
|
9
LICENSE
9
LICENSE
@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022-2023 sh-edraft.de
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
100
bot/.cpl/schematic_db_table.py
Normal file
100
bot/.cpl/schematic_db_table.py
Normal file
@ -0,0 +1,100 @@
|
||||
from cpl_cli.abc.generate_schematic_abc import GenerateSchematicABC
|
||||
|
||||
|
||||
class DBTable(GenerateSchematicABC):
|
||||
def __init__(self, *args: str):
|
||||
GenerateSchematicABC.__init__(self, *args)
|
||||
self._name = self._name.replace("_db_table", "")
|
||||
self._class_name = self._class_name.split("Db_table")[0]
|
||||
|
||||
def get_code(self) -> str:
|
||||
import textwrap
|
||||
|
||||
code = textwrap.dedent(
|
||||
"""\
|
||||
from datetime import datetime
|
||||
|
||||
from cpl_core.database import TableABC
|
||||
|
||||
|
||||
class $ClassName(TableABC):
|
||||
def __init__(
|
||||
self,
|
||||
value: str,
|
||||
created_at: datetime = None,
|
||||
modified_at: datetime = None,
|
||||
id=0,
|
||||
):
|
||||
self._id = id
|
||||
self._value = value
|
||||
|
||||
TableABC.__init__(self)
|
||||
self._created_at = created_at if created_at is not None else self._created_at
|
||||
self._modified_at = modified_at if modified_at is not None else self._modified_at
|
||||
|
||||
@property
|
||||
def value(self) -> str:
|
||||
return self._value
|
||||
|
||||
@value.setter
|
||||
def value(self, value: str):
|
||||
self._value = value
|
||||
|
||||
@staticmethod
|
||||
def get_select_all_string() -> str:
|
||||
return str(
|
||||
f\"""
|
||||
SELECT * FROM `$TableName`;
|
||||
\"""
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_select_by_id_string(id: int) -> str:
|
||||
return str(
|
||||
f\"""
|
||||
SELECT * FROM `$TableName`
|
||||
WHERE `Id` = {id};
|
||||
\"""
|
||||
)
|
||||
|
||||
@property
|
||||
def insert_string(self) -> str:
|
||||
return str(
|
||||
f\"""
|
||||
INSERT INTO `$TableName` (
|
||||
`Value`
|
||||
) VALUES (
|
||||
{self._value}
|
||||
);
|
||||
\"""
|
||||
)
|
||||
|
||||
@property
|
||||
def udpate_string(self) -> str:
|
||||
return str(
|
||||
f\"""
|
||||
UPDATE `$TableName`
|
||||
SET `Value` = {self._value}
|
||||
WHERE `Id` = {self._id};
|
||||
\"""
|
||||
)
|
||||
|
||||
@property
|
||||
def delete_string(self) -> str:
|
||||
return str(
|
||||
f\"""
|
||||
DELETE FROM `$TableName`
|
||||
WHERE `Id` = {self._id};
|
||||
\"""
|
||||
)
|
||||
"""
|
||||
)
|
||||
return self.build_code_str(
|
||||
code,
|
||||
ClassName=self._class_name,
|
||||
TableName=self._class_name,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def register(cls):
|
||||
GenerateSchematicABC.register(cls, "db-table", [])
|
55
bot/.cpl/schematic_migration.py
Normal file
55
bot/.cpl/schematic_migration.py
Normal file
@ -0,0 +1,55 @@
|
||||
from cpl_cli.abc.generate_schematic_abc import GenerateSchematicABC
|
||||
|
||||
|
||||
class Migration(GenerateSchematicABC):
|
||||
def __init__(self, *args: str):
|
||||
GenerateSchematicABC.__init__(self, *args)
|
||||
|
||||
def get_code(self) -> str:
|
||||
import textwrap
|
||||
|
||||
code = textwrap.dedent(
|
||||
"""\
|
||||
from bot_core.logging.database_logger import DatabaseLogger
|
||||
from bot_data.abc.migration_abc import MigrationABC
|
||||
from bot_data.db_context import DBContext
|
||||
|
||||
|
||||
class $ClassName(MigrationABC):
|
||||
name = "1.0_$ClassName"
|
||||
|
||||
def __init__(self, logger: DatabaseLogger, db: DBContext):
|
||||
MigrationABC.__init__(self)
|
||||
self._logger = logger
|
||||
self._db = db
|
||||
self._cursor = db.cursor
|
||||
|
||||
def upgrade(self):
|
||||
self._logger.debug(__name__, "Running upgrade")
|
||||
|
||||
self._cursor.execute(
|
||||
str(
|
||||
f\"""
|
||||
CREATE TABLE IF NOT EXISTS `$TableName` (
|
||||
`Id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`CreatedAt` DATETIME(6),
|
||||
`LastModifiedAt` DATETIME(6),
|
||||
PRIMARY KEY(`Id`)
|
||||
);
|
||||
\"""
|
||||
)
|
||||
)
|
||||
|
||||
def downgrade(self):
|
||||
self._cursor.execute("DROP TABLE `$TableName`;")
|
||||
"""
|
||||
)
|
||||
return self.build_code_str(
|
||||
code,
|
||||
ClassName=self._class_name,
|
||||
TableName=self._class_name.split("Migration")[0],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def register(cls):
|
||||
GenerateSchematicABC.register(cls, "migration", [])
|
34
bot/.cpl/schematic_query.py
Normal file
34
bot/.cpl/schematic_query.py
Normal file
@ -0,0 +1,34 @@
|
||||
from cpl_cli.abc.generate_schematic_abc import GenerateSchematicABC
|
||||
|
||||
|
||||
class Query(GenerateSchematicABC):
|
||||
def __init__(self, *args: str):
|
||||
GenerateSchematicABC.__init__(self, *args)
|
||||
|
||||
def get_code(self) -> str:
|
||||
import textwrap
|
||||
|
||||
code = textwrap.dedent(
|
||||
"""\
|
||||
from bot_graphql.abc.data_query_abc import DataQueryABC
|
||||
|
||||
|
||||
class $ClassName(DataQueryABC):
|
||||
def __init__(self):
|
||||
DataQueryABC.__init__(self, "Name")
|
||||
|
||||
self.set_field("id", self.resolve_id)
|
||||
|
||||
@staticmethod
|
||||
def resolve_id(x, *_):
|
||||
return x.id
|
||||
"""
|
||||
)
|
||||
return self.build_code_str(
|
||||
code,
|
||||
ClassName=self._class_name,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def register(cls):
|
||||
GenerateSchematicABC.register(cls, "query", [])
|
9
bot/LICENSE
Normal file
9
bot/LICENSE
Normal file
@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022-2023 sh-edraft.de
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
2
bot/README.md
Normal file
2
bot/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
# kd_discord_bot
|
||||
|
46
bot/cpl-workspace.json
Normal file
46
bot/cpl-workspace.json
Normal file
@ -0,0 +1,46 @@
|
||||
{
|
||||
"WorkspaceSettings": {
|
||||
"DefaultProject": "bot",
|
||||
"Projects": {
|
||||
"bot": "src/bot/bot.json",
|
||||
"bot-api": "src/bot_api/bot-api.json",
|
||||
"bot-core": "src/bot_core/bot-core.json",
|
||||
"bot-data": "src/bot_data/bot-data.json",
|
||||
"bot-graphql": "src/bot_graphql/bot-graphql.json",
|
||||
"achievements": "src/modules/achievements/achievements.json",
|
||||
"auto-role": "src/modules/auto_role/auto-role.json",
|
||||
"base": "src/modules/base/base.json",
|
||||
"boot-log": "src/modules/boot_log/boot-log.json",
|
||||
"config": "src/modules/config/config.json",
|
||||
"database": "src/modules/database/database.json",
|
||||
"level": "src/modules/level/level.json",
|
||||
"technician": "src/modules/technician/technician.json",
|
||||
"short-role-name": "src/modules/short_role_name/short-role-name.json",
|
||||
"special-offers": "src/modules/special_offers/special-offers.json",
|
||||
"checks": "tools/checks/checks.json",
|
||||
"get-version": "tools/get_version/get-version.json",
|
||||
"post-build": "tools/post_build/post-build.json",
|
||||
"set-version": "tools/set_version/set-version.json",
|
||||
"migration-to-sql": "tools/migration_to_sql/migration-to-sql.json"
|
||||
},
|
||||
"Scripts": {
|
||||
"format": "black ./",
|
||||
"sv": "cpl set-version $ARGS",
|
||||
"set-version": "cpl run set-version $ARGS --dev; echo '';",
|
||||
"gv": "cpl get-version",
|
||||
"get-version": "export VERSION=$(cpl run get-version --dev); echo $VERSION;",
|
||||
"pre-build": "cpl set-version $ARGS; black ./;",
|
||||
"post-build": "cpl run post-build --dev; black ./;",
|
||||
"pre-prod": "cpl build",
|
||||
"prod": "export SDB_ENVIRONMENT=production; export SDB_NAME=SDB-Prod; cpl start;",
|
||||
"pre-stage": "cpl build",
|
||||
"stage": "export SDB_ENVIRONMENT=staging; export SDB_NAME=SDB-Stage; cpl start;",
|
||||
"pre-dev": "cpl build",
|
||||
"dev": "export SDB_ENVIRONMENT=development; export SDB_NAME=SDB-Dev; cpl start;",
|
||||
"docker-build": "cpl build $ARGS; docker build -t sh-edraft.de/sdb-bot:$(cpl gv) .;",
|
||||
"dc-up": "docker-compose up -d",
|
||||
"dc-down": "docker-compose down",
|
||||
"docker": "cpl dc-down; cpl docker-build; cpl dc-up;"
|
||||
}
|
||||
}
|
||||
}
|
1
bot/docker
Submodule
1
bot/docker
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit fbcd9226c4d199529fdbce5169b38b1b23074adb
|
21
bot/dockerfile
Normal file
21
bot/dockerfile
Normal file
@ -0,0 +1,21 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM python:3.10.4-alpine
|
||||
|
||||
WORKDIR /app
|
||||
COPY ./dist/bot/build/bot/ .
|
||||
COPY ./dist/bot/build/requirements.txt .
|
||||
|
||||
RUN python -m pip install --upgrade pip
|
||||
|
||||
RUN apk update
|
||||
RUN apk add --update alpine-sdk linux-headers
|
||||
RUN apk add bash
|
||||
RUN apk add nano
|
||||
|
||||
RUN pip install -r requirements.txt --extra-index-url https://pip.sh-edraft.de
|
||||
RUN pip install flask[async]
|
||||
|
||||
# RUN pip install dnspython==2.2.1 # https://stackoverflow.com/questions/75137717/eventlet-dns-python-attribute-error-module-dns-rdtypes-has-no-attribute-any
|
||||
# ^ probably fixed py package updates
|
||||
|
||||
CMD [ "bash", "/app/bot/bot"]
|
2
bot/pyproject.toml
Normal file
2
bot/pyproject.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[tool.black]
|
||||
line-length = 120
|
26
bot/src/bot/__init__.py
Normal file
26
bot/src/bot/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "bot"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.8"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="8")
|
90
bot/src/bot/application.py
Normal file
90
bot/src/bot/application.py
Normal file
@ -0,0 +1,90 @@
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.console import Console
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
from cpl_core.logging import LoggerABC
|
||||
from cpl_discord.application import DiscordBotApplicationABC
|
||||
from cpl_discord.configuration import DiscordBotSettings
|
||||
from cpl_discord.service import DiscordBotServiceABC, DiscordBotService
|
||||
from cpl_translation import TranslatePipe, TranslationServiceABC, TranslationSettings
|
||||
|
||||
from bot_api.api_thread import ApiThread
|
||||
from bot_core.abc.task_abc import TaskABC
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_core.environment_variables import MAINTENANCE
|
||||
from bot_core.service.data_integrity_service import DataIntegrityService
|
||||
|
||||
|
||||
class Application(DiscordBotApplicationABC):
|
||||
def __init__(self, config: ConfigurationABC, services: ServiceProviderABC):
|
||||
DiscordBotApplicationABC.__init__(self, config, services)
|
||||
|
||||
self._services = services
|
||||
self._config = config
|
||||
|
||||
# cpl-core
|
||||
self._logger: LoggerABC = services.get_service(LoggerABC)
|
||||
self._data_integrity: DataIntegrityService = services.get_service(DataIntegrityService)
|
||||
# cpl-discord
|
||||
self._bot: DiscordBotServiceABC = services.get_service(DiscordBotServiceABC)
|
||||
self._bot_settings: DiscordBotSettings = config.get_configuration(DiscordBotSettings)
|
||||
# cpl-translation
|
||||
self._translation: TranslationServiceABC = services.get_service(TranslationServiceABC)
|
||||
self._t: TranslatePipe = services.get_service(TranslatePipe)
|
||||
# internal stuff
|
||||
self._tasks = services.get_services(TaskABC)
|
||||
|
||||
self._feature_flags: FeatureFlagsSettings = config.get_configuration(FeatureFlagsSettings)
|
||||
|
||||
# api
|
||||
if self._feature_flags.get_flag(FeatureFlagsEnum.api_module):
|
||||
self._api: ApiThread = services.get_service(ApiThread)
|
||||
|
||||
self._is_stopping = False
|
||||
|
||||
async def configure(self):
|
||||
self._translation.load_by_settings(self._configuration.get_configuration(TranslationSettings))
|
||||
|
||||
async def main(self):
|
||||
try:
|
||||
self._logger.debug(__name__, f"Starting...")
|
||||
|
||||
if (
|
||||
self._feature_flags.get_flag(FeatureFlagsEnum.api_module)
|
||||
and self._feature_flags.get_flag(FeatureFlagsEnum.api_only)
|
||||
and self._environment.environment_name == "development"
|
||||
):
|
||||
self._api.start()
|
||||
self._api.join()
|
||||
return
|
||||
|
||||
self._logger.info(__name__, f"Try to start {DiscordBotService.__name__}")
|
||||
if not self._config.get_configuration(MAINTENANCE):
|
||||
for task in self._tasks:
|
||||
await self._bot.add_cog(task)
|
||||
|
||||
await self._bot.start_async()
|
||||
await self._bot.stop_async()
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, "Start failed", e)
|
||||
|
||||
async def stop_async(self):
|
||||
if self._is_stopping:
|
||||
return
|
||||
|
||||
self._is_stopping = True
|
||||
try:
|
||||
self._logger.info(__name__, f"Try to stop {DiscordBotService.__name__}")
|
||||
if self._feature_flags.get_flag(FeatureFlagsEnum.api_module):
|
||||
self._api.stop()
|
||||
|
||||
await self._bot.close()
|
||||
await self._data_integrity.check_data_integrity(is_for_shutdown=True)
|
||||
self._logger.info(__name__, f"Stopped {DiscordBotService.__name__}")
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, "stop failed", e)
|
||||
|
||||
Console.write_line()
|
||||
|
||||
def is_restart(self):
|
||||
return True if self._configuration.get_configuration("IS_RESTART") == "true" else False
|
3
src/bot/bot → bot/src/bot/bot
Normal file → Executable file
3
src/bot/bot → bot/src/bot/bot
Normal file → Executable file
@ -14,9 +14,6 @@ elif [[ $1 == "-stage" ]]; then
|
||||
elif [[ $1 == "-prod" ]]; then
|
||||
export KDB_ENVIRONMENT=production
|
||||
export KDB_NAME=KDB
|
||||
else
|
||||
export KDB_ENVIRONMENT=production
|
||||
export KDB_NAME=KDB-prod
|
||||
fi
|
||||
|
||||
export PYTHONPATH=./:$PYTHONPATH
|
79
bot/src/bot/bot.json
Normal file
79
bot/src/bot/bot.json
Normal file
@ -0,0 +1,79 @@
|
||||
{
|
||||
"ProjectSettings": {
|
||||
"Name": "bot",
|
||||
"Version": {
|
||||
"Major": "1",
|
||||
"Minor": "2",
|
||||
"Micro": "8"
|
||||
},
|
||||
"Author": "Sven Heidemann",
|
||||
"AuthorEmail": "sven.heidemann@sh-edraft.de",
|
||||
"Description": "sh-edraft.de Discord bot",
|
||||
"LongDescription": "Discord bot for customers of sh-edraft.de",
|
||||
"URL": "https://www.sh-edraft.de",
|
||||
"CopyrightDate": "2022 - 2023",
|
||||
"CopyrightName": "sh-edraft.de",
|
||||
"LicenseName": "MIT",
|
||||
"LicenseDescription": "MIT, see LICENSE for more details.",
|
||||
"Dependencies": [
|
||||
"cpl-translation==2023.4.0.post1",
|
||||
"cpl-query==2023.10.0",
|
||||
"cpl-discord==2023.10.0.post1",
|
||||
"Flask==3.0.0",
|
||||
"Flask-Classful==0.16.0",
|
||||
"Flask-Cors==4.0.0",
|
||||
"PyJWT==2.8.0",
|
||||
"waitress==2.1.2",
|
||||
"Flask-SocketIO==5.3.6",
|
||||
"eventlet==0.33.3",
|
||||
"requests-oauthlib==1.3.1",
|
||||
"icmplib==3.0.4",
|
||||
"ariadne==0.20.1",
|
||||
"cryptography==41.0.4",
|
||||
"discord==2.3.2",
|
||||
"bs4==0.0.1",
|
||||
"lxml==4.9.3",
|
||||
"python-valve==0.2.1",
|
||||
"cpl-core==2023.10.2"
|
||||
],
|
||||
"DevDependencies": [
|
||||
"cpl-cli==2023.4.0.post3",
|
||||
"pygount==1.6.1",
|
||||
"black==23.10.1"
|
||||
],
|
||||
"PythonVersion": ">=3.10.4",
|
||||
"PythonPath": {},
|
||||
"Classifiers": []
|
||||
},
|
||||
"BuildSettings": {
|
||||
"ProjectType": "console",
|
||||
"SourcePath": "",
|
||||
"OutputPath": "../../dist",
|
||||
"Main": "bot.main",
|
||||
"EntryPoint": "bot",
|
||||
"IncludePackageData": false,
|
||||
"Included": [],
|
||||
"Excluded": [
|
||||
"*/__pycache__",
|
||||
"*/logs",
|
||||
"*/tests"
|
||||
],
|
||||
"PackageData": {},
|
||||
"ProjectReferences": [
|
||||
"../bot_api/bot-api.json",
|
||||
"../bot_core/bot-core.json",
|
||||
"../bot_data/bot-data.json",
|
||||
"../bot_graphql/bot-graphql.json",
|
||||
"../modules/achievements/achievements.json",
|
||||
"../modules/auto_role/auto-role.json",
|
||||
"../modules/base/base.json",
|
||||
"../modules/boot_log/boot-log.json",
|
||||
"../modules/config/config.json",
|
||||
"../modules/database/database.json",
|
||||
"../modules/level/level.json",
|
||||
"../modules/short_role_name/short-role-name.json",
|
||||
"../modules/special_offers/special-offers.json",
|
||||
"../modules/technician/technician.json"
|
||||
]
|
||||
}
|
||||
}
|
1
bot/src/bot/config
Submodule
1
bot/src/bot/config
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit eeebd13f80c6ceecc922ede5771e55212a884019
|
26
bot/src/bot/extension/__init__.py
Normal file
26
bot/src/bot/extension/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "bot.extension"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.8"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="8")
|
22
bot/src/bot/extension/clean_logs_extension.py
Normal file
22
bot/src/bot/extension/clean_logs_extension.py
Normal file
@ -0,0 +1,22 @@
|
||||
import os
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
|
||||
from cpl_core.application.application_extension_abc import ApplicationExtensionABC
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
from cpl_query.extension import List
|
||||
|
||||
|
||||
class CleanLogsExtension(ApplicationExtensionABC):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
async def run(self, config: ConfigurationABC, services: ServiceProviderABC):
|
||||
(
|
||||
List(str, os.listdir("logs/"))
|
||||
.where(lambda x: os.path.isdir(f"logs/{x}"))
|
||||
.order_by()
|
||||
.where(lambda x: (datetime.now() - datetime.strptime(x, "%Y-%m-%d")).days >= 7)
|
||||
.for_each(lambda x: shutil.rmtree(f"logs/{x}"))
|
||||
)
|
16
bot/src/bot/extension/init_bot_extension.py
Normal file
16
bot/src/bot/extension/init_bot_extension.py
Normal file
@ -0,0 +1,16 @@
|
||||
from cpl_core.application import ApplicationExtensionABC
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
|
||||
from bot_data.model.technician_config import TechnicianConfig
|
||||
|
||||
|
||||
class InitBotExtension(ApplicationExtensionABC):
|
||||
def __init__(self):
|
||||
ApplicationExtensionABC.__init__(self)
|
||||
|
||||
async def run(self, config: ConfigurationABC, services: ServiceProviderABC):
|
||||
settings = config.get_configuration(TechnicianConfig)
|
||||
|
||||
bot: DiscordBotServiceABC = services.get_service(DiscordBotServiceABC, max_messages=settings.cache_max_messages)
|
90
bot/src/bot/main.py
Normal file
90
bot/src/bot/main.py
Normal file
@ -0,0 +1,90 @@
|
||||
import asyncio
|
||||
import traceback
|
||||
from typing import Optional
|
||||
|
||||
from cpl_core.application import ApplicationBuilder
|
||||
from cpl_core.console import Console
|
||||
|
||||
from bot.application import Application
|
||||
from bot.extension.clean_logs_extension import CleanLogsExtension
|
||||
from bot.extension.init_bot_extension import InitBotExtension
|
||||
from bot.startup import Startup
|
||||
from bot.startup_discord_extension import StartupDiscordExtension
|
||||
from bot_data.startup_migration_extension import StartupMigrationExtension
|
||||
from bot.startup_module_extension import StartupModuleExtension
|
||||
from bot.startup_settings_extension import StartupSettingsExtension
|
||||
from bot_api.app_api_extension import AppApiExtension
|
||||
from bot_core.core_extension.core_extension import CoreExtension
|
||||
from modules.boot_log.boot_log_extension import BootLogExtension
|
||||
from modules.config.config_extension import ConfigExtension
|
||||
from modules.database.database_extension import DatabaseExtension
|
||||
|
||||
|
||||
class Program:
|
||||
def __init__(self):
|
||||
self.app: Optional[Application] = None
|
||||
|
||||
async def start(self):
|
||||
# discord extension has to be loaded before modules (modules depends on discord stuff)
|
||||
app_builder = (
|
||||
ApplicationBuilder(Application)
|
||||
.use_extension(StartupSettingsExtension)
|
||||
.use_extension(StartupDiscordExtension)
|
||||
.use_extension(StartupModuleExtension)
|
||||
.use_extension(StartupMigrationExtension)
|
||||
.use_extension(CleanLogsExtension)
|
||||
.use_extension(DatabaseExtension)
|
||||
.use_extension(ConfigExtension)
|
||||
.use_extension(InitBotExtension)
|
||||
.use_extension(BootLogExtension)
|
||||
.use_extension(AppApiExtension)
|
||||
.use_extension(CoreExtension)
|
||||
.use_startup(Startup)
|
||||
)
|
||||
self.app: Application = await app_builder.build_async()
|
||||
await self.app.run_async()
|
||||
Console.write_line(f"[ INFO ] [ {__name__} ]: Finished app.run_async")
|
||||
|
||||
async def stop(self):
|
||||
if self.app is None:
|
||||
return
|
||||
await self.app.stop_async()
|
||||
|
||||
|
||||
def main():
|
||||
program = Program()
|
||||
try:
|
||||
asyncio.run(program.start())
|
||||
except KeyboardInterrupt:
|
||||
asyncio.run(program.stop())
|
||||
except Exception as e:
|
||||
Console.error(
|
||||
f"[ ERROR ] [ {__name__} ]: Cannot start the bot",
|
||||
f"{e} -> {traceback.format_exc()}",
|
||||
)
|
||||
finally:
|
||||
try:
|
||||
asyncio.run(program.stop())
|
||||
except Exception as e:
|
||||
Console.error(
|
||||
f"[ ERROR ] [ {__name__} ]: Cannot stop the bot",
|
||||
f"{e} -> {traceback.format_exc()}",
|
||||
)
|
||||
|
||||
if program.app is not None and program.app.is_restart():
|
||||
del program
|
||||
main()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
# ((
|
||||
# ( `)
|
||||
# ; / ,
|
||||
# / \/
|
||||
# / |
|
||||
# / ~/
|
||||
# / ) ) ~ edraft
|
||||
# ___// | /
|
||||
# `--' \_~-,
|
44
bot/src/bot/module_list.py
Normal file
44
bot/src/bot/module_list.py
Normal file
@ -0,0 +1,44 @@
|
||||
from cpl_query.extension import List
|
||||
|
||||
from bot_api.api_module import ApiModule
|
||||
from bot_core.core_extension.core_extension_module import CoreExtensionModule
|
||||
from bot_core.core_module import CoreModule
|
||||
from bot_data.data_module import DataModule
|
||||
from bot_graphql.graphql_module import GraphQLModule
|
||||
from modules.achievements.achievements_module import AchievementsModule
|
||||
from modules.auto_role.auto_role_module import AutoRoleModule
|
||||
from modules.base.base_module import BaseModule
|
||||
from modules.boot_log.boot_log_module import BootLogModule
|
||||
from modules.config.config_module import ConfigModule
|
||||
from modules.database.database_module import DatabaseModule
|
||||
from modules.level.level_module import LevelModule
|
||||
from modules.short_role_name.short_role_name_module import ShortRoleNameModule
|
||||
from modules.special_offers.special_offers_module import SteamSpecialOffersModule
|
||||
from modules.technician.technician_module import TechnicianModule
|
||||
|
||||
|
||||
class ModuleList:
|
||||
@staticmethod
|
||||
def get_modules():
|
||||
# core modules (modules out of modules folder) should be loaded first!
|
||||
return List(
|
||||
type,
|
||||
[
|
||||
CoreModule, # has to be first!
|
||||
DataModule,
|
||||
ConfigModule, # has to be before db check
|
||||
DatabaseModule,
|
||||
GraphQLModule,
|
||||
AutoRoleModule,
|
||||
BaseModule,
|
||||
LevelModule,
|
||||
ApiModule,
|
||||
TechnicianModule,
|
||||
AchievementsModule,
|
||||
ShortRoleNameModule,
|
||||
SteamSpecialOffersModule,
|
||||
# has to be last!
|
||||
BootLogModule,
|
||||
CoreExtensionModule,
|
||||
],
|
||||
)
|
69
bot/src/bot/startup.py
Normal file
69
bot/src/bot/startup.py
Normal file
@ -0,0 +1,69 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from cpl_core.application import StartupABC
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.database import DatabaseSettings
|
||||
from cpl_core.dependency_injection import ServiceCollectionABC
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
from cpl_core.environment import ApplicationEnvironment
|
||||
from cpl_core.logging import LoggerABC
|
||||
|
||||
from bot_api.logging.api_logger import ApiLogger
|
||||
from bot_core.abc.custom_file_logger_abc import CustomFileLoggerABC
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_core.logging.command_logger import CommandLogger
|
||||
from bot_core.logging.database_logger import DatabaseLogger
|
||||
from bot_core.logging.event_logger import EventLogger
|
||||
from bot_core.logging.message_logger import MessageLogger
|
||||
from bot_core.logging.task_logger import TaskLogger
|
||||
from bot_data.db_context import DBContext
|
||||
|
||||
|
||||
class Startup(StartupABC):
|
||||
def __init__(self):
|
||||
StartupABC.__init__(self)
|
||||
self._start_time = datetime.now()
|
||||
|
||||
self._config: Optional[ConfigurationABC] = None
|
||||
self._feature_flags: Optional[FeatureFlagsSettings] = None
|
||||
|
||||
def configure_configuration(
|
||||
self, configuration: ConfigurationABC, environment: ApplicationEnvironment
|
||||
) -> ConfigurationABC:
|
||||
self._config = configuration
|
||||
self._feature_flags = configuration.get_configuration(FeatureFlagsSettings)
|
||||
return configuration
|
||||
|
||||
def configure_services(
|
||||
self, services: ServiceCollectionABC, environment: ApplicationEnvironment
|
||||
) -> ServiceProviderABC:
|
||||
services.add_logging()
|
||||
if self._feature_flags.get_flag(FeatureFlagsEnum.core_module):
|
||||
# custom logging
|
||||
services.add_singleton(CustomFileLoggerABC, CommandLogger)
|
||||
services.add_singleton(CustomFileLoggerABC, DatabaseLogger)
|
||||
services.add_singleton(CustomFileLoggerABC, MessageLogger)
|
||||
services.add_singleton(CustomFileLoggerABC, TaskLogger)
|
||||
services.add_singleton(CustomFileLoggerABC, EventLogger)
|
||||
|
||||
if self._feature_flags.get_flag(FeatureFlagsEnum.api_module):
|
||||
services.add_singleton(CustomFileLoggerABC, ApiLogger)
|
||||
|
||||
services.add_translation()
|
||||
services.add_db_context(DBContext, self._config.get_configuration(DatabaseSettings))
|
||||
|
||||
provider = services.build_service_provider()
|
||||
# instantiate custom logger
|
||||
for c in CustomFileLoggerABC.__subclasses__():
|
||||
i: LoggerABC = provider.get_service(c)
|
||||
|
||||
logger: LoggerABC = provider.get_service(LoggerABC)
|
||||
for flag in [f for f in FeatureFlagsEnum]:
|
||||
logger.debug(
|
||||
__name__,
|
||||
f"Loaded feature-flag: {flag} = {self._feature_flags.get_flag(flag)}",
|
||||
)
|
||||
|
||||
return provider
|
17
bot/src/bot/startup_discord_extension.py
Normal file
17
bot/src/bot/startup_discord_extension.py
Normal file
@ -0,0 +1,17 @@
|
||||
from cpl_core.application import StartupExtensionABC
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.dependency_injection import ServiceCollectionABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from cpl_discord import get_discord_collection
|
||||
|
||||
|
||||
class StartupDiscordExtension(StartupExtensionABC):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC):
|
||||
pass
|
||||
|
||||
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
|
||||
services.add_discord()
|
||||
dcc = get_discord_collection(services)
|
38
bot/src/bot/startup_module_extension.py
Normal file
38
bot/src/bot/startup_module_extension.py
Normal file
@ -0,0 +1,38 @@
|
||||
from typing import Optional
|
||||
|
||||
from cpl_core.application import StartupExtensionABC
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.console import Console, ForegroundColorEnum
|
||||
from cpl_core.dependency_injection import ServiceCollectionABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
|
||||
|
||||
from bot.module_list import ModuleList
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
|
||||
|
||||
class StartupModuleExtension(StartupExtensionABC):
|
||||
def __init__(self):
|
||||
self._config: Optional[ConfigurationABC] = None
|
||||
self._feature_flags: Optional[FeatureFlagsSettings] = None
|
||||
|
||||
self._modules = ModuleList.get_modules()
|
||||
|
||||
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC):
|
||||
self._config = config
|
||||
self._feature_flags = config.get_configuration(FeatureFlagsSettings)
|
||||
|
||||
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
|
||||
provider = services.build_service_provider()
|
||||
dc_collection: DiscordCollectionABC = provider.get_service(DiscordCollectionABC)
|
||||
|
||||
for module_type in self._modules:
|
||||
module = module_type(dc_collection)
|
||||
if not self._feature_flags.get_flag(module.feature_flag):
|
||||
continue
|
||||
|
||||
Console.set_foreground_color(ForegroundColorEnum.green)
|
||||
Console.write_line(f"[{__name__}] Loaded module: {module_type}")
|
||||
Console.color_reset()
|
||||
module.configure_configuration(self._config, env)
|
||||
module.configure_services(services, env)
|
55
bot/src/bot/startup_settings_extension.py
Normal file
55
bot/src/bot/startup_settings_extension.py
Normal file
@ -0,0 +1,55 @@
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Optional, Type, Callable
|
||||
|
||||
from cpl_core.application import StartupExtensionABC
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.dependency_injection import ServiceCollectionABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
|
||||
from bot_core.configuration.bot_logging_settings import BotLoggingSettings
|
||||
from bot_core.environment_variables import MAINTENANCE, MIGRATION_ONLY
|
||||
|
||||
|
||||
class StartupSettingsExtension(StartupExtensionABC):
|
||||
def __init__(self):
|
||||
self._start_time = datetime.now()
|
||||
|
||||
def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironmentABC):
|
||||
# this shit has to be done here because we need settings in subsequent startup extensions
|
||||
environment.set_working_directory(os.path.dirname(os.path.realpath(__file__)))
|
||||
configuration.add_environment_variables("SDB_")
|
||||
configuration.add_environment_variables("DISCORD_")
|
||||
configuration.add_configuration(
|
||||
MAINTENANCE, configuration.get_configuration(MAINTENANCE) in [True, "true", "True"]
|
||||
)
|
||||
configuration.add_configuration(
|
||||
MIGRATION_ONLY, configuration.get_configuration(MIGRATION_ONLY) in [True, "true", "True"]
|
||||
)
|
||||
|
||||
configuration.add_json_file(f"config/appsettings.json", optional=False)
|
||||
configuration.add_json_file(f"config/appsettings.{environment.environment_name}.json", optional=True)
|
||||
configuration.add_json_file(f"config/appsettings.{environment.host_name}.json", optional=True)
|
||||
# load feature-flags
|
||||
configuration.add_json_file(f"config/feature-flags.json", optional=False)
|
||||
configuration.add_json_file(f"config/feature-flags.{environment.environment_name}.json", optional=True)
|
||||
configuration.add_json_file(f"config/feature-flags.{environment.host_name}.json", optional=True)
|
||||
|
||||
configuration.add_configuration("Startup_StartTime", str(self._start_time))
|
||||
self._configure_settings_with_sub_settings(
|
||||
configuration, BotLoggingSettings, lambda x: x.files, lambda x: x.key
|
||||
)
|
||||
|
||||
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _configure_settings_with_sub_settings(
|
||||
config: ConfigurationABC, settings_type: Type, list_atr: Callable, atr: Callable
|
||||
):
|
||||
settings: Optional[settings_type] = config.get_configuration(settings_type)
|
||||
if settings is None:
|
||||
return
|
||||
|
||||
for sub_settings in list_atr(settings):
|
||||
config.add_configuration(f"{type(sub_settings).__name__}_{atr(sub_settings)}", sub_settings)
|
383
bot/src/bot/translation/de.json
Normal file
383
bot/src/bot/translation/de.json
Normal file
@ -0,0 +1,383 @@
|
||||
{
|
||||
"api": {
|
||||
"api": {
|
||||
"test_mail": {
|
||||
"message": "Dies ist eine Test-Mail vom Krümelmonster Web Interface\r\nGesendet von {}-{}",
|
||||
"subject": "Krümelmonster Web Interface Test-Mail"
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"confirmation": {
|
||||
"message": "Öffne den Link, um die E-Mail zu bestätigen:\r\n{}auth/register/{}",
|
||||
"subject": "E-Mail für {} {} bestätigen"
|
||||
},
|
||||
"forgot_password": {
|
||||
"message": "Öffne den Link, um das Passwort zu ändern:\r\n{}auth/forgot-password/{}",
|
||||
"subject": "Passwort für {} {} zurücksetzen"
|
||||
}
|
||||
},
|
||||
"mail": {
|
||||
"automatic_mail": "\r\n\r\nDies ist eine automatische E-Mail.\r\nGesendet von {}-{}@{}"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"bot_has_no_permission_message": "Ey!!!\nWas soll das?\nIch habe keine Berechtigungen :(\nScheiß System...",
|
||||
"colors": {
|
||||
"blue": "Blau",
|
||||
"dark_blue": "Dunkelblau",
|
||||
"dark_gold": "Dunkelgold",
|
||||
"dark_gray": "Dunkelgrau",
|
||||
"dark_green": "Dunkelgrün",
|
||||
"dark_grey": "Dunkelgrau",
|
||||
"dark_magenta": "Dunkelmagenta",
|
||||
"dark_orange": "Dunkelorange",
|
||||
"dark_purple": "Dunkelviolett",
|
||||
"dark_red": "Dunkelrot",
|
||||
"dark_teal": "Dunkelblaugrün",
|
||||
"default": "Standard",
|
||||
"gold": "Gold",
|
||||
"green": "Grün",
|
||||
"greyple": "Graugrün",
|
||||
"light_grey": "Hellgrau",
|
||||
"magenta": "Magenta",
|
||||
"orange": "Orange",
|
||||
"purple": "Violett",
|
||||
"red": "Rot",
|
||||
"teal": "Blaugrün",
|
||||
"yellow": "Gelb"
|
||||
},
|
||||
"errors": {
|
||||
"argument_parsing_error": "Fehler: Parameter konnte nicht gelesen werden!",
|
||||
"bad_argument": "Fehler: Ungültiger Parameter!",
|
||||
"bad_union_argument": "Fehler: Ungültiger Union Parameter!",
|
||||
"bot_missing_any_role": "Fehler: Mir fehlen alle benötigten Rollen!",
|
||||
"bot_missing_permissions": "Fehler: Mir fehlen Berechtigungen!",
|
||||
"bot_missing_role": "Fehler: Mir fehlt eine benötigte Rolle!",
|
||||
"bot_not_ready_yet": "Ey Alter! Gedulde dich doch mal! ...",
|
||||
"check_any_failure": "Fehler: Alle Checks sind Fehlgeschlagen!",
|
||||
"check_failure": "Fehler: Du hast nicht die benötigte Berechtigung!",
|
||||
"command_error": "Es gab einen Fehler beim Bearbeiten des Befehls. Melde dich bitte bei einem Admin.",
|
||||
"command_invoke_error": "Fehler: Befehl konnte nicht aufgerufen werden!",
|
||||
"command_not_found": "Fehler: Befehl konnte nicht gefunden werden!",
|
||||
"command_on_cooldown": "Fehler: Befehl befindet sich im Cooldown!",
|
||||
"disabled_command": "Fehler: Befehl wurde deaktiviert!",
|
||||
"error": "Es gab einen Fehler. Melde dich bitte bei einem Admin.",
|
||||
"expected_closing_quote_error": "Fehler: Erwarte Zitatende!",
|
||||
"extension_already_loaded": "Fehler: Erweiterung wurde bereits geladen!",
|
||||
"extension_error": "Fehler: Erweiterungsfehler!",
|
||||
"extension_failed": "Fehler: Erweiterung ist fehlgeschlagen!",
|
||||
"extension_not_loaded": "Fehler: Erweiterung wurde nicht geladen!",
|
||||
"invalid_end_of_quoted_string_error": "Fehler: Ungültiges Zitatende!",
|
||||
"max_concurrency_reached": "Fehler: Maximale Parallelität erreicht!",
|
||||
"missing_any_role": "Fehler: Alle benötigten Rollen fehlen!",
|
||||
"missing_permissions": "Fehler: Berechtigungen fehlen!",
|
||||
"missing_required_argument": "Fehler: Ein benötigter Parameter fehlt!",
|
||||
"missing_role": "Fehler: Benötigte Rolle fehlt!",
|
||||
"no_entry_point_error": "Fehler: Kein Eintrittspunkt!",
|
||||
"no_private_message": "Fehler: Private Nachrichten sind nicht erlaubt!",
|
||||
"not_owner": "Fehler: Du bist nicht mein besitzer!",
|
||||
"nsfw_channel_required": "Fehler: NSFW Kanal benötigt!",
|
||||
"private_message_only": "Fehler: Nur private Nachrichten sind erlaubt!",
|
||||
"too_many_arguments": "Fehler: Zu viele Parameter!",
|
||||
"unexpected_quote_error": "Fehler: Unerwarteter Fehler beim Anführungszeichen!",
|
||||
"user_input_error": "Fehler: Eingabefehler!"
|
||||
},
|
||||
"feature_not_activated": "Diese Funktion ist deaktiviert",
|
||||
"hello_world": "Hallo Welt",
|
||||
"no_permission_message": "Nein!\nIch höre nicht auf dich ¯\\_(ツ)_/¯",
|
||||
"not_implemented_yet": "Ey Alter, das kann ich noch nicht...",
|
||||
"presence": {
|
||||
"booting": "Ich fahre gerade hoch...",
|
||||
"restart": "Muss neue Kekse holen...",
|
||||
"running": "Ich esse Kekse :D",
|
||||
"shutdown": "Ich werde bestimmt wieder kommen...",
|
||||
"maintenance": "In Wartung!"
|
||||
}
|
||||
},
|
||||
"modules": {
|
||||
"special_offers": {
|
||||
"price": "Preis",
|
||||
"discount": "Rabatt",
|
||||
"discount_price": "Neuer Preis"
|
||||
},
|
||||
"achievements": {
|
||||
"commands": {
|
||||
"check": "Alles klar, ich schaue eben nach... nom nom"
|
||||
},
|
||||
"got_new_achievement": "{} hat die Errungenschaft {} freigeschaltet :D"
|
||||
},
|
||||
"auto_role": {
|
||||
"add": {
|
||||
"error": {
|
||||
"already_exists": "auto-role für die Nachricht {} existiert bereits!",
|
||||
"not_found": "Nachricht {} in {} nicht gefunden!"
|
||||
},
|
||||
"success": "auto-role für die Nachricht {} wurde hinzugefügt :D"
|
||||
},
|
||||
"error": {
|
||||
"nothing_found": "Keine auto-role Einträge gefunden."
|
||||
},
|
||||
"list": {
|
||||
"auto_role_id": "auto-role Id",
|
||||
"description": "Von auto-role beobachtete Nachrichten:",
|
||||
"message_id": "Nachricht-Id",
|
||||
"title": "Beobachtete Nachrichten:"
|
||||
},
|
||||
"remove": {
|
||||
"error": {
|
||||
"not_found": "auto-role {} nicht gefunden!"
|
||||
},
|
||||
"success": "auto-role {} wurde entfernt :D"
|
||||
},
|
||||
"react": {
|
||||
"success": "Alle Reaktionen wurden hinzugefügt"
|
||||
},
|
||||
"rule": {
|
||||
"add": {
|
||||
"error": {
|
||||
"already_exists": "Regel für auto-role {} existiert bereits!",
|
||||
"emoji_not_found": "Emoji {} für auto-role Regel {} nicht gefunden!",
|
||||
"not_found": "Regel für auto-role {} nicht gefunden!",
|
||||
"role_not_found": "Rolle {} für auto-role Regel {} nicht gefunden!"
|
||||
},
|
||||
"success": "Regel {} -> {} für auto-role {} wurde hinzugefügt :D"
|
||||
},
|
||||
"error": {
|
||||
"id_not_found": "Kein auto-role Eintrag mit der Id gefunden!"
|
||||
},
|
||||
"list": {
|
||||
"auto_role_rule_id": "auto-role Regel Id",
|
||||
"description": "Von auto-role angewendete Regeln:",
|
||||
"emoji": "Emoji",
|
||||
"role": "Rolle",
|
||||
"title": "auto-role Regeln:"
|
||||
},
|
||||
"remove": {
|
||||
"error": {
|
||||
"not_found": "Regel für auto-role {} nicht gefunden!"
|
||||
},
|
||||
"success": "Regel für auto-role {} wurde entfernt :D"
|
||||
}
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
"afk_command_channel_missing_message": "Zu unfähig einem Sprachkanal beizutreten?",
|
||||
"afk_command_move_message": "Ich verschiebe dich ja schon... (◔_◔)",
|
||||
"bug": {
|
||||
"label": "Bug",
|
||||
"message": "{} meldet einen Bug:\n{}",
|
||||
"response": "Danke für dein Feedback :D",
|
||||
"title": "Bug melden"
|
||||
},
|
||||
"complaints": {
|
||||
"label": "Beschwerde",
|
||||
"message": "{} hat eine Beschwerde eingereicht:\n{}",
|
||||
"response": "Danke für deine Beschwerde",
|
||||
"title": "Beschwerde einreichen"
|
||||
},
|
||||
"game_server": {
|
||||
"add": {
|
||||
"success": "Gameserver {} wurde hinzugefügt :)"
|
||||
},
|
||||
"error": {
|
||||
"nothing_found": "Keine Gameserver gefunden."
|
||||
},
|
||||
"list": {
|
||||
"api_key": "API Key",
|
||||
"description": "Konfigurierte Gameserver:",
|
||||
"name": "Name",
|
||||
"title": "Gameserver"
|
||||
},
|
||||
"list_members": {
|
||||
"description": "Konfigurierte Mitglieder:",
|
||||
"title": "Mitglieder",
|
||||
"users": "Mitglieder"
|
||||
},
|
||||
"remove": {
|
||||
"success": "Gameserver wurde entfernt :D"
|
||||
}
|
||||
},
|
||||
"goodbye_message": "Schade, dass du uns so schnell verlässt :(",
|
||||
"info": {
|
||||
"description": "Informationen über mich",
|
||||
"fields": {
|
||||
"deleted_message_count": "Gelöschte Nachrichten",
|
||||
"modules": "Module",
|
||||
"moved_users_count": "Verschobene Benutzer",
|
||||
"ontime": "Ontime",
|
||||
"received_command_count": "Empfangene Befehle",
|
||||
"received_message_count": "Empfangene Nachrichten",
|
||||
"sent_message_count": "Gesendete Nachrichten",
|
||||
"version": "Version"
|
||||
},
|
||||
"footer": "",
|
||||
"title": "Krümelmonster"
|
||||
},
|
||||
"mass_move": {
|
||||
"channel_from_error": "Du musst dich in einem Voicechannel befinden oder die Option \"channel_from\" mit angeben.",
|
||||
"moved": "Alle Personen aus {} wurden nach {} verschoben."
|
||||
},
|
||||
"member_joined_help_voice_channel": "{} braucht Hilfe, bitte kümmere dich drum :D",
|
||||
"member_left_message": "{} hat uns leider verlassen :(",
|
||||
"pong": "Pong",
|
||||
"presence": {
|
||||
"changed": "Presence wurde geändert.",
|
||||
"max_char_count_exceeded": "Der Text darf nicht mehr als 128 Zeichen lang sein!",
|
||||
"removed": "Presence wurde entfernt."
|
||||
},
|
||||
"register": {
|
||||
"not_found": "Benutzer konnte nicht gefunden werden!",
|
||||
"success": "Spieler wurde mit dem Mitglied verlinkt :D"
|
||||
},
|
||||
"technician_command_error_message": "Es gab ein Fehler mit dem Befehl: {} ausgelöst von {} -> {}\nDatum und Zeit: {}\nSchau bitte ins Log für Details.\nUUID: {}",
|
||||
"technician_error_message": "Es gab ein Fehler mit dem Event: {}\nDatum und Zeit: {}\nSchau bitte ins Log für Details.\nUUID: {}",
|
||||
"unregister": {
|
||||
"success": "Verlinkung wurde entfernt :D"
|
||||
},
|
||||
"user": {
|
||||
"birthday": {
|
||||
"has_birthday": "Alles Gute zum Geburtag {} :D",
|
||||
"success": "Dein Geburtstag wurde eingetragen.",
|
||||
"success_team": "{} hat seinen Geburtstag eingetragen: {}"
|
||||
},
|
||||
"add": {
|
||||
"xp": "Die {} von {} wurden um {} erhöht"
|
||||
},
|
||||
"atr": {
|
||||
"discord_join": "Discord beigetreten am",
|
||||
"id": "Id",
|
||||
"joins": "Beitritte",
|
||||
"last_join": "Server beigetreten am",
|
||||
"lefts": "Abgänge",
|
||||
"name": "Name",
|
||||
"ontime": "Ontime",
|
||||
"roles": "Rollen",
|
||||
"warnings": "Verwarnungen",
|
||||
"xp": "XP"
|
||||
},
|
||||
"error": {
|
||||
"atr_not_found": "Das Attribut {} konnte nicht gefunden werden :("
|
||||
},
|
||||
"get": {
|
||||
"ontime": "{} war insgesamt {} Stunden aktiv in einem Sprachkanal",
|
||||
"xp": "{} hat {} xp"
|
||||
},
|
||||
"info": {
|
||||
"footer": ""
|
||||
},
|
||||
"remove": {
|
||||
"xp": "Die {} von {} wurden um {} verringert"
|
||||
},
|
||||
"reset": {
|
||||
"ontime": "Die {} von {} wurden entfernt",
|
||||
"xp": "Die {} von {} wurden entfernt"
|
||||
},
|
||||
"set": {
|
||||
"error": {
|
||||
"type_error": "Der angegebene Wert ist keine Zahl! :(",
|
||||
"value_type_not_numeric": "Der angegebende Wert ist keine Ganzzahl! :("
|
||||
},
|
||||
"xp": "{} hat nun {} xp"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
"add": {
|
||||
"failed": "Verwarnung konnte nicht hinzugefügt werden :(",
|
||||
"success": "Verwarnung wurde hinzugefügt :)"
|
||||
},
|
||||
"first": "Bei der nächsten Verwarnung wirst du auf das vorherige Level zurückgesetzt!",
|
||||
"kick": "Ich musste {} aufgrund zu vieler Verwarnungen kicken",
|
||||
"remove": {
|
||||
"failed": "Verwarnung konnte nicht entfernt werden :(",
|
||||
"success": "Verwarnung wurde entfernt :)"
|
||||
},
|
||||
"removed": "Die Verwarnung '{}' wurde entfernt.",
|
||||
"second": "Bei der nächsten verwarnung wirst du auf das erste Level zurückgesetzt!",
|
||||
"show": {
|
||||
"description": "Beschreibung",
|
||||
"id": "Id"
|
||||
},
|
||||
"team_removed": "Die Verwarnung '{}' an {} wurde entfernt.",
|
||||
"team_warned": "{} wurde verwarnt. Der Grund ist: {}",
|
||||
"third": "Bei der nächsten verwarnung wirst du gekickt und zurückgesetzt!",
|
||||
"warned": "Du wurdest verwarnt. Der Grund ist: {}"
|
||||
},
|
||||
"welcome_message": "Hello There!\nIch heiße dich bei {} herzlichst Willkommen!",
|
||||
"welcome_message_for_team": "{} hat gerade das Irrenhaus betreten."
|
||||
},
|
||||
"boot_log": {
|
||||
"login_message": "Ich bin on the line :D\nDer Scheiß hat {} Sekunden gedauert"
|
||||
},
|
||||
"level": {
|
||||
"create": {
|
||||
"created": "Level {} mit Berechtigungen {} wurde erstellt :D"
|
||||
},
|
||||
"down": {
|
||||
"already_first": "{} hat bereits das erste Level.",
|
||||
"failed": "{} konnte nicht runtergesetzt werden :(",
|
||||
"success": "{} wurde auf Level {} runtergesetzt :)"
|
||||
},
|
||||
"edit": {
|
||||
"color_invalid": "Die Farbe {} ist ungültig!",
|
||||
"edited": "Level {} wurde bearbeitet :D",
|
||||
"not_found": "Level {} nicht gefunden!",
|
||||
"permission_invalid": "Der Berechtigungswert {} ist ungültig!"
|
||||
},
|
||||
"error": {
|
||||
"level_with_name_already_exists": "Ein Level mit dem Namen {} existiert bereits!",
|
||||
"level_with_xp_already_exists": "Das Level {} hat bereits die Mindest-XP {}!",
|
||||
"nothing_found": "Keine Einträge gefunden."
|
||||
},
|
||||
"list": {
|
||||
"description": "Konfigurierte Level:",
|
||||
"min_xp": "Mindest-XP",
|
||||
"name": "Name",
|
||||
"permission_int": "Berechtigungen",
|
||||
"title": "Level:"
|
||||
},
|
||||
"new_level_message": "{} ist nun Level {}",
|
||||
"remove": {
|
||||
"error": {
|
||||
"not_found": "Level {} nicht gefunden!"
|
||||
},
|
||||
"success": "Level {} wurde entfernt :D"
|
||||
},
|
||||
"seeding_failed": "Levelsystem konnte nicht neu geladen werden :(",
|
||||
"seeding_finished": "Levelsystem wurde erfolgreich neu geladen :)",
|
||||
"seeding_started": "Levelsystem wird neu geladen...",
|
||||
"set": {
|
||||
"already_level": "{} hat bereits das Level {} :/",
|
||||
"failed": "Das Level von {} konnte nicht auf {} gesetzt werden :(",
|
||||
"not_found": "Das Level {} konnte nicht gefunden werden :(",
|
||||
"success": "{} ist nun Level {} :)"
|
||||
},
|
||||
"up": {
|
||||
"already_last": "{} hat bereits das höchste Level.",
|
||||
"failed": "{} konnte nicht hochgesetzt werden :(",
|
||||
"success": "{} wurde auf Level {} hochgesetzt :)"
|
||||
}
|
||||
},
|
||||
"moderator": {
|
||||
"purge_message": "Na gut..., ich lösche alle Nachrichten wenns sein muss."
|
||||
},
|
||||
"short_role_name": {
|
||||
"checked_message": "Die Rollen Kürzel wurden überprüft"
|
||||
},
|
||||
"technician": {
|
||||
"api_key": {
|
||||
"add": {
|
||||
"success": "API-Schlüssel für {} wurde erstellt: {}"
|
||||
},
|
||||
"get": "API-Schlüssel für {}: {}",
|
||||
"remove": {
|
||||
"not_found": "API-Schlüssel konnte nicht gefunden werden!",
|
||||
"success": "API-Schlüssel wurde entfernt :D"
|
||||
}
|
||||
},
|
||||
"log_message": "Hier sind deine Logdateien! :)",
|
||||
"restart_message": "Bin gleich wieder da :D",
|
||||
"shutdown_message": "Trauert nicht um mich, es war eine logische Entscheidung. Das Wohl von Vielen, es wiegt schwerer als das Wohl von Wenigen oder eines Einzelnen. Ich war es und ich werde es immer sein, euer Freund. Lebt lange und in Frieden :)",
|
||||
"synced_message": "Der Sync wurde abgeschlossen."
|
||||
}
|
||||
}
|
||||
}
|
26
bot/src/bot_api/__init__.py
Normal file
26
bot/src/bot_api/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "bot_api"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.8"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="8")
|
26
bot/src/bot_api/abc/__init__.py
Normal file
26
bot/src/bot_api/abc/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "bot_api.abc"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.8"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="8")
|
120
bot/src/bot_api/abc/auth_service_abc.py
Normal file
120
bot/src/bot_api/abc/auth_service_abc.py
Normal file
@ -0,0 +1,120 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional
|
||||
|
||||
from cpl_query.extension import List
|
||||
|
||||
from bot_api.filter.auth_user_select_criteria import AuthUserSelectCriteria
|
||||
from bot_api.model.auth_user_dto import AuthUserDTO
|
||||
from bot_api.model.auth_user_filtered_result_dto import AuthUserFilteredResultDTO
|
||||
from bot_api.model.email_string_dto import EMailStringDTO
|
||||
from bot_api.model.o_auth_dto import OAuthDTO
|
||||
from bot_api.model.reset_password_dto import ResetPasswordDTO
|
||||
from bot_api.model.token_dto import TokenDTO
|
||||
from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO
|
||||
from bot_data.model.auth_user import AuthUser
|
||||
|
||||
|
||||
class AuthServiceABC(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def generate_token(self, user: AuthUser) -> str:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def decode_token(self, token: str) -> dict:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_decoded_token_from_request(self) -> dict:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def find_decoded_token_from_request(self) -> Optional[dict]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_all_auth_users_async(self) -> List[AuthUserDTO]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_filtered_auth_users_async(self, criteria: AuthUserSelectCriteria) -> AuthUserFilteredResultDTO:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_auth_user_by_email_async(self, email: str, with_password: bool = False) -> AuthUserDTO:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def find_auth_user_by_email_async(self, email: str) -> AuthUserDTO:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_auth_user(self, user_dto: AuthUserDTO):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def add_auth_user_by_oauth_async(self, dto: OAuthDTO):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def update_user_async(self, update_user_dto: UpdateAuthUserDTO):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def update_user_as_admin_async(self, update_user_dto: UpdateAuthUserDTO):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def delete_auth_user_by_email_async(self, email: str):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def delete_auth_user_async(self, user_dto: AuthUserDTO):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def verify_login(self, token_str: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def verify_api_key(self, api_key: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def login_discord_async(self, oauth_dto: AuthUserDTO, dc_id: int) -> TokenDTO:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def refresh_async(self, token_dto: TokenDTO) -> TokenDTO:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def revoke_async(self, token_dto: TokenDTO):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def confirm_email_async(self, id: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def forgot_password_async(self, email: str):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def confirm_forgot_password_async(self, id: str) -> EMailStringDTO:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def reset_password_async(self, rp_dto: ResetPasswordDTO):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def resend_confirmation_email_by_mail(self, mail: str):
|
||||
pass
|
15
bot/src/bot_api/abc/dto_abc.py
Normal file
15
bot/src/bot_api/abc/dto_abc.py
Normal file
@ -0,0 +1,15 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class DtoABC(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def from_dict(self, values: dict):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def to_dict(self) -> dict:
|
||||
pass
|
10
bot/src/bot_api/abc/select_criteria_abc.py
Normal file
10
bot/src/bot_api/abc/select_criteria_abc.py
Normal file
@ -0,0 +1,10 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class SelectCriteriaABC(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self, page_index: int, page_size: int, sort_direction: str, sort_column: str):
|
||||
self.page_index = page_index
|
||||
self.page_size = page_size
|
||||
self.sort_direction = sort_direction
|
||||
self.sort_column = sort_column
|
17
bot/src/bot_api/abc/transformer_abc.py
Normal file
17
bot/src/bot_api/abc/transformer_abc.py
Normal file
@ -0,0 +1,17 @@
|
||||
from abc import abstractmethod
|
||||
|
||||
from cpl_core.database import TableABC
|
||||
|
||||
from bot_api.abc.dto_abc import DtoABC
|
||||
|
||||
|
||||
class TransformerABC:
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def to_db(dto: DtoABC) -> TableABC:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def to_dto(db: TableABC) -> DtoABC:
|
||||
pass
|
174
bot/src/bot_api/api.py
Normal file
174
bot/src/bot_api/api.py
Normal file
@ -0,0 +1,174 @@
|
||||
import socket
|
||||
import sys
|
||||
import textwrap
|
||||
import uuid
|
||||
from functools import partial
|
||||
from typing import Union, Optional
|
||||
|
||||
import eventlet
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
from cpl_core.utils import CredentialManager
|
||||
from eventlet import wsgi
|
||||
from flask import Flask, request, jsonify, Response
|
||||
from flask_cors import CORS
|
||||
from flask_socketio import SocketIO
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from bot_api.configuration.api_settings import ApiSettings
|
||||
from bot_api.configuration.authentication_settings import AuthenticationSettings
|
||||
from bot_core.exception.service_error_code_enum import ServiceErrorCode
|
||||
from bot_core.exception.service_exception import ServiceException
|
||||
from bot_api.logging.api_logger import ApiLogger
|
||||
from bot_api.model.error_dto import ErrorDTO
|
||||
from bot_api.route.route import Route
|
||||
|
||||
|
||||
class Api(Flask):
|
||||
def __init__(
|
||||
self,
|
||||
logger: ApiLogger,
|
||||
services: ServiceProviderABC,
|
||||
api_settings: ApiSettings,
|
||||
auth_settings: AuthenticationSettings,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
if not args:
|
||||
kwargs.setdefault("import_name", __name__)
|
||||
|
||||
Flask.__init__(self, *args, **kwargs)
|
||||
|
||||
self._logger = logger
|
||||
self._services = services
|
||||
self._api_settings = api_settings
|
||||
self._auth_settings = auth_settings
|
||||
|
||||
self._cors = CORS(self, support_credentials=True)
|
||||
|
||||
# register hooks
|
||||
self.before_request(self.before_request_hook)
|
||||
self.after_request(self.after_request_hook)
|
||||
|
||||
# register error handler
|
||||
exc_class, code = self._get_exc_class_and_code(Exception)
|
||||
self.register_error_handler(exc_class, self.handle_exception)
|
||||
|
||||
# websockets
|
||||
# Added async_mode see link below
|
||||
# https://github.com/miguelgrinberg/Flask-SocketIO/discussions/1849
|
||||
# https://stackoverflow.com/questions/39370848/flask-socket-io-sometimes-client-calls-freeze-the-server
|
||||
self._socketio = SocketIO(self, cors_allowed_origins="*", path="/api/socket.io", async_mode="eventlet")
|
||||
self._socketio.on_event("connect", self.on_connect)
|
||||
self._socketio.on_event("disconnect", self.on_disconnect)
|
||||
|
||||
self._socket: Optional[socket] = None
|
||||
|
||||
self._requests = {}
|
||||
|
||||
@staticmethod
|
||||
def _get_methods_from_registered_route() -> Union[list[str], str]:
|
||||
methods = ["Unknown"]
|
||||
if (
|
||||
request.path in Route.registered_routes
|
||||
and len(Route.registered_routes[request.path]) >= 1
|
||||
and "methods" in Route.registered_routes[request.path][1]
|
||||
):
|
||||
methods = Route.registered_routes[request.path][1]["methods"]
|
||||
|
||||
if len(methods) == 1:
|
||||
return methods[0]
|
||||
return methods
|
||||
|
||||
def _register_routes(self):
|
||||
for path, f in Route.registered_routes.items():
|
||||
route = f[0]
|
||||
kwargs = f[1]
|
||||
cls = None
|
||||
qual_name_split = route.__qualname__.split(".")
|
||||
if len(qual_name_split) > 0:
|
||||
cls_type = vars(sys.modules[route.__module__])[qual_name_split[0]]
|
||||
cls = self._services.get_service(cls_type)
|
||||
|
||||
partial_f = partial(route, self if cls is None else cls)
|
||||
partial_f.__name__ = route.__name__
|
||||
self.route(path, **kwargs)(partial_f)
|
||||
|
||||
def handle_exception(self, e: Exception):
|
||||
self._logger.error(__name__, f"Caught error", e)
|
||||
|
||||
if isinstance(e, ServiceException):
|
||||
ex: ServiceException = e
|
||||
self._logger.error(__name__, ex.get_detailed_message())
|
||||
error = ErrorDTO(ex.error_code, ex.message)
|
||||
return jsonify(error.to_dict()), 500
|
||||
elif isinstance(e, NotFound):
|
||||
self._logger.error(__name__, e.description)
|
||||
error = ErrorDTO(ServiceErrorCode.NotFound, e.description)
|
||||
return jsonify(error.to_dict()), 404
|
||||
else:
|
||||
tracking_id = uuid.uuid4()
|
||||
user_message = f"Tracking Id: {tracking_id}"
|
||||
self._logger.error(__name__, user_message, e)
|
||||
error = ErrorDTO(None, user_message)
|
||||
return jsonify(error.to_dict()), 400
|
||||
|
||||
def before_request_hook(self):
|
||||
request_id = uuid.uuid4()
|
||||
self._requests[request] = request_id
|
||||
method = request.access_control_request_method
|
||||
|
||||
self._logger.info(
|
||||
__name__,
|
||||
f"Received {request_id} @ {self._get_methods_from_registered_route() if method is None else method} {request.url} from {request.remote_addr}",
|
||||
)
|
||||
|
||||
headers = str(request.headers).replace("\n", "\n\t\t")
|
||||
data = request.get_data()
|
||||
data = "" if len(data) == 0 else str(data.decode(encoding="utf-8"))
|
||||
|
||||
text = textwrap.dedent(
|
||||
f"Request: {request_id}:\n\tHeader:\n\t\t{headers}\n\tUser-Agent: {request.user_agent.string}\n\tBody: {data}"
|
||||
)
|
||||
self._logger.trace(__name__, text)
|
||||
|
||||
def after_request_hook(self, response: Response):
|
||||
method = request.access_control_request_method
|
||||
request_id = f"{self._get_methods_from_registered_route() if method is None else method} {request.url} from {request.remote_addr}"
|
||||
if request in self._requests:
|
||||
request_id = self._requests[request]
|
||||
|
||||
self._logger.info(__name__, f"Answered {request_id}")
|
||||
|
||||
headers = str(request.headers).replace("\n", "\n\t\t")
|
||||
data = request.get_data()
|
||||
data = "" if len(data) == 0 else str(data.decode(encoding="utf-8"))
|
||||
|
||||
text = textwrap.dedent(f"Request: {request_id}:\n\tHeader:\n\t\t{headers}\n\tResponse: {data}")
|
||||
self._logger.trace(__name__, text)
|
||||
|
||||
return response
|
||||
|
||||
def start(self):
|
||||
self._logger.info(
|
||||
__name__,
|
||||
f"Starting API {self._api_settings.host}:{self._api_settings.port}",
|
||||
)
|
||||
self._register_routes()
|
||||
self.secret_key = CredentialManager.decrypt(self._auth_settings.secret_key)
|
||||
# from waitress import serve
|
||||
# https://docs.pylonsproject.org/projects/waitress/en/stable/arguments.html
|
||||
# serve(self, host=self._apt_settings.host, port=self._apt_settings.port, threads=10, connection_limit=1000, channel_timeout=10)
|
||||
self._socket = eventlet.listen((self._api_settings.host, self._api_settings.port))
|
||||
wsgi.server(self._socket, self, log_output=False)
|
||||
|
||||
def stop(self):
|
||||
if self._socket is None:
|
||||
return
|
||||
self._socket.shutdown(socket.SHUT_RDWR)
|
||||
self._socket.close()
|
||||
|
||||
def on_connect(self):
|
||||
self._logger.info(__name__, f"Client connected")
|
||||
|
||||
def on_disconnect(self):
|
||||
self._logger.info(__name__, f"Client disconnected")
|
51
bot/src/bot_api/api_module.py
Normal file
51
bot/src/bot_api/api_module.py
Normal file
@ -0,0 +1,51 @@
|
||||
import os
|
||||
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.dependency_injection import ServiceCollectionABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from cpl_core.mailing import EMailClientABC, EMailClient
|
||||
from cpl_discord.discord_event_types_enum import DiscordEventTypesEnum
|
||||
from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
|
||||
from flask import Flask
|
||||
|
||||
from bot_api.abc.auth_service_abc import AuthServiceABC
|
||||
from bot_api.api import Api
|
||||
from bot_api.api_thread import ApiThread
|
||||
from bot_api.controller.auth_controller import AuthController
|
||||
from bot_api.controller.auth_discord_controller import AuthDiscordController
|
||||
from bot_api.controller.graphql_controller import GraphQLController
|
||||
from bot_api.controller.gui_controller import GuiController
|
||||
from bot_api.event.bot_api_on_ready_event import BotApiOnReadyEvent
|
||||
from bot_api.service.auth_service import AuthService
|
||||
from bot_api.service.discord_service import DiscordService
|
||||
from bot_core.abc.module_abc import ModuleABC
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
|
||||
|
||||
class ApiModule(ModuleABC):
|
||||
def __init__(self, dc: DiscordCollectionABC):
|
||||
ModuleABC.__init__(self, dc, FeatureFlagsEnum.api_module)
|
||||
|
||||
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC):
|
||||
cwd = env.working_directory
|
||||
env.set_working_directory(os.path.dirname(os.path.realpath(__file__)))
|
||||
config.add_json_file(f"config/apisettings.json", optional=False)
|
||||
config.add_json_file(f"config/apisettings.{env.environment_name}.json", optional=True)
|
||||
config.add_json_file(f"config/apisettings.{env.host_name}.json", optional=True)
|
||||
env.set_working_directory(cwd)
|
||||
|
||||
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
|
||||
services.add_singleton(EMailClientABC, EMailClient)
|
||||
|
||||
services.add_singleton(ApiThread)
|
||||
services.add_singleton(Flask, Api)
|
||||
|
||||
services.add_transient(AuthServiceABC, AuthService)
|
||||
services.add_transient(AuthController)
|
||||
services.add_transient(AuthDiscordController)
|
||||
services.add_transient(GuiController)
|
||||
services.add_transient(DiscordService)
|
||||
services.add_transient(GraphQLController)
|
||||
|
||||
# cpl-discord
|
||||
services.add_transient(DiscordEventTypesEnum.on_ready.value, BotApiOnReadyEvent)
|
26
bot/src/bot_api/api_thread.py
Normal file
26
bot/src/bot_api/api_thread.py
Normal file
@ -0,0 +1,26 @@
|
||||
import threading
|
||||
|
||||
from bot_api.api import Api
|
||||
from bot_api.logging.api_logger import ApiLogger
|
||||
|
||||
|
||||
class ApiThread(threading.Thread):
|
||||
def __init__(self, logger: ApiLogger, api: Api):
|
||||
threading.Thread.__init__(self, daemon=True)
|
||||
|
||||
self._logger = logger
|
||||
self._api = api
|
||||
|
||||
def run(self) -> None:
|
||||
try:
|
||||
self._logger.trace(__name__, f"Try to start {type(self._api).__name__}")
|
||||
self._api.start()
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, "Start failed", e)
|
||||
|
||||
def stop(self):
|
||||
try:
|
||||
self._logger.trace(__name__, f"Try to stop {type(self._api).__name__}")
|
||||
self._api.stop()
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, "Stop failed", e)
|
19
bot/src/bot_api/app_api_extension.py
Normal file
19
bot/src/bot_api/app_api_extension.py
Normal file
@ -0,0 +1,19 @@
|
||||
from cpl_core.application import ApplicationExtensionABC
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
|
||||
from bot_api.route.route import Route
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
|
||||
|
||||
class AppApiExtension(ApplicationExtensionABC):
|
||||
def __init__(self):
|
||||
ApplicationExtensionABC.__init__(self)
|
||||
|
||||
async def run(self, config: ConfigurationABC, services: ServiceProviderABC):
|
||||
feature_flags: FeatureFlagsSettings = config.get_configuration(FeatureFlagsSettings)
|
||||
if not feature_flags.get_flag(FeatureFlagsEnum.api_module):
|
||||
return
|
||||
|
||||
Route.init_authorize()
|
44
bot/src/bot_api/bot-api.json
Normal file
44
bot/src/bot_api/bot-api.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"ProjectSettings": {
|
||||
"Name": "bot-api",
|
||||
"Version": {
|
||||
"Major": "1",
|
||||
"Minor": "2",
|
||||
"Micro": "8"
|
||||
},
|
||||
"Author": "",
|
||||
"AuthorEmail": "",
|
||||
"Description": "",
|
||||
"LongDescription": "",
|
||||
"URL": "",
|
||||
"CopyrightDate": "",
|
||||
"CopyrightName": "",
|
||||
"LicenseName": "",
|
||||
"LicenseDescription": "",
|
||||
"Dependencies": [
|
||||
"cpl-core==2022.12.0"
|
||||
],
|
||||
"DevDependencies": [
|
||||
"cpl-cli==2022.12.0"
|
||||
],
|
||||
"PythonVersion": ">=3.10.4",
|
||||
"PythonPath": {},
|
||||
"Classifiers": []
|
||||
},
|
||||
"BuildSettings": {
|
||||
"ProjectType": "library",
|
||||
"SourcePath": "",
|
||||
"OutputPath": "../../dist",
|
||||
"Main": "bot_api.main",
|
||||
"EntryPoint": "bot-api",
|
||||
"IncludePackageData": false,
|
||||
"Included": [],
|
||||
"Excluded": [
|
||||
"*/__pycache__",
|
||||
"*/logs",
|
||||
"*/tests"
|
||||
],
|
||||
"PackageData": {},
|
||||
"ProjectReferences": []
|
||||
}
|
||||
}
|
1
bot/src/bot_api/config
Submodule
1
bot/src/bot_api/config
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 12ffcbcd9b88612251a1e23cb6724e21562f74b8
|
26
bot/src/bot_api/configuration/__init__.py
Normal file
26
bot/src/bot_api/configuration/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "bot_api.configuration"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.8"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="8")
|
22
bot/src/bot_api/configuration/api_settings.py
Normal file
22
bot/src/bot_api/configuration/api_settings.py
Normal file
@ -0,0 +1,22 @@
|
||||
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
|
||||
|
||||
class ApiSettings(ConfigurationModelABC):
|
||||
def __init__(self, port: int = None, host: str = None, redirect_uri: bool = None):
|
||||
ConfigurationModelABC.__init__(self)
|
||||
|
||||
self._port = 80 if port is None else port
|
||||
self._host = "" if host is None else host
|
||||
self._redirect_to_https = False if redirect_uri is None else redirect_uri
|
||||
|
||||
@property
|
||||
def port(self) -> int:
|
||||
return self._port
|
||||
|
||||
@property
|
||||
def host(self) -> str:
|
||||
return self._host
|
||||
|
||||
@property
|
||||
def redirect_to_https(self) -> bool:
|
||||
return self._redirect_to_https
|
39
bot/src/bot_api/configuration/authentication_settings.py
Normal file
39
bot/src/bot_api/configuration/authentication_settings.py
Normal file
@ -0,0 +1,39 @@
|
||||
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
|
||||
|
||||
class AuthenticationSettings(ConfigurationModelABC):
|
||||
def __init__(
|
||||
self,
|
||||
secret_key: str = None,
|
||||
issuer: str = None,
|
||||
audience: str = None,
|
||||
token_expire_time: int = None,
|
||||
refresh_token_expire_time: int = None,
|
||||
):
|
||||
ConfigurationModelABC.__init__(self)
|
||||
|
||||
self._secret_key = "" if secret_key is None else secret_key
|
||||
self._issuer = "" if issuer is None else issuer
|
||||
self._audience = "" if audience is None else audience
|
||||
self._token_expire_time = 0 if token_expire_time is None else token_expire_time
|
||||
self._refresh_token_expire_time = 0 if refresh_token_expire_time is None else refresh_token_expire_time
|
||||
|
||||
@property
|
||||
def secret_key(self) -> str:
|
||||
return self._secret_key
|
||||
|
||||
@property
|
||||
def issuer(self) -> str:
|
||||
return self._issuer
|
||||
|
||||
@property
|
||||
def audience(self) -> str:
|
||||
return self._audience
|
||||
|
||||
@property
|
||||
def token_expire_time(self) -> int:
|
||||
return self._token_expire_time
|
||||
|
||||
@property
|
||||
def refresh_token_expire_time(self) -> int:
|
||||
return self._refresh_token_expire_time
|
@ -0,0 +1,40 @@
|
||||
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
from cpl_query.extension import List
|
||||
|
||||
|
||||
class DiscordAuthenticationSettings(ConfigurationModelABC):
|
||||
def __init__(
|
||||
self,
|
||||
client_secret: str = None,
|
||||
redirect_uri: str = None,
|
||||
scope: list = None,
|
||||
token_url: str = None,
|
||||
auth_url: str = None,
|
||||
):
|
||||
ConfigurationModelABC.__init__(self)
|
||||
|
||||
self._client_secret = "" if client_secret is None else client_secret
|
||||
self._redirect_url = "" if redirect_uri is None else redirect_uri
|
||||
self._scope = List() if scope is None else List(str, scope)
|
||||
self._token_url = "" if token_url is None else token_url
|
||||
self._auth_url = "" if auth_url is None else auth_url
|
||||
|
||||
@property
|
||||
def client_secret(self) -> str:
|
||||
return self._client_secret
|
||||
|
||||
@property
|
||||
def redirect_url(self) -> str:
|
||||
return self._redirect_url
|
||||
|
||||
@property
|
||||
def scope(self) -> List[str]:
|
||||
return self._scope
|
||||
|
||||
@property
|
||||
def token_url(self) -> str:
|
||||
return self._token_url
|
||||
|
||||
@property
|
||||
def auth_url(self) -> str:
|
||||
return self._auth_url
|
12
bot/src/bot_api/configuration/frontend_settings.py
Normal file
12
bot/src/bot_api/configuration/frontend_settings.py
Normal file
@ -0,0 +1,12 @@
|
||||
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
|
||||
|
||||
class FrontendSettings(ConfigurationModelABC):
|
||||
def __init__(self, url: str = None):
|
||||
ConfigurationModelABC.__init__(self)
|
||||
|
||||
self._url = "" if url is None else url
|
||||
|
||||
@property
|
||||
def url(self) -> str:
|
||||
return self._url
|
26
bot/src/bot_api/controller/__init__.py
Normal file
26
bot/src/bot_api/controller/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "bot_api.controller"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.8"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="8")
|
184
bot/src/bot_api/controller/auth_controller.py
Normal file
184
bot/src/bot_api/controller/auth_controller.py
Normal file
@ -0,0 +1,184 @@
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from cpl_core.mailing import EMailClientABC, EMailClientSettings
|
||||
from cpl_translation import TranslatePipe
|
||||
from flask import request, jsonify, Response
|
||||
|
||||
from bot_api.abc.auth_service_abc import AuthServiceABC
|
||||
from bot_api.api import Api
|
||||
from bot_api.filter.auth_user_select_criteria import AuthUserSelectCriteria
|
||||
from bot_api.json_processor import JSONProcessor
|
||||
from bot_api.logging.api_logger import ApiLogger
|
||||
from bot_api.model.auth_user_dto import AuthUserDTO
|
||||
from bot_api.model.reset_password_dto import ResetPasswordDTO
|
||||
from bot_api.model.token_dto import TokenDTO
|
||||
from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO
|
||||
from bot_api.route.route import Route
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_data.model.auth_role_enum import AuthRoleEnum
|
||||
from bot_data.model.technician_config import TechnicianConfig
|
||||
|
||||
|
||||
class AuthController:
|
||||
BasePath = "/api/auth"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
env: ApplicationEnvironmentABC,
|
||||
logger: ApiLogger,
|
||||
t: TranslatePipe,
|
||||
api: Api,
|
||||
mail_settings: EMailClientSettings,
|
||||
mailer: EMailClientABC,
|
||||
auth_service: AuthServiceABC,
|
||||
technician_config: TechnicianConfig,
|
||||
):
|
||||
self._config = config
|
||||
self._env = env
|
||||
self._logger = logger
|
||||
self._t = t
|
||||
self._api = api
|
||||
self._mail_settings = mail_settings
|
||||
self._mailer = mailer
|
||||
self._auth_service = auth_service
|
||||
self._technician_config = technician_config
|
||||
|
||||
@Route.get(f"{BasePath}/users")
|
||||
@Route.authorize(role=AuthRoleEnum.admin)
|
||||
async def get_all_users(self) -> Response:
|
||||
result = await self._auth_service.get_all_auth_users_async()
|
||||
return jsonify(result.select(lambda x: x.to_dict()).to_list())
|
||||
|
||||
@Route.post(f"{BasePath}/users/get/filtered")
|
||||
@Route.authorize(role=AuthRoleEnum.admin)
|
||||
async def get_filtered_users(self) -> Response:
|
||||
dto: AuthUserSelectCriteria = JSONProcessor.process(
|
||||
AuthUserSelectCriteria, request.get_json(force=True, silent=True)
|
||||
)
|
||||
result = await self._auth_service.get_filtered_auth_users_async(dto)
|
||||
result.result = result.result.select(lambda x: x.to_dict()).to_list()
|
||||
return jsonify(result.to_dict())
|
||||
|
||||
@Route.get(f"{BasePath}/users/get/<email>")
|
||||
@Route.authorize
|
||||
async def get_user_from_email(self, email: str) -> Response:
|
||||
result = await self._auth_service.get_auth_user_by_email_async(email)
|
||||
return jsonify(result.to_dict())
|
||||
|
||||
@Route.get(f"{BasePath}/users/find/<email>")
|
||||
@Route.authorize
|
||||
async def find_user_from_email(self, email: str) -> Response:
|
||||
result = await self._auth_service.find_auth_user_by_email_async(email)
|
||||
return jsonify(result.to_dict())
|
||||
|
||||
@Route.post(f"{BasePath}/register")
|
||||
async def register(self):
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
self._technician_config.feature_flags, FeatureFlagsEnum.basic_registration
|
||||
):
|
||||
return
|
||||
|
||||
dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True))
|
||||
self._auth_service.add_auth_user(dto)
|
||||
return "", 200
|
||||
|
||||
@Route.post(f"{BasePath}/resend-confirmation-email-by-mail/<mail>")
|
||||
async def resend_confirmation_email_by_user_id(self, mail: str):
|
||||
await self._auth_service.resend_confirmation_email_by_mail(mail)
|
||||
return "", 200
|
||||
|
||||
@Route.post(f"{BasePath}/register-by-id/<id>")
|
||||
async def register_id(self, id: str):
|
||||
result = await self._auth_service.confirm_email_async(id)
|
||||
return jsonify(result)
|
||||
|
||||
@Route.post(f"{BasePath}/login")
|
||||
async def login(self) -> Response:
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
self._technician_config.feature_flags, FeatureFlagsEnum.basic_login
|
||||
):
|
||||
return jsonify({})
|
||||
|
||||
dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True))
|
||||
result = await self._auth_service.login_async(dto)
|
||||
return jsonify(result.to_dict())
|
||||
|
||||
@Route.get(f"{BasePath}/verify-login")
|
||||
async def verify_login(self):
|
||||
token = None
|
||||
result = False
|
||||
if "Authorization" in request.headers:
|
||||
bearer = request.headers.get("Authorization")
|
||||
token = bearer.split()[1]
|
||||
|
||||
if token is not None:
|
||||
result = self._auth_service.verify_login(token)
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
@Route.post(f"{BasePath}/forgot-password/<email>")
|
||||
async def forgot_password(self, email: str):
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
self._technician_config.feature_flags, FeatureFlagsEnum.basic_login
|
||||
):
|
||||
return "", 409
|
||||
|
||||
await self._auth_service.forgot_password_async(email)
|
||||
return "", 200
|
||||
|
||||
@Route.post(f"{BasePath}/confirm-forgot-password/<id>")
|
||||
async def confirm_forgot_password(self, id: str):
|
||||
result = await self._auth_service.confirm_forgot_password_async(id)
|
||||
return jsonify(result.to_dict())
|
||||
|
||||
@Route.post(f"{BasePath}/reset-password")
|
||||
async def reset_password(self):
|
||||
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||
self._technician_config.feature_flags, FeatureFlagsEnum.basic_login
|
||||
):
|
||||
return "", 409
|
||||
|
||||
dto: ResetPasswordDTO = JSONProcessor.process(ResetPasswordDTO, request.get_json(force=True, silent=True))
|
||||
await self._auth_service.reset_password_async(dto)
|
||||
return "", 200
|
||||
|
||||
@Route.post(f"{BasePath}/update-user")
|
||||
@Route.authorize
|
||||
async def update_user(self):
|
||||
dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True))
|
||||
await self._auth_service.update_user_async(dto)
|
||||
return "", 200
|
||||
|
||||
@Route.post(f"{BasePath}/update-user-as-admin")
|
||||
@Route.authorize(role=AuthRoleEnum.admin)
|
||||
async def update_user_as_admin(self):
|
||||
dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True))
|
||||
await self._auth_service.update_user_as_admin_async(dto)
|
||||
return "", 200
|
||||
|
||||
@Route.post(f"{BasePath}/refresh")
|
||||
async def refresh(self) -> Response:
|
||||
dto: TokenDTO = JSONProcessor.process(TokenDTO, request.get_json(force=True, silent=True))
|
||||
result = await self._auth_service.refresh_async(dto)
|
||||
return jsonify(result.to_dict())
|
||||
|
||||
@Route.post(f"{BasePath}/revoke")
|
||||
async def revoke(self):
|
||||
dto: TokenDTO = JSONProcessor.process(TokenDTO, request.get_json(force=True, silent=True))
|
||||
await self._auth_service.revoke_async(dto)
|
||||
return "", 200
|
||||
|
||||
@Route.post(f"{BasePath}/delete-user")
|
||||
@Route.authorize(role=AuthRoleEnum.admin)
|
||||
async def delete_user(self):
|
||||
dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True))
|
||||
await self._auth_service.delete_auth_user_async(dto)
|
||||
return "", 200
|
||||
|
||||
@Route.post(f"{BasePath}/delete-user-by-mail/<email>")
|
||||
@Route.authorize(role=AuthRoleEnum.admin)
|
||||
async def delete_user_by_mail(self, email: str):
|
||||
await self._auth_service.delete_auth_user_by_email_async(email)
|
||||
return "", 200
|
100
bot/src/bot_api/controller/auth_discord_controller.py
Normal file
100
bot/src/bot_api/controller/auth_discord_controller.py
Normal file
@ -0,0 +1,100 @@
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from cpl_core.mailing import EMailClientABC, EMailClientSettings
|
||||
from cpl_core.utils import CredentialManager
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from cpl_translation import TranslatePipe
|
||||
from flask import jsonify, Response
|
||||
from flask import request
|
||||
from requests_oauthlib import OAuth2Session
|
||||
|
||||
from bot_api.abc.auth_service_abc import AuthServiceABC
|
||||
from bot_api.api import Api
|
||||
from bot_api.configuration.discord_authentication_settings import (
|
||||
DiscordAuthenticationSettings,
|
||||
)
|
||||
from bot_core.exception.service_exception import ServiceException
|
||||
from bot_api.logging.api_logger import ApiLogger
|
||||
from bot_api.model.auth_user_dto import AuthUserDTO
|
||||
from bot_api.route.route import Route
|
||||
from bot_data.model.auth_role_enum import AuthRoleEnum
|
||||
|
||||
# Disable SSL requirement
|
||||
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
|
||||
|
||||
|
||||
class AuthDiscordController:
|
||||
BasePath = "/api/auth/discord"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
auth_settings: DiscordAuthenticationSettings,
|
||||
config: ConfigurationABC,
|
||||
env: ApplicationEnvironmentABC,
|
||||
logger: ApiLogger,
|
||||
bot: DiscordBotServiceABC,
|
||||
t: TranslatePipe,
|
||||
api: Api,
|
||||
mail_settings: EMailClientSettings,
|
||||
mailer: EMailClientABC,
|
||||
auth_service: AuthServiceABC,
|
||||
):
|
||||
self._auth_settings = auth_settings
|
||||
self._config = config
|
||||
self._env = env
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._t = t
|
||||
self._api = api
|
||||
self._mail_settings = mail_settings
|
||||
self._mailer = mailer
|
||||
self._auth_service = auth_service
|
||||
|
||||
def _get_user_from_discord_response(self) -> dict:
|
||||
discord = OAuth2Session(
|
||||
self._bot.user.id,
|
||||
redirect_uri=self._auth_settings.redirect_url,
|
||||
state=request.args.get("state"),
|
||||
scope=self._auth_settings.scope.to_list(),
|
||||
)
|
||||
token = discord.fetch_token(
|
||||
self._auth_settings.token_url,
|
||||
client_secret=CredentialManager.decrypt(self._auth_settings.client_secret),
|
||||
authorization_response=request.url,
|
||||
)
|
||||
discord = OAuth2Session(self._bot.user.id, token=token)
|
||||
return discord.get("https://discordapp.com/api" + "/users/@me").json()
|
||||
|
||||
@Route.get(f"{BasePath}/get-url")
|
||||
async def get_url(self):
|
||||
oauth = OAuth2Session(
|
||||
self._bot.user.id,
|
||||
redirect_uri=self._auth_settings.redirect_url,
|
||||
scope=self._auth_settings.scope.to_list(),
|
||||
)
|
||||
login_url, state = oauth.authorization_url(self._auth_settings.auth_url)
|
||||
return jsonify({"loginUrl": login_url})
|
||||
|
||||
@Route.get(f"{BasePath}/login")
|
||||
async def discord_login(self) -> Response:
|
||||
response = self._get_user_from_discord_response()
|
||||
dto = AuthUserDTO(
|
||||
0,
|
||||
response["username"],
|
||||
response["discriminator"],
|
||||
response["email"],
|
||||
str(uuid.uuid4()),
|
||||
None,
|
||||
AuthRoleEnum.normal,
|
||||
)
|
||||
|
||||
try:
|
||||
result = await self._auth_service.login_discord_async(dto, response["id"])
|
||||
return jsonify(result.to_dict())
|
||||
except ServiceException as e:
|
||||
r = jsonify({"email": dto.email})
|
||||
r.status_code = 403
|
||||
return r
|
44
bot/src/bot_api/controller/graphql_controller.py
Normal file
44
bot/src/bot_api/controller/graphql_controller.py
Normal file
@ -0,0 +1,44 @@
|
||||
from ariadne import graphql_sync
|
||||
from ariadne.explorer import ExplorerPlayground
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from flask import request, jsonify
|
||||
|
||||
from bot_api.logging.api_logger import ApiLogger
|
||||
from bot_api.route.route import Route
|
||||
from bot_graphql.schema import Schema
|
||||
|
||||
|
||||
class GraphQLController:
|
||||
BasePath = f"/api/graphql"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
env: ApplicationEnvironmentABC,
|
||||
logger: ApiLogger,
|
||||
schema: Schema,
|
||||
):
|
||||
self._config = config
|
||||
self._env = env
|
||||
self._logger = logger
|
||||
self._schema = schema
|
||||
|
||||
@Route.get(f"{BasePath}/playground")
|
||||
@Route.authorize(skip_in_dev=True)
|
||||
async def playground(self):
|
||||
if self._env.environment_name != "development":
|
||||
return "", 403
|
||||
|
||||
return ExplorerPlayground().html(None), 200
|
||||
|
||||
@Route.post(f"{BasePath}")
|
||||
@Route.authorize(by_api_key=True)
|
||||
async def graphql(self):
|
||||
data = request.get_json()
|
||||
|
||||
# Note: Passing the request to the context is optional.
|
||||
# In Flask, the current request is always accessible as flask.request
|
||||
success, result = graphql_sync(self._schema.schema, data, context_value=request)
|
||||
|
||||
return jsonify(result), 200 if success else 400
|
95
bot/src/bot_api/controller/gui_controller.py
Normal file
95
bot/src/bot_api/controller/gui_controller.py
Normal file
@ -0,0 +1,95 @@
|
||||
import os
|
||||
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from cpl_core.mailing import EMail, EMailClientABC, EMailClientSettings
|
||||
from cpl_translation import TranslatePipe
|
||||
from flask import jsonify
|
||||
|
||||
from bot_api.api import Api
|
||||
from bot_api.configuration.authentication_settings import AuthenticationSettings
|
||||
from bot_api.logging.api_logger import ApiLogger
|
||||
from bot_api.model.settings_dto import SettingsDTO
|
||||
from bot_api.model.version_dto import VersionDTO
|
||||
from bot_api.route.route import Route
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_data.model.technician_config import TechnicianConfig
|
||||
|
||||
|
||||
class GuiController:
|
||||
BasePath = f"/api/gui"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
env: ApplicationEnvironmentABC,
|
||||
logger: ApiLogger,
|
||||
t: TranslatePipe,
|
||||
api: Api,
|
||||
mail_settings: EMailClientSettings,
|
||||
mailer: EMailClientABC,
|
||||
auth_settings: AuthenticationSettings,
|
||||
):
|
||||
self._config = config
|
||||
self._env = env
|
||||
self._logger = logger
|
||||
self._t = t
|
||||
self._api = api
|
||||
self._mail_settings = mail_settings
|
||||
self._mailer = mailer
|
||||
self._auth_settings = auth_settings
|
||||
|
||||
@Route.get(f"{BasePath}/api-version")
|
||||
async def api_version(self):
|
||||
import bot_api
|
||||
|
||||
version = bot_api.version_info
|
||||
return VersionDTO(version.major, version.minor, version.micro).to_dict()
|
||||
|
||||
@Route.get(f"{BasePath}/settings")
|
||||
@Route.authorize
|
||||
async def settings(self):
|
||||
import bot_api
|
||||
|
||||
version = bot_api.version_info
|
||||
|
||||
return jsonify(
|
||||
SettingsDTO(
|
||||
"",
|
||||
VersionDTO(version.major, version.minor, version.micro),
|
||||
os.path.abspath(os.path.join(self._env.working_directory, "config")),
|
||||
"/",
|
||||
"/",
|
||||
self._auth_settings.token_expire_time,
|
||||
self._auth_settings.refresh_token_expire_time,
|
||||
self._mail_settings.user_name,
|
||||
self._mail_settings.port,
|
||||
self._mail_settings.host,
|
||||
self._mail_settings.user_name,
|
||||
self._mail_settings.user_name,
|
||||
).to_dict()
|
||||
)
|
||||
|
||||
@Route.post(f"{BasePath}/send-test-mail/<email>")
|
||||
@Route.authorize
|
||||
async def send_test_mail(self, email: str):
|
||||
mail = EMail()
|
||||
mail.add_header("Mime-Version: 1.0")
|
||||
mail.add_header("Content-Type: text/plain; charset=utf-8")
|
||||
mail.add_header("Content-Transfer-Encoding: quoted-printable")
|
||||
mail.add_receiver(email)
|
||||
mail.subject = self._t.transform("api.api.test_mail.subject")
|
||||
mail.body = self._t.transform("api.api.test_mail.message").format(
|
||||
self._env.host_name, self._env.environment_name
|
||||
)
|
||||
self._mailer.send_mail(mail)
|
||||
return "", 200
|
||||
|
||||
@Route.get(f"{BasePath}/has-feature-flag/<flag>")
|
||||
async def has_feature_flag(self, flag: str):
|
||||
settings: TechnicianConfig = self._config.get_configuration(TechnicianConfig)
|
||||
return {
|
||||
"key": flag,
|
||||
"value": FeatureFlagsSettings.get_flag_from_dict(settings.feature_flags, FeatureFlagsEnum(flag)),
|
||||
}
|
26
bot/src/bot_api/event/__init__.py
Normal file
26
bot/src/bot_api/event/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "bot_api.event"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.8"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="8")
|
12
bot/src/bot_api/event/bot_api_on_ready_event.py
Normal file
12
bot/src/bot_api/event/bot_api_on_ready_event.py
Normal file
@ -0,0 +1,12 @@
|
||||
from cpl_discord.events import OnReadyABC
|
||||
|
||||
from bot_api.api_thread import ApiThread
|
||||
|
||||
|
||||
class BotApiOnReadyEvent(OnReadyABC):
|
||||
def __init__(self, api: ApiThread):
|
||||
OnReadyABC.__init__(self)
|
||||
self._api = api
|
||||
|
||||
async def on_ready(self):
|
||||
self._api.start()
|
26
bot/src/bot_api/filter/__init__.py
Normal file
26
bot/src/bot_api/filter/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "bot_api.filter"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.8"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="8")
|
21
bot/src/bot_api/filter/auth_user_select_criteria.py
Normal file
21
bot/src/bot_api/filter/auth_user_select_criteria.py
Normal file
@ -0,0 +1,21 @@
|
||||
from bot_api.abc.select_criteria_abc import SelectCriteriaABC
|
||||
|
||||
|
||||
class AuthUserSelectCriteria(SelectCriteriaABC):
|
||||
def __init__(
|
||||
self,
|
||||
page_index: int,
|
||||
page_size: int,
|
||||
sort_direction: str,
|
||||
sort_column: str,
|
||||
first_name: str,
|
||||
last_name: str,
|
||||
email: str,
|
||||
auth_role: int,
|
||||
):
|
||||
SelectCriteriaABC.__init__(self, page_index, page_size, sort_direction, sort_column)
|
||||
|
||||
self.first_name = first_name
|
||||
self.last_name = last_name
|
||||
self.email = email
|
||||
self.auth_role = auth_role
|
26
bot/src/bot_api/filter/discord/__init__.py
Normal file
26
bot/src/bot_api/filter/discord/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "bot_api.filter.discord"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.8"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="8")
|
15
bot/src/bot_api/filter/discord/server_select_criteria.py
Normal file
15
bot/src/bot_api/filter/discord/server_select_criteria.py
Normal file
@ -0,0 +1,15 @@
|
||||
from bot_api.abc.select_criteria_abc import SelectCriteriaABC
|
||||
|
||||
|
||||
class ServerSelectCriteria(SelectCriteriaABC):
|
||||
def __init__(
|
||||
self,
|
||||
page_index: int,
|
||||
page_size: int,
|
||||
sort_direction: str,
|
||||
sort_column: str,
|
||||
name: str,
|
||||
):
|
||||
SelectCriteriaABC.__init__(self, page_index, page_size, sort_direction, sort_column)
|
||||
|
||||
self.name = name
|
42
bot/src/bot_api/json_processor.py
Normal file
42
bot/src/bot_api/json_processor.py
Normal file
@ -0,0 +1,42 @@
|
||||
import enum
|
||||
from inspect import signature, Parameter
|
||||
|
||||
from cpl_core.utils import String
|
||||
|
||||
|
||||
class JSONProcessor:
|
||||
@staticmethod
|
||||
def process(_t: type, values: dict) -> object:
|
||||
args = []
|
||||
|
||||
sig = signature(_t.__init__)
|
||||
for param in sig.parameters.items():
|
||||
parameter = param[1]
|
||||
if parameter.name == "self" or parameter.annotation == Parameter.empty:
|
||||
continue
|
||||
|
||||
name = String.convert_to_camel_case(parameter.name)
|
||||
name = name.replace("Dto", "DTO")
|
||||
name_first_lower = String.first_to_lower(name)
|
||||
if name in values or name_first_lower in values:
|
||||
value = ""
|
||||
if name in values:
|
||||
value = values[name]
|
||||
else:
|
||||
value = values[name_first_lower]
|
||||
|
||||
if isinstance(value, dict):
|
||||
value = JSONProcessor.process(parameter.annotation, value)
|
||||
|
||||
if issubclass(parameter.annotation, enum.Enum):
|
||||
value = parameter.annotation(value)
|
||||
|
||||
args.append(value)
|
||||
|
||||
elif parameter.default != Parameter.empty:
|
||||
args.append(parameter.default)
|
||||
|
||||
else:
|
||||
args.append(None)
|
||||
|
||||
return _t(*args)
|
26
bot/src/bot_api/logging/__init__.py
Normal file
26
bot/src/bot_api/logging/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "bot_api.logging"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.8"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="8")
|
15
bot/src/bot_api/logging/api_logger.py
Normal file
15
bot/src/bot_api/logging/api_logger.py
Normal file
@ -0,0 +1,15 @@
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from cpl_core.time import TimeFormatSettings
|
||||
|
||||
from bot_core.abc.custom_file_logger_abc import CustomFileLoggerABC
|
||||
|
||||
|
||||
class ApiLogger(CustomFileLoggerABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
time_format: TimeFormatSettings,
|
||||
env: ApplicationEnvironmentABC,
|
||||
):
|
||||
CustomFileLoggerABC.__init__(self, "Api", config, time_format, env)
|
26
bot/src/bot_api/model/__init__.py
Normal file
26
bot/src/bot_api/model/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "bot_api.model"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.8"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="8")
|
136
bot/src/bot_api/model/auth_user_dto.py
Normal file
136
bot/src/bot_api/model/auth_user_dto.py
Normal file
@ -0,0 +1,136 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from cpl_query.extension import List
|
||||
|
||||
from bot_api.abc.dto_abc import DtoABC
|
||||
from bot_api.model.user_dto import UserDTO
|
||||
from bot_data.model.auth_role_enum import AuthRoleEnum
|
||||
|
||||
|
||||
class AuthUserDTO(DtoABC):
|
||||
def __init__(
|
||||
self,
|
||||
id: int = None,
|
||||
first_name: str = None,
|
||||
last_name: str = None,
|
||||
email: str = None,
|
||||
password: str = None,
|
||||
confirmation_id: Optional[str] = None,
|
||||
auth_role: AuthRoleEnum = None,
|
||||
users: List[UserDTO] = None,
|
||||
created_at: datetime = None,
|
||||
modified_at: datetime = None,
|
||||
):
|
||||
DtoABC.__init__(self)
|
||||
|
||||
self._id = id
|
||||
self._first_name = first_name
|
||||
self._last_name = last_name
|
||||
self._email = email
|
||||
self._password = password
|
||||
self._is_confirmed = confirmation_id is None
|
||||
self._auth_role = auth_role
|
||||
self._created_at = created_at
|
||||
self._modified_at = modified_at
|
||||
|
||||
if users is None:
|
||||
self._users = List(UserDTO)
|
||||
else:
|
||||
self._users = users
|
||||
|
||||
@property
|
||||
def id(self) -> int:
|
||||
return self._id
|
||||
|
||||
@property
|
||||
def first_name(self) -> str:
|
||||
return self._first_name
|
||||
|
||||
@first_name.setter
|
||||
def first_name(self, value: str):
|
||||
self._first_name = value
|
||||
|
||||
@property
|
||||
def last_name(self) -> str:
|
||||
return self._last_name
|
||||
|
||||
@last_name.setter
|
||||
def last_name(self, value: str):
|
||||
self._last_name = value
|
||||
|
||||
@property
|
||||
def email(self) -> str:
|
||||
return self._email
|
||||
|
||||
@email.setter
|
||||
def email(self, value: str):
|
||||
self._email = value
|
||||
|
||||
@property
|
||||
def password(self) -> str:
|
||||
return self._password
|
||||
|
||||
@password.setter
|
||||
def password(self, value: str):
|
||||
self._password = value
|
||||
|
||||
@property
|
||||
def is_confirmed(self) -> Optional[str]:
|
||||
return self._is_confirmed
|
||||
|
||||
@is_confirmed.setter
|
||||
def is_confirmed(self, value: Optional[str]):
|
||||
self._is_confirmed = value
|
||||
|
||||
@property
|
||||
def auth_role(self) -> AuthRoleEnum:
|
||||
return self._auth_role
|
||||
|
||||
@auth_role.setter
|
||||
def auth_role(self, value: AuthRoleEnum):
|
||||
self._auth_role = value
|
||||
|
||||
@property
|
||||
def users(self) -> List[UserDTO]:
|
||||
return self._users
|
||||
|
||||
@property
|
||||
def created_at(self) -> datetime:
|
||||
return self._created_at
|
||||
|
||||
@property
|
||||
def modified_at(self) -> datetime:
|
||||
return self._modified_at
|
||||
|
||||
def from_dict(self, values: dict):
|
||||
self._id = values["id"]
|
||||
self._first_name = values["firstName"]
|
||||
self._last_name = values["lastName"]
|
||||
self._email = values["email"]
|
||||
self._password = values["password"]
|
||||
self._is_confirmed = values["isConfirmed"]
|
||||
self._auth_role = AuthRoleEnum(values["authRole"])
|
||||
if "users" in values:
|
||||
self._users = List(UserDTO)
|
||||
for u in values["users"]:
|
||||
user = UserDTO()
|
||||
user.from_dict(u)
|
||||
self._users.add(user)
|
||||
|
||||
self._created_at = values["createdAt"]
|
||||
self._modified_at = values["modifiedAt"]
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"id": self._id,
|
||||
"firstName": self._first_name,
|
||||
"lastName": self._last_name,
|
||||
"email": self._email,
|
||||
"password": self._password,
|
||||
"isConfirmed": self._is_confirmed,
|
||||
"authRole": self._auth_role.value,
|
||||
"users": self._users.select(lambda u: u.to_dict()).to_list(),
|
||||
"createdAt": self._created_at,
|
||||
"modifiedAt": self._modified_at,
|
||||
}
|
17
bot/src/bot_api/model/auth_user_filtered_result_dto.py
Normal file
17
bot/src/bot_api/model/auth_user_filtered_result_dto.py
Normal file
@ -0,0 +1,17 @@
|
||||
from cpl_query.extension import List
|
||||
|
||||
from bot_api.abc.dto_abc import DtoABC
|
||||
from bot_data.filtered_result import FilteredResult
|
||||
|
||||
|
||||
class AuthUserFilteredResultDTO(DtoABC, FilteredResult):
|
||||
def __init__(self, result: List = None, total_count: int = 0):
|
||||
DtoABC.__init__(self)
|
||||
FilteredResult.__init__(self, result, total_count)
|
||||
|
||||
def from_dict(self, values: dict):
|
||||
self._result = values["users"]
|
||||
self._total_count = values["totalCount"]
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {"users": self.result, "totalCount": self.total_count}
|
26
bot/src/bot_api/model/discord/__init__.py
Normal file
26
bot/src/bot_api/model/discord/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "bot_api.model.discord"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.8"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="8")
|
56
bot/src/bot_api/model/discord/server_dto.py
Normal file
56
bot/src/bot_api/model/discord/server_dto.py
Normal file
@ -0,0 +1,56 @@
|
||||
from typing import Optional
|
||||
|
||||
from bot_api.abc.dto_abc import DtoABC
|
||||
|
||||
|
||||
class ServerDTO(DtoABC):
|
||||
def __init__(
|
||||
self,
|
||||
server_id: int,
|
||||
discord_id: int,
|
||||
name: str,
|
||||
member_count: int,
|
||||
icon_url: Optional[str],
|
||||
):
|
||||
DtoABC.__init__(self)
|
||||
|
||||
self._server_id = server_id
|
||||
self._discord_id = discord_id
|
||||
self._name = name
|
||||
self._member_count = member_count
|
||||
self._icon_url = icon_url
|
||||
|
||||
@property
|
||||
def server_id(self) -> int:
|
||||
return self._server_id
|
||||
|
||||
@property
|
||||
def discord_id(self) -> int:
|
||||
return self._discord_id
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def member_count(self) -> int:
|
||||
return self._member_count
|
||||
|
||||
@property
|
||||
def icon_url(self) -> Optional[str]:
|
||||
return self._icon_url
|
||||
|
||||
def from_dict(self, values: dict):
|
||||
self._server_id = int(values["serverId"])
|
||||
self._discord_id = int(values["discordId"])
|
||||
self._name = values["name"]
|
||||
self._icon_url = values["iconURL"]
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"serverId": self._server_id,
|
||||
"discordId": self._discord_id,
|
||||
"name": self._name,
|
||||
"memberCount": self._member_count,
|
||||
"iconURL": self._icon_url,
|
||||
}
|
17
bot/src/bot_api/model/discord/server_filtered_result_dto.py
Normal file
17
bot/src/bot_api/model/discord/server_filtered_result_dto.py
Normal file
@ -0,0 +1,17 @@
|
||||
from cpl_query.extension import List
|
||||
|
||||
from bot_api.abc.dto_abc import DtoABC
|
||||
from bot_data.filtered_result import FilteredResult
|
||||
|
||||
|
||||
class ServerFilteredResultDTO(DtoABC, FilteredResult):
|
||||
def __init__(self, result: List = None, total_count: int = 0):
|
||||
DtoABC.__init__(self)
|
||||
FilteredResult.__init__(self, result, total_count)
|
||||
|
||||
def from_dict(self, values: dict):
|
||||
self._result = values["servers"]
|
||||
self._total_count = values["totalCount"]
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {"servers": self.result, "totalCount": self.total_count}
|
18
bot/src/bot_api/model/email_string_dto.py
Normal file
18
bot/src/bot_api/model/email_string_dto.py
Normal file
@ -0,0 +1,18 @@
|
||||
import traceback
|
||||
|
||||
from cpl_core.console import Console
|
||||
|
||||
from bot_api.abc.dto_abc import DtoABC
|
||||
|
||||
|
||||
class EMailStringDTO(DtoABC):
|
||||
def __init__(self, email: str):
|
||||
DtoABC.__init__(self)
|
||||
|
||||
self._email = email
|
||||
|
||||
def from_dict(self, values: dict):
|
||||
self._email = values["email"]
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {"email": self._email}
|
27
bot/src/bot_api/model/error_dto.py
Normal file
27
bot/src/bot_api/model/error_dto.py
Normal file
@ -0,0 +1,27 @@
|
||||
from typing import Optional
|
||||
|
||||
from bot_api.abc.dto_abc import DtoABC
|
||||
from bot_core.exception.service_error_code_enum import ServiceErrorCode
|
||||
|
||||
|
||||
class ErrorDTO(DtoABC):
|
||||
def __init__(self, error_code: Optional[ServiceErrorCode], message: str):
|
||||
DtoABC.__init__(self)
|
||||
|
||||
self._error_code = ServiceErrorCode.Unknown if error_code is None else error_code
|
||||
self._message = message
|
||||
|
||||
@property
|
||||
def error_code(self) -> ServiceErrorCode:
|
||||
return self._error_code
|
||||
|
||||
@property
|
||||
def message(self) -> str:
|
||||
return self._message
|
||||
|
||||
def from_dict(self, values: dict):
|
||||
self._error_code = values["ErrorCode"]
|
||||
self._message = values["Message"]
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {"errorCode": int(self._error_code.value), "message": self._message}
|
40
bot/src/bot_api/model/o_auth_dto.py
Normal file
40
bot/src/bot_api/model/o_auth_dto.py
Normal file
@ -0,0 +1,40 @@
|
||||
from typing import Optional
|
||||
|
||||
from bot_api.abc.dto_abc import DtoABC
|
||||
from bot_api.model.auth_user_dto import AuthUserDTO
|
||||
from bot_data.model.auth_role_enum import AuthRoleEnum
|
||||
|
||||
|
||||
class OAuthDTO(DtoABC):
|
||||
def __init__(
|
||||
self,
|
||||
user: AuthUserDTO,
|
||||
o_auth_id: Optional[str],
|
||||
):
|
||||
DtoABC.__init__(self)
|
||||
|
||||
self._user = user
|
||||
self._oauth_id = o_auth_id
|
||||
|
||||
@property
|
||||
def user(self) -> AuthUserDTO:
|
||||
return self._user
|
||||
|
||||
@user.setter
|
||||
def user(self, value: AuthUserDTO):
|
||||
self._user = value
|
||||
|
||||
@property
|
||||
def oauth_id(self) -> Optional[str]:
|
||||
return self._oauth_id
|
||||
|
||||
@oauth_id.setter
|
||||
def oauth_id(self, value: Optional[str]):
|
||||
self._oauth_id = value
|
||||
|
||||
def from_dict(self, values: dict):
|
||||
self._user = AuthUserDTO().from_dict(values["user"])
|
||||
self._oauth_id = values["oAuthId"]
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {"user": self._user.to_dict(), "oAuthId": self._oauth_id}
|
28
bot/src/bot_api/model/reset_password_dto.py
Normal file
28
bot/src/bot_api/model/reset_password_dto.py
Normal file
@ -0,0 +1,28 @@
|
||||
import traceback
|
||||
|
||||
from cpl_core.console import Console
|
||||
|
||||
from bot_api.abc.dto_abc import DtoABC
|
||||
|
||||
|
||||
class ResetPasswordDTO(DtoABC):
|
||||
def __init__(self, id: str, password: str):
|
||||
DtoABC.__init__(self)
|
||||
|
||||
self._id = id
|
||||
self._password = password
|
||||
|
||||
@property
|
||||
def id(self) -> str:
|
||||
return self._id
|
||||
|
||||
@property
|
||||
def password(self) -> str:
|
||||
return self._password
|
||||
|
||||
def from_dict(self, values: dict):
|
||||
self._id = values["id"]
|
||||
self._password = values["password"]
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {"id": self._id, "password": self._password}
|
66
bot/src/bot_api/model/settings_dto.py
Normal file
66
bot/src/bot_api/model/settings_dto.py
Normal file
@ -0,0 +1,66 @@
|
||||
from bot_api.abc.dto_abc import DtoABC
|
||||
from bot_api.model.version_dto import VersionDTO
|
||||
|
||||
|
||||
class SettingsDTO(DtoABC):
|
||||
def __init__(
|
||||
self,
|
||||
web_version: str,
|
||||
api_version: VersionDTO,
|
||||
config_path: str,
|
||||
web_base_url: str,
|
||||
api_base_url: str,
|
||||
token_expire_time: int,
|
||||
refresh_token_expire_time: int,
|
||||
mail_user: str,
|
||||
mail_port: int,
|
||||
mail_host: str,
|
||||
mail_transceiver: str,
|
||||
mail_transceiver_address: str,
|
||||
):
|
||||
DtoABC.__init__(self)
|
||||
|
||||
self._web_version = web_version
|
||||
self._api_version = api_version
|
||||
self._config_path = config_path
|
||||
self._web_base_url = web_base_url
|
||||
self._api_base_url = api_base_url
|
||||
|
||||
self._token_expire_time = token_expire_time
|
||||
self._refresh_token_expire_time = refresh_token_expire_time
|
||||
|
||||
self._mail_user = mail_user
|
||||
self._mail_port = mail_port
|
||||
self._mail_host = mail_host
|
||||
self._mail_transceiver = mail_transceiver
|
||||
self._mail_transceiver_address = mail_transceiver_address
|
||||
|
||||
def from_dict(self, values: dict):
|
||||
self._web_version = values["webVersion"]
|
||||
self._api_version.from_dict(values["apiVersion"])
|
||||
self._config_path = values["configPath"]
|
||||
self._web_base_url = values["webBaseURL"]
|
||||
self._api_base_url = values["apiBaseURL"]
|
||||
self._token_expire_time = values["tokenExpireTime"]
|
||||
self._refresh_token_expire_time = values["refreshTokenExpireTime"]
|
||||
self._mail_user = values["mailUser"]
|
||||
self._mail_port = values["mailPort"]
|
||||
self._mail_host = values["mailHost"]
|
||||
self._mail_transceiver = values["mailTransceiver"]
|
||||
self._mail_transceiver_address = values["mailTransceiverAddress"]
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"webVersion": self._web_version,
|
||||
"apiVersion": self._api_version.str,
|
||||
"configPath": self._config_path,
|
||||
"webBaseURL": self._web_base_url,
|
||||
"apiBaseURL": self._api_base_url,
|
||||
"tokenExpireTime": self._token_expire_time,
|
||||
"refreshTokenExpireTime": self._refresh_token_expire_time,
|
||||
"mailUser": self._mail_user,
|
||||
"mailPort": self._mail_port,
|
||||
"mailHost": self._mail_host,
|
||||
"mailTransceiver": self._mail_transceiver,
|
||||
"mailTransceiverAddress": self._mail_transceiver_address,
|
||||
}
|
34
bot/src/bot_api/model/token_dto.py
Normal file
34
bot/src/bot_api/model/token_dto.py
Normal file
@ -0,0 +1,34 @@
|
||||
from bot_api.abc.dto_abc import DtoABC
|
||||
|
||||
|
||||
class TokenDTO(DtoABC):
|
||||
def __init__(self, token: str, refresh_token: str, first_login: bool = False):
|
||||
DtoABC.__init__(self)
|
||||
|
||||
self._token = token
|
||||
self._refresh_token = refresh_token
|
||||
self._first_login = first_login
|
||||
|
||||
@property
|
||||
def token(self) -> str:
|
||||
return self._token
|
||||
|
||||
@property
|
||||
def refresh_token(self) -> str:
|
||||
return self._refresh_token
|
||||
|
||||
@property
|
||||
def first_login(self) -> bool:
|
||||
return self._first_login
|
||||
|
||||
def from_dict(self, values: dict):
|
||||
self._token = values["token"]
|
||||
self._refresh_token = values["refreshToken"]
|
||||
self._first_login = values["firstLogin"]
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"token": self._token,
|
||||
"refreshToken": self._refresh_token,
|
||||
"firstLogin": self._first_login,
|
||||
}
|
44
bot/src/bot_api/model/update_auth_user_dto.py
Normal file
44
bot/src/bot_api/model/update_auth_user_dto.py
Normal file
@ -0,0 +1,44 @@
|
||||
import traceback
|
||||
|
||||
from cpl_core.console import Console
|
||||
|
||||
from bot_api.abc.dto_abc import DtoABC
|
||||
from bot_api.model.auth_user_dto import AuthUserDTO
|
||||
|
||||
|
||||
class UpdateAuthUserDTO(DtoABC):
|
||||
def __init__(
|
||||
self,
|
||||
auth_user_dto: AuthUserDTO,
|
||||
new_auth_user_dto: AuthUserDTO,
|
||||
change_password: bool = False,
|
||||
):
|
||||
DtoABC.__init__(self)
|
||||
|
||||
self._auth_user = auth_user_dto
|
||||
self._new_auth_user = new_auth_user_dto
|
||||
self._change_password = change_password
|
||||
|
||||
@property
|
||||
def auth_user(self) -> AuthUserDTO:
|
||||
return self._auth_user
|
||||
|
||||
@property
|
||||
def new_auth_user(self) -> AuthUserDTO:
|
||||
return self._new_auth_user
|
||||
|
||||
@property
|
||||
def change_password(self) -> bool:
|
||||
return self._change_password
|
||||
|
||||
def from_dict(self, values: dict):
|
||||
self._auth_user = AuthUserDTO().from_dict(values["authUser"])
|
||||
self._new_auth_user = AuthUserDTO().from_dict(values["newAuthUser"])
|
||||
self._change_password = False if "changePassword" not in values else bool(values["changePassword"])
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"authUser": self._auth_user,
|
||||
"newAuthUser": self._new_auth_user,
|
||||
"changePassword": self._change_password,
|
||||
}
|
76
bot/src/bot_api/model/user_dto.py
Normal file
76
bot/src/bot_api/model/user_dto.py
Normal file
@ -0,0 +1,76 @@
|
||||
from typing import Optional
|
||||
|
||||
from bot_api.abc.dto_abc import DtoABC
|
||||
from bot_data.model.server import Server
|
||||
|
||||
|
||||
class UserDTO(DtoABC):
|
||||
def __init__(
|
||||
self,
|
||||
id: int = None,
|
||||
dc_id: int = None,
|
||||
xp: int = None,
|
||||
server: Optional[Server] = None,
|
||||
is_technician: Optional[bool] = None,
|
||||
is_admin: Optional[bool] = None,
|
||||
is_moderator: Optional[bool] = None,
|
||||
):
|
||||
DtoABC.__init__(self)
|
||||
|
||||
self._user_id = id
|
||||
self._discord_id = dc_id
|
||||
self._xp = xp
|
||||
self._server = server
|
||||
|
||||
self._is_technician = is_technician
|
||||
self._is_admin = is_admin
|
||||
self._is_moderator = is_moderator
|
||||
|
||||
@property
|
||||
def user_id(self) -> int:
|
||||
return self._user_id
|
||||
|
||||
@property
|
||||
def discord_id(self) -> int:
|
||||
return self._discord_id
|
||||
|
||||
@property
|
||||
def xp(self) -> int:
|
||||
return self._xp
|
||||
|
||||
@xp.setter
|
||||
def xp(self, value: int):
|
||||
self._xp = value
|
||||
|
||||
@property
|
||||
def server(self) -> Optional[Server]:
|
||||
return self._server
|
||||
|
||||
@property
|
||||
def is_technician(self) -> bool:
|
||||
return self._is_technician if self._is_technician is not None else False
|
||||
|
||||
@property
|
||||
def is_admin(self) -> bool:
|
||||
return self._is_admin if self._is_admin is not None else False
|
||||
|
||||
@property
|
||||
def is_moderator(self) -> bool:
|
||||
return self._is_moderator if self._is_moderator is not None else False
|
||||
|
||||
def from_dict(self, values: dict):
|
||||
self._user_id = values["id"]
|
||||
self._discord_id = values["dcId"]
|
||||
self._xp = values["xp"]
|
||||
self._server = values["server"]
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"id": self._user_id,
|
||||
"dcId": self._discord_id,
|
||||
"xp": self._xp,
|
||||
"server": self._server.id,
|
||||
"isTechnician": self.is_technician,
|
||||
"isAdmin": self.is_admin,
|
||||
"isModerator": self.is_moderator,
|
||||
}
|
42
bot/src/bot_api/model/version_dto.py
Normal file
42
bot/src/bot_api/model/version_dto.py
Normal file
@ -0,0 +1,42 @@
|
||||
import traceback
|
||||
|
||||
from cpl_core.console import Console
|
||||
|
||||
from bot_api.abc.dto_abc import DtoABC
|
||||
|
||||
|
||||
class VersionDTO(DtoABC):
|
||||
def __init__(self, major: str = None, minor: str = None, micro: str = None):
|
||||
DtoABC.__init__(self)
|
||||
|
||||
self._major = major
|
||||
self._minor = minor
|
||||
self._micro = micro
|
||||
|
||||
@property
|
||||
def major(self) -> str:
|
||||
return self._major
|
||||
|
||||
@property
|
||||
def minor(self) -> str:
|
||||
return self._minor
|
||||
|
||||
@property
|
||||
def micro(self) -> str:
|
||||
return self._micro
|
||||
|
||||
@property
|
||||
def str(self) -> str:
|
||||
return f"{self._major}.{self._minor}.{self._micro}"
|
||||
|
||||
def from_dict(self, values: dict):
|
||||
self._major = values["major"]
|
||||
self._minor = values["minor"]
|
||||
self._micro = values["micro"]
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"major": self._major,
|
||||
"minor": self._minor,
|
||||
"micro": self._micro,
|
||||
}
|
26
bot/src/bot_api/route/__init__.py
Normal file
26
bot/src/bot_api/route/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "bot_api.route"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.8"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="8")
|
171
bot/src/bot_api/route/route.py
Normal file
171
bot/src/bot_api/route/route.py
Normal file
@ -0,0 +1,171 @@
|
||||
import functools
|
||||
from functools import wraps
|
||||
from typing import Optional, Callable, Union
|
||||
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from flask import request, jsonify
|
||||
from flask_cors import cross_origin
|
||||
|
||||
from bot_api.abc.auth_service_abc import AuthServiceABC
|
||||
from bot_core.exception.service_error_code_enum import ServiceErrorCode
|
||||
from bot_core.exception.service_exception import ServiceException
|
||||
from bot_api.model.error_dto import ErrorDTO
|
||||
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
|
||||
from bot_data.model.auth_role_enum import AuthRoleEnum
|
||||
from bot_data.model.auth_user import AuthUser
|
||||
|
||||
|
||||
class Route:
|
||||
registered_routes = {}
|
||||
|
||||
_auth_users: Optional[AuthUserRepositoryABC] = None
|
||||
_auth: Optional[AuthServiceABC] = None
|
||||
_env = "production"
|
||||
|
||||
@classmethod
|
||||
@ServiceProviderABC.inject
|
||||
def init_authorize(
|
||||
cls,
|
||||
env: ApplicationEnvironmentABC,
|
||||
auth_users: AuthUserRepositoryABC,
|
||||
auth: AuthServiceABC,
|
||||
):
|
||||
cls._auth_users = auth_users
|
||||
cls._auth = auth
|
||||
cls._env = env.environment_name
|
||||
|
||||
@classmethod
|
||||
def get_user(cls) -> Optional[Union[str, AuthUser]]:
|
||||
token = None
|
||||
api_key = None
|
||||
authorization = request.headers.get("Authorization").split()
|
||||
match authorization[0]:
|
||||
case "Bearer":
|
||||
token = authorization[1]
|
||||
case "API-Key":
|
||||
api_key = authorization[1]
|
||||
|
||||
if api_key is not None:
|
||||
return "system"
|
||||
|
||||
if token is None:
|
||||
return None
|
||||
|
||||
jwt = cls._auth.decode_token(token)
|
||||
user = cls._auth_users.get_auth_user_by_email(jwt["email"])
|
||||
return user
|
||||
|
||||
@classmethod
|
||||
def authorize(
|
||||
cls,
|
||||
f: Callable = None,
|
||||
role: AuthRoleEnum = None,
|
||||
skip_in_dev=False,
|
||||
by_api_key=False,
|
||||
):
|
||||
if f is None:
|
||||
return functools.partial(cls.authorize, role=role, skip_in_dev=skip_in_dev, by_api_key=by_api_key)
|
||||
|
||||
@wraps(f)
|
||||
async def decorator(*args, **kwargs):
|
||||
if skip_in_dev and cls._env == "development":
|
||||
return await f(*args, **kwargs)
|
||||
|
||||
token = None
|
||||
api_key = None
|
||||
if "Authorization" in request.headers:
|
||||
if " " not in request.headers.get("Authorization"):
|
||||
ex = ServiceException(ServiceErrorCode.Unauthorized, f"Token not set")
|
||||
error = ErrorDTO(ex.error_code, ex.message)
|
||||
return jsonify(error.to_dict()), 401
|
||||
|
||||
authorization = request.headers.get("Authorization").split()
|
||||
match authorization[0]:
|
||||
case "Bearer":
|
||||
token = authorization[1]
|
||||
case "API-Key":
|
||||
api_key = authorization[1]
|
||||
|
||||
if api_key is not None:
|
||||
valid = False
|
||||
try:
|
||||
valid = cls._auth.verify_api_key(api_key)
|
||||
except ServiceException as e:
|
||||
error = ErrorDTO(e.error_code, e.message)
|
||||
return jsonify(error.to_dict()), 403
|
||||
except Exception as e:
|
||||
return jsonify(e), 500
|
||||
|
||||
if not valid:
|
||||
ex = ServiceException(ServiceErrorCode.Unauthorized, f"API-Key invalid")
|
||||
error = ErrorDTO(ex.error_code, ex.message)
|
||||
return jsonify(error.to_dict()), 401
|
||||
|
||||
return await f(*args, **kwargs)
|
||||
|
||||
if token is None:
|
||||
ex = ServiceException(ServiceErrorCode.Unauthorized, f"Token not set")
|
||||
error = ErrorDTO(ex.error_code, ex.message)
|
||||
return jsonify(error.to_dict()), 401
|
||||
|
||||
if cls._auth_users is None or cls._auth is None:
|
||||
ex = ServiceException(ServiceErrorCode.Unauthorized, f"Authorize is not initialized")
|
||||
error = ErrorDTO(ex.error_code, ex.message)
|
||||
return jsonify(error.to_dict()), 401
|
||||
|
||||
if not cls._auth.verify_login(token):
|
||||
ex = ServiceException(ServiceErrorCode.Unauthorized, f"Token expired")
|
||||
error = ErrorDTO(ex.error_code, ex.message)
|
||||
return jsonify(error.to_dict()), 401
|
||||
|
||||
token = cls._auth.decode_token(token)
|
||||
if token is None or "email" not in token:
|
||||
ex = ServiceException(ServiceErrorCode.Unauthorized, f"Token invalid")
|
||||
error = ErrorDTO(ex.error_code, ex.message)
|
||||
return jsonify(error.to_dict()), 401
|
||||
|
||||
user = cls._auth_users.get_auth_user_by_email(token["email"])
|
||||
if user is None:
|
||||
ex = ServiceException(ServiceErrorCode.Unauthorized, f"Token invalid")
|
||||
error = ErrorDTO(ex.error_code, ex.message)
|
||||
return jsonify(error.to_dict()), 401
|
||||
|
||||
if role is not None and user.auth_role.value < role.value:
|
||||
ex = ServiceException(ServiceErrorCode.Unauthorized, f"Role {role} required")
|
||||
error = ErrorDTO(ex.error_code, ex.message)
|
||||
return jsonify(error.to_dict()), 403
|
||||
|
||||
return await f(*args, **kwargs)
|
||||
|
||||
return decorator
|
||||
|
||||
@classmethod
|
||||
def route(cls, path=None, **kwargs):
|
||||
# simple decorator for class based views
|
||||
def inner(fn):
|
||||
cross_origin(fn)
|
||||
cls.registered_routes[path] = (fn, kwargs)
|
||||
return fn
|
||||
|
||||
return inner
|
||||
|
||||
@classmethod
|
||||
def get(cls, path=None, **kwargs):
|
||||
return cls.route(path, methods=["GET"], **kwargs)
|
||||
|
||||
@classmethod
|
||||
def post(cls, path=None, **kwargs):
|
||||
return cls.route(path, methods=["POST"], **kwargs)
|
||||
|
||||
@classmethod
|
||||
def head(cls, path=None, **kwargs):
|
||||
return cls.route(path, methods=["HEAD"], **kwargs)
|
||||
|
||||
@classmethod
|
||||
def put(cls, path=None, **kwargs):
|
||||
return cls.route(path, methods=["PUT"], **kwargs)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, path=None, **kwargs):
|
||||
return cls.route(path, methods=["DELETE"], **kwargs)
|
26
bot/src/bot_api/service/__init__.py
Normal file
26
bot/src/bot_api/service/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "bot_api.service"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.8"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="8")
|
605
bot/src/bot_api/service/auth_service.py
Normal file
605
bot/src/bot_api/service/auth_service.py
Normal file
@ -0,0 +1,605 @@
|
||||
import hashlib
|
||||
import re
|
||||
import uuid
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from threading import Thread
|
||||
from typing import Optional
|
||||
|
||||
import jwt
|
||||
from cpl_core.database.context import DatabaseContextABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from cpl_core.mailing import EMail, EMailClientABC
|
||||
from cpl_core.utils import CredentialManager
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from cpl_query.extension import List
|
||||
from cpl_translation import TranslatePipe
|
||||
from flask import request
|
||||
|
||||
from bot_api.abc.auth_service_abc import AuthServiceABC
|
||||
from bot_api.configuration.authentication_settings import AuthenticationSettings
|
||||
from bot_api.configuration.frontend_settings import FrontendSettings
|
||||
from bot_core.exception.service_error_code_enum import ServiceErrorCode
|
||||
from bot_core.exception.service_exception import ServiceException
|
||||
from bot_api.filter.auth_user_select_criteria import AuthUserSelectCriteria
|
||||
from bot_api.logging.api_logger import ApiLogger
|
||||
from bot_api.model.auth_user_dto import AuthUserDTO
|
||||
from bot_api.model.auth_user_filtered_result_dto import AuthUserFilteredResultDTO
|
||||
from bot_api.model.email_string_dto import EMailStringDTO
|
||||
from bot_api.model.o_auth_dto import OAuthDTO
|
||||
from bot_api.model.reset_password_dto import ResetPasswordDTO
|
||||
from bot_api.model.token_dto import TokenDTO
|
||||
from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO
|
||||
from bot_api.transformer.auth_user_transformer import AuthUserTransformer as AUT
|
||||
from bot_data.abc.api_key_repository_abc import ApiKeyRepositoryABC
|
||||
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.abc.user_repository_abc import UserRepositoryABC
|
||||
from bot_data.model.api_key import ApiKey
|
||||
from bot_data.model.auth_role_enum import AuthRoleEnum
|
||||
from bot_data.model.auth_user import AuthUser
|
||||
from bot_data.model.auth_user_users_relation import AuthUserUsersRelation
|
||||
|
||||
_email_regex = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"
|
||||
|
||||
|
||||
class AuthService(AuthServiceABC):
|
||||
def __init__(
|
||||
self,
|
||||
env: ApplicationEnvironmentABC,
|
||||
logger: ApiLogger,
|
||||
bot: DiscordBotServiceABC,
|
||||
db: DatabaseContextABC,
|
||||
auth_users: AuthUserRepositoryABC,
|
||||
api_keys: ApiKeyRepositoryABC,
|
||||
users: UserRepositoryABC,
|
||||
servers: ServerRepositoryABC,
|
||||
mailer: EMailClientABC,
|
||||
t: TranslatePipe,
|
||||
auth_settings: AuthenticationSettings,
|
||||
frontend_settings: FrontendSettings,
|
||||
):
|
||||
AuthServiceABC.__init__(self)
|
||||
|
||||
self._environment = env
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._db = db
|
||||
self._auth_users = auth_users
|
||||
self._api_keys = api_keys
|
||||
self._users = users
|
||||
self._servers = servers
|
||||
self._mailer = mailer
|
||||
self._t = t
|
||||
self._auth_settings = auth_settings
|
||||
self._frontend_settings = frontend_settings
|
||||
|
||||
@staticmethod
|
||||
def _hash_sha256(password: str, salt: str) -> str:
|
||||
return hashlib.sha256(f"{password}{salt}".encode("utf-8")).hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def _is_email_valid(email: str) -> bool:
|
||||
if email is None:
|
||||
raise False
|
||||
|
||||
if re.fullmatch(_email_regex, email) is not None:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _get_api_key_str(self, api_key: ApiKey) -> str:
|
||||
return hashlib.sha256(
|
||||
f"{api_key.identifier}:{api_key.key}+{self._auth_settings.secret_key}".encode("utf-8")
|
||||
).hexdigest()
|
||||
|
||||
def generate_token(self, user: AuthUser) -> str:
|
||||
token = jwt.encode(
|
||||
payload={
|
||||
"user_id": user.id,
|
||||
"email": user.email,
|
||||
"role": user.auth_role.value,
|
||||
"exp": datetime.now(tz=timezone.utc) + timedelta(days=self._auth_settings.token_expire_time),
|
||||
"iss": self._auth_settings.issuer,
|
||||
"aud": self._auth_settings.audience,
|
||||
},
|
||||
key=CredentialManager.decrypt(self._auth_settings.secret_key),
|
||||
)
|
||||
|
||||
return token
|
||||
|
||||
def decode_token(self, token: str) -> dict:
|
||||
return jwt.decode(
|
||||
token,
|
||||
key=CredentialManager.decrypt(self._auth_settings.secret_key),
|
||||
issuer=self._auth_settings.issuer,
|
||||
audience=self._auth_settings.audience,
|
||||
algorithms=["HS256"],
|
||||
)
|
||||
|
||||
def get_decoded_token_from_request(self) -> dict:
|
||||
token = None
|
||||
if "Authorization" in request.headers:
|
||||
bearer = request.headers.get("Authorization")
|
||||
token = bearer.split()[1]
|
||||
|
||||
if token is None:
|
||||
raise ServiceException(ServiceErrorCode.Unauthorized, f"Token not set")
|
||||
|
||||
return jwt.decode(
|
||||
token,
|
||||
key=CredentialManager.decrypt(self._auth_settings.secret_key),
|
||||
issuer=self._auth_settings.issuer,
|
||||
audience=self._auth_settings.audience,
|
||||
algorithms=["HS256"],
|
||||
)
|
||||
|
||||
def find_decoded_token_from_request(self) -> Optional[dict]:
|
||||
token = None
|
||||
if "Authorization" in request.headers:
|
||||
bearer = request.headers.get("Authorization")
|
||||
token = bearer.split()[1]
|
||||
|
||||
return (
|
||||
jwt.decode(
|
||||
token,
|
||||
key=CredentialManager.decrypt(self._auth_settings.secret_key),
|
||||
issuer=self._auth_settings.issuer,
|
||||
audience=self._auth_settings.audience,
|
||||
algorithms=["HS256"],
|
||||
)
|
||||
if token is not None
|
||||
else None
|
||||
)
|
||||
|
||||
def _create_and_save_refresh_token(self, user: AuthUser) -> str:
|
||||
token = str(uuid.uuid4())
|
||||
user.refresh_token = token
|
||||
user.refresh_token_expire_time = datetime.now() + timedelta(days=self._auth_settings.refresh_token_expire_time)
|
||||
self._auth_users.update_auth_user(user)
|
||||
self._db.save_changes()
|
||||
return token
|
||||
|
||||
def _send_link_mail(self, email: str, subject: str, message: str):
|
||||
url = self._frontend_settings.url
|
||||
if not url.endswith("/"):
|
||||
url = f"{url}/"
|
||||
|
||||
self._mailer.connect()
|
||||
mail = EMail()
|
||||
mail.add_header("Mime-Version: 1.0")
|
||||
mail.add_header("Content-Type: text/plain; charset=utf-8")
|
||||
mail.add_header("Content-Transfer-Encoding: quoted-printable")
|
||||
mail.add_receiver(str(email))
|
||||
mail.subject = subject
|
||||
mail.body = f"{message}\r\n{self._t.transform('api.mail.automatic_mail').format(self._environment.application_name, self._environment.environment_name, self._environment.host_name)}"
|
||||
|
||||
thr = Thread(target=self._mailer.send_mail, args=[mail])
|
||||
thr.start()
|
||||
|
||||
def _send_confirmation_id_to_user(self, user: AuthUser):
|
||||
url = self._frontend_settings.url
|
||||
if not url.endswith("/"):
|
||||
url = f"{url}/"
|
||||
|
||||
self._send_link_mail(
|
||||
user.email,
|
||||
self._t.transform("api.auth.confirmation.subject").format(user.first_name, user.last_name),
|
||||
self._t.transform("api.auth.confirmation.message").format(url, user.confirmation_id),
|
||||
)
|
||||
|
||||
def _send_forgot_password_id_to_user(self, user: AuthUser):
|
||||
url = self._frontend_settings.url
|
||||
if not url.endswith("/"):
|
||||
url = f"{url}/"
|
||||
|
||||
self._send_link_mail(
|
||||
user.email,
|
||||
self._t.transform("api.auth.forgot_password.subject").format(user.first_name, user.last_name),
|
||||
self._t.transform("api.auth.forgot_password.message").format(url, user.forgot_password_id),
|
||||
)
|
||||
|
||||
async def get_all_auth_users_async(self) -> List[AuthUserDTO]:
|
||||
result = self._auth_users.get_all_auth_users().select(lambda x: AUT.to_dto(x))
|
||||
return List(AuthUserDTO, result)
|
||||
|
||||
async def get_filtered_auth_users_async(self, criteria: AuthUserSelectCriteria) -> AuthUserFilteredResultDTO:
|
||||
users = self._auth_users.get_filtered_auth_users(criteria)
|
||||
result = users.result.select(lambda x: AUT.to_dto(x))
|
||||
|
||||
return AuthUserFilteredResultDTO(List(AuthUserDTO, result), users.total_count)
|
||||
|
||||
async def get_auth_user_by_email_async(self, email: str, with_password: bool = False) -> AuthUserDTO:
|
||||
try:
|
||||
# todo: check if logged in user is admin then send mail
|
||||
user = self._auth_users.get_auth_user_by_email(email)
|
||||
return AUT.to_dto(user, password=user.password if with_password else None)
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"AuthUser not found", e)
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, f"User not found {email}")
|
||||
|
||||
async def find_auth_user_by_email_async(self, email: str) -> Optional[AuthUser]:
|
||||
user = self._auth_users.find_auth_user_by_email(email)
|
||||
return AUT.to_dto(user) if user is not None else None
|
||||
|
||||
def add_auth_user(self, user_dto: AuthUserDTO):
|
||||
db_user = self._auth_users.find_auth_user_by_email(user_dto.email)
|
||||
if db_user is not None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidUser, "User already exists")
|
||||
|
||||
user = AUT.to_db(user_dto)
|
||||
if self._auth_users.get_all_auth_users().count() == 0:
|
||||
user.auth_role = AuthRoleEnum.admin
|
||||
|
||||
user.password_salt = uuid.uuid4()
|
||||
user.password = self._hash_sha256(user_dto.password, user.password_salt)
|
||||
if not self._is_email_valid(user.email):
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, "Invalid E-Mail address")
|
||||
|
||||
try:
|
||||
user.confirmation_id = uuid.uuid4()
|
||||
self._auth_users.add_auth_user(user)
|
||||
self._send_confirmation_id_to_user(user)
|
||||
self._db.save_changes()
|
||||
self._logger.info(__name__, f"Added auth user with E-Mail: {user_dto.email}")
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Cannot add user with E-Mail {user_dto.email}", e)
|
||||
raise ServiceException(ServiceErrorCode.UnableToAdd, "Invalid E-Mail")
|
||||
|
||||
async def add_auth_user_by_oauth_async(self, dto: OAuthDTO):
|
||||
db_user = self._auth_users.find_auth_user_by_email(dto.user.email)
|
||||
|
||||
if db_user is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidUser, "User not found")
|
||||
|
||||
if db_user.oauth_id != dto.oauth_id:
|
||||
raise ServiceException(ServiceErrorCode.InvalidUser, "Wrong OAuthId")
|
||||
|
||||
try:
|
||||
db_user.first_name = dto.user.first_name
|
||||
db_user.last_name = dto.user.last_name
|
||||
db_user.password_salt = uuid.uuid4()
|
||||
db_user.password = self._hash_sha256(dto.user.password, db_user.password_salt)
|
||||
db_user.oauth_id = None
|
||||
db_user.confirmation_id = uuid.uuid4()
|
||||
self._send_confirmation_id_to_user(db_user)
|
||||
self._auth_users.update_auth_user(db_user)
|
||||
self._logger.info(__name__, f"Added auth user with E-Mail: {dto.user.email}")
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Cannot add user with E-Mail {dto.user.email}", e)
|
||||
raise ServiceException(ServiceErrorCode.UnableToAdd, "Invalid E-Mail")
|
||||
|
||||
self._db.save_changes()
|
||||
|
||||
async def update_user_async(self, update_user_dto: UpdateAuthUserDTO):
|
||||
if update_user_dto is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, f"User is empty")
|
||||
|
||||
if update_user_dto.auth_user is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, f"Existing user is empty")
|
||||
|
||||
if update_user_dto.new_auth_user is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, f"New user is empty")
|
||||
|
||||
if not self._is_email_valid(update_user_dto.auth_user.email) or not self._is_email_valid(
|
||||
update_user_dto.new_auth_user.email
|
||||
):
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, f"Invalid E-Mail")
|
||||
|
||||
user = self._auth_users.find_auth_user_by_email(update_user_dto.auth_user.email)
|
||||
if user is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidUser, "User not found")
|
||||
|
||||
if user.confirmation_id is not None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidUser, "E-Mail not confirmed")
|
||||
|
||||
# update first name
|
||||
if (
|
||||
update_user_dto.new_auth_user.first_name is not None
|
||||
and update_user_dto.auth_user.first_name != update_user_dto.new_auth_user.first_name
|
||||
):
|
||||
user.first_name = update_user_dto.new_auth_user.first_name
|
||||
|
||||
# update last name
|
||||
if (
|
||||
update_user_dto.new_auth_user.last_name is not None
|
||||
and update_user_dto.new_auth_user.last_name != ""
|
||||
and update_user_dto.auth_user.last_name != update_user_dto.new_auth_user.last_name
|
||||
):
|
||||
user.last_name = update_user_dto.new_auth_user.last_name
|
||||
|
||||
# update E-Mail
|
||||
if (
|
||||
update_user_dto.new_auth_user.email is not None
|
||||
and update_user_dto.new_auth_user.email != ""
|
||||
and update_user_dto.auth_user.email != update_user_dto.new_auth_user.email
|
||||
):
|
||||
user_by_new_e_mail = self._auth_users.find_auth_user_by_email(update_user_dto.new_auth_user.email)
|
||||
if user_by_new_e_mail is not None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidUser, "User already exists")
|
||||
user.email = update_user_dto.new_auth_user.email
|
||||
|
||||
update_user_dto.auth_user.password = self._hash_sha256(update_user_dto.auth_user.password, user.password_salt)
|
||||
if update_user_dto.auth_user.password != user.password:
|
||||
raise ServiceException(ServiceErrorCode.InvalidUser, "Wrong password")
|
||||
|
||||
# update password
|
||||
if (
|
||||
update_user_dto.new_auth_user.password is not None
|
||||
and self._hash_sha256(update_user_dto.new_auth_user.password, user.password_salt) != user.password
|
||||
):
|
||||
user.password_salt = uuid.uuid4()
|
||||
user.password = self._hash_sha256(update_user_dto.new_auth_user.password, user.password_salt)
|
||||
|
||||
self._auth_users.update_auth_user(user)
|
||||
self._db.save_changes()
|
||||
|
||||
async def update_user_as_admin_async(self, update_user_dto: UpdateAuthUserDTO):
|
||||
if update_user_dto is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, f"User is empty")
|
||||
|
||||
if update_user_dto.auth_user is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, f"Existing user is empty")
|
||||
|
||||
if update_user_dto.new_auth_user is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, f"New user is empty")
|
||||
|
||||
if not self._is_email_valid(update_user_dto.auth_user.email) or not self._is_email_valid(
|
||||
update_user_dto.new_auth_user.email
|
||||
):
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, f"Invalid E-Mail")
|
||||
|
||||
user = self._auth_users.find_auth_user_by_email(update_user_dto.auth_user.email)
|
||||
if user is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidUser, "User not found")
|
||||
|
||||
if user.confirmation_id is not None and update_user_dto.new_auth_user.is_confirmed:
|
||||
user.confirmation_id = None
|
||||
elif user.confirmation_id is None and not update_user_dto.new_auth_user.is_confirmed:
|
||||
user.confirmation_id = uuid.uuid4()
|
||||
# else
|
||||
# raise ServiceException(ServiceErrorCode.InvalidUser, 'E-Mail not confirmed')
|
||||
|
||||
# update first name
|
||||
if (
|
||||
update_user_dto.new_auth_user.first_name is not None
|
||||
and update_user_dto.auth_user.first_name != update_user_dto.new_auth_user.first_name
|
||||
):
|
||||
user.first_name = update_user_dto.new_auth_user.first_name
|
||||
|
||||
# update last name
|
||||
if (
|
||||
update_user_dto.new_auth_user.last_name is not None
|
||||
and update_user_dto.new_auth_user.last_name != ""
|
||||
and update_user_dto.auth_user.last_name != update_user_dto.new_auth_user.last_name
|
||||
):
|
||||
user.last_name = update_user_dto.new_auth_user.last_name
|
||||
|
||||
# update E-Mail
|
||||
if (
|
||||
update_user_dto.new_auth_user.email is not None
|
||||
and update_user_dto.new_auth_user.email != ""
|
||||
and update_user_dto.auth_user.email != update_user_dto.new_auth_user.email
|
||||
):
|
||||
user_by_new_e_mail = self._auth_users.find_auth_user_by_email(update_user_dto.new_auth_user.email)
|
||||
if user_by_new_e_mail is not None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidUser, "User already exists")
|
||||
user.email = update_user_dto.new_auth_user.email
|
||||
|
||||
# update password
|
||||
if (
|
||||
update_user_dto.new_auth_user.password is not None
|
||||
and update_user_dto.change_password
|
||||
and user.password != self._hash_sha256(update_user_dto.new_auth_user.password, user.password_salt)
|
||||
):
|
||||
user.password_salt = uuid.uuid4()
|
||||
user.password = self._hash_sha256(update_user_dto.new_auth_user.password, user.password_salt)
|
||||
|
||||
# update role
|
||||
if (
|
||||
user.auth_role == update_user_dto.auth_user.auth_role
|
||||
and user.auth_role != update_user_dto.new_auth_user.auth_role
|
||||
):
|
||||
user.auth_role = update_user_dto.new_auth_user.auth_role
|
||||
|
||||
self._auth_users.update_auth_user(user)
|
||||
self._db.save_changes()
|
||||
|
||||
async def delete_auth_user_by_email_async(self, email: str):
|
||||
try:
|
||||
user = self._auth_users.get_auth_user_by_email(email)
|
||||
self._auth_users.delete_auth_user(user)
|
||||
self._db.save_changes()
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Cannot delete user", e)
|
||||
raise ServiceException(ServiceErrorCode.UnableToDelete, f"Cannot delete user by mail {email}")
|
||||
|
||||
async def delete_auth_user_async(self, user_dto: AuthUser):
|
||||
try:
|
||||
self._auth_users.delete_auth_user(AUT.to_db(user_dto))
|
||||
self._db.save_changes()
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Cannot delete user", e)
|
||||
raise ServiceException(
|
||||
ServiceErrorCode.UnableToDelete,
|
||||
f"Cannot delete user by mail {user_dto.email}",
|
||||
)
|
||||
|
||||
def verify_login(self, token_str: str) -> bool:
|
||||
try:
|
||||
token = self.decode_token(token_str)
|
||||
if token is None or "email" not in token:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, "Token invalid")
|
||||
|
||||
user = self._auth_users.find_auth_user_by_email(token["email"])
|
||||
if user is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, "Token expired")
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Token invalid", e)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def verify_api_key(self, api_key: str) -> bool:
|
||||
try:
|
||||
keys = self._api_keys.get_api_keys().select(self._get_api_key_str)
|
||||
|
||||
if not keys.contains(api_key):
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, "API-Key invalid")
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"API-Key invalid", e)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
async def login_async(self, user_dto: AuthUser) -> TokenDTO:
|
||||
if user_dto is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, "User not set")
|
||||
|
||||
db_user = self._auth_users.find_auth_user_by_email(user_dto.email)
|
||||
if db_user is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidUser, f"User not found")
|
||||
|
||||
user_dto.password = self._hash_sha256(user_dto.password, db_user.password_salt)
|
||||
if db_user.password != user_dto.password:
|
||||
raise ServiceException(ServiceErrorCode.InvalidUser, "Wrong password")
|
||||
|
||||
if db_user.confirmation_id is not None:
|
||||
raise ServiceException(ServiceErrorCode.Forbidden, "E-Mail not verified")
|
||||
|
||||
token = self.generate_token(db_user)
|
||||
refresh_token = self._create_and_save_refresh_token(db_user)
|
||||
if db_user.forgot_password_id is not None:
|
||||
db_user.forgot_password_id = None
|
||||
|
||||
self._db.save_changes()
|
||||
return TokenDTO(token, refresh_token)
|
||||
|
||||
async def login_discord_async(self, user_dto: AuthUserDTO, dc_id: int) -> TokenDTO:
|
||||
if user_dto is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, "User not set")
|
||||
|
||||
members = self._users.get_users_by_discord_id(dc_id)
|
||||
if members.count() == 0:
|
||||
raise ServiceException(ServiceErrorCode.InvalidUser, f"Member not found")
|
||||
|
||||
added_user = False
|
||||
db_user = self._auth_users.find_auth_user_by_email(user_dto.email)
|
||||
if db_user is None:
|
||||
self.add_auth_user(user_dto)
|
||||
added_user = True
|
||||
|
||||
db_user = self._auth_users.get_auth_user_by_email(user_dto.email)
|
||||
user_ids = db_user.users.select(lambda x: x.id)
|
||||
|
||||
for user in self._users.get_users_by_discord_id(dc_id):
|
||||
if user.id in user_ids:
|
||||
continue
|
||||
|
||||
self._auth_users.add_auth_user_user_rel(AuthUserUsersRelation(db_user, user))
|
||||
|
||||
if db_user.confirmation_id is not None and not added_user:
|
||||
raise ServiceException(ServiceErrorCode.Forbidden, "E-Mail not verified")
|
||||
|
||||
token = self.generate_token(db_user)
|
||||
refresh_token = self._create_and_save_refresh_token(db_user)
|
||||
if db_user.forgot_password_id is not None:
|
||||
db_user.forgot_password_id = None
|
||||
|
||||
self._db.save_changes()
|
||||
return TokenDTO(token, refresh_token, first_login=added_user)
|
||||
|
||||
async def refresh_async(self, token_dto: TokenDTO) -> TokenDTO:
|
||||
if token_dto is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, f"Token not set")
|
||||
|
||||
try:
|
||||
token = self.decode_token(token_dto.token)
|
||||
if token is None or "email" not in token:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, "Token invalid")
|
||||
|
||||
user = self._auth_users.get_auth_user_by_email(token["email"])
|
||||
if (
|
||||
user is None
|
||||
or user.refresh_token != token_dto.refresh_token
|
||||
or user.refresh_token_expire_time <= datetime.now()
|
||||
):
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, "Token expired")
|
||||
|
||||
return TokenDTO(self.generate_token(user), self._create_and_save_refresh_token(user))
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Refreshing token failed", e)
|
||||
return TokenDTO("", "")
|
||||
|
||||
async def revoke_async(self, token_dto: TokenDTO):
|
||||
if token_dto is None or token_dto.token is None or token_dto.refresh_token is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, "Token not set")
|
||||
|
||||
try:
|
||||
token = self.decode_token(token_dto.token)
|
||||
|
||||
user = self._auth_users.get_auth_user_by_email(token["email"])
|
||||
if (
|
||||
user is None
|
||||
or user.refresh_token != token_dto.refresh_token
|
||||
or user.refresh_token_expire_time <= datetime.now()
|
||||
):
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, "Token expired")
|
||||
|
||||
user.refresh_token = None
|
||||
self._auth_users.update_auth_user(user)
|
||||
self._db.save_changes()
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Refreshing token failed", e)
|
||||
|
||||
async def confirm_email_async(self, id: str) -> bool:
|
||||
user = self._auth_users.find_auth_user_by_confirmation_id(id)
|
||||
if user is None:
|
||||
return False
|
||||
|
||||
user.confirmation_id = None
|
||||
self._auth_users.update_auth_user(user)
|
||||
self._db.save_changes()
|
||||
return True
|
||||
|
||||
async def forgot_password_async(self, email: str):
|
||||
user = self._auth_users.find_auth_user_by_email(email)
|
||||
if user is None:
|
||||
return
|
||||
|
||||
user.forgot_password_id = uuid.uuid4()
|
||||
self._auth_users.update_auth_user(user)
|
||||
self._send_forgot_password_id_to_user(user)
|
||||
self._db.save_changes()
|
||||
|
||||
async def confirm_forgot_password_async(self, id: str) -> EMailStringDTO:
|
||||
user = self._auth_users.find_auth_user_by_forgot_password_id(id)
|
||||
return EMailStringDTO(user.email)
|
||||
|
||||
async def reset_password_async(self, rp_dto: ResetPasswordDTO):
|
||||
user = self._auth_users.find_auth_user_by_forgot_password_id(rp_dto.id)
|
||||
if user is None:
|
||||
raise ServiceException(
|
||||
ServiceErrorCode.InvalidUser,
|
||||
f"User by forgot password id {rp_dto.id} not found",
|
||||
)
|
||||
|
||||
if user.confirmation_id is not None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidUser, f"E-Mail not confirmed")
|
||||
|
||||
if user.password is None or rp_dto.password == "":
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, f"Password not set")
|
||||
|
||||
user.password_salt = uuid.uuid4()
|
||||
user.password = self._hash_sha256(rp_dto.password, user.password_salt)
|
||||
user.forgot_password_id = None
|
||||
self._auth_users.update_auth_user(user)
|
||||
self._db.save_changes()
|
||||
|
||||
async def resend_confirmation_email_by_mail(self, mail: str):
|
||||
user = self._auth_users.find_auth_user_by_email(mail)
|
||||
if user is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidUser, f"User not found")
|
||||
if user.confirmation_id is None:
|
||||
raise ServiceException(ServiceErrorCode.DataAlreadyExists, f"User already confirmed")
|
||||
|
||||
self._send_confirmation_id_to_user(user)
|
91
bot/src/bot_api/service/discord_service.py
Normal file
91
bot/src/bot_api/service/discord_service.py
Normal file
@ -0,0 +1,91 @@
|
||||
from typing import Optional
|
||||
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from cpl_query.extension import List
|
||||
|
||||
from bot_api.abc.auth_service_abc import AuthServiceABC
|
||||
from bot_core.exception.service_error_code_enum import ServiceErrorCode
|
||||
from bot_core.exception.service_exception import ServiceException
|
||||
from bot_api.filter.discord.server_select_criteria import ServerSelectCriteria
|
||||
from bot_api.model.discord.server_dto import ServerDTO
|
||||
from bot_api.model.discord.server_filtered_result_dto import ServerFilteredResultDTO
|
||||
from bot_api.transformer.server_transformer import ServerTransformer
|
||||
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
|
||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||
from bot_data.abc.user_repository_abc import UserRepositoryABC
|
||||
from bot_data.model.auth_role_enum import AuthRoleEnum
|
||||
from bot_data.model.server import Server
|
||||
|
||||
|
||||
class DiscordService:
|
||||
def __init__(
|
||||
self,
|
||||
bot: DiscordBotServiceABC,
|
||||
servers: ServerRepositoryABC,
|
||||
auth: AuthServiceABC,
|
||||
auth_users: AuthUserRepositoryABC,
|
||||
users: UserRepositoryABC,
|
||||
):
|
||||
self._bot = bot
|
||||
self._servers = servers
|
||||
self._auth = auth
|
||||
self._auth_users = auth_users
|
||||
self._users = users
|
||||
|
||||
def _to_dto(self, x: Server) -> Optional[ServerDTO]:
|
||||
guild = self._bot.get_guild(x.discord_id)
|
||||
if guild is None:
|
||||
return ServerTransformer.to_dto(x, "", 0, None)
|
||||
|
||||
return ServerTransformer.to_dto(x, guild.name, guild.member_count, guild.icon)
|
||||
|
||||
async def get_all_servers(self) -> List[ServerDTO]:
|
||||
servers = List(ServerDTO, self._servers.get_servers())
|
||||
return servers.select(self._to_dto).where(lambda x: x.name != "")
|
||||
|
||||
async def get_all_servers_by_user(self) -> List[ServerDTO]:
|
||||
token = self._auth.get_decoded_token_from_request()
|
||||
if token is None or "email" not in token or "role" not in token:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, "Token invalid")
|
||||
|
||||
role = AuthRoleEnum(token["role"])
|
||||
servers = self._servers.get_servers()
|
||||
if role != AuthRoleEnum.admin:
|
||||
auth_user = self._auth_users.find_auth_user_by_email(token["email"])
|
||||
if auth_user is not None:
|
||||
user_ids = auth_user.users.select(lambda x: x.server is not None and x.server.id)
|
||||
servers = servers.where(lambda x: x.id in user_ids)
|
||||
|
||||
servers = List(ServerDTO, servers)
|
||||
return servers.select(self._to_dto).where(lambda x: x.name != "")
|
||||
|
||||
async def get_filtered_servers_async(self, criteria: ServerSelectCriteria) -> ServerFilteredResultDTO:
|
||||
token = self._auth.get_decoded_token_from_request()
|
||||
if token is None or "email" not in token or "role" not in token:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, "Token invalid")
|
||||
|
||||
role = AuthRoleEnum(token["role"])
|
||||
filtered_result = self._servers.get_filtered_servers(criteria)
|
||||
# filter out servers, where the user not exists
|
||||
if role != AuthRoleEnum.admin:
|
||||
auth_user = self._auth_users.find_auth_user_by_email(token["email"])
|
||||
if auth_user is not None:
|
||||
user_ids = auth_user.users.select(lambda x: x.server is not None and x.server.id)
|
||||
filtered_result.result = filtered_result.result.where(lambda x: x.id in user_ids)
|
||||
|
||||
servers: List = filtered_result.result.select(self._to_dto).where(lambda x: x.name != "")
|
||||
result = List(ServerDTO, servers)
|
||||
|
||||
if criteria.name is not None and criteria.name != "":
|
||||
result = result.where(
|
||||
lambda x: criteria.name.lower() in x.name.lower() or x.name.lower() == criteria.name.lower()
|
||||
)
|
||||
|
||||
return ServerFilteredResultDTO(List(ServerDTO, result), servers.count())
|
||||
|
||||
async def get_server_by_id_async(self, id: int) -> ServerDTO:
|
||||
server = self._servers.get_server_by_id(id)
|
||||
guild = self._bot.get_guild(server.discord_id)
|
||||
|
||||
server_dto = ServerTransformer.to_dto(server, guild.name, guild.member_count, guild.icon)
|
||||
return server_dto
|
26
bot/src/bot_api/transformer/__init__.py
Normal file
26
bot/src/bot_api/transformer/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "bot_api.transformer"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.8"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="8")
|
97
bot/src/bot_api/transformer/auth_user_transformer.py
Normal file
97
bot/src/bot_api/transformer/auth_user_transformer.py
Normal file
@ -0,0 +1,97 @@
|
||||
from datetime import datetime
|
||||
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from cpl_query.extension import List
|
||||
|
||||
from bot_api.abc.transformer_abc import TransformerABC
|
||||
from bot_api.model.auth_user_dto import AuthUserDTO
|
||||
from bot_api.model.user_dto import UserDTO
|
||||
from bot_data.model.auth_role_enum import AuthRoleEnum
|
||||
from bot_data.model.auth_user import AuthUser
|
||||
from bot_data.model.user import User
|
||||
from bot_core.abc.permission_service_abc import PermissionServiceABC
|
||||
|
||||
|
||||
class AuthUserTransformer(TransformerABC):
|
||||
@staticmethod
|
||||
def to_db(dto: AuthUserDTO) -> AuthUser:
|
||||
return AuthUser(
|
||||
dto.first_name,
|
||||
dto.last_name,
|
||||
dto.email,
|
||||
dto.password,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
datetime.now(),
|
||||
AuthRoleEnum.normal if dto.auth_role is None else AuthRoleEnum(dto.auth_role),
|
||||
auth_user_id=0 if dto.id is None else dto.id,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@ServiceProviderABC.inject
|
||||
def _is_technician(user: User, bot: DiscordBotServiceABC, permissions: PermissionServiceABC):
|
||||
guild = bot.get_guild(user.server.discord_id)
|
||||
if guild is None:
|
||||
return permissions.is_member_technician_by_id(user.discord_id)
|
||||
|
||||
member = guild.get_member(user.discord_id)
|
||||
if member is None:
|
||||
return permissions.is_member_technician_by_id(user.discord_id)
|
||||
|
||||
return permissions.is_member_technician(member)
|
||||
|
||||
@staticmethod
|
||||
@ServiceProviderABC.inject
|
||||
def _is_admin(user: User, bot: DiscordBotServiceABC, permissions: PermissionServiceABC):
|
||||
guild = bot.get_guild(user.server.discord_id)
|
||||
if guild is None:
|
||||
return False
|
||||
|
||||
member = guild.get_member(user.discord_id)
|
||||
if member is None:
|
||||
return False
|
||||
return permissions.is_member_admin(member)
|
||||
|
||||
@staticmethod
|
||||
@ServiceProviderABC.inject
|
||||
def _is_moderator(user: User, bot: DiscordBotServiceABC, permissions: PermissionServiceABC):
|
||||
guild = bot.get_guild(user.server.discord_id)
|
||||
if guild is None:
|
||||
return False
|
||||
|
||||
member = guild.get_member(user.discord_id)
|
||||
if member is None:
|
||||
return False
|
||||
return permissions.is_member_moderator(member)
|
||||
|
||||
@classmethod
|
||||
def to_dto(cls, db: AuthUser, password: str = None) -> AuthUserDTO:
|
||||
return AuthUserDTO(
|
||||
db.id,
|
||||
db.first_name,
|
||||
db.last_name,
|
||||
db.email,
|
||||
"" if password is None else password,
|
||||
db.confirmation_id,
|
||||
db.auth_role,
|
||||
List(
|
||||
UserDTO,
|
||||
db.users.select(
|
||||
lambda u: UserDTO(
|
||||
u.id,
|
||||
u.discord_id,
|
||||
u.xp,
|
||||
u.server,
|
||||
cls._is_technician(u),
|
||||
cls._is_admin(u),
|
||||
cls._is_moderator(u),
|
||||
)
|
||||
),
|
||||
),
|
||||
db.created_at,
|
||||
db.modified_at,
|
||||
)
|
23
bot/src/bot_api/transformer/server_transformer.py
Normal file
23
bot/src/bot_api/transformer/server_transformer.py
Normal file
@ -0,0 +1,23 @@
|
||||
from typing import Optional
|
||||
|
||||
import discord
|
||||
|
||||
from bot_api.abc.transformer_abc import TransformerABC
|
||||
from bot_api.model.discord.server_dto import ServerDTO
|
||||
from bot_data.model.server import Server
|
||||
|
||||
|
||||
class ServerTransformer(TransformerABC):
|
||||
@staticmethod
|
||||
def to_db(dto: ServerDTO) -> Server:
|
||||
return Server(dto.discord_id)
|
||||
|
||||
@staticmethod
|
||||
def to_dto(db: Server, name: str, member_count: int, icon_url: Optional[discord.Asset]) -> ServerDTO:
|
||||
return ServerDTO(
|
||||
db.id,
|
||||
db.discord_id,
|
||||
name,
|
||||
member_count,
|
||||
icon_url.url if icon_url is not None else None,
|
||||
)
|
26
bot/src/bot_core/__init__.py
Normal file
26
bot/src/bot_core/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "bot_core"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.8"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="8")
|
26
bot/src/bot_core/abc/__init__.py
Normal file
26
bot/src/bot_core/abc/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "bot_core.abc"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.8"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="8")
|
91
bot/src/bot_core/abc/client_utils_abc.py
Normal file
91
bot/src/bot_core/abc/client_utils_abc.py
Normal file
@ -0,0 +1,91 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from datetime import datetime
|
||||
from typing import Callable, Union
|
||||
|
||||
import discord
|
||||
from cpl_query.extension import List
|
||||
from discord.ext.commands import Context
|
||||
|
||||
from bot_data.model.auto_role_rule import AutoRoleRule
|
||||
from bot_data.model.server_config import ServerConfig
|
||||
from bot_data.model.user import User
|
||||
|
||||
|
||||
class ClientUtilsABC(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def received_command(self, guild_id: int):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def moved_user(self, guild_id: int):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def moved_users(self, guild_id: int, count: int):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_client(self, dc_ic: int, guild_id: int):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def check_if_bot_is_ready_yet(self) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def check_if_bot_is_ready_yet_and_respond(self, ctx: Context) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def presence_game(self, t_key: str):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_auto_complete_list(self, _l: List, current: str, select: Callable = None) -> List:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update_user_message_xp_count_by_hour(
|
||||
self,
|
||||
created_at: datetime,
|
||||
user: User,
|
||||
settings: ServerConfig,
|
||||
is_reaction: bool = False,
|
||||
):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_message_xp_count_by_hour_higher_that_max_message_count_per_hour(
|
||||
self,
|
||||
created_at: datetime,
|
||||
user: User,
|
||||
settings: ServerConfig,
|
||||
is_reaction: bool = False,
|
||||
) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_ontime_for_user(self, user: User) -> float:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def react_to_message_by_auto_role_rule(
|
||||
self,
|
||||
discord_channel_id: int,
|
||||
discord_message_id: int,
|
||||
rule: AutoRoleRule,
|
||||
guild: discord.Guild,
|
||||
):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def check_default_role(self, member: Union[discord.User, discord.Member]):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def set_maintenance_mode(self, state: bool):
|
||||
pass
|
69
bot/src/bot_core/abc/custom_file_logger_abc.py
Normal file
69
bot/src/bot_core/abc/custom_file_logger_abc.py
Normal file
@ -0,0 +1,69 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from cpl_core.logging import LoggingSettings, Logger, LoggingLevelEnum, LoggerABC
|
||||
from cpl_core.time import TimeFormatSettings
|
||||
|
||||
from bot_core.configuration.file_logging_settings import FileLoggingSettings
|
||||
|
||||
|
||||
class CustomFileLoggerABC(Logger, ABC):
|
||||
@abstractmethod
|
||||
def __init__(
|
||||
self,
|
||||
key: str,
|
||||
config: ConfigurationABC,
|
||||
time_format: TimeFormatSettings,
|
||||
env: ApplicationEnvironmentABC,
|
||||
):
|
||||
self._key = key
|
||||
self._settings: LoggingSettings = config.get_configuration(f"{FileLoggingSettings.__name__}_{key}")
|
||||
Logger.__init__(self, self._settings, time_format, env)
|
||||
self._begin_log()
|
||||
|
||||
@property
|
||||
def settings(self) -> LoggingSettings:
|
||||
return self._settings
|
||||
|
||||
def _begin_log(self):
|
||||
console_level = self._console.value
|
||||
self._console = LoggingLevelEnum.OFF
|
||||
self.info(__name__, f"Starting...")
|
||||
self._console = LoggingLevelEnum(console_level)
|
||||
|
||||
def _get_string(self, name_list_as_str: str, level: LoggingLevelEnum, message: str) -> str:
|
||||
names = name_list_as_str.split(" ")
|
||||
log_level = level.name
|
||||
string = f"<{self._get_datetime_now()}> [ {log_level} ]"
|
||||
for name in names:
|
||||
string += f" [ {name} ]"
|
||||
string += f": {message}"
|
||||
return string
|
||||
|
||||
def header(self, string: str):
|
||||
super().header(string)
|
||||
|
||||
def trace(self, name: str, message: str):
|
||||
name = f"{name} {self._key}"
|
||||
super().trace(name, message)
|
||||
|
||||
def debug(self, name: str, message: str):
|
||||
name = f"{name} {self._key}"
|
||||
super().debug(name, message)
|
||||
|
||||
def info(self, name: str, message: str):
|
||||
name = f"{name} {self._key}"
|
||||
super().info(name, message)
|
||||
|
||||
def warn(self, name: str, message: str):
|
||||
name = f"{name} {self._key}"
|
||||
super().warn(name, message)
|
||||
|
||||
def error(self, name: str, message: str, ex: Exception = None):
|
||||
name = f"{name} {self._key}"
|
||||
super().error(name, message, ex)
|
||||
|
||||
def fatal(self, name: str, message: str, ex: Exception = None):
|
||||
name = f"{name} {self._key}"
|
||||
super().fatal(name, message, ex)
|
67
bot/src/bot_core/abc/message_service_abc.py
Normal file
67
bot/src/bot_core/abc/message_service_abc.py
Normal file
@ -0,0 +1,67 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Union, Optional
|
||||
|
||||
import discord
|
||||
from cpl_query.extension import List
|
||||
from discord import Interaction
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
class MessageServiceABC(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def delete_messages(self, messages: List[discord.Message], guild_id: int, without_tracking=False):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def delete_message(self, message: discord.Message, without_tracking=False):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def send_channel_message(
|
||||
self,
|
||||
channel: discord.TextChannel,
|
||||
message: Union[str, discord.Embed],
|
||||
is_persistent: bool = False,
|
||||
wait_before_delete: int = None,
|
||||
without_tracking=False,
|
||||
):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def send_dm_message(
|
||||
self,
|
||||
message: Union[str, discord.Embed],
|
||||
receiver: Union[discord.User, discord.Member],
|
||||
without_tracking=False,
|
||||
):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def send_ctx_msg(
|
||||
self,
|
||||
ctx: Context,
|
||||
message: Union[str, discord.Embed],
|
||||
file: discord.File = None,
|
||||
is_persistent: bool = False,
|
||||
is_public: bool = False,
|
||||
wait_before_delete: int = None,
|
||||
without_tracking=True,
|
||||
) -> Optional[discord.Message]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def send_interaction_msg(
|
||||
self,
|
||||
interaction: Interaction,
|
||||
message: Union[str, discord.Embed],
|
||||
is_persistent: bool = False,
|
||||
is_public: bool = False,
|
||||
wait_before_delete: int = None,
|
||||
without_tracking=True,
|
||||
**kwargs
|
||||
):
|
||||
pass
|
19
bot/src/bot_core/abc/module_abc.py
Normal file
19
bot/src/bot_core/abc/module_abc.py
Normal file
@ -0,0 +1,19 @@
|
||||
from abc import abstractmethod
|
||||
|
||||
from cpl_core.application import StartupExtensionABC
|
||||
from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
|
||||
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
|
||||
|
||||
class ModuleABC(StartupExtensionABC):
|
||||
@abstractmethod
|
||||
def __init__(self, dc: DiscordCollectionABC, feature_flag: FeatureFlagsEnum):
|
||||
StartupExtensionABC.__init__(self)
|
||||
|
||||
self._dc = dc
|
||||
self._feature_flag = feature_flag
|
||||
|
||||
@property
|
||||
def feature_flag(self) -> FeatureFlagsEnum:
|
||||
return self._feature_flag
|
25
bot/src/bot_core/abc/permission_service_abc.py
Normal file
25
bot/src/bot_core/abc/permission_service_abc.py
Normal file
@ -0,0 +1,25 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import discord
|
||||
|
||||
|
||||
class PermissionServiceABC(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_member_admin(self, member: discord.Member) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_member_moderator(self, member: discord.Member) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_member_technician(self, member: discord.Member) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_member_technician_by_id(self, member_id: int) -> bool:
|
||||
pass
|
33
bot/src/bot_core/abc/task_abc.py
Normal file
33
bot/src/bot_core/abc/task_abc.py
Normal file
@ -0,0 +1,33 @@
|
||||
import asyncio
|
||||
from abc import abstractmethod
|
||||
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
from discord.ext import commands
|
||||
|
||||
from bot_core.environment_variables import MAINTENANCE
|
||||
from bot_core.logging.task_logger import TaskLogger
|
||||
|
||||
|
||||
class TaskABC(commands.Cog):
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
commands.Cog.__init__(self)
|
||||
|
||||
@ServiceProviderABC.inject
|
||||
def _is_maintenance(self, config: ConfigurationABC) -> bool:
|
||||
return config.get_configuration(MAINTENANCE) is True
|
||||
|
||||
@ServiceProviderABC.inject
|
||||
async def _wait_until_ready(self, config: ConfigurationABC, logger: TaskLogger, bot: DiscordBotServiceABC):
|
||||
logger.debug(__name__, f"Waiting before ready {type(self).__name__}")
|
||||
await bot.wait_until_ready()
|
||||
|
||||
async def wait():
|
||||
is_ready = config.get_configuration("IS_READY") is True
|
||||
if not is_ready:
|
||||
await asyncio.sleep(1)
|
||||
await wait()
|
||||
|
||||
await wait()
|
@ -3,8 +3,8 @@
|
||||
"Name": "bot-core",
|
||||
"Version": {
|
||||
"Major": "1",
|
||||
"Minor": "0",
|
||||
"Micro": "0.dev1"
|
||||
"Minor": "2",
|
||||
"Micro": "8"
|
||||
},
|
||||
"Author": "Sven Heidemann",
|
||||
"AuthorEmail": "sven.heidemann@sh-edraft.de",
|
||||
@ -16,15 +16,13 @@
|
||||
"LicenseName": "MIT",
|
||||
"LicenseDescription": "MIT, see LICENSE for more details.",
|
||||
"Dependencies": [
|
||||
"cpl-core>=2022.7.0"
|
||||
"cpl-core==2022.12.0"
|
||||
],
|
||||
"DevDependencies": [
|
||||
"cpl-cli>=2022.7.0"
|
||||
"cpl-cli==2022.12.0"
|
||||
],
|
||||
"PythonVersion": ">=3.10.4",
|
||||
"PythonPath": {
|
||||
"linux": ""
|
||||
},
|
||||
"PythonPath": {},
|
||||
"Classifiers": []
|
||||
},
|
||||
"BuildSettings": {
|
26
bot/src/bot_core/configuration/__init__.py
Normal file
26
bot/src/bot_core/configuration/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "bot_core.configuration"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.8"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="8")
|
25
bot/src/bot_core/configuration/bot_logging_settings.py
Normal file
25
bot/src/bot_core/configuration/bot_logging_settings.py
Normal file
@ -0,0 +1,25 @@
|
||||
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
from cpl_core.utils.json_processor import JSONProcessor
|
||||
from cpl_query.extension import List
|
||||
|
||||
from bot_core.configuration.file_logging_settings import FileLoggingSettings
|
||||
|
||||
|
||||
class BotLoggingSettings(ConfigurationModelABC):
|
||||
def __init__(self, **kwargs: dict):
|
||||
ConfigurationModelABC.__init__(self)
|
||||
self._files: List[FileLoggingSettings] = List(FileLoggingSettings)
|
||||
|
||||
if kwargs is not None:
|
||||
self._files_from_dict(kwargs)
|
||||
|
||||
@property
|
||||
def files(self) -> List[FileLoggingSettings]:
|
||||
return self._files
|
||||
|
||||
def _files_from_dict(self, settings: dict):
|
||||
files = List(FileLoggingSettings)
|
||||
for s in settings:
|
||||
settings[s]["Key"] = s
|
||||
files.append(JSONProcessor.process(FileLoggingSettings, settings[s]))
|
||||
self._files = files
|
31
bot/src/bot_core/configuration/feature_flags_enum.py
Normal file
31
bot/src/bot_core/configuration/feature_flags_enum.py
Normal file
@ -0,0 +1,31 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class FeatureFlagsEnum(Enum):
|
||||
# modules
|
||||
achievements_module = "AchievementsModule"
|
||||
api_module = "ApiModule"
|
||||
auto_role_module = "AutoRoleModule"
|
||||
base_module = "BaseModule"
|
||||
boot_log_module = "BootLogModule"
|
||||
core_module = "CoreModule"
|
||||
core_extension_module = "CoreExtensionModule"
|
||||
config_module = "ConfigModule"
|
||||
data_module = "DataModule"
|
||||
database_module = "DatabaseModule"
|
||||
level_module = "LevelModule"
|
||||
moderator_module = "ModeratorModule"
|
||||
short_role_name_module = "ShortRoleNameModule"
|
||||
steam_special_offers_module = "SteamSpecialOffersModule"
|
||||
# features
|
||||
api_only = "ApiOnly"
|
||||
presence = "Presence"
|
||||
version_in_presence = "VersionInPresence"
|
||||
game_server = "GameServer"
|
||||
sync_xp = "SyncXp"
|
||||
short_role_name = "ShortRoleName"
|
||||
technician_full_access = "TechnicianFullAccess"
|
||||
steam_special_offers = "SteamSpecialOffers"
|
||||
scheduled_events = "ScheduledEvents"
|
||||
basic_registration = "BasicRegistration"
|
||||
basic_login = "BasicLogin"
|
65
bot/src/bot_core/configuration/feature_flags_settings.py
Normal file
65
bot/src/bot_core/configuration/feature_flags_settings.py
Normal file
@ -0,0 +1,65 @@
|
||||
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
|
||||
|
||||
class FeatureFlagsSettings(ConfigurationModelABC):
|
||||
_flags = {
|
||||
# modules
|
||||
FeatureFlagsEnum.achievements_module.value: False, # 14.06.2023 #268
|
||||
FeatureFlagsEnum.api_module.value: False, # 13.10.2022 #70
|
||||
FeatureFlagsEnum.auto_role_module.value: False, # 03.10.2022 #54
|
||||
FeatureFlagsEnum.base_module.value: True, # 02.10.2022 #48
|
||||
FeatureFlagsEnum.boot_log_module.value: True, # 02.10.2022 #48
|
||||
FeatureFlagsEnum.core_module.value: True, # 03.10.2022 #56
|
||||
FeatureFlagsEnum.core_extension_module.value: True, # 03.10.2022 #56
|
||||
FeatureFlagsEnum.data_module.value: True, # 03.10.2022 #56
|
||||
FeatureFlagsEnum.database_module.value: True, # 02.10.2022 #48
|
||||
FeatureFlagsEnum.moderator_module.value: False, # 02.10.2022 #48
|
||||
FeatureFlagsEnum.config_module.value: True, # 19.07.2023 #127
|
||||
FeatureFlagsEnum.short_role_name_module.value: True, # 28.09.2023 #378
|
||||
FeatureFlagsEnum.steam_special_offers_module.value: True, # 11.10.2023 #188
|
||||
# features
|
||||
FeatureFlagsEnum.api_only.value: False, # 13.10.2022 #70
|
||||
FeatureFlagsEnum.presence.value: True, # 03.10.2022 #56
|
||||
FeatureFlagsEnum.version_in_presence.value: False, # 21.03.2023 #253
|
||||
FeatureFlagsEnum.game_server.value: False, # 25.09.2023 #366
|
||||
FeatureFlagsEnum.sync_xp.value: False, # 25.09.2023 #366
|
||||
FeatureFlagsEnum.short_role_name.value: False, # 28.09.2023 #378
|
||||
FeatureFlagsEnum.technician_full_access.value: False, # 03.10.2023 #393
|
||||
FeatureFlagsEnum.steam_special_offers.value: False, # 11.10.2023 #188
|
||||
FeatureFlagsEnum.scheduled_events.value: False, # 14.11.2023 #410
|
||||
FeatureFlagsEnum.basic_registration.value: False, # 19.11.2023 #440
|
||||
FeatureFlagsEnum.basic_login.value: False, # 19.11.2023 #440
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs: dict):
|
||||
ConfigurationModelABC.__init__(self)
|
||||
|
||||
if len(kwargs.keys()) == 0:
|
||||
return
|
||||
|
||||
for flag in [f.value for f in FeatureFlagsEnum]:
|
||||
self._load_flag(kwargs, FeatureFlagsEnum(flag))
|
||||
|
||||
@classmethod
|
||||
def get_flag_from_dict(cls, flags: dict, key: FeatureFlagsEnum) -> bool:
|
||||
def get_flag():
|
||||
if key.value not in cls._flags:
|
||||
return False
|
||||
return cls._flags[key.value]
|
||||
|
||||
if key.value not in flags:
|
||||
return get_flag()
|
||||
return flags[key.value]
|
||||
|
||||
def get_flag(self, key: FeatureFlagsEnum) -> bool:
|
||||
if key.value not in self._flags:
|
||||
return False
|
||||
return self._flags[key.value]
|
||||
|
||||
def _load_flag(self, settings: dict, key: FeatureFlagsEnum):
|
||||
if key.value not in settings:
|
||||
return
|
||||
|
||||
self._flags[key.value] = bool(settings[key.value])
|
19
bot/src/bot_core/configuration/file_logging_settings.py
Normal file
19
bot/src/bot_core/configuration/file_logging_settings.py
Normal file
@ -0,0 +1,19 @@
|
||||
from cpl_core.logging import LoggingSettings, LoggingLevelEnum
|
||||
|
||||
|
||||
class FileLoggingSettings(LoggingSettings):
|
||||
def __init__(
|
||||
self,
|
||||
key: str,
|
||||
path: str = None,
|
||||
filename: str = None,
|
||||
console_log_level: LoggingLevelEnum = None,
|
||||
file_log_level: LoggingLevelEnum = None,
|
||||
):
|
||||
LoggingSettings.__init__(self, path, filename, console_log_level, file_log_level)
|
||||
|
||||
self._key = key
|
||||
|
||||
@property
|
||||
def key(self) -> str:
|
||||
return self._key
|
26
bot/src/bot_core/core_extension/__init__.py
Normal file
26
bot/src/bot_core/core_extension/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
bot sh-edraft.de Discord bot
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Discord bot for customers of sh-edraft.de
|
||||
|
||||
:copyright: (c) 2022 - 2023 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "bot_core.core_extension"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
|
||||
__version__ = "1.2.8"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="1", minor="2", micro="8")
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user