16 Commits

593 changed files with 7847 additions and 12704 deletions

View File

@@ -6,108 +6,52 @@ on:
- dev - dev
jobs: jobs:
pre-build: on-push-deploy_sh-edraft:
runs-on: [ dobby ] runs-on: [ dobby.sh-edraft.de, ubuntu-latest ]
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest container: catthehacker/ubuntu:act-latest
steps: steps:
- name: Shutdown stack - name: Setup Python 3.10
run: docker stack rm sdb_dev uses: actions/setup-python@v3
with:
python-version: "3.10.12"
- run: python -v
- name: Setup docker
uses: https://github.com/papodaca/install-docker-action@main
- run: docker -v
build-bot:
needs: pre-build
runs-on: [ dobby ]
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
steps:
- name: Clone Repository - name: Clone Repository
uses: https://github.com/actions/checkout@v3 uses: https://github.com/actions/checkout@v3
with:
token: ${{ secrets.CI_ACCESS_TOKEN }} - name: Shutdown stack
submodules: true run: docker stack rm kdb_staging
- name: Prepare bot build - name: Prepare bot build
run: | run: |
python3.10 -m pip install --extra-index-url https://pip.sh-edraft.de cpl-cli cd kdb-bot
cd bot pip install --extra-index-url https://pip.sh-edraft.de cpl-cli
cpl i cpl i
- name: Build docker bot - name: Build docker bot
run: | run: |
cd bot cd kdb-bot
docker image prune -f docker image prune -f
cpl build cpl docker-build
docker build -t git.sh-edraft.de/sh-edraft.de/sdb-bot:$(cpl gv)-dev .
- name: Login to registry git.sh-edraft.de
uses: https://github.com/docker/login-action@v1
with:
registry: git.sh-edraft.de
username: ${{ secrets.CI_USERNAME }}
password: ${{ secrets.CI_ACCESS_TOKEN }}
- name: Push image
run: |
cd bot
docker push git.sh-edraft.de/sh-edraft.de/sdb-bot:$(cpl gv)-dev
build-web:
needs: pre-build
runs-on: [ dobby ]
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
steps:
- name: Clone Repository
uses: https://github.com/actions/checkout@v3
with:
token: ${{ secrets.CI_ACCESS_TOKEN }}
submodules: true
- name: Setup node - name: Setup node
uses: https://github.com/actions/setup-node@v3 uses: https://github.com/actions/setup-node@v3
- name: Prepare web build - name: Prepare web build
run: | run: |
cd web cd kdb-web
npm install -g ts-node npm install -g ts-node
npm ci npm i
- name: Build docker web - name: Build docker web
run: | run: |
cd web cd kdb-web
docker image prune -f docker image prune -f
cp src/favicon.dev.ico src/favicon.ico npm run docker-build
npm run build
docker build -t git.sh-edraft.de/sh-edraft.de/sdb-web:$(npm run -s gv)-dev .
- name: Login to registry git.sh-edraft.de
uses: https://github.com/docker/login-action@v1
with:
registry: git.sh-edraft.de
username: ${{ secrets.CI_USERNAME }}
password: ${{ secrets.CI_ACCESS_TOKEN }}
- name: Push image
run: |
cd web
docker push git.sh-edraft.de/sh-edraft.de/sdb-web:$(npm run -s gv)-dev
deploy:
needs: [ build-bot, build-web ]
runs-on: [ dobby ]
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
steps:
- name: Clone Repository
uses: https://github.com/actions/checkout@v3
with:
token: ${{ secrets.CI_ACCESS_TOKEN }}
submodules: true
- name: Install cpl
run: python3.10 -m pip install --extra-index-url https://pip.sh-edraft.de cpl-cli
- name: Set version
run: |
cd bot/docker
chmod +x ./set-docker-compose-image-version.sh
./set-docker-compose-image-version.sh git.sh-edraft.de/sh-edraft.de/sdb-bot:$(cd ../; cpl gv)-dev git.sh-edraft.de/sh-edraft.de/sdb-web:$(cd ../../web; npm run -s gv;)-dev
- name: Deploy Stack to sh-edraft.de - name: Deploy Stack to sh-edraft.de
uses: https://github.com/kgierke/portainer-stack-deployment@v1 uses: https://github.com/kgierke/portainer-stack-deployment@v1
@@ -116,6 +60,6 @@ jobs:
portainer-username: "gitea_job" portainer-username: "gitea_job"
portainer-password: "${{ secrets.docker_job }}" portainer-password: "${{ secrets.docker_job }}"
portainer-endpoint: 2 portainer-endpoint: 2
name: sdb_dev name: kdb_staging
file: bot/docker/docker-compose.dev.yml file: ./docker-compose.staging.yml
variables: '{}' variables: '{}'

View File

@@ -1,112 +1,57 @@
name: Deploy prod on push name: Deploy dev on push
run-name: Deploy prod on push run-name: Deploy dev on push
on: on:
push: push:
branches: branches:
- master - master
jobs: jobs:
pre-build: on-push-deploy_sh-edraft:
runs-on: [ dobby ] runs-on: [ dobby.sh-edraft.de, ubuntu-latest ]
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest container: catthehacker/ubuntu:act-latest
steps: steps:
- name: Shutdown stack - name: Setup Python 3.10
run: docker stack rm sdb_prod uses: actions/setup-python@v3
with:
python-version: "3.10.12"
- run: python -v
- name: Setup docker
uses: https://github.com/papodaca/install-docker-action@main
- run: docker -v
build-bot:
needs: pre-build
runs-on: [ dobby ]
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
steps:
- name: Clone Repository - name: Clone Repository
uses: https://github.com/actions/checkout@v3 uses: https://github.com/actions/checkout@v3
with:
token: ${{ secrets.CI_ACCESS_TOKEN }} - name: Shutdown stack
submodules: true run: docker stack rm kdb_prod
- name: Prepare bot build - name: Prepare bot build
run: | run: |
python3.10 -m pip install --extra-index-url https://pip.sh-edraft.de cpl-cli cd kdb-bot
cd bot pip install --extra-index-url https://pip.sh-edraft.de cpl-cli
cpl i cpl i
- name: Build docker bot - name: Build docker bot
run: | run: |
cd bot cd kdb-bot
docker image prune -f docker image prune -f
cpl build cpl docker-build
docker build -t git.sh-edraft.de/sh-edraft.de/sdb-bot:$(cpl gv) .
- name: Login to registry git.sh-edraft.de
uses: https://github.com/docker/login-action@v1
with:
registry: git.sh-edraft.de
username: ${{ secrets.CI_USERNAME }}
password: ${{ secrets.CI_ACCESS_TOKEN }}
- name: Push image
run: |
cd bot
docker push git.sh-edraft.de/sh-edraft.de/sdb-bot:$(cpl gv)
build-web:
needs: pre-build
runs-on: [ dobby ]
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
steps:
- name: Clone Repository
uses: https://github.com/actions/checkout@v3
with:
token: ${{ secrets.CI_ACCESS_TOKEN }}
submodules: true
- name: Setup node - name: Setup node
uses: https://github.com/actions/setup-node@v3 uses: https://github.com/actions/setup-node@v3
- name: Prepare web build - name: Prepare web build
run: | run: |
cd web cd kdb-web
npm install -g ts-node npm install -g ts-node
npm ci npm i
- name: Build docker web - name: Build docker web
run: | run: |
cd web cd kdb-web
docker image prune -f docker image prune -f
npm run build npm run docker-build
docker build -t git.sh-edraft.de/sh-edraft.de/sdb-web:$(npm run -s gv) .
- name: Login to registry git.sh-edraft.de
uses: https://github.com/docker/login-action@v1
with:
registry: git.sh-edraft.de
username: ${{ secrets.CI_USERNAME }}
password: ${{ secrets.CI_ACCESS_TOKEN }}
- name: Push image
run: |
cd web
docker push git.sh-edraft.de/sh-edraft.de/sdb-web:$(npm run -s gv)
deploy:
needs: [ build-bot, build-web ]
runs-on: [ dobby ]
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
steps:
- name: Clone Repository
uses: https://github.com/actions/checkout@v3
with:
token: ${{ secrets.CI_ACCESS_TOKEN }}
submodules: true
- name: Install cpl
run: python3.10 -m pip install --extra-index-url https://pip.sh-edraft.de cpl-cli
- name: Set version
run: |
cd bot/docker
chmod +x ./set-docker-compose-image-version.sh
./set-docker-compose-image-version.sh git.sh-edraft.de/sh-edraft.de/sdb-bot:$(cd ../; cpl gv) git.sh-edraft.de/sh-edraft.de/sdb-web:$(cd ../../web; npm run -s gv;)
- name: Deploy Stack to sh-edraft.de - name: Deploy Stack to sh-edraft.de
uses: https://github.com/kgierke/portainer-stack-deployment@v1 uses: https://github.com/kgierke/portainer-stack-deployment@v1
@@ -115,6 +60,6 @@ jobs:
portainer-username: "gitea_job" portainer-username: "gitea_job"
portainer-password: "${{ secrets.docker_job }}" portainer-password: "${{ secrets.docker_job }}"
portainer-endpoint: 2 portainer-endpoint: 2
name: sdb_prod name: kdb_prod
file: bot/docker/docker-compose.yml file: ./docker-compose.yml
variables: '{}' variables: '{}'

View File

@@ -1,121 +0,0 @@
name: Deploy staging on push
run-name: Deploy staging on push
on:
push:
branches:
- staging
jobs:
pre-build:
runs-on: [ dobby ]
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
steps:
- name: Shutdown stack
run: docker stack rm sdb_staging
build-bot:
needs: pre-build
runs-on: [ dobby ]
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
steps:
- name: Clone Repository
uses: https://github.com/actions/checkout@v3
with:
token: ${{ secrets.CI_ACCESS_TOKEN }}
submodules: true
- name: Prepare bot build
run: |
python3.10 -m pip install --extra-index-url https://pip.sh-edraft.de cpl-cli
cd bot
cpl i
- name: Build docker bot
run: |
cd bot
docker image prune -f
cpl build
docker build -t git.sh-edraft.de/sh-edraft.de/sdb-bot:$(cpl gv)-staging .
- name: Login to registry git.sh-edraft.de
uses: https://github.com/docker/login-action@v1
with:
registry: git.sh-edraft.de
username: ${{ secrets.CI_USERNAME }}
password: ${{ secrets.CI_ACCESS_TOKEN }}
- name: Push image
run: |
cd bot
docker push git.sh-edraft.de/sh-edraft.de/sdb-bot:$(cpl gv)-staging
build-web:
needs: pre-build
runs-on: [ dobby ]
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
steps:
- name: Clone Repository
uses: https://github.com/actions/checkout@v3
with:
token: ${{ secrets.CI_ACCESS_TOKEN }}
submodules: true
- name: Setup node
uses: https://github.com/actions/setup-node@v3
- name: Prepare web build
run: |
cd web
npm install -g ts-node
npm ci
- name: Build docker web
run: |
cd web
docker image prune -f
cp src/favicon.staging.ico src/favicon.ico
npm run build
docker build -t git.sh-edraft.de/sh-edraft.de/sdb-web:$(npm run -s gv)-staging .
- name: Login to registry git.sh-edraft.de
uses: https://github.com/docker/login-action@v1
with:
registry: git.sh-edraft.de
username: ${{ secrets.CI_USERNAME }}
password: ${{ secrets.CI_ACCESS_TOKEN }}
- name: Push image
run: |
cd web
docker push git.sh-edraft.de/sh-edraft.de/sdb-web:$(npm run -s gv)-staging
deploy:
needs: [ build-bot, build-web ]
runs-on: [ dobby ]
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
steps:
- name: Clone Repository
uses: https://github.com/actions/checkout@v3
with:
token: ${{ secrets.CI_ACCESS_TOKEN }}
submodules: true
- name: Install cpl
run: python3.10 -m pip install --extra-index-url https://pip.sh-edraft.de cpl-cli
- name: Set version
run: |
cd bot/docker
chmod +x ./set-docker-compose-image-version.sh
./set-docker-compose-image-version.sh git.sh-edraft.de/sh-edraft.de/sdb-bot:$(cd ../; cpl gv)-staging git.sh-edraft.de/sh-edraft.de/sdb-web:$(cd ../../web; npm run -s gv;)-staging
- name: Deploy Stack to sh-edraft.de
uses: https://github.com/kgierke/portainer-stack-deployment@v1
with:
portainer-url: "https://docker.sh-edraft.de"
portainer-username: "gitea_job"
portainer-password: "${{ secrets.docker_job }}"
portainer-endpoint: 2
name: sdb_staging
file: bot/docker/docker-compose.staging.yml
variables: '{}'

View File

