Initial commit from monorepo
This commit is contained in:
329
teams_app/schemas/m2m_schema.py
Normal file
329
teams_app/schemas/m2m_schema.py
Normal file
@@ -0,0 +1,329 @@
|
||||
"""
|
||||
M2M (Machine-to-Machine) GraphQL schema.
|
||||
Used by internal services (Temporal workflows, etc.) without user authentication.
|
||||
"""
|
||||
import graphene
|
||||
import logging
|
||||
from django.utils import timezone
|
||||
from graphene_django import DjangoObjectType
|
||||
from ..models import Team as TeamModel, TeamMember as TeamMemberModel, TeamAddress as TeamAddressModel, TeamInvitation as TeamInvitationModel, TeamInvitationToken as TeamInvitationTokenModel
|
||||
from .user_schema import _get_or_create_user_with_profile
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Team(DjangoObjectType):
|
||||
id = graphene.String()
|
||||
|
||||
class Meta:
|
||||
model = TeamModel
|
||||
fields = ('uuid', 'name', 'logto_org_id', 'created_at', 'updated_at')
|
||||
|
||||
def resolve_id(self, info):
|
||||
return self.uuid
|
||||
|
||||
|
||||
class M2MQuery(graphene.ObjectType):
|
||||
team = graphene.Field(Team, teamId=graphene.String(required=True))
|
||||
invitation = graphene.Field(lambda: TeamInvitation, invitationUuid=graphene.String(required=True))
|
||||
|
||||
def resolve_team(self, info, teamId):
|
||||
try:
|
||||
return TeamModel.objects.get(uuid=teamId)
|
||||
except TeamModel.DoesNotExist:
|
||||
return None
|
||||
|
||||
def resolve_invitation(self, info, invitationUuid):
|
||||
try:
|
||||
return TeamInvitationModel.objects.get(uuid=invitationUuid)
|
||||
except TeamInvitationModel.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
class TeamInvitation(DjangoObjectType):
|
||||
class Meta:
|
||||
model = TeamInvitationModel
|
||||
fields = ('uuid', 'team', 'email', 'role', 'status', 'invited_by', 'expires_at', 'created_at')
|
||||
|
||||
|
||||
class TeamInvitationToken(DjangoObjectType):
|
||||
class Meta:
|
||||
model = TeamInvitationTokenModel
|
||||
fields = ('uuid', 'invitation', 'workflow_status', 'expires_at', 'created_at')
|
||||
|
||||
|
||||
class CreateInvitationFromWorkflowInput(graphene.InputObjectType):
|
||||
teamUuid = graphene.String(required=True)
|
||||
email = graphene.String(required=True)
|
||||
role = graphene.String()
|
||||
invitedBy = graphene.String(required=True)
|
||||
expiresAt = graphene.DateTime(required=True)
|
||||
|
||||
|
||||
class CreateInvitationFromWorkflow(graphene.Mutation):
|
||||
class Arguments:
|
||||
input = CreateInvitationFromWorkflowInput(required=True)
|
||||
|
||||
success = graphene.Boolean()
|
||||
message = graphene.String()
|
||||
invitationUuid = graphene.String()
|
||||
invitation = graphene.Field(TeamInvitation)
|
||||
|
||||
def mutate(self, info, input):
|
||||
try:
|
||||
team = TeamModel.objects.get(uuid=input.teamUuid)
|
||||
invitation = TeamInvitationModel.objects.create(
|
||||
team=team,
|
||||
email=input.email,
|
||||
role=input.role or 'MEMBER',
|
||||
status='PENDING',
|
||||
invited_by=input.invitedBy,
|
||||
expires_at=input.expiresAt,
|
||||
)
|
||||
return CreateInvitationFromWorkflow(
|
||||
success=True,
|
||||
message="Invitation created",
|
||||
invitationUuid=invitation.uuid,
|
||||
invitation=invitation,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.exception("Failed to create invitation")
|
||||
return CreateInvitationFromWorkflow(success=False, message=str(exc))
|
||||
|
||||
|
||||
class CreateInvitationTokenInput(graphene.InputObjectType):
|
||||
invitationUuid = graphene.String(required=True)
|
||||
tokenHash = graphene.String(required=True)
|
||||
expiresAt = graphene.DateTime(required=True)
|
||||
|
||||
|
||||
class CreateInvitationToken(graphene.Mutation):
|
||||
class Arguments:
|
||||
input = CreateInvitationTokenInput(required=True)
|
||||
|
||||
success = graphene.Boolean()
|
||||
message = graphene.String()
|
||||
token = graphene.Field(TeamInvitationToken)
|
||||
|
||||
def mutate(self, info, input):
|
||||
try:
|
||||
invitation = TeamInvitationModel.objects.get(uuid=input.invitationUuid)
|
||||
token = TeamInvitationTokenModel.objects.create(
|
||||
invitation=invitation,
|
||||
token_hash=input.tokenHash,
|
||||
expires_at=input.expiresAt,
|
||||
workflow_status='pending',
|
||||
)
|
||||
return CreateInvitationToken(success=True, message="Token created", token=token)
|
||||
except Exception as exc:
|
||||
logger.exception("Failed to create invitation token")
|
||||
return CreateInvitationToken(success=False, message=str(exc))
|
||||
|
||||
|
||||
class UpdateInvitationTokenStatusInput(graphene.InputObjectType):
|
||||
tokenUuid = graphene.String(required=True)
|
||||
status = graphene.String(required=True) # pending | active | error
|
||||
|
||||
|
||||
class UpdateInvitationTokenStatus(graphene.Mutation):
|
||||
class Arguments:
|
||||
input = UpdateInvitationTokenStatusInput(required=True)
|
||||
|
||||
success = graphene.Boolean()
|
||||
message = graphene.String()
|
||||
token = graphene.Field(TeamInvitationToken)
|
||||
|
||||
def mutate(self, info, input):
|
||||
try:
|
||||
token = TeamInvitationTokenModel.objects.get(uuid=input.tokenUuid)
|
||||
token.workflow_status = input.status
|
||||
token.save(update_fields=['workflow_status'])
|
||||
return UpdateInvitationTokenStatus(success=True, message="Status updated", token=token)
|
||||
except TeamInvitationTokenModel.DoesNotExist:
|
||||
return UpdateInvitationTokenStatus(success=False, message="Token not found")
|
||||
|
||||
|
||||
class SetLogtoOrgIdMutation(graphene.Mutation):
|
||||
"""Set Logto organization ID (used by Temporal workflows)"""
|
||||
class Arguments:
|
||||
teamId = graphene.String(required=True)
|
||||
logtoOrgId = graphene.String(required=True)
|
||||
|
||||
team = graphene.Field(Team)
|
||||
success = graphene.Boolean()
|
||||
|
||||
def mutate(self, info, teamId, logtoOrgId):
|
||||
try:
|
||||
team = TeamModel.objects.get(uuid=teamId)
|
||||
team.logto_org_id = logtoOrgId
|
||||
team.save(update_fields=['logto_org_id'])
|
||||
logger.info("Team %s: logto_org_id set to %s", teamId, logtoOrgId)
|
||||
return SetLogtoOrgIdMutation(team=team, success=True)
|
||||
except TeamModel.DoesNotExist:
|
||||
raise Exception(f"Team {teamId} not found")
|
||||
|
||||
|
||||
class CreateAddressFromWorkflowMutation(graphene.Mutation):
|
||||
"""Create TeamAddress from Temporal workflow (workflow-first pattern)"""
|
||||
class Arguments:
|
||||
workflowId = graphene.String(required=True)
|
||||
teamUuid = graphene.String(required=True)
|
||||
name = graphene.String(required=True)
|
||||
address = graphene.String(required=True)
|
||||
latitude = graphene.Float()
|
||||
longitude = graphene.Float()
|
||||
countryCode = graphene.String()
|
||||
isDefault = graphene.Boolean()
|
||||
|
||||
success = graphene.Boolean()
|
||||
addressUuid = graphene.String()
|
||||
teamType = graphene.String() # "buyer" or "seller"
|
||||
message = graphene.String()
|
||||
|
||||
def mutate(self, info, workflowId, teamUuid, name, address,
|
||||
latitude=None, longitude=None, countryCode=None, isDefault=False):
|
||||
try:
|
||||
team = TeamModel.objects.get(uuid=teamUuid)
|
||||
|
||||
# Если новый адрес default - сбрасываем старые
|
||||
if isDefault:
|
||||
team.addresses.update(is_default=False)
|
||||
|
||||
address_obj = TeamAddressModel.objects.create(
|
||||
team=team,
|
||||
name=name,
|
||||
address=address,
|
||||
latitude=latitude,
|
||||
longitude=longitude,
|
||||
country_code=countryCode,
|
||||
is_default=isDefault,
|
||||
status='pending',
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"Created address %s for team %s (workflow=%s, team_type=%s)",
|
||||
address_obj.uuid, teamUuid, workflowId, team.team_type
|
||||
)
|
||||
|
||||
return CreateAddressFromWorkflowMutation(
|
||||
success=True,
|
||||
addressUuid=str(address_obj.uuid),
|
||||
teamType=team.team_type.lower() if team.team_type else "buyer",
|
||||
message="Address created"
|
||||
)
|
||||
except TeamModel.DoesNotExist:
|
||||
return CreateAddressFromWorkflowMutation(
|
||||
success=False,
|
||||
message=f"Team {teamUuid} not found"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Failed to create address: %s", e)
|
||||
return CreateAddressFromWorkflowMutation(
|
||||
success=False,
|
||||
message=str(e)
|
||||
)
|
||||
|
||||
|
||||
class CreateTeamFromWorkflowMutation(graphene.Mutation):
|
||||
"""Create Team from Temporal workflow (KYC approval flow)"""
|
||||
class Arguments:
|
||||
teamName = graphene.String(required=True)
|
||||
ownerId = graphene.String(required=True) # Logto user ID
|
||||
teamType = graphene.String() # BUYER | SELLER, default BUYER
|
||||
countryCode = graphene.String()
|
||||
|
||||
success = graphene.Boolean()
|
||||
teamId = graphene.String()
|
||||
teamUuid = graphene.String()
|
||||
message = graphene.String()
|
||||
|
||||
def mutate(self, info, teamName, ownerId, teamType=None, countryCode=None):
|
||||
try:
|
||||
# Получаем или создаём пользователя
|
||||
owner = _get_or_create_user_with_profile(ownerId)
|
||||
|
||||
# Создаём команду
|
||||
team = TeamModel.objects.create(
|
||||
name=teamName,
|
||||
owner=owner,
|
||||
team_type=teamType or 'BUYER'
|
||||
)
|
||||
|
||||
# Добавляем owner как участника команды с ролью OWNER
|
||||
TeamMemberModel.objects.create(
|
||||
team=team,
|
||||
user=owner,
|
||||
role='OWNER'
|
||||
)
|
||||
|
||||
# Устанавливаем как активную команду
|
||||
if hasattr(owner, 'profile') and not owner.profile.active_team:
|
||||
owner.profile.active_team = team
|
||||
owner.profile.save(update_fields=['active_team'])
|
||||
|
||||
logger.info(
|
||||
"Created team %s (%s) for owner %s from workflow",
|
||||
team.uuid, teamName, ownerId
|
||||
)
|
||||
|
||||
return CreateTeamFromWorkflowMutation(
|
||||
success=True,
|
||||
teamId=str(team.id),
|
||||
teamUuid=str(team.uuid),
|
||||
message="Team created"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Failed to create team from workflow: %s", e)
|
||||
return CreateTeamFromWorkflowMutation(
|
||||
success=False,
|
||||
message=str(e)
|
||||
)
|
||||
|
||||
|
||||
class UpdateAddressStatusMutation(graphene.Mutation):
|
||||
"""Update address processing status (used by Temporal workflows)"""
|
||||
class Arguments:
|
||||
addressUuid = graphene.String(required=True)
|
||||
status = graphene.String(required=True) # pending, active, error
|
||||
errorMessage = graphene.String()
|
||||
|
||||
success = graphene.Boolean()
|
||||
message = graphene.String()
|
||||
|
||||
def mutate(self, info, addressUuid, status, errorMessage=None):
|
||||
try:
|
||||
address = TeamAddressModel.objects.get(uuid=addressUuid)
|
||||
address.status = status
|
||||
update_fields = ['status', 'updated_at']
|
||||
|
||||
if errorMessage:
|
||||
address.error_message = errorMessage
|
||||
update_fields.append('error_message')
|
||||
|
||||
if status == 'active':
|
||||
address.processed_at = timezone.now()
|
||||
update_fields.append('processed_at')
|
||||
|
||||
address.save(update_fields=update_fields)
|
||||
|
||||
logger.info("Address %s status updated to %s", addressUuid, status)
|
||||
|
||||
return UpdateAddressStatusMutation(success=True, message="Status updated")
|
||||
except TeamAddressModel.DoesNotExist:
|
||||
return UpdateAddressStatusMutation(
|
||||
success=False,
|
||||
message=f"Address {addressUuid} not found"
|
||||
)
|
||||
|
||||
|
||||
class M2MMutation(graphene.ObjectType):
|
||||
setLogtoOrgId = SetLogtoOrgIdMutation.Field()
|
||||
createTeamFromWorkflow = CreateTeamFromWorkflowMutation.Field()
|
||||
createAddressFromWorkflow = CreateAddressFromWorkflowMutation.Field()
|
||||
updateAddressStatus = UpdateAddressStatusMutation.Field()
|
||||
createInvitationFromWorkflow = CreateInvitationFromWorkflow.Field()
|
||||
createInvitationToken = CreateInvitationToken.Field()
|
||||
updateInvitationTokenStatus = UpdateInvitationTokenStatus.Field()
|
||||
|
||||
|
||||
m2m_schema = graphene.Schema(query=M2MQuery, mutation=M2MMutation)
|
||||
Reference in New Issue
Block a user