Moved folders #405
This commit is contained in:
		
							
								
								
									
										26
									
								
								bot/src/bot_api/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								bot/src/bot_api/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| bot sh-edraft.de Discord bot | ||||
| ~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| Discord bot for customers of sh-edraft.de | ||||
|  | ||||
| :copyright: (c) 2022 - 2023 sh-edraft.de | ||||
| :license: MIT, see LICENSE for more details. | ||||
|  | ||||
| """ | ||||
|  | ||||
| __title__ = "bot_api" | ||||
| __author__ = "Sven Heidemann" | ||||
| __license__ = "MIT" | ||||
| __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" | ||||
| __version__ = "1.2.0" | ||||
|  | ||||
| from collections import namedtuple | ||||
|  | ||||
|  | ||||
| # imports: | ||||
|  | ||||
| VersionInfo = namedtuple("VersionInfo", "major minor micro") | ||||
| version_info = VersionInfo(major="1", minor="2", micro="0") | ||||
							
								
								
									
										26
									
								
								bot/src/bot_api/abc/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								bot/src/bot_api/abc/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| bot sh-edraft.de Discord bot | ||||
| ~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| Discord bot for customers of sh-edraft.de | ||||
|  | ||||
| :copyright: (c) 2022 - 2023 sh-edraft.de | ||||
| :license: MIT, see LICENSE for more details. | ||||
|  | ||||
| """ | ||||
|  | ||||
| __title__ = "bot_api.abc" | ||||
| __author__ = "Sven Heidemann" | ||||
| __license__ = "MIT" | ||||
| __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" | ||||
| __version__ = "1.2.0" | ||||
|  | ||||
| from collections import namedtuple | ||||
|  | ||||
|  | ||||
| # imports: | ||||
|  | ||||
| VersionInfo = namedtuple("VersionInfo", "major minor micro") | ||||
| version_info = VersionInfo(major="1", minor="2", micro="0") | ||||
							
								
								
									
										120
									
								
								bot/src/bot_api/abc/auth_service_abc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								bot/src/bot_api/abc/auth_service_abc.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| from abc import ABC, abstractmethod | ||||
| from typing import Optional | ||||
|  | ||||
| from cpl_query.extension import List | ||||
|  | ||||
| from bot_api.filter.auth_user_select_criteria import AuthUserSelectCriteria | ||||
| from bot_api.model.auth_user_dto import AuthUserDTO | ||||
| from bot_api.model.auth_user_filtered_result_dto import AuthUserFilteredResultDTO | ||||
| from bot_api.model.email_string_dto import EMailStringDTO | ||||
| from bot_api.model.o_auth_dto import OAuthDTO | ||||
| from bot_api.model.reset_password_dto import ResetPasswordDTO | ||||
| from bot_api.model.token_dto import TokenDTO | ||||
| from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO | ||||
| from bot_data.model.auth_user import AuthUser | ||||
|  | ||||
|  | ||||
| class AuthServiceABC(ABC): | ||||
|     @abstractmethod | ||||
|     def __init__(self): | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     def generate_token(self, user: AuthUser) -> str: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     def decode_token(self, token: str) -> dict: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     def get_decoded_token_from_request(self) -> dict: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     def find_decoded_token_from_request(self) -> Optional[dict]: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def get_all_auth_users_async(self) -> List[AuthUserDTO]: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def get_filtered_auth_users_async( | ||||
|         self, criteria: AuthUserSelectCriteria | ||||
|     ) -> AuthUserFilteredResultDTO: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def get_auth_user_by_email_async( | ||||
|         self, email: str, with_password: bool = False | ||||
|     ) -> AuthUserDTO: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def find_auth_user_by_email_async(self, email: str) -> AuthUserDTO: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     def add_auth_user(self, user_dto: AuthUserDTO): | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def add_auth_user_by_oauth_async(self, dto: OAuthDTO): | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def update_user_async(self, update_user_dto: UpdateAuthUserDTO): | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def update_user_as_admin_async(self, update_user_dto: UpdateAuthUserDTO): | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def delete_auth_user_by_email_async(self, email: str): | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def delete_auth_user_async(self, user_dto: AuthUserDTO): | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def verify_login(self, token_str: str) -> bool: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     def verify_api_key(self, api_key: str) -> bool: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def login_discord_async(self, oauth_dto: AuthUserDTO, dc_id: int) -> TokenDTO: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def refresh_async(self, token_dto: TokenDTO) -> TokenDTO: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def revoke_async(self, token_dto: TokenDTO): | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def confirm_email_async(self, id: str) -> bool: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def forgot_password_async(self, email: str): | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def confirm_forgot_password_async(self, id: str) -> EMailStringDTO: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def reset_password_async(self, rp_dto: ResetPasswordDTO): | ||||
|         pass | ||||
							
								
								
									
										15
									
								
								bot/src/bot_api/abc/dto_abc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								bot/src/bot_api/abc/dto_abc.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| from abc import ABC, abstractmethod | ||||
|  | ||||
|  | ||||
| class DtoABC(ABC): | ||||
|     @abstractmethod | ||||
|     def __init__(self): | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     def from_dict(self, values: dict): | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     def to_dict(self) -> dict: | ||||
|         pass | ||||
							
								
								
									
										12
									
								
								bot/src/bot_api/abc/select_criteria_abc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								bot/src/bot_api/abc/select_criteria_abc.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| from abc import ABC, abstractmethod | ||||
|  | ||||
|  | ||||
| class SelectCriteriaABC(ABC): | ||||
|     @abstractmethod | ||||
|     def __init__( | ||||
|         self, page_index: int, page_size: int, sort_direction: str, sort_column: str | ||||
|     ): | ||||
|         self.page_index = page_index | ||||
|         self.page_size = page_size | ||||
|         self.sort_direction = sort_direction | ||||
|         self.sort_column = sort_column | ||||
							
								
								
									
										17
									
								
								bot/src/bot_api/abc/transformer_abc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								bot/src/bot_api/abc/transformer_abc.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| from abc import abstractmethod | ||||
|  | ||||
| from cpl_core.database import TableABC | ||||
|  | ||||
| from bot_api.abc.dto_abc import DtoABC | ||||
|  | ||||
|  | ||||
| class TransformerABC: | ||||
|     @staticmethod | ||||
|     @abstractmethod | ||||
|     def to_db(dto: DtoABC) -> TableABC: | ||||
|         pass | ||||
|  | ||||
|     @staticmethod | ||||
|     @abstractmethod | ||||
|     def to_dto(db: TableABC) -> DtoABC: | ||||
|         pass | ||||
							
								
								
									
										180
									
								
								bot/src/bot_api/api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								bot/src/bot_api/api.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,180 @@ | ||||
| import socket | ||||
| import sys | ||||
| import textwrap | ||||
| import uuid | ||||
| from functools import partial | ||||
| from typing import Union, Optional | ||||
|  | ||||
| import eventlet | ||||
| from cpl_core.dependency_injection import ServiceProviderABC | ||||
| from cpl_core.utils import CredentialManager | ||||
| from eventlet import wsgi | ||||
| from flask import Flask, request, jsonify, Response | ||||
| from flask_cors import CORS | ||||
| from flask_socketio import SocketIO | ||||
| from werkzeug.exceptions import NotFound | ||||
|  | ||||
| from bot_api.configuration.api_settings import ApiSettings | ||||
| from bot_api.configuration.authentication_settings import AuthenticationSettings | ||||
| from bot_api.exception.service_error_code_enum import ServiceErrorCode | ||||
| from bot_api.exception.service_exception import ServiceException | ||||
| from bot_api.logging.api_logger import ApiLogger | ||||
| from bot_api.model.error_dto import ErrorDTO | ||||
| from bot_api.route.route import Route | ||||
|  | ||||
|  | ||||
| class Api(Flask): | ||||
|     def __init__( | ||||
|         self, | ||||
|         logger: ApiLogger, | ||||
|         services: ServiceProviderABC, | ||||
|         api_settings: ApiSettings, | ||||
|         auth_settings: AuthenticationSettings, | ||||
|         *args, | ||||
|         **kwargs, | ||||
|     ): | ||||
|         if not args: | ||||
|             kwargs.setdefault("import_name", __name__) | ||||
|  | ||||
|         Flask.__init__(self, *args, **kwargs) | ||||
|  | ||||
|         self._logger = logger | ||||
|         self._services = services | ||||
|         self._api_settings = api_settings | ||||
|         self._auth_settings = auth_settings | ||||
|  | ||||
|         self._cors = CORS(self, support_credentials=True) | ||||
|  | ||||
|         # register hooks | ||||
|         self.before_request(self.before_request_hook) | ||||
|         self.after_request(self.after_request_hook) | ||||
|  | ||||
|         # register error handler | ||||
|         exc_class, code = self._get_exc_class_and_code(Exception) | ||||
|         self.register_error_handler(exc_class, self.handle_exception) | ||||
|  | ||||
|         # websockets | ||||
|         # Added async_mode see link below | ||||
|         # https://github.com/miguelgrinberg/Flask-SocketIO/discussions/1849 | ||||
|         # https://stackoverflow.com/questions/39370848/flask-socket-io-sometimes-client-calls-freeze-the-server | ||||
|         self._socketio = SocketIO( | ||||
|             self, cors_allowed_origins="*", path="/api/socket.io", async_mode="eventlet" | ||||
|         ) | ||||
|         self._socketio.on_event("connect", self.on_connect) | ||||
|         self._socketio.on_event("disconnect", self.on_disconnect) | ||||
|  | ||||
|         self._socket: Optional[socket] = None | ||||
|  | ||||
|         self._requests = {} | ||||
|  | ||||
|     @staticmethod | ||||
|     def _get_methods_from_registered_route() -> Union[list[str], str]: | ||||
|         methods = ["Unknown"] | ||||
|         if ( | ||||
|             request.path in Route.registered_routes | ||||
|             and len(Route.registered_routes[request.path]) >= 1 | ||||
|             and "methods" in Route.registered_routes[request.path][1] | ||||
|         ): | ||||
|             methods = Route.registered_routes[request.path][1]["methods"] | ||||
|  | ||||
|         if len(methods) == 1: | ||||
|             return methods[0] | ||||
|         return methods | ||||
|  | ||||
|     def _register_routes(self): | ||||
|         for path, f in Route.registered_routes.items(): | ||||
|             route = f[0] | ||||
|             kwargs = f[1] | ||||
|             cls = None | ||||
|             qual_name_split = route.__qualname__.split(".") | ||||
|             if len(qual_name_split) > 0: | ||||
|                 cls_type = vars(sys.modules[route.__module__])[qual_name_split[0]] | ||||
|                 cls = self._services.get_service(cls_type) | ||||
|  | ||||
|             partial_f = partial(route, self if cls is None else cls) | ||||
|             partial_f.__name__ = route.__name__ | ||||
|             self.route(path, **kwargs)(partial_f) | ||||
|  | ||||
|     def handle_exception(self, e: Exception): | ||||
|         self._logger.error(__name__, f"Caught error", e) | ||||
|  | ||||
|         if isinstance(e, ServiceException): | ||||
|             ex: ServiceException = e | ||||
|             self._logger.error(__name__, ex.get_detailed_message()) | ||||
|             error = ErrorDTO(ex.error_code, ex.message) | ||||
|             return jsonify(error.to_dict()), 500 | ||||
|         elif isinstance(e, NotFound): | ||||
|             self._logger.error(__name__, e.description) | ||||
|             error = ErrorDTO(ServiceErrorCode.NotFound, e.description) | ||||
|             return jsonify(error.to_dict()), 404 | ||||
|         else: | ||||
|             tracking_id = uuid.uuid4() | ||||
|             user_message = f"Tracking Id: {tracking_id}" | ||||
|             self._logger.error(__name__, user_message, e) | ||||
|             error = ErrorDTO(None, user_message) | ||||
|             return jsonify(error.to_dict()), 400 | ||||
|  | ||||
|     def before_request_hook(self): | ||||
|         request_id = uuid.uuid4() | ||||
|         self._requests[request] = request_id | ||||
|         method = request.access_control_request_method | ||||
|  | ||||
|         self._logger.info( | ||||
|             __name__, | ||||
|             f"Received {request_id} @ {self._get_methods_from_registered_route() if method is None else method} {request.url} from {request.remote_addr}", | ||||
|         ) | ||||
|  | ||||
|         headers = str(request.headers).replace("\n", "\n\t\t") | ||||
|         data = request.get_data() | ||||
|         data = "" if len(data) == 0 else str(data.decode(encoding="utf-8")) | ||||
|  | ||||
|         text = textwrap.dedent( | ||||
|             f"Request: {request_id}:\n\tHeader:\n\t\t{headers}\n\tUser-Agent: {request.user_agent.string}\n\tBody: {data}" | ||||
|         ) | ||||
|         self._logger.trace(__name__, text) | ||||
|  | ||||
|     def after_request_hook(self, response: Response): | ||||
|         method = request.access_control_request_method | ||||
|         request_id = f"{self._get_methods_from_registered_route() if method is None else method} {request.url} from {request.remote_addr}" | ||||
|         if request in self._requests: | ||||
|             request_id = self._requests[request] | ||||
|  | ||||
|         self._logger.info(__name__, f"Answered {request_id}") | ||||
|  | ||||
|         headers = str(request.headers).replace("\n", "\n\t\t") | ||||
|         data = request.get_data() | ||||
|         data = "" if len(data) == 0 else str(data.decode(encoding="utf-8")) | ||||
|  | ||||
|         text = textwrap.dedent( | ||||
|             f"Request: {request_id}:\n\tHeader:\n\t\t{headers}\n\tResponse: {data}" | ||||
|         ) | ||||
|         self._logger.trace(__name__, text) | ||||
|  | ||||
|         return response | ||||
|  | ||||
|     def start(self): | ||||
|         self._logger.info( | ||||
|             __name__, | ||||
|             f"Starting API {self._api_settings.host}:{self._api_settings.port}", | ||||
|         ) | ||||
|         self._register_routes() | ||||
|         self.secret_key = CredentialManager.decrypt(self._auth_settings.secret_key) | ||||
|         # from waitress import serve | ||||
|         # https://docs.pylonsproject.org/projects/waitress/en/stable/arguments.html | ||||
|         # serve(self, host=self._apt_settings.host, port=self._apt_settings.port, threads=10, connection_limit=1000, channel_timeout=10) | ||||
|         self._socket = eventlet.listen( | ||||
|             (self._api_settings.host, self._api_settings.port) | ||||
|         ) | ||||
|         wsgi.server(self._socket, self, log_output=False) | ||||
|  | ||||
|     def stop(self): | ||||
|         if self._socket is None: | ||||
|             return | ||||
|         self._socket.shutdown(socket.SHUT_RDWR) | ||||
|         self._socket.close() | ||||
|  | ||||
|     def on_connect(self): | ||||
|         self._logger.info(__name__, f"Client connected") | ||||
|  | ||||
|     def on_disconnect(self): | ||||
|         self._logger.info(__name__, f"Client disconnected") | ||||
							
								
								
									
										57
									
								
								bot/src/bot_api/api_module.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								bot/src/bot_api/api_module.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| import os | ||||
|  | ||||
| from cpl_core.configuration import ConfigurationABC | ||||
| from cpl_core.dependency_injection import ServiceCollectionABC | ||||
| from cpl_core.environment import ApplicationEnvironmentABC | ||||
| from cpl_core.mailing import EMailClientABC, EMailClient | ||||
| from cpl_discord.discord_event_types_enum import DiscordEventTypesEnum | ||||
| from cpl_discord.service.discord_collection_abc import DiscordCollectionABC | ||||
| from flask import Flask | ||||
|  | ||||
| from bot_api.abc.auth_service_abc import AuthServiceABC | ||||
| from bot_api.api import Api | ||||
| from bot_api.api_thread import ApiThread | ||||
| from bot_api.controller.auth_controller import AuthController | ||||
| from bot_api.controller.auth_discord_controller import AuthDiscordController | ||||
| from bot_api.controller.graphql_controller import GraphQLController | ||||
| from bot_api.controller.gui_controller import GuiController | ||||
| from bot_api.event.bot_api_on_ready_event import BotApiOnReadyEvent | ||||
| from bot_api.service.auth_service import AuthService | ||||
| from bot_api.service.discord_service import DiscordService | ||||
| from bot_core.abc.module_abc import ModuleABC | ||||
| from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum | ||||
|  | ||||
|  | ||||
| class ApiModule(ModuleABC): | ||||
|     def __init__(self, dc: DiscordCollectionABC): | ||||
|         ModuleABC.__init__(self, dc, FeatureFlagsEnum.api_module) | ||||
|  | ||||
|     def configure_configuration( | ||||
|         self, config: ConfigurationABC, env: ApplicationEnvironmentABC | ||||
|     ): | ||||
|         cwd = env.working_directory | ||||
|         env.set_working_directory(os.path.dirname(os.path.realpath(__file__))) | ||||
|         config.add_json_file(f"config/apisettings.json", optional=False) | ||||
|         config.add_json_file( | ||||
|             f"config/apisettings.{env.environment_name}.json", optional=True | ||||
|         ) | ||||
|         config.add_json_file(f"config/apisettings.{env.host_name}.json", optional=True) | ||||
|         env.set_working_directory(cwd) | ||||
|  | ||||
|     def configure_services( | ||||
|         self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC | ||||
|     ): | ||||
|         services.add_singleton(EMailClientABC, EMailClient) | ||||
|  | ||||
|         services.add_singleton(ApiThread) | ||||
|         services.add_singleton(Flask, Api) | ||||
|  | ||||
|         services.add_transient(AuthServiceABC, AuthService) | ||||
|         services.add_transient(AuthController) | ||||
|         services.add_transient(AuthDiscordController) | ||||
|         services.add_transient(GuiController) | ||||
|         services.add_transient(DiscordService) | ||||
|         services.add_transient(GraphQLController) | ||||
|  | ||||
|         # cpl-discord | ||||
|         services.add_transient(DiscordEventTypesEnum.on_ready.value, BotApiOnReadyEvent) | ||||
							
								
								
									
										26
									
								
								bot/src/bot_api/api_thread.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								bot/src/bot_api/api_thread.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| import threading | ||||