@@ -14,15 +14,14 @@
"config": "src/modules/config/config.json", "config": "src/modules/config/config.json",
"database": "src/modules/database/database.json", "database": "src/modules/database/database.json",
"level": "src/modules/level/level.json", "level": "src/modules/level/level.json",
"realms": "src/modules/realms/realms.json", "permission": "src/modules/permission/permission.json",
"technician": "src/modules/technician/technician.json", "technician": "src/modules/technician/technician.json",
"short-role-name": "src/modules/short_role_name/short-role-name.json", "short-role-name": "src/modules/short_role_name/short-role-name.json",
"special-offers": "src/modules/special_offers/special-offers.json", "special-offers": "src/modules/special_offers/special-offers.json",
"checks": "tools/checks/checks.json", "checks": "tools/checks/checks.json",
"get-version": "tools/get_version/get-version.json", "get-version": "tools/get_version/get-version.json",
"post-build": "tools/post_build/post-build.json", "post-build": "tools/post_build/post-build.json",
"set-version": "tools/set_version/set-version.json", "set-version": "tools/set_version/set-version.json"
"migration-to-sql": "tools/migration_to_sql/migration-to-sql.json"
}, },
"Scripts": { "Scripts": {
"format": "black ./", "format": "black ./",
@@ -33,12 +32,12 @@
"pre-build": "cpl set-version $ARGS; black ./;", "pre-build": "cpl set-version $ARGS; black ./;",
"post-build": "cpl run post-build --dev; black ./;", "post-build": "cpl run post-build --dev; black ./;",
"pre-prod": "cpl build", "pre-prod": "cpl build",
"prod": "export SDB_ENVIRONMENT=production; export SDB_NAME=SDB-Prod; cpl start;", "prod": "export KDB_ENVIRONMENT=production; export KDB_NAME=KDB-Prod; cpl start;",
"pre-stage": "cpl build", "pre-stage": "cpl build",
"stage": "export SDB_ENVIRONMENT=staging; export SDB_NAME=SDB-Stage; cpl start;", "stage": "export KDB_ENVIRONMENT=staging; export KDB_NAME=KDB-Stage; cpl start;",
"pre-dev": "cpl build", "pre-dev": "cpl build",
"dev": "export SDB_ENVIRONMENT=development; export SDB_NAME=SDB-Dev; cpl start;", "dev": "export KDB_ENVIRONMENT=development; export KDB_NAME=KDB-Dev; cpl start;",
"docker-build": "cpl build $ARGS; docker build -t sh-edraft.de/sdb-bot:$(cpl gv) .;", "docker-build": "cpl build $ARGS; docker build -t sh-edraft.de/kdb-bot:$(cpl gv) .;",
"dc-up": "docker-compose up -d", "dc-up": "docker-compose up -d",
"dc-down": "docker-compose down", "dc-down": "docker-compose down",
"docker": "cpl dc-down; cpl docker-build; cpl dc-up;" "docker": "cpl dc-down; cpl docker-build; cpl dc-up;"

Submodule bot/docker deleted from c26ab4be08

View File

@@ -2,7 +2,7 @@
FROM python:3.10.4-alpine FROM python:3.10.4-alpine
WORKDIR /app WORKDIR /app
COPY ./dist/bot/build/bot/ . COPY ./dist/bot/build/kdb-bot/ .
COPY ./dist/bot/build/requirements.txt . COPY ./dist/bot/build/requirements.txt .
RUN python -m pip install --upgrade pip RUN python -m pip install --upgrade pip

View File

@@ -15,7 +15,7 @@ __title__ = "bot"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -11,7 +11,6 @@ from bot_api.api_thread import ApiThread
from bot_core.abc.task_abc import TaskABC from bot_core.abc.task_abc import TaskABC
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
from bot_core.environment_variables import MAINTENANCE
from bot_core.service.data_integrity_service import DataIntegrityService from bot_core.service.data_integrity_service import DataIntegrityService
@@ -24,17 +23,25 @@ class Application(DiscordBotApplicationABC):
# cpl-core # cpl-core
self._logger: LoggerABC = services.get_service(LoggerABC) self._logger: LoggerABC = services.get_service(LoggerABC)
self._data_integrity: DataIntegrityService = services.get_service(DataIntegrityService) self._data_integrity: DataIntegrityService = services.get_service(
DataIntegrityService
)
# cpl-discord # cpl-discord
self._bot: DiscordBotServiceABC = services.get_service(DiscordBotServiceABC) self._bot: DiscordBotServiceABC = services.get_service(DiscordBotServiceABC)
self._bot_settings: DiscordBotSettings = config.get_configuration(DiscordBotSettings) self._bot_settings: DiscordBotSettings = config.get_configuration(
DiscordBotSettings
)
# cpl-translation # cpl-translation
self._translation: TranslationServiceABC = services.get_service(TranslationServiceABC) self._translation: TranslationServiceABC = services.get_service(
TranslationServiceABC
)
self._t: TranslatePipe = services.get_service(TranslatePipe) self._t: TranslatePipe = services.get_service(TranslatePipe)
# internal stuff # internal stuff
self._tasks = services.get_services(TaskABC) self._tasks = services.get_services(TaskABC)
self._feature_flags: FeatureFlagsSettings = config.get_configuration(FeatureFlagsSettings) self._feature_flags: FeatureFlagsSettings = config.get_configuration(
FeatureFlagsSettings
)
# api # api
if self._feature_flags.get_flag(FeatureFlagsEnum.api_module): if self._feature_flags.get_flag(FeatureFlagsEnum.api_module):
@@ -43,7 +50,9 @@ class Application(DiscordBotApplicationABC):
self._is_stopping = False self._is_stopping = False
async def configure(self): async def configure(self):
self._translation.load_by_settings(self._configuration.get_configuration(TranslationSettings)) self._translation.load_by_settings(
self._configuration.get_configuration(TranslationSettings)
)
async def main(self): async def main(self):
try: try:
@@ -59,7 +68,6 @@ class Application(DiscordBotApplicationABC):
return return
self._logger.info(__name__, f"Try to start {DiscordBotService.__name__}") self._logger.info(__name__, f"Try to start {DiscordBotService.__name__}")
if not self._config.get_configuration(MAINTENANCE):
for task in self._tasks: for task in self._tasks:
await self._bot.add_cog(task) await self._bot.add_cog(task)
@@ -87,4 +95,8 @@ class Application(DiscordBotApplicationABC):
Console.write_line() Console.write_line()
def is_restart(self): 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
)

0
bot/src/bot/bot Executable file → Normal file
View File

View File

@@ -4,7 +4,7 @@
"Version": { "Version": {
"Major": "1", "Major": "1",
"Minor": "2", "Minor": "2",
"Micro": "11" "Micro": "0"
}, },
"Author": "Sven Heidemann", "Author": "Sven Heidemann",
"AuthorEmail": "sven.heidemann@sh-edraft.de", "AuthorEmail": "sven.heidemann@sh-edraft.de",
@@ -16,6 +16,7 @@
"LicenseName": "MIT", "LicenseName": "MIT",
"LicenseDescription": "MIT, see LICENSE for more details.", "LicenseDescription": "MIT, see LICENSE for more details.",
"Dependencies": [ "Dependencies": [
"cpl-core==2023.10.0",
"cpl-translation==2023.4.0.post1", "cpl-translation==2023.4.0.post1",
"cpl-query==2023.10.0", "cpl-query==2023.10.0",
"cpl-discord==2023.10.0.post1", "cpl-discord==2023.10.0.post1",
@@ -30,16 +31,11 @@
"icmplib==3.0.4", "icmplib==3.0.4",
"ariadne==0.20.1", "ariadne==0.20.1",
"cryptography==41.0.4", "cryptography==41.0.4",
"discord==2.3.2", "discord==2.3.2"
"bs4==0.0.1",
"lxml==4.9.3",
"python-valve==0.2.1",
"cpl-core==2023.10.2"
], ],
"DevDependencies": [ "DevDependencies": [
"cpl-cli==2023.4.0.post3", "cpl-cli==2023.4.0.post3",
"pygount==1.6.1", "pygount==1.6.1"
"black==23.10.1"
], ],
"PythonVersion": ">=3.10.4", "PythonVersion": ">=3.10.4",
"PythonPath": {}, "PythonPath": {},
@@ -71,7 +67,7 @@
"../modules/config/config.json", "../modules/config/config.json",
"../modules/database/database.json", "../modules/database/database.json",
"../modules/level/level.json", "../modules/level/level.json",
"../modules/realms/realms.json", "../modules/permission/permission.json",
"../modules/short_role_name/short-role-name.json", "../modules/short_role_name/short-role-name.json",
"../modules/special_offers/special-offers.json", "../modules/special_offers/special-offers.json",
"../modules/technician/technician.json" "../modules/technician/technician.json"

View File

@@ -15,7 +15,7 @@ __title__ = "bot.extension"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -1,22 +0,0 @@
import os
import shutil
from datetime import datetime
from cpl_core.application.application_extension_abc import ApplicationExtensionABC
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceProviderABC
from cpl_query.extension import List
class CleanLogsExtension(ApplicationExtensionABC):
def __init__(self):
pass
async def run(self, config: ConfigurationABC, services: ServiceProviderABC):
(
List(str, os.listdir("logs/"))
.where(lambda x: os.path.isdir(f"logs/{x}"))
.order_by()
.where(lambda x: (datetime.now() - datetime.strptime(x, "%Y-%m-%d")).days >= 7)
.for_each(lambda x: shutil.rmtree(f"logs/{x}"))
)

View File

@@ -13,4 +13,6 @@ class InitBotExtension(ApplicationExtensionABC):
async def run(self, config: ConfigurationABC, services: ServiceProviderABC): async def run(self, config: ConfigurationABC, services: ServiceProviderABC):
settings = config.get_configuration(TechnicianConfig) settings = config.get_configuration(TechnicianConfig)
bot: DiscordBotServiceABC = services.get_service(DiscordBotServiceABC, max_messages=settings.cache_max_messages) bot: DiscordBotServiceABC = services.get_service(
DiscordBotServiceABC, max_messages=settings.cache_max_messages
)

View File

@@ -6,11 +6,10 @@ from cpl_core.application import ApplicationBuilder
from cpl_core.console import Console from cpl_core.console import Console
from bot.application import Application from bot.application import Application
from bot.extension.clean_logs_extension import CleanLogsExtension
from bot.extension.init_bot_extension import InitBotExtension from bot.extension.init_bot_extension import InitBotExtension
from bot.startup import Startup from bot.startup import Startup
from bot.startup_discord_extension import StartupDiscordExtension from bot.startup_discord_extension import StartupDiscordExtension
from bot_data.startup_migration_extension import StartupMigrationExtension from bot.startup_migration_extension import StartupMigrationExtension
from bot.startup_module_extension import StartupModuleExtension from bot.startup_module_extension import StartupModuleExtension
from bot.startup_settings_extension import StartupSettingsExtension from bot.startup_settings_extension import StartupSettingsExtension
from bot_api.app_api_extension import AppApiExtension from bot_api.app_api_extension import AppApiExtension
@@ -32,7 +31,6 @@ class Program:
.use_extension(StartupDiscordExtension) .use_extension(StartupDiscordExtension)
.use_extension(StartupModuleExtension) .use_extension(StartupModuleExtension)
.use_extension(StartupMigrationExtension) .use_extension(StartupMigrationExtension)
.use_extension(CleanLogsExtension)
.use_extension(DatabaseExtension) .use_extension(DatabaseExtension)
.use_extension(ConfigExtension) .use_extension(ConfigExtension)
.use_extension(InitBotExtension) .use_extension(InitBotExtension)

View File

@@ -12,7 +12,7 @@ from modules.boot_log.boot_log_module import BootLogModule
from modules.config.config_module import ConfigModule from modules.config.config_module import ConfigModule
from modules.database.database_module import DatabaseModule from modules.database.database_module import DatabaseModule
from modules.level.level_module import LevelModule from modules.level.level_module import LevelModule
from modules.realms.reams_module import RealmsModule from modules.permission.permission_module import PermissionModule
from modules.short_role_name.short_role_name_module import ShortRoleNameModule from modules.short_role_name.short_role_name_module import ShortRoleNameModule
from modules.special_offers.special_offers_module import SteamSpecialOffersModule from modules.special_offers.special_offers_module import SteamSpecialOffersModule
from modules.technician.technician_module import TechnicianModule from modules.technician.technician_module import TechnicianModule
@@ -30,6 +30,7 @@ class ModuleList:
ConfigModule, # has to be before db check ConfigModule, # has to be before db check
DatabaseModule, DatabaseModule,
GraphQLModule, GraphQLModule,
PermissionModule,
AutoRoleModule, AutoRoleModule,
BaseModule, BaseModule,
LevelModule, LevelModule,
@@ -38,7 +39,6 @@ class ModuleList:
AchievementsModule, AchievementsModule,
ShortRoleNameModule, ShortRoleNameModule,
SteamSpecialOffersModule, SteamSpecialOffersModule,
RealmsModule,
# has to be last! # has to be last!
BootLogModule, BootLogModule,
CoreExtensionModule, CoreExtensionModule,

View File

@@ -15,7 +15,6 @@ from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
from bot_core.logging.command_logger import CommandLogger from bot_core.logging.command_logger import CommandLogger
from bot_core.logging.database_logger import DatabaseLogger from bot_core.logging.database_logger import DatabaseLogger
from bot_core.logging.event_logger import EventLogger
from bot_core.logging.message_logger import MessageLogger from bot_core.logging.message_logger import MessageLogger
from bot_core.logging.task_logger import TaskLogger from bot_core.logging.task_logger import TaskLogger
from bot_data.db_context import DBContext from bot_data.db_context import DBContext
@@ -46,13 +45,14 @@ class Startup(StartupABC):
services.add_singleton(CustomFileLoggerABC, DatabaseLogger) services.add_singleton(CustomFileLoggerABC, DatabaseLogger)
services.add_singleton(CustomFileLoggerABC, MessageLogger) services.add_singleton(CustomFileLoggerABC, MessageLogger)
services.add_singleton(CustomFileLoggerABC, TaskLogger) services.add_singleton(CustomFileLoggerABC, TaskLogger)
services.add_singleton(CustomFileLoggerABC, EventLogger)
if self._feature_flags.get_flag(FeatureFlagsEnum.api_module): if self._feature_flags.get_flag(FeatureFlagsEnum.api_module):
services.add_singleton(CustomFileLoggerABC, ApiLogger) services.add_singleton(CustomFileLoggerABC, ApiLogger)
services.add_translation() services.add_translation()
services.add_db_context(DBContext, self._config.get_configuration(DatabaseSettings)) services.add_db_context(
DBContext, self._config.get_configuration(DatabaseSettings)
)
provider = services.build_service_provider() provider = services.build_service_provider()
# instantiate custom logger # instantiate custom logger

View File

@@ -9,9 +9,13 @@ class StartupDiscordExtension(StartupExtensionABC):
def __init__(self): def __init__(self):
pass pass
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC): def configure_configuration(
self, config: ConfigurationABC, env: ApplicationEnvironmentABC
):
pass pass
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): def configure_services(
self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC
):
services.add_discord() services.add_discord()
dcc = get_discord_collection(services) dcc = get_discord_collection(services)

View File

@@ -0,0 +1,106 @@
from cpl_core.application import StartupExtensionABC
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceCollectionABC
from cpl_core.environment import ApplicationEnvironmentABC
from bot_data.abc.migration_abc import MigrationABC
from bot_data.migration.achievements_migration import AchievementsMigration
from bot_data.migration.api_key_migration import ApiKeyMigration
from bot_data.migration.api_migration import ApiMigration
from bot_data.migration.auto_role_fix1_migration import AutoRoleFix1Migration
from bot_data.migration.auto_role_migration import AutoRoleMigration
from bot_data.migration.birthday_migration import BirthdayMigration
from bot_data.migration.config_feature_flags_migration import (
ConfigFeatureFlagsMigration,
)
from bot_data.migration.config_migration import ConfigMigration
from bot_data.migration.db_history_migration import DBHistoryMigration
from bot_data.migration.default_role_migration import DefaultRoleMigration
from bot_data.migration.fix_updates_migration import FixUpdatesMigration
from bot_data.migration.fix_user_history_migration import FixUserHistoryMigration
from bot_data.migration.initial_migration import InitialMigration
from bot_data.migration.level_migration import LevelMigration
from bot_data.migration.remove_stats_migration import RemoveStatsMigration
from bot_data.migration.short_role_name_migration import ShortRoleNameMigration
from bot_data.migration.short_role_name_only_highest_migration import (
ShortRoleNameOnlyHighestMigration,
)
from bot_data.migration.stats_migration import StatsMigration
from bot_data.migration.steam_special_offer_migration import SteamSpecialOfferMigration
from bot_data.migration.user_joined_game_server_migration import (
UserJoinedGameServerMigration,
)
from bot_data.migration.user_message_count_per_hour_migration import (
UserMessageCountPerHourMigration,
)
from bot_data.migration.user_warning_migration import UserWarningMigration
from bot_data.service.migration_service import MigrationService
class StartupMigrationExtension(StartupExtensionABC):
def __init__(self):
pass
def configure_configuration(
self, config: ConfigurationABC, env: ApplicationEnvironmentABC
):
pass
def configure_services(
self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC
):
services.add_transient(MigrationService)
services.add_transient(MigrationABC, InitialMigration)
services.add_transient(
MigrationABC, AutoRoleMigration
) # 03.10.2022 #54 - 0.2.2
services.add_transient(MigrationABC, ApiMigration) # 15.10.2022 #70 - 0.3.0
services.add_transient(MigrationABC, LevelMigration) # 06.11.2022 #25 - 0.3.0
services.add_transient(MigrationABC, StatsMigration) # 09.11.2022 #46 - 0.3.0
services.add_transient(
MigrationABC, AutoRoleFix1Migration
) # 30.12.2022 #151 - 0.3.0
services.add_transient(
MigrationABC, UserMessageCountPerHourMigration
) # 11.01.2023 #168 - 0.3.1
services.add_transient(MigrationABC, ApiKeyMigration) # 09.02.2023 #162 - 1.0.0
services.add_transient(
MigrationABC, UserJoinedGameServerMigration
) # 12.02.2023 #181 - 1.0.0
services.add_transient(
MigrationABC, RemoveStatsMigration
) # 19.02.2023 #190 - 1.0.0
services.add_transient(
MigrationABC, UserWarningMigration
) # 21.02.2023 #35 - 1.0.0
services.add_transient(
MigrationABC, DBHistoryMigration
) # 06.03.2023 #246 - 1.0.0
services.add_transient(
MigrationABC, AchievementsMigration
) # 14.06.2023 #268 - 1.1.0
services.add_transient(MigrationABC, ConfigMigration) # 19.07.2023 #127 - 1.1.0
services.add_transient(
MigrationABC, ConfigFeatureFlagsMigration
) # 15.08.2023 #334 - 1.1.0
services.add_transient(
MigrationABC, DefaultRoleMigration
) # 24.09.2023 #360 - 1.1.3
services.add_transient(
MigrationABC, ShortRoleNameMigration
) # 28.09.2023 #378 - 1.1.7
services.add_transient(
MigrationABC, FixUpdatesMigration
) # 28.09.2023 #378 - 1.1.7
services.add_transient(
MigrationABC, ShortRoleNameOnlyHighestMigration
) # 02.10.2023 #391 - 1.1.9
services.add_transient(
MigrationABC, FixUserHistoryMigration
) # 10.10.2023 #401 - 1.2.0
services.add_transient(
MigrationABC, BirthdayMigration
) # 10.10.2023 #401 - 1.2.0
services.add_transient(
MigrationABC, SteamSpecialOfferMigration
) # 10.10.2023 #188 - 1.2.0

View File

@@ -18,11 +18,15 @@ class StartupModuleExtension(StartupExtensionABC):
self._modules = ModuleList.get_modules() self._modules = ModuleList.get_modules()
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC): def configure_configuration(
self, config: ConfigurationABC, env: ApplicationEnvironmentABC
):
self._config = config self._config = config
self._feature_flags = config.get_configuration(FeatureFlagsSettings) self._feature_flags = config.get_configuration(FeatureFlagsSettings)
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): def configure_services(
self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC
):
provider = services.build_service_provider() provider = services.build_service_provider()
dc_collection: DiscordCollectionABC = provider.get_service(DiscordCollectionABC) dc_collection: DiscordCollectionABC = provider.get_service(DiscordCollectionABC)

View File

@@ -8,39 +8,44 @@ from cpl_core.dependency_injection import ServiceCollectionABC
from cpl_core.environment import ApplicationEnvironmentABC from cpl_core.environment import ApplicationEnvironmentABC
from bot_core.configuration.bot_logging_settings import BotLoggingSettings from bot_core.configuration.bot_logging_settings import BotLoggingSettings
from bot_core.environment_variables import MAINTENANCE, MIGRATION_ONLY
class StartupSettingsExtension(StartupExtensionABC): class StartupSettingsExtension(StartupExtensionABC):
def __init__(self): def __init__(self):
self._start_time = datetime.now() self._start_time = datetime.now()
def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironmentABC): def configure_configuration(
self, configuration: ConfigurationABC, environment: ApplicationEnvironmentABC
):
# this shit has to be done here because we need settings in subsequent startup extensions # this shit has to be done here because we need settings in subsequent startup extensions
environment.set_working_directory(os.path.dirname(os.path.realpath(__file__))) environment.set_working_directory(os.path.dirname(os.path.realpath(__file__)))
configuration.add_environment_variables("SDB_") configuration.add_environment_variables("KDB_")
configuration.add_environment_variables("DISCORD_") configuration.add_environment_variables("DISCORD_")
configuration.add_configuration(
MAINTENANCE, configuration.get_configuration(MAINTENANCE) in [True, "true", "True"]
)
configuration.add_configuration(
MIGRATION_ONLY, configuration.get_configuration(MIGRATION_ONLY) in [True, "true", "True"]
)
configuration.add_json_file(f"config/appsettings.json", optional=False) configuration.add_json_file(f"config/appsettings.json", optional=False)
configuration.add_json_file(f"config/appsettings.{environment.environment_name}.json", optional=True) configuration.add_json_file(
configuration.add_json_file(f"config/appsettings.{environment.host_name}.json", optional=True) f"config/appsettings.{environment.environment_name}.json", optional=True
)
configuration.add_json_file(
f"config/appsettings.{environment.host_name}.json", optional=True
)
# load feature-flags # load feature-flags
configuration.add_json_file(f"config/feature-flags.json", optional=False) configuration.add_json_file(f"config/feature-flags.json", optional=False)
configuration.add_json_file(f"config/feature-flags.{environment.environment_name}.json", optional=True) configuration.add_json_file(
configuration.add_json_file(f"config/feature-flags.{environment.host_name}.json", optional=True) f"config/feature-flags.{environment.environment_name}.json", optional=True
)
configuration.add_json_file(
f"config/feature-flags.{environment.host_name}.json", optional=True
)
configuration.add_configuration("Startup_StartTime", str(self._start_time)) configuration.add_configuration("Startup_StartTime", str(self._start_time))
self._configure_settings_with_sub_settings( self._configure_settings_with_sub_settings(
configuration, BotLoggingSettings, lambda x: x.files, lambda x: x.key configuration, BotLoggingSettings, lambda x: x.files, lambda x: x.key
) )
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): def configure_services(
self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC
):
pass pass
@staticmethod @staticmethod
@@ -52,4 +57,6 @@ class StartupSettingsExtension(StartupExtensionABC):
return return
for sub_settings in list_atr(settings): for sub_settings in list_atr(settings):
config.add_configuration(f"{type(sub_settings).__name__}_{atr(sub_settings)}", sub_settings) config.add_configuration(
f"{type(sub_settings).__name__}_{atr(sub_settings)}", sub_settings
)

View File

