@ -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;"
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
18
dockerfile
@ -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"]
|
43
kdb-bot/cpl-workspace.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"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",
|
||||
"bot-api": "src/bot_api/bot-api.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;"
|
||||
}
|
||||
}
|
||||
}
|
49
kdb-bot/docker-compose.dev.yml
Normal 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
|
49
kdb-bot/docker-compose.staging.yml
Normal 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
|
48
kdb-bot/docker-compose.yml
Normal 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
@ -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"]
|
@ -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.dev70'
|
||||
|
||||
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='dev70')
|
@ -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 #
|
||||
edraft marked this conversation as resolved
|
@ -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
|
@ -3,8 +3,8 @@
|
||||
"Name": "bot",
|
||||
"Version": {
|
||||
"Major": "0",
|
||||
"Minor": "2",
|
||||
"Micro": "3"
|
||||
"Minor": "3",
|
||||
"Micro": "dev70"
|
||||
},
|
||||
"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.post1",
|
||||
"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",
|
@ -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,
|
@ -12,6 +12,12 @@
|
||||
"FileLogLevel": "TRACE"
|
||||
},
|
||||
"BotLoggingSettings": {
|
||||
"Api": {
|
||||
"Path": "logs/",
|
||||
"Filename": "api.log",
|
||||
"ConsoleLogLevel": "TRACE",
|
||||
"FileLogLevel": "TRACE"
|
||||
},
|
||||
"Command": {
|
||||
"Path": "logs/",
|
||||
"Filename": "commands.log",
|
@ -12,6 +12,12 @@
|
||||
"FileLogLevel": "TRACE"
|
||||
},
|
||||
"BotLoggingSettings": {
|
||||
"Api": {
|
||||
"Path": "logs/",
|
||||
"Filename": "api.log",
|
||||
"ConsoleLogLevel": "TRACE",
|
||||
"FileLogLevel": "TRACE"
|
||||
},
|
||||
"Command": {
|
||||
"Path": "logs/",
|
||||
"Filename": "commands.log",
|
@ -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",
|
@ -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",
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"FeatureFlags": {
|
||||
"ApiModule": true,
|
||||
"AdminModule": true,
|
||||
"AutoRoleModule": true,
|
||||
"BaseModule": true,
|
@ -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()
|
@ -1,5 +1,6 @@
|
||||
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
|
||||
@ -26,6 +27,7 @@ class ModuleList:
|
||||
DatabaseModule,
|
||||
ModeratorModule,
|
||||
PermissionModule,
|
||||
ApiModule,
|
||||
# has to be last!
|
||||
BootLogModule,
|
||||
CoreExtensionModule,
|
@ -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))
|
||||
|
@ -4,6 +4,7 @@ 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.service.migration_service import MigrationService
|
||||
@ -21,3 +22,4 @@ 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
|
@ -152,5 +152,26 @@
|
||||
"database": {},
|
||||
"permission": {
|
||||
}
|
||||
},
|
||||
"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/{}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
kdb-bot/src/bot_api/__init__.py
Normal 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'
|
||||
__author__ = 'Sven Heidemann'
|
||||
__license__ = 'MIT'
|
||||
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
|
||||
__version__ = '0.3.dev70'
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||
version_info = VersionInfo(major='0', minor='3', micro='dev70')
|
26
kdb-bot/src/bot_api/abc/__init__.py
Normal 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.abc'
|
||||
__author__ = 'Sven Heidemann'
|
||||
__license__ = 'MIT'
|
||||
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
|
||||
__version__ = '0.3.dev70'
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||
version_info = VersionInfo(major='0', minor='3', micro='dev70')
|
89
kdb-bot/src/bot_api/abc/auth_service_abc.py
Normal 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
|
13
kdb-bot/src/bot_api/abc/dto_abc.py
Normal 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
|
17
kdb-bot/src/bot_api/abc/select_criteria_abc.py
Normal 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
|
16
kdb-bot/src/bot_api/abc/transformer_abc.py
Normal 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
@ -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')
|
52
kdb-bot/src/bot_api/api_module.py
Normal 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)
|
24
kdb-bot/src/bot_api/api_thread.py
Normal 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)
|
||||
|
||||
edraft marked this conversation as resolved
Ebola-Chan
commented
Muss hier dasd feature_flags nicht assigned werden? Muss hier dasd feature_flags nicht assigned werden?
|
||||
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)
|
26
kdb-bot/src/bot_api/app_api_extension.py
Normal 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)
|
44
kdb-bot/src/bot_api/bot-api.json
Normal 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": []
|
||||
}
|
||||
}
|
33
kdb-bot/src/bot_api/config/apisettings.development.json
Normal 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=="
|
||||
}
|
||||
}
|
28
kdb-bot/src/bot_api/config/apisettings.edrafts-lapi.json
Normal 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/"
|
||||
}
|
||||
}
|
27
kdb-bot/src/bot_api/config/apisettings.edrafts-pc.json
Normal 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/"
|
||||
}
|
||||
}
|
1
kdb-bot/src/bot_api/config/apisettings.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
33
kdb-bot/src/bot_api/config/apisettings.production.json
Normal 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"
|
||||
}
|
||||
}
|
33
kdb-bot/src/bot_api/config/apisettings.staging.json
Normal 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"
|
||||
}
|
||||
}
|
1
kdb-bot/src/bot_api/config/appsettings.PC-Nick.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
26
kdb-bot/src/bot_api/configuration/__init__.py
Normal 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.dev70'
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports
|
||||
|
||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||
version_info = VersionInfo(major='0', minor='3', micro='dev70')
|
35
kdb-bot/src/bot_api/configuration/api_settings.py
Normal 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()}')
|
48
kdb-bot/src/bot_api/configuration/authentication_settings.py
Normal 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()}')
|
@ -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()}')
|
23
kdb-bot/src/bot_api/configuration/frontend_settings.py
Normal 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()}')
|
55
kdb-bot/src/bot_api/configuration/version_settings.py
Normal 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
|
26
kdb-bot/src/bot_api/controller/__init__.py
Normal 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.dev70'
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||
version_info = VersionInfo(major='0', minor='3', micro='dev70')
|
155
kdb-bot/src/bot_api/controller/auth_controller.py
Normal 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:
|
||||
edraft marked this conversation as resolved
Ebola-Chan
commented
Was passiert, wenn eine andere E-Mail-Adresse angegeben wird? Was passiert, wenn eine andere E-Mail-Adresse angegeben wird?
Wenn eine positive Antwort gegeben wird: Ist es gewollt, dass ein User eine andere E-Mail-Adresse aufrufen kann?
Wenn ja: Soll ein User eine E-Mail-Adresse abfragen können, mit dem dieser nichts zu tun hat?
edraft
commented
was spricht dagegen? was spricht dagegen?
So kannst du Profile von anderen usern sehen, kannst damit ja nichts ans pw kommen.
Ebola-Chan
commented
Naja, so könnte sich jemand registrieren und dann somit eine Liste von gültigen E-Mails erstellen für Spammers. Naja, so könnte sich jemand registrieren und dann somit eine Liste von gültigen E-Mails erstellen für Spammers.
Ebola-Chan
commented
Wenn der User nur Profile von anderen Usern aufrufen kann, mit dem er zu tun hat, dann wäre es besser. Wenn der User nur Profile von anderen Usern aufrufen kann, mit dem er zu tun hat, dann wäre es besser.
|
||||
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):
|
||||
edraft marked this conversation as resolved
Ebola-Chan
commented
Was passiert, wenn ein User eine JSON sendet mit der E-Mail-Adresse eines anderen Users? Was passiert, wenn ein User eine JSON sendet mit der E-Mail-Adresse eines anderen Users?
Also versuchen die Einstellungen eines anderen Users überscheiben.
edraft
commented
Er muss das Passwort des Users kennen Er muss das Passwort des Users kennen
|
||||
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
|
89
kdb-bot/src/bot_api/controller/auth_discord_controller.py
Normal 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
|
26
kdb-bot/src/bot_api/controller/discord/__init__.py
Normal 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.dev70'
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||
version_info = VersionInfo(major='0', minor='3', micro='dev70')
|
65
kdb-bot/src/bot_api/controller/discord/server_controller.py
Normal 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())
|
78
kdb-bot/src/bot_api/controller/gui_controller.py
Normal 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
|
26
kdb-bot/src/bot_api/event/__init__.py
Normal 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.event'
|
||||
__author__ = 'Sven Heidemann'
|
||||
__license__ = 'MIT'
|
||||
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
|
||||
__version__ = '0.3.dev70'
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||
version_info = VersionInfo(major='0', minor='3', micro='dev70')
|
13
kdb-bot/src/bot_api/event/bot_api_on_ready_event.py
Normal 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()
|
26
kdb-bot/src/bot_api/exception/__init__.py
Normal 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.dev70'
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||
version_info = VersionInfo(major='0', minor='3', micro='dev70')
|
24
kdb-bot/src/bot_api/exception/service_error_code_enum.py
Normal 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
|
13
kdb-bot/src/bot_api/exception/service_exception.py
Normal 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}'
|
26
kdb-bot/src/bot_api/filter/__init__.py
Normal 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.dev70'
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||
version_info = VersionInfo(major='0', minor='3', micro='dev70')
|
23
kdb-bot/src/bot_api/filter/auth_user_select_criteria.py
Normal 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
|
26
kdb-bot/src/bot_api/filter/discord/__init__.py
Normal 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.dev70'
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||
version_info = VersionInfo(major='0', minor='3', micro='dev70')
|
17
kdb-bot/src/bot_api/filter/discord/server_select_criteria.py
Normal 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
|
39
kdb-bot/src/bot_api/json_processor.py
Normal 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)
|
26
kdb-bot/src/bot_api/logging/__init__.py
Normal 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.dev70'
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||
version_info = VersionInfo(major='0', minor='3', micro='dev70')
|
11
kdb-bot/src/bot_api/logging/api_logger.py
Normal 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)
|
26
kdb-bot/src/bot_api/model/__init__.py
Normal 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.dev70'
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||
version_info = VersionInfo(major='0', minor='3', micro='dev70')
|
99
kdb-bot/src/bot_api/model/auth_user_dto.py
Normal 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,
|
||||
}
|
21
kdb-bot/src/bot_api/model/auth_user_filtered_result_dto.py
Normal 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
|
||||
}
|
26
kdb-bot/src/bot_api/model/discord/__init__.py
Normal 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.dev70'
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||
version_info = VersionInfo(major='0', minor='3', micro='dev70')
|
58
kdb-bot/src/bot_api/model/discord/server_dto.py
Normal 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']
|
||||
edraft marked this conversation as resolved
Outdated
Ebola-Chan
commented
Rückgabewert von
wird in der Property als
angegeben und hier mit
zugewiesen. Vielleicht klassischer Copy&Paste error? Rückgabewert von
```python
self._icon_url
```
wird in der Property als
```python
Optional[str]
```
angegeben und hier mit
```python
int(values['iconURL'])
```
zugewiesen.
Vielleicht klassischer Copy&Paste error?
|
||||
|
||||
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,
|
||||
}
|
@ -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
|
||||
}
|
21
kdb-bot/src/bot_api/model/email_string_dto.py
Normal 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
|
||||
}
|
34
kdb-bot/src/bot_api/model/error_dto.py
Normal 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
|
||||
}
|
44
kdb-bot/src/bot_api/model/o_auth_dto.py
Normal 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
|
||||
}
|
32
kdb-bot/src/bot_api/model/reset_password_dto.py
Normal 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
|
||||
}
|
67
kdb-bot/src/bot_api/model/settings_dto.py
Normal 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,
|
||||
}
|
32
kdb-bot/src/bot_api/model/token_dto.py
Normal 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
|
||||
}
|
45
kdb-bot/src/bot_api/model/update_auth_user_dto.py
Normal 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
|
||||
}
|
43
kdb-bot/src/bot_api/model/version_dto.py
Normal 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}'
|
||||
edraft marked this conversation as resolved
Ebola-Chan
commented
Wenn ich mich recht entsinne, hattest du eine Mothode geschrieben, welche die Micro Nummer nicht mit ausgibt, wenn diese nicht gesetzt ist. Soll dies vielleicht auch hier eingebaut werden? Wenn ich mich recht entsinne, hattest du eine Mothode geschrieben, welche die Micro Nummer nicht mit ausgibt, wenn diese nicht gesetzt ist. Soll dies vielleicht auch hier eingebaut werden?
edraft
commented
war in nem anderen kontext, könnte man hier auch machen, da sehe ich den Zweck aber nicht, da unser erster Release die 1.0.0 sein wird. war in nem anderen kontext, könnte man hier auch machen, da sehe ich den Zweck aber nicht, da unser erster Release die 1.0.0 sein wird.
|
||||
|
||||
def from_dict(self, values: dict):
|
||||
self._major = values['major']
|
||||
self._minor = values['minor']
|
||||
self._micro = values['micro']
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
'major': self._major,
|
||||
'minor': self._minor,
|
||||
'micro': self._micro,
|
||||
}
|
26
kdb-bot/src/bot_api/route/__init__.py
Normal 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.dev70'
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||
version_info = VersionInfo(major='0', minor='3', micro='dev70')
|
103
kdb-bot/src/bot_api/route/route.py
Normal 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):
|
||||
edraft marked this conversation as resolved
Ebola-Chan
commented
Vorschlag: Die HTTP-Response-Status-Codes in einem Enum packen, damit man schneller erkennen kann, um was die Response handelt. Vorschlag: Die HTTP-Response-Status-Codes in einem Enum packen, damit man schneller erkennen kann, um was die Response handelt.
edraft
commented
Kann man später mal nachbessern aber die API ist jetzt noch übersichtlich genug Kann man später mal nachbessern aber die API ist jetzt noch übersichtlich genug
|
||||
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)
|
26
kdb-bot/src/bot_api/service/__init__.py
Normal 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.dev70'
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports
|
||||
|
||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||
version_info = VersionInfo(major='0', minor='3', micro='dev70')
|
537
kdb-bot/src/bot_api/service/auth_service.py
Normal 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()
|
105
kdb-bot/src/bot_api/service/discord_service.py
Normal 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
|
26
kdb-bot/src/bot_api/transformer/__init__.py
Normal 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.dev70'
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||
version_info = VersionInfo(major='0', minor='3', micro='dev70')
|
38
kdb-bot/src/bot_api/transformer/auth_user_transformer.py
Normal 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
|
||||
)
|
24
kdb-bot/src/bot_api/transformer/server_transformer.py
Normal 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,
|
||||
)
|
@ -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.dev70'
|
||||
|
||||
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='dev70')
|
@ -15,7 +15,7 @@ __title__ = 'bot_core.abc'
|
||||
__author__ = 'Sven Heidemann'
|
||||
__license__ = 'MIT'
|
||||
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
|
||||
__version__ = '0.2.3'
|
||||
__version__ = '0.3.dev70'
|
||||
|
||||
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='dev70')
|
@ -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": {
|
Reicht hier nicht ein
oder ist das so ein Pythin ding dank der Typitisierung?
Jain, gebe das als string rein, ist bisschen besser an der stelle, liegt an CPL