1167644824
Check Cross-Plugin Imports / check (push) Has been cancelled
All built-in plugins should be auto-activated. The is_core flag was only set on some plugins, leaving Mail, DMS, Calendar, Automation, Unified Search etc. inactive by default.
269 lines
11 KiB
Python
269 lines
11 KiB
Python
"""System Notification plugin — converts system events into chat messages."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from app.plugins.base import BasePlugin
|
|
from app.plugins.manifest import PluginManifest, FrontendSettingsPage
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SystemNotifPlugin(BasePlugin):
|
|
"""System notifications as a participant in the kommunikation plugin."""
|
|
|
|
manifest = PluginManifest(
|
|
name="system_notif",
|
|
version="1.0.0",
|
|
display_name="System Benachrichtigungen",
|
|
description=(
|
|
"Wandelt System-Events in Chat-Nachrichten um. "
|
|
"Registriert sich als 'system' Teilnehmer am kommunikation Plugin."
|
|
),
|
|
dependencies=["kommunikation"],
|
|
routes=[],
|
|
events=[
|
|
"lead.created",
|
|
"contact.created",
|
|
"contact.updated",
|
|
"task.overdue",
|
|
"task.created",
|
|
"mail.received",
|
|
"user.created",
|
|
"workflow.completed",
|
|
"notification.created",
|
|
"backup.completed",
|
|
"backup.failed",
|
|
"conversation.created",
|
|
"participant.joined",
|
|
"participant.left",
|
|
"reaction.added",
|
|
],
|
|
migrations=[],
|
|
permissions=["system_notif:read"],
|
|
is_core=True,
|
|
settings_pages=[
|
|
FrontendSettingsPage(path='notifications', label_key='settings.notifications', label='Notifications', component='@/pages/SettingsNotifications', icon='Bell', order=40),
|
|
],
|
|
|
|
author="LeoCRM Team",
|
|
min_app_version="1.0.0",
|
|
contract_version="1.0.0")
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__()
|
|
self._system_handler = None
|
|
|
|
async def on_activate(self, db, service_container, event_bus) -> None:
|
|
"""Register as system participant and subscribe to events."""
|
|
await super().on_activate(db, service_container, event_bus)
|
|
|
|
from app.plugins.builtins.system_notif.participant_handler import SystemParticipantHandler
|
|
from app.plugins.builtins.kommunikation.contracts import get_participant_registry
|
|
|
|
self._system_handler = SystemParticipantHandler(service_container)
|
|
registry = get_participant_registry()
|
|
registry.register("system", self._system_handler)
|
|
|
|
logger.info("System notification plugin activated — registered as 'system' participant")
|
|
|
|
async def on_deactivate(self, db, service_container, event_bus) -> None:
|
|
"""Unregister participant."""
|
|
# Contract abmelden
|
|
from app.plugins.builtins.contracts import get_contract_registry
|
|
get_contract_registry().unregister(self.manifest.name)
|
|
from app.plugins.builtins.kommunikation.contracts import get_participant_registry
|
|
|
|
get_participant_registry().unregister("system")
|
|
self._system_handler = None
|
|
await super().on_deactivate(db, service_container, event_bus)
|
|
logger.info("System notification plugin deactivated")
|
|
|
|
# ─── Event Handlers ───
|
|
|
|
async def on_lead_created(self, payload: dict[str, Any]) -> None:
|
|
"""Handle lead.created event → system message."""
|
|
await self._create_system_notification(payload, event_type="lead.created")
|
|
|
|
async def on_contact_created(self, payload: dict[str, Any]) -> None:
|
|
"""Handle contact.created event → system message."""
|
|
await self._create_system_notification(payload, event_type="contact.created")
|
|
|
|
async def on_contact_updated(self, payload: dict[str, Any]) -> None:
|
|
"""Handle contact.updated event → system message."""
|
|
await self._create_system_notification(payload, event_type="contact.updated")
|
|
|
|
async def on_task_overdue(self, payload: dict[str, Any]) -> None:
|
|
"""Handle task.overdue event → system message (warning)."""
|
|
await self._create_system_notification(payload, event_type="task.overdue", severity="warning")
|
|
|
|
async def on_task_created(self, payload: dict[str, Any]) -> None:
|
|
"""Handle task.created event → system message."""
|
|
await self._create_system_notification(payload, event_type="task.created")
|
|
|
|
async def on_mail_received(self, payload: dict[str, Any]) -> None:
|
|
"""Handle mail.received event → system message."""
|
|
await self._create_system_notification(payload, event_type="mail.received")
|
|
|
|
async def on_user_created(self, payload: dict[str, Any]) -> None:
|
|
"""Handle user.created event → system message."""
|
|
await self._create_system_notification(payload, event_type="user.created")
|
|
|
|
async def on_workflow_completed(self, payload: dict[str, Any]) -> None:
|
|
"""Handle workflow.completed event → system message."""
|
|
await self._create_system_notification(payload, event_type="workflow.completed")
|
|
|
|
async def on_notification_created(self, payload: dict[str, Any]) -> None:
|
|
"""Handle notification.created event → system message.
|
|
|
|
This is the bridge from the core notification system to the kommunikation plugin.
|
|
Core code publishes 'notification.created' on the EventBus, this plugin
|
|
converts it into a comm_message in the user's System room.
|
|
"""
|
|
await self._create_system_notification(payload, event_type="notification.created")
|
|
|
|
async def on_backup_completed(self, payload: dict[str, Any]) -> None:
|
|
"""Handle backup.completed event → system message."""
|
|
await self._create_system_notification(payload, event_type="backup.completed")
|
|
|
|
async def on_backup_failed(self, payload: dict[str, Any]) -> None:
|
|
"""Handle backup.failed event → system message (error)."""
|
|
await self._create_system_notification(payload, event_type="backup.failed", severity="error")
|
|
|
|
async def on_conversation_created(self, payload: dict[str, Any]) -> None:
|
|
"""Handle conversation.created event → system message."""
|
|
await self._create_system_notification(payload, event_type="conversation.created")
|
|
|
|
async def on_participant_joined(self, payload: dict[str, Any]) -> None:
|
|
"""Handle participant.joined event → system message."""
|
|
await self._create_system_notification(payload, event_type="participant.joined")
|
|
|
|
async def on_participant_left(self, payload: dict[str, Any]) -> None:
|
|
"""Handle participant.left event → system message."""
|
|
await self._create_system_notification(payload, event_type="participant.left")
|
|
|
|
async def on_reaction_added(self, payload: dict[str, Any]) -> None:
|
|
"""Handle reaction.added event → system message."""
|
|
await self._create_system_notification(payload, event_type="reaction.added")
|
|
|
|
async def _create_system_notification(
|
|
self,
|
|
payload: dict[str, Any],
|
|
event_type: str,
|
|
severity: str = "info",
|
|
) -> None:
|
|
"""Create a system notification message in the user's System room."""
|
|
import uuid
|
|
|
|
from app.core.db import create_db_session
|
|
from app.plugins.builtins.kommunikation.contracts import create_plugin_room, send_message
|
|
|
|
tenant_id_str = payload.get("tenant_id")
|
|
user_id_str = payload.get("user_id")
|
|
|
|
if not tenant_id_str or not user_id_str:
|
|
logger.warning("System notification missing tenant_id or user_id in payload: %s", payload)
|
|
return
|
|
|
|
try:
|
|
tenant_id = uuid.UUID(tenant_id_str)
|
|
user_id = uuid.UUID(user_id_str)
|
|
except (ValueError, TypeError):
|
|
logger.warning("System notification invalid UUIDs: tenant=%s user=%s", tenant_id_str, user_id_str)
|
|
return
|
|
|
|
# Build notification content from payload
|
|
title = payload.get("title", "")
|
|
body = payload.get("body", "")
|
|
action_url = payload.get("action_url", "")
|
|
|
|
if not title:
|
|
# Generate title from event type
|
|
event_titles = {
|
|
"lead.created": "Neuer Lead",
|
|
"contact.created": "Neuer Kontakt",
|
|
"contact.updated": "Kontakt aktualisiert",
|
|
"task.overdue": "Aufgabe überfällig",
|
|
"task.created": "Neue Aufgabe",
|
|
"mail.received": "Neue E-Mail",
|
|
"user.created": "Neuer Benutzer",
|
|
"workflow.completed": "Workflow abgeschlossen",
|
|
"notification.created": "Benachrichtigung",
|
|
"backup.completed": "Backup erfolgreich",
|
|
"backup.failed": "Backup fehlgeschlagen",
|
|
"conversation.created": "Neue Konversation",
|
|
"participant.joined": "Teilnehmer beigetreten",
|
|
"participant.left": "Teilnehmer verlassen",
|
|
"reaction.added": "Reaktion hinzugefügt",
|
|
}
|
|
title = event_titles.get(event_type, event_type)
|
|
|
|
content = f"**{title}**"
|
|
if body:
|
|
content += f"\n{body}"
|
|
|
|
# Build action_card block if action_url is present
|
|
blocks = []
|
|
if action_url:
|
|
blocks.append({
|
|
"block_type": "action_card",
|
|
"block_data": {
|
|
"title": title,
|
|
"body": body or "",
|
|
"actions": [
|
|
{"label": "Öffnen", "action": action_url, "type": "primary"},
|
|
{"label": "Archivieren", "action": "dismiss", "type": "secondary"},
|
|
],
|
|
},
|
|
})
|
|
|
|
async with create_db_session(tenant_id) as db:
|
|
# Ensure System room exists for this user
|
|
await create_plugin_room(
|
|
db, tenant_id, user_id,
|
|
plugin_name="system_notif",
|
|
title="System",
|
|
participant_type="system",
|
|
user_role="reader",
|
|
)
|
|
|
|
# Find the System room conversation
|
|
from sqlalchemy import select
|
|
from app.plugins.builtins.kommunikation.contracts import CommConversation, CommParticipant
|
|
|
|
result = await db.execute(
|
|
select(CommConversation).where(
|
|
CommConversation.tenant_id == tenant_id,
|
|
CommConversation.title == "System",
|
|
CommConversation.is_locked == True,
|
|
CommConversation.locked_by == "system_notif",
|
|
CommConversation.deleted_at.is_(None),
|
|
).join(CommParticipant, CommParticipant.conversation_id == CommConversation.id).where(
|
|
CommParticipant.participant_id == user_id,
|
|
CommParticipant.participant_type == "user",
|
|
CommParticipant.left_at.is_(None),
|
|
)
|
|
)
|
|
conv = result.scalar_one_or_none()
|
|
if conv is None:
|
|
logger.warning("Could not find System room for user %s", user_id)
|
|
return
|
|
|
|
# Send the system message
|
|
await send_message(
|
|
db, tenant_id, conv.id,
|
|
sender_id=None,
|
|
sender_type="system",
|
|
content=content,
|
|
content_format="markdown",
|
|
blocks=blocks if blocks else None,
|
|
metadata={"event_type": event_type, "severity": severity},
|
|
)
|
|
|
|
logger.info("System notification created: %s for user %s", event_type, user_id)
|
|
|
|
def get_notification_types(self) -> list[dict[str, Any]]:
|
|
return []
|