2024-06-12 02:23:46 +00:00
|
|
|
import crypto from 'crypto';
|
|
|
|
|
2024-06-12 00:34:13 +00:00
|
|
|
import { knexOwner as knex } from './knex.js';
|
|
|
|
import { curateEntity } from './entities.js';
|
2024-06-12 02:23:46 +00:00
|
|
|
import redis from './redis.js';
|
|
|
|
import initLogger from './logger.js';
|
|
|
|
|
|
|
|
const logger = initLogger();
|
2024-06-12 00:34:13 +00:00
|
|
|
|
|
|
|
function curateCampaign(campaign) {
|
|
|
|
if (!campaign) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
id: campaign.id,
|
|
|
|
url: campaign.url,
|
2024-06-12 15:09:53 +00:00
|
|
|
entity: campaign.entity && curateEntity({ ...campaign.entity, parent: campaign.parent_entity }),
|
2024-06-12 00:34:13 +00:00
|
|
|
banner: campaign.banner && {
|
|
|
|
id: campaign.banner.id,
|
|
|
|
type: campaign.banner.type,
|
2024-06-12 02:23:46 +00:00
|
|
|
width: campaign.banner.width,
|
|
|
|
height: campaign.banner.height,
|
|
|
|
ratio: campaign.banner.ratio,
|
2024-06-12 00:34:13 +00:00
|
|
|
entity: campaign.banner_entity && curateEntity({ ...campaign.banner_entity, parent: campaign.banner_parent_entity }),
|
2024-06-13 00:26:34 +00:00
|
|
|
tags: campaign.banner_tags || [],
|
2024-06-12 00:34:13 +00:00
|
|
|
},
|
2024-06-12 02:23:46 +00:00
|
|
|
affiliate: campaign.affiliate && {
|
|
|
|
id: campaign.affiliate.id,
|
|
|
|
url: campaign.affiliate.url,
|
|
|
|
parameters: campaign.affiliate.parameters,
|
|
|
|
},
|
2024-06-12 00:34:13 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-06-13 00:26:34 +00:00
|
|
|
export async function getRandomCampaign(options = {}, context = {}) {
|
2024-06-12 15:09:53 +00:00
|
|
|
const campaigns = options.campaigns
|
|
|
|
|| await redis.hGetAll('traxxx:campaigns').then((rawCampaigns) => Object.values(rawCampaigns).map((rawCampaign) => JSON.parse(rawCampaign)));
|
2024-06-12 02:23:46 +00:00
|
|
|
|
|
|
|
const validCampaigns = campaigns.filter((campaign) => {
|
|
|
|
if (options.minRatio && (!campaign.banner || campaign.banner.ratio < options.minRatio)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options.maxRatio && (!campaign.banner || campaign.banner.ratio > options.maxRatio)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-06-12 15:09:53 +00:00
|
|
|
if (options.entityIds && !options.entityIds.some((entityId) => campaign.entity.id === entityId || campaign.entity.parent?.id === entityId)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-06-13 00:26:34 +00:00
|
|
|
if (context.tagFilter && campaign.banner && campaign.banner.tags.some((tag) => context.tagFilter.includes(tag))) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-06-12 02:23:46 +00:00
|
|
|
return true;
|
2024-06-12 00:34:13 +00:00
|
|
|
});
|
|
|
|
|
2024-06-13 01:10:11 +00:00
|
|
|
const campaignsByEntityId = validCampaigns.reduce((acc, campaign) => {
|
|
|
|
const entityId = campaign.entity.parent?.id || campaign.entity.id;
|
|
|
|
|
|
|
|
if (!acc[entityId]) {
|
|
|
|
acc[entityId] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
acc[entityId].push(campaign);
|
|
|
|
|
|
|
|
return acc;
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
// randomize entities first to ensure fair exposure for entities with fewer banners
|
|
|
|
const entityIds = Object.keys(campaignsByEntityId);
|
|
|
|
const randomEntityCampaigns = campaignsByEntityId[entityIds[crypto.randomInt(entityIds.length)]];
|
|
|
|
|
|
|
|
const primaryCampaigns = randomEntityCampaigns.filter((campaign) => campaign.entity.id === options.entityIds?.[0]);
|
2024-06-12 15:09:53 +00:00
|
|
|
|
|
|
|
if (validCampaigns.length > 0) {
|
2024-06-13 01:10:11 +00:00
|
|
|
const randomCampaign = (primaryCampaigns.length > 0 ? primaryCampaigns[crypto.randomInt(primaryCampaigns.length)] : null)
|
|
|
|
|| (randomEntityCampaigns.length > 0 ? randomEntityCampaigns[crypto.randomInt(randomEntityCampaigns.length)] : null)
|
|
|
|
|| validCampaigns[crypto.randomInt(validCampaigns.length)];
|
2024-06-12 15:09:53 +00:00
|
|
|
|
|
|
|
return randomCampaign;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2024-06-13 00:26:34 +00:00
|
|
|
export async function getRandomCampaigns(allOptions = [], context = {}) {
|
2024-06-12 15:09:53 +00:00
|
|
|
const rawCampaigns = await redis.hGetAll('traxxx:campaigns');
|
|
|
|
const campaigns = Object.values(rawCampaigns).map((rawCampaign) => JSON.parse(rawCampaign));
|
2024-06-12 02:23:46 +00:00
|
|
|
|
2024-06-12 15:09:53 +00:00
|
|
|
return Promise.all(allOptions.map(async (options) => getRandomCampaign({
|
|
|
|
...options,
|
|
|
|
campaigns,
|
2024-06-13 00:26:34 +00:00
|
|
|
}, context)));
|
2024-06-12 02:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export async function cacheCampaigns() {
|
|
|
|
const campaigns = await knex('campaigns')
|
|
|
|
.select('campaigns.*')
|
|
|
|
.select(
|
|
|
|
'campaigns.*',
|
|
|
|
knex.raw('row_to_json(affiliates) as affiliate'),
|
|
|
|
knex.raw('row_to_json(banners) as banner'),
|
2024-06-12 15:09:53 +00:00
|
|
|
knex.raw('row_to_json(entities) as entity'),
|
|
|
|
knex.raw('row_to_json(parents) as parent_entity'),
|
|
|
|
knex.raw('row_to_json(banner_entities) as banner_entity'),
|
|
|
|
knex.raw('row_to_json(banner_parents) as banner_parent_entity'),
|
2024-06-12 02:23:46 +00:00
|
|
|
knex.raw('json_agg(tags.slug) filter (where tags.id is not null) as banner_tags'),
|
|
|
|
)
|
|
|
|
.leftJoin('affiliates', 'affiliates.id', 'campaigns.affiliate_id')
|
|
|
|
.leftJoin('banners', 'banners.id', 'campaigns.banner_id')
|
|
|
|
.leftJoin('banners_tags', 'banners_tags.banner_id', 'banners.id')
|
|
|
|
.leftJoin('tags', 'tags.id', 'banners_tags.tag_id')
|
2024-06-12 15:09:53 +00:00
|
|
|
.leftJoin('entities', 'entities.id', 'campaigns.entity_id')
|
2024-06-12 02:23:46 +00:00
|
|
|
.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
|
2024-06-12 15:09:53 +00:00
|
|
|
.leftJoin('entities as banner_entities', 'banner_entities.id', 'banners.entity_id')
|
|
|
|
.leftJoin('entities as banner_parents', 'banner_parents.id', 'banner_entities.parent_id')
|
|
|
|
.groupBy(
|
|
|
|
'campaigns.id',
|
|
|
|
'affiliates.id',
|
|
|
|
'entities.id',
|
|
|
|
'parents.id',
|
|
|
|
'banners.id',
|
|
|
|
'banner_entities.id',
|
|
|
|
'banner_parents.id',
|
|
|
|
);
|
2024-06-12 02:23:46 +00:00
|
|
|
|
|
|
|
await redis.del('traxxx:campaigns');
|
|
|
|
|
|
|
|
await Promise.all(campaigns.map(async (campaign) => {
|
|
|
|
const curatedCampaign = curateCampaign(campaign);
|
|
|
|
|
|
|
|
await redis.hSet('traxxx:campaigns', campaign.id, JSON.stringify(curatedCampaign));
|
|
|
|
}));
|
|
|
|
|
|
|
|
logger.info('Cached campaigns');
|
2024-06-12 00:34:13 +00:00
|
|
|
}
|