"""Participant management for the kommunikation plugin.""" from __future__ import annotations import logging import uuid from datetime import UTC, datetime from typing import Any from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.core.event_bus import get_event_bus from app.plugins.builtins.kommunikation.models import ( CommParticipant, ) from app.plugins.builtins.kommunikation.serializers import participant_to_response logger = logging.getLogger(__name__) # ─── participants ─── async def add_participant( db: AsyncSession, tenant_id: uuid.UUID, conversation_id: uuid.UUID, participant_id: str, participant_type: str = "user", role: str = "member", display_name: str | None = None, ) -> dict[str, Any] | None: """Add a participant to a conversation.""" try: pid = uuid.UUID(participant_id) if participant_type == "user" else None except ValueError: return None existing = await db.execute( select(CommParticipant).where( CommParticipant.conversation_id == conversation_id, CommParticipant.participant_id == pid if pid else CommParticipant.participant_type == participant_type, CommParticipant.participant_type == participant_type, CommParticipant.left_at.is_(None), ) ) if existing.scalar_one_or_none() is not None: return None # Already a participant p = CommParticipant( tenant_id=tenant_id, conversation_id=conversation_id, participant_id=pid, participant_type=participant_type, role=role, display_name=display_name, ) db.add(p) await db.flush() # Publish event event_bus = get_event_bus() await event_bus.publish("participant.joined", { "conversation_id": str(conversation_id), "participant_id": participant_id, "participant_type": participant_type, "tenant_id": str(tenant_id), }) return participant_to_response(p) async def remove_participant( db: AsyncSession, conversation_id: uuid.UUID, participant_id: uuid.UUID, ) -> bool: """Remove a participant from a conversation (set left_at).""" result = await db.execute( select(CommParticipant).where( CommParticipant.conversation_id == conversation_id, CommParticipant.participant_id == participant_id, CommParticipant.participant_type == "user", CommParticipant.left_at.is_(None), ) ) p = result.scalar_one_or_none() if p is None: return False p.left_at = datetime.now(UTC) await db.flush() event_bus = get_event_bus() await event_bus.publish("participant.left", { "conversation_id": str(conversation_id), "participant_id": str(participant_id), }) return True async def change_role( db: AsyncSession, conversation_id: uuid.UUID, participant_id: uuid.UUID, new_role: str, ) -> dict[str, Any] | None: """Change a participant's role.""" result = await db.execute( select(CommParticipant).where( CommParticipant.conversation_id == conversation_id, CommParticipant.participant_id == participant_id, CommParticipant.participant_type == "user", CommParticipant.left_at.is_(None), ) ) p = result.scalar_one_or_none() if p is None: return None p.role = new_role await db.flush() return participant_to_response(p) # ─── Messages ───