diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index a0c11d11ef8..3814466322e 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -544,6 +544,12 @@ def post(self, request, slug, project_id, cycle_id): ) cycle.archived_at = timezone.now() cycle.save() + UserFavorite.objects.filter( + entity_type="cycle", + entity_identifier=cycle_id, + project_id=project_id, + workspace__slug=slug, + ).delete() return Response(status=status.HTTP_204_NO_CONTENT) def delete(self, request, slug, project_id, cycle_id): diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py index f197e2eaa01..8c374baf6bd 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -634,6 +634,12 @@ def post(self, request, slug, project_id, pk): ) module.archived_at = timezone.now() module.save() + UserFavorite.objects.filter( + entity_type="module", + entity_identifier=pk, + project_id=project_id, + workspace__slug=slug, + ).delete() return Response(status=status.HTTP_204_NO_CONTENT) def delete(self, request, slug, project_id, pk): diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index 2f8dddd6d05..0052c9fe62c 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -377,6 +377,10 @@ def post(self, request, slug, project_id): project = Project.objects.get(pk=project_id, workspace__slug=slug) project.archived_at = timezone.now() project.save() + UserFavorite.objects.filter( + workspace__slug=slug, + project=project_id, + ).delete() return Response(status=status.HTTP_204_NO_CONTENT) def delete(self, request, slug, project_id): diff --git a/apiserver/plane/app/urls/issue.py b/apiserver/plane/app/urls/issue.py index aa6a8e2f06c..4ad7f611821 100644 --- a/apiserver/plane/app/urls/issue.py +++ b/apiserver/plane/app/urls/issue.py @@ -19,7 +19,6 @@ IssueUserDisplayPropertyEndpoint, IssueViewSet, LabelViewSet, - BulkIssueOperationsEndpoint, BulkArchiveIssuesEndpoint, ) @@ -304,10 +303,5 @@ } ), name="project-issue-draft", - ), - path( - "workspaces//projects//bulk-operation-issues/", - BulkIssueOperationsEndpoint.as_view(), - name="bulk-operations-issues", - ), + ) ] diff --git a/apiserver/plane/app/views/__init__.py b/apiserver/plane/app/views/__init__.py index 9d8929fda51..5568542f70a 100644 --- a/apiserver/plane/app/views/__init__.py +++ b/apiserver/plane/app/views/__init__.py @@ -156,9 +156,6 @@ IssueSubscriberViewSet, ) - -from .issue.bulk_operations import BulkIssueOperationsEndpoint - from .module.base import ( ModuleViewSet, ModuleLinkViewSet, diff --git a/apiserver/plane/app/views/cycle/archive.py b/apiserver/plane/app/views/cycle/archive.py index 5f7f1434738..22f21f4bf3e 100644 --- a/apiserver/plane/app/views/cycle/archive.py +++ b/apiserver/plane/app/views/cycle/archive.py @@ -607,6 +607,12 @@ def post(self, request, slug, project_id, cycle_id): cycle.archived_at = timezone.now() cycle.save() + UserFavorite.objects.filter( + entity_type="cycle", + entity_identifier=cycle_id, + project_id=project_id, + workspace__slug=slug, + ).delete() return Response( {"archived_at": str(cycle.archived_at)}, status=status.HTTP_200_OK, diff --git a/apiserver/plane/app/views/issue/archive.py b/apiserver/plane/app/views/issue/archive.py index 53dec689818..811e6f8f92d 100644 --- a/apiserver/plane/app/views/issue/archive.py +++ b/apiserver/plane/app/views/issue/archive.py @@ -47,6 +47,7 @@ SubGroupedOffsetPaginator, ) from plane.app.permissions import allow_permission, ROLE +from plane.utils.error_codes import ERROR_CODES # Module imports from .. import BaseViewSet, BaseAPIView @@ -345,7 +346,9 @@ def post(self, request, slug, project_id): if issue.state.group not in ["completed", "cancelled"]: return Response( { - "error_code": 4091, + "error_code": ERROR_CODES[ + "INVALID_ARCHIVE_STATE_GROUP" + ], "error_message": "INVALID_ARCHIVE_STATE_GROUP", }, status=status.HTTP_400_BAD_REQUEST, diff --git a/apiserver/plane/app/views/issue/bulk_operations.py b/apiserver/plane/app/views/issue/bulk_operations.py deleted file mode 100644 index 1965b4e31ee..00000000000 --- a/apiserver/plane/app/views/issue/bulk_operations.py +++ /dev/null @@ -1,293 +0,0 @@ -# Python imports -import json -from datetime import datetime - -# Django imports -from django.utils import timezone - -# Third Party imports -from rest_framework.response import Response -from rest_framework import status - -# Module imports -from .. import BaseAPIView -from plane.app.permissions import ( - ProjectEntityPermission, -) -from plane.db.models import ( - Project, - Issue, - IssueLabel, - IssueAssignee, -) -from plane.bgtasks.issue_activities_task import issue_activity - - -class BulkIssueOperationsEndpoint(BaseAPIView): - permission_classes = [ - ProjectEntityPermission, - ] - - def post(self, request, slug, project_id): - issue_ids = request.data.get("issue_ids", []) - if not len(issue_ids): - return Response( - {"error": "Issue IDs are required"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - # Get all the issues - issues = ( - Issue.objects.filter( - workspace__slug=slug, project_id=project_id, pk__in=issue_ids - ) - .select_related("state") - .prefetch_related("labels", "assignees") - ) - # Current epoch - epoch = int(timezone.now().timestamp()) - - # Project details - project = Project.objects.get(workspace__slug=slug, pk=project_id) - workspace_id = project.workspace_id - - # Initialize arrays - bulk_update_issues = [] - bulk_issue_activities = [] - bulk_update_issue_labels = [] - bulk_update_issue_assignees = [] - - properties = request.data.get("properties", {}) - - if properties.get("start_date", False) and properties.get( - "target_date", False - ): - if ( - datetime.strptime( - properties.get("start_date"), "%Y-%m-%d" - ).date() - > datetime.strptime( - properties.get("target_date"), "%Y-%m-%d" - ).date() - ): - return Response( - { - "error_code": 4100, - "error_message": "INVALID_ISSUE_DATES", - }, - status=status.HTTP_400_BAD_REQUEST, - ) - - for issue in issues: - # Priority - if properties.get("priority", False): - bulk_issue_activities.append( - { - "type": "issue.activity.updated", - "requested_data": json.dumps( - {"priority": properties.get("priority")} - ), - "current_instance": json.dumps( - {"priority": (issue.priority)} - ), - "issue_id": str(issue.id), - "actor_id": str(request.user.id), - "project_id": str(project_id), - "epoch": epoch, - } - ) - issue.priority = properties.get("priority") - - # State - if properties.get("state_id", False): - bulk_issue_activities.append( - { - "type": "issue.activity.updated", - "requested_data": json.dumps( - {"state": properties.get("state")} - ), - "current_instance": json.dumps( - {"state": str(issue.state_id)} - ), - "issue_id": str(issue.id), - "actor_id": str(request.user.id), - "project_id": str(project_id), - "epoch": epoch, - } - ) - issue.state_id = properties.get("state_id") - - # Start date - if properties.get("start_date", False): - if ( - issue.target_date - and not properties.get("target_date", False) - and issue.target_date - <= datetime.strptime( - properties.get("start_date"), "%Y-%m-%d" - ).date() - ): - return Response( - { - "error_code": 4101, - "error_message": "INVALID_ISSUE_START_DATE", - }, - status=status.HTTP_400_BAD_REQUEST, - ) - bulk_issue_activities.append( - { - "type": "issue.activity.updated", - "requested_data": json.dumps( - {"start_date": properties.get("start_date")} - ), - "current_instance": json.dumps( - {"start_date": str(issue.start_date)} - ), - "issue_id": str(issue.id), - "actor_id": str(request.user.id), - "project_id": str(project_id), - "epoch": epoch, - } - ) - issue.start_date = properties.get("start_date") - - # Target date - if properties.get("target_date", False): - if ( - issue.start_date - and not properties.get("start_date", False) - and issue.start_date - >= datetime.strptime( - properties.get("target_date"), "%Y-%m-%d" - ).date() - ): - return Response( - { - "error_code": 4102, - "error_message": "INVALID_ISSUE_TARGET_DATE", - }, - status=status.HTTP_400_BAD_REQUEST, - ) - bulk_issue_activities.append( - { - "type": "issue.activity.updated", - "requested_data": json.dumps( - {"target_date": properties.get("target_date")} - ), - "current_instance": json.dumps( - {"target_date": str(issue.target_date)} - ), - "issue_id": str(issue.id), - "actor_id": str(request.user.id), - "project_id": str(project_id), - "epoch": epoch, - } - ) - issue.target_date = properties.get("target_date") - - bulk_update_issues.append(issue) - - # Labels - if properties.get("label_ids", []): - for label_id in properties.get("label_ids", []): - bulk_update_issue_labels.append( - IssueLabel( - issue=issue, - label_id=label_id, - created_by=request.user, - project_id=project_id, - workspace_id=workspace_id, - ) - ) - bulk_issue_activities.append( - { - "type": "issue.activity.updated", - "requested_data": json.dumps( - {"label_ids": properties.get("label_ids", [])} - ), - "current_instance": json.dumps( - { - "label_ids": [ - str(label.id) - for label in issue.labels.all() - ] - } - ), - "issue_id": str(issue.id), - "actor_id": str(request.user.id), - "project_id": str(project_id), - "epoch": epoch, - } - ) - - # Assignees - if properties.get("assignee_ids", []): - for assignee_id in properties.get( - "assignee_ids", issue.assignees - ): - bulk_update_issue_assignees.append( - IssueAssignee( - issue=issue, - assignee_id=assignee_id, - created_by=request.user, - project_id=project_id, - workspace_id=workspace_id, - ) - ) - bulk_issue_activities.append( - { - "type": "issue.activity.updated", - "requested_data": json.dumps( - { - "assignee_ids": properties.get( - "assignee_ids", [] - ) - } - ), - "current_instance": json.dumps( - { - "assignee_ids": [ - str(assignee.id) - for assignee in issue.assignees.all() - ] - } - ), - "issue_id": str(issue.id), - "actor_id": str(request.user.id), - "project_id": str(project_id), - "epoch": epoch, - } - ) - - # Bulk update all the objects - Issue.objects.bulk_update( - bulk_update_issues, - [ - "priority", - "start_date", - "target_date", - "state", - ], - batch_size=100, - ) - - # Create new labels - IssueLabel.objects.bulk_create( - bulk_update_issue_labels, - ignore_conflicts=True, - batch_size=100, - ) - - # Create new assignees - IssueAssignee.objects.bulk_create( - bulk_update_issue_assignees, - ignore_conflicts=True, - batch_size=100, - ) - # update the issue activity - [ - issue_activity.delay(**activity) - for activity in bulk_issue_activities - ] - - return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apiserver/plane/app/views/module/archive.py b/apiserver/plane/app/views/module/archive.py index 243c680cad1..b38d83487c9 100644 --- a/apiserver/plane/app/views/module/archive.py +++ b/apiserver/plane/app/views/module/archive.py @@ -575,6 +575,12 @@ def post(self, request, slug, project_id, module_id): ) module.archived_at = timezone.now() module.save() + UserFavorite.objects.filter( + entity_type="module", + entity_identifier=module_id, + project_id=project_id, + workspace__slug=slug, + ).delete() return Response( {"archived_at": str(module.archived_at)}, status=status.HTTP_200_OK, diff --git a/apiserver/plane/app/views/page/base.py b/apiserver/plane/app/views/page/base.py index fdd1f2cca22..01fa6649c22 100644 --- a/apiserver/plane/app/views/page/base.py +++ b/apiserver/plane/app/views/page/base.py @@ -33,7 +33,7 @@ ProjectMember, ProjectPage, ) - +from plane.utils.error_codes import ERROR_CODES # Module imports from ..base import BaseAPIView, BaseViewSet @@ -305,6 +305,13 @@ def archive(self, request, slug, project_id, pk): status=status.HTTP_400_BAD_REQUEST, ) + UserFavorite.objects.filter( + entity_type="page", + entity_identifier=pk, + project_id=project_id, + workspace__slug=slug, + ).delete() + unarchive_archive_page_and_descendants(pk, datetime.now()) return Response( @@ -479,6 +486,11 @@ def retrieve(self, request, slug, project_id, pk): .filter(Q(owned_by=self.request.user) | Q(access=0)) .first() ) + if page is None: + return Response( + {"error": "Page not found"}, + status=404, + ) binary_data = page.description_binary def stream_data(): @@ -513,14 +525,20 @@ def partial_update(self, request, slug, project_id, pk): if page.is_locked: return Response( - {"error": "Page is locked"}, - status=471, + { + "error_code": ERROR_CODES["PAGE_LOCKED"], + "error_message": "PAGE_LOCKED", + }, + status=status.HTTP_400_BAD_REQUEST, ) if page.archived_at: return Response( - {"error": "Page is archived"}, - status=472, + { + "error_code": ERROR_CODES["PAGE_ARCHIVED"], + "error_message": "PAGE_ARCHIVED", + }, + status=status.HTTP_400_BAD_REQUEST, ) # Serialize the existing instance diff --git a/apiserver/plane/app/views/project/base.py b/apiserver/plane/app/views/project/base.py index f0497758977..ebc0e83fd8e 100644 --- a/apiserver/plane/app/views/project/base.py +++ b/apiserver/plane/app/views/project/base.py @@ -493,6 +493,10 @@ def post(self, request, slug, project_id): project = Project.objects.get(pk=project_id, workspace__slug=slug) project.archived_at = timezone.now() project.save() + UserFavorite.objects.filter( + workspace__slug=slug, + project=project_id, + ).delete() return Response( {"archived_at": str(project.archived_at)}, status=status.HTTP_200_OK, diff --git a/apiserver/plane/app/views/view/base.py b/apiserver/plane/app/views/view/base.py index c16f7ef5d8f..4a571ef2576 100644 --- a/apiserver/plane/app/views/view/base.py +++ b/apiserver/plane/app/views/view/base.py @@ -149,7 +149,7 @@ def retrieve(self, request, slug, pk): ) @allow_permission( - allowed_roles=[ROLE.ADMIN], + allowed_roles=[], level="WORKSPACE", creator=True, model=IssueView, @@ -159,19 +159,6 @@ def destroy(self, request, slug, pk): pk=pk, workspace__slug=slug, ) - if not ( - WorkspaceMember.objects.filter( - workspace__slug=slug, - member=request.user, - role=20, - is_active=True, - ).exists() - and workspace_view.owned_by_id != request.user.id - ): - return Response( - {"error": "You do not have permission to delete this view"}, - status=status.HTTP_403_FORBIDDEN, - ) workspace_member = WorkspaceMember.objects.filter( workspace__slug=slug, diff --git a/apiserver/plane/utils/error_codes.py b/apiserver/plane/utils/error_codes.py new file mode 100644 index 00000000000..15d38f6bf96 --- /dev/null +++ b/apiserver/plane/utils/error_codes.py @@ -0,0 +1,10 @@ +ERROR_CODES = { + # issues + "INVALID_ARCHIVE_STATE_GROUP": 4091, + "INVALID_ISSUE_DATES": 4100, + "INVALID_ISSUE_START_DATE": 4101, + "INVALID_ISSUE_TARGET_DATE": 4102, + # pages + "PAGE_LOCKED": 4701, + "PAGE_ARCHIVED": 4702, +}