This commit is contained in:
2025-12-16 10:50:15 +01:00
parent 775d41e055
commit 8ed6ba1fd1
144 changed files with 5763 additions and 1985 deletions

View File

@@ -1,5 +1,134 @@
import { Provider } from '@nestjs/common';
import { SponsorService } from './SponsorService';
export const SponsorProviders = [
// Import core interfaces
import { ISponsorRepository } from '@gridpilot/racing/domain/repositories/ISponsorRepository';
import { ISeasonSponsorshipRepository } from '@gridpilot/racing/domain/repositories/ISeasonSponsorshipRepository';
import { ISeasonRepository } from '@gridpilot/racing/domain/repositories/ISeasonRepository';
import { ILeagueRepository } from '@gridpilot/racing/domain/repositories/ILeagueRepository';
import { ILeagueMembershipRepository } from '@gridpilot/racing/domain/repositories/ILeagueMembershipRepository';
import { IRaceRepository } from '@gridpilot/racing/domain/repositories/IRaceRepository';
import { ISponsorshipPricingRepository } from '@gridpilot/racing/domain/repositories/ISponsorshipPricingRepository';
import { ISponsorshipRequestRepository } from '@gridpilot/racing/domain/repositories/ISponsorshipRequestRepository';
import { Logger } from '@gridpilot/shared/application';
// Import use cases
import { GetSponsorshipPricingUseCase } from '@gridpilot/racing/application/use-cases/GetSponsorshipPricingUseCase';
import { GetSponsorsUseCase } from '@gridpilot/racing/application/use-cases/GetSponsorsUseCase';
import { CreateSponsorUseCase } from '@gridpilot/racing/application/use-cases/CreateSponsorUseCase';
import { GetSponsorDashboardUseCase } from '@gridpilot/racing/application/use-cases/GetSponsorDashboardUseCase';
import { GetSponsorSponsorshipsUseCase } from '@gridpilot/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
import { GetEntitySponsorshipPricingUseCase } from '@gridpilot/racing/application/use-cases/GetEntitySponsorshipPricingUseCase';
// Import concrete in-memory implementations
import { InMemorySponsorRepository } from 'adapters/racing/persistence/inmemory/InMemorySponsorRepository';
import { InMemorySeasonSponsorshipRepository } from 'adapters/racing/persistence/inmemory/InMemorySeasonSponsorshipRepository';
import { InMemorySeasonRepository } from 'adapters/racing/persistence/inmemory/InMemorySeasonRepository';
import { InMemoryLeagueRepository } from 'adapters/racing/persistence/inmemory/InMemoryLeagueRepository';
import { InMemoryLeagueMembershipRepository } from 'adapters/racing/persistence/inmemory/InMemoryLeagueMembershipRepository';
import { InMemoryRaceRepository } from 'adapters/racing/persistence/inmemory/InMemoryRaceRepository';
import { InMemorySponsorshipPricingRepository } from 'adapters/racing/persistence/inmemory/InMemorySponsorshipPricingRepository';
import { InMemorySponsorshipRequestRepository } from 'adapters/racing/persistence/inmemory/InMemorySponsorshipRequestRepository';
import { ConsoleLogger } from 'adapters/logging/ConsoleLogger';
// Define injection tokens
export const SPONSOR_REPOSITORY_TOKEN = 'ISponsorRepository';
export const SEASON_SPONSORSHIP_REPOSITORY_TOKEN = 'ISeasonSponsorshipRepository';
export const SEASON_REPOSITORY_TOKEN = 'ISeasonRepository';
export const LEAGUE_REPOSITORY_TOKEN = 'ILeagueRepository';
export const LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN = 'ILeagueMembershipRepository';
export const RACE_REPOSITORY_TOKEN = 'IRaceRepository';
export const SPONSORSHIP_PRICING_REPOSITORY_TOKEN = 'ISponsorshipPricingRepository';
export const SPONSORSHIP_REQUEST_REPOSITORY_TOKEN = 'ISponsorshipRequestRepository';
export const LOGGER_TOKEN = 'Logger';
// Use case tokens
export const GET_SPONSORSHIP_PRICING_USE_CASE_TOKEN = 'GetSponsorshipPricingUseCase';
export const GET_SPONSORS_USE_CASE_TOKEN = 'GetSponsorsUseCase';
export const CREATE_SPONSOR_USE_CASE_TOKEN = 'CreateSponsorUseCase';
export const GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN = 'GetSponsorDashboardUseCase';
export const GET_SPONSOR_SPONSORSHIPS_USE_CASE_TOKEN = 'GetSponsorSponsorshipsUseCase';
export const GET_ENTITY_SPONSORSHIP_PRICING_USE_CASE_TOKEN = 'GetEntitySponsorshipPricingUseCase';
export const SponsorProviders: Provider[] = [
SponsorService,
// Repositories
{
provide: SPONSOR_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemorySponsorRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: SEASON_SPONSORSHIP_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemorySeasonSponsorshipRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: SEASON_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemorySeasonRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: LEAGUE_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryLeagueRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryLeagueMembershipRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: RACE_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryRaceRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: SPONSORSHIP_PRICING_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemorySponsorshipPricingRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: SPONSORSHIP_REQUEST_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemorySponsorshipRequestRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: LOGGER_TOKEN,
useClass: ConsoleLogger,
},
// Use cases
{
provide: GET_SPONSORSHIP_PRICING_USE_CASE_TOKEN,
useFactory: () => new GetSponsorshipPricingUseCase(),
inject: [],
},
{
provide: GET_SPONSORS_USE_CASE_TOKEN,
useFactory: (sponsorRepo: ISponsorRepository) => new GetSponsorsUseCase(sponsorRepo),
inject: [SPONSOR_REPOSITORY_TOKEN],
},
{
provide: CREATE_SPONSOR_USE_CASE_TOKEN,
useFactory: (sponsorRepo: ISponsorRepository) => new CreateSponsorUseCase(sponsorRepo),
inject: [SPONSOR_REPOSITORY_TOKEN],
},
{
provide: GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN,
useFactory: (sponsorRepo: ISponsorRepository, seasonSponsorshipRepo: ISeasonSponsorshipRepository, seasonRepo: ISeasonRepository, leagueRepo: ILeagueRepository, leagueMembershipRepo: ILeagueMembershipRepository, raceRepo: IRaceRepository) =>
new GetSponsorDashboardUseCase(sponsorRepo, seasonSponsorshipRepo, seasonRepo, leagueRepo, leagueMembershipRepo, raceRepo),
inject: [SPONSOR_REPOSITORY_TOKEN, SEASON_SPONSORSHIP_REPOSITORY_TOKEN, SEASON_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN],
},
{
provide: GET_SPONSOR_SPONSORSHIPS_USE_CASE_TOKEN,
useFactory: (sponsorRepo: ISponsorRepository, seasonSponsorshipRepo: ISeasonSponsorshipRepository, seasonRepo: ISeasonRepository, raceRepo: IRaceRepository) =>
new GetSponsorSponsorshipsUseCase(sponsorRepo, seasonSponsorshipRepo, seasonRepo, raceRepo),
inject: [SPONSOR_REPOSITORY_TOKEN, SEASON_SPONSORSHIP_REPOSITORY_TOKEN, SEASON_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN],
},
{
provide: GET_ENTITY_SPONSORSHIP_PRICING_USE_CASE_TOKEN,
useFactory: (sponsorshipPricingRepo: ISponsorshipPricingRepository, sponsorshipRequestRepo: ISponsorshipRequestRepository, seasonSponsorshipRepo: ISeasonSponsorshipRepository, logger: Logger) =>
new GetEntitySponsorshipPricingUseCase(sponsorshipPricingRepo, sponsorshipRequestRepo, seasonSponsorshipRepo, logger),
inject: [SPONSORSHIP_PRICING_REPOSITORY_TOKEN, SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, SEASON_SPONSORSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
];

View File

@@ -1,162 +1,72 @@
import { Injectable } from '@nestjs/common';
import { GetEntitySponsorshipPricingResultDto, SponsorDto, GetSponsorsOutput, CreateSponsorInput, CreateSponsorOutput, GetSponsorDashboardQueryParams, SponsorDashboardDTO, GetSponsorSponsorshipsQueryParams, SponsorshipDetailDTO, SponsorSponsorshipsDTO, SponsoredLeagueDTO, SponsorDashboardMetricsDTO, SponsorDashboardInvestmentDTO } from './dto/SponsorDto';
import { Injectable, Inject } from '@nestjs/common';
import { GetEntitySponsorshipPricingResultDto, GetSponsorsOutput, CreateSponsorInput, CreateSponsorOutput, GetSponsorDashboardQueryParams, SponsorDashboardDTO, GetSponsorSponsorshipsQueryParams, SponsorSponsorshipsDTO, SponsorDto, SponsorDashboardMetricsDTO, SponsoredLeagueDTO, SponsorDashboardInvestmentDTO, SponsorshipDetailDTO } from './dto/SponsorDto';
const sponsors: Map<string, SponsorDto> = new Map();
// Use cases
import { GetSponsorshipPricingUseCase } from '@gridpilot/racing/application/use-cases/GetSponsorshipPricingUseCase';
import { GetSponsorsUseCase } from '@gridpilot/racing/application/use-cases/GetSponsorsUseCase';
import { CreateSponsorUseCase } from '@gridpilot/racing/application/use-cases/CreateSponsorUseCase';
import { GetSponsorDashboardUseCase } from '@gridpilot/racing/application/use-cases/GetSponsorDashboardUseCase';
import { GetSponsorSponsorshipsUseCase } from '@gridpilot/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
// Presenters
import { GetSponsorshipPricingPresenter } from './presenters/GetSponsorshipPricingPresenter';
import { GetSponsorsPresenter } from './presenters/GetSponsorsPresenter';
import { CreateSponsorPresenter } from './presenters/CreateSponsorPresenter';
import { GetSponsorDashboardPresenter } from './presenters/GetSponsorDashboardPresenter';
import { GetSponsorSponsorshipsPresenter } from './presenters/GetSponsorSponsorshipsPresenter';
// Tokens
import { GET_SPONSORSHIP_PRICING_USE_CASE_TOKEN, GET_SPONSORS_USE_CASE_TOKEN, CREATE_SPONSOR_USE_CASE_TOKEN, GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN, GET_SPONSOR_SPONSORSHIPS_USE_CASE_TOKEN, LOGGER_TOKEN } from './SponsorProviders';
import { Logger } from '@gridpilot/shared/application';
@Injectable()
export class SponsorService {
constructor(
@Inject(GET_SPONSORSHIP_PRICING_USE_CASE_TOKEN) private readonly getSponsorshipPricingUseCase: GetSponsorshipPricingUseCase,
@Inject(GET_SPONSORS_USE_CASE_TOKEN) private readonly getSponsorsUseCase: GetSponsorsUseCase,
@Inject(CREATE_SPONSOR_USE_CASE_TOKEN) private readonly createSponsorUseCase: CreateSponsorUseCase,
@Inject(GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN) private readonly getSponsorDashboardUseCase: GetSponsorDashboardUseCase,
@Inject(GET_SPONSOR_SPONSORSHIPS_USE_CASE_TOKEN) private readonly getSponsorSponsorshipsUseCase: GetSponsorSponsorshipsUseCase,
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
) {}
constructor() {
// Seed some demo sponsors for dashboard if empty
if (sponsors.size === 0) {
const demoSponsor1: SponsorDto = {
id: 'sponsor-demo-1',
name: 'Demo Sponsor Co.',
contactEmail: 'contact@demosponsor.com',
websiteUrl: 'https://demosponsor.com',
logoUrl: 'https://fakeimg.pl/200x100/aaaaaa/ffffff?text=DemoCo',
createdAt: new Date(),
};
const demoSponsor2: SponsorDto = {
id: 'sponsor-demo-2',
name: 'Second Brand',
contactEmail: 'info@secondbrand.net',
websiteUrl: 'https://secondbrand.net',
logoUrl: 'https://fakeimg.pl/200x100/cccccc/ffffff?text=Brand2',
createdAt: new Date(Date.now() - 86400000 * 5),
};
sponsors.set(demoSponsor1.id, demoSponsor1);
sponsors.set(demoSponsor2.id, demoSponsor2);
}
}
async getEntitySponsorshipPricing(): Promise<GetEntitySponsorshipPricingResultDto> {
this.logger.debug('[SponsorService] Fetching sponsorship pricing.');
getEntitySponsorshipPricing(): Promise<GetEntitySponsorshipPricingResultDto> {
// This logic relies on external factors (e.g., pricing configuration, entity type)
// For now, return mock data
return Promise.resolve({
pricing: [
{ id: 'tier-bronze', level: 'Bronze', price: 100, currency: 'USD' },
{ id: 'tier-silver', level: 'Silver', price: 250, currency: 'USD' },
{ id: 'tier-gold', level: 'Gold', price: 500, currency: 'USD' },
],
});
const presenter = new GetSponsorshipPricingPresenter();
await this.getSponsorshipPricingUseCase.execute(undefined, presenter);
return presenter.viewModel;
}
async getSponsors(): Promise<GetSponsorsOutput> {
return { sponsors: Array.from(sponsors.values()) };
this.logger.debug('[SponsorService] Fetching sponsors.');
const presenter = new GetSponsorsPresenter();
await this.getSponsorsUseCase.execute(undefined, presenter);
return presenter.viewModel;
}
async createSponsor(input: CreateSponsorInput): Promise<CreateSponsorOutput> {
const id = `sponsor-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const newSponsor: SponsorDto = {
id,
name: input.name,
contactEmail: input.contactEmail,
websiteUrl: input.websiteUrl,
logoUrl: input.logoUrl,
createdAt: new Date(),
};
sponsors.set(id, newSponsor);
return { sponsor: newSponsor };
this.logger.debug('[SponsorService] Creating sponsor.', { input });
const presenter = new CreateSponsorPresenter();
await this.createSponsorUseCase.execute(input, presenter);
return presenter.viewModel;
}
async getSponsorDashboard(params: GetSponsorDashboardQueryParams): Promise<SponsorDashboardDTO | null> {
const { sponsorId } = params;
this.logger.debug('[SponsorService] Fetching sponsor dashboard.', { params });
const sponsor = sponsors.get(sponsorId);
if (!sponsor) {
return null;
}
// Simplified mock data for dashboard metrics and sponsored leagues
const metrics: SponsorDashboardMetricsDTO = {
impressions: 10000,
impressionsChange: 12.5,
uniqueViewers: 7000,
viewersChange: 8.3,
races: 50,
drivers: 100,
exposure: 75,
exposureChange: 5.2,
};
const sponsoredLeagues: SponsoredLeagueDTO[] = [
{ id: 'league-1', name: 'League 1', tier: 'main', drivers: 50, races: 10, impressions: 5000, status: 'active' },
{ id: 'league-2', name: 'League 2', tier: 'secondary', drivers: 30, races: 5, impressions: 1500, status: 'upcoming' },
];
const investment: SponsorDashboardInvestmentDTO = {
activeSponsorships: 2,
totalInvestment: 5000,
costPerThousandViews: 0.5,
};
return {
sponsorId,
sponsorName: sponsor.name,
metrics,
sponsoredLeagues,
investment,
};
const presenter = new GetSponsorDashboardPresenter();
await this.getSponsorDashboardUseCase.execute(params, presenter);
return presenter.viewModel as SponsorDashboardDTO | null;
}
async getSponsorSponsorships(params: GetSponsorSponsorshipsQueryParams): Promise<SponsorSponsorshipsDTO | null> {
const { sponsorId } = params;
this.logger.debug('[SponsorService] Fetching sponsor sponsorships.', { params });
const sponsor = sponsors.get(sponsorId);
if (!sponsor) {
return null;
};
const sponsorshipDetails: SponsorshipDetailDTO[] = [
{
id: 'sponsorship-1',
leagueId: 'league-1',
leagueName: 'League 1',
seasonId: 'season-1',
seasonName: 'Season 1',
seasonStartDate: new Date('2025-01-01'),
seasonEndDate: new Date('2025-12-31'),
tier: 'main',
status: 'active',
pricing: { amount: 1000, currency: 'USD' },
platformFee: { amount: 100, currency: 'USD' },
netAmount: { amount: 900, currency: 'USD' },
metrics: { drivers: 50, races: 10, completedRaces: 8, impressions: 5000 },
createdAt: new Date('2024-12-01'),
activatedAt: new Date('2025-01-01'),
},
{
id: 'sponsorship-2',
leagueId: 'league-2',
leagueName: 'League 2',
seasonId: 'season-2',
seasonName: 'Season 2',
tier: 'secondary',
status: 'pending',
pricing: { amount: 500, currency: 'USD' },
platformFee: { amount: 50, currency: 'USD' },
netAmount: { amount: 450, currency: 'USD' },
metrics: { drivers: 30, races: 5, completedRaces: 0, impressions: 0 },
createdAt: new Date('2025-03-15'),
},
];
const totalInvestment = sponsorshipDetails.reduce((sum, s) => sum + s.pricing.amount, 0);
const totalPlatformFees = sponsorshipDetails.reduce((sum, s) => sum + s.platformFee.amount, 0);
const activeSponsorships = sponsorshipDetails.filter(s => s.status === 'active').length;
return {
sponsorId,
sponsorName: sponsor.name,
sponsorships: sponsorshipDetails,
summary: {
totalSponsorships: sponsorshipDetails.length,
activeSponsorships,
totalInvestment,
totalPlatformFees,
currency: 'USD',
},
};
const presenter = new GetSponsorSponsorshipsPresenter();
await this.getSponsorSponsorshipsUseCase.execute(params, presenter);
return presenter.viewModel as SponsorSponsorshipsDTO | null;
}
}

View File

@@ -0,0 +1,22 @@
import { CreateSponsorViewModel, CreateSponsorResultDTO, ICreateSponsorPresenter } from '@gridpilot/racing/application/presenters/ICreateSponsorPresenter';
export class CreateSponsorPresenter implements ICreateSponsorPresenter {
private result: CreateSponsorViewModel | null = null;
reset() {
this.result = null;
}
present(dto: CreateSponsorResultDTO) {
this.result = dto;
}
getViewModel(): CreateSponsorViewModel | null {
return this.result;
}
get viewModel(): CreateSponsorViewModel {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}
}

View File

@@ -0,0 +1,22 @@
import type { GetEntitySponsorshipPricingResultDTO } from '@gridpilot/racing/application/use-cases/GetEntitySponsorshipPricingUseCase';
import type { IEntitySponsorshipPricingPresenter } from '@gridpilot/racing/application/presenters/IEntitySponsorshipPricingPresenter';
export class GetEntitySponsorshipPricingPresenter implements IEntitySponsorshipPricingPresenter {
private result: GetEntitySponsorshipPricingResultDTO | null = null;
reset() {
this.result = null;
}
present(dto: GetEntitySponsorshipPricingResultDTO | null) {
this.result = dto;
}
getViewModel(): GetEntitySponsorshipPricingResultDTO | null {
return this.result;
}
get viewModel(): GetEntitySponsorshipPricingResultDTO | null {
return this.result;
}
}

View File

@@ -0,0 +1,22 @@
import type { SponsorDashboardDTO } from '@gridpilot/racing/application/use-cases/GetSponsorDashboardUseCase';
import type { ISponsorDashboardPresenter, SponsorDashboardViewModel } from '@gridpilot/racing/application/presenters/ISponsorDashboardPresenter';
export class GetSponsorDashboardPresenter implements ISponsorDashboardPresenter {
private result: SponsorDashboardViewModel | null = null;
reset() {
this.result = null;
}
present(dto: SponsorDashboardDTO | null) {
this.result = dto;
}
getViewModel(): SponsorDashboardViewModel | null {
return this.result;
}
get viewModel(): SponsorDashboardViewModel | null {
return this.result;
}
}

View File

@@ -0,0 +1,22 @@
import type { SponsorSponsorshipsDTO } from '@gridpilot/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
import type { ISponsorSponsorshipsPresenter, SponsorSponsorshipsViewModel } from '@gridpilot/racing/application/presenters/ISponsorSponsorshipsPresenter';
export class GetSponsorSponsorshipsPresenter implements ISponsorSponsorshipsPresenter {
private result: SponsorSponsorshipsViewModel | null = null;
reset() {
this.result = null;
}
present(dto: SponsorSponsorshipsDTO | null) {
this.result = dto;
}
getViewModel(): SponsorSponsorshipsViewModel | null {
return this.result;
}
get viewModel(): SponsorSponsorshipsViewModel | null {
return this.result;
}
}

View File

@@ -0,0 +1,22 @@
import { GetSponsorsViewModel, GetSponsorsResultDTO, IGetSponsorsPresenter } from '@gridpilot/racing/application/presenters/IGetSponsorsPresenter';
export class GetSponsorsPresenter implements IGetSponsorsPresenter {
private result: GetSponsorsViewModel | null = null;
reset() {
this.result = null;
}
present(dto: GetSponsorsResultDTO) {
this.result = dto;
}
getViewModel(): GetSponsorsViewModel | null {
return this.result;
}
get viewModel(): GetSponsorsViewModel {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}
}

View File

@@ -0,0 +1,22 @@
import { GetSponsorshipPricingViewModel, GetSponsorshipPricingResultDTO, IGetSponsorshipPricingPresenter } from '@gridpilot/racing/application/presenters/IGetSponsorshipPricingPresenter';
export class GetSponsorshipPricingPresenter implements IGetSponsorshipPricingPresenter {
private result: GetSponsorshipPricingViewModel | null = null;
reset() {
this.result = null;
}
present(dto: GetSponsorshipPricingResultDTO) {
this.result = dto;
}
getViewModel(): GetSponsorshipPricingViewModel | null {
return this.result;
}
get viewModel(): GetSponsorshipPricingViewModel {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}
}