import knex from './knex.js'; import { searchApi } from './manticore.js'; import { HttpError } from './errors.js'; import { curateActor, sortActorsByGender } from './actors.js'; function curateMedia(media) { if (!media) { return null; } return { id: media.id, path: media.path, thumbnail: media.thumbnail, lazy: media.lazy, isS3: media.is_s3, width: media.width, height: media.height, }; } function curateScene(rawScene, assets) { if (!rawScene) { return null; } return { id: rawScene.id, title: rawScene.title, slug: rawScene.slug, url: rawScene.url, date: rawScene.date, createdAt: rawScene.created_at, effectiveDate: rawScene.effective_date, description: rawScene.description, duration: rawScene.duration, channel: { id: assets.channel.id, slug: assets.channel.slug, name: assets.channel.name, type: assets.channel.type, isIndependent: assets.channel.independent, hasLogo: assets.channel.has_logo, }, network: assets.channel.network_id ? { id: assets.channel.network_id, slug: assets.channel.network_slug, name: assets.channel.network_name, type: assets.channel.network_type, hasLogo: assets.channel.has_logo, } : null, actors: sortActorsByGender(assets.actors.map((actor) => curateActor(actor, { sceneDate: rawScene.effective_date }))), directors: assets.directors.map((director) => ({ id: director.id, slug: director.slug, name: director.name, })), tags: assets.tags.map((tag) => ({ id: tag.id, slug: tag.slug, name: tag.name, })), poster: curateMedia(assets.poster), photos: assets.photos.map((photo) => curateMedia(photo)), createdBatchId: rawScene.created_batch_id, updatedBatchId: rawScene.updated_batch_id, }; } function curateOptions(options) { if (options?.limit > 100) { throw new HttpError('Limit must be <= 100', 400); } return { limit: options.limit || 30, }; } export async function fetchScenesById(sceneIds) { const [scenes, channels, actors, directors, tags, posters, photos] = await Promise.all([ knex('releases').whereIn('id', sceneIds), knex('releases') .select('channels.*', 'networks.id as network_id', 'networks.slug as network_slug', 'networks.name as network_name', 'networks.type as network_type') .whereIn('releases.id', sceneIds) .leftJoin('entities as channels', 'channels.id', 'releases.entity_id') .leftJoin('entities as networks', 'networks.id', 'channels.parent_id') .groupBy('channels.id', 'networks.id'), knex('releases_actors') .select( 'actors.*', 'releases_actors.release_id', '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', 'birth_countries.alpha2 as birth_country_alpha2', 'birth_countries.name as birth_country_name', 'residence_countries.alpha2 as residence_country_alpha2', 'residence_countries.name as residence_country_name', ) .whereIn('release_id', sceneIds) .leftJoin('actors', 'actors.id', 'releases_actors.actor_id') .leftJoin('media as avatars', 'avatars.id', 'actors.avatar_media_id') .leftJoin('countries as birth_countries', 'birth_countries.alpha2', 'actors.birth_country_alpha2') .leftJoin('countries as residence_countries', 'residence_countries.alpha2', 'actors.residence_country_alpha2'), knex('releases_directors') .whereIn('release_id', sceneIds) .leftJoin('actors as directors', 'directors.id', 'releases_directors.director_id'), knex('releases_tags') .select('id', 'slug', 'name', 'release_id') .whereNotNull('tags.id') .whereIn('release_id', sceneIds) .leftJoin('tags', 'tags.id', 'releases_tags.tag_id'), knex('releases_posters') .whereIn('release_id', sceneIds) .leftJoin('media', 'media.id', 'releases_posters.media_id'), knex('releases_photos') .whereIn('release_id', sceneIds) .leftJoin('media', 'media.id', 'releases_photos.media_id'), ]); return sceneIds.map((sceneId) => { const scene = scenes.find((sceneEntry) => sceneEntry.id === sceneId); if (!scene) { return null; } const sceneChannel = channels.find((entity) => entity.id === scene.entity_id); const sceneActors = actors.filter((actor) => actor.release_id === sceneId); const sceneDirectors = directors.filter((director) => director.release_id === sceneId); const sceneTags = tags.filter((tag) => tag.release_id === sceneId); const scenePoster = posters.find((poster) => poster.release_id === sceneId); const scenePhotos = photos.filter((photo) => photo.release_id === sceneId); return curateScene(scene, { channel: sceneChannel, actors: sceneActors, directors: sceneDirectors, tags: sceneTags, poster: scenePoster, photos: scenePhotos, }); }).filter(Boolean); } export async function fetchLatest(page, rawOptions) { const { limit } = curateOptions(rawOptions); const result = await searchApi.search({ index: 'scenes', query: { bool: { must: [ { range: { effective_date: { lte: Math.round(Date.now() / 1000), }, }, }, ], /* must_not: [ { in: { 'any(tag_ids)': [101, 180, 32], }, }, ], */ }, }, limit, offset: (page - 1) * limit, sort: [{ effective_date: 'desc' }], }); const sceneIds = result.hits.hits.map((hit) => Number(hit._id)); const scenes = await fetchScenesById(sceneIds); return { scenes, total: result.hits.total, limit, }; } export async function fetchUpcoming(page, rawOptions) { const { limit } = curateOptions(rawOptions); const result = await searchApi.search({ index: 'scenes', query: { bool: { must: [ { range: { effective_date: { gt: Math.round(Date.now() / 1000), }, }, }, ], }, }, limit, offset: (page - 1) * limit, sort: [{ effective_date: 'asc' }], }); const sceneIds = result.hits.hits.map((hit) => Number(hit._id)); const scenes = await fetchScenesById(sceneIds); return { scenes, total: result.hits.total, limit, }; } export async function fetchNew(page, rawOptions) { const { limit } = curateOptions(rawOptions); const result = await searchApi.search({ index: 'scenes', limit, offset: (page - 1) * limit, sort: [{ created_at: 'desc' }, { effective_date: 'asc' }], }); const sceneIds = result.hits.hits.map((hit) => Number(hit._id)); const scenes = await fetchScenesById(sceneIds); return { scenes, total: result.hits.total, limit, }; }