harden media
This commit is contained in:
@@ -111,7 +111,7 @@ export class GetAllLeaguesWithCapacityAndScoringUseCase {
|
||||
});
|
||||
}
|
||||
|
||||
this.output.present({ leagues: enrichedLeagues });
|
||||
await this.output.present({ leagues: enrichedLeagues });
|
||||
|
||||
return Result.ok(undefined);
|
||||
} catch (error: unknown) {
|
||||
|
||||
@@ -3,7 +3,6 @@ import { GetAllTeamsUseCase, type GetAllTeamsInput, type GetAllTeamsResult } fro
|
||||
import type { ITeamRepository } from '../../domain/repositories/ITeamRepository';
|
||||
import type { ITeamMembershipRepository } from '../../domain/repositories/ITeamMembershipRepository';
|
||||
import type { ITeamStatsRepository } from '../../domain/repositories/ITeamStatsRepository';
|
||||
import type { IMediaRepository } from '../../domain/repositories/IMediaRepository';
|
||||
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
@@ -55,15 +54,6 @@ describe('GetAllTeamsUseCase', () => {
|
||||
existsByRaceId: vi.fn(),
|
||||
};
|
||||
|
||||
const mockMediaRepo: IMediaRepository = {
|
||||
getDriverAvatar: vi.fn(),
|
||||
getTeamLogo: vi.fn(),
|
||||
getTrackImage: vi.fn(),
|
||||
getCategoryIcon: vi.fn(),
|
||||
getSponsorLogo: vi.fn(),
|
||||
clear: vi.fn(),
|
||||
};
|
||||
|
||||
const mockLogger: Logger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
@@ -85,7 +75,6 @@ describe('GetAllTeamsUseCase', () => {
|
||||
mockTeamRepo,
|
||||
mockTeamMembershipRepo,
|
||||
mockTeamStatsRepo,
|
||||
mockMediaRepo,
|
||||
mockResultRepo,
|
||||
mockLogger,
|
||||
output,
|
||||
@@ -99,6 +88,9 @@ describe('GetAllTeamsUseCase', () => {
|
||||
ownerId: { toString: () => 'owner1' },
|
||||
leagues: [{ toString: () => 'league1' }],
|
||||
createdAt: { toDate: () => new Date('2023-01-01T00:00:00Z') },
|
||||
logoRef: { toJSON: () => ({ type: 'generated', generationRequestId: 'team-team1' }) },
|
||||
category: undefined,
|
||||
isRecruiting: false,
|
||||
};
|
||||
const team2 = {
|
||||
id: 'team2',
|
||||
@@ -108,11 +100,39 @@ describe('GetAllTeamsUseCase', () => {
|
||||
ownerId: { toString: () => 'owner2' },
|
||||
leagues: [{ toString: () => 'league2' }],
|
||||
createdAt: { toDate: () => new Date('2023-01-02T00:00:00Z') },
|
||||
logoRef: { toJSON: () => ({ type: 'generated', generationRequestId: 'team-team2' }) },
|
||||
category: undefined,
|
||||
isRecruiting: true,
|
||||
};
|
||||
|
||||
mockTeamFindAll.mockResolvedValue([team1, team2]);
|
||||
mockTeamMembershipCountByTeamId.mockImplementation((id: string) => Promise.resolve(id === 'team1' ? 5 : 3));
|
||||
|
||||
// Provide precomputed stats so the use case doesn't compute from results.
|
||||
(mockTeamStatsRepo.getTeamStats as unknown as Mock).mockImplementation((teamId: string) =>
|
||||
Promise.resolve(
|
||||
teamId === 'team1'
|
||||
? {
|
||||
performanceLevel: 'intermediate',
|
||||
specialization: 'mixed',
|
||||
region: 'EU',
|
||||
languages: ['en'],
|
||||
totalWins: 2,
|
||||
totalRaces: 10,
|
||||
rating: 1200,
|
||||
}
|
||||
: {
|
||||
performanceLevel: 'advanced',
|
||||
specialization: 'mixed',
|
||||
region: 'US',
|
||||
languages: ['en', 'de'],
|
||||
totalWins: 5,
|
||||
totalRaces: 20,
|
||||
rating: 1400,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const result = await useCase.execute({} as GetAllTeamsInput);
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
@@ -132,6 +152,17 @@ describe('GetAllTeamsUseCase', () => {
|
||||
leagues: ['league1'],
|
||||
createdAt: new Date('2023-01-01T00:00:00Z'),
|
||||
memberCount: 5,
|
||||
totalWins: 2,
|
||||
totalRaces: 10,
|
||||
performanceLevel: 'intermediate',
|
||||
specialization: 'mixed',
|
||||
region: 'EU',
|
||||
languages: ['en'],
|
||||
logoRef: team1.logoRef,
|
||||
logoUrl: null,
|
||||
rating: 1200,
|
||||
category: undefined,
|
||||
isRecruiting: false,
|
||||
},
|
||||
{
|
||||
id: 'team2',
|
||||
@@ -142,6 +173,17 @@ describe('GetAllTeamsUseCase', () => {
|
||||
leagues: ['league2'],
|
||||
createdAt: new Date('2023-01-02T00:00:00Z'),
|
||||
memberCount: 3,
|
||||
totalWins: 5,
|
||||
totalRaces: 20,
|
||||
performanceLevel: 'advanced',
|
||||
specialization: 'mixed',
|
||||
region: 'US',
|
||||
languages: ['en', 'de'],
|
||||
logoRef: team2.logoRef,
|
||||
logoUrl: null,
|
||||
rating: 1400,
|
||||
category: undefined,
|
||||
isRecruiting: true,
|
||||
},
|
||||
],
|
||||
totalCount: 2,
|
||||
@@ -153,7 +195,6 @@ describe('GetAllTeamsUseCase', () => {
|
||||
mockTeamRepo,
|
||||
mockTeamMembershipRepo,
|
||||
mockTeamStatsRepo,
|
||||
mockMediaRepo,
|
||||
mockResultRepo,
|
||||
mockLogger,
|
||||
output,
|
||||
@@ -180,7 +221,6 @@ describe('GetAllTeamsUseCase', () => {
|
||||
mockTeamRepo,
|
||||
mockTeamMembershipRepo,
|
||||
mockTeamStatsRepo,
|
||||
mockMediaRepo,
|
||||
mockResultRepo,
|
||||
mockLogger,
|
||||
output,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { ITeamRepository } from '../../domain/repositories/ITeamRepository';
|
||||
import type { ITeamMembershipRepository } from '../../domain/repositories/ITeamMembershipRepository';
|
||||
import type { ITeamStatsRepository } from '../../domain/repositories/ITeamStatsRepository';
|
||||
import type { IMediaRepository } from '../../domain/repositories/IMediaRepository';
|
||||
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import { MediaReference } from '@core/domain/media/MediaReference';
|
||||
|
||||
export type GetAllTeamsInput = {};
|
||||
|
||||
@@ -27,7 +27,8 @@ export interface TeamSummary {
|
||||
specialization?: string;
|
||||
region?: string;
|
||||
languages?: string[];
|
||||
logoUrl?: string;
|
||||
logoRef?: MediaReference;
|
||||
logoUrl?: string | null;
|
||||
rating?: number;
|
||||
category?: string | undefined;
|
||||
isRecruiting: boolean;
|
||||
@@ -46,7 +47,6 @@ export class GetAllTeamsUseCase {
|
||||
private readonly teamRepository: ITeamRepository,
|
||||
private readonly teamMembershipRepository: ITeamMembershipRepository,
|
||||
private readonly teamStatsRepository: ITeamStatsRepository,
|
||||
private readonly mediaRepository: IMediaRepository,
|
||||
private readonly resultRepository: IResultRepository,
|
||||
private readonly logger: Logger,
|
||||
private readonly output: UseCaseOutputPort<GetAllTeamsResult>,
|
||||
@@ -64,7 +64,9 @@ export class GetAllTeamsUseCase {
|
||||
const enrichedTeams: TeamSummary[] = await Promise.all(
|
||||
teams.map(async (team) => {
|
||||
const memberCount = await this.teamMembershipRepository.countByTeamId(team.id);
|
||||
const logoUrl = await this.mediaRepository.getTeamLogo(team.id);
|
||||
|
||||
// Get logo reference from team entity
|
||||
const logoRef = team.logoRef;
|
||||
|
||||
// Try to get pre-computed stats first
|
||||
let stats = await this.teamStatsRepository.getTeamStats(team.id);
|
||||
@@ -95,7 +97,6 @@ export class GetAllTeamsUseCase {
|
||||
else performanceLevel = 'beginner';
|
||||
|
||||
stats = {
|
||||
logoUrl: await this.mediaRepository.getTeamLogo(team.id) || '',
|
||||
performanceLevel,
|
||||
specialization: 'mixed',
|
||||
region: 'International',
|
||||
@@ -121,7 +122,8 @@ export class GetAllTeamsUseCase {
|
||||
specialization: stats!.specialization,
|
||||
region: stats!.region,
|
||||
languages: stats!.languages,
|
||||
logoUrl: logoUrl || stats!.logoUrl,
|
||||
logoRef: logoRef,
|
||||
logoUrl: null, // Will be resolved by presenter
|
||||
rating: stats!.rating,
|
||||
category: team.category,
|
||||
isRecruiting: team.isRecruiting,
|
||||
|
||||
@@ -33,7 +33,6 @@ describe('GetDriversLeaderboardUseCase', () => {
|
||||
getDriverStats: mockDriverStatsGetDriverStats,
|
||||
};
|
||||
|
||||
const mockGetDriverAvatar = vi.fn();
|
||||
const mockLogger: Logger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
@@ -50,13 +49,22 @@ describe('GetDriversLeaderboardUseCase', () => {
|
||||
mockDriverRepo,
|
||||
mockRankingUseCase,
|
||||
mockDriverStatsUseCase,
|
||||
mockGetDriverAvatar,
|
||||
mockLogger,
|
||||
mockOutput,
|
||||
);
|
||||
|
||||
const driver1 = { id: 'driver1', name: { value: 'Driver One' }, country: { value: 'US' } };
|
||||
const driver2 = { id: 'driver2', name: { value: 'Driver Two' }, country: { value: 'US' } };
|
||||
const driver1 = {
|
||||
id: 'driver1',
|
||||
name: { value: 'Driver One' },
|
||||
country: { value: 'US' },
|
||||
avatarRef: { type: 'system-default', variant: 'avatar' }
|
||||
};
|
||||
const driver2 = {
|
||||
id: 'driver2',
|
||||
name: { value: 'Driver Two' },
|
||||
country: { value: 'US' },
|
||||
avatarRef: { type: 'system-default', variant: 'avatar' }
|
||||
};
|
||||
const rankings = [
|
||||
{ driverId: 'driver1', rating: 2500, overallRank: 1 },
|
||||
{ driverId: 'driver2', rating: 2400, overallRank: 2 },
|
||||
@@ -71,11 +79,6 @@ describe('GetDriversLeaderboardUseCase', () => {
|
||||
if (id === 'driver2') return stats2;
|
||||
return null;
|
||||
});
|
||||
mockGetDriverAvatar.mockImplementation((driverId: string) => {
|
||||
if (driverId === 'driver1') return Promise.resolve('avatar-driver1');
|
||||
if (driverId === 'driver2') return Promise.resolve('avatar-driver2');
|
||||
return Promise.resolve('avatar-default');
|
||||
});
|
||||
|
||||
const input: GetDriversLeaderboardInput = { leagueId: 'league-1' };
|
||||
|
||||
@@ -94,7 +97,7 @@ describe('GetDriversLeaderboardUseCase', () => {
|
||||
podiums: 7,
|
||||
isActive: true,
|
||||
rank: 1,
|
||||
avatarUrl: 'avatar-driver1',
|
||||
avatarRef: driver1.avatarRef,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
driver: driver2,
|
||||
@@ -105,7 +108,7 @@ describe('GetDriversLeaderboardUseCase', () => {
|
||||
podiums: 4,
|
||||
isActive: true,
|
||||
rank: 2,
|
||||
avatarUrl: 'avatar-driver2',
|
||||
avatarRef: driver2.avatarRef,
|
||||
}),
|
||||
],
|
||||
totalRaces: 18,
|
||||
@@ -119,7 +122,6 @@ describe('GetDriversLeaderboardUseCase', () => {
|
||||
mockDriverRepo,
|
||||
mockRankingUseCase,
|
||||
mockDriverStatsUseCase,
|
||||
mockGetDriverAvatar,
|
||||
mockLogger,
|
||||
mockOutput,
|
||||
);
|
||||
@@ -146,18 +148,21 @@ describe('GetDriversLeaderboardUseCase', () => {
|
||||
mockDriverRepo,
|
||||
mockRankingUseCase,
|
||||
mockDriverStatsUseCase,
|
||||
mockGetDriverAvatar,
|
||||
mockLogger,
|
||||
mockOutput,
|
||||
);
|
||||
|
||||
const driver1 = { id: 'driver1', name: { value: 'Driver One' }, country: { value: 'US' } };
|
||||
const driver1 = {
|
||||
id: 'driver1',
|
||||
name: { value: 'Driver One' },
|
||||
country: { value: 'US' },
|
||||
avatarRef: { type: 'system-default', variant: 'avatar' }
|
||||
};
|
||||
const rankings = [{ driverId: 'driver1', rating: 2500, overallRank: 1 }];
|
||||
|
||||
mockDriverFindAll.mockResolvedValue([driver1]);
|
||||
mockRankingGetAllDriverRankings.mockReturnValue(rankings);
|
||||
mockDriverStatsGetDriverStats.mockReturnValue(null);
|
||||
mockGetDriverAvatar.mockResolvedValue('avatar-driver1');
|
||||
|
||||
const input: GetDriversLeaderboardInput = { leagueId: 'league-1' };
|
||||
|
||||
@@ -176,7 +181,7 @@ describe('GetDriversLeaderboardUseCase', () => {
|
||||
podiums: 0,
|
||||
isActive: false,
|
||||
rank: 1,
|
||||
avatarUrl: 'avatar-driver1',
|
||||
avatarRef: driver1.avatarRef,
|
||||
}),
|
||||
],
|
||||
totalRaces: 0,
|
||||
@@ -190,7 +195,6 @@ describe('GetDriversLeaderboardUseCase', () => {
|
||||
mockDriverRepo,
|
||||
mockRankingUseCase,
|
||||
mockDriverStatsUseCase,
|
||||
mockGetDriverAvatar,
|
||||
mockLogger,
|
||||
mockOutput,
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { IDriverRepository } from '../../domain/repositories/IDriverReposit
|
||||
import type { IDriverStatsUseCase } from './IDriverStatsUseCase';
|
||||
import type { IRankingUseCase } from './IRankingUseCase';
|
||||
import { SkillLevelService, type SkillLevel } from '../../domain/services/SkillLevelService';
|
||||
import { MediaReference } from '@core/domain/media/MediaReference';
|
||||
|
||||
export type GetDriversLeaderboardInput = {
|
||||
leagueId?: string;
|
||||
@@ -23,7 +24,7 @@ export interface DriverLeaderboardItem {
|
||||
podiums: number;
|
||||
isActive: boolean;
|
||||
rank: number;
|
||||
avatarUrl?: string;
|
||||
avatarRef?: MediaReference;
|
||||
}
|
||||
|
||||
export interface GetDriversLeaderboardResult {
|
||||
@@ -47,7 +48,6 @@ export class GetDriversLeaderboardUseCase implements UseCase<GetDriversLeaderboa
|
||||
private readonly driverRepository: IDriverRepository,
|
||||
private readonly rankingUseCase: IRankingUseCase,
|
||||
private readonly driverStatsUseCase: IDriverStatsUseCase,
|
||||
private readonly getDriverAvatar: (driverId: string) => Promise<string | undefined>,
|
||||
private readonly logger: Logger,
|
||||
private readonly output: UseCaseOutputPort<GetDriversLeaderboardResult>,
|
||||
) {}
|
||||
@@ -66,12 +66,6 @@ export class GetDriversLeaderboardUseCase implements UseCase<GetDriversLeaderboa
|
||||
const drivers = await this.driverRepository.findAll();
|
||||
const rankings = await this.rankingUseCase.getAllDriverRankings();
|
||||
|
||||
const avatarUrls: Record<string, string | undefined> = {};
|
||||
|
||||
for (const driver of drivers) {
|
||||
avatarUrls[driver.id] = await this.getDriverAvatar(driver.id);
|
||||
}
|
||||
|
||||
// Get stats for all drivers
|
||||
const statsPromises = drivers.map(driver =>
|
||||
this.driverStatsUseCase.getDriverStats(driver.id)
|
||||
@@ -90,7 +84,6 @@ export class GetDriversLeaderboardUseCase implements UseCase<GetDriversLeaderboa
|
||||
const rating = ranking?.rating ?? 0;
|
||||
const racesCompleted = stats?.totalRaces ?? 0;
|
||||
const skillLevel: SkillLevel = SkillLevelService.getSkillLevel(rating);
|
||||
const avatarUrl = avatarUrls[driver.id];
|
||||
|
||||
return {
|
||||
driver,
|
||||
@@ -101,7 +94,7 @@ export class GetDriversLeaderboardUseCase implements UseCase<GetDriversLeaderboa
|
||||
podiums: stats?.podiums ?? 0,
|
||||
isActive: racesCompleted > 0,
|
||||
rank: ranking?.overallRank ?? 0,
|
||||
...(avatarUrl !== undefined ? { avatarUrl } : {}),
|
||||
avatarRef: driver.avatarRef,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -132,4 +125,4 @@ export class GetDriversLeaderboardUseCase implements UseCase<GetDriversLeaderboa
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user