import { Campaign, ChoiceRequest, RequestInventorySource } from "../models"; import { config } from "../utils/config"; import logger from "../utils/logger"; import { KafkaService } from "./kafka.service"; import { EnvironmentType } from "../utils/RequestUtils"; import { CategoryProviderType, ICategoryValue } from "../categories/categoryProvider"; import { Device } from "../models/device.model"; type IPType = "ipv4" | "ipv6" | "truncated" | "none"; export interface Report { advert_id: string; request_id?: string | null; ssp?: string | null; device_name?: string | null; os_version?: string | null; connectivity?: string | null; source_type?: EnvironmentType | null; ip_type?: IPType | null; inventory_source?: RequestInventorySource | null; bidfloor?: number | null; geoip_isp?: string | null; ad_unit_id?: string | null; app_api_key?: string | number | null; app_publisher_id?: number | null; app_bundle_id?: string | null; app_id?: string | null; site_page?: string | null; geoip_code?: string | null; odid?: string | null; timestamp?: number | null; campaigns: CampaignReportData[]; categories?: Categories[] | null; device_categories?: Categories[] | null; } export type Categories = { provider: string; segments: string[] }; export type CampaignReportData = { [key: string]: string | number | boolean | null | undefined }; class ChoicesTopicService { private static instance: ChoicesTopicService; private kafkaConnector: KafkaService; constructor() { this.kafkaConnector = new KafkaService(config.BROKER_LIST_CLUSTER_B, "choices"); } public static getInstance(): ChoicesTopicService { return this.instance || (this.instance = new ChoicesTopicService()); } /** * Builds and sends 'choices' topic message. * @param {ChoiceRequest} request * @return {Promise} */ public async sendReport(request: ChoiceRequest): Promise { const report: Report = { advert_id: request.id, request_id: request.request_id, ssp: request.context?.ssp, device_name: request.device?.name, os_version: request.device?.os_version, connectivity: request.device?.connectivity, source_type: request.environment_type, inventory_source: request.inventory_source, ip_type: this.generateIpType(request.device), bidfloor: request.bidfloor, geoip_isp: request.device?.isp, ad_unit_id: request.app?.ad_unit_id, app_api_key: request.app?.app_api_key, app_publisher_id: request.app?.app_publisher_id, app_bundle_id: request.app?.bundle_id, app_id: request.app?.app_id, site_page: request.site?.page, geoip_code: request.context?.country, odid: request.user?.odid, timestamp: Math.floor(Date.now() / 1000), campaigns: this.getCampaignsData(request), ...request.metadata, }; report.categories = this.generateCategoriesReport(request.categories); report.device_categories = this.generateCategoriesReport(request.deviceCategories); // send message logger.debug(() => ({ message: "Choices report", report, })); this.kafkaConnector.send(report, report.advert_id); } private generateIpType(device: Device | undefined): IPType { if (device?.ip) { if (device.ip.endsWith(".0")) { return "truncated"; } return "ipv4"; } if (device?.ipv6) { return "ipv6"; } return "none"; } private generateCategoriesReport( categories: Map | undefined ): Categories[] | null { if (categories !== undefined) { const result: Categories[] = []; for (const [key, value] of categories) { const cat: Categories = { provider: key, segments: value.toSegmentList(), }; result.push(cat); } return result; } return null; } public getCampaignsData(request: ChoiceRequest): CampaignReportData[] { const excluded = request.excluded ?? []; logger.debug(() => `[END] selected [${request.campaigns.map((c) => c.id).join(",")}]`); logger.debug(() => `[END] excluded [${excluded.map((c) => c.id).join(",")}]`); return [ ...request.campaigns.map((campaign) => this.toCampaignData(campaign)), ...excluded.map((campaign) => this.toCampaignData(campaign)), ]; } private toCampaignData(campaign: Campaign): CampaignReportData { return { ...campaign.metadata, ...campaign.stepsPassedStatus, campaign_id: campaign.id }; } } export default ChoicesTopicService;