Files
gridpilot.gg/apps/website/lib/services/leagues/LeagueMembershipService.ts
2025-12-26 20:54:20 +01:00

168 lines
5.8 KiB
TypeScript

import { ApiClient } from '@/lib/api';
import type { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { LeagueMemberViewModel } from '@/lib/view-models/LeagueMemberViewModel';
import type { LeagueMemberDTO } from '@/lib/types/generated/LeagueMemberDTO';
import type { LeagueMembership } from '@/lib/types/LeagueMembership';
let cachedLeaguesApiClient: LeaguesApiClient | undefined;
function getDefaultLeaguesApiClient(): LeaguesApiClient {
if (cachedLeaguesApiClient) return cachedLeaguesApiClient;
const api = new ApiClient(getWebsiteApiBaseUrl());
cachedLeaguesApiClient = (api as any).leagues as LeaguesApiClient;
return cachedLeaguesApiClient;
}
export class LeagueMembershipService {
// In-memory cache for memberships (populated via API calls)
private static leagueMemberships = new Map<string, LeagueMembership[]>();
constructor(private readonly leaguesApiClient?: LeaguesApiClient) {}
private getClient(): LeaguesApiClient {
return this.leaguesApiClient ?? getDefaultLeaguesApiClient();
}
async getLeagueMemberships(leagueId: string, currentUserId: string): Promise<LeagueMemberViewModel[]> {
const dto = await this.getClient().getMemberships(leagueId);
const members: LeagueMemberDTO[] = ((dto as any)?.members ?? (dto as any)?.memberships ?? []) as LeagueMemberDTO[];
return members.map((m) => new LeagueMemberViewModel(m, currentUserId));
}
async removeMember(leagueId: string, performerDriverId: string, targetDriverId: string): Promise<{ success: boolean }> {
return this.getClient().removeMember(leagueId, performerDriverId, targetDriverId) as unknown as { success: boolean };
}
/**
* Get a specific membership from cache.
*/
static getMembership(leagueId: string, driverId: string): LeagueMembership | null {
const list = this.leagueMemberships.get(leagueId);
if (!list) return null;
return list.find((m) => m.driverId === driverId) ?? null;
}
/**
* Get all members of a league from cache.
*/
static getLeagueMembers(leagueId: string): LeagueMembership[] {
return [...(this.leagueMemberships.get(leagueId) ?? [])];
}
/**
* Fetch and cache memberships for a league via API.
*/
static async fetchLeagueMemberships(leagueId: string): Promise<LeagueMembership[]> {
try {
const result = await getDefaultLeaguesApiClient().getMemberships(leagueId);
const memberships: LeagueMembership[] = ((result as any)?.members ?? []).map((member: any) => ({
id: `${member.driverId}-${leagueId}`, // Generate ID since API doesn't provide it
leagueId,
driverId: member.driverId,
role: member.role,
status: 'active', // Assume active since API returns current members
joinedAt: member.joinedAt,
}));
this.setLeagueMemberships(leagueId, memberships);
return memberships;
} catch (error) {
console.error('Failed to fetch league memberships:', error);
return [];
}
}
/**
* Join a league.
*
* NOTE: The API currently exposes membership mutations via league member management endpoints.
* For now we keep the website decoupled by consuming only the API through this service.
*/
async joinLeague(leagueId: string, driverId: string): Promise<void> {
// Temporary: no join endpoint exposed yet in API.
// Keep behavior predictable for UI.
throw new Error('Joining leagues is not available in this build.');
}
/**
* Leave a league.
*/
async leaveLeague(leagueId: string, driverId: string): Promise<void> {
// Temporary: no leave endpoint exposed yet in API.
throw new Error('Leaving leagues is not available in this build.');
}
/**
* Set memberships in cache (for use after API calls).
*/
static setLeagueMemberships(leagueId: string, memberships: LeagueMembership[]): void {
this.leagueMemberships.set(leagueId, memberships);
}
/**
* Clear cached memberships for a league.
*/
static clearLeagueMemberships(leagueId: string): void {
this.leagueMemberships.delete(leagueId);
}
/**
* Get iterator for cached memberships (for utility functions).
*/
static getCachedMembershipsIterator(): IterableIterator<[string, LeagueMembership[]]> {
return this.leagueMemberships.entries();
}
/**
* Get all memberships for a specific driver across all leagues.
*/
static getAllMembershipsForDriver(driverId: string): LeagueMembership[] {
const allMemberships: LeagueMembership[] = [];
for (const [leagueId, memberships] of this.leagueMemberships.entries()) {
const driverMembership = memberships.find((m) => m.driverId === driverId);
if (driverMembership) {
allMemberships.push(driverMembership);
}
}
return allMemberships;
}
// Instance methods that delegate to static methods for consistency with service pattern
/**
* Get a specific membership from cache.
*/
getMembership(leagueId: string, driverId: string): LeagueMembership | null {
return LeagueMembershipService.getMembership(leagueId, driverId);
}
/**
* Get all members of a league from cache.
*/
getLeagueMembers(leagueId: string): LeagueMembership[] {
return LeagueMembershipService.getLeagueMembers(leagueId);
}
/**
* Fetch and cache memberships for a league via API.
*/
async fetchLeagueMemberships(leagueId: string): Promise<LeagueMembership[]> {
return LeagueMembershipService.fetchLeagueMemberships(leagueId);
}
/**
* Set memberships in cache (for use after API calls).
*/
setLeagueMemberships(leagueId: string, memberships: LeagueMembership[]): void {
LeagueMembershipService.setLeagueMemberships(leagueId, memberships);
}
/**
* Clear cached memberships for a league.
*/
clearLeagueMemberships(leagueId: string): void {
LeagueMembershipService.clearLeagueMemberships(leagueId);
}
}