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 @@