From 9e9879497ad7436797f8dd5685f378f229d74cd2 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 14 Jun 2023 19:30:30 +0200 Subject: [PATCH 01/23] 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 From a49188b412f5d00c43f7b4eab12906e79f701a59 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 14 Jun 2023 19:56:38 +0200 Subject: [PATCH 02/23] Fixed models #268_achievements --- .../migration/achievements_migration.py | 29 ++++++++++++------- kdb-bot/src/bot_data/model/user.py | 28 ++++++++++++++++-- .../service/user_repository_service.py | 6 ++-- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/kdb-bot/src/bot_data/migration/achievements_migration.py b/kdb-bot/src/bot_data/migration/achievements_migration.py index 8c310526..160d379c 100644 --- a/kdb-bot/src/bot_data/migration/achievements_migration.py +++ b/kdb-bot/src/bot_data/migration/achievements_migration.py @@ -37,20 +37,26 @@ class AchievementsMigration(MigrationABC): 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; + CREATE TABLE IF NOT EXISTS `AchievementsHistory` + ( + `Id` BIGINT(20) NOT NULL, + `ServerId` BIGINT, + `Name` VARCHAR(255) NOT NULL, + `Attribute` VARCHAR(255) NOT NULL, + `Operator` VARCHAR(2) NOT NULL, + `Value` VARCHAR(255) NOT NULL, + `Deleted` BOOL DEFAULT FALSE, + `DateFrom` DATETIME(6) NOT NULL, + `DateTo` DATETIME(6) NOT NULL + ); """ ) ) - 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"""ALTER TABLE Users ADD MessageCount BIGINT NOT NULL DEFAULT 0 AFTER XP;""")) + self._cursor.execute(str(f"""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;""")) + self._cursor.execute(str(f"""ALTER TABLE UsersHistory ADD ReactionCount BIGINT NOT NULL DEFAULT 0 AFTER XP;""")) self._cursor.execute( str( @@ -85,7 +91,8 @@ class AchievementsMigration(MigrationABC): ); END; """ - ) + ), + multi=True, ) def downgrade(self): diff --git a/kdb-bot/src/bot_data/model/user.py b/kdb-bot/src/bot_data/model/user.py index 7472e689..ff654ae3 100644 --- a/kdb-bot/src/bot_data/model/user.py +++ b/kdb-bot/src/bot_data/model/user.py @@ -15,6 +15,8 @@ class User(TableABC): self, dc_id: int, xp: int, + message_count: int, + reaction_count: int, server: Optional[Server], created_at: datetime = None, modified_at: datetime = None, @@ -23,6 +25,8 @@ class User(TableABC): self._user_id = id self._discord_id = dc_id self._xp = xp + self._message_count = message_count + self._reaction_count = reaction_count self._server = server TableABC.__init__(self) @@ -59,6 +63,22 @@ class User(TableABC): def xp(self, value: int): self._xp = value + @property + def message_count(self) -> int: + return self._message_count + + @message_count.setter + def message_count(self, value: int): + self._message_count = value + + @property + def reaction_count(self) -> int: + return self._reaction_count + + @reaction_count.setter + def reaction_count(self, value: int): + self._reaction_count = value + @property @ServiceProviderABC.inject def ontime(self, services: ServiceProviderABC) -> float: @@ -151,10 +171,12 @@ class User(TableABC): return str( f""" INSERT INTO `Users` ( - `DiscordId`, `XP`, `ServerId` + `DiscordId`, `XP`, `MessageCount`, `ReactionCount`, `ServerId` ) VALUES ( {self._discord_id}, {self._xp}, + {self._message_count}, + {self._reaction_count}, {self._server.id} ); """ @@ -165,7 +187,9 @@ class User(TableABC): return str( f""" UPDATE `Users` - SET `XP` = {self._xp} + SET `XP` = {self._xp}, + `MessageCount` = {self._message_count}, + `ReactionCount` = {self._reaction_count} WHERE `UserId` = {self._user_id}; """ ) diff --git a/kdb-bot/src/bot_data/service/user_repository_service.py b/kdb-bot/src/bot_data/service/user_repository_service.py index adc9685c..73387908 100644 --- a/kdb-bot/src/bot_data/service/user_repository_service.py +++ b/kdb-bot/src/bot_data/service/user_repository_service.py @@ -27,9 +27,11 @@ class UserRepositoryService(UserRepositoryABC): return User( result[1], result[2], - self._servers.get_server_by_id(result[3]), + result[3], result[4], - result[5], + self._servers.get_server_by_id(result[5]), + result[6], + result[7], id=result[0], ) From 2c9434396d6388e00104589461fb7bc75328b8ba Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 11 Jul 2023 18:16:32 +0200 Subject: [PATCH 03/23] Added achievement gql endpoint [UNTESTED] #268_achievements --- .../bot_graphql/filter/achievement_filter.py | 60 +++++++++++++++++++ kdb-bot/src/bot_graphql/graphql_module.py | 6 ++ kdb-bot/src/bot_graphql/model/achievement.gql | 52 ++++++++++++++++ kdb-bot/src/bot_graphql/model/mutation.gql | 1 + kdb-bot/src/bot_graphql/model/query.gql | 3 + kdb-bot/src/bot_graphql/mutation.py | 4 +- .../mutations/achievement_mutation.py | 43 +++++++++++++ .../bot_graphql/queries/achievement_query.py | 19 ++++++ kdb-bot/src/bot_graphql/query.py | 4 ++ kdb-web/package.json | 2 +- kdb-web/src/assets/config.json | 2 +- 11 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 kdb-bot/src/bot_graphql/filter/achievement_filter.py create mode 100644 kdb-bot/src/bot_graphql/model/achievement.gql create mode 100644 kdb-bot/src/bot_graphql/mutations/achievement_mutation.py create mode 100644 kdb-bot/src/bot_graphql/queries/achievement_query.py diff --git a/kdb-bot/src/bot_graphql/filter/achievement_filter.py b/kdb-bot/src/bot_graphql/filter/achievement_filter.py new file mode 100644 index 00000000..49c0eab9 --- /dev/null +++ b/kdb-bot/src/bot_graphql/filter/achievement_filter.py @@ -0,0 +1,60 @@ +from cpl_query.extension import List + +from bot_data.model.user import User +from bot_graphql.abc.filter_abc import FilterABC + + +class AchievementFilter(FilterABC): + def __init__(self): + FilterABC.__init__(self) + + self._id = None + self._name = None + self._attribute = None + self._operator = None + self._value = None + self._server = None + + def from_dict(self, values: dict): + if "id" in values: + self._id = int(values["id"]) + + if "name" in values: + self._name = values["name"] + + if "attribute" in values: + self._attribute = values["attribute"] + + if "operator" in values: + self._operator = values["operator"] + + if "value" in values: + self._value = values["value"] + + if "server" in values: + from bot_graphql.filter.server_filter import ServerFilter + + self._server: ServerFilter = self._services.get_service(ServerFilter) + self._server.from_dict(values["server"]) + + def filter(self, query: List[User]) -> List[User]: + if self._id is not None: + query = query.where(lambda x: x.id == self._id) + + if self._name is not None: + query = query.where(lambda x: x.name == self._name) + + if self._attribute is not None: + query = query.where(lambda x: x.attribute == self._attribute) + + if self._operator is not None: + query = query.where(lambda x: x.operator == self._operator) + + if self._value is not None: + query = query.where(lambda x: x.value == self._value) + + if self._server is not None: + servers = self._server.filter(query.select(lambda x: x.server)).select(lambda x: x.id) + query = query.where(lambda x: x.server.id in servers) + + return query diff --git a/kdb-bot/src/bot_graphql/graphql_module.py b/kdb-bot/src/bot_graphql/graphql_module.py index 5ef7df09..b861aec1 100644 --- a/kdb-bot/src/bot_graphql/graphql_module.py +++ b/kdb-bot/src/bot_graphql/graphql_module.py @@ -8,6 +8,7 @@ from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum from bot_data.service.seeder_service import SeederService from bot_graphql.abc.filter_abc import FilterABC from bot_graphql.abc.query_abc import QueryABC +from bot_graphql.filter.achievement_filter import AchievementFilter from bot_graphql.filter.auto_role_filter import AutoRoleFilter from bot_graphql.filter.auto_role_rule_filter import AutoRoleRuleFilter from bot_graphql.filter.client_filter import ClientFilter @@ -19,11 +20,13 @@ from bot_graphql.filter.user_joined_server_filter import UserJoinedServerFilter from bot_graphql.filter.user_joined_voice_channel_filter import UserJoinedVoiceChannelFilter from bot_graphql.graphql_service import GraphQLService from bot_graphql.mutation import Mutation +from bot_graphql.mutations.achievement_mutation import AchievementMutation from bot_graphql.mutations.auto_role_mutation import AutoRoleMutation from bot_graphql.mutations.auto_role_rule_mutation import AutoRoleRuleMutation from bot_graphql.mutations.level_mutation import LevelMutation from bot_graphql.mutations.user_joined_game_server_mutation import UserJoinedGameServerMutation from bot_graphql.mutations.user_mutation import UserMutation +from bot_graphql.queries.achievement_query import AchievementQuery from bot_graphql.queries.auto_role_history_query import AutoRoleHistoryQuery from bot_graphql.queries.auto_role_query import AutoRoleQuery from bot_graphql.queries.auto_role_rule_history_query import AutoRoleRuleHistoryQuery @@ -76,6 +79,7 @@ class GraphQLModule(ModuleABC): services.add_transient(QueryABC, ServerQuery) services.add_transient(QueryABC, UserHistoryQuery) services.add_transient(QueryABC, UserQuery) + services.add_transient(QueryABC, AchievementQuery) services.add_transient(QueryABC, UserJoinedServerHistoryQuery) services.add_transient(QueryABC, UserJoinedServerQuery) services.add_transient(QueryABC, UserJoinedVoiceChannelHistoryQuery) @@ -90,6 +94,7 @@ class GraphQLModule(ModuleABC): services.add_transient(FilterABC, LevelFilter) services.add_transient(FilterABC, ServerFilter) services.add_transient(FilterABC, UserFilter) + services.add_transient(FilterABC, AchievementFilter) services.add_transient(FilterABC, UserJoinedServerFilter) services.add_transient(FilterABC, UserJoinedVoiceChannelFilter) services.add_transient(FilterABC, UserJoinedGameServerFilter) @@ -99,6 +104,7 @@ class GraphQLModule(ModuleABC): services.add_transient(QueryABC, AutoRoleRuleMutation) services.add_transient(QueryABC, LevelMutation) services.add_transient(QueryABC, UserMutation) + services.add_transient(QueryABC, AchievementMutation) services.add_transient(QueryABC, UserJoinedGameServerMutation) services.add_transient(SeederService) diff --git a/kdb-bot/src/bot_graphql/model/achievement.gql b/kdb-bot/src/bot_graphql/model/achievement.gql new file mode 100644 index 00000000..3a5f1328 --- /dev/null +++ b/kdb-bot/src/bot_graphql/model/achievement.gql @@ -0,0 +1,52 @@ +type Achievement implements TableWithHistoryQuery { + id: ID + name: String + attribute: String + operator: String + value: String + + server: Server + + createdAt: String + modifiedAt: String + + history: [AchievementHistory] +} + +type AchievementHistory implements HistoryTableQuery { + id: ID + name: String + attribute: String + operator: String + value: String + + server: Server + + deleted: Boolean + dateFrom: String + dateTo: String +} + +input AchievementFilter { + id: ID + name: String + attribute: String + operator: String + value: String + server: ServerFilter +} + +type AchievementMutation { + createAchievement(input: AchievementInput!): Achievement + updateAchievement(input: AchievementInput!): Achievement + deleteAchievement(id: ID): Achievement +} + +input AchievementInput { + id: ID + name: String + attribute: String + operator: String + value: String + serverId: ID +} \ No newline at end of file diff --git a/kdb-bot/src/bot_graphql/model/mutation.gql b/kdb-bot/src/bot_graphql/model/mutation.gql index 9631fd8d..0fb69b50 100644 --- a/kdb-bot/src/bot_graphql/model/mutation.gql +++ b/kdb-bot/src/bot_graphql/model/mutation.gql @@ -4,4 +4,5 @@ type Mutation { level: LevelMutation user: UserMutation userJoinedGameServer: UserJoinedGameServerMutation + achievement: AchievementMutation } \ No newline at end of file diff --git a/kdb-bot/src/bot_graphql/model/query.gql b/kdb-bot/src/bot_graphql/model/query.gql index 69b5a74a..17d8634c 100644 --- a/kdb-bot/src/bot_graphql/model/query.gql +++ b/kdb-bot/src/bot_graphql/model/query.gql @@ -29,5 +29,8 @@ type Query { userCount: Int users(filter: UserFilter, page: Page, sort: Sort): [User] + achievementCount: Int + achievements(filter: AchievementFilter, page: Page, sort: Sort): [Achievement] + guilds(filter: GuildFilter): [Guild] } \ No newline at end of file diff --git a/kdb-bot/src/bot_graphql/mutation.py b/kdb-bot/src/bot_graphql/mutation.py index 56bfa7c5..d8287d96 100644 --- a/kdb-bot/src/bot_graphql/mutation.py +++ b/kdb-bot/src/bot_graphql/mutation.py @@ -1,6 +1,6 @@ from ariadne import MutationType -from bot_data.model.user_joined_game_server import UserJoinedGameServer +from bot_graphql.mutations.achievement_mutation import AchievementMutation from bot_graphql.mutations.auto_role_mutation import AutoRoleMutation from bot_graphql.mutations.auto_role_rule_mutation import AutoRoleRuleMutation from bot_graphql.mutations.level_mutation import LevelMutation @@ -15,6 +15,7 @@ class Mutation(MutationType): auto_role_rule_mutation: AutoRoleRuleMutation, level_mutation: LevelMutation, user_mutation: UserMutation, + achievement_mutation: AchievementMutation, user_joined_game_server: UserJoinedGameServerMutation, ): MutationType.__init__(self) @@ -23,4 +24,5 @@ class Mutation(MutationType): self.set_field("autoRoleRule", lambda *_: auto_role_rule_mutation) self.set_field("level", lambda *_: level_mutation) self.set_field("user", lambda *_: user_mutation) + self.set_field("achievement", lambda *_: achievement_mutation) self.set_field("userJoinedGameServer", lambda *_: user_joined_game_server) diff --git a/kdb-bot/src/bot_graphql/mutations/achievement_mutation.py b/kdb-bot/src/bot_graphql/mutations/achievement_mutation.py new file mode 100644 index 00000000..c7daa8c8 --- /dev/null +++ b/kdb-bot/src/bot_graphql/mutations/achievement_mutation.py @@ -0,0 +1,43 @@ +from cpl_core.database.context import DatabaseContextABC +from cpl_discord.service import DiscordBotServiceABC + +from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC +from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.model.user_role_enum import UserRoleEnum +from bot_graphql.abc.query_abc import QueryABC +from modules.permission.service.permission_service import PermissionService + + +class AchievementMutation(QueryABC): + def __init__( + self, + servers: ServerRepositoryABC, + achievements: AchievementRepositoryABC, + bot: DiscordBotServiceABC, + db: DatabaseContextABC, + permissions: PermissionService, + ): + QueryABC.__init__(self, "AchievementMutation") + + self._servers = servers + self._achievements = achievements + self._bot = bot + self._db = db + self._permissions = permissions + + self.set_field("updateAchievement", self.resolve_update_achievement) + + def resolve_update_achievement(self, *_, input: dict): + achievement = self._achievements.get_achievement_by_id(input["id"]) + self._can_user_mutate_data(achievement.server, UserRoleEnum.moderator) + + achievement.name = input["name"] if "name" in input else achievement.name + achievement.attribute = input["attribute"] if "attribute" in input else achievement.attribute + achievement.operator = input["operator"] if "operator" in input else achievement.operator + achievement.value = input["value"] if "value" in input else achievement.value + + self._achievements.update_achievement(achievement) + self._db.save_changes() + + achievement = self._achievements.get_achievement_by_id(input["id"]) + return achievement diff --git a/kdb-bot/src/bot_graphql/queries/achievement_query.py b/kdb-bot/src/bot_graphql/queries/achievement_query.py new file mode 100644 index 00000000..c9782973 --- /dev/null +++ b/kdb-bot/src/bot_graphql/queries/achievement_query.py @@ -0,0 +1,19 @@ +from cpl_core.database.context import DatabaseContextABC + +from bot_data.model.user_history import UserHistory +from bot_graphql.abc.data_query_with_history_abc import DataQueryWithHistoryABC + + +class AchievementQuery(DataQueryWithHistoryABC): + def __init__( + self, + db: DatabaseContextABC, + ): + DataQueryWithHistoryABC.__init__(self, "Achievement", "AchievementsHistory", UserHistory, db) + + self.set_field("id", lambda x: x.id) + self.set_field("name", lambda x: x.name) + self.set_field("attribute", lambda x: x.attribute) + self.set_field("operator", lambda x: x.operator) + self.set_field("value", lambda x: x.value) + self.set_field("server", lambda x: x.server) diff --git a/kdb-bot/src/bot_graphql/query.py b/kdb-bot/src/bot_graphql/query.py index 8133e4dc..976b1950 100644 --- a/kdb-bot/src/bot_graphql/query.py +++ b/kdb-bot/src/bot_graphql/query.py @@ -1,5 +1,6 @@ from cpl_discord.service import DiscordBotServiceABC +from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC from bot_data.abc.client_repository_abc import ClientRepositoryABC from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC @@ -10,6 +11,7 @@ from bot_data.abc.user_joined_server_repository_abc import UserJoinedServerRepos from bot_data.abc.user_joined_voice_channel_repository_abc import UserJoinedVoiceChannelRepositoryABC from bot_data.abc.user_repository_abc import UserRepositoryABC from bot_graphql.abc.query_abc import QueryABC +from bot_graphql.filter.achievement_filter import AchievementFilter from bot_graphql.filter.auto_role_filter import AutoRoleFilter from bot_graphql.filter.auto_role_rule_filter import AutoRoleRuleFilter from bot_graphql.filter.client_filter import ClientFilter @@ -34,6 +36,7 @@ class Query(QueryABC): user_joined_voice_channels: UserJoinedVoiceChannelRepositoryABC, user_joined_game_server: UserJoinedGameServerRepositoryABC, users: UserRepositoryABC, + achievements: AchievementRepositoryABC, ): QueryABC.__init__(self, "Query") @@ -59,6 +62,7 @@ class Query(QueryABC): UserJoinedGameServerFilter, ) self.add_collection("user", lambda *_: users.get_users(), UserFilter) + self.add_collection("achievement", lambda *_: achievements.get_achievements(), AchievementFilter) self.set_field("guilds", self._resolve_guilds) diff --git a/kdb-web/package.json b/kdb-web/package.json index 8e415632..e77d9ec0 100644 --- a/kdb-web/package.json +++ b/kdb-web/package.json @@ -51,4 +51,4 @@ "tslib": "^2.4.1", "typescript": "~4.9.5" } -} +} \ No newline at end of file diff --git a/kdb-web/src/assets/config.json b/kdb-web/src/assets/config.json index f9117e39..57eaafa7 100644 --- a/kdb-web/src/assets/config.json +++ b/kdb-web/src/assets/config.json @@ -25,4 +25,4 @@ "Name": "sh-edraft-dark-theme" } ] -} +} \ No newline at end of file From 0e5ec588fc85677c9ac5bd2c9ca7ad567e2ef68a Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 12 Jul 2023 11:23:14 +0200 Subject: [PATCH 04/23] Fixed achievement query #268_achievements --- .../migration/achievements_migration.py | 2 +- kdb-bot/src/bot_data/model/achievement.py | 8 ++++ .../achievements_repository_service.py | 6 +-- .../mutations/achievement_mutation.py | 37 +++++++++++++++++++ .../bot_graphql/mutations/level_mutation.py | 1 + .../bot_graphql/queries/achievement_query.py | 12 +++--- 6 files changed, 56 insertions(+), 10 deletions(-) diff --git a/kdb-bot/src/bot_data/migration/achievements_migration.py b/kdb-bot/src/bot_data/migration/achievements_migration.py index 160d379c..294d1b8b 100644 --- a/kdb-bot/src/bot_data/migration/achievements_migration.py +++ b/kdb-bot/src/bot_data/migration/achievements_migration.py @@ -20,11 +20,11 @@ class AchievementsMigration(MigrationABC): 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, + `ServerId` BIGINT, `CreatedAt` DATETIME(6) NULL DEFAULT CURRENT_TIMESTAMP(6), `LastModifiedAt` DATETIME(6) NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), PRIMARY KEY(`Id`), diff --git a/kdb-bot/src/bot_data/model/achievement.py b/kdb-bot/src/bot_data/model/achievement.py index b9a44089..a4c8b831 100644 --- a/kdb-bot/src/bot_data/model/achievement.py +++ b/kdb-bot/src/bot_data/model/achievement.py @@ -41,6 +41,14 @@ class Achievement(TableABC): def name(self, value: str): self._name = value + @property + def attribute(self) -> str: + return self._attribute + + @attribute.setter + def attribute(self, value: str): + self._attribute = value + @property def operator(self) -> str: return self._operator diff --git a/kdb-bot/src/bot_data/service/achievements_repository_service.py b/kdb-bot/src/bot_data/service/achievements_repository_service.py index d910a6c2..d9c157e2 100644 --- a/kdb-bot/src/bot_data/service/achievements_repository_service.py +++ b/kdb-bot/src/bot_data/service/achievements_repository_service.py @@ -23,11 +23,11 @@ class AchievementRepositoryService(AchievementRepositoryABC): def _from_result(self, result: tuple): return Achievement( + result[1], + result[2], result[3], result[4], - result[5], - result[6], - self._servers.get_server_by_id(result[2]), + self._servers.get_server_by_id(result[5]), result[6], result[7], id=result[0], diff --git a/kdb-bot/src/bot_graphql/mutations/achievement_mutation.py b/kdb-bot/src/bot_graphql/mutations/achievement_mutation.py index c7daa8c8..99459515 100644 --- a/kdb-bot/src/bot_graphql/mutations/achievement_mutation.py +++ b/kdb-bot/src/bot_graphql/mutations/achievement_mutation.py @@ -3,6 +3,7 @@ from cpl_discord.service import DiscordBotServiceABC 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 from bot_data.model.user_role_enum import UserRoleEnum from bot_graphql.abc.query_abc import QueryABC from modules.permission.service.permission_service import PermissionService @@ -25,7 +26,34 @@ class AchievementMutation(QueryABC): self._db = db self._permissions = permissions + self.set_field("createAchievement", self.resolve_create_achievement) self.set_field("updateAchievement", self.resolve_update_achievement) + self.set_field("deleteAchievement", self.resolve_delete_achievement) + + def resolve_create_achievement(self, *_, input: dict): + server = self._servers.get_server_by_id(input["serverId"]) + self._can_user_mutate_data(server, UserRoleEnum.admin) + + achievement = Achievement( + input["name"], + input["attribute"], + input["operator"], + input["value"], + server, + ) + self._achievements.add_achievement(achievement) + self._db.save_changes() + + def get_new_achievement(a: Achievement): + return ( + a.name == achievement.name + and a.attribute == achievement.attribute + and a.operator == achievement.operator + and a.value == achievement.value + and a.server.id == server.id + ) + + return self._achievements.get_achievements_by_server_id(achievement.server.id).where(get_new_achievement).last() def resolve_update_achievement(self, *_, input: dict): achievement = self._achievements.get_achievement_by_id(input["id"]) @@ -41,3 +69,12 @@ class AchievementMutation(QueryABC): achievement = self._achievements.get_achievement_by_id(input["id"]) return achievement + + def resolve_delete_achievement(self, *_, id: int): + achievement = self._achievements.get_achievement_by_id(id) + self._can_user_mutate_data(achievement.server, UserRoleEnum.admin) + + self._achievements.delete_achievement(achievement) + self._db.save_changes() + + return achievement diff --git a/kdb-bot/src/bot_graphql/mutations/level_mutation.py b/kdb-bot/src/bot_graphql/mutations/level_mutation.py index 47f61e7c..215538b7 100644 --- a/kdb-bot/src/bot_graphql/mutations/level_mutation.py +++ b/kdb-bot/src/bot_graphql/mutations/level_mutation.py @@ -50,6 +50,7 @@ class LevelMutation(QueryABC): and l.color == level.color and l.min_xp == level.min_xp and l.permissions == level.permissions + and l.server.id == server.id ) self._bot.loop.create_task(self._level_seeder.seed()) diff --git a/kdb-bot/src/bot_graphql/queries/achievement_query.py b/kdb-bot/src/bot_graphql/queries/achievement_query.py index c9782973..46ee137d 100644 --- a/kdb-bot/src/bot_graphql/queries/achievement_query.py +++ b/kdb-bot/src/bot_graphql/queries/achievement_query.py @@ -11,9 +11,9 @@ class AchievementQuery(DataQueryWithHistoryABC): ): DataQueryWithHistoryABC.__init__(self, "Achievement", "AchievementsHistory", UserHistory, db) - self.set_field("id", lambda x: x.id) - self.set_field("name", lambda x: x.name) - self.set_field("attribute", lambda x: x.attribute) - self.set_field("operator", lambda x: x.operator) - self.set_field("value", lambda x: x.value) - self.set_field("server", lambda x: x.server) + self.set_field("id", lambda x, *_: x.id) + self.set_field("name", lambda x, *_: x.name) + self.set_field("attribute", lambda x, *_: x.attribute) + self.set_field("operator", lambda x, *_: x.operator) + self.set_field("value", lambda x, *_: x.value) + self.set_field("server", lambda x, *_: x.server) From 3db548fb86d0f6568d77fd3f07cafa86e5c7310e Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 12 Jul 2023 11:26:21 +0200 Subject: [PATCH 05/23] Fixed achievement query #268_achievements --- kdb-bot/src/bot_data/model/achievement.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kdb-bot/src/bot_data/model/achievement.py b/kdb-bot/src/bot_data/model/achievement.py index a4c8b831..e01bafad 100644 --- a/kdb-bot/src/bot_data/model/achievement.py +++ b/kdb-bot/src/bot_data/model/achievement.py @@ -106,7 +106,7 @@ class Achievement(TableABC): def udpate_string(self) -> str: return str( f""" - UPDATE `Levels` + UPDATE `Achievements` SET `Name` = '{self._name}', `Attribute` = '{self._attribute}', `Operator` = '{self._operator}', From 109bbf3729c48960c7efcf618a7aed83f60cb389 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 12 Jul 2023 11:56:52 +0200 Subject: [PATCH 06/23] First step to add achievements to frontend #268_achievements --- kdb-bot/src/bot_graphql/model/server.gql | 3 + .../src/bot_graphql/queries/server_query.py | 7 +- .../src/app/models/data/achievement.model.ts | 20 ++ .../src/app/models/graphql/mutations.model.ts | 42 +++ .../src/app/models/graphql/queries.model.ts | 44 +++ kdb-web/src/app/models/graphql/query.model.ts | 6 + .../src/app/models/graphql/result.model.ts | 9 + .../achievements-routing.module.ts | 16 ++ .../achievements/achievements.module.ts | 18 ++ .../achievement/achievement.component.html | 215 ++++++++++++++ .../achievement/achievement.component.scss | 0 .../achievement/achievement.component.spec.ts | 23 ++ .../achievement/achievement.component.ts | 268 ++++++++++++++++++ .../view/server/server-routing.module.ts | 3 +- .../app/services/sidebar/sidebar.service.ts | 11 +- kdb-web/src/assets/i18n/de.json | 26 ++ 16 files changed, 708 insertions(+), 3 deletions(-) create mode 100644 kdb-web/src/app/models/data/achievement.model.ts create mode 100644 kdb-web/src/app/modules/view/server/achievements/achievements-routing.module.ts create mode 100644 kdb-web/src/app/modules/view/server/achievements/achievements.module.ts create mode 100644 kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html create mode 100644 kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.scss create mode 100644 kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.spec.ts create mode 100644 kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts diff --git a/kdb-bot/src/bot_graphql/model/server.gql b/kdb-bot/src/bot_graphql/model/server.gql index db94dc89..90f07f95 100644 --- a/kdb-bot/src/bot_graphql/model/server.gql +++ b/kdb-bot/src/bot_graphql/model/server.gql @@ -16,6 +16,9 @@ type Server implements TableWithHistoryQuery { userCount: Int users(filter: UserFilter, page: Page, sort: Sort): [User] + achievementCount: Int + achievements(filter: AchievementFilter, page: Page, sort: Sort): [Achievement] + createdAt: String modifiedAt: String diff --git a/kdb-bot/src/bot_graphql/queries/server_query.py b/kdb-bot/src/bot_graphql/queries/server_query.py index 39012d49..52d53d23 100644 --- a/kdb-bot/src/bot_graphql/queries/server_query.py +++ b/kdb-bot/src/bot_graphql/queries/server_query.py @@ -1,6 +1,7 @@ from cpl_core.database.context import DatabaseContextABC from cpl_discord.service import DiscordBotServiceABC +from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC from bot_data.abc.client_repository_abc import ClientRepositoryABC from bot_data.abc.level_repository_abc import LevelRepositoryABC @@ -9,8 +10,8 @@ from bot_data.abc.user_joined_voice_channel_repository_abc import UserJoinedVoic from bot_data.abc.user_repository_abc import UserRepositoryABC from bot_data.model.server import Server from bot_data.model.server_history import ServerHistory -from bot_graphql.abc.data_query_abc import DataQueryABC from bot_graphql.abc.data_query_with_history_abc import DataQueryWithHistoryABC +from bot_graphql.filter.achievement_filter import AchievementFilter from bot_graphql.filter.auto_role_filter import AutoRoleFilter from bot_graphql.filter.client_filter import ClientFilter from bot_graphql.filter.level_filter import LevelFilter @@ -28,6 +29,7 @@ class ServerQuery(DataQueryWithHistoryABC): users: UserRepositoryABC, ujs: UserJoinedServerRepositoryABC, ujvs: UserJoinedVoiceChannelRepositoryABC, + achievements: AchievementRepositoryABC, ): DataQueryWithHistoryABC.__init__(self, "Server", "ServersHistory", ServerHistory, db) @@ -54,6 +56,9 @@ class ServerQuery(DataQueryWithHistoryABC): ) self.add_collection("level", lambda server, *_: self._levels.get_levels_by_server_id(server.id), LevelFilter) self.add_collection("user", lambda server, *_: self._users.get_users_by_server_id(server.id), UserFilter) + self.add_collection( + "achievement", lambda server, *_: achievements.get_achievements_by_server_id(server.id), AchievementFilter + ) @staticmethod def resolve_id(server: Server, *_): diff --git a/kdb-web/src/app/models/data/achievement.model.ts b/kdb-web/src/app/models/data/achievement.model.ts new file mode 100644 index 00000000..0b1af096 --- /dev/null +++ b/kdb-web/src/app/models/data/achievement.model.ts @@ -0,0 +1,20 @@ +import { DataWithHistory } from "./data.model"; +import { Server, ServerFilter } from "./server.model"; + +export interface Achievement extends DataWithHistory { + id?: number; + name?: string; + attribute?: string; + operator?: string; + value?: string; + server?: Server; +} + +export interface AchievementFilter { + id?: number; + name?: string; + attribute?: string; + operator?: string; + value?: string; + server?: ServerFilter; +} diff --git a/kdb-web/src/app/models/graphql/mutations.model.ts b/kdb-web/src/app/models/graphql/mutations.model.ts index 0cba6ef3..db95bd86 100644 --- a/kdb-web/src/app/models/graphql/mutations.model.ts +++ b/kdb-web/src/app/models/graphql/mutations.model.ts @@ -121,4 +121,46 @@ export class Mutations { } } `; + + static createAchievement = ` + mutation createAchievement($name: String, $attribute: String, $operator: String, $value: String, $serverId: ID) { + level { + createAchievement(input: { name: $name, attribute: $attribute, operator: $operator, value: $value, serverId: $serverId}) { + id + name + attribute + operator + value + server { + id + } + } + } + } + `; + + static updateAchievement = ` + mutation updateAchievement($name: String, $attribute: String, $operator: String, $value: String, $serverId: ID) { + level { + updateAchievement(input: { name: $name, attribute: $attribute, operator: $operator, value: $value}) { + id + name + attribute + operator + value + } + } + } + `; + + static deleteAchievement = ` + mutation deleteAchievement($id: ID) { + level { + deleteLevel(id: $id) { + id + name + } + } + } + `; } diff --git a/kdb-web/src/app/models/graphql/queries.model.ts b/kdb-web/src/app/models/graphql/queries.model.ts index f3d68257..df3cf125 100644 --- a/kdb-web/src/app/models/graphql/queries.model.ts +++ b/kdb-web/src/app/models/graphql/queries.model.ts @@ -90,6 +90,50 @@ export class Queries { } `; + static achievementQuery = ` + query AchievementList($serverId: ID, $filter: AchievementFilter, $page: Page, $sort: Sort) { + servers(filter: {id: $serverId}) { + achievementCount + achievements(filter: $filter, page: $page, sort: $sort) { + id + name + attribute + operator + value + server { + id + name + } + createdAt + modifiedAt + } + } + } + `; + + static achievementWithHistoryQuery = ` + query AchievementHistory($serverId: ID, $id: ID) { + servers(filter: {id: $serverId}) { + achievementCount + achievements(filter: {id: $id}) { + id + + history { + id + name + attribute + operator + value + server + deleted + dateFrom + dateTo + } + } + } + } + `; + static usersQuery = ` query UsersList($serverId: ID, $filter: UserFilter, $page: Page, $sort: Sort) { servers(filter: {id: $serverId}) { diff --git a/kdb-web/src/app/models/graphql/query.model.ts b/kdb-web/src/app/models/graphql/query.model.ts index 3f7e85a1..60e1964d 100644 --- a/kdb-web/src/app/models/graphql/query.model.ts +++ b/kdb-web/src/app/models/graphql/query.model.ts @@ -3,6 +3,7 @@ import { User } from "../data/user.model"; import { AutoRole, AutoRoleRule } from "../data/auto_role.model"; import { Guild } from "../data/discord.model"; import { Level } from "../data/level.model"; +import { Achievement } from "../data/achievement.model"; export interface Query { serverCount: number; @@ -23,6 +24,11 @@ export interface LevelListQuery { levels: Level[]; } +export interface AchievementListQuery { + achievementCount: number; + achievements: Achievement[]; +} + export interface AutoRoleQuery { autoRoleCount: number; autoRoles: AutoRole[]; diff --git a/kdb-web/src/app/models/graphql/result.model.ts b/kdb-web/src/app/models/graphql/result.model.ts index d3d6e4a4..d77c2cdc 100644 --- a/kdb-web/src/app/models/graphql/result.model.ts +++ b/kdb-web/src/app/models/graphql/result.model.ts @@ -2,6 +2,7 @@ import { User } from "../data/user.model"; import { AutoRole, AutoRoleRule } from "../data/auto_role.model"; import { Level } from "../data/level.model"; import { Server } from "../data/server.model"; +import { Achievement } from "../data/achievement.model"; export interface GraphQLResult { data: { @@ -45,3 +46,11 @@ export interface LevelMutationResult { deleteLevel?: Level }; } + +export interface AchievementMutationResult { + achievement: { + createAchievement?: Achievement + updateAchievement?: Achievement + deleteAchievement?: Achievement + }; +} diff --git a/kdb-web/src/app/modules/view/server/achievements/achievements-routing.module.ts b/kdb-web/src/app/modules/view/server/achievements/achievements-routing.module.ts new file mode 100644 index 00000000..ae847fb9 --- /dev/null +++ b/kdb-web/src/app/modules/view/server/achievements/achievements-routing.module.ts @@ -0,0 +1,16 @@ +import {NgModule} from "@angular/core"; +import {RouterModule, Routes} from "@angular/router"; +import { AchievementComponent } from "./components/achievement/achievement.component"; + +const routes: Routes = [ + + {path: '', component: AchievementComponent}, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class AchievementsRoutingModule { + +} diff --git a/kdb-web/src/app/modules/view/server/achievements/achievements.module.ts b/kdb-web/src/app/modules/view/server/achievements/achievements.module.ts new file mode 100644 index 00000000..ccf7e426 --- /dev/null +++ b/kdb-web/src/app/modules/view/server/achievements/achievements.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { AchievementComponent } from "./components/achievement/achievement.component"; +import { AchievementsRoutingModule } from "./achievements-routing.module"; +import { SharedModule } from "../../../shared/shared.module"; + + +@NgModule({ + declarations: [ + AchievementComponent + ], + imports: [ + CommonModule, + AchievementsRoutingModule, + SharedModule + ] +}) +export class AchievementsModule { } diff --git a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html new file mode 100644 index 00000000..83879d48 --- /dev/null +++ b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html @@ -0,0 +1,215 @@ +

