From 7f197a0ea780f0cae359a430c42610abc8742feb Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 14 Jun 2023 19:30:30 +0200 Subject: [PATCH] Added achievement data model #268_achievements --- kdb-bot/cpl-workspace.json | 10 +- kdb-bot/src/bot/module_list.py | 2 + .../src/bot/startup_migration_extension.py | 2 + .../configuration/feature_flags_enum.py | 1 + .../configuration/feature_flags_settings.py | 1 + .../abc/achievement_repository_abc.py | 35 ++++++ kdb-bot/src/bot_data/data_module.py | 3 + .../migration/achievements_migration.py | 95 ++++++++++++++ kdb-bot/src/bot_data/model/achievement.py | 117 ++++++++++++++++++ .../src/bot_data/model/achievement_history.py | 66 ++++++++++ .../achievements_repository_service.py | 72 +++++++++++ kdb-bot/src/modules/achievements/__init__.py | 1 + .../modules/achievements/achievements.json | 44 +++++++ .../achievements/achievements_module.py | 18 +++ 14 files changed, 459 insertions(+), 8 deletions(-) create mode 100644 kdb-bot/src/bot_data/abc/achievement_repository_abc.py create mode 100644 kdb-bot/src/bot_data/migration/achievements_migration.py create mode 100644 kdb-bot/src/bot_data/model/achievement.py create mode 100644 kdb-bot/src/bot_data/model/achievement_history.py create mode 100644 kdb-bot/src/bot_data/service/achievements_repository_service.py create mode 100644 kdb-bot/src/modules/achievements/__init__.py create mode 100644 kdb-bot/src/modules/achievements/achievements.json create mode 100644 kdb-bot/src/modules/achievements/achievements_module.py diff --git a/kdb-bot/cpl-workspace.json b/kdb-bot/cpl-workspace.json index 90d77e00..1e44549f 100644 --- a/kdb-bot/cpl-workspace.json +++ b/kdb-bot/cpl-workspace.json @@ -17,29 +17,23 @@ "checks": "tools/checks/checks.json", "get-version": "tools/get_version/get-version.json", "post-build": "tools/post_build/post-build.json", - "set-version": "tools/set_version/set-version.json" + "set-version": "tools/set_version/set-version.json", + "modules/achievements": "src/modules/achievements/modules/achievements.json" }, "Scripts": { "format": "black ./", - "sv": "cpl set-version $ARGS", "set-version": "cpl run set-version $ARGS --dev; echo '';", - "gv": "cpl get-version", "get-version": "export VERSION=$(cpl run get-version --dev); echo $VERSION;", - "pre-build": "cpl set-version $ARGS; black ./;", "post-build": "cpl run post-build --dev; black ./;", - "pre-prod": "cpl build", "prod": "export KDB_ENVIRONMENT=production; export KDB_NAME=KDB-Prod; cpl start;", - "pre-stage": "cpl build", "stage": "export KDB_ENVIRONMENT=staging; export KDB_NAME=KDB-Stage; cpl start;", - "pre-dev": "cpl build", "dev": "export KDB_ENVIRONMENT=development; export KDB_NAME=KDB-Dev; cpl start;", - "docker-build": "cpl build $ARGS; docker build -t kdb-bot/kdb-bot:$(cpl gv) .;", "dc-up": "docker-compose up -d", "dc-down": "docker-compose down", diff --git a/kdb-bot/src/bot/module_list.py b/kdb-bot/src/bot/module_list.py index 700f0c73..7c6fb418 100644 --- a/kdb-bot/src/bot/module_list.py +++ b/kdb-bot/src/bot/module_list.py @@ -5,6 +5,7 @@ from bot_core.core_extension.core_extension_module import CoreExtensionModule from bot_core.core_module import CoreModule from bot_data.data_module import DataModule from bot_graphql.graphql_module import GraphQLModule +from modules.achievements.achievements_module import AchievementsModule from modules.auto_role.auto_role_module import AutoRoleModule from modules.base.base_module import BaseModule from modules.boot_log.boot_log_module import BootLogModule @@ -31,6 +32,7 @@ class ModuleList: LevelModule, ApiModule, TechnicianModule, + AchievementsModule, # has to be last! BootLogModule, CoreExtensionModule, diff --git a/kdb-bot/src/bot/startup_migration_extension.py b/kdb-bot/src/bot/startup_migration_extension.py index 5eba461b..a2d639fa 100644 --- a/kdb-bot/src/bot/startup_migration_extension.py +++ b/kdb-bot/src/bot/startup_migration_extension.py @@ -4,6 +4,7 @@ from cpl_core.dependency_injection import ServiceCollectionABC from cpl_core.environment import ApplicationEnvironmentABC from bot_data.abc.migration_abc import MigrationABC +from bot_data.migration.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 @@ -42,3 +43,4 @@ class StartupMigrationExtension(StartupExtensionABC): 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 diff --git a/kdb-bot/src/bot_core/configuration/feature_flags_enum.py b/kdb-bot/src/bot_core/configuration/feature_flags_enum.py index aeb18a99..37a579fe 100644 --- a/kdb-bot/src/bot_core/configuration/feature_flags_enum.py +++ b/kdb-bot/src/bot_core/configuration/feature_flags_enum.py @@ -3,6 +3,7 @@ from enum import Enum class FeatureFlagsEnum(Enum): # modules + achievements_module = "AchievementsModule" api_module = "ApiModule" admin_module = "AdminModule" auto_role_module = "AutoRoleModule" diff --git a/kdb-bot/src/bot_core/configuration/feature_flags_settings.py b/kdb-bot/src/bot_core/configuration/feature_flags_settings.py index 777e256b..b0d297b8 100644 --- a/kdb-bot/src/bot_core/configuration/feature_flags_settings.py +++ b/kdb-bot/src/bot_core/configuration/feature_flags_settings.py @@ -12,6 +12,7 @@ class FeatureFlagsSettings(ConfigurationModelABC): self._flags = { # modules + FeatureFlagsEnum.achievements_module.value: False, # 14.06.2023 #268 FeatureFlagsEnum.api_module.value: False, # 13.10.2022 #70 FeatureFlagsEnum.admin_module.value: False, # 02.10.2022 #48 FeatureFlagsEnum.auto_role_module.value: True, # 03.10.2022 #54 diff --git a/kdb-bot/src/bot_data/abc/achievement_repository_abc.py b/kdb-bot/src/bot_data/abc/achievement_repository_abc.py new file mode 100644 index 00000000..4b817759 --- /dev/null +++ b/kdb-bot/src/bot_data/abc/achievement_repository_abc.py @@ -0,0 +1,35 @@ +from abc import ABC, abstractmethod + +from cpl_query.extension import List + +from bot_data.model.achievement import Achievement + + +class AchievementRepositoryABC(ABC): + @abstractmethod + def __init__(self): + pass + + @abstractmethod + def get_achievements(self) -> List[Achievement]: + pass + + @abstractmethod + def get_achievement_by_id(self, id: int) -> Achievement: + pass + + @abstractmethod + def get_achievements_by_server_id(self, server_id: int) -> List[Achievement]: + pass + + @abstractmethod + def add_achievement(self, achievement: Achievement): + pass + + @abstractmethod + def update_achievement(self, achievement: Achievement): + pass + + @abstractmethod + def delete_achievement(self, achievement: Achievement): + pass diff --git a/kdb-bot/src/bot_data/data_module.py b/kdb-bot/src/bot_data/data_module.py index 899151d6..fd571abb 100644 --- a/kdb-bot/src/bot_data/data_module.py +++ b/kdb-bot/src/bot_data/data_module.py @@ -5,6 +5,7 @@ from cpl_discord.service.discord_collection_abc import DiscordCollectionABC from bot_core.abc.module_abc import ModuleABC from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum +from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC from bot_data.abc.api_key_repository_abc import ApiKeyRepositoryABC from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC @@ -24,6 +25,7 @@ from bot_data.abc.user_message_count_per_hour_repository_abc import ( ) from bot_data.abc.user_repository_abc import UserRepositoryABC from bot_data.abc.user_warnings_repository_abc import UserWarningsRepositoryABC +from bot_data.service.achievements_repository_service import AchievementRepositoryService from bot_data.service.api_key_repository_service import ApiKeyRepositoryService from bot_data.service.auth_user_repository_service import AuthUserRepositoryService from bot_data.service.auto_role_repository_service import AutoRoleRepositoryService @@ -77,5 +79,6 @@ class DataModule(ModuleABC): ) services.add_transient(GameServerRepositoryABC, GameServerRepositoryService) services.add_transient(UserGameIdentRepositoryABC, UserGameIdentRepositoryService) + services.add_transient(AchievementRepositoryABC, AchievementRepositoryService) services.add_transient(SeederService) diff --git a/kdb-bot/src/bot_data/migration/achievements_migration.py b/kdb-bot/src/bot_data/migration/achievements_migration.py new file mode 100644 index 00000000..8c310526 --- /dev/null +++ b/kdb-bot/src/bot_data/migration/achievements_migration.py @@ -0,0 +1,95 @@ +from bot_core.logging.database_logger import DatabaseLogger +from bot_data.abc.migration_abc import MigrationABC +from bot_data.db_context import DBContext + + +class AchievementsMigration(MigrationABC): + name = "1.1.0_AchievementsMigration" + + def __init__(self, logger: DatabaseLogger, db: DBContext): + MigrationABC.__init__(self) + self._logger = logger + self._db = db + self._cursor = db.cursor + + def upgrade(self): + self._logger.debug(__name__, "Running upgrade") + + self._cursor.execute( + str( + f""" + CREATE TABLE IF NOT EXISTS `Achievements` ( + `Id` BIGINT NOT NULL AUTO_INCREMENT, + `ServerId` BIGINT, + `Name` VARCHAR(255) NOT NULL, + `Attribute` VARCHAR(255) NOT NULL, + `Operator` VARCHAR(2) NOT NULL, + `Value` VARCHAR(255) NOT NULL, + `CreatedAt` DATETIME(6) NULL DEFAULT CURRENT_TIMESTAMP(6), + `LastModifiedAt` DATETIME(6) NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + PRIMARY KEY(`Id`), + FOREIGN KEY (`ServerId`) REFERENCES `Servers`(`ServerId`) + ); + """ + ) + ) + + self._cursor.execute( + str( + f""" + ALTER TABLE Users ADD MessageCount BIGINT NOT NULL DEFAULT 0 AFTER XP; + ALTER TABLE Users ADD ReactionCount BIGINT NOT NULL DEFAULT 0 AFTER XP; + """ + ) + ) + + self._cursor.execute( + str( + f""" + ALTER TABLE UsersHistory ADD MessageCount BIGINT NOT NULL DEFAULT 0 AFTER XP; + ALTER TABLE UsersHistory ADD ReactionCount BIGINT NOT NULL DEFAULT 0 AFTER XP; + """ + ) + ) + + self._cursor.execute( + str( + f""" + DROP TRIGGER IF EXISTS `TR_AchievementsUpdate`; + + CREATE TRIGGER `TR_AchievementsUpdate` + AFTER UPDATE + ON `Achievements` + FOR EACH ROW + BEGIN + INSERT INTO `AchievementsHistory` ( + `Id`, `Name`, `Attribute`, `Operator`, `Value`, `ServerId`, `DateFrom`, `DateTo` + ) + VALUES ( + OLD.Id, OLD.Name, OLD.Attribute, OLD.Operator, OLD.Value, OLD.ServerId, OLD.LastModifiedAt, CURRENT_TIMESTAMP(6) + ); + END; + + DROP TRIGGER IF EXISTS `TR_AchievementsDelete`; + + CREATE TRIGGER `TR_AchievementsDelete` + AFTER DELETE + ON `Achievements` + FOR EACH ROW + BEGIN + INSERT INTO `AchievementsHistory` ( + `Id`, `Name`, `Attribute`, `Operator`, `Value`, `ServerId`, `Deleted`, `DateFrom`, `DateTo` + ) + VALUES ( + OLD.Id, OLD.Name, OLD.Attribute, OLD.Operator, OLD.Value, OLD.ServerId, TRUE, OLD.LastModifiedAt, CURRENT_TIMESTAMP(6) + ); + END; + """ + ) + ) + + def downgrade(self): + self._cursor.execute("DROP TABLE `Achievements`;") + + self._cursor.execute(str(f"""ALTER TABLE Users DROP COLUMN MessageCount;""")) + self._cursor.execute(str(f"""ALTER TABLE Users DROP COLUMN ReactionCount;""")) diff --git a/kdb-bot/src/bot_data/model/achievement.py b/kdb-bot/src/bot_data/model/achievement.py new file mode 100644 index 00000000..b9a44089 --- /dev/null +++ b/kdb-bot/src/bot_data/model/achievement.py @@ -0,0 +1,117 @@ +from datetime import datetime +from typing import Optional + +from cpl_core.database import TableABC + +from bot_data.model.server import Server + + +class Achievement(TableABC): + def __init__( + self, + name: str, + attribute: str, + operator: str, + value: str, + server: Optional[Server], + created_at: datetime = None, + modified_at: datetime = None, + id=0, + ): + self._id = id + self._name = name + self._attribute = attribute + self._operator = operator + self._value = value + self._server = server + + TableABC.__init__(self) + self._created_at = created_at if created_at is not None else self._created_at + self._modified_at = modified_at if modified_at is not None else self._modified_at + + @property + def id(self) -> int: + return self._id + + @property + def name(self) -> str: + return self._name + + @name.setter + def name(self, value: str): + self._name = value + + @property + def operator(self) -> str: + return self._operator + + @operator.setter + def operator(self, value: str): + self._operator = value + + @property + def value(self) -> str: + return self._value + + @value.setter + def value(self, value: str): + self._value = value + + @property + def server(self) -> Server: + return self._server + + @staticmethod + def get_select_all_string() -> str: + return str( + f""" + SELECT * FROM `Achievements`; + """ + ) + + @staticmethod + def get_select_by_id_string(id: int) -> str: + return str( + f""" + SELECT * FROM `Achievements` + WHERE `Id` = {id}; + """ + ) + + @property + def insert_string(self) -> str: + return str( + f""" + INSERT INTO `Achievements` ( + `Name`, `Attribute`, `Operator`, `Value`, `ServerId` + ) VALUES ( + '{self._name}', + '{self._attribute}', + '{self._operator}', + '{self._value}', + {self._server.id} + ); + """ + ) + + @property + def udpate_string(self) -> str: + return str( + f""" + UPDATE `Levels` + SET `Name` = '{self._name}', + `Attribute` = '{self._attribute}', + `Operator` = '{self._operator}', + `Value` = '{self._value}' + WHERE `Id` = {self._id}; + """ + ) + + @property + def delete_string(self) -> str: + return str( + f""" + DELETE FROM `Achievements` + WHERE `Id` = {self._id}; + """ + ) diff --git a/kdb-bot/src/bot_data/model/achievement_history.py b/kdb-bot/src/bot_data/model/achievement_history.py new file mode 100644 index 00000000..74208075 --- /dev/null +++ b/kdb-bot/src/bot_data/model/achievement_history.py @@ -0,0 +1,66 @@ +from datetime import datetime +from typing import Optional + +from cpl_core.database import TableABC + +from bot_data.abc.history_table_abc import HistoryTableABC +from bot_data.model.server import Server + + +class Achievement(HistoryTableABC): + def __init__( + self, + name: str, + attribute: str, + operator: str, + value: str, + server: Optional[Server], + deleted: bool, + date_from: str, + date_to: str, + id=0, + ): + HistoryTableABC.__init__(self) + + self._id = id + self._name = name + self._attribute = attribute + self._operator = operator + self._value = value + self._server = server + + self._deleted = deleted + self._date_from = date_from + self._date_to = date_to + + @property + def id(self) -> int: + return self._id + + @property + def name(self) -> str: + return self._name + + @name.setter + def name(self, value: str): + self._name = value + + @property + def operator(self) -> str: + return self._operator + + @operator.setter + def operator(self, value: str): + self._operator = value + + @property + def value(self) -> str: + return self._value + + @value.setter + def value(self, value: str): + self._value = value + + @property + def server(self) -> Server: + return self._server diff --git a/kdb-bot/src/bot_data/service/achievements_repository_service.py b/kdb-bot/src/bot_data/service/achievements_repository_service.py new file mode 100644 index 00000000..d910a6c2 --- /dev/null +++ b/kdb-bot/src/bot_data/service/achievements_repository_service.py @@ -0,0 +1,72 @@ +from cpl_core.database.context import DatabaseContextABC +from cpl_query.extension import List + +from bot_core.logging.database_logger import DatabaseLogger +from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC +from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.model.achievement import Achievement + + +class AchievementRepositoryService(AchievementRepositoryABC): + def __init__( + self, + logger: DatabaseLogger, + db_context: DatabaseContextABC, + servers: ServerRepositoryABC, + ): + self._logger = logger + self._context = db_context + + self._servers = servers + + AchievementRepositoryABC.__init__(self) + + def _from_result(self, result: tuple): + return Achievement( + result[3], + result[4], + result[5], + result[6], + self._servers.get_server_by_id(result[2]), + result[6], + result[7], + id=result[0], + ) + + def get_achievements(self) -> List[Achievement]: + achievements = List(Achievement) + self._logger.trace(__name__, f"Send SQL command: {Achievement.get_select_all_string()}") + results = self._context.select(Achievement.get_select_all_string()) + for result in results: + self._logger.trace(__name__, f"Get user with id {result[0]}") + achievements.append(self._from_result(result)) + + return achievements + + def get_achievement_by_id(self, id: int) -> Achievement: + self._logger.trace(__name__, f"Send SQL command: {Achievement.get_select_by_id_string(id)}") + result = self._context.select(Achievement.get_select_by_id_string(id))[0] + + return self._from_result(result) + + def get_achievements_by_server_id(self, server_id: int) -> List[Achievement]: + achievements = List(Achievement) + self._logger.trace(__name__, f"Send SQL command: {Achievement.get_select_by_id_string(server_id)}") + results = self._context.select(Achievement.get_select_all_string()) + for result in results: + self._logger.trace(__name__, f"Get user with id {result[0]}") + achievements.append(self._from_result(result)) + + return achievements + + def add_achievement(self, achievement: Achievement): + self._logger.trace(__name__, f"Send SQL command: {achievement.insert_string}") + self._context.cursor.execute(achievement.insert_string) + + def update_achievement(self, achievement: Achievement): + self._logger.trace(__name__, f"Send SQL command: {achievement.udpate_string}") + self._context.cursor.execute(achievement.udpate_string) + + def delete_achievement(self, achievement: Achievement): + self._logger.trace(__name__, f"Send SQL command: {achievement.delete_string}") + self._context.cursor.execute(achievement.delete_string) diff --git a/kdb-bot/src/modules/achievements/__init__.py b/kdb-bot/src/modules/achievements/__init__.py new file mode 100644 index 00000000..425ab6c1 --- /dev/null +++ b/kdb-bot/src/modules/achievements/__init__.py @@ -0,0 +1 @@ +# imports diff --git a/kdb-bot/src/modules/achievements/achievements.json b/kdb-bot/src/modules/achievements/achievements.json new file mode 100644 index 00000000..5937d753 --- /dev/null +++ b/kdb-bot/src/modules/achievements/achievements.json @@ -0,0 +1,44 @@ +{ + "ProjectSettings": { + "Name": "achievements", + "Version": { + "Major": "1", + "Minor": "1", + "Micro": "0" + }, + "Author": "Sven Heidemann", + "AuthorEmail": "sven.heidemann@sh-edraft.de", + "Description": "Keksdose bot - achievements", + "LongDescription": "Discord bot for the Keksdose discord Server - achievements module", + "URL": "https://www.sh-edraft.de", + "CopyrightDate": "2023", + "CopyrightName": "sh-edraft.de", + "LicenseName": "MIT", + "LicenseDescription": "MIT, see LICENSE for more details.", + "Dependencies": [ + "cpl-core>=2023.4.0.post2" + ], + "DevDependencies": [ + "cpl-cli>=2023.4.0.post3" + ], + "PythonVersion": ">=3.10.4", + "PythonPath": {}, + "Classifiers": [] + }, + "BuildSettings": { + "ProjectType": "library", + "SourcePath": "", + "OutputPath": "../../dist", + "Main": "achievements.main", + "EntryPoint": "achievements", + "IncludePackageData": false, + "Included": [], + "Excluded": [ + "*/__pycache__", + "*/logs", + "*/tests" + ], + "PackageData": {}, + "ProjectReferences": [] + } +} \ No newline at end of file diff --git a/kdb-bot/src/modules/achievements/achievements_module.py b/kdb-bot/src/modules/achievements/achievements_module.py new file mode 100644 index 00000000..c3a7d5cd --- /dev/null +++ b/kdb-bot/src/modules/achievements/achievements_module.py @@ -0,0 +1,18 @@ +from cpl_core.configuration import ConfigurationABC +from cpl_core.dependency_injection import ServiceCollectionABC +from cpl_core.environment import ApplicationEnvironmentABC +from cpl_discord.service.discord_collection_abc import DiscordCollectionABC + +from bot_core.abc.module_abc import ModuleABC +from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum + + +class AchievementsModule(ModuleABC): + def __init__(self, dc: DiscordCollectionABC): + ModuleABC.__init__(self, dc, FeatureFlagsEnum.auto_role_module) + + def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC): + pass + + def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): + pass