|  | ||||
| from bot_api.api import Api | ||||
| from bot_api.logging.api_logger import ApiLogger | ||||
|  | ||||
|  | ||||
| class ApiThread(threading.Thread): | ||||
|     def __init__(self, logger: ApiLogger, api: Api): | ||||
|         threading.Thread.__init__(self, daemon=True) | ||||
|  | ||||
|         self._logger = logger | ||||
|         self._api = api | ||||
|  | ||||
|     def run(self) -> None: | ||||
|         try: | ||||
|             self._logger.trace(__name__, f"Try to start {type(self._api).__name__}") | ||||
|             self._api.start() | ||||
|         except Exception as e: | ||||
|             self._logger.error(__name__, "Start failed", e) | ||||
|  | ||||
|     def stop(self): | ||||
|         try: | ||||
|             self._logger.trace(__name__, f"Try to stop {type(self._api).__name__}") | ||||
|             self._api.stop() | ||||
|         except Exception as e: | ||||
|             self._logger.error(__name__, "Stop failed", e) | ||||
							
								
								
									
										21
									
								
								bot/src/bot_api/app_api_extension.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								bot/src/bot_api/app_api_extension.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| from cpl_core.application import ApplicationExtensionABC | ||||
| from cpl_core.configuration import ConfigurationABC | ||||
| from cpl_core.dependency_injection import ServiceProviderABC | ||||
|  | ||||
| from bot_api.route.route import Route | ||||
| from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum | ||||
| from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings | ||||
|  | ||||
|  | ||||
| class AppApiExtension(ApplicationExtensionABC): | ||||
|     def __init__(self): | ||||
|         ApplicationExtensionABC.__init__(self) | ||||
|  | ||||
|     async def run(self, config: ConfigurationABC, services: ServiceProviderABC): | ||||
|         feature_flags: FeatureFlagsSettings = config.get_configuration( | ||||
|             FeatureFlagsSettings | ||||
|         ) | ||||
|         if not feature_flags.get_flag(FeatureFlagsEnum.api_module): | ||||
|             return | ||||
|  | ||||
|         Route.init_authorize() | ||||
							
								
								
									
										44
									
								
								bot/src/bot_api/bot-api.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								bot/src/bot_api/bot-api.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| { | ||||
|   "ProjectSettings": { | ||||
|     "Name": "bot-api", | ||||
|     "Version": { | ||||
|       "Major": "1", | ||||
|       "Minor": "2", | ||||
|       "Micro": "0" | ||||
|     }, | ||||
|     "Author": "", | ||||
|     "AuthorEmail": "", | ||||
|     "Description": "", | ||||
|     "LongDescription": "", | ||||
|     "URL": "", | ||||
|     "CopyrightDate": "", | ||||
|     "CopyrightName": "", | ||||
|     "LicenseName": "", | ||||
|     "LicenseDescription": "", | ||||
|     "Dependencies": [ | ||||
|       "cpl-core==2022.12.0" | ||||
|     ], | ||||
|     "DevDependencies": [ | ||||
|       "cpl-cli==2022.12.0" | ||||
|     ], | ||||
|     "PythonVersion": ">=3.10.4", | ||||
|     "PythonPath": {}, | ||||
|     "Classifiers": [] | ||||
|   }, | ||||
|   "BuildSettings": { | ||||
|     "ProjectType": "library", | ||||
|     "SourcePath": "", | ||||
|     "OutputPath": "../../dist", | ||||
|     "Main": "bot_api.main", | ||||
|     "EntryPoint": "bot-api", | ||||
|     "IncludePackageData": false, | ||||
|     "Included": [], | ||||
|     "Excluded": [ | ||||
|       "*/__pycache__", | ||||
|       "*/logs", | ||||
|       "*/tests" | ||||
|     ], | ||||
|     "PackageData": {}, | ||||
|     "ProjectReferences": [] | ||||
|   } | ||||
| } | ||||
							
								
								
									
										26
									
								
								bot/src/bot_api/configuration/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								bot/src/bot_api/configuration/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| bot sh-edraft.de Discord bot | ||||
| ~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| Discord bot for customers of sh-edraft.de | ||||
|  | ||||
| :copyright: (c) 2022 - 2023 sh-edraft.de | ||||
| :license: MIT, see LICENSE for more details. | ||||
|  | ||||
| """ | ||||
|  | ||||
| __title__ = "bot_api.configuration" | ||||
| __author__ = "Sven Heidemann" | ||||
| __license__ = "MIT" | ||||
| __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" | ||||
| __version__ = "1.2.0" | ||||
|  | ||||
| from collections import namedtuple | ||||
|  | ||||
|  | ||||
| # imports | ||||
|  | ||||
| VersionInfo = namedtuple("VersionInfo", "major minor micro") | ||||
| version_info = VersionInfo(major="1", minor="2", micro="0") | ||||
							
								
								
									
										22
									
								
								bot/src/bot_api/configuration/api_settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								bot/src/bot_api/configuration/api_settings.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC | ||||
|  | ||||
|  | ||||
| class ApiSettings(ConfigurationModelABC): | ||||
|     def __init__(self, port: int = None, host: str = None, redirect_uri: bool = None): | ||||
|         ConfigurationModelABC.__init__(self) | ||||
|  | ||||
|         self._port = 80 if port is None else port | ||||
|         self._host = "" if host is None else host | ||||
|         self._redirect_to_https = False if redirect_uri is None else redirect_uri | ||||
|  | ||||
|     @property | ||||
|     def port(self) -> int: | ||||
|         return self._port | ||||
|  | ||||
|     @property | ||||
|     def host(self) -> str: | ||||
|         return self._host | ||||
|  | ||||
|     @property | ||||
|     def redirect_to_https(self) -> bool: | ||||
|         return self._redirect_to_https | ||||
							
								
								
									
										41
									
								
								bot/src/bot_api/configuration/authentication_settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								bot/src/bot_api/configuration/authentication_settings.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC | ||||
|  | ||||
|  | ||||
| class AuthenticationSettings(ConfigurationModelABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         secret_key: str = None, | ||||
|         issuer: str = None, | ||||
|         audience: str = None, | ||||
|         token_expire_time: int = None, | ||||
|         refresh_token_expire_time: int = None, | ||||
|     ): | ||||
|         ConfigurationModelABC.__init__(self) | ||||
|  | ||||
|         self._secret_key = "" if secret_key is None else secret_key | ||||
|         self._issuer = "" if issuer is None else issuer | ||||
|         self._audience = "" if audience is None else audience | ||||
|         self._token_expire_time = 0 if token_expire_time is None else token_expire_time | ||||
|         self._refresh_token_expire_time = ( | ||||
|             0 if refresh_token_expire_time is None else refresh_token_expire_time | ||||
|         ) | ||||
|  | ||||
|     @property | ||||
|     def secret_key(self) -> str: | ||||
|         return self._secret_key | ||||
|  | ||||
|     @property | ||||
|     def issuer(self) -> str: | ||||
|         return self._issuer | ||||
|  | ||||
|     @property | ||||
|     def audience(self) -> str: | ||||
|         return self._audience | ||||
|  | ||||
|     @property | ||||
|     def token_expire_time(self) -> int: | ||||
|         return self._token_expire_time | ||||
|  | ||||
|     @property | ||||
|     def refresh_token_expire_time(self) -> int: | ||||
|         return self._refresh_token_expire_time | ||||
| @@ -0,0 +1,40 @@ | ||||
| from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC | ||||
| from cpl_query.extension import List | ||||
|  | ||||
|  | ||||
| class DiscordAuthenticationSettings(ConfigurationModelABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         client_secret: str = None, | ||||
|         redirect_uri: str = None, | ||||
|         scope: list = None, | ||||
|         token_url: str = None, | ||||
|         auth_url: str = None, | ||||
|     ): | ||||
|         ConfigurationModelABC.__init__(self) | ||||
|  | ||||
|         self._client_secret = "" if client_secret is None else client_secret | ||||
|         self._redirect_url = "" if redirect_uri is None else redirect_uri | ||||
|         self._scope = List() if scope is None else List(str, scope) | ||||
|         self._token_url = "" if token_url is None else token_url | ||||
|         self._auth_url = "" if auth_url is None else auth_url | ||||
|  | ||||
|     @property | ||||
|     def client_secret(self) -> str: | ||||
|         return self._client_secret | ||||
|  | ||||
|     @property | ||||
|     def redirect_url(self) -> str: | ||||
|         return self._redirect_url | ||||
|  | ||||
|     @property | ||||
|     def scope(self) -> List[str]: | ||||
|         return self._scope | ||||
|  | ||||
|     @property | ||||
|     def token_url(self) -> str: | ||||
|         return self._token_url | ||||
|  | ||||
|     @property | ||||
|     def auth_url(self) -> str: | ||||
|         return self._auth_url | ||||
							
								
								
									
										12
									
								
								bot/src/bot_api/configuration/frontend_settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								bot/src/bot_api/configuration/frontend_settings.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC | ||||
|  | ||||
|  | ||||
| class FrontendSettings(ConfigurationModelABC): | ||||
|     def __init__(self, url: str = None): | ||||
|         ConfigurationModelABC.__init__(self) | ||||
|  | ||||
|         self._url = "" if url is None else url | ||||
|  | ||||
|     @property | ||||
|     def url(self) -> str: | ||||
|         return self._url | ||||
							
								
								
									
										26
									
								
								bot/src/bot_api/controller/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								bot/src/bot_api/controller/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| bot sh-edraft.de Discord bot | ||||
| ~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| Discord bot for customers of sh-edraft.de | ||||
|  | ||||
| :copyright: (c) 2022 - 2023 sh-edraft.de | ||||
| :license: MIT, see LICENSE for more details. | ||||
|  | ||||
| """ | ||||
|  | ||||
| __title__ = "bot_api.controller" | ||||
| __author__ = "Sven Heidemann" | ||||
| __license__ = "MIT" | ||||
| __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" | ||||
| __version__ = "1.2.0" | ||||
|  | ||||
| from collections import namedtuple | ||||
|  | ||||
|  | ||||
| # imports: | ||||
|  | ||||
| VersionInfo = namedtuple("VersionInfo", "major minor micro") | ||||
| version_info = VersionInfo(major="1", minor="2", micro="0") | ||||
							
								
								
									
										170
									
								
								bot/src/bot_api/controller/auth_controller.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								bot/src/bot_api/controller/auth_controller.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,170 @@ | ||||
| from cpl_core.configuration import ConfigurationABC | ||||
| from cpl_core.environment import ApplicationEnvironmentABC | ||||
| from cpl_core.mailing import EMailClientABC, EMailClientSettings | ||||
| from cpl_translation import TranslatePipe | ||||
| from flask import request, jsonify, Response | ||||
|  | ||||
| from bot_api.abc.auth_service_abc import AuthServiceABC | ||||
| from bot_api.api import Api | ||||
| from bot_api.filter.auth_user_select_criteria import AuthUserSelectCriteria | ||||
| from bot_api.json_processor import JSONProcessor | ||||
| from bot_api.logging.api_logger import ApiLogger | ||||
| from bot_api.model.auth_user_dto import AuthUserDTO | ||||
| from bot_api.model.reset_password_dto import ResetPasswordDTO | ||||
| from bot_api.model.token_dto import TokenDTO | ||||
| from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO | ||||
| from bot_api.route.route import Route | ||||
| from bot_data.model.auth_role_enum import AuthRoleEnum | ||||
|  | ||||
|  | ||||
| class AuthController: | ||||
|     BasePath = "/api/auth" | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         config: ConfigurationABC, | ||||
|         env: ApplicationEnvironmentABC, | ||||
|         logger: ApiLogger, | ||||
|         t: TranslatePipe, | ||||
|         api: Api, | ||||
|         mail_settings: EMailClientSettings, | ||||
|         mailer: EMailClientABC, | ||||
|         auth_service: AuthServiceABC, | ||||
|     ): | ||||
|         self._config = config | ||||
|         self._env = env | ||||
|         self._logger = logger | ||||
|         self._t = t | ||||
|         self._api = api | ||||
|         self._mail_settings = mail_settings | ||||
|         self._mailer = mailer | ||||
|         self._auth_service = auth_service | ||||
|  | ||||
|     @Route.get(f"{BasePath}/users") | ||||
|     @Route.authorize(role=AuthRoleEnum.admin) | ||||
|     async def get_all_users(self) -> Response: | ||||
|         result = await self._auth_service.get_all_auth_users_async() | ||||
|         return jsonify(result.select(lambda x: x.to_dict()).to_list()) | ||||
|  | ||||
|     @Route.post(f"{BasePath}/users/get/filtered") | ||||
|     @Route.authorize(role=AuthRoleEnum.admin) | ||||
|     async def get_filtered_users(self) -> Response: | ||||
|         dto: AuthUserSelectCriteria = JSONProcessor.process( | ||||
|             AuthUserSelectCriteria, request.get_json(force=True, silent=True) | ||||
|         ) | ||||
|         result = await self._auth_service.get_filtered_auth_users_async(dto) | ||||
|         result.result = result.result.select(lambda x: x.to_dict()).to_list() | ||||
|         return jsonify(result.to_dict()) | ||||
|  | ||||
|     @Route.get(f"{BasePath}/users/get/<email>") | ||||
|     @Route.authorize | ||||
|     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/<email>") | ||||
|     @Route.authorize | ||||
|     async def find_user_from_email(self, email: str) -> Response: | ||||
|         result = await self._auth_service.find_auth_user_by_email_async(email) | ||||
|         return jsonify(result.to_dict()) | ||||
|  | ||||
|     @Route.post(f"{BasePath}/register") | ||||
|     async def register(self): | ||||
|         dto: AuthUserDTO = JSONProcessor.process( | ||||
|             AuthUserDTO, request.get_json(force=True, silent=True) | ||||
|         ) | ||||
|         self._auth_service.add_auth_user(dto) | ||||
|         return "", 200 | ||||
|  | ||||
|     @Route.post(f"{BasePath}/register-by-id/<id>") | ||||
|     async def register_id(self, id: str): | ||||
|         result = await self._auth_service.confirm_email_async(id) | ||||
|         return jsonify(result) | ||||
|  | ||||
|     @Route.post(f"{BasePath}/login") | ||||
|     async def login(self) -> Response: | ||||
|         dto: AuthUserDTO = JSONProcessor.process( | ||||
|             AuthUserDTO, request.get_json(force=True, silent=True) | ||||
|         ) | ||||
|         result = await self._auth_service.login_async(dto) | ||||
|         return jsonify(result.to_dict()) | ||||
|  | ||||
|     @Route.get(f"{BasePath}/verify-login") | ||||
|     async def verify_login(self): | ||||
|         token = None | ||||
|         result = False | ||||
|         if "Authorization" in request.headers: | ||||
|             bearer = request.headers.get("Authorization") | ||||
|             token = bearer.split()[1] | ||||
|  | ||||
|         if token is not None: | ||||
|             result = self._auth_service.verify_login(token) | ||||
|  | ||||
|         return jsonify(result) | ||||
|  | ||||
|     @Route.post(f"{BasePath}/forgot-password/<email>") | ||||
|     async def forgot_password(self, email: str): | ||||
|         await self._auth_service.forgot_password_async(email) | ||||
|         return "", 200 | ||||
|  | ||||
|     @Route.post(f"{BasePath}/confirm-forgot-password/<id>") | ||||
|     async def confirm_forgot_password(self, id: str): | ||||
|         result = await self._auth_service.confirm_forgot_password_async(id) | ||||
|         return jsonify(result.to_dict()) | ||||
|  | ||||
|     @Route.post(f"{BasePath}/reset-password") | ||||
|     async def reset_password(self): | ||||
|         dto: ResetPasswordDTO = JSONProcessor.process( | ||||
|             ResetPasswordDTO, request.get_json(force=True, silent=True) | ||||
|         ) | ||||
|         await self._auth_service.reset_password_async(dto) | ||||
|         return "", 200 | ||||
|  | ||||
|     @Route.post(f"{BasePath}/update-user") | ||||
|     @Route.authorize | ||||
|     async def update_user(self): | ||||
|         dto: UpdateAuthUserDTO = JSONProcessor.process( | ||||
|             UpdateAuthUserDTO, request.get_json(force=True, silent=True) | ||||
|         ) | ||||
|         await self._auth_service.update_user_async(dto) | ||||
|         return "", 200 | ||||
|  | ||||
|     @Route.post(f"{BasePath}/update-user-as-admin") | ||||
|     @Route.authorize(role=AuthRoleEnum.admin) | ||||
|     async def update_user_as_admin(self): | ||||
|         dto: UpdateAuthUserDTO = JSONProcessor.process( | ||||
|             UpdateAuthUserDTO, request.get_json(force=True, silent=True) | ||||
|         ) | ||||
|         await self._auth_service.update_user_as_admin_async(dto) | ||||
|         return "", 200 | ||||
|  | ||||
|     @Route.post(f"{BasePath}/refresh") | ||||
|     async def refresh(self) -> Response: | ||||
|         dto: TokenDTO = JSONProcessor.process( | ||||
|             TokenDTO, request.get_json(force=True, silent=True) | ||||
|         ) | ||||
|         result = await self._auth_service.refresh_async(dto) | ||||
|         return jsonify(result.to_dict()) | ||||
|  | ||||
|     @Route.post(f"{BasePath}/revoke") | ||||
|     async def revoke(self): | ||||
|         dto: TokenDTO = JSONProcessor.process( | ||||
|             TokenDTO, request.get_json(force=True, silent=True) | ||||
|         ) | ||||
|         await self._auth_service.revoke_async(dto) | ||||
|         return "", 200 | ||||
|  | ||||
|     @Route.post(f"{BasePath}/delete-user") | ||||
|     @Route.authorize(role=AuthRoleEnum.admin) | ||||
|     async def delete_user(self): | ||||
|         dto: AuthUserDTO = JSONProcessor.process( | ||||
|             AuthUserDTO, request.get_json(force=True, silent=True) | ||||
|         ) | ||||
|         await self._auth_service.delete_auth_user_async(dto) | ||||
|         return "", 200 | ||||
|  | ||||
|     @Route.post(f"{BasePath}/delete-user-by-mail/<email>") | ||||
|     @Route.authorize(role=AuthRoleEnum.admin) | ||||
|     async def delete_user_by_mail(self, email: str): | ||||
|         await self._auth_service.delete_auth_user_by_email_async(email) | ||||
|         return "", 200 | ||||
							
								
								
									
										94
									
								
								bot/src/bot_api/controller/auth_discord_controller.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								bot/src/bot_api/controller/auth_discord_controller.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | ||||
