website refactor
This commit is contained in:
@@ -31,7 +31,7 @@ export class ApiConnectionMonitor extends EventEmitter {
|
||||
private isChecking = false;
|
||||
private checkInterval: NodeJS.Timeout | null = null;
|
||||
private healthCheckEndpoint: string;
|
||||
private readonly CHECK_INTERVAL = 30000; // 30 seconds
|
||||
private readonly CHECK_INTERVAL = 300000; // 5 minutes
|
||||
private readonly DEGRADATION_THRESHOLD = 0.7; // 70% failure rate
|
||||
|
||||
private constructor(healthCheckEndpoint: string = '/health') {
|
||||
|
||||
@@ -47,7 +47,7 @@ export class BaseApiClient {
|
||||
};
|
||||
|
||||
// Start monitoring connection health
|
||||
this.connectionMonitor.startMonitoring();
|
||||
// this.connectionMonitor.startMonitoring();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,7 @@ export interface RetryConfig {
|
||||
}
|
||||
|
||||
export const DEFAULT_RETRY_CONFIG: RetryConfig = {
|
||||
maxRetries: 3,
|
||||
maxRetries: 1,
|
||||
baseDelay: 1000,
|
||||
maxDelay: 10000,
|
||||
backoffMultiplier: 2,
|
||||
|
||||
@@ -28,7 +28,10 @@ export class DriversApiClient extends BaseApiClient {
|
||||
|
||||
/** Get current driver (based on session) */
|
||||
getCurrent(): Promise<GetDriverOutputDTO | null> {
|
||||
return this.get<GetDriverOutputDTO | null>('/drivers/current', { allowUnauthenticated: true });
|
||||
return this.get<GetDriverOutputDTO | null>('/drivers/current', {
|
||||
allowUnauthenticated: true,
|
||||
retry: false
|
||||
});
|
||||
}
|
||||
|
||||
/** Get driver registration status for a specific race */
|
||||
|
||||
@@ -46,12 +46,12 @@ function parseRaceDTOArray(value: unknown): RaceDTO[] {
|
||||
export class LeaguesApiClient extends BaseApiClient {
|
||||
/** Get all leagues with capacity information */
|
||||
getAllWithCapacity(): Promise<AllLeaguesWithCapacityDTO> {
|
||||
return this.get<AllLeaguesWithCapacityDTO>('/leagues/all-with-capacity');
|
||||
return this.get<AllLeaguesWithCapacityDTO>('/leagues/all-with-capacity', { retry: false });
|
||||
}
|
||||
|
||||
/** Get all leagues with capacity + scoring summary (for leagues page filters) */
|
||||
getAllWithCapacityAndScoring(): Promise<AllLeaguesWithCapacityAndScoringDTO> {
|
||||
return this.get<AllLeaguesWithCapacityAndScoringDTO>('/leagues/all-with-capacity-and-scoring');
|
||||
return this.get<AllLeaguesWithCapacityAndScoringDTO>('/leagues/all-with-capacity-and-scoring', { retry: false });
|
||||
}
|
||||
|
||||
/** Get total number of leagues */
|
||||
|
||||
@@ -22,10 +22,10 @@ export function QueryClientProvider({ children }: QueryClientProviderProps) {
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
// Disable automatic refetching in production for better performance
|
||||
refetchOnWindowFocus: process.env.NODE_ENV === 'development',
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: true,
|
||||
retry: 1,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
staleTime: 10 * 60 * 1000, // 10 minutes
|
||||
},
|
||||
mutations: {
|
||||
retry: 0,
|
||||
|
||||
@@ -263,13 +263,13 @@ export const routeMatchers = {
|
||||
* Check if path is public
|
||||
*/
|
||||
isPublic(path: string): boolean {
|
||||
logger.info('[RouteConfig] isPublic check', { path });
|
||||
// logger.info('[RouteConfig] isPublic check', { path });
|
||||
|
||||
const publicPatterns = this.getPublicPatterns();
|
||||
|
||||
// Check exact matches
|
||||
if (publicPatterns.includes(path)) {
|
||||
logger.info('[RouteConfig] Path is public (exact match)', { path });
|
||||
// logger.info('[RouteConfig] Path is public (exact match)', { path });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -279,19 +279,19 @@ export const routeMatchers = {
|
||||
if (segments.length === 2) {
|
||||
const [group, slug] = segments;
|
||||
if (group === 'leagues' && slug !== 'create') {
|
||||
logger.info('[RouteConfig] Path is public (league detail)', { path });
|
||||
// logger.info('[RouteConfig] Path is public (league detail)', { path });
|
||||
return true;
|
||||
}
|
||||
if (group === 'races') {
|
||||
logger.info('[RouteConfig] Path is public (race detail)', { path });
|
||||
// logger.info('[RouteConfig] Path is public (race detail)', { path });
|
||||
return true;
|
||||
}
|
||||
if (group === 'drivers') {
|
||||
logger.info('[RouteConfig] Path is public (driver detail)', { path });
|
||||
// logger.info('[RouteConfig] Path is public (driver detail)', { path });
|
||||
return true;
|
||||
}
|
||||
if (group === 'teams' && slug !== 'create') {
|
||||
logger.info('[RouteConfig] Path is public (team detail)', { path });
|
||||
// logger.info('[RouteConfig] Path is public (team detail)', { path });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -306,11 +306,11 @@ export const routeMatchers = {
|
||||
return false;
|
||||
});
|
||||
|
||||
if (isPublicParam) {
|
||||
logger.info('[RouteConfig] Path is public (parameterized match)', { path });
|
||||
} else {
|
||||
logger.info('[RouteConfig] Path is NOT public', { path });
|
||||
}
|
||||
// if (isPublicParam) {
|
||||
// logger.info('[RouteConfig] Path is public (parameterized match)', { path });
|
||||
// } else {
|
||||
// logger.info('[RouteConfig] Path is NOT public', { path });
|
||||
// }
|
||||
|
||||
return isPublicParam;
|
||||
},
|
||||
@@ -326,11 +326,11 @@ export const routeMatchers = {
|
||||
* Check if path requires specific role
|
||||
*/
|
||||
requiresRole(path: string): string[] | null {
|
||||
logger.info('[RouteConfig] requiresRole check', { path });
|
||||
// logger.info('[RouteConfig] requiresRole check', { path });
|
||||
|
||||
// Public routes never require a role
|
||||
if (this.isPublic(path)) {
|
||||
logger.info('[RouteConfig] Path is public, no role required', { path });
|
||||
// logger.info('[RouteConfig] Path is public, no role required', { path });
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -338,14 +338,14 @@ export const routeMatchers = {
|
||||
// Website session roles come from the API and are more specific than just "admin".
|
||||
// Keep "admin"/"owner" for backwards compatibility.
|
||||
const roles = ['admin', 'owner', 'league-admin', 'league-steward', 'league-owner', 'system-owner', 'super-admin'];
|
||||
logger.info('[RouteConfig] Path requires admin roles', { path, roles });
|
||||
// logger.info('[RouteConfig] Path requires admin roles', { path, roles });
|
||||
return roles;
|
||||
}
|
||||
if (this.isInGroup(path, 'sponsor')) {
|
||||
logger.info('[RouteConfig] Path requires sponsor role', { path });
|
||||
// logger.info('[RouteConfig] Path requires sponsor role', { path });
|
||||
return ['sponsor'];
|
||||
}
|
||||
logger.info('[RouteConfig] Path requires no specific role', { path });
|
||||
// logger.info('[RouteConfig] Path requires no specific role', { path });
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { LeagueDetailViewModel, LeagueViewModel, DriverViewModel, RaceViewModel } from './LeagueDetailViewModel';
|
||||
import { LeagueDetailViewModel, LeagueViewModel, LeagueDetailDriverViewModel, LeagueDetailRaceViewModel } from './LeagueDetailViewModel';
|
||||
|
||||
describe('LeagueDetailViewModel', () => {
|
||||
const baseLeague = {
|
||||
@@ -40,9 +40,9 @@ describe('LeagueDetailViewModel', () => {
|
||||
|
||||
expect(vm.league).toBeInstanceOf(LeagueViewModel);
|
||||
expect(vm.drivers).toHaveLength(2);
|
||||
expect(vm.drivers[0]).toBeInstanceOf(DriverViewModel);
|
||||
expect(vm.drivers[0]).toBeInstanceOf(LeagueDetailDriverViewModel);
|
||||
expect(vm.races).toHaveLength(2);
|
||||
expect(vm.races[0]).toBeInstanceOf(RaceViewModel);
|
||||
expect(vm.races[0]).toBeInstanceOf(LeagueDetailRaceViewModel);
|
||||
});
|
||||
|
||||
it('keeps core league metrics available on LeagueViewModel', () => {
|
||||
|
||||
@@ -8,13 +8,39 @@ import { RaceViewModel as SharedRaceViewModel } from "./RaceViewModel";
|
||||
*/
|
||||
export class LeagueDetailViewModel {
|
||||
league: LeagueViewModel;
|
||||
drivers: SharedDriverViewModel[];
|
||||
races: SharedRaceViewModel[];
|
||||
drivers: LeagueDetailDriverViewModel[];
|
||||
races: LeagueDetailRaceViewModel[];
|
||||
|
||||
constructor(data: { league: unknown; drivers: unknown[]; races: unknown[] }) {
|
||||
this.league = new LeagueViewModel(data.league);
|
||||
this.drivers = data.drivers.map(driver => new SharedDriverViewModel(driver as any));
|
||||
this.races = data.races.map(race => new SharedRaceViewModel(race as any));
|
||||
this.drivers = data.drivers.map(driver => new LeagueDetailDriverViewModel(driver as any));
|
||||
this.races = data.races.map(race => new LeagueDetailRaceViewModel(race as any));
|
||||
}
|
||||
}
|
||||
|
||||
export class LeagueDetailDriverViewModel extends SharedDriverViewModel {
|
||||
impressions: number;
|
||||
|
||||
constructor(dto: any) {
|
||||
super(dto);
|
||||
this.impressions = dto.impressions || 0;
|
||||
}
|
||||
|
||||
get formattedImpressions(): string {
|
||||
return this.impressions.toLocaleString();
|
||||
}
|
||||
}
|
||||
|
||||
export class LeagueDetailRaceViewModel extends SharedRaceViewModel {
|
||||
views: number;
|
||||
|
||||
constructor(dto: any) {
|
||||
super(dto);
|
||||
this.views = dto.views || 0;
|
||||
}
|
||||
|
||||
get formattedViews(): string {
|
||||
return this.views.toLocaleString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export interface LeagueSummaryViewModel {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
description: string | null;
|
||||
logoUrl: string | null;
|
||||
ownerId: string;
|
||||
createdAt: string;
|
||||
@@ -12,7 +12,7 @@ export interface LeagueSummaryViewModel {
|
||||
structureSummary: string;
|
||||
scoringPatternSummary?: string;
|
||||
timingSummary: string;
|
||||
category?: string;
|
||||
category?: string | null;
|
||||
scoring?: {
|
||||
gameId: string;
|
||||
gameName: string;
|
||||
|
||||
Reference in New Issue
Block a user