traxxx-web/src/scenes.js

257 lines
6.9 KiB
JavaScript

import knex from './knex.js';
import { searchApi } from './manticore.js';
import { HttpError } from './errors.js';
import { fetchActorsById, 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,
};
}
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')
.orderBy('priority', 'desc'),
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);
}
function curateOptions(options) {
if (options?.limit > 100) {
throw new HttpError('Limit must be <= 100', 400);
}
return {
limit: options?.limit || 30,
page: Number(options?.page) || 1,
aggregate: options.aggregate ?? true,
};
}
function buildQuery(filters = {}) {
const query = {
bool: {
must: [],
},
};
let sort = [{ effective_date: 'desc' }];
if (!filters.scope || filters.scope === 'latest') {
query.bool.must.push({
range: {
effective_date: {
lte: Math.round(Date.now() / 1000),
},
},
});
}
if (filters.scope === 'upcoming') {
query.bool.must.push({
range: {
effective_date: {
gt: Math.round(Date.now() / 1000),
},
},
});
sort = [{ effective_date: 'asc' }];
}
if (filters.scope === 'new') {
sort = [{ created_at: 'desc' }, { effective_date: 'asc' }];
}
if (filters.actorIds) {
console.log('actor ids', filters.actorIds);
filters.actorIds.forEach((actorId) => {
query.bool.must.push({
equals: {
'any(actor_ids)': actorId,
},
});
});
}
/* tag filter
must_not: [
{
in: {
'any(tag_ids)': [101, 180, 32],
},
},
],
*/
console.log(query.bool.must);
return { query, sort };
}
export async function fetchScenes(filters, rawOptions) {
const options = curateOptions(rawOptions);
const { query, sort } = buildQuery(filters);
console.log(filters);
const result = await searchApi.search({
index: 'scenes',
query,
limit: options.limit,
offset: (options.page - 1) * options.limit,
sort,
...(options.aggregate && {
aggs: {
actorIds: {
terms: {
field: 'actor_ids',
size: 5000,
},
// sort: [{ doc_count: { order: 'asc' } }],
},
},
}),
});
const actorCounts = result.aggregations?.actorIds && Object.fromEntries(result.aggregations?.actorIds?.buckets.map((bucket) => [bucket.key, { count: bucket.doc_count }]));
const [actors] = await Promise.all([
result.aggregations?.actorIds ? fetchActorsById(result.aggregations.actorIds.buckets.map((bucket) => bucket.key), { order: ['name', 'asc'], append: actorCounts }) : [],
]);
const sceneIds = result.hits.hits.map((hit) => Number(hit._id));
const scenes = await fetchScenesById(sceneIds);
return {
scenes,
actors,
total: result.hits.total,
limit: options.limit,
};
}