+ {{'view.server.achievements.header' | translate}} +

+
+
+ + + +
+
+ {{achievements.length}} {{'common.of' | translate}} + {{dt.totalRecords}} + + {{'view.server.achievements.achievements' | translate}} +
+ +
+ + +
+
+
+ + + + +
+
{{'common.id' | translate}}
+ +
+ + + +
+
{{'view.server.achievements.headers.name' | translate}}
+ +
+ + + +
+
{{'view.server.achievements.headers.attribute' | translate}}
+ +
+ + + +
+
{{'view.server.achievements.headers.operator' | translate}}
+ +
+ + + +
+
{{'view.server.achievements.headers.value' | translate}}
+ +
+ + + +
+
{{'common.created_at' | translate}}
+
+ + + +
+
{{'common.modified_at' | translate}}
+
+ + + +
+
{{'common.actions' | translate}}
+
+ + + + + +
+ +
+ + +
+ +
+ + + + + + + + +
+ + + + + + + {{achievement.id}} + + + {{achievement.id}} + + + + + + + + + + + {{achievement.name}} + + + + + + + + + + + {{achievement.attribute}} + + + + + + + + + + + {{achievement.operator}} + + + + + + + + + + + {{achievement.value}} + + + + + + + + {{achievement.createdAt | date:'dd.MM.yy HH:mm'}} + + + {{achievement.createdAt | date:'dd.MM.yy HH:mm'}} + + + + + + + {{achievement.modifiedAt | date:'dd.MM.yy HH:mm'}} + + + {{achievement.modifiedAt | date:'dd.MM.yy HH:mm'}} + + + + +
+ + + + + + +
+ + +
+ + + + + {{'common.no_entries_found' | translate}} + + + + + + +
+
+
+ diff --git a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.scss b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.spec.ts b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.spec.ts new file mode 100644 index 00000000..771e808b --- /dev/null +++ b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AchievementComponent } from './achievement.component'; + +describe('AchievementComponent', () => { + let component: AchievementComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AchievementComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(AchievementComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts new file mode 100644 index 00000000..5eb90432 --- /dev/null +++ b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts @@ -0,0 +1,268 @@ +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { Achievement, AchievementFilter } from "../../../../../../models/data/achievement.model"; +import { FormBuilder, FormControl, FormGroup } from "@angular/forms"; +import { Page } from "../../../../../../models/graphql/filter/page.model"; +import { Sort, SortDirection } from "../../../../../../models/graphql/filter/sort.model"; +import { Subject, throwError } from "rxjs"; +import { Server } from "../../../../../../models/data/server.model"; +import { UserDTO } from "../../../../../../models/auth/auth-user.dto"; +import { Queries } from "../../../../../../models/graphql/queries.model"; +import { AuthService } from "../../../../../../services/auth/auth.service"; +import { SpinnerService } from "../../../../../../services/spinner/spinner.service"; +import { ToastService } from "../../../../../../services/toast/toast.service"; +import { ConfirmationDialogService } from "../../../../../../services/confirmation-dialog/confirmation-dialog.service"; +import { TranslateService } from "@ngx-translate/core"; +import { DataService } from "../../../../../../services/data/data.service"; +import { SidebarService } from "../../../../../../services/sidebar/sidebar.service"; +import { ActivatedRoute } from "@angular/router"; +import { AchievementListQuery, Query } from "../../../../../../models/graphql/query.model"; +import { catchError, debounceTime, takeUntil } from "rxjs/operators"; +import { LazyLoadEvent } from "primeng/api"; +import { Table } from "primeng/table"; +import { User } from "../../../../../../models/data/user.model"; +import { AchievementMutationResult, UpdateUserMutationResult } from "../../../../../../models/graphql/result.model"; +import { Mutations } from "../../../../../../models/graphql/mutations.model"; + +@Component({ + selector: "app-achievement", + templateUrl: "./achievement.component.html", + styleUrls: ["./achievement.component.scss"] +}) +export class AchievementComponent implements OnInit, OnDestroy { + public achievements: Achievement[] = []; + public loading = true; + + public isEditingNew: boolean = false; + + public filterForm!: FormGroup<{ + id: FormControl, + name: FormControl, + color: FormControl, + min_xp: FormControl, + permissions: FormControl, + }>; + + public filter: AchievementFilter = {}; + public page: Page = { + pageSize: undefined, + pageIndex: undefined + }; + public sort: Sort = { + sortColumn: undefined, + sortDirection: undefined + }; + + public totalRecords: number = 0; + + public clonedAchievements: { [s: string]: Achievement; } = {}; + + private unsubscriber = new Subject(); + private server: Server = {}; + public user: UserDTO | null = null; + + query: string = Queries.achievementWithHistoryQuery; + + public constructor( + private authService: AuthService, + private spinner: SpinnerService, + private toastService: ToastService, + private confirmDialog: ConfirmationDialogService, + private fb: FormBuilder, + private translate: TranslateService, + private data: DataService, + private sidebar: SidebarService, + private route: ActivatedRoute) { + } + + public ngOnInit(): void { + this.setFilterForm(); + this.data.getServerFromRoute(this.route).then(async server => { + this.server = server; + this.loadNextPage(); + let authUser = await this.authService.getLoggedInUser(); + this.user = authUser?.users?.find(u => u.server == this.server.id) ?? null; + }); + } + + public ngOnDestroy(): void { + this.unsubscriber.next(); + this.unsubscriber.complete(); + } + + public loadNextPage(): void { + this.loading = true; + this.data.query(Queries.achievementQuery, { + serverId: this.server.id, filter: this.filter, page: this.page, sort: this.sort + }, + (data: Query) => { + return data.servers[0]; + } + ).subscribe(data => { + this.totalRecords = data.achievementCount; + this.achievements = data.achievements; + this.spinner.hideSpinner(); + this.loading = false; + }); + } + + public setFilterForm(): void { + this.filterForm = this.fb.group({ + id: new FormControl(null), + name: new FormControl(null), + color: new FormControl(null), + min_xp: new FormControl(null), + permissions: new FormControl(null) + }); + + this.filterForm.valueChanges.pipe( + takeUntil(this.unsubscriber), + debounceTime(600) + ).subscribe(changes => { + if (changes.id) { + this.filter.id = changes.id; + } else { + this.filter.id = undefined; + } + + if (changes.name) { + this.filter.name = changes.name; + } else { + this.filter.name = undefined; + } + + if (this.page.pageSize) + this.page.pageSize = 10; + + if (this.page.pageIndex) + this.page.pageIndex = 0; + + this.loadNextPage(); + }); + } + + public newAchievementTemplate: Achievement = { + id: 0, + createdAt: "", + modifiedAt: "" + }; + + public nextPage(event: LazyLoadEvent): void { + this.page.pageSize = event.rows ?? 0; + if (event.first != null && event.rows != null) + this.page.pageIndex = event.first / event.rows; + this.sort.sortColumn = event.sortField ?? undefined; + this.sort.sortDirection = event.sortOrder === 1 ? SortDirection.ASC : event.sortOrder === -1 ? SortDirection.DESC : SortDirection.ASC; + + this.loadNextPage(); + } + + public resetFilters(): void { + this.filterForm.reset(); + } + + public onRowEditInit(table: Table, user: User, index: number): void { + this.clonedAchievements[index] = { ...user }; + } + + public onRowEditSave(table: Table, newAchievement: Achievement, index: number): void { + if (this.isEditingNew && JSON.stringify(newAchievement) === JSON.stringify(this.newAchievementTemplate)) { + this.isEditingNew = false; + this.achievements.splice(index, 1); + return; + } + + if (!newAchievement.id && !this.isEditingNew || !newAchievement.name && !newAchievement.attribute && !newAchievement?.operator && !newAchievement?.value) { + return; + } + + if (this.isEditingNew) { + this.spinner.showSpinner(); + this.data.mutation(Mutations.createAchievement, { + name: newAchievement.name, + attribute: newAchievement.attribute, + operator: newAchievement.operator, + value: newAchievement.value, + serverId: this.server.id + } + ).pipe(catchError(err => { + this.isEditingNew = false; + this.spinner.hideSpinner(); + this.toastService.error(this.translate.instant("view.server.achievements.message.achievement_create_failed"), this.translate.instant("view.server.achievements.message.achievement_create_failed_d")); + return throwError(err); + })).subscribe(result => { + this.isEditingNew = false; + this.spinner.hideSpinner(); + this.toastService.success(this.translate.instant("view.server.achievements.message.achievement_create"), this.translate.instant("view.server.achievements.message.achievement_create_d", { name: result.achievement.createAchievement?.name })); + this.loadNextPage(); + }); + return; + } + + this.spinner.showSpinner(); + this.data.mutation(Mutations.updateAchievement, { + id: newAchievement.id, + name: newAchievement.name, + attribute: newAchievement.attribute, + operator: newAchievement.operator, + value: newAchievement.value + } + ).pipe(catchError(err => { + this.spinner.hideSpinner(); + this.toastService.error(this.translate.instant("view.server.achievements.message.achievement_update_failed"), this.translate.instant("view.server.achievements.message.achievement_update_failed_d", { name: newAchievement.name })); + return throwError(err); + })).subscribe(_ => { + this.spinner.hideSpinner(); + this.toastService.success(this.translate.instant("view.server.achievements.message.achievement_update"), this.translate.instant("view.server.achievements.message.achievement_update_d", { name: newAchievement.name })); + this.loadNextPage(); + }); + + } + + public onRowEditCancel(index: number): void { + if (this.isEditingNew) { + this.achievements.splice(index, 1); + delete this.clonedAchievements[index]; + this.isEditingNew = false; + return; + } + + this.achievements[index] = this.clonedAchievements[index]; + delete this.clonedAchievements[index]; + } + + public deleteAchievement(achievement: Achievement): void { + this.confirmDialog.confirmDialog( + this.translate.instant("view.server.achievements.message.achievement_delete"), this.translate.instant("view.server.achievements.message.achievement_delete_q", { name: achievement.name }), + () => { + this.spinner.showSpinner(); + this.data.mutation(Mutations.deleteAchievement, { + id: achievement.id + } + ).pipe(catchError(err => { + this.spinner.hideSpinner(); + this.toastService.error(this.translate.instant("view.server.achievements.message.achievement_delete_failed"), this.translate.instant("view.server.achievements.message.achievement_delete_failed_d", { name: achievement.name })); + return throwError(err); + })).subscribe(l => { + this.spinner.hideSpinner(); + this.toastService.success(this.translate.instant("view.server.achievements.message.achievement_deleted"), this.translate.instant("view.server.achievements.message.achievement_deleted_d", { name: achievement.name })); + this.loadNextPage(); + }); + }); + } + + public addAchievement(table: Table): void { + const newAchievement = JSON.parse(JSON.stringify(this.newAchievementTemplate)); + newAchievement.id = Math.max.apply(Math, this.achievements.map(l => { + return l.id ?? 0; + })) + 1; + + this.achievements.push(newAchievement); + + table.initRowEdit(newAchievement); + + const index = this.achievements.findIndex(l => l.id == newAchievement.id); + this.onRowEditInit(table, newAchievement, index); + + this.isEditingNew = true; + } +} diff --git a/kdb-web/src/app/modules/view/server/server-routing.module.ts b/kdb-web/src/app/modules/view/server/server-routing.module.ts index 147c40ea..a83dab96 100644 --- a/kdb-web/src/app/modules/view/server/server-routing.module.ts +++ b/kdb-web/src/app/modules/view/server/server-routing.module.ts @@ -9,7 +9,8 @@ const routes: Routes = [ { path: "members", component: MembersComponent }, { path: "members/:memberId", component: ProfileComponent }, { path: "auto-roles", loadChildren: () => import("./auto-role/auto-role.module").then(m => m.AutoRoleModule) }, - { path: "levels", loadChildren: () => import("./levels/levels.module").then(m => m.LevelsModule) } + { path: "levels", loadChildren: () => import("./levels/levels.module").then(m => m.LevelsModule) }, + { path: "achievements", loadChildren: () => import("./achievements/achievements.module").then(m => m.AchievementsModule) } ]; @NgModule({ diff --git a/kdb-web/src/app/services/sidebar/sidebar.service.ts b/kdb-web/src/app/services/sidebar/sidebar.service.ts index 1bd9476a..81ab4548 100644 --- a/kdb-web/src/app/services/sidebar/sidebar.service.ts +++ b/kdb-web/src/app/services/sidebar/sidebar.service.ts @@ -24,6 +24,7 @@ export class SidebarService { serverMembers: MenuItem = {}; serverAutoRoles: MenuItem = {}; serverLevels: MenuItem = {}; + serverAchievements: MenuItem = {}; serverMenu: MenuItem = {}; adminConfig: MenuItem = {}; adminUsers: MenuItem = {}; @@ -102,12 +103,19 @@ export class SidebarService { routerLink: `server/${this.server$.value?.id}/levels` }; + this.serverAchievements = { + label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.achievements") : "", + icon: "pi pi-angle-double-up", + visible: true, + routerLink: `server/${this.server$.value?.id}/achievements` + }; + this.serverMenu = { label: this.isSidebarOpen ? this.server$.value?.name : "", icon: "pi pi-server", visible: false, expanded: true, - items: [this.serverDashboard, this.serverProfile, this.serverMembers, this.serverAutoRoles, this.serverLevels] + items: [this.serverDashboard, this.serverProfile, this.serverMembers, this.serverAutoRoles, this.serverLevels, this.serverAchievements] }; this.adminConfig = { label: this.isSidebarOpen ? this.translateService.instant("sidebar.config") : "", @@ -142,6 +150,7 @@ export class SidebarService { this.serverMembers.visible = !!user?.isModerator; this.serverAutoRoles.visible = !!user?.isModerator; this.serverLevels.visible = !!user?.isModerator; + this.serverAchievements.visible = !!user?.isModerator; } else { this.serverMenu.visible = false; } diff --git a/kdb-web/src/assets/i18n/de.json b/kdb-web/src/assets/i18n/de.json index 7a1cdf40..4078a0be 100644 --- a/kdb-web/src/assets/i18n/de.json +++ b/kdb-web/src/assets/i18n/de.json @@ -397,6 +397,32 @@ "level_update_failed_d": "Die Bearbeitung des Levels ist fehlgeschlagen!" } }, + "achievements": { + "header": "Errungenschaften", + "headers": { + "name": "Name", + "attribute": "Attribut", + "operator": "Operator", + "value": "Wert" + }, + "achievements": "Errungenschaften", + "message": { + "achievement_create": "Errungenschaft erstellt", + "achievement_create_d": "Errungenschaft {{name}} erfolgreich erstellt", + "achievement_create_failed": "Errungenschaft Erstellung fehlgeschlagen", + "achievement_create_failed_d": "Die Erstellung der Errungenschaft ist fehlgeschlagen!", + "achievement_delete": "Errungenschaft löschen", + "achievement_delete_failed": "Errungenschaft Löschung fehlgeschlagen", + "achievement_delete_failed_d": "Die Löschung der Errungenschaft {{name}} ist fehlgeschlagen!", + "achievement_delete_q": "Sind Sie sich sicher, dass Sie das Errungenschaft {{name}} löschen möchten?", + "achievement_deleted": "Errungenschaft gelöscht", + "achievement_deleted_d": "Errungenschaft {{name}} erfolgreich gelöscht", + "achievement_update": "Errungenschaft bearbeitet", + "achievement_update_d": "Errungenschaft {{name}} erfolgreich bearbeitet", + "achievement_update_failed": "Errungenschaft Bearbeitung fehlgeschlagen", + "achievement_update_failed_d": "Die Bearbeitung der Errungenschaft ist fehlgeschlagen!" + } + }, "members": { "header": "Mitglieder", "headers": { From 8aa96482c12fe7088a24cfbef1460c47aba3b22b Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 12 Jul 2023 13:11:26 +0200 Subject: [PATCH 07/23] Fixed server cache & improved frontend implementation #268_achievements --- kdb-bot/src/bot_data/service/cache_service.py | 19 ++++++++++++++----- .../service/server_repository_service.py | 5 +++-- .../src/app/models/graphql/mutations.model.ts | 12 ++++++------ .../achievement/achievement.component.html | 12 ++++++------ kdb-web/src/assets/i18n/de.json | 3 ++- 5 files changed, 31 insertions(+), 20 deletions(-) diff --git a/kdb-bot/src/bot_data/service/cache_service.py b/kdb-bot/src/bot_data/service/cache_service.py index 31dcd73a..50d05e7e 100644 --- a/kdb-bot/src/bot_data/service/cache_service.py +++ b/kdb-bot/src/bot_data/service/cache_service.py @@ -5,12 +5,21 @@ from bot_data.model.server import Server class CacheService: def __init__(self): - self._cached_server = List(Server) + self._cached_servers = List(Server) @property def cached_server(self) -> List[Server]: - return self._cached_server + return self._cached_servers - @cached_server.setter - def cached_server(self, value: List[Server]): - self._cached_server = value + def add_server(self, server: Server): + if self._cached_servers.where(lambda x: x.id == server.id).count() > 0: + return + self._cached_servers.add(server) + + def add_servers(self, servers: List[Server]): + new_ids = servers.select(lambda x: x.id) + for s in self._cached_servers: + if s.id in new_ids: + return + + self._cached_servers.extend(servers) diff --git a/kdb-bot/src/bot_data/service/server_repository_service.py b/kdb-bot/src/bot_data/service/server_repository_service.py index 2d158fad..924cc036 100644 --- a/kdb-bot/src/bot_data/service/server_repository_service.py +++ b/kdb-bot/src/bot_data/service/server_repository_service.py @@ -26,7 +26,7 @@ class ServerRepositoryService(ServerRepositoryABC): for result in results: servers.append(Server(result[1], result[2], result[3], id=result[0])) - self._cache.cached_server = List(Server, servers) + self._cache.add_servers(servers) return servers def get_filtered_servers(self, criteria: ServerSelectCriteria) -> FilteredResult: @@ -56,13 +56,14 @@ class ServerRepositoryService(ServerRepositoryABC): def get_server_by_id(self, server_id: int) -> Server: cs = self._cache.cached_server.where(lambda x: x.id == server_id).single_or_default() + if cs is not None: return cs self._logger.trace(__name__, f"Send SQL command: {Server.get_select_by_id_string(server_id)}") result = self._context.select(Server.get_select_by_id_string(server_id))[0] server = Server(result[1], result[2], result[3], id=result[0]) - self._cache.cached_server.add(server) + self._cache.add_server(server) return server def get_server_by_discord_id(self, discord_id: int) -> Server: diff --git a/kdb-web/src/app/models/graphql/mutations.model.ts b/kdb-web/src/app/models/graphql/mutations.model.ts index db95bd86..df3b539d 100644 --- a/kdb-web/src/app/models/graphql/mutations.model.ts +++ b/kdb-web/src/app/models/graphql/mutations.model.ts @@ -124,7 +124,7 @@ export class Mutations { static createAchievement = ` mutation createAchievement($name: String, $attribute: String, $operator: String, $value: String, $serverId: ID) { - level { + achievement { createAchievement(input: { name: $name, attribute: $attribute, operator: $operator, value: $value, serverId: $serverId}) { id name @@ -140,9 +140,9 @@ export class Mutations { `; static updateAchievement = ` - mutation updateAchievement($name: String, $attribute: String, $operator: String, $value: String, $serverId: ID) { - level { - updateAchievement(input: { name: $name, attribute: $attribute, operator: $operator, value: $value}) { + mutation updateAchievement($id: ID, $name: String, $attribute: String, $operator: String, $value: String) { + achievement { + updateAchievement(input: { id: $id, name: $name, attribute: $attribute, operator: $operator, value: $value}) { id name attribute @@ -155,8 +155,8 @@ export class Mutations { static deleteAchievement = ` mutation deleteAchievement($id: ID) { - level { - deleteLevel(id: $id) { + achievement { + deleteAchievement(id: $id) { id name } diff --git a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html index 83879d48..7bdfc233 100644 --- a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html +++ b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html @@ -18,7 +18,7 @@
+ (click)="onRowEditInit(dt, achievement, ri)" [disabled]="!user || !user.isModerator && !user.isAdmin"> + (click)="deleteAchievement(achievement)" [disabled]="!user || !user.isModerator && !user.isAdmin"> + icon="pi pi-check-circle" (click)="onRowEditSave(dt, achievement, ri)" [disabled]="!user || !user.isModerator && !user.isAdmin"> + icon="pi pi-times-circle" (click)="onRowEditCancel(ri)" [disabled]="!user || !user.isModerator && !user.isAdmin">
diff --git a/kdb-web/src/assets/i18n/de.json b/kdb-web/src/assets/i18n/de.json index 4078a0be..70139bd4 100644 --- a/kdb-web/src/assets/i18n/de.json +++ b/kdb-web/src/assets/i18n/de.json @@ -280,7 +280,8 @@ "dashboard": "Dashboard", "levels": "Level", "members": "Mitglieder", - "profile": "Dein Profil" + "profile": "Dein Profil", + "achievements": "Errungenschaften" }, "server_empty": "Kein Server ausgewählt", "settings": "Einstellungen", From dda2e2f5f6ecefa63ec463540ec6977594810056 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 12 Jul 2023 22:33:43 +0200 Subject: [PATCH 08/23] Improved achievement component #268_achievements --- kdb-web/package.json | 2 +- kdb-web/src/app/models/data/user.model.ts | 2 ++ .../components/achievement/achievement.component.html | 4 ++-- .../components/achievement/achievement.component.ts | 3 +++ kdb-web/src/assets/config.json | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/kdb-web/package.json b/kdb-web/package.json index e77d9ec0..70c9306f 100644 --- a/kdb-web/package.json +++ b/kdb-web/package.json @@ -1,6 +1,6 @@ { "name": "kdb-web", - "version": "1.0.7", + "version": "1.0.dev268_achievements", "scripts": { "ng": "ng", "update-version": "ts-node-esm update-version.ts", diff --git a/kdb-web/src/app/models/data/user.model.ts b/kdb-web/src/app/models/data/user.model.ts index fb36964e..0b054cc6 100644 --- a/kdb-web/src/app/models/data/user.model.ts +++ b/kdb-web/src/app/models/data/user.model.ts @@ -10,6 +10,8 @@ export interface User extends DataWithHistory { discordId?: number; name?: string; xp?: number; + message_count?: number; + reaction_count?: number; ontime?: number; level?: Level; server?: Server; diff --git a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html index 7bdfc233..2c080fd2 100644 --- a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html +++ b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html @@ -132,7 +132,7 @@ - + {{achievement.attribute}} @@ -143,7 +143,7 @@ - + {{achievement.operator}} diff --git a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts index 5eb90432..2836c8b7 100644 --- a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts +++ b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts @@ -60,6 +60,9 @@ export class AchievementComponent implements OnInit, OnDestroy { private server: Server = {}; public user: UserDTO | null = null; + public operators = ["==", "!=", "<=", ">=", "<", ">"]; + public attributes = ["xp", "message_count", "reaction_count", "ontime", "level"] + query: string = Queries.achievementWithHistoryQuery; public constructor( diff --git a/kdb-web/src/assets/config.json b/kdb-web/src/assets/config.json index 57eaafa7..a2d9a6c2 100644 --- a/kdb-web/src/assets/config.json +++ b/kdb-web/src/assets/config.json @@ -5,7 +5,7 @@ "WebVersion": { "Major": "1", "Minor": "0", - "Micro": "7" + "Micro": "dev268_achievements" }, "Themes": [ { From 2578c47c44142e7c85d76172132bc26510f292d1 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 12 Jul 2023 22:34:00 +0200 Subject: [PATCH 09/23] Improved achievement endpoint #268_achievements --- kdb-bot/src/bot_data/model/achievement.py | 4 ++++ kdb-bot/src/bot_graphql/model/user.gql | 2 ++ kdb-bot/src/bot_graphql/queries/user_query.py | 2 ++ 3 files changed, 8 insertions(+) diff --git a/kdb-bot/src/bot_data/model/achievement.py b/kdb-bot/src/bot_data/model/achievement.py index e01bafad..be78379e 100644 --- a/kdb-bot/src/bot_data/model/achievement.py +++ b/kdb-bot/src/bot_data/model/achievement.py @@ -21,6 +21,10 @@ class Achievement(TableABC): self._id = id self._name = name self._attribute = attribute + + if operator not in ["==", "!=", "<=", ">=", "<", ">"]: + raise ValueError("operator must be ==,!=,<=,>=,< or >") + self._operator = operator self._value = value self._server = server diff --git a/kdb-bot/src/bot_graphql/model/user.gql b/kdb-bot/src/bot_graphql/model/user.gql index e7f03f38..2925e223 100644 --- a/kdb-bot/src/bot_graphql/model/user.gql +++ b/kdb-bot/src/bot_graphql/model/user.gql @@ -3,6 +3,8 @@ type User implements TableWithHistoryQuery { discordId: String name: String xp: Int + messageCount: Int + reactionCount: Int ontime: Float level: Level diff --git a/kdb-bot/src/bot_graphql/queries/user_query.py b/kdb-bot/src/bot_graphql/queries/user_query.py index 9e924f5f..53c6a7c1 100644 --- a/kdb-bot/src/bot_graphql/queries/user_query.py +++ b/kdb-bot/src/bot_graphql/queries/user_query.py @@ -41,6 +41,8 @@ class UserQuery(DataQueryWithHistoryABC): self.set_field("discordId", self.resolve_discord_id) self.set_field("name", self.resolve_name) self.set_field("xp", self.resolve_xp) + self.set_field("messageCount", lambda x, *_: x.message_count) + self.set_field("reactionCount", lambda x, *_: x.reaction_count) self.set_field("ontime", self.resolve_ontime) self.set_field("level", self.resolve_level) self.add_collection( From e36f0b8c76b3265bcc16a91aca31d50e7966fccd Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Thu, 13 Jul 2023 13:54:13 +0200 Subject: [PATCH 10/23] Improved achievement logic #268_achievements --- kdb-bot/cpl-workspace.json | 1 + kdb-bot/src/bot/config | 2 +- kdb-bot/src/bot/startup_settings_extension.py | 6 +- kdb-bot/src/bot/translation/de.json | 3 + .../bot_core/configuration/bot_settings.py | 77 +++++++++------ .../bot_core/configuration/server_settings.py | 34 ++++--- .../abc/achievement_repository_abc.py | 13 +++ .../migration/achievements_migration.py | 21 +++- .../bot_data/model/user_got_achievement.py | 99 +++++++++++++++++++ .../achievements_repository_service.py | 38 +++++++ .../achievements/achievement_service.py | 78 +++++++++++++++ .../achievements/achievements_module.py | 6 +- .../modules/achievements/events/__init__.py | 1 + .../events/achievement_on_message_event.py | 43 ++++++++ .../configuration/level_server_settings.py | 28 ------ .../level/configuration/level_settings.py | 31 ------ .../modules/level/service/level_service.py | 6 +- 17 files changed, 375 insertions(+), 112 deletions(-) create mode 100644 kdb-bot/src/bot_data/model/user_got_achievement.py create mode 100644 kdb-bot/src/modules/achievements/achievement_service.py create mode 100644 kdb-bot/src/modules/achievements/events/__init__.py create mode 100644 kdb-bot/src/modules/achievements/events/achievement_on_message_event.py delete mode 100644 kdb-bot/src/modules/level/configuration/level_server_settings.py delete mode 100644 kdb-bot/src/modules/level/configuration/level_settings.py diff --git a/kdb-bot/cpl-workspace.json b/kdb-bot/cpl-workspace.json index 1e44549f..df40bb8b 100644 --- a/kdb-bot/cpl-workspace.json +++ b/kdb-bot/cpl-workspace.json @@ -7,6 +7,7 @@ "bot-core": "src/bot_core/bot-core.json", "bot-data": "src/bot_data/bot-data.json", "bot-graphql": "src/bot_graphql/bot-graphql.json", + "achievements": "src/modules/achievements/achievements.json", "auto-role": "src/modules/auto_role/auto-role.json", "base": "src/modules/base/base.json", "boot-log": "src/modules/boot_log/boot-log.json", diff --git a/kdb-bot/src/bot/config b/kdb-bot/src/bot/config index e1c1efac..35c9d4ec 160000 --- a/kdb-bot/src/bot/config +++ b/kdb-bot/src/bot/config @@ -1 +1 @@ -Subproject commit e1c1efac984a04826c0c2713a26129b9d34b21d6 +Subproject commit 35c9d4ecb4102b1b4ce758221d1efda25672e7e0 diff --git a/kdb-bot/src/bot/startup_settings_extension.py b/kdb-bot/src/bot/startup_settings_extension.py index 008ebd10..7a3dc599 100644 --- a/kdb-bot/src/bot/startup_settings_extension.py +++ b/kdb-bot/src/bot/startup_settings_extension.py @@ -11,7 +11,6 @@ from bot_core.configuration.bot_logging_settings import BotLoggingSettings from bot_core.configuration.bot_settings import BotSettings from modules.base.configuration.base_settings import BaseSettings from modules.boot_log.configuration.boot_log_settings import BootLogSettings -from modules.level.configuration.level_settings import LevelSettings from modules.permission.configuration.permission_settings import PermissionSettings @@ -37,7 +36,6 @@ class StartupSettingsExtension(StartupExtensionABC): self._configure_settings_with_sub_settings(configuration, BotSettings, lambda x: x.servers, lambda x: x.id) self._configure_settings_with_sub_settings(configuration, BaseSettings, lambda x: x.servers, lambda x: x.id) self._configure_settings_with_sub_settings(configuration, BootLogSettings, lambda x: x.servers, lambda x: x.id) - self._configure_settings_with_sub_settings(configuration, LevelSettings, lambda x: x.servers, lambda x: x.id) self._configure_settings_with_sub_settings( configuration, PermissionSettings, lambda x: x.servers, lambda x: x.id ) @@ -50,9 +48,9 @@ class StartupSettingsExtension(StartupExtensionABC): @staticmethod def _configure_settings_with_sub_settings( - config: ConfigurationABC, settings: Type, list_atr: Callable, atr: Callable + config: ConfigurationABC, settings_type: Type, list_atr: Callable, atr: Callable ): - settings: Optional[settings] = config.get_configuration(settings) + settings: Optional[settings_type] = config.get_configuration(settings_type) if settings is None: return diff --git a/kdb-bot/src/bot/translation/de.json b/kdb-bot/src/bot/translation/de.json index 4b6654d9..ca84b079 100644 --- a/kdb-bot/src/bot/translation/de.json +++ b/kdb-bot/src/bot/translation/de.json @@ -93,6 +93,9 @@ } }, "modules": { + "achievements": { + "got_new_achievement": "{} hat die Errungenschaft {} freigeschaltet :D" + }, "auto_role": { "add": { "error": { diff --git a/kdb-bot/src/bot_core/configuration/bot_settings.py b/kdb-bot/src/bot_core/configuration/bot_settings.py index 5fb5e0ee..dc6b8bbf 100644 --- a/kdb-bot/src/bot_core/configuration/bot_settings.py +++ b/kdb-bot/src/bot_core/configuration/bot_settings.py @@ -1,21 +1,30 @@ -import traceback - from cpl_core.configuration import ConfigurationModelABC -from cpl_core.console import Console from cpl_query.extension import List +from bot_api.json_processor import JSONProcessor from bot_core.configuration.server_settings import ServerSettings class BotSettings(ConfigurationModelABC): - def __init__(self): + def __init__( + self, + technicians: list = None, + wait_for_restart: int = 2, + wait_for_shutdown: int = 2, + cache_max_messages: int = 1000, + server_settings: dict = None, + ): ConfigurationModelABC.__init__(self) + self._technicians: List[int] = List(int) if technicians is None else technicians + self._wait_for_restart = wait_for_restart + self._wait_for_shutdown = wait_for_shutdown + self._cache_max_messages = cache_max_messages + self._servers: List[ServerSettings] = List(ServerSettings) - self._technicians: List[int] = List(int) - self._wait_for_restart = 2 - self._wait_for_shutdown = 2 - self._cache_max_messages = 1000 + + if server_settings is not None: + self._servers_from_dict(server_settings) @property def servers(self) -> List[ServerSettings]: @@ -37,26 +46,34 @@ class BotSettings(ConfigurationModelABC): def cache_max_messages(self) -> int: return self._cache_max_messages - def from_dict(self, settings: dict): - try: - self._technicians = settings["Technicians"] - self._wait_for_restart = settings["WaitForRestart"] - self._wait_for_shutdown = settings["WaitForShutdown"] - settings.pop("Technicians") - settings.pop("WaitForRestart") - settings.pop("WaitForShutdown") + def _servers_from_dict(self, settings: dict): + servers = List(ServerSettings) + for s in settings: + settings[s]["id"] = int(s) + st = JSONProcessor.process(ServerSettings, settings[s]) + servers.append(st) + self._servers = servers - if "CacheMaxMessages" in settings: - self._cache_max_messages = settings["CacheMaxMessages"] - settings.pop("CacheMaxMessages") - - servers = List(ServerSettings) - for s in settings: - st = ServerSettings() - settings[s]["Id"] = s - st.from_dict(settings[s]) - servers.append(st) - self._servers = servers - except Exception as e: - Console.error(f"[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings") - Console.error(f"[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}") + # def from_dict(self, settings: dict): + # try: + # self._technicians = settings["Technicians"] + # self._wait_for_restart = settings["WaitForRestart"] + # self._wait_for_shutdown = settings["WaitForShutdown"] + # settings.pop("Technicians") + # settings.pop("WaitForRestart") + # settings.pop("WaitForShutdown") + # + # if "CacheMaxMessages" in settings: + # self._cache_max_messages = settings["CacheMaxMessages"] + # settings.pop("CacheMaxMessages") + # + # servers = List(ServerSettings) + # for s in settings: + # st = ServerSettings() + # settings[s]["Id"] = s + # st.from_dict(settings[s]) + # servers.append(st) + # self._servers = servers + # except Exception as e: + # Console.error(f"[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings") + # Console.error(f"[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}") diff --git a/kdb-bot/src/bot_core/configuration/server_settings.py b/kdb-bot/src/bot_core/configuration/server_settings.py index 92b73d29..ca382886 100644 --- a/kdb-bot/src/bot_core/configuration/server_settings.py +++ b/kdb-bot/src/bot_core/configuration/server_settings.py @@ -1,15 +1,18 @@ -import traceback - from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC -from cpl_core.console import Console class ServerSettings(ConfigurationModelABC): - def __init__(self): + def __init__( + self, + id: int = None, + message_delete_timer: int = None, + notification_chat_id: int = None, + ): ConfigurationModelABC.__init__(self) - self._id: int = 0 - self._message_delete_timer: int = 0 + self._id: int = id + self._message_delete_timer: int = message_delete_timer + self._notification_chat_id: int = notification_chat_id @property def id(self) -> int: @@ -19,10 +22,15 @@ class ServerSettings(ConfigurationModelABC): def message_delete_timer(self) -> int: return self._message_delete_timer - def from_dict(self, settings: dict): - try: - self._id = int(settings["Id"]) - self._message_delete_timer = int(settings["MessageDeleteTimer"]) - except Exception as e: - Console.error(f"[ ERROR ] [ {__name__} ]: Reading error in settings") - Console.error(f"[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}") + @property + def notification_chat_id(self) -> int: + return self._notification_chat_id + + # def from_dict(self, settings: dict): + # try: + # self._id = int(settings["Id"]) + # self._message_delete_timer = int(settings["MessageDeleteTimer"]) + # self._notification_chat_id = int(settings["NotificationChatId"]) + # except Exception as e: + # Console.error(f"[ ERROR ] [ {__name__} ]: Reading error in settings") + # Console.error(f"[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}") diff --git a/kdb-bot/src/bot_data/abc/achievement_repository_abc.py b/kdb-bot/src/bot_data/abc/achievement_repository_abc.py index 4b817759..3850dc27 100644 --- a/kdb-bot/src/bot_data/abc/achievement_repository_abc.py +++ b/kdb-bot/src/bot_data/abc/achievement_repository_abc.py @@ -3,6 +3,7 @@ from abc import ABC, abstractmethod from cpl_query.extension import List from bot_data.model.achievement import Achievement +from bot_data.model.user_got_achievement import UserGotAchievement class AchievementRepositoryABC(ABC): @@ -22,6 +23,10 @@ class AchievementRepositoryABC(ABC): def get_achievements_by_server_id(self, server_id: int) -> List[Achievement]: pass + @abstractmethod + def get_achievements_by_user_id(self, user_id: int) -> List[Achievement]: + pass + @abstractmethod def add_achievement(self, achievement: Achievement): pass @@ -33,3 +38,11 @@ class AchievementRepositoryABC(ABC): @abstractmethod def delete_achievement(self, achievement: Achievement): pass + + @abstractmethod + def add_user_got_achievement(self, join: UserGotAchievement): + pass + + @abstractmethod + def delete_user_got_achievement(self, join: UserGotAchievement): + pass diff --git a/kdb-bot/src/bot_data/migration/achievements_migration.py b/kdb-bot/src/bot_data/migration/achievements_migration.py index 294d1b8b..1c453269 100644 --- a/kdb-bot/src/bot_data/migration/achievements_migration.py +++ b/kdb-bot/src/bot_data/migration/achievements_migration.py @@ -45,7 +45,7 @@ class AchievementsMigration(MigrationABC): `Attribute` VARCHAR(255) NOT NULL, `Operator` VARCHAR(2) NOT NULL, `Value` VARCHAR(255) NOT NULL, - `Deleted` BOOL DEFAULT FALSE, + `Deleted` BOOL DEFAULT FALSE, `DateFrom` DATETIME(6) NOT NULL, `DateTo` DATETIME(6) NOT NULL ); @@ -53,6 +53,25 @@ class AchievementsMigration(MigrationABC): ) ) + self._cursor.execute( + str( + f""" + CREATE TABLE IF NOT EXISTS `UserGotAchievements` ( + `Id` BIGINT NOT NULL AUTO_INCREMENT, + `UserId` BIGINT, + `AchievementId` BIGINT, + `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 (`UserId`) REFERENCES `Users`(`UserId`), + FOREIGN KEY (`AchievementId`) REFERENCES `Achievements`(`Id`) + ); + """ + ) + ) + + # A join table history between users and achievements is not necessary. + self._cursor.execute(str(f"""ALTER TABLE Users ADD MessageCount BIGINT NOT NULL DEFAULT 0 AFTER XP;""")) self._cursor.execute(str(f"""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;""")) diff --git a/kdb-bot/src/bot_data/model/user_got_achievement.py b/kdb-bot/src/bot_data/model/user_got_achievement.py new file mode 100644 index 00000000..3ca33d5d --- /dev/null +++ b/kdb-bot/src/bot_data/model/user_got_achievement.py @@ -0,0 +1,99 @@ +from datetime import datetime +from typing import Optional + +from cpl_core.database import TableABC + +from bot_data.model.achievement import Achievement +from bot_data.model.server import Server +from bot_data.model.user import User + + +class UserGotAchievement(TableABC): + def __init__( + self, + user: Optional[User], + achievement: Optional[Achievement], + server: Optional[Server], + created_at: datetime = None, + modified_at: datetime = None, + id=0, + ): + self._id = id + self._user = user + self._achievement = achievement + 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 user(self) -> User: + return self._user + + @property + def achievement(self) -> Achievement: + return self._achievement + + @staticmethod + def get_select_all_string() -> str: + return str( + f""" + SELECT * FROM `UserGotAchievements`; + """ + ) + + @staticmethod + def get_select_by_id_string(id: int) -> str: + return str( + f""" + SELECT * FROM `UserGotAchievements` + WHERE `Id` = {id}; + """ + ) + + @staticmethod + def get_select_by_user_id_string(id: int) -> str: + return str( + f""" + SELECT * FROM `UserGotAchievements` + WHERE `UserId` = {id}; + """ + ) + + @property + def insert_string(self) -> str: + return str( + f""" + INSERT INTO `UserGotAchievements` ( + `UserId`, `AchievementId` + ) VALUES ( + {self._user.id}, + {self._achievement.id} + ); + """ + ) + + @property + def udpate_string(self) -> str: + return str( + f""" + UPDATE `UserGotAchievements` + SET `UserId` = '{self._user.id}', + `AchievementId` = '{self._achievement.id}' + WHERE `Id` = {self._id}; + """ + ) + + @property + def delete_string(self) -> str: + return str( + f""" + DELETE FROM `UserGotAchievements` + WHERE `Id` = {self._id}; + """ + ) diff --git a/kdb-bot/src/bot_data/service/achievements_repository_service.py b/kdb-bot/src/bot_data/service/achievements_repository_service.py index d9c157e2..da09786c 100644 --- a/kdb-bot/src/bot_data/service/achievements_repository_service.py +++ b/kdb-bot/src/bot_data/service/achievements_repository_service.py @@ -4,7 +4,9 @@ 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.abc.user_repository_abc import UserRepositoryABC from bot_data.model.achievement import Achievement +from bot_data.model.user_got_achievement import UserGotAchievement class AchievementRepositoryService(AchievementRepositoryABC): @@ -13,11 +15,13 @@ class AchievementRepositoryService(AchievementRepositoryABC): logger: DatabaseLogger, db_context: DatabaseContextABC, servers: ServerRepositoryABC, + users: UserRepositoryABC, ): self._logger = logger self._context = db_context self._servers = servers + self._users = users AchievementRepositoryABC.__init__(self) @@ -33,6 +37,15 @@ class AchievementRepositoryService(AchievementRepositoryABC): id=result[0], ) + def _join_from_result(self, result: tuple): + return UserGotAchievement( + self._users.get_user_by_id(result[1]), + self.get_achievement_by_id(result[2]), + self._servers.get_server_by_id(result[3]), + result[5], + 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()}") @@ -59,6 +72,23 @@ class AchievementRepositoryService(AchievementRepositoryABC): return achievements + def get_achievements_by_user_id(self, user_id: int) -> List[Achievement]: + achievements = List(Achievement) + achievements_joins = List(UserGotAchievement) + self._logger.trace(__name__, f"Send SQL command: {UserGotAchievement.get_select_by_user_id_string(user_id)}") + results = self._context.select(UserGotAchievement.get_select_all_string()) + for result in results: + self._logger.trace(__name__, f"Got UserGotAchievement with id {result[0]}") + achievements_joins.append(self._join_from_result(result)) + + for achievements_join in achievements_joins: + results = self._context.select(Achievement.get_select_by_id_string(achievements_join.achievement.id)) + for result in results: + self._logger.trace(__name__, f"Got Achievement 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) @@ -70,3 +100,11 @@ class AchievementRepositoryService(AchievementRepositoryABC): def delete_achievement(self, achievement: Achievement): self._logger.trace(__name__, f"Send SQL command: {achievement.delete_string}") self._context.cursor.execute(achievement.delete_string) + + def add_user_got_achievement(self, join: UserGotAchievement): + self._logger.trace(__name__, f"Send SQL command: {join.insert_string}") + self._context.cursor.execute(join.insert_string) + + def delete_user_got_achievement(self, join: UserGotAchievement): + self._logger.trace(__name__, f"Send SQL command: {join.delete_string}") + self._context.cursor.execute(join.delete_string) diff --git a/kdb-bot/src/modules/achievements/achievement_service.py b/kdb-bot/src/modules/achievements/achievement_service.py new file mode 100644 index 00000000..73e5ffe2 --- /dev/null +++ b/kdb-bot/src/modules/achievements/achievement_service.py @@ -0,0 +1,78 @@ +from cpl_core.configuration import ConfigurationABC +from cpl_core.database.context import DatabaseContextABC +from cpl_core.logging import LoggerABC +from cpl_discord.service import DiscordBotServiceABC +from cpl_translation import TranslatePipe + +from bot_core.configuration.server_settings import ServerSettings +from bot_core.service.message_service import MessageService +from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC +from bot_data.model.achievement import Achievement +from bot_data.model.user import User +from bot_data.model.user_got_achievement import UserGotAchievement + + +class AchievementService: + def __init__( + self, + config: ConfigurationABC, + logger: LoggerABC, + bot: DiscordBotServiceABC, + achievements: AchievementRepositoryABC, + db: DatabaseContextABC, + message_service: MessageService, + t: TranslatePipe, + ): + self._config = config + self._logger = logger + self._bot = bot + self._achievements = achievements + self._db = db + self._message_service = message_service + self._t = t + + def _match(self, value: str, operator: str, expected_value: str) -> bool: + match operator: + case "==": + return value == expected_value + case "!=": + return value != expected_value + case "<=": + return value <= expected_value + case ">=": + return value >= expected_value + case "<": + return value < expected_value + case ">": + return value > expected_value + case _: + raise ValueError(f"Invalid operator: ${operator}") + + def has_user_achievement_already(self, user: User, achievement: Achievement) -> bool: + user_achievements = self._achievements.get_achievements_by_user_id(user.id) + return user_achievements.where(lambda x: x.name == achievement.name).count() > 0 + + def has_user_achievement(self, user: User, achievement: Achievement) -> bool: + return self._match(str(getattr(user, achievement.attribute)), achievement.operator, achievement.value) + + async def validate_achievements_for_user(self, user: User): + achievements = self._achievements.get_achievements_by_server_id(user.server.id) + for achievement in achievements: + if self.has_user_achievement_already(user, achievement) and not self.has_user_achievement( + user, achievement + ): + continue + + self._achievements.add_user_got_achievement(UserGotAchievement(user, achievement, user.server)) + self._db.save_changes() + await self._send_achievement_notification(user.server.discord_id, user.discord_id, achievement.name) + + async def _send_achievement_notification(self, guild_id: int, member_id: int, achievement_name: str): + member = self._bot.get_guild(guild_id).get_member(member_id) + + settings: ServerSettings = self._config.get_configuration(f"ServerSettings_{guild_id}") + await self._message_service.send_channel_message( + self._bot.get_channel(settings.notification_chat_id), + self._t.transform("modules.achievements.got_new_achievement").format(member.mention, achievement_name), + is_persistent=True, + ) diff --git a/kdb-bot/src/modules/achievements/achievements_module.py b/kdb-bot/src/modules/achievements/achievements_module.py index c3a7d5cd..3f70eaa5 100644 --- a/kdb-bot/src/modules/achievements/achievements_module.py +++ b/kdb-bot/src/modules/achievements/achievements_module.py @@ -1,10 +1,13 @@ from cpl_core.configuration import ConfigurationABC from cpl_core.dependency_injection import ServiceCollectionABC from cpl_core.environment import ApplicationEnvironmentABC +from cpl_discord.discord_event_types_enum import DiscordEventTypesEnum 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 modules.achievements.achievement_service import AchievementService +from modules.achievements.events.achievement_on_message_event import AchievementOnMessageEvent class AchievementsModule(ModuleABC): @@ -15,4 +18,5 @@ class AchievementsModule(ModuleABC): pass def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): - pass + services.add_transient(AchievementService) + self._dc.add_event(DiscordEventTypesEnum.on_message.value, AchievementOnMessageEvent) diff --git a/kdb-bot/src/modules/achievements/events/__init__.py b/kdb-bot/src/modules/achievements/events/__init__.py new file mode 100644 index 00000000..425ab6c1 --- /dev/null +++ b/kdb-bot/src/modules/achievements/events/__init__.py @@ -0,0 +1 @@ +# imports diff --git a/kdb-bot/src/modules/achievements/events/achievement_on_message_event.py b/kdb-bot/src/modules/achievements/events/achievement_on_message_event.py new file mode 100644 index 00000000..5b45cc5d --- /dev/null +++ b/kdb-bot/src/modules/achievements/events/achievement_on_message_event.py @@ -0,0 +1,43 @@ +import discord +from cpl_core.database.context import DatabaseContextABC +from cpl_core.logging import LoggerABC +from cpl_discord.events import OnMessageABC +from cpl_discord.service import DiscordBotServiceABC + +from bot_core.helper.event_checks import EventChecks +from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.abc.user_repository_abc import UserRepositoryABC +from modules.achievements.achievement_service import AchievementService + + +class AchievementOnMessageEvent(OnMessageABC): + def __init__( + self, + logger: LoggerABC, + bot: DiscordBotServiceABC, + achievements: AchievementService, + db: DatabaseContextABC, + servers: ServerRepositoryABC, + users: UserRepositoryABC, + ): + OnMessageABC.__init__(self) + + self._logger = logger + self._bot = bot + self._achievements = achievements + self._db = db + self._servers = servers + self._users = users + + @EventChecks.check_is_ready() + async def on_message(self, message: discord.Message): + if message.author.bot: + return + + server = self._servers.get_server_by_discord_id(message.guild.id) + user = self._users.get_user_by_discord_id_and_server_id(message.author.id, server.id) + + user.message_count += 1 + self._db.save_changes() + + await self._achievements.validate_achievements_for_user(user) diff --git a/kdb-bot/src/modules/level/configuration/level_server_settings.py b/kdb-bot/src/modules/level/configuration/level_server_settings.py deleted file mode 100644 index f6dce290..00000000 --- a/kdb-bot/src/modules/level/configuration/level_server_settings.py +++ /dev/null @@ -1,28 +0,0 @@ -import traceback - -from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC -from cpl_core.console import Console - - -class LevelServerSettings(ConfigurationModelABC): - def __init__(self): - ConfigurationModelABC.__init__(self) - - self._id: int = 0 - self._changed_level_notification_channel = 0 - - @property - def id(self) -> int: - return self._id - - @property - def changed_level_notification_channel(self) -> int: - return self._changed_level_notification_channel - - def from_dict(self, settings: dict): - try: - self._id = int(settings["Id"]) - self._changed_level_notification_channel = int(settings["ChangedLevelNotificationChannelId"]) - except Exception as e: - Console.error(f"[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings") - Console.error(f"[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}") diff --git a/kdb-bot/src/modules/level/configuration/level_settings.py b/kdb-bot/src/modules/level/configuration/level_settings.py deleted file mode 100644 index 7dd7db42..00000000 --- a/kdb-bot/src/modules/level/configuration/level_settings.py +++ /dev/null @@ -1,31 +0,0 @@ -import traceback - -from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC -from cpl_core.console import Console -from cpl_query.extension import List - -from modules.level.configuration.level_server_settings import LevelServerSettings - - -class LevelSettings(ConfigurationModelABC): - def __init__(self): - ConfigurationModelABC.__init__(self) - - self._servers: List[LevelServerSettings] = List() - - @property - def servers(self) -> List[LevelServerSettings]: - return self._servers - - def from_dict(self, settings: dict): - try: - servers = List(LevelServerSettings) - for s in settings: - st = LevelServerSettings() - settings[s]["Id"] = s - st.from_dict(settings[s]) - servers.append(st) - self._servers = servers - except Exception as e: - Console.error(f"[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings") - Console.error(f"[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}") diff --git a/kdb-bot/src/modules/level/service/level_service.py b/kdb-bot/src/modules/level/service/level_service.py index 412139ba..f4782592 100644 --- a/kdb-bot/src/modules/level/service/level_service.py +++ b/kdb-bot/src/modules/level/service/level_service.py @@ -6,13 +6,13 @@ from cpl_discord.container import Guild, Role, Member from cpl_discord.service import DiscordBotServiceABC from cpl_translation import TranslatePipe +from bot_core.configuration.server_settings import ServerSettings from bot_core.service.message_service import MessageService from bot_data.model.level import Level from bot_data.model.user import User from bot_data.service.level_repository_service import LevelRepositoryService from bot_data.service.server_repository_service import ServerRepositoryService from bot_data.service.user_repository_service import UserRepositoryService -from modules.level.configuration.level_server_settings import LevelServerSettings class LevelService: @@ -75,9 +75,9 @@ class LevelService: self._logger.error(__name__, f"Adding role {level_role.name} to {member.name} failed!", e) if notification_needed: - level_settings: LevelServerSettings = self._config.get_configuration(f"LevelServerSettings_{guild.id}") + settings: ServerSettings = self._config.get_configuration(f"ServerSettings_{guild.id}") await self._message_service.send_channel_message( - self._bot.get_channel(level_settings.changed_level_notification_channel), + self._bot.get_channel(settings.notification_chat_id), self._t.transform("modules.level.new_level_message").format(member.mention, level.name), is_persistent=True, ) From 51928dcb4d857751b4dd19325a1cad3379e80938 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Sat, 15 Jul 2023 11:14:59 +0200 Subject: [PATCH 11/23] Fixed config #268_achievements --- kdb-bot/src/bot/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kdb-bot/src/bot/config b/kdb-bot/src/bot/config index 35c9d4ec..0179ab1e 160000 --- a/kdb-bot/src/bot/config +++ b/kdb-bot/src/bot/config @@ -1 +1 @@ -Subproject commit 35c9d4ecb4102b1b4ce758221d1efda25672e7e0 +Subproject commit 0179ab1ed725c233c5844ee94ed51cad176d9c7e From 0ec67d41e2291503f955a8c2ba6109a19051fae6 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Sat, 15 Jul 2023 12:52:54 +0200 Subject: [PATCH 12/23] Added logic to make achievement config more generic #268_achievements --- kdb-bot/src/bot_graphql/model/achievement.gql | 5 ++ kdb-bot/src/bot_graphql/model/query.gql | 2 + .../queries/achievement_attribute_query.py | 11 ++++ kdb-bot/src/bot_graphql/query.py | 4 ++ .../achievements/achievement_service.py | 13 ++++ .../modules/achievements/model/__init__.py | 1 + .../model/achievement_attribute.py | 20 +++++++ .../src/app/models/data/achievement.model.ts | 7 ++- .../src/app/models/graphql/queries.model.ts | 10 ++++ kdb-web/src/app/models/graphql/query.model.ts | 7 ++- .../achievement/achievement.component.html | 16 ++++- .../achievement/achievement.component.ts | 60 +++++++++++++++---- 12 files changed, 138 insertions(+), 18 deletions(-) create mode 100644 kdb-bot/src/bot_graphql/queries/achievement_attribute_query.py create mode 100644 kdb-bot/src/modules/achievements/model/__init__.py create mode 100644 kdb-bot/src/modules/achievements/model/achievement_attribute.py diff --git a/kdb-bot/src/bot_graphql/model/achievement.gql b/kdb-bot/src/bot_graphql/model/achievement.gql index 3a5f1328..ef124653 100644 --- a/kdb-bot/src/bot_graphql/model/achievement.gql +++ b/kdb-bot/src/bot_graphql/model/achievement.gql @@ -1,3 +1,8 @@ +type AchievementAttribute { + name: String + type: String +} + type Achievement implements TableWithHistoryQuery { id: ID name: String diff --git a/kdb-bot/src/bot_graphql/model/query.gql b/kdb-bot/src/bot_graphql/model/query.gql index 17d8634c..9f96dc61 100644 --- a/kdb-bot/src/bot_graphql/model/query.gql +++ b/kdb-bot/src/bot_graphql/model/query.gql @@ -31,6 +31,8 @@ type Query { achievementCount: Int achievements(filter: AchievementFilter, page: Page, sort: Sort): [Achievement] + AchievementAttributes: [AchievementAttribute] + AchievementOperators: [String] guilds(filter: GuildFilter): [Guild] } \ No newline at end of file diff --git a/kdb-bot/src/bot_graphql/queries/achievement_attribute_query.py b/kdb-bot/src/bot_graphql/queries/achievement_attribute_query.py new file mode 100644 index 00000000..2884c32b --- /dev/null +++ b/kdb-bot/src/bot_graphql/queries/achievement_attribute_query.py @@ -0,0 +1,11 @@ +from bot_graphql.abc.data_query_abc import DataQueryABC + + +class AchievementQuery(DataQueryABC): + def __init__( + self, + ): + DataQueryABC.__init__(self, "AchievementAttribute") + + self.set_field("name", lambda x, *_: x.name) + self.set_field("type", lambda x, *_: x.type) diff --git a/kdb-bot/src/bot_graphql/query.py b/kdb-bot/src/bot_graphql/query.py index 976b1950..8f3562ea 100644 --- a/kdb-bot/src/bot_graphql/query.py +++ b/kdb-bot/src/bot_graphql/query.py @@ -21,6 +21,7 @@ from bot_graphql.filter.user_filter import UserFilter from bot_graphql.filter.user_joined_game_server_filter import UserJoinedGameServerFilter from bot_graphql.filter.user_joined_server_filter import UserJoinedServerFilter from bot_graphql.filter.user_joined_voice_channel_filter import UserJoinedVoiceChannelFilter +from modules.achievements.achievement_service import AchievementService class Query(QueryABC): @@ -37,6 +38,7 @@ class Query(QueryABC): user_joined_game_server: UserJoinedGameServerRepositoryABC, users: UserRepositoryABC, achievements: AchievementRepositoryABC, + achievement_service: AchievementService, ): QueryABC.__init__(self, "Query") @@ -65,6 +67,8 @@ class Query(QueryABC): self.add_collection("achievement", lambda *_: achievements.get_achievements(), AchievementFilter) self.set_field("guilds", self._resolve_guilds) + self.set_field("AchievementAttributes", lambda x, *_: achievement_service.get_attributes()) + self.set_field("AchievementOperators", lambda x, *_: ["==", "!=", "<=", ">=", "<", ">"]) def _resolve_guilds(self, *_, filter=None): if filter is None or "id" not in filter: diff --git a/kdb-bot/src/modules/achievements/achievement_service.py b/kdb-bot/src/modules/achievements/achievement_service.py index 73e5ffe2..f767ef87 100644 --- a/kdb-bot/src/modules/achievements/achievement_service.py +++ b/kdb-bot/src/modules/achievements/achievement_service.py @@ -2,6 +2,7 @@ from cpl_core.configuration import ConfigurationABC from cpl_core.database.context import DatabaseContextABC from cpl_core.logging import LoggerABC from cpl_discord.service import DiscordBotServiceABC +from cpl_query.extension import List from cpl_translation import TranslatePipe from bot_core.configuration.server_settings import ServerSettings @@ -10,6 +11,7 @@ from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC from bot_data.model.achievement import Achievement from bot_data.model.user import User from bot_data.model.user_got_achievement import UserGotAchievement +from modules.achievements.model.achievement_attribute import AchievementAttribute class AchievementService: @@ -31,6 +33,17 @@ class AchievementService: self._message_service = message_service self._t = t + def get_attributes(self) -> List[AchievementAttribute]: + attributes = List(AchievementAttribute) + + attributes.add(AchievementAttribute("xp", lambda user: user.xp, "number")) + attributes.add(AchievementAttribute("message_count", lambda user: user.message_count, "number")) + attributes.add(AchievementAttribute("reaction_count", lambda user: user.reaction_count, "number")) + attributes.add(AchievementAttribute("ontime", lambda user: user.ontime, "number")) + attributes.add(AchievementAttribute("level", lambda user: user.level, "Level")) + + return attributes + def _match(self, value: str, operator: str, expected_value: str) -> bool: match operator: case "==": diff --git a/kdb-bot/src/modules/achievements/model/__init__.py b/kdb-bot/src/modules/achievements/model/__init__.py new file mode 100644 index 00000000..425ab6c1 --- /dev/null +++ b/kdb-bot/src/modules/achievements/model/__init__.py @@ -0,0 +1 @@ +# imports diff --git a/kdb-bot/src/modules/achievements/model/achievement_attribute.py b/kdb-bot/src/modules/achievements/model/achievement_attribute.py new file mode 100644 index 00000000..c90d4722 --- /dev/null +++ b/kdb-bot/src/modules/achievements/model/achievement_attribute.py @@ -0,0 +1,20 @@ +from typing import Callable + + +class AchievementAttribute: + # frontend type = TypeScript types + def __init__(self, name: str, resolver: Callable, frontend_type: str): + self._name = name + self._resolver = resolver + self._frontend_type = frontend_type + + @property + def name(self) -> str: + return self._name + + @property + def type(self) -> str: + return self._frontend_type + + def resolve(self, *args, **kwargs): + return self._resolver(*args, **kwargs) diff --git a/kdb-web/src/app/models/data/achievement.model.ts b/kdb-web/src/app/models/data/achievement.model.ts index 0b1af096..23037847 100644 --- a/kdb-web/src/app/models/data/achievement.model.ts +++ b/kdb-web/src/app/models/data/achievement.model.ts @@ -1,10 +1,15 @@ import { DataWithHistory } from "./data.model"; import { Server, ServerFilter } from "./server.model"; +export interface AchievementAttribute { + name?: string; + type?: string; +} + export interface Achievement extends DataWithHistory { id?: number; name?: string; - attribute?: string; + attribute?: string | AchievementAttribute; operator?: string; value?: string; server?: Server; diff --git a/kdb-web/src/app/models/graphql/queries.model.ts b/kdb-web/src/app/models/graphql/queries.model.ts index df3cf125..dafb897e 100644 --- a/kdb-web/src/app/models/graphql/queries.model.ts +++ b/kdb-web/src/app/models/graphql/queries.model.ts @@ -90,6 +90,16 @@ export class Queries { } `; + static achievementTypeQuery = ` + query AchievementType { + AchievementOperators + AchievementAttributes { + name + type + } + } + `; + static achievementQuery = ` query AchievementList($serverId: ID, $filter: AchievementFilter, $page: Page, $sort: Sort) { servers(filter: {id: $serverId}) { diff --git a/kdb-web/src/app/models/graphql/query.model.ts b/kdb-web/src/app/models/graphql/query.model.ts index 60e1964d..fe7ba7f9 100644 --- a/kdb-web/src/app/models/graphql/query.model.ts +++ b/kdb-web/src/app/models/graphql/query.model.ts @@ -3,7 +3,7 @@ import { User } from "../data/user.model"; import { AutoRole, AutoRoleRule } from "../data/auto_role.model"; import { Guild } from "../data/discord.model"; import { Level } from "../data/level.model"; -import { Achievement } from "../data/achievement.model"; +import { Achievement, AchievementAttribute } from "../data/achievement.model"; export interface Query { serverCount: number; @@ -24,6 +24,11 @@ export interface LevelListQuery { levels: Level[]; } +export interface AchievementTypeQuery { + AchievementAttributes: AchievementAttribute[]; + AchievementOperators: string[]; +} + export interface AchievementListQuery { achievementCount: number; achievements: Achievement[]; diff --git a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html index 2c080fd2..94b7e14a 100644 --- a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html +++ b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html @@ -132,7 +132,8 @@ - + {{achievement.attribute}} @@ -152,9 +153,18 @@ - + - + + + + {{achievement.value}} + + + + + + {{achievement.value}} diff --git a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts index 2836c8b7..3237048e 100644 --- a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts +++ b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts @@ -1,5 +1,5 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; -import { Achievement, AchievementFilter } from "../../../../../../models/data/achievement.model"; +import { Achievement, AchievementAttribute, AchievementFilter } from "../../../../../../models/data/achievement.model"; import { FormBuilder, FormControl, FormGroup } from "@angular/forms"; import { Page } from "../../../../../../models/graphql/filter/page.model"; import { Sort, SortDirection } from "../../../../../../models/graphql/filter/sort.model"; @@ -15,12 +15,12 @@ import { TranslateService } from "@ngx-translate/core"; import { DataService } from "../../../../../../services/data/data.service"; import { SidebarService } from "../../../../../../services/sidebar/sidebar.service"; import { ActivatedRoute } from "@angular/router"; -import { AchievementListQuery, Query } from "../../../../../../models/graphql/query.model"; +import { AchievementListQuery, AchievementTypeQuery, LevelListQuery, Query } from "../../../../../../models/graphql/query.model"; import { catchError, debounceTime, takeUntil } from "rxjs/operators"; -import { LazyLoadEvent } from "primeng/api"; +import { LazyLoadEvent, MenuItem } from "primeng/api"; import { Table } from "primeng/table"; import { User } from "../../../../../../models/data/user.model"; -import { AchievementMutationResult, UpdateUserMutationResult } from "../../../../../../models/graphql/result.model"; +import { AchievementMutationResult } from "../../../../../../models/graphql/result.model"; import { Mutations } from "../../../../../../models/graphql/mutations.model"; @Component({ @@ -60,8 +60,10 @@ export class AchievementComponent implements OnInit, OnDestroy { private server: Server = {}; public user: UserDTO | null = null; - public operators = ["==", "!=", "<=", ">=", "<", ">"]; - public attributes = ["xp", "message_count", "reaction_count", "ontime", "level"] + public operators: string[] = []; + public attributes: MenuItem[] = []; + private achievementsAttributes: AchievementAttribute[] = []; + levels!: MenuItem[]; query: string = Queries.achievementWithHistoryQuery; @@ -78,10 +80,10 @@ export class AchievementComponent implements OnInit, OnDestroy { } public ngOnInit(): void { + this.loading = true; this.setFilterForm(); this.data.getServerFromRoute(this.route).then(async server => { this.server = server; - this.loadNextPage(); let authUser = await this.authService.getLoggedInUser(); this.user = authUser?.users?.find(u => u.server == this.server.id) ?? null; }); @@ -92,8 +94,22 @@ export class AchievementComponent implements OnInit, OnDestroy { this.unsubscriber.complete(); } - public loadNextPage(): void { - this.loading = true; + + private loadLevels() { + this.data.query(Queries.levelQuery, { + serverId: this.server.id + }, + (data: Query) => { + return data.servers[0]; + } + ).subscribe(data => { + this.levels = data.levels.map(level => { + return { label: level.name, value: level.name }; + }); + }); + } + + private loadNextData() { this.data.query(Queries.achievementQuery, { serverId: this.server.id, filter: this.filter, page: this.page, sort: this.sort }, @@ -108,6 +124,19 @@ export class AchievementComponent implements OnInit, OnDestroy { }); } + public loadNextPage(): void { + this.data.query(Queries.achievementTypeQuery + ).subscribe(data => { + this.operators = data.AchievementOperators; + this.achievementsAttributes = data.AchievementAttributes; + this.attributes = data.AchievementAttributes.map(attribute => { + return { label: attribute.name, value: attribute.name }; + }); + this.loadLevels(); + this.loadNextData(); + }); + } + public setFilterForm(): void { this.filterForm = this.fb.group({ id: new FormControl(null), @@ -182,9 +211,9 @@ export class AchievementComponent implements OnInit, OnDestroy { this.spinner.showSpinner(); this.data.mutation(Mutations.createAchievement, { name: newAchievement.name, - attribute: newAchievement.attribute, - operator: newAchievement.operator, - value: newAchievement.value, + attribute: newAchievement.attribute, + operator: newAchievement.operator, + value: newAchievement.value + "", serverId: this.server.id } ).pipe(catchError(err => { @@ -207,7 +236,7 @@ export class AchievementComponent implements OnInit, OnDestroy { name: newAchievement.name, attribute: newAchievement.attribute, operator: newAchievement.operator, - value: newAchievement.value + value: newAchievement.value + "" } ).pipe(catchError(err => { this.spinner.hideSpinner(); @@ -268,4 +297,9 @@ export class AchievementComponent implements OnInit, OnDestroy { this.isEditingNew = true; } + + public getAchievementAttributeByName(name: string): AchievementAttribute { + const [found] = this.achievementsAttributes.filter(x => x.name === name); + return found; + } } From fd10614b20da01c2d9c958165d452b358a6ce244 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Sat, 15 Jul 2023 13:16:13 +0200 Subject: [PATCH 13/23] Improved internal achievement checks #268_achievements --- kdb-bot/src/bot_data/db_context.py | 5 ++++ .../bot_data/model/user_got_achievement.py | 3 -- .../achievements_repository_service.py | 4 +-- .../achievements/achievement_service.py | 29 ++++++++++--------- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/kdb-bot/src/bot_data/db_context.py b/kdb-bot/src/bot_data/db_context.py index 46eff3bb..fb5e9f55 100644 --- a/kdb-bot/src/bot_data/db_context.py +++ b/kdb-bot/src/bot_data/db_context.py @@ -11,6 +11,7 @@ class DBContext(DatabaseContext): self._logger = logger DatabaseContext.__init__(self) + self._fails = 0 def connect(self, database_settings: DatabaseSettings): try: @@ -32,7 +33,11 @@ class DBContext(DatabaseContext): try: return super(DBContext, self).select(statement) except Exception as e: + if self._fails >= 3: + self._logger.fatal(__name__, f"Database error caused by {statement}", e) + self._logger.error(__name__, f"Database error caused by {statement}", e) + self._fails += 1 try: time.sleep(0.5) return self.select(statement) diff --git a/kdb-bot/src/bot_data/model/user_got_achievement.py b/kdb-bot/src/bot_data/model/user_got_achievement.py index 3ca33d5d..18bb6b92 100644 --- a/kdb-bot/src/bot_data/model/user_got_achievement.py +++ b/kdb-bot/src/bot_data/model/user_got_achievement.py @@ -4,7 +4,6 @@ from typing import Optional from cpl_core.database import TableABC from bot_data.model.achievement import Achievement -from bot_data.model.server import Server from bot_data.model.user import User @@ -13,7 +12,6 @@ class UserGotAchievement(TableABC): self, user: Optional[User], achievement: Optional[Achievement], - server: Optional[Server], created_at: datetime = None, modified_at: datetime = None, id=0, @@ -21,7 +19,6 @@ class UserGotAchievement(TableABC): self._id = id self._user = user self._achievement = achievement - self._server = server TableABC.__init__(self) self._created_at = created_at if created_at is not None else self._created_at diff --git a/kdb-bot/src/bot_data/service/achievements_repository_service.py b/kdb-bot/src/bot_data/service/achievements_repository_service.py index da09786c..6ee69abc 100644 --- a/kdb-bot/src/bot_data/service/achievements_repository_service.py +++ b/kdb-bot/src/bot_data/service/achievements_repository_service.py @@ -41,8 +41,8 @@ class AchievementRepositoryService(AchievementRepositoryABC): return UserGotAchievement( self._users.get_user_by_id(result[1]), self.get_achievement_by_id(result[2]), - self._servers.get_server_by_id(result[3]), - result[5], + result[3], + result[4], id=result[0], ) diff --git a/kdb-bot/src/modules/achievements/achievement_service.py b/kdb-bot/src/modules/achievements/achievement_service.py index f767ef87..23fb3754 100644 --- a/kdb-bot/src/modules/achievements/achievement_service.py +++ b/kdb-bot/src/modules/achievements/achievement_service.py @@ -33,16 +33,20 @@ class AchievementService: self._message_service = message_service self._t = t + self._attributes = List(AchievementAttribute) + + self._attributes.extend( + [ + AchievementAttribute("xp", lambda user: user.xp, "number"), + AchievementAttribute("message_count", lambda user: user.message_count, "number"), + AchievementAttribute("reaction_count", lambda user: user.reaction_count, "number"), + AchievementAttribute("ontime", lambda user: user.ontime, "number"), + AchievementAttribute("level", lambda user: user.level, "Level"), + ] + ) + def get_attributes(self) -> List[AchievementAttribute]: - attributes = List(AchievementAttribute) - - attributes.add(AchievementAttribute("xp", lambda user: user.xp, "number")) - attributes.add(AchievementAttribute("message_count", lambda user: user.message_count, "number")) - attributes.add(AchievementAttribute("reaction_count", lambda user: user.reaction_count, "number")) - attributes.add(AchievementAttribute("ontime", lambda user: user.ontime, "number")) - attributes.add(AchievementAttribute("level", lambda user: user.level, "Level")) - - return attributes + return self._attributes def _match(self, value: str, operator: str, expected_value: str) -> bool: match operator: @@ -66,14 +70,13 @@ class AchievementService: return user_achievements.where(lambda x: x.name == achievement.name).count() > 0 def has_user_achievement(self, user: User, achievement: Achievement) -> bool: - return self._match(str(getattr(user, achievement.attribute)), achievement.operator, achievement.value) + attribute: AchievementAttribute = self._attributes.where(lambda x: x.name == achievement.attribute).single() + return self._match(str(attribute.resolve(user)), achievement.operator, achievement.value) async def validate_achievements_for_user(self, user: User): achievements = self._achievements.get_achievements_by_server_id(user.server.id) for achievement in achievements: - if self.has_user_achievement_already(user, achievement) and not self.has_user_achievement( - user, achievement - ): + if self.has_user_achievement_already(user, achievement) or not self.has_user_achievement(user, achievement): continue self._achievements.add_user_got_achievement(UserGotAchievement(user, achievement, user.server)) From 6b86cc3ca8724751f8a19f05f492a5ed28c60a98 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Sat, 15 Jul 2023 13:38:10 +0200 Subject: [PATCH 14/23] Improved generic achievement logic #268_achievements --- .../achievement_attribute_resolver.py | 43 +++++++++++++++++++ .../achievements/achievement_service.py | 36 +++++++++------- 2 files changed, 64 insertions(+), 15 deletions(-) create mode 100644 kdb-bot/src/modules/achievements/achievement_attribute_resolver.py diff --git a/kdb-bot/src/modules/achievements/achievement_attribute_resolver.py b/kdb-bot/src/modules/achievements/achievement_attribute_resolver.py new file mode 100644 index 00000000..9006da17 --- /dev/null +++ b/kdb-bot/src/modules/achievements/achievement_attribute_resolver.py @@ -0,0 +1,43 @@ +from typing import List + +from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC +from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC +from bot_data.abc.client_repository_abc import ClientRepositoryABC +from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC +from bot_data.abc.level_repository_abc import LevelRepositoryABC +from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.abc.user_joined_game_server_repository_abc import UserJoinedGameServerRepositoryABC +from bot_data.abc.user_joined_server_repository_abc import UserJoinedServerRepositoryABC +from bot_data.abc.user_joined_voice_channel_repository_abc import UserJoinedVoiceChannelRepositoryABC +from bot_data.abc.user_repository_abc import UserRepositoryABC +from bot_data.model.user import User + + +class AchievementAttributeResolver: + def __init__( + self, + auto_roles: AutoRoleRepositoryABC, + clients: ClientRepositoryABC, + known_users: KnownUserRepositoryABC, + levels: LevelRepositoryABC, + servers: ServerRepositoryABC, + user_joined_servers: UserJoinedServerRepositoryABC, + user_joined_voice_channels: UserJoinedVoiceChannelRepositoryABC, + user_joined_game_server: UserJoinedGameServerRepositoryABC, + users: UserRepositoryABC, + achievements: AchievementRepositoryABC, + ): + self._auto_roles = auto_roles + self._clients = clients + self._known_users = known_users + self._levels = levels + self._servers = servers + self._user_joined_servers = user_joined_servers + self._user_joined_voice_channels = user_joined_voice_channels + self._user_joined_game_server = user_joined_game_server + self._users = users + self._achievements = achievements + + def get_played_on_game_server(self, user: User) -> List[str]: + joins = self._user_joined_game_server.get_user_joined_game_servers_by_user_id(user.id) + return joins.select(lambda x: x.name) diff --git a/kdb-bot/src/modules/achievements/achievement_service.py b/kdb-bot/src/modules/achievements/achievement_service.py index 23fb3754..f6bf30ad 100644 --- a/kdb-bot/src/modules/achievements/achievement_service.py +++ b/kdb-bot/src/modules/achievements/achievement_service.py @@ -11,6 +11,7 @@ from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC from bot_data.model.achievement import Achievement from bot_data.model.user import User from bot_data.model.user_got_achievement import UserGotAchievement +from modules.achievements.achievement_attribute_resolver import AchievementAttributeResolver from modules.achievements.model.achievement_attribute import AchievementAttribute @@ -23,6 +24,7 @@ class AchievementService: achievements: AchievementRepositoryABC, db: DatabaseContextABC, message_service: MessageService, + resolver: AchievementAttributeResolver, t: TranslatePipe, ): self._config = config @@ -42,28 +44,32 @@ class AchievementService: AchievementAttribute("reaction_count", lambda user: user.reaction_count, "number"), AchievementAttribute("ontime", lambda user: user.ontime, "number"), AchievementAttribute("level", lambda user: user.level, "Level"), + # special cases + AchievementAttribute( + "played_on_game_server", lambda user: resolver.get_played_on_game_server(user), "GameServer" + ), + AchievementAttribute("last_single_ontime_hours", lambda user: user.level, "GameServer"), ] ) + self._operators = { + "==": lambda value, expected_value: value == expected_value, + "!=": lambda value, expected_value: value != expected_value, + "<=": lambda value, expected_value: value <= expected_value, + ">=": lambda value, expected_value: value >= expected_value, + "<": lambda value, expected_value: value < expected_value, + ">": lambda value, expected_value: value > expected_value, + "contains": lambda value, expected_value: expected_value in value, + } + + def get_operators(self) -> list[str]: + return [x for x in self._operators.keys()] + def get_attributes(self) -> List[AchievementAttribute]: return self._attributes def _match(self, value: str, operator: str, expected_value: str) -> bool: - match operator: - case "==": - return value == expected_value - case "!=": - return value != expected_value - case "<=": - return value <= expected_value - case ">=": - return value >= expected_value - case "<": - return value < expected_value - case ">": - return value > expected_value - case _: - raise ValueError(f"Invalid operator: ${operator}") + return self._operators[operator](value, expected_value) def has_user_achievement_already(self, user: User, achievement: Achievement) -> bool: user_achievements = self._achievements.get_achievements_by_user_id(user.id) From 642a4d4dac9a44c295a16cb0012443b9a23aca1f Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Sat, 15 Jul 2023 14:42:01 +0200 Subject: [PATCH 15/23] Added played_on_game_server achievement logic #268_achievements --- kdb-bot/src/bot/translation/de.json | 5 +- .../migration/achievements_migration.py | 4 +- kdb-bot/src/bot_data/model/achievement.py | 12 +++- kdb-bot/src/bot_graphql/graphql_module.py | 6 +- kdb-bot/src/bot_graphql/model/achievement.gql | 3 + kdb-bot/src/bot_graphql/model/query.gql | 7 ++- kdb-bot/src/bot_graphql/model/server.gql | 12 ++++ .../queries/achievement_attribute_query.py | 2 +- .../bot_graphql/queries/game_server_query.py | 10 ++++ .../src/bot_graphql/queries/server_query.py | 3 + kdb-bot/src/bot_graphql/query.py | 7 ++- .../achievement_attribute_resolver.py | 5 +- .../achievements/achievement_service.py | 4 +- .../achievements/achievements_module.py | 6 ++ .../modules/achievements/commands/__init__.py | 1 + .../commands/achievements_group.py | 57 +++++++++++++++++++ kdb-web/src/app/models/data/server.model.ts | 5 ++ .../src/app/models/graphql/queries.model.ts | 14 ++++- kdb-web/src/app/models/graphql/query.model.ts | 11 +++- .../achievement/achievement.component.html | 13 ++++- .../achievement/achievement.component.ts | 28 +++++++-- 21 files changed, 188 insertions(+), 27 deletions(-) create mode 100644 kdb-bot/src/bot_graphql/queries/game_server_query.py create mode 100644 kdb-bot/src/modules/achievements/commands/__init__.py create mode 100644 kdb-bot/src/modules/achievements/commands/achievements_group.py diff --git a/kdb-bot/src/bot/translation/de.json b/kdb-bot/src/bot/translation/de.json index ca84b079..27dd9749 100644 --- a/kdb-bot/src/bot/translation/de.json +++ b/kdb-bot/src/bot/translation/de.json @@ -94,7 +94,10 @@ }, "modules": { "achievements": { - "got_new_achievement": "{} hat die Errungenschaft {} freigeschaltet :D" + "got_new_achievement": "{} hat die Errungenschaft {} freigeschaltet :D", + "commands": { + "check": "Alles klar, ich schaue eben nach... nom nom" + } }, "auto_role": { "add": { diff --git a/kdb-bot/src/bot_data/migration/achievements_migration.py b/kdb-bot/src/bot_data/migration/achievements_migration.py index 1c453269..d9851cb5 100644 --- a/kdb-bot/src/bot_data/migration/achievements_migration.py +++ b/kdb-bot/src/bot_data/migration/achievements_migration.py @@ -22,7 +22,7 @@ class AchievementsMigration(MigrationABC): `Id` BIGINT NOT NULL AUTO_INCREMENT, `Name` VARCHAR(255) NOT NULL, `Attribute` VARCHAR(255) NOT NULL, - `Operator` VARCHAR(2) NOT NULL, + `Operator` VARCHAR(255) NOT NULL, `Value` VARCHAR(255) NOT NULL, `ServerId` BIGINT, `CreatedAt` DATETIME(6) NULL DEFAULT CURRENT_TIMESTAMP(6), @@ -43,7 +43,7 @@ class AchievementsMigration(MigrationABC): `ServerId` BIGINT, `Name` VARCHAR(255) NOT NULL, `Attribute` VARCHAR(255) NOT NULL, - `Operator` VARCHAR(2) NOT NULL, + `Operator` VARCHAR(255) NOT NULL, `Value` VARCHAR(255) NOT NULL, `Deleted` BOOL DEFAULT FALSE, `DateFrom` DATETIME(6) NOT NULL, diff --git a/kdb-bot/src/bot_data/model/achievement.py b/kdb-bot/src/bot_data/model/achievement.py index be78379e..9766710e 100644 --- a/kdb-bot/src/bot_data/model/achievement.py +++ b/kdb-bot/src/bot_data/model/achievement.py @@ -2,6 +2,7 @@ from datetime import datetime from typing import Optional from cpl_core.database import TableABC +from cpl_core.dependency_injection import ServiceProviderABC from bot_data.model.server import Server @@ -22,8 +23,8 @@ class Achievement(TableABC): self._name = name self._attribute = attribute - if operator not in ["==", "!=", "<=", ">=", "<", ">"]: - raise ValueError("operator must be ==,!=,<=,>=,< or >") + if self._is_operator_valid(operator): + raise ValueError("Operator invalid") self._operator = operator self._value = value @@ -33,6 +34,13 @@ class Achievement(TableABC): 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 + @ServiceProviderABC.inject + def _is_operator_valid(self, operator, service: ServiceProviderABC) -> bool: + from modules.achievements.achievement_service import AchievementService + + achievements: AchievementService = service.get_service(AchievementService) + return operator not in achievements.get_operators() + @property def id(self) -> int: return self._id diff --git a/kdb-bot/src/bot_graphql/graphql_module.py b/kdb-bot/src/bot_graphql/graphql_module.py index b861aec1..5e54b86d 100644 --- a/kdb-bot/src/bot_graphql/graphql_module.py +++ b/kdb-bot/src/bot_graphql/graphql_module.py @@ -26,6 +26,7 @@ from bot_graphql.mutations.auto_role_rule_mutation import AutoRoleRuleMutation from bot_graphql.mutations.level_mutation import LevelMutation from bot_graphql.mutations.user_joined_game_server_mutation import UserJoinedGameServerMutation from bot_graphql.mutations.user_mutation import UserMutation +from bot_graphql.queries.achievement_attribute_query import AchievementAttributeQuery from bot_graphql.queries.achievement_query import AchievementQuery from bot_graphql.queries.auto_role_history_query import AutoRoleHistoryQuery from bot_graphql.queries.auto_role_query import AutoRoleQuery @@ -33,6 +34,7 @@ from bot_graphql.queries.auto_role_rule_history_query import AutoRoleRuleHistory from bot_graphql.queries.auto_role_rule_query import AutoRoleRuleQuery from bot_graphql.queries.client_history_query import ClientHistoryQuery from bot_graphql.queries.client_query import ClientQuery +from bot_graphql.queries.game_server_query import GameServerQuery from bot_graphql.queries.known_user_history_query import KnownUserHistoryQuery from bot_graphql.queries.known_user_query import KnownUserQuery from bot_graphql.queries.level_history_query import LevelHistoryQuery @@ -65,6 +67,8 @@ class GraphQLModule(ModuleABC): services.add_singleton(Mutation) # queries + services.add_transient(QueryABC, AchievementAttributeQuery) + services.add_transient(QueryABC, AchievementQuery) services.add_transient(QueryABC, AutoRoleHistoryQuery) services.add_transient(QueryABC, AutoRoleQuery) services.add_transient(QueryABC, AutoRoleRuleHistoryQuery) @@ -77,9 +81,9 @@ class GraphQLModule(ModuleABC): services.add_transient(QueryABC, LevelQuery) services.add_transient(QueryABC, ServerHistoryQuery) services.add_transient(QueryABC, ServerQuery) + services.add_transient(QueryABC, GameServerQuery) services.add_transient(QueryABC, UserHistoryQuery) services.add_transient(QueryABC, UserQuery) - services.add_transient(QueryABC, AchievementQuery) services.add_transient(QueryABC, UserJoinedServerHistoryQuery) services.add_transient(QueryABC, UserJoinedServerQuery) services.add_transient(QueryABC, UserJoinedVoiceChannelHistoryQuery) diff --git a/kdb-bot/src/bot_graphql/model/achievement.gql b/kdb-bot/src/bot_graphql/model/achievement.gql index ef124653..7cd86cf9 100644 --- a/kdb-bot/src/bot_graphql/model/achievement.gql +++ b/kdb-bot/src/bot_graphql/model/achievement.gql @@ -1,6 +1,9 @@ type AchievementAttribute { name: String type: String + + createdAt: String + modifiedAt: String } type Achievement implements TableWithHistoryQuery { diff --git a/kdb-bot/src/bot_graphql/model/query.gql b/kdb-bot/src/bot_graphql/model/query.gql index 9f96dc61..fefde64d 100644 --- a/kdb-bot/src/bot_graphql/model/query.gql +++ b/kdb-bot/src/bot_graphql/model/query.gql @@ -17,6 +17,9 @@ type Query { serverCount: Int servers(filter: ServerFilter, page: Page, sort: Sort): [Server] + gameServerCount: Int + gameServers: [GameServer] + userJoinedServerCount: Int userJoinedServers(filter: UserJoinedServerFilter, page: Page, sort: Sort): [UserJoinedServer] @@ -31,8 +34,8 @@ type Query { achievementCount: Int achievements(filter: AchievementFilter, page: Page, sort: Sort): [Achievement] - AchievementAttributes: [AchievementAttribute] - AchievementOperators: [String] + achievementAttributes: [AchievementAttribute] + achievementOperators: [String] guilds(filter: GuildFilter): [Guild] } \ No newline at end of file diff --git a/kdb-bot/src/bot_graphql/model/server.gql b/kdb-bot/src/bot_graphql/model/server.gql index 90f07f95..c4689ac9 100644 --- a/kdb-bot/src/bot_graphql/model/server.gql +++ b/kdb-bot/src/bot_graphql/model/server.gql @@ -1,3 +1,12 @@ +type GameServer { + id: ID + name: String + server: Server + + createdAt: String + modifiedAt: String +} + type Server implements TableWithHistoryQuery { id: ID discordId: String @@ -13,6 +22,9 @@ type Server implements TableWithHistoryQuery { levelCount: Int levels(filter: LevelFilter, page: Page, sort: Sort): [Level] + gameServerCount: Int + gameServers: [GameServer] + userCount: Int users(filter: UserFilter, page: Page, sort: Sort): [User] diff --git a/kdb-bot/src/bot_graphql/queries/achievement_attribute_query.py b/kdb-bot/src/bot_graphql/queries/achievement_attribute_query.py index 2884c32b..f9b77505 100644 --- a/kdb-bot/src/bot_graphql/queries/achievement_attribute_query.py +++ b/kdb-bot/src/bot_graphql/queries/achievement_attribute_query.py @@ -1,7 +1,7 @@ from bot_graphql.abc.data_query_abc import DataQueryABC -class AchievementQuery(DataQueryABC): +class AchievementAttributeQuery(DataQueryABC): def __init__( self, ): diff --git a/kdb-bot/src/bot_graphql/queries/game_server_query.py b/kdb-bot/src/bot_graphql/queries/game_server_query.py new file mode 100644 index 00000000..8b04b3b9 --- /dev/null +++ b/kdb-bot/src/bot_graphql/queries/game_server_query.py @@ -0,0 +1,10 @@ +from bot_graphql.abc.data_query_abc import DataQueryABC + + +class GameServerQuery(DataQueryABC): + def __init__(self): + DataQueryABC.__init__(self, "GameServer") + + self.set_field("id", lambda x, *_: x.id) + self.set_field("name", lambda x, *_: x.name) + self.set_field("server", lambda x, *_: x.server) diff --git a/kdb-bot/src/bot_graphql/queries/server_query.py b/kdb-bot/src/bot_graphql/queries/server_query.py index 52d53d23..5da12f2d 100644 --- a/kdb-bot/src/bot_graphql/queries/server_query.py +++ b/kdb-bot/src/bot_graphql/queries/server_query.py @@ -4,6 +4,7 @@ from cpl_discord.service import DiscordBotServiceABC from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC from bot_data.abc.client_repository_abc import ClientRepositoryABC +from bot_data.abc.game_server_repository_abc import GameServerRepositoryABC from bot_data.abc.level_repository_abc import LevelRepositoryABC from bot_data.abc.user_joined_server_repository_abc import UserJoinedServerRepositoryABC from bot_data.abc.user_joined_voice_channel_repository_abc import UserJoinedVoiceChannelRepositoryABC @@ -26,6 +27,7 @@ class ServerQuery(DataQueryWithHistoryABC): auto_roles: AutoRoleRepositoryABC, clients: ClientRepositoryABC, levels: LevelRepositoryABC, + game_servers: GameServerRepositoryABC, users: UserRepositoryABC, ujs: UserJoinedServerRepositoryABC, ujvs: UserJoinedVoiceChannelRepositoryABC, @@ -56,6 +58,7 @@ class ServerQuery(DataQueryWithHistoryABC): ) self.add_collection("level", lambda server, *_: self._levels.get_levels_by_server_id(server.id), LevelFilter) self.add_collection("user", lambda server, *_: self._users.get_users_by_server_id(server.id), UserFilter) + self.add_collection("gameServer", lambda server, *_: game_servers.get_game_servers_by_server_id(server.id)) self.add_collection( "achievement", lambda server, *_: achievements.get_achievements_by_server_id(server.id), AchievementFilter ) diff --git a/kdb-bot/src/bot_graphql/query.py b/kdb-bot/src/bot_graphql/query.py index 8f3562ea..5c1d2263 100644 --- a/kdb-bot/src/bot_graphql/query.py +++ b/kdb-bot/src/bot_graphql/query.py @@ -3,6 +3,7 @@ from cpl_discord.service import DiscordBotServiceABC from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC from bot_data.abc.client_repository_abc import ClientRepositoryABC +from bot_data.abc.game_server_repository_abc import GameServerRepositoryABC from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC from bot_data.abc.level_repository_abc import LevelRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC @@ -33,6 +34,7 @@ class Query(QueryABC): known_users: KnownUserRepositoryABC, levels: LevelRepositoryABC, servers: ServerRepositoryABC, + game_servers: GameServerRepositoryABC, user_joined_servers: UserJoinedServerRepositoryABC, user_joined_voice_channels: UserJoinedVoiceChannelRepositoryABC, user_joined_game_server: UserJoinedGameServerRepositoryABC, @@ -50,6 +52,7 @@ class Query(QueryABC): self.add_collection("knownUser", lambda *_: known_users.get_users()) self.add_collection("level", lambda *_: levels.get_levels(), LevelFilter) self.add_collection("server", lambda *_: servers.get_servers(), ServerFilter) + self.add_collection("gameServer", lambda *_: game_servers.get_game_servers()) self.add_collection( "userJoinedServer", lambda *_: user_joined_servers.get_user_joined_servers(), UserJoinedServerFilter ) @@ -67,8 +70,8 @@ class Query(QueryABC): self.add_collection("achievement", lambda *_: achievements.get_achievements(), AchievementFilter) self.set_field("guilds", self._resolve_guilds) - self.set_field("AchievementAttributes", lambda x, *_: achievement_service.get_attributes()) - self.set_field("AchievementOperators", lambda x, *_: ["==", "!=", "<=", ">=", "<", ">"]) + self.set_field("achievementAttributes", lambda x, *_: achievement_service.get_attributes()) + self.set_field("achievementOperators", lambda x, *_: achievement_service.get_operators()) def _resolve_guilds(self, *_, filter=None): if filter is None or "id" not in filter: diff --git a/kdb-bot/src/modules/achievements/achievement_attribute_resolver.py b/kdb-bot/src/modules/achievements/achievement_attribute_resolver.py index 9006da17..4badad37 100644 --- a/kdb-bot/src/modules/achievements/achievement_attribute_resolver.py +++ b/kdb-bot/src/modules/achievements/achievement_attribute_resolver.py @@ -3,6 +3,7 @@ from typing import List from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC from bot_data.abc.client_repository_abc import ClientRepositoryABC +from bot_data.abc.game_server_repository_abc import GameServerRepositoryABC from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC from bot_data.abc.level_repository_abc import LevelRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC @@ -21,6 +22,7 @@ class AchievementAttributeResolver: known_users: KnownUserRepositoryABC, levels: LevelRepositoryABC, servers: ServerRepositoryABC, + game_servers: GameServerRepositoryABC, user_joined_servers: UserJoinedServerRepositoryABC, user_joined_voice_channels: UserJoinedVoiceChannelRepositoryABC, user_joined_game_server: UserJoinedGameServerRepositoryABC, @@ -32,6 +34,7 @@ class AchievementAttributeResolver: self._known_users = known_users self._levels = levels self._servers = servers + self._game_servers = game_servers self._user_joined_servers = user_joined_servers self._user_joined_voice_channels = user_joined_voice_channels self._user_joined_game_server = user_joined_game_server @@ -40,4 +43,4 @@ class AchievementAttributeResolver: def get_played_on_game_server(self, user: User) -> List[str]: joins = self._user_joined_game_server.get_user_joined_game_servers_by_user_id(user.id) - return joins.select(lambda x: x.name) + return joins.select(lambda x: x.game_server.name) diff --git a/kdb-bot/src/modules/achievements/achievement_service.py b/kdb-bot/src/modules/achievements/achievement_service.py index f6bf30ad..17486f80 100644 --- a/kdb-bot/src/modules/achievements/achievement_service.py +++ b/kdb-bot/src/modules/achievements/achievement_service.py @@ -68,7 +68,7 @@ class AchievementService: def get_attributes(self) -> List[AchievementAttribute]: return self._attributes - def _match(self, value: str, operator: str, expected_value: str) -> bool: + def _match(self, value: any, operator: str, expected_value: str) -> bool: return self._operators[operator](value, expected_value) def has_user_achievement_already(self, user: User, achievement: Achievement) -> bool: @@ -77,7 +77,7 @@ class AchievementService: def has_user_achievement(self, user: User, achievement: Achievement) -> bool: attribute: AchievementAttribute = self._attributes.where(lambda x: x.name == achievement.attribute).single() - return self._match(str(attribute.resolve(user)), achievement.operator, achievement.value) + return self._match(attribute.resolve(user), achievement.operator, achievement.value) async def validate_achievements_for_user(self, user: User): achievements = self._achievements.get_achievements_by_server_id(user.server.id) diff --git a/kdb-bot/src/modules/achievements/achievements_module.py b/kdb-bot/src/modules/achievements/achievements_module.py index 3f70eaa5..dc59af6c 100644 --- a/kdb-bot/src/modules/achievements/achievements_module.py +++ b/kdb-bot/src/modules/achievements/achievements_module.py @@ -6,7 +6,9 @@ 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 modules.achievements.achievement_attribute_resolver import AchievementAttributeResolver from modules.achievements.achievement_service import AchievementService +from modules.achievements.commands.achievements_group import AchievementGroup from modules.achievements.events.achievement_on_message_event import AchievementOnMessageEvent @@ -18,5 +20,9 @@ class AchievementsModule(ModuleABC): pass def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): + services.add_transient(AchievementAttributeResolver) services.add_transient(AchievementService) + + self._dc.add_command(AchievementGroup) + self._dc.add_event(DiscordEventTypesEnum.on_message.value, AchievementOnMessageEvent) diff --git a/kdb-bot/src/modules/achievements/commands/__init__.py b/kdb-bot/src/modules/achievements/commands/__init__.py new file mode 100644 index 00000000..425ab6c1 --- /dev/null +++ b/kdb-bot/src/modules/achievements/commands/__init__.py @@ -0,0 +1 @@ +# imports diff --git a/kdb-bot/src/modules/achievements/commands/achievements_group.py b/kdb-bot/src/modules/achievements/commands/achievements_group.py new file mode 100644 index 00000000..189b8dae --- /dev/null +++ b/kdb-bot/src/modules/achievements/commands/achievements_group.py @@ -0,0 +1,57 @@ +import discord +from cpl_discord.command import DiscordCommandABC +from cpl_discord.service import DiscordBotServiceABC +from cpl_translation import TranslatePipe +from discord.ext import commands +from discord.ext.commands import Context + +from bot_core.abc.message_service_abc import MessageServiceABC +from bot_core.helper.command_checks import CommandChecks +from bot_core.logging.command_logger import CommandLogger +from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.abc.user_repository_abc import UserRepositoryABC +from modules.achievements.achievement_service import AchievementService + + +class AchievementGroup(DiscordCommandABC): + def __init__( + self, + logger: CommandLogger, + message_service: MessageServiceABC, + bot: DiscordBotServiceABC, + servers: ServerRepositoryABC, + users: UserRepositoryABC, + achievement_service: AchievementService, + translate: TranslatePipe, + ): + DiscordCommandABC.__init__(self) + + self._logger = logger + self._message_service = message_service + self._bot = bot + self._servers = servers + self._users = users + self._achievement_service = achievement_service + self._t = translate + + @commands.hybrid_group() + @commands.guild_only() + async def achievement(self, ctx: Context): + pass + + @achievement.command() + @commands.guild_only() + @CommandChecks.check_is_ready() + @CommandChecks.check_is_member_moderator() + async def check(self, ctx: Context, member: discord.Member): + self._logger.debug(__name__, f"Received command achievement check {ctx}") + + server = self._servers.get_server_by_discord_id(member.guild.id) + user = self._users.get_user_by_discord_id_and_server_id(member.id, server.id) + await self._message_service.send_ctx_msg( + ctx, + self._t.transform("modules.achievements.commands.check"), + ) + await self._achievement_service.validate_achievements_for_user(user) + + self._logger.trace(__name__, f"Finished command achievement check") diff --git a/kdb-web/src/app/models/data/server.model.ts b/kdb-web/src/app/models/data/server.model.ts index cc160e68..a045186d 100644 --- a/kdb-web/src/app/models/data/server.model.ts +++ b/kdb-web/src/app/models/data/server.model.ts @@ -4,6 +4,11 @@ import {Level} from "./level.model"; import {Client} from "./client.model"; import { AutoRole } from "./auto_role.model"; +export interface GameServer { + id?: number; + name?: string; +} + export interface Server extends Data { id?: number; discordId?: String; diff --git a/kdb-web/src/app/models/graphql/queries.model.ts b/kdb-web/src/app/models/graphql/queries.model.ts index dafb897e..fd46191b 100644 --- a/kdb-web/src/app/models/graphql/queries.model.ts +++ b/kdb-web/src/app/models/graphql/queries.model.ts @@ -46,6 +46,16 @@ export class Queries { } `; + static gameServerQuery = ` + query GameServersList($serverId: ID) { + servers(filter: {id: $serverId}) { + gameServers { + name + } + } + } + ` + static levelQuery = ` query LevelsList($serverId: ID, $filter: LevelFilter, $page: Page, $sort: Sort) { servers(filter: {id: $serverId}) { @@ -92,8 +102,8 @@ export class Queries { static achievementTypeQuery = ` query AchievementType { - AchievementOperators - AchievementAttributes { + achievementOperators + achievementAttributes { name type } diff --git a/kdb-web/src/app/models/graphql/query.model.ts b/kdb-web/src/app/models/graphql/query.model.ts index fe7ba7f9..92c54ae7 100644 --- a/kdb-web/src/app/models/graphql/query.model.ts +++ b/kdb-web/src/app/models/graphql/query.model.ts @@ -1,4 +1,4 @@ -import { Server } from "../data/server.model"; +import { GameServer, Server } from "../data/server.model"; import { User } from "../data/user.model"; import { AutoRole, AutoRoleRule } from "../data/auto_role.model"; import { Guild } from "../data/discord.model"; @@ -19,14 +19,19 @@ export interface UserListQuery { users: User[]; } +export interface GameServerListQuery { + gameServerCount: number; + gameServers: GameServer[]; +} + export interface LevelListQuery { levelCount: number; levels: Level[]; } export interface AchievementTypeQuery { - AchievementAttributes: AchievementAttribute[]; - AchievementOperators: string[]; + achievementAttributes: AchievementAttribute[]; + achievementOperators: string[]; } export interface AchievementListQuery { diff --git a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html index 94b7e14a..e9ceeb2a 100644 --- a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html +++ b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html @@ -153,7 +153,7 @@ - + @@ -162,7 +162,7 @@ - + @@ -170,6 +170,15 @@ {{achievement.value}} + + + + + + + {{achievement.value}} + + diff --git a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts index 3237048e..13d18fe7 100644 --- a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts +++ b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts @@ -15,7 +15,7 @@ import { TranslateService } from "@ngx-translate/core"; import { DataService } from "../../../../../../services/data/data.service"; import { SidebarService } from "../../../../../../services/sidebar/sidebar.service"; import { ActivatedRoute } from "@angular/router"; -import { AchievementListQuery, AchievementTypeQuery, LevelListQuery, Query } from "../../../../../../models/graphql/query.model"; +import { AchievementListQuery, AchievementTypeQuery, GameServerListQuery, LevelListQuery, Query } from "../../../../../../models/graphql/query.model"; import { catchError, debounceTime, takeUntil } from "rxjs/operators"; import { LazyLoadEvent, MenuItem } from "primeng/api"; import { Table } from "primeng/table"; @@ -63,7 +63,9 @@ export class AchievementComponent implements OnInit, OnDestroy { public operators: string[] = []; public attributes: MenuItem[] = []; private achievementsAttributes: AchievementAttribute[] = []; - levels!: MenuItem[]; + + public levels!: MenuItem[]; + public gameServers!: MenuItem[]; query: string = Queries.achievementWithHistoryQuery; @@ -94,7 +96,6 @@ export class AchievementComponent implements OnInit, OnDestroy { this.unsubscriber.complete(); } - private loadLevels() { this.data.query(Queries.levelQuery, { serverId: this.server.id @@ -109,6 +110,20 @@ export class AchievementComponent implements OnInit, OnDestroy { }); } + private loadGameServers() { + this.data.query(Queries.gameServerQuery, { + serverId: this.server.id + }, + (data: Query) => { + return data.servers[0]; + } + ).subscribe(data => { + this.gameServers = data.gameServers.map(gameServer => { + return { label: gameServer.name, value: gameServer.name }; + }); + }); + } + private loadNextData() { this.data.query(Queries.achievementQuery, { serverId: this.server.id, filter: this.filter, page: this.page, sort: this.sort @@ -127,12 +142,13 @@ export class AchievementComponent implements OnInit, OnDestroy { public loadNextPage(): void { this.data.query(Queries.achievementTypeQuery ).subscribe(data => { - this.operators = data.AchievementOperators; - this.achievementsAttributes = data.AchievementAttributes; - this.attributes = data.AchievementAttributes.map(attribute => { + this.operators = data.achievementOperators; + this.achievementsAttributes = data.achievementAttributes; + this.attributes = data.achievementAttributes.map(attribute => { return { label: attribute.name, value: attribute.name }; }); this.loadLevels(); + this.loadGameServers(); this.loadNextData(); }); } From 67baf350fa029405c39ab0278b86b9f8c31a7993 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Sat, 15 Jul 2023 14:48:04 +0200 Subject: [PATCH 16/23] Added last_single_ontime_hours achievement logic #268_achievements --- .../modules/achievements/achievement_attribute_resolver.py | 4 ++++ kdb-bot/src/modules/achievements/achievement_service.py | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/kdb-bot/src/modules/achievements/achievement_attribute_resolver.py b/kdb-bot/src/modules/achievements/achievement_attribute_resolver.py index 4badad37..f08f1579 100644 --- a/kdb-bot/src/modules/achievements/achievement_attribute_resolver.py +++ b/kdb-bot/src/modules/achievements/achievement_attribute_resolver.py @@ -44,3 +44,7 @@ class AchievementAttributeResolver: def get_played_on_game_server(self, user: User) -> List[str]: joins = self._user_joined_game_server.get_user_joined_game_servers_by_user_id(user.id) return joins.select(lambda x: x.game_server.name) + + def get_last_ontime_hours(self, user: User) -> int: + ujvs = self._user_joined_voice_channels.get_user_joined_voice_channels_by_user_id(user.id) + return int(str(ujvs.max(lambda join: (join.leaved_on - join.joined_on).total_seconds() / 3600))) diff --git a/kdb-bot/src/modules/achievements/achievement_service.py b/kdb-bot/src/modules/achievements/achievement_service.py index 17486f80..b446d095 100644 --- a/kdb-bot/src/modules/achievements/achievement_service.py +++ b/kdb-bot/src/modules/achievements/achievement_service.py @@ -48,7 +48,9 @@ class AchievementService: AchievementAttribute( "played_on_game_server", lambda user: resolver.get_played_on_game_server(user), "GameServer" ), - AchievementAttribute("last_single_ontime_hours", lambda user: user.level, "GameServer"), + AchievementAttribute( + "last_single_ontime_hours", lambda user: resolver.get_last_ontime_hours(user), "number" + ), ] ) @@ -62,6 +64,9 @@ class AchievementService: "contains": lambda value, expected_value: expected_value in value, } + def add_achievement_attribute(self, atr: AchievementAttribute): + self._attributes.add(atr) + def get_operators(self) -> list[str]: return [x for x in self._operators.keys()] From 9dcb75109f5db345e66ca53a11fb2a5a25e490c6 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Sat, 15 Jul 2023 15:16:01 +0200 Subject: [PATCH 17/23] Added achievements to user profile #268_achievements --- .../achievements_repository_service.py | 2 +- kdb-bot/src/bot_graphql/model/user.gql | 3 + kdb-bot/src/bot_graphql/queries/user_query.py | 8 +++ .../src/app/models/data/achievement.model.ts | 2 + kdb-web/src/app/models/data/user.model.ts | 4 ++ .../src/app/models/graphql/queries.model.ts | 6 ++ .../server/profile/profile.component.html | 35 ++++++++-- kdb-web/src/assets/i18n/de.json | 65 ++++++++++--------- kdb-web/src/assets/i18n/en.json | 36 +++++++++- 9 files changed, 121 insertions(+), 40 deletions(-) diff --git a/kdb-bot/src/bot_data/service/achievements_repository_service.py b/kdb-bot/src/bot_data/service/achievements_repository_service.py index 6ee69abc..130da9c8 100644 --- a/kdb-bot/src/bot_data/service/achievements_repository_service.py +++ b/kdb-bot/src/bot_data/service/achievements_repository_service.py @@ -76,7 +76,7 @@ class AchievementRepositoryService(AchievementRepositoryABC): achievements = List(Achievement) achievements_joins = List(UserGotAchievement) self._logger.trace(__name__, f"Send SQL command: {UserGotAchievement.get_select_by_user_id_string(user_id)}") - results = self._context.select(UserGotAchievement.get_select_all_string()) + results = self._context.select(UserGotAchievement.get_select_by_user_id_string(user_id)) for result in results: self._logger.trace(__name__, f"Got UserGotAchievement with id {result[0]}") achievements_joins.append(self._join_from_result(result)) diff --git a/kdb-bot/src/bot_graphql/model/user.gql b/kdb-bot/src/bot_graphql/model/user.gql index 2925e223..38db3573 100644 --- a/kdb-bot/src/bot_graphql/model/user.gql +++ b/kdb-bot/src/bot_graphql/model/user.gql @@ -17,6 +17,9 @@ type User implements TableWithHistoryQuery { userJoinedGameServerCount: Int userJoinedGameServers(filter: UserJoinedGameServerFilter, page: Page, sort: Sort): [UserJoinedGameServer] + achievementCount: Int + achievements(filter: AchievementFilter, page: Page, sort: Sort): [Achievement] + server: Server leftServer: Boolean diff --git a/kdb-bot/src/bot_graphql/queries/user_query.py b/kdb-bot/src/bot_graphql/queries/user_query.py index 53c6a7c1..9bb4a48b 100644 --- a/kdb-bot/src/bot_graphql/queries/user_query.py +++ b/kdb-bot/src/bot_graphql/queries/user_query.py @@ -2,12 +2,14 @@ from cpl_core.database.context import DatabaseContextABC from cpl_discord.service import DiscordBotServiceABC from bot_core.abc.client_utils_abc import ClientUtilsABC +from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC from bot_data.abc.user_joined_game_server_repository_abc import UserJoinedGameServerRepositoryABC from bot_data.abc.user_joined_server_repository_abc import UserJoinedServerRepositoryABC from bot_data.abc.user_joined_voice_channel_repository_abc import UserJoinedVoiceChannelRepositoryABC from bot_data.model.user import User from bot_data.model.user_history import UserHistory from bot_graphql.abc.data_query_with_history_abc import DataQueryWithHistoryABC +from bot_graphql.filter.achievement_filter import AchievementFilter from bot_graphql.filter.user_joined_game_server_filter import UserJoinedGameServerFilter from bot_graphql.filter.user_joined_server_filter import UserJoinedServerFilter from bot_graphql.filter.user_joined_voice_channel_filter import UserJoinedVoiceChannelFilter @@ -26,6 +28,7 @@ class UserQuery(DataQueryWithHistoryABC): ujvs: UserJoinedVoiceChannelRepositoryABC, user_joined_game_server: UserJoinedGameServerRepositoryABC, permissions: PermissionServiceABC, + achievements: AchievementRepositoryABC, ): DataQueryWithHistoryABC.__init__(self, "User", "UsersHistory", UserHistory, db) @@ -36,6 +39,7 @@ class UserQuery(DataQueryWithHistoryABC): self._ujs = ujs self._ujvs = ujvs self._permissions = permissions + self._achievements = achievements self.set_field("id", self.resolve_id) self.set_field("discordId", self.resolve_discord_id) @@ -60,6 +64,10 @@ class UserQuery(DataQueryWithHistoryABC): lambda user, *_: self._user_joined_game_server.get_user_joined_game_servers_by_user_id(user.id), UserJoinedGameServerFilter, ) + self.add_collection( + "achievement", lambda user, *_: achievements.get_achievements_by_user_id(user.id), AchievementFilter + ) + self.set_field("server", self.resolve_server) self.set_field("leftServer", self.resolve_left_server) diff --git a/kdb-web/src/app/models/data/achievement.model.ts b/kdb-web/src/app/models/data/achievement.model.ts index 23037847..84f6f88e 100644 --- a/kdb-web/src/app/models/data/achievement.model.ts +++ b/kdb-web/src/app/models/data/achievement.model.ts @@ -13,6 +13,8 @@ export interface Achievement extends DataWithHistory { operator?: string; value?: string; server?: Server; + + createdAt?: string; } export interface AchievementFilter { diff --git a/kdb-web/src/app/models/data/user.model.ts b/kdb-web/src/app/models/data/user.model.ts index 0b054cc6..6d649da4 100644 --- a/kdb-web/src/app/models/data/user.model.ts +++ b/kdb-web/src/app/models/data/user.model.ts @@ -4,6 +4,7 @@ import { Server, ServerFilter } from "./server.model"; import { UserJoinedServer } from "./user_joined_server.model"; import { UserJoinedVoiceChannel } from "./user_joined_voice_channel.model"; import { UserJoinedGameServer } from "./user_joined_game_server.model"; +import { Achievement } from "./achievement.model"; export interface User extends DataWithHistory { id?: number; @@ -25,6 +26,9 @@ export interface User extends DataWithHistory { userJoinedGameServerCount?: number; userJoinedGameServers?: UserJoinedGameServer[]; + + achievementCount?: number; + achievements?: Achievement[]; } export interface UserFilter { diff --git a/kdb-web/src/app/models/graphql/queries.model.ts b/kdb-web/src/app/models/graphql/queries.model.ts index fd46191b..453b7b64 100644 --- a/kdb-web/src/app/models/graphql/queries.model.ts +++ b/kdb-web/src/app/models/graphql/queries.model.ts @@ -222,6 +222,12 @@ export class Queries { joinedOn leavedOn } + + achievements { + id + name + createdAt + } } } } diff --git a/kdb-web/src/app/modules/view/server/profile/profile.component.html b/kdb-web/src/app/modules/view/server/profile/profile.component.html index 05d3ccc5..175be4ae 100644 --- a/kdb-web/src/app/modules/view/server/profile/profile.component.html +++ b/kdb-web/src/app/modules/view/server/profile/profile.component.html @@ -41,12 +41,12 @@ - - - - - - + + + + + +
@@ -78,6 +78,27 @@
+ +
+
+
+
{{'common.name' | translate}}:
+
{{achievement.name}}
+
+ +
+
{{'common.name' | translate}}:
+
{{achievement.name}}
+
+ +
+
{{'view.server.profile.achievements.time' | translate}}:
+
{{achievement.createdAt | date:'dd.MM.yyyy HH:mm:ss'}}
+
+
+
+
+
@@ -102,8 +123,8 @@
- +
diff --git a/kdb-web/src/assets/i18n/de.json b/kdb-web/src/assets/i18n/de.json index 70139bd4..6bcc1328 100644 --- a/kdb-web/src/assets/i18n/de.json +++ b/kdb-web/src/assets/i18n/de.json @@ -97,6 +97,7 @@ "wrong_password": "Falsches Passwort" }, "register": { + "confirm_privacy": "Ich erkläre mich mit der Datenschutzerklärung einverstanden.", "email_required": "E-Mail benötigt", "emails_not_match": "E-Mails stimmen nicht überein", "first_name": "Vorname", @@ -113,8 +114,7 @@ "register_with_discord": "Mit Discord Registrieren", "repeat_email": "E-Mail wiederholen", "repeat_password": "Passwort wiederholen", - "user_already_exists": "Benutzer existiert bereits", - "confirm_privacy": "Ich erkläre mich mit der Datenschutzerklärung einverstanden." + "user_already_exists": "Benutzer existiert bereits" } }, "common": { @@ -153,6 +153,7 @@ "joined_at": "Beigetreten am", "leaved_at": "Verlassen am", "modified_at": "Bearbeitet am", + "name": "Name", "no_entries_found": "Keine Einträge gefunden", "of": "von", "reset_filters": "Filter zurücksetzen" @@ -276,12 +277,12 @@ "dashboard": "Dashboard", "members": "Mitglieder", "server": { + "achievements": "Errungenschaften", "auto_roles": "Auto Rollen", "dashboard": "Dashboard", "levels": "Level", "members": "Mitglieder", - "profile": "Dein Profil", - "achievements": "Errungenschaften" + "profile": "Dein Profil" }, "server_empty": "Kein Server ausgewählt", "settings": "Einstellungen", @@ -316,6 +317,32 @@ "servers": "Server" }, "server": { + "achievements": { + "achievements": "Errungenschaften", + "header": "Errungenschaften", + "headers": { + "attribute": "Attribut", + "name": "Name", + "operator": "Operator", + "value": "Wert" + }, + "message": { + "achievement_create": "Errungenschaft erstellt", + "achievement_create_d": "Errungenschaft {{name}} erfolgreich erstellt", + "achievement_create_failed": "Errungenschaft Erstellung fehlgeschlagen", + "achievement_create_failed_d": "Die Erstellung der Errungenschaft ist fehlgeschlagen!", + "achievement_delete": "Errungenschaft löschen", + "achievement_delete_failed": "Errungenschaft Löschung fehlgeschlagen", + "achievement_delete_failed_d": "Die Löschung der Errungenschaft {{name}} ist fehlgeschlagen!", + "achievement_delete_q": "Sind Sie sich sicher, dass Sie das Errungenschaft {{name}} löschen möchten?", + "achievement_deleted": "Errungenschaft gelöscht", + "achievement_deleted_d": "Errungenschaft {{name}} erfolgreich gelöscht", + "achievement_update": "Errungenschaft bearbeitet", + "achievement_update_d": "Errungenschaft {{name}} erfolgreich bearbeitet", + "achievement_update_failed": "Errungenschaft Bearbeitung fehlgeschlagen", + "achievement_update_failed_d": "Die Bearbeitung der Errungenschaft ist fehlgeschlagen!" + } + }, "auto_roles": { "auto_roles": "Auto Rollen", "header": "Auto Rollen", @@ -398,32 +425,6 @@ "level_update_failed_d": "Die Bearbeitung des Levels ist fehlgeschlagen!" } }, - "achievements": { - "header": "Errungenschaften", - "headers": { - "name": "Name", - "attribute": "Attribut", - "operator": "Operator", - "value": "Wert" - }, - "achievements": "Errungenschaften", - "message": { - "achievement_create": "Errungenschaft erstellt", - "achievement_create_d": "Errungenschaft {{name}} erfolgreich erstellt", - "achievement_create_failed": "Errungenschaft Erstellung fehlgeschlagen", - "achievement_create_failed_d": "Die Erstellung der Errungenschaft ist fehlgeschlagen!", - "achievement_delete": "Errungenschaft löschen", - "achievement_delete_failed": "Errungenschaft Löschung fehlgeschlagen", - "achievement_delete_failed_d": "Die Löschung der Errungenschaft {{name}} ist fehlgeschlagen!", - "achievement_delete_q": "Sind Sie sich sicher, dass Sie das Errungenschaft {{name}} löschen möchten?", - "achievement_deleted": "Errungenschaft gelöscht", - "achievement_deleted_d": "Errungenschaft {{name}} erfolgreich gelöscht", - "achievement_update": "Errungenschaft bearbeitet", - "achievement_update_d": "Errungenschaft {{name}} erfolgreich bearbeitet", - "achievement_update_failed": "Errungenschaft Bearbeitung fehlgeschlagen", - "achievement_update_failed_d": "Die Bearbeitung der Errungenschaft ist fehlgeschlagen!" - } - }, "members": { "header": "Mitglieder", "headers": { @@ -442,6 +443,10 @@ } }, "profile": { + "achievements": { + "header": "Errungeschaften", + "time": "Erreicht am" + }, "header": "Dein Profil", "joined_game_server": { "header": "Gameserver-beitritte", diff --git a/kdb-web/src/assets/i18n/en.json b/kdb-web/src/assets/i18n/en.json index 62a3f623..862fcf4a 100644 --- a/kdb-web/src/assets/i18n/en.json +++ b/kdb-web/src/assets/i18n/en.json @@ -97,6 +97,7 @@ "wrong_password": "Wrong password" }, "register": { + "confirm_privacy": "I agree to the Privacy Policy.", "email_required": "E-Mail required", "emails_not_match": "E-Mails do not match", "first_name": "First name", @@ -113,8 +114,7 @@ "register_with_discord": "Register with discord", "repeat_email": "Repeat E-mail", "repeat_password": "Repeat password", - "user_already_exists": "User already exists", - "confirm_privacy": "I agree to the Privacy Policy." + "user_already_exists": "User already exists" } }, "common": { @@ -153,6 +153,7 @@ "joined_at": "Joined at", "leaved_at": "Leaved at", "modified_at": "Modified at", + "name": "Name", "no_entries_found": "No entries found", "of": "of", "reset_filters": "Reset filters" @@ -276,6 +277,7 @@ "dashboard": "Dashboard", "members": "Members", "server": { + "achievements": "Achievements", "auto_roles": "Auto role", "dashboard": "Dashboard", "levels": "Level", @@ -315,6 +317,32 @@ "servers": "Server" }, "server": { + "achievements": { + "achievements": "Achievements", + "header": "Achievements", + "headers": { + "attribute": "Attribute", + "name": "Namer", + "operator": "Operator", + "value": "Value" + }, + "message": { + "achievement_create": "Achievement created", + "achievement_create_d": "Achievement {{name}} successfully created", + "achievement_create_failed": "Achievement creation failed", + "achievement_create_failed_d": "Creation of achievement failed!", + "achievement_delete": "Delete achievement", + "achievement_delete_failed": "Achievement deletion failed", + "achievement_delete_failed_d": "Deletion of achievement {{name}} failed!", + "achievement_delete_q": "Are you sure you want to delete the {{name}} achievement?", + "achievement_deleted": "Achievement deleted", + "achievement_deleted_d": "Achievement {{name}} successfully deleted\t", + "achievement_update": "Achievement edited", + "achievement_update_d": "Achievement {{name}} edited successfully", + "achievement_update_failed": "Achievement editing failed", + "achievement_update_failed_d": "Achievement editing failed!" + } + }, "auto_roles": { "auto_roles": "Auto roles", "header": "Auto roles", @@ -415,6 +443,10 @@ } }, "profile": { + "achievements": { + "header": "Achievements", + "time": "Reached at" + }, "header": "Profile", "joined_game_server": { "header": "Game server accessions", From c0b7e0913c08f27026bd67fdd330ff5957348ce7 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Sat, 15 Jul 2023 15:16:54 +0200 Subject: [PATCH 18/23] Fixed achievements in user profile #268_achievements --- .../app/modules/view/server/profile/profile.component.html | 5 ----- 1 file changed, 5 deletions(-) diff --git a/kdb-web/src/app/modules/view/server/profile/profile.component.html b/kdb-web/src/app/modules/view/server/profile/profile.component.html index 175be4ae..28de57cb 100644 --- a/kdb-web/src/app/modules/view/server/profile/profile.component.html +++ b/kdb-web/src/app/modules/view/server/profile/profile.component.html @@ -86,11 +86,6 @@
{{achievement.name}}
-
-
{{'common.name' | translate}}:
-
{{achievement.name}}
-
-
{{'view.server.profile.achievements.time' | translate}}:
{{achievement.createdAt | date:'dd.MM.yyyy HH:mm:ss'}}
From 4d4320dbcd54229a6d685006de26b2333d3127af Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Sat, 15 Jul 2023 15:47:40 +0200 Subject: [PATCH 19/23] Added history for achievements to frontend #268_achievements --- .../migration/achievements_migration.py | 2 +- .../src/bot_data/model/achievement_history.py | 26 +++++-------------- kdb-bot/src/bot_data/model/user_history.py | 12 +++++++++ kdb-bot/src/bot_graphql/graphql_module.py | 2 ++ kdb-bot/src/bot_graphql/model/achievement.gql | 2 +- .../queries/achievement_history_query.py | 13 ++++++++++ .../bot_graphql/queries/achievement_query.py | 4 +-- kdb-web/src/assets/i18n/de.json | 5 +++- kdb-web/src/assets/i18n/en.json | 5 +++- 9 files changed, 45 insertions(+), 26 deletions(-) create mode 100644 kdb-bot/src/bot_graphql/queries/achievement_history_query.py diff --git a/kdb-bot/src/bot_data/migration/achievements_migration.py b/kdb-bot/src/bot_data/migration/achievements_migration.py index d9851cb5..eb6933ec 100644 --- a/kdb-bot/src/bot_data/migration/achievements_migration.py +++ b/kdb-bot/src/bot_data/migration/achievements_migration.py @@ -40,11 +40,11 @@ class AchievementsMigration(MigrationABC): CREATE TABLE IF NOT EXISTS `AchievementsHistory` ( `Id` BIGINT(20) NOT NULL, - `ServerId` BIGINT, `Name` VARCHAR(255) NOT NULL, `Attribute` VARCHAR(255) NOT NULL, `Operator` VARCHAR(255) NOT NULL, `Value` VARCHAR(255) NOT NULL, + `ServerId` BIGINT, `Deleted` BOOL DEFAULT FALSE, `DateFrom` DATETIME(6) NOT NULL, `DateTo` DATETIME(6) NOT NULL diff --git a/kdb-bot/src/bot_data/model/achievement_history.py b/kdb-bot/src/bot_data/model/achievement_history.py index 74208075..2fd5d218 100644 --- a/kdb-bot/src/bot_data/model/achievement_history.py +++ b/kdb-bot/src/bot_data/model/achievement_history.py @@ -1,20 +1,14 @@ -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): +class AchievementHistory(HistoryTableABC): def __init__( self, name: str, attribute: str, operator: str, value: str, - server: Optional[Server], + server: int, deleted: bool, date_from: str, date_to: str, @@ -41,26 +35,18 @@ class Achievement(HistoryTableABC): def name(self) -> str: return self._name - @name.setter - def name(self, value: str): - self._name = value + @property + def attribute(self) -> str: + return self._attribute @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: + def server(self) -> int: return self._server diff --git a/kdb-bot/src/bot_data/model/user_history.py b/kdb-bot/src/bot_data/model/user_history.py index d3b88ca7..2bf39954 100644 --- a/kdb-bot/src/bot_data/model/user_history.py +++ b/kdb-bot/src/bot_data/model/user_history.py @@ -9,6 +9,8 @@ class UserHistory(HistoryTableABC): self, dc_id: int, xp: int, + message_count: int, + reaction_count: int, server: int, deleted: bool, date_from: str, @@ -20,6 +22,8 @@ class UserHistory(HistoryTableABC): self._user_id = id self._discord_id = dc_id self._xp = xp + self._message_count = message_count + self._reaction_count = reaction_count self._server = server self._deleted = deleted @@ -38,6 +42,14 @@ class UserHistory(HistoryTableABC): def xp(self) -> int: return self._xp + @property + def message_count(self) -> int: + return self._message_count + + @property + def reaction_count(self) -> int: + return self._reaction_count + @property def server(self) -> int: return self._server diff --git a/kdb-bot/src/bot_graphql/graphql_module.py b/kdb-bot/src/bot_graphql/graphql_module.py index 5e54b86d..480b4abc 100644 --- a/kdb-bot/src/bot_graphql/graphql_module.py +++ b/kdb-bot/src/bot_graphql/graphql_module.py @@ -27,6 +27,7 @@ from bot_graphql.mutations.level_mutation import LevelMutation from bot_graphql.mutations.user_joined_game_server_mutation import UserJoinedGameServerMutation from bot_graphql.mutations.user_mutation import UserMutation from bot_graphql.queries.achievement_attribute_query import AchievementAttributeQuery +from bot_graphql.queries.achievement_history_query import AchievementHistoryQuery from bot_graphql.queries.achievement_query import AchievementQuery from bot_graphql.queries.auto_role_history_query import AutoRoleHistoryQuery from bot_graphql.queries.auto_role_query import AutoRoleQuery @@ -69,6 +70,7 @@ class GraphQLModule(ModuleABC): # queries services.add_transient(QueryABC, AchievementAttributeQuery) services.add_transient(QueryABC, AchievementQuery) + services.add_transient(QueryABC, AchievementHistoryQuery) services.add_transient(QueryABC, AutoRoleHistoryQuery) services.add_transient(QueryABC, AutoRoleQuery) services.add_transient(QueryABC, AutoRoleRuleHistoryQuery) diff --git a/kdb-bot/src/bot_graphql/model/achievement.gql b/kdb-bot/src/bot_graphql/model/achievement.gql index 7cd86cf9..a49cdae9 100644 --- a/kdb-bot/src/bot_graphql/model/achievement.gql +++ b/kdb-bot/src/bot_graphql/model/achievement.gql @@ -28,7 +28,7 @@ type AchievementHistory implements HistoryTableQuery { operator: String value: String - server: Server + server: ID deleted: Boolean dateFrom: String diff --git a/kdb-bot/src/bot_graphql/queries/achievement_history_query.py b/kdb-bot/src/bot_graphql/queries/achievement_history_query.py new file mode 100644 index 00000000..f8a8eddf --- /dev/null +++ b/kdb-bot/src/bot_graphql/queries/achievement_history_query.py @@ -0,0 +1,13 @@ +from bot_graphql.abc.history_query_abc import HistoryQueryABC + + +class AchievementHistoryQuery(HistoryQueryABC): + def __init__(self): + HistoryQueryABC.__init__(self, "Achievement") + + self.set_field("id", lambda x, *_: x.id) + self.set_field("name", lambda x, *_: x.name) + self.set_field("attribute", lambda x, *_: x.attribute) + self.set_field("operator", lambda x, *_: x.operator) + self.set_field("value", lambda x, *_: x.value) + self.set_field("server", lambda x, *_: x.server) diff --git a/kdb-bot/src/bot_graphql/queries/achievement_query.py b/kdb-bot/src/bot_graphql/queries/achievement_query.py index 46ee137d..e64ff3db 100644 --- a/kdb-bot/src/bot_graphql/queries/achievement_query.py +++ b/kdb-bot/src/bot_graphql/queries/achievement_query.py @@ -1,6 +1,6 @@ from cpl_core.database.context import DatabaseContextABC -from bot_data.model.user_history import UserHistory +from bot_data.model.achievement_history import AchievementHistory from bot_graphql.abc.data_query_with_history_abc import DataQueryWithHistoryABC @@ -9,7 +9,7 @@ class AchievementQuery(DataQueryWithHistoryABC): self, db: DatabaseContextABC, ): - DataQueryWithHistoryABC.__init__(self, "Achievement", "AchievementsHistory", UserHistory, db) + DataQueryWithHistoryABC.__init__(self, "Achievement", "AchievementsHistory", AchievementHistory, db) self.set_field("id", lambda x, *_: x.id) self.set_field("name", lambda x, *_: x.name) diff --git a/kdb-web/src/assets/i18n/de.json b/kdb-web/src/assets/i18n/de.json index 6bcc1328..e8d1bde5 100644 --- a/kdb-web/src/assets/i18n/de.json +++ b/kdb-web/src/assets/i18n/de.json @@ -147,7 +147,10 @@ "permissions": "Berechtigung", "roleId": "Rolle", "server": "Server", - "xp": "XP" + "xp": "XP", + "attribute": "Attribut", + "operator": "Operator", + "value": "Wert" }, "id": "Id", "joined_at": "Beigetreten am", diff --git a/kdb-web/src/assets/i18n/en.json b/kdb-web/src/assets/i18n/en.json index 862fcf4a..b4e584ad 100644 --- a/kdb-web/src/assets/i18n/en.json +++ b/kdb-web/src/assets/i18n/en.json @@ -147,7 +147,10 @@ "permissions": "Permissions", "roleId": "Role", "server": "Server", - "xp": "XP" + "xp": "XP", + "attribute": "Attribute", + "operator": "Operator", + "value": "Value" }, "id": "Id", "joined_at": "Joined at", From fedf2f4b8bdd39bb8faa041a5f249f822b4c337c Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Sun, 16 Jul 2023 13:30:57 +0200 Subject: [PATCH 20/23] Added description to achievements #268_achievements --- .../abc/achievement_repository_abc.py | 4 +++ .../migration/achievements_migration.py | 28 +++++++++++-------- kdb-bot/src/bot_data/model/achievement.py | 14 +++++++++- .../src/bot_data/model/achievement_history.py | 6 ++++ .../bot_data/model/user_got_achievement.py | 9 ++++++ .../achievements_repository_service.py | 17 +++++++++-- .../bot_graphql/filter/achievement_filter.py | 11 ++++++-- kdb-bot/src/bot_graphql/model/achievement.gql | 4 +++ .../mutations/achievement_mutation.py | 7 +++++ .../queries/achievement_history_query.py | 1 + .../bot_graphql/queries/achievement_query.py | 1 + .../src/app/models/data/achievement.model.ts | 2 ++ .../src/app/models/graphql/mutations.model.ts | 10 ++++--- .../src/app/models/graphql/queries.model.ts | 2 ++ .../achievement/achievement.component.html | 24 ++++++++++++++++ .../achievement/achievement.component.ts | 2 ++ kdb-web/src/assets/i18n/de.json | 1 + 17 files changed, 123 insertions(+), 20 deletions(-) diff --git a/kdb-bot/src/bot_data/abc/achievement_repository_abc.py b/kdb-bot/src/bot_data/abc/achievement_repository_abc.py index 3850dc27..ff3014fe 100644 --- a/kdb-bot/src/bot_data/abc/achievement_repository_abc.py +++ b/kdb-bot/src/bot_data/abc/achievement_repository_abc.py @@ -27,6 +27,10 @@ class AchievementRepositoryABC(ABC): def get_achievements_by_user_id(self, user_id: int) -> List[Achievement]: pass + @abstractmethod + def get_user_got_achievements_by_achievement_id(self, achievement_id: int) -> List[Achievement]: + pass + @abstractmethod def add_achievement(self, achievement: Achievement): pass diff --git a/kdb-bot/src/bot_data/migration/achievements_migration.py b/kdb-bot/src/bot_data/migration/achievements_migration.py index eb6933ec..f3a454d3 100644 --- a/kdb-bot/src/bot_data/migration/achievements_migration.py +++ b/kdb-bot/src/bot_data/migration/achievements_migration.py @@ -21,6 +21,7 @@ class AchievementsMigration(MigrationABC): CREATE TABLE IF NOT EXISTS `Achievements` ( `Id` BIGINT NOT NULL AUTO_INCREMENT, `Name` VARCHAR(255) NOT NULL, + `Description` VARCHAR(255) NOT NULL, `Attribute` VARCHAR(255) NOT NULL, `Operator` VARCHAR(255) NOT NULL, `Value` VARCHAR(255) NOT NULL, @@ -41,6 +42,7 @@ class AchievementsMigration(MigrationABC): ( `Id` BIGINT(20) NOT NULL, `Name` VARCHAR(255) NOT NULL, + `Description` VARCHAR(255) NOT NULL, `Attribute` VARCHAR(255) NOT NULL, `Operator` VARCHAR(255) NOT NULL, `Value` VARCHAR(255) NOT NULL, @@ -77,41 +79,45 @@ class AchievementsMigration(MigrationABC): self._cursor.execute(str(f"""ALTER TABLE UsersHistory ADD MessageCount BIGINT NOT NULL DEFAULT 0 AFTER XP;""")) self._cursor.execute(str(f"""ALTER TABLE UsersHistory ADD ReactionCount BIGINT NOT NULL DEFAULT 0 AFTER XP;""")) + self._cursor.execute(str(f"""DROP TRIGGER IF EXISTS `TR_AchievementsUpdate`;""")) 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` + `Id`, `Name`, `Description`, `Attribute`, `Operator`, `Value`, `ServerId`, `DateFrom`, `DateTo` ) VALUES ( - OLD.Id, OLD.Name, OLD.Attribute, OLD.Operator, OLD.Value, OLD.ServerId, OLD.LastModifiedAt, CURRENT_TIMESTAMP(6) + OLD.Id, OLD.Name, OLD.Description, OLD.Attribute, OLD.Operator, OLD.Value, OLD.ServerId, OLD.LastModifiedAt, CURRENT_TIMESTAMP(6) ); END; - - DROP TRIGGER IF EXISTS `TR_AchievementsDelete`; - + """ + ) + ) + + self._cursor.execute(str(f"""DROP TRIGGER IF EXISTS `TR_AchievementsDelete`;""")) + + self._cursor.execute( + str( + f""" CREATE TRIGGER `TR_AchievementsDelete` AFTER DELETE ON `Achievements` FOR EACH ROW BEGIN INSERT INTO `AchievementsHistory` ( - `Id`, `Name`, `Attribute`, `Operator`, `Value`, `ServerId`, `Deleted`, `DateFrom`, `DateTo` + `Id`, `Name`, `Description`, `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) + OLD.Id, OLD.Name, OLD.Description, OLD.Attribute, OLD.Operator, OLD.Value, OLD.ServerId, TRUE, OLD.LastModifiedAt, CURRENT_TIMESTAMP(6) ); END; """ - ), - multi=True, + ) ) def downgrade(self): diff --git a/kdb-bot/src/bot_data/model/achievement.py b/kdb-bot/src/bot_data/model/achievement.py index 9766710e..0b33b693 100644 --- a/kdb-bot/src/bot_data/model/achievement.py +++ b/kdb-bot/src/bot_data/model/achievement.py @@ -11,6 +11,7 @@ class Achievement(TableABC): def __init__( self, name: str, + description: str, attribute: str, operator: str, value: str, @@ -21,6 +22,7 @@ class Achievement(TableABC): ): self._id = id self._name = name + self._description = description self._attribute = attribute if self._is_operator_valid(operator): @@ -53,6 +55,14 @@ class Achievement(TableABC): def name(self, value: str): self._name = value + @property + def description(self) -> str: + return self._description + + @description.setter + def description(self, value: str): + self._description = value + @property def attribute(self) -> str: return self._attribute @@ -103,9 +113,10 @@ class Achievement(TableABC): return str( f""" INSERT INTO `Achievements` ( - `Name`, `Attribute`, `Operator`, `Value`, `ServerId` + `Name`, `Description`, `Attribute`, `Operator`, `Value`, `ServerId` ) VALUES ( '{self._name}', + '{self._description}', '{self._attribute}', '{self._operator}', '{self._value}', @@ -120,6 +131,7 @@ class Achievement(TableABC): f""" UPDATE `Achievements` SET `Name` = '{self._name}', + `Description` = '{self._description}', `Attribute` = '{self._attribute}', `Operator` = '{self._operator}', `Value` = '{self._value}' diff --git a/kdb-bot/src/bot_data/model/achievement_history.py b/kdb-bot/src/bot_data/model/achievement_history.py index 2fd5d218..ae8a9230 100644 --- a/kdb-bot/src/bot_data/model/achievement_history.py +++ b/kdb-bot/src/bot_data/model/achievement_history.py @@ -5,6 +5,7 @@ class AchievementHistory(HistoryTableABC): def __init__( self, name: str, + description: str, attribute: str, operator: str, value: str, @@ -18,6 +19,7 @@ class AchievementHistory(HistoryTableABC): self._id = id self._name = name + self._description = description self._attribute = attribute self._operator = operator self._value = value @@ -35,6 +37,10 @@ class AchievementHistory(HistoryTableABC): def name(self) -> str: return self._name + @property + def description(self) -> str: + return self._description + @property def attribute(self) -> str: return self._attribute diff --git a/kdb-bot/src/bot_data/model/user_got_achievement.py b/kdb-bot/src/bot_data/model/user_got_achievement.py index 18bb6b92..d2c37cf5 100644 --- a/kdb-bot/src/bot_data/model/user_got_achievement.py +++ b/kdb-bot/src/bot_data/model/user_got_achievement.py @@ -62,6 +62,15 @@ class UserGotAchievement(TableABC): """ ) + @staticmethod + def get_select_by_achievement_id_string(id: int) -> str: + return str( + f""" + SELECT * FROM `UserGotAchievements` + WHERE `AchievementId` = {id}; + """ + ) + @property def insert_string(self) -> str: return str( diff --git a/kdb-bot/src/bot_data/service/achievements_repository_service.py b/kdb-bot/src/bot_data/service/achievements_repository_service.py index 130da9c8..9c7e9117 100644 --- a/kdb-bot/src/bot_data/service/achievements_repository_service.py +++ b/kdb-bot/src/bot_data/service/achievements_repository_service.py @@ -31,9 +31,10 @@ class AchievementRepositoryService(AchievementRepositoryABC): result[2], result[3], result[4], - self._servers.get_server_by_id(result[5]), - result[6], + result[5], + self._servers.get_server_by_id(result[6]), result[7], + result[8], id=result[0], ) @@ -89,6 +90,18 @@ class AchievementRepositoryService(AchievementRepositoryABC): return achievements + def get_user_got_achievements_by_achievement_id(self, achievement_id: int) -> List[Achievement]: + achievements_joins = List(UserGotAchievement) + self._logger.trace( + __name__, f"Send SQL command: {UserGotAchievement.get_select_by_achievement_id_string(achievement_id)}" + ) + results = self._context.select(UserGotAchievement.get_select_by_achievement_id_string(achievement_id)) + for result in results: + self._logger.trace(__name__, f"Got UserGotAchievement with id {result[0]}") + achievements_joins.append(self._join_from_result(result)) + + return achievements_joins + def add_achievement(self, achievement: Achievement): self._logger.trace(__name__, f"Send SQL command: {achievement.insert_string}") self._context.cursor.execute(achievement.insert_string) diff --git a/kdb-bot/src/bot_graphql/filter/achievement_filter.py b/kdb-bot/src/bot_graphql/filter/achievement_filter.py index 49c0eab9..674af502 100644 --- a/kdb-bot/src/bot_graphql/filter/achievement_filter.py +++ b/kdb-bot/src/bot_graphql/filter/achievement_filter.py @@ -10,6 +10,7 @@ class AchievementFilter(FilterABC): self._id = None self._name = None + self._description = None self._attribute = None self._operator = None self._value = None @@ -22,6 +23,9 @@ class AchievementFilter(FilterABC): if "name" in values: self._name = values["name"] + if "description" in values: + self._description = values["description"] + if "attribute" in values: self._attribute = values["attribute"] @@ -42,10 +46,13 @@ class AchievementFilter(FilterABC): query = query.where(lambda x: x.id == self._id) if self._name is not None: - query = query.where(lambda x: x.name == self._name) + query = query.where(lambda x: x.name == self._name or self._name in x.name) + + if self._description is not None: + query = query.where(lambda x: x.description == self._description or self._description in x.description) if self._attribute is not None: - query = query.where(lambda x: x.attribute == self._attribute) + query = query.where(lambda x: x.attribute == self._attribute or self._attribute in x.attribute) if self._operator is not None: query = query.where(lambda x: x.operator == self._operator) diff --git a/kdb-bot/src/bot_graphql/model/achievement.gql b/kdb-bot/src/bot_graphql/model/achievement.gql index a49cdae9..df16dd4e 100644 --- a/kdb-bot/src/bot_graphql/model/achievement.gql +++ b/kdb-bot/src/bot_graphql/model/achievement.gql @@ -9,6 +9,7 @@ type AchievementAttribute { type Achievement implements TableWithHistoryQuery { id: ID name: String + description: String attribute: String operator: String value: String @@ -24,6 +25,7 @@ type Achievement implements TableWithHistoryQuery { type AchievementHistory implements HistoryTableQuery { id: ID name: String + description: String attribute: String operator: String value: String @@ -38,6 +40,7 @@ type AchievementHistory implements HistoryTableQuery { input AchievementFilter { id: ID name: String + description: String attribute: String operator: String value: String @@ -53,6 +56,7 @@ type AchievementMutation { input AchievementInput { id: ID name: String + description: String attribute: String operator: String value: String diff --git a/kdb-bot/src/bot_graphql/mutations/achievement_mutation.py b/kdb-bot/src/bot_graphql/mutations/achievement_mutation.py index 99459515..40975a38 100644 --- a/kdb-bot/src/bot_graphql/mutations/achievement_mutation.py +++ b/kdb-bot/src/bot_graphql/mutations/achievement_mutation.py @@ -36,6 +36,7 @@ class AchievementMutation(QueryABC): achievement = Achievement( input["name"], + input["description"], input["attribute"], input["operator"], input["value"], @@ -47,6 +48,7 @@ class AchievementMutation(QueryABC): def get_new_achievement(a: Achievement): return ( a.name == achievement.name + and a.description == achievement.description and a.attribute == achievement.attribute and a.operator == achievement.operator and a.value == achievement.value @@ -60,6 +62,7 @@ class AchievementMutation(QueryABC): self._can_user_mutate_data(achievement.server, UserRoleEnum.moderator) achievement.name = input["name"] if "name" in input else achievement.name + achievement.description = input["description"] if "description" in input else achievement.description achievement.attribute = input["attribute"] if "attribute" in input else achievement.attribute achievement.operator = input["operator"] if "operator" in input else achievement.operator achievement.value = input["value"] if "value" in input else achievement.value @@ -74,6 +77,10 @@ class AchievementMutation(QueryABC): achievement = self._achievements.get_achievement_by_id(id) self._can_user_mutate_data(achievement.server, UserRoleEnum.admin) + joins = self._achievements.get_user_got_achievements_by_achievement_id(id) + for join in joins: + self._achievements.delete_user_got_achievement(join) + self._achievements.delete_achievement(achievement) self._db.save_changes() diff --git a/kdb-bot/src/bot_graphql/queries/achievement_history_query.py b/kdb-bot/src/bot_graphql/queries/achievement_history_query.py index f8a8eddf..d8db3fd3 100644 --- a/kdb-bot/src/bot_graphql/queries/achievement_history_query.py +++ b/kdb-bot/src/bot_graphql/queries/achievement_history_query.py @@ -7,6 +7,7 @@ class AchievementHistoryQuery(HistoryQueryABC): self.set_field("id", lambda x, *_: x.id) self.set_field("name", lambda x, *_: x.name) + self.set_field("description", lambda x, *_: x.description) self.set_field("attribute", lambda x, *_: x.attribute) self.set_field("operator", lambda x, *_: x.operator) self.set_field("value", lambda x, *_: x.value) diff --git a/kdb-bot/src/bot_graphql/queries/achievement_query.py b/kdb-bot/src/bot_graphql/queries/achievement_query.py index e64ff3db..ab72fde0 100644 --- a/kdb-bot/src/bot_graphql/queries/achievement_query.py +++ b/kdb-bot/src/bot_graphql/queries/achievement_query.py @@ -13,6 +13,7 @@ class AchievementQuery(DataQueryWithHistoryABC): self.set_field("id", lambda x, *_: x.id) self.set_field("name", lambda x, *_: x.name) + self.set_field("description", lambda x, *_: x.description) self.set_field("attribute", lambda x, *_: x.attribute) self.set_field("operator", lambda x, *_: x.operator) self.set_field("value", lambda x, *_: x.value) diff --git a/kdb-web/src/app/models/data/achievement.model.ts b/kdb-web/src/app/models/data/achievement.model.ts index 84f6f88e..c32b6440 100644 --- a/kdb-web/src/app/models/data/achievement.model.ts +++ b/kdb-web/src/app/models/data/achievement.model.ts @@ -9,6 +9,7 @@ export interface AchievementAttribute { export interface Achievement extends DataWithHistory { id?: number; name?: string; + description?: string; attribute?: string | AchievementAttribute; operator?: string; value?: string; @@ -20,6 +21,7 @@ export interface Achievement extends DataWithHistory { export interface AchievementFilter { id?: number; name?: string; + description?: string; attribute?: string; operator?: string; value?: string; diff --git a/kdb-web/src/app/models/graphql/mutations.model.ts b/kdb-web/src/app/models/graphql/mutations.model.ts index df3b539d..ac4448d0 100644 --- a/kdb-web/src/app/models/graphql/mutations.model.ts +++ b/kdb-web/src/app/models/graphql/mutations.model.ts @@ -123,11 +123,12 @@ export class Mutations { `; static createAchievement = ` - mutation createAchievement($name: String, $attribute: String, $operator: String, $value: String, $serverId: ID) { + mutation createAchievement($name: String, $description: String, $attribute: String, $operator: String, $value: String, $serverId: ID) { achievement { - createAchievement(input: { name: $name, attribute: $attribute, operator: $operator, value: $value, serverId: $serverId}) { + createAchievement(input: { name: $name, description: $description, attribute: $attribute, operator: $operator, value: $value, serverId: $serverId}) { id name + description attribute operator value @@ -140,11 +141,12 @@ export class Mutations { `; static updateAchievement = ` - mutation updateAchievement($id: ID, $name: String, $attribute: String, $operator: String, $value: String) { + mutation updateAchievement($id: ID, $name: String, $description: String, $attribute: String, $operator: String, $value: String) { achievement { - updateAchievement(input: { id: $id, name: $name, attribute: $attribute, operator: $operator, value: $value}) { + updateAchievement(input: { id: $id, name: $name, description: $description, attribute: $attribute, operator: $operator, value: $value}) { id name + description attribute operator value diff --git a/kdb-web/src/app/models/graphql/queries.model.ts b/kdb-web/src/app/models/graphql/queries.model.ts index 453b7b64..6dc78904 100644 --- a/kdb-web/src/app/models/graphql/queries.model.ts +++ b/kdb-web/src/app/models/graphql/queries.model.ts @@ -117,6 +117,7 @@ export class Queries { achievements(filter: $filter, page: $page, sort: $sort) { id name + description attribute operator value @@ -141,6 +142,7 @@ export class Queries { history { id name + description attribute operator value diff --git a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html index e9ceeb2a..44f88ad5 100644 --- a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html +++ b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html @@ -43,6 +43,13 @@
+ +
+
{{'view.server.achievements.headers.description' | translate}}
+ +
+ +
{{'view.server.achievements.headers.attribute' | translate}}
@@ -96,6 +103,12 @@ placeholder="{{'view.server.achievements.headers.name' | translate}}"> + +
+ +
+ @@ -129,6 +142,17 @@ + + + + + + + {{achievement.description}} + + + + diff --git a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts index 13d18fe7..114d1d8a 100644 --- a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts +++ b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts @@ -227,6 +227,7 @@ export class AchievementComponent implements OnInit, OnDestroy { this.spinner.showSpinner(); this.data.mutation(Mutations.createAchievement, { name: newAchievement.name, + description: newAchievement.description, attribute: newAchievement.attribute, operator: newAchievement.operator, value: newAchievement.value + "", @@ -250,6 +251,7 @@ export class AchievementComponent implements OnInit, OnDestroy { this.data.mutation(Mutations.updateAchievement, { id: newAchievement.id, name: newAchievement.name, + description: newAchievement.description, attribute: newAchievement.attribute, operator: newAchievement.operator, value: newAchievement.value + "" diff --git a/kdb-web/src/assets/i18n/de.json b/kdb-web/src/assets/i18n/de.json index e8d1bde5..7deee1ea 100644 --- a/kdb-web/src/assets/i18n/de.json +++ b/kdb-web/src/assets/i18n/de.json @@ -326,6 +326,7 @@ "headers": { "attribute": "Attribut", "name": "Name", + "description": "Beschreibung", "operator": "Operator", "value": "Wert" }, From 926323252ad09261c811f55c523760f2beb62f52 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Mon, 17 Jul 2023 11:09:34 +0200 Subject: [PATCH 21/23] Add xp for achievement #268_achievements --- kdb-bot/src/bot/config | 2 +- .../src/modules/achievements/achievement_service.py | 11 +++++++++++ .../base/configuration/base_server_settings.py | 5 +++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/kdb-bot/src/bot/config b/kdb-bot/src/bot/config index 0179ab1e..440fb3bd 160000 --- a/kdb-bot/src/bot/config +++ b/kdb-bot/src/bot/config @@ -1 +1 @@ -Subproject commit 0179ab1ed725c233c5844ee94ed51cad176d9c7e +Subproject commit 440fb3bd353dce31a2408c977a3168e3cfc32f9a diff --git a/kdb-bot/src/modules/achievements/achievement_service.py b/kdb-bot/src/modules/achievements/achievement_service.py index b446d095..9e8a0ed8 100644 --- a/kdb-bot/src/modules/achievements/achievement_service.py +++ b/kdb-bot/src/modules/achievements/achievement_service.py @@ -8,11 +8,13 @@ from cpl_translation import TranslatePipe from bot_core.configuration.server_settings import ServerSettings from bot_core.service.message_service import MessageService from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC +from bot_data.abc.user_repository_abc import UserRepositoryABC from bot_data.model.achievement import Achievement from bot_data.model.user import User from bot_data.model.user_got_achievement import UserGotAchievement from modules.achievements.achievement_attribute_resolver import AchievementAttributeResolver from modules.achievements.model.achievement_attribute import AchievementAttribute +from modules.base.configuration.base_server_settings import BaseServerSettings class AchievementService: @@ -22,6 +24,7 @@ class AchievementService: logger: LoggerABC, bot: DiscordBotServiceABC, achievements: AchievementRepositoryABC, + users: UserRepositoryABC, db: DatabaseContextABC, message_service: MessageService, resolver: AchievementAttributeResolver, @@ -31,6 +34,7 @@ class AchievementService: self._logger = logger self._bot = bot self._achievements = achievements + self._users = users self._db = db self._message_service = message_service self._t = t @@ -92,8 +96,15 @@ class AchievementService: self._achievements.add_user_got_achievement(UserGotAchievement(user, achievement, user.server)) self._db.save_changes() + await self._give_user_xp(user) await self._send_achievement_notification(user.server.discord_id, user.discord_id, achievement.name) + async def _give_user_xp(self, user: User): + settings: BaseServerSettings = self._config.get_configuration(f"BaseServerSettings_{user.server.discord_id}") + user.xp += settings.xp_per_achievement + self._users.update_user(user) + self._db.save_changes() + async def _send_achievement_notification(self, guild_id: int, member_id: int, achievement_name: str): member = self._bot.get_guild(guild_id).get_member(member_id) diff --git a/kdb-bot/src/modules/base/configuration/base_server_settings.py b/kdb-bot/src/modules/base/configuration/base_server_settings.py index f0538374..8bd987b4 100644 --- a/kdb-bot/src/modules/base/configuration/base_server_settings.py +++ b/kdb-bot/src/modules/base/configuration/base_server_settings.py @@ -16,6 +16,7 @@ class BaseServerSettings(ConfigurationModelABC): self._max_message_xp_per_hour: int = 0 self._xp_per_ontime_hour: int = 0 self._xp_per_event_participation: int = 0 + self._xp_per_achievement: int = 0 self._afk_channel_ids: List[int] = List(int) self._afk_command_channel_id: int = 0 self._help_command_reference_url: str = "" @@ -51,6 +52,10 @@ class BaseServerSettings(ConfigurationModelABC): def xp_per_event_participation(self) -> int: return self._xp_per_event_participation + @property + def xp_per_achievement(self) -> int: + return self._xp_per_achievement + @property def afk_channel_ids(self) -> List[int]: return self._afk_channel_ids From 0846bbb29b2d3ddc844d7abfbeab2bf3db55f876 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 18 Jul 2023 11:10:02 +0200 Subject: [PATCH 22/23] Added achievements to data integrity service #268_achievements --- .../service/data_integrity_service.py | 23 +++++++++++++++++++ .../achievements/achievement_service.py | 6 ++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/kdb-bot/src/bot_core/service/data_integrity_service.py b/kdb-bot/src/bot_core/service/data_integrity_service.py index c3f76912..01f55653 100644 --- a/kdb-bot/src/bot_core/service/data_integrity_service.py +++ b/kdb-bot/src/bot_core/service/data_integrity_service.py @@ -24,6 +24,7 @@ from bot_data.model.user_joined_server import UserJoinedServer from bot_data.model.user_joined_voice_channel import UserJoinedVoiceChannel from bot_data.service.seeder_service import SeederService from bot_data.service.user_repository_service import ServerRepositoryABC +from modules.achievements.achievement_service import AchievementService from modules.base.configuration.base_server_settings import BaseServerSettings @@ -42,6 +43,7 @@ class DataIntegrityService: user_joins: UserJoinedServerRepositoryABC, user_joins_vc: UserJoinedVoiceChannelRepositoryABC, user_joined_gs: UserJoinedGameServerRepositoryABC, + achievement_service: AchievementService, dtp: DateTimeOffsetPipe, ): self._config = config @@ -57,6 +59,7 @@ class DataIntegrityService: self._user_joins = user_joins self._user_joins_vc = user_joins_vc self._user_joined_gs = user_joined_gs + self._achievements = achievement_service self._dtp = dtp self._is_for_shutdown = False @@ -360,6 +363,25 @@ class DataIntegrityService: except Exception as e: self._logger.error(__name__, f"Cannot get UserJoinedGameServer", e) + def _check_for_user_achievements(self): + self._logger.debug(__name__, f"Start checking UserGotAchievement table") + + 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}") + + self._bot.loop.create_task(self._achievements.validate_achievements_for_user(user)) + def check_data_integrity(self, is_for_shutdown=False): if is_for_shutdown != self._is_for_shutdown: self._is_for_shutdown = is_for_shutdown @@ -371,3 +393,4 @@ class DataIntegrityService: self._check_user_joins() self._check_user_joins_vc() self._check_user_joined_gs() + self._check_for_user_achievements() diff --git a/kdb-bot/src/modules/achievements/achievement_service.py b/kdb-bot/src/modules/achievements/achievement_service.py index 9e8a0ed8..fe875965 100644 --- a/kdb-bot/src/modules/achievements/achievement_service.py +++ b/kdb-bot/src/modules/achievements/achievement_service.py @@ -78,7 +78,7 @@ class AchievementService: return self._attributes def _match(self, value: any, operator: str, expected_value: str) -> bool: - return self._operators[operator](value, expected_value) + return self._operators[operator](str(value), expected_value) def has_user_achievement_already(self, user: User, achievement: Achievement) -> bool: user_achievements = self._achievements.get_achievements_by_user_id(user.id) @@ -96,10 +96,10 @@ class AchievementService: self._achievements.add_user_got_achievement(UserGotAchievement(user, achievement, user.server)) self._db.save_changes() - await self._give_user_xp(user) + self._give_user_xp(user) await self._send_achievement_notification(user.server.discord_id, user.discord_id, achievement.name) - async def _give_user_xp(self, user: User): + def _give_user_xp(self, user: User): settings: BaseServerSettings = self._config.get_configuration(f"BaseServerSettings_{user.server.discord_id}") user.xp += settings.xp_per_achievement self._users.update_user(user) From f11d9af6c7a4fdd27a71022f6c5b40cd1ed6fa28 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Mon, 14 Aug 2023 09:35:46 +0200 Subject: [PATCH 23/23] Fixed workspace #268_achievements --- kdb-bot/cpl-workspace.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kdb-bot/cpl-workspace.json b/kdb-bot/cpl-workspace.json index df40bb8b..c11fb4ac 100644 --- a/kdb-bot/cpl-workspace.json +++ b/kdb-bot/cpl-workspace.json @@ -18,8 +18,7 @@ "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", - "modules/achievements": "src/modules/achievements/modules/achievements.json" + "set-version": "tools/set_version/set-version.json" }, "Scripts": { "format": "black ./",