Skip to content

Commit

Permalink
version 2 of user/organization tenant account module - UN-1424 (#432)
Browse files Browse the repository at this point in the history
* version 2 of user/organization tenant account module - UN-1424

* removed migrations, Will be created once after merge

* removed unwanted templates

---------

Co-authored-by: Rahul Johny <[email protected]>
Co-authored-by: Hari John Kuriakose <[email protected]>
  • Loading branch information
3 people authored Jul 18, 2024
1 parent 9ac1f2d commit 9781e5c
Show file tree
Hide file tree
Showing 16 changed files with 795 additions and 0 deletions.
Empty file.
1 change: 1 addition & 0 deletions backend/tenant_account_v2/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Register your models here.
6 changes: 6 additions & 0 deletions backend/tenant_account_v2/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class TenantAccountV2Config(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "tenant_account_v2"
14 changes: 14 additions & 0 deletions backend/tenant_account_v2/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class PlatformServiceConstants:
IS_ACTIVE = "is_active"
KEY = "key"
ORGANIZATION = "organization"
ID = "id"
ACTIVATE = "ACTIVATE"
DEACTIVATE = "DEACTIVATE"
ACTION = "action"
KEY_NAME = "key_name"


class ErrorMessage:
KEY_EXIST = "Key name already exists"
DUPLICATE_API = "It appears that a duplicate call may have been made."
15 changes: 15 additions & 0 deletions backend/tenant_account_v2/dto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from dataclasses import dataclass


@dataclass
class OrganizationLoginResponse:
name: str
display_name: str
organization_id: str
created_at: str


@dataclass
class ResetUserPasswordDto:
status: bool
message: str
6 changes: 6 additions & 0 deletions backend/tenant_account_v2/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from enum import Enum


class UserRole(Enum):
USER = "user"
ADMIN = "admin"
20 changes: 20 additions & 0 deletions backend/tenant_account_v2/invitation_urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from django.urls import path
from tenant_account_v2.invitation_views import InvitationViewSet

invitation_list = InvitationViewSet.as_view(
{
"get": InvitationViewSet.list_invitations.__name__,
}
)

invitation_details = InvitationViewSet.as_view(
{
"delete": InvitationViewSet.delete_invitation.__name__,
}
)


urlpatterns = [
path("", invitation_list, name="invitation_list"),
path("<str:id>/", invitation_details, name="invitation_details"),
]
46 changes: 46 additions & 0 deletions backend/tenant_account_v2/invitation_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import logging

from account_v2.authentication_controller import AuthenticationController
from account_v2.dto import MemberInvitation
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
from tenant_account_v2.serializer import ListInvitationsResponseSerializer
from utils.user_session import UserSessionUtils

Logger = logging.getLogger(__name__)


class InvitationViewSet(viewsets.ViewSet):
@action(detail=False, methods=["GET"])
def list_invitations(self, request: Request) -> Response:
auth_controller = AuthenticationController()
invitations: list[MemberInvitation] = auth_controller.get_user_invitations(
organization_id=UserSessionUtils.get_organization_id(request),
)
serialized_members = ListInvitationsResponseSerializer(
invitations, many=True
).data
return Response(
status=status.HTTP_200_OK,
data={"message": "success", "members": serialized_members},
)

@action(detail=False, methods=["DELETE"])
def delete_invitation(self, request: Request, id: str) -> Response:
auth_controller = AuthenticationController()
is_deleted: bool = auth_controller.delete_user_invitation(
organization_id=UserSessionUtils.get_organization_id(request),
invitation_id=id,
)
if is_deleted:
return Response(
status=status.HTTP_204_NO_CONTENT,
data={"status": "success", "message": "success"},
)
else:
return Response(
status=status.HTTP_404_NOT_FOUND,
data={"status": "failed", "message": "failed"},
)
48 changes: 48 additions & 0 deletions backend/tenant_account_v2/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from account_v2.models import User
from django.db import models
from utils.models.organization_mixin import (
DefaultOrganizationManagerMixin,
DefaultOrganizationMixin,
)


class OrganizationMemberModelManager(DefaultOrganizationManagerMixin, models.Manager):
pass


class OrganizationMember(DefaultOrganizationMixin):
member_id = models.BigAutoField(primary_key=True)
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
default=None,
related_name="organization_user",
)
role = models.CharField()
is_login_onboarding_msg = models.BooleanField(
default=True,
db_comment="Flag to indicate whether the onboarding messages are shown",
)
is_prompt_studio_onboarding_msg = models.BooleanField(
default=True,
db_comment="Flag to indicate whether the prompt studio messages are shown",
)

def __str__(self): # type: ignore
return (
f"OrganizationMember("
f"{self.member_id}, role: {self.role}, userId: {self.user.user_id})"
)

objects = OrganizationMemberModelManager()

class Meta:
db_table = "organization_member_v2"
verbose_name = "Organization Member"
verbose_name_plural = "Organization Members"
constraints = [
models.UniqueConstraint(
fields=["organization", "user"],
name="unique_organization_member",
),
]
144 changes: 144 additions & 0 deletions backend/tenant_account_v2/organization_member_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
from typing import Any, Optional

from tenant_account_v2.models import OrganizationMember
from utils.cache_service import CacheService


class OrganizationMemberService:

@staticmethod
def get_user_by_email(email: str) -> Optional[OrganizationMember]:
try:
return OrganizationMember.objects.get(user__email=email) # type: ignore
except OrganizationMember.DoesNotExist:
return None

@staticmethod
def get_user_by_user_id(user_id: str) -> Optional[OrganizationMember]:
try:
return OrganizationMember.objects.get(user__user_id=user_id) # type: ignore
except OrganizationMember.DoesNotExist:
return None

@staticmethod
def get_user_by_id(id: str) -> Optional[OrganizationMember]:
try:
return OrganizationMember.objects.get(user=id) # type: ignore
except OrganizationMember.DoesNotExist:
return None

@staticmethod
def get_members() -> list[OrganizationMember]:
return OrganizationMember.objects.all()

@staticmethod
def get_members_by_user_email(
user_emails: list[str], values_list_fields: list[str]
) -> list[dict[str, Any]]:
"""Get members by user emails.
Parameters:
user_emails (list[str]): The emails of the users to get.
values_list_fields (list[str]): The fields to include in the result.
Returns:
list[dict[str, Any]]: The members.
"""
if not user_emails:
return []
queryset = OrganizationMember.objects.filter(user__email__in=user_emails)
if values_list_fields is None:
users = queryset.values()
else:
users = queryset.values_list(*values_list_fields)

return list(users)

@staticmethod
def delete_user(user: OrganizationMember) -> None:
"""Delete a user from an organization.
Parameters:
user (OrganizationMember): The user to delete.
"""
user.delete()

@staticmethod
def remove_users_by_user_pks(user_pks: list[str]) -> None:
"""Remove a users from an organization.
Parameters:
user_pks (list[str]): The primary keys of the users to remove.
"""
OrganizationMember.objects.filter(user__in=user_pks).delete()

@classmethod
def remove_user_by_user_id(cls, user_id: str) -> None:
"""Remove a user from an organization.
Parameters:
user_id (str): The user_id of the user to remove.
"""
user = cls.get_user_by_user_id(user_id)
if user:
cls.delete_user(user)

@staticmethod
def get_organization_user_cache_key(user_id: str, organization_id: str) -> str:
"""Get the cache key for a user in an organization.
Parameters:
organization_id (str): The ID of the organization.
Returns:
str: The cache key for a user in the organization.
"""
return f"user_organization:{user_id}:{organization_id}"

@classmethod
def check_user_membership_in_organization_cache(
cls, user_id: str, organization_id: str
) -> bool:
"""Check if a user exists in an organization.
Parameters:
user_id (str): The ID of the user to check.
organization_id (str): The ID of the organization to check.
Returns:
bool: True if the user exists in the organization, False otherwise.
"""
user_organization_key = cls.get_organization_user_cache_key(
user_id, organization_id
)
return CacheService.check_a_key_exist(user_organization_key)

@classmethod
def set_user_membership_in_organization_cache(
cls, user_id: str, organization_id: str
) -> None:
"""Set a user's membership in an organization in the cache.
Parameters:
user_id (str): The ID of the user.
organization_id (str): The ID of the organization.
"""
user_organization_key = cls.get_organization_user_cache_key(
user_id, organization_id
)
CacheService.set_key(user_organization_key, {})

@classmethod
def remove_user_membership_in_organization_cache(
cls, user_id: str, organization_id: str
) -> None:
"""Remove a user's membership in an organization from the cache.
Parameters:
user_id (str): The ID of the user.
organization_id (str): The ID of the organization.
"""
user_organization_key = cls.get_organization_user_cache_key(
user_id, organization_id
)
CacheService.delete_a_key(user_organization_key)
Loading

0 comments on commit 9781e5c

Please sign in to comment.