from contextlib import asynccontextmanager from ariadne.asgi import GraphQL from ariadne.asgi.handlers import GraphQLTransportWSHandler from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.middleware.cors import CORSMiddleware from starlette.routing import WebSocketRoute from api.api import API from api.auth.keycloak_client import Keycloak from api.broadcast import broadcast from api.middleware.logging import LoggingMiddleware from api.middleware.request import RequestMiddleware from api.middleware.websocket import AuthenticatedGraphQLTransportWSHandler from api.route import Route from api_graphql.service.schema import schema from core.database.database import Database from core.database.database_settings import DatabaseSettings from core.database.db_context import DBContext from core.environment import Environment from core.logger import Logger from data.seeder.api_key_seeder import ApiKeySeeder from data.seeder.feature_flags_seeder import FeatureFlagsSeeder from data.seeder.file_hash_seeder import FileHashSeeder from data.seeder.permission_seeder import PermissionSeeder from data.seeder.role_seeder import RoleSeeder from data.seeder.settings_seeder import SettingsSeeder from data.service.migration_service import MigrationService from service.file_service import FileService logger = Logger(__name__) class Startup: @classmethod def _get_db_settings(cls): host = Environment.get("DB_HOST", str) port = Environment.get("DB_PORT", int) user = Environment.get("DB_USER", str) password = Environment.get("DB_PASSWORD", str) database = Environment.get("DB_DATABASE", str) if None in [host, port, user, password, database]: logger.fatal( "DB settings are not set correctly", EnvironmentError("DB settings are not set correctly"), ) return DatabaseSettings( host=host, port=port, user=user, password=password, database=database ) @classmethod async def _startup_db(cls): logger.info("Init DB") db = DBContext() await db.connect(cls._get_db_settings()) Database.init(db) migrations = MigrationService(db) await migrations.migrate() @staticmethod async def _seed_data(): seeders = [ SettingsSeeder, FeatureFlagsSeeder, PermissionSeeder, RoleSeeder, ApiKeySeeder, FileHashSeeder, ] for seeder in [x() for x in seeders]: await seeder.seed() @staticmethod def _startup_keycloak(): logger.info("Init Keycloak") Keycloak.init() @classmethod async def _startup_broadcast(cls): logger.info("Init Broadcast") await broadcast.connect() @classmethod async def configure_api(cls): await cls._startup_db() await FileService.clean_files() await cls._seed_data() cls._startup_keycloak() await cls._startup_broadcast() @staticmethod @asynccontextmanager async def api_lifespan(app: Starlette): await Startup.configure_api() port = Environment.get("PORT", int, 5000) logger.info(f"Start API server on port: {port}") if Environment.get_environment() == "development": logger.info(f"Playground: http://localhost:{port}/ui/playground") app.debug = Environment.get_environment() == "development" yield logger.info("Shutdown API") @classmethod def init_api(cls): logger.info("Init API") API.import_routes() API.create( Starlette( lifespan=cls.api_lifespan, routes=[ *Route.registered_routes, WebSocketRoute( "/graphql", endpoint=GraphQL( schema, websocket_handler=AuthenticatedGraphQLTransportWSHandler(), ), ), ], middleware=[ Middleware(RequestMiddleware), Middleware(LoggingMiddleware), Middleware( CORSMiddleware, allow_origins=API.get_allowed_origins(), allow_methods=["*"], allow_headers=["*"], ), ], exception_handlers={Exception: API.handle_exception}, ) ) @classmethod def configure(cls): Logger.set_level(Environment.get("LOG_LEVEL", str, "info")) Environment.set_environment(Environment.get("ENVIRONMENT", str, "production")) logger.info(f"Environment: {Environment.get_environment()}") cls.init_api()