From ffd2adb032f05d43f47b42ff8b68dc5924edd5d8 Mon Sep 17 00:00:00 2001 From: ACX <8075870+acx10@users.noreply.github.com> Date: Fri, 13 Feb 2026 22:09:01 -0700 Subject: [PATCH] perf: strip unused fields from book list API response (#2741) * perf: strip unused fields from book list API response * fix: regenerate package-lock.json to sync with package.json --- .../org/booklore/model/dto/BookMetadata.java | 2 + .../service/book/BookQueryService.java | 181 ++++++- booklore-ui/package-lock.json | 471 +++++++++++++++++- .../src/app/features/book/model/book.model.ts | 1 + .../app/features/book/service/sort.service.ts | 5 +- 5 files changed, 650 insertions(+), 10 deletions(-) diff --git a/booklore-api/src/main/java/org/booklore/model/dto/BookMetadata.java b/booklore-api/src/main/java/org/booklore/model/dto/BookMetadata.java index a05c96653..c7d455eae 100644 --- a/booklore-api/src/main/java/org/booklore/model/dto/BookMetadata.java +++ b/booklore-api/src/main/java/org/booklore/model/dto/BookMetadata.java @@ -70,6 +70,8 @@ public class BookMetadata { private List bookReviews; private Double rating; + private Boolean allMetadataLocked; + private Boolean titleLocked; private Boolean subtitleLocked; private Boolean publisherLocked; diff --git a/booklore-api/src/main/java/org/booklore/service/book/BookQueryService.java b/booklore-api/src/main/java/org/booklore/service/book/BookQueryService.java index 0268b2880..6d6736753 100644 --- a/booklore-api/src/main/java/org/booklore/service/book/BookQueryService.java +++ b/booklore-api/src/main/java/org/booklore/service/book/BookQueryService.java @@ -2,6 +2,8 @@ package org.booklore.service.book; import org.booklore.mapper.v2.BookMapperV2; import org.booklore.model.dto.Book; +import org.booklore.model.dto.BookMetadata; +import org.booklore.model.dto.ComicMetadata; import org.booklore.model.entity.BookEntity; import org.booklore.repository.BookRepository; import org.booklore.service.restriction.ContentRestrictionService; @@ -22,13 +24,13 @@ public class BookQueryService { public List getAllBooks(boolean includeDescription) { List books = bookRepository.findAllWithMetadata(); - return mapBooksToDto(books, includeDescription, null); + return mapBooksToDto(books, includeDescription, null, !includeDescription); } public List getAllBooksByLibraryIds(Set libraryIds, boolean includeDescription, Long userId) { List books = bookRepository.findAllWithMetadataByLibraryIds(libraryIds); books = contentRestrictionService.applyRestrictions(books, userId); - return mapBooksToDto(books, includeDescription, userId); + return mapBooksToDto(books, includeDescription, userId, !includeDescription); } public List findAllWithMetadataByIds(Set bookIds) { @@ -43,13 +45,13 @@ public class BookQueryService { bookRepository.saveAll(books); } - private List mapBooksToDto(List books, boolean includeDescription, Long userId) { + private List mapBooksToDto(List books, boolean includeDescription, Long userId, boolean stripForListView) { return books.stream() - .map(book -> mapBookToDto(book, includeDescription, userId)) + .map(book -> mapBookToDto(book, includeDescription, userId, stripForListView)) .collect(Collectors.toList()); } - private Book mapBookToDto(BookEntity bookEntity, boolean includeDescription, Long userId) { + private Book mapBookToDto(BookEntity bookEntity, boolean includeDescription, Long userId, boolean stripForListView) { Book dto = bookMapperV2.toDTO(bookEntity); if (!includeDescription && dto.getMetadata() != null) { @@ -62,6 +64,175 @@ public class BookQueryService { .collect(Collectors.toSet())); } + if (stripForListView) { + stripFieldsForListView(dto); + } + return dto; } + + private void stripFieldsForListView(Book dto) { + dto.setLibraryPath(null); + + BookMetadata m = dto.getMetadata(); + if (m != null) { + // Compute allMetadataLocked before stripping lock flags + m.setAllMetadataLocked(computeAllMetadataLocked(m)); + + // Strip lock flags + m.setTitleLocked(null); + m.setSubtitleLocked(null); + m.setPublisherLocked(null); + m.setPublishedDateLocked(null); + m.setDescriptionLocked(null); + m.setSeriesNameLocked(null); + m.setSeriesNumberLocked(null); + m.setSeriesTotalLocked(null); + m.setIsbn13Locked(null); + m.setIsbn10Locked(null); + m.setAsinLocked(null); + m.setGoodreadsIdLocked(null); + m.setComicvineIdLocked(null); + m.setHardcoverIdLocked(null); + m.setHardcoverBookIdLocked(null); + m.setDoubanIdLocked(null); + m.setGoogleIdLocked(null); + m.setPageCountLocked(null); + m.setLanguageLocked(null); + m.setAmazonRatingLocked(null); + m.setAmazonReviewCountLocked(null); + m.setGoodreadsRatingLocked(null); + m.setGoodreadsReviewCountLocked(null); + m.setHardcoverRatingLocked(null); + m.setHardcoverReviewCountLocked(null); + m.setDoubanRatingLocked(null); + m.setDoubanReviewCountLocked(null); + m.setLubimyczytacIdLocked(null); + m.setLubimyczytacRatingLocked(null); + m.setRanobedbIdLocked(null); + m.setRanobedbRatingLocked(null); + m.setAudibleIdLocked(null); + m.setAudibleRatingLocked(null); + m.setAudibleReviewCountLocked(null); + m.setExternalUrlLocked(null); + m.setCoverLocked(null); + m.setAudiobookCoverLocked(null); + m.setAuthorsLocked(null); + m.setCategoriesLocked(null); + m.setMoodsLocked(null); + m.setTagsLocked(null); + m.setReviewsLocked(null); + m.setNarratorLocked(null); + m.setAbridgedLocked(null); + m.setAgeRatingLocked(null); + m.setContentRatingLocked(null); + + // Strip external IDs + m.setAsin(null); + m.setGoodreadsId(null); + m.setComicvineId(null); + m.setHardcoverId(null); + m.setHardcoverBookId(null); + m.setGoogleId(null); + m.setLubimyczytacId(null); + m.setRanobedbId(null); + m.setAudibleId(null); + m.setDoubanId(null); + + // Strip unused detail fields + m.setSubtitle(null); + m.setSeriesTotal(null); + m.setAbridged(null); + m.setExternalUrl(null); + m.setThumbnailUrl(null); + m.setProvider(null); + m.setAudiobookMetadata(null); + m.setBookReviews(null); + + // Strip unused ratings + m.setDoubanRating(null); + m.setDoubanReviewCount(null); + m.setAudibleRating(null); + m.setAudibleReviewCount(null); + m.setLubimyczytacRating(null); + + // Strip ComicMetadata fields + ComicMetadata cm = m.getComicMetadata(); + if (cm != null) { + // Strip comic lock flags + cm.setIssueNumberLocked(null); + cm.setVolumeNameLocked(null); + cm.setVolumeNumberLocked(null); + cm.setStoryArcLocked(null); + cm.setStoryArcNumberLocked(null); + cm.setAlternateSeriesLocked(null); + cm.setAlternateIssueLocked(null); + cm.setImprintLocked(null); + cm.setFormatLocked(null); + cm.setBlackAndWhiteLocked(null); + cm.setMangaLocked(null); + cm.setReadingDirectionLocked(null); + cm.setWebLinkLocked(null); + cm.setNotesLocked(null); + cm.setCreatorsLocked(null); + cm.setPencillersLocked(null); + cm.setInkersLocked(null); + cm.setColoristsLocked(null); + cm.setLetterersLocked(null); + cm.setCoverArtistsLocked(null); + cm.setEditorsLocked(null); + cm.setCharactersLocked(null); + cm.setTeamsLocked(null); + cm.setLocationsLocked(null); + + // Strip non-filter detail fields + cm.setIssueNumber(null); + cm.setVolumeName(null); + cm.setVolumeNumber(null); + cm.setStoryArc(null); + cm.setStoryArcNumber(null); + cm.setAlternateSeries(null); + cm.setAlternateIssue(null); + cm.setImprint(null); + cm.setFormat(null); + cm.setBlackAndWhite(null); + cm.setManga(null); + cm.setReadingDirection(null); + cm.setWebLink(null); + cm.setNotes(null); + } + } + } + + private boolean computeAllMetadataLocked(BookMetadata m) { + Boolean[] bookLocks = { + m.getTitleLocked(), m.getSubtitleLocked(), m.getPublisherLocked(), + m.getPublishedDateLocked(), m.getDescriptionLocked(), m.getSeriesNameLocked(), + m.getSeriesNumberLocked(), m.getSeriesTotalLocked(), m.getIsbn13Locked(), + m.getIsbn10Locked(), m.getAsinLocked(), m.getGoodreadsIdLocked(), + m.getComicvineIdLocked(), m.getHardcoverIdLocked(), m.getHardcoverBookIdLocked(), + m.getDoubanIdLocked(), m.getGoogleIdLocked(), m.getPageCountLocked(), + m.getLanguageLocked(), m.getAmazonRatingLocked(), m.getAmazonReviewCountLocked(), + m.getGoodreadsRatingLocked(), m.getGoodreadsReviewCountLocked(), + m.getHardcoverRatingLocked(), m.getHardcoverReviewCountLocked(), + m.getDoubanRatingLocked(), m.getDoubanReviewCountLocked(), + m.getLubimyczytacIdLocked(), m.getLubimyczytacRatingLocked(), + m.getRanobedbIdLocked(), m.getRanobedbRatingLocked(), + m.getAudibleIdLocked(), m.getAudibleRatingLocked(), m.getAudibleReviewCountLocked(), + m.getExternalUrlLocked(), m.getCoverLocked(), m.getAudiobookCoverLocked(), + m.getAuthorsLocked(), m.getCategoriesLocked(), m.getMoodsLocked(), + m.getTagsLocked(), m.getReviewsLocked(), m.getNarratorLocked(), + m.getAbridgedLocked(), m.getAgeRatingLocked(), m.getContentRatingLocked() + }; + + boolean hasAnyLock = false; + for (Boolean lock : bookLocks) { + if (Boolean.TRUE.equals(lock)) { + hasAnyLock = true; + } else { + return false; + } + } + return hasAnyLock; + } } diff --git a/booklore-ui/package-lock.json b/booklore-ui/package-lock.json index 5fb6b55f3..b54d1110a 100644 --- a/booklore-ui/package-lock.json +++ b/booklore-ui/package-lock.json @@ -463,6 +463,22 @@ "typescript": "*" } }, + "node_modules/@angular-eslint/schematics/node_modules/@angular-eslint/template-parser": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-21.2.0.tgz", + "integrity": "sha512-TCb3qYOC/uXKZCo56cJ6N9sHeWdFhyVqrbbYfFjTi09081T6jllgHDZL5Ms7gOMNY8KywWGGbhxwvzeA0RwTgA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "21.2.0", + "eslint-scope": "^9.0.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, "node_modules/@angular-eslint/schematics/node_modules/@angular-eslint/utils": { "version": "21.2.0", "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-21.2.0.tgz", @@ -478,6 +494,276 @@ "typescript": "*" } }, + "node_modules/@angular-eslint/schematics/node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/@typescript-eslint/utils": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz", + "integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/@angular-eslint/schematics/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/eslint/node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@angular-eslint/schematics/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@angular/animations": { "version": "21.1.4", "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.1.4.tgz", @@ -1880,6 +2166,141 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, "node_modules/@eslint/object-schema": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.1.tgz", @@ -5489,6 +5910,24 @@ "node": ">=18" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/chardet": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", @@ -5715,6 +6154,14 @@ "node": "^12.20.0 || >=14" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/content-disposition": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", @@ -7000,6 +7447,20 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -7803,6 +8264,14 @@ "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", "license": "MIT" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/log-symbols": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", @@ -10300,7 +10769,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/booklore-ui/src/app/features/book/model/book.model.ts b/booklore-ui/src/app/features/book/model/book.model.ts index c3ab0b8da..d5492dcb8 100644 --- a/booklore-ui/src/app/features/book/model/book.model.ts +++ b/booklore-ui/src/app/features/book/model/book.model.ts @@ -239,6 +239,7 @@ export interface BookMetadata { contentRating?: string | null; ageRatingLocked?: boolean; contentRatingLocked?: boolean; + allMetadataLocked?: boolean; [key: string]: unknown; } diff --git a/booklore-ui/src/app/features/book/service/sort.service.ts b/booklore-ui/src/app/features/book/service/sort.service.ts index 9aea14c63..a70be77e4 100644 --- a/booklore-ui/src/app/features/book/service/sort.service.ts +++ b/booklore-ui/src/app/features/book/service/sort.service.ts @@ -88,10 +88,7 @@ export class SortService { hardcoverRating: (book) => book.metadata?.hardcoverRating || null, hardcoverReviewCount: (book) => book.metadata?.hardcoverReviewCount || null, ranobedbRating: (book) => book.metadata?.ranobedbRating || null, - locked: (book) => - Object.keys(book.metadata ?? {}) - .filter((key) => key.endsWith('Locked')) - .every((key) => book.metadata?.[key] === true), + locked: (book) => book.metadata?.allMetadataLocked ?? false, lastReadTime: (book) => book.lastReadTime ? new Date(book.lastReadTime).getTime() : null, addedOn: (book) => book.addedOn ? new Date(book.addedOn).getTime() : null, fileSizeKb: (book) => book.fileSizeKb || null,