website refactor
This commit is contained in:
@@ -5,9 +5,9 @@ import { Service, type DomainError } from '@/lib/contracts/services/Service';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
|
||||
import { isProductionEnvironment } from '@/lib/config/env';
|
||||
import { LeagueMemberViewModel } from '@/lib/view-models/LeagueMemberViewModel';
|
||||
import type { LeagueRosterMemberDTO } from '@/lib/types/generated/LeagueRosterMemberDTO';
|
||||
import type { LeagueRosterJoinRequestDTO } from '@/lib/types/generated/LeagueRosterJoinRequestDTO';
|
||||
import type { LeagueMembershipDTO } from '@/lib/types/generated/LeagueMembershipDTO';
|
||||
|
||||
export interface LeagueRosterAdminData {
|
||||
leagueId: string;
|
||||
@@ -20,15 +20,40 @@ export class LeagueMembershipService implements Service {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private static cachedMemberships = new Map<string, any[]>();
|
||||
|
||||
constructor() {
|
||||
const baseUrl = getWebsiteApiBaseUrl();
|
||||
const logger = new ConsoleLogger();
|
||||
const errorReporter = new EnhancedErrorReporter(logger, {
|
||||
showUserNotifications: false,
|
||||
logToConsole: true,
|
||||
reportToExternal: isProductionEnvironment(),
|
||||
});
|
||||
this.apiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
|
||||
constructor(apiClient?: LeaguesApiClient) {
|
||||
if (apiClient) {
|
||||
this.apiClient = apiClient;
|
||||
} else {
|
||||
const baseUrl = getWebsiteApiBaseUrl();
|
||||
const logger = new ConsoleLogger();
|
||||
const errorReporter = new EnhancedErrorReporter(logger, {
|
||||
showUserNotifications: false,
|
||||
logToConsole: true,
|
||||
reportToExternal: isProductionEnvironment(),
|
||||
});
|
||||
this.apiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
|
||||
}
|
||||
}
|
||||
|
||||
async getLeagueMemberships(leagueId: string, currentUserId: string): Promise<any[]> {
|
||||
const res = await this.apiClient.getMemberships(leagueId);
|
||||
const members = (res as any).members || res;
|
||||
return members.map((m: any) => new LeagueMemberViewModel({ ...m, currentUserId }, currentUserId as any));
|
||||
}
|
||||
|
||||
async removeMember(leagueId: string, performerDriverId: string, targetDriverId: string): Promise<any> {
|
||||
const res = await this.apiClient.removeMember(leagueId, performerDriverId, targetDriverId);
|
||||
return (res as any).value || res;
|
||||
}
|
||||
|
||||
async removeRosterMember(leagueId: string, targetDriverId: string): Promise<Result<{ success: boolean }, DomainError>> {
|
||||
try {
|
||||
const res = await this.apiClient.removeRosterMember(leagueId, targetDriverId);
|
||||
const dto = (res as any).value || res;
|
||||
return Result.ok({ success: dto.success });
|
||||
} catch (error: unknown) {
|
||||
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to remove member' });
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -101,18 +126,10 @@ export class LeagueMembershipService implements Service {
|
||||
}
|
||||
}
|
||||
|
||||
async removeMember(leagueId: string, targetDriverId: string): Promise<Result<{ success: boolean }, DomainError>> {
|
||||
try {
|
||||
const dto = await this.apiClient.removeRosterMember(leagueId, targetDriverId);
|
||||
return Result.ok({ success: dto.success });
|
||||
} catch (error: unknown) {
|
||||
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to remove member' });
|
||||
}
|
||||
}
|
||||
|
||||
async approveJoinRequest(leagueId: string, joinRequestId: string): Promise<Result<{ success: boolean }, DomainError>> {
|
||||
try {
|
||||
const dto = await this.apiClient.approveRosterJoinRequest(leagueId, joinRequestId);
|
||||
const res = await this.apiClient.approveRosterJoinRequest(leagueId, joinRequestId);
|
||||
const dto = (res as any).value || res;
|
||||
return Result.ok({ success: dto.success });
|
||||
} catch (error: unknown) {
|
||||
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to approve join request' });
|
||||
@@ -121,7 +138,8 @@ export class LeagueMembershipService implements Service {
|
||||
|
||||
async rejectJoinRequest(leagueId: string, joinRequestId: string): Promise<Result<{ success: boolean }, DomainError>> {
|
||||
try {
|
||||
const dto = await this.apiClient.rejectRosterJoinRequest(leagueId, joinRequestId);
|
||||
const res = await this.apiClient.rejectRosterJoinRequest(leagueId, joinRequestId);
|
||||
const dto = (res as any).value || res;
|
||||
return Result.ok({ success: dto.success });
|
||||
} catch (error: unknown) {
|
||||
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to reject join request' });
|
||||
|
||||
@@ -56,25 +56,82 @@ export class LeagueService implements Service {
|
||||
private sponsorsApiClient?: SponsorsApiClient;
|
||||
private racesApiClient?: RacesApiClient;
|
||||
|
||||
constructor() {
|
||||
const baseUrl = getWebsiteApiBaseUrl();
|
||||
const logger = new ConsoleLogger();
|
||||
const errorReporter = new EnhancedErrorReporter(logger, {
|
||||
showUserNotifications: false,
|
||||
logToConsole: true,
|
||||
reportToExternal: isProductionEnvironment(),
|
||||
});
|
||||
this.apiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
|
||||
constructor(apiClient?: LeaguesApiClient) {
|
||||
if (apiClient) {
|
||||
this.apiClient = apiClient;
|
||||
} else {
|
||||
const baseUrl = getWebsiteApiBaseUrl();
|
||||
const logger = new ConsoleLogger();
|
||||
const errorReporter = new EnhancedErrorReporter(logger, {
|
||||
showUserNotifications: false,
|
||||
logToConsole: true,
|
||||
reportToExternal: isProductionEnvironment(),
|
||||
});
|
||||
this.apiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
|
||||
}
|
||||
// Optional clients can be initialized if needed
|
||||
}
|
||||
|
||||
async getAllLeagues(): Promise<Result<AllLeaguesWithCapacityAndScoringDTO, DomainError>> {
|
||||
async getLeagueStandings(leagueId: string): Promise<any> {
|
||||
try {
|
||||
const data = await this.apiClient.getStandings(leagueId);
|
||||
return (data as any).value || data;
|
||||
} catch (error: unknown) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getLeagueStats(): Promise<any> {
|
||||
try {
|
||||
const data = await this.apiClient.getTotal();
|
||||
return (data as any).value || data;
|
||||
} catch (error: unknown) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getLeagueSchedule(leagueId: string): Promise<any> {
|
||||
try {
|
||||
const data = await this.apiClient.getSchedule(leagueId);
|
||||
return (data as any).value || data;
|
||||
} catch (error: unknown) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getLeagueMemberships(leagueId: string): Promise<any> {
|
||||
try {
|
||||
const data = await this.apiClient.getMemberships(leagueId);
|
||||
return (data as any).value || data;
|
||||
} catch (error: unknown) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async createLeague(input: CreateLeagueInputDTO): Promise<any> {
|
||||
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<any> {
|
||||
try {
|
||||
const dto = await this.apiClient.removeRosterMember(leagueId, targetDriverId);
|
||||
return { success: dto.success };
|
||||
} catch (error: unknown) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getAllLeagues(): Promise<any> {
|
||||
try {
|
||||
const dto = await this.apiClient.getAllWithCapacityAndScoring();
|
||||
return Result.ok(dto);
|
||||
return (dto as any).value || dto;
|
||||
} catch (error: unknown) {
|
||||
console.error('LeagueService.getAllLeagues failed:', error);
|
||||
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch leagues' });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,28 +199,6 @@ export class LeagueService implements Service {
|
||||
}
|
||||
}
|
||||
|
||||
async getLeagueStandings(): Promise<Result<never, DomainError>> {
|
||||
return Result.err({ type: 'notImplemented', message: 'League standings endpoint not implemented' });
|
||||
}
|
||||
|
||||
async getLeagueStats(): Promise<Result<TotalLeaguesDTO, DomainError>> {
|
||||
try {
|
||||
const data = await this.apiClient.getTotal();
|
||||
return Result.ok(data);
|
||||
} catch (error: unknown) {
|
||||
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch league stats' });
|
||||
}
|
||||
}
|
||||
|
||||
async getLeagueSchedule(leagueId: string): Promise<Result<LeagueScheduleDTO, DomainError>> {
|
||||
try {
|
||||
const data = await this.apiClient.getSchedule(leagueId);
|
||||
return Result.ok(data);
|
||||
} catch (error: unknown) {
|
||||
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch league schedule' });
|
||||
}
|
||||
}
|
||||
|
||||
async getLeagueSeasons(leagueId: string): Promise<Result<LeagueSeasonSummaryDTO[], DomainError>> {
|
||||
try {
|
||||
const data = await this.apiClient.getSeasons(leagueId);
|
||||
@@ -289,33 +324,6 @@ export class LeagueService implements Service {
|
||||
return this.deleteAdminScheduleRace(leagueId, seasonId, raceId);
|
||||
}
|
||||
|
||||
async getLeagueMemberships(leagueId: string): Promise<Result<LeagueMembershipsDTO, DomainError>> {
|
||||
try {
|
||||
const data = await this.apiClient.getMemberships(leagueId);
|
||||
return Result.ok(data);
|
||||
} catch (error: unknown) {
|
||||
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch memberships' });
|
||||
}
|
||||
}
|
||||
|
||||
async createLeague(input: CreateLeagueInputDTO): Promise<Result<CreateLeagueOutputDTO, DomainError>> {
|
||||
try {
|
||||
const data = await this.apiClient.create(input);
|
||||
return Result.ok(data);
|
||||
} catch (error: unknown) {
|
||||
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to create league' });
|
||||
}
|
||||
}
|
||||
|
||||
async removeMember(leagueId: string, targetDriverId: string): Promise<Result<{ success: boolean }, DomainError>> {
|
||||
try {
|
||||
const dto = await this.apiClient.removeRosterMember(leagueId, targetDriverId);
|
||||
return Result.ok({ success: dto.success });
|
||||
} catch (error: unknown) {
|
||||
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to remove member' });
|
||||
}
|
||||
}
|
||||
|
||||
async updateMemberRole(leagueId: string, targetDriverId: string, newRole: MembershipRole): Promise<Result<{ success: boolean }, DomainError>> {
|
||||
try {
|
||||
const dto = await this.apiClient.updateRosterMemberRole(leagueId, targetDriverId, newRole);
|
||||
|
||||
@@ -1,10 +1,64 @@
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { Service, type DomainError } from '@/lib/contracts/services/Service';
|
||||
import { type LeagueSettingsApiDto } from '@/lib/types/tbd/LeagueSettingsApiDto';
|
||||
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
|
||||
import { DriversApiClient } from '@/lib/api/drivers/DriversApiClient';
|
||||
import { LeagueSettingsViewModel } from '@/lib/view-models/LeagueSettingsViewModel';
|
||||
|
||||
export class LeagueSettingsService implements Service {
|
||||
private static cachedMemberships = new Map<string, unknown[]>();
|
||||
|
||||
constructor(
|
||||
private readonly leaguesApiClient?: LeaguesApiClient,
|
||||
private readonly driversApiClient?: DriversApiClient,
|
||||
) {}
|
||||
|
||||
async getLeagueSettings(leagueId: string): Promise<LeagueSettingsViewModel | null> {
|
||||
if (!this.leaguesApiClient || !this.driversApiClient) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const [leaguesRes, configRes, presetsRes, leaderboardRes, membershipsRes] = await Promise.all([
|
||||
this.leaguesApiClient.getAllWithCapacity(),
|
||||
this.leaguesApiClient.getLeagueConfig(leagueId),
|
||||
this.leaguesApiClient.getScoringPresets(),
|
||||
this.driversApiClient.getLeaderboard(),
|
||||
this.leaguesApiClient.getMemberships(leagueId),
|
||||
]);
|
||||
|
||||
const leaguesData = (leaguesRes as any).value || leaguesRes;
|
||||
const configData = (configRes as any).value || configRes;
|
||||
const presetsData = (presetsRes as any).value || presetsRes;
|
||||
const leaderboardData = (leaderboardRes as any).value || leaderboardRes;
|
||||
const membershipsData = (membershipsRes as any).value || membershipsRes;
|
||||
|
||||
const league = leaguesData.leagues.find((l: any) => l.id === leagueId);
|
||||
if (!league) return null;
|
||||
|
||||
const ownerRes = await this.driversApiClient.getDriver(league.ownerId);
|
||||
const owner = (ownerRes as any).value || ownerRes;
|
||||
|
||||
return new LeagueSettingsViewModel({
|
||||
league,
|
||||
config: configData.config || configData,
|
||||
presets: presetsData.presets,
|
||||
owner,
|
||||
members: membershipsData.members,
|
||||
drivers: leaderboardData.drivers,
|
||||
} as any);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async transferOwnership(leagueId: string, currentOwnerId: string, newOwnerId: string): Promise<boolean> {
|
||||
if (!this.leaguesApiClient) throw new Error('API client not initialized');
|
||||
const res = await this.leaguesApiClient.transferOwnership(leagueId, currentOwnerId, newOwnerId);
|
||||
const data = (res as any).value || res;
|
||||
return data.success;
|
||||
}
|
||||
|
||||
async getSettingsData(leagueId: string): Promise<Result<LeagueSettingsApiDto, DomainError>> {
|
||||
// Mock data since backend not implemented
|
||||
const mockData: LeagueSettingsApiDto = {
|
||||
|
||||
@@ -1,8 +1,86 @@
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { Service, type DomainError } from '@/lib/contracts/services/Service';
|
||||
import { type StewardingApiDto } from '@/lib/types/tbd/StewardingApiDto';
|
||||
import { RaceService } from '../races/RaceService';
|
||||
import { ProtestService } from '../protests/ProtestService';
|
||||
import { PenaltyService } from '../penalties/PenaltyService';
|
||||
import { DriverService } from '../drivers/DriverService';
|
||||
import { LeagueMembershipService } from './LeagueMembershipService';
|
||||
import { LeagueStewardingViewModel } from '@/lib/view-models/LeagueStewardingViewModel';
|
||||
|
||||
export class LeagueStewardingService implements Service {
|
||||
constructor(
|
||||
private readonly raceService?: RaceService,
|
||||
private readonly protestService?: ProtestService,
|
||||
private readonly penaltyService?: PenaltyService,
|
||||
private readonly driverService?: DriverService,
|
||||
private readonly leagueMembershipService?: LeagueMembershipService,
|
||||
) {}
|
||||
|
||||
async getLeagueStewardingData(leagueId: string): Promise<LeagueStewardingViewModel> {
|
||||
if (!this.raceService || !this.protestService || !this.penaltyService || !this.driverService) {
|
||||
return new LeagueStewardingViewModel([], {});
|
||||
}
|
||||
|
||||
const racesRes = await this.raceService.findByLeagueId(leagueId);
|
||||
const races = (racesRes as any).value || racesRes;
|
||||
const racesWithData = await Promise.all(
|
||||
races.map(async (race: any) => {
|
||||
const [protestsRes, penaltiesRes] = await Promise.all([
|
||||
this.protestService!.findByRaceId(race.id),
|
||||
this.penaltyService!.findByRaceId(race.id),
|
||||
]);
|
||||
const protests = (protestsRes as any).value || protestsRes;
|
||||
const penalties = (penaltiesRes as any).value || penaltiesRes;
|
||||
return {
|
||||
race: {
|
||||
id: race.id,
|
||||
track: race.track,
|
||||
scheduledAt: new Date(race.scheduledAt),
|
||||
},
|
||||
pendingProtests: protests.filter((p: any) => p.status === 'pending' || p.status === 'under_review'),
|
||||
resolvedProtests: protests.filter((p: any) => p.status !== 'pending' && p.status !== 'under_review'),
|
||||
penalties: penalties,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const driverIds = new Set<string>();
|
||||
racesWithData.forEach((r: any) => {
|
||||
r.pendingProtests.forEach((p: any) => {
|
||||
driverIds.add(p.protestingDriverId);
|
||||
driverIds.add(p.accusedDriverId);
|
||||
});
|
||||
r.resolvedProtests.forEach((p: any) => {
|
||||
driverIds.add(p.protestingDriverId);
|
||||
driverIds.add(p.accusedDriverId);
|
||||
});
|
||||
r.penalties.forEach((p: any) => driverIds.add(p.driverId));
|
||||
});
|
||||
|
||||
const driversRes = await this.driverService.findByIds(Array.from(driverIds));
|
||||
const drivers = (driversRes as any).value || driversRes;
|
||||
|
||||
const driverMap: Record<string, any> = {};
|
||||
drivers.forEach((d: any) => {
|
||||
driverMap[d.id] = d;
|
||||
});
|
||||
|
||||
return new LeagueStewardingViewModel(racesWithData as any, driverMap);
|
||||
}
|
||||
|
||||
async reviewProtest(input: any): Promise<void> {
|
||||
if (this.protestService) {
|
||||
await this.protestService.reviewProtest(input);
|
||||
}
|
||||
}
|
||||
|
||||
async applyPenalty(input: any): Promise<void> {
|
||||
if (this.penaltyService) {
|
||||
await this.penaltyService.applyPenalty(input);
|
||||
}
|
||||
}
|
||||
|
||||
async getStewardingData(leagueId: string): Promise<Result<StewardingApiDto, DomainError>> {
|
||||
// Mock data since backend not implemented
|
||||
const mockData: StewardingApiDto = {
|
||||
@@ -16,7 +94,22 @@ export class LeagueStewardingService implements Service {
|
||||
return Result.ok(mockData);
|
||||
}
|
||||
|
||||
async getProtestDetailViewModel(_: string, __: string): Promise<Result<any, DomainError>> {
|
||||
return Result.err({ type: 'notImplemented', message: 'getProtestDetailViewModel' });
|
||||
async getProtestDetailViewModel(leagueId: string, protestId: string): Promise<any> {
|
||||
if (!this.protestService || !this.penaltyService) return null;
|
||||
|
||||
const [protestRes, penaltyTypesRes] = await Promise.all([
|
||||
this.protestService.getProtestById(leagueId, protestId),
|
||||
this.penaltyService.getPenaltyTypesReference(),
|
||||
]);
|
||||
|
||||
const protestData = (protestRes as any).value || protestRes;
|
||||
const penaltyTypesData = (penaltyTypesRes as any).value || penaltyTypesRes;
|
||||
|
||||
return {
|
||||
...protestData,
|
||||
penaltyTypes: penaltyTypesData.penaltyTypes,
|
||||
defaultReasons: penaltyTypesData.defaultReasons,
|
||||
initialPenaltyType: penaltyTypesData.penaltyTypes[0]?.type,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { Service, DomainError } from '@/lib/contracts/services/Service';
|
||||
import { LeagueWalletApiDto } from '@/lib/types/tbd/LeagueWalletApiDto';
|
||||
import { WalletsApiClient } from '@/lib/api/wallets/WalletsApiClient';
|
||||
|
||||
export class LeagueWalletService implements Service {
|
||||
constructor(private readonly apiClient?: WalletsApiClient) {}
|
||||
|
||||
async getWalletForLeague(leagueId: string): Promise<LeagueWalletApiDto> {
|
||||
if (this.apiClient) {
|
||||
const res = await this.apiClient.getLeagueWallet(leagueId);
|
||||
return ((res as any).value || res) as any;
|
||||
}
|
||||
const result = await this.getWalletData(leagueId);
|
||||
if (result.isErr()) throw new Error(result.getError().message);
|
||||
return result.unwrap();
|
||||
@@ -14,8 +21,17 @@ export class LeagueWalletService implements Service {
|
||||
amount: number,
|
||||
currency: string,
|
||||
seasonId: string,
|
||||
destinationId: string
|
||||
destinationAccount: string
|
||||
): Promise<{ success: boolean; message?: string }> {
|
||||
if (this.apiClient) {
|
||||
const res = await this.apiClient.withdrawFromLeagueWallet(leagueId, {
|
||||
amount,
|
||||
currency,
|
||||
seasonId,
|
||||
destinationAccount,
|
||||
});
|
||||
return (res as any).value || res;
|
||||
}
|
||||
// Mock implementation
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user