90 lines
3.9 KiB
Python
90 lines
3.9 KiB
Python
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
|
|
from govoplan_core.security import credential_envelopes as core_credential_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)
|