From 280aa14e1629b394a79aa6bd5a0aee236c2f46d0 Mon Sep 17 00:00:00 2001 From: DebaucheryLibrarian Date: Wed, 11 Nov 2026 22:24:51 +0100 Subject: [PATCH] Resyncing actor scenes on gender change. --- src/actors.js | 44 +++++++++++++++++++++++++++++++++++++++++++- src/update-search.js | 6 ++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/actors.js b/src/actors.js index 37c26259..fd820993 100755 --- a/src/actors.js +++ b/src/actors.js @@ -30,7 +30,7 @@ const { toBaseReleases } = require('./deep'); const { associateAvatars, flushOrphanedMedia } = require('./media'); const { fetchEntitiesBySlug } = require('./entities'); const { deleteScenes } = require('./releases'); -const { updateActorSearch } = require('./update-search'); +const { updateActorSearch, resyncSceneSearch } = require('./update-search'); const { setBiometrics } = require('./biometrics'); const actorsCommon = import('../common/actors.mjs'); // eslint-disable-line import/extensions, import/no-relative-packages @@ -378,14 +378,56 @@ async function fetchProfiles(actorIdsOrNames) { .leftJoin('media', 'actors_profiles.avatar_media_id', 'media.id'); } +// same selection as the interpolation, null selects all actors +async function fetchActorGenders(actorIdsOrNames) { + const actors = await knex('actors') + .select('id', 'gender') + .modify((builder) => { + if (actorIdsOrNames) { + builder + .whereIn('id', actorIdsOrNames.filter((idOrName) => typeof idOrName === 'number')) + .orWhere((whereBuilder) => { + whereBuilder + .whereIn('name', actorIdsOrNames.filter((idOrName) => typeof idOrName === 'string')) + .whereNull('entity_id'); + }); + } + }); + + return Object.fromEntries(actors.map((actor) => [actor.id, actor.gender])); +} + +// scene search documents count actors per gender, so scenes need a resync when an actor's gender changes +async function resyncGenderChangedScenes(actorIdsOrNames, gendersBefore) { + const gendersAfter = await fetchActorGenders(actorIdsOrNames); + const changedActorIds = Object.keys(gendersAfter).filter((actorId) => gendersAfter[actorId] !== gendersBefore[actorId]).map(Number); + + if (changedActorIds.length === 0) { + return; + } + + const sceneIds = await knex('releases_actors') + .distinct('release_id') + .whereIn('actor_id', changedActorIds) + .pluck('release_id'); + + logger.info(`Gender changed for ${changedActorIds.length} actors, resyncing ${sceneIds.length} scenes`); + + await resyncSceneSearch(sceneIds); +} + async function interpolateProfiles(actorIdsOrNames, refreshView) { const { interpolateProfiles: interpolateProfilesUtil } = await actorsCommon; try { + const gendersBefore = await fetchActorGenders(actorIdsOrNames); + await interpolateProfilesUtil(actorIdsOrNames, commonContext, { refreshView, ...config.interpolation, }); + + await resyncGenderChangedScenes(actorIdsOrNames, gendersBefore); } catch (error) { console.log(error); } diff --git a/src/update-search.js b/src/update-search.js index d8499efa..01f74801 100644 --- a/src/update-search.js +++ b/src/update-search.js @@ -29,6 +29,11 @@ async function updateSceneSearch(releaseIds) { await syncWeb('scene', releaseIds); } +// queue scenes for resync without refreshing the materialized views, for changes the views don't depend on, e.g. actor genders +async function resyncSceneSearch(releaseIds) { + await syncWeb('scene', releaseIds); +} + async function updateMovieSearch(releaseIds) { await knex.raw('REFRESH MATERIALIZED VIEW movies_meta;'); @@ -43,6 +48,7 @@ async function updateActorSearch(actorIds) { module.exports = { updateSceneSearch, + resyncSceneSearch, updateMovieSearch, updateActorSearch, };