| import os | ||||
| import uuid | ||||
|  | ||||
| from cpl_core.configuration import ConfigurationABC | ||||
| from cpl_core.environment import ApplicationEnvironmentABC | ||||
| from cpl_core.mailing import EMailClientABC, EMailClientSettings | ||||
| from cpl_core.utils import CredentialManager | ||||
| from cpl_discord.service import DiscordBotServiceABC | ||||
| from cpl_translation import TranslatePipe | ||||
| from flask import jsonify, Response | ||||
| from flask import request | ||||
| from requests_oauthlib import OAuth2Session | ||||
|  | ||||
| from bot_api.abc.auth_service_abc import AuthServiceABC | ||||
| from bot_api.api import Api | ||||
| from bot_api.configuration.discord_authentication_settings import ( | ||||
|     DiscordAuthenticationSettings, | ||||
| ) | ||||
| from bot_api.logging.api_logger import ApiLogger | ||||
| from bot_api.model.auth_user_dto import AuthUserDTO | ||||
| from bot_api.route.route import Route | ||||
| from bot_data.model.auth_role_enum import AuthRoleEnum | ||||
|  | ||||
| # Disable SSL requirement | ||||
| os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1" | ||||
|  | ||||
|  | ||||
| class AuthDiscordController: | ||||
|     BasePath = "/api/auth/discord" | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         auth_settings: DiscordAuthenticationSettings, | ||||
|         config: ConfigurationABC, | ||||
|         env: ApplicationEnvironmentABC, | ||||
|         logger: ApiLogger, | ||||
|         bot: DiscordBotServiceABC, | ||||
|         t: TranslatePipe, | ||||
|         api: Api, | ||||
|         mail_settings: EMailClientSettings, | ||||
|         mailer: EMailClientABC, | ||||
|         auth_service: AuthServiceABC, | ||||
|     ): | ||||
|         self._auth_settings = auth_settings | ||||
|         self._config = config | ||||
|         self._env = env | ||||
|         self._logger = logger | ||||
|         self._bot = bot | ||||
|         self._t = t | ||||
|         self._api = api | ||||
|         self._mail_settings = mail_settings | ||||
|         self._mailer = mailer | ||||
|         self._auth_service = auth_service | ||||
|  | ||||
|     def _get_user_from_discord_response(self) -> dict: | ||||
|         discord = OAuth2Session( | ||||
|             self._bot.user.id, | ||||
|             redirect_uri=self._auth_settings.redirect_url, | ||||
|             state=request.args.get("state"), | ||||
|             scope=self._auth_settings.scope.to_list(), | ||||
|         ) | ||||
|         token = discord.fetch_token( | ||||
|             self._auth_settings.token_url, | ||||
|             client_secret=CredentialManager.decrypt(self._auth_settings.client_secret), | ||||
|             authorization_response=request.url, | ||||
|         ) | ||||
|         discord = OAuth2Session(self._bot.user.id, token=token) | ||||
|         return discord.get("https://discordapp.com/api" + "/users/@me").json() | ||||
|  | ||||
|     @Route.get(f"{BasePath}/get-url") | ||||
|     async def get_url(self): | ||||
|         oauth = OAuth2Session( | ||||
|             self._bot.user.id, | ||||
|             redirect_uri=self._auth_settings.redirect_url, | ||||
|             scope=self._auth_settings.scope.to_list(), | ||||
|         ) | ||||
|         login_url, state = oauth.authorization_url(self._auth_settings.auth_url) | ||||
|         return jsonify({"loginUrl": login_url}) | ||||
|  | ||||
|     @Route.get(f"{BasePath}/login") | ||||
|     async def discord_login(self) -> Response: | ||||
|         response = self._get_user_from_discord_response() | ||||
|         dto = AuthUserDTO( | ||||
|             0, | ||||
|             response["username"], | ||||
|             response["discriminator"], | ||||
|             response["email"], | ||||
|             str(uuid.uuid4()), | ||||
|             None, | ||||
|             AuthRoleEnum.normal, | ||||
|         ) | ||||
|  | ||||
|         result = await self._auth_service.login_discord_async(dto, response["id"]) | ||||
|         return jsonify(result.to_dict()) | ||||
							
								
								
									
										44
									
								
								bot/src/bot_api/controller/graphql_controller.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								bot/src/bot_api/controller/graphql_controller.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| from ariadne import graphql_sync | ||||
| from ariadne.explorer import ExplorerPlayground | ||||
| from cpl_core.configuration import ConfigurationABC | ||||
| from cpl_core.environment import ApplicationEnvironmentABC | ||||
| from flask import request, jsonify | ||||
|  | ||||
| from bot_api.logging.api_logger import ApiLogger | ||||
| from bot_api.route.route import Route | ||||
| from bot_graphql.schema import Schema | ||||
|  | ||||
|  | ||||
| class GraphQLController: | ||||
|     BasePath = f"/api/graphql" | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         config: ConfigurationABC, | ||||
|         env: ApplicationEnvironmentABC, | ||||
|         logger: ApiLogger, | ||||
|         schema: Schema, | ||||
|     ): | ||||
|         self._config = config | ||||
|         self._env = env | ||||
|         self._logger = logger | ||||
|         self._schema = schema | ||||
|  | ||||
|     @Route.get(f"{BasePath}/playground") | ||||
|     @Route.authorize(skip_in_dev=True) | ||||
|     async def playground(self): | ||||
|         if self._env.environment_name != "development": | ||||
|             return "", 403 | ||||
|  | ||||
|         return ExplorerPlayground().html(None), 200 | ||||
|  | ||||
|     @Route.post(f"{BasePath}") | ||||
|     @Route.authorize(by_api_key=True) | ||||
|     async def graphql(self): | ||||
|         data = request.get_json() | ||||
|  | ||||
|         # Note: Passing the request to the context is optional. | ||||
|         # In Flask, the current request is always accessible as flask.request | ||||
|         success, result = graphql_sync(self._schema.schema, data, context_value=request) | ||||
|  | ||||
|         return jsonify(result), 200 if success else 400 | ||||
							
								
								
									
										84
									
								
								bot/src/bot_api/controller/gui_controller.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								bot/src/bot_api/controller/gui_controller.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| import os | ||||
|  | ||||
| from cpl_core.configuration import ConfigurationABC | ||||
| from cpl_core.environment import ApplicationEnvironmentABC | ||||
| from cpl_core.mailing import EMail, EMailClientABC, EMailClientSettings | ||||
| from cpl_translation import TranslatePipe | ||||
| from flask import jsonify | ||||
|  | ||||
| from bot_api.api import Api | ||||
| from bot_api.configuration.authentication_settings import AuthenticationSettings | ||||
| from bot_api.logging.api_logger import ApiLogger | ||||
| from bot_api.model.settings_dto import SettingsDTO | ||||
| from bot_api.model.version_dto import VersionDTO | ||||
| from bot_api.route.route import Route | ||||
|  | ||||
|  | ||||
| class GuiController: | ||||
|     BasePath = f"/api/gui" | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         config: ConfigurationABC, | ||||
|         env: ApplicationEnvironmentABC, | ||||
|         logger: ApiLogger, | ||||
|         t: TranslatePipe, | ||||
|         api: Api, | ||||
|         mail_settings: EMailClientSettings, | ||||
|         mailer: EMailClientABC, | ||||
|         auth_settings: AuthenticationSettings, | ||||
|     ): | ||||
|         self._config = config | ||||
|         self._env = env | ||||
|         self._logger = logger | ||||
|         self._t = t | ||||
|         self._api = api | ||||
|         self._mail_settings = mail_settings | ||||
|         self._mailer = mailer | ||||
|         self._auth_settings = auth_settings | ||||
|  | ||||
|     @Route.get(f"{BasePath}/api-version") | ||||
|     async def api_version(self): | ||||
|         import bot_api | ||||
|  | ||||
|         version = bot_api.version_info | ||||
|         return VersionDTO(version.major, version.minor, version.micro).to_dict() | ||||
|  | ||||
|     @Route.get(f"{BasePath}/settings") | ||||
|     @Route.authorize | ||||
|     async def settings(self): | ||||
|         import bot_api | ||||
|  | ||||
|         version = bot_api.version_info | ||||
|  | ||||
|         return jsonify( | ||||
|             SettingsDTO( | ||||
|                 "", | ||||
|                 VersionDTO(version.major, version.minor, version.micro), | ||||
|                 os.path.abspath(os.path.join(self._env.working_directory, "config")), | ||||
|                 "/", | ||||
|                 "/", | ||||
|                 self._auth_settings.token_expire_time, | ||||
|                 self._auth_settings.refresh_token_expire_time, | ||||
|                 self._mail_settings.user_name, | ||||
|                 self._mail_settings.port, | ||||
|                 self._mail_settings.host, | ||||
|                 self._mail_settings.user_name, | ||||
|                 self._mail_settings.user_name, | ||||
|             ).to_dict() | ||||
|         ) | ||||
|  | ||||
|     @Route.post(f"{BasePath}/send-test-mail/<email>") | ||||
|     @Route.authorize | ||||
|     async def send_test_mail(self, email: str): | ||||
|         mail = EMail() | ||||
|         mail.add_header("Mime-Version: 1.0") | ||||
|         mail.add_header("Content-Type: text/plain; charset=utf-8") | ||||
|         mail.add_header("Content-Transfer-Encoding: quoted-printable") | ||||
|         mail.add_receiver(email) | ||||
|         mail.subject = self._t.transform("api.api.test_mail.subject") | ||||
|         mail.body = self._t.transform("api.api.test_mail.message").format( | ||||
|             self._env.host_name, self._env.environment_name | ||||
|         ) | ||||
|         self._mailer.send_mail(mail) | ||||
|         return "", 200 | ||||
							
								
								
									
										26
									
								
								bot/src/bot_api/event/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								bot/src/bot_api/event/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| bot sh-edraft.de Discord bot | ||||
| ~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| Discord bot for customers of sh-edraft.de | ||||
|  | ||||
| :copyright: (c) 2022 - 2023 sh-edraft.de | ||||
| :license: MIT, see LICENSE for more details. | ||||
|  | ||||
| """ | ||||
|  | ||||
| __title__ = "bot_api.event" | ||||
| __author__ = "Sven Heidemann" | ||||
| __license__ = "MIT" | ||||
| __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" | ||||
| __version__ = "1.2.0" | ||||
|  | ||||
| from collections import namedtuple | ||||
|  | ||||
|  | ||||
| # imports: | ||||
|  | ||||
| VersionInfo = namedtuple("VersionInfo", "major minor micro") | ||||
| version_info = VersionInfo(major="1", minor="2", micro="0") | ||||
							
								
								
									
										12
									
								
								bot/src/bot_api/event/bot_api_on_ready_event.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								bot/src/bot_api/event/bot_api_on_ready_event.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| from cpl_discord.events import OnReadyABC | ||||
|  | ||||
| from bot_api.api_thread import ApiThread | ||||
|  | ||||
|  | ||||
| class BotApiOnReadyEvent(OnReadyABC): | ||||
|     def __init__(self, api: ApiThread): | ||||
|         OnReadyABC.__init__(self) | ||||
|         self._api = api | ||||
|  | ||||
|     async def on_ready(self): | ||||
|         self._api.start() | ||||
							
								
								
									
										26
									
								
								bot/src/bot_api/exception/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								bot/src/bot_api/exception/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| bot sh-edraft.de Discord bot | ||||
| ~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| Discord bot for customers of sh-edraft.de | ||||
|  | ||||
| :copyright: (c) 2022 - 2023 sh-edraft.de | ||||
| :license: MIT, see LICENSE for more details. | ||||
|  | ||||
| """ | ||||
|  | ||||
| __title__ = "bot_api.exception" | ||||
| __author__ = "Sven Heidemann" | ||||
| __license__ = "MIT" | ||||
| __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" | ||||
| __version__ = "1.2.0" | ||||
|  | ||||
| from collections import namedtuple | ||||
|  | ||||
|  | ||||
| # imports: | ||||
|  | ||||
| VersionInfo = namedtuple("VersionInfo", "major minor micro") | ||||
| version_info = VersionInfo(major="1", minor="2", micro="0") | ||||
							
								
								
									
										23
									
								
								bot/src/bot_api/exception/service_error_code_enum.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								bot/src/bot_api/exception/service_error_code_enum.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| from enum import Enum | ||||
|  | ||||
| from werkzeug.exceptions import Unauthorized | ||||
|  | ||||
|  | ||||
| class ServiceErrorCode(Enum): | ||||
|     Unknown = 0 | ||||
|  | ||||
|     InvalidDependencies = 1 | ||||
|     InvalidData = 2 | ||||
|     NotFound = 3 | ||||
|     DataAlreadyExists = 4 | ||||
|     UnableToAdd = 5 | ||||
|     UnableToDelete = 6 | ||||
|  | ||||
|     InvalidUser = 7 | ||||
|  | ||||
|     ConnectionFailed = 8 | ||||
|     Timeout = 9 | ||||
|     MailError = 10 | ||||
|  | ||||
|     Unauthorized = 11 | ||||
|     Forbidden = 12 | ||||
							
								
								
									
										12
									
								
								bot/src/bot_api/exception/service_exception.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								bot/src/bot_api/exception/service_exception.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| from bot_api.exception.service_error_code_enum import ServiceErrorCode | ||||
|  | ||||
|  | ||||
| class ServiceException(Exception): | ||||
|     def __init__(self, error_code: ServiceErrorCode, message: str, *args): | ||||
|         Exception.__init__(self, *args) | ||||
|  | ||||
|         self.error_code = error_code | ||||
|         self.message = message | ||||
|  | ||||
|     def get_detailed_message(self) -> str: | ||||
|         return f"ServiceException - ErrorCode: {self.error_code} - ErrorMessage: {self.message}" | ||||
							
								
								
									
										26
									
								
								bot/src/bot_api/filter/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								bot/src/bot_api/filter/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| bot sh-edraft.de Discord bot | ||||
