api client refactor

This commit is contained in:
2025-12-17 18:01:47 +01:00
parent bab55955e1
commit 4177644b18
190 changed files with 6403 additions and 1624 deletions

View File

@@ -0,0 +1,24 @@
import { BaseApiClient } from '../base/BaseApiClient';
import type {
RecordPageViewInputDto,
RecordPageViewOutputDto,
RecordEngagementInputDto,
RecordEngagementOutputDto,
} from '../../dtos';
/**
* Analytics API Client
*
* Handles all analytics-related API operations.
*/
export class AnalyticsApiClient extends BaseApiClient {
/** Record a page view */
recordPageView(input: RecordPageViewInputDto): Promise<RecordPageViewOutputDto> {
return this.post<RecordPageViewOutputDto>('/analytics/page-view', input);
}
/** Record an engagement event */
recordEngagement(input: RecordEngagementInputDto): Promise<RecordEngagementOutputDto> {
return this.post<RecordEngagementOutputDto>('/analytics/engagement', input);
}
}

View File

@@ -0,0 +1,40 @@
import { BaseApiClient } from '../base/BaseApiClient';
import type {
LoginParamsDto,
SignupParamsDto,
SessionDataDto,
} from '../../dtos';
/**
* Auth API Client
*
* Handles all authentication-related API operations.
*/
export class AuthApiClient extends BaseApiClient {
/** Sign up with email */
signup(params: SignupParamsDto): Promise<SessionDataDto> {
return this.post<SessionDataDto>('/auth/signup', params);
}
/** Login with email */
login(params: LoginParamsDto): Promise<SessionDataDto> {
return this.post<SessionDataDto>('/auth/login', params);
}
/** Get current session */
getSession(): Promise<SessionDataDto | null> {
return this.get<SessionDataDto | null>('/auth/session');
}
/** Logout */
logout(): Promise<void> {
return this.post<void>('/auth/logout', {});
}
/** Start iRacing auth redirect */
getIracingAuthUrl(returnTo?: string): string {
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001';
const params = returnTo ? `?returnTo=${encodeURIComponent(returnTo)}` : '';
return `${baseUrl}/auth/iracing/start${params}`;
}
}

View File

@@ -0,0 +1,68 @@
/**
* Base API Client for HTTP operations
*
* Provides generic HTTP methods with common request/response handling,
* error handling, and authentication.
*/
export class BaseApiClient {
private baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
protected async request<T>(method: string, path: string, data?: object): Promise<T> {
const headers: HeadersInit = {
'Content-Type': 'application/json',
};
const config: RequestInit = {
method,
headers,
credentials: 'include', // Include cookies for auth
};
if (data) {
config.body = JSON.stringify(data);
}
const response = await fetch(`${this.baseUrl}${path}`, config);
if (!response.ok) {
let errorData: { message?: string } = { message: response.statusText };
try {
errorData = await response.json();
} catch {
// Keep default error message
}
throw new Error(errorData.message || `API request failed with status ${response.status}`);
}
const text = await response.text();
if (!text) {
return null as T;
}
return JSON.parse(text) as T;
}
protected get<T>(path: string): Promise<T> {
return this.request<T>('GET', path);
}
protected post<T>(path: string, data: object): Promise<T> {
return this.request<T>('POST', path, data);
}
protected put<T>(path: string, data: object): Promise<T> {
return this.request<T>('PUT', path, data);
}
protected delete<T>(path: string): Promise<T> {
return this.request<T>('DELETE', path);
}
protected patch<T>(path: string, data: object): Promise<T> {
return this.request<T>('PATCH', path, data);
}
}

View File