@@ -2,22 +2,22 @@
"api": { "api": {
"api": { "api": {
"test_mail": { "test_mail": {
"message": "Dies ist eine Test-Mail vom Krümelmonster Web Interface\r\nGesendet von {}-{}", "message": "Dies ist eine Test-Mail vom Krümelmonster Web Interface\nGesendet von {}-{}",
"subject": "Krümelmonster Web Interface Test-Mail" "subject": "Krümelmonster Web Interface Test-Mail"
} }
}, },
"auth": { "auth": {
"confirmation": { "confirmation": {
"message": "Öffne den Link, um die E-Mail zu bestätigen:\r\n{}auth/register/{}", "message": "Öffne den Link, um die E-Mail zu bestätigen:\n{}auth/register/{}",
"subject": "E-Mail für {} {} bestätigen" "subject": "E-Mail für {} {} bestätigen"
}, },
"forgot_password": { "forgot_password": {
"message": "Öffne den Link, um das Passwort zu ändern:\r\n{}auth/forgot-password/{}", "message": "Öffne den Link, um das Passwort zu ändern:\n{}auth/forgot-password/{}",
"subject": "Passwort für {} {} zurücksetzen" "subject": "Passwort für {} {} zurücksetzen"
} }
}, },
"mail": { "mail": {
"automatic_mail": "\r\n\r\nDies ist eine automatische E-Mail.\r\nGesendet von {}-{}@{}" "automatic_mail": "\n\nDies ist eine automatische E-Mail.\nGesendet von {}-{}@{}"
} }
}, },
"common": { "common": {
@@ -90,61 +90,10 @@
"booting": "Ich fahre gerade hoch...", "booting": "Ich fahre gerade hoch...",
"restart": "Muss neue Kekse holen...", "restart": "Muss neue Kekse holen...",
"running": "Ich esse Kekse :D", "running": "Ich esse Kekse :D",
"shutdown": "Ich werde bestimmt wieder kommen...", "shutdown": "Ich werde bestimmt wieder kommen..."
"maintenance": "In Wartung!"
} }
}, },
"modules": { "modules": {
"realm": {
"list": {
"title": "Level:",
"description": "Konfigurierte Level:",
"realm_names": "Realm-Namen",
"error": {
"nothing_found": "Keine Einträge gefunden."
}
},
"add": {
"error": {
"role_exists": "Die Rolle {} existiert bereits.",
"category_exists": "Die Kategorie {} existiert bereits.",
"realm_exists": "Das Realm {} existiert bereits.",
"realm_create": "Fehler beim Erstellen des Realms {}."
},
"success": "Das Realm {} wurde erfolgreich erstellt."
},
"edit": {
"error": {
"realm_not_found": "Das Realm {} wurde nicht gefunden."
},
"success": "Das Realm {} wurde erfolgreich in {} umbenannt."
},
"error": {
"not_moderator": "Du bist kein Moderator des Realms {}."
},
"delete": {
"error": {
"realm_not_found": "Das Realm {} wurde nicht gefunden."
},
"success": "Das Realm {} wurde erfolgreich gelöscht."
},
"member": {
"add": {
"success": "{} wurde erfolgreich zum Realm {} hinzugefügt."
},
"remove": {
"success": "{} wurde erfolgreich aus dem Realm {} entfernt."
}
},
"moderator": {
"add": {
"success": "{} wurde erfolgreich zum Moderator des Realms {} hinzugefügt."
},
"remove": {
"success": "{} wurde erfolgreich als Moderator des Realms {} entfernt."
}
}
},
"special_offers": { "special_offers": {
"price": "Preis", "price": "Preis",
"discount": "Rabatt", "discount": "Rabatt",
@@ -286,7 +235,7 @@
}, },
"user": { "user": {
"birthday": { "birthday": {
"has_birthday": "Alles Gute zum Geburtstag {} :D", "has_birthday": "Alles Gute zum Geburtag {} :D",
"success": "Dein Geburtstag wurde eingetragen.", "success": "Dein Geburtstag wurde eingetragen.",
"success_team": "{} hat seinen Geburtstag eingetragen: {}" "success_team": "{} hat seinen Geburtstag eingetragen: {}"
}, },

View File

@@ -15,7 +15,7 @@ __title__ = "bot_api"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -15,7 +15,7 @@ __title__ = "bot_api.abc"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -40,11 +40,15 @@ class AuthServiceABC(ABC):
pass pass
@abstractmethod @abstractmethod
async def get_filtered_auth_users_async(self, criteria: AuthUserSelectCriteria) -> AuthUserFilteredResultDTO: async def get_filtered_auth_users_async(
self, criteria: AuthUserSelectCriteria
) -> AuthUserFilteredResultDTO:
pass pass
@abstractmethod @abstractmethod
async def get_auth_user_by_email_async(self, email: str, with_password: bool = False) -> AuthUserDTO: async def get_auth_user_by_email_async(
self, email: str, with_password: bool = False
) -> AuthUserDTO:
pass pass
@abstractmethod @abstractmethod
@@ -114,7 +118,3 @@ class AuthServiceABC(ABC):
@abstractmethod @abstractmethod
async def reset_password_async(self, rp_dto: ResetPasswordDTO): async def reset_password_async(self, rp_dto: ResetPasswordDTO):
pass pass
@abstractmethod
async def resend_confirmation_email_by_mail(self, mail: str):
pass

View File

@@ -3,7 +3,9 @@ from abc import ABC, abstractmethod
class SelectCriteriaABC(ABC): class SelectCriteriaABC(ABC):
@abstractmethod @abstractmethod
def __init__(self, page_index: int, page_size: int, sort_direction: str, sort_column: str): def __init__(
self, page_index: int, page_size: int, sort_direction: str, sort_column: str
):
self.page_index = page_index self.page_index = page_index
self.page_size = page_size self.page_size = page_size
self.sort_direction = sort_direction self.sort_direction = sort_direction

View File

@@ -16,8 +16,8 @@ from werkzeug.exceptions import NotFound
from bot_api.configuration.api_settings import ApiSettings from bot_api.configuration.api_settings import ApiSettings
from bot_api.configuration.authentication_settings import AuthenticationSettings from bot_api.configuration.authentication_settings import AuthenticationSettings
from bot_core.exception.service_error_code_enum import ServiceErrorCode from bot_api.exception.service_error_code_enum import ServiceErrorCode
from bot_core.exception.service_exception import ServiceException from bot_api.exception.service_exception import ServiceException
from bot_api.logging.api_logger import ApiLogger from bot_api.logging.api_logger import ApiLogger
from bot_api.model.error_dto import ErrorDTO from bot_api.model.error_dto import ErrorDTO
from bot_api.route.route import Route from bot_api.route.route import Route
@@ -57,7 +57,9 @@ class Api(Flask):
# Added async_mode see link below # Added async_mode see link below
# https://github.com/miguelgrinberg/Flask-SocketIO/discussions/1849 # https://github.com/miguelgrinberg/Flask-SocketIO/discussions/1849
# https://stackoverflow.com/questions/39370848/flask-socket-io-sometimes-client-calls-freeze-the-server # https://stackoverflow.com/questions/39370848/flask-socket-io-sometimes-client-calls-freeze-the-server
self._socketio = SocketIO(self, cors_allowed_origins="*", path="/api/socket.io", async_mode="eventlet") self._socketio = SocketIO(
self, cors_allowed_origins="*", path="/api/socket.io", async_mode="eventlet"
)
self._socketio.on_event("connect", self.on_connect) self._socketio.on_event("connect", self.on_connect)
self._socketio.on_event("disconnect", self.on_disconnect) self._socketio.on_event("disconnect", self.on_disconnect)
@@ -143,7 +145,9 @@ class Api(Flask):
data = request.get_data() data = request.get_data()
data = "" if len(data) == 0 else str(data.decode(encoding="utf-8")) 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}") text = textwrap.dedent(
f"Request: {request_id}:\n\tHeader:\n\t\t{headers}\n\tResponse: {data}"
)
self._logger.trace(__name__, text) self._logger.trace(__name__, text)
return response return response
@@ -158,7 +162,9 @@ class Api(Flask):
# from waitress import serve # from waitress import serve
# https://docs.pylonsproject.org/projects/waitress/en/stable/arguments.html # 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) # serve(self, host=self._apt_settings.host, port=self._apt_settings.port, threads=10, connection_limit=1000, channel_timeout=10)
self._socket = eventlet.listen((self._api_settings.host, self._api_settings.port)) self._socket = eventlet.listen(
(self._api_settings.host, self._api_settings.port)
)
wsgi.server(self._socket, self, log_output=False) wsgi.server(self._socket, self, log_output=False)
def stop(self): def stop(self):

View File

@@ -26,15 +26,21 @@ class ApiModule(ModuleABC):
def __init__(self, dc: DiscordCollectionABC): def __init__(self, dc: DiscordCollectionABC):
ModuleABC.__init__(self, dc, FeatureFlagsEnum.api_module) ModuleABC.__init__(self, dc, FeatureFlagsEnum.api_module)
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC): def configure_configuration(
self, config: ConfigurationABC, env: ApplicationEnvironmentABC
):
cwd = env.working_directory cwd = env.working_directory
env.set_working_directory(os.path.dirname(os.path.realpath(__file__))) 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.json", optional=False)
config.add_json_file(f"config/apisettings.{env.environment_name}.json", optional=True) 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) config.add_json_file(f"config/apisettings.{env.host_name}.json", optional=True)
env.set_working_directory(cwd) env.set_working_directory(cwd)
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): def configure_services(
self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC
):
services.add_singleton(EMailClientABC, EMailClient) services.add_singleton(EMailClientABC, EMailClient)
services.add_singleton(ApiThread) services.add_singleton(ApiThread)

View File

@@ -12,7 +12,9 @@ class AppApiExtension(ApplicationExtensionABC):
ApplicationExtensionABC.__init__(self) ApplicationExtensionABC.__init__(self)
async def run(self, config: ConfigurationABC, services: ServiceProviderABC): async def run(self, config: ConfigurationABC, services: ServiceProviderABC):
feature_flags: FeatureFlagsSettings = config.get_configuration(FeatureFlagsSettings) feature_flags: FeatureFlagsSettings = config.get_configuration(
FeatureFlagsSettings
)
if not feature_flags.get_flag(FeatureFlagsEnum.api_module): if not feature_flags.get_flag(FeatureFlagsEnum.api_module):
return return

View File

@@ -4,7 +4,7 @@
"Version": { "Version": {
"Major": "1", "Major": "1",
"Minor": "2", "Minor": "2",
"Micro": "11" "Micro": "0"
}, },
"Author": "", "Author": "",
"AuthorEmail": "", "AuthorEmail": "",

View File

@@ -15,7 +15,7 @@ __title__ = "bot_api.configuration"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports # imports
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -16,7 +16,9 @@ class AuthenticationSettings(ConfigurationModelABC):
self._issuer = "" if issuer is None else issuer self._issuer = "" if issuer is None else issuer
self._audience = "" if audience is None else audience self._audience = "" if audience is None else audience
self._token_expire_time = 0 if token_expire_time is None else token_expire_time self._token_expire_time = 0 if token_expire_time is None else token_expire_time
self._refresh_token_expire_time = 0 if refresh_token_expire_time is None else refresh_token_expire_time self._refresh_token_expire_time = (
0 if refresh_token_expire_time is None else refresh_token_expire_time
)
@property @property
def secret_key(self) -> str: def secret_key(self) -> str:

View File

@@ -15,7 +15,7 @@ __title__ = "bot_api.controller"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -14,10 +14,7 @@ from bot_api.model.reset_password_dto import ResetPasswordDTO
from bot_api.model.token_dto import TokenDTO from bot_api.model.token_dto import TokenDTO
from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO
from bot_api.route.route import Route from bot_api.route.route import Route
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
from bot_data.model.auth_role_enum import AuthRoleEnum from bot_data.model.auth_role_enum import AuthRoleEnum
from bot_data.model.technician_config import TechnicianConfig
class AuthController: class AuthController:
@@ -33,7 +30,6 @@ class AuthController:
mail_settings: EMailClientSettings, mail_settings: EMailClientSettings,
mailer: EMailClientABC, mailer: EMailClientABC,
auth_service: AuthServiceABC, auth_service: AuthServiceABC,
technician_config: TechnicianConfig,
): ):
self._config = config self._config = config
self._env = env self._env = env
@@ -43,7 +39,6 @@ class AuthController:
self._mail_settings = mail_settings self._mail_settings = mail_settings
self._mailer = mailer self._mailer = mailer
self._auth_service = auth_service self._auth_service = auth_service
self._technician_config = technician_config
@Route.get(f"{BasePath}/users") @Route.get(f"{BasePath}/users")
@Route.authorize(role=AuthRoleEnum.admin) @Route.authorize(role=AuthRoleEnum.admin)
@@ -75,20 +70,12 @@ class AuthController:
@Route.post(f"{BasePath}/register") @Route.post(f"{BasePath}/register")
async def register(self): async def register(self):
if not FeatureFlagsSettings.get_flag_from_dict( dto: AuthUserDTO = JSONProcessor.process(
self._technician_config.feature_flags, FeatureFlagsEnum.basic_registration AuthUserDTO, request.get_json(force=True, silent=True)
): )
return
dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True))
self._auth_service.add_auth_user(dto) self._auth_service.add_auth_user(dto)
return "", 200 return "", 200
@Route.post(f"{BasePath}/resend-confirmation-email-by-mail/<mail>")
async def resend_confirmation_email_by_user_id(self, mail: str):
await self._auth_service.resend_confirmation_email_by_mail(mail)
return "", 200
@Route.post(f"{BasePath}/register-by-id/<id>") @Route.post(f"{BasePath}/register-by-id/<id>")
async def register_id(self, id: str): async def register_id(self, id: str):
result = await self._auth_service.confirm_email_async(id) result = await self._auth_service.confirm_email_async(id)
@@ -96,12 +83,9 @@ class AuthController:
@Route.post(f"{BasePath}/login") @Route.post(f"{BasePath}/login")
async def login(self) -> Response: async def login(self) -> Response:
if not FeatureFlagsSettings.get_flag_from_dict( dto: AuthUserDTO = JSONProcessor.process(
self._technician_config.feature_flags, FeatureFlagsEnum.basic_login AuthUserDTO, request.get_json(force=True, silent=True)
): )
return jsonify({})
dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True))
result = await self._auth_service.login_async(dto) result = await self._auth_service.login_async(dto)
return jsonify(result.to_dict()) return jsonify(result.to_dict())
@@ -120,11 +104,6 @@ class AuthController:
@Route.post(f"{BasePath}/forgot-password/<email>") @Route.post(f"{BasePath}/forgot-password/<email>")
async def forgot_password(self, email: str): async def forgot_password(self, email: str):
if not FeatureFlagsSettings.get_flag_from_dict(
self._technician_config.feature_flags, FeatureFlagsEnum.basic_login
):
return "", 409
await self._auth_service.forgot_password_async(email) await self._auth_service.forgot_password_async(email)
return "", 200 return "", 200
@@ -135,45 +114,52 @@ class AuthController:
@Route.post(f"{BasePath}/reset-password") @Route.post(f"{BasePath}/reset-password")
async def reset_password(self): async def reset_password(self):
if not FeatureFlagsSettings.get_flag_from_dict( dto: ResetPasswordDTO = JSONProcessor.process(
self._technician_config.feature_flags, FeatureFlagsEnum.basic_login ResetPasswordDTO, request.get_json(force=True, silent=True)
): )
return "", 409
dto: ResetPasswordDTO = JSONProcessor.process(ResetPasswordDTO, request.get_json(force=True, silent=True))
await self._auth_service.reset_password_async(dto) await self._auth_service.reset_password_async(dto)
return "", 200 return "", 200
@Route.post(f"{BasePath}/update-user") @Route.post(f"{BasePath}/update-user")
@Route.authorize @Route.authorize
async def update_user(self): async def update_user(self):
dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True)) dto: UpdateAuthUserDTO = JSONProcessor.process(
UpdateAuthUserDTO, request.get_json(force=True, silent=True)
)
await self._auth_service.update_user_async(dto) await self._auth_service.update_user_async(dto)
return "", 200 return "", 200
@Route.post(f"{BasePath}/update-user-as-admin") @Route.post(f"{BasePath}/update-user-as-admin")
@Route.authorize(role=AuthRoleEnum.admin) @Route.authorize(role=AuthRoleEnum.admin)
async def update_user_as_admin(self): async def update_user_as_admin(self):
dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True)) dto: UpdateAuthUserDTO = JSONProcessor.process(
UpdateAuthUserDTO, request.get_json(force=True, silent=True)
)
await self._auth_service.update_user_as_admin_async(dto) await self._auth_service.update_user_as_admin_async(dto)
return "", 200 return "", 200
@Route.post(f"{BasePath}/refresh") @Route.post(f"{BasePath}/refresh")
async def refresh(self) -> Response: async def refresh(self) -> Response:
dto: TokenDTO = JSONProcessor.process(TokenDTO, request.get_json(force=True, silent=True)) dto: TokenDTO = JSONProcessor.process(
TokenDTO, request.get_json(force=True, silent=True)
)
result = await self._auth_service.refresh_async(dto) result = await self._auth_service.refresh_async(dto)
return jsonify(result.to_dict()) return jsonify(result.to_dict())
@Route.post(f"{BasePath}/revoke") @Route.post(f"{BasePath}/revoke")
async def revoke(self): async def revoke(self):
dto: TokenDTO = JSONProcessor.process(TokenDTO, request.get_json(force=True, silent=True)) dto: TokenDTO = JSONProcessor.process(
TokenDTO, request.get_json(force=True, silent=True)
)
await self._auth_service.revoke_async(dto) await self._auth_service.revoke_async(dto)
return "", 200 return "", 200
@Route.post(f"{BasePath}/delete-user") @Route.post(f"{BasePath}/delete-user")
@Route.authorize(role=AuthRoleEnum.admin) @Route.authorize(role=AuthRoleEnum.admin)
async def delete_user(self): async def delete_user(self):
dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True)) dto: AuthUserDTO = JSONProcessor.process(
AuthUserDTO, request.get_json(force=True, silent=True)
)
await self._auth_service.delete_auth_user_async(dto) await self._auth_service.delete_auth_user_async(dto)
return "", 200 return "", 200

View File

@@ -16,7 +16,6 @@ from bot_api.api import Api
from bot_api.configuration.discord_authentication_settings import ( from bot_api.configuration.discord_authentication_settings import (
DiscordAuthenticationSettings, DiscordAuthenticationSettings,
) )
from bot_core.exception.service_exception import ServiceException
from bot_api.logging.api_logger import ApiLogger from bot_api.logging.api_logger import ApiLogger
from bot_api.model.auth_user_dto import AuthUserDTO from bot_api.model.auth_user_dto import AuthUserDTO
from bot_api.route.route import Route from bot_api.route.route import Route
@@ -91,10 +90,5 @@ class AuthDiscordController:
AuthRoleEnum.normal, AuthRoleEnum.normal,
) )
try:
result = await self._auth_service.login_discord_async(dto, response["id"]) result = await self._auth_service.login_discord_async(dto, response["id"])
return jsonify(result.to_dict()) return jsonify(result.to_dict())
except ServiceException as e:
r = jsonify({"email": dto.email})
r.status_code = 403
return r

View File

@@ -12,9 +12,6 @@ from bot_api.logging.api_logger import ApiLogger
from bot_api.model.settings_dto import SettingsDTO from bot_api.model.settings_dto import SettingsDTO
from bot_api.model.version_dto import VersionDTO from bot_api.model.version_dto import VersionDTO
from bot_api.route.route import Route from bot_api.route.route import Route
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
from bot_data.model.technician_config import TechnicianConfig
class GuiController: class GuiController:
@@ -85,11 +82,3 @@ class GuiController:
) )
self._mailer.send_mail(mail) self._mailer.send_mail(mail)
return "", 200 return "", 200
@Route.get(f"{BasePath}/has-feature-flag/<flag>")
async def has_feature_flag(self, flag: str):
settings: TechnicianConfig = self._config.get_configuration(TechnicianConfig)
return {
"key": flag,
"value": FeatureFlagsSettings.get_flag_from_dict(settings.feature_flags, FeatureFlagsEnum(flag)),
}

View File

@@ -15,7 +15,7 @@ __title__ = "bot_api.event"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -11,11 +11,11 @@ Discord bot for customers of sh-edraft.de
""" """
__title__ = "modules.realms" __title__ = "bot_api.exception"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -1,4 +1,4 @@
from bot_core.exception.service_error_code_enum import ServiceErrorCode from bot_api.exception.service_error_code_enum import ServiceErrorCode
class ServiceException(Exception): class ServiceException(Exception):

View File

@@ -15,7 +15,7 @@ __title__ = "bot_api.filter"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -13,7 +13,9 @@ class AuthUserSelectCriteria(SelectCriteriaABC):
email: str, email: str,
auth_role: int, auth_role: int,
): ):
SelectCriteriaABC.__init__(self, page_index, page_size, sort_direction, sort_column) SelectCriteriaABC.__init__(
self, page_index, page_size, sort_direction, sort_column
)
self.first_name = first_name self.first_name = first_name
self.last_name = last_name self.last_name = last_name

View File

@@ -15,7 +15,7 @@ __title__ = "bot_api.filter.discord"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -10,6 +10,8 @@ class ServerSelectCriteria(SelectCriteriaABC):
sort_column: str, sort_column: str,
name: str, name: str,
): ):
SelectCriteriaABC.__init__(self, page_index, page_size, sort_direction, sort_column) SelectCriteriaABC.__init__(
self, page_index, page_size, sort_direction, sort_column
)
self.name = name self.name = name

View File

@@ -15,7 +15,7 @@ __title__ = "bot_api.logging"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -15,7 +15,7 @@ __title__ = "bot_api.model"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -15,7 +15,7 @@ __title__ = "bot_api.model.discord"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -1,14 +1,19 @@
import traceback
from typing import Optional from typing import Optional
from cpl_core.console import Console
from bot_api.abc.dto_abc import DtoABC from bot_api.abc.dto_abc import DtoABC
from bot_core.exception.service_error_code_enum import ServiceErrorCode from bot_api.exception.service_error_code_enum import ServiceErrorCode
class ErrorDTO(DtoABC): class ErrorDTO(DtoABC):
def __init__(self, error_code: Optional[ServiceErrorCode], message: str): def __init__(self, error_code: Optional[ServiceErrorCode], message: str):
DtoABC.__init__(self) DtoABC.__init__(self)
self._error_code = ServiceErrorCode.Unknown if error_code is None else error_code self._error_code = (
ServiceErrorCode.Unknown if error_code is None else error_code
)
self._message = message self._message = message
@property @property

View File

