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

@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.DTOs;
using API.DTOs.Person;
using API.Entities.Enums;
using API.Entities.Person;
using API.Extensions;
@ -14,6 +16,17 @@ using Microsoft.EntityFrameworkCore;
namespace API.Data.Repositories;
#nullable enable
[Flags]
public enum PersonIncludes
{
None = 1 << 0,
Aliases = 1 << 1,
ChapterPeople = 1 << 2,
SeriesPeople = 1 << 3,
All = Aliases | ChapterPeople | SeriesPeople,
}
public interface IPersonRepository
{
void Attach(Person person);
@ -23,24 +36,41 @@ public interface IPersonRepository
void Remove(SeriesMetadataPeople person);
void Update(Person person);
Task<IList<Person>> GetAllPeople();
Task<IList<PersonDto>> GetAllPersonDtosAsync(int userId);
Task<IList<PersonDto>> GetAllPersonDtosByRoleAsync(int userId, PersonRole role);
Task<IList<Person>> GetAllPeople(PersonIncludes includes = PersonIncludes.Aliases);
Task<IList<PersonDto>> GetAllPersonDtosAsync(int userId, PersonIncludes includes = PersonIncludes.None);
Task<IList<PersonDto>> GetAllPersonDtosByRoleAsync(int userId, PersonRole role, PersonIncludes includes = PersonIncludes.None);
Task RemoveAllPeopleNoLongerAssociated();
Task<IList<PersonDto>> GetAllPeopleDtosForLibrariesAsync(int userId, List<int>? libraryIds = null);
Task<IList<PersonDto>> GetAllPeopleDtosForLibrariesAsync(int userId, List<int>? libraryIds = null, PersonIncludes includes = PersonIncludes.None);
Task<string?> GetCoverImageAsync(int personId);
Task<string?> GetCoverImageByNameAsync(string name);
Task<IEnumerable<PersonRole>> GetRolesForPersonByName(int personId, int userId);
Task<PagedList<BrowsePersonDto>> GetAllWritersAndSeriesCount(int userId, UserParams userParams);
Task<Person?> GetPersonById(int personId);
Task<PersonDto?> GetPersonDtoByName(string name, int userId);
Task<Person?> GetPersonById(int personId, PersonIncludes includes = PersonIncludes.None);
Task<PersonDto?> GetPersonDtoByName(string name, int userId, PersonIncludes includes = PersonIncludes.Aliases);
/// <summary>
/// Returns a person matched on normalized name or alias
/// </summary>
/// <param name="name"></param>
/// <param name="includes"></param>
/// <returns></returns>
Task<Person?> GetPersonByNameOrAliasAsync(string name, PersonIncludes includes = PersonIncludes.Aliases);
Task<bool> IsNameUnique(string name);
Task<IEnumerable<SeriesDto>> GetSeriesKnownFor(int personId);
Task<IEnumerable<StandaloneChapterDto>> GetChaptersForPersonByRole(int personId, int userId, PersonRole role);
Task<IList<Person>> GetPeopleByNames(List<string> normalizedNames);
Task<Person?> GetPersonByAniListId(int aniListId);
/// <summary>
/// Returns all people with a matching name, or alias
/// </summary>
/// <param name="normalizedNames"></param>
/// <param name="includes"></param>
/// <returns></returns>
Task<IList<Person>> GetPeopleByNames(List<string> normalizedNames, PersonIncludes includes = PersonIncludes.Aliases);
Task<Person?> GetPersonByAniListId(int aniListId, PersonIncludes includes = PersonIncludes.Aliases);
Task<IList<PersonDto>> SearchPeople(string searchQuery, PersonIncludes includes = PersonIncludes.Aliases);
Task<bool> AnyAliasExist(string alias);
}
public class PersonRepository : IPersonRepository
@ -99,7 +129,7 @@ public class PersonRepository : IPersonRepository
}
public async Task<IList<PersonDto>> GetAllPeopleDtosForLibrariesAsync(int userId, List<int>? libraryIds = null)
public async Task<IList<PersonDto>> GetAllPeopleDtosForLibrariesAsync(int userId, List<int>? libraryIds = null, PersonIncludes includes = PersonIncludes.Aliases)
{
var ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
var userLibs = await _context.Library.GetUserLibraries(userId).ToListAsync();
@ -113,6 +143,7 @@ public class PersonRepository : IPersonRepository
.Where(s => userLibs.Contains(s.LibraryId))
.RestrictAgainstAgeRestriction(ageRating)
.SelectMany(s => s.Metadata.People.Select(p => p.Person))
.Includes(includes)
.Distinct()
.OrderBy(p => p.Name)
.AsNoTracking()
@ -193,27 +224,41 @@ public class PersonRepository : IPersonRepository
return await PagedList<BrowsePersonDto>.CreateAsync(query, userParams.PageNumber, userParams.PageSize);
}
public async Task<Person?> GetPersonById(int personId)
public async Task<Person?> GetPersonById(int personId, PersonIncludes includes = PersonIncludes.None)
{
return await _context.Person.Where(p => p.Id == personId)
.Includes(includes)
.FirstOrDefaultAsync();
}
public async Task<PersonDto?> GetPersonDtoByName(string name, int userId)
public async Task<PersonDto?> GetPersonDtoByName(string name, int userId, PersonIncludes includes = PersonIncludes.Aliases)
{
var normalized = name.ToNormalized();
var ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
return await _context.Person
.Where(p => p.NormalizedName == normalized)
.Includes(includes)
.RestrictAgainstAgeRestriction(ageRating)
.ProjectTo<PersonDto>(_mapper.ConfigurationProvider)
.FirstOrDefaultAsync();
}
public Task<Person?> GetPersonByNameOrAliasAsync(string name, PersonIncludes includes = PersonIncludes.Aliases)
{
var normalized = name.ToNormalized();
return _context.Person
.Includes(includes)
.Where(p => p.NormalizedName == normalized || p.Aliases.Any(pa => pa.NormalizedAlias == normalized))
.FirstOrDefaultAsync();
}
public async Task<bool> IsNameUnique(string name)
{
return !(await _context.Person.AnyAsync(p => p.Name == name));
// Should this use Normalized to check?
return !(await _context.Person
.Includes(PersonIncludes.Aliases)
.AnyAsync(p => p.Name == name || p.Aliases.Any(pa => pa.Alias == name)));
}
public async Task<IEnumerable<SeriesDto>> GetSeriesKnownFor(int personId)
@ -245,45 +290,69 @@ public class PersonRepository : IPersonRepository
.ToListAsync();
}
public async Task<IList<Person>> GetPeopleByNames(List<string> normalizedNames)
public async Task<IList<Person>> GetPeopleByNames(List<string> normalizedNames, PersonIncludes includes = PersonIncludes.Aliases)
{
return await _context.Person
.Where(p => normalizedNames.Contains(p.NormalizedName))
.Includes(includes)
.Where(p => normalizedNames.Contains(p.NormalizedName) || p.Aliases.Any(pa => normalizedNames.Contains(pa.NormalizedAlias)))
.OrderBy(p => p.Name)
.ToListAsync();
}
public async Task<Person?> GetPersonByAniListId(int aniListId)
public async Task<Person?> GetPersonByAniListId(int aniListId, PersonIncludes includes = PersonIncludes.Aliases)
{
return await _context.Person
.Where(p => p.AniListId == aniListId)
.Includes(includes)
.FirstOrDefaultAsync();
}
public async Task<IList<Person>> GetAllPeople()
public async Task<IList<PersonDto>> SearchPeople(string searchQuery, PersonIncludes includes = PersonIncludes.Aliases)
{
searchQuery = searchQuery.ToNormalized();
return await _context.Person
.Includes(includes)
.Where(p => EF.Functions.Like(p.Name, $"%{searchQuery}%")
|| p.Aliases.Any(pa => EF.Functions.Like(pa.Alias, $"%{searchQuery}%")))
.ProjectTo<PersonDto>(_mapper.ConfigurationProvider)
.ToListAsync();
}
public async Task<bool> AnyAliasExist(string alias)
{
return await _context.PersonAlias.AnyAsync(pa => pa.NormalizedAlias == alias.ToNormalized());
}
public async Task<IList<Person>> GetAllPeople(PersonIncludes includes = PersonIncludes.Aliases)
{
return await _context.Person
.Includes(includes)
.OrderBy(p => p.Name)
.ToListAsync();
}
public async Task<IList<PersonDto>> GetAllPersonDtosAsync(int userId)
public async Task<IList<PersonDto>> GetAllPersonDtosAsync(int userId, PersonIncludes includes = PersonIncludes.Aliases)
{
var ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
return await _context.Person
.Includes(includes)
.OrderBy(p => p.Name)
.RestrictAgainstAgeRestriction(ageRating)
.ProjectTo<PersonDto>(_mapper.ConfigurationProvider)
.ToListAsync();
}
public async Task<IList<PersonDto>> GetAllPersonDtosByRoleAsync(int userId, PersonRole role)
public async Task<IList<PersonDto>> GetAllPersonDtosByRoleAsync(int userId, PersonRole role, PersonIncludes includes = PersonIncludes.Aliases)
{
var ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
return await _context.Person
.Where(p => p.SeriesMetadataPeople.Any(smp => smp.Role == role) || p.ChapterPeople.Any(cp => cp.Role == role)) // Filter by role in both series and chapters
.Includes(includes)
.OrderBy(p => p.Name)
.RestrictAgainstAgeRestriction(ageRating)
.ProjectTo<PersonDto>(_mapper.ConfigurationProvider)