@@ -0,0 +1,29 @@
import { BaseApiClient } from '../base/BaseApiClient';
import type {
DriversLeaderboardDto,
CompleteOnboardingInputDto,
CompleteOnboardingOutputDto,
DriverDto,
} from '../../dtos';
/**
* Drivers API Client
*
* Handles all driver-related API operations.
*/
export class DriversApiClient extends BaseApiClient {
/** Get drivers leaderboard */
getLeaderboard(): Promise<DriversLeaderboardDto> {
return this.get<DriversLeaderboardDto>('/drivers/leaderboard');
}
/** Complete driver onboarding */
completeOnboarding(input: CompleteOnboardingInputDto): Promise<CompleteOnboardingOutputDto> {
return this.post<CompleteOnboardingOutputDto>('/drivers/complete-onboarding', input);
}
/** Get current driver (based on session) */
getCurrent(): Promise<DriverDto | null> {
return this.get<DriverDto | null>('/drivers/current');
}
}

View File

@@ -0,0 +1,46 @@
import { LeaguesApiClient } from './leagues/LeaguesApiClient';
import { RacesApiClient } from './races/RacesApiClient';
import { DriversApiClient } from './drivers/DriversApiClient';
import { TeamsApiClient } from './teams/TeamsApiClient';
import { SponsorsApiClient } from './sponsors/SponsorsApiClient';
import { MediaApiClient } from './media/MediaApiClient';
import { AnalyticsApiClient } from './analytics/AnalyticsApiClient';
import { AuthApiClient } from './auth/AuthApiClient';
import { PaymentsApiClient } from './payments/PaymentsApiClient';
/**
* Main API Client
*
* Orchestrates all domain-specific API clients with consistent configuration.
*/
export class ApiClient {
public readonly leagues: LeaguesApiClient;
public readonly races: RacesApiClient;
public readonly drivers: DriversApiClient;
public readonly teams: TeamsApiClient;
public readonly sponsors: SponsorsApiClient;
public readonly media: MediaApiClient;
public readonly analytics: AnalyticsApiClient;
public readonly auth: AuthApiClient;
public readonly payments: PaymentsApiClient;
constructor(baseUrl: string) {
this.leagues = new LeaguesApiClient(baseUrl);
this.races = new RacesApiClient(baseUrl);
this.drivers = new DriversApiClient(baseUrl);
this.teams = new TeamsApiClient(baseUrl);
this.sponsors = new SponsorsApiClient(baseUrl);
this.media = new MediaApiClient(baseUrl);
this.analytics = new AnalyticsApiClient(baseUrl);
this.auth = new AuthApiClient(baseUrl);
this.payments = new PaymentsApiClient(baseUrl);
}
}
// ============================================================================
// Singleton Instance
// ============================================================================
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001';
export const api = new ApiClient(API_BASE_URL);

View File

@@ -0,0 +1,52 @@
import { BaseApiClient } from '../base/BaseApiClient';
import type {
AllLeaguesWithCapacityDto,
LeagueStatsDto,
LeagueStandingsDto,
LeagueScheduleDto,
LeagueMembershipsDto,
CreateLeagueInputDto,
CreateLeagueOutputDto,
} from '../../dtos';
/**
* Leagues API Client
*
* Handles all league-related API operations.
*/
export class LeaguesApiClient extends BaseApiClient {
/** Get all leagues with capacity information */
getAllWithCapacity(): Promise<AllLeaguesWithCapacityDto> {
return this.get<AllLeaguesWithCapacityDto>('/leagues/all-with-capacity');
}
/** Get total number of leagues */
getTotal(): Promise<LeagueStatsDto> {
return this.get<LeagueStatsDto>('/leagues/total-leagues');
}
/** Get league standings */
getStandings(leagueId: string): Promise<LeagueStandingsDto> {
return this.get<LeagueStandingsDto>(`/leagues/${leagueId}/standings`);
}
/** Get league schedule */
getSchedule(leagueId: string): Promise<LeagueScheduleDto> {
return this.get<LeagueScheduleDto>(`/leagues/${leagueId}/schedule`);
}
/** Get league memberships */
getMemberships(leagueId: string): Promise<LeagueMembershipsDto> {
return this.get<LeagueMembershipsDto>(`/leagues/${leagueId}/memberships`);
}
/** Create a new league */
create(input: CreateLeagueInputDto): Promise<CreateLeagueOutputDto> {
return this.post<CreateLeagueOutputDto>('/leagues', input);
}
/** Remove a member from league */
removeMember(leagueId: string, performerDriverId: string, targetDriverId: string): Promise<{ success: boolean }> {
return this.patch<{ success: boolean }>(`/leagues/${leagueId}/members/${targetDriverId}/remove`, { performerDriverId });
}
}

