# retoor <retoor@molodetz.nl>
import logging
from datetime import datetime, timezone
from typing import Any, Optional
from devplacepy.attachments import get_attachments, link_attachments
from devplacepy.database import get_table, get_blocked_uids
from devplacepy.templating import clear_messages_cache
from devplacepy.utils import (
create_mention_notifications,
create_notification,
generate_uid,
time_ago,
track_action,
)
from devplacepy.services.audit import record as audit
from devplacepy.services.correction import schedule_correction
from devplacepy.services.ai_modifier import schedule_modification
logger = logging.getLogger("messaging.persist")
MAX_CONTENT_LENGTH = 2000
def _slim_attachment(attachment: dict[str, Any]) -> dict[str, Any]:
return {
"uid": attachment["uid"],
"url": attachment["url"],
"thumbnail_url": attachment.get("thumbnail_url"),
"is_image": bool(attachment.get("is_image")),
"is_video": bool(attachment.get("is_video")),
"original_filename": attachment.get("original_filename", ""),
"file_size": attachment.get("file_size", 0),
"mime_type": attachment.get("mime_type", ""),
}
def message_frame(
message: dict[str, Any], sender_username: str, client_id: Optional[str] = None,
sender_role: Optional[str] = None,
) -> dict[str, Any]:
attachments = get_attachments("message", message["uid"])
return {
"type": "message",
"uid": message["uid"],
"sender_uid": message["sender_uid"],
"sender_username": sender_username,
"sender_role": sender_role,
"receiver_uid": message["receiver_uid"],
"content": message["content"],
"created_at": message["created_at"],
"time_ago": time_ago(message["created_at"]),
"client_id": client_id,
"attachments": [_slim_attachment(a) for a in attachments],
}
def persist_message(
sender: dict[str, Any],
receiver_uid: str,
content: str,
attachment_uids: Optional[list[str]] = None,
*,
request: Any = None,
origin: str = "web",
) -> Optional[dict[str, Any]]:
content = (content or "").strip()[:MAX_CONTENT_LENGTH]
attachment_uids = attachment_uids or []
if not content and not attachment_uids:
return None
receiver = get_table("users").find_one(uid=receiver_uid)
if not receiver:
return None
if sender["uid"] in get_blocked_uids(receiver_uid):
return None
sender_uid = sender["uid"]
sender_username = sender.get("username", "")
messages_table = get_table("messages")
msg_uid = generate_uid()
created_at = datetime.now(timezone.utc).isoformat()
messages_table.insert(
{
"uid": msg_uid,
"sender_uid": sender_uid,
"receiver_uid": receiver_uid,
"content": content,
"read": False,
"created_at": created_at,
}
)
link_attachments(attachment_uids, "message", msg_uid)
schedule_correction(sender, "messages", msg_uid, request)
schedule_modification(sender, "messages", msg_uid, request)
if sender_uid != receiver_uid:
create_notification(
receiver_uid,
"message",
f"{sender_username} sent you a message",
sender_uid,
f"/messages?with_uid={sender_uid}",
)
clear_messages_cache(receiver_uid)
create_mention_notifications(
content, sender_uid, f"/messages?with_uid={receiver_uid}"
)
track_action(sender_uid, "message")
logger.info(
"Message %s sent from %s to %s via %s",
msg_uid,
sender_username,
receiver_uid,
origin,
)
logger.debug("message %s content length=%d", msg_uid, len(content))
summary = (
f"{sender_username} sent a message to "
f"{receiver.get('username') or receiver_uid}: {content}"
)
links = [
audit.target("message", msg_uid),
audit.recipient(receiver_uid, receiver.get("username")),
]
if request is not None:
audit.record(
request,
"message.send",
user=sender,
target_type="message",
target_uid=msg_uid,
summary=summary,
metadata={"origin": origin},
links=links,
)
else:
audit.record_system(
"message.send",
actor_kind="user",
actor_uid=sender_uid,
actor_username=sender_username,
actor_role="admin" if sender.get("role") == "Admin" else "member",
origin=origin,
target_type="message",
target_uid=msg_uid,
summary=summary,
metadata={"origin": origin},
links=links,
)
return {
"uid": msg_uid,
"sender_uid": sender_uid,
"receiver_uid": receiver_uid,
"content": content,
"read": False,
"created_at": created_at,
}