import { injectable, unmanaged } from 'inversify'; import { LeaguesApiClient } from "@/lib/api/leagues/LeaguesApiClient"; import { DriversApiClient } from "@/lib/api/drivers/DriversApiClient"; import { SponsorsApiClient } from "@/lib/api/sponsors/SponsorsApiClient"; import { RacesApiClient } from "@/lib/api/races/RacesApiClient"; import { CreateLeagueInputDTO } from "@/lib/types/generated/CreateLeagueInputDTO"; import { CreateLeagueOutputDTO } from "@/lib/types/generated/CreateLeagueOutputDTO"; import type { MembershipRole } from "@/lib/types/MembershipRole"; import type { LeagueRosterJoinRequestDTO } from "@/lib/types/generated/LeagueRosterJoinRequestDTO"; import type { TotalLeaguesDTO } from '@/lib/types/generated/TotalLeaguesDTO'; import type { LeagueSeasonSummaryDTO } from '@/lib/types/generated/LeagueSeasonSummaryDTO'; import type { LeagueScheduleDTO } from '@/lib/types/generated/LeagueScheduleDTO'; import type { CreateLeagueScheduleRaceInputDTO } from '@/lib/types/generated/CreateLeagueScheduleRaceInputDTO'; import type { CreateLeagueScheduleRaceOutputDTO } from '@/lib/types/generated/CreateLeagueScheduleRaceOutputDTO'; import type { UpdateLeagueScheduleRaceInputDTO } from '@/lib/types/generated/UpdateLeagueScheduleRaceInputDTO'; import type { LeagueScheduleRaceMutationSuccessDTO } from '@/lib/types/generated/LeagueScheduleRaceMutationSuccessDTO'; import type { LeagueSeasonSchedulePublishOutputDTO } from '@/lib/types/generated/LeagueSeasonSchedulePublishOutputDTO'; import type { LeagueRosterMemberDTO } from '@/lib/types/generated/LeagueRosterMemberDTO'; import type { LeagueMembershipsDTO } from '@/lib/types/generated/LeagueMembershipsDTO'; import type { GetTeamDetailsOutputDTO } from '@/lib/types/generated/GetTeamDetailsOutputDTO'; import type { GetDriverOutputDTO } from '@/lib/types/generated/GetDriverOutputDTO'; import type { RaceDTO } from '@/lib/types/generated/RaceDTO'; import type { LeagueScoringConfigDTO } from '@/lib/types/generated/LeagueScoringConfigDTO'; import { TeamMemberViewModel } from '@/lib/view-models/TeamMemberViewModel'; import { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel'; import { TeamDetailsViewModel } from '@/lib/view-models/TeamDetailsViewModel'; import { Result } from '@/lib/contracts/Result'; import { DomainError, Service } from '@/lib/contracts/services/Service'; import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger'; import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter'; import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl'; import { isProductionEnvironment } from '@/lib/config/env'; import { AllLeaguesWithCapacityAndScoringDTO } from '@/lib/types/AllLeaguesWithCapacityAndScoringDTO'; import type { LeagueWithCapacityAndScoringDTO } from '@/lib/types/generated/LeagueWithCapacityAndScoringDTO'; export interface LeagueScheduleAdminData { leagueId: string; seasonId: string; seasons: LeagueSeasonSummaryDTO[]; schedule: LeagueScheduleDTO; } export interface LeagueRosterAdminData { leagueId: string; members: LeagueRosterMemberDTO[]; joinRequests: LeagueRosterJoinRequestDTO[]; } export interface LeagueDetailData { league: LeagueWithCapacityAndScoringDTO; owner: GetDriverOutputDTO | null; scoringConfig: LeagueScoringConfigDTO | null; memberships: LeagueMembershipsDTO; races: RaceDTO[]; sponsors: any[]; } /** * League Service - DTO Only * * Returns Result. No ViewModels or UX logic. * All client-side presentation logic must be handled by hooks/components. * @server-safe */ @injectable() export class LeagueService implements Service { private apiClient: LeaguesApiClient; private driversApiClient: DriversApiClient; private sponsorsApiClient: SponsorsApiClient; private racesApiClient: RacesApiClient; constructor(@unmanaged() apiClient?: LeaguesApiClient) { const baseUrl = getWebsiteApiBaseUrl(); const logger = new ConsoleLogger(); const errorReporter = new EnhancedErrorReporter(logger, { showUserNotifications: false, logToConsole: true, reportToExternal: isProductionEnvironment(), }); if (apiClient) { this.apiClient = apiClient; } else { this.apiClient = new LeaguesApiClient(baseUrl, errorReporter, logger); } this.driversApiClient = new DriversApiClient(baseUrl, errorReporter, logger); this.sponsorsApiClient = new SponsorsApiClient(baseUrl, errorReporter, logger); this.racesApiClient = new RacesApiClient(baseUrl, errorReporter, logger); } async getLeagueStandings(leagueId: string): Promise { try { const data = await this.apiClient.getStandings(leagueId); return (data as any).value || data; } catch (error: unknown) { throw error; } } async getLeagueStats(): Promise { try { const data = await this.apiClient.getTotal(); return (data as any).value || data; } catch (error: unknown) { throw error; } } async getLeagueSchedule(leagueId: string): Promise { try { const data = await this.apiClient.getSchedule(leagueId); return (data as any).value || data; } catch (error: unknown) { throw error; } } async getLeagueMemberships(leagueId: string): Promise { try { const data = await this.apiClient.getMemberships(leagueId); return (data as any).value || data; } catch (error: unknown) { throw error; } } async createLeague(input: CreateLeagueInputDTO): Promise { try { const data = await this.apiClient.create(input); return (data as any).value || data; } catch (error: unknown) { throw error; } } async removeMember(leagueId: string, targetDriverId: string): Promise { try { const dto = await this.apiClient.removeRosterMember(leagueId, targetDriverId); return { success: dto.success }; } catch (error: unknown) { throw error; } } async getAllLeagues(): Promise> { try { const dto = await this.apiClient.getAllWithCapacityAndScoring(); return Result.ok(dto); } catch (error: unknown) { return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch leagues' }); } } async getLeagueDetailData(leagueId: string): Promise> { try { const [apiDto, memberships, racesResponse] = await Promise.all([ this.apiClient.getAllWithCapacityAndScoring(), this.apiClient.getMemberships(leagueId), this.apiClient.getRaces(leagueId), ]); if (!apiDto || !apiDto.leagues) { return Result.err({ type: 'notFound', message: 'Leagues not found' }); } const league = apiDto.leagues.find(l => l.id === leagueId); if (!league) { return Result.err({ type: 'notFound', message: 'League not found' }); } // Fetch owner if ownerId exists let owner: GetDriverOutputDTO | null = null; if (league.ownerId) { owner = await this.driversApiClient.getDriver(league.ownerId); } // Fetch scoring config if available let scoringConfig: LeagueScoringConfigDTO | null = null; try { const config = await this.apiClient.getLeagueConfig(leagueId); if (config.form?.scoring) { // Map form scoring to LeagueScoringConfigDTO if possible, or just use partial scoringConfig = { leagueId, seasonId: '', // Not available in this context gameId: '', gameName: '', scoringPresetId: (config.form.scoring as any).presetId, dropPolicySummary: '', championships: [], }; } } catch (e) { console.warn('Failed to fetch league scoring config', e); } return Result.ok({ league, owner, scoringConfig, memberships, races: racesResponse.races, sponsors: [], // Sponsors integration can be added here }); } catch (error: unknown) { console.error('LeagueService.getLeagueDetailData failed:', error); return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch league detail' }); } } async getScheduleAdminData(leagueId: string, seasonId?: string): Promise> { try { const seasons = await this.apiClient.getSeasons(leagueId); if (!seasons || seasons.length === 0) { return Result.err({ type: 'notFound', message: 'No seasons found for league' }); } const targetSeasonId = seasonId || (seasons.find(s => s.status === 'active')?.seasonId || seasons[0].seasonId); const schedule = await this.apiClient.getSchedule(leagueId, targetSeasonId); return Result.ok({ leagueId, seasonId: targetSeasonId, seasons, schedule, }); } catch (error: unknown) { console.error('LeagueService.getScheduleAdminData failed:', error); return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch schedule admin data' }); } } async getRosterAdminData(leagueId: string): Promise> { try { const [members, joinRequests] = await Promise.all([ this.apiClient.getAdminRosterMembers(leagueId), this.apiClient.getAdminRosterJoinRequests(leagueId), ]); return Result.ok({ leagueId, members, joinRequests, }); } catch (error: unknown) { console.error('LeagueService.getRosterAdminData failed:', error); return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch roster data' }); } } async getLeagueSeasons(leagueId: string): Promise> { try { const data = await this.apiClient.getSeasons(leagueId); return Result.ok(data); } catch (error: unknown) { return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch league seasons' }); } } async getLeagueSeasonSummaries(leagueId: string): Promise> { return this.getLeagueSeasons(leagueId); } async getAdminSchedule(leagueId: string, seasonId: string): Promise> { try { const data = await this.apiClient.getSchedule(leagueId, seasonId); return Result.ok(data); } catch (error: unknown) { return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch admin schedule' }); } } async publishAdminSchedule(leagueId: string, seasonId: string): Promise> { try { const data = await this.apiClient.publishSeasonSchedule(leagueId, seasonId); return Result.ok(data); } catch (error: unknown) { return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to publish schedule' }); } } async unpublishAdminSchedule(leagueId: string, seasonId: string): Promise> { try { const data = await this.apiClient.unpublishSeasonSchedule(leagueId, seasonId); return Result.ok(data); } catch (error: unknown) { return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to unpublish schedule' }); } } async createAdminScheduleRace( leagueId: string, seasonId: string, input: { track: string; car: string; scheduledAtIso: string }, ): Promise> { try { const payload: CreateLeagueScheduleRaceInputDTO = { ...input, example: '' }; const data = await this.apiClient.createSeasonScheduleRace(leagueId, seasonId, payload); return Result.ok(data); } catch (error: unknown) { return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to create race' }); } } async updateAdminScheduleRace( leagueId: string, seasonId: string, raceId: string, input: Partial<{ track: string; car: string; scheduledAtIso: string }>, ): Promise> { try { const payload: UpdateLeagueScheduleRaceInputDTO = { ...input, example: '' }; const data = await this.apiClient.updateSeasonScheduleRace(leagueId, seasonId, raceId, payload); return Result.ok(data); } catch (error: unknown) { return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to update race' }); } } async deleteAdminScheduleRace(leagueId: string, seasonId: string, raceId: string): Promise> { try { const data = await this.apiClient.deleteSeasonScheduleRace(leagueId, seasonId, raceId); return Result.ok(data); } catch (error: unknown) { return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to delete race' }); } } async getLeagueScheduleDto(leagueId: string, seasonId: string): Promise> { return this.getAdminSchedule(leagueId, seasonId); } async publishLeagueSeasonSchedule(leagueId: string, seasonId: string): Promise> { return this.publishAdminSchedule(leagueId, seasonId); } async unpublishLeagueSeasonSchedule(leagueId: string, seasonId: string): Promise> { return this.unpublishAdminSchedule(leagueId, seasonId); } async createLeagueSeasonScheduleRace( leagueId: string, seasonId: string, input: CreateLeagueScheduleRaceInputDTO, ): Promise> { try { const data = await this.apiClient.createSeasonScheduleRace(leagueId, seasonId, input); return Result.ok(data); } catch (error: unknown) { return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to create race' }); } } async updateLeagueSeasonScheduleRace( leagueId: string, seasonId: string, raceId: string, input: UpdateLeagueScheduleRaceInputDTO, ): Promise> { try { const data = await this.apiClient.updateSeasonScheduleRace(leagueId, seasonId, raceId, input); return Result.ok(data); } catch (error: unknown) { return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to update race' }); } } async deleteLeagueSeasonScheduleRace( leagueId: string, seasonId: string, raceId: string, ): Promise> { return this.deleteAdminScheduleRace(leagueId, seasonId, raceId); } async updateMemberRole(leagueId: string, targetDriverId: string, newRole: MembershipRole): Promise> { try { const dto = await this.apiClient.updateRosterMemberRole(leagueId, targetDriverId, newRole); return Result.ok({ success: dto.success }); } catch (error: unknown) { return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to update member role' }); } } async getAdminRosterMembers(leagueId: string): Promise> { try { const data = await this.apiClient.getAdminRosterMembers(leagueId); return Result.ok(data); } catch (error: unknown) { return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch roster members' }); } } async getAdminRosterJoinRequests(leagueId: string): Promise> { try { const data = await this.apiClient.getAdminRosterJoinRequests(leagueId); return Result.ok(data); } catch (error: unknown) { return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch join requests' }); } } async approveJoinRequest(leagueId: string, joinRequestId: string): Promise> { try { const dto = await this.apiClient.approveRosterJoinRequest(leagueId, joinRequestId); return Result.ok({ success: dto.success }); } catch (error: unknown) { return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to approve join request' }); } } async rejectJoinRequest(leagueId: string, joinRequestId: string): Promise> { try { const dto = await this.apiClient.rejectRosterJoinRequest(leagueId, joinRequestId); return Result.ok({ success: dto.success }); } catch (error: unknown) { return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to reject join request' }); } } async getLeagueDetail(): Promise> { return Result.err({ type: 'notImplemented', message: 'League detail endpoint not implemented' }); } async getLeagueDetailPageData(): Promise> { return Result.err({ type: 'notImplemented', message: 'League detail page data endpoint not implemented' }); } async getScoringPresets(): Promise> { return Result.err({ type: 'notImplemented', message: 'Scoring presets endpoint not implemented' }); } }