diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/api/serializers/__init__.py index 2b72c5ae1..085bb9bd1 100644 --- a/apiserver/plane/api/serializers/__init__.py +++ b/apiserver/plane/api/serializers/__init__.py @@ -41,6 +41,7 @@ from .issue import ( IssueLinkSerializer, IssueLiteSerializer, IssueAttachmentSerializer, + IssueSubscriberSerializer, ) from .module import ( diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py index 14782dbe5..540ea9097 100644 --- a/apiserver/plane/api/serializers/issue.py +++ b/apiserver/plane/api/serializers/issue.py @@ -19,6 +19,7 @@ from plane.db.models import ( IssueProperty, IssueBlocker, IssueAssignee, + IssueSubscriber, IssueLabel, Label, IssueBlocker, @@ -530,3 +531,14 @@ class IssueLiteSerializer(BaseSerializer): "created_at", "updated_at", ] + + +class IssueSubscriberSerializer(BaseSerializer): + project = serializers.PrimaryKeyRelatedField(read_only=True) + workspace = serializers.PrimaryKeyRelatedField(read_only=True) + issue = serializers.PrimaryKeyRelatedField(read_only=True) + subscriber = serializers.PrimaryKeyRelatedField(read_only=True) + + class Meta: + model = IssueSubscriber + fields = "__all__" diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index 936fd73ab..bf370063a 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -76,6 +76,7 @@ from plane.api.views import ( IssueLinkViewSet, BulkCreateIssueLabelsEndpoint, IssueAttachmentEndpoint, + IssueSubscriberViewSet, ## End Issues # States StateViewSet, @@ -798,6 +799,19 @@ urlpatterns = [ name="project-issue-comment", ), ## End IssueComments + # Issue Subscribers + path( + "workspaces//projects//issues//subscribers/", + IssueSubscriberViewSet.as_view( + { + "get": "list", + "post": "create", + "delete": "destroy" + } + ), + name="project-issue-subscriber", + ), + ## End Issue Subscribers ## IssueProperty path( "workspaces//projects//issue-properties/", diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index f8d170532..a3c166e80 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -65,6 +65,7 @@ from .issue import ( IssueLinkViewSet, BulkCreateIssueLabelsEndpoint, IssueAttachmentEndpoint, + IssueSubscriberViewSet, ) from .auth_extended import ( diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index cd9f65e48..85d205fed 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -42,6 +42,7 @@ from plane.api.serializers import ( IssueLinkSerializer, IssueLiteSerializer, IssueAttachmentSerializer, + IssueSubscriberSerializer, ) from plane.api.permissions import ( ProjectEntityPermission, @@ -58,6 +59,7 @@ from plane.db.models import ( IssueLink, IssueAttachment, State, + IssueSubscriber, ) from plane.bgtasks.issue_activites_task import issue_activity from plane.utils.grouper import group_results @@ -842,3 +844,29 @@ class IssueAttachmentEndpoint(BaseAPIView): {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, ) + + +class IssueSubscriberViewSet(BaseViewSet): + serializer_class = IssueSubscriberSerializer + model = IssueSubscriber + + permission_classes = [ + ProjectEntityPermission, + ] + + def perform_create(self, serializer): + serializer.save( + subscriber_id=self.request.user.id, issue_id=self.kwargs.get("issue_id") + ) + + def get_queryset(self): + return ( + super() + .get_queryset() + .filter(workspace__slug=self.kwargs.get("slug")) + .filter(project_id=self.kwargs.get("project_id")) + .filter(issue_id=self.kwargs.get("issue_id")) + .filter(project__project_projectmember__member=self.request.user) + .order_by("-created_at") + .distinct() + ) diff --git a/apiserver/plane/db/models/__init__.py b/apiserver/plane/db/models/__init__.py index 96c649a83..47585207c 100644 --- a/apiserver/plane/db/models/__init__.py +++ b/apiserver/plane/db/models/__init__.py @@ -33,6 +33,7 @@ from .issue import ( IssueLink, IssueSequence, IssueAttachment, + IssueSubscriber, ) from .asset import FileAsset diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py index 7efe86d46..ab6c65840 100644 --- a/apiserver/plane/db/models/issue.py +++ b/apiserver/plane/db/models/issue.py @@ -399,6 +399,27 @@ class IssueSequence(ProjectBaseModel): ordering = ("-created_at",) +class IssueSubscriber(ProjectBaseModel): + issue = models.ForeignKey( + Issue, on_delete=models.CASCADE, related_name="issue_subscribers" + ) + subscriber = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + related_name="issue_subscribers", + ) + + class Meta: + unique_together = ["issue", "subscriber"] + verbose_name = "Issue Subscriber" + verbose_name_plural = "Issue Subscribers" + db_table = "issue_subscribers" + ordering = ("-created_at",) + + def __str__(self): + return f"{self.issue.name} {self.subscriber.email}" + + # TODO: Find a better method to save the model @receiver(post_save, sender=Issue) def create_issue_sequence(sender, instance, created, **kwargs):