@@ -34,7 +34,9 @@ class UpdateAuthUserDTO(DtoABC):
def from_dict(self, values: dict): def from_dict(self, values: dict):
self._auth_user = AuthUserDTO().from_dict(values["authUser"]) self._auth_user = AuthUserDTO().from_dict(values["authUser"])
self._new_auth_user = AuthUserDTO().from_dict(values["newAuthUser"]) self._new_auth_user = AuthUserDTO().from_dict(values["newAuthUser"])
self._change_password = False if "changePassword" not in values else bool(values["changePassword"]) self._change_password = (
False if "changePassword" not in values else bool(values["changePassword"])
)
def to_dict(self) -> dict: def to_dict(self) -> dict:
return { return {

View File

@@ -15,7 +15,7 @@ __title__ = "bot_api.route"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -8,8 +8,8 @@ from flask import request, jsonify
from flask_cors import cross_origin from flask_cors import cross_origin
from bot_api.abc.auth_service_abc import AuthServiceABC from bot_api.abc.auth_service_abc import AuthServiceABC
from bot_core.exception.service_error_code_enum import ServiceErrorCode from bot_api.exception.service_error_code_enum import ServiceErrorCode
from bot_core.exception.service_exception import ServiceException from bot_api.exception.service_exception import ServiceException
from bot_api.model.error_dto import ErrorDTO from bot_api.model.error_dto import ErrorDTO
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
from bot_data.model.auth_role_enum import AuthRoleEnum from bot_data.model.auth_role_enum import AuthRoleEnum
@@ -65,7 +65,9 @@ class Route:
by_api_key=False, by_api_key=False,
): ):
if f is None: if f is None:
return functools.partial(cls.authorize, role=role, skip_in_dev=skip_in_dev, by_api_key=by_api_key) return functools.partial(
cls.authorize, role=role, skip_in_dev=skip_in_dev, by_api_key=by_api_key
)
@wraps(f) @wraps(f)
async def decorator(*args, **kwargs): async def decorator(*args, **kwargs):
@@ -76,7 +78,9 @@ class Route:
api_key = None api_key = None
if "Authorization" in request.headers: if "Authorization" in request.headers:
if " " not in request.headers.get("Authorization"): if " " not in request.headers.get("Authorization"):
ex = ServiceException(ServiceErrorCode.Unauthorized, f"Token not set") ex = ServiceException(
ServiceErrorCode.Unauthorized, f"Token not set"
)
error = ErrorDTO(ex.error_code, ex.message) error = ErrorDTO(ex.error_code, ex.message)
return jsonify(error.to_dict()), 401 return jsonify(error.to_dict()), 401
@@ -98,7 +102,9 @@ class Route:
return jsonify(e), 500 return jsonify(e), 500
if not valid: if not valid:
ex = ServiceException(ServiceErrorCode.Unauthorized, f"API-Key invalid") ex = ServiceException(
ServiceErrorCode.Unauthorized, f"API-Key invalid"
)
error = ErrorDTO(ex.error_code, ex.message) error = ErrorDTO(ex.error_code, ex.message)
return jsonify(error.to_dict()), 401 return jsonify(error.to_dict()), 401
@@ -110,7 +116,9 @@ class Route:
return jsonify(error.to_dict()), 401 return jsonify(error.to_dict()), 401
if cls._auth_users is None or cls._auth is None: if cls._auth_users is None or cls._auth is None:
ex = ServiceException(ServiceErrorCode.Unauthorized, f"Authorize is not initialized") ex = ServiceException(
ServiceErrorCode.Unauthorized, f"Authorize is not initialized"
)
error = ErrorDTO(ex.error_code, ex.message) error = ErrorDTO(ex.error_code, ex.message)
return jsonify(error.to_dict()), 401 return jsonify(error.to_dict()), 401
@@ -132,7 +140,9 @@ class Route:
return jsonify(error.to_dict()), 401 return jsonify(error.to_dict()), 401
if role is not None and user.auth_role.value < role.value: if role is not None and user.auth_role.value < role.value:
ex = ServiceException(ServiceErrorCode.Unauthorized, f"Role {role} required") ex = ServiceException(
ServiceErrorCode.Unauthorized, f"Role {role} required"
)
error = ErrorDTO(ex.error_code, ex.message) error = ErrorDTO(ex.error_code, ex.message)
return jsonify(error.to_dict()), 403 return jsonify(error.to_dict()), 403

View File

@@ -15,7 +15,7 @@ __title__ = "bot_api.service"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports # imports
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -1,5 +1,6 @@
import hashlib import hashlib
import re import re
import textwrap
import uuid import uuid
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from threading import Thread from threading import Thread
@@ -18,8 +19,8 @@ from flask import request
from bot_api.abc.auth_service_abc import AuthServiceABC from bot_api.abc.auth_service_abc import AuthServiceABC
from bot_api.configuration.authentication_settings import AuthenticationSettings from bot_api.configuration.authentication_settings import AuthenticationSettings
from bot_api.configuration.frontend_settings import FrontendSettings from bot_api.configuration.frontend_settings import FrontendSettings
from bot_core.exception.service_error_code_enum import ServiceErrorCode from bot_api.exception.service_error_code_enum import ServiceErrorCode
from bot_core.exception.service_exception import ServiceException from bot_api.exception.service_exception import ServiceException
from bot_api.filter.auth_user_select_criteria import AuthUserSelectCriteria from bot_api.filter.auth_user_select_criteria import AuthUserSelectCriteria
from bot_api.logging.api_logger import ApiLogger from bot_api.logging.api_logger import ApiLogger
from bot_api.model.auth_user_dto import AuthUserDTO from bot_api.model.auth_user_dto import AuthUserDTO
@@ -89,7 +90,9 @@ class AuthService(AuthServiceABC):
def _get_api_key_str(self, api_key: ApiKey) -> str: def _get_api_key_str(self, api_key: ApiKey) -> str:
return hashlib.sha256( return hashlib.sha256(
f"{api_key.identifier}:{api_key.key}+{self._auth_settings.secret_key}".encode("utf-8") f"{api_key.identifier}:{api_key.key}+{self._auth_settings.secret_key}".encode(
"utf-8"
)
).hexdigest() ).hexdigest()
def generate_token(self, user: AuthUser) -> str: def generate_token(self, user: AuthUser) -> str:
@@ -98,7 +101,8 @@ class AuthService(AuthServiceABC):
"user_id": user.id, "user_id": user.id,
"email": user.email, "email": user.email,
"role": user.auth_role.value, "role": user.auth_role.value,
"exp": datetime.now(tz=timezone.utc) + timedelta(days=self._auth_settings.token_expire_time), "exp": datetime.now(tz=timezone.utc)
+ timedelta(days=self._auth_settings.token_expire_time),
"iss": self._auth_settings.issuer, "iss": self._auth_settings.issuer,
"aud": self._auth_settings.audience, "aud": self._auth_settings.audience,
}, },
@@ -154,7 +158,9 @@ class AuthService(AuthServiceABC):
def _create_and_save_refresh_token(self, user: AuthUser) -> str: def _create_and_save_refresh_token(self, user: AuthUser) -> str:
token = str(uuid.uuid4()) token = str(uuid.uuid4())
user.refresh_token = token user.refresh_token = token
user.refresh_token_expire_time = datetime.now() + timedelta(days=self._auth_settings.refresh_token_expire_time) user.refresh_token_expire_time = datetime.now() + timedelta(
days=self._auth_settings.refresh_token_expire_time
)
self._auth_users.update_auth_user(user) self._auth_users.update_auth_user(user)
self._db.save_changes() self._db.save_changes()
return token return token
@@ -171,7 +177,11 @@ class AuthService(AuthServiceABC):
mail.add_header("Content-Transfer-Encoding: quoted-printable") mail.add_header("Content-Transfer-Encoding: quoted-printable")
mail.add_receiver(str(email)) mail.add_receiver(str(email))
mail.subject = subject mail.subject = subject
mail.body = f"{message}\r\n{self._t.transform('api.mail.automatic_mail').format(self._environment.application_name, self._environment.environment_name, self._environment.host_name)}" 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 = Thread(target=self._mailer.send_mail, args=[mail])
thr.start() thr.start()
@@ -183,8 +193,12 @@ class AuthService(AuthServiceABC):
self._send_link_mail( self._send_link_mail(
user.email, user.email,
self._t.transform("api.auth.confirmation.subject").format(user.first_name, user.last_name), self._t.transform("api.auth.confirmation.subject").format(
self._t.transform("api.auth.confirmation.message").format(url, user.confirmation_id), 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): def _send_forgot_password_id_to_user(self, user: AuthUser):
@@ -194,28 +208,38 @@ class AuthService(AuthServiceABC):
self._send_link_mail( self._send_link_mail(
user.email, user.email,
self._t.transform("api.auth.forgot_password.subject").format(user.first_name, user.last_name), self._t.transform("api.auth.forgot_password.subject").format(
self._t.transform("api.auth.forgot_password.message").format(url, user.forgot_password_id), 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]: 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)) result = self._auth_users.get_all_auth_users().select(lambda x: AUT.to_dto(x))
return List(AuthUserDTO, result) return List(AuthUserDTO, result)
async def get_filtered_auth_users_async(self, criteria: AuthUserSelectCriteria) -> AuthUserFilteredResultDTO: async def get_filtered_auth_users_async(
self, criteria: AuthUserSelectCriteria
) -> AuthUserFilteredResultDTO:
users = self._auth_users.get_filtered_auth_users(criteria) users = self._auth_users.get_filtered_auth_users(criteria)
result = users.result.select(lambda x: AUT.to_dto(x)) result = users.result.select(lambda x: AUT.to_dto(x))
return AuthUserFilteredResultDTO(List(AuthUserDTO, result), users.total_count) return AuthUserFilteredResultDTO(List(AuthUserDTO, result), users.total_count)
async def get_auth_user_by_email_async(self, email: str, with_password: bool = False) -> AuthUserDTO: async def get_auth_user_by_email_async(
self, email: str, with_password: bool = False
) -> AuthUserDTO:
try: try:
# todo: check if logged in user is admin then send mail # todo: check if logged in user is admin then send mail
user = self._auth_users.get_auth_user_by_email(email) user = self._auth_users.get_auth_user_by_email(email)
return AUT.to_dto(user, password=user.password if with_password else None) return AUT.to_dto(user, password=user.password if with_password else None)
except Exception as e: except Exception as e:
self._logger.error(__name__, f"AuthUser not found", e) self._logger.error(__name__, f"AuthUser not found", e)
raise ServiceException(ServiceErrorCode.InvalidData, f"User not found {email}") raise ServiceException(
ServiceErrorCode.InvalidData, f"User not found {email}"
)
async def find_auth_user_by_email_async(self, email: str) -> Optional[AuthUser]: async def find_auth_user_by_email_async(self, email: str) -> Optional[AuthUser]:
user = self._auth_users.find_auth_user_by_email(email) user = self._auth_users.find_auth_user_by_email(email)
@@ -233,16 +257,22 @@ class AuthService(AuthServiceABC):
user.password_salt = uuid.uuid4() user.password_salt = uuid.uuid4()
user.password = self._hash_sha256(user_dto.password, user.password_salt) user.password = self._hash_sha256(user_dto.password, user.password_salt)
if not self._is_email_valid(user.email): if not self._is_email_valid(user.email):
raise ServiceException(ServiceErrorCode.InvalidData, "Invalid E-Mail address") raise ServiceException(
ServiceErrorCode.InvalidData, "Invalid E-Mail address"
)
try: try:
user.confirmation_id = uuid.uuid4() user.confirmation_id = uuid.uuid4()
self._auth_users.add_auth_user(user) self._auth_users.add_auth_user(user)
self._send_confirmation_id_to_user(user) self._send_confirmation_id_to_user(user)
self._db.save_changes() self._db.save_changes()
self._logger.info(__name__, f"Added auth user with E-Mail: {user_dto.email}") self._logger.info(
__name__, f"Added auth user with E-Mail: {user_dto.email}"
)
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Cannot add user with E-Mail {user_dto.email}", e) self._logger.error(
__name__, f"Cannot add user with E-Mail {user_dto.email}", e
)
raise ServiceException(ServiceErrorCode.UnableToAdd, "Invalid E-Mail") raise ServiceException(ServiceErrorCode.UnableToAdd, "Invalid E-Mail")
async def add_auth_user_by_oauth_async(self, dto: OAuthDTO): async def add_auth_user_by_oauth_async(self, dto: OAuthDTO):
@@ -258,14 +288,20 @@ class AuthService(AuthServiceABC):
db_user.first_name = dto.user.first_name db_user.first_name = dto.user.first_name
db_user.last_name = dto.user.last_name db_user.last_name = dto.user.last_name
db_user.password_salt = uuid.uuid4() db_user.password_salt = uuid.uuid4()
db_user.password = self._hash_sha256(dto.user.password, db_user.password_salt) db_user.password = self._hash_sha256(
dto.user.password, db_user.password_salt
)
db_user.oauth_id = None db_user.oauth_id = None
db_user.confirmation_id = uuid.uuid4() db_user.confirmation_id = uuid.uuid4()
self._send_confirmation_id_to_user(db_user) self._send_confirmation_id_to_user(db_user)
self._auth_users.update_auth_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}") self._logger.info(
__name__, f"Added auth user with E-Mail: {dto.user.email}"
)
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Cannot add user with E-Mail {dto.user.email}", e) self._logger.error(
__name__, f"Cannot add user with E-Mail {dto.user.email}", e
)
raise ServiceException(ServiceErrorCode.UnableToAdd, "Invalid E-Mail") raise ServiceException(ServiceErrorCode.UnableToAdd, "Invalid E-Mail")
self._db.save_changes() self._db.save_changes()
@@ -275,14 +311,16 @@ class AuthService(AuthServiceABC):
raise ServiceException(ServiceErrorCode.InvalidData, f"User is empty") raise ServiceException(ServiceErrorCode.InvalidData, f"User is empty")
if update_user_dto.auth_user is None: if update_user_dto.auth_user is None:
raise ServiceException(ServiceErrorCode.InvalidData, f"Existing user is empty") raise ServiceException(
ServiceErrorCode.InvalidData, f"Existing user is empty"
)
if update_user_dto.new_auth_user is None: if update_user_dto.new_auth_user is None:
raise ServiceException(ServiceErrorCode.InvalidData, f"New user is empty") 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( if not self._is_email_valid(
update_user_dto.new_auth_user.email 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") raise ServiceException(ServiceErrorCode.InvalidData, f"Invalid E-Mail")
user = self._auth_users.find_auth_user_by_email(update_user_dto.auth_user.email) user = self._auth_users.find_auth_user_by_email(update_user_dto.auth_user.email)
@@ -295,7 +333,8 @@ class AuthService(AuthServiceABC):
# update first name # update first name
if ( if (
update_user_dto.new_auth_user.first_name is not None 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 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 user.first_name = update_user_dto.new_auth_user.first_name
@@ -303,7 +342,8 @@ class AuthService(AuthServiceABC):
if ( if (
update_user_dto.new_auth_user.last_name is not None update_user_dto.new_auth_user.last_name is not None
and update_user_dto.new_auth_user.last_name != "" 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 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 user.last_name = update_user_dto.new_auth_user.last_name
@@ -313,22 +353,33 @@ class AuthService(AuthServiceABC):
and update_user_dto.new_auth_user.email != "" and update_user_dto.new_auth_user.email != ""
and update_user_dto.auth_user.email != 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) 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: if user_by_new_e_mail is not None:
raise ServiceException(ServiceErrorCode.InvalidUser, "User already exists") raise ServiceException(
ServiceErrorCode.InvalidUser, "User already exists"
)
user.email = update_user_dto.new_auth_user.email 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) 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: if update_user_dto.auth_user.password != user.password:
raise ServiceException(ServiceErrorCode.InvalidUser, "Wrong password") raise ServiceException(ServiceErrorCode.InvalidUser, "Wrong password")
# update password # update password
if ( if (
update_user_dto.new_auth_user.password is not None 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 and self._hash_sha256(
update_user_dto.new_auth_user.password, user.password_salt
)
!= user.password
): ):
user.password_salt = uuid.uuid4() user.password_salt = uuid.uuid4()
user.password = self._hash_sha256(update_user_dto.new_auth_user.password, user.password_salt) user.password = self._hash_sha256(
update_user_dto.new_auth_user.password, user.password_salt
)
self._auth_users.update_auth_user(user) self._auth_users.update_auth_user(user)
self._db.save_changes() self._db.save_changes()
@@ -338,23 +389,31 @@ class AuthService(AuthServiceABC):
raise ServiceException(ServiceErrorCode.InvalidData, f"User is empty") raise ServiceException(ServiceErrorCode.InvalidData, f"User is empty")
if update_user_dto.auth_user is None: if update_user_dto.auth_user is None:
raise ServiceException(ServiceErrorCode.InvalidData, f"Existing user is empty") raise ServiceException(
ServiceErrorCode.InvalidData, f"Existing user is empty"
)
if update_user_dto.new_auth_user is None: if update_user_dto.new_auth_user is None:
raise ServiceException(ServiceErrorCode.InvalidData, f"New user is empty") 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( if not self._is_email_valid(
update_user_dto.new_auth_user.email 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") raise ServiceException(ServiceErrorCode.InvalidData, f"Invalid E-Mail")
user = self._auth_users.find_auth_user_by_email(update_user_dto.auth_user.email) user = self._auth_users.find_auth_user_by_email(update_user_dto.auth_user.email)
if user is None: if user is None:
raise ServiceException(ServiceErrorCode.InvalidUser, "User not found") raise ServiceException(ServiceErrorCode.InvalidUser, "User not found")
if user.confirmation_id is not None and update_user_dto.new_auth_user.is_confirmed: if (
user.confirmation_id is not None
and update_user_dto.new_auth_user.is_confirmed
):
user.confirmation_id = None user.confirmation_id = None
elif user.confirmation_id is None and not update_user_dto.new_auth_user.is_confirmed: elif (
user.confirmation_id is None
and not update_user_dto.new_auth_user.is_confirmed
):
user.confirmation_id = uuid.uuid4() user.confirmation_id = uuid.uuid4()
# else # else
# raise ServiceException(ServiceErrorCode.InvalidUser, 'E-Mail not confirmed') # raise ServiceException(ServiceErrorCode.InvalidUser, 'E-Mail not confirmed')
@@ -362,7 +421,8 @@ class AuthService(AuthServiceABC):
# update first name # update first name
if ( if (
update_user_dto.new_auth_user.first_name is not None 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 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 user.first_name = update_user_dto.new_auth_user.first_name
@@ -370,7 +430,8 @@ class AuthService(AuthServiceABC):
if ( if (
update_user_dto.new_auth_user.last_name is not None update_user_dto.new_auth_user.last_name is not None
and update_user_dto.new_auth_user.last_name != "" 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 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 user.last_name = update_user_dto.new_auth_user.last_name
@@ -380,19 +441,28 @@ class AuthService(AuthServiceABC):
and update_user_dto.new_auth_user.email != "" and update_user_dto.new_auth_user.email != ""
and update_user_dto.auth_user.email != 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) 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: if user_by_new_e_mail is not None:
raise ServiceException(ServiceErrorCode.InvalidUser, "User already exists") raise ServiceException(
ServiceErrorCode.InvalidUser, "User already exists"
)
user.email = update_user_dto.new_auth_user.email user.email = update_user_dto.new_auth_user.email
# update password # update password
if ( if (
update_user_dto.new_auth_user.password is not None update_user_dto.new_auth_user.password is not None
and update_user_dto.change_password and update_user_dto.change_password
and user.password != self._hash_sha256(update_user_dto.new_auth_user.password, user.password_salt) and user.password
!= self._hash_sha256(
update_user_dto.new_auth_user.password, user.password_salt
)
): ):
user.password_salt = uuid.uuid4() user.password_salt = uuid.uuid4()
user.password = self._hash_sha256(update_user_dto.new_auth_user.password, user.password_salt) user.password = self._hash_sha256(
update_user_dto.new_auth_user.password, user.password_salt
)
# update role # update role
if ( if (
@@ -411,7 +481,9 @@ class AuthService(AuthServiceABC):
self._db.save_changes() self._db.save_changes()
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Cannot delete user", e) self._logger.error(__name__, f"Cannot delete user", e)
raise ServiceException(ServiceErrorCode.UnableToDelete, f"Cannot delete user by mail {email}") raise ServiceException(
ServiceErrorCode.UnableToDelete, f"Cannot delete user by mail {email}"
)
async def delete_auth_user_async(self, user_dto: AuthUser): async def delete_auth_user_async(self, user_dto: AuthUser):
try: try:
@@ -495,7 +567,9 @@ class AuthService(AuthServiceABC):
if user.id in user_ids: if user.id in user_ids:
continue continue
self._auth_users.add_auth_user_user_rel(AuthUserUsersRelation(db_user, user)) self._auth_users.add_auth_user_user_rel(
AuthUserUsersRelation(db_user, user)
)
if db_user.confirmation_id is not None and not added_user: if db_user.confirmation_id is not None and not added_user:
raise ServiceException(ServiceErrorCode.Forbidden, "E-Mail not verified") raise ServiceException(ServiceErrorCode.Forbidden, "E-Mail not verified")
@@ -525,13 +599,19 @@ class AuthService(AuthServiceABC):
): ):
raise ServiceException(ServiceErrorCode.InvalidData, "Token expired") raise ServiceException(ServiceErrorCode.InvalidData, "Token expired")
return TokenDTO(self.generate_token(user), self._create_and_save_refresh_token(user)) return TokenDTO(
self.generate_token(user), self._create_and_save_refresh_token(user)
)
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Refreshing token failed", e) self._logger.error(__name__, f"Refreshing token failed", e)
return TokenDTO("", "") return TokenDTO("", "")
async def revoke_async(self, token_dto: 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: 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") raise ServiceException(ServiceErrorCode.InvalidData, "Token not set")
try: try:
@@ -584,7 +664,9 @@ class AuthService(AuthServiceABC):
) )
if user.confirmation_id is not None: if user.confirmation_id is not None:
raise ServiceException(ServiceErrorCode.InvalidUser, f"E-Mail not confirmed") raise ServiceException(
ServiceErrorCode.InvalidUser, f"E-Mail not confirmed"
)
if user.password is None or rp_dto.password == "": if user.password is None or rp_dto.password == "":
raise ServiceException(ServiceErrorCode.InvalidData, f"Password not set") raise ServiceException(ServiceErrorCode.InvalidData, f"Password not set")
@@ -594,12 +676,3 @@ class AuthService(AuthServiceABC):
user.forgot_password_id = None user.forgot_password_id = None
self._auth_users.update_auth_user(user) self._auth_users.update_auth_user(user)
self._db.save_changes() self._db.save_changes()
async def resend_confirmation_email_by_mail(self, mail: str):
user = self._auth_users.find_auth_user_by_email(mail)
if user is None:
raise ServiceException(ServiceErrorCode.InvalidUser, f"User not found")
if user.confirmation_id is None:
raise ServiceException(ServiceErrorCode.DataAlreadyExists, f"User already confirmed")
self._send_confirmation_id_to_user(user)

View File

@@ -4,8 +4,8 @@ from cpl_discord.service import DiscordBotServiceABC
from cpl_query.extension import List from cpl_query.extension import List
from bot_api.abc.auth_service_abc import AuthServiceABC from bot_api.abc.auth_service_abc import AuthServiceABC
from bot_core.exception.service_error_code_enum import ServiceErrorCode from bot_api.exception.service_error_code_enum import ServiceErrorCode
from bot_core.exception.service_exception import ServiceException from bot_api.exception.service_exception import ServiceException
from bot_api.filter.discord.server_select_criteria import ServerSelectCriteria 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_dto import ServerDTO
from bot_api.model.discord.server_filtered_result_dto import ServerFilteredResultDTO from bot_api.model.discord.server_filtered_result_dto import ServerFilteredResultDTO
@@ -53,13 +53,17 @@ class DiscordService:
if role != AuthRoleEnum.admin: if role != AuthRoleEnum.admin:
auth_user = self._auth_users.find_auth_user_by_email(token["email"]) auth_user = self._auth_users.find_auth_user_by_email(token["email"])
if auth_user is not None: if auth_user is not None:
user_ids = auth_user.users.select(lambda x: x.server is not None and x.server.id) user_ids = auth_user.users.select(
lambda x: x.server is not None and x.server.id
)
servers = servers.where(lambda x: x.id in user_ids) servers = servers.where(lambda x: x.id in user_ids)
servers = List(ServerDTO, servers) servers = List(ServerDTO, servers)
return servers.select(self._to_dto).where(lambda x: x.name != "") return servers.select(self._to_dto).where(lambda x: x.name != "")
async def get_filtered_servers_async(self, criteria: ServerSelectCriteria) -> ServerFilteredResultDTO: async def get_filtered_servers_async(
self, criteria: ServerSelectCriteria
) -> ServerFilteredResultDTO:
token = self._auth.get_decoded_token_from_request() token = self._auth.get_decoded_token_from_request()
if token is None or "email" not in token or "role" not in token: if token is None or "email" not in token or "role" not in token:
raise ServiceException(ServiceErrorCode.InvalidData, "Token invalid") raise ServiceException(ServiceErrorCode.InvalidData, "Token invalid")
@@ -70,15 +74,22 @@ class DiscordService:
if role != AuthRoleEnum.admin: if role != AuthRoleEnum.admin:
auth_user = self._auth_users.find_auth_user_by_email(token["email"]) auth_user = self._auth_users.find_auth_user_by_email(token["email"])
if auth_user is not None: if auth_user is not None:
user_ids = auth_user.users.select(lambda x: x.server is not None and x.server.id) user_ids = auth_user.users.select(
filtered_result.result = filtered_result.result.where(lambda x: x.id in user_ids) lambda x: x.server is not None and x.server.id
)
filtered_result.result = filtered_result.result.where(
lambda x: x.id in user_ids
)
servers: List = filtered_result.result.select(self._to_dto).where(lambda x: x.name != "") servers: List = filtered_result.result.select(self._to_dto).where(
lambda x: x.name != ""
)
result = List(ServerDTO, servers) result = List(ServerDTO, servers)
if criteria.name is not None and criteria.name != "": if criteria.name is not None and criteria.name != "":
result = result.where( result = result.where(
lambda x: criteria.name.lower() in x.name.lower() or x.name.lower() == criteria.name.lower() lambda x: criteria.name.lower() in x.name.lower()
or x.name.lower() == criteria.name.lower()
) )
return ServerFilteredResultDTO(List(ServerDTO, result), servers.count()) return ServerFilteredResultDTO(List(ServerDTO, result), servers.count())
@@ -87,5 +98,7 @@ class DiscordService:
server = self._servers.get_server_by_id(id) server = self._servers.get_server_by_id(id)
guild = self._bot.get_guild(server.discord_id) guild = self._bot.get_guild(server.discord_id)
server_dto = ServerTransformer.to_dto(server, guild.name, guild.member_count, guild.icon) server_dto = ServerTransformer.to_dto(
server, guild.name, guild.member_count, guild.icon
)
return server_dto return server_dto

View File

@@ -15,7 +15,7 @@ __title__ = "bot_api.transformer"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -10,7 +10,7 @@ from bot_api.model.user_dto import UserDTO
from bot_data.model.auth_role_enum import AuthRoleEnum from bot_data.model.auth_role_enum import AuthRoleEnum
from bot_data.model.auth_user import AuthUser from bot_data.model.auth_user import AuthUser
from bot_data.model.user import User from bot_data.model.user import User
from bot_core.abc.permission_service_abc import PermissionServiceABC from modules.permission.abc.permission_service_abc import PermissionServiceABC
class AuthUserTransformer(TransformerABC): class AuthUserTransformer(TransformerABC):
@@ -27,45 +27,37 @@ class AuthUserTransformer(TransformerABC):
None, None,
None, None,
datetime.now(), datetime.now(),
AuthRoleEnum.normal if dto.auth_role is None else AuthRoleEnum(dto.auth_role), 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, auth_user_id=0 if dto.id is None else dto.id,
) )
@staticmethod @staticmethod
@ServiceProviderABC.inject @ServiceProviderABC.inject
def _is_technician(user: User, bot: DiscordBotServiceABC, permissions: PermissionServiceABC): def _is_technician(
user: User, bot: DiscordBotServiceABC, permissions: PermissionServiceABC
):
guild = bot.get_guild(user.server.discord_id) guild = bot.get_guild(user.server.discord_id)
if guild is None:
return permissions.is_member_technician_by_id(user.discord_id)
member = guild.get_member(user.discord_id) member = guild.get_member(user.discord_id)
if member is None:
return permissions.is_member_technician_by_id(user.discord_id)
return permissions.is_member_technician(member) return permissions.is_member_technician(member)
@staticmethod @staticmethod
@ServiceProviderABC.inject @ServiceProviderABC.inject
def _is_admin(user: User, bot: DiscordBotServiceABC, permissions: PermissionServiceABC): def _is_admin(
user: User, bot: DiscordBotServiceABC, permissions: PermissionServiceABC
):
guild = bot.get_guild(user.server.discord_id) guild = bot.get_guild(user.server.discord_id)
if guild is None:
return False
member = guild.get_member(user.discord_id) member = guild.get_member(user.discord_id)
if member is None:
return False
return permissions.is_member_admin(member) return permissions.is_member_admin(member)
@staticmethod @staticmethod
@ServiceProviderABC.inject @ServiceProviderABC.inject
def _is_moderator(user: User, bot: DiscordBotServiceABC, permissions: PermissionServiceABC): def _is_moderator(
user: User, bot: DiscordBotServiceABC, permissions: PermissionServiceABC
):
guild = bot.get_guild(user.server.discord_id) guild = bot.get_guild(user.server.discord_id)
if guild is None:
return False
member = guild.get_member(user.discord_id) member = guild.get_member(user.discord_id)
if member is None:
return False
return permissions.is_member_moderator(member) return permissions.is_member_moderator(member)
@classmethod @classmethod

View File

@@ -13,7 +13,9 @@ class ServerTransformer(TransformerABC):
return Server(dto.discord_id) return Server(dto.discord_id)
@staticmethod @staticmethod
def to_dto(db: Server, name: str, member_count: int, icon_url: Optional[discord.Asset]) -> ServerDTO: def to_dto(
db: Server, name: str, member_count: int, icon_url: Optional[discord.Asset]
) -> ServerDTO:
return ServerDTO( return ServerDTO(
db.id, db.id,
db.discord_id, db.discord_id,

View File

@@ -15,7 +15,7 @@ __title__ = "bot_core"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports # imports
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -15,7 +15,7 @@ __title__ = "bot_core.abc"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -45,17 +45,9 @@ class ClientUtilsABC(ABC):
pass pass
@abstractmethod @abstractmethod
def get_auto_complete_list(self, _l: List, current: str, select: Callable = None) -> List: def get_auto_complete_list(
pass self, _l: List, current: str, select: Callable = None
) -> List:
@abstractmethod
def update_user_message_xp_count_by_hour(
self,
created_at: datetime,
user: User,
settings: ServerConfig,
is_reaction: bool = False,
):
pass pass
@abstractmethod @abstractmethod
@@ -85,7 +77,3 @@ class ClientUtilsABC(ABC):
@abstractmethod @abstractmethod
async def check_default_role(self, member: Union[discord.User, discord.Member]): async def check_default_role(self, member: Union[discord.User, discord.Member]):
pass pass
@abstractmethod
async def set_maintenance_mode(self, state: bool):
pass

View File

@@ -18,7 +18,9 @@ class CustomFileLoggerABC(Logger, ABC):
env: ApplicationEnvironmentABC, env: ApplicationEnvironmentABC,
): ):
self._key = key self._key = key
self._settings: LoggingSettings = config.get_configuration(f"{FileLoggingSettings.__name__}_{key}") self._settings: LoggingSettings = config.get_configuration(
f"{FileLoggingSettings.__name__}_{key}"
)
Logger.__init__(self, self._settings, time_format, env) Logger.__init__(self, self._settings, time_format, env)
self._begin_log() self._begin_log()
@@ -32,7 +34,9 @@ class CustomFileLoggerABC(Logger, ABC):
self.info(__name__, f"Starting...") self.info(__name__, f"Starting...")
self._console = LoggingLevelEnum(console_level) self._console = LoggingLevelEnum(console_level)
def _get_string(self, name_list_as_str: str, level: LoggingLevelEnum, message: str) -> str: def _get_string(
self, name_list_as_str: str, level: LoggingLevelEnum, message: str
) -> str:
names = name_list_as_str.split(" ") names = name_list_as_str.split(" ")
log_level = level.name log_level = level.name
string = f"<{self._get_datetime_now()}> [ {log_level} ]" string = f"<{self._get_datetime_now()}> [ {log_level} ]"

View File

@@ -13,7 +13,9 @@ class MessageServiceABC(ABC):
pass pass
@abstractmethod @abstractmethod
async def delete_messages(self, messages: List[discord.Message], guild_id: int, without_tracking=False): async def delete_messages(
self, messages: List[discord.Message], guild_id: int, without_tracking=False
):
pass pass
@abstractmethod @abstractmethod

View File

@@ -6,7 +6,6 @@ from cpl_core.dependency_injection import ServiceProviderABC
from cpl_discord.service import DiscordBotServiceABC from cpl_discord.service import DiscordBotServiceABC
from discord.ext import commands from discord.ext import commands
from bot_core.environment_variables import MAINTENANCE
from bot_core.logging.task_logger import TaskLogger from bot_core.logging.task_logger import TaskLogger
@@ -16,17 +15,15 @@ class TaskABC(commands.Cog):
commands.Cog.__init__(self) commands.Cog.__init__(self)
@ServiceProviderABC.inject @ServiceProviderABC.inject
def _is_maintenance(self, config: ConfigurationABC) -> bool: async def _wait_until_ready(
return config.get_configuration(MAINTENANCE) is True self, config: ConfigurationABC, logger: TaskLogger, bot: DiscordBotServiceABC
):
@ServiceProviderABC.inject logger.debug(__name__, f"Waiting before {type(self).__name__}")
async def _wait_until_ready(self, config: ConfigurationABC, logger: TaskLogger, bot: DiscordBotServiceABC):
logger.debug(__name__, f"Waiting before ready {type(self).__name__}")
await bot.wait_until_ready() await bot.wait_until_ready()
async def wait(): async def wait():
is_ready = config.get_configuration("IS_READY") is True is_ready = config.get_configuration("IS_READY")
if not is_ready: if is_ready != "true":
await asyncio.sleep(1) await asyncio.sleep(1)
await wait() await wait()

View File

@@ -4,7 +4,7 @@
"Version": { "Version": {
"Major": "1", "Major": "1",
"Minor": "2", "Minor": "2",
"Micro": "11" "Micro": "0"
}, },
"Author": "Sven Heidemann", "Author": "Sven Heidemann",
"AuthorEmail": "sven.heidemann@sh-edraft.de", "AuthorEmail": "sven.heidemann@sh-edraft.de",

View File

@@ -15,7 +15,7 @@ __title__ = "bot_core.configuration"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -15,9 +15,9 @@ class FeatureFlagsEnum(Enum):
database_module = "DatabaseModule" database_module = "DatabaseModule"
level_module = "LevelModule" level_module = "LevelModule"
moderator_module = "ModeratorModule" moderator_module = "ModeratorModule"
permission_module = "PermissionModule"
short_role_name_module = "ShortRoleNameModule" short_role_name_module = "ShortRoleNameModule"
steam_special_offers_module = "SteamSpecialOffersModule" steam_special_offers_module = "SteamSpecialOffersModule"
realm_module = "RealmModule"
# features # features
api_only = "ApiOnly" api_only = "ApiOnly"
presence = "Presence" presence = "Presence"
@@ -27,6 +27,3 @@ class FeatureFlagsEnum(Enum):
short_role_name = "ShortRoleName" short_role_name = "ShortRoleName"
technician_full_access = "TechnicianFullAccess" technician_full_access = "TechnicianFullAccess"
steam_special_offers = "SteamSpecialOffers" steam_special_offers = "SteamSpecialOffers"
scheduled_events = "ScheduledEvents"
basic_registration = "BasicRegistration"
basic_login = "BasicLogin"

View File

@@ -16,10 +16,10 @@ class FeatureFlagsSettings(ConfigurationModelABC):
FeatureFlagsEnum.data_module.value: True, # 03.10.2022 #56 FeatureFlagsEnum.data_module.value: True, # 03.10.2022 #56
FeatureFlagsEnum.database_module.value: True, # 02.10.2022 #48 FeatureFlagsEnum.database_module.value: True, # 02.10.2022 #48
FeatureFlagsEnum.moderator_module.value: False, # 02.10.2022 #48 FeatureFlagsEnum.moderator_module.value: False, # 02.10.2022 #48
FeatureFlagsEnum.permission_module.value: True, # 02.10.2022 #48
FeatureFlagsEnum.config_module.value: True, # 19.07.2023 #127 FeatureFlagsEnum.config_module.value: True, # 19.07.2023 #127
FeatureFlagsEnum.short_role_name_module.value: True, # 28.09.2023 #378 FeatureFlagsEnum.short_role_name_module.value: True, # 28.09.2023 #378
FeatureFlagsEnum.steam_special_offers_module.value: True, # 11.10.2023 #188 FeatureFlagsEnum.steam_special_offers_module.value: True, # 11.10.2023 #188
FeatureFlagsEnum.realm_module.value: True, # 07.01.2025 #481
# features # features
FeatureFlagsEnum.api_only.value: False, # 13.10.2022 #70 FeatureFlagsEnum.api_only.value: False, # 13.10.2022 #70
FeatureFlagsEnum.presence.value: True, # 03.10.2022 #56 FeatureFlagsEnum.presence.value: True, # 03.10.2022 #56
@@ -29,9 +29,6 @@ class FeatureFlagsSettings(ConfigurationModelABC):
FeatureFlagsEnum.short_role_name.value: False, # 28.09.2023 #378 FeatureFlagsEnum.short_role_name.value: False, # 28.09.2023 #378
FeatureFlagsEnum.technician_full_access.value: False, # 03.10.2023 #393 FeatureFlagsEnum.technician_full_access.value: False, # 03.10.2023 #393
FeatureFlagsEnum.steam_special_offers.value: False, # 11.10.2023 #188 FeatureFlagsEnum.steam_special_offers.value: False, # 11.10.2023 #188
FeatureFlagsEnum.scheduled_events.value: False, # 14.11.2023 #410
FeatureFlagsEnum.basic_registration.value: False, # 19.11.2023 #440
FeatureFlagsEnum.basic_login.value: False, # 19.11.2023 #440
} }
def __init__(self, **kwargs: dict): def __init__(self, **kwargs: dict):

View File

@@ -10,7 +10,9 @@ class FileLoggingSettings(LoggingSettings):
console_log_level: LoggingLevelEnum = None, console_log_level: LoggingLevelEnum = None,
file_log_level: LoggingLevelEnum = None, file_log_level: LoggingLevelEnum = None,
): ):
LoggingSettings.__init__(self, path, filename, console_log_level, file_log_level) LoggingSettings.__init__(
self, path, filename, console_log_level, file_log_level
)
self._key = key self._key = key

View File

@@ -15,7 +15,7 @@ __title__ = "bot_core.core_extension"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -9,7 +9,7 @@ from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
from bot_core.helper.command_checks import CommandChecks from bot_core.helper.command_checks import CommandChecks
from bot_core.helper.event_checks import EventChecks from bot_core.helper.event_checks import EventChecks
from bot_core.abc.permission_service_abc import PermissionServiceABC from modules.permission.abc.permission_service_abc import PermissionServiceABC
class CoreExtension(ApplicationExtensionABC): class CoreExtension(ApplicationExtensionABC):
@@ -17,7 +17,9 @@ class CoreExtension(ApplicationExtensionABC):
ApplicationExtensionABC.__init__(self) ApplicationExtensionABC.__init__(self)
async def run(self, config: ConfigurationABC, services: ServiceProviderABC): async def run(self, config: ConfigurationABC, services: ServiceProviderABC):
feature_flags: FeatureFlagsSettings = config.get_configuration(FeatureFlagsSettings) feature_flags: FeatureFlagsSettings = config.get_configuration(
FeatureFlagsSettings
)
if not feature_flags.get_flag(FeatureFlagsEnum.core_module): if not feature_flags.get_flag(FeatureFlagsEnum.core_module):
return return

View File

@@ -15,8 +15,14 @@ class CoreExtensionModule(ModuleABC):
def __init__(self, dc: DiscordCollectionABC): def __init__(self, dc: DiscordCollectionABC):
ModuleABC.__init__(self, dc, FeatureFlagsEnum.core_extension_module) ModuleABC.__init__(self, dc, FeatureFlagsEnum.core_extension_module)
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC): def configure_configuration(
self, config: ConfigurationABC, env: ApplicationEnvironmentABC
):
pass pass
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): def configure_services(
services.add_transient(DiscordEventTypesEnum.on_ready.value, CoreExtensionOnReadyEvent) self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC
):
services.add_transient(
DiscordEventTypesEnum.on_ready.value, CoreExtensionOnReadyEvent
)

View File

