Initial commit from monorepo
This commit is contained in:
BIN
teams_app/schemas/__pycache__/m2m_schema.cpython-313.pyc
Normal file
BIN
teams_app/schemas/__pycache__/m2m_schema.cpython-313.pyc
Normal file
Binary file not shown.
BIN
teams_app/schemas/__pycache__/public_schema.cpython-313.pyc
Normal file
BIN
teams_app/schemas/__pycache__/public_schema.cpython-313.pyc
Normal file
Binary file not shown.
BIN
teams_app/schemas/__pycache__/team_schema.cpython-313.pyc
Normal file
BIN
teams_app/schemas/__pycache__/team_schema.cpython-313.pyc
Normal file
Binary file not shown.
BIN
teams_app/schemas/__pycache__/user_schema.cpython-313.pyc
Normal file
BIN
teams_app/schemas/__pycache__/user_schema.cpython-313.pyc
Normal file
Binary file not shown.
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)
|
||||
12
teams_app/schemas/public_schema.py
Normal file
12
teams_app/schemas/public_schema.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import graphene
|
||||
|
||||
|
||||
class PublicQuery(graphene.ObjectType):
|
||||
"""Public schema - no authentication required"""
|
||||
_placeholder = graphene.String(description="Placeholder field")
|
||||
|
||||
def resolve__placeholder(self, info):
|
||||
return None
|
||||
|
||||
|
||||
public_schema = graphene.Schema(query=PublicQuery)
|
||||
367
teams_app/schemas/team_schema.py
Normal file
367
teams_app/schemas/team_schema.py
Normal file
@@ -0,0 +1,367 @@
|
||||
import graphene
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
from graphene_django import DjangoObjectType
|
||||
from django.contrib.auth import get_user_model
|
||||
from ..models import Team as TeamModel, TeamMember as TeamMemberModel, TeamAddress as TeamAddressModel
|
||||
from ..permissions import require_scopes
|
||||
|
||||
UserModel = get_user_model()
|
||||
|
||||
class User(DjangoObjectType):
|
||||
id = graphene.String()
|
||||
firstName = graphene.String()
|
||||
lastName = graphene.String()
|
||||
phone = graphene.String()
|
||||
avatarId = graphene.String()
|
||||
createdAt = graphene.String()
|
||||
|
||||
class Meta:
|
||||
model = UserModel
|
||||
fields = ('username', 'first_name', 'last_name', 'email')
|
||||
|
||||
def resolve_id(self, info):
|
||||
if hasattr(self, 'profile') and self.profile:
|
||||
return self.profile.logto_id
|
||||
return self.username
|
||||
|
||||
def resolve_firstName(self, info):
|
||||
return self.first_name
|
||||
|
||||
def resolve_lastName(self, info):
|
||||
return self.last_name
|
||||
|
||||
def resolve_phone(self, info):
|
||||
return getattr(self.profile, 'phone', None)
|
||||
|
||||
def resolve_avatarId(self, info):
|
||||
return getattr(self.profile, 'avatar_id', None)
|
||||
|
||||
def resolve_createdAt(self, info):
|
||||
return self.date_joined.isoformat() if self.date_joined else None
|
||||
|
||||
class TeamMember(DjangoObjectType):
|
||||
user = graphene.Field(User)
|
||||
joinedAt = graphene.String()
|
||||
|
||||
class Meta:
|
||||
model = TeamMemberModel
|
||||
fields = ('role', 'joined_at')
|
||||
|
||||
def resolve_user(self, info):
|
||||
return self.user
|
||||
|
||||
def resolve_joinedAt(self, info):
|
||||
return self.joined_at.isoformat() if self.joined_at else None
|
||||
|
||||
class TeamAddress(DjangoObjectType):
|
||||
isDefault = graphene.Boolean()
|
||||
createdAt = graphene.String()
|
||||
graphNodeId = graphene.String()
|
||||
processedAt = graphene.String()
|
||||
countryCode = graphene.String()
|
||||
|
||||
class Meta:
|
||||
model = TeamAddressModel
|
||||
fields = ('uuid', 'name', 'address', 'latitude', 'longitude', 'is_default', 'created_at', 'country_code')
|
||||
|
||||
def resolve_isDefault(self, info):
|
||||
return self.is_default
|
||||
|
||||
def resolve_createdAt(self, info):
|
||||
return self.created_at.isoformat() if self.created_at else None
|
||||
|
||||
def resolve_graphNodeId(self, info):
|
||||
return self.graph_node_id
|
||||
|
||||
def resolve_processedAt(self, info):
|
||||
return self.processed_at.isoformat() if self.processed_at else None
|
||||
|
||||
def resolve_countryCode(self, info):
|
||||
return self.country_code
|
||||
|
||||
|
||||
class SelectedLocation(graphene.ObjectType):
|
||||
type = graphene.String()
|
||||
uuid = graphene.String()
|
||||
name = graphene.String()
|
||||
latitude = graphene.Float()
|
||||
longitude = graphene.Float()
|
||||
|
||||
|
||||
class Team(DjangoObjectType):
|
||||
id = graphene.String()
|
||||
ownerId = graphene.String()
|
||||
members = graphene.List(TeamMember)
|
||||
addresses = graphene.List(lambda: TeamAddress)
|
||||
selectedLocation = graphene.Field(SelectedLocation)
|
||||
|
||||
class Meta:
|
||||
model = TeamModel
|
||||
fields = ('uuid', 'name', 'logto_org_id', 'owner', 'created_at', 'updated_at')
|
||||
|
||||
def resolve_id(self, info):
|
||||
return self.uuid
|
||||
|
||||
def resolve_ownerId(self, info):
|
||||
if self.owner and hasattr(self.owner, 'profile'):
|
||||
return self.owner.profile.logto_id
|
||||
return self.owner.username if self.owner else None
|
||||
|
||||
def resolve_members(self, info):
|
||||
return self.members.all()
|
||||
|
||||
def resolve_addresses(self, info):
|
||||
return self.addresses.all()
|
||||
|
||||
def resolve_selectedLocation(self, info):
|
||||
loc_type = getattr(self, 'selected_location_type', None)
|
||||
loc_uuid = getattr(self, 'selected_location_uuid', None)
|
||||
if loc_type and loc_uuid:
|
||||
return SelectedLocation(
|
||||
type=loc_type,
|
||||
uuid=loc_uuid,
|
||||
name=getattr(self, 'selected_location_name', None),
|
||||
latitude=getattr(self, 'selected_location_latitude', None),
|
||||
longitude=getattr(self, 'selected_location_longitude', None)
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
class TeamQuery(graphene.ObjectType):
|
||||
team = graphene.Field(Team)
|
||||
getTeam = graphene.Field(Team, teamId=graphene.String(required=True))
|
||||
team_members = graphene.List(TeamMember)
|
||||
team_addresses = graphene.List(TeamAddress)
|
||||
|
||||
@require_scopes("teams:member")
|
||||
def resolve_team(self, info):
|
||||
team_uuid = getattr(info.context, 'team_uuid', None)
|
||||
if not team_uuid:
|
||||
return None
|
||||
|
||||
try:
|
||||
return TeamModel.objects.get(uuid=team_uuid)
|
||||
except TeamModel.DoesNotExist:
|
||||
return None
|
||||
|
||||
@require_scopes("teams:member")
|
||||
def resolve_getTeam(self, info, teamId):
|
||||
# Получаем конкретную команду по ID
|
||||
try:
|
||||
return TeamModel.objects.get(uuid=teamId)
|
||||
except TeamModel.DoesNotExist:
|
||||
return None
|
||||
|
||||
@require_scopes("teams:member")
|
||||
def resolve_team_members(self, info):
|
||||
# Получаем участников команды
|
||||
team_uuid = getattr(info.context, 'team_uuid', None)
|
||||
if not team_uuid:
|
||||
return []
|
||||
|
||||
try:
|
||||
team = TeamModel.objects.get(uuid=team_uuid)
|
||||
return team.members.all()
|
||||
except TeamModel.DoesNotExist:
|
||||
return []
|
||||
|
||||
@require_scopes("teams:member")
|
||||
def resolve_team_addresses(self, info):
|
||||
team_uuid = getattr(info.context, 'team_uuid', None)
|
||||
if not team_uuid:
|
||||
return []
|
||||
|
||||
try:
|
||||
team = TeamModel.objects.get(uuid=team_uuid)
|
||||
return team.addresses.all()
|
||||
except TeamModel.DoesNotExist:
|
||||
return []
|
||||
|
||||
|
||||
class InviteMemberInput(graphene.InputObjectType):
|
||||
email = graphene.String(required=True)
|
||||
role = graphene.String()
|
||||
|
||||
class InviteMemberMutation(graphene.Mutation):
|
||||
class Arguments:
|
||||
input = InviteMemberInput(required=True)
|
||||
|
||||
success = graphene.Boolean()
|
||||
message = graphene.String()
|
||||
|
||||
@require_scopes("teams:member")
|
||||
def mutate(self, info, input):
|
||||
from ..temporal_client import start_invite_workflow
|
||||
|
||||
# Проверяем права - только owner может приглашать
|
||||
team_uuid = getattr(info.context, 'team_uuid', None)
|
||||
user_id = getattr(info.context, 'user_id', None)
|
||||
|
||||
if not team_uuid or not user_id:
|
||||
return InviteMemberMutation(success=False, message="Недостаточно прав")
|
||||
|
||||
try:
|
||||
team = TeamModel.objects.get(uuid=team_uuid)
|
||||
|
||||
# Проверяем что пользователь - owner команды
|
||||
if not team.owner:
|
||||
return InviteMemberMutation(success=False, message="Только owner может приглашать")
|
||||
owner_identifier = team.owner.profile.logto_id if hasattr(team.owner, 'profile') and team.owner.profile else team.owner.username
|
||||
if owner_identifier != user_id:
|
||||
return InviteMemberMutation(success=False, message="Только owner может приглашать")
|
||||
|
||||
expires_at = timezone.now() + timedelta(days=7)
|
||||
start_invite_workflow(
|
||||
team_uuid=str(team.uuid),
|
||||
email=input.email,
|
||||
role=input.role or 'MEMBER',
|
||||
invited_by=owner_identifier,
|
||||
expires_at=expires_at.isoformat(),
|
||||
)
|
||||
|
||||
return InviteMemberMutation(success=True, message="Приглашение отправлено")
|
||||
|
||||
except TeamModel.DoesNotExist:
|
||||
return InviteMemberMutation(success=False, message="Команда не найдена")
|
||||
|
||||
class CreateTeamAddressInput(graphene.InputObjectType):
|
||||
name = graphene.String(required=True)
|
||||
address = graphene.String(required=True)
|
||||
latitude = graphene.Float()
|
||||
longitude = graphene.Float()
|
||||
countryCode = graphene.String()
|
||||
isDefault = graphene.Boolean()
|
||||
|
||||
|
||||
class CreateTeamAddressMutation(graphene.Mutation):
|
||||
class Arguments:
|
||||
input = CreateTeamAddressInput(required=True)
|
||||
|
||||
success = graphene.Boolean()
|
||||
message = graphene.String()
|
||||
workflowId = graphene.String()
|
||||
|
||||
@require_scopes("teams:member")
|
||||
def mutate(self, info, input):
|
||||
from ..temporal_client import start_address_workflow
|
||||
|
||||
team_uuid = getattr(info.context, 'team_uuid', None)
|
||||
if not team_uuid:
|
||||
return CreateTeamAddressMutation(success=False, message="Не авторизован")
|
||||
|
||||
try:
|
||||
team = TeamModel.objects.get(uuid=team_uuid)
|
||||
|
||||
# Запускаем workflow - он сам создаст адрес через M2M мутацию
|
||||
workflow_id, _ = start_address_workflow(
|
||||
team_uuid=str(team.uuid),
|
||||
name=input.name,
|
||||
address=input.address,
|
||||
latitude=input.get('latitude'),
|
||||
longitude=input.get('longitude'),
|
||||
country_code=input.get('countryCode'),
|
||||
is_default=input.get('isDefault', False),
|
||||
)
|
||||
|
||||
return CreateTeamAddressMutation(
|
||||
success=True,
|
||||
message="Адрес создается",
|
||||
workflowId=workflow_id,
|
||||
)
|
||||
|
||||
except TeamModel.DoesNotExist:
|
||||
return CreateTeamAddressMutation(success=False, message="Команда не найдена")
|
||||
except Exception as e:
|
||||
return CreateTeamAddressMutation(success=False, message=str(e))
|
||||
|
||||
|
||||
class DeleteTeamAddressMutation(graphene.Mutation):
|
||||
class Arguments:
|
||||
uuid = graphene.String(required=True)
|
||||
|
||||
success = graphene.Boolean()
|
||||
message = graphene.String()
|
||||
|
||||
@require_scopes("teams:member")
|
||||
def mutate(self, info, uuid):
|
||||
team_uuid = getattr(info.context, 'team_uuid', None)
|
||||
if not team_uuid:
|
||||
return DeleteTeamAddressMutation(success=False, message="Не авторизован")
|
||||
|
||||
try:
|
||||
team = TeamModel.objects.get(uuid=team_uuid)
|
||||
address = team.addresses.get(uuid=uuid)
|
||||
address.delete()
|
||||
return DeleteTeamAddressMutation(success=True, message="Адрес удален")
|
||||
|
||||
except TeamModel.DoesNotExist:
|
||||
return DeleteTeamAddressMutation(success=False, message="Команда не найдена")
|
||||
except TeamAddressModel.DoesNotExist:
|
||||
return DeleteTeamAddressMutation(success=False, message="Адрес не найден")
|
||||
|
||||
|
||||
class SetSelectedLocationInput(graphene.InputObjectType):
|
||||
type = graphene.String(required=True) # 'address' или 'hub'
|
||||
uuid = graphene.String(required=True)
|
||||
name = graphene.String(required=True)
|
||||
latitude = graphene.Float(required=True)
|
||||
longitude = graphene.Float(required=True)
|
||||
|
||||
|
||||
class SetSelectedLocationMutation(graphene.Mutation):
|
||||
class Arguments:
|
||||
input = SetSelectedLocationInput(required=True)
|
||||
|
||||
success = graphene.Boolean()
|
||||
message = graphene.String()
|
||||
selectedLocation = graphene.Field(SelectedLocation)
|
||||
|
||||
@require_scopes("teams:member")
|
||||
def mutate(self, info, input):
|
||||
team_uuid = getattr(info.context, 'team_uuid', None)
|
||||
if not team_uuid:
|
||||
return SetSelectedLocationMutation(success=False, message="Не авторизован")
|
||||
|
||||
location_type = input.type
|
||||
if location_type not in ('address', 'hub'):
|
||||
return SetSelectedLocationMutation(success=False, message="Неверный тип локации")
|
||||
|
||||
try:
|
||||
team = TeamModel.objects.get(uuid=team_uuid)
|
||||
team.selected_location_type = location_type
|
||||
team.selected_location_uuid = input.uuid
|
||||
team.selected_location_name = input.name
|
||||
team.selected_location_latitude = input.latitude
|
||||
team.selected_location_longitude = input.longitude
|
||||
team.save(update_fields=[
|
||||
'selected_location_type',
|
||||
'selected_location_uuid',
|
||||
'selected_location_name',
|
||||
'selected_location_latitude',
|
||||
'selected_location_longitude'
|
||||
])
|
||||
|
||||
return SetSelectedLocationMutation(
|
||||
success=True,
|
||||
message="Локация выбрана",
|
||||
selectedLocation=SelectedLocation(
|
||||
type=location_type,
|
||||
uuid=input.uuid,
|
||||
name=input.name,
|
||||
latitude=input.latitude,
|
||||
longitude=input.longitude
|
||||
)
|
||||
)
|
||||
|
||||
except TeamModel.DoesNotExist:
|
||||
return SetSelectedLocationMutation(success=False, message="Команда не найдена")
|
||||
|
||||
|
||||
class TeamMutation(graphene.ObjectType):
|
||||
invite_member = InviteMemberMutation.Field()
|
||||
create_team_address = CreateTeamAddressMutation.Field()
|
||||
delete_team_address = DeleteTeamAddressMutation.Field()
|
||||
set_selected_location = SetSelectedLocationMutation.Field()
|
||||
|
||||
team_schema = graphene.Schema(query=TeamQuery, mutation=TeamMutation)
|
||||
287
teams_app/schemas/user_schema.py
Normal file
287
teams_app/schemas/user_schema.py
Normal file
@@ -0,0 +1,287 @@
|
||||
import graphene
|
||||
from graphene_django import DjangoObjectType
|
||||
from django.contrib.auth import get_user_model
|
||||
from ..models import Team as TeamModel, TeamMember as TeamMemberModel, TeamInvitation as TeamInvitationModel, UserProfile
|
||||
from .team_schema import SelectedLocation
|
||||
|
||||
UserModel = get_user_model()
|
||||
|
||||
|
||||
def _get_or_create_user_with_profile(logto_id: str):
|
||||
user, _ = UserModel.objects.get_or_create(
|
||||
username=logto_id,
|
||||
defaults={'email': ''}
|
||||
)
|
||||
profile, _ = UserProfile.objects.get_or_create(
|
||||
logto_id=logto_id,
|
||||
defaults={'user': user}
|
||||
)
|
||||
if profile.user_id != user.id:
|
||||
profile.user = user
|
||||
profile.save(update_fields=['user'])
|
||||
# Attach profile to user for resolvers
|
||||
user.profile = profile
|
||||
return user
|
||||
|
||||
class Team(DjangoObjectType):
|
||||
id = graphene.String()
|
||||
logtoOrgId = graphene.String()
|
||||
teamType = graphene.String()
|
||||
selectedLocation = graphene.Field(SelectedLocation)
|
||||
|
||||
class Meta:
|
||||
model = TeamModel
|
||||
fields = ('uuid', 'name', 'logto_org_id', 'team_type', 'created_at')
|
||||
|
||||
def resolve_id(self, info):
|
||||
return self.uuid
|
||||
|
||||
def resolve_logtoOrgId(self, info):
|
||||
return self.logto_org_id
|
||||
|
||||
def resolve_teamType(self, info):
|
||||
return self.team_type
|
||||
|
||||
def resolve_selectedLocation(self, info):
|
||||
loc_type = getattr(self, 'selected_location_type', None)
|
||||
loc_uuid = getattr(self, 'selected_location_uuid', None)
|
||||
if loc_type and loc_uuid:
|
||||
return SelectedLocation(
|
||||
type=loc_type,
|
||||
uuid=loc_uuid,
|
||||
name=getattr(self, 'selected_location_name', None),
|
||||
latitude=getattr(self, 'selected_location_latitude', None),
|
||||
longitude=getattr(self, 'selected_location_longitude', None)
|
||||
)
|
||||
return None
|
||||
|
||||
class User(DjangoObjectType):
|
||||
id = graphene.String()
|
||||
firstName = graphene.String()
|
||||
lastName = graphene.String()
|
||||
phone = graphene.String()
|
||||
avatarId = graphene.String()
|
||||
activeTeamId = graphene.String()
|
||||
activeTeam = graphene.Field(Team)
|
||||
teams = graphene.List(Team)
|
||||
|
||||
class Meta:
|
||||
model = UserModel
|
||||
fields = ('username', 'first_name', 'last_name', 'email')
|
||||
|
||||
def resolve_id(self, info):
|
||||
if hasattr(self, 'profile') and self.profile:
|
||||
return self.profile.logto_id
|
||||
return self.username
|
||||
|
||||
def resolve_firstName(self, info):
|
||||
return self.first_name
|
||||
|
||||
def resolve_lastName(self, info):
|
||||
return self.last_name
|
||||
|
||||
def resolve_phone(self, info):
|
||||
return getattr(self.profile, 'phone', None)
|
||||
|
||||
def resolve_avatarId(self, info):
|
||||
return getattr(self.profile, 'avatar_id', None)
|
||||
|
||||
def resolve_activeTeamId(self, info):
|
||||
return self.profile.active_team.uuid if getattr(self, 'profile', None) and self.profile.active_team else None
|
||||
|
||||
def resolve_activeTeam(self, info):
|
||||
return self.profile.active_team if getattr(self, 'profile', None) else None
|
||||
|
||||
def resolve_teams(self, info):
|
||||
# Возвращаем Team объекты через TeamMember отношения
|
||||
from ..models import TeamMember as TeamMemberModel
|
||||
team_members = TeamMemberModel.objects.filter(user=self)
|
||||
return [member.team for member in team_members]
|
||||
|
||||
class TeamMember(DjangoObjectType):
|
||||
user = graphene.Field(User)
|
||||
role = graphene.String()
|
||||
joinedAt = graphene.String()
|
||||
|
||||
class Meta:
|
||||
from ..models import TeamMember as TeamMemberModel
|
||||
model = TeamMemberModel
|
||||
fields = ('uuid', 'role')
|
||||
|
||||
def resolve_joinedAt(self, info):
|
||||
return self.joined_at.isoformat() if self.joined_at else None
|
||||
|
||||
|
||||
class TeamInvitation(DjangoObjectType):
|
||||
email = graphene.String()
|
||||
role = graphene.String()
|
||||
status = graphene.String()
|
||||
invitedBy = graphene.String()
|
||||
expiresAt = graphene.String()
|
||||
createdAt = graphene.String()
|
||||
|
||||
class Meta:
|
||||
model = TeamInvitationModel
|
||||
fields = ('uuid', 'email', 'role', 'status')
|
||||
|
||||
def resolve_invitedBy(self, info):
|
||||
return self.invited_by
|
||||
|
||||
def resolve_expiresAt(self, info):
|
||||
return self.expires_at.isoformat() if self.expires_at else None
|
||||
|
||||
def resolve_createdAt(self, info):
|
||||
return self.created_at.isoformat() if self.created_at else None
|
||||
|
||||
|
||||
class TeamWithMembers(DjangoObjectType):
|
||||
id = graphene.String()
|
||||
members = graphene.List(TeamMember)
|
||||
invitations = graphene.List(TeamInvitation)
|
||||
|
||||
class Meta:
|
||||
model = TeamModel
|
||||
fields = ('uuid', 'name', 'created_at')
|
||||
|
||||
def resolve_id(self, info):
|
||||
return self.uuid
|
||||
|
||||
def resolve_members(self, info):
|
||||
return self.members.all()
|
||||
|
||||
def resolve_invitations(self, info):
|
||||
return self.invitations.filter(status='PENDING')
|
||||
|
||||
|
||||
class UserQuery(graphene.ObjectType):
|
||||
me = graphene.Field(User)
|
||||
get_team = graphene.Field(TeamWithMembers, team_id=graphene.String(required=True))
|
||||
|
||||
def resolve_me(self, info):
|
||||
# Получаем user_id из ID Token
|
||||
user_id = getattr(info.context, 'user_id', None)
|
||||
if not user_id:
|
||||
return None
|
||||
|
||||
try:
|
||||
return _get_or_create_user_with_profile(user_id)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def resolve_get_team(self, info, team_id):
|
||||
try:
|
||||
return TeamModel.objects.get(uuid=team_id)
|
||||
except TeamModel.DoesNotExist:
|
||||
return None
|
||||
|
||||
class CreateTeamInput(graphene.InputObjectType):
|
||||
name = graphene.String(required=True)
|
||||
teamType = graphene.String() # BUYER или SELLER
|
||||
|
||||
|
||||
class UpdateUserInput(graphene.InputObjectType):
|
||||
firstName = graphene.String()
|
||||
lastName = graphene.String()
|
||||
phone = graphene.String()
|
||||
avatarId = graphene.String()
|
||||
|
||||
class CreateTeamMutation(graphene.Mutation):
|
||||
class Arguments:
|
||||
input = CreateTeamInput(required=True)
|
||||
|
||||
team = graphene.Field(Team)
|
||||
|
||||
def mutate(self, info, input):
|
||||
# Получаем user_id из контекста (ID Token)
|
||||
user_id = getattr(info.context, 'user_id', None)
|
||||
if not user_id:
|
||||
raise Exception("User not authenticated")
|
||||
|
||||
try:
|
||||
owner = _get_or_create_user_with_profile(user_id)
|
||||
|
||||
team = TeamModel.objects.create(
|
||||
name=input.name,
|
||||
owner=owner,
|
||||
team_type=input.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'])
|
||||
|
||||
return CreateTeamMutation(team=team)
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"Failed to create team: {str(e)}")
|
||||
|
||||
|
||||
class UpdateUserMutation(graphene.Mutation):
|
||||
class Arguments:
|
||||
userId = graphene.String(required=True)
|
||||
input = UpdateUserInput(required=True)
|
||||
|
||||
user = graphene.Field(User)
|
||||
|
||||
def mutate(self, info, userId, input):
|
||||
# Проверяем права - пользователь может редактировать только себя
|
||||
context_user_id = getattr(info.context, 'user_id', None)
|
||||
if context_user_id != userId:
|
||||
return UpdateUserMutation(user=None)
|
||||
|
||||
try:
|
||||
user = _get_or_create_user_with_profile(userId)
|
||||
|
||||
if input.firstName is not None:
|
||||
user.first_name = input.firstName
|
||||
if input.lastName is not None:
|
||||
user.last_name = input.lastName
|
||||
user.save()
|
||||
if hasattr(user, 'profile'):
|
||||
if input.phone is not None:
|
||||
user.profile.phone = input.phone
|
||||
if input.avatarId is not None:
|
||||
user.profile.avatar_id = input.avatarId
|
||||
user.profile.save()
|
||||
return UpdateUserMutation(user=user)
|
||||
|
||||
except Exception:
|
||||
return UpdateUserMutation(user=None)
|
||||
|
||||
|
||||
class SwitchTeamMutation(graphene.Mutation):
|
||||
class Arguments:
|
||||
teamId = graphene.String(required=True)
|
||||
|
||||
user = graphene.Field(User)
|
||||
|
||||
def mutate(self, info, teamId):
|
||||
user_id = getattr(info.context, 'user_id', None)
|
||||
if not user_id:
|
||||
raise Exception("User not authenticated")
|
||||
|
||||
try:
|
||||
team = TeamModel.objects.get(uuid=teamId)
|
||||
user = _get_or_create_user_with_profile(user_id)
|
||||
if hasattr(user, 'profile'):
|
||||
user.profile.active_team = team
|
||||
user.profile.save(update_fields=['active_team'])
|
||||
return SwitchTeamMutation(user=user)
|
||||
except TeamModel.DoesNotExist:
|
||||
raise Exception("Team not found")
|
||||
|
||||
|
||||
class UserMutation(graphene.ObjectType):
|
||||
create_team = CreateTeamMutation.Field()
|
||||
update_user = UpdateUserMutation.Field()
|
||||
switch_team = SwitchTeamMutation.Field()
|
||||
|
||||
user_schema = graphene.Schema(query=UserQuery, mutation=UserMutation)
|
||||
Reference in New Issue
Block a user