Showing curated campaigns on tags page.

This commit is contained in:
DebaucheryLibrarian 2024-06-17 01:28:20 +02:00
parent 64bf3b65ac
commit f9b7a8731e
7 changed files with 81 additions and 25 deletions

View File

@ -72,5 +72,6 @@ const bannerSrc = (() => {
height: auto; height: auto;
max-height: 100%; max-height: 100%;
max-width: 100%; max-width: 100%;
object-fit: contain;
} }
</style> </style>

View File

@ -34,16 +34,19 @@ export async function onBeforeRender(pageContext) {
{ {
entityIds: [entity.id, entity.parent?.id].filter(Boolean), entityIds: [entity.id, entity.parent?.id].filter(Boolean),
minRatio: 1.5, minRatio: 1.5,
allowRandomFallback: false,
}, },
{ {
entityIds: [entity.id, entity.parent?.id].filter(Boolean), entityIds: [entity.id, entity.parent?.id].filter(Boolean),
minRatio: 0.75, minRatio: 0.75,
maxRatio: 1.25, maxRatio: 1.25,
allowRandomFallback: false,
}, },
{ {
entityIds: [entity.id, entity.parent?.id].filter(Boolean), entityIds: [entity.id, entity.parent?.id].filter(Boolean),
parentEntityId: entity.parent?.id, parentEntityId: entity.parent?.id,
minRatio: 1.5, minRatio: 1.5,
allowRandomFallback: false,
}, },
], { tagFilter: pageContext.tagFilter }); ], { tagFilter: pageContext.tagFilter });

View File

