From 9f318aa6cca707f355df5abe5a8a742c1c0714e0 Mon Sep 17 00:00:00 2001 From: Ali Asadi Date: Tue, 21 Jul 2026 14:45:10 +0330 Subject: [PATCH] filter messages on client side --- apps/chat/integrations/mattermost/client.py | 32 +++------------- apps/chat/serializers/messages.py | 1 + apps/chat/services/message.py | 2 + apps/chat/tests/test_mattermost_client.py | 42 +++++++-------------- apps/chat/tests/test_messages.py | 15 +++++++- 5 files changed, 37 insertions(+), 55 deletions(-) diff --git a/apps/chat/integrations/mattermost/client.py b/apps/chat/integrations/mattermost/client.py index bafb5ed..8f5724a 100644 --- a/apps/chat/integrations/mattermost/client.py +++ b/apps/chat/integrations/mattermost/client.py @@ -156,15 +156,6 @@ class MattermostClient: return channel_id - @staticmethod - def _is_system_post(post: dict) -> bool: - """ - Mattermost auto-generates posts like "user-x added to the channel" - (type e.g. "system_add_to_channel") whenever channel membership - changes. Real user-sent posts have an empty type. - """ - return post.get("type", "").startswith("system_") - def post_message(self, channel_id: str, message: str) -> str: """ Post a message to the given channel using the admin token. @@ -190,8 +181,8 @@ class MattermostClient: ) -> list[dict]: """ Fetch posts from a channel and return them in chronological order. - System posts (channel membership changes, etc.) are excluded — only - real user-sent posts are returned. + Includes Mattermost system posts (channel membership changes, etc.) + as-is, with their raw `type` intact — callers filter if they need to. `since` is a Unix timestamp in **milliseconds**. When provided, `page` is ignored and all posts after that timestamp are returned. @@ -211,30 +202,19 @@ class MattermostClient: posts_map: dict = data.get("posts", {}) # Mattermost returns newest-first; reverse to get chronological order. - return [ - posts_map[pid] - for pid in reversed(order) - if pid in posts_map and not self._is_system_post(posts_map[pid]) - ] + return [posts_map[pid] for pid in reversed(order) if pid in posts_map] def get_latest_post_id(self, channel_id: str) -> str | None: """ - Return the ID of the most recent real (non-system) post in the - channel, or None if there are none yet. + Return the ID of the most recent post in the channel, or None if empty. Used to derive `has_unread` against a stored last_read_mattermost_post_id. """ try: data = self._driver.posts.get_posts_for_channel( - channel_id, params={"page": 0, "per_page": 20} + channel_id, params={"page": 0, "per_page": 1} ) except Exception as exc: raise MattermostError(f"Fetching latest post failed: {exc}") from exc order: list[str] = data.get("order", []) - posts_map: dict = data.get("posts", {}) - - for pid in order: # newest-first - post = posts_map.get(pid) - if post and not self._is_system_post(post): - return pid - return None + return order[0] if order else None diff --git a/apps/chat/serializers/messages.py b/apps/chat/serializers/messages.py index fba8f8c..8c493be 100644 --- a/apps/chat/serializers/messages.py +++ b/apps/chat/serializers/messages.py @@ -12,6 +12,7 @@ class MessageSerializer(serializers.Serializer): post_id = serializers.CharField() sender_id = serializers.UUIDField(allow_null=True) message_type = serializers.CharField(default="text") + mattermost_message_type = serializers.CharField(default="", allow_blank=True) text = serializers.CharField(allow_null=True, required=False) url = serializers.CharField(allow_null=True, required=False) created_at = serializers.DateTimeField(allow_null=True, required=False) diff --git a/apps/chat/services/message.py b/apps/chat/services/message.py index 947c477..cee7faa 100644 --- a/apps/chat/services/message.py +++ b/apps/chat/services/message.py @@ -72,6 +72,7 @@ class MessageService: "post_id": post_id, "sender_id": sender_id, "message_type": message_type, + "mattermost_message_type": "", "text": text, "url": file_url, "created_at": None, @@ -118,6 +119,7 @@ class MessageService: "post_id": post.get("id"), "sender_id": sender_id, "message_type": "file" if is_url else "text", + "mattermost_message_type": post.get("type", ""), "text": None if is_url else msg, "url": msg if is_url else None, "created_at": created_at, diff --git a/apps/chat/tests/test_mattermost_client.py b/apps/chat/tests/test_mattermost_client.py index 91a1924..23d8b52 100644 --- a/apps/chat/tests/test_mattermost_client.py +++ b/apps/chat/tests/test_mattermost_client.py @@ -21,20 +21,8 @@ def _post(post_id, msg="hi", post_type="", user_id="u1"): } -def test_get_posts_excludes_system_posts(): +def test_get_posts_keeps_chronological_order(): # Mattermost order is newest-first. - posts_map = { - "p1": _post("p1", "real message"), - "sys1": _post("sys1", "user-x added to the channel", post_type="system_add_to_channel"), - } - client = _make_client({"order": ["sys1", "p1"], "posts": posts_map}) - - result = client.get_posts("chan-1") - - assert [p["id"] for p in result] == ["p1"] - - -def test_get_posts_keeps_chronological_order_with_no_system_posts(): posts_map = { "p1": _post("p1", "first"), "p2": _post("p2", "second"), @@ -46,28 +34,20 @@ def test_get_posts_keeps_chronological_order_with_no_system_posts(): assert [p["id"] for p in result] == ["p1", "p2"] -def test_get_latest_post_id_skips_leading_system_post(): - # sys1 is newest (e.g. someone was just added), p1 is the latest real message. +def test_get_posts_includes_system_posts_with_raw_type(): posts_map = { - "sys1": _post("sys1", "user-y added to the channel", post_type="system_add_to_channel"), "p1": _post("p1", "real message"), + "sys1": _post("sys1", "user-x added to the channel", post_type="system_add_to_channel"), } client = _make_client({"order": ["sys1", "p1"], "posts": posts_map}) - assert client.get_latest_post_id("chan-1") == "p1" + result = client.get_posts("chan-1") + + assert [p["id"] for p in result] == ["p1", "sys1"] + assert result[1]["type"] == "system_add_to_channel" -def test_get_latest_post_id_returns_none_when_only_system_posts_exist(): - posts_map = { - "sys1": _post("sys1", "user-x added to the channel", post_type="system_add_to_channel"), - "sys2": _post("sys2", "user-y added to the channel", post_type="system_add_to_channel"), - } - client = _make_client({"order": ["sys2", "sys1"], "posts": posts_map}) - - assert client.get_latest_post_id("chan-1") is None - - -def test_get_latest_post_id_returns_newest_real_post_when_no_system_posts(): +def test_get_latest_post_id_returns_newest_post_id(): posts_map = { "p1": _post("p1", "first"), "p2": _post("p2", "second"), @@ -75,3 +55,9 @@ def test_get_latest_post_id_returns_newest_real_post_when_no_system_posts(): client = _make_client({"order": ["p2", "p1"], "posts": posts_map}) assert client.get_latest_post_id("chan-1") == "p2" + + +def test_get_latest_post_id_returns_none_when_empty(): + client = _make_client({"order": [], "posts": {}}) + + assert client.get_latest_post_id("chan-1") is None diff --git a/apps/chat/tests/test_messages.py b/apps/chat/tests/test_messages.py index afb7fca..3b56fb6 100644 --- a/apps/chat/tests/test_messages.py +++ b/apps/chat/tests/test_messages.py @@ -90,6 +90,13 @@ def test_list_messages_normalized(): "message": "http://minio.local/chat/file.pdf", "create_at": 1700000001000, }, + { + "id": "sys1", + "type": "system_add_to_channel", + "user_id": mm_user_id, + "message": "user-x added to the channel", + "create_at": 1700000002000, + }, ] mm_client = Mock() @@ -99,12 +106,13 @@ def test_list_messages_normalized(): messages = svc.list_messages(conv.id, page=0, per_page=20) - assert len(messages) == 2 + assert len(messages) == 3 text_msg = messages[0] assert text_msg["post_id"] == "p1" assert text_msg["sender_id"] == sender_uuid assert text_msg["message_type"] == "text" + assert text_msg["mattermost_message_type"] == "" assert text_msg["text"] == "First message" assert text_msg["url"] is None @@ -112,5 +120,10 @@ def test_list_messages_normalized(): assert file_msg["post_id"] == "p2" assert file_msg["sender_id"] == sender_uuid assert file_msg["message_type"] == "file" + assert file_msg["mattermost_message_type"] == "" assert file_msg["url"] == "http://minio.local/chat/file.pdf" assert file_msg["text"] is None + + system_msg = messages[2] + assert system_msg["post_id"] == "sys1" + assert system_msg["mattermost_message_type"] == "system_add_to_channel"