@@ -1,25 +1,23 @@
from cpl_core.configuration import ConfigurationABC import asyncio
from cpl_core.logging import LoggerABC
from cpl_discord.events import OnReadyABC from cpl_discord.events import OnReadyABC
from cpl_discord.service import DiscordBotServiceABC from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe from cpl_translation import TranslatePipe
from bot_core.abc.client_utils_abc import ClientUtilsABC from bot_core.abc.client_utils_abc import ClientUtilsABC
from bot_core.environment_variables import MAINTENANCE
from bot_core.logging.event_logger import EventLogger
class CoreExtensionOnReadyEvent(OnReadyABC): class CoreExtensionOnReadyEvent(OnReadyABC):
def __init__( def __init__(
self, self,
config: ConfigurationABC, logger: LoggerABC,
logger: EventLogger,
bot: DiscordBotServiceABC, bot: DiscordBotServiceABC,
client_utils: ClientUtilsABC, client_utils: ClientUtilsABC,
t: TranslatePipe, t: TranslatePipe,
): ):
OnReadyABC.__init__(self) OnReadyABC.__init__(self)
self._config = config
self._logger = logger self._logger = logger
self._bot = bot self._bot = bot
self._client_utils = client_utils self._client_utils = client_utils
@@ -29,5 +27,5 @@ class CoreExtensionOnReadyEvent(OnReadyABC):
async def on_ready(self): async def on_ready(self):
self._logger.debug(__name__, f"Module {type(self)} started") self._logger.debug(__name__, f"Module {type(self)} started")
await self._client_utils.set_maintenance_mode(self._config.get_configuration(MAINTENANCE)) await self._client_utils.presence_game("common.presence.running")
self._logger.trace(__name__, f"Module {type(self)} stopped") self._logger.trace(__name__, f"Module {type(self)} stopped")

View File

@@ -7,7 +7,6 @@ from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
from bot_core.abc.client_utils_abc import ClientUtilsABC from bot_core.abc.client_utils_abc import ClientUtilsABC
from bot_core.abc.message_service_abc import MessageServiceABC from bot_core.abc.message_service_abc import MessageServiceABC
from bot_core.abc.module_abc import ModuleABC from bot_core.abc.module_abc import ModuleABC
from bot_core.abc.permission_service_abc import PermissionServiceABC
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from bot_core.events.core_on_ready_event import CoreOnReadyEvent from bot_core.events.core_on_ready_event import CoreOnReadyEvent
from bot_core.pipes.date_time_offset_pipe import DateTimeOffsetPipe from bot_core.pipes.date_time_offset_pipe import DateTimeOffsetPipe
@@ -15,22 +14,24 @@ from bot_core.service.client_utils_service import ClientUtilsService
from bot_core.service.config_service import ConfigService from bot_core.service.config_service import ConfigService
from bot_core.service.data_integrity_service import DataIntegrityService from bot_core.service.data_integrity_service import DataIntegrityService
from bot_core.service.message_service import MessageService from bot_core.service.message_service import MessageService
from bot_core.service.permission_service_with_cache import PermissionServiceWithCache
class CoreModule(ModuleABC): class CoreModule(ModuleABC):
def __init__(self, dc: DiscordCollectionABC): def __init__(self, dc: DiscordCollectionABC):
ModuleABC.__init__(self, dc, FeatureFlagsEnum.core_module) ModuleABC.__init__(self, dc, FeatureFlagsEnum.core_module)
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC): def configure_configuration(
self, config: ConfigurationABC, env: ApplicationEnvironmentABC
):
pass pass
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): def configure_services(
self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC
):
services.add_transient(ConfigService) services.add_transient(ConfigService)
services.add_transient(MessageServiceABC, MessageService) services.add_transient(MessageServiceABC, MessageService)
services.add_transient(ClientUtilsABC, ClientUtilsService) services.add_transient(ClientUtilsABC, ClientUtilsService)
services.add_transient(DataIntegrityService) services.add_transient(DataIntegrityService)
services.add_singleton(PermissionServiceABC, PermissionServiceWithCache)
# pipes # pipes
services.add_transient(DateTimeOffsetPipe) services.add_transient(DateTimeOffsetPipe)

View File

@@ -1,2 +0,0 @@
MIGRATION_ONLY = "MIGRATION_ONLY"
MAINTENANCE = "MAINTENANCE"

View File