| ~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| Discord bot for customers of sh-edraft.de | ||||
|  | ||||
| :copyright: (c) 2022 - 2023 sh-edraft.de | ||||
| :license: MIT, see LICENSE for more details. | ||||
|  | ||||
| """ | ||||
|  | ||||
| __title__ = "bot_api.filter" | ||||
| __author__ = "Sven Heidemann" | ||||
| __license__ = "MIT" | ||||
| __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" | ||||
| __version__ = "1.2.0" | ||||
|  | ||||
| from collections import namedtuple | ||||
|  | ||||
|  | ||||
| # imports: | ||||
|  | ||||
| VersionInfo = namedtuple("VersionInfo", "major minor micro") | ||||
| version_info = VersionInfo(major="1", minor="2", micro="0") | ||||
							
								
								
									
										23
									
								
								bot/src/bot_api/filter/auth_user_select_criteria.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								bot/src/bot_api/filter/auth_user_select_criteria.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| from bot_api.abc.select_criteria_abc import SelectCriteriaABC | ||||
|  | ||||
|  | ||||
| class AuthUserSelectCriteria(SelectCriteriaABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         page_index: int, | ||||
|         page_size: int, | ||||
|         sort_direction: str, | ||||
|         sort_column: str, | ||||
|         first_name: str, | ||||
|         last_name: str, | ||||
|         email: str, | ||||
|         auth_role: int, | ||||
|     ): | ||||
|         SelectCriteriaABC.__init__( | ||||
|             self, page_index, page_size, sort_direction, sort_column | ||||
|         ) | ||||
|  | ||||
|         self.first_name = first_name | ||||
|         self.last_name = last_name | ||||
|         self.email = email | ||||
|         self.auth_role = auth_role | ||||
							
								
								
									
										26
									
								
								bot/src/bot_api/filter/discord/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								bot/src/bot_api/filter/discord/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| bot sh-edraft.de Discord bot | ||||
| ~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| Discord bot for customers of sh-edraft.de | ||||
|  | ||||
| :copyright: (c) 2022 - 2023 sh-edraft.de | ||||
| :license: MIT, see LICENSE for more details. | ||||
|  | ||||
| """ | ||||
|  | ||||
| __title__ = "bot_api.filter.discord" | ||||
| __author__ = "Sven Heidemann" | ||||
| __license__ = "MIT" | ||||
| __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" | ||||
| __version__ = "1.2.0" | ||||
|  | ||||
| from collections import namedtuple | ||||
|  | ||||
|  | ||||
| # imports: | ||||
|  | ||||
| VersionInfo = namedtuple("VersionInfo", "major minor micro") | ||||
| version_info = VersionInfo(major="1", minor="2", micro="0") | ||||
							
								
								
									
										17
									
								
								bot/src/bot_api/filter/discord/server_select_criteria.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								bot/src/bot_api/filter/discord/server_select_criteria.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| from bot_api.abc.select_criteria_abc import SelectCriteriaABC | ||||
|  | ||||
|  | ||||
| class ServerSelectCriteria(SelectCriteriaABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         page_index: int, | ||||
|         page_size: int, | ||||
|         sort_direction: str, | ||||
|         sort_column: str, | ||||
|         name: str, | ||||
|     ): | ||||
|         SelectCriteriaABC.__init__( | ||||
|             self, page_index, page_size, sort_direction, sort_column | ||||
|         ) | ||||
|  | ||||
|         self.name = name | ||||
							
								
								
									
										42
									
								
								bot/src/bot_api/json_processor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								bot/src/bot_api/json_processor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| import enum | ||||
| from inspect import signature, Parameter | ||||
|  | ||||
| from cpl_core.utils import String | ||||
|  | ||||
|  | ||||
| class JSONProcessor: | ||||
|     @staticmethod | ||||
|     def process(_t: type, values: dict) -> object: | ||||
|         args = [] | ||||
|  | ||||
|         sig = signature(_t.__init__) | ||||
|         for param in sig.parameters.items(): | ||||
|             parameter = param[1] | ||||
|             if parameter.name == "self" or parameter.annotation == Parameter.empty: | ||||
|                 continue | ||||
|  | ||||
|             name = String.convert_to_camel_case(parameter.name) | ||||
|             name = name.replace("Dto", "DTO") | ||||
|             name_first_lower = String.first_to_lower(name) | ||||
|             if name in values or name_first_lower in values: | ||||
|                 value = "" | ||||
|                 if name in values: | ||||
|                     value = values[name] | ||||
|                 else: | ||||
|                     value = values[name_first_lower] | ||||
|  | ||||
|                 if isinstance(value, dict): | ||||
|                     value = JSONProcessor.process(parameter.annotation, value) | ||||
|  | ||||
|                 if issubclass(parameter.annotation, enum.Enum): | ||||
|                     value = parameter.annotation(value) | ||||
|  | ||||
|                 args.append(value) | ||||
|  | ||||
|             elif parameter.default != Parameter.empty: | ||||
|                 args.append(parameter.default) | ||||
|  | ||||
|             else: | ||||
|                 args.append(None) | ||||
|  | ||||
|         return _t(*args) | ||||
							
								
								
									
										26
									
								
								bot/src/bot_api/logging/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								bot/src/bot_api/logging/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| bot sh-edraft.de Discord bot | ||||
| ~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| Discord bot for customers of sh-edraft.de | ||||
|  | ||||
| :copyright: (c) 2022 - 2023 sh-edraft.de | ||||
| :license: MIT, see LICENSE for more details. | ||||
|  | ||||
| """ | ||||
|  | ||||
| __title__ = "bot_api.logging" | ||||
| __author__ = "Sven Heidemann" | ||||
| __license__ = "MIT" | ||||
| __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" | ||||
| __version__ = "1.2.0" | ||||
|  | ||||
| from collections import namedtuple | ||||
|  | ||||
|  | ||||
| # imports: | ||||
|  | ||||
| VersionInfo = namedtuple("VersionInfo", "major minor micro") | ||||
| version_info = VersionInfo(major="1", minor="2", micro="0") | ||||
							
								
								
									
										15
									
								
								bot/src/bot_api/logging/api_logger.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								bot/src/bot_api/logging/api_logger.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| from cpl_core.configuration import ConfigurationABC | ||||
| from cpl_core.environment import ApplicationEnvironmentABC | ||||
| from cpl_core.time import TimeFormatSettings | ||||
|  | ||||
| from bot_core.abc.custom_file_logger_abc import CustomFileLoggerABC | ||||
|  | ||||
|  | ||||
| class ApiLogger(CustomFileLoggerABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         config: ConfigurationABC, | ||||
|         time_format: TimeFormatSettings, | ||||
|         env: ApplicationEnvironmentABC, | ||||
|     ): | ||||
|         CustomFileLoggerABC.__init__(self, "Api", config, time_format, env) | ||||
							
								
								
									
										26
									
								
								bot/src/bot_api/model/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								bot/src/bot_api/model/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| bot sh-edraft.de Discord bot | ||||
| ~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| Discord bot for customers of sh-edraft.de | ||||
|  | ||||
| :copyright: (c) 2022 - 2023 sh-edraft.de | ||||
| :license: MIT, see LICENSE for more details. | ||||
|  | ||||
| """ | ||||
|  | ||||
| __title__ = "bot_api.model" | ||||
| __author__ = "Sven Heidemann" | ||||
| __license__ = "MIT" | ||||
| __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" | ||||
| __version__ = "1.2.0" | ||||
|  | ||||
| from collections import namedtuple | ||||
|  | ||||
|  | ||||
| # imports: | ||||
|  | ||||
| VersionInfo = namedtuple("VersionInfo", "major minor micro") | ||||
| version_info = VersionInfo(major="1", minor="2", micro="0") | ||||
							
								
								
									
										136
									
								
								bot/src/bot_api/model/auth_user_dto.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								bot/src/bot_api/model/auth_user_dto.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | ||||
| from datetime import datetime | ||||
| from typing import Optional | ||||
|  | ||||
| from cpl_query.extension import List | ||||
|  | ||||
| from bot_api.abc.dto_abc import DtoABC | ||||
| from bot_api.model.user_dto import UserDTO | ||||
| from bot_data.model.auth_role_enum import AuthRoleEnum | ||||
|  | ||||
|  | ||||
| class AuthUserDTO(DtoABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         id: int = None, | ||||
|         first_name: str = None, | ||||
|         last_name: str = None, | ||||
|         email: str = None, | ||||
|         password: str = None, | ||||
|         confirmation_id: Optional[str] = None, | ||||
|         auth_role: AuthRoleEnum = None, | ||||
|         users: List[UserDTO] = None, | ||||
|         created_at: datetime = None, | ||||
|         modified_at: datetime = None, | ||||
|     ): | ||||
|         DtoABC.__init__(self) | ||||
|  | ||||
|         self._id = id | ||||
|         self._first_name = first_name | ||||
|         self._last_name = last_name | ||||
|         self._email = email | ||||
|         self._password = password | ||||
|         self._is_confirmed = confirmation_id is None | ||||
|         self._auth_role = auth_role | ||||
|         self._created_at = created_at | ||||
|         self._modified_at = modified_at | ||||
|  | ||||
|         if users is None: | ||||
|             self._users = List(UserDTO) | ||||
|         else: | ||||
|             self._users = users | ||||
|  | ||||
|     @property | ||||
|     def id(self) -> int: | ||||
|         return self._id | ||||
|  | ||||
|     @property | ||||
|     def first_name(self) -> str: | ||||
|         return self._first_name | ||||
|  | ||||
|     @first_name.setter | ||||
|     def first_name(self, value: str): | ||||
|         self._first_name = value | ||||
|  | ||||
|     @property | ||||
|     def last_name(self) -> str: | ||||
|         return self._last_name | ||||
|  | ||||
|     @last_name.setter | ||||
|     def last_name(self, value: str): | ||||
|         self._last_name = value | ||||
|  | ||||
|     @property | ||||
|     def email(self) -> str: | ||||
|         return self._email | ||||
|  | ||||
|     @email.setter | ||||
|     def email(self, value: str): | ||||
|         self._email = value | ||||
|  | ||||
|     @property | ||||
|     def password(self) -> str: | ||||
|         return self._password | ||||
|  | ||||
|     @password.setter | ||||
|     def password(self, value: str): | ||||
|         self._password = value | ||||
|  | ||||
|     @property | ||||
|     def is_confirmed(self) -> Optional[str]: | ||||
|         return self._is_confirmed | ||||
|  | ||||
|     @is_confirmed.setter | ||||
|     def is_confirmed(self, value: Optional[str]): | ||||
|         self._is_confirmed = value | ||||
|  | ||||
|     @property | ||||
|     def auth_role(self) -> AuthRoleEnum: | ||||
|         return self._auth_role | ||||
|  | ||||
|     @auth_role.setter | ||||
|     def auth_role(self, value: AuthRoleEnum): | ||||
|         self._auth_role = value | ||||
|  | ||||
|     @property | ||||
|     def users(self) -> List[UserDTO]: | ||||
|         return self._users | ||||
|  | ||||
|     @property | ||||
|     def created_at(self) -> datetime: | ||||
|         return self._created_at | ||||
|  | ||||
|     @property | ||||
|     def modified_at(self) -> datetime: | ||||
|         return self._modified_at | ||||
|  | ||||
|     def from_dict(self, values: dict): | ||||
|         self._id = values["id"] | ||||
|         self._first_name = values["firstName"] | ||||
|         self._last_name = values["lastName"] | ||||
|         self._email = values["email"] | ||||
|         self._password = values["password"] | ||||
|         self._is_confirmed = values["isConfirmed"] | ||||
|         self._auth_role = AuthRoleEnum(values["authRole"]) | ||||
|         if "users" in values: | ||||
|             self._users = List(UserDTO) | ||||
|             for u in values["users"]: | ||||
|                 user = UserDTO() | ||||
|                 user.from_dict(u) | ||||
|                 self._users.add(user) | ||||
|  | ||||
|         self._created_at = values["createdAt"] | ||||
|         self._modified_at = values["modifiedAt"] | ||||
|  | ||||
|     def to_dict(self) -> dict: | ||||
|         return { | ||||
|             "id": self._id, | ||||
|             "firstName": self._first_name, | ||||
|             "lastName": self._last_name, | ||||
|             "email": self._email, | ||||
|             "password": self._password, | ||||
|             "isConfirmed": self._is_confirmed, | ||||
|             "authRole": self._auth_role.value, | ||||
|             "users": self._users.select(lambda u: u.to_dict()).to_list(), | ||||
|             "createdAt": self._created_at, | ||||
|             "modifiedAt": self._modified_at, | ||||
|         } | ||||
							
								
								
									
										17
									
								
								bot/src/bot_api/model/auth_user_filtered_result_dto.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								bot/src/bot_api/model/auth_user_filtered_result_dto.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| from cpl_query.extension import List | ||||
|  | ||||
| from bot_api.abc.dto_abc import DtoABC | ||||
| from bot_data.filtered_result import FilteredResult | ||||
|  | ||||
|  | ||||
| class AuthUserFilteredResultDTO(DtoABC, FilteredResult): | ||||
|     def __init__(self, result: List = None, total_count: int = 0): | ||||
|         DtoABC.__init__(self) | ||||
|         FilteredResult.__init__(self, result, total_count) | ||||
|  | ||||
|     def from_dict(self, values: dict): | ||||
|         self._result = values["users"] | ||||
|         self._total_count = values["totalCount"] | ||||
|  | ||||
|     def to_dict(self) -> dict: | ||||
|         return {"users": self.result, "totalCount": self.total_count} | ||||
							
								
								
									
										26
									
								
								bot/src/bot_api/model/discord/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								bot/src/bot_api/model/discord/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| bot sh-edraft.de Discord bot | ||||
