harden media
This commit is contained in:
@@ -13,6 +13,7 @@ import type { ISeasonRepository } from '@core/racing/domain/repositories/ISeason
|
||||
import type { ISeasonSponsorshipRepository } from '@core/racing/domain/repositories/ISeasonSponsorshipRepository';
|
||||
import type { IStandingRepository } from '@core/racing/domain/repositories/IStandingRepository';
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
import type { MediaResolverPort } from '@core/ports/media/MediaResolverPort';
|
||||
|
||||
// Import concrete in-memory implementations
|
||||
import type { ILeagueWalletRepository } from "@core/racing/domain/repositories/ILeagueWalletRepository";
|
||||
@@ -20,6 +21,7 @@ import type { ITransactionRepository } from "@core/racing/domain/repositories/IT
|
||||
import { getLeagueScoringPresetById, listLeagueScoringPresets } from '@adapters/bootstrap/LeagueScoringPresets';
|
||||
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
||||
import { InMemoryLeagueStandingsRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueStandingsRepository';
|
||||
import { MediaResolverAdapter } from '@adapters/media/MediaResolverAdapter';
|
||||
|
||||
// Import use cases
|
||||
import { ApproveLeagueJoinRequestUseCase } from '@core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase';
|
||||
@@ -110,6 +112,8 @@ export const DRIVER_REPOSITORY_TOKEN = 'IDriverRepository';
|
||||
export const LEAGUE_WALLET_REPOSITORY_TOKEN = 'ILeagueWalletRepository';
|
||||
export const TRANSACTION_REPOSITORY_TOKEN = 'ITransactionRepository';
|
||||
export const LOGGER_TOKEN = 'Logger';
|
||||
export const MEDIA_RESOLVER_TOKEN = 'MediaResolverPort';
|
||||
|
||||
export const GET_LEAGUE_STANDINGS_USE_CASE = 'GetLeagueStandingsUseCase';
|
||||
export const GET_ALL_LEAGUES_WITH_CAPACITY_USE_CASE = 'GetAllLeaguesWithCapacityUseCase';
|
||||
export const GET_ALL_LEAGUES_WITH_CAPACITY_AND_SCORING_USE_CASE = 'GetAllLeaguesWithCapacityAndScoringUseCase';
|
||||
@@ -177,9 +181,21 @@ export const LeagueProviders: Provider[] = [
|
||||
provide: LOGGER_TOKEN,
|
||||
useClass: ConsoleLogger,
|
||||
},
|
||||
{
|
||||
provide: MEDIA_RESOLVER_TOKEN,
|
||||
useFactory: () => new MediaResolverAdapter({}),
|
||||
},
|
||||
// Presenters
|
||||
AllLeaguesWithCapacityPresenter,
|
||||
AllLeaguesWithCapacityAndScoringPresenter,
|
||||
{
|
||||
provide: AllLeaguesWithCapacityAndScoringPresenter,
|
||||
useFactory: (mediaResolver: MediaResolverPort) => {
|
||||
const presenter = new AllLeaguesWithCapacityAndScoringPresenter();
|
||||
presenter.setMediaResolver(mediaResolver);
|
||||
return presenter;
|
||||
},
|
||||
inject: [MEDIA_RESOLVER_TOKEN],
|
||||
},
|
||||
ApproveLeagueJoinRequestPresenter,
|
||||
CreateLeaguePresenter,
|
||||
GetLeagueAdminPermissionsPresenter,
|
||||
|
||||
@@ -319,6 +319,9 @@ export class LeagueService {
|
||||
throw new Error(err.code);
|
||||
}
|
||||
|
||||
// The use case calls presenter.present() internally
|
||||
// The presenter now handles logo resolution synchronously
|
||||
// Just get the view model which contains the resolved logo URLs
|
||||
return this.allLeaguesWithCapacityAndScoringPresenter.getViewModel();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ export const DRIVER_REPOSITORY_TOKEN = 'IDriverRepository';
|
||||
export const LEAGUE_WALLET_REPOSITORY_TOKEN = 'ILeagueWalletRepository';
|
||||
export const TRANSACTION_REPOSITORY_TOKEN = 'ITransactionRepository';
|
||||
export const LOGGER_TOKEN = 'Logger';
|
||||
export const MEDIA_RESOLVER_TOKEN = 'MediaResolverPort';
|
||||
|
||||
export const GET_LEAGUE_STANDINGS_USE_CASE = 'GetLeagueStandingsUseCase';
|
||||
export const GET_ALL_LEAGUES_WITH_CAPACITY_USE_CASE = 'GetAllLeaguesWithCapacityUseCase';
|
||||
|
||||
@@ -116,6 +116,11 @@ export class LeagueWithCapacityAndScoringDTO {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
timingSummary?: string;
|
||||
|
||||
@ApiProperty({ required: false, nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
logoUrl?: string | null;
|
||||
}
|
||||
|
||||
export class AllLeaguesWithCapacityAndScoringDTO {
|
||||
|
||||
@@ -18,7 +18,7 @@ export class LeagueSummaryDTO {
|
||||
@ApiProperty({ nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
logoUrl?: string;
|
||||
logoUrl!: string | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
@IsOptional()
|
||||
|
||||
@@ -4,52 +4,92 @@ import type {
|
||||
AllLeaguesWithCapacityAndScoringDTO,
|
||||
LeagueWithCapacityAndScoringDTO,
|
||||
} from '../dtos/AllLeaguesWithCapacityAndScoringDTO';
|
||||
import type { MediaResolverPort } from '@core/ports/media/MediaResolverPort';
|
||||
import { MediaReference } from '@core/domain/media/MediaReference';
|
||||
|
||||
export class AllLeaguesWithCapacityAndScoringPresenter
|
||||
implements UseCaseOutputPort<GetAllLeaguesWithCapacityAndScoringResult>
|
||||
{
|
||||
private result: AllLeaguesWithCapacityAndScoringDTO | null = null;
|
||||
private mediaResolver?: MediaResolverPort;
|
||||
|
||||
present(result: GetAllLeaguesWithCapacityAndScoringResult): void {
|
||||
const leagues: LeagueWithCapacityAndScoringDTO[] = result.leagues.map((summary) => {
|
||||
const timingSummary = summary.preset
|
||||
? formatTimingSummary(summary.preset.defaultTimings.mainRaceMinutes)
|
||||
: undefined;
|
||||
setMediaResolver(resolver: MediaResolverPort): void {
|
||||
this.mediaResolver = resolver;
|
||||
}
|
||||
|
||||
return {
|
||||
id: summary.league.id.toString(),
|
||||
name: summary.league.name.toString(),
|
||||
description: summary.league.description?.toString() || '',
|
||||
ownerId: summary.league.ownerId.toString(),
|
||||
createdAt: summary.league.createdAt.toDate().toISOString(),
|
||||
settings: {
|
||||
maxDrivers: summary.maxDrivers,
|
||||
...(summary.league.settings.sessionDuration !== undefined
|
||||
? { sessionDuration: summary.league.settings.sessionDuration }
|
||||
: {}),
|
||||
...(summary.league.settings.qualifyingFormat !== undefined
|
||||
? { qualifyingFormat: summary.league.settings.qualifyingFormat.toString() }
|
||||
: {}),
|
||||
},
|
||||
usedSlots: summary.currentDrivers,
|
||||
...(summary.league.category ? { category: summary.league.category } : {}),
|
||||
...mapSocialLinks(summary.league.socialLinks),
|
||||
...(summary.scoringConfig && summary.game && summary.preset
|
||||
? {
|
||||
scoring: {
|
||||
gameId: summary.game.id.toString(),
|
||||
gameName: summary.game.name.toString(),
|
||||
primaryChampionshipType: summary.preset.primaryChampionshipType,
|
||||
scoringPresetId: summary.scoringConfig.scoringPresetId?.toString() ?? 'custom',
|
||||
scoringPresetName: summary.preset.name,
|
||||
dropPolicySummary: summary.preset.dropPolicySummary,
|
||||
scoringPatternSummary: summary.preset.sessionSummary,
|
||||
},
|
||||
async present(result: GetAllLeaguesWithCapacityAndScoringResult): Promise<void> {
|
||||
const leagues: LeagueWithCapacityAndScoringDTO[] = await Promise.all(
|
||||
result.leagues.map(async (summary) => {
|
||||
const timingSummary = summary.preset
|
||||
? formatTimingSummary(summary.preset.defaultTimings.mainRaceMinutes)
|
||||
: undefined;
|
||||
|
||||
// Resolve logo URL
|
||||
let logoUrl: string | null | undefined;
|
||||
if (summary.league.logoRef) {
|
||||
const ref = summary.league.logoRef instanceof MediaReference
|
||||
? summary.league.logoRef
|
||||
: MediaReference.fromJSON(summary.league.logoRef);
|
||||
|
||||
if (this.mediaResolver) {
|
||||
logoUrl = await this.mediaResolver.resolve(ref);
|
||||
} else {
|
||||
// Fallback to manual construction
|
||||
if (ref.type === 'generated' && ref.generationRequestId) {
|
||||
const requestId = ref.generationRequestId;
|
||||
const firstHyphenIndex = requestId.indexOf('-');
|
||||
if (firstHyphenIndex !== -1) {
|
||||
const type = requestId.substring(0, firstHyphenIndex);
|
||||
const id = requestId.substring(firstHyphenIndex + 1);
|
||||
|
||||
if (type === 'league') {
|
||||
logoUrl = `/media/leagues/${id}/logo`;
|
||||
}
|
||||
}
|
||||
} else if (ref.type === 'uploaded' && ref.mediaId) {
|
||||
logoUrl = `/media/uploaded/${ref.mediaId}`;
|
||||
} else if (ref.type === 'system-default') {
|
||||
logoUrl = null;
|
||||
}
|
||||
: {}),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: summary.league.id.toString(),
|
||||
name: summary.league.name.toString(),
|
||||
description: summary.league.description?.toString() || '',
|
||||
ownerId: summary.league.ownerId.toString(),
|
||||
createdAt: summary.league.createdAt.toDate().toISOString(),
|
||||
settings: {
|
||||
maxDrivers: summary.maxDrivers,
|
||||
...(summary.league.settings.sessionDuration !== undefined
|
||||
? { sessionDuration: summary.league.settings.sessionDuration }
|
||||
: {}),
|
||||
...(summary.league.settings.qualifyingFormat !== undefined
|
||||
? { qualifyingFormat: summary.league.settings.qualifyingFormat.toString() }
|
||||
: {}),
|
||||
},
|
||||
usedSlots: summary.currentDrivers,
|
||||
...(summary.league.category ? { category: summary.league.category } : {}),
|
||||
...mapSocialLinks(summary.league.socialLinks),
|
||||
...(summary.scoringConfig && summary.game && summary.preset
|
||||
? {
|
||||
scoring: {
|
||||
gameId: summary.game.id.toString(),
|
||||
gameName: summary.game.name.toString(),
|
||||
primaryChampionshipType: summary.preset.primaryChampionshipType,
|
||||
scoringPresetId: summary.scoringConfig.scoringPresetId?.toString() ?? 'custom',
|
||||
scoringPresetName: summary.preset.name,
|
||||
dropPolicySummary: summary.preset.dropPolicySummary,
|
||||
scoringPatternSummary: summary.preset.sessionSummary,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(timingSummary ? { timingSummary } : {}),
|
||||
};
|
||||
});
|
||||
...(logoUrl !== undefined ? { logoUrl } : {}),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
this.result = {
|
||||
leagues,
|
||||
|
||||
Reference in New Issue
Block a user