Write out the code needed to pass the unit tests (no UI)
This commit is contained in:
parent
96d130d0b5
commit
5ece8503ff
12 changed files with 3908 additions and 40 deletions
|
|
@ -221,4 +221,6 @@ public class PersonHelperTests : AbstractDbTest
|
||||||
allPeople = await UnitOfWork.PersonRepository.GetAllPeople();
|
allPeople = await UnitOfWork.PersonRepository.GetAllPeople();
|
||||||
Assert.Single(allPeople);
|
Assert.Single(allPeople);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Unit tests for series
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1692,6 +1692,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest
|
||||||
.Build())
|
.Build())
|
||||||
.Build();
|
.Build();
|
||||||
Context.Series.Attach(series);
|
Context.Series.Attach(series);
|
||||||
|
Context.Person.Add(new PersonBuilder("John Doe").Build());
|
||||||
await Context.SaveChangesAsync();
|
await Context.SaveChangesAsync();
|
||||||
|
|
||||||
var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings();
|
var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings();
|
||||||
|
|
@ -1706,7 +1707,50 @@ public class ExternalMetadataServiceTests : AbstractDbTest
|
||||||
await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto()
|
await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto()
|
||||||
{
|
{
|
||||||
Name = seriesName,
|
Name = seriesName,
|
||||||
Staff = [CreateStaff("John", "Doe", "Story"), CreateStaff("Doe", "John", "Story")]
|
Staff = [CreateStaff("Doe", "John", "Story")]
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata);
|
||||||
|
Assert.NotNull(postSeries);
|
||||||
|
|
||||||
|
var allWriters = postSeries.Metadata.People.Where(p => p.Role == PersonRole.Writer).ToList();
|
||||||
|
Assert.Single(allWriters);
|
||||||
|
|
||||||
|
var johnDoe = allWriters[0].Person;
|
||||||
|
|
||||||
|
Assert.Contains("Doe John", johnDoe.Aliases.Select(pa => pa.Alias));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PeopleAliasing_AddOnAlias()
|
||||||
|
{
|
||||||
|
await ResetDb();
|
||||||
|
|
||||||
|
const string seriesName = "Test - People - Add as Alias";
|
||||||
|
var series = new SeriesBuilder(seriesName)
|
||||||
|
.WithLibraryId(1)
|
||||||
|
.WithMetadata(new SeriesMetadataBuilder()
|
||||||
|
.Build())
|
||||||
|
.Build();
|
||||||
|
Context.Series.Attach(series);
|
||||||
|
|
||||||
|
Context.Person.Add(new PersonBuilder("John Doe").WithAlias("Doe John").Build());
|
||||||
|
|
||||||
|
await Context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings();
|
||||||
|
metadataSettings.Enabled = true;
|
||||||
|
metadataSettings.EnablePeople = true;
|
||||||
|
metadataSettings.FirstLastPeopleNaming = true;
|
||||||
|
metadataSettings.Overrides = [MetadataSettingField.People];
|
||||||
|
metadataSettings.PersonRoles = [PersonRole.Writer];
|
||||||
|
Context.MetadataSettings.Update(metadataSettings);
|
||||||
|
await Context.SaveChangesAsync();
|
||||||
|
|
||||||
|
await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto()
|
||||||
|
{
|
||||||
|
Name = seriesName,
|
||||||
|
Staff = [CreateStaff("Doe", "John", "Story")]
|
||||||
}, 1);
|
}, 1);
|
||||||
|
|
||||||
var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata);
|
var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using API.Data.Repositories;
|
||||||
|
using API.Entities;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
using API.Entities.Person;
|
using API.Entities.Person;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
|
|
@ -86,6 +88,10 @@ public class PersonServiceTests: AbstractDbTest
|
||||||
UnitOfWork.LibraryRepository.Add(library);
|
UnitOfWork.LibraryRepository.Add(library);
|
||||||
await UnitOfWork.CommitAsync();
|
await UnitOfWork.CommitAsync();
|
||||||
|
|
||||||
|
var user = new AppUserBuilder("Amelia", "amelia@localhost")
|
||||||
|
.WithLibrary(library).Build();
|
||||||
|
UnitOfWork.UserRepository.Add(user);
|
||||||
|
|
||||||
var person = new PersonBuilder("Jillian Cowan").Build();
|
var person = new PersonBuilder("Jillian Cowan").Build();
|
||||||
|
|
||||||
var person2 = new PersonBuilder("Cowan Jillian").Build();
|
var person2 = new PersonBuilder("Cowan Jillian").Build();
|
||||||
|
|
@ -124,9 +130,18 @@ public class PersonServiceTests: AbstractDbTest
|
||||||
|
|
||||||
Assert.Equal("Jillian Cowan", mergedPerson.Name);
|
Assert.Equal("Jillian Cowan", mergedPerson.Name);
|
||||||
|
|
||||||
// UserId here might bug out?
|
|
||||||
var chapters = await UnitOfWork.PersonRepository.GetChaptersForPersonByRole(1, 1, PersonRole.Editor);
|
var chapters = await UnitOfWork.PersonRepository.GetChaptersForPersonByRole(1, 1, PersonRole.Editor);
|
||||||
Assert.Equal(2, chapters.Count());
|
Assert.Equal(2, chapters.Count());
|
||||||
|
|
||||||
|
chapter = await UnitOfWork.ChapterRepository.GetChapterAsync(1, ChapterIncludes.People);
|
||||||
|
Assert.NotNull(chapter);
|
||||||
|
Assert.Single(chapter.People);
|
||||||
|
|
||||||
|
chapter2 = await UnitOfWork.ChapterRepository.GetChapterAsync(2, ChapterIncludes.People);
|
||||||
|
Assert.NotNull(chapter2);
|
||||||
|
Assert.Single(chapter2.People);
|
||||||
|
|
||||||
|
Assert.Equal(chapter.People.First().PersonId, chapter2.People.First().PersonId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task ResetDb()
|
protected override async Task ResetDb()
|
||||||
|
|
|
||||||
3567
API/Data/Migrations/20250504212806_PersonAliases.Designer.cs
generated
Normal file
3567
API/Data/Migrations/20250504212806_PersonAliases.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
46
API/Data/Migrations/20250504212806_PersonAliases.cs
Normal file
46
API/Data/Migrations/20250504212806_PersonAliases.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace API.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class PersonAliases : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PersonAlias",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
Alias = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
NormalizedAlias = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
PersonId = table.Column<int>(type: "INTEGER", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_PersonAlias", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PersonAlias_Person_PersonId",
|
||||||
|
column: x => x.PersonId,
|
||||||
|
principalTable: "Person",
|
||||||
|
principalColumn: "Id");
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PersonAlias_PersonId",
|
||||||
|
table: "PersonAlias",
|
||||||
|
column: "PersonId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "PersonAlias");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1836,6 +1836,28 @@ namespace API.Data.Migrations
|
||||||
b.ToTable("Person");
|
b.ToTable("Person");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.Person.PersonAlias", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Alias")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedAlias")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("PersonId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("PersonId");
|
||||||
|
|
||||||
|
b.ToTable("PersonAlias");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.Person.SeriesMetadataPeople", b =>
|
modelBuilder.Entity("API.Entities.Person.SeriesMetadataPeople", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("SeriesMetadataId")
|
b.Property<int>("SeriesMetadataId")
|
||||||
|
|
@ -3082,6 +3104,13 @@ namespace API.Data.Migrations
|
||||||
b.Navigation("Person");
|
b.Navigation("Person");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.Person.PersonAlias", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Entities.Person.Person", null)
|
||||||
|
.WithMany("Aliases")
|
||||||
|
.HasForeignKey("PersonId");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.Person.SeriesMetadataPeople", b =>
|
modelBuilder.Entity("API.Entities.Person.SeriesMetadataPeople", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Entities.Person.Person", "Person")
|
b.HasOne("API.Entities.Person.Person", "Person")
|
||||||
|
|
@ -3496,6 +3525,8 @@ namespace API.Data.Migrations
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.Person.Person", b =>
|
modelBuilder.Entity("API.Entities.Person.Person", b =>
|
||||||
{
|
{
|
||||||
|
b.Navigation("Aliases");
|
||||||
|
|
||||||
b.Navigation("ChapterPeople");
|
b.Navigation("ChapterPeople");
|
||||||
|
|
||||||
b.Navigation("SeriesMetadataPeople");
|
b.Navigation("SeriesMetadataPeople");
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -14,6 +15,13 @@ using Microsoft.EntityFrameworkCore;
|
||||||
namespace API.Data.Repositories;
|
namespace API.Data.Repositories;
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum PersonIncludes
|
||||||
|
{
|
||||||
|
None = 1 << 0,
|
||||||
|
Aliases = 1 << 1,
|
||||||
|
}
|
||||||
|
|
||||||
public interface IPersonRepository
|
public interface IPersonRepository
|
||||||
{
|
{
|
||||||
void Attach(Person person);
|
void Attach(Person person);
|
||||||
|
|
@ -23,24 +31,37 @@ public interface IPersonRepository
|
||||||
void Remove(SeriesMetadataPeople person);
|
void Remove(SeriesMetadataPeople person);
|
||||||
void Update(Person person);
|
void Update(Person person);
|
||||||
|
|
||||||
Task<IList<Person>> GetAllPeople();
|
Task<IList<Person>> GetAllPeople(PersonIncludes includes = PersonIncludes.Aliases);
|
||||||
Task<IList<PersonDto>> GetAllPersonDtosAsync(int userId);
|
Task<IList<PersonDto>> GetAllPersonDtosAsync(int userId, PersonIncludes includes = PersonIncludes.Aliases);
|
||||||
Task<IList<PersonDto>> GetAllPersonDtosByRoleAsync(int userId, PersonRole role);
|
Task<IList<PersonDto>> GetAllPersonDtosByRoleAsync(int userId, PersonRole role, PersonIncludes includes = PersonIncludes.Aliases);
|
||||||
Task RemoveAllPeopleNoLongerAssociated();
|
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.Aliases);
|
||||||
|
|
||||||
Task<string?> GetCoverImageAsync(int personId);
|
Task<string?> GetCoverImageAsync(int personId);
|
||||||
Task<string?> GetCoverImageByNameAsync(string name);
|
Task<string?> GetCoverImageByNameAsync(string name);
|
||||||
Task<IEnumerable<PersonRole>> GetRolesForPersonByName(int personId, int userId);
|
Task<IEnumerable<PersonRole>> GetRolesForPersonByName(int personId, int userId);
|
||||||
Task<PagedList<BrowsePersonDto>> GetAllWritersAndSeriesCount(int userId, UserParams userParams);
|
Task<PagedList<BrowsePersonDto>> GetAllWritersAndSeriesCount(int userId, UserParams userParams);
|
||||||
Task<Person?> GetPersonById(int personId);
|
Task<Person?> GetPersonById(int personId, PersonIncludes includes = PersonIncludes.Aliases);
|
||||||
Task<PersonDto?> GetPersonDtoByName(string name, int userId);
|
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?> GetPersonByNameAsync(string name, PersonIncludes includes = PersonIncludes.Aliases);
|
||||||
Task<bool> IsNameUnique(string name);
|
Task<bool> IsNameUnique(string name);
|
||||||
|
|
||||||
Task<IEnumerable<SeriesDto>> GetSeriesKnownFor(int personId);
|
Task<IEnumerable<SeriesDto>> GetSeriesKnownFor(int personId);
|
||||||
Task<IEnumerable<StandaloneChapterDto>> GetChaptersForPersonByRole(int personId, int userId, PersonRole role);
|
Task<IEnumerable<StandaloneChapterDto>> GetChaptersForPersonByRole(int personId, int userId, PersonRole role);
|
||||||
Task<IList<Person>> GetPeopleByNames(List<string> normalizedNames);
|
/// <summary>
|
||||||
Task<Person?> GetPersonByAniListId(int aniListId);
|
/// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PersonRepository : IPersonRepository
|
public class PersonRepository : IPersonRepository
|
||||||
|
|
@ -99,7 +120,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 ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
var userLibs = await _context.Library.GetUserLibraries(userId).ToListAsync();
|
var userLibs = await _context.Library.GetUserLibraries(userId).ToListAsync();
|
||||||
|
|
@ -113,6 +134,7 @@ public class PersonRepository : IPersonRepository
|
||||||
.Where(s => userLibs.Contains(s.LibraryId))
|
.Where(s => userLibs.Contains(s.LibraryId))
|
||||||
.RestrictAgainstAgeRestriction(ageRating)
|
.RestrictAgainstAgeRestriction(ageRating)
|
||||||
.SelectMany(s => s.Metadata.People.Select(p => p.Person))
|
.SelectMany(s => s.Metadata.People.Select(p => p.Person))
|
||||||
|
.Includes(includes)
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.OrderBy(p => p.Name)
|
.OrderBy(p => p.Name)
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
|
|
@ -193,24 +215,35 @@ public class PersonRepository : IPersonRepository
|
||||||
return await PagedList<BrowsePersonDto>.CreateAsync(query, userParams.PageNumber, userParams.PageSize);
|
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.Aliases)
|
||||||
{
|
{
|
||||||
return await _context.Person.Where(p => p.Id == personId)
|
return await _context.Person.Where(p => p.Id == personId)
|
||||||
|
.Includes(includes)
|
||||||
.FirstOrDefaultAsync();
|
.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 normalized = name.ToNormalized();
|
||||||
var ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
var ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
|
|
||||||
return await _context.Person
|
return await _context.Person
|
||||||
.Where(p => p.NormalizedName == normalized)
|
.Where(p => p.NormalizedName == normalized)
|
||||||
|
.Includes(includes)
|
||||||
.RestrictAgainstAgeRestriction(ageRating)
|
.RestrictAgainstAgeRestriction(ageRating)
|
||||||
.ProjectTo<PersonDto>(_mapper.ConfigurationProvider)
|
.ProjectTo<PersonDto>(_mapper.ConfigurationProvider)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<Person?> GetPersonByNameAsync(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)
|
public async Task<bool> IsNameUnique(string name)
|
||||||
{
|
{
|
||||||
return !(await _context.Person.AnyAsync(p => p.Name == name));
|
return !(await _context.Person.AnyAsync(p => p.Name == name));
|
||||||
|
|
@ -245,45 +278,50 @@ public class PersonRepository : IPersonRepository
|
||||||
.ToListAsync();
|
.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
|
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)
|
.OrderBy(p => p.Name)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Person?> GetPersonByAniListId(int aniListId)
|
public async Task<Person?> GetPersonByAniListId(int aniListId, PersonIncludes includes = PersonIncludes.Aliases)
|
||||||
{
|
{
|
||||||
return await _context.Person
|
return await _context.Person
|
||||||
.Where(p => p.AniListId == aniListId)
|
.Where(p => p.AniListId == aniListId)
|
||||||
|
.Includes(includes)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IList<Person>> GetAllPeople()
|
public async Task<IList<Person>> GetAllPeople(PersonIncludes includes = PersonIncludes.Aliases)
|
||||||
{
|
{
|
||||||
return await _context.Person
|
return await _context.Person
|
||||||
|
.Includes(includes)
|
||||||
.OrderBy(p => p.Name)
|
.OrderBy(p => p.Name)
|
||||||
.ToListAsync();
|
.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);
|
var ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
|
|
||||||
return await _context.Person
|
return await _context.Person
|
||||||
|
.Includes(includes)
|
||||||
.OrderBy(p => p.Name)
|
.OrderBy(p => p.Name)
|
||||||
.RestrictAgainstAgeRestriction(ageRating)
|
.RestrictAgainstAgeRestriction(ageRating)
|
||||||
.ProjectTo<PersonDto>(_mapper.ConfigurationProvider)
|
.ProjectTo<PersonDto>(_mapper.ConfigurationProvider)
|
||||||
.ToListAsync();
|
.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);
|
var ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
|
|
||||||
return await _context.Person
|
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
|
.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)
|
.OrderBy(p => p.Name)
|
||||||
.RestrictAgainstAgeRestriction(ageRating)
|
.RestrictAgainstAgeRestriction(ageRating)
|
||||||
.ProjectTo<PersonDto>(_mapper.ConfigurationProvider)
|
.ProjectTo<PersonDto>(_mapper.ConfigurationProvider)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using API.Data.Repositories;
|
using API.Data.Repositories;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Entities.Metadata;
|
using API.Entities.Metadata;
|
||||||
|
using API.Entities.Person;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace API.Extensions.QueryExtensions;
|
namespace API.Extensions.QueryExtensions;
|
||||||
|
|
@ -321,4 +322,15 @@ public static class IncludesExtensions
|
||||||
|
|
||||||
return query.AsSplitQuery();
|
return query.AsSplitQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IQueryable<Person> Includes(this IQueryable<Person> queryable, PersonIncludes includeFlags)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (includeFlags.HasFlag(PersonIncludes.Aliases))
|
||||||
|
{
|
||||||
|
queryable = queryable.Include(p => p.Aliases);
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,20 @@ namespace API.Helpers;
|
||||||
public static class PersonHelper
|
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,
|
public static async Task UpdateSeriesMetadataPeopleAsync(SeriesMetadata metadata, ICollection<SeriesMetadataPeople> metadataPeople,
|
||||||
IEnumerable<ChapterPeople> chapterPeople, PersonRole role, IUnitOfWork unitOfWork)
|
IEnumerable<ChapterPeople> chapterPeople, PersonRole role, IUnitOfWork unitOfWork)
|
||||||
{
|
{
|
||||||
|
|
@ -38,7 +52,9 @@ public static class PersonHelper
|
||||||
|
|
||||||
// Identify people to remove from metadataPeople
|
// Identify people to remove from metadataPeople
|
||||||
var peopleToRemove = existingMetadataPeople
|
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();
|
.ToList();
|
||||||
|
|
||||||
// Remove identified people from metadataPeople
|
// Remove identified people from metadataPeople
|
||||||
|
|
@ -53,11 +69,7 @@ public static class PersonHelper
|
||||||
.GetPeopleByNames(peopleToAdd.Select(p => p.NormalizedName).ToList());
|
.GetPeopleByNames(peopleToAdd.Select(p => p.NormalizedName).ToList());
|
||||||
|
|
||||||
// Prepare a dictionary for quick lookup of existing people by normalized name
|
// Prepare a dictionary for quick lookup of existing people by normalized name
|
||||||
var existingPeopleDict = new Dictionary<string, Person>();
|
var existingPeopleDict = ConstructNameAndAliasDictionary(existingPeopleInDb);
|
||||||
foreach (var person in existingPeopleInDb)
|
|
||||||
{
|
|
||||||
existingPeopleDict.TryAdd(person.NormalizedName, person);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track the people to attach (newly created people)
|
// Track the people to attach (newly created people)
|
||||||
var peopleToAttach = new List<Person>();
|
var peopleToAttach = new List<Person>();
|
||||||
|
|
@ -129,15 +141,12 @@ public static class PersonHelper
|
||||||
var existingPeople = await unitOfWork.PersonRepository.GetPeopleByNames(normalizedPeople);
|
var existingPeople = await unitOfWork.PersonRepository.GetPeopleByNames(normalizedPeople);
|
||||||
|
|
||||||
// Prepare a dictionary for quick lookup by normalized name
|
// Prepare a dictionary for quick lookup by normalized name
|
||||||
var existingPeopleDict = new Dictionary<string, Person>();
|
var existingPeopleDict = ConstructNameAndAliasDictionary(existingPeople);
|
||||||
foreach (var person in existingPeople)
|
|
||||||
{
|
|
||||||
existingPeopleDict.TryAdd(person.NormalizedName, person);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Identify people to remove (those present in ChapterPeople but not in the new list)
|
// Identify people to remove (those present in ChapterPeople but not in the new list)
|
||||||
foreach (var existingChapterPerson in existingChapterPeople
|
var toRemove = existingChapterPeople
|
||||||
.Where(existingChapterPerson => !normalizedPeople.Contains(existingChapterPerson.Person.NormalizedName)))
|
.Where(existingChapterPerson => !normalizedPeople.Contains(existingChapterPerson.Person.NormalizedName));
|
||||||
|
foreach (var existingChapterPerson in toRemove)
|
||||||
{
|
{
|
||||||
chapter.People.Remove(existingChapterPerson);
|
chapter.People.Remove(existingChapterPerson);
|
||||||
unitOfWork.PersonRepository.Remove(existingChapterPerson);
|
unitOfWork.PersonRepository.Remove(existingChapterPerson);
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,68 @@ public class PersonService(IUnitOfWork unitOfWork): IPersonService
|
||||||
|
|
||||||
public async Task MergePeopleAsync(Person dst, Person src)
|
public async Task MergePeopleAsync(Person dst, Person src)
|
||||||
{
|
{
|
||||||
throw new System.NotImplementedException();
|
|
||||||
|
if (string.IsNullOrWhiteSpace(dst.Description) && !string.IsNullOrWhiteSpace(src.Description))
|
||||||
|
{
|
||||||
|
dst.Description = src.Description;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dst.MalId == 0 && src.MalId != 0)
|
||||||
|
{
|
||||||
|
dst.MalId = src.MalId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dst.AniListId == 0 && src.AniListId != 0)
|
||||||
|
{
|
||||||
|
dst.AniListId = src.AniListId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dst.HardcoverId == null && src.HardcoverId != null)
|
||||||
|
{
|
||||||
|
dst.HardcoverId = src.HardcoverId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dst.Asin == null && src.Asin != null)
|
||||||
|
{
|
||||||
|
dst.Asin = src.Asin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dst.CoverImage == null && src.CoverImage != null)
|
||||||
|
{
|
||||||
|
dst.CoverImage = src.CoverImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var chapter in src.ChapterPeople)
|
||||||
|
{
|
||||||
|
dst.ChapterPeople.Add(new ChapterPeople
|
||||||
|
{
|
||||||
|
Role = chapter.Role,
|
||||||
|
Chapter = chapter.Chapter,
|
||||||
|
Person = dst,
|
||||||
|
KavitaPlusConnection = chapter.KavitaPlusConnection,
|
||||||
|
OrderWeight = chapter.OrderWeight,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var series in src.SeriesMetadataPeople)
|
||||||
|
{
|
||||||
|
dst.SeriesMetadataPeople.Add(new SeriesMetadataPeople
|
||||||
|
{
|
||||||
|
Role = series.Role,
|
||||||
|
Person = dst,
|
||||||
|
KavitaPlusConnection = series.KavitaPlusConnection,
|
||||||
|
OrderWeight = series.OrderWeight,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dst.Aliases.Add(new PersonAlias
|
||||||
|
{
|
||||||
|
Alias = src.Name,
|
||||||
|
NormalizedAlias = src.NormalizedName,
|
||||||
|
});
|
||||||
|
|
||||||
|
unitOfWork.PersonRepository.Remove(src);
|
||||||
|
unitOfWork.PersonRepository.Update(dst);
|
||||||
|
await unitOfWork.CommitAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ using API.Entities;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
using API.Entities.Metadata;
|
using API.Entities.Metadata;
|
||||||
using API.Entities.MetadataMatching;
|
using API.Entities.MetadataMatching;
|
||||||
|
using API.Entities.Person;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
using API.Helpers;
|
using API.Helpers;
|
||||||
using API.Services.Tasks.Metadata;
|
using API.Services.Tasks.Metadata;
|
||||||
|
|
@ -614,12 +615,8 @@ public class ExternalMetadataService : IExternalMetadataService
|
||||||
madeModification = await UpdateTags(series, settings, externalMetadata, processedTags) || madeModification;
|
madeModification = await UpdateTags(series, settings, externalMetadata, processedTags) || madeModification;
|
||||||
madeModification = UpdateAgeRating(series, settings, processedGenres.Concat(processedTags)) || madeModification;
|
madeModification = UpdateAgeRating(series, settings, processedGenres.Concat(processedTags)) || madeModification;
|
||||||
|
|
||||||
var staff = (externalMetadata.Staff ?? []).Select(s =>
|
var staff = await SetNameAndAddAliases(settings, externalMetadata.Staff);
|
||||||
{
|
|
||||||
s.Name = settings.FirstLastPeopleNaming ? $"{s.FirstName} {s.LastName}" : $"{s.LastName} {s.FirstName}";
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}).ToList();
|
|
||||||
madeModification = await UpdateWriters(series, settings, staff) || madeModification;
|
madeModification = await UpdateWriters(series, settings, staff) || madeModification;
|
||||||
madeModification = await UpdateArtists(series, settings, staff) || madeModification;
|
madeModification = await UpdateArtists(series, settings, staff) || madeModification;
|
||||||
madeModification = await UpdateCharacters(series, settings, externalMetadata.Characters) || madeModification;
|
madeModification = await UpdateCharacters(series, settings, externalMetadata.Characters) || madeModification;
|
||||||
|
|
@ -632,6 +629,52 @@ public class ExternalMetadataService : IExternalMetadataService
|
||||||
return madeModification;
|
return madeModification;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<List<SeriesStaffDto>> SetNameAndAddAliases(MetadataSettingsDto settings, IList<SeriesStaffDto>? staff)
|
||||||
|
{
|
||||||
|
if (staff == null || staff.Count == 0) return [];
|
||||||
|
|
||||||
|
var nameMappings = staff.Select(s => new
|
||||||
|
{
|
||||||
|
Staff = s,
|
||||||
|
PreferredName = settings.FirstLastPeopleNaming ? $"{s.FirstName} {s.LastName}" : $"{s.LastName} {s.FirstName}",
|
||||||
|
AlternativeName = !settings.FirstLastPeopleNaming ? $"{s.FirstName} {s.LastName}" : $"{s.LastName} {s.FirstName}"
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
var preferredNames = nameMappings.Select(n => n.PreferredName.ToNormalized()).Distinct().ToList();
|
||||||
|
var alternativeNames = nameMappings.Select(n => n.AlternativeName.ToNormalized()).Distinct().ToList();
|
||||||
|
|
||||||
|
var existingPeople = await _unitOfWork.PersonRepository.GetPeopleByNames(preferredNames.Union(alternativeNames).ToList());
|
||||||
|
var existingPeopleDictionary = PersonHelper.ConstructNameAndAliasDictionary(existingPeople);
|
||||||
|
|
||||||
|
var modified = false;
|
||||||
|
foreach (var mapping in nameMappings)
|
||||||
|
{
|
||||||
|
mapping.Staff.Name = mapping.PreferredName;
|
||||||
|
|
||||||
|
if (existingPeopleDictionary.ContainsKey(mapping.PreferredName.ToNormalized()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (existingPeopleDictionary.TryGetValue(mapping.AlternativeName.ToNormalized(), out var person))
|
||||||
|
{
|
||||||
|
modified = true;
|
||||||
|
person.Aliases.Add(new PersonAlias
|
||||||
|
{
|
||||||
|
Alias = mapping.PreferredName,
|
||||||
|
NormalizedAlias = mapping.PreferredName.ToNormalized(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modified)
|
||||||
|
{
|
||||||
|
// Can I do this? Is this safe? Am I commiting other stuff to early
|
||||||
|
// Tests do fail without
|
||||||
|
await _unitOfWork.CommitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [.. staff];
|
||||||
|
}
|
||||||
|
|
||||||
private static void GenerateGenreAndTagLists(ExternalSeriesDetailDto externalMetadata, MetadataSettingsDto settings,
|
private static void GenerateGenreAndTagLists(ExternalSeriesDetailDto externalMetadata, MetadataSettingsDto settings,
|
||||||
ref List<string> processedTags, ref List<string> processedGenres)
|
ref List<string> processedTags, ref List<string> processedGenres)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -361,8 +361,7 @@ public class SeriesService : ISeriesService
|
||||||
var existingPeople = await unitOfWork.PersonRepository.GetPeopleByNames(normalizedNames);
|
var existingPeople = await unitOfWork.PersonRepository.GetPeopleByNames(normalizedNames);
|
||||||
|
|
||||||
// Use a dictionary for quick lookups
|
// Use a dictionary for quick lookups
|
||||||
var existingPeopleDictionary = existingPeople.DistinctBy(p => p.NormalizedName)
|
var existingPeopleDictionary = PersonHelper.ConstructNameAndAliasDictionary(existingPeople);
|
||||||
.ToDictionary(p => p.NormalizedName, p => p);
|
|
||||||
|
|
||||||
// List to track people that will be added to the metadata
|
// List to track people that will be added to the metadata
|
||||||
var peopleToAdd = new List<Person>();
|
var peopleToAdd = new List<Person>();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue