From 1857473ccc1cb2ff706df625a0ca2636032dcd69 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Sun, 16 Oct 2022 20:59:02 +0200 Subject: [PATCH 01/11] Added logic to get discord servers #72 --- kdb-bot/src/bot/config/feature-flags.json | 3 +- .../bot_api/controller/discord/__init__.py | 0 .../controller/discord/server_controller.py | 39 +++++++++++++++ kdb-bot/src/bot_api/event/__init__.py | 0 kdb-bot/src/bot_api/model/discord/__init__.py | 0 .../src/bot_api/model/discord/server_dto.py | 49 +++++++++++++++++++ .../src/bot_api/service/discord_service.py | 23 +++++++++ .../bot_api/transformer/server_transformer.py | 19 +++++++ 8 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 kdb-bot/src/bot_api/controller/discord/__init__.py create mode 100644 kdb-bot/src/bot_api/controller/discord/server_controller.py create mode 100644 kdb-bot/src/bot_api/event/__init__.py create mode 100644 kdb-bot/src/bot_api/model/discord/__init__.py create mode 100644 kdb-bot/src/bot_api/model/discord/server_dto.py create mode 100644 kdb-bot/src/bot_api/service/discord_service.py create mode 100644 kdb-bot/src/bot_api/transformer/server_transformer.py diff --git a/kdb-bot/src/bot/config/feature-flags.json b/kdb-bot/src/bot/config/feature-flags.json index 2d214f7f3e..68bf6e002c 100644 --- a/kdb-bot/src/bot/config/feature-flags.json +++ b/kdb-bot/src/bot/config/feature-flags.json @@ -10,7 +10,6 @@ "DatabaseModule": true, "ModeratorModule": true, "PermissionModule": true, - "PresenceModule": true, - "ApiOnly": true + "PresenceModule": true } } diff --git a/kdb-bot/src/bot_api/controller/discord/__init__.py b/kdb-bot/src/bot_api/controller/discord/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/kdb-bot/src/bot_api/controller/discord/server_controller.py b/kdb-bot/src/bot_api/controller/discord/server_controller.py new file mode 100644 index 0000000000..957624a465 --- /dev/null +++ b/kdb-bot/src/bot_api/controller/discord/server_controller.py @@ -0,0 +1,39 @@ +from cpl_core.configuration import ConfigurationABC +from cpl_core.environment import ApplicationEnvironmentABC +from cpl_core.mailing import EMailClientABC, EMailClientSettings +from cpl_translation import TranslatePipe +from flask import Response, jsonify + +from bot_api.api import Api +from bot_api.logging.api_logger import ApiLogger +from bot_api.route.route import Route +from bot_api.service.discord_service import DiscordService + + +class ServerController: + BasePath = f'/api/discord/server' + + def __init__( + self, + config: ConfigurationABC, + env: ApplicationEnvironmentABC, + logger: ApiLogger, + t: TranslatePipe, + api: Api, + mail_settings: EMailClientSettings, + mailer: EMailClientABC, + discord_service: DiscordService + ): + self._config = config + self._env = env + self._logger = logger + self._t = t + self._api = api + self._mail_settings = mail_settings + self._mailer = mailer + self._discord_service = discord_service + + @Route.get(f'{BasePath}/servers') + @Route.authorize + async def get_all_servers(self) -> Response: + return jsonify(self._discord_service.get_all_servers().select(lambda x: x.to_dict())) diff --git a/kdb-bot/src/bot_api/event/__init__.py b/kdb-bot/src/bot_api/event/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/kdb-bot/src/bot_api/model/discord/__init__.py b/kdb-bot/src/bot_api/model/discord/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/kdb-bot/src/bot_api/model/discord/server_dto.py b/kdb-bot/src/bot_api/model/discord/server_dto.py new file mode 100644 index 0000000000..e7115dd231 --- /dev/null +++ b/kdb-bot/src/bot_api/model/discord/server_dto.py @@ -0,0 +1,49 @@ +from bot_api.abc.dto_abc import DtoABC + + +class ServerDTO(DtoABC): + + def __init__( + self, + server_id: int, + discord_id: int, + name: str, + member_count: int + + ): + DtoABC.__init__(self) + + self._server_id = server_id + self._discord_id = discord_id + self._name = name + self._member_count = member_count + + @property + def server_id(self) -> int: + return self._server_id + + @property + def discord_id(self) -> int: + return self._discord_id + + @property + def name(self) -> str: + return self._name + + @property + def member_count(self) -> int: + return self._member_count + + def from_dict(self, values: dict): + self._server_id = int(values['serverId']) + self._discord_id = int(values['discordId']) + self._name = values['name'] + self._member_count = int(values['memberCount']) + + def to_dict(self) -> dict: + return { + 'serverId': self._server_id, + 'discordId': self._discord_id, + 'name': self._name, + 'memberCount': self._member_count, + } diff --git a/kdb-bot/src/bot_api/service/discord_service.py b/kdb-bot/src/bot_api/service/discord_service.py new file mode 100644 index 0000000000..448d4d7f18 --- /dev/null +++ b/kdb-bot/src/bot_api/service/discord_service.py @@ -0,0 +1,23 @@ +from cpl_discord.service import DiscordBotServiceABC +from cpl_query.extension import List + +from bot_api.model.discord.server_dto import ServerDTO +from bot_api.transformer.server_transformer import ServerTransformer +from bot_data.abc.server_repository_abc import ServerRepositoryABC + + +class DiscordService: + + def __init__( + self, + bot: DiscordBotServiceABC, + servers: ServerRepositoryABC, + ): + self._bot = bot + self._servers = servers + + def get_all_servers(self) -> List[ServerDTO]: + servers = self._servers.get_servers().select() + return servers.select( + lambda x: ServerTransformer.to_dto(x, self._bot.get_guild(x.discord_server_id).name, self._bot.get_guild(x.discord_server_id).member_count) + ) diff --git a/kdb-bot/src/bot_api/transformer/server_transformer.py b/kdb-bot/src/bot_api/transformer/server_transformer.py new file mode 100644 index 0000000000..920fd506d0 --- /dev/null +++ b/kdb-bot/src/bot_api/transformer/server_transformer.py @@ -0,0 +1,19 @@ +from bot_api.abc.transformer_abc import TransformerABC +from bot_api.model.discord.server_dto import ServerDTO +from bot_data.model.server import Server + + +class ServerTransformer(TransformerABC): + + @staticmethod + def to_db(dto: ServerDTO) -> Server: + return Server(dto.discord_id) + + @staticmethod + def to_dto(db: Server, name: str, member_count: int) -> ServerDTO: + return ServerDTO( + db.server_id, + db.discord_server_id, + name, + member_count, + ) From dfcc516389a0a981931e640ed92bc4a83176fc42 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Mon, 17 Oct 2022 13:53:37 +0200 Subject: [PATCH 02/11] Added role check to authorization #72 --- kdb-bot/src/bot_api/api_module.py | 4 +++ .../src/bot_api/controller/auth_controller.py | 15 ++++++----- .../controller/discord/server_controller.py | 3 ++- .../exception/service_error_code_enum.py | 1 + kdb-bot/src/bot_api/route/route.py | 26 +++++++++++++++++-- kdb-bot/src/bot_api/service/auth_service.py | 4 ++- .../transformer/auth_user_transformer.py | 2 +- 7 files changed, 43 insertions(+), 12 deletions(-) diff --git a/kdb-bot/src/bot_api/api_module.py b/kdb-bot/src/bot_api/api_module.py index d6d659ecba..0a9446ad5d 100644 --- a/kdb-bot/src/bot_api/api_module.py +++ b/kdb-bot/src/bot_api/api_module.py @@ -11,10 +11,12 @@ from flask import Flask from bot_api.abc.auth_service_abc import AuthServiceABC from bot_api.api import Api from bot_api.api_thread import ApiThread +from bot_api.controller.discord.server_controller import ServerController from bot_api.controller.gui_controller import GuiController from bot_api.controller.auth_controller import AuthController from bot_api.event.bot_api_on_ready_event import BotApiOnReadyEvent from bot_api.service.auth_service import AuthService +from bot_api.service.discord_service import DiscordService from bot_core.abc.module_abc import ModuleABC from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum @@ -41,6 +43,8 @@ class ApiModule(ModuleABC): services.add_transient(AuthServiceABC, AuthService) services.add_transient(AuthController) services.add_transient(GuiController) + services.add_transient(DiscordService) + services.add_transient(ServerController) # cpl-discord self._dc.add_event(DiscordEventTypesEnum.on_ready.value, BotApiOnReadyEvent) diff --git a/kdb-bot/src/bot_api/controller/auth_controller.py b/kdb-bot/src/bot_api/controller/auth_controller.py index a23d490dff..78a17e79e6 100644 --- a/kdb-bot/src/bot_api/controller/auth_controller.py +++ b/kdb-bot/src/bot_api/controller/auth_controller.py @@ -15,6 +15,7 @@ from bot_api.model.auth_user_dto import AuthUserDTO from bot_api.model.token_dto import TokenDTO from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO from bot_api.route.route import Route +from bot_data.model.auth_role_enum import AuthRoleEnum class AuthController: @@ -41,13 +42,13 @@ class AuthController: self._auth_service = auth_service @Route.get(f'{BasePath}/users') - @Route.authorize + @Route.authorize(role=AuthRoleEnum.admin) async def get_all_users(self) -> Response: result = await self._auth_service.get_all_auth_users_async() return jsonify(result.select(lambda x: x.to_dict())) @Route.post(f'{BasePath}/users/get/filtered') - @Route.authorize + @Route.authorize(role=AuthRoleEnum.admin) async def get_filtered_users(self) -> Response: dto: AuthUserSelectCriteria = JSONProcessor.process(AuthUserSelectCriteria, request.get_json(force=True, silent=True)) result = await self._auth_service.get_filtered_auth_users_async(dto) @@ -55,13 +56,13 @@ class AuthController: return jsonify(result.to_dict()) @Route.get(f'{BasePath}/users/get/') - @Route.authorize + @Route.authorize(role=AuthRoleEnum.admin) async def get_user_from_email(self, email: str) -> Response: result = await self._auth_service.get_auth_user_by_email_async(email) return jsonify(result.to_dict()) @Route.get(f'{BasePath}/users/find/') - @Route.authorize + @Route.authorize(role=AuthRoleEnum.admin) async def find_user_from_email(self, email: str) -> Response: result = await self._auth_service.find_auth_user_by_email_async(email) return jsonify(result.to_dict()) @@ -109,7 +110,7 @@ class AuthController: return '', 200 @Route.post(f'{BasePath}/update-user-as-admin') - @Route.authorize + @Route.authorize(role=AuthRoleEnum.admin) async def update_user_as_admin(self): dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True)) await self._auth_service.update_user_as_admin_async(dto) @@ -129,14 +130,14 @@ class AuthController: return '', 200 @Route.post(f'{BasePath}/delete-user') - @Route.authorize + @Route.authorize(role=AuthRoleEnum.admin) async def delete_user(self): dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True)) await self._auth_service.delete_auth_user_async(dto) return '', 200 @Route.post(f'{BasePath}/delete-user-by-mail/') - @Route.authorize + @Route.authorize(role=AuthRoleEnum.admin) async def delete_user_by_mail(self, email: str): await self._auth_service.delete_auth_user_by_email_async(email) return '', 200 diff --git a/kdb-bot/src/bot_api/controller/discord/server_controller.py b/kdb-bot/src/bot_api/controller/discord/server_controller.py index 957624a465..59b7f531b8 100644 --- a/kdb-bot/src/bot_api/controller/discord/server_controller.py +++ b/kdb-bot/src/bot_api/controller/discord/server_controller.py @@ -8,6 +8,7 @@ from bot_api.api import Api from bot_api.logging.api_logger import ApiLogger from bot_api.route.route import Route from bot_api.service.discord_service import DiscordService +from bot_data.model.auth_role_enum import AuthRoleEnum class ServerController: @@ -34,6 +35,6 @@ class ServerController: self._discord_service = discord_service @Route.get(f'{BasePath}/servers') - @Route.authorize + @Route.authorize(role=AuthRoleEnum.admin) async def get_all_servers(self) -> Response: return jsonify(self._discord_service.get_all_servers().select(lambda x: x.to_dict())) diff --git a/kdb-bot/src/bot_api/exception/service_error_code_enum.py b/kdb-bot/src/bot_api/exception/service_error_code_enum.py index 7b19aeb18c..5848bf3374 100644 --- a/kdb-bot/src/bot_api/exception/service_error_code_enum.py +++ b/kdb-bot/src/bot_api/exception/service_error_code_enum.py @@ -21,3 +21,4 @@ class ServiceErrorCode(Enum): MailError = 10 Unauthorized = 11 + Forbidden = 12 diff --git a/kdb-bot/src/bot_api/route/route.py b/kdb-bot/src/bot_api/route/route.py index ce8826bcd1..26bdad2b80 100644 --- a/kdb-bot/src/bot_api/route/route.py +++ b/kdb-bot/src/bot_api/route/route.py @@ -1,5 +1,6 @@ +import functools from functools import wraps -from typing import Optional +from typing import Optional, Callable from flask import request, jsonify from flask_cors import cross_origin @@ -9,6 +10,7 @@ from bot_api.exception.service_error_code_enum import ServiceErrorCode from bot_api.exception.service_exception import ServiceException from bot_api.model.error_dto import ErrorDTO from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC +from bot_data.model.auth_role_enum import AuthRoleEnum class Route: @@ -23,7 +25,10 @@ class Route: cls._auth = auth @classmethod - def authorize(cls, f): + def authorize(cls, f: Callable = None, role: AuthRoleEnum = None): + if f is None: + return functools.partial(cls.authorize, role=role) + @wraps(f) async def decorator(*args, **kwargs): token = None @@ -46,6 +51,23 @@ class Route: error = ErrorDTO(ex.error_code, ex.message) return jsonify(error.to_dict()), 401 + token = cls._auth.decode_token(token) + if token is None or 'email' not in token: + ex = ServiceException(ServiceErrorCode.Unauthorized, f'Token invalid') + error = ErrorDTO(ex.error_code, ex.message) + return jsonify(error.to_dict()), 401 + + user = cls._auth_users.get_auth_user_by_email(token['email']) + if user is None: + ex = ServiceException(ServiceErrorCode.Unauthorized, f'Token invalid') + error = ErrorDTO(ex.error_code, ex.message) + return jsonify(error.to_dict()), 401 + + if role is not None and user.auth_role.value < role.value: + ex = ServiceException(ServiceErrorCode.Unauthorized, f'Role {role} required') + error = ErrorDTO(ex.error_code, ex.message) + return jsonify(error.to_dict()), 403 + return await f(*args, **kwargs) return decorator diff --git a/kdb-bot/src/bot_api/service/auth_service.py b/kdb-bot/src/bot_api/service/auth_service.py index e1e4a2b943..540f5d1409 100644 --- a/kdb-bot/src/bot_api/service/auth_service.py +++ b/kdb-bot/src/bot_api/service/auth_service.py @@ -27,6 +27,8 @@ from bot_api.transformer.auth_user_transformer import AuthUserTransformer as AUT from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC from bot_data.model.auth_user import AuthUser +_email_regex = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' + class AuthService(AuthServiceABC): @@ -65,7 +67,7 @@ class AuthService(AuthServiceABC): @staticmethod def _is_email_valid(email: str) -> bool: - if re.match(re.compile(r'^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w{2,3}$'), email) is not None: + if re.fullmatch(_email_regex, email) is not None: return True return False diff --git a/kdb-bot/src/bot_api/transformer/auth_user_transformer.py b/kdb-bot/src/bot_api/transformer/auth_user_transformer.py index 3f84a9399a..3506611aad 100644 --- a/kdb-bot/src/bot_api/transformer/auth_user_transformer.py +++ b/kdb-bot/src/bot_api/transformer/auth_user_transformer.py @@ -19,7 +19,7 @@ class AuthUserTransformer(TransformerABC): None, None, datetime.now(tz=timezone.utc), - AuthRoleEnum.normal if dto.auth_role is None else dto.auth_role, + AuthRoleEnum.normal if dto.auth_role is None else AuthRoleEnum(dto.auth_role), id=0 if dto.id is None else dto.id ) From d7a0706e0c333a395f539b3e911feffe7494f755 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Mon, 17 Oct 2022 16:07:33 +0200 Subject: [PATCH 03/11] Improved get server logic #72 --- kdb-bot/src/bot_api/abc/auth_service_abc.py | 7 ++++ .../controller/discord/server_controller.py | 11 +++++- kdb-bot/src/bot_api/model/auth_user_dto.py | 12 +++++++ kdb-bot/src/bot_api/service/auth_service.py | 32 +++++++++++++++++ .../src/bot_api/service/discord_service.py | 34 +++++++++++++++++-- .../transformer/auth_user_transformer.py | 6 ++-- .../src/bot_data/abc/user_repository_abc.py | 3 ++ .../src/bot_data/migration/api_migration.py | 4 ++- kdb-bot/src/bot_data/model/auth_user.py | 13 +++++++ .../service/auth_user_repository_service.py | 1 + .../service/user_repository_service.py | 15 ++++++++ 11 files changed, 132 insertions(+), 6 deletions(-) diff --git a/kdb-bot/src/bot_api/abc/auth_service_abc.py b/kdb-bot/src/bot_api/abc/auth_service_abc.py index 3a006a0dcf..cd54128dda 100644 --- a/kdb-bot/src/bot_api/abc/auth_service_abc.py +++ b/kdb-bot/src/bot_api/abc/auth_service_abc.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from typing import Optional from cpl_query.extension import List @@ -23,6 +24,12 @@ class AuthServiceABC(ABC): @abstractmethod def decode_token(self, token: str) -> dict: pass + @abstractmethod + def get_decoded_token_from_request(self) -> dict: pass + + @abstractmethod + def find_decoded_token_from_request(self) -> Optional[dict]: pass + @abstractmethod async def get_all_auth_users_async(self) -> List[AuthUserDTO]: pass diff --git a/kdb-bot/src/bot_api/controller/discord/server_controller.py b/kdb-bot/src/bot_api/controller/discord/server_controller.py index 59b7f531b8..394338699e 100644 --- a/kdb-bot/src/bot_api/controller/discord/server_controller.py +++ b/kdb-bot/src/bot_api/controller/discord/server_controller.py @@ -37,4 +37,13 @@ class ServerController: @Route.get(f'{BasePath}/servers') @Route.authorize(role=AuthRoleEnum.admin) async def get_all_servers(self) -> Response: - return jsonify(self._discord_service.get_all_servers().select(lambda x: x.to_dict())) + result = await self._discord_service.get_all_servers() + result = result.select(lambda x: x.to_dict()) + return jsonify(result) + + @Route.get(f'{BasePath}/servers-by-user') + @Route.authorize + async def get_all_servers_by_user(self) -> Response: + result = await self._discord_service.get_all_servers_by_user() + result = result.select(lambda x: x.to_dict()) + return jsonify(result) diff --git a/kdb-bot/src/bot_api/model/auth_user_dto.py b/kdb-bot/src/bot_api/model/auth_user_dto.py index 4fb5339455..8d42e1fad6 100644 --- a/kdb-bot/src/bot_api/model/auth_user_dto.py +++ b/kdb-bot/src/bot_api/model/auth_user_dto.py @@ -15,6 +15,7 @@ class AuthUserDTO(DtoABC): password: str, confirmation_id: Optional[str], auth_role: AuthRoleEnum, + user_id: Optional[int], ): DtoABC.__init__(self) @@ -25,6 +26,7 @@ class AuthUserDTO(DtoABC): self._password = password self._is_confirmed = confirmation_id is None self._auth_role = auth_role + self._user_id = user_id @property def id(self) -> int: @@ -78,6 +80,14 @@ class AuthUserDTO(DtoABC): def auth_role(self, value: AuthRoleEnum): self._auth_role = value + @property + def user_id(self) -> Optional[int]: + return self._user_id + + @user_id.setter + def user_id(self, value: Optional[int]): + self._user_id = value + def from_dict(self, values: dict): self._id = values['id'] self._first_name = values['firstName'] @@ -86,6 +96,7 @@ class AuthUserDTO(DtoABC): self._password = values['password'] self._is_confirmed = values['isConfirmed'] self._auth_role = values['authRole'] + self._user_id = values['userId'] def to_dict(self) -> dict: return { @@ -96,4 +107,5 @@ class AuthUserDTO(DtoABC): 'password': self._password, 'isConfirmed': self._is_confirmed, 'authRole': self._auth_role.value, + 'userId': self._user_id, } diff --git a/kdb-bot/src/bot_api/service/auth_service.py b/kdb-bot/src/bot_api/service/auth_service.py index 540f5d1409..b2056530d7 100644 --- a/kdb-bot/src/bot_api/service/auth_service.py +++ b/kdb-bot/src/bot_api/service/auth_service.py @@ -9,6 +9,7 @@ from cpl_core.database.context import DatabaseContextABC from cpl_core.mailing import EMailClientABC, EMail from cpl_query.extension import List from cpl_translation import TranslatePipe +from flask import request from bot_api.abc.auth_service_abc import AuthServiceABC from bot_api.configuration.authentication_settings import AuthenticationSettings @@ -96,6 +97,37 @@ class AuthService(AuthServiceABC): algorithms=['HS256'] ) + def get_decoded_token_from_request(self) -> dict: + token = None + if 'Authorization' in request.headers: + bearer = request.headers.get('Authorization') + token = bearer.split()[1] + + if token is None: + raise ServiceException(ServiceErrorCode.Unauthorized, f'Token not set') + + return jwt.decode( + token, + key=self._auth_settings.secret_key, + issuer=self._auth_settings.issuer, + audience=self._auth_settings.audience, + algorithms=['HS256'] + ) + + def find_decoded_token_from_request(self) -> Optional[dict]: + token = None + if 'Authorization' in request.headers: + bearer = request.headers.get('Authorization') + token = bearer.split()[1] + + return jwt.decode( + token, + key=self._auth_settings.secret_key, + issuer=self._auth_settings.issuer, + audience=self._auth_settings.audience, + algorithms=['HS256'] + ) if token is not None else None + def _create_and_save_refresh_token(self, user: AuthUser) -> str: token = str(uuid.uuid4()) user.refresh_token = token diff --git a/kdb-bot/src/bot_api/service/discord_service.py b/kdb-bot/src/bot_api/service/discord_service.py index 448d4d7f18..e6b3056c4e 100644 --- a/kdb-bot/src/bot_api/service/discord_service.py +++ b/kdb-bot/src/bot_api/service/discord_service.py @@ -1,9 +1,18 @@ +from typing import Optional + from cpl_discord.service import DiscordBotServiceABC from cpl_query.extension import List +from flask import jsonify +from bot_api.abc.auth_service_abc import AuthServiceABC +from bot_api.exception.service_error_code_enum import ServiceErrorCode +from bot_api.exception.service_exception import ServiceException from bot_api.model.discord.server_dto import ServerDTO +from bot_api.model.error_dto import ErrorDTO from bot_api.transformer.server_transformer import ServerTransformer from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.abc.user_repository_abc import UserRepositoryABC +from bot_data.model.auth_role_enum import AuthRoleEnum class DiscordService: @@ -12,12 +21,33 @@ class DiscordService: self, bot: DiscordBotServiceABC, servers: ServerRepositoryABC, + auth: AuthServiceABC, + users: UserRepositoryABC, ): self._bot = bot self._servers = servers + self._auth = auth + self._users = users + + async def get_all_servers(self) -> List[ServerDTO]: + servers = self._servers.get_servers() + return servers.select( + lambda x: ServerTransformer.to_dto(x, self._bot.get_guild(x.discord_server_id).name, self._bot.get_guild(x.discord_server_id).member_count) + ) + + async def get_all_servers_by_user(self) -> List[ServerDTO]: + token = self._auth.get_decoded_token_from_request() + if token is None or 'email' not in token or 'role' not in token: + raise ServiceException(ServiceErrorCode.InvalidData, 'Token invalid') + + role = AuthRoleEnum(token['role']) + if role == AuthRoleEnum.admin: + servers = self._servers.get_servers() + else: + user = await self._auth.find_auth_user_by_email_async(token['email']) + user_from_db = self._users.find_user_by_id(0 if user.user_id is None else user.user_id) + servers = self._servers.get_servers().where(lambda x: user_from_db is not None and x.server_id == user_from_db.server.server_id) - def get_all_servers(self) -> List[ServerDTO]: - servers = self._servers.get_servers().select() return servers.select( lambda x: ServerTransformer.to_dto(x, self._bot.get_guild(x.discord_server_id).name, self._bot.get_guild(x.discord_server_id).member_count) ) diff --git a/kdb-bot/src/bot_api/transformer/auth_user_transformer.py b/kdb-bot/src/bot_api/transformer/auth_user_transformer.py index 3506611aad..b5608e2376 100644 --- a/kdb-bot/src/bot_api/transformer/auth_user_transformer.py +++ b/kdb-bot/src/bot_api/transformer/auth_user_transformer.py @@ -9,7 +9,7 @@ from bot_data.model.auth_user import AuthUser class AuthUserTransformer(TransformerABC): @staticmethod - def to_db(dto: AuthUser) -> AuthUser: + def to_db(dto: AuthUserDTO) -> AuthUser: return AuthUser( dto.first_name, dto.last_name, @@ -20,6 +20,7 @@ class AuthUserTransformer(TransformerABC): None, datetime.now(tz=timezone.utc), AuthRoleEnum.normal if dto.auth_role is None else AuthRoleEnum(dto.auth_role), + dto.user_id, id=0 if dto.id is None else dto.id ) @@ -32,5 +33,6 @@ class AuthUserTransformer(TransformerABC): db.email, db.password, db.confirmation_id, - db.auth_role + db.auth_role, + db.user_id ) diff --git a/kdb-bot/src/bot_data/abc/user_repository_abc.py b/kdb-bot/src/bot_data/abc/user_repository_abc.py index ff7f625445..12878b6fd6 100644 --- a/kdb-bot/src/bot_data/abc/user_repository_abc.py +++ b/kdb-bot/src/bot_data/abc/user_repository_abc.py @@ -16,6 +16,9 @@ class UserRepositoryABC(ABC): @abstractmethod def get_user_by_id(self, id: int) -> User: pass + + @abstractmethod + def find_user_by_id(self, id: int) -> Optional[User]: pass @abstractmethod def get_users_by_discord_id(self, discord_id: int) -> List[User]: pass diff --git a/kdb-bot/src/bot_data/migration/api_migration.py b/kdb-bot/src/bot_data/migration/api_migration.py index 80754c88a5..6860ae1440 100644 --- a/kdb-bot/src/bot_data/migration/api_migration.py +++ b/kdb-bot/src/bot_data/migration/api_migration.py @@ -28,9 +28,11 @@ class ApiMigration(MigrationABC): `ForgotPasswordId` VARCHAR(255) DEFAULT NULL, `RefreshTokenExpiryTime` DATETIME(6) NOT NULL, `AuthRole` INT NOT NULL DEFAULT '0', + `UserId` BIGINT NOT NULL DEFAULT '0', `CreatedOn` DATETIME(6) NOT NULL, `LastModifiedOn` DATETIME(6) NOT NULL, - PRIMARY KEY(`Id`) + PRIMARY KEY(`Id`), + FOREIGN KEY (`UserId`) REFERENCES `Users`(`UserId`) ) """) ) diff --git a/kdb-bot/src/bot_data/model/auth_user.py b/kdb-bot/src/bot_data/model/auth_user.py index 26ba09a868..fe92ad1eb9 100644 --- a/kdb-bot/src/bot_data/model/auth_user.py +++ b/kdb-bot/src/bot_data/model/auth_user.py @@ -19,6 +19,7 @@ class AuthUser(TableABC): forgot_password_id: Optional[str], refresh_token_expire_time: datetime, auth_role: AuthRoleEnum, + user_id: Optional[int], created_at: datetime = None, modified_at: datetime = None, id=0 @@ -34,6 +35,7 @@ class AuthUser(TableABC): self._refresh_token_expire_time = refresh_token_expire_time self._auth_role_id = auth_role + self._user_id = user_id TableABC.__init__(self) self._created_at = created_at if created_at is not None else self._created_at @@ -115,6 +117,14 @@ class AuthUser(TableABC): def auth_role(self, value: AuthRoleEnum): self._auth_role_id = value + @property + def user_id(self) -> Optional[int]: + return self._user_id + + @user_id.setter + def user_id(self, value: Optional[int]): + self._user_id = value + @staticmethod def get_select_all_string() -> str: return str(f""" @@ -163,6 +173,7 @@ class AuthUser(TableABC): `ForgotPasswordId`, `RefreshTokenExpiryTime`, `AuthRole`, + `UserId`, `CreatedOn`, `LastModifiedOn` ) VALUES ( @@ -176,6 +187,7 @@ class AuthUser(TableABC): '{"NULL" if self._forgot_password_id is None else self._forgot_password_id}', '{self._refresh_token_expire_time}', {self._auth_role_id.value}, + {"NULL" if self._user_id is None else self._user_id} '{self._created_at}', '{self._modified_at}' ) @@ -194,6 +206,7 @@ class AuthUser(TableABC): `ForgotPasswordId` = '{"NULL" if self._forgot_password_id is None else self._forgot_password_id}', `RefreshTokenExpiryTime` = '{self._refresh_token_expire_time}', `AuthRole` = {self._auth_role_id.value}, + `UserId` = {"NULL" if self._user_id is None else self._user_id}, `LastModifiedOn` = '{self._modified_at}' WHERE `AuthUsers`.`Id` = {self._auth_user_id}; """) diff --git a/kdb-bot/src/bot_data/service/auth_user_repository_service.py b/kdb-bot/src/bot_data/service/auth_user_repository_service.py index 4918b26980..c8fee7b4aa 100644 --- a/kdb-bot/src/bot_data/service/auth_user_repository_service.py +++ b/kdb-bot/src/bot_data/service/auth_user_repository_service.py @@ -37,6 +37,7 @@ class AuthUserRepositoryService(AuthUserRepositoryABC): self._get_value_from_result(result[7]), self._get_value_from_result(result[8]), AuthRoleEnum(self._get_value_from_result(result[9])), + self._get_value_from_result(result[10]), id=self._get_value_from_result(result[0]) ) 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 f2543e5dd7..83f9862778 100644 --- a/kdb-bot/src/bot_data/service/user_repository_service.py +++ b/kdb-bot/src/bot_data/service/user_repository_service.py @@ -44,6 +44,21 @@ class UserRepositoryService(UserRepositoryABC): self._servers.get_server_by_id(result[3]), id=result[0] ) + + def find_user_by_id(self, id: int) -> Optional[User]: + self._logger.trace(__name__, f'Send SQL command: {User.get_select_by_id_string(id)}') + result = self._context.select(User.get_select_by_id_string(id)) + if result is None or len(result) == 0: + return None + + result = result[0] + + return User( + result[1], + result[2], + self._servers.get_server_by_id(result[3]), + id=result[0] + ) def get_users_by_discord_id(self, discord_id: int) -> List[User]: users = List(User) From 3d17bb7703042236f58272be2beba8c0d4a9829e Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Mon, 17 Oct 2022 16:22:09 +0200 Subject: [PATCH 04/11] Added get filtered servers #72 --- .../controller/discord/server_controller.py | 12 ++++++++- .../src/bot_api/filter/discord/__init__.py | 0 .../filter/discord/server_select_criteria.py | 17 +++++++++++++ .../discord/server_filtered_result_dto.py | 21 ++++++++++++++++ .../src/bot_api/service/discord_service.py | 25 +++++++++++++++++++ .../src/bot_data/abc/server_repository_abc.py | 5 ++++ .../service/server_repository_service.py | 25 +++++++++++++++++++ 7 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 kdb-bot/src/bot_api/filter/discord/__init__.py create mode 100644 kdb-bot/src/bot_api/filter/discord/server_select_criteria.py create mode 100644 kdb-bot/src/bot_api/model/discord/server_filtered_result_dto.py diff --git a/kdb-bot/src/bot_api/controller/discord/server_controller.py b/kdb-bot/src/bot_api/controller/discord/server_controller.py index 394338699e..35a3fa9c96 100644 --- a/kdb-bot/src/bot_api/controller/discord/server_controller.py +++ b/kdb-bot/src/bot_api/controller/discord/server_controller.py @@ -2,9 +2,11 @@ from cpl_core.configuration import ConfigurationABC from cpl_core.environment import ApplicationEnvironmentABC from cpl_core.mailing import EMailClientABC, EMailClientSettings from cpl_translation import TranslatePipe -from flask import Response, jsonify +from flask import Response, jsonify, request from bot_api.api import Api +from bot_api.filter.discord.server_select_criteria import ServerSelectCriteria +from bot_api.json_processor import JSONProcessor from bot_api.logging.api_logger import ApiLogger from bot_api.route.route import Route from bot_api.service.discord_service import DiscordService @@ -41,6 +43,14 @@ class ServerController: result = result.select(lambda x: x.to_dict()) return jsonify(result) + @Route.get(f'{BasePath}/servers/get/filtered') + @Route.authorize + async def get_all_servers_by_user(self) -> Response: + dto: ServerSelectCriteria = JSONProcessor.process(ServerSelectCriteria, request.get_json(force=True, silent=True)) + result = await self._discord_service.get_filtered_servers_async(dto) + result.result = result.result.select(lambda x: x.to_dict()) + return jsonify(result.to_dict()) + @Route.get(f'{BasePath}/servers-by-user') @Route.authorize async def get_all_servers_by_user(self) -> Response: diff --git a/kdb-bot/src/bot_api/filter/discord/__init__.py b/kdb-bot/src/bot_api/filter/discord/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/kdb-bot/src/bot_api/filter/discord/server_select_criteria.py b/kdb-bot/src/bot_api/filter/discord/server_select_criteria.py new file mode 100644 index 0000000000..f1454a8c95 --- /dev/null +++ b/kdb-bot/src/bot_api/filter/discord/server_select_criteria.py @@ -0,0 +1,17 @@ +from bot_api.abc.select_criteria_abc import SelectCriteriaABC + + +class ServerSelectCriteria(SelectCriteriaABC): + + def __init__( + self, + page_index: int, + page_size: int, + sort_direction: str, + sort_column: str, + + name: str, + ): + SelectCriteriaABC.__init__(self, page_index, page_size, sort_direction, sort_column) + + self.name = name diff --git a/kdb-bot/src/bot_api/model/discord/server_filtered_result_dto.py b/kdb-bot/src/bot_api/model/discord/server_filtered_result_dto.py new file mode 100644 index 0000000000..eceb642ca0 --- /dev/null +++ b/kdb-bot/src/bot_api/model/discord/server_filtered_result_dto.py @@ -0,0 +1,21 @@ +from cpl_query.extension import List + +from bot_api.abc.dto_abc import DtoABC +from bot_data.filtered_result import FilteredResult + + +class ServerFilteredResultDTO(DtoABC, FilteredResult): + + def __init__(self, result: List = None, total_count: int = 0): + DtoABC.__init__(self) + FilteredResult.__init__(self, result, total_count) + + def from_dict(self, values: dict): + self._result = values['servers'] + self._total_count = values['totalCount'] + + def to_dict(self) -> dict: + return { + 'servers': self.result, + 'totalCount': self.total_count + } diff --git a/kdb-bot/src/bot_api/service/discord_service.py b/kdb-bot/src/bot_api/service/discord_service.py index e6b3056c4e..e00e5b8486 100644 --- a/kdb-bot/src/bot_api/service/discord_service.py +++ b/kdb-bot/src/bot_api/service/discord_service.py @@ -7,7 +7,9 @@ from flask import jsonify from bot_api.abc.auth_service_abc import AuthServiceABC from bot_api.exception.service_error_code_enum import ServiceErrorCode from bot_api.exception.service_exception import ServiceException +from bot_api.filter.discord.server_select_criteria import ServerSelectCriteria from bot_api.model.discord.server_dto import ServerDTO +from bot_api.model.discord.server_filtered_result_dto import ServerFilteredResultDTO from bot_api.model.error_dto import ErrorDTO from bot_api.transformer.server_transformer import ServerTransformer from bot_data.abc.server_repository_abc import ServerRepositoryABC @@ -35,6 +37,29 @@ class DiscordService: lambda x: ServerTransformer.to_dto(x, self._bot.get_guild(x.discord_server_id).name, self._bot.get_guild(x.discord_server_id).member_count) ) + async def get_filtered_servers_async(self, criteria: ServerSelectCriteria) -> ServerFilteredResultDTO: + token = self._auth.get_decoded_token_from_request() + if token is None or 'email' not in token or 'role' not in token: + raise ServiceException(ServiceErrorCode.InvalidData, 'Token invalid') + + role = AuthRoleEnum(token['role']) + role = AuthRoleEnum(token['role']) + filtered_result = self._servers.get_filtered_servers(criteria) + servers = filtered_result.result + if role != AuthRoleEnum.admin: + user = await self._auth.find_auth_user_by_email_async(token['email']) + user_from_db = self._users.find_user_by_id(0 if user.user_id is None else user.user_id) + servers = servers.where(lambda x: user_from_db is not None and x.server_id == user_from_db.server.server_id) + + result = servers.select( + lambda x: ServerTransformer.to_dto(x, self._bot.get_guild(x.discord_server_id).name, self._bot.get_guild(x.discord_server_id).member_count) + ) + + return ServerFilteredResultDTO( + List(ServerDTO, result), + servers.total_count + ) + async def get_all_servers_by_user(self) -> List[ServerDTO]: token = self._auth.get_decoded_token_from_request() if token is None or 'email' not in token or 'role' not in token: diff --git a/kdb-bot/src/bot_data/abc/server_repository_abc.py b/kdb-bot/src/bot_data/abc/server_repository_abc.py index d1ab13fe63..f3eea75a9d 100644 --- a/kdb-bot/src/bot_data/abc/server_repository_abc.py +++ b/kdb-bot/src/bot_data/abc/server_repository_abc.py @@ -3,6 +3,8 @@ from typing import Optional from cpl_query.extension import List +from bot_api.filter.discord.server_select_criteria import ServerSelectCriteria +from bot_data.filtered_result import FilteredResult from bot_data.model.server import Server @@ -13,6 +15,9 @@ class ServerRepositoryABC(ABC): @abstractmethod def get_servers(self) -> List[Server]: pass + + @abstractmethod + def get_filtered_servers(self, criteria: ServerSelectCriteria) -> FilteredResult: pass @abstractmethod def get_server_by_id(self, id: int) -> Server: pass 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 728d2bc679..37b2918e2c 100644 --- a/kdb-bot/src/bot_data/service/server_repository_service.py +++ b/kdb-bot/src/bot_data/service/server_repository_service.py @@ -3,8 +3,10 @@ from typing import Optional from cpl_core.database.context import DatabaseContextABC from cpl_query.extension import List +from bot_api.filter.discord.server_select_criteria import ServerSelectCriteria from bot_core.logging.database_logger import DatabaseLogger from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.filtered_result import FilteredResult from bot_data.model.server import Server @@ -28,6 +30,29 @@ class ServerRepositoryService(ServerRepositoryABC): return servers + def get_filtered_servers(self, criteria: ServerSelectCriteria) -> FilteredResult: + servers = self.get_servers() + self._logger.trace(__name__, f'Send SQL command: {Server.get_select_all_string()}') + query = servers + + if criteria.name is not None and criteria.name != '': + query = query.where(lambda x: criteria.name in x.first_name or x.first_name == criteria.name) + + # sort + if criteria.sort_column is not None and criteria.sort_column != '' and criteria.sort_direction is not None and criteria.sort_direction: + crit_sort_direction = criteria.sort_direction.lower() + if crit_sort_direction == "desc" or crit_sort_direction == "descending": + query = query.order_by_descending(lambda x: getattr(x, criteria.sort_column)) + else: + query = query.order_by(lambda x: getattr(x, criteria.sort_column)) + + result = FilteredResult() + result.total_count = query.count() + skip = criteria.page_size * criteria.page_index + result.result = query.skip(skip).take(criteria.page_size) + + return result + def get_server_by_id(self, server_id: int) -> Server: 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] From 1baa8cee60036e4e15cc2e103db62fc273c9259e Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Mon, 17 Oct 2022 16:50:09 +0200 Subject: [PATCH 05/11] Added logic to get servers to dashboard #72 --- .../controller/discord/server_controller.py | 20 ++-- .../src/bot_api/service/discord_service.py | 45 +++++---- kdb-web/src/app/models/discord/server.dto.ts | 6 ++ .../logins/login-select-criterion.dto.ts | 8 -- .../server/get-filtered-servers-result.dto.ts | 7 ++ .../server/server-select-criterion.dto.ts | 5 + .../auth-user/auth-user.component.ts | 2 +- .../dashboard/dashboard.component.html | 15 ++- .../dashboard/dashboard.component.ts | 95 ++++++++++++++++++- kdb-web/src/app/services/data/data.service.ts | 33 ++++++- kdb-web/src/assets/i18n/de.json | 4 +- 11 files changed, 191 insertions(+), 49 deletions(-) create mode 100644 kdb-web/src/app/models/discord/server.dto.ts delete mode 100644 kdb-web/src/app/models/selection/logins/login-select-criterion.dto.ts create mode 100644 kdb-web/src/app/models/selection/server/get-filtered-servers-result.dto.ts create mode 100644 kdb-web/src/app/models/selection/server/server-select-criterion.dto.ts diff --git a/kdb-bot/src/bot_api/controller/discord/server_controller.py b/kdb-bot/src/bot_api/controller/discord/server_controller.py index 35a3fa9c96..d2c89b1049 100644 --- a/kdb-bot/src/bot_api/controller/discord/server_controller.py +++ b/kdb-bot/src/bot_api/controller/discord/server_controller.py @@ -36,24 +36,24 @@ class ServerController: self._mailer = mailer self._discord_service = discord_service - @Route.get(f'{BasePath}/servers') + @Route.get(f'{BasePath}/get/servers') @Route.authorize(role=AuthRoleEnum.admin) async def get_all_servers(self) -> Response: result = await self._discord_service.get_all_servers() result = result.select(lambda x: x.to_dict()) return jsonify(result) - @Route.get(f'{BasePath}/servers/get/filtered') - @Route.authorize - async def get_all_servers_by_user(self) -> Response: - dto: ServerSelectCriteria = JSONProcessor.process(ServerSelectCriteria, request.get_json(force=True, silent=True)) - result = await self._discord_service.get_filtered_servers_async(dto) - result.result = result.result.select(lambda x: x.to_dict()) - return jsonify(result.to_dict()) - - @Route.get(f'{BasePath}/servers-by-user') + @Route.get(f'{BasePath}/get/servers-by-user') @Route.authorize async def get_all_servers_by_user(self) -> Response: result = await self._discord_service.get_all_servers_by_user() result = result.select(lambda x: x.to_dict()) return jsonify(result) + + @Route.post(f'{BasePath}/get/filtered') + @Route.authorize + async def get_filtered_servers(self) -> Response: + dto: ServerSelectCriteria = JSONProcessor.process(ServerSelectCriteria, request.get_json(force=True, silent=True)) + result = await self._discord_service.get_filtered_servers_async(dto) + result.result = result.result.select(lambda x: x.to_dict()) + return jsonify(result.to_dict()) diff --git a/kdb-bot/src/bot_api/service/discord_service.py b/kdb-bot/src/bot_api/service/discord_service.py index e00e5b8486..6f8e5f13bb 100644 --- a/kdb-bot/src/bot_api/service/discord_service.py +++ b/kdb-bot/src/bot_api/service/discord_service.py @@ -37,29 +37,6 @@ class DiscordService: lambda x: ServerTransformer.to_dto(x, self._bot.get_guild(x.discord_server_id).name, self._bot.get_guild(x.discord_server_id).member_count) ) - async def get_filtered_servers_async(self, criteria: ServerSelectCriteria) -> ServerFilteredResultDTO: - token = self._auth.get_decoded_token_from_request() - if token is None or 'email' not in token or 'role' not in token: - raise ServiceException(ServiceErrorCode.InvalidData, 'Token invalid') - - role = AuthRoleEnum(token['role']) - role = AuthRoleEnum(token['role']) - filtered_result = self._servers.get_filtered_servers(criteria) - servers = filtered_result.result - if role != AuthRoleEnum.admin: - user = await self._auth.find_auth_user_by_email_async(token['email']) - user_from_db = self._users.find_user_by_id(0 if user.user_id is None else user.user_id) - servers = servers.where(lambda x: user_from_db is not None and x.server_id == user_from_db.server.server_id) - - result = servers.select( - lambda x: ServerTransformer.to_dto(x, self._bot.get_guild(x.discord_server_id).name, self._bot.get_guild(x.discord_server_id).member_count) - ) - - return ServerFilteredResultDTO( - List(ServerDTO, result), - servers.total_count - ) - async def get_all_servers_by_user(self) -> List[ServerDTO]: token = self._auth.get_decoded_token_from_request() if token is None or 'email' not in token or 'role' not in token: @@ -76,3 +53,25 @@ class DiscordService: return servers.select( lambda x: ServerTransformer.to_dto(x, self._bot.get_guild(x.discord_server_id).name, self._bot.get_guild(x.discord_server_id).member_count) ) + + async def get_filtered_servers_async(self, criteria: ServerSelectCriteria) -> ServerFilteredResultDTO: + token = self._auth.get_decoded_token_from_request() + if token is None or 'email' not in token or 'role' not in token: + raise ServiceException(ServiceErrorCode.InvalidData, 'Token invalid') + + role = AuthRoleEnum(token['role']) + filtered_result = self._servers.get_filtered_servers(criteria) + servers = filtered_result.result + if role != AuthRoleEnum.admin: + user = await self._auth.find_auth_user_by_email_async(token['email']) + user_from_db = self._users.find_user_by_id(0 if user.user_id is None else user.user_id) + servers = servers.where(lambda x: user_from_db is not None and x.server_id == user_from_db.server.server_id) + + result = servers.select( + lambda x: ServerTransformer.to_dto(x, self._bot.get_guild(x.discord_server_id).name, self._bot.get_guild(x.discord_server_id).member_count) + ) + + return ServerFilteredResultDTO( + List(ServerDTO, result), + filtered_result.total_count + ) diff --git a/kdb-web/src/app/models/discord/server.dto.ts b/kdb-web/src/app/models/discord/server.dto.ts new file mode 100644 index 0000000000..4cba26ac6a --- /dev/null +++ b/kdb-web/src/app/models/discord/server.dto.ts @@ -0,0 +1,6 @@ +export interface ServerDTO { + serverId: number; + discordId: number; + name: string; + memberCount: number; +} \ No newline at end of file diff --git a/kdb-web/src/app/models/selection/logins/login-select-criterion.dto.ts b/kdb-web/src/app/models/selection/logins/login-select-criterion.dto.ts deleted file mode 100644 index e1998d60bc..0000000000 --- a/kdb-web/src/app/models/selection/logins/login-select-criterion.dto.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { SelectCriterion } from "../select-criterion.model"; - -export interface LoginSelectCriterion extends SelectCriterion { - timeFrom: string; - timeTo: string; - userName: string; - hostName: string; -} \ No newline at end of file diff --git a/kdb-web/src/app/models/selection/server/get-filtered-servers-result.dto.ts b/kdb-web/src/app/models/selection/server/get-filtered-servers-result.dto.ts new file mode 100644 index 0000000000..4dfe801a2b --- /dev/null +++ b/kdb-web/src/app/models/selection/server/get-filtered-servers-result.dto.ts @@ -0,0 +1,7 @@ +import { AuthUserDTO } from "../../auth/auth-user.dto"; +import { ServerDTO } from "../../discord/server.dto"; + +export interface GetFilteredServersResultDTO { + servers: ServerDTO[]; + totalCount: number; +} \ No newline at end of file diff --git a/kdb-web/src/app/models/selection/server/server-select-criterion.dto.ts b/kdb-web/src/app/models/selection/server/server-select-criterion.dto.ts new file mode 100644 index 0000000000..08b9ea5925 --- /dev/null +++ b/kdb-web/src/app/models/selection/server/server-select-criterion.dto.ts @@ -0,0 +1,5 @@ +import { SelectCriterion } from "../select-criterion.model"; + +export interface ServerSelectCriterion extends SelectCriterion { + name: string | null; +} \ No newline at end of file diff --git a/kdb-web/src/app/modules/admin/auth-users/components/auth-user/auth-user.component.ts b/kdb-web/src/app/modules/admin/auth-users/components/auth-user/auth-user.component.ts index c972e7f215..a9243563b6 100644 --- a/kdb-web/src/app/modules/admin/auth-users/components/auth-user/auth-user.component.ts +++ b/kdb-web/src/app/modules/admin/auth-users/components/auth-user/auth-user.component.ts @@ -85,7 +85,7 @@ export class AuthUserComponent implements OnInit { }; this.setFilterForm(); - // this.loadNextPage(); + this.loadNextPage(); } setFilterForm() { diff --git a/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.html b/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.html index 04e795ec56..e4139bace0 100644 --- a/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.html +++ b/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.html @@ -1,3 +1,12 @@ -

dashboard works!

- -
+

+ {{'view.dashboard.header' | translate}} +

+
+
+
    +
  • + {{server.name}} +
  • +
+
+
\ No newline at end of file diff --git a/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.ts b/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.ts index 25d48a7ae5..0fb3ff19a4 100644 --- a/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.ts +++ b/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.ts @@ -1,4 +1,14 @@ import { Component, OnInit } from '@angular/core'; +import { FormGroup, FormControl, FormBuilder } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { LazyLoadEvent } from 'primeng/api'; +import { catchError, debounceTime, throwError } from 'rxjs'; +import { ServerDTO } from 'src/app/models/discord/server.dto'; +import { ServerSelectCriterion } from 'src/app/models/selection/server/server-select-criterion.dto'; +import { ConfirmationDialogService } from 'src/app/services/confirmation-dialog/confirmation-dialog.service'; +import { DataService } from 'src/app/services/data/data.service'; +import { SpinnerService } from 'src/app/services/spinner/spinner.service'; +import { ToastService } from 'src/app/services/toast/toast.service'; @Component({ selector: 'app-dashboard', @@ -7,8 +17,89 @@ import { Component, OnInit } from '@angular/core'; }) export class DashboardComponent implements OnInit { - constructor() { } + loading = true; + servers: ServerDTO[] = []; - ngOnInit(): void {} + searchCriterions: ServerSelectCriterion = { + name: null, + pageIndex: 0, + pageSize: 10, + sortColumn: null, + sortDirection: null + }; + totalRecords!: number; + + + filterForm!: FormGroup<{ + name: FormControl, + }>; + + constructor( + private data: DataService, + private spinnerService: SpinnerService, + private toastService: ToastService, + private confirmDialog: ConfirmationDialogService, + private fb: FormBuilder, + private translate: TranslateService + ) { } + + ngOnInit(): void { + this.setFilterForm(); + this.loadNextPage(); + } + + setFilterForm() { + this.filterForm = this.fb.group({ + name: [''], + }); + + this.filterForm.valueChanges.pipe( + debounceTime(600) + ).subscribe(changes => { + if (changes.name) { + this.searchCriterions.name = changes.name; + } else { + this.searchCriterions.name = null; + } + + if (this.searchCriterions.pageSize) + this.searchCriterions.pageSize = 10; + + if (this.searchCriterions.pageSize) + this.searchCriterions.pageIndex = 0; + + this.loadNextPage(); + }); + } + + loadNextPage() { + this.data.getFilteredServers(this.searchCriterions).pipe(catchError(err => { + this.loading = false; + return throwError(() => err); + })).subscribe(list => { + this.totalRecords = list.totalCount; + this.servers = list.servers; + this.loading = false; + }); + } + + nextPage(event: LazyLoadEvent) { + this.searchCriterions.pageSize = event.rows ?? 0; + if (event.first != null && event.rows != null) + this.searchCriterions.pageIndex = event.first / event.rows; + this.searchCriterions.sortColumn = event.sortField ?? null; + this.searchCriterions.sortDirection = event.sortOrder === 1 ? 'asc' : event.sortOrder === -1 ? 'desc' : 'asc'; + + if (event.filters) { + // + "" => convert to string + this.searchCriterions.name = event.filters['name'] ? event.filters['name'] + "" : null; + } + + this.loadNextPage(); + } + + resetFilters() { + this.filterForm.reset(); + } } diff --git a/kdb-web/src/app/services/data/data.service.ts b/kdb-web/src/app/services/data/data.service.ts index 36cccbb5a4..80a6060082 100644 --- a/kdb-web/src/app/services/data/data.service.ts +++ b/kdb-web/src/app/services/data/data.service.ts @@ -1,5 +1,9 @@ -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { ServerDTO } from 'src/app/models/discord/server.dto'; +import { GetFilteredServersResultDTO } from 'src/app/models/selection/server/get-filtered-servers-result.dto'; +import { ServerSelectCriterion } from 'src/app/models/selection/server/server-select-criterion.dto'; import { SettingsService } from '../settings/settings.service'; @Injectable({ @@ -11,4 +15,31 @@ export class DataService { private appsettings: SettingsService, private http: HttpClient, ) { } + + + + /* data requests */ + getAllServers(): Observable> { + return this.http.get>(`${this.appsettings.getApiURL()}/api/discord/server/servers`, { + headers: new HttpHeaders({ + 'Content-Type': 'application/json' + }) + }); + } + + getAllServersByUser(): Observable> { + return this.http.get>(`${this.appsettings.getApiURL()}/api/discord/server/servers-by-user`, { + headers: new HttpHeaders({ + 'Content-Type': 'application/json' + }) + }); + } + + getFilteredServers(selectCriterions: ServerSelectCriterion): Observable { + return this.http.post(`${this.appsettings.getApiURL()}/api/discord/server/get/filtered`, selectCriterions, { + headers: new HttpHeaders({ + 'Content-Type': 'application/json' + }) + }); + } } diff --git a/kdb-web/src/assets/i18n/de.json b/kdb-web/src/assets/i18n/de.json index 894bd46195..ae0a0f4cf9 100644 --- a/kdb-web/src/assets/i18n/de.json +++ b/kdb-web/src/assets/i18n/de.json @@ -106,7 +106,9 @@ } }, "view": { - "dashboard": {}, + "dashboard": { + "header": "Dashboard" + }, "user-list": {}, "change-password": { "header": "Passwort ändern", From a69c223a332b503134f542abd76e51b7b94c4c3c Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Mon, 17 Oct 2022 18:12:43 +0200 Subject: [PATCH 06/11] Added server list to dashboard #72 --- .../src/bot_api/model/discord/server_dto.py | 13 +++- .../src/bot_api/service/discord_service.py | 28 ++++++-- .../bot_api/transformer/server_transformer.py | 7 +- .../service/server_repository_service.py | 3 - kdb-web/src/app/models/discord/server.dto.ts | 1 + .../dashboard/dashboard.component.html | 48 +++++++++++-- kdb-web/src/assets/i18n/de.json | 11 ++- kdb-web/src/styles.scss | 67 +++++++++++++++++-- .../src/styles/themes/default-dark-theme.scss | 2 +- .../styles/themes/sh-edraft-dark-theme.scss | 43 +++++++++--- 10 files changed, 190 insertions(+), 33 deletions(-) diff --git a/kdb-bot/src/bot_api/model/discord/server_dto.py b/kdb-bot/src/bot_api/model/discord/server_dto.py index e7115dd231..07cd4365c4 100644 --- a/kdb-bot/src/bot_api/model/discord/server_dto.py +++ b/kdb-bot/src/bot_api/model/discord/server_dto.py @@ -1,3 +1,5 @@ +from typing import Optional + from bot_api.abc.dto_abc import DtoABC @@ -8,7 +10,8 @@ class ServerDTO(DtoABC): server_id: int, discord_id: int, name: str, - member_count: int + member_count: int, + icon_url: Optional[str] ): DtoABC.__init__(self) @@ -17,6 +20,7 @@ class ServerDTO(DtoABC): self._discord_id = discord_id self._name = name self._member_count = member_count + self._icon_url = icon_url @property def server_id(self) -> int: @@ -34,11 +38,15 @@ class ServerDTO(DtoABC): def member_count(self) -> int: return self._member_count + @property + def icon_url(self) -> Optional[str]: + return self._icon_url + def from_dict(self, values: dict): self._server_id = int(values['serverId']) self._discord_id = int(values['discordId']) self._name = values['name'] - self._member_count = int(values['memberCount']) + self._icon_url = int(values['iconURL']) def to_dict(self) -> dict: return { @@ -46,4 +54,5 @@ class ServerDTO(DtoABC): 'discordId': self._discord_id, 'name': self._name, 'memberCount': self._member_count, + 'iconURL': self._icon_url, } diff --git a/kdb-bot/src/bot_api/service/discord_service.py b/kdb-bot/src/bot_api/service/discord_service.py index 6f8e5f13bb..a0163dcc9e 100644 --- a/kdb-bot/src/bot_api/service/discord_service.py +++ b/kdb-bot/src/bot_api/service/discord_service.py @@ -33,9 +33,12 @@ class DiscordService: async def get_all_servers(self) -> List[ServerDTO]: servers = self._servers.get_servers() - return servers.select( - lambda x: ServerTransformer.to_dto(x, self._bot.get_guild(x.discord_server_id).name, self._bot.get_guild(x.discord_server_id).member_count) - ) + return servers.select(lambda x: ServerTransformer.to_dto( + x, + self._bot.get_guild(x.discord_server_id).name, + self._bot.get_guild(x.discord_server_id).member_count, + self._bot.get_guild(x.discord_server_id).icon + )) async def get_all_servers_by_user(self) -> List[ServerDTO]: token = self._auth.get_decoded_token_from_request() @@ -50,9 +53,12 @@ class DiscordService: user_from_db = self._users.find_user_by_id(0 if user.user_id is None else user.user_id) servers = self._servers.get_servers().where(lambda x: user_from_db is not None and x.server_id == user_from_db.server.server_id) - return servers.select( - lambda x: ServerTransformer.to_dto(x, self._bot.get_guild(x.discord_server_id).name, self._bot.get_guild(x.discord_server_id).member_count) - ) + return servers.select(lambda x: ServerTransformer.to_dto( + x, + self._bot.get_guild(x.discord_server_id).name, + self._bot.get_guild(x.discord_server_id).member_count, + self._bot.get_guild(x.discord_server_id).icon + )) async def get_filtered_servers_async(self, criteria: ServerSelectCriteria) -> ServerFilteredResultDTO: token = self._auth.get_decoded_token_from_request() @@ -68,9 +74,17 @@ class DiscordService: servers = servers.where(lambda x: user_from_db is not None and x.server_id == user_from_db.server.server_id) result = servers.select( - lambda x: ServerTransformer.to_dto(x, self._bot.get_guild(x.discord_server_id).name, self._bot.get_guild(x.discord_server_id).member_count) + lambda x: ServerTransformer.to_dto( + x, + self._bot.get_guild(x.discord_server_id).name, + self._bot.get_guild(x.discord_server_id).member_count, + self._bot.get_guild(x.discord_server_id).icon + ) ) + if criteria.name is not None and criteria.name != '': + result = result.where(lambda x: criteria.name.lower() in x.name.lower() or x.name.lower() == criteria.name.lower()) + return ServerFilteredResultDTO( List(ServerDTO, result), filtered_result.total_count diff --git a/kdb-bot/src/bot_api/transformer/server_transformer.py b/kdb-bot/src/bot_api/transformer/server_transformer.py index 920fd506d0..d883c42d5d 100644 --- a/kdb-bot/src/bot_api/transformer/server_transformer.py +++ b/kdb-bot/src/bot_api/transformer/server_transformer.py @@ -1,3 +1,7 @@ +from typing import Optional + +import discord + from bot_api.abc.transformer_abc import TransformerABC from bot_api.model.discord.server_dto import ServerDTO from bot_data.model.server import Server @@ -10,10 +14,11 @@ class ServerTransformer(TransformerABC): return Server(dto.discord_id) @staticmethod - def to_dto(db: Server, name: str, member_count: int) -> ServerDTO: + def to_dto(db: Server, name: str, member_count: int, icon_url: Optional[discord.Asset]) -> ServerDTO: return ServerDTO( db.server_id, db.discord_server_id, name, member_count, + icon_url.url if icon_url is not None else None, ) 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 37b2918e2c..210a6dee95 100644 --- a/kdb-bot/src/bot_data/service/server_repository_service.py +++ b/kdb-bot/src/bot_data/service/server_repository_service.py @@ -35,9 +35,6 @@ class ServerRepositoryService(ServerRepositoryABC): self._logger.trace(__name__, f'Send SQL command: {Server.get_select_all_string()}') query = servers - if criteria.name is not None and criteria.name != '': - query = query.where(lambda x: criteria.name in x.first_name or x.first_name == criteria.name) - # sort if criteria.sort_column is not None and criteria.sort_column != '' and criteria.sort_direction is not None and criteria.sort_direction: crit_sort_direction = criteria.sort_direction.lower() diff --git a/kdb-web/src/app/models/discord/server.dto.ts b/kdb-web/src/app/models/discord/server.dto.ts index 4cba26ac6a..a97625b50a 100644 --- a/kdb-web/src/app/models/discord/server.dto.ts +++ b/kdb-web/src/app/models/discord/server.dto.ts @@ -3,4 +3,5 @@ export interface ServerDTO { discordId: number; name: string; memberCount: number; + iconURL: string | null; } \ No newline at end of file diff --git a/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.html b/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.html index e4139bace0..c7dd5fdfbe 100644 --- a/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.html +++ b/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.html @@ -2,11 +2,49 @@ {{'view.dashboard.header' | translate}}
+
+

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

+
+
-
    -
  • - {{server.name}} -
  • -
+
+
+
+
+ +
+
+
+ +
+ {{servers.length}} {{'view.dashboard.of' | translate}} {{totalRecords}} {{'view.dashboard.servers' | translate}}: +
+
+ +
+
+ + +
+

+ {{server.name}} +

+ +
+ + {{server.memberCount}} + {{'view.dashboard.server.member_count' | translate}} +
+
+ +
+
+
\ No newline at end of file diff --git a/kdb-web/src/assets/i18n/de.json b/kdb-web/src/assets/i18n/de.json index ae0a0f4cf9..83fd1ae359 100644 --- a/kdb-web/src/assets/i18n/de.json +++ b/kdb-web/src/assets/i18n/de.json @@ -107,7 +107,16 @@ }, "view": { "dashboard": { - "header": "Dashboard" + "header": "Dashboard", + "of": "von", + "servers": "Server", + "server": { + "header": "Server", + "member_count": "Mitglid(er)" + }, + "filter": { + "name": "Name" + } }, "user-list": {}, "change-password": { diff --git a/kdb-web/src/styles.scss b/kdb-web/src/styles.scss index ee146861c3..8bbd6cd8a5 100644 --- a/kdb-web/src/styles.scss +++ b/kdb-web/src/styles.scss @@ -10,6 +10,8 @@ body { height: 100%; padding: 0; margin: 0; + + font-size: 1rem; } main { @@ -18,15 +20,19 @@ main { min-height: 100vh; } -h1, -h2 { +h1 { margin: 0; - font-size: 30px; + font-size: 1.75rem; } h2 { margin: 0; - font-size: 25px; + font-size: 1.5rem; +} + +h3 { + margin: 0; + font-size: 1.25rem; } header { @@ -207,6 +213,59 @@ header { .table-header-small-dropdown { width: 150px; } + + .server-list-wrapper { + display: flex; + flex-direction: column; + gap: 10px; + + .server-filter { + } + + .server-count { + } + + .server-list { + display: flex; + flex-direction: column; + + gap: 15px; + + .server { + display: flex; + gap: 15px; + + padding: 20px; + + .logo { + overflow: hidden; + + img { + width: 4rem; + height: 4rem; + object-fit: contain; + } + } + + .info { + display: flex; + flex-direction: column; + + gap: 10px; + + .name { + margin: 0px; + + justify-content: center; + align-items: center; + } + + .data { + } + } + } + } + } } } } diff --git a/kdb-web/src/styles/themes/default-dark-theme.scss b/kdb-web/src/styles/themes/default-dark-theme.scss index 7507a6cb05..1a3bd5d178 100644 --- a/kdb-web/src/styles/themes/default-dark-theme.scss +++ b/kdb-web/src/styles/themes/default-dark-theme.scss @@ -432,7 +432,7 @@ color: $primaryTextColor !important; border: 0 !important; padding: 0px !important; - + &:hover { background-color: transparent !important; color: $primaryHeaderColor !important; diff --git a/kdb-web/src/styles/themes/sh-edraft-dark-theme.scss b/kdb-web/src/styles/themes/sh-edraft-dark-theme.scss index ee2da9f469..8fa192f1f0 100644 --- a/kdb-web/src/styles/themes/sh-edraft-dark-theme.scss +++ b/kdb-web/src/styles/themes/sh-edraft-dark-theme.scss @@ -15,17 +15,10 @@ $primaryErrorColor: #b00020; $secondaryErrorColor: #e94948; - $default-border: 1px solid $secondaryBackgroundColor3; + $default-border: 2px solid $secondaryBackgroundColor3; background-color: $primaryBackgroundColor !important; - html, - body { - margin: 0; - - font-size: 16px; - } - h1, h2 { color: $primaryHeaderColor; @@ -122,6 +115,35 @@ .content-divider { border-bottom: $default-border; } + + .server-list-wrapper { + .server-filter { + } + + .server-count { + } + + .server-list { + .server { + border: $default-border; + border-radius: 15px; + + .logo { + img { + border-radius: 100%; + } + } + + .name { + color: $primaryHeaderColor; + } + + &:hover { + border-color: $primaryHeaderColor !important; + } + } + } + } } } } @@ -390,6 +412,9 @@ input, .p-password { + border-radius: 10px; + border: $default-border; + &:focus { box-shadow: none !important; } @@ -434,7 +459,7 @@ color: $primaryTextColor !important; border: 0 !important; padding: 0px !important; - + &:hover { background-color: transparent !important; color: $primaryHeaderColor !important; From 2a974384172e127f835afc1daa2ac013c0297c02 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Mon, 17 Oct 2022 19:44:28 +0200 Subject: [PATCH 07/11] [WIP] Added server dashboard #72 --- .../controller/discord/server_controller.py | 6 +++ .../src/bot_api/service/discord_service.py | 7 +++ kdb-web/src/app/app-routing.module.ts | 1 + .../dashboard/dashboard.component.html | 2 +- .../dashboard/dashboard.component.ts | 17 ++++--- .../dashboard/dashboard-routing.module.ts | 2 +- .../view/dashboard/dashboard.module.ts | 6 +-- .../server-dashboard.component.html | 36 +++++++++++++++ .../server-dashboard.component.scss | 0 .../server-dashboard.component.spec.ts | 23 ++++++++++ .../server-dashboard.component.ts | 45 +++++++++++++++++++ .../view/server/server-routing.module.ts | 14 ++++++ .../app/modules/view/server/server.module.ts | 19 ++++++++ kdb-web/src/app/services/data/data.service.ts | 8 ++++ kdb-web/src/assets/i18n/de.json | 9 ++-- 15 files changed, 181 insertions(+), 14 deletions(-) create mode 100644 kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.html create mode 100644 kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.scss create mode 100644 kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.spec.ts create mode 100644 kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.ts create mode 100644 kdb-web/src/app/modules/view/server/server-routing.module.ts create mode 100644 kdb-web/src/app/modules/view/server/server.module.ts diff --git a/kdb-bot/src/bot_api/controller/discord/server_controller.py b/kdb-bot/src/bot_api/controller/discord/server_controller.py index d2c89b1049..1c362ab957 100644 --- a/kdb-bot/src/bot_api/controller/discord/server_controller.py +++ b/kdb-bot/src/bot_api/controller/discord/server_controller.py @@ -57,3 +57,9 @@ class ServerController: result = await self._discord_service.get_filtered_servers_async(dto) result.result = result.result.select(lambda x: x.to_dict()) return jsonify(result.to_dict()) + + @Route.get(f'{BasePath}/get/') + @Route.authorize + async def get_server_by_id(self, id: int) -> Response: + result = await self._discord_service.get_server_by_id_async(id) + return jsonify(result.to_dict()) diff --git a/kdb-bot/src/bot_api/service/discord_service.py b/kdb-bot/src/bot_api/service/discord_service.py index a0163dcc9e..7266e1bd4a 100644 --- a/kdb-bot/src/bot_api/service/discord_service.py +++ b/kdb-bot/src/bot_api/service/discord_service.py @@ -89,3 +89,10 @@ class DiscordService: List(ServerDTO, result), filtered_result.total_count ) + + async def get_server_by_id_async(self, id: int) -> ServerDTO: + server = self._servers.get_server_by_id(id) + guild = self._bot.get_guild(server.discord_server_id) + + server_dto = ServerTransformer.to_dto(server, guild.name, guild.member_count, guild.icon) + return server_dto diff --git a/kdb-web/src/app/app-routing.module.ts b/kdb-web/src/app/app-routing.module.ts index 803deb705b..1a1c9c3b46 100644 --- a/kdb-web/src/app/app-routing.module.ts +++ b/kdb-web/src/app/app-routing.module.ts @@ -7,6 +7,7 @@ import { AuthGuard } from './modules/shared/guards/auth/auth.guard'; const routes: Routes = [ { path: '', redirectTo: 'dashboard', pathMatch: 'full' }, { path: 'dashboard', loadChildren: () => import('./modules/view/dashboard/dashboard.module').then(m => m.DashboardModule), canActivate: [AuthGuard] }, + { path: 'server', loadChildren: () => import('./modules/view/server/server.module').then(m => m.ServerModule), canActivate: [AuthGuard] }, { path: 'change-password', loadChildren: () => import('./modules/view/change-password/change-password.module').then(m => m.ChangePasswordModule), canActivate: [AuthGuard] }, { path: 'user-settings', loadChildren: () => import('./modules/view/user-settings/user-settings.module').then(m => m.UserSettingsModule), canActivate: [AuthGuard] }, { path: 'auth', loadChildren: () => import('./modules/auth/auth.module').then(m => m.AuthModule) }, diff --git a/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.html b/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.html index c7dd5fdfbe..ded533101a 100644 --- a/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.html +++ b/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.html @@ -26,7 +26,7 @@
-
+
diff --git a/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.ts b/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.ts index 0fb3ff19a4..d149298e49 100644 --- a/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.ts +++ b/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit } from '@angular/core'; -import { FormGroup, FormControl, FormBuilder } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; +import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { LazyLoadEvent } from 'primeng/api'; import { catchError, debounceTime, throwError } from 'rxjs'; @@ -17,7 +18,6 @@ import { ToastService } from 'src/app/services/toast/toast.service'; }) export class DashboardComponent implements OnInit { - loading = true; servers: ServerDTO[] = []; searchCriterions: ServerSelectCriterion = { @@ -40,10 +40,12 @@ export class DashboardComponent implements OnInit { private toastService: ToastService, private confirmDialog: ConfirmationDialogService, private fb: FormBuilder, - private translate: TranslateService + private translate: TranslateService, + private router: Router ) { } ngOnInit(): void { + this.spinnerService.showSpinner(); this.setFilterForm(); this.loadNextPage(); } @@ -73,13 +75,14 @@ export class DashboardComponent implements OnInit { } loadNextPage() { + this.spinnerService.showSpinner(); this.data.getFilteredServers(this.searchCriterions).pipe(catchError(err => { - this.loading = false; + this.spinnerService.hideSpinner(); return throwError(() => err); })).subscribe(list => { this.totalRecords = list.totalCount; this.servers = list.servers; - this.loading = false; + this.spinnerService.hideSpinner(); }); } @@ -102,4 +105,8 @@ export class DashboardComponent implements OnInit { this.filterForm.reset(); } + selectServer(server: ServerDTO) { + this.router.navigate(['/server', server.serverId]); + } + } diff --git a/kdb-web/src/app/modules/view/dashboard/dashboard-routing.module.ts b/kdb-web/src/app/modules/view/dashboard/dashboard-routing.module.ts index 27f772462c..37add74128 100644 --- a/kdb-web/src/app/modules/view/dashboard/dashboard-routing.module.ts +++ b/kdb-web/src/app/modules/view/dashboard/dashboard-routing.module.ts @@ -3,7 +3,7 @@ import { RouterModule, Routes } from '@angular/router'; import { DashboardComponent } from './components/dashboard/dashboard.component'; const routes: Routes = [ - {path: '', component: DashboardComponent} + { path: '', component: DashboardComponent } ]; @NgModule({ diff --git a/kdb-web/src/app/modules/view/dashboard/dashboard.module.ts b/kdb-web/src/app/modules/view/dashboard/dashboard.module.ts index 0b3abe11d6..80438b6ee9 100644 --- a/kdb-web/src/app/modules/view/dashboard/dashboard.module.ts +++ b/kdb-web/src/app/modules/view/dashboard/dashboard.module.ts @@ -1,9 +1,9 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; -import { DashboardRoutingModule } from './dashboard-routing.module'; -import { DashboardComponent } from './components/dashboard/dashboard.component'; import { SharedModule } from '../../shared/shared.module'; +import { DashboardComponent } from './components/dashboard/dashboard.component'; +import { DashboardRoutingModule } from './dashboard-routing.module'; @NgModule({ diff --git a/kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.html b/kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.html new file mode 100644 index 0000000000..1fbf61d3c5 --- /dev/null +++ b/kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.html @@ -0,0 +1,36 @@ +

+ {{'view.dashboard.header' | translate}} +

+
+
+

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

+
+ +
+
+
+
+ + +
+

+ {{server.name}} +

+ +
+ + {{server.memberCount}} + {{'view.dashboard.server.member_count' | translate}} +
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.scss b/kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.spec.ts b/kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.spec.ts new file mode 100644 index 0000000000..9f49b43a16 --- /dev/null +++ b/kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ServerDashboardComponent } from './server-dashboard.component'; + +describe('ServerDashboardComponent', () => { + let component: ServerDashboardComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ServerDashboardComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ServerDashboardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.ts b/kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.ts new file mode 100644 index 0000000000..540ac8b639 --- /dev/null +++ b/kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.ts @@ -0,0 +1,45 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { catchError, throwError } from 'rxjs'; +import { ServerDTO } from 'src/app/models/discord/server.dto'; +import { DataService } from 'src/app/services/data/data.service'; +import { SpinnerService } from 'src/app/services/spinner/spinner.service'; + +@Component({ + selector: 'app-server-dashboard', + templateUrl: './server-dashboard.component.html', + styleUrls: ['./server-dashboard.component.scss'] +}) +export class ServerDashboardComponent implements OnInit { + + id!: number; + server!: ServerDTO; + + constructor( + private route: ActivatedRoute, + private router: Router, + private data: DataService, + private spinner: SpinnerService + ) { } + + ngOnInit(): void { + this.route.params.subscribe(params => { + this.id = +params['id']; + + if (!this.id) { + this.router.navigate(['/dashboard']); + return; + } + + this.spinner.showSpinner(); + this.data.getServerByID(this.id).pipe(catchError(err => { + this.spinner.hideSpinner(); + return throwError(() => err); + })).subscribe(server => { + this.server = server; + this.spinner.hideSpinner(); + }); + }); + } + +} 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 new file mode 100644 index 0000000000..063beab12c --- /dev/null +++ b/kdb-web/src/app/modules/view/server/server-routing.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { ServerDashboardComponent } from './server-dashboard/server-dashboard.component'; + +const routes: Routes = [ + { path: '', component: ServerDashboardComponent }, + { path: ':id', component: ServerDashboardComponent } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class ServerRoutingModule { } diff --git a/kdb-web/src/app/modules/view/server/server.module.ts b/kdb-web/src/app/modules/view/server/server.module.ts new file mode 100644 index 0000000000..824e2412df --- /dev/null +++ b/kdb-web/src/app/modules/view/server/server.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ServerDashboardComponent } from './server-dashboard/server-dashboard.component'; +import { ServerRoutingModule } from './server-routing.module'; +import { SharedModule } from '../../shared/shared.module'; + + + +@NgModule({ + declarations: [ + ServerDashboardComponent + ], + imports: [ + CommonModule, + ServerRoutingModule, + SharedModule + ] +}) +export class ServerModule { } diff --git a/kdb-web/src/app/services/data/data.service.ts b/kdb-web/src/app/services/data/data.service.ts index 80a6060082..6b1a0f76a7 100644 --- a/kdb-web/src/app/services/data/data.service.ts +++ b/kdb-web/src/app/services/data/data.service.ts @@ -42,4 +42,12 @@ export class DataService { }) }); } + + getServerByID(id: number): Observable { + return this.http.get(`${this.appsettings.getApiURL()}/api/discord/server/get/${id}`, { + headers: new HttpHeaders({ + 'Content-Type': 'application/json' + }) + }); + } } diff --git a/kdb-web/src/assets/i18n/de.json b/kdb-web/src/assets/i18n/de.json index 83fd1ae359..96fa7213ee 100644 --- a/kdb-web/src/assets/i18n/de.json +++ b/kdb-web/src/assets/i18n/de.json @@ -7,10 +7,8 @@ }, "sidebar": { "dashboard": "Dashboard", - "domain_list": "Domänen", - "host_list": "Rechner", - "user_list": "Benutzer", - "login_list": "Logins", + "server": "Server", + "server_empty": "Kein Server ausgewählt", "config": "Konfiguration", "auth_user_list": "Benutzer" }, @@ -118,6 +116,9 @@ "name": "Name" } }, + "server": { + "header": "Server" + }, "user-list": {}, "change-password": { "header": "Passwort ändern", From c094a3efae3a05ff82e4fdd45cac4d7db5a356ff Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 18 Oct 2022 12:41:42 +0200 Subject: [PATCH 08/11] Improved get server logic #72 --- .../src/bot_api/service/discord_service.py | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/kdb-bot/src/bot_api/service/discord_service.py b/kdb-bot/src/bot_api/service/discord_service.py index 7266e1bd4a..304e340a95 100644 --- a/kdb-bot/src/bot_api/service/discord_service.py +++ b/kdb-bot/src/bot_api/service/discord_service.py @@ -15,6 +15,7 @@ from bot_api.transformer.server_transformer import ServerTransformer from bot_data.abc.server_repository_abc import ServerRepositoryABC from bot_data.abc.user_repository_abc import UserRepositoryABC from bot_data.model.auth_role_enum import AuthRoleEnum +from bot_data.model.server import Server class DiscordService: @@ -31,14 +32,26 @@ class DiscordService: self._auth = auth self._users = users + def _to_dto(self, x: Server) -> Optional[ServerDTO]: + guild = self._bot.get_guild(x.discord_server_id) + if guild is None: + return ServerTransformer.to_dto( + x, + '', + 0, + None + ) + + return ServerTransformer.to_dto( + x, + guild.name, + guild.member_count, + guild.icon + ) + async def get_all_servers(self) -> List[ServerDTO]: - servers = self._servers.get_servers() - return servers.select(lambda x: ServerTransformer.to_dto( - x, - self._bot.get_guild(x.discord_server_id).name, - self._bot.get_guild(x.discord_server_id).member_count, - self._bot.get_guild(x.discord_server_id).icon - )) + servers = List(ServerDTO, self._servers.get_servers()) + return servers.select(self._to_dto).where(lambda x: x.name != '') async def get_all_servers_by_user(self) -> List[ServerDTO]: token = self._auth.get_decoded_token_from_request() @@ -53,12 +66,8 @@ class DiscordService: user_from_db = self._users.find_user_by_id(0 if user.user_id is None else user.user_id) servers = self._servers.get_servers().where(lambda x: user_from_db is not None and x.server_id == user_from_db.server.server_id) - return servers.select(lambda x: ServerTransformer.to_dto( - x, - self._bot.get_guild(x.discord_server_id).name, - self._bot.get_guild(x.discord_server_id).member_count, - self._bot.get_guild(x.discord_server_id).icon - )) + servers = List(ServerDTO, servers) + return servers.select(self._to_dto).where(lambda x: x.name != '') async def get_filtered_servers_async(self, criteria: ServerSelectCriteria) -> ServerFilteredResultDTO: token = self._auth.get_decoded_token_from_request() @@ -67,27 +76,21 @@ class DiscordService: role = AuthRoleEnum(token['role']) filtered_result = self._servers.get_filtered_servers(criteria) - servers = filtered_result.result + # filter out servers, where the user not exists if role != AuthRoleEnum.admin: user = await self._auth.find_auth_user_by_email_async(token['email']) user_from_db = self._users.find_user_by_id(0 if user.user_id is None else user.user_id) - servers = servers.where(lambda x: user_from_db is not None and x.server_id == user_from_db.server.server_id) + filtered_result.result = filtered_result.result.where(lambda x: user_from_db is not None and x.server_id == user_from_db.server.server_id) - result = servers.select( - lambda x: ServerTransformer.to_dto( - x, - self._bot.get_guild(x.discord_server_id).name, - self._bot.get_guild(x.discord_server_id).member_count, - self._bot.get_guild(x.discord_server_id).icon - ) - ) + servers: List = filtered_result.result.select(self._to_dto).where(lambda x: x.name != '') + result = List(ServerDTO, servers) if criteria.name is not None and criteria.name != '': result = result.where(lambda x: criteria.name.lower() in x.name.lower() or x.name.lower() == criteria.name.lower()) return ServerFilteredResultDTO( List(ServerDTO, result), - filtered_result.total_count + servers.count() ) async def get_server_by_id_async(self, id: int) -> ServerDTO: From 1055d5c2e19f632da0916618158498c05426712f Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 18 Oct 2022 13:44:13 +0200 Subject: [PATCH 09/11] Improved design of menu to handle servers #72 --- kdb-web/.prettierrc | 4 +++ .../components/sidebar/sidebar.component.html | 2 +- .../components/sidebar/sidebar.component.ts | 16 ++++++++-- .../src/app/modules/shared/shared.module.ts | 5 ++- .../src/app/services/theme/theme.service.ts | 2 +- kdb-web/src/assets/i18n/de.json | 25 ++++----------- kdb-web/src/styles/primeng-fixes.scss | 32 +++++++++++++++++-- .../styles/themes/sh-edraft-dark-theme.scss | 25 ++++++++++++--- 8 files changed, 79 insertions(+), 32 deletions(-) create mode 100644 kdb-web/.prettierrc diff --git a/kdb-web/.prettierrc b/kdb-web/.prettierrc new file mode 100644 index 0000000000..5a938ce18e --- /dev/null +++ b/kdb-web/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 4, + "useTabs": false +} diff --git a/kdb-web/src/app/components/sidebar/sidebar.component.html b/kdb-web/src/app/components/sidebar/sidebar.component.html index b3bb4f3ba9..6f72d17e6c 100644 --- a/kdb-web/src/app/components/sidebar/sidebar.component.html +++ b/kdb-web/src/app/components/sidebar/sidebar.component.html @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/kdb-web/src/app/components/sidebar/sidebar.component.ts b/kdb-web/src/app/components/sidebar/sidebar.component.ts index cbf13c6f7c..f7815fbdcb 100644 --- a/kdb-web/src/app/components/sidebar/sidebar.component.ts +++ b/kdb-web/src/app/components/sidebar/sidebar.component.ts @@ -40,6 +40,13 @@ export class SidebarComponent implements OnInit { this.menuItems = []; this.menuItems = [ { label: this.isSidebarOpen ? this.translateService.instant('sidebar.dashboard') : '', icon: 'pi pi-th-large', routerLink: 'dashboard' }, + + { + label: this.isSidebarOpen ? this.translateService.instant('sidebar.server') : '', icon: 'pi pi-server', items: [ + { label: this.isSidebarOpen ? this.translateService.instant('sidebar.settings') : '', icon: 'pi pi-cog', routerLink: 'server/id/settings' }, + { label: this.isSidebarOpen ? this.translateService.instant('sidebar.members') : '', icon: 'pi pi-user-edit', routerLink: 'server/id/members' }, + ] + }, ]; if (!hasPermission) { @@ -47,9 +54,12 @@ export class SidebarComponent implements OnInit { } this.menuItems.push( - { separator: true }, - { label: this.isSidebarOpen ? this.translateService.instant('sidebar.config') : '', icon: 'pi pi-cog', routerLink: '/admin/settings' }, - { label: this.isSidebarOpen ? this.translateService.instant('sidebar.auth_user_list') : '', icon: 'pi pi-user-edit', routerLink: '/admin/users' }, + { + label: this.isSidebarOpen ? this.translateService.instant('sidebar.administration') : '', icon: 'pi pi-cog', items: [ + { label: this.isSidebarOpen ? this.translateService.instant('sidebar.config') : '', icon: 'pi pi-cog', routerLink: '/admin/settings' }, + { label: this.isSidebarOpen ? this.translateService.instant('sidebar.auth_user_list') : '', icon: 'pi pi-user-edit', routerLink: '/admin/users' }, + ] + }, ); this.menuItems = this.menuItems.slice(); }); diff --git a/kdb-web/src/app/modules/shared/shared.module.ts b/kdb-web/src/app/modules/shared/shared.module.ts index 9a77a513cc..59c0d0ef06 100644 --- a/kdb-web/src/app/modules/shared/shared.module.ts +++ b/kdb-web/src/app/modules/shared/shared.module.ts @@ -18,6 +18,7 @@ import { ToastModule } from 'primeng/toast'; import { AuthRolePipe } from './pipes/auth-role.pipe'; import { IpAddressPipe } from './pipes/ip-address.pipe'; import { BoolPipe } from './pipes/bool.pipe'; +import { PanelMenuModule } from 'primeng/panelmenu'; @@ -44,7 +45,8 @@ import { BoolPipe } from './pipes/bool.pipe'; CheckboxModule, DropdownModule, TranslateModule, - DynamicDialogModule + DynamicDialogModule, + PanelMenuModule, ], exports: [ ButtonModule, @@ -63,6 +65,7 @@ import { BoolPipe } from './pipes/bool.pipe'; DropdownModule, TranslateModule, DynamicDialogModule, + PanelMenuModule, AuthRolePipe, IpAddressPipe, BoolPipe, diff --git a/kdb-web/src/app/services/theme/theme.service.ts b/kdb-web/src/app/services/theme/theme.service.ts index 914ba774ef..6dc3410ab2 100644 --- a/kdb-web/src/app/services/theme/theme.service.ts +++ b/kdb-web/src/app/services/theme/theme.service.ts @@ -27,7 +27,7 @@ export class ThemeService { }); this.isSidebarOpen$.subscribe(isSidebarOpen => { this.isSidebarOpen = isSidebarOpen; - this.sidebarWidth$.next(isSidebarOpen ? '150px' : '50px'); + this.sidebarWidth$.next(isSidebarOpen ? '175px' : '75px'); }); this.sidebarWidth$.subscribe(sidebarWidth => { this.sidebarWidth = sidebarWidth; diff --git a/kdb-web/src/assets/i18n/de.json b/kdb-web/src/assets/i18n/de.json index 96fa7213ee..9eec0bf3a4 100644 --- a/kdb-web/src/assets/i18n/de.json +++ b/kdb-web/src/assets/i18n/de.json @@ -9,6 +9,9 @@ "dashboard": "Dashboard", "server": "Server", "server_empty": "Kein Server ausgewählt", + "settings": "Einstellungen", + "members": "Mitglieder", + "administration": "Administration", "config": "Konfiguration", "auth_user_list": "Benutzer" }, @@ -212,24 +215,8 @@ "Freitag", "Samstag" ], - "dayNamesShort": [ - "Son", - "Mon", - "Die", - "Mit", - "Don", - "Fre", - "Sam" - ], - "dayNamesMin": [ - "So", - "Mo", - "Di", - "Mi", - "Do", - "Fr", - "Sa" - ], + "dayNamesShort": ["Son", "Mon", "Die", "Mit", "Don", "Fre", "Sam"], + "dayNamesMin": ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"], "monthNames": [ "Januar", "Februar", @@ -267,4 +254,4 @@ "emptyMessage": "Keine Ergebnisse gefunden", "emptyFilterMessage": "Keine Ergebnisse gefunden" } -} \ No newline at end of file +} diff --git a/kdb-web/src/styles/primeng-fixes.scss b/kdb-web/src/styles/primeng-fixes.scss index 560ad4e34c..5bad03ee8e 100644 --- a/kdb-web/src/styles/primeng-fixes.scss +++ b/kdb-web/src/styles/primeng-fixes.scss @@ -6,14 +6,17 @@ } } -.p-menu { +.p-menu, +.p-panelmenu { background: none !important; border: none !important; width: auto !important; border-radius: 0px !important; padding: 0 !important; - .p-menuitem-link { + .p-menuitem-link, + .p-panelmenu-header > a, + .p-panelmenu-content .p-menuitem .p-menuitem-link { $distance: 10px; padding: $distance 0px $distance $distance !important; margin: 4px 0px 4px 6px !important; @@ -24,6 +27,31 @@ top: $headerHeight !important; } +.p-panelmenu { + .p-panelmenu-icon { + order: 1; // to be the first item on right side. + } + .p-menuitem-text { + flex-grow: 1; // to fill the whole space and push the icon to the end + } + + .p-panelmenu-header > a { + border: none !important; + border-radius: none !important; + font-weight: none !important; + transition: none !important; + } + + .p-panelmenu-content { + border: none !important; + background: none !important; + } + + .p-menuitem-text { + line-height: normal !important; + } +} + ui-menu .ui-menu-parent .ui-menu-child { width: 400px; /* exagerated !! */ } diff --git a/kdb-web/src/styles/themes/sh-edraft-dark-theme.scss b/kdb-web/src/styles/themes/sh-edraft-dark-theme.scss index 8fa192f1f0..6e0d8eb0d6 100644 --- a/kdb-web/src/styles/themes/sh-edraft-dark-theme.scss +++ b/kdb-web/src/styles/themes/sh-edraft-dark-theme.scss @@ -252,28 +252,43 @@ stroke: $primaryHeaderColor !important; } - .p-menu { + .p-menu, + .p-panelmenu { color: $primaryTextColor !important; .p-menuitem-link .p-menuitem-text, - .p-menuitem-link .p-menuitem-icon { + .p-menuitem-link .p-menuitem-icon, + .p-panelmenu-header > a { color: $primaryTextColor !important; + background: transparent !important; + font-size: 1rem !important; + font-weight: normal !important; } - .p-menuitem-link:focus { + .p-menuitem-link:focus, + .p-panelmenu-header > a:focus, + .p-panelmenu-content .p-menuitem .p-menuitem-link:focus { box-shadow: none !important; } - .p-menuitem-link:hover { + .p-menuitem-link:hover, + .p-panelmenu-header > a:hover, + .p-panelmenu-content .p-menuitem .p-menuitem-link:hover { background-color: $secondaryBackgroundColor !important; $border-radius: 20px; border-radius: $border-radius 0px 0px $border-radius; .p-menuitem-text, - .p-menuitem-icon { + .p-menuitem-icon, + .p-menuitem-text, + .p-panelmenu-icon { color: $primaryHeaderColor !important; } } + + .p-panelmenu-content { + margin: 5px 0px 5px 10px; + } } .p-menu-overlay { From 7760ee57254c8a1f409ab01d40509da3ea5e424c Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 18 Oct 2022 16:23:40 +0200 Subject: [PATCH 10/11] Update menu when server is selected #72 --- .../components/sidebar/sidebar.component.ts | 64 +++++++++++++------ .../dashboard/dashboard.component.ts | 7 +- .../server-dashboard.component.ts | 29 ++++----- .../view/server/server-routing.module.ts | 3 +- .../app/services/data/server.service.spec.ts | 16 +++++ .../src/app/services/data/server.service.ts | 23 +++++++ 6 files changed, 100 insertions(+), 42 deletions(-) create mode 100644 kdb-web/src/app/services/data/server.service.spec.ts create mode 100644 kdb-web/src/app/services/data/server.service.ts diff --git a/kdb-web/src/app/components/sidebar/sidebar.component.ts b/kdb-web/src/app/components/sidebar/sidebar.component.ts index f7815fbdcb..d43b42e522 100644 --- a/kdb-web/src/app/components/sidebar/sidebar.component.ts +++ b/kdb-web/src/app/components/sidebar/sidebar.component.ts @@ -1,8 +1,10 @@ import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; import { LangChangeEvent, TranslateService } from '@ngx-translate/core'; import { MenuItem } from 'primeng/api'; import { AuthRoles } from 'src/app/models/auth/auth-roles.enum'; import { AuthService } from 'src/app/services/auth/auth.service'; +import { ServerService } from 'src/app/services/data/server.service'; import { ThemeService } from 'src/app/services/theme/theme.service'; @Component({ @@ -13,13 +15,16 @@ import { ThemeService } from 'src/app/services/theme/theme.service'; export class SidebarComponent implements OnInit { isSidebarOpen: boolean = true; - menuItems!: MenuItem[]; + private serverId!: number; + constructor( private authService: AuthService, private translateService: TranslateService, - private themeService: ThemeService + private themeService: ThemeService, + private route: ActivatedRoute, + private serverService: ServerService ) { this.themeService.isSidebarOpen$.subscribe(value => { this.isSidebarOpen = value; @@ -29,6 +34,15 @@ export class SidebarComponent implements OnInit { this.translateService.onLangChange.subscribe((event: LangChangeEvent) => { this.setMenu(); }); + + this.serverService.server$.subscribe(server => { + if (!server) { + return; + } + + this.serverId = server.serverId; + this.setMenu(); + }); } ngOnInit(): void { @@ -39,30 +53,40 @@ export class SidebarComponent implements OnInit { this.authService.hasUserPermission(AuthRoles.Admin).then(hasPermission => { this.menuItems = []; this.menuItems = [ - { label: this.isSidebarOpen ? this.translateService.instant('sidebar.dashboard') : '', icon: 'pi pi-th-large', routerLink: 'dashboard' }, - - { - label: this.isSidebarOpen ? this.translateService.instant('sidebar.server') : '', icon: 'pi pi-server', items: [ - { label: this.isSidebarOpen ? this.translateService.instant('sidebar.settings') : '', icon: 'pi pi-cog', routerLink: 'server/id/settings' }, - { label: this.isSidebarOpen ? this.translateService.instant('sidebar.members') : '', icon: 'pi pi-user-edit', routerLink: 'server/id/members' }, - ] - }, + { label: this.isSidebarOpen ? this.translateService.instant('sidebar.dashboard') : '', icon: 'pi pi-th-large', routerLink: 'dashboard' } ]; - if (!hasPermission) { - return; + if (this.serverId) { + this.addServerMenu(); } - this.menuItems.push( - { - label: this.isSidebarOpen ? this.translateService.instant('sidebar.administration') : '', icon: 'pi pi-cog', items: [ - { label: this.isSidebarOpen ? this.translateService.instant('sidebar.config') : '', icon: 'pi pi-cog', routerLink: '/admin/settings' }, - { label: this.isSidebarOpen ? this.translateService.instant('sidebar.auth_user_list') : '', icon: 'pi pi-user-edit', routerLink: '/admin/users' }, - ] - }, - ); + if (hasPermission) { + this.addAdminMenu(); + } this.menuItems = this.menuItems.slice(); }); } + addServerMenu() { + this.menuItems.push( + { + label: this.isSidebarOpen ? this.translateService.instant('sidebar.server') : '', icon: 'pi pi-server', items: [ + { label: this.isSidebarOpen ? this.translateService.instant('sidebar.settings') : '', icon: 'pi pi-cog', routerLink: 'server/settings' }, + { label: this.isSidebarOpen ? this.translateService.instant('sidebar.members') : '', icon: 'pi pi-users', routerLink: 'server/members' }, + ] + } + ); + } + + addAdminMenu() { + this.menuItems.push( + { + label: this.isSidebarOpen ? this.translateService.instant('sidebar.administration') : '', icon: 'pi pi-cog', items: [ + { label: this.isSidebarOpen ? this.translateService.instant('sidebar.config') : '', icon: 'pi pi-cog', routerLink: '/admin/settings' }, + { label: this.isSidebarOpen ? this.translateService.instant('sidebar.auth_user_list') : '', icon: 'pi pi-user-edit', routerLink: '/admin/users' }, + ] + }, + ); + } + } diff --git a/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.ts b/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.ts index d149298e49..046e00c618 100644 --- a/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.ts +++ b/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.ts @@ -8,6 +8,7 @@ import { ServerDTO } from 'src/app/models/discord/server.dto'; import { ServerSelectCriterion } from 'src/app/models/selection/server/server-select-criterion.dto'; import { ConfirmationDialogService } from 'src/app/services/confirmation-dialog/confirmation-dialog.service'; import { DataService } from 'src/app/services/data/data.service'; +import { ServerService } from 'src/app/services/data/server.service'; import { SpinnerService } from 'src/app/services/spinner/spinner.service'; import { ToastService } from 'src/app/services/toast/toast.service'; @@ -41,7 +42,8 @@ export class DashboardComponent implements OnInit { private confirmDialog: ConfirmationDialogService, private fb: FormBuilder, private translate: TranslateService, - private router: Router + private router: Router, + private serverService: ServerService ) { } ngOnInit(): void { @@ -106,7 +108,8 @@ export class DashboardComponent implements OnInit { } selectServer(server: ServerDTO) { - this.router.navigate(['/server', server.serverId]); + this.serverService.server$.next(server); + this.router.navigate(['/server']); } } diff --git a/kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.ts b/kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.ts index 540ac8b639..158c771167 100644 --- a/kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.ts +++ b/kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.ts @@ -1,8 +1,8 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { catchError, throwError } from 'rxjs'; import { ServerDTO } from 'src/app/models/discord/server.dto'; import { DataService } from 'src/app/services/data/data.service'; +import { ServerService } from 'src/app/services/data/server.service'; import { SpinnerService } from 'src/app/services/spinner/spinner.service'; @Component({ @@ -19,27 +19,20 @@ export class ServerDashboardComponent implements OnInit { private route: ActivatedRoute, private router: Router, private data: DataService, - private spinner: SpinnerService + private spinner: SpinnerService, + private serverService: ServerService ) { } ngOnInit(): void { - this.route.params.subscribe(params => { - this.id = +params['id']; + this.spinner.showSpinner(); + if (!this.serverService.server$.value) { + this.spinner.hideSpinner(); + this.router.navigate(['/dashboard']); + return; + } - if (!this.id) { - this.router.navigate(['/dashboard']); - return; - } - - this.spinner.showSpinner(); - this.data.getServerByID(this.id).pipe(catchError(err => { - this.spinner.hideSpinner(); - return throwError(() => err); - })).subscribe(server => { - this.server = server; - this.spinner.hideSpinner(); - }); - }); + this.server = this.serverService.server$.value; + this.spinner.hideSpinner(); } } 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 063beab12c..7b9f3ee70d 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 @@ -3,8 +3,7 @@ import { RouterModule, Routes } from '@angular/router'; import { ServerDashboardComponent } from './server-dashboard/server-dashboard.component'; const routes: Routes = [ - { path: '', component: ServerDashboardComponent }, - { path: ':id', component: ServerDashboardComponent } + { path: '', component: ServerDashboardComponent } ]; @NgModule({ diff --git a/kdb-web/src/app/services/data/server.service.spec.ts b/kdb-web/src/app/services/data/server.service.spec.ts new file mode 100644 index 0000000000..906c1601bb --- /dev/null +++ b/kdb-web/src/app/services/data/server.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ServerService } from './server.service'; + +describe('ServerService', () => { + let service: ServerService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ServerService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/kdb-web/src/app/services/data/server.service.ts b/kdb-web/src/app/services/data/server.service.ts new file mode 100644 index 0000000000..05d8673d46 --- /dev/null +++ b/kdb-web/src/app/services/data/server.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { ServerDTO } from 'src/app/models/discord/server.dto'; + +@Injectable({ + providedIn: 'root' +}) +export class ServerService { + + private server!: ServerDTO; + server$ = new BehaviorSubject(null); + + constructor() { + this.server$.subscribe(server => { + if (!server) { + return; + } + this.server = server; + }); + } + + +} From dee8e4fe74280c7594699bf5a1962522f76a8b8a Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 18 Oct 2022 16:36:24 +0200 Subject: [PATCH 11/11] Fixed themes #72 --- .../src/styles/themes/default-dark-theme.scss | 68 ++++++++++++++---- .../styles/themes/default-light-theme.scss | 70 +++++++++++++++---- .../styles/themes/sh-edraft-dark-theme.scss | 2 +- .../styles/themes/sh-edraft-light-theme.scss | 68 ++++++++++++++---- 4 files changed, 166 insertions(+), 42 deletions(-) diff --git a/kdb-web/src/styles/themes/default-dark-theme.scss b/kdb-web/src/styles/themes/default-dark-theme.scss index 1a3bd5d178..8a5b60fae4 100644 --- a/kdb-web/src/styles/themes/default-dark-theme.scss +++ b/kdb-web/src/styles/themes/default-dark-theme.scss @@ -15,16 +15,9 @@ $primaryErrorColor: #b00020; $secondaryErrorColor: #e94948; - $default-border: 1px solid $secondaryBackgroundColor3; + $default-border: 2px solid $secondaryBackgroundColor3; - background-color: $primaryBackgroundColor !important; - - html, - body { - margin: 0; - - font-size: 16px; - } + background-color: $primaryBackgroundColor; h1, h2 { @@ -122,6 +115,35 @@ .content-divider { border-bottom: $default-border; } + + .server-list-wrapper { + .server-filter { + } + + .server-count { + } + + .server-list { + .server { + border: $default-border; + border-radius: 15px; + + .logo { + img { + border-radius: 100%; + } + } + + .name { + color: $primaryHeaderColor; + } + + &:hover { + border-color: $primaryHeaderColor !important; + } + } + } + } } } } @@ -228,28 +250,43 @@ stroke: $primaryHeaderColor !important; } - .p-menu { + .p-menu, + .p-panelmenu { color: $primaryTextColor !important; .p-menuitem-link .p-menuitem-text, - .p-menuitem-link .p-menuitem-icon { + .p-menuitem-link .p-menuitem-icon, + .p-panelmenu-header > a { color: $primaryTextColor !important; + background: transparent !important; + font-size: 1rem !important; + font-weight: normal !important; } - .p-menuitem-link:focus { + .p-menuitem-link:focus, + .p-panelmenu-header > a:focus, + .p-panelmenu-content .p-menuitem .p-menuitem-link:focus { box-shadow: none !important; } - .p-menuitem-link:hover { + .p-menuitem-link:hover, + .p-panelmenu-header > a:hover, + .p-panelmenu-content .p-menuitem .p-menuitem-link:hover { background-color: $secondaryBackgroundColor !important; $border-radius: 20px; border-radius: $border-radius 0px 0px $border-radius; .p-menuitem-text, - .p-menuitem-icon { + .p-menuitem-icon, + .p-menuitem-text, + .p-panelmenu-icon { color: $primaryHeaderColor !important; } } + + .p-panelmenu-content { + margin: 5px 0px 5px 10px; + } } .p-menu-overlay { @@ -388,6 +425,9 @@ input, .p-password { + border-radius: 10px; + border: $default-border; + &:focus { box-shadow: none !important; } diff --git a/kdb-web/src/styles/themes/default-light-theme.scss b/kdb-web/src/styles/themes/default-light-theme.scss index 5993a05808..bc58a6f7a0 100644 --- a/kdb-web/src/styles/themes/default-light-theme.scss +++ b/kdb-web/src/styles/themes/default-light-theme.scss @@ -15,14 +15,9 @@ $primaryErrorColor: #b00020; $secondaryErrorColor: #e94948; - $default-border: 1px solid $secondaryBackgroundColor; + $default-border: 2px solid $secondaryBackgroundColor; - html, - body { - margin: 0; - - font-size: 16px; - } + background-color: $primaryBackgroundColor; h1, h2 { @@ -120,6 +115,35 @@ .content-divider { border-bottom: $default-border; } + + .server-list-wrapper { + .server-filter { + } + + .server-count { + } + + .server-list { + .server { + border: $default-border; + border-radius: 15px; + + .logo { + img { + border-radius: 100%; + } + } + + .name { + color: $primaryHeaderColor; + } + + &:hover { + border-color: $primaryHeaderColor !important; + } + } + } + } } } } @@ -226,28 +250,43 @@ stroke: $primaryHeaderColor !important; } - .p-menu { + .p-menu, + .p-panelmenu { color: $primaryTextColor !important; .p-menuitem-link .p-menuitem-text, - .p-menuitem-link .p-menuitem-icon { + .p-menuitem-link .p-menuitem-icon, + .p-panelmenu-header > a { color: $primaryTextColor !important; + background: transparent !important; + font-size: 1rem !important; + font-weight: normal !important; } - .p-menuitem-link:focus { + .p-menuitem-link:focus, + .p-panelmenu-header > a:focus, + .p-panelmenu-content .p-menuitem .p-menuitem-link:focus { box-shadow: none !important; } - .p-menuitem-link:hover { + .p-menuitem-link:hover, + .p-panelmenu-header > a:hover, + .p-panelmenu-content .p-menuitem .p-menuitem-link:hover { background-color: $secondaryBackgroundColor !important; $border-radius: 20px; border-radius: $border-radius 0px 0px $border-radius; - + .p-menuitem-text, - .p-menuitem-icon { + .p-menuitem-icon, + .p-menuitem-text, + .p-panelmenu-icon { color: $primaryHeaderColor !important; } } + + .p-panelmenu-content { + margin: 5px 0px 5px 10px; + } } .p-menu-overlay { @@ -386,6 +425,9 @@ input, .p-password { + border-radius: 10px; + border: $default-border; + &:focus { box-shadow: none !important; } @@ -430,7 +472,7 @@ color: $primaryTextColor !important; border: 0 !important; padding: 0px !important; - + &:hover { background-color: transparent !important; color: $primaryHeaderColor !important; diff --git a/kdb-web/src/styles/themes/sh-edraft-dark-theme.scss b/kdb-web/src/styles/themes/sh-edraft-dark-theme.scss index 6e0d8eb0d6..9e45019f04 100644 --- a/kdb-web/src/styles/themes/sh-edraft-dark-theme.scss +++ b/kdb-web/src/styles/themes/sh-edraft-dark-theme.scss @@ -17,7 +17,7 @@ $default-border: 2px solid $secondaryBackgroundColor3; - background-color: $primaryBackgroundColor !important; + background-color: $primaryBackgroundColor; h1, h2 { diff --git a/kdb-web/src/styles/themes/sh-edraft-light-theme.scss b/kdb-web/src/styles/themes/sh-edraft-light-theme.scss index fdd809491c..26c70cd5d0 100644 --- a/kdb-web/src/styles/themes/sh-edraft-light-theme.scss +++ b/kdb-web/src/styles/themes/sh-edraft-light-theme.scss @@ -15,14 +15,9 @@ $primaryErrorColor: #b00020; $secondaryErrorColor: #e94948; - $default-border: 1px solid $secondaryBackgroundColor; + $default-border: 2px solid $secondaryBackgroundColor; - html, - body { - margin: 0; - - font-size: 16px; - } + background-color: $primaryBackgroundColor; h1, h2 { @@ -121,6 +116,35 @@ border-bottom: $default-border; } } + + .server-list-wrapper { + .server-filter { + } + + .server-count { + } + + .server-list { + .server { + border: $default-border; + border-radius: 15px; + + .logo { + img { + border-radius: 100%; + } + } + + .name { + color: $primaryHeaderColor; + } + + &:hover { + border-color: $primaryHeaderColor !important; + } + } + } + } } } } @@ -226,28 +250,43 @@ stroke: $primaryHeaderColor !important; } - .p-menu { + .p-menu, + .p-panelmenu { color: $primaryTextColor !important; .p-menuitem-link .p-menuitem-text, - .p-menuitem-link .p-menuitem-icon { + .p-menuitem-link .p-menuitem-icon, + .p-panelmenu-header > a { color: $primaryTextColor !important; + background: transparent !important; + font-size: 1rem !important; + font-weight: normal !important; } - .p-menuitem-link:focus { + .p-menuitem-link:focus, + .p-panelmenu-header > a:focus, + .p-panelmenu-content .p-menuitem .p-menuitem-link:focus { box-shadow: none !important; } - .p-menuitem-link:hover { + .p-menuitem-link:hover, + .p-panelmenu-header > a:hover, + .p-panelmenu-content .p-menuitem .p-menuitem-link:hover { background-color: $secondaryBackgroundColor !important; $border-radius: 20px; border-radius: $border-radius 0px 0px $border-radius; .p-menuitem-text, - .p-menuitem-icon { + .p-menuitem-icon, + .p-menuitem-text, + .p-panelmenu-icon { color: $primaryHeaderColor !important; } } + + .p-panelmenu-content { + margin: 5px 0px 5px 10px; + } } .p-menu-overlay { @@ -386,6 +425,9 @@ input, .p-password { + border-radius: 10px; + border: $default-border; + &:focus { box-shadow: none !important; } @@ -430,7 +472,7 @@ color: $primaryTextColor !important; border: 0 !important; padding: 0px !important; - + &:hover { background-color: transparent !important; color: $primaryHeaderColor !important;