View File

@@ -0,0 +1,17 @@
import { BaseApiClient } from '../base/BaseApiClient';
import type {
RequestAvatarGenerationInputDto,
RequestAvatarGenerationOutputDto,
} from '../../dtos';
/**
* Media API Client
*
* Handles all media-related API operations.
*/
export class MediaApiClient extends BaseApiClient {
/** Request avatar generation */
requestAvatarGeneration(input: RequestAvatarGenerationInputDto): Promise<RequestAvatarGenerationOutputDto> {
return this.post<RequestAvatarGenerationOutputDto>('/media/avatar/generate', input);
}
}

View File

@@ -0,0 +1,49 @@
import { BaseApiClient } from '../base/BaseApiClient';
import type {
GetPaymentsOutputDto,
CreatePaymentInputDto,
CreatePaymentOutputDto,
GetMembershipFeesOutputDto,
GetPrizesOutputDto,
GetWalletOutputDto,
} from '../../dtos';
/**
* Payments API Client
*
* Handles all payment-related API operations.
*/
export class PaymentsApiClient extends BaseApiClient {
/** Get payments */
getPayments(leagueId?: string, driverId?: string): Promise<GetPaymentsOutputDto> {
const params = new URLSearchParams();
if (leagueId) params.append('leagueId', leagueId);
if (driverId) params.append('driverId', driverId);
const query = params.toString();
return this.get<GetPaymentsOutputDto>(`/payments${query ? `?${query}` : ''}`);
}
/** Create a payment */
createPayment(input: CreatePaymentInputDto): Promise<CreatePaymentOutputDto> {
return this.post<CreatePaymentOutputDto>('/payments', input);
}
/** Get membership fees */
getMembershipFees(leagueId: string): Promise<GetMembershipFeesOutputDto> {
return this.get<GetMembershipFeesOutputDto>(`/payments/membership-fees?leagueId=${leagueId}`);
}
/** Get prizes */
getPrizes(leagueId?: string, seasonId?: string): Promise<GetPrizesOutputDto> {
const params = new URLSearchParams();
if (leagueId) params.append('leagueId', leagueId);
if (seasonId) params.append('seasonId', seasonId);
const query = params.toString();
return this.get<GetPrizesOutputDto>(`/payments/prizes${query ? `?${query}` : ''}`);
}
/** Get wallet */
getWallet(driverId: string): Promise<GetWalletOutputDto> {
return this.get<GetWalletOutputDto>(`/payments/wallets?driverId=${driverId}`);
}
}

View File

@@ -0,0 +1,53 @@
import { BaseApiClient } from '../base/BaseApiClient';
import type {
RaceStatsDto,
RacesPageDataDto,
RaceDetailDto,
RaceResultsDetailDto,
RaceWithSOFDto,
RegisterForRaceInputDto,
ImportRaceResultsInputDto,
ImportRaceResultsSummaryDto,
} from '../../dtos';
/**
* Races API Client
*
* Handles all race-related API operations.
*/
export class RacesApiClient extends BaseApiClient {
/** Get total number of races */
getTotal(): Promise<RaceStatsDto> {
return this.get<RaceStatsDto>('/races/total-races');
}
/** Get races page data */
getPageData(): Promise<RacesPageDataDto> {
return this.get<RacesPageDataDto>('/races/page-data');
}
/** Get race detail */
getDetail(raceId: string, driverId: string): Promise<RaceDetailDto> {
return this.get<RaceDetailDto>(`/races/${raceId}?driverId=${driverId}`);
}
/** Get race results detail */
getResultsDetail(raceId: string): Promise<RaceResultsDetailDto> {
return this.get<RaceResultsDetailDto>(`/races/${raceId}/results`);
}
/** Get race with strength of field */
getWithSOF(raceId: string): Promise<RaceWithSOFDto> {
return this.get<RaceWithSOFDto>(`/races/${raceId}/sof`);
}
/** Register for race */
register(raceId: string, input: RegisterForRaceInputDto): Promise<void> {
return this.post<void>(`/races/${raceId}/register`, input);
}
/** Import race results */
importResults(raceId: string, input: ImportRaceResultsInputDto): Promise<ImportRaceResultsSummaryDto> {
return this.post<ImportRaceResultsSummaryDto>(`/races/${raceId}/import-results`, input);
}
}

