refactor(kommunikation): split god-object services.py into 6 focused sub-modules with re-export facade — behavior identical (comm suite 132P/1F/6E pre-existing, failures byte-identical to pre-split baseline)
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
"""Reactions and read-state handling for the kommunikation plugin."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.event_bus import get_event_bus
|
||||
from app.plugins.builtins.kommunikation.models import (
|
||||
CommMessage,
|
||||
CommMessageReaction,
|
||||
CommMessageRead,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
# ─── interactions ───
|
||||
|
||||
|
||||
|
||||
async def add_reaction(
|
||||
db: AsyncSession,
|
||||
tenant_id: uuid.UUID,
|
||||
message_id: uuid.UUID,
|
||||
user_id: uuid.UUID,
|
||||
emoji: str,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Add an emoji reaction to a message."""
|
||||
existing = await db.execute(
|
||||
select(CommMessageReaction).where(
|
||||
CommMessageReaction.message_id == message_id,
|
||||
CommMessageReaction.user_id == user_id,
|
||||
CommMessageReaction.emoji == emoji,
|
||||
)
|
||||
)
|
||||
if existing.scalar_one_or_none() is not None:
|
||||
return None # Already reacted
|
||||
|
||||
r = CommMessageReaction(
|
||||
tenant_id=tenant_id,
|
||||
message_id=message_id,
|
||||
user_id=user_id,
|
||||
emoji=emoji,
|
||||
)
|
||||
db.add(r)
|
||||
await db.flush()
|
||||
|
||||
event_bus = get_event_bus()
|
||||
await event_bus.publish("reaction.added", {
|
||||
"message_id": str(message_id),
|
||||
"emoji": emoji,
|
||||
"user_id": str(user_id),
|
||||
})
|
||||
|
||||
return {
|
||||
"id": str(r.id),
|
||||
"message_id": str(r.message_id),
|
||||
"user_id": str(r.user_id),
|
||||
"emoji": r.emoji,
|
||||
}
|
||||
|
||||
|
||||
async def remove_reaction(
|
||||
db: AsyncSession,
|
||||
message_id: uuid.UUID,
|
||||
user_id: uuid.UUID,
|
||||
emoji: str,
|
||||
) -> bool:
|
||||
"""Remove an emoji reaction."""
|
||||
result = await db.execute(
|
||||
select(CommMessageReaction).where(
|
||||
CommMessageReaction.message_id == message_id,
|
||||
CommMessageReaction.user_id == user_id,
|
||||
CommMessageReaction.emoji == emoji,
|
||||
)
|
||||
)
|
||||
r = result.scalar_one_or_none()
|
||||
if r is None:
|
||||
return False
|
||||
await db.delete(r)
|
||||
await db.flush()
|
||||
return True
|
||||
|
||||
|
||||
# ─── Read State ───
|
||||
|
||||
|
||||
async def mark_read(
|
||||
db: AsyncSession,
|
||||
tenant_id: uuid.UUID,
|
||||
conversation_id: uuid.UUID,
|
||||
user_id: uuid.UUID,
|
||||
last_read_msg_id: str | None = None,
|
||||
) -> bool:
|
||||
"""Mark conversation as read up to a message."""
|
||||
result = await db.execute(
|
||||
select(CommMessageRead).where(
|
||||
CommMessageRead.conversation_id == conversation_id,
|
||||
CommMessageRead.user_id == user_id,
|
||||
)
|
||||
)
|
||||
read = result.scalar_one_or_none()
|
||||
|
||||
msg_id = uuid.UUID(last_read_msg_id) if last_read_msg_id else None
|
||||
|
||||
if read is None:
|
||||
read = CommMessageRead(
|
||||
tenant_id=tenant_id,
|
||||
conversation_id=conversation_id,
|
||||
user_id=user_id,
|
||||
last_read_msg_id=msg_id,
|
||||
)
|
||||
db.add(read)
|
||||
else:
|
||||
read.last_read_msg_id = msg_id
|
||||
read.last_read_at = datetime.now(UTC)
|
||||
|
||||
await db.flush()
|
||||
return True
|
||||
|
||||
|
||||
async def _get_unread_count(
|
||||
db: AsyncSession,
|
||||
tenant_id: uuid.UUID,
|
||||
conversation_id: uuid.UUID,
|
||||
user_id: uuid.UUID,
|
||||
) -> int:
|
||||
"""Get unread message count for a user in a conversation."""
|
||||
# Get last read message
|
||||
read_result = await db.execute(
|
||||
select(CommMessageRead).where(
|
||||
CommMessageRead.conversation_id == conversation_id,
|
||||
CommMessageRead.user_id == user_id,
|
||||
)
|
||||
)
|
||||
read = read_result.scalar_one_or_none()
|
||||
|
||||
query = select(func.count()).select_from(CommMessage).where(
|
||||
CommMessage.conversation_id == conversation_id,
|
||||
CommMessage.tenant_id == tenant_id,
|
||||
CommMessage.deleted_at.is_(None),
|
||||
CommMessage.sender_type != "system", # Don't count system messages? Or count all?
|
||||
)
|
||||
|
||||
if read and read.last_read_at:
|
||||
query = query.where(CommMessage.created_at > read.last_read_at)
|
||||
|
||||
result = await db.execute(query)
|
||||
return result.scalar() or 0
|
||||
|
||||
|
||||
# ─── Plugin Room Creation ───
|
||||
Reference in New Issue
Block a user