Added actor stash.

This commit is contained in:
2024-03-21 02:54:05 +01:00
parent 9b50b53df6
commit a8aab600c7
37 changed files with 1292 additions and 490 deletions

View File

@@ -2,10 +2,11 @@ import config from 'config';
import { differenceInYears } from 'date-fns';
import { unit } from 'mathjs';
import knex from './knex.js';
import { searchApi } from './manticore.js';
import { knexOwner as knex, knexManticore } from './knex.js';
import { utilsApi } from './manticore.js';
import { HttpError } from './errors.js';
import { fetchCountriesByAlpha2 } from './countries.js';
import { curateStash } from './stashes.js';
export function curateActor(actor, context = {}) {
return {
@@ -58,6 +59,7 @@ export function curateActor(actor, context = {}) {
createdAt: actor.created_at,
updatedAt: actor.updated_at,
likes: actor.stashed,
stashes: context.stashes?.map((stash) => curateStash(stash)) || [],
...context.append?.[actor.id],
};
}
@@ -73,8 +75,8 @@ export function sortActorsByGender(actors) {
return genderActors;
}
export async function fetchActorsById(actorIds, options = {}) {
const [actors] = await Promise.all([
export async function fetchActorsById(actorIds, options = {}, reqUser) {
const [actors, stashes] = await Promise.all([
knex('actors')
.select(
'actors.*',
@@ -93,10 +95,19 @@ export async function fetchActorsById(actorIds, options = {}) {
builder.orderBy(...options.order);
}
}),
reqUser
? knex('stashes_actors')
.leftJoin('stashes', 'stashes.id', 'stashes_actors.stash_id')
.where('stashes.user_id', reqUser.id)
.whereIn('stashes_actors.actor_id', actorIds)
: [],
]);
if (options.order) {
return actors.map((actorEntry) => curateActor(actorEntry, { append: options.append }));
return actors.map((actorEntry) => curateActor(actorEntry, {
stashes: stashes.filter((stash) => stash.actor_id === actorEntry.id),
append: options.append,
}));
}
const curatedActors = actorIds.map((actorId) => {
@@ -107,7 +118,10 @@ export async function fetchActorsById(actorIds, options = {}) {
return null;
}
return curateActor(actor, { append: options.append });
return curateActor(actor, {
stashes: stashes.filter((stash) => stash.actor_id === actor.id),
append: options.append,
});
}).filter(Boolean);
return curatedActors;
@@ -126,6 +140,30 @@ function curateOptions(options) {
};
}
/*
const sortMap = {
likes: 'stashed',
scenes: 'scenes',
relevance: '_score',
};
function getSort(order) {
if (order[0] === 'name') {
return [{
slug: order[1],
}];
}
return [
{
[sortMap[order[0]]]: order[1],
},
{
slug: 'asc', // sort by name where primary order is equal
},
];
}
function buildQuery(filters) {
const query = {
bool: {
@@ -230,31 +268,7 @@ function buildQuery(filters) {
return { query, expressions };
}
const sortMap = {
likes: 'stashed',
scenes: 'scenes',
relevance: '_score',
};
function getSort(order) {
if (order[0] === 'name') {
return [{
slug: order[1],
}];
}
return [
{
[sortMap[order[0]]]: order[1],
},
{
slug: 'asc', // sort by name where primary order is equal
},
];
}
export async function fetchActors(filters, rawOptions) {
const options = curateOptions(rawOptions);
async function queryManticoreJson(filters, options) {
const { query, expressions } = buildQuery(filters);
const result = await searchApi.search({
@@ -279,16 +293,176 @@ export async function fetchActors(filters, rawOptions) {
},
});
const actorIds = result.hits.hits.map((hit) => Number(hit._id));
const actors = result.hits.hits.map((hit) => ({
id: hit._id,
...hit._source,
_score: hit._score,
}));
return {
actors,
total: result.hits.total,
aggregations: result.aggregations && Object.fromEntries(Object.entries(result.aggregations).map(([key, { buckets }]) => [key, buckets])),
};
}
*/
async function queryManticoreSql(filters, options, _reqUser) {
const aggSize = config.database.manticore.maxAggregateSize;
const sqlQuery = knexManticore.raw(`
:query:
OPTION
max_matches=:maxMatches:,
max_query_time=:maxQueryTime:
:countriesFacet:;
show meta;
`, {
query: knexManticore(filters.stashId ? 'actors_stashed' : 'actors')
.modify((builder) => {
if (filters.stashId) {
builder.select(knex.raw(`
actors.id as id,
actors.country as country,
actors.scenes as scenes,
actors.stashed as stashed,
created_at as stashed_at
`));
// weight() as _score
builder
.innerJoin('actors', 'actors.id', 'actors_stashed.actor_id')
.where('stash_id', filters.stashId);
} else {
// builder.select(knex.raw('*, weight() as _score'));
builder.select(knex.raw('*'));
}
if (filters.query) {
builder.whereRaw('match(\'@name :query:\', actors)', { query: filters.query });
}
['gender', 'country'].forEach((attribute) => {
if (filters[attribute]) {
builder.where(attribute, filters[attribute]);
}
});
['age', 'height', 'weight'].forEach((attribute) => {
if (filters[attribute]) {
builder
.where(attribute, '>=', filters[attribute][0])
.where(attribute, '<=', filters[attribute][1]);
}
});
if (filters.dateOfBirth && filters.dobType === 'dateOfBirth') {
builder.where('date_of_birth', Math.floor(filters.dateOfBirth.getTime() / 1000));
}
if (filters.dateOfBirth && filters.dobType === 'birthday') {
const month = filters.dateOfBirth.getMonth() + 1;
const day = filters.dateOfBirth.getDate();
builder
.where('month(date_of_birth)', month)
.where('day(date_of_birth)', day);
}
if (filters.cup) {
builder.where(`regex(cup, '^[${filters.cup[0]}-${filters.cup[1]}]')`, 1);
}
if (typeof filters.naturalBoobs === 'boolean') {
builder.where('natural_boobs', filters.naturalBoobs ? 2 : 1); // manticore boolean does not support null, so 0 = null, 1 = false (enhanced), 2 = true (natural)
}
if (filters.requireAvatar) {
builder.where('has_avatar', 1);
}
if (options.order?.[0] === 'name') {
builder.orderBy('actors.slug', options.order[1]);
} else if (options.order?.[0] === 'likes') {
builder.orderBy([
{ column: 'actors.stashed', order: options.order[1] },
{ column: 'actors.slug', order: 'asc' },
]);
} else if (options.order?.[0] === 'scenes') {
builder.orderBy([
{ column: 'actors.scenes', order: options.order[1] },
{ column: 'actors.slug', order: 'asc' },
]);
} else if (options.order?.[0] === 'stashed' && filters.stashId) {
builder.orderBy([
{ column: 'stashed_at', order: options.order[1] },
{ column: 'actors.slug', order: 'asc' },
]);
} else if (options.order) {
builder.orderBy([
{ column: `actors.${options.order[0]}`, order: options.order[1] },
{ column: 'actors.slug', order: 'asc' },
]);
} else {
builder.orderBy('actors.slug', 'asc');
}
})
.limit(options.limit)
.offset((options.page - 1) * options.limit)
.toString(),
// option threads=1 fixes actors, but drastically slows down performance, wait for fix
countriesFacet: options.aggregateActors ? knex.raw('facet actors.country order by count(*) desc limit 300', [aggSize]) : null,
maxMatches: config.database.manticore.maxMatches,
maxQueryTime: config.database.manticore.maxQueryTime,
}).toString();
// manticore does not seem to accept table.column syntax if 'table' is primary (yet?), crude work-around
const curatedSqlQuery = filters.stashId
? sqlQuery
: sqlQuery.replace(/actors\./g, '');
if (process.env.NODE_ENV === 'development') {
console.log(curatedSqlQuery);
}
const results = await utilsApi.sql(curatedSqlQuery);
// console.log(results[0]);
const countries = results
.find((result) => (result.columns[0].actor_ids || result.columns[0]['scenes.country']) && result.columns[1]['count(*)'])
?.data.map((row) => ({ key: row.actor_ids || row['scenes.country'], doc_count: row['count(*)'] }))
|| [];
const total = Number(results.at(-1).data.find((entry) => entry.Variable_name === 'total_found').Value);
return {
actors: results[0].data,
total,
aggregations: {
countries,
},
};
}
export async function fetchActors(filters, rawOptions, reqUser) {
const options = curateOptions(rawOptions);
console.log('filters', filters);
console.log('options', options);
const result = await queryManticoreSql(filters, options, reqUser);
const actorIds = result.actors.map((actor) => Number(actor.id));
const [actors, countries] = await Promise.all([
fetchActorsById(actorIds),
fetchCountriesByAlpha2(result.aggregations.countries.buckets.map((bucket) => bucket.key)),
fetchActorsById(actorIds, {}, reqUser),
fetchCountriesByAlpha2(result.aggregations.countries.map((bucket) => bucket.key)),
]);
return {
actors,
countries,
total: result.hits.total,
total: result.total,
limit: options.limit,
};
}