from __future__ import annotations from dataclasses import dataclass from sqlalchemy.orm import Session from govoplan_core.admin.common import AdminValidationError from govoplan_core.core.access import CAPABILITY_ACCESS_TENANT_PROVISIONER, CreatedApiKeyRef, TenantAccessProvisioner, UserRef from govoplan_core.core.modules import ModuleContext from govoplan_core.core.runtime import configure_runtime, get_registry from govoplan_core.db.base import Base from govoplan_core.db.session import get_database from govoplan_core.core.module_management import load_startup_enabled_modules, startup_candidate_module_ids from govoplan_core.security.permissions import TENANT_SCOPES from govoplan_core.server.registry import available_module_manifests, build_platform_registry from govoplan_core.settings import settings from govoplan_core.tenancy.scope import Tenant from govoplan_core.tenancy.scope import create_scope_tables DEFAULT_SCOPES = sorted(TENANT_SCOPES) @dataclass(slots=True) class BootstrapResult: tenant: Tenant user: UserRef created_api_key: CreatedApiKeyRef | None def create_all_tables() -> None: # Build the configured registry so enabled module manifests register their # model metadata with the shared SQLAlchemy base before create_all runs. from govoplan_core.admin import models as core_admin_models # noqa: F401 from govoplan_core.core import change_sequence as core_change_sequence_models # noqa: F401 raw_enabled_modules = load_startup_enabled_modules(settings.enabled_modules) candidate_modules = startup_candidate_module_ids(settings.enabled_modules, raw_enabled_modules) available_modules = available_module_manifests(enabled_modules=candidate_modules, ignore_load_errors=True) enabled_modules = load_startup_enabled_modules(settings.enabled_modules, available=available_modules) build_platform_registry(enabled_modules) engine = get_database().engine create_scope_tables(engine) Base.metadata.create_all(bind=engine) def _access_provisioner() -> TenantAccessProvisioner: registry = get_registry() if registry is None: registry = build_platform_registry(settings.enabled_modules) context = ModuleContext(registry=registry, settings=settings) registry.configure_capability_context(context) configure_runtime(context) if registry is None or not hasattr(registry, "has_capability") or not registry.has_capability(CAPABILITY_ACCESS_TENANT_PROVISIONER): raise RuntimeError("Development bootstrap requires the access tenant provisioner capability.") capability = registry.require_capability(CAPABILITY_ACCESS_TENANT_PROVISIONER) if not isinstance(capability, TenantAccessProvisioner): raise RuntimeError("Access tenant provisioner capability is invalid.") return capability def bootstrap_dev_data( session: Session, *, api_key_secret: str | None = None, tenant_slug: str = "default", user_email: str = "admin@example.local", user_password: str = "dev-admin", # noqa: S107 - development bootstrap only. ) -> BootstrapResult: tenant = session.query(Tenant).filter(Tenant.slug == tenant_slug).one_or_none() if tenant is None: tenant = Tenant(slug=tenant_slug, name="Default Tenant", default_locale="en", settings={}) session.add(tenant) session.flush() try: result = _access_provisioner().ensure_development_admin( session, tenant=tenant, user_email=user_email, user_password=user_password, api_key_secret=api_key_secret, scopes=DEFAULT_SCOPES, ) except AdminValidationError as exc: raise RuntimeError(str(exc)) from exc session.commit() return BootstrapResult(tenant=tenant, user=result.user, created_api_key=result.created_api_key)