wip
This commit is contained in:
@@ -18,6 +18,7 @@ type AuthContextValue = {
|
||||
loading: boolean;
|
||||
login: (returnTo?: string) => void;
|
||||
logout: () => Promise<void>;
|
||||
refreshSession: () => Promise<void>;
|
||||
};
|
||||
|
||||
const AuthContext = createContext<AuthContextValue | undefined>(undefined);
|
||||
@@ -32,41 +33,34 @@ export function AuthProvider({ initialSession = null, children }: AuthProviderPr
|
||||
const [session, setSession] = useState<AuthSession | null>(initialSession);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const fetchSession = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetch('/api/auth/session', {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
setSession(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = (await res.json()) as { session: AuthSession | null };
|
||||
setSession(data.session ?? null);
|
||||
} catch {
|
||||
setSession(null);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const refreshSession = useCallback(async () => {
|
||||
await fetchSession();
|
||||
}, [fetchSession]);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialSession) return;
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
async function loadSession() {
|
||||
try {
|
||||
const res = await fetch('/api/auth/session', {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
if (!cancelled) setSession(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = (await res.json()) as { session: AuthSession | null };
|
||||
|
||||
if (!cancelled) {
|
||||
setSession(data.session ?? null);
|
||||
}
|
||||
} catch {
|
||||
if (!cancelled) {
|
||||
setSession(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadSession();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [initialSession]);
|
||||
fetchSession();
|
||||
}, [initialSession, fetchSession]);
|
||||
|
||||
const login = useCallback(
|
||||
(returnTo?: string) => {
|
||||
@@ -105,8 +99,9 @@ export function AuthProvider({ initialSession = null, children }: AuthProviderPr
|
||||
loading,
|
||||
login,
|
||||
logout,
|
||||
refreshSession,
|
||||
}),
|
||||
[session, loading, login, logout],
|
||||
[session, loading, login, logout, refreshSession],
|
||||
);
|
||||
|
||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||
|
||||
@@ -12,6 +12,8 @@ import { Result } from '@gridpilot/racing/domain/entities/Result';
|
||||
import { Standing } from '@gridpilot/racing/domain/entities/Standing';
|
||||
import { Game } from '@gridpilot/racing/domain/entities/Game';
|
||||
import { Season } from '@gridpilot/racing/domain/entities/Season';
|
||||
import { Track } from '@gridpilot/racing/domain/entities/Track';
|
||||
import { Car } from '@gridpilot/racing/domain/entities/Car';
|
||||
|
||||
import type { IDriverRepository } from '@gridpilot/racing/domain/repositories/IDriverRepository';
|
||||
import type { ILeagueRepository } from '@gridpilot/racing/domain/repositories/ILeagueRepository';
|
||||
@@ -22,6 +24,8 @@ import type { IPenaltyRepository } from '@gridpilot/racing/domain/repositories/I
|
||||
import type { IGameRepository } from '@gridpilot/racing/domain/repositories/IGameRepository';
|
||||
import type { ISeasonRepository } from '@gridpilot/racing/domain/repositories/ISeasonRepository';
|
||||
import type { ILeagueScoringConfigRepository } from '@gridpilot/racing/domain/repositories/ILeagueScoringConfigRepository';
|
||||
import type { ITrackRepository } from '@gridpilot/racing/domain/repositories/ITrackRepository';
|
||||
import type { ICarRepository } from '@gridpilot/racing/domain/repositories/ICarRepository';
|
||||
import type {
|
||||
ITeamRepository,
|
||||
ITeamMembershipRepository,
|
||||
@@ -39,6 +43,8 @@ import { InMemoryRaceRepository } from '@gridpilot/racing/infrastructure/reposit
|
||||
import { InMemoryResultRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryResultRepository';
|
||||
import { InMemoryStandingRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryStandingRepository';
|
||||
import { InMemoryPenaltyRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryPenaltyRepository';
|
||||
import { InMemoryTrackRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryTrackRepository';
|
||||
import { InMemoryCarRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryCarRepository';
|
||||
import {
|
||||
InMemoryGameRepository,
|
||||
InMemorySeasonRepository,
|
||||
@@ -75,7 +81,10 @@ import {
|
||||
GetLeagueScoringConfigQuery,
|
||||
CreateLeagueWithSeasonAndScoringUseCase,
|
||||
GetLeagueFullConfigQuery,
|
||||
GetRaceWithSOFQuery,
|
||||
GetLeagueStatsQuery,
|
||||
} from '@gridpilot/racing/application';
|
||||
import type { DriverRatingProvider } from '@gridpilot/racing/application';
|
||||
import {
|
||||
createStaticRacingSeed,
|
||||
type RacingSeedData,
|
||||
@@ -172,6 +181,8 @@ class DIContainer {
|
||||
private _feedRepository: IFeedRepository;
|
||||
private _socialRepository: ISocialGraphRepository;
|
||||
private _imageService: ImageServicePort;
|
||||
private _trackRepository: ITrackRepository;
|
||||
private _carRepository: ICarRepository;
|
||||
|
||||
// Racing application use-cases / queries
|
||||
private _joinLeagueUseCase: JoinLeagueUseCase;
|
||||
@@ -189,6 +200,9 @@ class DIContainer {
|
||||
private _getLeagueFullConfigQuery: GetLeagueFullConfigQuery;
|
||||
// Placeholder for future schedule preview wiring
|
||||
private _previewLeagueScheduleQuery: PreviewLeagueScheduleQuery;
|
||||
private _getRaceWithSOFQuery: GetRaceWithSOFQuery;
|
||||
private _getLeagueStatsQuery: GetLeagueStatsQuery;
|
||||
private _driverRatingProvider: DriverRatingProvider;
|
||||
|
||||
private _createTeamUseCase: CreateTeamUseCase;
|
||||
private _joinTeamUseCase: JoinTeamUseCase;
|
||||
@@ -226,8 +240,33 @@ class DIContainer {
|
||||
this._leagueRepository
|
||||
);
|
||||
|
||||
// Race registrations (start empty; populated via use-cases)
|
||||
this._raceRegistrationRepository = new InMemoryRaceRegistrationRepository();
|
||||
// Race registrations - seed from results for completed races, plus some upcoming races
|
||||
const seedRaceRegistrations: Array<{ raceId: string; driverId: string; registeredAt: Date }> = [];
|
||||
|
||||
// For completed races, extract driver registrations from results
|
||||
for (const result of seedData.results) {
|
||||
seedRaceRegistrations.push({
|
||||
raceId: result.raceId,
|
||||
driverId: result.driverId,
|
||||
registeredAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // 7 days ago
|
||||
});
|
||||
}
|
||||
|
||||
// For some upcoming races, add random registrations
|
||||
const upcomingRaces = seedData.races.filter(r => r.status === 'scheduled').slice(0, 10);
|
||||
for (const race of upcomingRaces) {
|
||||
const participantCount = Math.floor(Math.random() * 12) + 8; // 8-20 participants
|
||||
const shuffledDrivers = [...seedData.drivers].sort(() => Math.random() - 0.5).slice(0, participantCount);
|
||||
for (const driver of shuffledDrivers) {
|
||||
seedRaceRegistrations.push({
|
||||
raceId: race.id,
|
||||
driverId: driver.id,
|
||||
registeredAt: new Date(Date.now() - Math.floor(Math.random() * 5) * 24 * 60 * 60 * 1000),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this._raceRegistrationRepository = new InMemoryRaceRegistrationRepository(seedRaceRegistrations);
|
||||
|
||||
// Penalties (seeded in-memory adapter)
|
||||
this._penaltyRepository = new InMemoryPenaltyRepository();
|
||||
@@ -490,6 +529,39 @@ class DIContainer {
|
||||
// Schedule preview query (used by league creation wizard step 3)
|
||||
this._previewLeagueScheduleQuery = new PreviewLeagueScheduleQuery();
|
||||
|
||||
// DriverRatingProvider adapter using driverStats
|
||||
this._driverRatingProvider = {
|
||||
getRating: (driverId: string): number | null => {
|
||||
const stats = driverStats[driverId];
|
||||
return stats?.rating ?? null;
|
||||
},
|
||||
getRatings: (driverIds: string[]): Map<string, number> => {
|
||||
const result = new Map<string, number>();
|
||||
for (const id of driverIds) {
|
||||
const stats = driverStats[id];
|
||||
if (stats?.rating) {
|
||||
result.set(id, stats.rating);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
||||
// SOF queries
|
||||
this._getRaceWithSOFQuery = new GetRaceWithSOFQuery(
|
||||
this._raceRepository,
|
||||
this._raceRegistrationRepository,
|
||||
this._resultRepository,
|
||||
this._driverRatingProvider,
|
||||
);
|
||||
|
||||
this._getLeagueStatsQuery = new GetLeagueStatsQuery(
|
||||
this._leagueRepository,
|
||||
this._raceRepository,
|
||||
this._resultRepository,
|
||||
this._driverRatingProvider,
|
||||
);
|
||||
|
||||
this._createTeamUseCase = new CreateTeamUseCase(
|
||||
this._teamRepository,
|
||||
this._teamMembershipRepository,
|
||||
@@ -529,6 +601,184 @@ class DIContainer {
|
||||
|
||||
// Image service backed by demo adapter
|
||||
this._imageService = new DemoImageServiceAdapter();
|
||||
|
||||
// Seed Track and Car data for demo
|
||||
const seedTracks = [
|
||||
Track.create({
|
||||
id: 'track-spa',
|
||||
name: 'Spa-Francorchamps',
|
||||
shortName: 'SPA',
|
||||
country: 'Belgium',
|
||||
category: 'road',
|
||||
difficulty: 'advanced',
|
||||
lengthKm: 7.004,
|
||||
turns: 19,
|
||||
imageUrl: '/images/tracks/spa.jpg',
|
||||
gameId: 'iracing',
|
||||
}),
|
||||
Track.create({
|
||||
id: 'track-monza',
|
||||
name: 'Autodromo Nazionale Monza',
|
||||
shortName: 'MON',
|
||||
country: 'Italy',
|
||||
category: 'road',
|
||||
difficulty: 'intermediate',
|
||||
lengthKm: 5.793,
|
||||
turns: 11,
|
||||
imageUrl: '/images/tracks/monza.jpg',
|
||||
gameId: 'iracing',
|
||||
}),
|
||||
Track.create({
|
||||
id: 'track-nurburgring',
|
||||
name: 'Nürburgring Grand Prix',
|
||||
shortName: 'NUR',
|
||||
country: 'Germany',
|
||||
category: 'road',
|
||||
difficulty: 'advanced',
|
||||
lengthKm: 5.148,
|
||||
turns: 15,
|
||||
imageUrl: '/images/tracks/nurburgring.jpg',
|
||||
gameId: 'iracing',
|
||||
}),
|
||||
Track.create({
|
||||
id: 'track-silverstone',
|
||||
name: 'Silverstone Circuit',
|
||||
shortName: 'SIL',
|
||||
country: 'United Kingdom',
|
||||
category: 'road',
|
||||
difficulty: 'intermediate',
|
||||
lengthKm: 5.891,
|
||||
turns: 18,
|
||||
imageUrl: '/images/tracks/silverstone.jpg',
|
||||
gameId: 'iracing',
|
||||
}),
|
||||
Track.create({
|
||||
id: 'track-suzuka',
|
||||
name: 'Suzuka International Racing Course',
|
||||
shortName: 'SUZ',
|
||||
country: 'Japan',
|
||||
category: 'road',
|
||||
difficulty: 'expert',
|
||||
lengthKm: 5.807,
|
||||
turns: 18,
|
||||
imageUrl: '/images/tracks/suzuka.jpg',
|
||||
gameId: 'iracing',
|
||||
}),
|
||||
Track.create({
|
||||
id: 'track-daytona',
|
||||
name: 'Daytona International Speedway',
|
||||
shortName: 'DAY',
|
||||
country: 'United States',
|
||||
category: 'oval',
|
||||
difficulty: 'intermediate',
|
||||
lengthKm: 4.023,
|
||||
turns: 4,
|
||||
imageUrl: '/images/tracks/daytona.jpg',
|
||||
gameId: 'iracing',
|
||||
}),
|
||||
Track.create({
|
||||
id: 'track-laguna',
|
||||
name: 'WeatherTech Raceway Laguna Seca',
|
||||
shortName: 'LAG',
|
||||
country: 'United States',
|
||||
category: 'road',
|
||||
difficulty: 'advanced',
|
||||
lengthKm: 3.602,
|
||||
turns: 11,
|
||||
imageUrl: '/images/tracks/laguna.jpg',
|
||||
gameId: 'iracing',
|
||||
}),
|
||||
];
|
||||
|
||||
const seedCars = [
|
||||
Car.create({
|
||||
id: 'car-porsche-992',
|
||||
name: '911 GT3 R',
|
||||
shortName: '992 GT3R',
|
||||
manufacturer: 'Porsche',
|
||||
carClass: 'gt',
|
||||
license: 'B',
|
||||
year: 2023,
|
||||
horsepower: 565,
|
||||
weight: 1300,
|
||||
gameId: 'iracing',
|
||||
}),
|
||||
Car.create({
|
||||
id: 'car-ferrari-296',
|
||||
name: '296 GT3',
|
||||
shortName: '296 GT3',
|
||||
manufacturer: 'Ferrari',
|
||||
carClass: 'gt',
|
||||
license: 'B',
|
||||
year: 2023,
|
||||
horsepower: 600,
|
||||
weight: 1270,
|
||||
gameId: 'iracing',
|
||||
}),
|
||||
Car.create({
|
||||
id: 'car-mclaren-720s',
|
||||
name: '720S GT3 Evo',
|
||||
shortName: '720S',
|
||||
manufacturer: 'McLaren',
|
||||
carClass: 'gt',
|
||||
license: 'B',
|
||||
year: 2023,
|
||||
horsepower: 552,
|
||||
weight: 1290,
|
||||
gameId: 'iracing',
|
||||
}),
|
||||
Car.create({
|
||||
id: 'car-mercedes-gt3',
|
||||
name: 'AMG GT3 2020',
|
||||
shortName: 'AMG GT3',
|
||||
manufacturer: 'Mercedes',
|
||||
carClass: 'gt',
|
||||
license: 'B',
|
||||
year: 2020,
|
||||
horsepower: 550,
|
||||
weight: 1285,
|
||||
gameId: 'iracing',
|
||||
}),
|
||||
Car.create({
|
||||
id: 'car-lmp2',
|
||||
name: 'Dallara P217 LMP2',
|
||||
shortName: 'LMP2',
|
||||
manufacturer: 'Dallara',
|
||||
carClass: 'prototype',
|
||||
license: 'A',
|
||||
year: 2021,
|
||||
horsepower: 600,
|
||||
weight: 930,
|
||||
gameId: 'iracing',
|
||||
}),
|
||||
Car.create({
|
||||
id: 'car-f4',
|
||||
name: 'Formula 4',
|
||||
shortName: 'F4',
|
||||
manufacturer: 'Tatuus',
|
||||
carClass: 'formula',
|
||||
license: 'D',
|
||||
year: 2022,
|
||||
horsepower: 160,
|
||||
weight: 570,
|
||||
gameId: 'iracing',
|
||||
}),
|
||||
Car.create({
|
||||
id: 'car-mx5',
|
||||
name: 'MX-5 Cup',
|
||||
shortName: 'MX5',
|
||||
manufacturer: 'Mazda',
|
||||
carClass: 'sports',
|
||||
license: 'D',
|
||||
year: 2023,
|
||||
horsepower: 181,
|
||||
weight: 1128,
|
||||
gameId: 'iracing',
|
||||
}),
|
||||
];
|
||||
|
||||
this._trackRepository = new InMemoryTrackRepository(seedTracks);
|
||||
this._carRepository = new InMemoryCarRepository(seedCars);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -652,6 +902,18 @@ class DIContainer {
|
||||
return this._previewLeagueScheduleQuery;
|
||||
}
|
||||
|
||||
get getRaceWithSOFQuery(): GetRaceWithSOFQuery {
|
||||
return this._getRaceWithSOFQuery;
|
||||
}
|
||||
|
||||
get getLeagueStatsQuery(): GetLeagueStatsQuery {
|
||||
return this._getLeagueStatsQuery;
|
||||
}
|
||||
|
||||
get driverRatingProvider(): DriverRatingProvider {
|
||||
return this._driverRatingProvider;
|
||||
}
|
||||
|
||||
get createLeagueWithSeasonAndScoringUseCase(): CreateLeagueWithSeasonAndScoringUseCase {
|
||||
return this._createLeagueWithSeasonAndScoringUseCase;
|
||||
}
|
||||
@@ -719,6 +981,14 @@ class DIContainer {
|
||||
get imageService(): ImageServicePort {
|
||||
return this._imageService;
|
||||
}
|
||||
|
||||
get trackRepository(): ITrackRepository {
|
||||
return this._trackRepository;
|
||||
}
|
||||
|
||||
get carRepository(): ICarRepository {
|
||||
return this._carRepository;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -813,6 +1083,18 @@ export function getCreateLeagueWithSeasonAndScoringUseCase(): CreateLeagueWithSe
|
||||
return DIContainer.getInstance().createLeagueWithSeasonAndScoringUseCase;
|
||||
}
|
||||
|
||||
export function getGetRaceWithSOFQuery(): GetRaceWithSOFQuery {
|
||||
return DIContainer.getInstance().getRaceWithSOFQuery;
|
||||
}
|
||||
|
||||
export function getGetLeagueStatsQuery(): GetLeagueStatsQuery {
|
||||
return DIContainer.getInstance().getLeagueStatsQuery;
|
||||
}
|
||||
|
||||
export function getDriverRatingProvider(): DriverRatingProvider {
|
||||
return DIContainer.getInstance().driverRatingProvider;
|
||||
}
|
||||
|
||||
export function getTeamRepository(): ITeamRepository {
|
||||
return DIContainer.getInstance().teamRepository;
|
||||
}
|
||||
@@ -877,6 +1159,14 @@ export function getImageService(): ImageServicePort {
|
||||
return DIContainer.getInstance().imageService;
|
||||
}
|
||||
|
||||
export function getTrackRepository(): ITrackRepository {
|
||||
return DIContainer.getInstance().trackRepository;
|
||||
}
|
||||
|
||||
export function getCarRepository(): ICarRepository {
|
||||
return DIContainer.getInstance().carRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset function for testing
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user