Skip to content

Commit 45a556c

Browse files
committed
✨(backend) add limit on distinct reactions per comment
Implement a configurable limit (default: 15) on the number of distinct emoji reactions per comment. - Backend validation ensures the limit cannot be exceeded via API Signed-off-by: Mohamed El Amine BOUKERFA <boukerfa.ma@gmail.com>
1 parent 394fbc5 commit 45a556c

6 files changed

Lines changed: 91 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ and this project adheres to
77
## [Unreleased]
88

99
### Added
10-
10+
- ✨(backend) add limit on distinct reactions per comment #1978
1111
- ✨(backend) create a dedicated endpoint to update document content
1212
- ⚡️(backend) stream s3 file content with a dedicated endpoint
1313

src/backend/core/api/viewsets.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2828,6 +2828,7 @@ def get(self, request):
28282828
"POSTHOG_KEY",
28292829
"LANGUAGES",
28302830
"LANGUAGE_CODE",
2831+
"REACTIONS_MAX_PER_COMMENT",
28312832
"SENTRY_DSN",
28322833
"TRASHBIN_CUTOFF_DAYS",
28332834
]
@@ -2945,7 +2946,9 @@ class CommentViewSet(
29452946
permission_classes = [permissions.CommentPermission]
29462947
pagination_class = Pagination
29472948
serializer_class = serializers.CommentSerializer
2948-
queryset = models.Comment.objects.select_related("user").all()
2949+
queryset = models.Comment.objects.select_related("user").prefetch_related(
2950+
"reactions__users"
2951+
).all()
29492952

29502953
def get_queryset(self):
29512954
"""Override to filter on related resource."""
@@ -2979,9 +2982,29 @@ def reactions(self, request, *args, **kwargs):
29792982
serializer.is_valid(raise_exception=True)
29802983

29812984
if request.method == "POST":
2985+
emoji = serializer.validated_data["emoji"]
2986+
2987+
if (
2988+
not models.Reaction.objects.filter(
2989+
comment=comment, emoji=emoji
2990+
).exists()
2991+
and comment.reactions.count() >= settings.REACTIONS_MAX_PER_COMMENT
2992+
):
2993+
return drf.response.Response(
2994+
{
2995+
"emoji": [
2996+
_(
2997+
"A comment can have a maximum of %(max)d distinct reactions."
2998+
)
2999+
% {"max": settings.REACTIONS_MAX_PER_COMMENT}
3000+
]
3001+
},
3002+
status=status.HTTP_400_BAD_REQUEST,
3003+
)
3004+
29823005
reaction, created = models.Reaction.objects.get_or_create(
29833006
comment=comment,
2984-
emoji=serializer.validated_data["emoji"],
3007+
emoji=emoji,
29853008
)
29863009
if not created and reaction.users.filter(id=request.user.id).exists():
29873010
return drf.response.Response(

src/backend/core/factories.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,11 @@ class Meta:
235235
comment = factory.SubFactory(CommentFactory)
236236
emoji = "test"
237237

238+
@classmethod
239+
def generate_emojis(cls, n=10):
240+
"""Generate a list of n unique emojis."""
241+
return [fake.unique.emoji() for _ in range(n)]
242+
238243
@factory.post_generation
239244
def users(self, create, extracted, **kwargs):
240245
"""Add users to reaction from a given list of users or create one if not provided."""

src/backend/core/tests/documents/test_api_documents_comments.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,3 +876,56 @@ def test_delete_reaction_owned_by_the_current_user():
876876

877877
reaction.refresh_from_db()
878878
assert reaction.users.exists()
879+
880+
881+
def test_create_reaction_exceeds_maximum(settings):
882+
"""
883+
Users should not be able to add more than REACTIONS_MAX_PER_COMMENT
884+
(here we set it to 10) distinct emoji reactions to a comment.
885+
They should, however, be able to add themselves to an existing reaction.
886+
"""
887+
user1 = factories.UserFactory()
888+
user2 = factories.UserFactory()
889+
document = factories.DocumentFactory(
890+
link_reach="restricted",
891+
users=[(user1, models.RoleChoices.ADMIN), (user2, models.RoleChoices.ADMIN)],
892+
)
893+
thread = factories.ThreadFactory(document=document)
894+
comment = factories.CommentFactory(thread=thread)
895+
896+
client = APIClient()
897+
client.force_login(user1)
898+
899+
# Add max distinct reactions
900+
max_reactions = settings.REACTIONS_MAX_PER_COMMENT
901+
emojis = factories.ReactionFactory.generate_emojis(max_reactions + 1)
902+
for emoji in emojis[:max_reactions]:
903+
response = client.post(
904+
f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/"
905+
f"comments/{comment.id!s}/reactions/",
906+
{"emoji": emoji},
907+
)
908+
assert response.status_code == 201
909+
910+
# Attempt to add another distinct reaction
911+
response = client.post(
912+
f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/"
913+
f"comments/{comment.id!s}/reactions/",
914+
{"emoji": emojis[max_reactions]},
915+
)
916+
assert response.status_code == 400
917+
expected_message = (
918+
f"A comment can have a maximum of {max_reactions} distinct reactions."
919+
)
920+
assert response.json() == {"emoji": [expected_message]}
921+
922+
# Attempt to add user2 to one of the existing reactions (should succeed)
923+
client.force_login(user2)
924+
response = client.post(
925+
f"/api/v1.0/documents/{document.id!s}/threads/{thread.id!s}/"
926+
f"comments/{comment.id!s}/reactions/",
927+
{"emoji": emojis[0]},
928+
)
929+
assert response.status_code == 201
930+
reaction = models.Reaction.objects.get(comment=comment, emoji=emojis[0])
931+
assert reaction.users.count() == 2

src/backend/core/tests/test_api_config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def test_api_config(is_authenticated):
7575
"LANGUAGE_CODE": "en-us",
7676
"MEDIA_BASE_URL": "http://testserver/",
7777
"POSTHOG_KEY": {"id": "132456", "host": "https://eu.i.posthog-test.com"},
78+
"REACTIONS_MAX_PER_COMMENT": 15,
7879
"SENTRY_DSN": "https://sentry.test/123",
7980
"TRASHBIN_CUTOFF_DAYS": 30,
8081
"theme_customization": {},

src/backend/impress/settings.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,12 @@ class Base(Configuration):
190190
environ_prefix=None,
191191
)
192192

193+
REACTIONS_MAX_PER_COMMENT = values.IntegerValue(
194+
15,
195+
environ_name="REACTIONS_MAX_PER_COMMENT",
196+
environ_prefix=None,
197+
)
198+
193199
DOCUMENT_UNSAFE_MIME_TYPES = [
194200
# Executable Files
195201
"application/x-msdownload",

0 commit comments

Comments
 (0)