From 2c391cb1f436d320a5e1cbd532b2d6e3ed4acf16 Mon Sep 17 00:00:00 2001 From: ACX <8075870+acx10@users.noreply.github.com> Date: Sun, 15 Feb 2026 09:42:43 -0700 Subject: [PATCH] chore: fix memory leaks, add aria-labels, improve type safety and logging (#2764) --- .../booklore/service/book/BookReviewService.java | 5 ++++- .../book-card/book-card.component.html | 11 ++++++----- .../book-card/book-card.component.ts | 4 ++-- .../book-uploader/book-uploader.component.ts | 6 ++++-- .../layout-menu/app.menuitem.component.ts | 16 ++++++---------- .../layout-topbar/app.topbar.component.html | 2 +- .../layout-topbar/app.topbar.component.ts | 2 +- booklore-ui/src/i18n/en/book.json | 7 ++++++- booklore-ui/src/i18n/es/book.json | 7 ++++++- 9 files changed, 36 insertions(+), 24 deletions(-) diff --git a/booklore-api/src/main/java/org/booklore/service/book/BookReviewService.java b/booklore-api/src/main/java/org/booklore/service/book/BookReviewService.java index 115c73660..9dba0d39f 100644 --- a/booklore-api/src/main/java/org/booklore/service/book/BookReviewService.java +++ b/booklore-api/src/main/java/org/booklore/service/book/BookReviewService.java @@ -16,6 +16,7 @@ import org.booklore.service.metadata.BookReviewUpdateService; import org.booklore.service.metadata.MetadataRefreshService; import jakarta.persistence.EntityNotFoundException; import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +@Slf4j @Service @AllArgsConstructor public class BookReviewService { @@ -67,7 +69,8 @@ public class BookReviewService { .map(mapper::toDto) .collect(Collectors.toList()); } - } catch (Exception ignored) { + } catch (Exception e) { + log.warn("Failed to auto-fetch reviews for book {}: {}", bookId, e.getMessage()); } return existingReviews; diff --git a/booklore-ui/src/app/features/book/components/book-browser/book-card/book-card.component.html b/booklore-ui/src/app/features/book/components/book-browser/book-card/book-card.component.html index 0472077b8..703543d28 100644 --- a/booklore-ui/src/app/features/book/components/book-browser/book-card/book-card.component.html +++ b/booklore-ui/src/app/features/book/components/book-browser/book-card/book-card.component.html @@ -32,14 +32,14 @@ @if (_isSeriesViewActive) { - + } @else { - - + + } @if (!_isSeriesViewActive && hasDigitalFile()) { - + } @if (isCheckboxEnabled) { @@ -103,7 +103,8 @@ size="small" [text]="true" (click)="onMenuToggle($event, menu)" - icon="pi pi-ellipsis-v"> + icon="pi pi-ellipsis-v" + [ariaLabel]="'book.card.alt.bookMenu' | transloco"> } diff --git a/booklore-ui/src/app/features/book/components/book-browser/book-card/book-card.component.ts b/booklore-ui/src/app/features/book/components/book-browser/book-card/book-card.component.ts index 246f3b4a9..812ea305b 100644 --- a/booklore-ui/src/app/features/book/components/book-browser/book-card/book-card.component.ts +++ b/booklore-ui/src/app/features/book/components/book-browser/book-card/book-card.component.ts @@ -13,7 +13,7 @@ import {MetadataRefreshType} from '../../../../metadata/model/request/metadata-r import {UrlHelperService} from '../../../../../shared/service/url-helper.service'; import {NgClass} from '@angular/common'; import {User, UserService} from '../../../../settings/user-management/user.service'; -import {filter, Subject} from 'rxjs'; +import {filter, Subject, Subscription} from 'rxjs'; import {EmailService} from '../../../../settings/email-v2/email.service'; import {TieredMenu} from 'primeng/tieredmenu'; import {Router} from '@angular/router'; @@ -101,7 +101,7 @@ export class BookCardComponent implements OnInit, OnChanges, OnDestroy { showBookTypePill = true; - private overlayPrefSub?: any; + private overlayPrefSub?: Subscription; ngOnInit(): void { this.computeAllMemoizedValues(); diff --git a/booklore-ui/src/app/shared/components/book-uploader/book-uploader.component.ts b/booklore-ui/src/app/shared/components/book-uploader/book-uploader.component.ts index 80642590d..ad00603c1 100644 --- a/booklore-ui/src/app/shared/components/book-uploader/book-uploader.component.ts +++ b/booklore-ui/src/app/shared/components/book-uploader/book-uploader.component.ts @@ -1,4 +1,4 @@ -import {Component, inject, OnInit, ViewChild} from '@angular/core'; +import {Component, DestroyRef, inject, OnInit, ViewChild} from '@angular/core'; import {FileSelectEvent, FileUpload, FileUploadHandlerEvent} from 'primeng/fileupload'; import {Button} from 'primeng/button'; import {AsyncPipe} from '@angular/common'; @@ -16,6 +16,7 @@ import {HttpClient, HttpEventType, HttpRequest} from '@angular/common/http'; import {Tooltip} from 'primeng/tooltip'; import {AppSettingsService} from '../../service/app-settings.service'; import {filter, take} from 'rxjs/operators'; +import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; import {AppSettings} from '../../model/app-settings.model'; import {SelectButton} from 'primeng/selectbutton'; import {DynamicDialogRef} from 'primeng/dynamicdialog'; @@ -62,6 +63,7 @@ export class BookUploaderComponent implements OnInit { private readonly http = inject(HttpClient); private readonly ref = inject(DynamicDialogRef); private readonly t = inject(TranslocoService); + private readonly destroyRef = inject(DestroyRef); readonly libraryState$: Observable = this.libraryService.libraryState$; appSettings$: Observable = this.appSettingsService.appSettings$; @@ -85,7 +87,7 @@ export class BookUploaderComponent implements OnInit { this.maxFileSizeDisplay = `${maxSizeMb} MB`; }); - this.libraryState$.subscribe(state => { + this.libraryState$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(state => { if (state?.libraries?.length !== 1 || this.selectedLibrary) { return; } diff --git a/booklore-ui/src/app/shared/layout/component/layout-menu/app.menuitem.component.ts b/booklore-ui/src/app/shared/layout/component/layout-menu/app.menuitem.component.ts index f883ec140..471c365be 100644 --- a/booklore-ui/src/app/shared/layout/component/layout-menu/app.menuitem.component.ts +++ b/booklore-ui/src/app/shared/layout/component/layout-menu/app.menuitem.component.ts @@ -60,6 +60,7 @@ export class AppMenuitemComponent implements OnInit, OnDestroy { return this.router.url.split('?')[0] === this.item.routerLink[0]; } + private userStateSubscription: Subscription; menuSourceSubscription: Subscription; menuResetSubscription: Subscription; private routerSubscription: Subscription; @@ -71,7 +72,7 @@ export class AppMenuitemComponent implements OnInit, OnDestroy { private dialogLauncher: DialogLauncherService, private bookDialogHelperService: BookDialogHelperService ) { - this.userService.userState$.subscribe(userState => { + this.userStateSubscription = this.userService.userState$.subscribe(userState => { if (userState?.user) { this.canManipulateLibrary = userState.user.permissions.canManageLibrary; this.admin = userState.user.permissions.admin; @@ -112,15 +113,10 @@ export class AppMenuitemComponent implements OnInit, OnDestroy { } ngOnDestroy() { - if (this.menuSourceSubscription) { - this.menuSourceSubscription.unsubscribe(); - } - if (this.menuResetSubscription) { - this.menuResetSubscription.unsubscribe(); - } - if (this.routerSubscription) { - this.routerSubscription.unsubscribe(); - } + this.userStateSubscription?.unsubscribe(); + this.menuSourceSubscription?.unsubscribe(); + this.menuResetSubscription?.unsubscribe(); + this.routerSubscription?.unsubscribe(); } toggleExpand(key: string) { diff --git a/booklore-ui/src/app/shared/layout/component/layout-topbar/app.topbar.component.html b/booklore-ui/src/app/shared/layout/component/layout-topbar/app.topbar.component.html index dd5873c41..093495c68 100644 --- a/booklore-ui/src/app/shared/layout/component/layout-topbar/app.topbar.component.html +++ b/booklore-ui/src/app/shared/layout/component/layout-topbar/app.topbar.component.html @@ -57,7 +57,7 @@