Fix a bunch of EF issues

I don't fully understand the issue that was present before, so this is
more of a workaround than a real fix.
This commit is contained in:
Amelia 2025-05-07 23:37:19 +02:00
parent 697a3bb52b
commit 4b13802301
No known key found for this signature in database
GPG key ID: D6D0ECE365407EAA
10 changed files with 44 additions and 13 deletions

View file

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using API.Data; using API.Data;
using API.Data.Repositories; using API.Data.Repositories;
@ -57,6 +58,17 @@ public class PersonController : BaseApiController
return Ok(await _unitOfWork.PersonRepository.GetRolesForPersonByName(personId, User.GetUserId())); return Ok(await _unitOfWork.PersonRepository.GetRolesForPersonByName(personId, User.GetUserId()));
} }
[HttpGet("aliases")]
public async Task<ActionResult<IList<string>>> GetAliasesForPerson([FromQuery] int personId)
{
var person = await _unitOfWork.PersonRepository.GetPersonById(personId);
if (person == null) return NotFound();
if (person.Aliases == null || person.Aliases.Count == 0) return new List<string>();
return Ok(person.Aliases.Select(a => a.Alias).ToList());
}
/// <summary> /// <summary>
/// Returns a list of authors and artists for browsing /// Returns a list of authors and artists for browsing
/// </summary> /// </summary>

View file