| ~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| Discord bot for customers of sh-edraft.de | ||||
|  | ||||
| :copyright: (c) 2022 - 2023 sh-edraft.de | ||||
| :license: MIT, see LICENSE for more details. | ||||
|  | ||||
| """ | ||||
|  | ||||
| __title__ = "bot_api.model.discord" | ||||
| __author__ = "Sven Heidemann" | ||||
| __license__ = "MIT" | ||||
| __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" | ||||
| __version__ = "1.2.0" | ||||
|  | ||||
| from collections import namedtuple | ||||
|  | ||||
|  | ||||
| # imports: | ||||
|  | ||||
| VersionInfo = namedtuple("VersionInfo", "major minor micro") | ||||
| version_info = VersionInfo(major="1", minor="2", micro="0") | ||||
							
								
								
									
										56
									
								
								bot/src/bot_api/model/discord/server_dto.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								bot/src/bot_api/model/discord/server_dto.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| from typing import Optional | ||||
|  | ||||
| from bot_api.abc.dto_abc import DtoABC | ||||
|  | ||||
|  | ||||
| class ServerDTO(DtoABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         server_id: int, | ||||
|         discord_id: int, | ||||
|         name: str, | ||||
|         member_count: int, | ||||
|         icon_url: Optional[str], | ||||
|     ): | ||||
|         DtoABC.__init__(self) | ||||
|  | ||||
|         self._server_id = server_id | ||||
|         self._discord_id = discord_id | ||||
|         self._name = name | ||||
|         self._member_count = member_count | ||||
|         self._icon_url = icon_url | ||||
|  | ||||
|     @property | ||||
|     def server_id(self) -> int: | ||||
|         return self._server_id | ||||
|  | ||||
|     @property | ||||
|     def discord_id(self) -> int: | ||||
|         return self._discord_id | ||||
|  | ||||
|     @property | ||||
|     def name(self) -> str: | ||||
|         return self._name | ||||
|  | ||||
|     @property | ||||
|     def member_count(self) -> int: | ||||
|         return self._member_count | ||||
|  | ||||
|     @property | ||||
|     def icon_url(self) -> Optional[str]: | ||||
|         return self._icon_url | ||||
|  | ||||
|     def from_dict(self, values: dict): | ||||
|         self._server_id = int(values["serverId"]) | ||||
|         self._discord_id = int(values["discordId"]) | ||||
|         self._name = values["name"] | ||||
|         self._icon_url = values["iconURL"] | ||||
|  | ||||
|     def to_dict(self) -> dict: | ||||
|         return { | ||||
|             "serverId": self._server_id, | ||||
|             "discordId": self._discord_id, | ||||
|             "name": self._name, | ||||
|             "memberCount": self._member_count, | ||||
|             "iconURL": self._icon_url, | ||||
|         } | ||||
							
								
								
									
										17
									
								
								bot/src/bot_api/model/discord/server_filtered_result_dto.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								bot/src/bot_api/model/discord/server_filtered_result_dto.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| 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} | ||||
							
								
								
									
										18
									
								
								bot/src/bot_api/model/email_string_dto.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								bot/src/bot_api/model/email_string_dto.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| import traceback | ||||
|  | ||||
| from cpl_core.console import Console | ||||
|  | ||||
| from bot_api.abc.dto_abc import DtoABC | ||||
|  | ||||
|  | ||||
| class EMailStringDTO(DtoABC): | ||||
|     def __init__(self, email: str): | ||||
|         DtoABC.__init__(self) | ||||
|  | ||||
|         self._email = email | ||||
|  | ||||
|     def from_dict(self, values: dict): | ||||
|         self._email = values["email"] | ||||
|  | ||||
|     def to_dict(self) -> dict: | ||||
|         return {"email": self._email} | ||||
							
								
								
									
										32
									
								
								bot/src/bot_api/model/error_dto.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								bot/src/bot_api/model/error_dto.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| import traceback | ||||
| from typing import Optional | ||||
|  | ||||
| from cpl_core.console import Console | ||||
|  | ||||
| from bot_api.abc.dto_abc import DtoABC | ||||
| from bot_api.exception.service_error_code_enum import ServiceErrorCode | ||||
|  | ||||
|  | ||||
| class ErrorDTO(DtoABC): | ||||
|     def __init__(self, error_code: Optional[ServiceErrorCode], message: str): | ||||
|         DtoABC.__init__(self) | ||||
|  | ||||
|         self._error_code = ( | ||||
|             ServiceErrorCode.Unknown if error_code is None else error_code | ||||
|         ) | ||||
|         self._message = message | ||||
|  | ||||
|     @property | ||||
|     def error_code(self) -> ServiceErrorCode: | ||||
|         return self._error_code | ||||
|  | ||||
|     @property | ||||
|     def message(self) -> str: | ||||
|         return self._message | ||||
|  | ||||
|     def from_dict(self, values: dict): | ||||
|         self._error_code = values["ErrorCode"] | ||||
|         self._message = values["Message"] | ||||
|  | ||||
|     def to_dict(self) -> dict: | ||||
|         return {"errorCode": int(self._error_code.value), "message": self._message} | ||||
							
								
								
									
										40
									
								
								bot/src/bot_api/model/o_auth_dto.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								bot/src/bot_api/model/o_auth_dto.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| from typing import Optional | ||||
|  | ||||
| from bot_api.abc.dto_abc import DtoABC | ||||
| from bot_api.model.auth_user_dto import AuthUserDTO | ||||
| from bot_data.model.auth_role_enum import AuthRoleEnum | ||||
|  | ||||
|  | ||||
| class OAuthDTO(DtoABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         user: AuthUserDTO, | ||||
|         o_auth_id: Optional[str], | ||||
|     ): | ||||
|         DtoABC.__init__(self) | ||||
|  | ||||
|         self._user = user | ||||
|         self._oauth_id = o_auth_id | ||||
|  | ||||
|     @property | ||||
|     def user(self) -> AuthUserDTO: | ||||
|         return self._user | ||||
|  | ||||
|     @user.setter | ||||
|     def user(self, value: AuthUserDTO): | ||||
|         self._user = value | ||||
|  | ||||
|     @property | ||||
|     def oauth_id(self) -> Optional[str]: | ||||
|         return self._oauth_id | ||||
|  | ||||
|     @oauth_id.setter | ||||
|     def oauth_id(self, value: Optional[str]): | ||||
|         self._oauth_id = value | ||||
|  | ||||
|     def from_dict(self, values: dict): | ||||
|         self._user = AuthUserDTO().from_dict(values["user"]) | ||||
|         self._oauth_id = values["oAuthId"] | ||||
|  | ||||
|     def to_dict(self) -> dict: | ||||
|         return {"user": self._user.to_dict(), "oAuthId": self._oauth_id} | ||||
							
								
								
									
										28
									
								
								bot/src/bot_api/model/reset_password_dto.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								bot/src/bot_api/model/reset_password_dto.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| import traceback | ||||
|  | ||||
| from cpl_core.console import Console | ||||
|  | ||||
| from bot_api.abc.dto_abc import DtoABC | ||||
|  | ||||
|  | ||||
| class ResetPasswordDTO(DtoABC): | ||||
|     def __init__(self, id: str, password: str): | ||||
|         DtoABC.__init__(self) | ||||
|  | ||||
|         self._id = id | ||||
|         self._password = password | ||||
|  | ||||
|     @property | ||||
|     def id(self) -> str: | ||||
|         return self._id | ||||
|  | ||||
|     @property | ||||
|     def password(self) -> str: | ||||
|         return self._password | ||||
|  | ||||
|     def from_dict(self, values: dict): | ||||
|         self._id = values["id"] | ||||
|         self._password = values["password"] | ||||
|  | ||||
|     def to_dict(self) -> dict: | ||||
|         return {"id": self._id, "password": self._password} | ||||
							
								
								
									
										66
									
								
								bot/src/bot_api/model/settings_dto.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								bot/src/bot_api/model/settings_dto.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| from bot_api.abc.dto_abc import DtoABC | ||||
| from bot_api.model.version_dto import VersionDTO | ||||
|  | ||||
|  | ||||
| class SettingsDTO(DtoABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         web_version: str, | ||||
|         api_version: VersionDTO, | ||||
|         config_path: str, | ||||
|         web_base_url: str, | ||||
|         api_base_url: str, | ||||
|         token_expire_time: int, | ||||
|         refresh_token_expire_time: int, | ||||
|         mail_user: str, | ||||
|         mail_port: int, | ||||
|         mail_host: str, | ||||
|         mail_transceiver: str, | ||||
|         mail_transceiver_address: str, | ||||
|     ): | ||||
|         DtoABC.__init__(self) | ||||
|  | ||||
|         self._web_version = web_version | ||||
|         self._api_version = api_version | ||||
|         self._config_path = config_path | ||||
|         self._web_base_url = web_base_url | ||||
|         self._api_base_url = api_base_url | ||||
|  | ||||
|         self._token_expire_time = token_expire_time | ||||
|         self._refresh_token_expire_time = refresh_token_expire_time | ||||
|  | ||||
|         self._mail_user = mail_user | ||||
|         self._mail_port = mail_port | ||||
|         self._mail_host = mail_host | ||||
|         self._mail_transceiver = mail_transceiver | ||||
|         self._mail_transceiver_address = mail_transceiver_address | ||||
|  | ||||
|     def from_dict(self, values: dict): | ||||
|         self._web_version = values["webVersion"] | ||||
|         self._api_version.from_dict(values["apiVersion"]) | ||||
|         self._config_path = values["configPath"] | ||||
|         self._web_base_url = values["webBaseURL"] | ||||
|         self._api_base_url = values["apiBaseURL"] | ||||
|         self._token_expire_time = values["tokenExpireTime"] | ||||
|         self._refresh_token_expire_time = values["refreshTokenExpireTime"] | ||||
|         self._mail_user = values["mailUser"] | ||||
|         self._mail_port = values["mailPort"] | ||||
|         self._mail_host = values["mailHost"] | ||||
|         self._mail_transceiver = values["mailTransceiver"] | ||||
|         self._mail_transceiver_address = values["mailTransceiverAddress"] | ||||
|  | ||||
|     def to_dict(self) -> dict: | ||||
|         return { | ||||
|             "webVersion": self._web_version, | ||||
|             "apiVersion": self._api_version.str, | ||||
|             "configPath": self._config_path, | ||||
|             "webBaseURL": self._web_base_url, | ||||
|             "apiBaseURL": self._api_base_url, | ||||
|             "tokenExpireTime": self._token_expire_time, | ||||
|             "refreshTokenExpireTime": self._refresh_token_expire_time, | ||||
|             "mailUser": self._mail_user, | ||||
|             "mailPort": self._mail_port, | ||||
|             "mailHost": self._mail_host, | ||||
|             "mailTransceiver": self._mail_transceiver, | ||||
|             "mailTransceiverAddress": self._mail_transceiver_address, | ||||
|         } | ||||
							
								
								
									
										34
									
								
								bot/src/bot_api/model/token_dto.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								bot/src/bot_api/model/token_dto.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| from bot_api.abc.dto_abc import DtoABC | ||||
|  | ||||
|  | ||||
| class TokenDTO(DtoABC): | ||||
|     def __init__(self, token: str, refresh_token: str, first_login: bool = False): | ||||
|         DtoABC.__init__(self) | ||||
|  | ||||
|         self._token = token | ||||
|         self._refresh_token = refresh_token | ||||
|         self._first_login = first_login | ||||
|  | ||||
|     @property | ||||
|     def token(self) -> str: | ||||
|         return self._token | ||||
|  | ||||
|     @property | ||||
|     def refresh_token(self) -> str: | ||||
|         return self._refresh_token | ||||
|  | ||||
|     @property | ||||
|     def first_login(self) -> bool: | ||||
|         return self._first_login | ||||
|  | ||||
|     def from_dict(self, values: dict): | ||||
|         self._token = values["token"] | ||||
|         self._refresh_token = values["refreshToken"] | ||||
|         self._first_login = values["firstLogin"] | ||||
|  | ||||
|     def to_dict(self) -> dict: | ||||
|         return { | ||||
|             "token": self._token, | ||||
|             "refreshToken": self._refresh_token, | ||||
|             "firstLogin": self._first_login, | ||||
|         } | ||||
							
								
								
									
										46
									
								
								bot/src/bot_api/model/update_auth_user_dto.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								bot/src/bot_api/model/update_auth_user_dto.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| import traceback | ||||
|  | ||||
| from cpl_core.console import Console | ||||
|  | ||||
| from bot_api.abc.dto_abc import DtoABC | ||||
| from bot_api.model.auth_user_dto import AuthUserDTO | ||||
|  | ||||
|  | ||||
| class UpdateAuthUserDTO(DtoABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         auth_user_dto: AuthUserDTO, | ||||
|         new_auth_user_dto: AuthUserDTO, | ||||
|         change_password: bool = False, | ||||
|     ): | ||||
|         DtoABC.__init__(self) | ||||
|  | ||||
|         self._auth_user = auth_user_dto | ||||
|         self._new_auth_user = new_auth_user_dto | ||||
|         self._change_password = change_password | ||||
|  | ||||
|     @property | ||||
|     def auth_user(self) -> AuthUserDTO: | ||||
|         return self._auth_user | ||||
|  | ||||
|     @property | ||||
|     def new_auth_user(self) -> AuthUserDTO: | ||||
|         return self._new_auth_user | ||||
|  | ||||
|     @property | ||||
|     def change_password(self) -> bool: | ||||
|         return self._change_password | ||||
|  | ||||
|     def from_dict(self, values: dict): | ||||
|         self._auth_user = AuthUserDTO().from_dict(values["authUser"]) | ||||
|         self._new_auth_user = AuthUserDTO().from_dict(values["newAuthUser"]) | ||||
|         self._change_password = ( | ||||
|             False if "changePassword" not in values else bool(values["changePassword"]) | ||||
|         ) | ||||
|  | ||||
|     def to_dict(self) -> dict: | ||||
|         return { | ||||
|             "authUser": self._auth_user, | ||||
|             "newAuthUser": self._new_auth_user, | ||||
|             "changePassword": self._change_password, | ||||
|         } | ||||
							
								
								
									
										76
									
								
								bot/src/bot_api/model/user_dto.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								bot/src/bot_api/model/user_dto.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| from typing import Optional | ||||
|  | ||||
| from bot_api.abc.dto_abc import DtoABC | ||||
| from bot_data.model.server import Server | ||||
|  | ||||
|  | ||||
| class UserDTO(DtoABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         id: int = None, | ||||
|         dc_id: int = None, | ||||
|         xp: int = None, | ||||
|         server: Optional[Server] = None, | ||||
|         is_technician: Optional[bool] = None, | ||||
|         is_admin: Optional[bool] = None, | ||||
|         is_moderator: Optional[bool] = None, | ||||
|     ): | ||||
|         DtoABC.__init__(self) | ||||
|  | ||||
|         self._user_id = id | ||||
|         self._discord_id = dc_id | ||||
|         self._xp = xp | ||||
|         self._server = server | ||||
|  | ||||
|         self._is_technician = is_technician | ||||
|         self._is_admin = is_admin | ||||
|         self._is_moderator = is_moderator | ||||
|  | ||||
|     @property | ||||
|     def user_id(self) -> int: | ||||
|         return self._user_id | ||||
|  | ||||
|     @property | ||||
|     def discord_id(self) -> int: | ||||
|         return self._discord_id | ||||
|  | ||||
|     @property | ||||
|     def xp(self) -> int: | ||||
|         return self._xp | ||||
|  | ||||
|     @xp.setter | ||||
|     def xp(self, value: int): | ||||
|         self._xp = value | ||||
|  | ||||
|     @property | ||||
|     def server(self) -> Optional[Server]: | ||||
|         return self._server | ||||
|  | ||||
|     @property | ||||
|     def is_technician(self) -> bool: | ||||
|         return self._is_technician if self._is_technician is not None else False | ||||
|  | ||||
|     @property | ||||
|     def is_admin(self) -> bool: | ||||
|         return self._is_admin if self._is_admin is not None else False | ||||
|  | ||||
|     @property | ||||
|     def is_moderator(self) -> bool: | ||||
|         return self._is_moderator if self._is_moderator is not None else False | ||||
|  | ||||
|     def from_dict(self, values: dict): | ||||
|         self._user_id = values["id"] | ||||
|         self._discord_id = values["dcId"] | ||||
|         self._xp = values["xp"] | ||||
|         self._server = values["server"] | ||||
|  | ||||
|     def to_dict(self) -> dict: | ||||
|         return { | ||||
|             "id": self._user_id, | ||||
|             "dcId": self._discord_id, | ||||
|             "xp": self._xp, | ||||
|             "server": self._server.id, | ||||
|             "isTechnician": self.is_technician, | ||||
|             "isAdmin": self.is_admin, | ||||
|             "isModerator": self.is_moderator, | ||||
|         } | ||||
							
								
								
									
										42
									
								
								bot/src/bot_api/model/version_dto.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								bot/src/bot_api/model/version_dto.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| import traceback | ||||
|  | ||||
| from cpl_core.console import Console | ||||
|  | ||||
| from bot_api.abc.dto_abc import DtoABC | ||||
|  | ||||
|  | ||||
| class VersionDTO(DtoABC): | ||||
|     def __init__(self, major: str = None, minor: str = None, micro: str = None): | ||||
|         DtoABC.__init__(self) | ||||
|  | ||||
|         self._major = major | ||||
|         self._minor = minor | ||||
|         self._micro = micro | ||||
|  | ||||
|     @property | ||||
|     def major(self) -> str: | ||||
|         return self._major | ||||
|  | ||||
|     @property | ||||
|     def minor(self) -> str: | ||||
|         return self._minor | ||||
|  | ||||
|     @property | ||||
|     def micro(self) -> str: | ||||
|         return self._micro | ||||
|  | ||||
|     @property | ||||
|     def str(self) -> str: | ||||
|         return f"{self._major}.{self._minor}.{self._micro}" | ||||
|  | ||||
|     def from_dict(self, values: dict): | ||||
|         self._major = values["major"] | ||||
|         self._minor = values["minor"] | ||||
|         self._micro = values["micro"] | ||||
|  | ||||
|     def to_dict(self) -> dict: | ||||
|         return { | ||||
|             "major": self._major, | ||||
|             "minor": self._minor, | ||||
|             "micro": self._micro, | ||||
|         } | ||||
							
								
								
									
										26
									
								
								bot/src/bot_api/route/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								bot/src/bot_api/route/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| bot sh-edraft.de Discord bot | ||||
| ~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| Discord bot for customers of sh-edraft.de | ||||
|  | ||||
| :copyright: (c) 2022 - 2023 sh-edraft.de | ||||
| :license: MIT, see LICENSE for more details. | ||||
|  | ||||
| """ | ||||
|  | ||||
| __title__ = "bot_api.route" | ||||
| __author__ = "Sven Heidemann" | ||||
| __license__ = "MIT" | ||||
| __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" | ||||
| __version__ = "1.2.0" | ||||
|  | ||||
| from collections import namedtuple | ||||
|  | ||||
|  | ||||
| # imports: | ||||
|  | ||||
| VersionInfo = namedtuple("VersionInfo", "major minor micro") | ||||
| version_info = VersionInfo(major="1", minor="2", micro="0") | ||||
							
								
								
									
										181
									
								
								bot/src/bot_api/route/route.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								bot/src/bot_api/route/route.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,181 @@ | ||||
