import { differenceInYears } from 'date-fns'; import knex from './knex.js'; import { searchApi } from './manticore.js'; import { HttpError } from './errors.js'; import { fetchCountriesByAlpha2 } from './countries.js'; export function curateActor(actor, context = {}) { return { id: actor.id, slug: actor.slug, name: actor.name, gender: actor.gender, age: actor.age, dateOfBirth: actor.date_of_birth, ageFromBirth: actor.date_of_birth && differenceInYears(Date.now(), actor.date_of_birth), ageThen: context.sceneDate && actor.date_of_birth && differenceInYears(context.sceneDate, actor.date_of_birth), birthCountry: actor.birth_country_alpha2 && { alpha2: actor.birth_country_alpha2, name: actor.birth_country_name, }, residenceCountry: actor.residence_country_alpha2 && { alpha2: actor.residence_country_alpha2, name: actor.residence_country_name, }, avatar: actor.avatar_id ? { id: actor.avatar_id, path: actor.avatar_path, thumbnail: actor.avatar_thumbnail, lazy: actor.avatar_lazy, isS3: actor.avatar_s3, } : null, }; } export function sortActorsByGender(actors) { if (!actors) { return actors; } const alphaActors = actors.sort((actorA, actorB) => actorA.name.localeCompare(actorB.name, 'en')); const genderActors = ['transsexual', 'female', 'male', undefined].flatMap((gender) => alphaActors.filter((actor) => actor.gender === gender)); return genderActors; } export async function fetchActorsById(actorIds) { const [actors] = await Promise.all([ knex('actors') .select( 'actors.*', 'avatars.id as avatar_id', 'avatars.path as avatar_path', 'avatars.thumbnail as avatar_thumbnail', 'avatars.lazy as avatar_lazy', 'avatars.width as avatar_width', 'avatars.height as avatar_height', 'avatars.is_s3 as avatar_s3', ) .whereIn('actors.id', actorIds) .leftJoin('media as avatars', 'avatars.id', 'actors.avatar_media_id') .groupBy('actors.id', 'avatars.id'), ]); return actorIds.map((actorId) => { const actor = actors.find((actorEntry) => actorEntry.id === actorId); if (!actor) { return null; } return curateActor(actor); }).filter(Boolean); } function curateOptions(options) { if (options?.limit > 100) { throw new HttpError('Limit must be <= 100', 400); } return { page: options?.page || 1, limit: options?.limit || 30, requireAvatar: options?.requireAvatar || false, }; } function buildQuery(filters) { console.log('filters', filters); const query = { bool: { must: [], }, }; const expressions = { age: 'if(date_of_birth, floor((now() - date_of_birth) / 31556952), 0)', }; if (filters.query) { query.bool.must.push({ match: { name: filters.query, }, }); } if (filters.gender) { query.bool.must.push({ equals: { gender: filters.gender, }, }); } ['age', 'height', 'weight'].forEach((attribute) => { if (filters[attribute]) { query.bool.must.push({ range: { [attribute]: { gte: filters[attribute][0], lte: filters[attribute][1], }, }, }); } }); if (filters.cup) { expressions.cup_in_range = `regex(cup, '^[${filters.cup[0]}-${filters.cup[1]}]')`; query.bool.must.push({ equals: { cup_in_range: 1, }, }); } if (typeof filters.naturalBoobs === 'boolean') { query.bool.must.push({ equals: { natural_boobs: filters.naturalBoobs ? 2 : 1, // manticore boolean does not support null, so 0 = null, 1 = false (enhanced), 2 = true (natural) }, }); } if (filters.requireAvatar) { query.bool.must.push({ equals: { has_avatar: 1, }, }); } return { query, expressions }; } export async function fetchActors(filters, rawOptions) { const options = curateOptions(rawOptions); const { query, expressions } = buildQuery(filters); const result = await searchApi.search({ index: 'actors', query, expressions, limit: options.limit, offset: (options.page - 1) * options.limit, sort: [{ slug: 'asc' }], aggs: { countries: { terms: { field: 'country', size: 300, }, sort: [{ country: { order: 'asc' } }], }, }, }); const actorIds = result.hits.hits.map((hit) => Number(hit._id)); const [actors, countries] = await Promise.all([ fetchActorsById(actorIds), fetchCountriesByAlpha2(result.aggregations.countries.buckets.map((bucket) => bucket.key)), ]); return { actors, countries, total: result.hits.total, limit: options.limit, }; }