View File

@@ -0,0 +1,41 @@
import { BaseApiClient } from '../base/BaseApiClient';
import type {
GetEntitySponsorshipPricingResultDto,
GetSponsorsOutputDto,
CreateSponsorInputDto,
CreateSponsorOutputDto,
SponsorDashboardDto,
SponsorSponsorshipsDto,
} from '../../dtos';
/**
* Sponsors API Client
*
* Handles all sponsor-related API operations.
*/
export class SponsorsApiClient extends BaseApiClient {
/** Get sponsorship pricing */
getPricing(): Promise<GetEntitySponsorshipPricingResultDto> {
return this.get<GetEntitySponsorshipPricingResultDto>('/sponsors/pricing');
}
/** Get all sponsors */
getAll(): Promise<GetSponsorsOutputDto> {
return this.get<GetSponsorsOutputDto>('/sponsors');
}
/** Create a new sponsor */
create(input: CreateSponsorInputDto): Promise<CreateSponsorOutputDto> {
return this.post<CreateSponsorOutputDto>('/sponsors', input);
}
/** Get sponsor dashboard */
getDashboard(sponsorId: string): Promise<SponsorDashboardDto | null> {
return this.get<SponsorDashboardDto | null>(`/sponsors/dashboard/${sponsorId}`);
}
/** Get sponsor sponsorships */
getSponsorships(sponsorId: string): Promise<SponsorSponsorshipsDto | null> {
return this.get<SponsorSponsorshipsDto | null>(`/sponsors/${sponsorId}/sponsorships`);
}
}

View File

@@ -0,0 +1,54 @@
import { BaseApiClient } from '../base/BaseApiClient';
import type {
AllTeamsDto,
TeamDetailsDto,
TeamMembersDto,
TeamJoinRequestsDto,
CreateTeamInputDto,
CreateTeamOutputDto,
UpdateTeamInputDto,
UpdateTeamOutputDto,
DriverTeamDto,
} from '../../dtos';
/**
* Teams API Client
*
* Handles all team-related API operations.
*/
export class TeamsApiClient extends BaseApiClient {
/** Get all teams */
getAll(): Promise<AllTeamsDto> {
return this.get<AllTeamsDto>('/teams/all');
}
/** Get team details */
getDetails(teamId: string): Promise<TeamDetailsDto | null> {
return this.get<TeamDetailsDto | null>(`/teams/${teamId}`);
}
/** Get team members */
getMembers(teamId: string): Promise<TeamMembersDto> {
return this.get<TeamMembersDto>(`/teams/${teamId}/members`);
}
/** Get team join requests */
getJoinRequests(teamId: string): Promise<TeamJoinRequestsDto> {
return this.get<TeamJoinRequestsDto>(`/teams/${teamId}/join-requests`);
}
/** Create a new team */
create(input: CreateTeamInputDto): Promise<CreateTeamOutputDto> {
return this.post<CreateTeamOutputDto>('/teams', input);
}
/** Update team */
update(teamId: string, input: UpdateTeamInputDto): Promise<UpdateTeamOutputDto> {
return this.patch<UpdateTeamOutputDto>(`/teams/${teamId}`, input);
}
/** Get driver's team */
getDriverTeam(driverId: string): Promise<DriverTeamDto | null> {
return this.get<DriverTeamDto | null>(`/teams/driver/${driverId}`);
}
}