Files
billing/billing_app/schemas/team_schema.py
2026-01-07 09:17:45 +07:00

135 lines
5.0 KiB
Python

"""
Team-level GraphQL schema for Billing service.
Requires teams:member scope (verified by middleware).
Provides teamBalance and teamTransactions for the authenticated team.
All data comes directly from TigerBeetle.
"""
import graphene
import uuid
import logging
from graphql import GraphQLError
from ..tigerbeetle_client import tigerbeetle_client
from ..permissions import require_scopes
from ..models import OperationCode
logger = logging.getLogger(__name__)
class TeamBalance(graphene.ObjectType):
"""Balance information for a team's account from TigerBeetle."""
balance = graphene.Float(required=True, description="Current balance (credits - debits)")
creditsPosted = graphene.Float(required=True, description="Total credits posted")
debitsPosted = graphene.Float(required=True, description="Total debits posted")
exists = graphene.Boolean(required=True, description="Whether account exists in TigerBeetle")
class TeamTransaction(graphene.ObjectType):
"""Transaction from TigerBeetle."""
id = graphene.String(required=True)
amount = graphene.Float(required=True)
timestamp = graphene.Float(description="TigerBeetle timestamp")
code = graphene.Int(description="Operation code")
codeName = graphene.String(description="Operation code name from OperationCode table")
direction = graphene.String(required=True, description="'credit' or 'debit' relative to team account")
counterpartyUuid = graphene.String(description="UUID of the other account in transaction")
class TeamQuery(graphene.ObjectType):
teamBalance = graphene.Field(TeamBalance, description="Get balance for the authenticated team")
teamTransactions = graphene.List(
TeamTransaction,
limit=graphene.Int(default_value=50),
description="Get transactions for the authenticated team"
)
@require_scopes("teams:member")
def resolve_teamBalance(self, info):
team_uuid = getattr(info.context, 'team_uuid', None)
if not team_uuid:
raise GraphQLError("Team UUID not found in context")
try:
team_uuid_obj = uuid.UUID(team_uuid)
tb_account_id = team_uuid_obj.int
# Look up account in TigerBeetle
accounts = tigerbeetle_client.lookup_accounts([tb_account_id])
if not accounts:
# Account doesn't exist yet - return zero balance
return TeamBalance(
balance=0.0,
creditsPosted=0.0,
debitsPosted=0.0,
exists=False
)
account = accounts[0]
credits_posted = float(account.credits_posted)
debits_posted = float(account.debits_posted)
balance = credits_posted - debits_posted
return TeamBalance(
balance=balance,
creditsPosted=credits_posted,
debitsPosted=debits_posted,
exists=True
)
except ValueError as e:
logger.error(f"Invalid team UUID format: {team_uuid} - {e}")
raise GraphQLError("Invalid team UUID format")
except Exception as e:
logger.error(f"Error fetching team balance: {e}")
raise GraphQLError("Failed to fetch team balance")
@require_scopes("teams:member")
def resolve_teamTransactions(self, info, limit=50):
team_uuid = getattr(info.context, 'team_uuid', None)
if not team_uuid:
raise GraphQLError("Team UUID not found in context")
try:
team_uuid_obj = uuid.UUID(team_uuid)
tb_account_id = team_uuid_obj.int
# Get transfers from TigerBeetle
transfers = tigerbeetle_client.get_account_transfers(tb_account_id, limit=limit)
# Load operation codes for name lookup
code_map = {oc.code: oc.name for oc in OperationCode.objects.all()}
result = []
for t in transfers:
# Determine direction relative to team account
if t.credit_account_id == tb_account_id:
direction = "credit"
counterparty_id = t.debit_account_id
else:
direction = "debit"
counterparty_id = t.credit_account_id
result.append(TeamTransaction(
id=str(uuid.UUID(int=t.id)),
amount=float(t.amount),
timestamp=float(t.timestamp),
code=t.code,
codeName=code_map.get(t.code),
direction=direction,
counterpartyUuid=str(uuid.UUID(int=counterparty_id)),
))
return result
except ValueError as e:
logger.error(f"Invalid team UUID format: {team_uuid} - {e}")
raise GraphQLError("Invalid team UUID format")
except Exception as e:
logger.error(f"Error fetching team transactions: {e}")
raise GraphQLError("Failed to fetch team transactions")
team_schema = graphene.Schema(query=TeamQuery)