@@ -15,7 +15,7 @@ __title__ = "bot_core.events"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -1,15 +1,15 @@
from cpl_core.logging import LoggerABC
from cpl_discord.events import OnReadyABC from cpl_discord.events import OnReadyABC
from cpl_discord.service import DiscordBotServiceABC from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe from cpl_translation import TranslatePipe
from bot_core.abc.client_utils_abc import ClientUtilsABC from bot_core.abc.client_utils_abc import ClientUtilsABC
from bot_core.logging.event_logger import EventLogger
class CoreOnReadyEvent(OnReadyABC): class CoreOnReadyEvent(OnReadyABC):
def __init__( def __init__(
self, self,
logger: EventLogger, logger: LoggerABC,
bot: DiscordBotServiceABC, bot: DiscordBotServiceABC,
client_utils: ClientUtilsABC, client_utils: ClientUtilsABC,
t: TranslatePipe, t: TranslatePipe,

View File

@@ -15,7 +15,7 @@ __title__ = "bot_core.exception"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -15,7 +15,7 @@ __title__ = "bot_core.helper"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -7,7 +7,7 @@ from discord.ext.commands import Context
from bot_core.abc.client_utils_abc import ClientUtilsABC from bot_core.abc.client_utils_abc import ClientUtilsABC
from bot_core.abc.message_service_abc import MessageServiceABC from bot_core.abc.message_service_abc import MessageServiceABC
from bot_core.exception.check_error import CheckError from bot_core.exception.check_error import CheckError
from bot_core.abc.permission_service_abc import PermissionServiceABC from modules.permission.abc.permission_service_abc import PermissionServiceABC
class CommandChecks: class CommandChecks:
@@ -44,7 +44,9 @@ class CommandChecks:
async def check_is_member_admin(ctx: Context): async def check_is_member_admin(ctx: Context):
has_permission = cls._permissions.is_member_admin(ctx.author) has_permission = cls._permissions.is_member_admin(ctx.author)
if not has_permission: if not has_permission:
await cls._message_service.send_ctx_msg(ctx, cls._t.transform("common.no_permission_message")) await cls._message_service.send_ctx_msg(
ctx, cls._t.transform("common.no_permission_message")
)
raise CheckError(f"Member {ctx.author.name} is not admin") raise CheckError(f"Member {ctx.author.name} is not admin")
return has_permission return has_permission
@@ -56,7 +58,9 @@ class CommandChecks:
async def check_is_member_technician(ctx: Context): async def check_is_member_technician(ctx: Context):
has_permission = cls._permissions.is_member_technician(ctx.author) has_permission = cls._permissions.is_member_technician(ctx.author)
if not has_permission: if not has_permission:
await cls._message_service.send_ctx_msg(ctx, cls._t.transform("common.no_permission_message")) await cls._message_service.send_ctx_msg(
ctx, cls._t.transform("common.no_permission_message")
)
raise CheckError(f"Member {ctx.author.name} is not technician") raise CheckError(f"Member {ctx.author.name} is not technician")
return has_permission return has_permission
@@ -68,7 +72,9 @@ class CommandChecks:
async def check_is_member_moderator(ctx: Context): async def check_is_member_moderator(ctx: Context):
has_permission = cls._permissions.is_member_moderator(ctx.author) has_permission = cls._permissions.is_member_moderator(ctx.author)
if not has_permission: if not has_permission:
await cls._message_service.send_ctx_msg(ctx, cls._t.transform("common.no_permission_message")) await cls._message_service.send_ctx_msg(
ctx, cls._t.transform("common.no_permission_message")
)
raise CheckError(f"Member {ctx.author.name} is not moderator") raise CheckError(f"Member {ctx.author.name} is not moderator")
return has_permission return has_permission

View File

@@ -1,4 +1,3 @@
import inspect
from typing import Optional from typing import Optional
from discord.ext import commands from discord.ext import commands
@@ -18,18 +17,11 @@ class EventChecks:
cls._client_utils = client_utils cls._client_utils = client_utils
@classmethod @classmethod
def check_is_ready(cls, func): def check_is_ready(cls):
async def check_if_bot_is_ready(*args, **kwargs): async def check_if_bot_is_ready() -> bool:
result = await cls._client_utils.check_if_bot_is_ready_yet() result = await cls._client_utils.check_if_bot_is_ready_yet()
if not result: if not result:
raise CheckError(f"Bot is not ready")
return result
def empty(*args, **kwargs): return commands.check(check_if_bot_is_ready)
return
return empty
return await func(*args, **kwargs)
check_if_bot_is_ready.__name__ = func.__name__
sig = inspect.signature(func)
check_if_bot_is_ready.__signature__ = sig.replace(parameters=tuple(sig.parameters.values())[1:])
return check_if_bot_is_ready

View File

@@ -15,7 +15,7 @@ __title__ = "bot_core.logging"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports # imports
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -1,15 +0,0 @@
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 EventLogger(CustomFileLoggerABC):
def __init__(
self,
config: ConfigurationABC,
time_format: TimeFormatSettings,
env: ApplicationEnvironmentABC,
):
CustomFileLoggerABC.__init__(self, "Event", config, time_format, env)

View File

@@ -15,7 +15,7 @@ __title__ = "bot_core.pipes"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports # imports
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -15,7 +15,7 @@ __title__ = "bot_core.service"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -16,7 +16,6 @@ from bot_core.abc.client_utils_abc import ClientUtilsABC
from bot_core.abc.message_service_abc import MessageServiceABC from bot_core.abc.message_service_abc import MessageServiceABC
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
from bot_core.environment_variables import MAINTENANCE
from bot_data.abc.client_repository_abc import ClientRepositoryABC from bot_data.abc.client_repository_abc import ClientRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.abc.user_joined_voice_channel_repository_abc import ( from bot_data.abc.user_joined_voice_channel_repository_abc import (
@@ -63,39 +62,40 @@ class ClientUtilsService(ClientUtilsABC):
def received_command(self, guild_id: int): def received_command(self, guild_id: int):
server = self._servers.get_server_by_discord_id(guild_id) server = self._servers.get_server_by_discord_id(guild_id)
client = self._clients.find_client_by_discord_id_and_server_id(self._bot.user.id, server.id) client = self._clients.find_client_by_discord_id_and_server_id(
self._bot.user.id, server.id
)
client.received_command_count += 1 client.received_command_count += 1
self._clients.update_client(client) self._clients.update_client(client)
self._db.save_changes() self._db.save_changes()
def moved_user(self, guild_id: int): def moved_user(self, guild_id: int):
server = self._servers.get_server_by_discord_id(guild_id) server = self._servers.get_server_by_discord_id(guild_id)
client = self._clients.find_client_by_discord_id_and_server_id(self._bot.user.id, server.id) client = self._clients.find_client_by_discord_id_and_server_id(
self._bot.user.id, server.id
)
client.moved_users_count += 1 client.moved_users_count += 1
self._clients.update_client(client) self._clients.update_client(client)
self._db.save_changes() self._db.save_changes()
def moved_users(self, guild_id: int, count: int): def moved_users(self, guild_id: int, count: int):
server = self._servers.get_server_by_discord_id(guild_id) server = self._servers.get_server_by_discord_id(guild_id)
client = self._clients.find_client_by_discord_id_and_server_id(self._bot.user.id, server.id) client = self._clients.find_client_by_discord_id_and_server_id(
self._bot.user.id, server.id
)
client.moved_users_count += count client.moved_users_count += count
self._clients.update_client(client) self._clients.update_client(client)
self._db.save_changes() self._db.save_changes()
def get_client(self, dc_ic: int, guild_id: int): def get_client(self, dc_ic: int, guild_id: int):
server = self._servers.get_server_by_discord_id(guild_id) server = self._servers.get_server_by_discord_id(guild_id)
client = self._clients.find_client_by_discord_id_and_server_id(self._bot.user.id, server.id) client = self._clients.find_client_by_discord_id_and_server_id(
self._bot.user.id, server.id
)
return client return client
async def check_if_bot_is_ready_yet(self) -> bool: async def check_if_bot_is_ready_yet(self) -> bool:
if self._config.get_configuration(MAINTENANCE): if self._config.get_configuration("IS_READY") == "true":
self._logger.warn(
__name__,
f"Bot is in maintenance mode",
)
return False
if self._config.get_configuration("IS_READY") is True:
return True return True
self._logger.debug( self._logger.debug(
@@ -129,7 +129,9 @@ class ClientUtilsService(ClientUtilsABC):
await self._bot.change_presence(activity=discord.Game(name=name)) await self._bot.change_presence(activity=discord.Game(name=name))
self._logger.info(__name__, f"Set presence {name}") self._logger.info(__name__, f"Set presence {name}")
def get_auto_complete_list(self, _l: List, current: str, select: Callable = None) -> List: def get_auto_complete_list(
self, _l: List, current: str, select: Callable = None
) -> List:
if current != "": if current != "":
if select is None: if select is None:
select = lambda x: x select = lambda x: x
@@ -143,15 +145,18 @@ class ClientUtilsService(ClientUtilsABC):
return _l.take(25) return _l.take(25)
def update_user_message_xp_count_by_hour( def is_message_xp_count_by_hour_higher_that_max_message_count_per_hour(
self, self,
created_at: datetime, created_at: datetime,
user: User, user: User,
settings: ServerConfig, settings: ServerConfig,
is_reaction: bool = False, is_reaction: bool = False,
): ) -> bool:
umcph = None
try: try:
umcph = self._umcphs.find_user_message_count_per_hour_by_user_id_and_date(user.id, created_at) umcph = self._umcphs.find_user_message_count_per_hour_by_user_id_and_date(
user.id, created_at
)
if umcph is None: if umcph is None:
self._umcphs.add_user_message_count_per_hour( self._umcphs.add_user_message_count_per_hour(
UserMessageCountPerHour( UserMessageCountPerHour(
@@ -161,50 +166,50 @@ class ClientUtilsService(ClientUtilsABC):
user, user,
) )
) )
self._db.save_changes()
umcph = self._umcphs.get_user_message_count_per_hour_by_user_id_and_date(user.id, created_at)
umcph.xp_count += settings.xp_per_reaction if is_reaction else settings.xp_per_message self._db.save_changes()
umcph = (
self._umcphs.get_user_message_count_per_hour_by_user_id_and_date(
user.id, created_at
)
)
except Exception as e:
self._logger.error(
__name__,
f"Cannot add user message count per hour with id {umcph.id}",
e,
)
return False
try:
if is_reaction:
umcph.xp_count += settings.xp_per_reaction
else:
umcph.xp_count += settings.xp_per_message
self._umcphs.update_user_message_count_per_hour(umcph) self._umcphs.update_user_message_count_per_hour(umcph)
self._db.save_changes() self._db.save_changes()
except Exception as e: except Exception as e:
self._logger.error( self._logger.error(
__name__, __name__,
f"Cannot update user message count per hour {created_at}", f"Cannot update user message count per hour with id {umcph.id}",
e, e,
) )
return False return False
def is_message_xp_count_by_hour_higher_that_max_message_count_per_hour( if umcph.xp_count is None:
self,
created_at: datetime,
user: User,
settings: ServerConfig,
is_reaction: bool = False,
) -> bool:
try:
umcph = self._umcphs.find_user_message_count_per_hour_by_user_id_and_date(user.id, created_at)
if umcph is None or umcph.xp_count is None:
return False return False
return umcph.xp_count > settings.max_message_xp_per_hour return umcph.xp_count > settings.max_message_xp_per_hour
except Exception as e:
self._logger.error(
__name__,
f"Cannot add user message count per hour with",
e,
)
return False
def get_ontime_for_user(self, user: User) -> float: def get_ontime_for_user(self, user: User) -> float:
return round( return round(
sum( self._user_joined_voice_channel.get_user_joined_voice_channels_by_user_id(
[ user.id
(join.leaved_on - join.joined_on).total_seconds() / 3600 )
for join in self._user_joined_voice_channel.get_user_joined_voice_channels_by_user_id(user.id) .where(lambda x: x.leaved_on is not None and x.joined_on is not None)
if join.leaved_on is not None and join.joined_on is not None .sum(lambda join: (join.leaved_on - join.joined_on).total_seconds() / 3600),
]
),
2, 2,
) )
@@ -219,7 +224,11 @@ class ClientUtilsService(ClientUtilsABC):
guild: Guild = self._bot.guilds.where(lambda g: g == guild).single() guild: Guild = self._bot.guilds.where(lambda g: g == guild).single()
channel = guild.get_channel(discord_channel_id) channel = guild.get_channel(discord_channel_id)
message = await channel.fetch_message(discord_message_id) message = await channel.fetch_message(discord_message_id)
emoji = List(discord.Emoji, [x for x in guild.emojis if x.name == rule.emoji_name]).single() emoji = (
List(discord.Emoji, guild.emojis)
.where(lambda x: x.name == rule.emoji_name)
.single()
)
if emoji is None: if emoji is None:
self._logger.debug(__name__, f"Emoji {rule.emoji_name} not found") self._logger.debug(__name__, f"Emoji {rule.emoji_name} not found")
@@ -239,7 +248,9 @@ class ClientUtilsService(ClientUtilsABC):
async def check_default_role(self, member: Union[discord.User, discord.Member]): async def check_default_role(self, member: Union[discord.User, discord.Member]):
try: try:
server = self._servers.get_server_by_discord_id(member.guild.id) server = self._servers.get_server_by_discord_id(member.guild.id)
settings: ServerConfig = self._config.get_configuration(f"ServerConfig_{server.discord_id}") settings: ServerConfig = self._config.get_configuration(
f"ServerConfig_{server.discord_id}"
)
if settings.default_role_id is None: if settings.default_role_id is None:
return return
@@ -251,11 +262,6 @@ class ClientUtilsService(ClientUtilsABC):
await member.add_roles(default_role) await member.add_roles(default_role)
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Cannot check for default role for member {member.id}", e) self._logger.error(
__name__, f"Cannot check for default role for member {member.id}", e
async def set_maintenance_mode(self, state: bool): )
self._config.add_configuration(MAINTENANCE, state)
if state:
await self.presence_game("common.presence.maintenance")
else:
await self.presence_game("common.presence.running")

View File

@@ -7,7 +7,6 @@ from bot_data.abc.technician_config_repository_abc import TechnicianConfigReposi
from bot_data.model.server import Server from bot_data.model.server import Server
from bot_data.model.technician_config import TechnicianConfig from bot_data.model.technician_config import TechnicianConfig
from bot_data.service.server_config_seeder import ServerConfigSeeder from bot_data.service.server_config_seeder import ServerConfigSeeder
from bot_data.service.technician_config_seeder import TechnicianConfigSeeder
class ConfigService: class ConfigService:
@@ -17,24 +16,17 @@ class ConfigService:
services: ServiceProviderABC, services: ServiceProviderABC,
technician_config_repo: TechnicianConfigRepositoryABC, technician_config_repo: TechnicianConfigRepositoryABC,
server_config_repo: ServerConfigRepositoryABC, server_config_repo: ServerConfigRepositoryABC,
technician_seeder: TechnicianConfigSeeder,
server_seeder: ServerConfigSeeder, server_seeder: ServerConfigSeeder,
): ):
self._config = config self._config = config
self._services = services self._services = services
self._technician_config_repo = technician_config_repo self._technician_config_repo = technician_config_repo
self._technician_seeder = technician_seeder
self._server_config_repo = server_config_repo self._server_config_repo = server_config_repo
self._server_seeder = server_seeder self._server_seeder = server_seeder
async def reload_technician_config(self): def reload_technician_config(self):
try:
technician_config = self._technician_config_repo.get_technician_config() technician_config = self._technician_config_repo.get_technician_config()
except Exception as e:
await self._technician_seeder.seed()
technician_config = self._technician_config_repo.get_technician_config()
self._config.add_configuration(TechnicianConfig, technician_config) self._config.add_configuration(TechnicianConfig, technician_config)
self._config.add_configuration( self._config.add_configuration(
FeatureFlagsSettings, FeatureFlagsSettings,

View File

@@ -1,8 +1,9 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Union
import discord
from cpl_core.configuration import ConfigurationABC from cpl_core.configuration import ConfigurationABC
from cpl_core.database.context import DatabaseContextABC from cpl_core.database.context import DatabaseContextABC
from cpl_discord.container import Member, Guild
from cpl_discord.service import DiscordBotServiceABC from cpl_discord.service import DiscordBotServiceABC
from bot_core.abc.client_utils_abc import ClientUtilsABC from bot_core.abc.client_utils_abc import ClientUtilsABC
@@ -27,7 +28,6 @@ from bot_data.model.user_joined_server import UserJoinedServer
from bot_data.model.user_joined_voice_channel import UserJoinedVoiceChannel from bot_data.model.user_joined_voice_channel import UserJoinedVoiceChannel
from bot_data.service.user_repository_service import ServerRepositoryABC from bot_data.service.user_repository_service import ServerRepositoryABC
from modules.achievements.achievement_service import AchievementService from modules.achievements.achievement_service import AchievementService
from modules.realms.realm_utils import RealmUtils
class DataIntegrityService: class DataIntegrityService:
@@ -47,7 +47,6 @@ class DataIntegrityService:
achievement_service: AchievementService, achievement_service: AchievementService,
client_utils: ClientUtilsABC, client_utils: ClientUtilsABC,
dtp: DateTimeOffsetPipe, dtp: DateTimeOffsetPipe,
ream_utils: RealmUtils,
): ):
self._config = config self._config = config
@@ -64,96 +63,98 @@ class DataIntegrityService:
self._achievements = achievement_service self._achievements = achievement_service
self._client_utils = client_utils self._client_utils = client_utils
self._dtp = dtp self._dtp = dtp
self._realm_utils = ream_utils
self._is_for_shutdown = False self._is_for_shutdown = False
async def check_data_integrity(self, is_for_shutdown=False): def _check_known_users(self):
self._logger.info(__name__, f"Data integrity service started") self._logger.debug(
if is_for_shutdown != self._is_for_shutdown: __name__, f"Start checking KnownUsers table, {len(self._bot.users)}"
self._is_for_shutdown = is_for_shutdown )
for u in self._bot.users:
u: discord.User = u
try: try:
for g in self._bot.guilds: if u.bot:
self._logger.debug(__name__, f"Start check for server: {g.id}") self._logger.trace(
s = self._get_or_create_server(g) __name__, f"User {u.id} is ignored, because its a bot"
self._logger.debug(__name__, f"Start check for clients") )
self._check_clients(g.id, s)
for m in [m for m in g.members if not m.bot]:
await self._check_default_role(m)
self._check_known_user(m.id)
self._logger.debug(__name__, f"Start check for member: {g.id}@{m.id}")
u = self._get_or_create_user(s, m.id)
self._logger.debug(__name__, f"Start check for user joined server: {g.id}@{m.id}")
self._check_user_join(g, m, u)
self._logger.debug(__name__, f"Start check for user joined voice channels: {g.id}@{m.id}")
self._check_user_joined_vc(g.id, m, u)
self._logger.debug(__name__, f"Start check for user joined game servers: {g.id}@{m.id}")
self._check_user_joined_gs(g.id, m.id, u)
self._logger.debug(__name__, f"Start check for user got achievements: {g.id}@{m.id}")
await self._check_for_user_achievements(u)
self._logger.debug(__name__, f"Start check for user got achievements: {g.id}@{m.id}")
await self._realm_utils.check_integrity(s, g)
for m in [m for m in g.members if m.bot]:
u = self._users.find_user_by_discord_id_and_server_id(m.id, s.id)
if u is None:
continue continue
self._remove_bot(u) user = self._known_users.find_user_by_discord_id(u.id)
self._logger.info(__name__, f"Data integrity service finished") if user is not None:
except Exception as e: continue
self._logger.fatal(__name__, f"Checking data integrity failed", e)
def _get_or_create_server(self, guild: Guild) -> Server: self._logger.warn(__name__, f"Unknown user: {u.id}")
try: self._logger.debug(__name__, f"Add user: {u.id}")
server = self._servers.find_server_by_discord_id(guild.id) self._known_users.add_user(KnownUser(u.id))
if server is not None:
return server
self._logger.warn(__name__, f"Server not found in database: {guild.id}")
self._logger.debug(__name__, f"Add server: {guild.id}")
self._servers.add_server(Server(guild.id))
self._db_context.save_changes() self._db_context.save_changes()
server = self._servers.find_server_by_discord_id(guild.id) user = self._known_users.find_user_by_discord_id(u.id)
if server is None: if user is None:
self._logger.fatal(__name__, f"Cannot add server: {guild.id}") self._logger.fatal(__name__, f"Cannot add user: {u.id}")
self._logger.trace(__name__, f"Added server: {guild.id}") self._logger.debug(__name__, f"Added user: {u.id}")
return server except Exception as e:
self._logger.error(__name__, f"Cannot get user", e)
def _check_servers(self):
self._logger.debug(__name__, f"Start checking Servers table")
for g in self._bot.guilds:
g: discord.Guild = g
try:
server = self._servers.find_server_by_discord_id(g.id)
if server is not None:
continue
self._logger.warn(__name__, f"Server not found in database: {g.id}")
self._logger.debug(__name__, f"Add server: {g.id}")
self._servers.add_server(Server(g.id))
self._db_context.save_changes()
server = self._servers.find_server_by_discord_id(g.id)
if server is None:
self._logger.fatal(__name__, f"Cannot add server: {g.id}")
self._logger.debug(__name__, f"Added server: {g.id}")
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Cannot get server", e) self._logger.error(__name__, f"Cannot get server", e)
def _check_clients(self, guild_id: int, server: Server): results = self._servers.get_servers()
if results is None or len(results) == 0:
self._logger.error(__name__, f"Table Servers is empty!")
def _check_clients(self):
self._logger.debug(__name__, f"Start checking Clients table")
for g in self._bot.guilds:
g: discord.Guild = g
try: try:
server: Server = self._servers.find_server_by_discord_id(g.id)
if server is None:
self._logger.fatal(
__name__, f"Server not found in database: {g.id}"
)
client = self._clients.find_client_by_server_id(server.id) client = self._clients.find_client_by_server_id(server.id)
if client is not None: if client is not None:
return continue
self._logger.warn( self._logger.warn(
__name__, __name__,
f"Client for server {guild_id} not found in database: {self._bot.user.id}", f"Client for server {g.id} not found in database: {self._bot.user.id}",
) )
self._logger.debug(__name__, f"Add client: {self._bot.user.id}") self._logger.debug(__name__, f"Add client: {self._bot.user.id}")
self._clients.add_client(Client(self._bot.user.id, 0, 0, 0, 0, 0, server)) self._clients.add_client(
Client(self._bot.user.id, 0, 0, 0, 0, 0, server)
)
self._db_context.save_changes() self._db_context.save_changes()
client = self._clients.find_client_by_server_id(server.id) client = self._clients.find_client_by_server_id(server.id)
if client is None: if client is None:
self._logger.fatal( self._logger.fatal(
__name__, __name__,
f"Cannot add client {self._bot.user.id} for server {guild_id}", f"Cannot add client {self._bot.user.id} for server {g.id}",
) )
self._logger.trace(__name__, f"Added client: {guild_id}") self._logger.debug(__name__, f"Added client: {g.id}")
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Cannot get client", e) self._logger.error(__name__, f"Cannot get client", e)
@@ -161,37 +162,38 @@ class DataIntegrityService:
if results is None or len(results) == 0: if results is None or len(results) == 0:
self._logger.error(__name__, f"Table Servers is empty!") self._logger.error(__name__, f"Table Servers is empty!")
def _check_known_user(self, member_id: int): def _check_users(self):
self._logger.debug(__name__, f"Start checking Users table")
for g in self._bot.guilds:
g: discord.Guild = g
try: try:
if self._known_users.find_user_by_discord_id(member_id) is not None: server = self._servers.find_server_by_discord_id(g.id)
return if server is None:
self._logger.fatal(
__name__, f"Server not found in database: {g.id}"
)
self._logger.warn(__name__, f"Unknown user: {member_id}") for u in g.members:
self._logger.trace(__name__, f"Add known user: {member_id}") u: Union[discord.Member, discord.User] = u
self._known_users.add_user(KnownUser(member_id)) if u.bot:
self._db_context.save_changes() self._logger.trace(
__name__, f"User {u.id} is ignored, because its a bot"
)
continue
user = self._known_users.find_user_by_discord_id(member_id) user = self._users.find_user_by_discord_id_and_server_id(
if user is None: u.id, server.id
self._logger.fatal(__name__, f"Cannot add user: {member_id}") )
self._logger.trace(__name__, f"Added known user: {member_id}")
except Exception as e:
self._logger.error(__name__, f"Cannot get user", e)
def _get_or_create_user(self, server: Server, member_id: int) -> User:
try:
user = self._users.find_user_by_discord_id_and_server_id(member_id, server.id)
if user is not None: if user is not None:
return user continue
self._logger.warn(__name__, f"User not found in database: {member_id}") self._logger.warn(__name__, f"User not found in database: {u.id}")
self._logger.debug(__name__, f"Add user: {member_id}") self._logger.debug(__name__, f"Add user: {u.id}")
self._users.add_user(User(member_id, 0, 0, 0, None, server)) self._users.add_user(User(u.id, 0, 0, 0, server))
self._db_context.save_changes() self._db_context.save_changes()
self._logger.trace(__name__, f"Added User: {member_id}") self._logger.debug(__name__, f"Added User: {u.id}")
return self._users.get_user_by_discord_id_and_server_id(member_id, server.id)
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Cannot get User", e) self._logger.error(__name__, f"Cannot get User", e)
@@ -199,34 +201,73 @@ class DataIntegrityService:
if results is None or len(results) == 0: if results is None or len(results) == 0:
self._logger.error(__name__, f"Table Users is empty!") self._logger.error(__name__, f"Table Users is empty!")
def _check_user_join(self, guild: Guild, member: Member, user: User): def _check_user_joins(self):
try: self._logger.debug(__name__, f"Start checking UserJoinedServers table")
join = self._user_joins.find_active_user_joined_server_by_user_id(user.id) for guild in self._bot.guilds:
if join is not None: guild: discord.Guild = guild
return
server = self._servers.find_server_by_discord_id(guild.id)
if server is None:
self._logger.fatal(
__name__, f"Server not found in database: {guild.id}"
)
try:
for u in guild.members:
u: discord.User = u
if u.bot:
self._logger.trace(
__name__, f"User {u.id} is ignored, because its a bot"
)
continue
user = self._users.find_user_by_discord_id_and_server_id(
u.id, server.id
)
if user is None:
self._logger.fatal(
__name__, f"User not found in database: {u.id}"
)
join = self._user_joins.find_active_user_joined_server_by_user_id(
user.id
)
if join is not None:
continue
m: discord.Member = u
self._logger.warn( self._logger.warn(
__name__, __name__,
f"Active UserJoinedServer not found in database: {guild.id}:{member.id}@{member.joined_at}", f"Active UserJoinedServer not found in database: {guild.id}:{u.id}@{m.joined_at}",
) )
self._logger.debug( self._logger.debug(
__name__, __name__,
f"Add UserJoinedServer: {guild.id}:{member.id}@{member.joined_at}", f"Add UserJoinedServer: {guild.id}:{u.id}@{m.joined_at}",
)
self._user_joins.add_user_joined_server(
UserJoinedServer(user, self._dtp.transform(m.joined_at), None)
) )
self._user_joins.add_user_joined_server(UserJoinedServer(user, self._dtp.transform(member.joined_at), None))
self._db_context.save_changes() self._db_context.save_changes()
self._logger.trace(__name__, f"Added UserJoinedServer: {member.id}") self._logger.debug(__name__, f"Added UserJoinedServer: {u.id}")
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Cannot get UserJoinedServer", e) self._logger.error(__name__, f"Cannot get UserJoinedServer", e)
try: results = self._users.get_users()
if results is None or len(results) == 0:
self._logger.error(__name__, f"Table Users is empty!")
joins = self._user_joins.get_user_joined_servers() joins = self._user_joins.get_user_joined_servers()
for join in [x for x in joins if x.user.server.discord_id == guild.id and x.leaved_on is None]: for join in joins:
dc_user = guild.get_member(join.user.discord_id) join: UserJoinedServer = join
if dc_user is not None: if join.user.server.discord_id != guild.id:
continue continue
if join.leaved_on is not None:
continue
dc_user = guild.get_member(join.user.discord_id)
if dc_user is None:
self._logger.warn( self._logger.warn(
__name__, __name__,
f"User {join.user.discord_id} already left the server.", f"User {join.user.discord_id} already left the server.",
@@ -235,27 +276,57 @@ class DataIntegrityService:
self._user_joins.update_user_joined_server(join) self._user_joins.update_user_joined_server(join)
self._db_context.save_changes() self._db_context.save_changes()
except Exception as e:
self._logger.error(__name__, f"Cannot update UserJoinedServer", e)
def _check_user_joined_vc(self, guild_id: int, member: Member, user: User): def _check_user_joins_vc(self):
settings: ServerConfig = self._config.get_configuration(f"ServerConfig_{guild_id}") self._logger.debug(__name__, f"Start checking UserJoinedVoiceChannel table")
for guild in self._bot.guilds:
guild: discord.Guild = guild
settings: ServerConfig = self._config.get_configuration(
f"ServerConfig_{guild.id}"
)
server = self._servers.find_server_by_discord_id(guild.id)
if server is None:
self._logger.fatal(
__name__, f"Server not found in database: {guild.id}"
)
try: try:
# close open voice states # close open voice states
joins = self._user_joins_vc.find_active_user_joined_voice_channels_by_user_id(user.id) for member in guild.members:
if member.bot:
self._logger.trace(
__name__, f"User {member.id} is ignored, because its a bot"
)
continue
user = self._users.find_user_by_discord_id_and_server_id(
member.id, server.id
)
if user is None:
self._logger.fatal(
__name__, f"User not found in database: {member.id}"
)
joins = self._user_joins_vc.find_active_user_joined_voice_channels_by_user_id(
user.id
)
if joins is None or len(joins) == 0: if joins is None or len(joins) == 0:
return continue
for join in joins: for join in joins:
self._logger.warn( self._logger.warn(
__name__, __name__,
f"Active UserJoinedVoiceChannel found in database: {guild_id}:{member.id}@{join.joined_on}", f"Active UserJoinedVoiceChannel found in database: {guild.id}:{member.id}@{join.joined_on}",
) )
join.leaved_on = datetime.now() join.leaved_on = datetime.now()
if ((join.leaved_on - join.joined_on).total_seconds() / 60 / 60) > settings.max_voice_state_hours: if (
join.leaved_on = join.joined_on + timedelta(hours=settings.max_voice_state_hours) (join.leaved_on - join.joined_on).total_seconds() / 60 / 60
) > settings.max_voice_state_hours:
join.leaved_on = join.joined_on + timedelta(
hours=settings.max_voice_state_hours
)
self._user_joins_vc.update_user_joined_voice_channel(join) self._user_joins_vc.update_user_joined_voice_channel(join)
@@ -268,31 +339,85 @@ class DataIntegrityService:
return return
# add open voice states # add open voice states
if member.voice is None or member.voice.channel.id in settings.afk_channel_ids: for member in guild.members:
return if member.bot:
self._logger.trace(
__name__, f"User {member.id} is ignored, because its a bot"
)
continue
join = UserJoinedVoiceChannel(user, member.voice.channel.id, datetime.now()) if (
member.voice is None
or member.voice.channel.id in settings.afk_channel_ids
):
continue
user = self._users.find_user_by_discord_id_and_server_id(
member.id, server.id
)
if user is None:
self._logger.fatal(
__name__, f"User not found in database: {member.id}"
)
join = UserJoinedVoiceChannel(
user, member.voice.channel.id, datetime.now()
)
self._user_joins_vc.add_user_joined_voice_channel(join) self._user_joins_vc.add_user_joined_voice_channel(join)
self._db_context.save_changes() self._db_context.save_changes()
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Cannot get UserJoinedVoiceChannel", e) self._logger.error(__name__, f"Cannot get UserJoinedVoiceChannel", e)
def _check_user_joined_gs(self, guild_id: int, member_id: int, user: User): def _check_user_joined_gs(self):
self._logger.debug(__name__, f"Start checking UserJoinedGameServer table")
for guild in self._bot.guilds:
guild: discord.Guild = guild
server = self._servers.find_server_by_discord_id(guild.id)
if server is None:
self._logger.fatal(
__name__, f"Server not found in database: {guild.id}"
)
try: try:
joins = self._user_joined_gs.find_active_user_joined_game_servers_by_user_id(user.id) for member in guild.members:
if member.bot:
self._logger.trace(
__name__, f"User {member.id} is ignored, because its a bot"
)
continue
user = self._users.find_user_by_discord_id_and_server_id(
member.id, server.id
)
if user is None:
self._logger.fatal(
__name__, f"User not found in database: {member.id}"
)
joins = self._user_joined_gs.find_active_user_joined_game_servers_by_user_id(
user.id
)
if joins is None or len(joins) == 0: if joins is None or len(joins) == 0:
return continue
for join in joins: for join in joins:
self._logger.warn( self._logger.warn(
__name__, __name__,
f"Active UserJoinedGameServer found in database: {guild_id}:{member_id}@{join.joined_on}", f"Active UserJoinedGameServer found in database: {guild.id}:{member.id}@{join.joined_on}",
) )
join.leaved_on = datetime.now() join.leaved_on = datetime.now()
settings: ServerConfig = self._config.get_configuration(f"ServerConfig_{guild_id}") settings: ServerConfig = self._config.get_configuration(
f"ServerConfig_{guild.id}"
)
if join.time > settings.max_voice_state_hours: if (
join.leaved_on = join.joined_on + timedelta(hours=settings.max_voice_state_hours) (join.leaved_on - join.joined_on).total_seconds() / 60 / 60
) > settings.max_voice_state_hours:
join.leaved_on = join.joined_on + timedelta(
hours=settings.max_voice_state_hours
)
self._user_joined_gs.update_user_joined_game_server(join) self._user_joined_gs.update_user_joined_game_server(join)
if self._is_for_shutdown: if self._is_for_shutdown:
@@ -303,23 +428,48 @@ class DataIntegrityService:
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Cannot get UserJoinedGameServer", e) self._logger.error(__name__, f"Cannot get UserJoinedGameServer", e)
async def _check_for_user_achievements(self, user: User): async def _check_for_user_achievements(self):
try: self._logger.debug(__name__, f"Start checking UserGotAchievement table")
await self._achievements.validate_achievements_for_user(user)
except Exception as e:
self._logger.error(__name__, f"Cannot check UserGotAchievement for {user.id}", e)
async def _check_default_role(self, member: Member): for guild in self._bot.guilds:
server = self._servers.find_server_by_discord_id(guild.id)
if server is None:
self._logger.fatal(
__name__, f"Server not found in database: {guild.id}"
)
for member in guild.members:
if member.bot:
self._logger.trace(
__name__, f"User {member.id} is ignored, because its a bot"
)
continue
user = self._users.find_user_by_discord_id_and_server_id(
member.id, server.id
)
if user is None:
self._logger.fatal(
__name__, f"User not found in database: {member.id}"
)
await self._achievements.validate_achievements_for_user(user)
async def _check_default_role(self):
for guild in self._bot.guilds:
for member in guild.members:
await self._client_utils.check_default_role(member) await self._client_utils.check_default_role(member)
def _remove_bot(self, user: User): async def check_data_integrity(self, is_for_shutdown=False):
known_user = self._known_users.find_user_by_discord_id(user.discord_id) if is_for_shutdown != self._is_for_shutdown:
if known_user is not None: self._is_for_shutdown = is_for_shutdown
self._known_users.delete_user(known_user)
for join in self._user_joins.get_user_joined_servers_by_user_id(user.id): await self._check_default_role()
self._user_joins.delete_user_joined_server(join) self._check_known_users()
self._check_servers()
self._user_joins_vc.delete_user_joined_voice_channel_by_user_id(user.id) self._check_clients()
self._users.delete_user(user) self._check_users()
self._db_context.save_changes() self._check_user_joins()
self._check_user_joins_vc()
self._check_user_joined_gs()
await self._check_for_user_achievements()

View File

@@ -31,15 +31,23 @@ class MessageService(MessageServiceABC):
self._clients = clients self._clients = clients
self._db = db self._db = db
async def delete_messages(self, messages: List[discord.Message], guild_id: int, without_tracking=False): async def delete_messages(
self, messages: List[discord.Message], guild_id: int, without_tracking=False
):
self._logger.debug(__name__, f"Try to delete {messages.count()} messages") self._logger.debug(__name__, f"Try to delete {messages.count()} messages")
server_st: ServerConfig = self._config.get_configuration(f"ServerConfig_{guild_id}") server_st: ServerConfig = self._config.get_configuration(
f"ServerConfig_{guild_id}"
)
await asyncio.sleep(server_st.message_delete_timer) await asyncio.sleep(server_st.message_delete_timer)
for message in messages: for message in messages:
await self.delete_message(message, mass_delete=True, without_tracking=without_tracking) await self.delete_message(
message, mass_delete=True, without_tracking=without_tracking
)
self._logger.debug(__name__, "Deleting messages finished") self._logger.debug(__name__, "Deleting messages finished")
async def delete_message(self, message: discord.Message, mass_delete=False, without_tracking=False): async def delete_message(
self, message: discord.Message, mass_delete=False, without_tracking=False
):
guild_id = ( guild_id = (
message.guild.id message.guild.id
if message.guild is not None if message.guild is not None
@@ -50,7 +58,9 @@ class MessageService(MessageServiceABC):
else None else None
) )
server_st: ServerConfig = self._config.get_configuration(f"ServerConfig_{guild_id}") server_st: ServerConfig = self._config.get_configuration(
f"ServerConfig_{guild_id}"
)
if not mass_delete: if not mass_delete:
await asyncio.sleep(server_st.message_delete_timer) await asyncio.sleep(server_st.message_delete_timer)
self._logger.debug( self._logger.debug(
@@ -64,7 +74,9 @@ class MessageService(MessageServiceABC):
self._logger.error(__name__, f"Deleting message failed", e) self._logger.error(__name__, f"Deleting message failed", e)
else: else:
if not without_tracking: if not without_tracking:
self._clients.append_deleted_message_count(self._bot.user.id, guild_id, 1) self._clients.append_deleted_message_count(
self._bot.user.id, guild_id, 1
)
self._db.save_changes() self._db.save_changes()
self._logger.info(__name__, f"Deleted message {message}") self._logger.info(__name__, f"Deleted message {message}")
@@ -76,7 +88,9 @@ class MessageService(MessageServiceABC):
wait_before_delete: int = None, wait_before_delete: int = None,
without_tracking=False, without_tracking=False,
): ):
self._logger.debug(__name__, f"Try to send message\n\t{message}\n\tto: {channel}") self._logger.debug(
__name__, f"Try to send message\n\t{message}\n\tto: {channel}"
)
msg = None msg = None
try: try:
if isinstance(message, discord.Embed): if isinstance(message, discord.Embed):
@@ -86,11 +100,15 @@ class MessageService(MessageServiceABC):
else: else:
msg = await channel.send(message) msg = await channel.send(message)
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Send message to channel {channel.id} failed", e) self._logger.error(
__name__, f"Send message to channel {channel.id} failed", e
)
else: else:
self._logger.info(__name__, f"Sent message to channel {channel.id}") self._logger.info(__name__, f"Sent message to channel {channel.id}")
if not without_tracking: if not without_tracking:
self._clients.append_sent_message_count(self._bot.user.id, channel.guild.id, 1) self._clients.append_sent_message_count(
self._bot.user.id, channel.guild.id, 1
)
self._db.save_changes() self._db.save_changes()
if wait_before_delete is not None: if wait_before_delete is not None:
@@ -107,17 +125,23 @@ class MessageService(MessageServiceABC):
receiver: Union[discord.User, discord.Member], receiver: Union[discord.User, discord.Member],
without_tracking=False, without_tracking=False,
): ):
self._logger.debug(__name__, f"Try to send message\n\t{message}\n\tto: {receiver}") self._logger.debug(
__name__, f"Try to send message\n\t{message}\n\tto: {receiver}"
)
try: try:
if isinstance(message, discord.Embed): if isinstance(message, discord.Embed):
msg = await receiver.send(embed=message) msg = await receiver.send(embed=message)
else: else:
msg = await receiver.send(message) msg = await receiver.send(message)
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Send message to user {receiver.id} failed", e) self._logger.error(
__name__, f"Send message to user {receiver.id} failed", e
)
else: else:
if not without_tracking: if not without_tracking:
self._clients.append_sent_message_count(self._bot.user.id, receiver.guild.id, 1) self._clients.append_sent_message_count(
self._bot.user.id, receiver.guild.id, 1
)
self._db.save_changes() self._db.save_changes()
self._logger.info(__name__, f"Sent message to user {receiver.id}") self._logger.info(__name__, f"Sent message to user {receiver.id}")
@@ -136,7 +160,9 @@ class MessageService(MessageServiceABC):
self._logger.debug(__name__, f"Message: {message}") self._logger.debug(__name__, f"Message: {message}")
return None return None
self._logger.debug(__name__, f"Try to send message\t\t{message}\n\tto: {ctx.channel}") self._logger.debug(
__name__, f"Try to send message\t\t{message}\n\tto: {ctx.channel}"
)
msg = None msg = None
try: try:
if isinstance(message, discord.Embed): if isinstance(message, discord.Embed):
@@ -144,11 +170,15 @@ class MessageService(MessageServiceABC):
else: else:
msg = await ctx.send(message, file=file, ephemeral=not is_public) msg = await ctx.send(message, file=file, ephemeral=not is_public)
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Send message to channel {ctx.channel.id} failed", e) self._logger.error(
__name__, f"Send message to channel {ctx.channel.id} failed", e
)
else: else:
self._logger.info(__name__, f"Sent message to channel {ctx.channel.id}") self._logger.info(__name__, f"Sent message to channel {ctx.channel.id}")
if not without_tracking and ctx.guild is not None: if not without_tracking and ctx.guild is not None:
self._clients.append_sent_message_count(self._bot.user.id, ctx.guild.id, 1) self._clients.append_sent_message_count(
self._bot.user.id, ctx.guild.id, 1
)
self._db.save_changes() self._db.save_changes()
if wait_before_delete is not None: if wait_before_delete is not None:
@@ -177,18 +207,30 @@ class MessageService(MessageServiceABC):
self._logger.debug(__name__, f"Message: {message}") self._logger.debug(__name__, f"Message: {message}")
return return
self._logger.debug(__name__, f"Try to send message\t\t{message}\n\tto: {interaction.channel}") self._logger.debug(
__name__, f"Try to send message\t\t{message}\n\tto: {interaction.channel}"
)
try: try:
if isinstance(message, discord.Embed): if isinstance(message, discord.Embed):
await interaction.response.send_message(embed=message, ephemeral=not is_public, **kwargs) await interaction.response.send_message(
embed=message, ephemeral=not is_public, **kwargs
)
else: else:
await interaction.response.send_message(message, ephemeral=not is_public, **kwargs) await interaction.response.send_message(
message, ephemeral=not is_public, **kwargs
)
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Send message to channel {interaction.channel.id} failed", e) self._logger.error(
__name__, f"Send message to channel {interaction.channel.id} failed", e
)
else: else:
self._logger.info(__name__, f"Sent message to channel {interaction.channel.id}") self._logger.info(
__name__, f"Sent message to channel {interaction.channel.id}"
)
if not without_tracking and interaction.guild is not None: if not without_tracking and interaction.guild is not None:
self._clients.append_sent_message_count(self._bot.user.id, interaction.guild.id, 1) self._clients.append_sent_message_count(
self._bot.user.id, interaction.guild.id, 1
)
self._db.save_changes() self._db.save_changes()
if wait_before_delete is not None: if wait_before_delete is not None:
@@ -197,4 +239,6 @@ class MessageService(MessageServiceABC):
if is_persistent: if is_persistent:
return return
await self.delete_message(await interaction.original_response(), without_tracking) await self.delete_message(
await interaction.original_response(), without_tracking
)

