135 lines
5.0 KiB
Python
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)
|