Don't add ChapterPeople / SeriesMetataPeople if already present for that role and series

Redirects on merge for users currently on that page
This commit is contained in:
Amelia 2025-05-07 18:07:36 +02:00
parent 6ec7e80a43
commit 95dc321bf9
No known key found for this signature in database
GPG key ID: D6D0ECE365407EAA
5 changed files with 94 additions and 25 deletions

View file

@ -196,6 +196,8 @@ public class PersonController : BaseApiController
if (src == null) return BadRequest(); if (src == null) return BadRequest();
await _personService.MergePeopleAsync(dst, src); await _personService.MergePeopleAsync(dst, src);
await _eventHub.SendMessageAsync(MessageFactory.PersonMerged, MessageFactory.PersonMergedMessage(dst, src));
return Ok(_mapper.Map<PersonDto>(dst)); return Ok(_mapper.Map<PersonDto>(dst));
} }

View file

@ -65,29 +65,8 @@ public class PersonService(IUnitOfWork unitOfWork): IPersonService
dst.CoverImage = src.CoverImage; dst.CoverImage = src.CoverImage;
} }
foreach (var chapter in src.ChapterPeople) MergeChapterPeople(dst, src);
{ MergeSeriesMetadataPeople(dst, src);
dst.ChapterPeople.Add(new ChapterPeople
{
Role = chapter.Role,
ChapterId = chapter.ChapterId,
Person = dst,
KavitaPlusConnection = chapter.KavitaPlusConnection,
OrderWeight = chapter.OrderWeight,
});
}
foreach (var series in src.SeriesMetadataPeople)
{
dst.SeriesMetadataPeople.Add(new SeriesMetadataPeople
{
SeriesMetadataId = series.SeriesMetadataId,
Role = series.Role,
Person = dst,
KavitaPlusConnection = series.KavitaPlusConnection,
OrderWeight = series.OrderWeight,
});
}
dst.Aliases.Add(new PersonAlias dst.Aliases.Add(new PersonAlias
{ {
@ -105,6 +84,44 @@ public class PersonService(IUnitOfWork unitOfWork): IPersonService
await unitOfWork.CommitAsync(); await unitOfWork.CommitAsync();
} }
private void MergeChapterPeople(Person dst, Person src)
{
foreach (var chapter in src.ChapterPeople)
{
var alreadyPresent = dst.ChapterPeople
.Any(x => x.ChapterId == chapter.ChapterId && x.Role == chapter.Role);
if (alreadyPresent) continue;
dst.ChapterPeople.Add(new ChapterPeople
{
Role = chapter.Role,
ChapterId = chapter.ChapterId,
Person = dst,
KavitaPlusConnection = chapter.KavitaPlusConnection,
OrderWeight = chapter.OrderWeight,
});
}
}
private void MergeSeriesMetadataPeople(Person dst, Person src)
{
foreach (var series in src.SeriesMetadataPeople)
{
var alreadyPresent = dst.SeriesMetadataPeople
.Any(x => x.SeriesMetadataId == series.SeriesMetadataId && x.Role == series.Role);
if (alreadyPresent) continue;
dst.SeriesMetadataPeople.Add(new SeriesMetadataPeople
{
SeriesMetadataId = series.SeriesMetadataId,
Role = series.Role,
Person = dst,
KavitaPlusConnection = series.KavitaPlusConnection,
OrderWeight = series.OrderWeight,
});
}
}
public async Task<bool> UpdatePersonAliasesAsync(Person person, IList<string> aliases) public async Task<bool> UpdatePersonAliasesAsync(Person person, IList<string> aliases)
{ {
var normalizedAliases = aliases var normalizedAliases = aliases

View file

@ -1,5 +1,6 @@
using System; using System;
using API.DTOs.Update; using API.DTOs.Update;
using API.Entities.Person;
using API.Extensions; using API.Extensions;
using API.Services.Plus; using API.Services.Plus;
@ -147,6 +148,10 @@ public static class MessageFactory
/// Volume is removed from server /// Volume is removed from server
/// </summary> /// </summary>
public const string VolumeRemoved = "VolumeRemoved"; public const string VolumeRemoved = "VolumeRemoved";
/// <summary>
/// A Person merged has been merged into another
/// </summary>
public const string PersonMerged = "PersonMerged";
public static SignalRMessage DashboardUpdateEvent(int userId) public static SignalRMessage DashboardUpdateEvent(int userId)
{ {
@ -661,4 +666,17 @@ public static class MessageFactory
EventType = ProgressEventType.Single, EventType = ProgressEventType.Single,
}; };
} }
public static SignalRMessage PersonMergedMessage(Person dst, Person src)
{
return new SignalRMessage()
{
Name = PersonMerged,
Body = new
{
srcId = src.Id,
dstName = dst.Name,
},
};
}
} }