@ -4,12 +4,15 @@ import markdownItClass from '@toycode/markdown-it-class';
import { fetchTagsById } from '#/src/tags.js'; import { fetchTagsById } from '#/src/tags.js';
import { fetchScenes } from '#/src/scenes.js'; import { fetchScenes } from '#/src/scenes.js';
import { curateScenesQuery } from '#/src/web/scenes.js'; import { curateScenesQuery } from '#/src/web/scenes.js';
import { getRandomCampaigns, getCampaignIndex } from '#/src/campaigns.js';
const md = markdownIt().use(markdownItClass, { a: 'link' }); const md = markdownIt().use(markdownItClass, { a: 'link' });
export async function onBeforeRender(pageContext) { export async function onBeforeRender(pageContext) {
const [[tag], tagScenes] = await Promise.all([ const tagSlug = pageContext.routeParams.tagSlug;
fetchTagsById([pageContext.routeParams.tagSlug]),
const [[tag], tagScenes, campaigns] = await Promise.all([
fetchTagsById([tagSlug]),
fetchScenes(await curateScenesQuery({ fetchScenes(await curateScenesQuery({
...pageContext.urlQuery, ...pageContext.urlQuery,
scope: pageContext.routeParams.scope || 'latest', scope: pageContext.routeParams.scope || 'latest',
@ -20,6 +23,11 @@ export async function onBeforeRender(pageContext) {
limit: Number(pageContext.urlParsed.search.limit) || 30, limit: Number(pageContext.urlParsed.search.limit) || 30,
aggregate: true, aggregate: true,
}, pageContext.user), }, pageContext.user),
getRandomCampaigns([
{ tagSlugs: [tagSlug], minRatio: 1.5 },
{ tagSlugs: [tagSlug], minRatio: 0.75, maxRatio: 1.25 },
{ tagSlugs: [tagSlug], minRatio: 1.5 },
], { tagFilter: pageContext.tagFilter }),
]); ]);
const { const {
@ -33,6 +41,9 @@ export async function onBeforeRender(pageContext) {
const description = tag.description && md.renderInline(tag.description); const description = tag.description && md.renderInline(tag.description);
const campaignIndex = getCampaignIndex(scenes.length);
const [metaCampaign, sceneCampaign, paginationCampaign] = campaigns;
return { return {
pageContext: { pageContext: {
title: tag.name, title: tag.name,
@ -46,6 +57,12 @@ export async function onBeforeRender(pageContext) {
total, total,
limit, limit,
}, },
campaigns: {
index: campaignIndex,
meta: metaCampaign,
scenes: scenes.length > 5 && sceneCampaign,
pagination: paginationCampaign,
},
}, },
}; };
} }

View File

@ -1,6 +1,6 @@
import { fetchScenes } from '#/src/scenes.js'; import { fetchScenes } from '#/src/scenes.js';
import { curateScenesQuery } from '#/src/web/scenes.js'; import { curateScenesQuery } from '#/src/web/scenes.js';
import { getRandomCampaigns } from '#/src/campaigns.js'; import { getRandomCampaigns, getCampaignIndex } from '#/src/campaigns.js';
export async function onBeforeRender(pageContext) { export async function onBeforeRender(pageContext) {
const withQuery = Object.hasOwn(pageContext.urlParsed.search, 'q'); const withQuery = Object.hasOwn(pageContext.urlParsed.search, 'q');
@ -35,7 +35,7 @@ export async function onBeforeRender(pageContext) {
total, total,
} = sceneResults; } = sceneResults;
const campaignIndex = Math.floor((Math.random() * (0.5 - 0.2) + 0.2) * scenes.length); const campaignIndex = getCampaignIndex(scenes.length);
const [scopeCampaign, sceneCampaign, paginationCampaign] = campaigns; const [scopeCampaign, sceneCampaign, paginationCampaign] = campaigns;
return { return {

View File

@ -33,6 +33,26 @@ function curateCampaign(campaign) {
}; };
} }
function selectRandomCampaign(primaryCampaigns, entityCampaigns, preferredCampaigns, allCampaigns, options) {
if (primaryCampaigns.length > 0) {
return primaryCampaigns[crypto.randomInt(primaryCampaigns.length)];
}
if (entityCampaigns.length > 0) {
return entityCampaigns[crypto.randomInt(entityCampaigns.length)];
}
if (preferredCampaigns.length > 0) {
return preferredCampaigns[crypto.randomInt(preferredCampaigns.length)];
}
if (allCampaigns.length > 0 && options.allowRandomFallback !== false) {
return allCampaigns[crypto.randomInt(allCampaigns.length)];
}
return null;
}
export async function getRandomCampaign(options = {}, context = {}) { export async function getRandomCampaign(options = {}, context = {}) {
const campaigns = options.campaigns const campaigns = options.campaigns
|| await redis.hGetAll('traxxx:campaigns').then((rawCampaigns) => Object.values(rawCampaigns).map((rawCampaign) => JSON.parse(rawCampaign))); || await redis.hGetAll('traxxx:campaigns').then((rawCampaigns) => Object.values(rawCampaigns).map((rawCampaign) => JSON.parse(rawCampaign)));
@ -50,40 +70,40 @@ export async function getRandomCampaign(options = {}, context = {}) {
return false; return false;
} }
if (context.tagFilter && campaign.banner && campaign.banner.tags.some((tag) => context.tagFilter.includes(tag))) { if (context.tagFilter && campaign.banner && campaign.banner.tags.some((tag) => context.tagFilter.includes(tag) && !options.tagSlugs?.includes(tag))) {
return false;
}
// tag page overrides tag filter
if (options.tagSlugs && campaign.banner && !campaign.banner.tags.some((tag) => options.tagSlugs.includes(tag))) {
return false; return false;
} }
return true; return true;
}); });
if (validCampaigns.length > 0) { // console.log(validCampaigns);
const campaignsByEntityId = validCampaigns.reduce((acc, campaign) => {
const entityId = campaign.entity.parent?.id || campaign.entity.id;
if (!acc[entityId]) { const campaignsByEntityId = validCampaigns.reduce((acc, campaign) => {
acc[entityId] = []; const entityId = campaign.entity.parent?.id || campaign.entity.id;
}
acc[entityId].push(campaign); if (!acc[entityId]) {
acc[entityId] = [];
}
return acc; acc[entityId].push(campaign);
}, {});
// randomize entities first to ensure fair exposure for entities with fewer banners return acc;
const entityIds = Object.keys(campaignsByEntityId); }, {});
const randomEntityCampaigns = campaignsByEntityId[entityIds[crypto.randomInt(entityIds.length)]];
const primaryCampaigns = randomEntityCampaigns.filter((campaign) => campaign.entity.id === options.entityIds?.[0]); // randomize entities first to ensure fair exposure for entities with fewer banners
const entityIds = Object.keys(campaignsByEntityId);
const randomEntityCampaigns = entityIds.length > 0 ? campaignsByEntityId[entityIds[crypto.randomInt(entityIds.length)]] : [];
const primaryCampaigns = randomEntityCampaigns.filter((campaign) => campaign.entity.id === options.entityIds?.[0]);
const randomCampaign = (primaryCampaigns.length > 0 ? primaryCampaigns[crypto.randomInt(primaryCampaigns.length)] : null) const randomCampaign = selectRandomCampaign(primaryCampaigns, randomEntityCampaigns, validCampaigns, campaigns, options);
|| (randomEntityCampaigns.length > 0 ? randomEntityCampaigns[crypto.randomInt(randomEntityCampaigns.length)] : null)
|| validCampaigns[crypto.randomInt(validCampaigns.length)];
return randomCampaign; return randomCampaign;
}
return null;
} }
export async function getRandomCampaigns(allOptions = [], context = {}) { export async function getRandomCampaigns(allOptions = [], context = {}) {
@ -96,6 +116,10 @@ export async function getRandomCampaigns(allOptions = [], context = {}) {
}, context))); }, context)));
} }
export function getCampaignIndex(scenesCount) {
return Math.floor((Math.random() * (0.5 - 0.2) + 0.2) * scenesCount); // avoid start and end of scenes list
}
export async function cacheCampaigns() { export async function cacheCampaigns() {
const campaigns = await knex('campaigns') const campaigns = await knex('campaigns')
.select('campaigns.*') .select('campaigns.*')

View File

@ -1,6 +1,11 @@
export default function consentHandler(req, res, next) { export default function consentHandler(req, res, next) {
const redirect = req.headers.referer && new URL(req.headers.referer).searchParams.get('redirect'); const redirect = req.headers.referer && new URL(req.headers.referer).searchParams.get('redirect');
if (req.path.includes('/api')) {
next();
return;
}
if (Object.hasOwn(req.query, 'lgbt')) { if (Object.hasOwn(req.query, 'lgbt')) {
const lgbtFilters = (req.tagFilter || []).filter((tag) => !['gay', 'bisexual', 'transsexual'].includes(tag)); const lgbtFilters = (req.tagFilter || []).filter((tag) => !['gay', 'bisexual', 'transsexual'].includes(tag));

View File

@ -24,6 +24,8 @@ export async function curateScenesQuery(query) {
notEntityIds: await getIdsBySlug(splitEntities.filter((entity) => entity.charAt(0) === '!').map((entity) => entity.slice(1)), 'entities'), notEntityIds: await getIdsBySlug(splitEntities.filter((entity) => entity.charAt(0) === '!').map((entity) => entity.slice(1)), 'entities'),
}); });
console.log('QUERY', query);
return { return {
scope: query.scope || 'latest', scope: query.scope || 'latest',
query: query.q, query: query.q,
@ -41,6 +43,8 @@ export async function curateScenesQuery(query) {
} }
export async function fetchScenesApi(req, res) { export async function fetchScenesApi(req, res) {
console.log('REQUEST', req.query);
const { const {
scenes, scenes,
aggActors, aggActors,
@ -56,6 +60,8 @@ export async function fetchScenesApi(req, res) {
limit: Number(req.query.limit) || 30, limit: Number(req.query.limit) || 30,
}, req.user); }, req.user);
console.log('OUTPUT', scenes.length);
res.send(stringify({ res.send(stringify({
scenes, scenes,
aggActors, aggActors,