Add ability to reset reading progress for a book

This commit is contained in:
aditya.chandel
2025-07-08 17:32:49 -06:00
committed by Aditya Chandel
parent 5afe0a1b0a
commit ceac960943
5 changed files with 88 additions and 13 deletions

View File

@@ -124,4 +124,11 @@ public class BookController {
bookService.updateReadStatus(bookId, request.status());
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);
}
}

View File

@@ -277,6 +277,24 @@ public class BookService {
return book;
}
public Book resetProgress(long bookId) {
BookLoreUser user = authenticationService.getAuthenticatedUser();
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.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);
}
@Transactional
public List<Book> assignShelvesToBooks(Set<Long> bookIds, Set<Long> shelfIdsToAssign, Set<Long> shelfIdsToUnassign) {

View File

@@ -190,7 +190,28 @@
{{ getFileExtension(book?.filePath) || '-' }}
</span>
</p>
<p class="whitespace-nowrap flex items-center">
<span class="font-bold mr-2">Progress:</span>
<span class="inline-flex items-center">
<span
class="inline-block px-2 py-0.5 rounded-full text-xs font-bold text-white"
[ngClass]="getProgressColorClass(getProgressPercent(book))">
{{ getProgressPercent(book) !== null ? getProgressPercent(book) + '%' : 'N/A' }}
</span>
@if (getProgressPercent(book) !== null) {
<p-button
pTooltip="Reset progress"
tooltipPosition="bottom"
icon="pi pi-refresh"
size="small"
severity="danger"
text
class="ml-1"
(onClick)="resetProgress(book)">
</p-button>
}
</span>
</p>
<p class="whitespace-nowrap flex items-center gap-2">
<span class="font-bold">Metadata Score:</span>
@if (book?.metadataMatchScore != null) {
@@ -203,14 +224,6 @@
<span>-</span>
}
</p>
<p class="whitespace-nowrap flex items-center gap-2">
<span class="font-bold">Progress:</span>
<span
class="inline-block px-2 py-0.5 rounded-full text-xs font-bold text-white"
[ngClass]="getProgressColorClass(getProgressPercent(book))">
{{ getProgressPercent(book) !== null ? getProgressPercent(book) + '%' : 'N/A' }}
</span>
</p>
<p class="whitespace-nowrap"><span class="font-bold">File Size:</span> {{ getFileSizeInMB(book) }}</p>
<p class="whitespace-nowrap"><span class="font-bold">ISBN 13:</span> {{ book?.metadata!.isbn13 || '-' }}</p>
</div>

View File

@@ -346,14 +346,14 @@ export class MetadataViewerComponent implements OnInit, OnChanges {
return 'bg-blue-500';
}
onPersonalRatingChange(book: Book, { value: personalRating }: RatingRateEvent): void {
onPersonalRatingChange(book: Book, {value: personalRating}: RatingRateEvent): void {
if (!book?.metadata) return;
const updatedMetadata = { ...book.metadata, personalRating };
const updatedMetadata = {...book.metadata, personalRating};
this.bookService.updateBookMetadata(book.id, {
metadata: updatedMetadata,
clearFlags: { personalRating: false }
clearFlags: {personalRating: false}
}, false).subscribe({
next: () => {
this.messageService.add({
@@ -461,4 +461,35 @@ export class MetadataViewerComponent implements OnInit, OnChanges {
});
});
}
resetProgress(book: Book): void {
this.confirmationService.confirm({
message: `Reset reading progress for "${book.metadata?.title}"?`,
header: 'Confirm Reset',
icon: 'pi pi-exclamation-triangle',
acceptLabel: 'Yes',
rejectLabel: 'Cancel',
acceptButtonStyleClass: 'p-button-danger',
accept: () => {
this.bookService.resetProgress(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
});
}
});
}
});
}
}

View File

@@ -397,6 +397,12 @@ 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))
);
}
/*------------------ All the websocket handlers go below ------------------*/
@@ -436,7 +442,7 @@ export class BookService {
updatedMap.has(book.id) ? updatedMap.get(book.id)! : book
);
this.bookStateSubject.next({ ...currentState, books: mergedBooks });
this.bookStateSubject.next({...currentState, books: mergedBooks});
}
handleBookMetadataUpdate(bookId: number, updatedMetadata: BookMetadata) {