From b4e95a3cc17281cef669655d8dc05cf63b12ca11 Mon Sep 17 00:00:00 2001 From: astrodad Date: Tue, 12 Aug 2025 16:42:51 -0400 Subject: [PATCH 1/4] Feature/update date finished integration (#895) * Fixed issue with not updating the book finished date when updating read status * Added ability to set a date finished after setting the book status to Read. --------- Co-authored-by: Aditya Chandel <8075870+adityachandelgit@users.noreply.github.com> --- .../booklore/model/dto/request/ReadProgressRequest.java | 2 +- .../java/com/adityachandel/booklore/service/BookService.java | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/ReadProgressRequest.java b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/ReadProgressRequest.java index e367b1179..fb49141ba 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/ReadProgressRequest.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/ReadProgressRequest.java @@ -19,6 +19,6 @@ public class ReadProgressRequest { @AssertTrue(message = "At least one progress field must be provided") public boolean isProgressValid() { - return epubProgress != null || pdfProgress != null || cbxProgress != null; + return epubProgress != null || pdfProgress != null || cbxProgress != null || dateFinished != null; } } diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/BookService.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/BookService.java index 4215d7e4d..1eb9a02f7 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/BookService.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/BookService.java @@ -312,9 +312,12 @@ public class BookService { userBookProgress.setCbxProgress(request.getCbxProgress().getPage()); userBookProgress.setCbxProgressPercent(request.getCbxProgress().getPercentage()); } + + // Update dateFinished if provided if (request.getDateFinished() != null) { userBookProgress.setDateFinished(request.getDateFinished()); } + userBookProgressRepository.save(userBookProgress); } From a7222c68e24d1a3abb952927fe5e4c9bc8079997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionu=C8=9B=20Staicu?= Date: Tue, 12 Aug 2025 23:44:12 +0300 Subject: [PATCH 2/4] add tooltips on book titles (#893) --- .../book-card/book-card.component.html | 20 ++++++++++++++++--- .../book-card/book-card.component.ts | 3 ++- .../book-table/book-table.component.html | 9 +++++++-- .../book-table/book-table.component.ts | 4 +++- .../book-card-lite-component.html | 4 ++++ .../book-card-lite-component.ts | 4 +++- 6 files changed, 36 insertions(+), 8 deletions(-) diff --git a/booklore-ui/src/app/book/components/book-browser/book-card/book-card.component.html b/booklore-ui/src/app/book/components/book-browser/book-card/book-card.component.html index 96f78e601..e8583a34b 100644 --- a/booklore-ui/src/app/book/components/book-browser/book-card/book-card.component.html +++ b/booklore-ui/src/app/book/components/book-browser/book-card/book-card.component.html @@ -1,4 +1,9 @@
@@ -31,8 +36,17 @@
} - - + + + @if (isCheckboxEnabled) {
-

{{ book.metadata?.title }}

+

{{ book.metadata?.title }}

- + Book Cover @@ -68,7 +68,12 @@ } @else { - + {{ getCellValue(metadata, book, col.field) }} } diff --git a/booklore-ui/src/app/book/components/book-browser/book-table/book-table.component.ts b/booklore-ui/src/app/book/components/book-browser/book-table/book-table.component.ts index 3fcd65a43..2f126ec95 100644 --- a/booklore-ui/src/app/book/components/book-browser/book-table/book-table.component.ts +++ b/booklore-ui/src/app/book/components/book-browser/book-table/book-table.component.ts @@ -3,6 +3,7 @@ import {TableModule} from 'primeng/table'; import {DatePipe} from '@angular/common'; import {Rating} from 'primeng/rating'; import {FormsModule} from '@angular/forms'; +import {TooltipModule} from "primeng/tooltip"; import {Book, BookMetadata} from '../../../model/book.model'; import {SortOption} from '../../../model/sort.model'; import {UrlHelperService} from '../../../../utilities/service/url-helper.service'; @@ -24,7 +25,8 @@ import {take, takeUntil} from 'rxjs/operators'; TableModule, Rating, FormsModule, - Button + Button, + TooltipModule ], styleUrls: ['./book-table.component.scss'], providers: [DatePipe] diff --git a/booklore-ui/src/app/book/components/book-card-lite/book-card-lite-component.html b/booklore-ui/src/app/book/components/book-card-lite/book-card-lite-component.html index 876e6e729..57e19a999 100644 --- a/booklore-ui/src/app/book/components/book-card-lite/book-card-lite-component.html +++ b/booklore-ui/src/app/book/components/book-card-lite/book-card-lite-component.html @@ -1,4 +1,8 @@
Date: Tue, 12 Aug 2025 22:46:51 +0200 Subject: [PATCH 3/4] Update docker-compose.yml (#888) --- example-docker/docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example-docker/docker-compose.yml b/example-docker/docker-compose.yml index 2851c7a0b..c48a00e3b 100644 --- a/example-docker/docker-compose.yml +++ b/example-docker/docker-compose.yml @@ -1,7 +1,7 @@ name: booklore services: booklore: - image: ghcr.io/adityachandelgit/booklore-app:${BOOKLORE_IMAGE_TAG} + image: booklore/booklore:${BOOKLORE_IMAGE_TAG} container_name: booklore_server env_file: - .env @@ -30,4 +30,4 @@ services: test: ["CMD", "mariadb-admin", "ping", "-h", "localhost"] interval: 10s timeout: 5s - retries: 5 \ No newline at end of file + retries: 5 From 22b2c013bdfafd3b720a454e61e214ec7fd23281 Mon Sep 17 00:00:00 2001 From: "aditya.chandel" <8075870+adityachandelgit@users.noreply.github.com> Date: Tue, 12 Aug 2025 22:41:37 -0600 Subject: [PATCH 4/4] Fix OIDC auto-login not triggering in new browser tabs --- .../src/app/auth-initialization-service.ts | 9 +- booklore-ui/src/app/auth-initializer.ts | 93 +++++++++---------- .../src/app/core/service/auth.service.ts | 22 +---- .../src/app/core/service/startup.service.ts | 24 ++--- 4 files changed, 59 insertions(+), 89 deletions(-) diff --git a/booklore-ui/src/app/auth-initialization-service.ts b/booklore-ui/src/app/auth-initialization-service.ts index 9ba1363e8..10d175d16 100644 --- a/booklore-ui/src/app/auth-initialization-service.ts +++ b/booklore-ui/src/app/auth-initialization-service.ts @@ -1,19 +1,12 @@ import {Injectable} from '@angular/core'; import {BehaviorSubject} from 'rxjs'; -@Injectable({ providedIn: 'root' }) +@Injectable({providedIn: 'root'}) export class AuthInitializationService { private initialized = new BehaviorSubject(false); initialized$ = this.initialized.asObservable(); - private oidcFailed = new BehaviorSubject(false); - oidcFailed$ = this.oidcFailed.asObservable(); - markAsInitialized() { this.initialized.next(true); } - - setOidcFailed(failed: boolean) { - this.oidcFailed.next(failed); - } } diff --git a/booklore-ui/src/app/auth-initializer.ts b/booklore-ui/src/app/auth-initializer.ts index 62398beba..9293905ad 100644 --- a/booklore-ui/src/app/auth-initializer.ts +++ b/booklore-ui/src/app/auth-initializer.ts @@ -16,59 +16,52 @@ export function initializeAuthFactory() { return new Promise((resolve) => { const sub = appSettingsService.appSettings$.subscribe(settings => { - if (!settings) return; + if (settings) { + if (settings.oidcEnabled && settings.oidcProviderDetails) { + const details = settings.oidcProviderDetails; - sub.unsubscribe(); - - if (settings.oidcEnabled && settings.oidcProviderDetails) { - const details = settings.oidcProviderDetails; - - oauthService.configure({ - issuer: details.issuerUri, - clientId: details.clientId, - scope: 'openid profile email offline_access', - redirectUri: window.location.origin + '/oauth2-callback', - responseType: 'code', - showDebugInformation: false, - requireHttps: false, - strictDiscoveryDocumentValidation: false, - }); - - oauthService.loadDiscoveryDocumentAndTryLogin() - .then(() => { - if (oauthService.hasValidAccessToken()) { - console.log('[OIDC] Valid access token found'); - oauthService.setupAutomaticSilentRefresh(); - websocketInitializer(authService); - } else { - console.warn('[OIDC] No valid access token. Will proceed to app and show login page.'); - } - }) - .catch(err => { - console.error('[OIDC] Failed to load discovery document or login:', err); - authInitService.setOidcFailed(true); - }) - .finally(() => { - authInitService.markAsInitialized(); - resolve(); + oauthService.configure({ + issuer: details.issuerUri, + clientId: details.clientId, + scope: 'openid profile email offline_access', + redirectUri: window.location.origin + '/oauth2-callback', + responseType: 'code', + showDebugInformation: false, + requireHttps: false, + strictDiscoveryDocumentValidation: false, }); - } else if (settings.remoteAuthEnabled) { - authService.remoteLogin().subscribe({ - next: () => { - authInitService.markAsInitialized(); - resolve(); - }, - error: err => { - console.error('[Remote Login] failed:', err); - authInitService.markAsInitialized(); - resolve(); - } - }); - - } else { - authInitService.markAsInitialized(); - resolve(); + oauthService.loadDiscoveryDocumentAndTryLogin() + .then(() => { + console.log('[OIDC] Discovery document loaded and login attempted'); + if (oauthService.hasValidAccessToken()) { + authService.tokenSubject.next(oauthService.getAccessToken()) + console.log('[OIDC] Valid access token found after tryLogin'); + router.navigate(['/dashboard']); + oauthService.setupAutomaticSilentRefresh(); + websocketInitializer(authService); + authInitService.markAsInitialized(); + resolve(); + } else { + console.log('[OIDC] No valid access token found, attempting silent login with prompt=none'); + oauthService.initImplicitFlow(); + resolve(); + } + }) + .catch(err => { + authInitService.markAsInitialized(); + console.error( + 'OIDC initialization failed: Unable to complete OpenID Connect discovery or login. ' + + 'This may be due to an incorrect issuer URL, client ID, or network issue. ' + + 'Falling back to local login. Details:', err + ); + resolve(); + }); + } else { + authInitService.markAsInitialized(); + resolve(); + } + sub.unsubscribe(); } }); }); diff --git a/booklore-ui/src/app/core/service/auth.service.ts b/booklore-ui/src/app/core/service/auth.service.ts index 283c02308..a7adc243b 100644 --- a/booklore-ui/src/app/core/service/auth.service.ts +++ b/booklore-ui/src/app/core/service/auth.service.ts @@ -20,11 +20,9 @@ export class AuthService { private oAuthService = inject(OAuthService); private router = inject(Router); - private tokenSubject = new BehaviorSubject(this.getOidcAccessToken() || this.getInternalAccessToken()); + public tokenSubject = new BehaviorSubject(this.getOidcAccessToken() || this.getInternalAccessToken()); public token$ = this.tokenSubject.asObservable(); - private logoutSubject = new Subject(); - public logout$ = this.logoutSubject.asObservable(); internalLogin(credentials: { username: string; password: string }): Observable<{ accessToken: string; refreshToken: string, isDefaultPassword: string }> { return this.http.post<{ accessToken: string; refreshToken: string, isDefaultPassword: string }>(`${this.apiUrl}/login`, credentials).pipe( @@ -48,17 +46,6 @@ export class AuthService { ); } - remoteLogin(): Observable<{ accessToken: string; refreshToken: string, isDefaultPassword: string }> { - return this.http.get<{ accessToken: string; refreshToken: string, isDefaultPassword: string }>(`${this.apiUrl}/remote`).pipe( - tap((response) => { - if (response.accessToken && response.refreshToken) { - this.saveInternalTokens(response.accessToken, response.refreshToken); - this.initializeWebSocketConnection(); - } - }) - ); - } - saveInternalTokens(accessToken: string, refreshToken: string): void { localStorage.setItem('accessToken_Internal', accessToken); localStorage.setItem('refreshToken_Internal', refreshToken); @@ -81,13 +68,8 @@ export class AuthService { localStorage.removeItem('accessToken_Internal'); localStorage.removeItem('refreshToken_Internal'); this.tokenSubject.next(null); - this.logoutSubject.next(); this.getRxStompService().deactivate(); - if (this.oAuthService.clientId) { - this.oAuthService.logOut(); - } else { - this.router.navigate(['/login']); - } + this.router.navigate(['/login']); } getRxStompService(): RxStompService { diff --git a/booklore-ui/src/app/core/service/startup.service.ts b/booklore-ui/src/app/core/service/startup.service.ts index a05a7dbb2..868274afb 100644 --- a/booklore-ui/src/app/core/service/startup.service.ts +++ b/booklore-ui/src/app/core/service/startup.service.ts @@ -1,26 +1,28 @@ -import { Injectable, inject } from '@angular/core'; -import { AuthService } from './auth.service'; -import { UserService } from '../../settings/user-management/user.service'; -import { filter, catchError } from 'rxjs/operators'; -import { of } from 'rxjs'; +import {Injectable, inject} from '@angular/core'; +import {AuthService} from './auth.service'; +import {UserService} from '../../settings/user-management/user.service'; +import {filter, catchError} from 'rxjs/operators'; +import {of} from 'rxjs'; +import {OAuthService} from 'angular-oauth2-oidc'; -@Injectable({ providedIn: 'root' }) +@Injectable({providedIn: 'root'}) export class StartupService { - private auth = inject(AuthService); - private userSvc = inject(UserService); + private authService = inject(AuthService); + private userService = inject(UserService); load(): Promise { - this.auth.token$ + this.authService.token$ .pipe(filter(t => !!t)) .subscribe(() => { - this.userSvc.getMyself() + this.userService.getMyself() .pipe(catchError(() => of(null))) .subscribe(user => { if (user) { - this.userSvc.setInitialUser(user); + this.userService.setInitialUser(user); } }); }); + return Promise.resolve(); } }