mirror of
https://github.com/adityachandelgit/BookLore.git
synced 2025-08-16 17:02:23 +00:00
Add support for bulk reading progress reset
This commit is contained in:

committed by
Aditya Chandel

parent
ec7846abf6
commit
77d68108ab
@ -1,6 +1,7 @@
|
||||
package com.adityachandel.booklore.controller;
|
||||
|
||||
import com.adityachandel.booklore.config.security.annotation.CheckBookAccess;
|
||||
import com.adityachandel.booklore.exception.ApiError;
|
||||
import com.adityachandel.booklore.model.dto.Book;
|
||||
import com.adityachandel.booklore.model.dto.BookRecommendation;
|
||||
import com.adityachandel.booklore.model.dto.BookViewerSettings;
|
||||
@ -125,10 +126,12 @@ public class BookController {
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@PostMapping("/{bookId}/reset-progress")
|
||||
@CheckBookAccess(bookIdParam = "bookId")
|
||||
public ResponseEntity<Book> resetProgress(@PathVariable long bookId) {
|
||||
Book book = bookService.resetProgress(bookId);
|
||||
return ResponseEntity.ok(book);
|
||||
@PostMapping("/reset-progress")
|
||||
public ResponseEntity<List<Book>> resetProgress(@RequestBody List<Long> bookIds) {
|
||||
if (bookIds == null || bookIds.isEmpty()) {
|
||||
throw ApiError.BAD_REQUEST.createException("No book IDs provided");
|
||||
}
|
||||
List<Book> updatedBooks = bookService.resetProgress(bookIds);
|
||||
return ResponseEntity.ok(updatedBooks);
|
||||
}
|
||||
}
|
@ -285,22 +285,34 @@ public class BookService {
|
||||
return book;
|
||||
}
|
||||
|
||||
public Book resetProgress(long bookId) {
|
||||
public List<Book> resetProgress(List<Long> bookIds) {
|
||||
BookLoreUser user = authenticationService.getAuthenticatedUser();
|
||||
BookEntity bookEntity = bookRepository.findById(bookId).orElseThrow(() -> ApiError.BOOK_NOT_FOUND.createException(bookId));
|
||||
List<Book> updatedBooks = new ArrayList<>();
|
||||
Optional<BookLoreUserEntity> userEntity = userRepository.findById(user.getId());
|
||||
|
||||
UserBookProgressEntity progress = userBookProgressRepository.findByUserIdAndBookId(user.getId(), bookId).orElse(new UserBookProgressEntity());
|
||||
progress.setBook(bookEntity);
|
||||
progress.setReadStatus(null);
|
||||
progress.setLastReadTime(null);
|
||||
progress.setPdfProgress(null);
|
||||
progress.setPdfProgressPercent(null);
|
||||
progress.setEpubProgress(null);
|
||||
progress.setEpubProgressPercent(null);
|
||||
progress.setCbxProgress(null);
|
||||
progress.setCbxProgressPercent(null);
|
||||
userBookProgressRepository.save(progress);
|
||||
return bookMapper.toBook(bookEntity);
|
||||
for (Long bookId : bookIds) {
|
||||
BookEntity bookEntity = bookRepository.findById(bookId).orElseThrow(() -> ApiError.BOOK_NOT_FOUND.createException(bookId));
|
||||
|
||||
UserBookProgressEntity progress = userBookProgressRepository
|
||||
.findByUserIdAndBookId(user.getId(), bookId)
|
||||
.orElse(new UserBookProgressEntity());
|
||||
|
||||
progress.setBook(bookEntity);
|
||||
progress.setUser(userEntity.orElseThrow());
|
||||
progress.setReadStatus(null);
|
||||
progress.setLastReadTime(null);
|
||||
progress.setPdfProgress(null);
|
||||
progress.setPdfProgressPercent(null);
|
||||
progress.setEpubProgress(null);
|
||||
progress.setEpubProgressPercent(null);
|
||||
progress.setCbxProgress(null);
|
||||
progress.setCbxProgressPercent(null);
|
||||
|
||||
userBookProgressRepository.save(progress);
|
||||
updatedBooks.add(bookMapper.toBook(bookEntity));
|
||||
}
|
||||
|
||||
return updatedBooks;
|
||||
}
|
||||
|
||||
|
||||
|
@ -293,9 +293,9 @@
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex">
|
||||
@if (entityType$ | async; as entityType) {
|
||||
<div class="flex gap-6 pr-2">
|
||||
<div class="flex gap-2 md:gap-6 pr-2">
|
||||
@if (userData.permissions.canEditMetadata) {
|
||||
<p-menu #menu [model]="metadataMenuItems" [popup]="true" appendTo="body"/>
|
||||
<p-menu #menu [model]="metadataMenuItems" [popup]="true" appendTo="body" class="hidden"/>
|
||||
<p-button
|
||||
(click)="menu.toggle($event)"
|
||||
pTooltip="Metadata actions"
|
||||
@ -343,10 +343,19 @@
|
||||
tooltipPosition="top">
|
||||
</p-button>
|
||||
}
|
||||
<p-menu #menuMore [model]="moreActionsMenuItems" [popup]="true" appendTo="body" class="hidden"/>
|
||||
<p-button
|
||||
(click)="menuMore.toggle($event)"
|
||||
pTooltip="More actions"
|
||||
tooltipPosition="top"
|
||||
outlined="true"
|
||||
severity="info"
|
||||
icon="pi pi-ellipsis-v">
|
||||
</p-button>
|
||||
</div>
|
||||
}
|
||||
<p-divider layout="vertical"></p-divider>
|
||||
<div class="flex gap-6 px-2">
|
||||
<div class="flex gap-2 md:gap-6 px-2">
|
||||
<p-button
|
||||
outlined="true"
|
||||
icon="pi pi-check-square"
|
||||
|
@ -149,6 +149,7 @@ export class BookBrowserComponent implements OnInit, AfterViewInit {
|
||||
lastAppliedSort: SortOption | null = null;
|
||||
private settingFiltersFromUrl = false;
|
||||
protected metadataMenuItems: MenuItem[] | undefined;
|
||||
protected moreActionsMenuItems: MenuItem[] | undefined;
|
||||
currentBooks: Book[] = [];
|
||||
lastSelectedIndex: number | null = null;
|
||||
showFilter: boolean = false;
|
||||
@ -226,8 +227,28 @@ export class BookBrowserComponent implements OnInit, AfterViewInit {
|
||||
command: () => this.multiBookEditMetadata()
|
||||
}
|
||||
];
|
||||
|
||||
this.moreActionsMenuItems = [
|
||||
{
|
||||
label: 'Reset Progress',
|
||||
icon: 'pi pi-undo',
|
||||
command: () => {
|
||||
this.confirmationService.confirm({
|
||||
message: 'Are you sure you want to reset progress for selected books?',
|
||||
header: 'Confirm Reset',
|
||||
icon: 'pi pi-exclamation-triangle',
|
||||
acceptLabel: 'Yes',
|
||||
rejectLabel: 'No',
|
||||
accept: () => {
|
||||
this.resetProgress();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
ngAfterViewInit() {
|
||||
|
||||
combineLatest({
|
||||
@ -688,4 +709,26 @@ export class BookBrowserComponent implements OnInit, AfterViewInit {
|
||||
moveFiles() {
|
||||
this.dialogHelperService.openFileMoverDialog(this.selectedBooks);
|
||||
}
|
||||
|
||||
resetProgress() {
|
||||
const bookIds = Array.from(this.selectedBooks);
|
||||
this.bookService.resetProgress(bookIds).subscribe({
|
||||
next: () => {
|
||||
this.messageService.add({
|
||||
severity: 'success',
|
||||
summary: 'Progress Reset',
|
||||
detail: 'Reading progress has been reset.',
|
||||
life: 1500
|
||||
});
|
||||
},
|
||||
error: () => {
|
||||
this.messageService.add({
|
||||
severity: 'error',
|
||||
summary: 'Failed',
|
||||
detail: 'Could not reset progress.',
|
||||
life: 1500
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -122,6 +122,7 @@ export class BookCardComponent implements OnInit, OnDestroy {
|
||||
},
|
||||
},
|
||||
...this.getPermissionBasedMenuItems(),
|
||||
...this.moreMenuItems(),
|
||||
];
|
||||
}
|
||||
|
||||
@ -258,6 +259,45 @@ export class BookCardComponent implements OnInit, OnDestroy {
|
||||
return items;
|
||||
}
|
||||
|
||||
private moreMenuItems(): MenuItem[] {
|
||||
const items: MenuItem[] = [];
|
||||
|
||||
if (this.hasEditMetadataPermission()) {
|
||||
items.push({
|
||||
label: 'More Actions',
|
||||
icon: 'pi pi-ellipsis-v',
|
||||
items: [
|
||||
{
|
||||
label: 'Reset Progress',
|
||||
icon: 'pi pi-undo',
|
||||
command: () => {
|
||||
this.bookService.resetProgress(this.book.id).subscribe({
|
||||
next: () => {
|
||||
this.messageService.add({
|
||||
severity: 'success',
|
||||
summary: 'Progress Reset',
|
||||
detail: 'Reading progress has been reset.',
|
||||
life: 1500
|
||||
});
|
||||
},
|
||||
error: () => {
|
||||
this.messageService.add({
|
||||
severity: 'error',
|
||||
summary: 'Failed',
|
||||
detail: 'Could not reset progress.',
|
||||
life: 1500
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private openShelfDialog(): void {
|
||||
this.dialogService.open(ShelfAssignerComponent, {
|
||||
header: `Update Book's Shelves`,
|
||||
|
@ -397,9 +397,10 @@ export class BookService {
|
||||
);
|
||||
}
|
||||
|
||||
resetProgress(bookId: number): Observable<Book> {
|
||||
return this.http.post<Book>(`${this.url}/${bookId}/reset-progress`, null).pipe(
|
||||
tap(updatedBook => this.handleBookUpdate(updatedBook))
|
||||
resetProgress(bookIds: number | number[]): Observable<Book[]> {
|
||||
const ids = Array.isArray(bookIds) ? bookIds : [bookIds];
|
||||
return this.http.post<Book[]>(`${this.url}/reset-progress`, ids).pipe(
|
||||
tap(updatedBooks => updatedBooks.forEach(book => this.handleBookUpdate(book)))
|
||||
);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user