diff --git a/src/actors.js b/src/actors.js index 4b27832..a74f848 100644 --- a/src/actors.js +++ b/src/actors.js @@ -598,9 +598,11 @@ export async function mergeActors(targetActorId, sourceActorIds, reqUser) { const trx = await knex.transaction(); - let mergedProfiles; - let mergedSceneActors; - let existingSceneActors; + let mergedProfiles = []; + let mergedSceneActors = []; + let existingSceneActors = []; + let duplicateSourceActors = []; + let mergedActorStashes = []; try { const [existingProfiles] = await Promise.all([ @@ -614,36 +616,83 @@ export async function mergeActors(targetActorId, sourceActorIds, reqUser) { trx('actors_avatars') .update('actor_id', targetActorId) .whereIn('actor_id', sourceActorIds), - trx('stashes_actors') - .update('actor_id', targetActorId) - .whereIn('actor_id', sourceActorIds) - .returning('id'), ]); - existingSceneActors = await trx('releases_actors') - .where('actor_id', targetActorId); - + // assign source actor profiles to target actor, unless a profile for that entity is already present mergedProfiles = await trx('actors_profiles') .update('actor_id', targetActorId) .whereIn('actor_id', sourceActorIds) .whereNotIn('entity_id', existingProfiles.map((profile) => profile.entity_id)) .returning('id'); + // find releases that have more than one source actor assigned + duplicateSourceActors = await trx('releases_actors') + .select('release_id', knex.raw('array_agg(actor_id) as actor_ids')) + .whereIn('actor_id', sourceActorIds) + .groupBy('release_id') + .having(trx.raw('COUNT(DISTINCT actor_id) > 1')); + + if (duplicateSourceActors.length > 0) { + // some scenes have multiple source actors assigned, which will cause a conflict after merging; we will need to remove all but one + await trx('releases_actors') + .whereIn('release_id', duplicateSourceActors.map((sceneActor) => sceneActor.release_id)) + .whereIn('actor_id', duplicateSourceActors.flatMap((sceneActor) => sceneActor.actor_ids.slice(1))) + .delete(); + } + + // find scenes that already have target actor assigned + existingSceneActors = await trx('releases_actors') + .where('actor_id', targetActorId); + + // delete release source actors for scenes that already have the target actor assigned + await trx('releases_actors') + .whereIn('release_id', existingSceneActors.map((sceneActor) => sceneActor.release_id)) + .whereIn('actor_id', sourceActorIds) + .delete(); + + // alias release source actors to target actors mergedSceneActors = await trx('releases_actors') .update({ actor_id: targetActorId, alias_id: knex.raw('actor_id'), }) .whereIn('actor_id', sourceActorIds) - .whereNotIn('release_id', existingSceneActors.map((sceneActor) => sceneActor.release_id)) // can't update entry if target actor already exists alongside source actor .returning('release_id'); - // delete aliased actors for scenes that already had both source/aliased and target actor assigned - const releases = await trx('releases_actors') - .whereIn('release_id', existingSceneActors.map((sceneActor) => sceneActor.release_id)) + const [targetActorStashes, sourceActorStashes] = await Promise.all([ + trx('stashes_actors') + .where('actor_id', targetActorId), + trx('stashes_actors') + .whereIn('actor_id', sourceActorIds), + ]); + + // remove source actors from stashes that already contain target actor + await trx('stashes_actors') + .whereIn('stash_id', targetActorStashes.map((stash) => stash.stash_id)) .whereIn('actor_id', sourceActorIds) .delete(); + // find stashes that have more than one source actor assigned + const duplicateStashActors = await trx('stashes_actors') + .select('stash_id', knex.raw('array_agg(actor_id order by created_at) as actor_ids')) + .whereIn('actor_id', sourceActorStashes.map((actorStash) => actorStash.actor_id)) + .groupBy('stash_id') + .having(trx.raw('COUNT(DISTINCT actor_id) > 1')); + + if (duplicateStashActors.length > 0) { + // some stashes have multiple source actors assigned, which will cause a conflict after merging; we will need to remove all but one + await trx('stashes_actors') + .whereIn('stash_id', duplicateStashActors.map((actorStash) => actorStash.stash_id)) + .whereIn('actor_id', duplicateStashActors.flatMap((actorStash) => actorStash.actor_ids.slice(1))) + .delete(); + } + + // we update an existing entry instead of creating a new one, so the original stash date is preserved + mergedActorStashes = await trx('stashes_actors') + .update('actor_id', targetActorId) + .whereIn('actor_id', sourceActorIds) + .returning('stash_id'); + await trx.commit(); } catch (error) { await trx.rollback(); @@ -660,7 +709,11 @@ export async function mergeActors(targetActorId, sourceActorIds, reqUser) { }, { refreshView: false }); await Promise.all([ - syncScenes([...mergedSceneActors.map((sceneActor) => sceneActor.release_id), ...existingSceneActors.map((sceneActor) => sceneActor.release_id)]), + syncScenes([ + ...mergedSceneActors.map((sceneActor) => sceneActor.release_id), + ...existingSceneActors.map((sceneActor) => sceneActor.release_id), + ...duplicateSourceActors.map((sceneActor) => sceneActor.release_id), + ]), syncActors([targetActorId, ...sourceActorIds]), syncStashes('actor', [targetActorId, ...sourceActorIds]), ]); @@ -668,6 +721,7 @@ export async function mergeActors(targetActorId, sourceActorIds, reqUser) { return { scenes: mergedSceneActors.length, profiles: mergedProfiles.length, + stashes: mergedActorStashes.length, }; }