"""Unified Search plugin class and manifest.""" from __future__ import annotations import logging from typing import Any from app.plugins.base import BasePlugin from app.plugins.manifest import PluginManifest, PluginRouteDef logger = logging.getLogger(__name__) class UnifiedSearchPlugin(BasePlugin): """Hybrid full-text and semantic search across all CRM entities.""" manifest = PluginManifest( name="unified_search", version="1.0.0", display_name="Unified Search", description=( "Hybrid full-text (PostgreSQL FTS) and semantic (pgvector) search " "with KI query understanding and RRF rank fusion across all CRM data." ), dependencies=[], routes=[ PluginRouteDef( path="/api/v1/search", module="app.plugins.builtins.unified_search.routes", router_attr="router", ), ], events=[ "mail.synced", "file.uploaded", "contact.created", "contact.updated", "company.created", "company.updated", "calendar.entry.created", ], migrations=["0001_initial.sql", "0002_embeddings.sql"], permissions=["search:read", "search:admin"], is_core=False, ) async def on_activate(self, db, service_container, event_bus) -> None: """Register search providers on activation.""" await super().on_activate(db, service_container, event_bus) try: from app.plugins.builtins.unified_search.provider_registry import ( auto_register_providers, ) await auto_register_providers(db) logger.info("Unified Search providers auto-registered") except Exception: logger.exception("Failed to auto-register search providers") async def on_deactivate(self, db, service_container, event_bus) -> None: """Clear provider registry on deactivation.""" from app.plugins.builtins.unified_search.provider_registry import ( get_search_registry, ) registry = get_search_registry() registry.clear() await super().on_deactivate(db, service_container, event_bus) # ─── Event Handlers ─── async def on_mail_synced(self, payload: dict[str, Any]) -> None: """Enqueue embedding jobs for synced mails.""" from app.core.jobs import enqueue_job mail_ids = payload.get("mail_ids", []) if mail_ids: await enqueue_job("index_mails", mail_ids) async def on_file_uploaded(self, payload: dict[str, Any]) -> None: """Enqueue file indexing job.""" from app.core.jobs import enqueue_job file_id = payload.get("file_id") if file_id: await enqueue_job("index_file", file_id) async def on_contact_created(self, payload: dict[str, Any]) -> None: from app.core.jobs import enqueue_job contact_id = payload.get("contact_id") if contact_id: await enqueue_job("index_contact", contact_id) async def on_contact_updated(self, payload: dict[str, Any]) -> None: from app.core.jobs import enqueue_job contact_id = payload.get("contact_id") if contact_id: await enqueue_job("index_contact", contact_id) async def on_company_created(self, payload: dict[str, Any]) -> None: from app.core.jobs import enqueue_job company_id = payload.get("company_id") if company_id: await enqueue_job("index_company", company_id) async def on_company_updated(self, payload: dict[str, Any]) -> None: from app.core.jobs import enqueue_job company_id = payload.get("company_id") if company_id: await enqueue_job("index_company", company_id) async def on_calendar_entry_created(self, payload: dict[str, Any]) -> None: from app.core.jobs import enqueue_job entry_id = payload.get("entry_id") if entry_id: await enqueue_job("index_event", entry_id) def get_notification_types(self) -> list[dict[str, Any]]: return [ { "type_key": "search_error", "category": "search", "label": "Suchfehler", "description": "Fehler bei der Suchausführung", "is_enabled_by_default": True, }, { "type_key": "search_reindex_complete", "category": "search", "label": "Reindex abgeschlossen", "description": "Neuindizierung abgeschlossen", "is_enabled_by_default": False, }, ]