mirror of
https://github.com/makeplane/plane.git
synced 2026-02-25 04:35:21 +01:00
[WIKI-635] feat: page comments in teamspace (#4122)
This commit is contained in:
committed by
GitHub
parent
2fabde97f2
commit
dc44268ac4
@@ -14,6 +14,7 @@ def get_anchor():
|
||||
|
||||
class DeployBoard(WorkspaceBaseModel):
|
||||
class DeployBoardType(models.TextChoices):
|
||||
TEAMSPACE_PAGE = "teamspace_page", "Teamspace Page"
|
||||
PROJECT = "project", "Project"
|
||||
ISSUE = "issue", "Issue"
|
||||
MODULE = "module", "Module"
|
||||
|
||||
@@ -3,7 +3,7 @@ from rest_framework.permissions import BasePermission, SAFE_METHODS
|
||||
from plane.payment.flags.flag_decorator import check_workspace_feature_flag
|
||||
from plane.db.models import WorkspaceMember, ProjectMember, Page
|
||||
from plane.app.permissions import ROLE
|
||||
from plane.ee.models import PageUser, TeamspaceMember
|
||||
from plane.ee.models import PageUser, TeamspaceMember, PageComment
|
||||
from plane.payment.flags.flag import FeatureFlag
|
||||
from plane.ee.utils.check_user_teamspace_member import (
|
||||
check_if_current_user_is_teamspace_member,
|
||||
@@ -61,6 +61,27 @@ def has_shared_page_access(request, slug, page_id, project_id=None):
|
||||
return False
|
||||
|
||||
|
||||
def has_comment_access(request, slug, page_id, comment_id, page_owner_id):
|
||||
"""
|
||||
Check if the user has permission to access a comment
|
||||
"""
|
||||
user_id = request.user.id
|
||||
method = request.method
|
||||
|
||||
page_comment = PageComment.objects.filter(
|
||||
id=comment_id, workspace__slug=slug, page_id=page_id
|
||||
).first()
|
||||
|
||||
if method in ["GET", "POST"]:
|
||||
return True
|
||||
|
||||
if method == "PATCH":
|
||||
return page_comment.created_by_id == user_id
|
||||
|
||||
if method == "DELETE":
|
||||
return page_comment.created_by_id == user_id or page_owner_id == user_id
|
||||
|
||||
|
||||
class WorkspacePagePermission(BasePermission):
|
||||
"""
|
||||
Custom permission to control access to pages within a workspace
|
||||
@@ -71,6 +92,7 @@ class WorkspacePagePermission(BasePermission):
|
||||
user_id = request.user.id
|
||||
slug = view.kwargs.get("slug")
|
||||
page_id = view.kwargs.get("page_id")
|
||||
comment_id = view.kwargs.get("comment_id", None)
|
||||
|
||||
if request.user.is_anonymous:
|
||||
return False
|
||||
@@ -82,9 +104,10 @@ class WorkspacePagePermission(BasePermission):
|
||||
|
||||
if page_id:
|
||||
page = Page.objects.get(id=page_id, workspace__slug=slug)
|
||||
page_owner_id = page.owned_by_id
|
||||
|
||||
# Allow access if the user is the owner of the page
|
||||
if page.owned_by_id == user_id:
|
||||
if page_owner_id == user_id:
|
||||
return True
|
||||
|
||||
# If the page is private, check access based on shared page feature flag
|
||||
@@ -98,6 +121,11 @@ class WorkspacePagePermission(BasePermission):
|
||||
# If shared pages feature is not enabled, only the owner can access
|
||||
return False
|
||||
|
||||
if comment_id:
|
||||
return has_comment_access(
|
||||
request, slug, page_id, comment_id, page_owner_id
|
||||
)
|
||||
|
||||
# If the page is public, check access based on workspace role
|
||||
return self._has_public_page_access(request, slug)
|
||||
|
||||
@@ -168,6 +196,7 @@ class ProjectPagePermission(BasePermission):
|
||||
slug = view.kwargs.get("slug")
|
||||
project_id = view.kwargs.get("project_id")
|
||||
page_id = view.kwargs.get("page_id")
|
||||
comment_id = view.kwargs.get("comment_id", None)
|
||||
|
||||
is_teamspace_member = None
|
||||
|
||||
@@ -187,9 +216,10 @@ class ProjectPagePermission(BasePermission):
|
||||
|
||||
if page_id:
|
||||
page = Page.objects.get(id=page_id, workspace__slug=slug)
|
||||
page_owner_id = page.owned_by_id
|
||||
|
||||
# Allow access if the user is the owner of the page
|
||||
if page.owned_by_id == user_id:
|
||||
if page_owner_id == user_id:
|
||||
return True
|
||||
|
||||
# If the page is private, check access based on shared page feature flag
|
||||
@@ -203,6 +233,11 @@ class ProjectPagePermission(BasePermission):
|
||||
# If shared pages feature is not enabled, only the owner can access
|
||||
return False
|
||||
|
||||
if comment_id:
|
||||
return has_comment_access(
|
||||
request, slug, page_id, comment_id, page_owner_id
|
||||
)
|
||||
|
||||
# If the page is public, check access based on workspace role
|
||||
# Short-circuit: if project-level access suffices, avoid teamspace check
|
||||
if self._has_public_page_access(request, slug, project_id):
|
||||
@@ -295,8 +330,9 @@ class TeamspacePagePermission(BasePermission):
|
||||
|
||||
user_id = request.user.id
|
||||
slug = view.kwargs.get("slug")
|
||||
team_space_id = view.kwargs.get("team_space_id")
|
||||
page_id = view.kwargs.get("page_id")
|
||||
team_space_id = view.kwargs.get("team_space_id")
|
||||
comment_id = view.kwargs.get("comment_id", None)
|
||||
|
||||
if not TeamspaceMember.objects.filter(
|
||||
member_id=user_id,
|
||||
@@ -307,22 +343,21 @@ class TeamspacePagePermission(BasePermission):
|
||||
|
||||
if page_id:
|
||||
page = Page.objects.get(id=page_id, workspace__slug=slug)
|
||||
page_owner_id = page.owned_by_id
|
||||
|
||||
# we dont have private pages in teamspace
|
||||
if page.access == Page.PRIVATE_ACCESS:
|
||||
return False
|
||||
|
||||
if comment_id:
|
||||
return has_comment_access(
|
||||
request, slug, page_id, comment_id, page_owner_id
|
||||
)
|
||||
|
||||
# Allow access if the user is the owner of the page
|
||||
if page.owned_by_id == user_id:
|
||||
if page_owner_id == user_id:
|
||||
return True
|
||||
|
||||
# If the page is private, check access based on shared page feature flag
|
||||
if page.access == Page.PRIVATE_ACCESS:
|
||||
if check_workspace_feature_flag(
|
||||
feature_key=FeatureFlag.SHARED_PAGES,
|
||||
slug=slug,
|
||||
user_id=user_id,
|
||||
):
|
||||
return has_shared_page_access(request, slug, page.id)
|
||||
# If shared pages feature is not enabled, only the owner can access
|
||||
return False
|
||||
|
||||
# If the page is public
|
||||
return True
|
||||
|
||||
|
||||
@@ -139,29 +139,29 @@ urlpatterns = [
|
||||
name="workspace-page-comments",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/pages/<uuid:page_id>/comments/<uuid:pk>/resolve/",
|
||||
"workspaces/<str:slug>/pages/<uuid:page_id>/comments/<uuid:comment_id>/resolve/",
|
||||
WorkspacePageCommentViewSet.as_view({"post": "resolve"}),
|
||||
name="workspace-page-comments-resolve",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/pages/<uuid:page_id>/comments/<uuid:pk>/un-resolve/",
|
||||
"workspaces/<str:slug>/pages/<uuid:page_id>/comments/<uuid:comment_id>/un-resolve/",
|
||||
WorkspacePageCommentViewSet.as_view({"post": "un_resolve"}),
|
||||
name="workspace-page-comments-un-resolve",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/pages/<uuid:page_id>/comments/<uuid:pk>/",
|
||||
"workspaces/<str:slug>/pages/<uuid:page_id>/comments/<uuid:comment_id>/",
|
||||
WorkspacePageCommentViewSet.as_view(
|
||||
{"patch": "partial_update", "delete": "destroy", "get": "list"}
|
||||
),
|
||||
name="workspace-page-comments",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/pages/<uuid:page_id>/comments/<uuid:pk>/restore/",
|
||||
"workspaces/<str:slug>/pages/<uuid:page_id>/comments/<uuid:comment_id>/restore/",
|
||||
WorkspacePageCommentViewSet.as_view({"post": "restore"}),
|
||||
name="workspace-page-comments-restore",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/pages/<uuid:page_id>/comments/<uuid:pk>/replies/",
|
||||
"workspaces/<str:slug>/pages/<uuid:page_id>/comments/<uuid:comment_id>/replies/",
|
||||
WorkspacePageCommentViewSet.as_view({"get": "replies"}),
|
||||
name="workspace-page-comments-replies",
|
||||
),
|
||||
@@ -278,29 +278,29 @@ urlpatterns = [
|
||||
name="project-page-comments",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/comments/<uuid:pk>/",
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/comments/<uuid:comment_id>/",
|
||||
ProjectPageCommentViewSet.as_view(
|
||||
{"get": "list", "patch": "partial_update", "delete": "destroy"}
|
||||
),
|
||||
name="project-page-comments",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/comments/<uuid:pk>/resolve/",
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/comments/<uuid:comment_id>/resolve/",
|
||||
ProjectPageCommentViewSet.as_view({"post": "resolve"}),
|
||||
name="project-page-comments-resolve",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/comments/<uuid:pk>/un-resolve/",
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/comments/<uuid:comment_id>/un-resolve/",
|
||||
ProjectPageCommentViewSet.as_view({"post": "un_resolve"}),
|
||||
name="project-page-comments-un-resolve",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/comments/<uuid:pk>/restore/",
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/comments/<uuid:comment_id>/restore/",
|
||||
ProjectPageCommentViewSet.as_view({"post": "restore"}),
|
||||
name="project-page-comments-restore",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/comments/<uuid:pk>/replies/",
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/comments/<uuid:comment_id>/replies/",
|
||||
ProjectPageCommentViewSet.as_view({"get": "replies"}),
|
||||
name="project-page-comments-replies",
|
||||
),
|
||||
|
||||
@@ -26,6 +26,16 @@ from plane.ee.views.app.teamspace import (
|
||||
TeamspacePageDuplicateEndpoint,
|
||||
TeamspaceProgressSummaryEndpoint,
|
||||
AddTeamspaceProjectEndpoint,
|
||||
TeamspacePagePublishEndpoint,
|
||||
TeamspaceSubPageEndpoint,
|
||||
TeamspaceParentPageEndpoint,
|
||||
TeamspacePageUserEndpoint,
|
||||
TeamspacePageCommentEndpoint,
|
||||
TeamspacePageCommentReactionEndpoint,
|
||||
TeamspacePageResolveCommentEndpoint,
|
||||
TeamspacePageUnresolveCommentEndpoint,
|
||||
TeamspacePageRestoreCommentEndpoint,
|
||||
TeamspacePageCommentRepliesEndpoint,
|
||||
TeamspaceSubPageEndpoint,
|
||||
TeamspaceParentPageEndpoint,
|
||||
TeamspacePageSummaryEndpoint,
|
||||
@@ -200,4 +210,67 @@ urlpatterns = [
|
||||
AddTeamspaceProjectEndpoint.as_view(),
|
||||
name="teamspace-projects",
|
||||
),
|
||||
# path(
|
||||
# "workspaces/<str:slug>/teamspaces/<uuid:team_space_id>/pages/<uuid:page_id>/publish/",
|
||||
# TeamspacePagePublishEndpoint.as_view(),
|
||||
# name="teamspace-pages-publish",
|
||||
# ),
|
||||
# path(
|
||||
# "workspaces/<str:slug>/teamspaces/<uuid:team_space_id>/pages/<uuid:page_id>/publish/<uuid:pk>/",
|
||||
# TeamspacePagePublishEndpoint.as_view(),
|
||||
# name="teamspace-pages-publish",
|
||||
# ),
|
||||
# path(
|
||||
# "workspaces/<str:slug>/teamspaces/<uuid:team_space_id>/pages/<uuid:page_id>/share/",
|
||||
# TeamspacePageUserEndpoint.as_view(),
|
||||
# name="teamspace-page-shared",
|
||||
# ),
|
||||
# path(
|
||||
# "workspaces/<str:slug>/teamspaces/<uuid:team_space_id>/pages/<uuid:page_id>/share/<uuid:user_id>/",
|
||||
# TeamspacePageUserEndpoint.as_view(),
|
||||
# name="teamspace-page-shared",
|
||||
# ),
|
||||
# teamspace page comments
|
||||
path(
|
||||
"workspaces/<str:slug>/teamspaces/<uuid:team_space_id>/pages/<uuid:page_id>/comments/",
|
||||
TeamspacePageCommentEndpoint.as_view(),
|
||||
name="teamspace-page-comments",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/teamspaces/<uuid:team_space_id>/pages/<uuid:page_id>/comments/<uuid:comment_id>/",
|
||||
TeamspacePageCommentEndpoint.as_view(),
|
||||
name="teamspace-page-comments",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/teamspaces/<uuid:team_space_id>/pages/<uuid:page_id>/comments/<uuid:comment_id>/resolve/",
|
||||
TeamspacePageResolveCommentEndpoint.as_view(),
|
||||
name="teamspace-page-comments-resolve",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/teamspaces/<uuid:team_space_id>/pages/<uuid:page_id>/comments/<uuid:comment_id>/un-resolve/",
|
||||
TeamspacePageUnresolveCommentEndpoint.as_view(),
|
||||
name="teamspace-page-comments-un-resolve",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/teamspaces/<uuid:team_space_id>/pages/<uuid:page_id>/comments/<uuid:comment_id>/restore/",
|
||||
TeamspacePageRestoreCommentEndpoint.as_view(),
|
||||
name="teamspace-page-comments-restore",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/teamspaces/<uuid:team_space_id>/pages/<uuid:page_id>/comments/<uuid:comment_id>/replies/",
|
||||
TeamspacePageCommentRepliesEndpoint.as_view(),
|
||||
name="teamspace-page-comments-replies",
|
||||
),
|
||||
# # Comment Reactions
|
||||
path(
|
||||
"workspaces/<str:slug>/teamspaces/<uuid:team_space_id>/pages/<uuid:page_id>/comments/<uuid:comment_id>/reactions/",
|
||||
TeamspacePageCommentReactionEndpoint.as_view(),
|
||||
name="teamspace-page-comment-reactions",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/teamspaces/<uuid:team_space_id>/pages/<uuid:page_id>/comments/<uuid:comment_id>/reactions/<str:reaction_code>/",
|
||||
TeamspacePageCommentReactionEndpoint.as_view(),
|
||||
name="teamspace-page-comment-reactions",
|
||||
),
|
||||
# end teamspace page comments
|
||||
]
|
||||
|
||||
@@ -28,11 +28,14 @@ class ProjectPageCommentViewSet(BaseViewSet):
|
||||
permission_classes = [ProjectPagePermission]
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def list(self, request, slug, project_id, page_id, pk=None):
|
||||
if pk:
|
||||
def list(self, request, slug, project_id, page_id, comment_id=None):
|
||||
if comment_id:
|
||||
page_comments = (
|
||||
PageComment.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, page_id=page_id, pk=pk
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
page_id=page_id,
|
||||
pk=comment_id,
|
||||
)
|
||||
.select_related("created_by", "updated_by", "workspace", "page")
|
||||
.prefetch_related(
|
||||
@@ -107,9 +110,9 @@ class ProjectPageCommentViewSet(BaseViewSet):
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def partial_update(self, request, slug, project_id, page_id, pk):
|
||||
def partial_update(self, request, slug, project_id, page_id, comment_id):
|
||||
page_comment = PageComment.objects.get(
|
||||
workspace__slug=slug, page_id=page_id, pk=pk
|
||||
workspace__slug=slug, page_id=page_id, pk=comment_id
|
||||
)
|
||||
serializer = PageCommentSerializer(
|
||||
page_comment, data=request.data, partial=True
|
||||
@@ -127,21 +130,21 @@ class ProjectPageCommentViewSet(BaseViewSet):
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def destroy(self, request, slug, project_id, page_id, pk):
|
||||
def destroy(self, request, slug, project_id, page_id, comment_id):
|
||||
page_comment = PageComment.objects.get(
|
||||
workspace__slug=slug, page_id=page_id, pk=pk
|
||||
workspace__slug=slug, page_id=page_id, pk=comment_id
|
||||
)
|
||||
page_comment.delete()
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def resolve(self, request, slug, project_id, page_id, pk):
|
||||
def resolve(self, request, slug, project_id, page_id, comment_id):
|
||||
page_comment = PageComment.objects.get(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
page_id=page_id,
|
||||
pk=pk,
|
||||
pk=comment_id,
|
||||
parent__isnull=True,
|
||||
)
|
||||
page_comment.is_resolved = True
|
||||
@@ -151,14 +154,14 @@ class ProjectPageCommentViewSet(BaseViewSet):
|
||||
action=PageAction.RESOLVED_COMMENT,
|
||||
slug=slug,
|
||||
user_id=request.user.id,
|
||||
extra={"comment_id": str(pk)},
|
||||
extra={"comment_id": str(comment_id)},
|
||||
)
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def un_resolve(self, request, slug, project_id, page_id, pk):
|
||||
def un_resolve(self, request, slug, project_id, page_id, comment_id):
|
||||
page_comment = PageComment.objects.get(
|
||||
project_id=project_id, page_id=page_id, pk=pk, parent__isnull=True
|
||||
project_id=project_id, page_id=page_id, pk=comment_id, parent__isnull=True
|
||||
)
|
||||
page_comment.is_resolved = False
|
||||
page_comment.save()
|
||||
@@ -167,14 +170,14 @@ class ProjectPageCommentViewSet(BaseViewSet):
|
||||
action=PageAction.UNRESOLVED_COMMENT,
|
||||
slug=slug,
|
||||
user_id=request.user.id,
|
||||
extra={"comment_id": str(pk)},
|
||||
extra={"comment_id": str(comment_id)},
|
||||
)
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def restore(self, request, slug, project_id, page_id, pk):
|
||||
def restore(self, request, slug, project_id, page_id, comment_id):
|
||||
page_comment = PageComment.all_objects.filter(
|
||||
Q(pk=pk) | Q(parent_id=pk),
|
||||
Q(pk=comment_id) | Q(parent_id=comment_id),
|
||||
workspace__slug=slug,
|
||||
page_id=page_id,
|
||||
project_id=project_id,
|
||||
@@ -183,9 +186,12 @@ class ProjectPageCommentViewSet(BaseViewSet):
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def replies(self, request, slug, project_id, page_id, pk):
|
||||
def replies(self, request, slug, project_id, page_id, comment_id):
|
||||
page_replies = PageComment.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, page_id=page_id, parent_id=pk
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
page_id=page_id,
|
||||
parent_id=comment_id,
|
||||
)
|
||||
serializer = PageCommentSerializer(page_replies, many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@@ -148,7 +148,7 @@ class ProjectPageUserViewSet(BaseViewSet):
|
||||
slug=slug,
|
||||
project_id=project_id,
|
||||
user_id=request.user.id,
|
||||
extra=json.dumps({"user_ids": list(user_id)}),
|
||||
extra=json.dumps({"user_ids": [str(user_id)]}),
|
||||
)
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@@ -30,10 +30,10 @@ class WorkspacePageCommentViewSet(BaseViewSet):
|
||||
permission_classes = [WorkspacePagePermission]
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def list(self, request, slug, page_id, pk=None):
|
||||
if pk:
|
||||
def list(self, request, slug, page_id, comment_id=None):
|
||||
if comment_id:
|
||||
page_comments = (
|
||||
PageComment.objects.filter(workspace__slug=slug, page_id=page_id, pk=pk)
|
||||
PageComment.objects.filter(workspace__slug=slug, page_id=page_id, pk=comment_id)
|
||||
.select_related("created_by", "updated_by", "workspace", "page")
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
@@ -100,9 +100,9 @@ class WorkspacePageCommentViewSet(BaseViewSet):
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def partial_update(self, request, slug, page_id, pk):
|
||||
def partial_update(self, request, slug, page_id, comment_id):
|
||||
page_comment = PageComment.objects.get(
|
||||
workspace__slug=slug, page_id=page_id, pk=pk
|
||||
workspace__slug=slug, page_id=page_id, pk=comment_id
|
||||
)
|
||||
serializer = PageCommentSerializer(
|
||||
page_comment, data=request.data, partial=True
|
||||
@@ -120,18 +120,18 @@ class WorkspacePageCommentViewSet(BaseViewSet):
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def destroy(self, request, slug, page_id, pk):
|
||||
def destroy(self, request, slug, page_id, comment_id):
|
||||
page_comment = PageComment.objects.get(
|
||||
workspace__slug=slug, page_id=page_id, pk=pk
|
||||
workspace__slug=slug, page_id=page_id, pk=comment_id
|
||||
)
|
||||
page_comment.delete()
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def resolve(self, request, slug, page_id, pk):
|
||||
def resolve(self, request, slug, page_id, comment_id):
|
||||
page_comment = PageComment.objects.get(
|
||||
workspace__slug=slug, page_id=page_id, pk=pk, parent__isnull=True
|
||||
workspace__slug=slug, page_id=page_id, pk=comment_id, parent__isnull=True
|
||||
)
|
||||
page_comment.is_resolved = True
|
||||
page_comment.save()
|
||||
@@ -140,14 +140,14 @@ class WorkspacePageCommentViewSet(BaseViewSet):
|
||||
action=PageAction.RESOLVED_COMMENT,
|
||||
slug=slug,
|
||||
user_id=request.user.id,
|
||||
extra={"comment_id": str(pk)},
|
||||
extra={"comment_id": str(comment_id)},
|
||||
)
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def un_resolve(self, request, slug, page_id, pk):
|
||||
def un_resolve(self, request, slug, page_id, comment_id):
|
||||
page_comment = PageComment.objects.get(
|
||||
workspace__slug=slug, page_id=page_id, pk=pk, parent__isnull=True
|
||||
workspace__slug=slug, page_id=page_id, pk=comment_id, parent__isnull=True
|
||||
)
|
||||
page_comment.is_resolved = False
|
||||
page_comment.save()
|
||||
@@ -156,24 +156,26 @@ class WorkspacePageCommentViewSet(BaseViewSet):
|
||||
action=PageAction.UNRESOLVED_COMMENT,
|
||||
slug=slug,
|
||||
user_id=request.user.id,
|
||||
extra={"comment_id": str(pk)},
|
||||
extra={"comment_id": str(comment_id)},
|
||||
)
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def restore(self, request, slug, page_id, pk):
|
||||
def restore(self, request, slug, page_id, comment_id):
|
||||
page_comment = PageComment.all_objects.filter(
|
||||
Q(pk=pk) | Q(parent_id=pk), workspace__slug=slug, page_id=page_id
|
||||
Q(pk=comment_id) | Q(parent_id=comment_id),
|
||||
workspace__slug=slug,
|
||||
page_id=page_id,
|
||||
)
|
||||
page_comment.update(deleted_at=None)
|
||||
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def replies(self, request, slug, page_id, pk):
|
||||
def replies(self, request, slug, page_id, comment_id):
|
||||
page_replies = (
|
||||
PageComment.objects.filter(
|
||||
workspace__slug=slug, page_id=page_id, parent_id=pk
|
||||
workspace__slug=slug, page_id=page_id, parent_id=comment_id
|
||||
)
|
||||
.select_related("created_by", "updated_by", "workspace", "page")
|
||||
.prefetch_related(
|
||||
|
||||
@@ -159,7 +159,7 @@ class WorkspacePageUserViewSet(BaseViewSet):
|
||||
action=PageAction.UNSHARED,
|
||||
slug=slug,
|
||||
user_id=request.user.id,
|
||||
extra=json.dumps({"user_ids": list(user_id)}),
|
||||
extra=json.dumps({"user_ids": [str(user_id)]}),
|
||||
)
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@@ -9,7 +9,7 @@ from .analytic import (
|
||||
)
|
||||
from .views import TeamspaceViewEndpoint
|
||||
from .cycle import TeamspaceCycleEndpoint
|
||||
from .page import (
|
||||
from .page.base import (
|
||||
TeamspacePageEndpoint,
|
||||
TeamspacePageVersionEndpoint,
|
||||
TeamspacePagesDescriptionEndpoint,
|
||||
@@ -22,6 +22,17 @@ from .page import (
|
||||
TeamspaceParentPageEndpoint,
|
||||
TeamspacePageSummaryEndpoint,
|
||||
)
|
||||
from .page.publish import TeamspacePagePublishEndpoint
|
||||
from .page.share import TeamspacePageUserEndpoint
|
||||
from .page.comment import (
|
||||
TeamspacePageCommentEndpoint,
|
||||
TeamspacePageCommentReactionEndpoint,
|
||||
TeamspacePageResolveCommentEndpoint,
|
||||
TeamspacePageUnresolveCommentEndpoint,
|
||||
TeamspacePageRestoreCommentEndpoint,
|
||||
TeamspacePageCommentRepliesEndpoint,
|
||||
)
|
||||
|
||||
from .issue import TeamspaceIssueEndpoint, TeamspaceUserPropertiesEndpoint
|
||||
from .activity import TeamspaceActivityEndpoint
|
||||
from .comment import TeamspaceCommentEndpoint, TeamspaceCommentReactionEndpoint
|
||||
|
||||
@@ -42,7 +42,7 @@ from plane.db.models import (
|
||||
ProjectMember,
|
||||
)
|
||||
|
||||
from plane.ee.models import TeamspacePage, TeamspaceMember, PageUser
|
||||
from plane.ee.models import TeamspacePage, PageUser
|
||||
from plane.ee.serializers import (
|
||||
TeamspacePageDetailSerializer,
|
||||
TeamspacePageSerializer,
|
||||
247
apps/api/plane/ee/views/app/teamspace/page/comment.py
Normal file
247
apps/api/plane/ee/views/app/teamspace/page/comment.py
Normal file
@@ -0,0 +1,247 @@
|
||||
# Django imports
|
||||
from django.utils import timezone
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Prefetch, OuterRef, Func, F, Q
|
||||
|
||||
# Third Party imports
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import Workspace
|
||||
from plane.ee.models import PageComment, PageCommentReaction
|
||||
from plane.ee.permissions.page import TeamspacePagePermission
|
||||
from plane.ee.serializers.app.page import (
|
||||
PageCommentSerializer,
|
||||
PageCommentReactionSerializer,
|
||||
)
|
||||
from plane.ee.views.base import BaseAPIView
|
||||
from plane.payment.flags.flag import FeatureFlag
|
||||
from plane.payment.flags.flag_decorator import check_feature_flag
|
||||
from plane.ee.bgtasks.page_update import nested_page_update, PageAction
|
||||
|
||||
|
||||
class TeamspacePageCommentEndpoint(BaseAPIView):
|
||||
serializer_class = PageCommentSerializer
|
||||
model = PageComment
|
||||
|
||||
permission_classes = [TeamspacePagePermission]
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def get(self, request, slug, team_space_id, page_id, comment_id=None):
|
||||
if comment_id:
|
||||
page_comments = (
|
||||
PageComment.objects.filter(workspace__slug=slug, page_id=page_id, pk=comment_id)
|
||||
.select_related("created_by", "updated_by", "workspace", "page")
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"page_comment_reactions",
|
||||
queryset=PageCommentReaction.objects.select_related("actor"),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
total_replies=PageComment.objects.filter(
|
||||
parent=OuterRef("id"), workspace__slug=slug, page_id=page_id
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.order_by("-created_at")
|
||||
)
|
||||
else:
|
||||
# fetch all the latest child comments
|
||||
latest_child_comments = (
|
||||
PageComment.objects.filter(
|
||||
workspace__slug=slug, page_id=page_id, parent__isnull=False
|
||||
)
|
||||
.order_by("parent_id", "-created_at")
|
||||
.distinct("parent_id")
|
||||
.values_list("id", flat=True)
|
||||
)
|
||||
|
||||
page_comments = (
|
||||
PageComment.objects.filter(
|
||||
Q(id__in=latest_child_comments) | Q(parent__isnull=True)
|
||||
)
|
||||
.filter(
|
||||
workspace__slug=slug,
|
||||
page_id=page_id,
|
||||
)
|
||||
.select_related("created_by", "updated_by", "workspace", "page")
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"page_comment_reactions",
|
||||
queryset=PageCommentReaction.objects.select_related("actor"),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
total_replies=PageComment.objects.filter(
|
||||
parent=OuterRef("id"), workspace__slug=slug, page_id=page_id
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.order_by("-created_at")
|
||||
)
|
||||
serializer = PageCommentSerializer(page_comments, many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def post(self, request, slug, team_space_id, page_id):
|
||||
workspace_id = Workspace.objects.get(slug=slug).id
|
||||
serializer = PageCommentSerializer(
|
||||
data=request.data,
|
||||
context={
|
||||
"workspace_id": workspace_id,
|
||||
},
|
||||
)
|
||||
if serializer.is_valid():
|
||||
serializer.save(page_id=page_id, workspace_id=workspace_id)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def patch(self, request, slug, team_space_id, page_id, comment_id):
|
||||
page_comment = PageComment.objects.get(
|
||||
workspace__slug=slug, page_id=page_id, pk=comment_id
|
||||
)
|
||||
serializer = PageCommentSerializer(
|
||||
page_comment, data=request.data, partial=True
|
||||
)
|
||||
if serializer.is_valid():
|
||||
if (
|
||||
"comment_html" in request.data
|
||||
and request.data["comment_html"] != page_comment.comment_html
|
||||
):
|
||||
serializer.save(edited_at=timezone.now())
|
||||
else:
|
||||
serializer.save()
|
||||
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def delete(self, request, slug, team_space_id, page_id, comment_id):
|
||||
page_comment = PageComment.objects.get(
|
||||
workspace__slug=slug, page_id=page_id, pk=comment_id
|
||||
)
|
||||
page_comment.delete()
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class TeamspacePageResolveCommentEndpoint(BaseAPIView):
|
||||
|
||||
permission_classes = [TeamspacePagePermission]
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def post(self, request, slug, team_space_id, page_id, comment_id):
|
||||
page_comment = PageComment.objects.get(
|
||||
workspace__slug=slug,
|
||||
page_id=page_id,
|
||||
pk=comment_id,
|
||||
parent__isnull=True,
|
||||
)
|
||||
page_comment.is_resolved = True
|
||||
page_comment.save()
|
||||
nested_page_update.delay(
|
||||
page_id=page_id,
|
||||
action=PageAction.RESOLVED_COMMENT,
|
||||
slug=slug,
|
||||
user_id=request.user.id,
|
||||
extra={"comment_id": str(comment_id)},
|
||||
)
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class TeamspacePageUnresolveCommentEndpoint(BaseAPIView):
|
||||
permission_classes = [TeamspacePagePermission]
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def post(self, request, slug, team_space_id, page_id, comment_id):
|
||||
page_comment = PageComment.objects.get(
|
||||
workspace__slug=slug, page_id=page_id, pk=comment_id, parent__isnull=True
|
||||
)
|
||||
page_comment.is_resolved = False
|
||||
page_comment.save()
|
||||
nested_page_update.delay(
|
||||
page_id=page_id,
|
||||
action=PageAction.UNRESOLVED_COMMENT,
|
||||
slug=slug,
|
||||
user_id=request.user.id,
|
||||
extra={"comment_id": str(comment_id)},
|
||||
)
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class TeamspacePageRestoreCommentEndpoint(BaseAPIView):
|
||||
permission_classes = [TeamspacePagePermission]
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def post(self, request, slug, team_space_id, page_id, comment_id):
|
||||
page_comment = PageComment.all_objects.filter(
|
||||
Q(pk=comment_id) | Q(parent_id=comment_id),
|
||||
workspace__slug=slug,
|
||||
page_id=page_id,
|
||||
)
|
||||
page_comment.update(deleted_at=None)
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class TeamspacePageCommentRepliesEndpoint(BaseAPIView):
|
||||
permission_classes = [TeamspacePagePermission]
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def get(self, request, slug, team_space_id, page_id, comment_id):
|
||||
page_replies = PageComment.objects.filter(
|
||||
workspace__slug=slug, page_id=page_id, parent_id=comment_id
|
||||
)
|
||||
serializer = PageCommentSerializer(page_replies, many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class TeamspacePageCommentReactionEndpoint(BaseAPIView):
|
||||
serializer_class = PageCommentReactionSerializer
|
||||
model = PageCommentReaction
|
||||
|
||||
permission_classes = [TeamspacePagePermission]
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
super()
|
||||
.get_queryset()
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.filter(comment_id=self.kwargs.get("comment_id"))
|
||||
.order_by("-created_at")
|
||||
.distinct()
|
||||
)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def post(self, request, slug, team_space_id, page_id, comment_id):
|
||||
try:
|
||||
serializer = PageCommentReactionSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(
|
||||
actor_id=request.user.id,
|
||||
comment_id=comment_id,
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
except IntegrityError:
|
||||
return Response(
|
||||
{"error": "Reaction already exists for the user"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_COMMENTS)
|
||||
def delete(self, request, slug, team_space_id, page_id, comment_id, reaction_code):
|
||||
comment_reaction = PageCommentReaction.objects.get(
|
||||
workspace__slug=slug,
|
||||
comment_id=comment_id,
|
||||
reaction=reaction_code,
|
||||
actor=request.user,
|
||||
)
|
||||
comment_reaction.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
134
apps/api/plane/ee/views/app/teamspace/page/publish.py
Normal file
134
apps/api/plane/ee/views/app/teamspace/page/publish.py
Normal file
@@ -0,0 +1,134 @@
|
||||
# Third party imports
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
||||
# Module imports
|
||||
from plane.ee.views.base import BaseAPIView
|
||||
from plane.db.models import DeployBoard, Workspace, Page
|
||||
from plane.app.serializers import DeployBoardSerializer
|
||||
from plane.payment.flags.flag_decorator import check_feature_flag
|
||||
from plane.payment.flags.flag import FeatureFlag
|
||||
from plane.ee.bgtasks.page_update import nested_page_update
|
||||
from plane.ee.utils.page_events import PageAction
|
||||
from plane.ee.permissions.page import TeamspacePagePermission
|
||||
|
||||
|
||||
class TeamspacePagePublishEndpoint(BaseAPIView):
|
||||
|
||||
permission_classes = [TeamspacePagePermission]
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_PUBLISH)
|
||||
def post(self, request, slug, team_space_id, page_id):
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
# Fetch the page
|
||||
page = Page.objects.get(pk=page_id, workspace=workspace, is_global=False)
|
||||
|
||||
if page.archived_at:
|
||||
return Response(
|
||||
{"error": "You cannot publish an archived page"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Throw error if the page is a workspace page
|
||||
if page.is_global:
|
||||
return Response(
|
||||
{"error": "Workspace pages cannot be published as teamspace pages"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Get the deploy board attributes
|
||||
comments = request.data.get("is_comments_enabled", False)
|
||||
reactions = request.data.get("is_reactions_enabled", False)
|
||||
intake = request.data.get("intake", None)
|
||||
votes = request.data.get("is_votes_enabled", False)
|
||||
view_props = request.data.get("view_props", {})
|
||||
|
||||
# Create a deploy board for the page
|
||||
deploy_board, _ = DeployBoard.objects.get_or_create(
|
||||
entity_identifier=page_id,
|
||||
entity_name=DeployBoard.DeployBoardType.TEAMSPACE_PAGE,
|
||||
defaults={
|
||||
"is_comments_enabled": comments,
|
||||
"is_reactions_enabled": reactions,
|
||||
"intake": intake,
|
||||
"is_votes_enabled": votes,
|
||||
"view_props": view_props,
|
||||
"workspace": workspace,
|
||||
},
|
||||
)
|
||||
|
||||
nested_page_update.delay(
|
||||
page_id=page_id,
|
||||
action=PageAction.PUBLISHED,
|
||||
slug=slug,
|
||||
user_id=request.user.id,
|
||||
)
|
||||
# Return the deploy board
|
||||
serializer = DeployBoardSerializer(deploy_board)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_PUBLISH)
|
||||
def patch(self, request, slug, team_space_id, page_id):
|
||||
# Get the deploy board
|
||||
deploy_board = DeployBoard.objects.get(
|
||||
entity_identifier=page_id,
|
||||
entity_name=DeployBoard.DeployBoardType.TEAMSPACE_PAGE,
|
||||
workspace__slug=slug,
|
||||
)
|
||||
# Get the deploy board attributes
|
||||
data = {
|
||||
"is_comments_enabled": request.data.get(
|
||||
"is_comments_enabled", deploy_board.is_comments_enabled
|
||||
),
|
||||
"is_reactions_enabled": request.data.get(
|
||||
"is_reactions_enabled", deploy_board.is_reactions_enabled
|
||||
),
|
||||
"intake": request.data.get("intake", deploy_board.intake),
|
||||
"is_votes_enabled": request.data.get(
|
||||
"is_votes_enabled", deploy_board.is_votes_enabled
|
||||
),
|
||||
"view_props": request.data.get("view_props", deploy_board.view_props),
|
||||
}
|
||||
|
||||
# Update the deploy board
|
||||
serializer = DeployBoardSerializer(deploy_board, data=data, partial=True)
|
||||
# Return the updated deploy board
|
||||
if serializer.is_valid():
|
||||
# Save the updated deploy board
|
||||
serializer.save()
|
||||
# Return the updated deploy board
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_PUBLISH)
|
||||
def get(self, request, slug, team_space_id, page_id):
|
||||
# Get the deploy board
|
||||
deploy_board = DeployBoard.objects.get(
|
||||
entity_identifier=page_id,
|
||||
entity_name=DeployBoard.DeployBoardType.TEAMSPACE_PAGE,
|
||||
workspace__slug=slug,
|
||||
)
|
||||
# Return the deploy board
|
||||
serializer = DeployBoardSerializer(deploy_board)
|
||||
# Return the deploy board
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@check_feature_flag(FeatureFlag.PAGE_PUBLISH)
|
||||
def delete(self, request, slug, team_space_id, page_id):
|
||||
# Get the deploy board and un publish all the sub page as well.
|
||||
deploy_board = DeployBoard.objects.get(
|
||||
entity_identifier=page_id,
|
||||
entity_name=DeployBoard.DeployBoardType.TEAMSPACE_PAGE,
|
||||
workspace__slug=slug,
|
||||
)
|
||||
# Delete the deploy board
|
||||
deploy_board.delete()
|
||||
|
||||
nested_page_update.delay(
|
||||
page_id=page_id,
|
||||
action=PageAction.UNPUBLISHED,
|
||||
slug=slug,
|
||||
user_id=request.user.id,
|
||||
)
|
||||
# Return the response
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
151
apps/api/plane/ee/views/app/teamspace/page/share.py
Normal file
151
apps/api/plane/ee/views/app/teamspace/page/share.py
Normal file
@@ -0,0 +1,151 @@
|
||||
import json
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
from plane.db.models import Page
|
||||
from plane.ee.models import PageUser
|
||||
from plane.ee.views.base import BaseAPIView
|
||||
from plane.payment.flags.flag import FeatureFlag
|
||||
from plane.app.serializers import PageUserSerializer
|
||||
from plane.payment.flags.flag_decorator import check_feature_flag
|
||||
|
||||
|
||||
## EE imports
|
||||
from plane.ee.permissions.page import TeamspacePagePermission
|
||||
from plane.ee.bgtasks.page_update import PageAction, nested_page_update
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
|
||||
class TeamspacePageUserEndpoint(BaseAPIView):
|
||||
serializer_class = PageUserSerializer
|
||||
model = PageUser
|
||||
permission_classes = [TeamspacePagePermission]
|
||||
|
||||
@check_feature_flag(FeatureFlag.SHARED_PAGES)
|
||||
def post(self, request, slug, team_space_id, page_id):
|
||||
page = Page.objects.get(id=page_id, workspace__slug=slug)
|
||||
if page.parent_id is not None:
|
||||
return Response(
|
||||
{"detail": "You can only share the root page"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if page.access == Page.PUBLIC_ACCESS:
|
||||
return Response(
|
||||
{"detail": "You can only share the private page"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
owner_id = page.owned_by_id
|
||||
|
||||
# remove owner from the requested users
|
||||
requested_user_map = {
|
||||
str(user["user_id"]): user["access"]
|
||||
for user in request.data
|
||||
if str(user["user_id"]) != str(owner_id)
|
||||
}
|
||||
requested_user_ids = set(requested_user_map.keys())
|
||||
|
||||
existing_users = PageUser.objects.filter(page_id=page_id, workspace__slug=slug)
|
||||
existing_user_map = {str(pu.user_id): pu for pu in existing_users}
|
||||
existing_user_ids = set(existing_user_map.keys())
|
||||
|
||||
# 1. Users to create (in request but not in existing)
|
||||
new_user_ids = requested_user_ids - existing_user_ids
|
||||
users_to_create = [
|
||||
PageUser(
|
||||
user_id=user_id,
|
||||
page_id=page_id,
|
||||
access=requested_user_map[user_id],
|
||||
workspace_id=page.workspace_id,
|
||||
created_by_id=request.user.id,
|
||||
updated_by_id=request.user.id,
|
||||
created_by=request.user,
|
||||
updated_by=request.user,
|
||||
)
|
||||
for user_id in new_user_ids
|
||||
]
|
||||
PageUser.objects.bulk_create(users_to_create, batch_size=10)
|
||||
|
||||
# 2. Users to delete (in existing but not in request)
|
||||
deleted_user_ids = existing_user_ids - requested_user_ids
|
||||
PageUser.objects.filter(
|
||||
page_id=page_id,
|
||||
user_id__in=deleted_user_ids,
|
||||
workspace__slug=slug,
|
||||
).delete()
|
||||
|
||||
# 3. Users to update access
|
||||
common_user_ids = requested_user_ids & existing_user_ids
|
||||
users_to_update = []
|
||||
for user_id in common_user_ids:
|
||||
existing = existing_user_map[user_id]
|
||||
new_access = requested_user_map[user_id]
|
||||
if existing.access != new_access:
|
||||
existing.access = new_access
|
||||
existing.updated_by = request.user
|
||||
existing.updated_at = timezone.now()
|
||||
users_to_update.append(existing)
|
||||
|
||||
if users_to_update:
|
||||
PageUser.objects.bulk_update(
|
||||
users_to_update, ["access", "updated_by", "updated_at"]
|
||||
)
|
||||
|
||||
# Fire shared and unshared events if needed
|
||||
if users_to_create or users_to_update:
|
||||
nested_page_update.delay(
|
||||
page_id=page.id,
|
||||
action=PageAction.SHARED,
|
||||
slug=slug,
|
||||
user_id=request.user.id,
|
||||
extra=json.dumps({"create_user_access": request.data}),
|
||||
)
|
||||
|
||||
if deleted_user_ids:
|
||||
nested_page_update.delay(
|
||||
page_id=page_id,
|
||||
action=PageAction.UNSHARED,
|
||||
slug=slug,
|
||||
user_id=request.user.id,
|
||||
extra=json.dumps({"user_ids": list(deleted_user_ids)}),
|
||||
)
|
||||
|
||||
return Response(status=status.HTTP_201_CREATED)
|
||||
|
||||
@check_feature_flag(FeatureFlag.SHARED_PAGES)
|
||||
def get(self, request, slug, team_space_id, page_id):
|
||||
shared_pages = PageUser.objects.filter(
|
||||
page_id=page_id,
|
||||
workspace__slug=slug,
|
||||
)
|
||||
serializer = PageUserSerializer(shared_pages, many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@check_feature_flag(FeatureFlag.SHARED_PAGES)
|
||||
def delete(self, request, slug, team_space_id, page_id, user_id):
|
||||
page_user = PageUser.objects.filter(
|
||||
page_id=page_id,
|
||||
user_id=user_id,
|
||||
workspace__slug=slug,
|
||||
).first()
|
||||
|
||||
if not page_user:
|
||||
return Response(
|
||||
{"detail": "Page user not found"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
if request.user.id == user_id:
|
||||
page_user.delete()
|
||||
nested_page_update.delay(
|
||||
page_id=page_id,
|
||||
action=PageAction.UNSHARED,
|
||||
slug=slug,
|
||||
user_id=request.user.id,
|
||||
extra=json.dumps({"user_ids": [str(user_id)]}),
|
||||
)
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
@@ -297,6 +297,20 @@ export class BasePage extends ExtendedBasePage implements TBasePage {
|
||||
return `/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}`;
|
||||
}
|
||||
);
|
||||
} else if (this.team) {
|
||||
// Set config for team page
|
||||
this.setConfig(
|
||||
{
|
||||
workspaceSlug,
|
||||
teamspaceId: this.team,
|
||||
},
|
||||
// Custom getBasePath function for team pages
|
||||
(params: TPageConfigParams) => {
|
||||
const { pageId, config } = params;
|
||||
const { workspaceSlug, teamspaceId } = config;
|
||||
return `/api/workspaces/${workspaceSlug}/teamspaces/${teamspaceId}/pages/${pageId}`;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// Set config for workspace page
|
||||
this.setConfig(
|
||||
|
||||
@@ -388,7 +388,10 @@ export class TeamspacePageStore implements ITeamspacePageStore {
|
||||
* Returns true if comments in pages feature is enabled
|
||||
* @returns boolean
|
||||
*/
|
||||
isCommentsEnabled = computedFn(() => false);
|
||||
isCommentsEnabled = computedFn((workspaceSlug: string) => {
|
||||
const { getFeatureFlag } = this.rootStore.featureFlags;
|
||||
return getFeatureFlag(workspaceSlug, "PAGE_COMMENTS", false);
|
||||
});
|
||||
|
||||
updateFilters = <T extends keyof TPageFilters>(filterKey: T, filterValue: TPageFilters[T]) => {
|
||||
runInAction(() => {
|
||||
|
||||
@@ -224,7 +224,8 @@ export class TeamspacePage extends BasePage implements TTeamspacePage {
|
||||
* @description returns true if the current logged in user can comment on the page/reply to the comments
|
||||
*/
|
||||
get canCurrentUserCommentOnPage() {
|
||||
return false;
|
||||
const userRole = this.getUserWorkspaceRole();
|
||||
return this.isCurrentUserOwner || userRole === EUserWorkspaceRoles.ADMIN;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user