457 lines
18 KiB
TypeScript
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;
|
|
}
|
|
} |