diff --git a/apiserver/plane/api/serializers/inbox.py b/apiserver/plane/api/serializers/inbox.py index df3fb9eb5..17ae8c1ed 100644 --- a/apiserver/plane/api/serializers/inbox.py +++ b/apiserver/plane/api/serializers/inbox.py @@ -8,6 +8,12 @@ class InboxIssueSerializer(BaseSerializer): model = InboxIssue fields = "__all__" read_only_fields = [ - "project", + "id", "workspace", + "project", + "issue", + "created_by", + "updated_by", + "created_at", + "updated_at", ] \ No newline at end of file diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py index 9ac0d4a17..224788ef1 100644 --- a/apiserver/plane/api/serializers/issue.py +++ b/apiserver/plane/api/serializers/issue.py @@ -42,6 +42,7 @@ class IssueSerializer(BaseSerializer): model = Issue fields = "__all__" read_only_fields = [ + "id", "workspace", "project", "created_by", @@ -65,6 +66,7 @@ class IssueSerializer(BaseSerializer): project_id=self.context.get("project_id"), is_active=True, member_id__in=data["assignees"], + is_active=True, ).values_list("member_id", flat=True) # Validate labels are from project @@ -232,8 +234,13 @@ class LabelSerializer(BaseSerializer): model = Label fields = "__all__" read_only_fields = [ + "id", "workspace", "project", + "created_by", + "updated_by", + "created_at", + "updated_at", ] @@ -242,13 +249,14 @@ class IssueLinkSerializer(BaseSerializer): model = IssueLink fields = "__all__" read_only_fields = [ + "id", "workspace", "project", + "issue", "created_by", "updated_by", "created_at", "updated_at", - "issue", ] # Validation if url already exists @@ -267,13 +275,14 @@ class IssueAttachmentSerializer(BaseSerializer): model = IssueAttachment fields = "__all__" read_only_fields = [ + "id", + "workspace", + "project", + "issue", "created_by", "updated_by", "created_at", "updated_at", - "workspace", - "project", - "issue", ] @@ -283,37 +292,21 @@ class IssueCommentSerializer(BaseSerializer): class Meta: model = IssueComment fields = "__all__" - read_only_fields = [ - "workspace", - "project", - "issue", - "created_by", - "updated_by", - "created_at", - "updated_at", - ] - - -class IssueAttachmentSerializer(BaseSerializer): - class Meta: - model = IssueAttachment - fields = "__all__" read_only_fields = [ "id", + "workspace", + "project", + "issue", "created_by", "updated_by", "created_at", "updated_at", - "workspace", - "project", - "issue", ] class IssueActivitySerializer(BaseSerializer): class Meta: model = IssueActivity - fields = "__all__" exclude = [ "created_by", "udpated_by", diff --git a/apiserver/plane/api/serializers/module.py b/apiserver/plane/api/serializers/module.py index fb2f2c870..d91ac8a62 100644 --- a/apiserver/plane/api/serializers/module.py +++ b/apiserver/plane/api/serializers/module.py @@ -33,6 +33,7 @@ class ModuleSerializer(BaseSerializer): model = Module fields = "__all__" read_only_fields = [ + "id", "workspace", "project", "created_by", diff --git a/apiserver/plane/api/serializers/project.py b/apiserver/plane/api/serializers/project.py index 61f4d6f60..932597799 100644 --- a/apiserver/plane/api/serializers/project.py +++ b/apiserver/plane/api/serializers/project.py @@ -20,8 +20,12 @@ class ProjectSerializer(BaseSerializer): model = Project fields = "__all__" read_only_fields = [ - "workspace", "id", + "workspace", + "created_at", + "updated_at", + "created_by", + "updated_by", ] def validate(self, data): diff --git a/apiserver/plane/api/urls/inbox.py b/apiserver/plane/api/urls/inbox.py index 3284fd81b..884676cbc 100644 --- a/apiserver/plane/api/urls/inbox.py +++ b/apiserver/plane/api/urls/inbox.py @@ -5,12 +5,12 @@ from plane.api.views import InboxIssueAPIEndpoint urlpatterns = [ path( - "workspaces//projects//inboxes//inbox-issues/", + "workspaces//projects//inbox-issues/", InboxIssueAPIEndpoint.as_view(), name="inbox-issue", ), path( - "workspaces//projects//inboxes//inbox-issues//", + "workspaces//projects//inbox-issues//", InboxIssueAPIEndpoint.as_view(), name="inbox-issue", ), diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index 464df736a..52458f317 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -17,7 +17,6 @@ from plane.app.permissions import ProjectEntityPermission from plane.api.serializers import ( CycleSerializer, CycleIssueSerializer, - IssueSerializer, ) from plane.bgtasks.issue_activites_task import issue_activity diff --git a/apiserver/plane/api/views/inbox.py b/apiserver/plane/api/views/inbox.py index e670578d1..5217c32e5 100644 --- a/apiserver/plane/api/views/inbox.py +++ b/apiserver/plane/api/views/inbox.py @@ -14,7 +14,7 @@ from rest_framework.response import Response from .base import BaseAPIView from plane.app.permissions import ProjectLitePermission from plane.api.serializers import InboxIssueSerializer, IssueSerializer -from plane.db.models import InboxIssue, Issue, State, ProjectMember +from plane.db.models import InboxIssue, Issue, State, ProjectMember, Project, Inbox from plane.bgtasks.issue_activites_task import issue_activity @@ -37,29 +37,39 @@ class InboxIssueAPIEndpoint(BaseAPIView): ] def get_queryset(self): - return self.filter_queryset( - super() - .get_queryset() - .filter( + inbox = Inbox.objects.filter( + workspace__slug=self.kwargs.get("slug"), + project_id=self.kwargs.get("project_id"), + ).first() + + project = Project.objects.get( + workspace__slug=self.kwargs.get("slug"), pk=self.kwargs.get("project_id") + ) + + if inbox is None and not project.inbox_view: + return InboxIssue.objects.none() + + return ( + InboxIssue.objects.filter( Q(snoozed_till__gte=timezone.now()) | Q(snoozed_till__isnull=True), workspace__slug=self.kwargs.get("slug"), project_id=self.kwargs.get("project_id"), - inbox_id=self.kwargs.get("inbox_id"), + inbox_id=inbox.id, ) .select_related("issue", "workspace", "project") .order_by(self.kwargs.get("order_by", "-created_at")) ) - def get(self, request, slug, project_id, inbox_id, pk=None): + def get(self, request, slug, project_id, pk=None): if pk: - issue_queryset = self.get_queryset().get(pk=pk) - issues_data = InboxIssueSerializer( - issue_queryset, + inbox_issue_queryset = self.get_queryset().get(pk=pk) + inbox_issue_data = InboxIssueSerializer( + inbox_issue_queryset, fields=self.fields, expand=self.expand, ).data return Response( - issues_data, + inbox_issue_data, status=status.HTTP_200_OK, ) issue_queryset = self.get_queryset() @@ -74,12 +84,30 @@ class InboxIssueAPIEndpoint(BaseAPIView): ).data, ) - def post(self, request, slug, project_id, inbox_id): + def post(self, request, slug, project_id): if not request.data.get("issue", {}).get("name", False): return Response( {"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST ) + inbox = Inbox.objects.filter( + workspace__slug=slug, project_id=project_id + ).first() + + project = Project.objects.get( + workspace__slug=slug, + pk=project_id, + ) + + # Inbox view + if inbox is None and not project.inbox_view: + return Response( + { + "error": "Inbox is not enabled for this project enable it through the project settings" + }, + status=status.HTTP_400_BAD_REQUEST, + ) + # Check for valid priority if not request.data.get("issue", {}).get("priority", "none") in [ "low", @@ -123,21 +151,45 @@ class InboxIssueAPIEndpoint(BaseAPIView): current_instance=None, epoch=int(timezone.now().timestamp()), ) + # create an inbox issue - InboxIssue.objects.create( - inbox_id=inbox_id, + inbox_issue = InboxIssue.objects.create( + inbox_id=inbox.id, project_id=project_id, issue=issue, source=request.data.get("source", "in-app"), ) - serializer = IssueSerializer(issue) + serializer = InboxIssueSerializer(inbox_issue) return Response(serializer.data, status=status.HTTP_200_OK) - def patch(self, request, slug, project_id, inbox_id, pk): - inbox_issue = InboxIssue.objects.get( - pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id + def patch(self, request, slug, project_id, pk): + inbox = Inbox.objects.filter( + workspace__slug=slug, project_id=project_id + ).first() + + project = Project.objects.get( + workspace__slug=slug, + pk=project_id, ) + + # Inbox view + if inbox is None and not project.inbox_view: + return Response( + { + "error": "Inbox is not enabled for this project enable it through the project settings" + }, + status=status.HTTP_400_BAD_REQUEST, + ) + + # Get the inbox issue + inbox_issue = InboxIssue.objects.get( + pk=pk, + workspace__slug=slug, + project_id=project_id, + inbox_id=inbox.id, + ) + # Get the project member project_member = ProjectMember.objects.get( workspace__slug=slug, @@ -145,6 +197,7 @@ class InboxIssueAPIEndpoint(BaseAPIView): member=request.user, is_active=True, ) + # Only project members admins and created_by users can access this endpoint if project_member.role <= 10 and str(inbox_issue.created_by_id) != str( request.user.id @@ -244,10 +297,33 @@ class InboxIssueAPIEndpoint(BaseAPIView): InboxIssueSerializer(inbox_issue).data, status=status.HTTP_200_OK ) - def delete(self, request, slug, project_id, inbox_id, pk): - inbox_issue = InboxIssue.objects.get( - pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id + def delete(self, request, slug, project_id, pk): + inbox = Inbox.objects.filter( + workspace__slug=slug, project_id=project_id + ).first() + + project = Project.objects.get( + workspace__slug=slug, + pk=project_id, ) + + # Inbox view + if inbox is None and not project.inbox_view: + return Response( + { + "error": "Inbox is not enabled for this project enable it through the project settings" + }, + status=status.HTTP_400_BAD_REQUEST, + ) + + # Get the inbox issue + inbox_issue = InboxIssue.objects.get( + pk=pk, + workspace__slug=slug, + project_id=project_id, + inbox_id=inbox.id, + ) + # Get the project member project_member = ProjectMember.objects.get( workspace__slug=slug, @@ -256,6 +332,7 @@ class InboxIssueAPIEndpoint(BaseAPIView): is_active=True, ) + # Check the inbox issue created if project_member.role <= 10 and str(inbox_issue.created_by_id) != str( request.user.id ): @@ -272,4 +349,4 @@ class InboxIssueAPIEndpoint(BaseAPIView): ).delete() inbox_issue.delete() - return Response(status=status.HTTP_204_NO_CONTENT) \ No newline at end of file + return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 31ebecbb9..f2ffb7f0d 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -22,7 +22,6 @@ from django.utils import timezone # Third party imports from rest_framework import status from rest_framework.response import Response -from rest_framework.parsers import MultiPartParser, FormParser # Module imports from .base import BaseAPIView, WebhookMixin @@ -48,7 +47,6 @@ from plane.api.serializers import ( LabelSerializer, IssueLinkSerializer, IssueCommentSerializer, - IssueAttachmentSerializer, IssueActivitySerializer, ) @@ -103,7 +101,6 @@ class IssueAPIEndpoint(WebhookMixin, BaseAPIView): status=status.HTTP_200_OK, ) - filters = issue_filters(request.query_params, "GET") # Custom ordering for priority and state priority_order = ["urgent", "high", "medium", "low", "none"] state_order = ["backlog", "unstarted", "started", "completed", "cancelled"] @@ -112,7 +109,6 @@ class IssueAPIEndpoint(WebhookMixin, BaseAPIView): issue_queryset = ( self.get_queryset() - .filter(**filters) .annotate(cycle_id=F("issue_cycle__cycle_id")) .annotate(module_id=F("issue_module__module_id")) .annotate( diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index 9ddc24d26..812a0072e 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -114,7 +114,7 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView): ).select_related("member"), ) ) - .order_by("sort_order", "name") + .order_by(request.GET.get("order_by", "sort_order")) ) return self.paginate( request=request, @@ -131,7 +131,6 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView): def post(self, request, slug): try: workspace = Workspace.objects.get(slug=slug) - serializer = ProjectSerializer( data={**request.data}, context={"workspace_id": workspace.id} ) diff --git a/apiserver/plane/app/urls/project.py b/apiserver/plane/app/urls/project.py index d496ccaa1..f1b4200ed 100644 --- a/apiserver/plane/app/urls/project.py +++ b/apiserver/plane/app/urls/project.py @@ -65,7 +65,7 @@ urlpatterns = [ name="project-member-invite", ), path( - "users/me/invitations/projects/", + "users/me/workspaces//projects/invitations/", UserProjectInvitationsViewset.as_view( { "get": "list",