People Aliases and Merging (#3795)

Co-authored-by: Joseph Milazzo <josephmajora@gmail.com>
This commit is contained in:
Fesaa 2025-05-10 00:18:13 +02:00 committed by GitHub
parent cd2a6af6f2
commit 7ce36bfc44
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
67 changed files with 5288 additions and 284 deletions

View file

@ -15,6 +15,7 @@ using API.DTOs.KavitaPlus.Manage;
using API.DTOs.KavitaPlus.Metadata;
using API.DTOs.MediaErrors;
using API.DTOs.Metadata;
using API.DTOs.Person;
using API.DTOs.Progress;
using API.DTOs.Reader;
using API.DTOs.ReadingLists;
@ -68,7 +69,8 @@ public class AutoMapperProfiles : Profile
CreateMap<AppUserCollection, AppUserCollectionDto>()
.ForMember(dest => dest.Owner, opt => opt.MapFrom(src => src.AppUser.UserName))
.ForMember(dest => dest.ItemCount, opt => opt.MapFrom(src => src.Items.Count));
CreateMap<Person, PersonDto>();
CreateMap<Person, PersonDto>()
.ForMember(dest => dest.Aliases, opt => opt.MapFrom(src => src.Aliases.Select(s => s.Alias)));
CreateMap<Genre, GenreTagDto>();
CreateMap<Tag, TagDto>();
CreateMap<AgeRating, AgeRatingDto>();

View file

@ -0,0 +1,19 @@
using API.Entities.Person;
using API.Extensions;
namespace API.Helpers.Builders;
public class PersonAliasBuilder : IEntityBuilder<PersonAlias>
{
private readonly PersonAlias _alias;
public PersonAlias Build() => _alias;
public PersonAliasBuilder(string name)
{
_alias = new PersonAlias()
{
Alias = name.Trim(),
NormalizedAlias = name.ToNormalized(),
};
}
}

View file

@ -1,7 +1,5 @@
using System.Collections.Generic;
using API.Entities;
using API.Entities.Enums;
using API.Entities.Metadata;
using System.Linq;
using API.Entities.Person;
using API.Extensions;
@ -34,6 +32,20 @@ public class PersonBuilder : IEntityBuilder<Person>
return this;
}
public PersonBuilder WithAlias(string alias)
{
if (_person.Aliases.Any(a => a.NormalizedAlias.Equals(alias.ToNormalized())))
{
return this;
}
_person.Aliases.Add(new PersonAliasBuilder(alias).Build());
return this;
}
public PersonBuilder WithSeriesMetadata(SeriesMetadataPeople seriesMetadataPeople)
{
_person.SeriesMetadataPeople.Add(seriesMetadataPeople);

View file

@ -17,6 +17,20 @@ namespace API.Helpers;
public static class PersonHelper
{
public static Dictionary<string, Person> ConstructNameAndAliasDictionary(IList<Person> people)
{
var dict = new Dictionary<string, Person>();
foreach (var person in people)
{
dict.TryAdd(person.NormalizedName, person);
foreach (var alias in person.Aliases)
{
dict.TryAdd(alias.NormalizedAlias, person);
}
}
return dict;
}
public static async Task UpdateSeriesMetadataPeopleAsync(SeriesMetadata metadata, ICollection<SeriesMetadataPeople> metadataPeople,
IEnumerable<ChapterPeople> chapterPeople, PersonRole role, IUnitOfWork unitOfWork)
{
@ -38,7 +52,9 @@ public static class PersonHelper
// Identify people to remove from metadataPeople
var peopleToRemove = existingMetadataPeople
.Where(person => !peopleToAddSet.Contains(person.Person.NormalizedName))
.Where(person =>
!peopleToAddSet.Contains(person.Person.NormalizedName) &&
!person.Person.Aliases.Any(pa => peopleToAddSet.Contains(pa.NormalizedAlias)))
.ToList();
// Remove identified people from metadataPeople
@ -53,11 +69,7 @@ public static class PersonHelper
.GetPeopleByNames(peopleToAdd.Select(p => p.NormalizedName).ToList());
// Prepare a dictionary for quick lookup of existing people by normalized name
var existingPeopleDict = new Dictionary<string, Person>();
foreach (var person in existingPeopleInDb)
{
existingPeopleDict.TryAdd(person.NormalizedName, person);
}
var existingPeopleDict = ConstructNameAndAliasDictionary(existingPeopleInDb);
// Track the people to attach (newly created people)
var peopleToAttach = new List<Person>();
@ -129,15 +141,12 @@ public static class PersonHelper
var existingPeople = await unitOfWork.PersonRepository.GetPeopleByNames(normalizedPeople);
// Prepare a dictionary for quick lookup by normalized name
var existingPeopleDict = new Dictionary<string, Person>();
foreach (var person in existingPeople)
{
existingPeopleDict.TryAdd(person.NormalizedName, person);
}
var existingPeopleDict = ConstructNameAndAliasDictionary(existingPeople);
// Identify people to remove (those present in ChapterPeople but not in the new list)
foreach (var existingChapterPerson in existingChapterPeople
.Where(existingChapterPerson => !normalizedPeople.Contains(existingChapterPerson.Person.NormalizedName)))
var toRemove = existingChapterPeople
.Where(existingChapterPerson => !normalizedPeople.Contains(existingChapterPerson.Person.NormalizedName));
foreach (var existingChapterPerson in toRemove)
{
chapter.People.Remove(existingChapterPerson);
unitOfWork.PersonRepository.Remove(existingChapterPerson);