View File

@@ -1,129 +0,0 @@
from typing import Optional
import discord
from cpl_core.configuration import ConfigurationABC
from cpl_core.logging import LoggerABC
from cpl_discord.service import DiscordBotServiceABC
from bot_core.abc.permission_service_abc import PermissionServiceABC
from bot_data.abc.server_config_repository_abc import ServerConfigRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.abc.technician_config_repository_abc import TechnicianConfigRepositoryABC
from bot_data.model.team_member_type_enum import TeamMemberTypeEnum
class PermissionServiceWithCache(PermissionServiceABC):
def __init__(
self,
logger: LoggerABC,
bot: DiscordBotServiceABC,
config: ConfigurationABC,
servers: ServerRepositoryABC,
server_configs: ServerConfigRepositoryABC,
technician_configs: TechnicianConfigRepositoryABC,
):
PermissionServiceABC.__init__(self)
self._logger = logger
self._bot = bot
self._config = config
self._servers = servers
self._server_configs = server_configs
self._technician_configs = technician_configs
# member_id: {team_member_type: {guild_id: bool}}
self._cache: dict[int, dict[TeamMemberTypeEnum, dict[int, bool]]] = {}
def reset_cache(self):
self._cache = {}
def get_cached_permission(
self, member_id: int, team_member_type: TeamMemberTypeEnum, guild_id: int = None
) -> Optional[bool]:
if member_id not in self._cache:
self._cache[member_id] = {}
if team_member_type not in self._cache[member_id]:
self._cache[member_id][team_member_type] = {}
return None
if guild_id not in self._cache[member_id][team_member_type]:
return None
return self._cache[member_id][team_member_type][guild_id]
def set_cached_permission(
self, value: bool, member_id: int, team_member_type: TeamMemberTypeEnum, guild_id: int = None
):
if member_id not in self._cache:
self._cache[member_id] = {}
if team_member_type not in self._cache[member_id]:
self._cache[member_id][team_member_type] = {}
self._cache[member_id][team_member_type][guild_id] = value
def _has_member_role(self, member: discord.Member, team_member_type: TeamMemberTypeEnum) -> bool:
if member is None or member.guild is None:
return False
try:
has_permission_cached = self.get_cached_permission(member.id, team_member_type, member.guild.id)
if has_permission_cached is not None:
return has_permission_cached
self._logger.debug(__name__, f"Checking is member {member.name} {team_member_type.value}")
has_permission = True in [
member.guild.get_role(x.role_id) in member.roles
for x in self._server_configs.get_server_config_by_server(
self._servers.get_server_by_discord_id(member.guild.id).id
).team_role_ids
if x.team_member_type == team_member_type
]
self.set_cached_permission(has_permission, member.id, team_member_type, member.guild.id)
return has_permission
except Exception as e:
self._logger.error(__name__, "Permission check failed", e)
return False
def is_member_admin(self, member: discord.Member) -> bool:
return self._has_member_role(member, TeamMemberTypeEnum.admin)
def is_member_moderator(self, member: discord.Member) -> bool:
return self._has_member_role(member, TeamMemberTypeEnum.moderator) or self._has_member_role(
member, TeamMemberTypeEnum.admin
)
def is_member_technician(self, member: discord.Member) -> bool:
if member is None or member.guild is None:
return False
has_permission_cached = self.get_cached_permission(member.id, TeamMemberTypeEnum.technician)
if has_permission_cached is not None:
return has_permission_cached
self._logger.debug(__name__, f"Checking is member {member.name} technician")
try:
has_permission = member.id in self._technician_configs.get_technician_config().technician_ids
self.set_cached_permission(has_permission, member.id, TeamMemberTypeEnum.technician)
return has_permission
except Exception as e:
self._logger.error(__name__, "Permission check failed", e)
return False
def is_member_technician_by_id(self, member_id: int):
has_permission_cached = self.get_cached_permission(member_id, TeamMemberTypeEnum.technician)
if has_permission_cached is not None:
return has_permission_cached
self._logger.debug(__name__, f"Checking is member {member_id} technician")
try:
has_permission = member_id in self._technician_configs.get_technician_config().technician_ids
self.set_cached_permission(has_permission, member_id, TeamMemberTypeEnum.technician)
return has_permission
except Exception as e:
self._logger.error(__name__, "Permission check failed", e)
return False

View File

@@ -15,7 +15,7 @@ __title__ = "bot_data"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports # imports
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -15,7 +15,7 @@ __title__ = "bot_data.abc"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.11" __version__ = "1.2.0"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports # imports
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="11") version_info = VersionInfo(major="1", minor="2", micro="0")

View File

@@ -28,7 +28,9 @@ class AchievementRepositoryABC(ABC):
pass pass
@abstractmethod @abstractmethod
def get_user_got_achievements_by_achievement_id(self, achievement_id: int) -> List[Achievement]: def get_user_got_achievements_by_achievement_id(
self, achievement_id: int
) -> List[Achievement]:
pass pass
@abstractmethod @abstractmethod

View File

@@ -23,7 +23,9 @@ class AuthUserRepositoryABC(ABC):
pass pass
@abstractmethod @abstractmethod
def get_filtered_auth_users(self, criteria: AuthUserSelectCriteria) -> FilteredResult: def get_filtered_auth_users(
self, criteria: AuthUserSelectCriteria
) -> FilteredResult:
pass pass
@abstractmethod @abstractmethod

View File

@@ -36,7 +36,9 @@ class ClientRepositoryABC(ABC):
pass pass
@abstractmethod @abstractmethod
def find_client_by_discord_id_and_server_id(self, discord_id: int, server_id: int) -> Optional[Client]: def find_client_by_discord_id_and_server_id(
self, discord_id: int, server_id: int
) -> Optional[Client]:
pass pass
@abstractmethod @abstractmethod

View File

@@ -1,69 +0,0 @@
from abc import ABC, abstractmethod
from typing import Optional
from cpl_query.extension import List
from bot_data.model.realm import Realm
from bot_data.model.realm_moderator import RealmModerator
class RealmRepositoryABC(ABC):
@abstractmethod
def __init__(self):
pass
@abstractmethod
def get_all(self) -> List[Realm]:
pass
@abstractmethod
def get_realm_by_id(self, id: int) -> Realm:
pass
@abstractmethod
def find_realm_by_id(self, id: int) -> Optional[Realm]:
pass
@abstractmethod
def find_realm_by_name(self, name: str) -> Optional[Realm]:
pass
@abstractmethod
def get_realms_by_server_id(self, id: int) -> List[Realm]:
pass
@abstractmethod
def add_realm(self, realm: Realm):
pass
@abstractmethod
def update_realm(self, realm: Realm):
pass
@abstractmethod
def delete_realm(self, realm: Realm):
pass
@abstractmethod
def get_realm_moderators(self) -> List[RealmModerator]:
pass
@abstractmethod
def get_realm_moderator_by_id(self, id: int) -> RealmModerator:
pass
@abstractmethod
def get_realm_moderators_by_realm_id(self, id: int) -> List[RealmModerator]:
pass
@abstractmethod
def add_realm_moderator(self, realm: RealmModerator):
pass
@abstractmethod
def update_realm_moderator(self, realm: RealmModerator):
pass
@abstractmethod
def delete_realm_moderator(self, realm: RealmModerator):
pass

View File

@@ -1,35 +0,0 @@
from abc import ABC, abstractmethod
from cpl_query.extension import List
from bot_data.model.scheduled_event import ScheduledEvent
class ScheduledEventRepositoryABC(ABC):
@abstractmethod
def __init__(self):
pass
@abstractmethod
def get_scheduled_events(self) -> List[ScheduledEvent]:
pass
@abstractmethod
def get_scheduled_event_by_id(self, id: int) -> ScheduledEvent:
pass
@abstractmethod
def get_scheduled_events_by_server_id(self, id: int) -> List[ScheduledEvent]:
pass
@abstractmethod
def add_scheduled_event(self, scheduled_event: ScheduledEvent):
pass
@abstractmethod
def update_scheduled_event(self, scheduled_event: ScheduledEvent):
pass
@abstractmethod
def delete_scheduled_event(self, scheduled_event: ScheduledEvent):
pass

View File

@@ -35,25 +35,37 @@ class ServerConfigRepositoryABC(ABC):
pass pass
@abstractmethod @abstractmethod
def add_server_team_role_id_config(self, server_team_role_id: ServerTeamRoleIdsConfig): def add_server_team_role_id_config(
self, server_team_role_id: ServerTeamRoleIdsConfig
):
pass pass
@abstractmethod @abstractmethod
def update_server_team_role_id_config(self, server_team_role_id: ServerTeamRoleIdsConfig): def update_server_team_role_id_config(
self, server_team_role_id: ServerTeamRoleIdsConfig
):
pass pass
@abstractmethod @abstractmethod
def delete_server_team_role_id_config(self, server_team_role_id: ServerTeamRoleIdsConfig): def delete_server_team_role_id_config(
self, server_team_role_id: ServerTeamRoleIdsConfig
):
pass pass
@abstractmethod @abstractmethod
def add_server_afk_channel_config(self, server_afk_channel: ServerAFKChannelIdsConfig): def add_server_afk_channel_config(
self, server_afk_channel: ServerAFKChannelIdsConfig
):
pass pass
@abstractmethod @abstractmethod
def update_server_afk_channel_config(self, server_afk_channel: ServerAFKChannelIdsConfig): def update_server_afk_channel_config(
self, server_afk_channel: ServerAFKChannelIdsConfig
):
pass pass
@abstractmethod @abstractmethod
def delete_server_afk_channel_config(self, server_afk_channel: ServerAFKChannelIdsConfig): def delete_server_afk_channel_config(
self, server_afk_channel: ServerAFKChannelIdsConfig
):
pass pass

View File

@@ -43,13 +43,19 @@ class TechnicianConfigRepositoryABC(ABC):
pass pass
@abstractmethod @abstractmethod
def add_technician_ping_url_config(self, technician_ping_url: TechnicianPingUrlConfig): def add_technician_ping_url_config(
self, technician_ping_url: TechnicianPingUrlConfig
):
pass pass
@abstractmethod @abstractmethod
def update_technician_ping_url_config(self, technician_ping_url: TechnicianPingUrlConfig): def update_technician_ping_url_config(
self, technician_ping_url: TechnicianPingUrlConfig
):
pass pass
@abstractmethod @abstractmethod
def delete_technician_ping_url_config(self, technician_ping_url: TechnicianPingUrlConfig): def delete_technician_ping_url_config(
self, technician_ping_url: TechnicianPingUrlConfig
):
pass pass

View File

@@ -20,31 +20,45 @@ class UserJoinedGameServerRepositoryABC(ABC):
pass pass
@abstractmethod @abstractmethod
def get_user_joined_game_servers_by_user_id(self, user_id: int) -> List[UserJoinedGameServer]: def get_user_joined_game_servers_by_user_id(
self, user_id: int
) -> List[UserJoinedGameServer]:
pass pass
@abstractmethod @abstractmethod
def get_active_user_joined_game_server_by_user_id(self, user_id: int) -> UserJoinedGameServer: def get_active_user_joined_game_server_by_user_id(
self, user_id: int
) -> UserJoinedGameServer:
pass pass
@abstractmethod @abstractmethod
def find_active_user_joined_game_server_by_user_id(self, user_id: int) -> Optional[UserJoinedGameServer]: def find_active_user_joined_game_server_by_user_id(
self, user_id: int
) -> Optional[UserJoinedGameServer]:
pass pass
@abstractmethod @abstractmethod
def find_active_user_joined_game_servers_by_user_id(self, user_id: int) -> List[Optional[UserJoinedGameServer]]: def find_active_user_joined_game_servers_by_user_id(
self, user_id: int
) -> List[Optional[UserJoinedGameServer]]:
pass pass
@abstractmethod @abstractmethod
def add_user_joined_game_server(self, user_joined_game_server: UserJoinedGameServer): def add_user_joined_game_server(
self, user_joined_game_server: UserJoinedGameServer
):
pass pass
@abstractmethod @abstractmethod
def update_user_joined_game_server(self, user_joined_game_server: UserJoinedGameServer): def update_user_joined_game_server(
self, user_joined_game_server: UserJoinedGameServer
):
pass pass
@abstractmethod @abstractmethod
def delete_user_joined_game_server(self, user_joined_game_server: UserJoinedGameServer): def delete_user_joined_game_server(
self, user_joined_game_server: UserJoinedGameServer
):
pass pass
@abstractmethod @abstractmethod

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