Repository: zulip/zulip

Issue:
Topic length too long error in `bulk_set_user_topic_visibility_policy_in_database`.
We received the following error log via sentry:

> New exception: DataError: value too long for type character varying(60)

> filename: zerver/lib/user_topics.py

```py
Traceback:

             rows.filter(user_profile__in=user_profiles_seeking_visibility_policy_update).update(
                 visibility_policy=visibility_policy, last_updated=last_updated
             )

         if user_profiles_without_visibility_policy:
--->         UserTopic.objects.bulk_create(
                 UserTopic(
                     user_profile=user_profile,
                     stream_id=stream_id,
                     recipient_id=recipient_id,
                     topic_name=topic_name,
```

Update: To be explicit -- Ideally we'd have validated the topic length before getting here. We should debug which codepath is not validating and truncating topic name, hence resulted in this error.


Discussion:
## Issue:
>The error log from Sentry indicates a DataError due to a value being too long for the 'topic_name' field, which is limited to 60 characters in the 'UserTopic' model.

## Solution:
>It seems the 'topic_name' being set exceeds the character limit defined in the database schema. To address this, consider truncating or ensuring that the 'topic_name' input does not exceed 60 characters before creating the UserTopic objects.

One possible approach:
- Before creating UserTopic objects using bulk_create, truncate the 'topic_name' field to fit within the 60-character limit.
- Example code snippet (assuming 'topic_name' needs truncation):
```python
# Truncate 'topic_name' if it exceeds 60 characters
topic_name = topic_name[:60]

# Create UserTopic objects after truncation
UserTopic.objects.bulk_create(
    UserTopic(
        user_profile=user_profile,
        stream_id=stream_id,
        recipient_id=recipient_id,
        topic_name=topic_name,
        # Additional fields if applicable
    )
@prakhar1144 You have been unassigned from this issue because you have not made any updates for over 14 days. Please feel free to reclaim the issue if you decide to pick up again. Thanks!

Hello @zulip/server-misc members, this issue was labeled with the "area: topics" label, so you may want to check it out!

<!-- areaLabelAddition -->

@zulipbot claim
Hello @khanumar03!

Thanks for your interest in Zulip! You have attempted to claim an issue without the label "help wanted". You can only claim and submit pull requests for issues with the [help wanted](https://github.com/zulip/zulip/issues?q=is%3Aopen+is%3Aissue+no%3Aassignee+label%3A%22help+wanted%22) label.

If this is your first time here, we recommend reading our [guide for new contributors](https://zulip.readthedocs.io/en/latest/overview/contributing.html) before getting started.

hi @CODEWITHRIZA I fixed this by using the textwrap module to truncate the topic name to 60 characters 

here is  an  example 
 truncate_topic_name = textwrap.shorten(topic_name,width=60, placeholder="")
  
 is that all  we  have to  do ?

@zulipbot claim

Hello @charu1603!

Thanks for your interest in Zulip! You have attempted to claim an issue without the label "help wanted". You can only claim and submit pull requests for issues with the [help wanted](https://github.com/zulip/zulip/issues?q=is%3Aopen+is%3Aissue+no%3Aassignee+label%3A%22help+wanted%22) label.

If this is your first time here, we recommend reading our [guide for new contributors](https://zulip.readthedocs.io/en/latest/overview/contributing.html) before getting started.

hello @prakhar1144  @timabbott 
I am interested in solving this issue
Can you please assign this issue to me ?
Thank You : )
@Kota-Karthik This issue doesn't have a "help-wanted" label, so please consider working on other such issues. Thanks
@prakhar1144  I am interested in working on this issue
can you please assign me this?
Thank You : )

Resolving patch:
diff --git a/zerver/views/user_topics.py b/zerver/views/user_topics.py
--- a/zerver/views/user_topics.py
+++ b/zerver/views/user_topics.py
@@ -15,8 +15,9 @@
     access_stream_to_remove_visibility_policy_by_name,
     check_for_exactly_one_stream_arg,
 )
-from zerver.lib.validator import check_int, check_int_in, check_string_in
+from zerver.lib.validator import check_capped_string, check_int, check_int_in, check_string_in
 from zerver.models import UserProfile, UserTopic
+from zerver.models.constants import MAX_TOPIC_NAME_LENGTH
 
 
 def mute_topic(
@@ -66,7 +67,7 @@ def update_muted_topic(
     user_profile: UserProfile,
     stream_id: Optional[int] = REQ(json_validator=check_int, default=None),
     stream: Optional[str] = REQ(default=None),
-    topic: str = REQ(),
+    topic: str = REQ(str_validator=check_capped_string(MAX_TOPIC_NAME_LENGTH)),
     op: str = REQ(str_validator=check_string_in(["add", "remove"])),
 ) -> HttpResponse:
     check_for_exactly_one_stream_arg(stream_id=stream_id, stream=stream)
@@ -94,7 +95,7 @@ def update_user_topic(
     request: HttpRequest,
     user_profile: UserProfile,
     stream_id: int = REQ(json_validator=check_int),
-    topic: str = REQ(),
+    topic: str = REQ(str_validator=check_capped_string(MAX_TOPIC_NAME_LENGTH)),
     visibility_policy: int = REQ(json_validator=check_int_in(UserTopic.VisibilityPolicy.values)),
 ) -> HttpResponse:
     if visibility_policy == UserTopic.VisibilityPolicy.INHERIT:


Test patch:
diff --git a/zerver/tests/test_user_topics.py b/zerver/tests/test_user_topics.py
--- a/zerver/tests/test_user_topics.py
+++ b/zerver/tests/test_user_topics.py
@@ -13,6 +13,7 @@
 from zerver.lib.test_helpers import get_subscription
 from zerver.lib.user_topics import get_topic_mutes, topic_has_visibility_policy
 from zerver.models import UserProfile, UserTopic
+from zerver.models.constants import MAX_TOPIC_NAME_LENGTH
 from zerver.models.streams import get_stream
 
 
@@ -199,6 +200,12 @@ def test_muted_topic_add_invalid(self) -> None:
         result = self.api_patch(user, url, data)
         self.assert_json_error(result, "Please choose one: 'stream' or 'stream_id'.")
 
+        data = {"stream_id": stream.id, "topic": "a" * (MAX_TOPIC_NAME_LENGTH + 1), "op": "add"}
+        result = self.api_patch(user, url, data)
+        self.assert_json_error(
+            result, f"topic is too long (limit: {MAX_TOPIC_NAME_LENGTH} characters)"
+        )
+
     def test_muted_topic_remove_invalid(self) -> None:
         user = self.example_user("hamlet")
         realm = user.realm
@@ -233,6 +240,12 @@ def test_muted_topic_remove_invalid(self) -> None:
         result = self.api_patch(user, url, data)
         self.assert_json_error(result, "Please choose one: 'stream' or 'stream_id'.")
 
+        data = {"stream_id": stream.id, "topic": "a" * (MAX_TOPIC_NAME_LENGTH + 1), "op": "remove"}
+        result = self.api_patch(user, url, data)
+        self.assert_json_error(
+            result, f"topic is too long (limit: {MAX_TOPIC_NAME_LENGTH} characters)"
+        )
+
 
 class MutedTopicsTests(ZulipTestCase):
     def test_get_deactivated_muted_topic(self) -> None:
@@ -436,6 +449,17 @@ def test_muted_topic_add_invalid(self) -> None:
         result = self.api_post(user, url, data)
         self.assert_json_error(result, "Invalid stream ID")
 
+        stream = get_stream("Verona", user.realm)
+        data = {
+            "stream_id": stream.id,
+            "topic": "a" * (MAX_TOPIC_NAME_LENGTH + 1),
+            "visibility_policy": UserTopic.VisibilityPolicy.MUTED,
+        }
+        result = self.api_post(user, url, data)
+        self.assert_json_error(
+            result, f"topic is too long (limit: {MAX_TOPIC_NAME_LENGTH} characters)"
+        )
+
     def test_muted_topic_remove_invalid(self) -> None:
         user = self.example_user("hamlet")
         self.login_user(user)
@@ -450,6 +474,17 @@ def test_muted_topic_remove_invalid(self) -> None:
         result = self.api_post(user, url, data)
         self.assert_json_error(result, "Invalid stream ID")
 
+        stream = get_stream("Verona", user.realm)
+        data = {
+            "stream_id": stream.id,
+            "topic": "a" * (MAX_TOPIC_NAME_LENGTH + 1),
+            "visibility_policy": UserTopic.VisibilityPolicy.INHERIT,
+        }
+        result = self.api_post(user, url, data)
+        self.assert_json_error(
+            result, f"topic is too long (limit: {MAX_TOPIC_NAME_LENGTH} characters)"
+        )
+
 
 class UnmutedTopicsTests(ZulipTestCase):
     def test_user_ids_unmuting_topic(self) -> None: