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 UpdateTeamAddressInput(graphene.InputObjectType): uuid = graphene.String(required=True) name = graphene.String() address = graphene.String() latitude = graphene.Float() longitude = graphene.Float() countryCode = graphene.String() isDefault = graphene.Boolean() class UpdateTeamAddressMutation(graphene.Mutation): class Arguments: input = UpdateTeamAddressInput(required=True) success = graphene.Boolean() message = graphene.String() address = graphene.Field(TeamAddress) @require_scopes("teams:member") def mutate(self, info, input): team_uuid = getattr(info.context, 'team_uuid', None) if not team_uuid: return UpdateTeamAddressMutation(success=False, message="Не авторизован") try: team = TeamModel.objects.get(uuid=team_uuid) address = team.addresses.get(uuid=input.uuid) if input.name is not None: address.name = input.name if input.address is not None: address.address = input.address if input.latitude is not None: address.latitude = input.latitude if input.longitude is not None: address.longitude = input.longitude if input.countryCode is not None: address.country_code = input.countryCode if input.isDefault is not None: address.is_default = input.isDefault address.save() return UpdateTeamAddressMutation(success=True, message="Адрес обновлен", address=address) except TeamModel.DoesNotExist: return UpdateTeamAddressMutation(success=False, message="Команда не найдена") except TeamAddressModel.DoesNotExist: return UpdateTeamAddressMutation(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() update_team_address = UpdateTeamAddressMutation.Field() delete_team_address = DeleteTeamAddressMutation.Field() set_selected_location = SetSelectedLocationMutation.Field() team_schema = graphene.Schema(query=TeamQuery, mutation=TeamMutation)