Files
gridpilot.gg/apps/api/src/domain/sponsor/SponsorService.ts
2025-12-22 19:17:33 +01:00

457 lines
18 KiB
TypeScript

import { Injectable, Inject } from '@nestjs/common';
import { CreateSponsorInputDTO } from './dtos/CreateSponsorInputDTO';
import { CreateSponsorOutputDTO } from './dtos/CreateSponsorOutputDTO';
import { GetSponsorDashboardQueryParamsDTO } from './dtos/GetSponsorDashboardQueryParamsDTO';
import { SponsorDashboardDTO } from './dtos/SponsorDashboardDTO';
import { GetSponsorSponsorshipsQueryParamsDTO } from './dtos/GetSponsorSponsorshipsQueryParamsDTO';
import { SponsorSponsorshipsDTO } from './dtos/SponsorSponsorshipsDTO';
import { GetSponsorOutputDTO } from './dtos/GetSponsorOutputDTO';
import { GetPendingSponsorshipRequestsOutputDTO } from './dtos/GetPendingSponsorshipRequestsOutputDTO';
import { GetEntitySponsorshipPricingResultDTO } from './dtos/GetEntitySponsorshipPricingResultDTO';
import { GetSponsorsOutputDTO } from './dtos/GetSponsorsOutputDTO';
import { AvailableLeagueDTO } from './dtos/AvailableLeagueDTO';
import { LeagueDetailDTO } from './dtos/LeagueDetailDTO';
import { DriverDTO } from './dtos/DriverDTO';
import { RaceDTO } from './dtos/RaceDTO';
import { SponsorProfileDTO } from './dtos/SponsorProfileDTO';
import { NotificationSettingsDTO } from './dtos/NotificationSettingsDTO';
import { PrivacySettingsDTO } from './dtos/PrivacySettingsDTO';
import { PaymentMethodDTO } from './dtos/PaymentMethodDTO';
import { InvoiceDTO } from './dtos/InvoiceDTO';
import { BillingStatsDTO } from './dtos/BillingStatsDTO';
// Use cases
import { GetSponsorshipPricingUseCase } from '@core/racing/application/use-cases/GetSponsorshipPricingUseCase';
import { GetSponsorsUseCase } from '@core/racing/application/use-cases/GetSponsorsUseCase';
import { CreateSponsorUseCase } from '@core/racing/application/use-cases/CreateSponsorUseCase';
import { GetSponsorDashboardUseCase } from '@core/racing/application/use-cases/GetSponsorDashboardUseCase';
import { GetSponsorSponsorshipsUseCase } from '@core/racing/application/use-cases/GetSponsorSponsorshipsUseCase';
import { GetSponsorUseCase } from '@core/racing/application/use-cases/GetSponsorUseCase';
import {
GetPendingSponsorshipRequestsUseCase,
GetPendingSponsorshipRequestsInput,
} from '@core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase';
import { AcceptSponsorshipRequestUseCase } from '@core/racing/application/use-cases/AcceptSponsorshipRequestUseCase';
import { RejectSponsorshipRequestUseCase } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase';
import { GetSponsorBillingUseCase } from '@core/payments/application/use-cases/GetSponsorBillingUseCase';
import { GET_SPONSOR_BILLING_USE_CASE_TOKEN } from './SponsorProviders';
import type { SponsorableEntityType } from '@core/racing/domain/entities/SponsorshipRequest';
import type { Logger } from '@core/shared/application';
// Presenters
import { GetEntitySponsorshipPricingPresenter } from './presenters/GetEntitySponsorshipPricingPresenter';
import { GetSponsorsPresenter } from './presenters/GetSponsorsPresenter';
import { CreateSponsorPresenter } from './presenters/CreateSponsorPresenter';
import { GetSponsorDashboardPresenter } from './presenters/GetSponsorDashboardPresenter';
import { GetSponsorSponsorshipsPresenter } from './presenters/GetSponsorSponsorshipsPresenter';
import { GetSponsorPresenter } from './presenters/GetSponsorPresenter';
import { GetPendingSponsorshipRequestsPresenter } from './presenters/GetPendingSponsorshipRequestsPresenter';
import { AcceptSponsorshipRequestPresenter } from './presenters/AcceptSponsorshipRequestPresenter';
import { RejectSponsorshipRequestPresenter } from './presenters/RejectSponsorshipRequestPresenter';
import { SponsorBillingPresenter } from './presenters/SponsorBillingPresenter';
import { AvailableLeaguesPresenter } from './presenters/AvailableLeaguesPresenter';
import { LeagueDetailPresenter } from './presenters/LeagueDetailPresenter';
import { SponsorSettingsPresenter } from './presenters/SponsorSettingsPresenter';
import { SponsorSettingsUpdatePresenter } from './presenters/SponsorSettingsUpdatePresenter';
import { AcceptSponsorshipRequestResultViewModel } from './presenters/AcceptSponsorshipRequestPresenter';
import type { RejectSponsorshipRequestResult } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase';
// 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,
GET_SPONSOR_USE_CASE_TOKEN,
GET_PENDING_SPONSORSHIP_REQUESTS_USE_CASE_TOKEN,
ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN,
REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN,
LOGGER_TOKEN,
GET_ENTITY_SPONSORSHIP_PRICING_PRESENTER_TOKEN,
GET_SPONSORS_PRESENTER_TOKEN,
CREATE_SPONSOR_PRESENTER_TOKEN,
GET_SPONSOR_DASHBOARD_PRESENTER_TOKEN,
GET_SPONSOR_SPONSORSHIPS_PRESENTER_TOKEN,
GET_SPONSOR_PRESENTER_TOKEN,
GET_PENDING_SPONSORSHIP_REQUESTS_PRESENTER_TOKEN,
ACCEPT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN,
REJECT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN,
GET_SPONSOR_BILLING_PRESENTER_TOKEN,
} from './SponsorProviders';
@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(GET_SPONSOR_USE_CASE_TOKEN)
private readonly getSponsorUseCase: GetSponsorUseCase,
@Inject(GET_PENDING_SPONSORSHIP_REQUESTS_USE_CASE_TOKEN)
private readonly getPendingSponsorshipRequestsUseCase: GetPendingSponsorshipRequestsUseCase,
@Inject(ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN)
private readonly acceptSponsorshipRequestUseCase: AcceptSponsorshipRequestUseCase,
@Inject(REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN)
private readonly rejectSponsorshipRequestUseCase: RejectSponsorshipRequestUseCase,
@Inject(GET_SPONSOR_BILLING_USE_CASE_TOKEN)
private readonly getSponsorBillingUseCase: GetSponsorBillingUseCase,
@Inject(LOGGER_TOKEN)
private readonly logger: Logger,
// Injected presenters
@Inject(GET_ENTITY_SPONSORSHIP_PRICING_PRESENTER_TOKEN)
private readonly getEntitySponsorshipPricingPresenter: GetEntitySponsorshipPricingPresenter,
@Inject(GET_SPONSORS_PRESENTER_TOKEN)
private readonly getSponsorsPresenter: GetSponsorsPresenter,
@Inject(CREATE_SPONSOR_PRESENTER_TOKEN)
private readonly createSponsorPresenter: CreateSponsorPresenter,
@Inject(GET_SPONSOR_DASHBOARD_PRESENTER_TOKEN)
private readonly getSponsorDashboardPresenter: GetSponsorDashboardPresenter,
@Inject(GET_SPONSOR_SPONSORSHIPS_PRESENTER_TOKEN)
private readonly getSponsorSponsorshipsPresenter: GetSponsorSponsorshipsPresenter,
@Inject(GET_SPONSOR_PRESENTER_TOKEN)
private readonly getSponsorPresenter: GetSponsorPresenter,
@Inject(GET_PENDING_SPONSORSHIP_REQUESTS_PRESENTER_TOKEN)
private readonly getPendingSponsorshipRequestsPresenter: GetPendingSponsorshipRequestsPresenter,
@Inject(ACCEPT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN)
private readonly acceptSponsorshipRequestPresenter: AcceptSponsorshipRequestPresenter,
@Inject(REJECT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN)
private readonly rejectSponsorshipRequestPresenter: RejectSponsorshipRequestPresenter,
@Inject(GET_SPONSOR_BILLING_PRESENTER_TOKEN)
private readonly sponsorBillingPresenter: SponsorBillingPresenter,
) {}
async getEntitySponsorshipPricing(): Promise<GetEntitySponsorshipPricingResultDTO> {
this.logger.debug('[SponsorService] Fetching sponsorship pricing.');
await this.getSponsorshipPricingUseCase.execute({});
return this.getEntitySponsorshipPricingPresenter.viewModel;
}
async getSponsors(): Promise<GetSponsorsOutputDTO> {
this.logger.debug('[SponsorService] Fetching sponsors.');
await this.getSponsorsUseCase.execute();
return this.getSponsorsPresenter.responseModel;
}
async createSponsor(input: CreateSponsorInputDTO): Promise<CreateSponsorOutputDTO> {
this.logger.debug('[SponsorService] Creating sponsor.', { input });
await this.createSponsorUseCase.execute(input);
return this.createSponsorPresenter.viewModel;
}
async getSponsorDashboard(
params: GetSponsorDashboardQueryParamsDTO,
): Promise<SponsorDashboardDTO> {
this.logger.debug('[SponsorService] Fetching sponsor dashboard.', { params });
await this.getSponsorDashboardUseCase.execute(params);
const result = this.getSponsorDashboardPresenter.viewModel;
if (!result) {
throw new Error('Sponsor dashboard not found');
}
return result;
}
async getSponsorSponsorships(
params: GetSponsorSponsorshipsQueryParamsDTO,
): Promise<SponsorSponsorshipsDTO> {
this.logger.debug('[SponsorService] Fetching sponsor sponsorships.', { params });
await this.getSponsorSponsorshipsUseCase.execute(params);
const result = this.getSponsorSponsorshipsPresenter.viewModel;
if (!result) {
throw new Error('Sponsor sponsorships not found');
}
return result;
}
async getSponsor(sponsorId: string): Promise<GetSponsorOutputDTO> {
this.logger.debug('[SponsorService] Fetching sponsor.', { sponsorId });
await this.getSponsorUseCase.execute({ sponsorId });
const result = this.getSponsorPresenter.viewModel;
if (!result) {
throw new Error('Sponsor not found');
}
return result;
}
async getPendingSponsorshipRequests(params: {
entityType: SponsorableEntityType;
entityId: string;
}): Promise<GetPendingSponsorshipRequestsOutputDTO> {
this.logger.debug('[SponsorService] Fetching pending sponsorship requests.', { params });
await this.getPendingSponsorshipRequestsUseCase.execute(
params as GetPendingSponsorshipRequestsInput,
);
const result = this.getPendingSponsorshipRequestsPresenter.viewModel;
if (!result) {
throw new Error('Pending sponsorship requests not found');
}
return result;
}
async acceptSponsorshipRequest(
requestId: string,
respondedBy: string,
): Promise<AcceptSponsorshipRequestResultViewModel> {
this.logger.debug('[SponsorService] Accepting sponsorship request.', {
requestId,
respondedBy,
});
await this.acceptSponsorshipRequestUseCase.execute({
requestId,
respondedBy,
});
const result = this.acceptSponsorshipRequestPresenter.viewModel;
if (!result) {
throw new Error('Accept sponsorship request failed');
}
return result;
}
async rejectSponsorshipRequest(
requestId: string,
respondedBy: string,
reason?: string,
): Promise<RejectSponsorshipRequestResult> {
this.logger.debug('[SponsorService] Rejecting sponsorship request.', {
requestId,
respondedBy,
reason,
});
const input: { requestId: string; respondedBy: string; reason?: string } = {
requestId,
respondedBy,
};
if (reason !== undefined) {
input.reason = reason;
}
await this.rejectSponsorshipRequestUseCase.execute(input);
const result = this.rejectSponsorshipRequestPresenter.viewModel;
if (!result) {
throw new Error('Reject sponsorship request failed');
}
return result;
}
async getSponsorBilling(sponsorId: string): Promise<{
paymentMethods: PaymentMethodDTO[];
invoices: InvoiceDTO[];
stats: BillingStatsDTO;
}> {
this.logger.debug('[SponsorService] Fetching sponsor billing.', { sponsorId });
await this.getSponsorBillingUseCase.execute({ sponsorId });
const result = this.sponsorBillingPresenter.viewModel;
if (!result) {
throw new Error('Sponsor billing not found');
}
return result;
}
async getAvailableLeagues(): Promise<AvailableLeaguesPresenter> {
this.logger.debug('[SponsorService] Fetching available leagues.');
const presenter = new AvailableLeaguesPresenter();
const leagues: AvailableLeagueDTO[] = [
{
id: 'league-1',
name: 'GT3 Masters Championship',
game: 'iRacing',
drivers: 48,
avgViewsPerRace: 8200,
mainSponsorSlot: { available: true, price: 1200 },
secondarySlots: { available: 1, total: 2, price: 400 },
rating: 4.8,
tier: 'premium',
nextRace: '2025-12-20',
seasonStatus: 'active',
description:
'Premier GT3 racing with top-tier drivers. Weekly broadcasts and active community.',
},
{
id: 'league-2',
name: 'Endurance Pro Series',
game: 'ACC',
drivers: 72,
avgViewsPerRace: 12500,
mainSponsorSlot: { available: false, price: 1500 },
secondarySlots: { available: 2, total: 2, price: 500 },
rating: 4.9,
tier: 'premium',
nextRace: '2026-01-05',
seasonStatus: 'active',
description:
'Multi-class endurance racing. High engagement from dedicated endurance fans.',
},
];
presenter.present(leagues);
return presenter;
}
async getLeagueDetail(leagueId: string): Promise<LeagueDetailPresenter> {
this.logger.debug('[SponsorService] Fetching league detail.', { leagueId });
const presenter = new LeagueDetailPresenter();
// Mock data
const league: LeagueDetailDTO = {
id: leagueId,
name: 'GT3 Masters Championship',
game: 'iRacing',
tier: 'premium',
season: 'Season 3',
description:
"Premier GT3 racing with top-tier drivers competing across the world's most iconic circuits.",
drivers: 48,
races: 12,
completedRaces: 8,
totalImpressions: 45200,
avgViewsPerRace: 5650,
engagement: 4.2,
rating: 4.8,
seasonStatus: 'active',
seasonDates: { start: '2025-10-01', end: '2026-02-28' },
nextRace: { name: 'Spa-Francorchamps', date: '2025-12-20' },
sponsorSlots: {
main: {
available: true,
price: 1200,
benefits: [
'Primary logo placement on all liveries',
'League page header banner',
'Race results page branding',
'Social media feature posts',
'Newsletter sponsor spot',
],
},
secondary: {
available: 1,
total: 2,
price: 400,
benefits: [
'Secondary logo on liveries',
'League page sidebar placement',
'Race results mention',
'Social media mentions',
],
},
},
};
const drivers: DriverDTO[] = [
{
id: 'd1',
name: 'Max Verstappen',
country: 'NL',
position: 1,
races: 8,
impressions: 4200,
team: 'Red Bull Racing',
},
{
id: 'd2',
name: 'Lewis Hamilton',
country: 'GB',
position: 2,
races: 8,
impressions: 3980,
team: 'Mercedes AMG',
},
];
const races: RaceDTO[] = [
{
id: 'r1',
name: 'Spa-Francorchamps',
date: '2025-12-20',
views: 0,
status: 'upcoming',
},
{
id: 'r2',
name: 'Monza',
date: '2025-12-08',
views: 5800,
status: 'completed',
},
];
presenter.present({ league, drivers, races });
return presenter;
}
async getSponsorSettings(sponsorId: string): Promise<SponsorSettingsPresenter> {
this.logger.debug('[SponsorService] Fetching sponsor settings.', { sponsorId });
const presenter = new SponsorSettingsPresenter();
// Mock data
const profile: SponsorProfileDTO = {
companyName: 'Acme Racing Co.',
contactName: 'John Smith',
contactEmail: 'sponsor@acme-racing.com',
contactPhone: '+1 (555) 123-4567',
website: 'https://acme-racing.com',
description:
'Premium sim racing equipment and accessories for competitive drivers.',
logoUrl: '',
industry: 'Racing Equipment',
address: {
street: '123 Racing Boulevard',
city: 'Indianapolis',
country: 'United States',
postalCode: '46222',
},
taxId: 'US12-3456789',
socialLinks: {
twitter: '@acmeracing',
linkedin: 'acme-racing-co',
instagram: '@acmeracing',
},
};
const notifications: NotificationSettingsDTO = {
emailNewSponsorships: true,
emailWeeklyReport: true,
emailRaceAlerts: false,
emailPaymentAlerts: true,
emailNewOpportunities: true,
emailContractExpiry: true,
};
const privacy: PrivacySettingsDTO = {
publicProfile: true,
showStats: false,
showActiveSponsorships: true,
allowDirectContact: true,
};
presenter.present({ profile, notifications, privacy });
return presenter;
}
async updateSponsorSettings(
sponsorId: string,
input: {
profile?: Partial<SponsorProfileDTO>;
notifications?: Partial<NotificationSettingsDTO>;
privacy?: Partial<PrivacySettingsDTO>;
},
): Promise<SponsorSettingsUpdatePresenter> {
this.logger.debug('[SponsorService] Updating sponsor settings.', { sponsorId, input });
// Mock implementation - in real app, this would persist to database
this.logger.info('[SponsorService] Settings updated successfully.', { sponsorId });
const presenter = new SponsorSettingsUpdatePresenter();
presenter.present({ success: true });
return presenter;
}
}