@ -7,7 +7,6 @@ public class PersonDto
{ {
public int Id { get; set; } public int Id { get; set; }
public required string Name { get; set; } public required string Name { get; set; }
public IList<string> Aliases { get; set; } = [];
public bool CoverImageLocked { get; set; } public bool CoverImageLocked { get; set; }
public string? PrimaryColor { get; set; } public string? PrimaryColor { get; set; }

View file

@ -68,9 +68,7 @@ public class AutoMapperProfiles : Profile
CreateMap<AppUserCollection, AppUserCollectionDto>() CreateMap<AppUserCollection, AppUserCollectionDto>()
.ForMember(dest => dest.Owner, opt => opt.MapFrom(src => src.AppUser.UserName)) .ForMember(dest => dest.Owner, opt => opt.MapFrom(src => src.AppUser.UserName))
.ForMember(dest => dest.ItemCount, opt => opt.MapFrom(src => src.Items.Count)); .ForMember(dest => dest.ItemCount, opt => opt.MapFrom(src => src.Items.Count));
CreateMap<Person, PersonDto>() CreateMap<Person, PersonDto>();
.ForMember(dst => dst.Aliases, opt =>
opt.MapFrom(src => src.Aliases.Select(pa => pa.Alias).ToList()));
CreateMap<Genre, GenreTagDto>(); CreateMap<Genre, GenreTagDto>();
CreateMap<Tag, TagDto>(); CreateMap<Tag, TagDto>();
CreateMap<AgeRating, AgeRatingDto>(); CreateMap<AgeRating, AgeRatingDto>();

View file

@ -21,7 +21,6 @@ export enum PersonRole {
export interface Person extends IHasCover { export interface Person extends IHasCover {
id: number; id: number;
name: string; name: string;
aliases: string[];
description: string; description: string;
coverImage?: string; coverImage?: string;
coverImageLocked: boolean; coverImageLocked: boolean;

View file

@ -30,6 +30,10 @@ export class PersonService {
return this.httpClient.get<Person | null>(this.baseUrl + `person?name=${name}`); return this.httpClient.get<Person | null>(this.baseUrl + `person?name=${name}`);
} }
getAliases(personId: number) {
return this.httpClient.get<Array<string>>(this.baseUrl + `person/aliases?personId=${personId}`);
}
getRolesForPerson(personId: number) { getRolesForPerson(personId: number) {
return this.httpClient.get<Array<PersonRole>>(this.baseUrl + `person/roles?personId=${personId}`); return this.httpClient.get<Array<PersonRole>>(this.baseUrl + `person/roles?personId=${personId}`);
} }

View file

@ -99,9 +99,9 @@
<li [ngbNavItem]="TabID.Aliases"> <li [ngbNavItem]="TabID.Aliases">
<a ngbNavLink>{{t(TabID.Aliases)}}</a> <a ngbNavLink>{{t(TabID.Aliases)}}</a>
<ng-template ngbNavContent> <ng-template ngbNavContent>
<app-edit-list [items]="person.aliases" <app-edit-list [items]="aliases"
[asyncValidators]="[aliasValidator()]" [asyncValidators]="[aliasValidator()]"
(updateItems)="person.aliases = $event" (updateItems)="aliases = $event"
[errorMessage]="t('alias-overlap')" [errorMessage]="t('alias-overlap')"
[label]="t('aliases-label')"/> [label]="t('aliases-label')"/>
</ng-template> </ng-template>

View file

@ -68,6 +68,7 @@ export class EditPersonModalComponent implements OnInit {
protected readonly TabID = TabID; protected readonly TabID = TabID;
@Input({required: true}) person!: Person; @Input({required: true}) person!: Person;
@Input({required: true}) aliases!: string[];
active = TabID.General; active = TabID.General;
editForm: FormGroup = new FormGroup({ editForm: FormGroup = new FormGroup({
@ -117,11 +118,11 @@ export class EditPersonModalComponent implements OnInit {
apis.push(this.uploadService.updatePersonCoverImage(this.person.id, this.selectedCover, !this.coverImageReset)); apis.push(this.uploadService.updatePersonCoverImage(this.person.id, this.selectedCover, !this.coverImageReset));
} }
const person: Person = { // UpdatePersonDto allows for aliases
const person: Person & { aliases: string[] } = {
id: this.person.id, id: this.person.id,
coverImageLocked: this.person.coverImageLocked, coverImageLocked: this.person.coverImageLocked,
name: this.editForm.get('name')!.value || '', name: this.editForm.get('name')!.value || '',
aliases: this.person.aliases,
description: this.editForm.get('description')!.value || '', description: this.editForm.get('description')!.value || '',
asin: this.editForm.get('asin')!.value || '', asin: this.editForm.get('asin')!.value || '',
// @ts-ignore // @ts-ignore
@ -129,6 +130,7 @@ export class EditPersonModalComponent implements OnInit {
// @ts-ignore // @ts-ignore
malId: this.editForm.get('malId')!.value === '' ? null : parseInt(this.editForm.get('malId').value, 10), malId: this.editForm.get('malId')!.value === '' ? null : parseInt(this.editForm.get('malId').value, 10),
hardcoverId: this.editForm.get('hardcoverId')!.value || '', hardcoverId: this.editForm.get('hardcoverId')!.value || '',
aliases: this.aliases,
}; };
apis.push(this.personService.updatePerson(person)); apis.push(this.personService.updatePerson(person));

View file

@ -38,6 +38,7 @@ export class MergePersonModalComponent implements OnInit {
@Input({required: true}) person!: Person; @Input({required: true}) person!: Person;
mergee: Person | null = null; mergee: Person | null = null;
aliases: string[] = [];
save() { save() {
if (!this.mergee) { if (!this.mergee) {
@ -80,6 +81,9 @@ export class MergePersonModalComponent implements OnInit {
this.typeAheadUnfocus.emit(this.typeAheadSettings.id); this.typeAheadUnfocus.emit(this.typeAheadSettings.id);
this.mergee = people[0]; this.mergee = people[0];
this.personService.getAliases(this.mergee.id).subscribe(aliases => {
this.aliases = aliases;
});
} }
protected readonly FilterField = FilterField; protected readonly FilterField = FilterField;
@ -87,6 +91,6 @@ export class MergePersonModalComponent implements OnInit {
allNewAliases() { allNewAliases() {
if (!this.mergee) return []; if (!this.mergee) return [];
return [this.mergee.name, ...this.mergee.aliases] return [this.mergee.name, ...this.aliases]
} }
} }

View file

@ -17,8 +17,10 @@
</h2> </h2>
</ng-container> </ng-container>
<ng-container subtitle> <ng-container subtitle>
@if (person.aliases.length > 0) { @if (aliases$ | async; as aliases) {
<span>{{t('aka')}} {{person.aliases.join(", ")}}</span> @if (aliases.length > 0) {
<span>{{t('aka')}} {{aliases.join(", ")}}</span>
}
} }
</ng-container> </ng-container>
</app-side-nav-companion-bar> </app-side-nav-companion-bar>

View file

@ -10,7 +10,7 @@ import {
} from '@angular/core'; } from '@angular/core';
import {ActivatedRoute, Router} from "@angular/router"; import {ActivatedRoute, Router} from "@angular/router";
import {PersonService} from "../_services/person.service"; import {PersonService} from "../_services/person.service";
import {BehaviorSubject, EMPTY, Observable, switchMap, tap} from "rxjs"; import {BehaviorSubject, EMPTY, Observable, of, switchMap, tap} from "rxjs";
import {Person, PersonRole} from "../_models/metadata/person"; import {Person, PersonRole} from "../_models/metadata/person";
import {AsyncPipe} from "@angular/common"; import {AsyncPipe} from "@angular/common";
import {ImageComponent} from "../shared/image/image.component"; import {ImageComponent} from "../shared/image/image.component";
@ -99,6 +99,8 @@ export class PersonDetailComponent implements OnInit {
roles: PersonRole[] | null = null; roles: PersonRole[] | null = null;
works$: Observable<Series[]> | null = null; works$: Observable<Series[]> | null = null;
filter: SeriesFilterV2 | null = null; filter: SeriesFilterV2 | null = null;
aliases$: Observable<string[]> | null = null;
aliases: string[] = [];
personActions: Array<ActionItem<Person>> = this.actionService.getPersonActions(this.handleAction.bind(this)); personActions: Array<ActionItem<Person>> = this.actionService.getPersonActions(this.handleAction.bind(this));
chaptersByRole: any = {}; chaptersByRole: any = {};
anilistUrl: string = ''; anilistUrl: string = '';
@ -176,6 +178,12 @@ export class PersonDetailComponent implements OnInit {
this.works$ = this.personService.getSeriesMostKnownFor(person.id).pipe( this.works$ = this.personService.getSeriesMostKnownFor(person.id).pipe(
takeUntilDestroyed(this.destroyRef) takeUntilDestroyed(this.destroyRef)
); );
this.aliases$ = this.personService.getAliases(person.id).pipe(
tap(aliases => this.aliases = aliases),
takeUntilDestroyed(this.destroyRef)
);
} }
createFilter(roles: PersonRole[]) { createFilter(roles: PersonRole[]) {
@ -233,14 +241,17 @@ export class PersonDetailComponent implements OnInit {
case(Action.Edit): case(Action.Edit):
const ref = this.modalService.open(EditPersonModalComponent, DefaultModalOptions); const ref = this.modalService.open(EditPersonModalComponent, DefaultModalOptions);
ref.componentInstance.person = this.person; ref.componentInstance.person = this.person;
ref.componentInstance.aliases = this.aliases;
ref.closed.subscribe(r => { ref.closed.subscribe(r => {
if (r.success) { if (r.success) {
const nameChanged = this.personName !== r.person.name; const nameChanged = this.personName !== r.person.name;
this.person = {...r.person}; this.person = {...r.person};
this.aliases = r.person.aliases; // UpdatePersonDto does include them
this.personName = this.person!.name; this.personName = this.person!.name;
this.personSubject.next(this.person); this.personSubject.next(this.person);
this.aliases$ = of(this.aliases);
// Update the url to reflect the new name change // Update the url to reflect the new name change
if (nameChanged) { if (nameChanged) {