| import functools | ||||
| from functools import wraps | ||||
| from typing import Optional, Callable, Union | ||||
|  | ||||
| from cpl_core.dependency_injection import ServiceProviderABC | ||||
| from cpl_core.environment import ApplicationEnvironmentABC | ||||
| from flask import request, jsonify | ||||
| from flask_cors import cross_origin | ||||
|  | ||||
| from bot_api.abc.auth_service_abc import AuthServiceABC | ||||
| from bot_api.exception.service_error_code_enum import ServiceErrorCode | ||||
| from bot_api.exception.service_exception import ServiceException | ||||
| from bot_api.model.error_dto import ErrorDTO | ||||
| from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC | ||||
| from bot_data.model.auth_role_enum import AuthRoleEnum | ||||
| from bot_data.model.auth_user import AuthUser | ||||
|  | ||||
|  | ||||
| class Route: | ||||
|     registered_routes = {} | ||||
|  | ||||
|     _auth_users: Optional[AuthUserRepositoryABC] = None | ||||
|     _auth: Optional[AuthServiceABC] = None | ||||
|     _env = "production" | ||||
|  | ||||
|     @classmethod | ||||
|     @ServiceProviderABC.inject | ||||
|     def init_authorize( | ||||
|         cls, | ||||
|         env: ApplicationEnvironmentABC, | ||||
|         auth_users: AuthUserRepositoryABC, | ||||
|         auth: AuthServiceABC, | ||||
|     ): | ||||
|         cls._auth_users = auth_users | ||||
|         cls._auth = auth | ||||
|         cls._env = env.environment_name | ||||
|  | ||||
|     @classmethod | ||||
|     def get_user(cls) -> Optional[Union[str, AuthUser]]: | ||||
|         token = None | ||||
|         api_key = None | ||||
|         authorization = request.headers.get("Authorization").split() | ||||
|         match authorization[0]: | ||||
|             case "Bearer": | ||||
|                 token = authorization[1] | ||||
|             case "API-Key": | ||||
|                 api_key = authorization[1] | ||||
|  | ||||
|         if api_key is not None: | ||||
|             return "system" | ||||
|  | ||||
|         if token is None: | ||||
|             return None | ||||
|  | ||||
|         jwt = cls._auth.decode_token(token) | ||||
|         user = cls._auth_users.get_auth_user_by_email(jwt["email"]) | ||||
|         return user | ||||
|  | ||||
|     @classmethod | ||||
|     def authorize( | ||||
|         cls, | ||||
|         f: Callable = None, | ||||
|         role: AuthRoleEnum = None, | ||||
|         skip_in_dev=False, | ||||
|         by_api_key=False, | ||||
|     ): | ||||
|         if f is None: | ||||
|             return functools.partial( | ||||
|                 cls.authorize, role=role, skip_in_dev=skip_in_dev, by_api_key=by_api_key | ||||
|             ) | ||||
|  | ||||
|         @wraps(f) | ||||
|         async def decorator(*args, **kwargs): | ||||
|             if skip_in_dev and cls._env == "development": | ||||
|                 return await f(*args, **kwargs) | ||||
|  | ||||
|             token = None | ||||
|             api_key = None | ||||
|             if "Authorization" in request.headers: | ||||
|                 if " " not in request.headers.get("Authorization"): | ||||
|                     ex = ServiceException( | ||||
|                         ServiceErrorCode.Unauthorized, f"Token not set" | ||||
|                     ) | ||||
|                     error = ErrorDTO(ex.error_code, ex.message) | ||||
|                     return jsonify(error.to_dict()), 401 | ||||
|  | ||||
|                 authorization = request.headers.get("Authorization").split() | ||||
|                 match authorization[0]: | ||||
|                     case "Bearer": | ||||
|                         token = authorization[1] | ||||
|                     case "API-Key": | ||||
|                         api_key = authorization[1] | ||||
|  | ||||
|             if api_key is not None: | ||||
|                 valid = False | ||||
|                 try: | ||||
|                     valid = cls._auth.verify_api_key(api_key) | ||||
|                 except ServiceException as e: | ||||
|                     error = ErrorDTO(e.error_code, e.message) | ||||
|                     return jsonify(error.to_dict()), 403 | ||||
|                 except Exception as e: | ||||
|                     return jsonify(e), 500 | ||||
|  | ||||
|                 if not valid: | ||||
|                     ex = ServiceException( | ||||
|                         ServiceErrorCode.Unauthorized, f"API-Key invalid" | ||||
|                     ) | ||||
|                     error = ErrorDTO(ex.error_code, ex.message) | ||||
|                     return jsonify(error.to_dict()), 401 | ||||
|  | ||||
|                 return await f(*args, **kwargs) | ||||
|  | ||||
|             if token is None: | ||||
|                 ex = ServiceException(ServiceErrorCode.Unauthorized, f"Token not set") | ||||
|                 error = ErrorDTO(ex.error_code, ex.message) | ||||
|                 return jsonify(error.to_dict()), 401 | ||||
|  | ||||
|             if cls._auth_users is None or cls._auth is None: | ||||
|                 ex = ServiceException( | ||||
|                     ServiceErrorCode.Unauthorized, f"Authorize is not initialized" | ||||
|                 ) | ||||
|                 error = ErrorDTO(ex.error_code, ex.message) | ||||
|                 return jsonify(error.to_dict()), 401 | ||||
|  | ||||
|             if not cls._auth.verify_login(token): | ||||
|                 ex = ServiceException(ServiceErrorCode.Unauthorized, f"Token expired") | ||||
|                 error = ErrorDTO(ex.error_code, ex.message) | ||||
|                 return jsonify(error.to_dict()), 401 | ||||
|  | ||||
|             token = cls._auth.decode_token(token) | ||||
|             if token is None or "email" not in token: | ||||
|                 ex = ServiceException(ServiceErrorCode.Unauthorized, f"Token invalid") | ||||
|                 error = ErrorDTO(ex.error_code, ex.message) | ||||
|                 return jsonify(error.to_dict()), 401 | ||||
|  | ||||
|             user = cls._auth_users.get_auth_user_by_email(token["email"]) | ||||
|             if user is None: | ||||
|                 ex = ServiceException(ServiceErrorCode.Unauthorized, f"Token invalid") | ||||
|                 error = ErrorDTO(ex.error_code, ex.message) | ||||
|                 return jsonify(error.to_dict()), 401 | ||||
|  | ||||
|             if role is not None and user.auth_role.value < role.value: | ||||
|                 ex = ServiceException( | ||||
|                     ServiceErrorCode.Unauthorized, f"Role {role} required" | ||||
|                 ) | ||||
|                 error = ErrorDTO(ex.error_code, ex.message) | ||||
|                 return jsonify(error.to_dict()), 403 | ||||
|  | ||||
|             return await f(*args, **kwargs) | ||||
|  | ||||
|         return decorator | ||||
|  | ||||
|     @classmethod | ||||
|     def route(cls, path=None, **kwargs): | ||||
|         # simple decorator for class based views | ||||
|         def inner(fn): | ||||
|             cross_origin(fn) | ||||
|             cls.registered_routes[path] = (fn, kwargs) | ||||
|             return fn | ||||
|  | ||||
|         return inner | ||||
|  | ||||
|     @classmethod | ||||
|     def get(cls, path=None, **kwargs): | ||||
|         return cls.route(path, methods=["GET"], **kwargs) | ||||
|  | ||||
|     @classmethod | ||||
|     def post(cls, path=None, **kwargs): | ||||
|         return cls.route(path, methods=["POST"], **kwargs) | ||||
|  | ||||
|     @classmethod | ||||
|     def head(cls, path=None, **kwargs): | ||||
|         return cls.route(path, methods=["HEAD"], **kwargs) | ||||
|  | ||||
|     @classmethod | ||||
|     def put(cls, path=None, **kwargs): | ||||
|         return cls.route(path, methods=["PUT"], **kwargs) | ||||
|  | ||||
|     @classmethod | ||||
|     def delete(cls, path=None, **kwargs): | ||||
|         return cls.route(path, methods=["DELETE"], **kwargs) | ||||
							
								
								
									
										26
									
								
								bot/src/bot_api/service/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								bot/src/bot_api/service/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| bot sh-edraft.de Discord bot | ||||