View file

@ -109,7 +109,11 @@ export enum EVENTS {
/** /**
* A Progress event when a smart collection is synchronizing * A Progress event when a smart collection is synchronizing
*/ */
SmartCollectionSync = 'SmartCollectionSync' SmartCollectionSync = 'SmartCollectionSync',
/**
* A Person merged has been merged into another
*/
PersonMerged = 'PersonMerged',
} }
export interface Message<T> { export interface Message<T> {
@ -336,6 +340,13 @@ export class MessageHubService {
payload: resp.body payload: resp.body
}); });
}); });
this.hubConnection.on(EVENTS.PersonMerged, resp => {
this.messagesSource.next({
event: EVENTS.PersonMerged,
payload: resp.body
});
})
} }
stopHubConnection() { stopHubConnection() {

View file

@ -5,6 +5,7 @@ import {
DestroyRef, DestroyRef,
ElementRef, ElementRef,
inject, inject,
OnInit,
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import {ActivatedRoute, Router} from "@angular/router"; import {ActivatedRoute, Router} from "@angular/router";
@ -42,6 +43,14 @@ import {ToastrService} from "ngx-toastr";
import {LicenseService} from "../_services/license.service"; import {LicenseService} from "../_services/license.service";
import {SafeUrlPipe} from "../_pipes/safe-url.pipe"; import {SafeUrlPipe} from "../_pipes/safe-url.pipe";
import {MergePersonModalComponent} from "./_modal/merge-person-modal/merge-person-modal.component"; import {MergePersonModalComponent} from "./_modal/merge-person-modal/merge-person-modal.component";
import {EVENTS, MessageHubService} from "../_services/message-hub.service";
interface PersonMergeEvent {
srcId: number,
dstId: number,
dstName: number,
}
@Component({ @Component({
selector: 'app-person-detail', selector: 'app-person-detail',
@ -63,7 +72,7 @@ import {MergePersonModalComponent} from "./_modal/merge-person-modal/merge-perso
styleUrl: './person-detail.component.scss', styleUrl: './person-detail.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class PersonDetailComponent { export class PersonDetailComponent implements OnInit {
private readonly route = inject(ActivatedRoute); private readonly route = inject(ActivatedRoute);
private readonly router = inject(Router); private readonly router = inject(Router);
private readonly filterUtilityService = inject(FilterUtilitiesService); private readonly filterUtilityService = inject(FilterUtilitiesService);
@ -77,6 +86,7 @@ export class PersonDetailComponent {
protected readonly licenseService = inject(LicenseService); protected readonly licenseService = inject(LicenseService);
private readonly themeService = inject(ThemeService); private readonly themeService = inject(ThemeService);
private readonly toastr = inject(ToastrService); private readonly toastr = inject(ToastrService);
private readonly messageHubService = inject(MessageHubService)
protected readonly TagBadgeCursor = TagBadgeCursor; protected readonly TagBadgeCursor = TagBadgeCursor;
@ -129,6 +139,17 @@ export class PersonDetailComponent {
).subscribe(); ).subscribe();
} }
ngOnInit(): void {
this.messageHubService.messages$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(message => {
if (message.event !== EVENTS.PersonMerged) return;
const event = message.payload as PersonMergeEvent;
if (event.srcId !== this.person?.id) return;
this.router.navigate(['person', event.dstName]);
});
}
private setPerson(person: Person) { private setPerson(person: Person) {
this.person = person; this.person = person;
this.personSubject.next(person); // emit the person data for subscribers this.personSubject.next(person); // emit the person data for subscribers