Initial commit from monorepo

This commit is contained in:
Ruslan Bakiev
2026-01-07 09:17:34 +07:00
commit 3e2570ae0b
69 changed files with 3777 additions and 0 deletions

View 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)

View 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)

View 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)

View 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)