ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: Tasker workspace adapter API
This commit is contained in:
parent
8ec762f790
commit
a5a347e839
|
|
@ -0,0 +1,186 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
from django.db import transaction
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views import View
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
|
from plane.authentication.views.nodedc_logout import is_internal_logout_request_authorized
|
||||||
|
from plane.db.models import ExternalIdentityLink, Profile, User, Workspace, WorkspaceMember
|
||||||
|
|
||||||
|
|
||||||
|
OIDC_PROVIDER = "authentik"
|
||||||
|
ROLE_VALUES = {
|
||||||
|
"guest": 5,
|
||||||
|
"viewer": 5,
|
||||||
|
"member": 15,
|
||||||
|
"admin": 20,
|
||||||
|
"owner": 20,
|
||||||
|
5: 5,
|
||||||
|
15: 15,
|
||||||
|
20: 20,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def internal_unauthorized_response():
|
||||||
|
return JsonResponse({"ok": False, "error": "internal_access_unauthorized"}, status=401)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_json_body(request):
|
||||||
|
if not request.body:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
return json.loads(request.body.decode("utf-8"))
|
||||||
|
except (UnicodeDecodeError, json.JSONDecodeError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_email(value):
|
||||||
|
return value.strip().lower() if isinstance(value, str) else ""
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_workspace(payload):
|
||||||
|
workspace_id = payload.get("workspaceId") or payload.get("workspace_id")
|
||||||
|
workspace_slug = payload.get("workspaceSlug") or payload.get("workspace_slug") or payload.get("slug")
|
||||||
|
|
||||||
|
queryset = Workspace.objects.filter(deleted_at__isnull=True)
|
||||||
|
if workspace_id:
|
||||||
|
return queryset.filter(id=workspace_id).first()
|
||||||
|
if workspace_slug:
|
||||||
|
return queryset.filter(slug=workspace_slug).first()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_user(payload):
|
||||||
|
plane_user_id = payload.get("planeUserId") or payload.get("plane_user_id")
|
||||||
|
subject = payload.get("subject")
|
||||||
|
email = normalize_email(payload.get("email"))
|
||||||
|
|
||||||
|
if plane_user_id:
|
||||||
|
user = User.objects.filter(id=plane_user_id, is_bot=False).first()
|
||||||
|
if user:
|
||||||
|
return user
|
||||||
|
|
||||||
|
if subject:
|
||||||
|
link = ExternalIdentityLink.objects.filter(
|
||||||
|
provider=OIDC_PROVIDER,
|
||||||
|
subject=subject,
|
||||||
|
status=ExternalIdentityLink.Status.ACTIVE,
|
||||||
|
).select_related("user").first()
|
||||||
|
if link:
|
||||||
|
return link.user
|
||||||
|
|
||||||
|
if email:
|
||||||
|
link = ExternalIdentityLink.objects.filter(
|
||||||
|
provider=OIDC_PROVIDER,
|
||||||
|
email__iexact=email,
|
||||||
|
status=ExternalIdentityLink.Status.ACTIVE,
|
||||||
|
).select_related("user").first()
|
||||||
|
if link:
|
||||||
|
return link.user
|
||||||
|
return User.objects.filter(email__iexact=email, is_bot=False).first()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_role(value):
|
||||||
|
return ROLE_VALUES.get(value, 15)
|
||||||
|
|
||||||
|
|
||||||
|
def serialize_workspace(workspace):
|
||||||
|
return {
|
||||||
|
"id": str(workspace.id),
|
||||||
|
"slug": workspace.slug,
|
||||||
|
"name": workspace.name,
|
||||||
|
"ownerEmail": workspace.owner.email if workspace.owner_id else None,
|
||||||
|
"memberCount": WorkspaceMember.objects.filter(
|
||||||
|
workspace=workspace,
|
||||||
|
deleted_at__isnull=True,
|
||||||
|
is_active=True,
|
||||||
|
member__is_bot=False,
|
||||||
|
).count(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def serialize_membership(membership, created):
|
||||||
|
return {
|
||||||
|
"created": created,
|
||||||
|
"workspace": serialize_workspace(membership.workspace),
|
||||||
|
"member": {
|
||||||
|
"id": str(membership.member.id),
|
||||||
|
"email": membership.member.email,
|
||||||
|
"displayName": membership.member.display_name,
|
||||||
|
},
|
||||||
|
"role": membership.role,
|
||||||
|
"isActive": membership.is_active,
|
||||||
|
"isBanned": membership.is_banned,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(csrf_exempt, name="dispatch")
|
||||||
|
class NodeDCInternalWorkspaceListEndpoint(View):
|
||||||
|
def get(self, request):
|
||||||
|
if not is_internal_logout_request_authorized(request):
|
||||||
|
return internal_unauthorized_response()
|
||||||
|
|
||||||
|
workspaces = Workspace.objects.filter(deleted_at__isnull=True).select_related("owner").order_by("name")
|
||||||
|
return JsonResponse({"ok": True, "workspaces": [serialize_workspace(workspace) for workspace in workspaces]})
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(csrf_exempt, name="dispatch")
|
||||||
|
class NodeDCInternalWorkspaceMembershipEnsureEndpoint(View):
|
||||||
|
def post(self, request):
|
||||||
|
if not is_internal_logout_request_authorized(request):
|
||||||
|
return internal_unauthorized_response()
|
||||||
|
|
||||||
|
payload = parse_json_body(request)
|
||||||
|
if payload is None:
|
||||||
|
return JsonResponse({"ok": False, "error": "invalid_json"}, status=400)
|
||||||
|
|
||||||
|
workspace = resolve_workspace(payload)
|
||||||
|
if workspace is None:
|
||||||
|
return JsonResponse({"ok": False, "error": "workspace_not_found"}, status=404)
|
||||||
|
|
||||||
|
user = resolve_user(payload)
|
||||||
|
if user is None:
|
||||||
|
return JsonResponse({"ok": False, "error": "user_not_found"}, status=404)
|
||||||
|
|
||||||
|
role = normalize_role(payload.get("role"))
|
||||||
|
company_role = payload.get("companyRole") or payload.get("company_role")
|
||||||
|
set_last_workspace = payload.get("setLastWorkspace", True) is not False
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
membership = WorkspaceMember.objects.filter(
|
||||||
|
workspace=workspace,
|
||||||
|
member=user,
|
||||||
|
deleted_at__isnull=True,
|
||||||
|
).first()
|
||||||
|
created = membership is None
|
||||||
|
|
||||||
|
if membership is None:
|
||||||
|
membership = WorkspaceMember.objects.create(
|
||||||
|
workspace=workspace,
|
||||||
|
member=user,
|
||||||
|
role=role,
|
||||||
|
company_role=company_role if isinstance(company_role, str) else None,
|
||||||
|
is_active=True,
|
||||||
|
is_banned=False,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
membership.role = role
|
||||||
|
if isinstance(company_role, str):
|
||||||
|
membership.company_role = company_role
|
||||||
|
membership.is_active = True
|
||||||
|
membership.is_banned = False
|
||||||
|
membership.banned_at = None
|
||||||
|
membership.banned_until = None
|
||||||
|
membership.save(update_fields=["role", "company_role", "is_active", "is_banned", "banned_at", "banned_until", "updated_at"])
|
||||||
|
|
||||||
|
if set_last_workspace:
|
||||||
|
profile, _ = Profile.objects.get_or_create(user=user)
|
||||||
|
profile.last_workspace_id = workspace.id
|
||||||
|
profile.save(update_fields=["last_workspace_id", "updated_at"])
|
||||||
|
|
||||||
|
return JsonResponse({"ok": True, "membership": serialize_membership(membership, created)})
|
||||||
|
|
@ -15,6 +15,10 @@ from plane.authentication.views.nodedc_logout import (
|
||||||
NodeDCFrontChannelLogoutEndpoint,
|
NodeDCFrontChannelLogoutEndpoint,
|
||||||
NodeDCInternalSessionLogoutEndpoint,
|
NodeDCInternalSessionLogoutEndpoint,
|
||||||
)
|
)
|
||||||
|
from plane.authentication.views.nodedc_workspace_adapter import (
|
||||||
|
NodeDCInternalWorkspaceListEndpoint,
|
||||||
|
NodeDCInternalWorkspaceMembershipEnsureEndpoint,
|
||||||
|
)
|
||||||
|
|
||||||
handler404 = "plane.app.views.error_404.custom_404_view"
|
handler404 = "plane.app.views.error_404.custom_404_view"
|
||||||
|
|
||||||
|
|
@ -24,6 +28,16 @@ urlpatterns = [
|
||||||
NodeDCInternalSessionLogoutEndpoint.as_view(),
|
NodeDCInternalSessionLogoutEndpoint.as_view(),
|
||||||
name="nodedc-internal-session-logout",
|
name="nodedc-internal-session-logout",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"api/internal/nodedc/workspaces/",
|
||||||
|
NodeDCInternalWorkspaceListEndpoint.as_view(),
|
||||||
|
name="nodedc-internal-workspaces",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"api/internal/nodedc/workspace-memberships/ensure/",
|
||||||
|
NodeDCInternalWorkspaceMembershipEnsureEndpoint.as_view(),
|
||||||
|
name="nodedc-internal-workspace-membership-ensure",
|
||||||
|
),
|
||||||
path("api/", include("plane.app.urls")),
|
path("api/", include("plane.app.urls")),
|
||||||
path("api/public/", include("plane.space.urls")),
|
path("api/public/", include("plane.space.urls")),
|
||||||
path("api/instances/", include("plane.license.urls")),
|
path("api/instances/", include("plane.license.urls")),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue