import { differenceInYears } from 'date-fns'; import knex from './knex.js'; import { searchApi } from './manticore.js'; import { HttpError } from './errors.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); const query = { bool: { must: [], }, }; if (filters.query) { query.bool.must.push({ match: { name: filters.query, }, }); } ['age', 'height', 'weight'].forEach((attribute) => { if (filters[attribute]) { query.bool.must.push({ range: { [attribute]: { gte: filters[attribute][0], lte: filters[attribute][1], }, }, }); } }); if (filters.requireAvatar) { query.bool.must.push({ equals: { has_avatar: 1, }, }); } return query; } export async function fetchActors(filters, rawOptions) { const options = curateOptions(rawOptions); const query = buildQuery(filters); const result = await searchApi.search({ index: 'actors', query, expressions: { age: 'if(date_of_birth, floor((now() - date_of_birth) / 31556952), 0)', }, limit: options.limit, offset: (options.page - 1) * options.limit, sort: [{ slug: 'asc' }], }); const actorIds = result.hits.hits.map((hit) => Number(hit._id)); const actors = await fetchActorsById(actorIds); return { actors, total: result.hits.total, limit: options.limit, }; }