diff --git a/API/Controllers/PersonController.cs b/API/Controllers/PersonController.cs index e99b81118..d361c0aa1 100644 --- a/API/Controllers/PersonController.cs +++ b/API/Controllers/PersonController.cs @@ -196,6 +196,8 @@ public class PersonController : BaseApiController if (src == null) return BadRequest(); await _personService.MergePeopleAsync(dst, src); + await _eventHub.SendMessageAsync(MessageFactory.PersonMerged, MessageFactory.PersonMergedMessage(dst, src)); + return Ok(_mapper.Map(dst)); } diff --git a/API/Services/PersonService.cs b/API/Services/PersonService.cs index fc8f82bec..27503ac8d 100644 --- a/API/Services/PersonService.cs +++ b/API/Services/PersonService.cs @@ -65,29 +65,8 @@ public class PersonService(IUnitOfWork unitOfWork): IPersonService dst.CoverImage = src.CoverImage; } - foreach (var chapter in src.ChapterPeople) - { - 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, - }); - } + MergeChapterPeople(dst, src); + MergeSeriesMetadataPeople(dst, src); dst.Aliases.Add(new PersonAlias { @@ -105,6 +84,44 @@ public class PersonService(IUnitOfWork unitOfWork): IPersonService 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 UpdatePersonAliasesAsync(Person person, IList aliases) { var normalizedAliases = aliases diff --git a/API/SignalR/MessageFactory.cs b/API/SignalR/MessageFactory.cs index de9818b79..ba967d8a6 100644 --- a/API/SignalR/MessageFactory.cs +++ b/API/SignalR/MessageFactory.cs @@ -1,5 +1,6 @@ using System; using API.DTOs.Update; +using API.Entities.Person; using API.Extensions; using API.Services.Plus; @@ -147,6 +148,10 @@ public static class MessageFactory /// Volume is removed from server /// public const string VolumeRemoved = "VolumeRemoved"; + /// + /// A Person merged has been merged into another + /// + public const string PersonMerged = "PersonMerged"; public static SignalRMessage DashboardUpdateEvent(int userId) { @@ -661,4 +666,17 @@ public static class MessageFactory EventType = ProgressEventType.Single, }; } + + public static SignalRMessage PersonMergedMessage(Person dst, Person src) + { + return new SignalRMessage() + { + Name = PersonMerged, + Body = new + { + srcId = src.Id, + dstName = dst.Name, + }, + }; + } } diff --git a/UI/Web/src/app/_services/message-hub.service.ts b/UI/Web/src/app/_services/message-hub.service.ts index ea1819bd7..67f07f32e 100644 --- a/UI/Web/src/app/_services/message-hub.service.ts +++ b/UI/Web/src/app/_services/message-hub.service.ts @@ -109,7 +109,11 @@ export enum EVENTS { /** * 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 { @@ -336,6 +340,13 @@ export class MessageHubService { payload: resp.body }); }); + + this.hubConnection.on(EVENTS.PersonMerged, resp => { + this.messagesSource.next({ + event: EVENTS.PersonMerged, + payload: resp.body + }); + }) } stopHubConnection() { diff --git a/UI/Web/src/app/person-detail/person-detail.component.ts b/UI/Web/src/app/person-detail/person-detail.component.ts index 2d7e89f61..9e12e6695 100644 --- a/UI/Web/src/app/person-detail/person-detail.component.ts +++ b/UI/Web/src/app/person-detail/person-detail.component.ts @@ -5,6 +5,7 @@ import { DestroyRef, ElementRef, inject, + OnInit, ViewChild } from '@angular/core'; import {ActivatedRoute, Router} from "@angular/router"; @@ -42,6 +43,14 @@ import {ToastrService} from "ngx-toastr"; import {LicenseService} from "../_services/license.service"; import {SafeUrlPipe} from "../_pipes/safe-url.pipe"; 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({ selector: 'app-person-detail', @@ -63,7 +72,7 @@ import {MergePersonModalComponent} from "./_modal/merge-person-modal/merge-perso styleUrl: './person-detail.component.scss', changeDetection: ChangeDetectionStrategy.OnPush }) -export class PersonDetailComponent { +export class PersonDetailComponent implements OnInit { private readonly route = inject(ActivatedRoute); private readonly router = inject(Router); private readonly filterUtilityService = inject(FilterUtilitiesService); @@ -77,6 +86,7 @@ export class PersonDetailComponent { protected readonly licenseService = inject(LicenseService); private readonly themeService = inject(ThemeService); private readonly toastr = inject(ToastrService); + private readonly messageHubService = inject(MessageHubService) protected readonly TagBadgeCursor = TagBadgeCursor; @@ -129,6 +139,17 @@ export class PersonDetailComponent { ).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) { this.person = person; this.personSubject.next(person); // emit the person data for subscribers