team rating

This commit is contained in:
2025-12-30 12:25:45 +01:00
parent ccaa39c39c
commit 83371ea839
93 changed files with 10324 additions and 490 deletions

View File

@@ -6,10 +6,10 @@ import { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepos
import { IRaceRegistrationRepository } from '@core/racing/domain/repositories/IRaceRegistrationRepository';
import type { ITeamMembershipRepository } from '@core/racing/domain/repositories/ITeamMembershipRepository';
import type { ITeamRepository } from '@core/racing/domain/repositories/ITeamRepository';
import { IDriverStatsService } from '@core/racing/domain/services/IDriverStatsService';
import { IRankingService } from '@core/racing/domain/services/IRankingService';
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
import type { ISocialGraphRepository } from '@core/social/domain/repositories/ISocialGraphRepository';
import type { IResultRepository } from '@core/racing/domain/repositories/IResultRepository';
import type { IStandingRepository } from '@core/racing/domain/repositories/IStandingRepository';
// Import use cases
import { CompleteDriverOnboardingUseCase } from '@core/racing/application/use-cases/CompleteDriverOnboardingUseCase';
@@ -25,9 +25,18 @@ import { InMemoryImageServiceAdapter } from '@adapters/media/ports/InMemoryImage
import { InMemoryNotificationPreferenceRepository } from '@adapters/notifications/persistence/inmemory/InMemoryNotificationPreferenceRepository';
import { InMemoryDriverExtendedProfileProvider } from '@adapters/racing/ports/InMemoryDriverExtendedProfileProvider';
import { InMemoryDriverRatingProvider } from '@adapters/racing/ports/InMemoryDriverRatingProvider';
import { InMemoryDriverStatsService } from '@adapters/racing/services/InMemoryDriverStatsService';
import { InMemoryRankingService } from '@adapters/racing/services/InMemoryRankingService';
import { IImageServicePort } from '@core/racing/application/ports/IImageServicePort';
// Import new use cases
import { RankingUseCase } from '@core/racing/application/use-cases/RankingUseCase';
import { DriverStatsUseCase } from '@core/racing/application/use-cases/DriverStatsUseCase';
// Import new repositories
import { InMemoryDriverStatsRepository } from '@adapters/racing/persistence/inmemory/InMemoryDriverStatsRepository';
import { InMemoryMediaRepository } from '@adapters/racing/persistence/media/InMemoryMediaRepository';
// Import repository tokens
import { IDriverStatsRepository } from '@core/racing/domain/repositories/IDriverStatsRepository';
import { IMediaRepository } from '@core/racing/domain/repositories/IMediaRepository';
// Import use case interfaces
import type { IRankingUseCase } from '@core/racing/application/use-cases/IRankingUseCase';
import type { IDriverStatsUseCase } from '@core/racing/application/use-cases/IDriverStatsUseCase';
// Import presenters
import { CompleteOnboardingPresenter } from './presenters/CompleteOnboardingPresenter';
@@ -39,8 +48,6 @@ import { DriverStatsPresenter } from './presenters/DriverStatsPresenter';
import {
DRIVER_REPOSITORY_TOKEN,
RANKING_SERVICE_TOKEN,
DRIVER_STATS_SERVICE_TOKEN,
DRIVER_RATING_PROVIDER_TOKEN,
DRIVER_EXTENDED_PROFILE_PROVIDER_TOKEN,
IMAGE_SERVICE_PORT_TOKEN,
@@ -62,6 +69,10 @@ import {
IS_DRIVER_REGISTERED_FOR_RACE_OUTPUT_PORT_TOKEN,
UPDATE_DRIVER_PROFILE_OUTPUT_PORT_TOKEN,
GET_PROFILE_OVERVIEW_OUTPUT_PORT_TOKEN,
DRIVER_STATS_REPOSITORY_TOKEN,
MEDIA_REPOSITORY_TOKEN,
RANKING_SERVICE_TOKEN,
DRIVER_STATS_SERVICE_TOKEN,
} from './DriverTokens';
export * from './DriverTokens';
@@ -73,7 +84,11 @@ export const DriverProviders: Provider[] = [
DriverStatsPresenter,
CompleteOnboardingPresenter,
DriverRegistrationStatusPresenter,
DriverPresenter,
{
provide: DriverPresenter,
useFactory: (driverStatsRepository: IDriverStatsRepository) => new DriverPresenter(driverStatsRepository),
inject: [DRIVER_STATS_REPOSITORY_TOKEN],
},
DriverProfilePresenter,
// Output ports (point to presenters)
@@ -110,15 +125,33 @@ export const DriverProviders: Provider[] = [
// Repositories (racing + social repos are provided by imported persistence modules)
{
provide: RANKING_SERVICE_TOKEN,
useFactory: (logger: Logger) => new InMemoryRankingService(logger),
provide: DRIVER_STATS_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryDriverStatsRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: DRIVER_STATS_SERVICE_TOKEN,
useFactory: (logger: Logger) => new InMemoryDriverStatsService(logger),
provide: MEDIA_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryMediaRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: RANKING_SERVICE_TOKEN,
useFactory: (
standingRepo: IStandingRepository,
driverRepo: IDriverRepository,
logger: Logger
) => new RankingUseCase(standingRepo, driverRepo, logger),
inject: ['IStandingRepository', DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: DRIVER_STATS_SERVICE_TOKEN,
useFactory: (
resultRepo: IResultRepository,
standingRepo: IStandingRepository,
logger: Logger
) => new DriverStatsUseCase(resultRepo, standingRepo, logger),
inject: ['IResultRepository', 'IStandingRepository', LOGGER_TOKEN],
},
{
provide: DRIVER_RATING_PROVIDER_TOKEN,
useFactory: (logger: Logger) => new InMemoryDriverRatingProvider(logger),
@@ -145,13 +178,23 @@ export const DriverProviders: Provider[] = [
provide: GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN,
useFactory: (
driverRepo: IDriverRepository,
rankingService: IRankingService,
driverStatsService: IDriverStatsService,
imageService: IImageServicePort,
rankingUseCase: IRankingUseCase,
driverStatsUseCase: IDriverStatsUseCase,
mediaRepository: IMediaRepository,
logger: Logger,
output: UseCaseOutputPort<unknown>,
) => new GetDriversLeaderboardUseCase(driverRepo, rankingService, driverStatsService, (driverId: string) => Promise.resolve(imageService.getDriverAvatar(driverId)), logger, output),
inject: [DRIVER_REPOSITORY_TOKEN, RANKING_SERVICE_TOKEN, DRIVER_STATS_SERVICE_TOKEN, IMAGE_SERVICE_PORT_TOKEN, LOGGER_TOKEN, GET_DRIVERS_LEADERBOARD_OUTPUT_PORT_TOKEN],
) => new GetDriversLeaderboardUseCase(
driverRepo,
rankingUseCase,
driverStatsUseCase,
async (driverId: string) => {
const avatar = await mediaRepository.getDriverAvatar(driverId);
return avatar ?? undefined;
},
logger,
output
),
inject: [DRIVER_REPOSITORY_TOKEN, RANKING_SERVICE_TOKEN, DRIVER_STATS_SERVICE_TOKEN, MEDIA_REPOSITORY_TOKEN, LOGGER_TOKEN, GET_DRIVERS_LEADERBOARD_OUTPUT_PORT_TOKEN],
},
{
provide: GET_TOTAL_DRIVERS_USE_CASE_TOKEN,
@@ -183,8 +226,8 @@ export const DriverProviders: Provider[] = [
teamMembershipRepository: ITeamMembershipRepository,
socialRepository: ISocialGraphRepository,
driverExtendedProfileProvider: DriverExtendedProfileProvider,
driverStatsService: IDriverStatsService,
rankingService: IRankingService,
driverStatsUseCase: IDriverStatsUseCase,
rankingUseCase: IRankingUseCase,
output: UseCaseOutputPort<unknown>,
) =>
new GetProfileOverviewUseCase(
@@ -193,32 +236,8 @@ export const DriverProviders: Provider[] = [
teamMembershipRepository,
socialRepository,
driverExtendedProfileProvider,
(driverId: string) => {
const stats = driverStatsService.getDriverStats(driverId);
if (!stats) {
return null;
}
return {
rating: stats.rating,
wins: stats.wins,
podiums: stats.podiums,
dnfs: (stats as { dnfs?: number }).dnfs ?? 0,
totalRaces: stats.totalRaces,
avgFinish: null,
bestFinish: null,
worstFinish: null,
overallRank: stats.overallRank,
consistency: null,
percentile: null,
};
},
() =>
rankingService.getAllDriverRankings().map(ranking => ({
driverId: ranking.driverId,
rating: ranking.rating,
overallRank: ranking.overallRank,
})),
driverStatsUseCase,
rankingUseCase,
output,
),
inject: [

View File

@@ -1,6 +1,6 @@
export const DRIVER_REPOSITORY_TOKEN = 'IDriverRepository';
export const RANKING_SERVICE_TOKEN = 'IRankingService';
export const DRIVER_STATS_SERVICE_TOKEN = 'IDriverStatsService';
export const RANKING_SERVICE_TOKEN = 'IRankingUseCase';
export const DRIVER_STATS_SERVICE_TOKEN = 'IDriverStatsUseCase';
export const DRIVER_RATING_PROVIDER_TOKEN = 'DriverRatingProvider';
export const DRIVER_EXTENDED_PROFILE_PROVIDER_TOKEN = 'DriverExtendedProfileProvider';
export const IMAGE_SERVICE_PORT_TOKEN = 'IImageServicePort';
@@ -13,6 +13,10 @@ export const TEAM_MEMBERSHIP_REPOSITORY_TOKEN = 'ITeamMembershipRepository';
export { SOCIAL_GRAPH_REPOSITORY_TOKEN };
export const LOGGER_TOKEN = 'Logger';
// New tokens for clean architecture
export const DRIVER_STATS_REPOSITORY_TOKEN = 'IDriverStatsRepository';
export const MEDIA_REPOSITORY_TOKEN = 'IMediaRepository';
export const GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN = 'GetDriversLeaderboardUseCase';
export const GET_TOTAL_DRIVERS_USE_CASE_TOKEN = 'GetTotalDriversUseCase';
export const COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN = 'CompleteDriverOnboardingUseCase';

View File

@@ -1,11 +1,15 @@
import { Result } from '@core/shared/application/Result';
import type { Driver } from '@core/racing/domain/entities/Driver';
import type { GetDriverOutputDTO } from '../dtos/GetDriverOutputDTO';
import { DriverStatsStore } from '@adapters/racing/services/DriverStatsStore';
import type { IDriverStatsRepository } from '@core/racing/domain/repositories/IDriverStatsRepository';
export class DriverPresenter {
private responseModel: GetDriverOutputDTO | null = null;
constructor(
private readonly driverStatsRepository: IDriverStatsRepository
) {}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
present(result: Result<Driver | null, any>): void {
if (result.isErr()) {
@@ -19,9 +23,8 @@ export class DriverPresenter {
return;
}
// Get stats from the store
const statsStore = DriverStatsStore.getInstance();
const stats = statsStore.getDriverStats(driver.id);
// Get stats from repository (synchronously for now, could be async)
const stats = this.driverStatsRepository.getDriverStatsSync(driver.id);
this.responseModel = {
id: driver.id,