| ~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| Discord bot for customers of sh-edraft.de | ||||
|  | ||||
| :copyright: (c) 2022 - 2023 sh-edraft.de | ||||
| :license: MIT, see LICENSE for more details. | ||||
|  | ||||
| """ | ||||
|  | ||||
| __title__ = "bot_api.service" | ||||
| __author__ = "Sven Heidemann" | ||||
| __license__ = "MIT" | ||||
| __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" | ||||
| __version__ = "1.2.0" | ||||
|  | ||||
| from collections import namedtuple | ||||
|  | ||||
|  | ||||
| # imports | ||||
|  | ||||
| VersionInfo = namedtuple("VersionInfo", "major minor micro") | ||||
| version_info = VersionInfo(major="1", minor="2", micro="0") | ||||
							
								
								
									
										678
									
								
								bot/src/bot_api/service/auth_service.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										678
									
								
								bot/src/bot_api/service/auth_service.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,678 @@ | ||||
| import hashlib | ||||
| import re | ||||
| import textwrap | ||||
| import uuid | ||||
| from datetime import datetime, timedelta, timezone | ||||
| from threading import Thread | ||||
| from typing import Optional | ||||
|  | ||||
| import jwt | ||||
| from cpl_core.database.context import DatabaseContextABC | ||||
| from cpl_core.environment import ApplicationEnvironmentABC | ||||
| from cpl_core.mailing import EMail, EMailClientABC | ||||
| from cpl_core.utils import CredentialManager | ||||
| from cpl_discord.service import DiscordBotServiceABC | ||||
| from cpl_query.extension import List | ||||
| from cpl_translation import TranslatePipe | ||||
| from flask import request | ||||
|  | ||||
| from bot_api.abc.auth_service_abc import AuthServiceABC | ||||
| from bot_api.configuration.authentication_settings import AuthenticationSettings | ||||
| from bot_api.configuration.frontend_settings import FrontendSettings | ||||
| from bot_api.exception.service_error_code_enum import ServiceErrorCode | ||||
| from bot_api.exception.service_exception import ServiceException | ||||
| from bot_api.filter.auth_user_select_criteria import AuthUserSelectCriteria | ||||
| from bot_api.logging.api_logger import ApiLogger | ||||
| from bot_api.model.auth_user_dto import AuthUserDTO | ||||
| from bot_api.model.auth_user_filtered_result_dto import AuthUserFilteredResultDTO | ||||
| from bot_api.model.email_string_dto import EMailStringDTO | ||||
| from bot_api.model.o_auth_dto import OAuthDTO | ||||
| from bot_api.model.reset_password_dto import ResetPasswordDTO | ||||
| from bot_api.model.token_dto import TokenDTO | ||||
| from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO | ||||
| from bot_api.transformer.auth_user_transformer import AuthUserTransformer as AUT | ||||
| from bot_data.abc.api_key_repository_abc import ApiKeyRepositoryABC | ||||
| from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC | ||||
| from bot_data.abc.server_repository_abc import ServerRepositoryABC | ||||
| from bot_data.abc.user_repository_abc import UserRepositoryABC | ||||
| from bot_data.model.api_key import ApiKey | ||||
| from bot_data.model.auth_role_enum import AuthRoleEnum | ||||
| from bot_data.model.auth_user import AuthUser | ||||
| from bot_data.model.auth_user_users_relation import AuthUserUsersRelation | ||||
|  | ||||
| _email_regex = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b" | ||||
|  | ||||
|  | ||||
| class AuthService(AuthServiceABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         env: ApplicationEnvironmentABC, | ||||
|         logger: ApiLogger, | ||||
|         bot: DiscordBotServiceABC, | ||||
|         db: DatabaseContextABC, | ||||
|         auth_users: AuthUserRepositoryABC, | ||||
|         api_keys: ApiKeyRepositoryABC, | ||||
|         users: UserRepositoryABC, | ||||
|         servers: ServerRepositoryABC, | ||||
|         mailer: EMailClientABC, | ||||
|         t: TranslatePipe, | ||||
|         auth_settings: AuthenticationSettings, | ||||
|         frontend_settings: FrontendSettings, | ||||
|     ): | ||||
|         AuthServiceABC.__init__(self) | ||||
|  | ||||
|         self._environment = env | ||||
|         self._logger = logger | ||||
|         self._bot = bot | ||||
|         self._db = db | ||||
|         self._auth_users = auth_users | ||||
|         self._api_keys = api_keys | ||||
|         self._users = users | ||||
|         self._servers = servers | ||||
|         self._mailer = mailer | ||||
|         self._t = t | ||||
|         self._auth_settings = auth_settings | ||||
|         self._frontend_settings = frontend_settings | ||||
|  | ||||
|     @staticmethod | ||||
|     def _hash_sha256(password: str, salt: str) -> str: | ||||
|         return hashlib.sha256(f"{password}{salt}".encode("utf-8")).hexdigest() | ||||
|  | ||||
|     @staticmethod | ||||
|     def _is_email_valid(email: str) -> bool: | ||||
|         if email is None: | ||||
|             raise False | ||||
|  | ||||
|         if re.fullmatch(_email_regex, email) is not None: | ||||
|             return True | ||||
|  | ||||
|         return False | ||||
|  | ||||
|     def _get_api_key_str(self, api_key: ApiKey) -> str: | ||||
|         return hashlib.sha256( | ||||
|             f"{api_key.identifier}:{api_key.key}+{self._auth_settings.secret_key}".encode( | ||||
|                 "utf-8" | ||||
|             ) | ||||
|         ).hexdigest() | ||||
|  | ||||
|     def generate_token(self, user: AuthUser) -> str: | ||||
|         token = jwt.encode( | ||||
|             payload={ | ||||
|                 "user_id": user.id, | ||||
|                 "email": user.email, | ||||
|                 "role": user.auth_role.value, | ||||
|                 "exp": datetime.now(tz=timezone.utc) | ||||
|                 + timedelta(days=self._auth_settings.token_expire_time), | ||||
|                 "iss": self._auth_settings.issuer, | ||||
|                 "aud": self._auth_settings.audience, | ||||
|             }, | ||||
|             key=CredentialManager.decrypt(self._auth_settings.secret_key), | ||||
|         ) | ||||
|  | ||||
|         return token | ||||
|  | ||||
|     def decode_token(self, token: str) -> dict: | ||||
|         return jwt.decode( | ||||
|             token, | ||||
|             key=CredentialManager.decrypt(self._auth_settings.secret_key), | ||||
|             issuer=self._auth_settings.issuer, | ||||
|             audience=self._auth_settings.audience, | ||||
|             algorithms=["HS256"], | ||||
|         ) | ||||
|  | ||||
|     def get_decoded_token_from_request(self) -> dict: | ||||
|         token = None | ||||
|         if "Authorization" in request.headers: | ||||
|             bearer = request.headers.get("Authorization") | ||||
|             token = bearer.split()[1] | ||||
|  | ||||
|         if token is None: | ||||
|             raise ServiceException(ServiceErrorCode.Unauthorized, f"Token not set") | ||||
|  | ||||
|         return jwt.decode( | ||||
|             token, | ||||
|             key=CredentialManager.decrypt(self._auth_settings.secret_key), | ||||
|             issuer=self._auth_settings.issuer, | ||||
|             audience=self._auth_settings.audience, | ||||
|             algorithms=["HS256"], | ||||
|         ) | ||||
|  | ||||
|     def find_decoded_token_from_request(self) -> Optional[dict]: | ||||
|         token = None | ||||
|         if "Authorization" in request.headers: | ||||
|             bearer = request.headers.get("Authorization") | ||||
|             token = bearer.split()[1] | ||||
|  | ||||
|         return ( | ||||
|             jwt.decode( | ||||
|                 token, | ||||
|                 key=CredentialManager.decrypt(self._auth_settings.secret_key), | ||||
|                 issuer=self._auth_settings.issuer, | ||||
|                 audience=self._auth_settings.audience, | ||||
|                 algorithms=["HS256"], | ||||
|             ) | ||||
|             if token is not None | ||||
|             else None | ||||
|         ) | ||||
|  | ||||
|     def _create_and_save_refresh_token(self, user: AuthUser) -> str: | ||||
|         token = str(uuid.uuid4()) | ||||
|         user.refresh_token = token | ||||
|         user.refresh_token_expire_time = datetime.now() + timedelta( | ||||
|             days=self._auth_settings.refresh_token_expire_time | ||||
|         ) | ||||
|         self._auth_users.update_auth_user(user) | ||||
|         self._db.save_changes() | ||||
|         return token | ||||
|  | ||||
|     def _send_link_mail(self, email: str, subject: str, message: str): | ||||
|         url = self._frontend_settings.url | ||||
|         if not url.endswith("/"): | ||||
|             url = f"{url}/" | ||||
|  | ||||
|         self._mailer.connect() | ||||
|         mail = EMail() | ||||
|         mail.add_header("Mime-Version: 1.0") | ||||
|         mail.add_header("Content-Type: text/plain; charset=utf-8") | ||||
|         mail.add_header("Content-Transfer-Encoding: quoted-printable") | ||||
|         mail.add_receiver(str(email)) | ||||
|         mail.subject = subject | ||||
|         mail.body = textwrap.dedent( | ||||
|             f"""{message} | ||||
|         {self._t.transform('api.mail.automatic_mail').format(self._environment.application_name, self._environment.environment_name, self._environment.host_name)} | ||||
|         """ | ||||
|         ) | ||||
|  | ||||
|         thr = Thread(target=self._mailer.send_mail, args=[mail]) | ||||
|         thr.start() | ||||
|  | ||||
|     def _send_confirmation_id_to_user(self, user: AuthUser): | ||||
|         url = self._frontend_settings.url | ||||
|         if not url.endswith("/"): | ||||
|             url = f"{url}/" | ||||
|  | ||||
|         self._send_link_mail( | ||||
|             user.email, | ||||
|             self._t.transform("api.auth.confirmation.subject").format( | ||||
|                 user.first_name, user.last_name | ||||
|             ), | ||||
|             self._t.transform("api.auth.confirmation.message").format( | ||||
|                 url, user.confirmation_id | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|     def _send_forgot_password_id_to_user(self, user: AuthUser): | ||||
|         url = self._frontend_settings.url | ||||
|         if not url.endswith("/"): | ||||
|             url = f"{url}/" | ||||
|  | ||||
|         self._send_link_mail( | ||||
|             user.email, | ||||
|             self._t.transform("api.auth.forgot_password.subject").format( | ||||
|                 user.first_name, user.last_name | ||||
|             ), | ||||
|             self._t.transform("api.auth.forgot_password.message").format( | ||||
|                 url, user.forgot_password_id | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|     async def get_all_auth_users_async(self) -> List[AuthUserDTO]: | ||||
|         result = self._auth_users.get_all_auth_users().select(lambda x: AUT.to_dto(x)) | ||||
|         return List(AuthUserDTO, result) | ||||
|  | ||||
|     async def get_filtered_auth_users_async( | ||||
|         self, criteria: AuthUserSelectCriteria | ||||
|     ) -> AuthUserFilteredResultDTO: | ||||
|         users = self._auth_users.get_filtered_auth_users(criteria) | ||||
|         result = users.result.select(lambda x: AUT.to_dto(x)) | ||||
|  | ||||
|         return AuthUserFilteredResultDTO(List(AuthUserDTO, result), users.total_count) | ||||
|  | ||||
|     async def get_auth_user_by_email_async( | ||||
|         self, email: str, with_password: bool = False | ||||
|     ) -> AuthUserDTO: | ||||
|         try: | ||||
|             # todo: check if logged in user is admin then send mail | ||||
|             user = self._auth_users.get_auth_user_by_email(email) | ||||
|             return AUT.to_dto(user, password=user.password if with_password else None) | ||||
|         except Exception as e: | ||||
|             self._logger.error(__name__, f"AuthUser not found", e) | ||||
|             raise ServiceException( | ||||
|                 ServiceErrorCode.InvalidData, f"User not found {email}" | ||||
|             ) | ||||
|  | ||||
|     async def find_auth_user_by_email_async(self, email: str) -> Optional[AuthUser]: | ||||
|         user = self._auth_users.find_auth_user_by_email(email) | ||||
|         return AUT.to_dto(user) if user is not None else None | ||||
|  | ||||
|     def add_auth_user(self, user_dto: AuthUserDTO): | ||||
|         db_user = self._auth_users.find_auth_user_by_email(user_dto.email) | ||||
|         if db_user is not None: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidUser, "User already exists") | ||||
|  | ||||
|         user = AUT.to_db(user_dto) | ||||
|         if self._auth_users.get_all_auth_users().count() == 0: | ||||
|             user.auth_role = AuthRoleEnum.admin | ||||
|  | ||||
|         user.password_salt = uuid.uuid4() | ||||
|         user.password = self._hash_sha256(user_dto.password, user.password_salt) | ||||
|         if not self._is_email_valid(user.email): | ||||
|             raise ServiceException( | ||||
|                 ServiceErrorCode.InvalidData, "Invalid E-Mail address" | ||||
|             ) | ||||
|  | ||||
|         try: | ||||
|             user.confirmation_id = uuid.uuid4() | ||||
|             self._auth_users.add_auth_user(user) | ||||
|             self._send_confirmation_id_to_user(user) | ||||
|             self._db.save_changes() | ||||
|             self._logger.info( | ||||
|                 __name__, f"Added auth user with E-Mail: {user_dto.email}" | ||||
|             ) | ||||
|         except Exception as e: | ||||
|             self._logger.error( | ||||
|                 __name__, f"Cannot add user with E-Mail {user_dto.email}", e | ||||
|             ) | ||||
|             raise ServiceException(ServiceErrorCode.UnableToAdd, "Invalid E-Mail") | ||||
|  | ||||
|     async def add_auth_user_by_oauth_async(self, dto: OAuthDTO): | ||||
|         db_user = self._auth_users.find_auth_user_by_email(dto.user.email) | ||||
|  | ||||
|         if db_user is None: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidUser, "User not found") | ||||
|  | ||||
|         if db_user.oauth_id != dto.oauth_id: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidUser, "Wrong OAuthId") | ||||
|  | ||||
|         try: | ||||
|             db_user.first_name = dto.user.first_name | ||||
|             db_user.last_name = dto.user.last_name | ||||
|             db_user.password_salt = uuid.uuid4() | ||||
|             db_user.password = self._hash_sha256( | ||||
|                 dto.user.password, db_user.password_salt | ||||
|             ) | ||||
|             db_user.oauth_id = None | ||||
|             db_user.confirmation_id = uuid.uuid4() | ||||
|             self._send_confirmation_id_to_user(db_user) | ||||
|             self._auth_users.update_auth_user(db_user) | ||||
|             self._logger.info( | ||||
|                 __name__, f"Added auth user with E-Mail: {dto.user.email}" | ||||
|             ) | ||||
|         except Exception as e: | ||||
|             self._logger.error( | ||||
|                 __name__, f"Cannot add user with E-Mail {dto.user.email}", e | ||||
|             ) | ||||
|             raise ServiceException(ServiceErrorCode.UnableToAdd, "Invalid E-Mail") | ||||
|  | ||||
|         self._db.save_changes() | ||||
|  | ||||
|     async def update_user_async(self, update_user_dto: UpdateAuthUserDTO): | ||||
|         if update_user_dto is None: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidData, f"User is empty") | ||||
|  | ||||
|         if update_user_dto.auth_user is None: | ||||
|             raise ServiceException( | ||||
|                 ServiceErrorCode.InvalidData, f"Existing user is empty" | ||||
|             ) | ||||
|  | ||||
|         if update_user_dto.new_auth_user is None: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidData, f"New user is empty") | ||||
|  | ||||
|         if not self._is_email_valid( | ||||
|             update_user_dto.auth_user.email | ||||
|         ) or not self._is_email_valid(update_user_dto.new_auth_user.email): | ||||
|             raise ServiceException(ServiceErrorCode.InvalidData, f"Invalid E-Mail") | ||||
|  | ||||
|         user = self._auth_users.find_auth_user_by_email(update_user_dto.auth_user.email) | ||||
|         if user is None: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidUser, "User not found") | ||||
|  | ||||
|         if user.confirmation_id is not None: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidUser, "E-Mail not confirmed") | ||||
|  | ||||
|         # update first name | ||||
|         if ( | ||||
|             update_user_dto.new_auth_user.first_name is not None | ||||
|             and update_user_dto.auth_user.first_name | ||||
|             != update_user_dto.new_auth_user.first_name | ||||
|         ): | ||||
|             user.first_name = update_user_dto.new_auth_user.first_name | ||||
|  | ||||
|         # update last name | ||||
|         if ( | ||||
|             update_user_dto.new_auth_user.last_name is not None | ||||
|             and update_user_dto.new_auth_user.last_name != "" | ||||
|             and update_user_dto.auth_user.last_name | ||||
|             != update_user_dto.new_auth_user.last_name | ||||
|         ): | ||||
|             user.last_name = update_user_dto.new_auth_user.last_name | ||||
|  | ||||
|         # update E-Mail | ||||
|         if ( | ||||
|             update_user_dto.new_auth_user.email is not None | ||||
|             and update_user_dto.new_auth_user.email != "" | ||||
|             and update_user_dto.auth_user.email != update_user_dto.new_auth_user.email | ||||
|         ): | ||||
|             user_by_new_e_mail = self._auth_users.find_auth_user_by_email( | ||||
|                 update_user_dto.new_auth_user.email | ||||
|             ) | ||||
|             if user_by_new_e_mail is not None: | ||||
|                 raise ServiceException( | ||||
|                     ServiceErrorCode.InvalidUser, "User already exists" | ||||
|                 ) | ||||
|             user.email = update_user_dto.new_auth_user.email | ||||
|  | ||||
|         update_user_dto.auth_user.password = self._hash_sha256( | ||||
|             update_user_dto.auth_user.password, user.password_salt | ||||
|         ) | ||||
|         if update_user_dto.auth_user.password != user.password: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidUser, "Wrong password") | ||||
|  | ||||
|         # update password | ||||
|         if ( | ||||
|             update_user_dto.new_auth_user.password is not None | ||||
|             and self._hash_sha256( | ||||
|                 update_user_dto.new_auth_user.password, user.password_salt | ||||
|             ) | ||||
|             != user.password | ||||
|         ): | ||||
|             user.password_salt = uuid.uuid4() | ||||
|             user.password = self._hash_sha256( | ||||
|                 update_user_dto.new_auth_user.password, user.password_salt | ||||
|             ) | ||||
|  | ||||
|         self._auth_users.update_auth_user(user) | ||||
|         self._db.save_changes() | ||||
|  | ||||
|     async def update_user_as_admin_async(self, update_user_dto: UpdateAuthUserDTO): | ||||
|         if update_user_dto is None: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidData, f"User is empty") | ||||
|  | ||||
|         if update_user_dto.auth_user is None: | ||||
|             raise ServiceException( | ||||
|                 ServiceErrorCode.InvalidData, f"Existing user is empty" | ||||
|             ) | ||||
|  | ||||
|         if update_user_dto.new_auth_user is None: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidData, f"New user is empty") | ||||
|  | ||||
|         if not self._is_email_valid( | ||||
|             update_user_dto.auth_user.email | ||||
|         ) or not self._is_email_valid(update_user_dto.new_auth_user.email): | ||||
|             raise ServiceException(ServiceErrorCode.InvalidData, f"Invalid E-Mail") | ||||
|  | ||||
|         user = self._auth_users.find_auth_user_by_email(update_user_dto.auth_user.email) | ||||
|         if user is None: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidUser, "User not found") | ||||
|  | ||||
|         if ( | ||||
|             user.confirmation_id is not None | ||||
|             and update_user_dto.new_auth_user.is_confirmed | ||||
|         ): | ||||
|             user.confirmation_id = None | ||||
|         elif ( | ||||
|             user.confirmation_id is None | ||||
|             and not update_user_dto.new_auth_user.is_confirmed | ||||
|         ): | ||||
|             user.confirmation_id = uuid.uuid4() | ||||
|         # else | ||||
|         #     raise ServiceException(ServiceErrorCode.InvalidUser, 'E-Mail not confirmed') | ||||
|  | ||||
|         # update first name | ||||
|         if ( | ||||
|             update_user_dto.new_auth_user.first_name is not None | ||||
|             and update_user_dto.auth_user.first_name | ||||
|             != update_user_dto.new_auth_user.first_name | ||||
|         ): | ||||
|             user.first_name = update_user_dto.new_auth_user.first_name | ||||
|  | ||||
|         # update last name | ||||
|         if ( | ||||
|             update_user_dto.new_auth_user.last_name is not None | ||||
|             and update_user_dto.new_auth_user.last_name != "" | ||||
|             and update_user_dto.auth_user.last_name | ||||
|             != update_user_dto.new_auth_user.last_name | ||||
|         ): | ||||
|             user.last_name = update_user_dto.new_auth_user.last_name | ||||
|  | ||||
|         # update E-Mail | ||||
|         if ( | ||||
|             update_user_dto.new_auth_user.email is not None | ||||
|             and update_user_dto.new_auth_user.email != "" | ||||
|             and update_user_dto.auth_user.email != update_user_dto.new_auth_user.email | ||||
|         ): | ||||
|             user_by_new_e_mail = self._auth_users.find_auth_user_by_email( | ||||
|                 update_user_dto.new_auth_user.email | ||||
|             ) | ||||
|             if user_by_new_e_mail is not None: | ||||
|                 raise ServiceException( | ||||
|                     ServiceErrorCode.InvalidUser, "User already exists" | ||||
|                 ) | ||||
|             user.email = update_user_dto.new_auth_user.email | ||||
|  | ||||
|         # update password | ||||
|         if ( | ||||
|             update_user_dto.new_auth_user.password is not None | ||||
|             and update_user_dto.change_password | ||||
|             and user.password | ||||
|             != self._hash_sha256( | ||||
|                 update_user_dto.new_auth_user.password, user.password_salt | ||||
|             ) | ||||
|         ): | ||||
|             user.password_salt = uuid.uuid4() | ||||
|             user.password = self._hash_sha256( | ||||
|                 update_user_dto.new_auth_user.password, user.password_salt | ||||
|             ) | ||||
|  | ||||
|         # update role | ||||
|         if ( | ||||
|             user.auth_role == update_user_dto.auth_user.auth_role | ||||
|             and user.auth_role != update_user_dto.new_auth_user.auth_role | ||||
|         ): | ||||
|             user.auth_role = update_user_dto.new_auth_user.auth_role | ||||
|  | ||||
|         self._auth_users.update_auth_user(user) | ||||
|         self._db.save_changes() | ||||
|  | ||||
|     async def delete_auth_user_by_email_async(self, email: str): | ||||
|         try: | ||||
|             user = self._auth_users.get_auth_user_by_email(email) | ||||
|             self._auth_users.delete_auth_user(user) | ||||
|             self._db.save_changes() | ||||
|         except Exception as e: | ||||
|             self._logger.error(__name__, f"Cannot delete user", e) | ||||
|             raise ServiceException( | ||||
|                 ServiceErrorCode.UnableToDelete, f"Cannot delete user by mail {email}" | ||||
|             ) | ||||
|  | ||||
|     async def delete_auth_user_async(self, user_dto: AuthUser): | ||||
|         try: | ||||
|             self._auth_users.delete_auth_user(AUT.to_db(user_dto)) | ||||
|             self._db.save_changes() | ||||
|         except Exception as e: | ||||
|             self._logger.error(__name__, f"Cannot delete user", e) | ||||
|             raise ServiceException( | ||||
|                 ServiceErrorCode.UnableToDelete, | ||||
|                 f"Cannot delete user by mail {user_dto.email}", | ||||
|             ) | ||||
|  | ||||
|     def verify_login(self, token_str: str) -> bool: | ||||
|         try: | ||||
|             token = self.decode_token(token_str) | ||||
|             if token is None or "email" not in token: | ||||
|                 raise ServiceException(ServiceErrorCode.InvalidData, "Token invalid") | ||||
|  | ||||
|             user = self._auth_users.find_auth_user_by_email(token["email"]) | ||||
|             if user is None: | ||||
|                 raise ServiceException(ServiceErrorCode.InvalidData, "Token expired") | ||||
|         except Exception as e: | ||||
|             self._logger.error(__name__, f"Token invalid", e) | ||||
|             return False | ||||
|  | ||||
|         return True | ||||
|  | ||||
|     def verify_api_key(self, api_key: str) -> bool: | ||||
|         try: | ||||
|             keys = self._api_keys.get_api_keys().select(self._get_api_key_str) | ||||
|  | ||||
|             if not keys.contains(api_key): | ||||
|                 raise ServiceException(ServiceErrorCode.InvalidData, "API-Key invalid") | ||||
|         except Exception as e: | ||||
|             self._logger.error(__name__, f"API-Key invalid", e) | ||||
|             return False | ||||
|  | ||||
|         return True | ||||
|  | ||||
|     async def login_async(self, user_dto: AuthUser) -> TokenDTO: | ||||
|         if user_dto is None: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidData, "User not set") | ||||
|  | ||||
|         db_user = self._auth_users.find_auth_user_by_email(user_dto.email) | ||||
|         if db_user is None: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidUser, f"User not found") | ||||
|  | ||||
|         user_dto.password = self._hash_sha256(user_dto.password, db_user.password_salt) | ||||
|         if db_user.password != user_dto.password: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidUser, "Wrong password") | ||||
|  | ||||
|         if db_user.confirmation_id is not None: | ||||
|             raise ServiceException(ServiceErrorCode.Forbidden, "E-Mail not verified") | ||||
|  | ||||
|         token = self.generate_token(db_user) | ||||
|         refresh_token = self._create_and_save_refresh_token(db_user) | ||||
|         if db_user.forgot_password_id is not None: | ||||
|             db_user.forgot_password_id = None | ||||
|  | ||||
|         self._db.save_changes() | ||||
|         return TokenDTO(token, refresh_token) | ||||
|  | ||||
|     async def login_discord_async(self, user_dto: AuthUserDTO, dc_id: int) -> TokenDTO: | ||||
|         if user_dto is None: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidData, "User not set") | ||||
|  | ||||
|         members = self._users.get_users_by_discord_id(dc_id) | ||||
|         if members.count() == 0: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidUser, f"Member not found") | ||||
|  | ||||
|         added_user = False | ||||
|         db_user = self._auth_users.find_auth_user_by_email(user_dto.email) | ||||
|         if db_user is None: | ||||
|             self.add_auth_user(user_dto) | ||||
|             added_user = True | ||||
|  | ||||
|         db_user = self._auth_users.get_auth_user_by_email(user_dto.email) | ||||
|         user_ids = db_user.users.select(lambda x: x.id) | ||||
|  | ||||
|         for user in self._users.get_users_by_discord_id(dc_id): | ||||
|             if user.id in user_ids: | ||||
|                 continue | ||||
|  | ||||
|             self._auth_users.add_auth_user_user_rel( | ||||
|                 AuthUserUsersRelation(db_user, user) | ||||
|             ) | ||||
|  | ||||
|         if db_user.confirmation_id is not None and not added_user: | ||||
|             raise ServiceException(ServiceErrorCode.Forbidden, "E-Mail not verified") | ||||
|  | ||||
|         token = self.generate_token(db_user) | ||||
|         refresh_token = self._create_and_save_refresh_token(db_user) | ||||
|         if db_user.forgot_password_id is not None: | ||||
|             db_user.forgot_password_id = None | ||||
|  | ||||
|         self._db.save_changes() | ||||
|         return TokenDTO(token, refresh_token, first_login=added_user) | ||||
|  | ||||
|     async def refresh_async(self, token_dto: TokenDTO) -> TokenDTO: | ||||
|         if token_dto is None: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidData, f"Token not set") | ||||
|  | ||||
|         try: | ||||
|             token = self.decode_token(token_dto.token) | ||||
|             if token is None or "email" not in token: | ||||
|                 raise ServiceException(ServiceErrorCode.InvalidData, "Token invalid") | ||||
|  | ||||
|             user = self._auth_users.get_auth_user_by_email(token["email"]) | ||||
|             if ( | ||||
|                 user is None | ||||
|                 or user.refresh_token != token_dto.refresh_token | ||||
|                 or user.refresh_token_expire_time <= datetime.now() | ||||
|             ): | ||||
|                 raise ServiceException(ServiceErrorCode.InvalidData, "Token expired") | ||||
|  | ||||
|             return TokenDTO( | ||||
|                 self.generate_token(user), self._create_and_save_refresh_token(user) | ||||
|             ) | ||||
|         except Exception as e: | ||||
|             self._logger.error(__name__, f"Refreshing token failed", e) | ||||
|             return TokenDTO("", "") | ||||
|  | ||||
|     async def revoke_async(self, token_dto: TokenDTO): | ||||
|         if ( | ||||
|             token_dto is None | ||||
|             or token_dto.token is None | ||||
|             or token_dto.refresh_token is None | ||||
|         ): | ||||
|             raise ServiceException(ServiceErrorCode.InvalidData, "Token not set") | ||||
|  | ||||
|         try: | ||||
|             token = self.decode_token(token_dto.token) | ||||
|  | ||||
|             user = self._auth_users.get_auth_user_by_email(token["email"]) | ||||
|             if ( | ||||
|                 user is None | ||||
|                 or user.refresh_token != token_dto.refresh_token | ||||
|                 or user.refresh_token_expire_time <= datetime.now() | ||||
|             ): | ||||
|                 raise ServiceException(ServiceErrorCode.InvalidData, "Token expired") | ||||
|  | ||||
|             user.refresh_token = None | ||||
|             self._auth_users.update_auth_user(user) | ||||
|             self._db.save_changes() | ||||
|         except Exception as e: | ||||
|             self._logger.error(__name__, f"Refreshing token failed", e) | ||||
|  | ||||
|     async def confirm_email_async(self, id: str) -> bool: | ||||
|         user = self._auth_users.find_auth_user_by_confirmation_id(id) | ||||
|         if user is None: | ||||
|             return False | ||||
|  | ||||
|         user.confirmation_id = None | ||||
|         self._auth_users.update_auth_user(user) | ||||
|         self._db.save_changes() | ||||
|         return True | ||||
|  | ||||
|     async def forgot_password_async(self, email: str): | ||||
|         user = self._auth_users.find_auth_user_by_email(email) | ||||
|         if user is None: | ||||
|             return | ||||
|  | ||||
|         user.forgot_password_id = uuid.uuid4() | ||||
|         self._auth_users.update_auth_user(user) | ||||
|         self._send_forgot_password_id_to_user(user) | ||||
|         self._db.save_changes() | ||||
|  | ||||
|     async def confirm_forgot_password_async(self, id: str) -> EMailStringDTO: | ||||
|         user = self._auth_users.find_auth_user_by_forgot_password_id(id) | ||||
|         return EMailStringDTO(user.email) | ||||
|  | ||||
|     async def reset_password_async(self, rp_dto: ResetPasswordDTO): | ||||
|         user = self._auth_users.find_auth_user_by_forgot_password_id(rp_dto.id) | ||||
|         if user is None: | ||||
|             raise ServiceException( | ||||
|                 ServiceErrorCode.InvalidUser, | ||||
|                 f"User by forgot password id {rp_dto.id} not found", | ||||
|             ) | ||||
|  | ||||
|         if user.confirmation_id is not None: | ||||
|             raise ServiceException( | ||||
|                 ServiceErrorCode.InvalidUser, f"E-Mail not confirmed" | ||||
|             ) | ||||
|  | ||||
|         if user.password is None or rp_dto.password == "": | ||||
|             raise ServiceException(ServiceErrorCode.InvalidData, f"Password not set") | ||||
|  | ||||
|         user.password_salt = uuid.uuid4() | ||||
|         user.password = self._hash_sha256(rp_dto.password, user.password_salt) | ||||
|         user.forgot_password_id = None | ||||
|         self._auth_users.update_auth_user(user) | ||||
|         self._db.save_changes() | ||||
							
								
								
									
										104
									
								
								bot/src/bot_api/service/discord_service.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								bot/src/bot_api/service/discord_service.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| from typing import Optional | ||||
|  | ||||
| from cpl_discord.service import DiscordBotServiceABC | ||||
| from cpl_query.extension import List | ||||
|  | ||||
| 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.transformer.server_transformer import ServerTransformer | ||||
| from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC | ||||
| from bot_data.abc.server_repository_abc import ServerRepositoryABC | ||||
| from bot_data.abc.user_repository_abc import UserRepositoryABC | ||||
| from bot_data.model.auth_role_enum import AuthRoleEnum | ||||
| from bot_data.model.server import Server | ||||
|  | ||||
|  | ||||
| class DiscordService: | ||||
|     def __init__( | ||||
|         self, | ||||
|         bot: DiscordBotServiceABC, | ||||
|         servers: ServerRepositoryABC, | ||||
|         auth: AuthServiceABC, | ||||
|         auth_users: AuthUserRepositoryABC, | ||||
|         users: UserRepositoryABC, | ||||
|     ): | ||||
|         self._bot = bot | ||||
|         self._servers = servers | ||||
|         self._auth = auth | ||||
|         self._auth_users = auth_users | ||||
|         self._users = users | ||||
|  | ||||
|     def _to_dto(self, x: Server) -> Optional[ServerDTO]: | ||||
|         guild = self._bot.get_guild(x.discord_id) | ||||
|         if guild is None: | ||||
|             return ServerTransformer.to_dto(x, "", 0, None) | ||||
|  | ||||
|         return ServerTransformer.to_dto(x, guild.name, guild.member_count, guild.icon) | ||||
|  | ||||
|     async def get_all_servers(self) -> List[ServerDTO]: | ||||
|         servers = List(ServerDTO, self._servers.get_servers()) | ||||
|         return servers.select(self._to_dto).where(lambda x: x.name != "") | ||||
|  | ||||
|     async def get_all_servers_by_user(self) -> List[ServerDTO]: | ||||
|         token = self._auth.get_decoded_token_from_request() | ||||
|         if token is None or "email" not in token or "role" not in token: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidData, "Token invalid") | ||||
|  | ||||
|         role = AuthRoleEnum(token["role"]) | ||||
|         servers = self._servers.get_servers() | ||||
|         if role != AuthRoleEnum.admin: | ||||
|             auth_user = self._auth_users.find_auth_user_by_email(token["email"]) | ||||
|             if auth_user is not None: | ||||
|                 user_ids = auth_user.users.select( | ||||
|                     lambda x: x.server is not None and x.server.id | ||||
|                 ) | ||||
|                 servers = servers.where(lambda x: x.id in user_ids) | ||||
|  | ||||
|         servers = List(ServerDTO, servers) | ||||
|         return servers.select(self._to_dto).where(lambda x: x.name != "") | ||||
|  | ||||
|     async def get_filtered_servers_async( | ||||
|         self, criteria: ServerSelectCriteria | ||||
|     ) -> ServerFilteredResultDTO: | ||||
|         token = self._auth.get_decoded_token_from_request() | ||||
|         if token is None or "email" not in token or "role" not in token: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidData, "Token invalid") | ||||
|  | ||||
|         role = AuthRoleEnum(token["role"]) | ||||
|         filtered_result = self._servers.get_filtered_servers(criteria) | ||||
|         # filter out servers, where the user not exists | ||||
|         if role != AuthRoleEnum.admin: | ||||
|             auth_user = self._auth_users.find_auth_user_by_email(token["email"]) | ||||
|             if auth_user is not None: | ||||
|                 user_ids = auth_user.users.select( | ||||
|                     lambda x: x.server is not None and x.server.id | ||||
|                 ) | ||||
|                 filtered_result.result = filtered_result.result.where( | ||||
|                     lambda x: x.id in user_ids | ||||
|                 ) | ||||
|  | ||||
|         servers: List = filtered_result.result.select(self._to_dto).where( | ||||
|             lambda x: x.name != "" | ||||
|         ) | ||||
|         result = List(ServerDTO, servers) | ||||
|  | ||||
|         if criteria.name is not None and criteria.name != "": | ||||
|             result = result.where( | ||||
|                 lambda x: criteria.name.lower() in x.name.lower() | ||||
|                 or x.name.lower() == criteria.name.lower() | ||||
|             ) | ||||
|  | ||||
|         return ServerFilteredResultDTO(List(ServerDTO, result), servers.count()) | ||||
|  | ||||
|     async def get_server_by_id_async(self, id: int) -> ServerDTO: | ||||
|         server = self._servers.get_server_by_id(id) | ||||
|         guild = self._bot.get_guild(server.discord_id) | ||||
|  | ||||
|         server_dto = ServerTransformer.to_dto( | ||||
|             server, guild.name, guild.member_count, guild.icon | ||||
|         ) | ||||
|         return server_dto | ||||
							
								
								
									
										26
									
								
								bot/src/bot_api/transformer/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								bot/src/bot_api/transformer/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| bot sh-edraft.de Discord bot | ||||
| ~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| Discord bot for customers of sh-edraft.de | ||||
|  | ||||
| :copyright: (c) 2022 - 2023 sh-edraft.de | ||||
| :license: MIT, see LICENSE for more details. | ||||
|  | ||||
| """ | ||||
|  | ||||
| __title__ = "bot_api.transformer" | ||||
| __author__ = "Sven Heidemann" | ||||
| __license__ = "MIT" | ||||
| __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" | ||||
| __version__ = "1.2.0" | ||||
|  | ||||
| from collections import namedtuple | ||||
|  | ||||
|  | ||||
| # imports: | ||||
|  | ||||
| VersionInfo = namedtuple("VersionInfo", "major minor micro") | ||||
| version_info = VersionInfo(major="1", minor="2", micro="0") | ||||
							
								
								
									
										89
									
								
								bot/src/bot_api/transformer/auth_user_transformer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								bot/src/bot_api/transformer/auth_user_transformer.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| from datetime import datetime | ||||
|  | ||||
| from cpl_core.dependency_injection import ServiceProviderABC | ||||
| from cpl_discord.service import DiscordBotServiceABC | ||||
| from cpl_query.extension import List | ||||
|  | ||||
| from bot_api.abc.transformer_abc import TransformerABC | ||||
| from bot_api.model.auth_user_dto import AuthUserDTO | ||||
| from bot_api.model.user_dto import UserDTO | ||||
| from bot_data.model.auth_role_enum import AuthRoleEnum | ||||
| from bot_data.model.auth_user import AuthUser | ||||
| from bot_data.model.user import User | ||||
| from modules.permission.abc.permission_service_abc import PermissionServiceABC | ||||
|  | ||||
|  | ||||
| class AuthUserTransformer(TransformerABC): | ||||
|     @staticmethod | ||||
|     def to_db(dto: AuthUserDTO) -> AuthUser: | ||||
|         return AuthUser( | ||||
|             dto.first_name, | ||||
|             dto.last_name, | ||||
|             dto.email, | ||||
|             dto.password, | ||||
|             None, | ||||
|             None, | ||||
|             None, | ||||
|             None, | ||||
|             None, | ||||
|             datetime.now(), | ||||
|             AuthRoleEnum.normal | ||||
|             if dto.auth_role is None | ||||
|             else AuthRoleEnum(dto.auth_role), | ||||
|             auth_user_id=0 if dto.id is None else dto.id, | ||||
|         ) | ||||
|  | ||||
|     @staticmethod | ||||
|     @ServiceProviderABC.inject | ||||
|     def _is_technician( | ||||
|         user: User, bot: DiscordBotServiceABC, permissions: PermissionServiceABC | ||||
|     ): | ||||
|         guild = bot.get_guild(user.server.discord_id) | ||||
|         member = guild.get_member(user.discord_id) | ||||
|         return permissions.is_member_technician(member) | ||||
|  | ||||
|     @staticmethod | ||||
|     @ServiceProviderABC.inject | ||||
|     def _is_admin( | ||||
|         user: User, bot: DiscordBotServiceABC, permissions: PermissionServiceABC | ||||
|     ): | ||||
|         guild = bot.get_guild(user.server.discord_id) | ||||
|         member = guild.get_member(user.discord_id) | ||||
|         return permissions.is_member_admin(member) | ||||
|  | ||||
|     @staticmethod | ||||
|     @ServiceProviderABC.inject | ||||
|     def _is_moderator( | ||||
|         user: User, bot: DiscordBotServiceABC, permissions: PermissionServiceABC | ||||
|     ): | ||||
|         guild = bot.get_guild(user.server.discord_id) | ||||
|         member = guild.get_member(user.discord_id) | ||||
|         return permissions.is_member_moderator(member) | ||||
|  | ||||
|     @classmethod | ||||
|     def to_dto(cls, db: AuthUser, password: str = None) -> AuthUserDTO: | ||||
|         return AuthUserDTO( | ||||
|             db.id, | ||||
|             db.first_name, | ||||
|             db.last_name, | ||||
|             db.email, | ||||
|             "" if password is None else password, | ||||
|             db.confirmation_id, | ||||
|             db.auth_role, | ||||
|             List( | ||||
|                 UserDTO, | ||||
|                 db.users.select( | ||||
|                     lambda u: UserDTO( | ||||
|                         u.id, | ||||
|                         u.discord_id, | ||||
|                         u.xp, | ||||
|                         u.server, | ||||
|                         cls._is_technician(u), | ||||
|                         cls._is_admin(u), | ||||
|                         cls._is_moderator(u), | ||||
|                     ) | ||||
|                 ), | ||||
|             ), | ||||
|             db.created_at, | ||||
|             db.modified_at, | ||||
|         ) | ||||
							
								
								
									
										25
									
								
								bot/src/bot_api/transformer/server_transformer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								bot/src/bot_api/transformer/server_transformer.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| from typing import Optional | ||||
|  | ||||
| import discord | ||||
|  | ||||
| from bot_api.abc.transformer_abc import TransformerABC | ||||
| from bot_api.model.discord.server_dto import ServerDTO | ||||
| from bot_data.model.server import Server | ||||
|  | ||||
|  | ||||
| class ServerTransformer(TransformerABC): | ||||
|     @staticmethod | ||||
|     def to_db(dto: ServerDTO) -> Server: | ||||
|         return Server(dto.discord_id) | ||||
|  | ||||
|     @staticmethod | ||||
|     def to_dto( | ||||
|         db: Server, name: str, member_count: int, icon_url: Optional[discord.Asset] | ||||
|     ) -> ServerDTO: | ||||
|         return ServerDTO( | ||||
|             db.id, | ||||
|             db.discord_id, | ||||
|             name, | ||||
|             member_count, | ||||
|             icon_url.url if icon_url is not None else None, | ||||
|         ) | ||||
		Reference in New Issue
	
	Block a user