Skip to content

feat: Add purchase date tracking for books#2673

Open
pratikscfr wants to merge 17 commits intobooklore-app:developfrom
pratikscfr:feat/add-purchase-date
Open

feat: Add purchase date tracking for books#2673
pratikscfr wants to merge 17 commits intobooklore-app:developfrom
pratikscfr:feat/add-purchase-date

Conversation

@pratikscfr
Copy link

📝 Description

Adds end-to-end support for tracking a book’s purchase date. This includes backend persistence, APIs, permissions, and validation, plus the corresponding frontend models, UI, sorting, and stats updates.

🏷️ Type of Change

  • Bug fix
  • New feature
  • Enhancement to existing feature
  • Refactor (no behavior change)
  • Breaking change (existing functionality affected)
  • Documentation update

🔧 Changes

  • add Flyway migration and entity/DTO/controller/service plumbing for [purchaseDate]
  • implement /api/v1/books/purchase-date with authorization + future-date validation
  • extend SortService, BookPatchService, BookService, and UI table/metadata viewer/charts to show & edit purchase dates (fallback to [addedOn]
  • add Vitest specs for BookPatchService + SortService purchase-date logic and JUnit coverage for new backend scenarios
  • prevent choosing future dates via PrimeNG picker and backend guardrails

🧪 Testing

  • docker compose -f dev.docker-compose.yml exec backend sh -c "cd /booklore-api && ./gradlew test --tests BookUpdateServiceTest"
  • docker compose -f dev.docker-compose.yml exec ui sh -c "cd /angular-app && npx vitest run"
  • docker compose -f dev.docker-compose.yml exec ui sh -c "cd /angular-app && npx ng build --configuration=development"

📸 Screenshots (if applicable)

Screenshot 2026-02-09 at 10 58 54 PM Screenshot 2026-02-09 at 11 02 15 PM Screenshot 2026-02-09 at 11 07 23 PM

✅ Pre-Submission Checklist

  • Code follows project style guidelines and conventions
  • Branch is up to date with develop (merge conflicts resolved)
  • Automated tests added or updated to cover changes (backend and frontend)
  • All tests pass locally (./gradlew test for backend, ng test for frontend)
  • Changes manually verified in local dev environment (including related features)
  • Flyway migration versioning is correct (if schema was modified)
  • Documentation PR submitted to booklore-docs (if user-facing changes)
  • PR is reasonably scoped (PRs over 500+ changed lines will be closed, split into smaller PRs)

🤖 AI-Assisted Contributions

  • Every line has been read, understood, and can be explained by the author
  • PR is scoped to a single logical change, not a dump of everything the AI suggested
  • Tests validate actual behavior, not just coverage (AI-generated tests often assert nothing meaningful)
  • No dead code, placeholder comments, TODOs, or unused scaffolding

- Add purchase_date column via V117 migration
- Update BookEntity and Book DTO
- Add updatePurchaseDate endpoint in BookController
- Implement update logic in BookUpdateService
- Update mappers and creator services to handle new field
- Add test cases for authorized purchase date updates
- Add test cases for unauthorized access attempts
- Add test cases for fallback to addedOn date
- Add test cases for missing book scenarios
- Add purchaseDate field to Book interface
- Update BookPatchService to handle purchase date updates
- Update sorting logic to support sorting by purchase date
- Add Purchase Date column to book table
- Add editable Purchase Date field in metadata viewer
- Update Reading Journey and Backlog charts to use purchase date (falling back to added date)
- Add BookPatchService tests for purchase date updates
- Test successful update scenarios
- Test fallback to addedOn date logic
- Restrict p-datepicker maxDate to current date
- Initialize maxDate in component
- Add validation check in BookUpdateService
- Throw INVALID_INPUT error if date is future
- Add unit test for future date validation
@pratikscfr pratikscfr marked this pull request as draft February 9, 2026 17:39
@pratikscfr pratikscfr marked this pull request as ready for review February 9, 2026 17:40
@pratikscfr
Copy link
Author

This PR addresses : #2496

@pratikscfr
Copy link
Author

Hello @acx10 / @balazs-szucs , can you review this PR?

.libraryPath(libraryFile.getLibraryPathEntity())
.addedOn(Instant.now())
.addedOn(now)
.purchaseDate(now)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not automatically set purchaseDate to now(). Adding a book to the library does not imply it was purchased at that time. This field should remain null unless explicitly provided.

@@ -0,0 +1,2 @@
ALTER TABLE book ADD COLUMN purchase_date DATETIME(6) NULL;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ALTER TABLE book ADD COLUMN IF NOT EXISTS purchase_date DATETIME(6) NULL;

@@ -0,0 +1,2 @@
ALTER TABLE book ADD COLUMN purchase_date DATETIME(6) NULL;
UPDATE book SET purchase_date = added_on WHERE purchase_date IS NULL;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This backfill isn’t needed. purchaseDate should remain NULL unless explicitly set.

// Track added dates
if (book.addedOn) {
const monthKey = this.getMonthKey(book.addedOn);
const acquisitionDate = book.purchaseDate || book.addedOn;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

purchaseDate is not needed in any of the current charts.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds end-to-end “purchase date” support for books across the API and UI, including persistence, update endpoint, and UI display/sorting/stats integration.

Changes:

  • Add purchase_date to the DB + backend entity/DTO mapping and initialize it for new books.
  • Add an API endpoint to update purchase dates with authorization and future-date validation.
  • Update UI models, metadata viewer editing, sorting options, and stats charts to use purchase date (with added-on fallback in several places), plus add Vitest/JUnit coverage.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
booklore-api/src/main/resources/db/migration/V117__Add_purchase_date_to_book.sql Adds purchase_date column and backfills it from added_on.
booklore-api/src/main/java/org/booklore/model/entity/BookEntity.java Adds purchaseDate field to the JPA entity.
booklore-api/src/main/java/org/booklore/model/dto/Book.java Exposes purchaseDate on the API DTO.
booklore-api/src/main/java/org/booklore/mapper/BookMapper.java Maps purchaseDate from entity to DTO.
booklore-api/src/main/java/org/booklore/model/dto/request/PurchaseDateUpdateRequest.java Adds request type for purchase-date updates.
booklore-api/src/main/java/org/booklore/controller/BookController.java Adds PUT /api/v1/books/purchase-date endpoint with authorization.
booklore-api/src/main/java/org/booklore/service/book/BookUpdateService.java Implements purchase-date update logic (authorization + future date validation).
booklore-api/src/main/java/org/booklore/service/book/BookCreatorService.java Sets purchase date when creating shell/imported books.
booklore-api/src/main/java/org/booklore/service/book/PhysicalBookService.java Sets purchase date when creating physical books.
booklore-api/src/test/java/org/booklore/service/book/BookUpdateServiceTest.java Adds JUnit coverage for purchase-date update scenarios.
booklore-ui/src/app/features/book/model/book.model.ts Adds purchaseDate?: string to the UI Book model.
booklore-ui/src/app/features/book/service/book.service.ts Adds UI service wrapper updatePurchaseDate(...).
booklore-ui/src/app/features/book/service/book-patch.service.ts Implements HTTP call + local state update for purchase date changes.
booklore-ui/src/app/features/book/service/book-patch.service.spec.ts Adds Vitest coverage for purchase-date patching/state updates.
booklore-ui/src/app/features/book/service/sort.service.ts Adds purchase-date sort field extractor.
booklore-ui/src/app/features/book/service/sort.service.spec.ts Adds Vitest coverage for purchase-date sorting.
booklore-ui/src/app/features/book/components/book-browser/sorting/BookSorter.ts Adds “Purchase Date” to selectable sort options.
booklore-ui/src/app/features/book/components/book-browser/book-table/book-table.component.ts Adds “Purchased” column rendering for purchase date.
booklore-ui/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-viewer.component.ts Adds purchase-date editing state + save/cancel handlers.
booklore-ui/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-viewer.component.html Adds purchase-date display + PrimeNG date picker editing UI.
booklore-ui/src/app/features/stats/component/user-stats/charts/reading-backlog-chart/reading-backlog-chart.component.ts Uses purchase date (fallback to added-on) in backlog calculations.
booklore-ui/src/app/features/stats/component/library-stats/charts/reading-journey-chart/reading-journey-chart.component.ts Uses purchase date (fallback to added-on) in journey stats/insights.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 103 to 105
addedOn: (book) => book.addedOn ? new Date(book.addedOn).getTime() : null,
purchaseDate: (book) => book.purchaseDate ? new Date(book.purchaseDate).getTime() : null,
fileSizeKb: (book) => book.fileSizeKb || null,
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new purchaseDate sort extractor ignores the stated fallback to addedOn that other parts of the UI use (charts/metadata viewer use purchaseDate || addedOn). If purchaseDate can be missing, sorting by it will push those books to the end instead of sorting by their acquisition date. Consider extracting from purchaseDate ?? addedOn for consistency.

Copilot uses AI. Check for mistakes.
Comment on lines 288 to 290
case 'purchaseDate':
return book.purchaseDate ? this.datePipe.transform(book.purchaseDate, 'dd-MMM-yyyy') ?? '' : '';

Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new Purchased column renders an empty string when purchaseDate is missing. Elsewhere (metadata viewer, stats) you fall back to addedOn. If the intended behavior is to always show an acquisition date, consider rendering purchaseDate ?? addedOn here as well.

Copilot uses AI. Check for mistakes.
BookLoreUser user = authenticationService.getAuthenticatedUser();
if (user == null) {
throw ApiError.FORBIDDEN.createException("You are not authorized to update purchase dates.");
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updatePurchaseDate calls bookRepository.findAllById(bookIds) before validating bookIds. If the request body omits bookIds (null) this will throw a server-side exception (500) instead of a controlled 4xx. Add an early guard for bookIds == null || bookIds.isEmpty() (and ideally return a BAD_REQUEST / INVALID_INPUT error) before the repository call.

Suggested change
}
}
if (bookIds == null || bookIds.isEmpty()) {
throw ApiError.INVALID_INPUT.createException("Book IDs must be provided.");
}

Copilot uses AI. Check for mistakes.
import java.time.Instant;
import java.util.List;

public record PurchaseDateUpdateRequest(List<Long> bookIds, Instant purchaseDate) {
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PurchaseDateUpdateRequest is annotated with @Valid at the controller, but the record components have no bean validation constraints. Consider adding at least @NotEmpty/@NotNull on bookIds so invalid payloads fail fast with a 400 rather than propagating deeper into the service layer.

Suggested change
public record PurchaseDateUpdateRequest(List<Long> bookIds, Instant purchaseDate) {
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
public record PurchaseDateUpdateRequest(
@NotEmpty List<Long> bookIds,
@NotNull Instant purchaseDate
) {

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,2 @@
ALTER TABLE book ADD COLUMN purchase_date DATETIME(6) NULL;
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The migration adds purchase_date as DATETIME(6), while the existing book.added_on column is TIMESTAMP (see V1__Create_Tables.sql). Mixing DATETIME and TIMESTAMP for the same logical timestamp type can introduce subtle timezone/default/precision inconsistencies. Consider using the same column type/precision as added_on (e.g., TIMESTAMP(6) if supported) or document why DATETIME(6) is required here.

Suggested change
ALTER TABLE book ADD COLUMN purchase_date DATETIME(6) NULL;
ALTER TABLE book ADD COLUMN purchase_date TIMESTAMP(6) NULL;

Copilot uses AI. Check for mistakes.
Comment on lines 237 to 239
private getBooksWithAddedDate(books: Book[]): Book[] {
return books.filter(book => book.addedOn);
return books.filter(book => book.purchaseDate || book.addedOn);
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getBooksWithAddedDate now filters on purchaseDate || addedOn, so the method name is no longer accurate and can mislead future readers. Rename it (and the booksWithDates variable) to reflect an acquisition date (purchase-or-added) concept.

Copilot uses AI. Check for mistakes.
Comment on lines 354 to 356
private calculateInsights(books: Book[], monthlyData: MonthlyData[]): JourneyInsights {
const booksWithAddedDate = books.filter(b => b.addedOn);
const booksWithAddedDate = books.filter(b => b.purchaseDate || b.addedOn);
const booksFinished = books.filter(b => b.dateFinished && b.readStatus === ReadStatus.READ);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

booksWithAddedDate now includes books with purchaseDate, so the variable name (and totalAdded meaning) is misleading. Consider renaming to something like booksWithAcquisitionDate / totalAcquired to match the updated logic and avoid confusion when interpreting the insights.

Copilot uses AI. Check for mistakes.
throw ApiError.GENERIC_NOT_FOUND.createException("One or more books not found");
}

boolean isAdmin = user != null && user.getPermissions() != null && user.getPermissions().isAdmin();
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is useless. user cannot be null at this check, since it is guarded by ... == ....

Suggested change
boolean isAdmin = user != null && user.getPermissions() != null && user.getPermissions().isAdmin();
boolean isAdmin = user.getPermissions() != null && user.getPermissions().isAdmin();

Copilot uses AI. Check for mistakes.
@pratikscfr
Copy link
Author

Thanks @acx10 for the review. I will look into them

@pratikscfr
Copy link
Author

Hello @acx10 , I made the changes requested by you. Can you take a look now?

@pratikscfr pratikscfr requested a review from acx10 February 12, 2026 05:01
@pratikscfr
Copy link
Author

Hi @acx10 , any more changes needed for this?

@balazs-szucs
Copy link
Collaborator

Hi, can you checkout if the field is written each file type e.g., PDFs EPUBs, and a CBZ? When the option enabled to "write to file"

@pratikscfr
Copy link
Author

Hi, can you checkout if the field is written each file type e.g., PDFs EPUBs, and a CBZ? When the option enabled to "write to file"

Added the support for adding purchase date meta data when enabled for types : epub, pdf, cbz

@balazs-szucs
Copy link
Collaborator

The primary reason for writing metadata to file is persistence. Simply writing to the metadata container is insufficient; you must validate the parser and do end-to-end testing. For example, if you write metadata to a file, delete the library, and cannot retrieve the same metadata when recreating the library, this indicates a problem.

Additionally, your current implementation does not conform to the Anansi Project's schema for CBX files: https://anansi-project.github.io/docs/comicinfo/schemas/v2.0

While writing metadata to the file is the straightforward component, please update the implementation to address these issues and ensure end-to-end testing is performed.

@pratikscfr
Copy link
Author

Hi @balazs-szucs , added your requested changes.

@pratikscfr
Copy link
Author

Hi @balazs-szucs / @acx10 , can I have this reviewed, thanks?

@Mock
private ContentRestrictionService contentRestrictionService;
@Mock
private org.booklore.service.metadata.writer.MetadataWriterFactory metadataWriterFactory;
Copy link
Collaborator

@acx10 acx10 Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new mock fields use fully qualified class names (FQN) instead of proper imports. This violates the project style. Please add proper import statements and use short names:

@Mock
private MetadataWriterFactory metadataWriterFactory;
@Mock
private AppSettingService appSettingService;
@Mock
private SidecarMetadataWriter sidecarMetadataWriter;

Same issue applies throughout the setUp() method where org.booklore.model.dto.settings.MetadataPersistenceSettings.SaveToOriginalFile, org.booklore.model.dto.settings.MetadataPersistenceSettings, and org.booklore.model.dto.settings.AppSettings are used as FQNs.

try {
builder.purchaseDate(java.time.Instant.parse(purchaseDateStr.trim()));
} catch (Exception ignored) {
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FQN usage here too: java.time.Instant.parse(purchaseDateStr.trim()). Please add import java.time.Instant; at the top and use Instant.parse(...).

Same issue in EpubMetadataExtractor.java and PdfMetadataExtractor.java with java.time.Instant.parse(content).

boolean isAdmin = user.getPermissions() != null && user.getPermissions().isAdmin();
if (!isAdmin) {
Set<Long> allowedLibraryIds = user.getAssignedLibraries().stream().map(Library::getId).collect(Collectors.toSet());
boolean hasAnyForbiddenLibrary = books.stream().anyMatch(b -> b.getLibrary() == null || !allowedLibraryIds.contains(b.getLibrary().getId()));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

java.io.File is used as FQN here. Please import it and use File.

@@ -359,6 +370,84 @@ private List<PersonalRatingUpdateResponse> buildRatingUpdateResponses(List<Long>
.collect(Collectors.toList());
Copy link
Collaborator

@acx10 acx10 Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing audit logging. The project convention requires auditService.log(...) calls for create/update/delete operations. Other update methods in this service use audit logging for significant state changes. Consider adding something like:

auditService.log(AuditAction.METADATA_UPDATED, "Updated purchase date for book(s): " + bookIds);

@Mapping(source = "libraryPath", target = "libraryPath", qualifiedByName = "mapLibraryPathIdOnly")
@Mapping(source = "metadata", target = "metadata")
@Mapping(source = "shelves", target = "shelves")
@Mapping(source = "bookFiles", target = "primaryFile", qualifiedByName = "mapPrimaryFile")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This @Mapping is redundant. When the source and target field names are identical, MapStruct maps them automatically. You can safely remove both of these purchaseDate mapping lines.

() -> bookUpdateService.updatePurchaseDate(List.of(1L), Instant.now()));
assertTrue(ex.getMessage().toLowerCase().contains("not authorized"));
verify(bookRepository, never()).saveAll(any());
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test name says "shouldFallbackToAddedOnWhenNull" but the backend doesn't actually fall back to addedOn. It just sets purchaseDate to null and the test asserts assertNull(book.getPurchaseDate()). The "fallback to addedOn" logic only exists in the frontend for display purposes.

Suggest renaming to something like updatePurchaseDate_shouldSetNullWhenPurchaseDateIsNull to accurately reflect the behavior being tested.

@@ -31,6 +31,7 @@ public class BookMetadata {
private String language;
private String narrator;
private Boolean abridged;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

purchaseDate is already on Book.java DTO (which is the book-level field). Adding it also to BookMetadata DTO seems redundant since purchase date is stored on BookEntity, not BookMetadataEntity. This dual placement could be confusing for future developers.

Consider removing it from BookMetadata and only keeping it on Book. The metadata viewer already accesses it via book.purchaseDate.

if (!isAdmin) {
Set<Long> allowedLibraryIds = user.getAssignedLibraries().stream().map(Library::getId).collect(Collectors.toSet());
boolean hasAnyForbiddenLibrary = books.stream().anyMatch(b -> b.getLibrary() == null || !allowedLibraryIds.contains(b.getLibrary().getId()));
if (hasAnyForbiddenLibrary) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method calls writer.saveMetadataToFile(file, book.getMetadata(), null, new MetadataClearFlags()) which writes all metadata to the file, not just the purchase date. If the user only updated the purchase date, this will also re-serialize every other metadata field.

This could cause issues if:

  1. The metadata entity isn't fully loaded (lazy loading)
  2. Another concurrent operation modified metadata

Is this intentional, or should this use a more targeted approach similar to how other single-field updates work?


public void copyPurchaseDate(boolean clear, Consumer<Instant> consumer) {
if (clear) consumer.accept(null);
else if (metadata.getBook() != null && metadata.getBook().getPurchaseDate() != null)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor style nit: the else-if body is missing braces, which is inconsistent with the rest of the methods in this class. Other methods like `copyPublishedDate` use braces:

```java
public void copyPurchaseDate(boolean clear, Consumer consumer) {
if (clear) {
consumer.accept(null);
} else if (metadata.getBook() != null && metadata.getBook().getPurchaseDate() != null) {
consumer.accept(metadata.getBook().getPurchaseDate());
}
}
```

Also, unlike other `copy*` methods, this one doesn't check a lock status. That seems intentional since purchase date lives on `BookEntity` rather than `BookMetadataEntity`, but it's worth calling out for clarity.

if (user == null) {
throw ApiError.FORBIDDEN.createException("You are not authorized to update purchase dates.");
}
List<BookEntity> books = bookRepository.findAllById(bookIds);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The null check bookIds != null on this line is redundant since you already checked bookIds == null above and would have thrown before reaching here.

books.forEach(book => {
const addedDate = new Date(book.addedOn!);
if (!book.addedOn) {
return;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The `reading-backlog-chart` null guard and the `let` to `const` changes in `book.service.ts` and `sort.service.ts` are unrelated to purchase date tracking. While individually fine, mixing unrelated cleanups into a feature PR makes the diff harder to review and bisect.

Consider keeping these as a separate commit or a small cleanup PR.

helper.copyPurchaseDate(clear != null && clear.isPurchaseDate(), date -> {
if (date != null) {
rdfDescription.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:booklore", "http://booklore.org/metadata/1.0/");
rdfDescription.appendChild(createSimpleElement(doc, "booklore:purchase_date", date.toString()));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the PDF writer, when `clear` is true, `copyPurchaseDate` passes `null` to the consumer. But the consumer here only acts when `date != null`:

```java
helper.copyPurchaseDate(clear != null && clear.isPurchaseDate(), date -> {
if (date != null) {
// ...
}
});
```

This means clearing the purchase date from a PDF is a no-op: the existing `booklore:purchase_date` element won't be removed. If clearing is expected to work, you need to handle removal of the existing element when `date` is null (similar to how `replaceBookloreMetaElement` works in the EPUB writer).

Resolve conflicts keeping both upstream changes and purchase date feature additions.
- Replace FQN imports with proper imports (Instant, File) in BookUpdateService,
  EpubMetadataExtractor, PdfMetadataExtractor, BookUpdateServiceTest
- Remove redundant @mapping for purchaseDate in BookMapper (MapStruct auto-maps)
- Remove redundant user null check and bookIds null check in BookUpdateService
- Add audit log.info for purchase date updates
- Use findAllWithMetadataByIds for eager loading in updatePurchaseDate
- Fix MetadataCopyHelper: add missing braces on else-if body
- Rename misleading test: shouldFallbackToAddedOnWhenNull -> shouldSetNullWhenPurchaseDateIsNull
- Fix sort.service.ts and book-table.component.ts: purchaseDate falls back to addedOn
@pratikscfr
Copy link
Author

Hi @acx10 / @balazs-szucs , completed your requested changes. Can you review once again?
Thanks.

@pratikscfr pratikscfr requested a review from acx10 February 23, 2026 06:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants