Aggregating channels, filter inoperable.
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
import initServer from './web/server.js';
|
||||
import { cacheTagIds } from './tags.js';
|
||||
import { cacheEntityIds } from './entities.js';
|
||||
|
||||
async function init() {
|
||||
await cacheTagIds();
|
||||
await Promise.all([
|
||||
cacheTagIds(),
|
||||
cacheEntityIds(),
|
||||
]);
|
||||
|
||||
initServer();
|
||||
}
|
||||
|
||||
63
src/entities.js
Normal file
63
src/entities.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import knex from './knex.js';
|
||||
import redis from './redis.js';
|
||||
import initLogger from './logger.js';
|
||||
|
||||
const logger = initLogger();
|
||||
|
||||
function curateEntity(entity, context) {
|
||||
if (!entity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: entity.id,
|
||||
name: entity.name,
|
||||
slug: entity.slug,
|
||||
type: entity.type,
|
||||
isIndependent: entity.independent,
|
||||
hasLogo: entity.has_logo,
|
||||
parent: curateEntity(entity.parent, context),
|
||||
...context?.append?.[entity.id],
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchEntitiesById(entityIds, options = {}) {
|
||||
const [entities] = await Promise.all([
|
||||
knex('entities')
|
||||
.select('entities.*', knex.raw('row_to_json(parents) as parent'))
|
||||
.whereIn('entities.id', entityIds)
|
||||
.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
|
||||
.modify((builder) => {
|
||||
if (options.order) {
|
||||
builder.orderBy(...options.order);
|
||||
}
|
||||
})
|
||||
.groupBy('entities.id', 'parents.id'),
|
||||
]);
|
||||
|
||||
if (options.order) {
|
||||
return entities.map((entityEntry) => curateEntity(entityEntry, { append: options.append }));
|
||||
}
|
||||
|
||||
const curatedEntities = entityIds.map((entityId) => {
|
||||
const entity = entities.find((entityEntry) => entityEntry.id === entityId);
|
||||
|
||||
if (!entity) {
|
||||
console.warn(`Can't match entity ${entityId}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return curateEntity(entity, { append: options.append });
|
||||
}).filter(Boolean);
|
||||
|
||||
return curatedEntities;
|
||||
}
|
||||
|
||||
export async function cacheEntityIds() {
|
||||
const entities = await knex('entities').select('id', 'slug', 'type');
|
||||
|
||||
await redis.del('traxxx:entities:id_by_slug');
|
||||
await redis.hSet('traxxx:entities:id_by_slug', entities.map((entity) => [entity.type === 'network' ? `_${entity.slug}` : entity.slug, entity.id]));
|
||||
|
||||
logger.info('Cached entity IDs by slug');
|
||||
}
|
||||
22
src/query.js
Normal file
22
src/query.js
Normal file
@@ -0,0 +1,22 @@
|
||||
export function getActorIdentifier(actor) {
|
||||
if (!actor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `${actor.slug}:${actor.id}`;
|
||||
}
|
||||
|
||||
export function parseActorIdentifier(identifier) {
|
||||
if (!identifier) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [slug, idString] = identifier.split(':');
|
||||
const id = Number(idString);
|
||||
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { slug, id };
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { searchApi } from './manticore.js';
|
||||
import { HttpError } from './errors.js';
|
||||
import { fetchActorsById, curateActor, sortActorsByGender } from './actors.js';
|
||||
import { fetchTagsById } from './tags.js';
|
||||
import { fetchEntitiesById } from './entities.js';
|
||||
|
||||
function curateMedia(media) {
|
||||
if (!media) {
|
||||
@@ -151,6 +152,7 @@ function curateOptions(options) {
|
||||
aggregate: options.aggregate ?? true,
|
||||
aggregateActors: (options.aggregate ?? true) && (options.aggregateActors ?? true),
|
||||
aggregateTags: (options.aggregate ?? true) && (options.aggregateTags ?? true),
|
||||
aggregateChannels: (options.aggregate ?? true) && (options.aggregateChannels ?? true),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -224,11 +226,54 @@ function buildQuery(filters = {}) {
|
||||
return { query, sort };
|
||||
}
|
||||
|
||||
function buildAggregates(options) {
|
||||
const aggregates = {};
|
||||
|
||||
if (options.aggregateActors) {
|
||||
aggregates.actorIds = {
|
||||
terms: {
|
||||
field: 'actor_ids',
|
||||
size: 5000,
|
||||
},
|
||||
// sort: [{ doc_count: { order: 'asc' } }],
|
||||
};
|
||||
}
|
||||
|
||||
if (options.aggregateTags) {
|
||||
aggregates.tagIds = {
|
||||
terms: {
|
||||
field: 'tag_ids',
|
||||
size: 1000,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (options.aggregateChannels) {
|
||||
aggregates.channelIds = {
|
||||
terms: {
|
||||
field: 'channel_id',
|
||||
size: 1000,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return aggregates;
|
||||
}
|
||||
|
||||
function countAggregations(buckets) {
|
||||
if (!buckets) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Object.fromEntries(buckets.map((bucket) => [bucket.key, { count: bucket.doc_count }]));
|
||||
}
|
||||
|
||||
export async function fetchScenes(filters, rawOptions) {
|
||||
const options = curateOptions(rawOptions);
|
||||
const { query, sort } = buildQuery(filters);
|
||||
|
||||
console.log(filters);
|
||||
console.log('filters', filters);
|
||||
console.log('options', options);
|
||||
|
||||
const result = await searchApi.search({
|
||||
index: 'scenes',
|
||||
@@ -236,34 +281,17 @@ export async function fetchScenes(filters, rawOptions) {
|
||||
limit: options.limit,
|
||||
offset: (options.page - 1) * options.limit,
|
||||
sort,
|
||||
aggs: {
|
||||
...(options.aggregateActors && {
|
||||
actorIds: {
|
||||
terms: {
|
||||
field: 'actor_ids',
|
||||
size: 5000,
|
||||
},
|
||||
// sort: [{ doc_count: { order: 'asc' } }],
|
||||
},
|
||||
}),
|
||||
...(options.aggregateTags && {
|
||||
tagIds: {
|
||||
terms: {
|
||||
field: 'tag_ids',
|
||||
size: 1000,
|
||||
},
|
||||
// sort: [{ doc_count: { order: 'asc' } }],
|
||||
},
|
||||
}),
|
||||
},
|
||||
aggs: buildAggregates(options),
|
||||
});
|
||||
|
||||
const actorCounts = options.aggregateActors && Object.fromEntries(result.aggregations?.actorIds?.buckets.map((bucket) => [bucket.key, { count: bucket.doc_count }]));
|
||||
const tagCounts = options.aggregateTags && Object.fromEntries(result.aggregations?.tagIds?.buckets.map((bucket) => [bucket.key, { count: bucket.doc_count }]));
|
||||
const actorCounts = options.aggregateActors && countAggregations(result.aggregations?.actorIds?.buckets);
|
||||
const tagCounts = options.aggregateTags && countAggregations(result.aggregations?.tagIds?.buckets);
|
||||
const channelCounts = options.aggregateChannels && countAggregations(result.aggregations?.channelIds?.buckets);
|
||||
|
||||
const [aggActors, aggTags] = await Promise.all([
|
||||
const [aggActors, aggTags, aggChannels] = await Promise.all([
|
||||
options.aggregateActors ? fetchActorsById(result.aggregations.actorIds.buckets.map((bucket) => bucket.key), { order: ['name', 'asc'], append: actorCounts }) : [],
|
||||
options.aggregateTags ? fetchTagsById(result.aggregations.tagIds.buckets.map((bucket) => bucket.key), { order: ['name', 'asc'], append: tagCounts }) : [],
|
||||
options.aggregateChannels ? fetchEntitiesById(result.aggregations.channelIds.buckets.map((bucket) => bucket.key), { order: ['name', 'asc'], append: channelCounts }) : [],
|
||||
]);
|
||||
|
||||
const sceneIds = result.hits.hits.map((hit) => Number(hit._id));
|
||||
@@ -273,6 +301,7 @@ export async function fetchScenes(filters, rawOptions) {
|
||||
scenes,
|
||||
aggActors,
|
||||
aggTags,
|
||||
aggChannels,
|
||||
total: result.hits.total,
|
||||
limit: options.limit,
|
||||
};
|
||||
|
||||
@@ -52,5 +52,5 @@ export async function cacheTagIds() {
|
||||
await redis.del('traxxx:tags:id_by_slug');
|
||||
await redis.hSet('traxxx:tags:id_by_slug', tags.map((tag) => [tag.slug, tag.id]));
|
||||
|
||||
logger.info('Cached tags IDs by slug');
|
||||
logger.info('Cached tag IDs by slug');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { stringify } from '@brillout/json-serializer/stringify'; /* eslint-disable-line import/extensions */
|
||||
|
||||
import { fetchScenes } from '../scenes.js';
|
||||
import { parseActorIdentifier } from '../query.js';
|
||||
import redis from '../redis.js';
|
||||
|
||||
async function getTagIdsBySlug(tagSlugs) {
|
||||
@@ -24,7 +25,7 @@ async function getTagIdsBySlug(tagSlugs) {
|
||||
export async function curateScenesQuery(query) {
|
||||
return {
|
||||
scope: query.scope || 'latest',
|
||||
actorIds: [query.actorId, ...(query.actors?.split(',') || [])].filter(Boolean).map((actorId) => Number(actorId)),
|
||||
actorIds: [query.actorId, ...(query.actors?.split(',') || []).map((identifier) => parseActorIdentifier(identifier)?.id)].filter(Boolean),
|
||||
tagIds: await getTagIdsBySlug([query.tagId, ...(query.tags?.split(',') || [])]),
|
||||
};
|
||||
}
|
||||
@@ -34,6 +35,7 @@ export async function fetchScenesApi(req, res) {
|
||||
scenes,
|
||||
aggActors,
|
||||
aggTags,
|
||||
aggChannels,
|
||||
limit,
|
||||
total,
|
||||
} = await fetchScenes(await curateScenesQuery(req.query), {
|
||||
@@ -45,6 +47,7 @@ export async function fetchScenesApi(req, res) {
|
||||
scenes,
|
||||
aggActors,
|
||||
aggTags,
|
||||
aggChannels,
|
||||
limit,
|
||||
total,
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user