2024-03-17 21:15:11 +00:00
import config from 'config' ;
2023-12-30 05:29:53 +00:00
import { differenceInYears } from 'date-fns' ;
2024-01-10 01:00:38 +00:00
import { unit } from 'mathjs' ;
2023-12-30 05:29:53 +00:00
2024-03-21 01:54:05 +00:00
import { knexOwner as knex , knexManticore } from './knex.js' ;
import { utilsApi } from './manticore.js' ;
2023-12-30 05:29:53 +00:00
import { HttpError } from './errors.js' ;
2023-12-31 02:02:03 +00:00
import { fetchCountriesByAlpha2 } from './countries.js' ;
2024-06-07 03:20:13 +00:00
import { curateEntity } from './entities.js' ;
import { curateMedia } from './media.js' ;
2024-03-21 01:54:05 +00:00
import { curateStash } from './stashes.js' ;
2024-03-31 23:50:24 +00:00
import escape from '../utils/escape-manticore.js' ;
2024-03-21 02:49:03 +00:00
import slugify from '../utils/slugify.js' ;
2023-12-30 05:29:53 +00:00
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 ) ,
2024-01-10 01:00:38 +00:00
bust : actor . bust ,
cup : actor . cup ,
waist : actor . waist ,
hip : actor . hip ,
naturalBoobs : actor . naturalBoobs ,
height : actor . height && {
metric : actor . height ,
imperial : unit ( actor . height , 'cm' ) . splitUnit ( [ 'ft' , 'in' ] ) . map ( ( value ) => Math . round ( value . toNumber ( ) ) ) ,
2023-12-30 05:29:53 +00:00
} ,
2024-01-10 01:00:38 +00:00
weight : actor . weight && {
metric : actor . weight ,
imperial : Math . round ( unit ( actor . weight , 'kg' ) . toNumeric ( 'lbs' ) ) ,
} ,
eyes : actor . eyes ,
hairColor : actor . hairColor ,
hasTattoos : actor . has _tattoos ,
tattoos : actor . tattoos ,
hasPiercings : actor . has _piercings ,
piercings : actor . piercings ,
2024-01-25 02:07:26 +00:00
origin : actor . birth _country _alpha2 && {
2024-01-10 01:00:38 +00:00
country : actor . birth _country _alpha2 && {
alpha2 : actor . birth _country _alpha2 ,
name : actor . birth _country _name ,
2024-04-02 01:01:15 +00:00
alias : actor . birth _country _alias ,
2024-01-10 01:00:38 +00:00
} ,
} ,
2024-01-25 02:07:26 +00:00
residence : actor . residence _country _alpha2 && {
2024-01-10 01:00:38 +00:00
country : actor . residence _country _alpha2 && {
alpha2 : actor . residence _country _alpha2 ,
name : actor . residence _country _name ,
2024-04-02 01:01:15 +00:00
alias : actor . residence _country _alias ,
2024-01-10 01:00:38 +00:00
} ,
2023-12-30 05:29:53 +00:00
} ,
2024-06-07 03:20:13 +00:00
avatar : curateMedia ( actor . avatar ) ,
profiles : context . profiles ? . map ( ( profile ) => ( {
id : profile . id ,
description : profile . description ,
descriptionHash : profile . description _hash ,
entity : curateEntity ( { ... profile . entity , parent : profile . parent _entity } ) ,
avatar : curateMedia ( profile . avatar ) ,
2024-06-07 01:15:37 +00:00
} ) ) ,
2024-01-10 01:00:38 +00:00
createdAt : actor . created _at ,
updatedAt : actor . updated _at ,
2024-01-05 23:30:30 +00:00
likes : actor . stashed ,
2024-03-21 01:54:05 +00:00
stashes : context . stashes ? . map ( ( stash ) => curateStash ( stash ) ) || [ ] ,
2024-01-07 22:44:33 +00:00
... context . append ? . [ actor . id ] ,
2023-12-30 05:29:53 +00:00
} ;
}
2024-03-21 02:49:03 +00:00
export function sortActorsByGender ( actors , context = { } ) {
2023-12-30 05:29:53 +00:00
if ( ! actors ) {
return actors ;
}
const alphaActors = actors . sort ( ( actorA , actorB ) => actorA . name . localeCompare ( actorB . name , 'en' ) ) ;
2024-03-24 03:22:37 +00:00
const genderActors = [ 'transsexual' , 'female' , undefined , null , 'male' ] . flatMap ( ( gender ) => alphaActors . filter ( ( actor ) => actor . gender === gender ) ) ;
2023-12-30 05:29:53 +00:00
2024-03-21 02:49:03 +00:00
const titleSlug = slugify ( context . title ) ;
const titleActors = titleSlug ? genderActors . sort ( ( actorA , actorB ) => {
2024-03-23 01:47:52 +00:00
const actorASlug = actorA . slug . split ( '-' ) [ 0 ] ;
const actorBSlug = actorB . slug . split ( '-' ) [ 0 ] ;
if ( titleSlug . includes ( actorASlug ) && ! titleSlug . includes ( actorBSlug ) ) {
2024-03-21 02:49:03 +00:00
return - 1 ;
}
2024-03-23 01:47:52 +00:00
if ( titleSlug . includes ( actorBSlug ) && ! titleSlug . includes ( actorASlug ) ) {
2024-03-21 02:49:03 +00:00
return 1 ;
}
return 0 ;
} ) : alphaActors ;
return titleActors ;
2023-12-30 05:29:53 +00:00
}
2024-03-21 01:54:05 +00:00
export async function fetchActorsById ( actorIds , options = { } , reqUser ) {
2024-06-07 03:20:13 +00:00
const [ actors , profiles , stashes ] = await Promise . all ( [
2024-02-27 00:20:15 +00:00
knex ( 'actors' )
2024-02-22 04:08:06 +00:00
. select (
2024-02-27 00:20:15 +00:00
'actors.*' ,
2024-02-22 04:08:06 +00:00
'actors_meta.*' ,
'birth_countries.alpha2 as birth_country_alpha2' ,
knex . raw ( 'COALESCE(birth_countries.alias, birth_countries.name) as birth_country_name' ) ,
'residence_countries.alpha2 as residence_country_alpha2' ,
knex . raw ( 'COALESCE(residence_countries.alias, residence_countries.name) as residence_country_name' ) ,
)
2024-02-27 00:20:15 +00:00
. leftJoin ( 'actors_meta' , 'actors_meta.actor_id' , 'actors.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' )
. whereIn ( 'actors.id' , actorIds )
2024-01-07 05:13:40 +00:00
. modify ( ( builder ) => {
if ( options . order ) {
builder . orderBy ( ... options . order ) ;
}
} ) ,
2024-06-07 01:15:37 +00:00
knex ( 'actors_profiles' )
2024-06-07 03:20:13 +00:00
. select ( 'actors_profiles.*' , knex . raw ( 'row_to_json(entities) as entity' ) , knex . raw ( 'row_to_json(parents) as parent_entity' ) , knex . raw ( 'row_to_json(media) as avatar' ) )
2024-06-07 01:15:37 +00:00
. leftJoin ( 'actors' , 'actors.id' , 'actors_profiles.actor_id' )
2024-06-07 03:20:13 +00:00
. leftJoin ( 'entities' , 'entities.id' , 'actors_profiles.entity_id' )
. leftJoin ( 'entities as parents' , 'parents.id' , 'entities.parent_id' )
2024-06-07 01:15:37 +00:00
. leftJoin ( 'media' , 'media.id' , 'actors_profiles.avatar_media_id' )
. whereIn ( 'actor_id' , actorIds )
2024-06-07 03:20:13 +00:00
. groupBy ( 'actors_profiles.id' , 'entities.id' , 'parents.id' , 'media.id' ) ,
2024-03-21 01:54:05 +00:00
reqUser
? knex ( 'stashes_actors' )
. leftJoin ( 'stashes' , 'stashes.id' , 'stashes_actors.stash_id' )
. where ( 'stashes.user_id' , reqUser . id )
. whereIn ( 'stashes_actors.actor_id' , actorIds )
: [ ] ,
2023-12-30 05:29:53 +00:00
] ) ;
2024-01-07 05:13:40 +00:00
if ( options . order ) {
2024-03-21 01:54:05 +00:00
return actors . map ( ( actorEntry ) => curateActor ( actorEntry , {
stashes : stashes . filter ( ( stash ) => stash . actor _id === actorEntry . id ) ,
append : options . append ,
} ) ) ;
2024-01-07 05:13:40 +00:00
}
2024-01-04 00:49:16 +00:00
const curatedActors = actorIds . map ( ( actorId ) => {
2023-12-30 05:29:53 +00:00
const actor = actors . find ( ( actorEntry ) => actorEntry . id === actorId ) ;
if ( ! actor ) {
2024-01-08 01:21:57 +00:00
console . warn ( ` Can't match actor ${ actorId } ` ) ;
2023-12-30 05:29:53 +00:00
return null ;
}
2024-03-21 01:54:05 +00:00
return curateActor ( actor , {
stashes : stashes . filter ( ( stash ) => stash . actor _id === actor . id ) ,
2024-06-07 03:20:13 +00:00
profiles : profiles . filter ( ( profile ) => profile . actor _id === actor . id ) ,
2024-03-21 01:54:05 +00:00
append : options . append ,
} ) ;
2023-12-30 05:29:53 +00:00
} ) . filter ( Boolean ) ;
2024-01-04 00:49:16 +00:00
return curatedActors ;
2023-12-30 05:29:53 +00:00
}
function curateOptions ( options ) {
2024-01-04 00:49:16 +00:00
if ( options ? . limit > 120 ) {
throw new HttpError ( 'Limit must be <= 120' , 400 ) ;
2023-12-30 05:29:53 +00:00
}
return {
page : options ? . page || 1 ,
limit : options ? . limit || 30 ,
2024-06-10 01:24:48 +00:00
aggregateCountries : true ,
2023-12-30 05:29:53 +00:00
requireAvatar : options ? . requireAvatar || false ,
2024-03-31 23:50:24 +00:00
order : [ escape ( options . order ? . [ 0 ] ) || 'name' , escape ( options . order ? . [ 1 ] ) || 'asc' ] ,
2023-12-30 05:29:53 +00:00
} ;
}
2024-03-21 01:54:05 +00:00
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 ,
2024-03-23 21:31:14 +00:00
actors . slug ,
2024-03-21 02:27:01 +00:00
actors . gender as gender ,
2024-03-21 01:54:05 +00:00
actors . country as country ,
2024-03-21 02:27:01 +00:00
actors . height as height ,
2024-03-24 03:22:37 +00:00
actors . mass as mass ,
2024-03-21 02:27:01 +00:00
actors . cup as cup ,
actors . natural _boobs as natural _boobs ,
actors . date _of _birth as date _of _birth ,
actors . has _avatar as has _avatar ,
2024-03-21 01:54:05 +00:00
actors . scenes as scenes ,
actors . stashed as stashed ,
2024-03-21 02:27:01 +00:00
created _at as stashed _at ,
2024-03-24 03:22:37 +00:00
if ( actors . date _of _birth , floor ( ( now ( ) - actors . date _of _birth ) / 31556952 ) , 0 ) as age ,
weight ( ) as _score
2024-03-21 01:54:05 +00:00
` ));
builder
. innerJoin ( 'actors' , 'actors.id' , 'actors_stashed.actor_id' )
. where ( 'stash_id' , filters . stashId ) ;
} else {
2024-03-24 03:22:37 +00:00
builder . select ( knex . raw ( '*, weight() as _score' ) ) ;
2024-03-21 01:54:05 +00:00
}
if ( filters . query ) {
2024-03-31 23:50:24 +00:00
builder . whereRaw ( 'match(\'@name :query:\', actors)' , { query : escape ( filters . query ) } ) ;
2024-03-21 01:54:05 +00:00
}
2024-03-24 17:16:10 +00:00
// attribute filters
[ 'country' ] . forEach ( ( attribute ) => {
2024-03-21 01:54:05 +00:00
if ( filters [ attribute ] ) {
builder . where ( attribute , filters [ attribute ] ) ;
}
} ) ;
2024-03-24 17:16:10 +00:00
if ( filters . gender === 'other' ) {
builder . whereNull ( 'gender' ) ;
} else if ( filters . gender ) {
builder . where ( 'gender' , filters . gender ) ;
}
if ( filters . age ) {
builder . select ( 'if(date_of_birth, floor((now() - date_of_birth) / 31556952), 0) as age' ) ;
}
// range filters
2024-03-24 03:22:37 +00:00
[ 'age' , 'height' ] . forEach ( ( attribute ) => {
2024-03-21 01:54:05 +00:00
if ( filters [ attribute ] ) {
builder
. where ( attribute , '>=' , filters [ attribute ] [ 0 ] )
. where ( attribute , '<=' , filters [ attribute ] [ 1 ] ) ;
}
} ) ;
2024-03-24 03:22:37 +00:00
if ( filters . weight ) {
// weight is a reserved keyword in manticore
builder
. where ( 'mass' , '>=' , filters . weight [ 0 ] )
. where ( 'mass' , '<=' , filters . weight [ 1 ] ) ;
}
2024-03-21 01:54:05 +00:00
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 ( ) ;
2024-03-24 17:16:10 +00:00
builder . select ( 'month(date_of_birth) as month_of_birth, day(date_of_birth) as day_of_birth' ) ;
2024-03-21 01:54:05 +00:00
builder
2024-03-24 17:16:10 +00:00
. where ( 'month_of_birth' , month )
. where ( 'day_of_birth' , day ) ;
2024-03-21 01:54:05 +00:00
}
if ( filters . cup ) {
2024-03-21 02:27:01 +00:00
builder . select ( ` regex(actors.cup, '^[ ${ filters . cup [ 0 ] } - ${ filters . cup [ 1 ] } ]') as cup_in_range ` ) ;
builder . where ( 'cup_in_range' , 1 ) ;
2024-03-21 01:54:05 +00:00
}
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' } ,
] ) ;
2024-03-24 03:22:37 +00:00
} else if ( options . order ? . [ 0 ] === 'results' ) {
builder . orderBy ( [
2024-03-24 22:36:25 +00:00
{ column : '_score' , order : options . order [ 1 ] } ,
2024-06-02 03:22:08 +00:00
{ column : 'actors.stashed' , order : 'desc' } ,
2024-03-24 03:22:37 +00:00
{ column : 'actors.slug' , order : 'asc' } ,
] ) ;
2024-03-21 01:54:05 +00:00
} 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
2024-06-10 01:24:48 +00:00
countriesFacet : options . aggregateCountries ? knex . raw ( 'facet actors.country order by count(*) desc limit :aggSize' , { aggSize } ) : null ,
2024-03-21 01:54:05 +00:00
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 ) ;
const countries = results
2024-06-10 01:24:48 +00:00
. find ( ( result ) => ( result . columns [ 0 ] . country || result . columns [ 0 ] [ 'actors.country' ] ) && result . columns [ 1 ] [ 'count(*)' ] )
? . data . map ( ( row ) => ( { key : row . country || row [ 'actors.country' ] , doc _count : row [ 'count(*)' ] } ) ) . filter ( ( country ) => ! ! country . key )
2024-03-21 01:54:05 +00:00
|| [ ] ;
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 ) ;
2024-03-21 02:27:01 +00:00
// console.log('result', result);
2024-03-21 01:54:05 +00:00
const actorIds = result . actors . map ( ( actor ) => Number ( actor . id ) ) ;
2023-12-31 02:02:03 +00:00
const [ actors , countries ] = await Promise . all ( [
2024-03-21 01:54:05 +00:00
fetchActorsById ( actorIds , { } , reqUser ) ,
fetchCountriesByAlpha2 ( result . aggregations . countries . map ( ( bucket ) => bucket . key ) ) ,
2023-12-31 02:02:03 +00:00
] ) ;
2023-12-30 05:29:53 +00:00
return {
actors ,
2023-12-31 02:02:03 +00:00
countries ,
2024-03-21 01:54:05 +00:00
total : result . total ,
2023-12-30 05:29:53 +00:00
limit : options . limit ,
} ;
}