196 Commits

Author SHA1 Message Date
2613fcc999 Merge pull request '0.3 - received command in allen neuen Befehlen (#105)' (#106) from #105 into 0.3
Reviewed-on: sh-edraft.de/kd_discord_bot#106
Reviewed-by: Ebola-Chan <nick.jungmann@gmail.com>
Closes #105
2022-11-10 07:12:26 +01:00
55c43efbdc Merge branch '0.3' into #105 2022-11-09 22:23:08 +01:00
e47318d8e9 Added received command to new commands #105 2022-11-09 22:21:32 +01:00
f551fdf87b Merge pull request '0.3 - auto-role list - möglicherweise nicht pro Server sondern allgemein (#93)' (#104) from #93 into 0.3
Reviewed-on: sh-edraft.de/kd_discord_bot#104
Reviewed-by: Ebola-Chan <nick.jungmann@gmail.com>
Closes #93
2022-11-09 22:06:32 +01:00
6f3877700e Fixed auto-role output by server #93 2022-11-09 21:40:04 +01:00
2483faef01 Merge pull request '0.3 - Statistiken (#46)' (#102) from #46 into 0.3
Reviewed-on: sh-edraft.de/kd_discord_bot#102
Reviewed-by: Ebola-Chan <nick.jungmann@gmail.com>
Closes #46
2022-11-09 20:55:05 +01:00
82365a24ba Fixed stats group #46 2022-11-09 20:52:58 +01:00
a29e33a3f9 Readded debug log to level seeder #26 2022-11-09 20:07:37 +01:00
273548411b Removed tests #26 2022-11-09 19:57:16 +01:00
a8f481ff4a Merge branch '#46' of https://git.sh-edraft.de/sh-edraft.de/kd_discord_bot into #46
 Conflicts:
	kdb-bot/src/modules/level/level_seeder.py
2022-11-09 19:54:40 +01:00
7fe0f19adf Added db to stats #46 2022-11-09 19:53:10 +01:00
b52cdf0f34 Added stats repo #46 2022-11-09 19:53:10 +01:00
1c9c265ba8 Added stats table #46 2022-11-09 19:53:10 +01:00
b5080d8c04 Added stats migration #46 2022-11-09 19:53:10 +01:00
bf107f9a18 Added stats remove command #46 2022-11-09 19:53:10 +01:00
8cd67cd68c Added stats edit command #46 2022-11-09 19:53:10 +01:00
5c7db72723 Fixed level seeder #46 2022-11-09 19:53:10 +01:00
86433e841b Fixed typo #46 2022-11-09 19:51:46 +01:00
61b2432d0b Added logic to handle custom statistics #46 2022-11-09 19:51:46 +01:00
ddb9d0e647 Removed old code #46 2022-11-09 19:51:46 +01:00
a57c8da72a Added logic to call statistics #46 2022-11-09 19:51:46 +01:00
c6a5d49942 Added logic to call statistics #46 2022-11-09 19:51:46 +01:00
27ccdbd980 Merge pull request '0.3 - level set (#26)' (#101) from #26 into 0.3
Reviewed-on: sh-edraft.de/kd_discord_bot#101
Reviewed-by: Ebola-Chan <nick.jungmann@gmail.com>
Closes #26
2022-11-09 19:51:22 +01:00
a5ef2551e8 Improved error translation #26 2022-11-09 19:50:57 +01:00
c632ad51d4 Fixed stuff caused by merges #26 2022-11-09 19:33:45 +01:00
4f433c3840 Merge branch '#26' of https://git.sh-edraft.de/sh-edraft.de/kd_discord_bot into #26 2022-11-09 19:26:47 +01:00
455c1e7023 Fixed translation #26 2022-11-09 19:26:41 +01:00
9ade299be7 Merge branch '0.3' into #26 2022-11-09 19:24:12 +01:00
09dcc8a9d3 Merge pull request '0.3 - level up (#27)' (#99) from #27 into 0.3
Reviewed-on: sh-edraft.de/kd_discord_bot#99
Reviewed-by: Ebola-Chan <nick.jungmann@gmail.com>
Closes #27
2022-11-09 19:24:01 +01:00
bd79831892 Added db to stats #46 2022-11-09 19:23:10 +01:00
c7967c6904 Added stats repo #46 2022-11-09 18:49:11 +01:00
a43676e9bf Added stats table #46 2022-11-09 18:36:48 +01:00
fa7e41469b Added stats migration #46 2022-11-09 18:22:27 +01:00
c9ce81701f Added stats remove command #46 2022-11-09 18:01:14 +01:00
c407c59c1a Added stats edit command #46 2022-11-09 17:59:55 +01:00
7e5706137e Fixed level seeder #46 2022-11-09 17:18:14 +01:00
144c1d7e79 Merge branch '0.3' into #27
# Conflicts:
#	kdb-bot/src/bot/translation/de.json
2022-11-09 10:16:06 +01:00
054266bfa0 Merge pull request '0.3 - level down (#28)' (#98) from #28 into 0.3
Reviewed-on: sh-edraft.de/kd_discord_bot#98
Reviewed-by: Ebola-Chan <nick.jungmann@gmail.com>
Closes #28
2022-11-09 10:03:21 +01:00
eaae058754 Fixed problems since merge #28 2022-11-09 08:51:08 +01:00
95a64732f3 Fixed typo #46 2022-11-08 22:06:36 +01:00
57d59e91ac Merge branch '0.3' into #28
# Conflicts:
#	kdb-bot/src/bot/translation/de.json
#	kdb-bot/src/modules/level/command/level_group.py
2022-11-08 22:04:14 +01:00
e3d3d200d6 Added logic to handle custom statistics #46 2022-11-08 21:54:33 +01:00
7da60e6f0a Merge pull request '0.3 - level remove Befehl (#91)' (#96) from #91 into 0.3
Reviewed-on: sh-edraft.de/kd_discord_bot#96
Reviewed-by: Ebola-Chan <nick.jungmann@gmail.com>
Closes #91
2022-11-08 21:47:46 +01:00
95e33109fe Removed old code #46 2022-11-08 20:36:31 +01:00
9e937f17b7 Fixed typo in translation... bruh #91 2022-11-08 20:33:55 +01:00
5dca2e5fed Added logic to call statistics #46 2022-11-08 20:28:44 +01:00
f24fd5e880 Added logic to call statistics #46 2022-11-08 20:28:38 +01:00
cfd96126df Merge branch '0.3' into #91 2022-11-08 18:59:41 +01:00
9099ebe4f3 Moved admin & moderator commands to base module 2022-11-08 18:59:24 +01:00
c666ceb51e Added level set command #26 2022-11-08 18:49:18 +01:00
e5eb50a3cb Fixed level seeder #26 2022-11-08 18:49:10 +01:00
f0f23163e4 Added level down command #27 2022-11-08 18:01:04 +01:00
faaadb0009 Fixed level seeder #28 2022-11-08 17:49:42 +01:00
fe32604a14 Added level down command #28 2022-11-08 17:49:32 +01:00
9b39d9baa5 Merge branch '#91' of https://git.sh-edraft.de/sh-edraft.de/kd_discord_bot into #91 2022-11-08 09:36:41 +01:00
7f2cdc5b1c Added remove command #91 2022-11-08 09:36:15 +01:00
4ffbbd5b89 Merge pull request '0.3 - level create Befehl (#90)' (#95) from #90 into 0.3
Reviewed-on: sh-edraft.de/kd_discord_bot#95
Reviewed-by: Ebola-Chan <nick.jungmann@gmail.com>
Closes #90
2022-11-08 09:35:29 +01:00
898b24c8ee Changed color names #90 2022-11-08 08:02:35 +01:00
ccbe60b747 Merge branch '0.3' into #90 2022-11-07 22:33:26 +01:00
22456946b8 Merge pull request '0.3 - level list Befehl (#29)' (#94) from #29 into 0.3
Reviewed-on: sh-edraft.de/kd_discord_bot#94
Reviewed-by: Ebola-Chan <nick.jungmann@gmail.com>
Closes #29
2022-11-07 22:29:36 +01:00
e4fcc7931e Merge branch '0.3' into #91 2022-11-07 22:16:32 +01:00
e0af7421a1 Merge branch '0.3' into #90 2022-11-07 22:16:22 +01:00
c2ed8b9bc5 Merge branch '0.3' into #29 2022-11-07 22:16:01 +01:00
e27d2ae682 Merge pull request '0.3 - Levelsystem (#25)' (#92) from #25 into 0.3
Reviewed-on: sh-edraft.de/kd_discord_bot#92
Reviewed-by: Ebola-Chan <nick.jungmann@gmail.com>
Closes #25
2022-11-07 22:15:37 +01:00
b8fcaa48a5 Added remove command #91 2022-11-07 20:54:27 +01:00
6a18b15447 Fixed level stuff #90 2022-11-07 20:35:24 +01:00
b2397fcc2f Added create command #90 2022-11-07 20:35:12 +01:00
7ed30c40be Added list command #29 2022-11-07 19:04:41 +01:00
63000636a8 Updated version of level project #25 2022-11-06 22:46:15 +01:00
3d27295b14 Added translation to level service #25 2022-11-06 22:43:42 +01:00
1b587b049a Build files #25 2022-11-06 22:40:30 +01:00
dc90258a28 "Repaired" workspace file #25 2022-11-06 22:30:57 +01:00
23fde793db Fixed my config #25 2022-11-06 22:19:41 +01:00
6640d70440 Added logic to send message after level up #25 2022-11-06 22:17:27 +01:00
6e39154c75 Fixed level assignment #25 2022-11-06 21:54:21 +01:00
164671a07c Added logic to handle level by xp #25 2022-11-06 21:34:58 +01:00
8aee72856c Added level service #25 2022-11-06 19:51:50 +01:00
8118d4edc3 Improved logic to create roles for levels by default #25 2022-11-06 19:36:15 +01:00
861a847088 Improved logic to create roles for levels by default #25 2022-11-06 19:18:11 +01:00
666c527730 Added logic to handle default levels #25 2022-11-06 16:26:15 +01:00
e6e26b77f9 Added level repo #25 2022-11-06 14:46:11 +01:00
5a3eb57c0b Added level model #25 2022-11-06 14:35:28 +01:00
e1dbab3f4f Added level module and migration #25 2022-11-06 14:26:41 +01:00
3516a164da Merge pull request 'Added flask support #70 #75' (#71) from #70 into 0.3
Reviewed-on: sh-edraft.de/kd_discord_bot#71
Reviewed-by: Ebola-Chan <nick.jungmann@gmail.com>
Reviewed-by: edraft-dev <dev.sven.heidemann@sh-edraft.de>
Closes #70 #72 #75 #83 #85 #86
2022-11-05 13:55:41 +01:00
e1304cc602 Added comment #70 2022-11-05 13:49:01 +01:00
88f00c6046 Fixed typing #70 2022-11-05 12:42:22 +01:00
09a6062992 Fixed typing #70 2022-11-05 12:40:21 +01:00
c92403f274 Removed unused deps #70 2022-11-05 12:39:49 +01:00
422c9bbb25 Fixed log level #70 2022-11-05 12:03:22 +01:00
e45d156e74 Changed config #70 2022-11-05 11:52:31 +01:00
8e32362333 Changed config #70 2022-11-05 11:51:58 +01:00
801d4bf16b Merge pull request 'Logging erweitern um IP-Adressen #70-4 (#85)' (#87) from #85 into #70
Reviewed-on: sh-edraft.de/kd_discord_bot#87
Closes #85
2022-10-25 21:24:27 +02:00
f7fd15b4bd Merge branch '#70' into #85 2022-10-25 21:24:11 +02:00
ac5271de6d Fixed update user #85 2022-10-25 21:15:40 +02:00
1f2087f50f Fixed update user #85 2022-10-25 21:11:55 +02:00
1c5b776efb Fixed change password autocomplete #70 2022-10-25 20:44:50 +02:00
368a00434b Fixed socket connect error #85 2022-10-25 20:27:59 +02:00
242af637f7 Improved api logging #85 2022-10-25 20:27:39 +02:00
6d8aa26883 Improved api logging #85 2022-10-25 20:19:05 +02:00
bf2a412dec Improved user confirmation #70 2022-10-25 08:31:08 +02:00
811e9991d0 Fixed user confirmation :/ #70 2022-10-25 08:07:15 +02:00
5cbd6ec00b Fixed user confirmation 2022-10-25 08:00:57 +02:00
b9c793ebb7 Merge pull request 'Set secured passwords #70-3' (#84) from #70-3 into #70
Reviewed-on: sh-edraft.de/kd_discord_bot#84
Closes #83
2022-10-25 07:50:58 +02:00
76c14a79ed Set secured passwords #70-3 2022-10-25 07:48:53 +02:00
877efe086b Fixed AuthUserTransformer #70 2022-10-25 07:41:13 +02:00
3b2ab6c8a0 Potentially fixed docker deployment #70 2022-10-23 22:41:18 +02:00
7c0f30ff95 Fixed nginx #70 2022-10-23 18:24:32 +02:00
8719452dad Fixed nginx #70 2022-10-23 18:24:13 +02:00
24a3a48478 Fixed socket service #70 2022-10-23 17:49:31 +02:00
ea026b351e Fixed docker dev env #70 2022-10-23 16:38:48 +02:00
109d3f28a2 Fixed revoke jwt #70 2022-10-23 16:35:24 +02:00
af5b9a2341 Improved dev stack #70 2022-10-23 15:37:40 +02:00
25a6758c40 Improved dev stack #70 2022-10-23 15:32:03 +02:00
a685ab9e06 Fixed docker cfg #70 2022-10-23 15:11:05 +02:00
7b8f42ad59 Added dev stack #70 2022-10-23 15:05:01 +02:00
a691808068 Fixed multiple send mails error #70 2022-10-23 14:37:42 +02:00
ecd648a9b2 Fixed confirmation id None on discord registration #70 2022-10-23 14:24:23 +02:00
f891587ce3 Improved docker support for deployment #70 2022-10-23 14:11:15 +02:00
ffe7b5e109 Repaired register by discord #70 2022-10-23 12:44:51 +02:00
602b1f3f98 Fixed docker env #70 2022-10-22 08:17:54 +02:00
17a14cedb6 Improved config #70 2022-10-22 07:37:47 +02:00
e31c5472af [WIP] Changed some stuff for docker #70 2022-10-21 23:01:09 +02:00
46ed616560 Added get version stuff to build docker #70 2022-10-21 19:25:17 +02:00
47f294a982 Improved set version for frontend #70 2022-10-21 13:50:11 +02:00
0fcf2c7b45 Set version correctly #70 2022-10-21 13:45:37 +02:00
fd2d30ee7f Added set version tool #70 2022-10-21 12:45:46 +02:00
e9b74140b5 Removed gitkeep #70 2022-10-20 21:19:24 +02:00
27442fe7c7 Merge pull request 'Bissl docker kram zum laufen gebracht #70-1_docker_and_build' (#76) from #70-1_docker_and_build into #70
Reviewed-on: sh-edraft.de/kd_discord_bot#76
2022-10-20 20:49:38 +02:00
b748435f98 Build tools project #70-1 2022-10-20 20:20:01 +02:00
d60baa0dce Added project information #70-1 2022-10-20 20:18:16 +02:00
48507962f8 Improved docker #70-1 2022-10-20 17:50:50 +02:00
7d4f3e9541 Added requirements.txt generator #70-1 2022-10-20 16:48:06 +02:00
3b9ef0048e Remove unnecessary configs #70-1 2022-10-20 16:32:05 +02:00
523e019aa0 Added post-build project #70-1 2022-10-20 15:46:08 +02:00
d0d053152d Improved docker compose #70-1 2022-10-20 15:45:52 +02:00
8231d20e3b Moved deps #70 2022-10-20 12:57:36 +02:00
f35dd0b15d Fixed auth user and user relation #70 2022-10-20 12:57:02 +02:00
d778bd2719 Added logic to register by discord #70 2022-10-20 09:47:23 +02:00
0c42c6554c Added logic to register by discord #70 2022-10-19 22:42:53 +02:00
fbc7b58780 Put send mail in thread #70 2022-10-19 18:42:20 +02:00
840193d5a8 Fixed send mails #70 2022-10-19 18:01:11 +02:00
4869039503 Fixed auto-role migration #70 2022-10-19 16:57:03 +02:00
d0ded956cb [WIP] Fixed forgot password #70 2022-10-18 21:00:23 +02:00
47a73a4298 Fixed password handling #70 2022-10-18 19:50:13 +02:00
a082b879ca Secured password handling #70 2022-10-18 18:33:03 +02:00
a51efa641d Improved auth views and imrpoved default settings handling #70 2022-10-18 18:04:53 +02:00
f553779797 Merge pull request '0.3-#70 - Im Web Interface Server auswählen - (#72)' (#73) from #72 into #70
Reviewed-on: sh-edraft.de/kd_discord_bot#73
Closes #72
2022-10-18 16:37:04 +02:00
dee8e4fe74 Fixed themes #72 2022-10-18 16:36:24 +02:00
7760ee5725 Update menu when server is selected #72 2022-10-18 16:23:40 +02:00
1055d5c2e1 Improved design of menu to handle servers #72 2022-10-18 13:44:13 +02:00
c094a3efae Improved get server logic #72 2022-10-18 12:41:42 +02:00
2a97438417 [WIP] Added server dashboard #72 2022-10-17 19:44:51 +02:00
a69c223a33 Added server list to dashboard #72 2022-10-17 18:12:43 +02:00
1baa8cee60 Added logic to get servers to dashboard #72 2022-10-17 16:50:09 +02:00
3d17bb7703 Added get filtered servers #72 2022-10-17 16:22:09 +02:00
d7a0706e0c Improved get server logic #72 2022-10-17 16:07:33 +02:00
dfcc516389 Added role check to authorization #72 2022-10-17 13:53:37 +02:00
1857473ccc Added logic to get discord servers #72 2022-10-17 12:19:50 +02:00
90bfee23b4 Added logic to load api after bot #70 2022-10-16 21:36:08 +02:00
f634ad552e Fixed settings dto #70 2022-10-16 21:29:58 +02:00
29fa35dffe Fixed update user #70 2022-10-16 21:16:04 +02:00
c5f371e0d5 Fixed update user #70 2022-10-16 21:10:20 +02:00
f319e89473 Fixed login state problems #70 2022-10-16 16:01:37 +02:00
d5ad5ded88 Fixed header styling #70 2022-10-16 14:49:46 +02:00
dbbd2b54c4 Fixed load menu #70 2022-10-16 13:09:26 +02:00
1a3126dfc5 Removed themeService from templates #70 2022-10-16 12:57:27 +02:00
ba881aefa8 Added verify-login #70 2022-10-16 12:06:18 +02:00
3fe8e1503c Added authorize check to controller #70 2022-10-16 10:11:21 +02:00
651482a1b9 Added api connection check #70 2022-10-16 01:55:37 +02:00
8c3cd1fae7 Added angular frontend #70 2022-10-16 00:13:03 +02:00
ee49bde961 Added waitress dep #70 2022-10-15 23:48:38 +02:00
2b08c1d71e Updated bot stuff #70 2022-10-15 23:38:38 +02:00
ead3f69a69 Moved bot to kdb-bot #70 2022-10-15 23:17:45 +02:00
029b46d7de Build #70 2022-10-15 17:34:16 +02:00
9da95f4dfb Fixed update user #70 2022-10-15 17:25:49 +02:00
8e56ff6a8e Set log level #70 2022-10-15 17:07:46 +02:00
21fcfaaa7f Fixed some random stuff #70 2022-10-15 17:07:22 +02:00
119f4e9d04 Improved api #70 2022-10-15 12:54:10 +02:00
5cd91d8341 Fixed error and dto handling #70 2022-10-15 09:32:41 +02:00
e0844a7f92 [WIP] Improved basics of api #70 2022-10-15 03:04:17 +02:00
6f95d0a055 Added auth service stuff with jwt #70 2022-10-15 00:42:55 +02:00
1090e502c2 [WIP] Added auth service stuff without jwt #70 2022-10-14 23:32:36 +02:00
1defe29406 Added auth user controller #70 2022-10-14 15:37:52 +02:00
126637306d [WIP] Added auth user service #70 2022-10-14 13:17:25 +02:00
2457107c63 Added auth user repository #70 2022-10-14 13:05:53 +02:00
411e44a681 [WIP] Added auth controller & updated pakages #70 2022-10-14 10:35:58 +02:00
91b054fdca Added auth service abc #70 2022-10-14 07:51:30 +02:00
e8c491a478 Made api async #70 2022-10-14 07:16:12 +02:00
aab3d62365 Added api settings #70 2022-10-14 07:04:56 +02:00
0927288bb5 Added api calls #70 2022-10-13 20:57:47 +02:00
0019f868ad Added flask support #70 2022-10-13 19:57:56 +02:00
d05dec3605 Updated my config 2022-10-13 16:34:04 +02:00
30dbce3f4b Merge pull request '0.3 - user-info zu /user info konvertieren #61' (#68) from Ebola-Chan/kd_discord_bot:#61 into 0.3
Reviewed-on: sh-edraft.de/kd_discord_bot#68
Reviewed-by: Sven Heidemann <sven.heidemann@sh-edraft.de>
Closes #61
2022-10-13 16:33:17 +02:00
56db9f42ad Merge pull request '0.3 - Techniker in PermissionService aufnehmen #60' (#69) from Ebola-Chan/kd_discord_bot:#60 into 0.3
Reviewed-on: sh-edraft.de/kd_discord_bot#69
Reviewed-by: Sven Heidemann <sven.heidemann@sh-edraft.de>
Closes #60
2022-10-13 16:32:44 +02:00
85f5717624 Addad technician to PermissionService #60 2022-10-12 21:27:55 +02:00
0c71e4934e Renamed user-info and added it to user group #61 2022-10-12 20:50:26 +02:00
468 changed files with 37730 additions and 479 deletions

View File

@@ -1,26 +0,0 @@
{
"WorkspaceSettings": {
"DefaultProject": "bot",
"Projects": {
"bot": "src/bot/bot.json",
"bot-core": "src/bot_core/bot-core.json",
"bot-data": "src/bot_data/bot-data.json",
"admin": "src/modules/admin/admin.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",
"database": "src/modules/database/database.json",
"moderator": "src/modules/moderator/moderator.json",
"permission": "src/modules/permission/permission.json"
},
"Scripts": {
"prod": "export KDB_ENVIRONMENT=production; export KDB_NAME=KDB-Prod; cpl start;",
"stage": "export KDB_ENVIRONMENT=staging; export KDB_NAME=KDB-Stage; cpl start;",
"dev": "export KDB_ENVIRONMENT=development; export KDB_NAME=KDB-Dev; cpl start;",
"build-docker": "cpl b; docker-compose down; docker build -t kdb .",
"compose": "docker-compose up -d",
"docker": "cpl build-docker; cpl compose;"
}
}
}

View File

@@ -1,64 +0,0 @@
version: "3.9"
volumes:
kdb_prod_1:
kdb_staging_1:
kdb_db_1:
kdb_db_2:
services:
kdb_prod_1:
image: kdb/kdb:0.2.1
container_name: kdb_prod_1
depends_on:
- kdb_db_1
volumes:
- kdb_prod_1:/app
environment:
KDB_ENVIRONMENT: "production"
KDB_TOKEN: ""
KDB_PREFIX: "!k "
restart: 'no'
kdb_staging_1:
image: kdb/kdb:0.2.1
container_name: kdb_staging_1
depends_on:
- kdb_db_1
volumes:
- kdb_staging_1:/app
environment:
KDB_ENVIRONMENT: "staging"
KDB_TOKEN: ""
KDB_PREFIX: "!kt "
restart: 'no'
kdb_db_1:
image: mysql:latest
container_name: kdb_db_1
command: mysqld --default-authentication-plugin=mysql_native_password
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: "kd_kdb"
MYSQL_USER: "kd_kdb"
MYSQL_PASSWORD: "kd_kdb"
MYSQL_DATABASE: "kd_kdb"
ports:
- "3307:3306"
volumes:
- kdb_db_1:/var/lib/mysql
kdb_db_2:
image: mysql:latest
container_name: kdb_db_2
command: mysqld --default-authentication-plugin=mysql_native_password
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: "kd_kdb"
MYSQL_USER: "kd_kdb"
MYSQL_PASSWORD: "kd_kdb"
MYSQL_DATABASE: "kd_kdb"
ports:
- "3308:3306"
volumes:
- kdb_db_2:/var/lib/mysql

View File

@@ -1,18 +0,0 @@
# syntax=docker/dockerfile:1
FROM python:3.10.7-bullseye
WORKDIR /app
COPY ./dist/bot/build/ .
RUN pip install cpl-core --extra-index-url https://pip.sh-edraft.de
RUN pip install cpl-discord --extra-index-url https://pip.sh-edraft.de
RUN pip install cpl-query --extra-index-url https://pip.sh-edraft.de
RUN pip install cpl-translation --extra-index-url https://pip.sh-edraft.de
RUN apt-get update -y
RUN apt-get install nano -y
ENV KDB_TOKEN=""
ENV KDB_PREFIX="!kdb "
ENV KDB_ENVIRONMENT="production"
CMD [ "bash", "/app/bot/bot"]

View File

@@ -0,0 +1,44 @@
{
"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",
"auto-role": "src/modules/auto_role/auto-role.json",
"base": "src/modules/base/base.json",
"boot-log": "src/modules/boot_log/boot-log.json",
"database": "src/modules/database/database.json",
"level": "src/modules/level/level.json",
"permission": "src/modules/permission/permission.json",
"stats": "src/modules/stats/stats.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"
},
"Scripts": {
"sv": "cpl set-version",
"set-version": "cpl run set-version $ARGS; echo '';",
"gv": "cpl get-version",
"get-version": "export VERSION=$(cpl run get-version); echo $VERSION;",
"pre-build": "cpl set-version $ARGS",
"post-build": "cpl run post-build",
"pre-prod": "cpl build",
"prod": "export KDB_ENVIRONMENT=production; export KDB_NAME=KDB-Prod; cpl start;",
"pre-stage": "cpl build",
"stage": "export KDB_ENVIRONMENT=staging; export KDB_NAME=KDB-Stage; cpl start;",
"pre-dev": "cpl build",
"dev": "export KDB_ENVIRONMENT=development; export KDB_NAME=KDB-Dev; cpl start;",
"docker-build": "cpl b; docker-compose down; docker build -t kdb-bot/kdb-bot:$(cpl gv) .",
"docker-compose": "docker-compose up -d",
"docker": "cpl docker-build; cpl docker-compose;"
}
}
}

View File

@@ -0,0 +1,49 @@
version: "3.9"
volumes:
kdb_bot_dev_1:
kdb_web_dev_1:
kdb_db_dev_1:
services:
kdb_bot_dev_1:
image: kdb-bot/kdb-bot:0.3
container_name: kdb_bot_dev_1
depends_on:
- kdb_db_dev_1
volumes:
- kdb_bot_dev_1:/app
environment:
KDB_ENVIRONMENT: "dev"
KDB_TOKEN: "OTk4MTU5NjczODkzMDYwNzM4.GN3QyA.yvWO6L7Eu36gXQ7ARDs0Jg2J1VqIDnHLou5lT4"
KDB_PREFIX: "!kd "
restart: 'no'
ports:
- '8044:80'
command: bash /app/bot/bot -dev
kdb_web_dev_1:
image: kdb-web/kdb-web:0.3
container_name: kdb_web_dev_1
depends_on:
- kdb_bot_dev_1
volumes:
- kdb_web_dev_1:/app
restart: 'no'
ports:
- '8043:80'
kdb_db_dev_1:
image: mysql:latest
container_name: kdb_db_dev_1
command: mysqld --default-authentication-plugin=mysql_native_password
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: "kd_kdb"
MYSQL_USER: "kd_kdb"
MYSQL_PASSWORD: "!w5D_&steCejfjq~b{0_DP@e:§X2?JUz"
MYSQL_DATABASE: "kd_kdb"
ports:
- "3308:3306"
volumes:
- kdb_db_dev_1:/var/lib/mysql

View File

@@ -0,0 +1,49 @@
version: "3.9"
volumes:
kdb_bot_staging_1:
kdb_web_staging_1:
kdb_db_staging_1:
services:
kdb_bot_staging_1:
image: kdb-bot/kdb-bot:0.3
container_name: kdb_bot_staging_1
depends_on:
- kdb_db_staging_1
volumes:
- kdb_bot_staging_1:/app
environment:
KDB_ENVIRONMENT: "staging"
KDB_TOKEN: "OTk4MTU5ODAyMzkzOTY0NTk0.G4rLkF.uBQ9pW8X1Lm5agHqvBfzf7qEf8Ton-3a1oJPmY"
KDB_PREFIX: "!kt "
restart: 'no'
ports:
- '8044:80'
command: bash /app/bot/bot -stage
kdb_web_staging_1:
image: kdb-web/kdb-web:0.3
container_name: kdb_web_staging_1
depends_on:
- kdb_bot_staging_1
volumes:
- kdb_web_staging_1:/app
restart: 'no'
ports:
- '8043:80'
kdb_db_staging_1:
image: mysql:latest
container_name: kdb_db_staging_1
command: mysqld --default-authentication-plugin=mysql_native_password
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: "kd_kdb"
MYSQL_USER: "kd_kdb"
MYSQL_PASSWORD: "3&YwVMCwüb=LUt7B§ÖsY?Kr~XRtD#&&f"
MYSQL_DATABASE: "kd_kdb"
ports:
- "3308:3306"
volumes:
- kdb_db_staging_1:/var/lib/mysql

View File

@@ -0,0 +1,48 @@
version: "3.9"
volumes:
kdb_bot_prod_1:
kdb_web_prod_1:
kdb_db_prod_1:
services:
kdb_bot_prod_1:
image: kdb-bot/kdb-bot:0.3
container_name: kdb_bot_prod_1
depends_on:
- kdb_db_prod_1
volumes:
- kdb_bot_prod_1:/app
environment:
KDB_ENVIRONMENT: "production"
KDB_TOKEN: ""
KDB_PREFIX: "!k "
restart: 'no'
ports:
- '8041:80'
kdb_web_prod_1:
image: kdb-web/kdb-web:0.3
container_name: kdb_web_prod_1
depends_on:
- kdb_bot_prod_1
volumes:
- kdb_web_prod_1:/app
restart: 'no'
ports:
- '8042:80'
kdb_db_prod_1:
image: mysql:latest
container_name: kdb_db_prod_1
command: mysqld --default-authentication-plugin=mysql_native_password
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: "kd_kdb"
MYSQL_USER: "kd_kdb"
MYSQL_PASSWORD: "+=gj}(ÄEbRG6_S&ö}ü>zaNT=rE{_~m<y"
MYSQL_DATABASE: "kd_kdb"
ports:
- "3307:3306"
volumes:
- kdb_db_prod_1:/var/lib/mysql

18
kdb-bot/dockerfile Normal file
View File

@@ -0,0 +1,18 @@
# syntax=docker/dockerfile:1
FROM python:3.10.4-alpine
WORKDIR /app
COPY ./dist/bot/build/ .
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]
CMD [ "bash", "/app/bot/bot"]

View File

@@ -15,7 +15,7 @@ __title__ = 'bot'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '0.2.3'
__version__ = '0.3.dev25'
from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='2', micro='3')
version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -7,6 +7,10 @@ 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.configuration.feature_flags_enum import FeatureFlagsEnum
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
class Application(DiscordBotApplicationABC):
@@ -14,6 +18,7 @@ class Application(DiscordBotApplicationABC):
DiscordBotApplicationABC.__init__(self, config, services)
self._services = services
self._config = config
# cpl-core
self._logger: LoggerABC = services.get_service(LoggerABC)
@@ -24,6 +29,12 @@ class Application(DiscordBotApplicationABC):
self._translation: TranslationServiceABC = services.get_service(TranslationServiceABC)
self._t: TranslatePipe = services.get_service(TranslatePipe)
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):
@@ -32,6 +43,12 @@ class Application(DiscordBotApplicationABC):
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.trace(__name__, f'Try to start {DiscordBotService.__name__}')
await self._bot.start_async()
await self._bot.stop_async()
@@ -53,4 +70,4 @@ class Application(DiscordBotApplicationABC):
Console.write_line()
def is_restart(self):
return True if self._configuration.get_configuration('IS_RESTART') == 'true' else False#
return True if self._configuration.get_configuration('IS_RESTART') == 'true' else False #

View 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

View File

@@ -3,8 +3,8 @@
"Name": "bot",
"Version": {
"Major": "0",
"Minor": "2",
"Micro": "3"
"Minor": "3",
"Micro": "dev25"
},
"Author": "Sven Heidemann",
"AuthorEmail": "sven.heidemann@sh-edraft.de",
@@ -16,18 +16,24 @@
"LicenseName": "MIT",
"LicenseDescription": "MIT, see LICENSE for more details.",
"Dependencies": [
"cpl-core==2022.10.0.post6",
"cpl-translation==2022.10.0",
"cpl-query==2022.10.0",
"cpl-discord==2022.10.0.post5"
"cpl-core==2022.10.0.post7",
"cpl-translation==2022.10.0.post2",
"cpl-query==2022.10.0.post2",
"cpl-discord==2022.10.0.post6",
"Flask==2.2.2",
"Flask-Classful==0.14.2",
"Flask-Cors==3.0.10",
"PyJWT==2.6.0",
"waitress==2.1.2",
"Flask-SocketIO==5.3.1",
"eventlet==0.33.1",
"requests-oauthlib==1.3.1"
],
"DevDependencies": [
"cpl-cli==2022.10.0"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {
"linux": ""
},
"PythonPath": {},
"Classifiers": []
},
"BuildSettings": {
@@ -45,6 +51,7 @@
],
"PackageData": {},
"ProjectReferences": [
"../bot_api/bot-api.json",
"../bot_core/bot-core.json",
"../bot_data/bot-data.json",
"../modules/base/base.json",
@@ -53,6 +60,7 @@
"../modules/base/base.json",
"../modules/boot_log/boot-log.json",
"../modules/database/database.json",
"../modules/level/level.json",
"../modules/moderator/moderator.json",
"../modules/permission/permission.json"
]

View File

@@ -33,17 +33,17 @@
},
"DatabaseSettings": {
"Host": "localhost",
"User": "kd_kdb",
"Password": "VGpZcihrb0N2T2MyZUlURQ==",
"Database": "keksdose_bot_dev",
"User": "root",
"Password": "MTAwNjE5OTdOaWNrLko=",
"Database": "kd_kdb",
"Charset": "utf8mb4",
"UseUnicode": "true",
"Buffered": "true",
"AuthPlugin": "mysql_native_password"
},
"DiscordBot": {
"Token": "OTk4MTYwNDI3Njg5MTgxMjM3.GI7h67.BqD6Lu1Tz0MuG8iktYrcLnHi1pNozyMiWFGTKI",
"Prefix": "!ke "
"Token": "MTAyOTgxMjE0Mjk4NTE5MTYxNA.G4ArAJ.sZh6pE-mwO2qDAr1mfHEoo7EwbJb-TZT8h6nGg",
"Prefix": "!kn "
},
"Bot": {
"910199451145076828": {

View File

@@ -6,44 +6,61 @@
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
},
"LoggingSettings": {
"Path": "logs/",
"Path": "logs/$date_now/",
"Filename": "bot.log",
"ConsoleLogLevel": "DEBUG",
"FileLogLevel": "DEBUG"
"ConsoleLogLevel": "TRACE",
"FileLogLevel": "TRACE"
},
"BotLoggingSettings": {
"Api": {
"Path": "logs/$date_now/",
"Filename": "api.log",
"ConsoleLogLevel": "TRACE",
"FileLogLevel": "TRACE"
},
"Command": {
"Path": "logs/",
"Path": "logs/$date_now/",
"Filename": "commands.log",
"ConsoleLogLevel": "DEBUG",
"FileLogLevel": "DEBUG"
"ConsoleLogLevel": "TRACE",
"FileLogLevel": "TRACE"
},
"Database": {
"Path": "logs/",
"Path": "logs/$date_now/",
"Filename": "database.log",
"ConsoleLogLevel": "DEBUG",
"FileLogLevel": "DEBUG"
"FileLogLevel": "TRACE"
},
"Message": {
"Path": "logs/",
"Path": "logs/$date_now/",
"Filename": "message.log",
"ConsoleLogLevel": "DEBUG",
"FileLogLevel": "DEBUG"
"ConsoleLogLevel": "TRACE",
"FileLogLevel": "TRACE"
}
},
"DiscordBot": {
"Token": "OTk4MTU5NjczODkzMDYwNzM4.GN3QyA.yvWO6L7Eu36gXQ7ARDs0Jg2J1VqIDnHLou5lT4",
"Prefix": "!kd "
},
"Translation": {
"DefaultLanguage": "de",
"Languages": [
"de"
]
},
"DiscordBot": {
"Token": "OTk4MTU5NjczODkzMDYwNzM4.GN3QyA.yvWO6L7Eu36gXQ7ARDs0Jg2J1VqIDnHLou5lT4",
"Prefix": "!kd "
"DatabaseSettings": {
"Host": "kdb_db_dev_1",
"User": "kd_kdb",
"Password": "IXc1RF8mc3RlQ2VqZmpxfmJ7MF9EUEBlOsKnWDI/SlV6",
"Database": "kd_kdb",
"Port": "3306",
"Charset": "utf8mb4",
"UseUnicode": "true",
"Buffered": "true",
"AuthPlugin": "mysql_native_password"
},
"Bot": {
"910199451145076828": {
"MessageDeleteTimer": 6
"MessageDeleteTimer": 4
},
"Technicians": [
240160344557879316,

View File

@@ -1,10 +1,42 @@
{
"TimeFormatSettings": {
"DateFormat": "%Y-%m-%d",
"TimeFormat": "%H:%M:%S",
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
},
"LoggingSettings": {
"Path": "logs/",
"Filename": "log_dev.log",
"Filename": "bot.log",
"ConsoleLogLevel": "DEBUG",
"FileLogLevel": "TRACE"
},
"BotLoggingSettings": {
"Api": {
"Path": "logs/",
"Filename": "api.log",
"ConsoleLogLevel": "TRACE",
"FileLogLevel": "TRACE"
},
"Command": {
"Path": "logs/",
"Filename": "commands.log",
"ConsoleLogLevel": "DEBUG",
"FileLogLevel": "TRACE"
},
"Database": {
"Path": "logs/",
"Filename": "database.log",
"ConsoleLogLevel": "DEBUG",
"FileLogLevel": "TRACE"
},
"Message": {
"Path": "logs/",
"Filename": "message.log",
"ConsoleLogLevel": "DEBUG",
"FileLogLevel": "TRACE"
}
},
"DatabaseSettings": {
"Host": "localhost",
"User": "kd_kdb",
@@ -26,7 +58,8 @@
"Technicians": [
240160344557879316
],
"DeployFilesPath": "../../deploy"
"WaitForRestart": 4,
"WaitForShutdown": 4
},
"Base": {
"910199451145076828": {
@@ -43,7 +76,12 @@
},
"BootLog": {
"910199451145076828": {
"LoginMessageChannelId": "910199452915093588"
"LoginMessageChannelId": 910199452915093588
}
},
"Level": {
"910199451145076828": {
"ChangedLevelNotificationChannelId": 910199452667637892
}
},
"Permission": {

View File

@@ -0,0 +1,97 @@
{
"TimeFormatSettings": {
"DateFormat": "%Y-%m-%d",
"TimeFormat": "%H:%M:%S",
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
},
"LoggingSettings": {
"Path": "logs/",
"Filename": "bot.log",
"ConsoleLogLevel": "DEBUG",
"FileLogLevel": "TRACE"
},
"BotLoggingSettings": {
"Api": {
"Path": "logs/",
"Filename": "api.log",
"ConsoleLogLevel": "TRACE",
"FileLogLevel": "TRACE"
},
"Command": {
"Path": "logs/",
"Filename": "commands.log",
"ConsoleLogLevel": "DEBUG",
"FileLogLevel": "TRACE"
},
"Database": {
"Path": "logs/",
"Filename": "database.log",
"ConsoleLogLevel": "DEBUG",
"FileLogLevel": "TRACE"
},
"Message": {
"Path": "logs/",
"Filename": "message.log",
"ConsoleLogLevel": "DEBUG",
"FileLogLevel": "TRACE"
}
},
"DatabaseSettings": {
"Host": "localhost",
"User": "kd_kdb",
"Password": "VGpZcihrb0N2T2MyZUlURQ==",
"Database": "keksdose_bot_dev",
"Charset": "utf8mb4",
"UseUnicode": "true",
"Buffered": "true",
"AuthPlugin": "mysql_native_password"
},
"DiscordBot": {
"Token": "OTk4MTYwNDI3Njg5MTgxMjM3.GI7h67.BqD6Lu1Tz0MuG8iktYrcLnHi1pNozyMiWFGTKI",
"Prefix": "!ke "
},
"Bot": {
"910199451145076828": {
"MessageDeleteTimer": 2
},
"Technicians": [
240160344557879316
],
"WaitForRestart": 4,
"WaitForShutdown": 4
},
"Base": {
"910199451145076828": {
"MaxVoiceStateHours": 24,
"XpPerMessage": 2,
"XpPerOntimeHour": 4,
"AFKChannelIds": [
910199452915093593,
910199452915093594
],
"AFKCommandChannelId": 910199452915093594,
"HelpCommandReferenceUrl": "https://git.sh-edraft.de/sh-edraft.de/kd_discord_bot/wiki/Befehle"
}
},
"BootLog": {
"910199451145076828": {
"LoginMessageChannelId": 910199452915093588
}
},
"Level": {
"910199451145076828": {
"ChangedLevelNotificationChannelId": 910199452667637892
}
},
"Permission": {
"910199451145076828": {
"AdminRoleIds": [
925072155203477584
],
"ModeratorRoleIds": [
925072209884635167
]
}
}
}

View File

@@ -12,6 +12,12 @@
"FileLogLevel": "INFO"
},
"BotLoggingSettings": {
"Api": {
"Path": "logs/$date_now/",
"Filename": "api.log",
"ConsoleLogLevel": "ERROR",
"FileLogLevel": "INFO"
},
"Command": {
"Path": "logs/$date_now/",
"Filename": "commands.log",
@@ -38,9 +44,9 @@
]
},
"DatabaseSettings": {
"Host": "kdb_db_1",
"Host": "kdb_db_prod_1",
"User": "kd_kdb",
"Password": "a2Rfa2Ri",
"Password": "Kz1nan0ow4RFYlJHNl9TJsO2fcO8PnphTlQ9ckV7X35tPHk=",
"Database": "kd_kdb",
"Port": "3306",
"Charset": "utf8mb4",

View File

@@ -12,6 +12,12 @@
"FileLogLevel": "DEBUG"
},
"BotLoggingSettings": {
"Api": {
"Path": "logs/$date_now/",
"Filename": "api.log",
"ConsoleLogLevel": "INFO",
"FileLogLevel": "DEBUG"
},
"Command": {
"Path": "logs/$date_now/",
"Filename": "commands.log",
@@ -38,9 +44,9 @@
]
},
"DatabaseSettings": {
"Host": "kdb_db_2",
"Host": "kdb_db_staging_1",
"User": "kd_kdb",
"Password": "a2Rfa2Ri",
"Password": "MyZZd1ZNQ3fDvGI9TFV0N0LCp8OWc1k/S3J+WFJ0RCMmJmY=",
"Database": "kd_kdb",
"Port": "3306",
"Charset": "utf8mb4",

View File

@@ -1,5 +1,6 @@
{
"FeatureFlags": {
"ApiModule": false,
"AdminModule": true,
"AutoRoleModule": true,
"BaseModule": true,
@@ -8,6 +9,7 @@
"CoreExtensionModule": true,
"DatabaseModule": true,
"ModeratorModule": true,
"LevelModule": true,
"PermissionModule": true,
"PresenceModule": true
}

View File

@@ -11,6 +11,7 @@ from bot.startup_discord_extension import StartupDiscordExtension
from bot.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 modules.boot_log.boot_log_extension import BootLogExtension
from modules.database.database_extension import DatabaseExtension
@@ -29,6 +30,7 @@ class Program:
.use_extension(StartupMigrationExtension) \
.use_extension(BootLogExtension) \
.use_extension(DatabaseExtension) \
.use_extension(AppApiExtension) \
.use_startup(Startup)
self.app: Application = await app_builder.build_async()
await self.app.run_async()

View File

@@ -1,15 +1,16 @@
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 modules.admin.admin_module import AdminModule
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.database.database_module import DatabaseModule
from modules.moderator.moderator_module import ModeratorModule
from modules.level.level_module import LevelModule
from modules.permission.permission_module import PermissionModule
from modules.stats.stats_module import StatsModule
class ModuleList:
@@ -20,12 +21,13 @@ class ModuleList:
return List(type, [
CoreModule, # has to be first!
DataModule,
AdminModule,
AutoRoleModule,
BaseModule,
DatabaseModule,
ModeratorModule,
LevelModule,
PermissionModule,
ApiModule,
StatsModule,
# has to be last!
BootLogModule,
CoreExtensionModule,

View File

@@ -9,6 +9,7 @@ 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
@@ -40,6 +41,9 @@ class Startup(StartupABC):
services.add_singleton(CustomFileLoggerABC, DatabaseLogger)
services.add_singleton(CustomFileLoggerABC, MessageLogger)
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))

View File

@@ -4,8 +4,11 @@ from cpl_core.dependency_injection import ServiceCollectionABC
from cpl_core.environment import ApplicationEnvironmentABC
from bot_data.abc.migration_abc import MigrationABC
from bot_data.migration.api_migration import ApiMigration
from bot_data.migration.auto_role_migration import AutoRoleMigration
from bot_data.migration.initial_migration import InitialMigration
from bot_data.migration.level_migration import LevelMigration
from bot_data.migration.stats_migration import StatsMigration
from bot_data.service.migration_service import MigrationService
@@ -21,3 +24,6 @@ class StartupMigrationExtension(StartupExtensionABC):
services.add_transient(MigrationService)
services.add_transient(MigrationABC, InitialMigration)
services.add_transient(MigrationABC, AutoRoleMigration) # 03.10.2022 #54 - 0.2.2
services.add_transient(MigrationABC, ApiMigration) # 15.10.2022 #70 - 0.3.0
services.add_transient(MigrationABC, LevelMigration) # 06.11.2022 #25 - 0.3.0
services.add_transient(MigrationABC, StatsMigration) # 09.11.2022 #46 - 0.3.0

View File

@@ -11,6 +11,7 @@ from bot_core.configuration.bot_logging_settings import BotLoggingSettings
from bot_core.configuration.bot_settings import BotSettings
from modules.base.configuration.base_settings import BaseSettings
from modules.boot_log.configuration.boot_log_settings import BootLogSettings
from modules.level.configuration.level_settings import LevelSettings
from modules.permission.configuration.permission_settings import PermissionSettings
@@ -35,6 +36,7 @@ class StartupSettingsExtension(StartupExtensionABC):
self._configure_settings_with_sub_settings(configuration, BotSettings, lambda x: x.servers, lambda x: x.id)
self._configure_settings_with_sub_settings(configuration, BaseSettings, lambda x: x.servers, lambda x: x.id)
self._configure_settings_with_sub_settings(configuration, BootLogSettings, lambda x: x.servers, lambda x: x.id)
self._configure_settings_with_sub_settings(configuration, LevelSettings, lambda x: x.servers, lambda x: x.id)
self._configure_settings_with_sub_settings(configuration, PermissionSettings, lambda x: x.servers, lambda x: x.id)
self._configure_settings_with_sub_settings(configuration, BotLoggingSettings, lambda x: x.files, lambda x: x.key)

View File

@@ -45,6 +45,30 @@
"no_entry_point_error": "Fehler: Kein Eintrittspunkt!",
"extension_failed": "Fehler: Erweiterung ist fehlgeschlagen!",
"bot_not_ready_yet": "Ey Alter! Gedulde dich doch mal! ..."
},
"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"
}
},
"modules": {
@@ -149,8 +173,95 @@
"boot_log": {
"login_message": "Ich bin on the line :D\nDer Scheiß hat {} Sekunden gedauert"
},
"level": {
"new_level_message": "<@{}> ist nun Level {}",
"seeding_started": "Levelsystem wird neu geladen.",
"seeding_failed": "Levelsystem konnte nicht neu geladen werden.",
"seeding_finished": "Levelsystem wurde Erfolgreich neu geladen.",
"error": {
"nothing_found": "Keine Level Einträge gefunden.",
"level_with_name_already_exists": "Ein Level mit dem Namen {} existiert bereits!",
"level_with_xp_already_exists": "Das Level {} hat bereits die Mindest XP {}!"
},
"list": {
"title": "Level:",
"description": "Konfigurierte Level:",
"name": "Name",
"min_xp": "Mindest XP",
"permission_int": "Berechtigungen"
},
"create": {
"created": "Level {} mit Berechtigungen {} wurde erstellt."
},
"remove": {
"success": "Level {} wurde entfernt :D",
"error": {
"not_found": "Level {} nicht gefunden!"
}
},
"down": {
"already_first": "{} hat bereits das erste Level.",
"success": "{} wurde auf Level {} runtergesetzt :)",
"failed": "{} konnte nicht runtergesetzt werden :("
},
"up": {
"already_last": "{} hat bereits das höchste Level.",
"success": "{} wurde auf Level {} hochgesetzt :)",
"failed": "{} konnte nicht hochgesetzt werden :("
},
"set": {
"already_level": "{} hat bereits das Level {} :/",
"success": "{} ist nun Level {} :)",
"failed": "Das Level von {} konnte nicht auf {} gesetzt werden :(",
"not_found": "Das Level {} konnte nicht gefunden werden :("
}
},
"database": {},
"permission": {
"permission": {},
"stats": {
"list": {
"statistic": "Statistik",
"description": "Beschreibung",
"nothing_found": "Keine Statistiken gefunden."
},
"view": {
"statistic": "Statistik",
"description": "Beschreibung",
"failed": "Statistik kann nicht gezeigt werden :("
},
"add": {
"failed": "Statistik kann nicht hinzugefügt werden :(",
"success": "Statistik wurde hinzugefügt :D"
},
"edit": {
"failed": "Statistik kann nicht bearbeitet werden :(",
"success": "Statistik wurde gespeichert :D"
},
"remove": {
"failed": "Statistik kann nicht gelöscht werden :(",
"success": "Statistik wurde gelöscht :D"
}
}
},
"api": {
"mail": {
"automatic_mail": "\n\nDies ist eine automatische E-Mail.\nGesendet von {}-{}@{}"
},
"api": {
"test_mail": {
"subject": "Krümmelmonster Web Interface Test-Mail",
"message": "Dies ist eine Test-Mail vom Krümmelmonster Web Interface\nGesendet von {}-{}"
}
},
"auth": {
"confirmation": {
"subject": "E-Mail für {} {} bestätigen",
"message": "Öffne den Link um die E-Mail zu bestätigen:\n{}auth/register/{}"
},
"forgot_password": {
"subject": "Passwort für {} {} zurücksetzen",
"message": "Öffne den Link um das Passwort zu ändern:\n{}auth/forgot-password/{}"
}
}
}
}

View File

@@ -11,11 +11,11 @@ Discord bot for the Keksdose discord Server
"""
__title__ = 'modules.admin'
__title__ = 'bot_api'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '0.2.3'
__version__ = '0.3.dev25'
from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='2', micro='3')
version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot Keksdose bot
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_api.service'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '0.3.dev25'
from collections import namedtuple
# imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -0,0 +1,89 @@
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
async def add_auth_user_async(self, user_dto: AuthUserDTO): pass
@abstractmethod
async def add_auth_user_by_oauth_async(self, dto: OAuthDTO): pass
@abstractmethod
async def add_auth_user_by_discord_async(self, user_dto: AuthUserDTO, dc_id: int) -> 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
async def login_async(self, user_dto: AuthUserDTO) -> 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

View File

@@ -0,0 +1,13 @@
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

View File

@@ -0,0 +1,17 @@
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

View File

@@ -0,0 +1,16 @@
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

156
kdb-bot/src/bot_api/api.py Normal file
View File

@@ -0,0 +1,156 @@
import re
import sys
import textwrap
import uuid
from functools import partial
from typing import Union
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_api.configuration.frontend_settings import FrontendSettings
from bot_api.exception.service_error_code_enum import ServiceErrorCode
from bot_api.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,
frontend_settings: FrontendSettings,
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
self._socketio = SocketIO(self, cors_allowed_origins='*', path='/api/socket.io')
self._socketio.on_event('connect', self.on_connect)
self._socketio.on_event('disconnect', self.on_disconnect)
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)
wsgi.server(
eventlet.listen((self._api_settings.host, self._api_settings.port)),
self,
log_output=False
)
def on_connect(self):
self._logger.info(__name__, f'Client connected')
def on_disconnect(self):
self._logger.info(__name__, f'Client disconnected')

View File

@@ -0,0 +1,52 @@
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.discord.server_controller import ServerController
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(ServerController)
# cpl-discord
self._dc.add_event(DiscordEventTypesEnum.on_ready.value, BotApiOnReadyEvent)

View File

@@ -0,0 +1,24 @@
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)

View File

@@ -0,0 +1,26 @@
from cpl_core.application import ApplicationExtensionABC
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceProviderABC
from bot_api.abc.auth_service_abc import AuthServiceABC
from bot_api.configuration.authentication_settings import AuthenticationSettings
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.abc.auth_user_repository_abc import AuthUserRepositoryABC
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
auth_settings: AuthenticationSettings = config.get_configuration(AuthenticationSettings)
auth_users: AuthUserRepositoryABC = services.get_service(AuthUserRepositoryABC)
auth: AuthServiceABC = services.get_service(AuthServiceABC)
Route.init_authorize(auth_users, auth)

View File

@@ -0,0 +1,44 @@
{
"ProjectSettings": {
"Name": "bot-api",
"Version": {
"Major": "0",
"Minor": "3",
"Micro": "dev70"
},
"Author": "",
"AuthorEmail": "",
"Description": "",
"LongDescription": "",
"URL": "",
"CopyrightDate": "",
"CopyrightName": "",
"LicenseName": "",
"LicenseDescription": "",
"Dependencies": [
"cpl-core==2022.10.0.post7"
],
"DevDependencies": [
"cpl-cli==2022.10.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": []
}
}

View File

@@ -0,0 +1,33 @@
{
"Api": {
"Port": 80,
"Host": "0.0.0.0",
"RedirectToHTTPS": false
},
"Authentication": {
"SecretKey": "RjNiNUxEeisjSnZ6Zz1XIUBnc2EleHNG",
"Issuer": "http://localhost:8044",
"Audience": "http://localhost:8043",
"TokenExpireTime": 1,
"RefreshTokenExpireTime": 7
},
"DiscordAuthentication": {
"ClientSecret": "cmhqYmF4MXBCd2IzeEZoSXRZQ29vY3NwUWwxQzFTZng=",
"RedirectURL": "http://localhost:8043/auth/register",
"Scope": [
"identify",
"email"
],
"TokenURL": "https://discordapp.com/api/oauth2/token",
"AuthURL": "https://discordapp.com/api/oauth2/authorize"
},
"Frontend": {
"URL": "http://localhost:8043/"
},
"EMailClientSettings": {
"Host": "mail.sh-edraft.de",
"Port": "587",
"UserName": "dev-srv@sh-edraft.de",
"Credentials": "RmBOQX1eNFYiYjgsSid3fV1nelc2WA=="
}
}

View File

@@ -0,0 +1,28 @@
{
"Api": {
"Port": 5000,
"Host": "0.0.0.0",
"RedirectToHTTPS": false
},
"Authentication": {
"SecretKey": "RjNiNUxEeisjSnZ6Zz1XIUBnc2EleHNG",
"Issuer": "http://localhost:5000",
"Audience": "http://localhost:4200",
"TokenExpireTime": 1,
"RefreshTokenExpireTime": 7
},
"DiscordAuthentication": {
"ClientSecret": "V3FTb3JYVFBiVktEeHZxdWJDWW4xcnBCbXRwdmpwcy0=",
"_RedirectURL": "http://localhost:5000/api/auth/discord/register",
"RedirectURL": "http://localhost:4200/auth/register",
"Scope": [
"identify",
"email"
],
"TokenURL": "https://discordapp.com/api/oauth2/token",
"AuthURL": "https://discordapp.com/api/oauth2/authorize"
},
"Frontend": {
"URL": "http://localhost:4200/"
}
}

View File

@@ -0,0 +1,27 @@
{
"Api": {
"Port": 8044,
"Host": "0.0.0.0",
"RedirectToHTTPS": false
},
"Authentication": {
"SecretKey": "RjNiNUxEeisjSnZ6Zz1XIUBnc2EleHNG",
"Issuer": "http://localhost:8084",
"Audience": "http://localhost:4200",
"TokenExpireTime": 1,
"RefreshTokenExpireTime": 7
},
"DiscordAuthentication": {
"ClientSecret": "V3FTb3JYVFBiVktEeHZxdWJDWW4xcnBCbXRwdmpwcy0=",
"RedirectURL": "http://localhost:4200/auth/register",
"Scope": [
"identify",
"email"
],
"TokenURL": "https://discordapp.com/api/oauth2/token",
"AuthURL": "https://discordapp.com/api/oauth2/authorize"
},
"Frontend": {
"URL": "http://localhost:4200/"
}
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,33 @@
{
"Api": {
"Port": 80,
"Host": "0.0.0.0",
"RedirectToHTTPS": false
},
"Authentication": {
"SecretKey": "cEwzW2BdcWxGLTBdPClJImNIbDFJXjVsPGw=",
"Issuer": "https://kdb.keksdose-gaming.de/",
"Audience": "https://kdb.keksdose-gaming.de/",
"TokenExpireTime": 1,
"RefreshTokenExpireTime": 7
},
"DiscordAuthentication": {
"ClientSecret": "SXpIOGY4ZzhWVEljOXJwSk5QcVpNU0lmRDNTb2c1Vk8=",
"RedirectURL": "https://kdb.keksdose-gaming.de/auth/register",
"Scope": [
"identify",
"email"
],
"TokenURL": "https://discordapp.com/api/oauth2/token",
"AuthURL": "https://discordapp.com/api/oauth2/authorize"
},
"Frontend": {
"URL": "https://kdb.keksdose-gaming.de/"
},
"EMailClientSettings": {
"Host": "mail.sh-edraft.de",
"Port": "587",
"UserName": "kruemmelmonster@sh-edraft.de",
"Credentials": "YjAwT3tPSVspezdadExdOEkoV3M3XiNb"
}
}

View File

@@ -0,0 +1,33 @@
{
"Api": {
"Port": 80,
"Host": "0.0.0.0",
"RedirectToHTTPS": false
},
"Authentication": {
"SecretKey": "Kj87RjklLUM1MytsUjtbcCswRidBV2VdMXU=",
"Issuer": "https://kdb-test.keksdose-gaming.de/",
"Audience": "https://kdb-test.keksdose-gaming.de/",
"TokenExpireTime": 1,
"RefreshTokenExpireTime": 7
},
"DiscordAuthentication": {
"ClientSecret": "VVdRZTg1SnFxUExCNmhzU1RZY05mTHV5TmVaV0NkUmc=",
"RedirectURL": "https://kdb-test.keksdose-gaming.de/auth/register",
"Scope": [
"identify",
"email"
],
"TokenURL": "https://discordapp.com/api/oauth2/token",
"AuthURL": "https://discordapp.com/api/oauth2/authorize"
},
"Frontend": {
"URL": "https://kdb-test.keksdose-gaming.de/"
},
"EMailClientSettings": {
"Host": "mail.sh-edraft.de",
"Port": "587",
"UserName": "kruemmelmonster@sh-edraft.de",
"Credentials": "YjAwT3tPSVspezdadExdOEkoV3M3XiNb"
}
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot Keksdose bot
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_api.configuration'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '0.3.dev25'
from collections import namedtuple
# imports
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -0,0 +1,35 @@
import traceback
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.console import Console
class ApiSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelABC.__init__(self)
self._port = 80
self._host = ''
self._redirect_to_https = False
@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
def from_dict(self, settings: dict):
try:
self._port = int(settings['Port'])
self._host = settings['Host']
self._redirect_to_https = bool(settings['RedirectToHTTPS'])
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

@@ -0,0 +1,48 @@
import traceback
from datetime import datetime
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.console import Console
class AuthenticationSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelABC.__init__(self)
self._secret_key = ''
self._issuer = ''
self._audience = ''
self._token_expire_time = 0
self._refresh_token_expire_time = 0
@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
def from_dict(self, settings: dict):
try:
self._secret_key = settings['SecretKey']
self._issuer = settings['Issuer']
self._audience = settings['Audience']
self._token_expire_time = int(settings['TokenExpireTime'])
self._refresh_token_expire_time = int(settings['RefreshTokenExpireTime'])
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

@@ -0,0 +1,48 @@
import traceback
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.console import Console
from cpl_query.extension import List
class DiscordAuthenticationSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelABC.__init__(self)
self._client_secret = ''
self._redirect_url = ''
self._scope = List()
self._token_url = ''
self._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
def from_dict(self, settings: dict):
try:
self._client_secret = settings['ClientSecret']
self._redirect_url = settings['RedirectURL']
self._scope = List(str, settings['Scope'])
self._token_url = settings['TokenURL']
self._auth_url = settings['AuthURL']
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

@@ -0,0 +1,23 @@
import traceback
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.console import Console
class FrontendSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelABC.__init__(self)
self._url = ''
@property
def url(self) -> str:
return self._url
def from_dict(self, settings: dict):
try:
self._url = settings['URL']
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

@@ -0,0 +1,55 @@
from typing import Optional
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_cli.configuration.version_settings_name_enum import VersionSettingsNameEnum
class VersionSettings(ConfigurationModelABC):
def __init__(
self,
major: str = None,
minor: str = None,
micro: str = None
):
ConfigurationModelABC.__init__(self)
self._major: Optional[str] = major
self._minor: Optional[str] = minor
self._micro: Optional[str] = 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
def to_str(self) -> str:
if self._micro is None:
return f'{self._major}.{self._minor}'
else:
return f'{self._major}.{self._minor}.{self._micro}'
def from_dict(self, settings: dict):
self._major = settings[VersionSettingsNameEnum.major.value]
self._minor = settings[VersionSettingsNameEnum.minor.value]
micro = settings[VersionSettingsNameEnum.micro.value]
if micro != '':
self._micro = micro
def to_dict(self) -> dict:
version = {
VersionSettingsNameEnum.major.value: self._major,
VersionSettingsNameEnum.minor.value: self._minor,
}
if self._micro is not None:
version[VersionSettingsNameEnum.micro.value] = self._micro
return version

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot Keksdose bot
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_api.controller'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '0.3.dev25'
from collections import namedtuple
# imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -0,0 +1,155 @@
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.exception.service_error_code_enum import ServiceErrorCode
from bot_api.exception.service_exception import ServiceException
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_data.model.auth_role_enum import AuthRoleEnum
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
):
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
@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()))
@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())
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):
dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True))
await self._auth_service.add_auth_user_async(dto)
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:
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):
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):
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')
@Route.authorize
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

View File

@@ -0,0 +1,89 @@
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, session
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_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.o_auth_dto import OAuthDTO
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)
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)
login_url, state = oauth.authorization_url(self._auth_settings.auth_url)
return jsonify({'loginUrl': login_url})
@Route.get(f'{BasePath}/create-user')
async def discord_create_user(self) -> Response:
response = self._get_user_from_discord_response()
result = await self._auth_service.add_auth_user_by_discord_async(AuthUserDTO(
0,
response['username'],
response['discriminator'],
response['email'],
str(uuid.uuid4()),
None,
AuthRoleEnum.normal
), response['id'])
return jsonify(result.to_dict())
@Route.post(f'{BasePath}/register')
async def discord_register(self):
dto: OAuthDTO = JSONProcessor.process(OAuthDTO, request.get_json(force=True, silent=True))
await self._auth_service.add_auth_user_by_oauth_async(dto)
return '', 200

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot Keksdose bot
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_api.controller.discord'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '0.3.dev25'
from collections import namedtuple
# imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -0,0 +1,65 @@
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 Response, jsonify, request
from bot_api.api import Api
from bot_api.filter.discord.server_select_criteria import ServerSelectCriteria
from bot_api.json_processor import JSONProcessor
from bot_api.logging.api_logger import ApiLogger
from bot_api.route.route import Route
from bot_api.service.discord_service import DiscordService
from bot_data.model.auth_role_enum import AuthRoleEnum
class ServerController:
BasePath = f'/api/discord/server'
def __init__(
self,
config: ConfigurationABC,
env: ApplicationEnvironmentABC,
logger: ApiLogger,
t: TranslatePipe,
api: Api,
mail_settings: EMailClientSettings,
mailer: EMailClientABC,
discord_service: DiscordService
):
self._config = config
self._env = env
self._logger = logger
self._t = t
self._api = api
self._mail_settings = mail_settings
self._mailer = mailer
self._discord_service = discord_service
@Route.get(f'{BasePath}/get/servers')
@Route.authorize(role=AuthRoleEnum.admin)
async def get_all_servers(self) -> Response:
result = await self._discord_service.get_all_servers()
result = result.select(lambda x: x.to_dict())
return jsonify(result)
@Route.get(f'{BasePath}/get/servers-by-user')
@Route.authorize
async def get_all_servers_by_user(self) -> Response:
result = await self._discord_service.get_all_servers_by_user()
result = result.select(lambda x: x.to_dict())
return jsonify(result)
@Route.post(f'{BasePath}/get/filtered')
@Route.authorize
async def get_filtered_servers(self) -> Response:
dto: ServerSelectCriteria = JSONProcessor.process(ServerSelectCriteria, request.get_json(force=True, silent=True))
result = await self._discord_service.get_filtered_servers_async(dto)
result.result = result.result.select(lambda x: x.to_dict())
return jsonify(result.to_dict())
@Route.get(f'{BasePath}/get/<id>')
@Route.authorize
async def get_server_by_id(self, id: int) -> Response:
result = await self._discord_service.get_server_by_id_async(id)
return jsonify(result.to_dict())

View File

@@ -0,0 +1,78 @@
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
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

View File

@@ -11,11 +11,11 @@ Discord bot for the Keksdose discord Server
"""
__title__ = 'bot_core.abc'
__title__ = 'bot_api.event'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '0.2.3'
__version__ = '0.3.dev25'
from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='2', micro='3')
version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -0,0 +1,13 @@
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()

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot Keksdose bot
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_api.exception'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '0.3.dev25'
from collections import namedtuple
# imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -0,0 +1,24 @@
from enum import Enum
from werkzeug.exceptions import Unauthorized
class ServiceErrorCode(Enum):
Unknown = 0
InvalidDependencies = 1
InvalidData = 2
NotFound = 3
DataAlreadyExists = 4
UnableToAdd = 5
UnableToDelete = 6
InvalidUser = 7
ConnectionFailed = 8
Timeout = 9
MailError = 10
Unauthorized = 11
Forbidden = 12

View File

@@ -0,0 +1,13 @@
from bot_api.exception.service_error_code_enum import ServiceErrorCode
class ServiceException(Exception):
def __init__(self, error_code: ServiceErrorCode, message: str, *args):
Exception.__init__(self, *args)
self.error_code = error_code
self.message = message
def get_detailed_message(self) -> str:
return f'ServiceException - ErrorCode: {self.error_code} - ErrorMessage: {self.message}'

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot Keksdose bot
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_api.filter'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '0.3.dev25'
from collections import namedtuple
# imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -0,0 +1,23 @@
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

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot Keksdose bot
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_api.filter.discord'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '0.3.dev25'
from collections import namedtuple
# imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -0,0 +1,17 @@
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

View File

@@ -0,0 +1,39 @@
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)
args.append(value)
elif parameter.default != Parameter.empty:
args.append(parameter.default)
else:
args.append(None)
return _t(*args)

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot Keksdose bot
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_api.logging'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '0.3.dev25'
from collections import namedtuple
# imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -0,0 +1,11 @@
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)

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot Keksdose bot
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_api.model'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '0.3.dev25'
from collections import namedtuple
# imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -0,0 +1,99 @@
from typing import Optional
from bot_api.abc.dto_abc import DtoABC
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,
):
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
@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
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 = values['authRole']
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,
}

View File

@@ -0,0 +1,21 @@
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
}

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot Keksdose bot
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_api.model.discord'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '0.3.dev25'
from collections import namedtuple
# imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -0,0 +1,58 @@
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,
}

View File

@@ -0,0 +1,21 @@
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
}

View File

@@ -0,0 +1,21 @@
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
}

View File

@@ -0,0 +1,34 @@
import traceback
from typing import Optional
from cpl_core.console import Console
from bot_api.abc.dto_abc import DtoABC
from bot_api.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
}

View File

@@ -0,0 +1,44 @@
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
}

View File

@@ -0,0 +1,32 @@
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
}

View File

@@ -0,0 +1,67 @@
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,
}

View File

@@ -0,0 +1,32 @@
import traceback
from cpl_core.console import Console
from bot_api.abc.dto_abc import DtoABC
class TokenDTO(DtoABC):
def __init__(self, token: str, refresh_token: str):
DtoABC.__init__(self)
self._token = token
self._refresh_token = refresh_token
@property
def token(self) -> str:
return self._token
@property
def refresh_token(self) -> str:
return self._refresh_token
def from_dict(self, values: dict):
self._token = values['token']
self._refresh_token = values['refreshToken']
def to_dict(self) -> dict:
return {
'token': self._token,
'refreshToken': self._refresh_token
}

View File

@@ -0,0 +1,45 @@
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 = values['authUser']
self._new_auth_user = 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
}

View File

@@ -0,0 +1,43 @@
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,
}

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot Keksdose bot
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_api.route'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '0.3.dev25'
from collections import namedtuple
# imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -0,0 +1,103 @@
import functools
from functools import wraps
from typing import Optional, Callable
from flask import request, jsonify
from flask_cors import cross_origin
from bot_api.abc.auth_service_abc import AuthServiceABC
from bot_api.exception.service_error_code_enum import ServiceErrorCode
from bot_api.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
class Route:
registered_routes = {}
_auth_users: Optional[AuthUserRepositoryABC] = None
_auth: Optional[AuthServiceABC] = None
@classmethod
def init_authorize(cls, auth_users: AuthUserRepositoryABC, auth: AuthServiceABC):
cls._auth_users = auth_users
cls._auth = auth
@classmethod
def authorize(cls, f: Callable = None, role: AuthRoleEnum = None):
if f is None:
return functools.partial(cls.authorize, role=role)
@wraps(f)
async def decorator(*args, **kwargs):
token = None
if 'Authorization' in request.headers:
bearer = request.headers.get('Authorization')
token = bearer.split()[1]
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)

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot Keksdose bot
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_api.service'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '0.3.dev25'
from collections import namedtuple
# imports
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -0,0 +1,537 @@
import hashlib
import re
import textwrap
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_api.exception.service_error_code_enum import ServiceErrorCode
from bot_api.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.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.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,
users: UserRepositoryABC,
servers: ServerRepositoryABC,
# mailer: MailThread,
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._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 re.fullmatch(_email_regex, email) is not None:
return True
return False
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 = textwrap.dedent(f"""{message}
{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
async def add_auth_user_async(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 add_auth_user_by_discord_async(self, user_dto: AuthUserDTO, dc_id: int) -> OAuthDTO:
db_auth_user = self._auth_users.find_auth_user_by_email(user_dto.email)
# user exists
if db_auth_user is not None and db_auth_user.users.count() > 0:
# raise ServiceException(ServiceErrorCode.InvalidUser, 'User already exists')
self._logger.debug(__name__, f'Discord user already exists')
return OAuthDTO(AUT.to_dto(db_auth_user), None)
# user exists but discord user id not set
elif db_auth_user is not None and db_auth_user.users.count() == 0:
self._logger.debug(__name__, f'Auth user exists but not linked with discord')
# users = self._users.get_users_by_discord_id(user_dto.user_id)
# add auth_user to user refs
db_auth_user.oauth_id = None
else:
# user does not exists
self._logger.debug(__name__, f'Auth user does not exist')
try:
user_dto.user_id = self._users.get_users_by_discord_id(user_dto.user_id).single().user_id
except Exception as e:
self._logger.error(__name__, f'User not found')
user_dto.user_id = None
await self.add_auth_user_async(user_dto)
db_auth_user = self._auth_users.get_auth_user_by_email(user_dto.email)
db_auth_user.oauth_id = uuid.uuid4()
for g in self._bot.guilds:
member = g.get_member(int(dc_id))
if member is None:
continue
server = self._servers.get_server_by_discord_id(g.id)
users = self._users.get_users_by_discord_id(dc_id)
for user in users:
if user.server.server_id != server.server_id:
continue
self._auth_users.add_auth_user_user_rel(AuthUserUsersRelation(db_auth_user, user))
self._auth_users.update_auth_user(db_auth_user)
self._db.save_changes()
return OAuthDTO(AUT.to_dto(db_auth_user), db_auth_user.oauth_id)
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
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')
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 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()

View File

@@ -0,0 +1,105 @@
from typing import Optional
from cpl_discord.service import DiscordBotServiceABC
from cpl_query.extension import List
from flask import jsonify
from bot_api.abc.auth_service_abc import AuthServiceABC
from bot_api.exception.service_error_code_enum import ServiceErrorCode
from bot_api.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.model.error_dto import ErrorDTO
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_server_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.server_id)
servers = servers.where(lambda x: x.server_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.server_id)
filtered_result.result = filtered_result.result.where(lambda x: x.server_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_server_id)
server_dto = ServerTransformer.to_dto(server, guild.name, guild.member_count, guild.icon)
return server_dto

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot Keksdose bot
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_api.transformer'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '0.3.dev25'
from collections import namedtuple
# imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -0,0 +1,38 @@
from datetime import datetime, timezone
from bot_api.abc.transformer_abc import TransformerABC
from bot_api.model.auth_user_dto import AuthUserDTO
from bot_data.model.auth_role_enum import AuthRoleEnum
from bot_data.model.auth_user import AuthUser
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
def to_dto(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
)

View File

@@ -0,0 +1,24 @@
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.server_id,
db.discord_server_id,
name,
member_count,
icon_url.url if icon_url is not None else None,
)

View File

@@ -15,7 +15,7 @@ __title__ = 'bot_core'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '0.2.3'
__version__ = '0.3.dev25'
from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='2', micro='3')
version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -15,7 +15,7 @@ __title__ = 'bot_core.service'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '0.2.3'
__version__ = '0.3.dev25'
from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='2', micro='3')
version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -3,6 +3,7 @@ from typing import Union
import discord
from cpl_query.extension import List
from discord import Interaction
from discord.ext.commands import Context
@@ -10,18 +11,21 @@ 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], without_tracking=True): 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, wait_before_delete: int = None, without_tracking=True): pass
@abstractmethod
async def send_interaction_msg(self, interaction: Interaction, message: Union[str, discord.Embed], is_persistent: bool = False, wait_before_delete: int = None, without_tracking=True): pass

View File

@@ -2,9 +2,9 @@
"ProjectSettings": {
"Name": "bot-core",
"Version": {
"Major": "1",
"Minor": "0",
"Micro": "0"
"Major": "0",
"Minor": "3",
"Micro": "dev70"
},
"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.10.0"
"cpl-core>=0.3.dev70"
],
"DevDependencies": [
"cpl-cli>=2022.10.0"
"cpl-cli==2022.10.0"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {
"linux": ""
},
"PythonPath": {},
"Classifiers": []
},
"BuildSettings": {

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