ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: единый realtime слой Tasker

Добавлен NODE.DC realtime event stream для workspace/project/member/invite/profile событий. Обновлены frontend stores и live controller. Доработаны settings modal, member dropdown и confirm remove под NODE.DC UX.
This commit is contained in:
DCCONSTRUCTIONS
2026-05-12 17:28:40 +03:00
parent fc59481703
commit 480f85cce8
23 changed files with 993 additions and 72 deletions
@@ -9,6 +9,8 @@ from django.db.models import Min
# Module imports
from .base import BaseViewSet, BaseAPIView
from plane.app.realtime.issue_events import publish_assignee_cleanup_issue_events_on_commit
from plane.app.realtime.nodedc_events import publish_nodedc_workspace_event_on_commit
from plane.authentication.nodedc_workspace_policy import (
is_nodedc_launcher_managed_workspace,
nodedc_launcher_managed_workspace_response,
@@ -176,6 +178,18 @@ class ProjectMemberViewSet(BaseViewSet):
],
batch_size=10,
)
for project_member in project_members:
publish_nodedc_workspace_event_on_commit(
project_member.workspace,
"project_member.created",
payload={
"project_id": str(project_member.project_id),
"member_id": str(project_member.member_id),
"role": project_member.role,
"source": "tasker",
},
extra_user_ids=[project_member.member_id],
)
# Send emails to notify the users
[
project_add_user_email.delay(
@@ -301,6 +315,17 @@ class ProjectMemberViewSet(BaseViewSet):
if serializer.is_valid():
serializer.save()
publish_nodedc_workspace_event_on_commit(
project_member.workspace,
"project_member.updated",
payload={
"project_id": str(project_member.project_id),
"member_id": str(project_member.member_id),
"role": project_member.role,
"source": "tasker",
},
extra_user_ids=[project_member.member_id],
)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@@ -336,9 +361,24 @@ class ProjectMemberViewSet(BaseViewSet):
status=status.HTTP_400_BAD_REQUEST,
)
publish_assignee_cleanup_issue_events_on_commit(
project_id=project_id,
assignee_id=project_member.member_id,
actor_id=request.user.id,
)
project_member.is_active = False
project_member.save()
IssueAssignee.objects.filter(project_id=project_id, assignee_id=project_member.member_id).delete()
publish_nodedc_workspace_event_on_commit(
project_member.workspace,
"project_member.deleted",
payload={
"project_id": str(project_member.project_id),
"member_id": str(project_member.member_id),
"source": "tasker",
},
extra_user_ids=[project_member.member_id],
)
return Response(status=status.HTTP_204_NO_CONTENT)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
@@ -367,10 +407,25 @@ class ProjectMemberViewSet(BaseViewSet):
},
status=status.HTTP_400_BAD_REQUEST,
)
publish_assignee_cleanup_issue_events_on_commit(
project_id=project_id,
assignee_id=project_member.member_id,
actor_id=request.user.id,
)
# Deactivate the user
project_member.is_active = False
project_member.save()
IssueAssignee.objects.filter(project_id=project_id, assignee_id=project_member.member_id).delete()
publish_nodedc_workspace_event_on_commit(
project_member.workspace,
"project_member.deleted",
payload={
"project_id": str(project_member.project_id),
"member_id": str(project_member.member_id),
"source": "tasker",
},
extra_user_ids=[project_member.member_id],
)
return Response(status=status.HTTP_204_NO_CONTENT)
@@ -20,6 +20,7 @@ from rest_framework.response import Response
# Module imports
from plane.app.permissions import WorkSpaceAdminPermission
from plane.app.realtime.nodedc_events import publish_nodedc_workspace_event_on_commit
from plane.authentication.nodedc_workspace_policy import (
get_nodedc_workspace_creation_policy,
is_nodedc_launcher_managed_workspace,
@@ -159,6 +160,18 @@ class WorkspaceInvitationsViewset(BaseViewSet):
invitation.nodedc_approval_request_id = approval_request_id
invitation.save(update_fields=["nodedc_approval_request_id", "updated_at"])
approved_requests.append(approval_request_id)
invited_user = User.objects.filter(email__iexact=invitation.email, is_bot=False).first()
publish_nodedc_workspace_event_on_commit(
workspace,
"workspace_invite.created",
payload={
"invite_id": str(invitation.id),
"email": invitation.email,
"status": invitation.nodedc_approval_status,
"source": "tasker",
},
extra_user_ids=[request.user.id, getattr(invited_user, "id", None)],
)
except Exception:
WorkspaceMemberInvite.objects.filter(id__in=[invitation.id for invitation in workspace_invitations]).delete()
return Response(
@@ -197,6 +210,18 @@ class WorkspaceInvitationsViewset(BaseViewSet):
"invitee_email": invitation.email,
},
)
invited_user = User.objects.filter(email__iexact=invitation.email, is_bot=False).first()
publish_nodedc_workspace_event_on_commit(
workspace,
"workspace_invite.created",
payload={
"invite_id": str(invitation.id),
"email": invitation.email,
"status": invitation.nodedc_approval_status,
"source": "tasker",
},
extra_user_ids=[request.user.id, getattr(invited_user, "id", None)],
)
return Response({"message": "Emails sent successfully"}, status=status.HTTP_200_OK)
@@ -214,7 +239,23 @@ class WorkspaceInvitationsViewset(BaseViewSet):
status=status.HTTP_502_BAD_GATEWAY,
)
invited_user = User.objects.filter(email__iexact=workspace_member_invite.email, is_bot=False).first()
workspace = workspace_member_invite.workspace
invite_payload = {
"invite_id": str(workspace_member_invite.id),
"email": workspace_member_invite.email,
"status": workspace_member_invite.nodedc_approval_status,
"source": "tasker",
}
extra_user_ids = [workspace_member_invite.created_by_id, getattr(invited_user, "id", None)]
workspace_member_invite.delete()
publish_nodedc_workspace_event_on_commit(
workspace,
"workspace_invite.deleted",
payload=invite_payload,
extra_user_ids=extra_user_ids,
)
return Response(status=status.HTTP_204_NO_CONTENT)
@@ -340,6 +381,17 @@ class WorkspaceJoinEndpoint(BaseAPIView):
},
)
publish_nodedc_workspace_event_on_commit(
workspace_invite.workspace,
"workspace_member.created",
payload={
"member_id": str(user.id),
"role": workspace_invite.role,
"source": "tasker",
},
extra_user_ids=[user.id, workspace_invite.created_by_id],
)
# Delete the invitation
workspace_invite.delete()
@@ -447,6 +499,16 @@ class UserWorkspaceInvitationsViewSet(BaseViewSet):
},
)
)
publish_nodedc_workspace_event_on_commit(
invitation.workspace,
"workspace_member.created",
payload={
"member_id": str(request.user.id),
"role": invitation.role,
"source": "tasker",
},
extra_user_ids=[request.user.id, invitation.created_by_id],
)
# Bulk create the user for all the workspaces
WorkspaceMember.objects.bulk_create(
@@ -12,6 +12,8 @@ from rest_framework import status
from rest_framework.response import Response
from plane.app.permissions import WorkspaceEntityPermission, allow_permission, ROLE
from plane.app.realtime.issue_events import publish_assignee_cleanup_issue_events_on_commit
from plane.app.realtime.nodedc_events import publish_nodedc_workspace_event_on_commit
from plane.authentication.nodedc_workspace_policy import (
get_nodedc_workspace_creation_policy,
is_nodedc_launcher_managed_workspace,
@@ -54,7 +56,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
workspace_member = WorkspaceMember.objects.get(member=request.user, workspace__slug=slug, is_active=True)
# Get all active workspace members
workspace_members = self.get_queryset()
workspace_members = self.get_queryset().filter(is_active=True)
if workspace_member.role > 5:
serializer = WorkspaceMemberAdminSerializer(workspace_members, fields=("id", "member", "role"), many=True)
else:
@@ -102,6 +104,16 @@ class WorkSpaceMemberViewSet(BaseViewSet):
if serializer.is_valid():
serializer.save()
publish_nodedc_workspace_event_on_commit(
workspace_member.workspace,
"workspace_member.updated",
payload={
"member_id": str(workspace_member.member_id),
"role": workspace_member.role,
"source": "tasker",
},
extra_user_ids=[workspace_member.member_id],
)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@@ -166,6 +178,20 @@ class WorkSpaceMemberViewSet(BaseViewSet):
status=status.HTTP_502_BAD_GATEWAY,
)
project_ids = list(
ProjectMember.objects.filter(
workspace__slug=slug,
member_id=workspace_member.member_id,
is_active=True,
).values_list("project_id", flat=True)
)
publish_assignee_cleanup_issue_events_on_commit(
workspace_id=workspace_member.workspace_id,
assignee_id=workspace_member.member_id,
actor_id=request.user.id,
)
# Deactivate the users from the projects where the user is part of
_ = ProjectMember.objects.filter(
workspace__slug=slug, member_id=workspace_member.member_id, is_active=True
@@ -174,6 +200,16 @@ class WorkSpaceMemberViewSet(BaseViewSet):
workspace_member.is_active = False
workspace_member.save()
publish_nodedc_workspace_event_on_commit(
workspace_member.workspace,
"workspace_member.deleted",
payload={
"member_id": str(workspace_member.member_id),
"project_ids": [str(project_id) for project_id in project_ids],
"source": "tasker",
},
extra_user_ids=[workspace_member.member_id],
)
return Response(status=status.HTTP_204_NO_CONTENT)
@invalidate_cache(
@@ -224,6 +260,20 @@ class WorkSpaceMemberViewSet(BaseViewSet):
status=status.HTTP_400_BAD_REQUEST,
)
project_ids = list(
ProjectMember.objects.filter(
workspace__slug=slug,
member_id=workspace_member.member_id,
is_active=True,
).values_list("project_id", flat=True)
)
publish_assignee_cleanup_issue_events_on_commit(
workspace_id=workspace_member.workspace_id,
assignee_id=workspace_member.member_id,
actor_id=request.user.id,
)
# # Deactivate the users from the projects where the user is part of
_ = ProjectMember.objects.filter(
workspace__slug=slug, member_id=workspace_member.member_id, is_active=True
@@ -233,6 +283,16 @@ class WorkSpaceMemberViewSet(BaseViewSet):
# # Deactivate the user
workspace_member.is_active = False
workspace_member.save()
publish_nodedc_workspace_event_on_commit(
workspace_member.workspace,
"workspace_member.deleted",
payload={
"member_id": str(workspace_member.member_id),
"project_ids": [str(project_id) for project_id in project_ids],
"source": "tasker",
},
extra_user_ids=[workspace_member.member_id],
)
return Response(status=status.HTTP_204_NO_CONTENT)