CLAUDE: Add team ownership to auth flow (CRIT-002)

Backend:
- Add get_teams_by_owner() to SBA API client
- Update /api/auth/me to return user's teams from SBA API
- Add sba_current_season config setting (default: 13)

Frontend:
- Replace hardcoded myTeamId with computed from auth store teams
- Fix isMyTurn logic to check actual team ownership
- Update REFACTORING_PLAN.json

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2026-01-13 20:28:08 -06:00
parent f2ce91a239
commit c5869f5ba2
5 changed files with 93 additions and 8 deletions

View File

@ -9,6 +9,7 @@ from pydantic import BaseModel
from app.config import get_settings from app.config import get_settings
from app.utils.auth import create_token, verify_token from app.utils.auth import create_token, verify_token
from app.services.sba_api_client import sba_api_client
from app.utils.cookies import ( from app.utils.cookies import (
ACCESS_TOKEN_COOKIE, ACCESS_TOKEN_COOKIE,
REFRESH_TOKEN_COOKIE, REFRESH_TOKEN_COOKIE,
@ -484,10 +485,22 @@ async def get_current_user_info(
discriminator="0", # Discord removed discriminators discriminator="0", # Discord removed discriminators
) )
# TODO: Load user's teams from database # Load user's teams from SBA API
teams = [] discord_id = payload["discord_id"]
sba_teams = await sba_api_client.get_teams_by_owner(
discord_id, season=settings.sba_current_season
)
teams = [
{
"id": team["id"],
"name": team.get("lname", team.get("sname", "Unknown")),
"owner_id": discord_id,
"league_id": "sba",
}
for team in sba_teams
]
logger.info(f"User info retrieved for {user.username}") logger.info(f"User info retrieved for {user.username}, {len(teams)} teams")
return UserInfoResponse(user=user, teams=teams) return UserInfoResponse(user=user, teams=teams)

View File

@ -33,6 +33,7 @@ class Settings(BaseSettings):
# League APIs # League APIs
sba_api_url: str sba_api_url: str
sba_api_key: str sba_api_key: str
sba_current_season: int = 13 # Current SBA season number
pd_api_url: str pd_api_url: str
pd_api_key: str pd_api_key: str

View File

@ -98,6 +98,43 @@ class SbaApiClient:
logger.error(f"Unexpected error fetching teams: {e}") logger.error(f"Unexpected error fetching teams: {e}")
raise raise
async def get_teams_by_owner(self, discord_id: str, season: int) -> list[dict[str, Any]]:
"""
Fetch teams owned by a specific Discord user.
Args:
discord_id: Discord user ID (gmid or gmid2)
season: Season number
Returns:
List of team dictionaries owned by this user
Example:
teams = await client.get_teams_by_owner("485217045408120833", season=13)
"""
url = f"{self.base_url}/teams"
params = {"season": season, "owner_id": discord_id}
try:
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.get(
url, headers=self._get_headers(), params=params
)
response.raise_for_status()
data = response.json()
teams = data.get("teams", [])
logger.info(f"Found {len(teams)} teams for owner {discord_id[:8]}...")
return teams
except httpx.HTTPError as e:
logger.error(f"Failed to fetch teams for owner {discord_id[:8]}...: {e}")
raise
except Exception as e:
logger.error(f"Unexpected error fetching teams by owner: {e}")
raise
async def get_team_by_id(self, team_id: int, season: int = 3) -> dict[str, Any] | None: async def get_team_by_id(self, team_id: int, season: int = 3) -> dict[str, Any] | None:
""" """
Get a single team by ID, using cache when available. Get a single team by ID, using cache when available.

View File

@ -6,7 +6,7 @@
"totalEstimatedHours": 52.75, "totalEstimatedHours": 52.75,
"criticalBlockersHours": 6.5, "criticalBlockersHours": 6.5,
"totalTasks": 47, "totalTasks": 47,
"completedTasks": 1 "completedTasks": 3
}, },
"categories": { "categories": {
"critical": "Must fix before any production use", "critical": "Must fix before any production use",

View File

@ -403,7 +403,22 @@ const currentDecisionPrompt = computed(() => gameStore.currentDecisionPrompt)
const isLoading = ref(true) const isLoading = ref(true)
const connectionStatus = ref<'connecting' | 'connected' | 'disconnected'>('connecting') const connectionStatus = ref<'connecting' | 'connected' | 'disconnected'>('connecting')
const showSubstitutions = ref(false) const showSubstitutions = ref(false)
const myTeamId = ref<number | null>(null) // TODO: Get from auth/game state
// Determine which team (if any) the current user owns in this game
const myTeamId = computed(() => {
if (!gameState.value) return null
const userTeamIds = authStore.userTeams.map(t => t.id)
if (userTeamIds.includes(gameState.value.home_team_id)) {
return gameState.value.home_team_id
}
if (userTeamIds.includes(gameState.value.away_team_id)) {
return gameState.value.away_team_id
}
return null // Spectator - doesn't own either team
})
// Dynamic ScoreBoard height tracking // Dynamic ScoreBoard height tracking
const scoreBoardRef = ref<HTMLElement | null>(null) const scoreBoardRef = ref<HTMLElement | null>(null)
@ -435,9 +450,28 @@ const currentTeam = computed(() => {
}) })
const isMyTurn = computed(() => { const isMyTurn = computed(() => {
// TODO: Implement actual team ownership logic if (!myTeamId.value || !gameState.value) return false
// For now, assume it's always the player's turn for testing
return true // Determine which team needs to act based on decision phase
if (needsDefensiveDecision.value) {
// Fielding team makes defensive decisions
// Top of inning: home fields, Bottom: away fields
const fieldingTeamId = gameState.value.half === 'top'
? gameState.value.home_team_id
: gameState.value.away_team_id
return myTeamId.value === fieldingTeamId
}
if (needsOffensiveDecision.value) {
// Batting team makes offensive decisions
// Top of inning: away bats, Bottom: home bats
const battingTeamId = gameState.value.half === 'top'
? gameState.value.away_team_id
: gameState.value.home_team_id
return myTeamId.value === battingTeamId
}
return false
}) })
const decisionPhase = computed(() => { const decisionPhase = computed(() => {