This commit is contained in:
2025-12-16 21:05:01 +01:00
parent f61e3a4e5a
commit 7532c7ed6d
207 changed files with 7861 additions and 2606 deletions

View File

@@ -10,11 +10,9 @@ import type { ISeasonRepository } from '../../domain/repositories/ISeasonReposit
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
import type {
ISponsorDashboardPresenter,
SponsorDashboardViewModel,
} from '../presenters/ISponsorDashboardPresenter';
import type { UseCase } from '@core/shared/application/UseCase';
import type { SponsorDashboardViewModel } from '../presenters/ISponsorDashboardPresenter';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
export interface GetSponsorDashboardQueryParams {
sponsorId: string;
@@ -51,9 +49,7 @@ export interface SponsorDashboardDTO {
};
}
export class GetSponsorDashboardUseCase
implements UseCase<GetSponsorDashboardQueryParams, SponsorDashboardDTO | null, SponsorDashboardViewModel, ISponsorDashboardPresenter>
{
export class GetSponsorDashboardUseCase {
constructor(
private readonly sponsorRepository: ISponsorRepository,
private readonly seasonSponsorshipRepository: ISeasonSponsorshipRepository,
@@ -65,115 +61,115 @@ export class GetSponsorDashboardUseCase
async execute(
params: GetSponsorDashboardQueryParams,
presenter: ISponsorDashboardPresenter,
): Promise<void> {
presenter.reset();
): Promise<Result<SponsorDashboardViewModel, ApplicationErrorCode<'REPOSITORY_ERROR'>>> {
try {
const { sponsorId } = params;
const { sponsorId } = params;
const sponsor = await this.sponsorRepository.findById(sponsorId);
if (!sponsor) {
presenter.present(null);
return;
}
// Get all sponsorships for this sponsor
const sponsorships = await this.seasonSponsorshipRepository.findBySponsorId(sponsorId);
// Aggregate data across all sponsorships
let totalImpressions = 0;
let totalDrivers = 0;
let totalRaces = 0;
let totalInvestment = 0;
const sponsoredLeagues: SponsoredLeagueDTO[] = [];
const seenLeagues = new Set<string>();
for (const sponsorship of sponsorships) {
// Get season to find league
const season = await this.seasonRepository.findById(sponsorship.seasonId);
if (!season) continue;
// Only process each league once
if (seenLeagues.has(season.leagueId)) continue;
seenLeagues.add(season.leagueId);
const league = await this.leagueRepository.findById(season.leagueId);
if (!league) continue;
// Get membership count for this league
const memberships = await this.leagueMembershipRepository.getLeagueMembers(season.leagueId);
const driverCount = memberships.length;
totalDrivers += driverCount;
// Get races for this league
const races = await this.raceRepository.findByLeagueId(season.leagueId);
const raceCount = races.length;
totalRaces += raceCount;
// Calculate impressions based on completed races and drivers
// This is a simplified calculation - in production would come from analytics
const completedRaces = races.filter(r => r.status === 'completed').length;
const leagueImpressions = completedRaces * driverCount * 100; // Simplified: 100 views per driver per race
totalImpressions += leagueImpressions;
// Determine status based on season dates
const now = new Date();
let status: 'active' | 'upcoming' | 'completed' = 'active';
if (season.endDate && season.endDate < now) {
status = 'completed';
} else if (season.startDate && season.startDate > now) {
status = 'upcoming';
const sponsor = await this.sponsorRepository.findById(sponsorId);
if (!sponsor) {
return Result.ok(null);
}
// Add investment
totalInvestment += sponsorship.pricing.amount;
// Get all sponsorships for this sponsor
const sponsorships = await this.seasonSponsorshipRepository.findBySponsorId(sponsorId);
sponsoredLeagues.push({
id: league.id,
name: league.name,
tier: sponsorship.tier,
drivers: driverCount,
races: raceCount,
impressions: leagueImpressions,
status,
});
// Aggregate data across all sponsorships
let totalImpressions = 0;
let totalDrivers = 0;
let totalRaces = 0;
let totalInvestment = 0;
const sponsoredLeagues: SponsoredLeagueDTO[] = [];
const seenLeagues = new Set<string>();
for (const sponsorship of sponsorships) {
// Get season to find league
const season = await this.seasonRepository.findById(sponsorship.seasonId);
if (!season) continue;
// Only process each league once
if (seenLeagues.has(season.leagueId)) continue;
seenLeagues.add(season.leagueId);
const league = await this.leagueRepository.findById(season.leagueId);
if (!league) continue;
// Get membership count for this league
const memberships = await this.leagueMembershipRepository.getLeagueMembers(season.leagueId);
const driverCount = memberships.length;
totalDrivers += driverCount;
// Get races for this league
const races = await this.raceRepository.findByLeagueId(season.leagueId);
const raceCount = races.length;
totalRaces += raceCount;
// Calculate impressions based on completed races and drivers
// This is a simplified calculation - in production would come from analytics
const completedRaces = races.filter(r => r.status === 'completed').length;
const leagueImpressions = completedRaces * driverCount * 100; // Simplified: 100 views per driver per race
totalImpressions += leagueImpressions;
// Determine status based on season dates
const now = new Date();
let status: 'active' | 'upcoming' | 'completed' = 'active';
if (season.endDate && season.endDate < now) {
status = 'completed';
} else if (season.startDate && season.startDate > now) {
status = 'upcoming';
}
// Add investment
totalInvestment += sponsorship.pricing.amount;
sponsoredLeagues.push({
id: league.id,
name: league.name,
tier: sponsorship.tier,
drivers: driverCount,
races: raceCount,
impressions: leagueImpressions,
status,
});
}
const activeSponsorships = sponsorships.filter(s => s.status === 'active').length;
const costPerThousandViews = totalImpressions > 0
? (totalInvestment / (totalImpressions / 1000))
: 0;
// Calculate unique viewers (simplified: assume 70% of impressions are unique)
const uniqueViewers = Math.round(totalImpressions * 0.7);
// Calculate exposure score (0-100 based on tier distribution)
const mainSponsorships = sponsorships.filter(s => s.tier === 'main').length;
const exposure = sponsorships.length > 0
? Math.min(100, (mainSponsorships * 30) + (sponsorships.length * 10))
: 0;
const dto: SponsorDashboardDTO = {
sponsorId,
sponsorName: sponsor.name,
metrics: {
impressions: totalImpressions,
impressionsChange: 0,
uniqueViewers,
viewersChange: 0,
races: totalRaces,
drivers: totalDrivers,
exposure,
exposureChange: 0,
},
sponsoredLeagues,
investment: {
activeSponsorships,
totalInvestment,
costPerThousandViews: Math.round(costPerThousandViews * 100) / 100,
},
};
return Result.ok(dto);
} catch {
return Result.err({ code: 'REPOSITORY_ERROR', message: 'Failed to fetch sponsor dashboard' });
}
const activeSponsorships = sponsorships.filter(s => s.status === 'active').length;
const costPerThousandViews = totalImpressions > 0
? (totalInvestment / (totalImpressions / 1000))
: 0;
// Calculate unique viewers (simplified: assume 70% of impressions are unique)
const uniqueViewers = Math.round(totalImpressions * 0.7);
// Calculate exposure score (0-100 based on tier distribution)
const mainSponsorships = sponsorships.filter(s => s.tier === 'main').length;
const exposure = sponsorships.length > 0
? Math.min(100, (mainSponsorships * 30) + (sponsorships.length * 10))
: 0;
const dto: SponsorDashboardDTO = {
sponsorId,
sponsorName: sponsor.name,
metrics: {
impressions: totalImpressions,
impressionsChange: 12.5, // Would come from analytics comparison
uniqueViewers,
viewersChange: 8.3, // Would come from analytics comparison
races: totalRaces,
drivers: totalDrivers,
exposure,
exposureChange: 5.2, // Would come from analytics comparison
},
sponsoredLeagues,
investment: {
activeSponsorships,
totalInvestment,
costPerThousandViews: Math.round(costPerThousandViews * 100) / 100,
},
};
presenter.present(dto);
}
}