graphql part3/3 #162-3 #194

Merged
edraft merged 4 commits from #162-3 into 1.0.0 2023-02-11 10:39:49 +01:00
18 changed files with 512 additions and 17 deletions

View File

@ -4,6 +4,7 @@ from cpl_core.dependency_injection import ServiceCollectionABC
from cpl_core.environment import ApplicationEnvironmentABC
from bot_data.abc.migration_abc import MigrationABC
from bot_data.migration.api_key_migration import ApiKeyMigration
from bot_data.migration.api_migration import ApiMigration
from bot_data.migration.auto_role_fix1_migration import AutoRoleFix1Migration
from bot_data.migration.auto_role_migration import AutoRoleMigration
@ -32,3 +33,4 @@ class StartupMigrationExtension(StartupExtensionABC):
services.add_transient(MigrationABC, StatsMigration) # 09.11.2022 #46 - 0.3.0
services.add_transient(MigrationABC, AutoRoleFix1Migration) # 30.12.2022 #151 - 0.3.0
services.add_transient(MigrationABC, UserMessageCountPerHourMigration) # 11.01.2023 #168 - 0.3.1
services.add_transient(MigrationABC, ApiKeyMigration) # 09.02.2023 #162 - 1.0.0

View File

@ -283,7 +283,17 @@
"technician": {
"restart_message": "Bin gleich wieder da :D",
"shutdown_message": "Trauert nicht um mich, es war eine logische Entscheidung. Das Wohl von Vielen, es wiegt schwerer als das Wohl von Wenigen oder eines Einzelnen. Ich war es und ich werde es immer sein, Euer Freund. Lebt lange und in Frieden :)",
"log_message": "Hier sind deine Logdateien! :)"
"log_message": "Hier sind deine Logdateien! :)",
"api_key": {
"get": "API-Schlüssel für {}: {}",
"add": {
"success": "API-Schlüssel für {} wurde erstellt: {}"
},
"remove": {
"not_found": "API-Schlüssel konnte nicht gefunden werden!",
"success": "API-Schlüssel wurde entfernt :D"
}
}
}
},
"api": {

View File

@ -83,6 +83,10 @@ class AuthServiceABC(ABC):
async def verify_login(self, token_str: str) -> bool:
pass
@abstractmethod
async def verify_api_key(self, api_key: str) -> bool:
pass
@abstractmethod
async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO:
pass

View File

@ -13,7 +13,7 @@ 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.grahpql_controller import GraphQLController
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

View File

@ -33,7 +33,7 @@ class GraphQLController:
return PLAYGROUND_HTML, 200
@Route.post(f"{BasePath}")
@Route.authorize
@Route.authorize(by_api_key=True)
async def graphql(self):
data = request.get_json()

View File

@ -30,15 +30,32 @@ class Route:
cls._env = env.environment_name
@classmethod
def authorize(cls, f: Callable = None, role: AuthRoleEnum = None, skip_in_dev=False):
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)
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)
if "Authorization" not in request.headers and by_api_key and "API-Key" in request.headers:
valid = False
try:
valid = cls._auth.verify_api_key(request.headers["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)
token = None
if "Authorization" in request.headers:
bearer = request.headers.get("Authorization")

View File

@ -31,9 +31,11 @@ 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
@ -49,9 +51,9 @@ class AuthService(AuthServiceABC):
bot: DiscordBotServiceABC,
db: DatabaseContextABC,
auth_users: AuthUserRepositoryABC,
api_keys: ApiKeyRepositoryABC,
users: UserRepositoryABC,
servers: ServerRepositoryABC,
# mailer: MailThread,
mailer: EMailClientABC,
t: TranslatePipe,
auth_settings: AuthenticationSettings,
@ -64,6 +66,7 @@ class AuthService(AuthServiceABC):
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
@ -82,6 +85,11 @@ class AuthService(AuthServiceABC):
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={
@ -221,7 +229,12 @@ class AuthService(AuthServiceABC):
raise ServiceException(ServiceErrorCode.InvalidUser, "User already exists")
user = AUT.to_db(user_dto)
if self._auth_users.get_all_auth_users().count() == 0:
if (
self._auth_users.get_all_auth_users()
.where(lambda x: x.name != "internal" and x.email != "internal@localhost")
.count()
== 0
):
user.auth_role = AuthRoleEnum.admin
user.password_salt = uuid.uuid4()
@ -478,6 +491,18 @@ class AuthService(AuthServiceABC):
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"Token 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")

View File

@ -0,0 +1,35 @@
from abc import ABC, abstractmethod
from cpl_query.extension import List
from bot_data.model.api_key import ApiKey
class ApiKeyRepositoryABC(ABC):
@abstractmethod
def __init__(self):
pass
@abstractmethod
def get_api_keys(self) -> List[ApiKey]:
pass
@abstractmethod
def get_api_key(self, identifier: str, key: str) -> ApiKey:
pass
@abstractmethod
def get_api_key_by_key(self, key: str) -> ApiKey:
pass
@abstractmethod
def add_api_key(self, api_key: ApiKey):
pass
@abstractmethod
def update_api_key(self, api_key: ApiKey):
pass
@abstractmethod
def delete_api_key(self, api_key: ApiKey):
pass

View File

@ -5,6 +5,7 @@ from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
from bot_core.abc.module_abc import ModuleABC
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from bot_data.abc.api_key_repository_abc import ApiKeyRepositoryABC
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC
from bot_data.abc.client_repository_abc import ClientRepositoryABC
@ -20,6 +21,7 @@ from bot_data.abc.user_message_count_per_hour_repository_abc import (
UserMessageCountPerHourRepositoryABC,
)
from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.service.api_key_repository_service import ApiKeyRepositoryService
from bot_data.service.auth_user_repository_service import AuthUserRepositoryService
from bot_data.service.auto_role_repository_service import AutoRoleRepositoryService
from bot_data.service.client_repository_service import ClientRepositoryService
@ -48,6 +50,7 @@ class DataModule(ModuleABC):
pass
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
services.add_transient(ApiKeyRepositoryABC, ApiKeyRepositoryService)
services.add_transient(AuthUserRepositoryABC, AuthUserRepositoryService)
services.add_transient(ServerRepositoryABC, ServerRepositoryService)
services.add_transient(UserRepositoryABC, UserRepositoryService)

View File

@ -0,0 +1,38 @@
from bot_core.logging.database_logger import DatabaseLogger
from bot_data.abc.migration_abc import MigrationABC
from bot_data.db_context import DBContext
class ApiKeyMigration(MigrationABC):
name = "1.0_ApiKeyMigration"
def __init__(self, logger: DatabaseLogger, db: DBContext):
MigrationABC.__init__(self)
self._logger = logger
self._db = db
self._cursor = db.cursor
def upgrade(self):
self._logger.debug(__name__, "Running upgrade")
self._cursor.execute(
str(
f"""
CREATE TABLE IF NOT EXISTS `ApiKeys` (
`Id` BIGINT NOT NULL AUTO_INCREMENT,
`Identifier` VARCHAR(255) NOT NULL,
`Key` VARCHAR(255) NOT NULL,
`CreatorId` BIGINT NULL,
`CreatedAt` DATETIME(6),
`LastModifiedAt` DATETIME(6),
PRIMARY KEY(`Id`),
FOREIGN KEY (`CreatorId`) REFERENCES `Users`(`UserId`),
CONSTRAINT UC_Identifier_Key UNIQUE (`Identifier`,`Key`),
CONSTRAINT UC_Key UNIQUE (`Key`)
);
"""
)
)
def downgrade(self):
self._cursor.execute("DROP TABLE `ApiKeys`;")

View File

@ -4,7 +4,7 @@ from bot_data.db_context import DBContext
class AutoRoleFix1Migration(MigrationABC):
name = "0.3.0_AutoRoleMigration"
name = "0.3.0_AutoRoleFixMigration"
def __init__(self, logger: DatabaseLogger, db: DBContext):
MigrationABC.__init__(self)

View File

@ -0,0 +1,102 @@
from datetime import datetime
from typing import Optional
from cpl_core.database import TableABC
from bot_data.model.user import User
class ApiKey(TableABC):
def __init__(
self,
identifier: str,
key: str,
creator: Optional[User],
created_at: datetime = None,
modified_at: datetime = None,
id=0,
):
self._id = id
self._identifier = identifier
self._key = key
self._creator = creator
TableABC.__init__(self)
self._created_at = created_at if created_at is not None else self._created_at
self._modified_at = modified_at if modified_at is not None else self._modified_at
@property
def identifier(self) -> str:
return self._identifier
@property
def key(self) -> str:
return self._key
@property
def creator(self) -> Optional[User]:
return self._creator
@staticmethod
def get_select_all_string() -> str:
return str(
f"""
SELECT * FROM `ApiKeys`;
"""
)
@staticmethod
def get_select_string(identifier: str, key: str) -> str:
return str(
f"""
SELECT * FROM `ApiKeys`
WHERE `Identifier` = '{identifier}'
AND `Key` = '{key}';
"""
)
@staticmethod
def get_select_by_key(key: str) -> str:
return str(
f"""
SELECT * FROM `ApiKeys`
WHERE `Key` = '{key}';
"""
)
@property
def insert_string(self) -> str:
return str(
f"""
INSERT INTO `ApiKeys` (
`Identifier`, `Key`, `CreatorId`, `CreatedAt`, `LastModifiedAt`
) VALUES (
'{self._identifier}',
'{self._key}',
{"NULL" if self._creator is None else self._creator.user_id},
'{self._created_at}',
'{self._modified_at}'
);
"""
)
@property
def udpate_string(self) -> str:
return str(
f"""
UPDATE `ApiKeys`
SET `Identifier` = '{self._identifier}',
`Key` = '{self._key}',
`LastModifiedAt` = '{self._modified_at}'
WHERE `Id` = {self._id};
"""
)
@property
def delete_string(self) -> str:
return str(
f"""
DELETE FROM `ApiKeys`
WHERE `Id` = {self._id};
"""
)

View File

@ -0,0 +1,73 @@
from typing import Optional
from cpl_core.database.context import DatabaseContextABC
from cpl_query.extension import List
from bot_core.logging.database_logger import DatabaseLogger
from bot_data.abc.api_key_repository_abc import ApiKeyRepositoryABC
from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.model.api_key import ApiKey
class ApiKeyRepositoryService(ApiKeyRepositoryABC):
def __init__(
self,
logger: DatabaseLogger,
db_context: DatabaseContextABC,
users: UserRepositoryABC,
):
self._logger = logger
self._context = db_context
self._users = users
ApiKeyRepositoryABC.__init__(self)
@staticmethod
def _get_value_from_result(value: any) -> Optional[any]:
if isinstance(value, str) and "NULL" in value:
return None
return value
def _api_key_from_result(self, sql_result: tuple) -> ApiKey:
creator = self._get_value_from_result(sql_result[3])
api_key = ApiKey(
self._get_value_from_result(sql_result[1]),
self._get_value_from_result(sql_result[2]),
None if creator is None else self._users.get_user_by_id(int(creator)),
self._get_value_from_result(sql_result[4]),
self._get_value_from_result(sql_result[5]),
id=self._get_value_from_result(sql_result[0]),
)
return api_key
def get_api_keys(self) -> List[ApiKey]:
api_keys = List(ApiKey)
self._logger.trace(__name__, f"Send SQL command: {ApiKey.get_select_all_string()}")
results = self._context.select(ApiKey.get_select_all_string())
for result in results:
api_keys.append(self._api_key_from_result(result))
return api_keys
def get_api_key(self, identifier: str, key: str) -> ApiKey:
self._logger.trace(__name__, f"Send SQL command: {ApiKey.get_select_string(identifier, key)}")
return self._api_key_from_result(self._context.select(ApiKey.get_select_string(identifier, key))[0])
def get_api_key_by_key(self, key: str) -> ApiKey:
self._logger.trace(__name__, f"Send SQL command: {ApiKey.get_select_by_key(key)}")
return self._api_key_from_result(self._context.select(ApiKey.get_select_by_key(key))[0])
def add_api_key(self, api_key: ApiKey):
self._logger.trace(__name__, f"Send SQL command: {api_key.insert_string}")
self._context.cursor.execute(api_key.insert_string)
def update_api_key(self, api_key: ApiKey):
self._logger.trace(__name__, f"Send SQL command: {api_key.udpate_string}")
self._context.cursor.execute(api_key.udpate_string)
def delete_api_key(self, api_key: ApiKey):
self._logger.trace(__name__, f"Send SQL command: {api_key.delete_string}")
self._context.cursor.execute(api_key.delete_string)

View File

@ -1,6 +1,5 @@
from cpl_core.database.context import DatabaseContextABC
from cpl_core.dependency_injection import ServiceProviderABC
from cpl_query.extension import List
from bot_core.logging.database_logger import DatabaseLogger
from bot_data.abc.data_seeder_abc import DataSeederABC
@ -18,12 +17,10 @@ class SeederService:
self._db = db
self._seeder = List(type, DataSeederABC.__subclasses__())
async def seed(self):
self._logger.info(__name__, f"Seed data")
for seeder in self._seeder:
seeder_as_service: DataSeederABC = self._services.get_service(seeder)
self._logger.debug(__name__, f"Starting seeder {seeder.__name__}")
await seeder_as_service.seed()
for seeder in self._services.get_services(list[DataSeederABC]):
seeder: DataSeederABC = seeder
self._logger.debug(__name__, f"Starting seeder {type(seeder).__name__}")
await seeder.seed()
self._db.save_changes()

View File

@ -8,6 +8,7 @@ from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
from bot_core.abc.module_abc import ModuleABC
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from bot_data.abc.data_seeder_abc import DataSeederABC
from modules.level.command.level_group import LevelGroup
from modules.level.events.level_on_member_join_event import LevelOnMemberJoinEvent
from modules.level.events.level_on_message_event import LevelOnMessageEvent
@ -29,7 +30,7 @@ class LevelModule(ModuleABC):
env.set_working_directory(cwd)
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
services.add_transient(LevelSeeder)
services.add_transient(DataSeederABC, LevelSeeder)
services.add_transient(LevelService)
# commands

View File

@ -0,0 +1,44 @@
from cpl_core.configuration import ConfigurationABC
from cpl_core.database.context import DatabaseContextABC
from cpl_discord.service import DiscordBotServiceABC
from bot_core.logging.database_logger import DatabaseLogger
from bot_data.abc.api_key_repository_abc import ApiKeyRepositoryABC
from bot_data.abc.data_seeder_abc import DataSeederABC
from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.model.api_key import ApiKey
class ApiKeySeeder(DataSeederABC):
def __init__(
self,
logger: DatabaseLogger,
config: ConfigurationABC,
bot: DiscordBotServiceABC,
db: DatabaseContextABC,
users: UserRepositoryABC,
api_keys: ApiKeyRepositoryABC,
):
DataSeederABC.__init__(self)
self._logger = logger
self._config = config
self._bot = bot
self._db = db
self._users = users
self._api_keys = api_keys
async def seed(self):
self._logger.debug(__name__, f"API-Key seeder started")
if self._api_keys.get_api_keys().count() > 0:
self._logger.debug(__name__, f"Skip API-Key seeder")
return
try:
frontend_key = ApiKey("frontend", "87f529fd-a32e-40b3-a1d1-7a1583cf3ff5", None)
self._api_keys.add_api_key(frontend_key)
self._db.save_changes()
self._logger.info(__name__, f"Created frontend API-Key")
except Exception as e:
self._logger.fatal(__name__, "Cannot create frontend API-Key", e)

View File

@ -0,0 +1,139 @@
import hashlib
import uuid
from typing import List as TList
import discord
from cpl_core.database.context import DatabaseContextABC
from cpl_discord.command import DiscordCommandABC
from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
from bot_api.configuration.authentication_settings import AuthenticationSettings
from bot_core.abc.client_utils_abc import ClientUtilsABC
from bot_core.abc.message_service_abc import MessageServiceABC
from bot_core.helper.command_checks import CommandChecks
from bot_core.logging.command_logger import CommandLogger
from bot_data.abc.api_key_repository_abc import ApiKeyRepositoryABC
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 modules.permission.abc.permission_service_abc import PermissionServiceABC
class ApiKeyGroup(DiscordCommandABC):
def __init__(
self,
logger: CommandLogger,
auth_settings: AuthenticationSettings,
message_service: MessageServiceABC,
bot: DiscordBotServiceABC,
client_utils: ClientUtilsABC,
permission_service: PermissionServiceABC,
translate: TranslatePipe,
db: DatabaseContextABC,
servers: ServerRepositoryABC,
users: UserRepositoryABC,
api_keys: ApiKeyRepositoryABC,
):
DiscordCommandABC.__init__(self)
self._logger = logger
self._auth_settings = auth_settings
self._message_service = message_service
self._bot = bot
self._client_utils = client_utils
self._permissions = permission_service
self._t = translate
self._db = db
self._servers = servers
self._users = users
self._api_keys = api_keys
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()
@commands.hybrid_group(name="api-key")
@commands.guild_only()
async def api_key(self, ctx: Context):
pass
@api_key.command()
@commands.guild_only()
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_technician()
async def get(self, ctx: Context, key: str, wait: int = None):
self._logger.debug(__name__, f"Received command api-key get {ctx}: {key},{wait}")
api_key = self._api_keys.get_api_key_by_key(key)
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.technician.api_key.get").format(
api_key.identifier, self._get_api_key_str(api_key)
),
)
self._logger.trace(__name__, f"Finished command api-key get")
@get.autocomplete("key")
async def get_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]:
keys = self._api_keys.get_api_keys()
return [
app_commands.Choice(name=f"{key.identifier}: {key.key}", value=key.key)
for key in self._client_utils.get_auto_complete_list(keys, current, lambda x: x.key)
]
@api_key.command()
@commands.guild_only()
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_moderator()
async def add(self, ctx: Context, identifier: str):
self._logger.debug(__name__, f"Received command api-key add {ctx}: {identifier}")
server = self._servers.get_server_by_discord_id(ctx.guild.id)
user = self._users.get_user_by_discord_id_and_server_id(ctx.author.id, server.server_id)
api_key = ApiKey(identifier, str(uuid.uuid4()), user)
self._api_keys.add_api_key(api_key)
self._db.save_changes()
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.technician.api_key.add.success").format(
identifier, self._get_api_key_str(api_key)
),
)
self._logger.trace(__name__, f"Finished command api-key add")
@api_key.command()
@commands.guild_only()
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_moderator()
async def remove(self, ctx: Context, key: str):
self._logger.debug(__name__, f"Received command api-key remove {ctx}: {key}")
keys = self._api_keys.get_api_keys().where(lambda x: x.key == key)
if keys.count() < 1:
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.technician.api_key.remove.not_found"),
)
api_key = keys.single()
self._api_keys.delete_api_key(api_key)
self._db.save_changes()
await self._message_service.send_ctx_msg(ctx, self._t.transform("modules.technician.api_key.remove.success"))
self._logger.trace(__name__, f"Finished command api-key remove")
@remove.autocomplete("key")
async def set_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]:
keys = self._api_keys.get_api_keys()
return [
app_commands.Choice(name=f"{key.identifier}: {key.key}", value=key.key)
for key in self._client_utils.get_auto_complete_list(keys, current, lambda x: x.key)
]

View File

@ -5,11 +5,14 @@ from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
from bot_core.abc.module_abc import ModuleABC
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from bot_data.abc.data_seeder_abc import DataSeederABC
from modules.base.abc.base_helper_abc import BaseHelperABC
from modules.base.service.base_helper_service import BaseHelperService
from modules.technician.api_key_seeder import ApiKeySeeder
from modules.technician.command.api_key_group import ApiKeyGroup
from modules.technician.command.log_command import LogCommand
from modules.technician.command.restart_command import RestartCommand
from modules.technician.command.shutdown_command import ShutdownCommand
from modules.base.service.base_helper_service import BaseHelperService
class TechnicianModule(ModuleABC):
@ -20,9 +23,11 @@ class TechnicianModule(ModuleABC):
pass
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
services.add_transient(DataSeederABC, ApiKeySeeder)
services.add_transient(BaseHelperABC, BaseHelperService)
# commands
self._dc.add_command(RestartCommand)
self._dc.add_command(ShutdownCommand)
self._dc.add_command(LogCommand)
self._dc.add_command(ApiKeyGroup)
# events