0.3 #146
@ -11,13 +11,13 @@
|
||||
"boot-log": "src/modules/boot_log/boot-log.json",
|
||||
"database": "src/modules/database/database.json",
|
||||
"moderator": "src/modules/moderator/moderator.json",
|
||||
"permission": "src/modules/permission/permission.json"
|
||||
"permission": "src/modules/permission/permission.json",
|
||||
"bot-api": "src/bot_api/bot-api.json"
|
||||
},
|
||||
"Scripts": {
|
||||
"prod": "export KDB_ENVIRONMENT=production; export KDB_NAME=KDB-Prod; cpl start;",
|
||||
"stage": "export KDB_ENVIRONMENT=staging; export KDB_NAME=KDB-Stage; cpl start;",
|
||||
"dev": "export KDB_ENVIRONMENT=development; export KDB_NAME=KDB-Dev; cpl start;",
|
||||
|
||||
"build-docker": "cpl b; docker-compose down; docker build -t kdb .",
|
||||
"compose": "docker-compose up -d",
|
||||
"docker": "cpl build-docker; cpl compose;"
|
||||
|
@ -7,6 +7,10 @@ from cpl_discord.configuration import DiscordBotSettings
|
||||
from cpl_discord.service import DiscordBotServiceABC, DiscordBotService
|
||||
from cpl_translation import TranslatePipe, TranslationServiceABC, TranslationSettings
|
||||
|
||||
from bot_api.api_thread import ApiThread
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
|
||||
|
||||
class Application(DiscordBotApplicationABC):
|
||||
|
||||
@ -14,6 +18,7 @@ class Application(DiscordBotApplicationABC):
|
||||
DiscordBotApplicationABC.__init__(self, config, services)
|
||||
|
||||
self._services = services
|
||||
self._config = config
|
||||
|
||||
# cpl-core
|
||||
self._logger: LoggerABC = services.get_service(LoggerABC)
|
||||
@ -24,6 +29,12 @@ class Application(DiscordBotApplicationABC):
|
||||
self._translation: TranslationServiceABC = services.get_service(TranslationServiceABC)
|
||||
self._t: TranslatePipe = services.get_service(TranslatePipe)
|
||||
|
||||
self._feature_flags: FeatureFlagsSettings = config.get_configuration(FeatureFlagsSettings)
|
||||
|
||||
# api
|
||||
if self._feature_flags.get_flag(FeatureFlagsEnum.api_module):
|
||||
self._api: ApiThread = services.get_service(ApiThread)
|
||||
|
||||
self._is_stopping = False
|
||||
|
||||
async def configure(self):
|
||||
@ -32,6 +43,12 @@ class Application(DiscordBotApplicationABC):
|
||||
async def main(self):
|
||||
try:
|
||||
self._logger.debug(__name__, f'Starting...')
|
||||
if self._feature_flags.get_flag(FeatureFlagsEnum.api_module):
|
||||
self._api.start()
|
||||
|
||||
if self._feature_flags.get_flag(FeatureFlagsEnum.api_only):
|
||||
return
|
||||
|
||||
self._logger.trace(__name__, f'Try to start {DiscordBotService.__name__}')
|
||||
await self._bot.start_async()
|
||||
await self._bot.stop_async()
|
||||
@ -53,4 +70,4 @@ class Application(DiscordBotApplicationABC):
|
||||
Console.write_line()
|
||||
|
||||
def is_restart(self):
|
||||
return True if self._configuration.get_configuration('IS_RESTART') == 'true' else False#
|
||||
return True if self._configuration.get_configuration('IS_RESTART') == 'true' else False #
|
||||
|
@ -12,6 +12,12 @@
|
||||
"FileLogLevel": "TRACE"
|
||||
},
|
||||
"BotLoggingSettings": {
|
||||
"Api": {
|
||||
"Path": "logs/",
|
||||
"Filename": "api.log",
|
||||
"ConsoleLogLevel": "TRACE",
|
||||
"FileLogLevel": "TRACE"
|
||||
},
|
||||
"Command": {
|
||||
"Path": "logs/",
|
||||
"Filename": "commands.log",
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"FeatureFlags": {
|
||||
"ApiModule": true,
|
||||
"AdminModule": true,
|
||||
"AutoRoleModule": true,
|
||||
"BaseModule": true,
|
||||
|
@ -1,5 +1,6 @@
|
||||
from cpl_query.extension import List
|
||||
|
||||
from bot_api.api_module import ApiModule
|
||||
from bot_core.core_extension.core_extension_module import CoreExtensionModule
|
||||
from bot_core.core_module import CoreModule
|
||||
from bot_data.data_module import DataModule
|
||||
@ -20,6 +21,7 @@ class ModuleList:
|
||||
return List(type, [
|
||||
CoreModule, # has to be first!
|
||||
DataModule,
|
||||
ApiModule,
|
||||
AdminModule,
|
||||
AutoRoleModule,
|
||||
BaseModule,
|
||||
|
@ -9,6 +9,7 @@ from cpl_core.dependency_injection import ServiceProviderABC
|
||||
from cpl_core.environment import ApplicationEnvironment
|
||||
from cpl_core.logging import LoggerABC
|
||||
|
||||
from bot_api.logging.api_logger import ApiLogger
|
||||
from bot_core.abc.custom_file_logger_abc import CustomFileLoggerABC
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
@ -40,6 +41,9 @@ class Startup(StartupABC):
|
||||
services.add_singleton(CustomFileLoggerABC, DatabaseLogger)
|
||||
services.add_singleton(CustomFileLoggerABC, MessageLogger)
|
||||
|
||||
if self._feature_flags.get_flag(FeatureFlagsEnum.api_module):
|
||||
services.add_singleton(CustomFileLoggerABC, ApiLogger)
|
||||
|
||||
services.add_translation()
|
||||
services.add_db_context(DBContext, self._config.get_configuration(DatabaseSettings))
|
||||
|
||||
|
1
src/bot_api/__init__.py
Normal file
1
src/bot_api/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# imports:
|
52
src/bot_api/api.py
Normal file
52
src/bot_api/api.py
Normal file
@ -0,0 +1,52 @@
|
||||
import sys
|
||||
from functools import partial
|
||||
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
from flask import Flask, request
|
||||
|
||||
from bot_api.logging.api_logger import ApiLogger
|
||||
from bot_api.route.route import Route
|
||||
|
||||
|
||||
class Api(Flask):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
logger: ApiLogger,
|
||||
services: ServiceProviderABC,
|
||||
*args, **kwargs
|
||||
):
|
||||
if not args:
|
||||
kwargs.setdefault('import_name', __name__)
|
||||
|
||||
Flask.__init__(self, *args, **kwargs)
|
||||
|
||||
self._logger = logger
|
||||
self._services = services
|
||||
|
||||
# register before request
|
||||
self.before_request_funcs.setdefault(None, []).append(self.before_request)
|
||||
|
||||
def _register_routes(self):
|
||||
for path, f in Route.registered_routes.items():
|
||||
cls = None
|
||||
qual_name_split = f.__qualname__.split('.')
|
||||
if len(qual_name_split) > 0:
|
||||
cls_type = vars(sys.modules[f.__module__])[qual_name_split[0]]
|
||||
cls = self._services.get_service(cls_type)
|
||||
|
||||
partial_f = partial(f, self if cls is None else cls)
|
||||
partial_f.__name__ = f.__name__
|
||||
self.route(path)(partial_f)
|
||||
|
||||
def before_request(self, *args, **kwargs):
|
||||
self._logger.debug(__name__, f'Received GET @{request.url}')
|
||||
headers = str(request.headers).replace("\n", "\n\t")
|
||||
self._logger.trace(__name__, f'Headers: \n\t{headers}')
|
||||
self._logger.trace(__name__, f'Body: {request.get_json(force=True, silent=True)}')
|
||||
|
||||
def start(self):
|
||||
self._logger.info(__name__, f'Starting API')
|
||||
self._register_routes()
|
||||
from waitress import serve
|
||||
serve(self, host="0.0.0.0", port=5000)
|
26
src/bot_api/api_module.py
Normal file
26
src/bot_api/api_module.py
Normal file
@ -0,0 +1,26 @@
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.dependency_injection import ServiceCollectionABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
|
||||
from flask import Flask
|
||||
|
||||
from bot_api.api import Api
|
||||
from bot_api.api_thread import ApiThread
|
||||
from bot_api.controller.api_controller import ApiController
|
||||
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):
|
||||
pass
|
||||
|
||||
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
|
||||
services.add_singleton(ApiThread)
|
||||
services.add_singleton(Flask, Api)
|
||||
|
||||
services.add_transient(ApiController)
|
27
src/bot_api/api_thread.py
Normal file
27
src/bot_api/api_thread.py
Normal file
@ -0,0 +1,27 @@
|
||||
import threading
|
||||
|
||||
from bot_api.api import Api
|
||||
from bot_api.logging.api_logger import ApiLogger
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
|
||||
|
||||
class ApiThread(threading.Thread):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
logger: ApiLogger,
|
||||
api: Api,
|
||||
feature_flags: FeatureFlagsSettings
|
||||
):
|
||||
threading.Thread.__init__(self, daemon=not feature_flags.get_flag(FeatureFlagsEnum.api_only))
|
||||
|
||||
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)
|
48
src/bot_api/bot-api.json
Normal file
48
src/bot_api/bot-api.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"ProjectSettings": {
|
||||
"Name": "bot-api",
|
||||
"Version": {
|
||||
"Major": "0",
|
||||
"Minor": "0",
|
||||
"Micro": "0"
|
||||
},
|
||||
"Author": "",
|
||||
"AuthorEmail": "",
|
||||
"Description": "",
|
||||
"LongDescription": "",
|
||||
"URL": "",
|
||||
"CopyrightDate": "",
|
||||
"CopyrightName": "",
|
||||
"LicenseName": "",
|
||||
"LicenseDescription": "",
|
||||
"Dependencies": [
|
||||
"cpl-core==2022.10.0.post6",
|
||||
"Flask==2.2.2",
|
||||
"Flask-Classful==0.14.2"
|
||||
],
|
||||
"DevDependencies": [
|
||||
"cpl-cli>=2022.10.0"
|
||||
],
|
||||
"PythonVersion": ">=3.10.4",
|
||||
"PythonPath": {
|
||||
"linux": ""
|
||||
},
|
||||
"Classifiers": []
|
||||
},
|
||||
"BuildSettings": {
|
||||
"ProjectType": "library",
|
||||
"SourcePath": "",
|
||||
"OutputPath": "../../dist",
|
||||
"Main": "bot_api.main",
|
||||
"EntryPoint": "bot-api",
|
||||
"IncludePackageData": false,
|
||||
"Included": [],
|
||||
"Excluded": [
|
||||
"*/__pycache__",
|
||||
"*/logs",
|
||||
"*/tests"
|
||||
],
|
||||
"PackageData": {},
|
||||
"ProjectReferences": []
|
||||
}
|
||||
}
|
0
src/bot_api/controller/__init__.py
Normal file
0
src/bot_api/controller/__init__.py
Normal file
22
src/bot_api/controller/api_controller.py
Normal file
22
src/bot_api/controller/api_controller.py
Normal file
@ -0,0 +1,22 @@
|
||||
from cpl_translation import TranslatePipe
|
||||
|
||||
from bot_api.api import Api
|
||||
from bot_api.logging.api_logger import ApiLogger
|
||||
from bot_api.route.route import Route
|
||||
|
||||
|
||||
class ApiController:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
logger: ApiLogger,
|
||||
t: TranslatePipe,
|
||||
api: Api
|
||||
):
|
||||
self._logger = logger
|
||||
self._t = t
|
||||
self._api = api
|
||||
|
||||
@Route.route('/api/hello-world')
|
||||
def hello_world(self):
|
||||
return self._t.transform('common.hello_world')
|
0
src/bot_api/controller/api_route.py
Normal file
0
src/bot_api/controller/api_route.py
Normal file
0
src/bot_api/logging/__init__.py
Normal file
0
src/bot_api/logging/__init__.py
Normal file
11
src/bot_api/logging/api_logger.py
Normal file
11
src/bot_api/logging/api_logger.py
Normal file
@ -0,0 +1,11 @@
|
||||
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)
|
0
src/bot_api/route/__init__.py
Normal file
0
src/bot_api/route/__init__.py
Normal file
11
src/bot_api/route/route.py
Normal file
11
src/bot_api/route/route.py
Normal file
@ -0,0 +1,11 @@
|
||||
class Route:
|
||||
registered_routes = {}
|
||||
|
||||
@classmethod
|
||||
def route(cls, path=None):
|
||||
# simple decorator for class based views
|
||||
def inner(fn):
|
||||
cls.registered_routes[path] = fn
|
||||
return fn
|
||||
|
||||
return inner
|
@ -4,6 +4,7 @@ from enum import Enum
|
||||
class FeatureFlagsEnum(Enum):
|
||||
|
||||
# modules
|
||||
api_module = 'ApiModule'
|
||||
admin_module = 'AdminModule'
|
||||
auto_role_module = 'AutoRoleModule'
|
||||
base_module = 'BaseModule'
|
||||
@ -15,4 +16,5 @@ class FeatureFlagsEnum(Enum):
|
||||
moderator_module = 'ModeratorModule'
|
||||
permission_module = 'PermissionModule'
|
||||
# features
|
||||
api_only = 'ApiOnly'
|
||||
presence = 'Presence'
|
||||
|
@ -14,6 +14,7 @@ class FeatureFlagsSettings(ConfigurationModelABC):
|
||||
|
||||
self._flags = {
|
||||
# modules
|
||||
FeatureFlagsEnum.api_module.value: False, # 13.10.2022 #70
|
||||
FeatureFlagsEnum.admin_module.value: False, # 02.10.2022 #48
|
||||
FeatureFlagsEnum.auto_role_module.value: True, # 03.10.2022 #54
|
||||
FeatureFlagsEnum.base_module.value: True, # 02.10.2022 #48
|
||||
@ -25,6 +26,7 @@ class FeatureFlagsSettings(ConfigurationModelABC):
|
||||
FeatureFlagsEnum.moderator_module.value: False, # 02.10.2022 #48
|
||||
FeatureFlagsEnum.permission_module.value: True, # 02.10.2022 #48
|
||||
# features
|
||||
FeatureFlagsEnum.api_only.value: False, # 13.10.2022 #70
|
||||
FeatureFlagsEnum.presence.value: True, # 03.10.2022 #56
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user