From 5dc4d6d4c086fba544085c1634663f3f4f917899 Mon Sep 17 00:00:00 2001 From: leocrm-bot Date: Thu, 2 Jul 2026 13:02:38 +0200 Subject: [PATCH] fix: activate plugins on startup + register routes + seed admin script - lifespan: load active plugins from DB, call on_activate, register routes - Fixes: Calendar/Mail/DMS/Tags/Permissions routes returning 404 - scripts/seed_admin.py: creates default tenant + admin user --- app/main.py | 36 +++++++++++++++++++ scripts/seed_admin.py | 81 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 scripts/seed_admin.py diff --git a/app/main.py b/app/main.py index 7edf374..e05d0b9 100644 --- a/app/main.py +++ b/app/main.py @@ -11,8 +11,12 @@ from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from starlette.middleware.base import BaseHTTPMiddleware +import importlib +import logging import os +logger = logging.getLogger(__name__) + from app.config import get_settings from app.core.db import close_engine, get_engine from app.core.middleware import CSRFMiddleware @@ -91,6 +95,38 @@ async def lifespan(app: FastAPI): registry.initialize(get_engine(), app) registry.discover_builtins() + # Activate plugins that are marked active in the database + from sqlalchemy import select as sa_select + from sqlalchemy.ext.asyncio import async_sessionmaker + from app.models.plugin import Plugin as PluginModel + from app.core.event_bus import get_event_bus + + event_bus = get_event_bus() + async_session = async_sessionmaker(get_engine(), expire_on_commit=False) + + async with async_session() as db: + result = await db.execute( + sa_select(PluginModel).where(PluginModel.active == True) # noqa: E712 + ) + active_plugins = result.scalars().all() + + for plugin_record in active_plugins: + plugin = registry.get_plugin(plugin_record.name) + if plugin is None: + continue + try: + await plugin.on_activate(db, container, event_bus) + # Register plugin routes into FastAPI app + for route_def in plugin.manifest.routes: + router_module = importlib.import_module(route_def.module) + router = getattr(router_module, route_def.router_attr) + app.include_router(router) + logger.info(f"Activated plugin: {plugin_record.name}") + except Exception as exc: + logger.warning(f"Failed to activate plugin {plugin_record.name}: {exc}") + + await db.commit() + yield await close_engine() diff --git a/scripts/seed_admin.py b/scripts/seed_admin.py new file mode 100644 index 0000000..ced172d --- /dev/null +++ b/scripts/seed_admin.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +"""Seed a default tenant and admin user for LeoCRM. + +Usage: python scripts/seed_admin.py + +Creates: + - Tenant: "Default Org" (slug: default) + - Admin user: admin@leocrm.local / Admin123! + +If tenant or user already exists, skips creation. +""" + +import asyncio +import sys +import os + +# Ensure app is importable +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from app.core.db import get_engine, close_engine +from app.core.auth import hash_password +from app.models.tenant import Tenant +from app.models.user import User, UserTenant +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.ext.asyncio import async_sessionmaker + + +async def seed(): + engine = get_engine() + async_session = async_sessionmaker(engine, expire_on_commit=False) + + async with async_session() as db: # type: AsyncSession + # Check if default tenant exists + result = await db.execute(select(Tenant).where(Tenant.slug == "default")) + tenant = result.scalar_one_or_none() + + if tenant is None: + tenant = Tenant(name="Default Org", slug="default") + db.add(tenant) + await db.flush() + print(f"Created tenant: {tenant.name} (slug: {tenant.slug}, id: {tenant.id})") + else: + print(f"Tenant exists: {tenant.name} (id: {tenant.id})") + + # Check if admin user exists + result = await db.execute(select(User).where(User.email == "admin@leocrm.local")) + user = result.scalar_one_or_none() + + if user is None: + user = User( + tenant_id=tenant.id, + email="admin@leocrm.local", + name="Administrator", + password_hash=hash_password("Admin123!"), + role="admin", + is_active=True, + preferences={}, + ) + db.add(user) + await db.flush() + + # Link user to tenant + ut = UserTenant(user_id=user.id, tenant_id=tenant.id, is_default=True) + db.add(ut) + await db.flush() + print(f"Created admin user: {user.email} (id: {user.id})") + else: + print(f"Admin user exists: {user.email} (id: {user.id})") + + await db.commit() + print("\nSeed complete!") + print(f" Login URL: https://crm.media-on.de/login") + print(f" Email: admin@leocrm.local") + print(f" Password: Admin123!") + + await close_engine() + + +if __name__ == "__main__": + asyncio.run(seed())