Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
Comment thread
hari-kuriakose marked this conversation as resolved.
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()
Comment thread
ritwik-g marked this conversation as resolved.

@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