From e975799b6d33542f8f7395a91062000dfd640797 Mon Sep 17 00:00:00 2001 From: Maxime Bret Date: Sun, 28 Jul 2024 14:07:08 +0200 Subject: [PATCH 1/7] fix: fixed missing books from search --- packages/web/src/books/states.ts | 77 ++++++------------- packages/web/src/books/useVisibleBooks.ts | 27 ------- .../collections/CollectionDetailsScreen.tsx | 12 ++- .../web/src/collections/useCollections.ts | 1 - .../web/src/download/useDownloadedBooks.ts | 18 +++++ packages/web/src/home/helpers.ts | 6 +- .../web/src/reader/navigation/BottomBar.tsx | 1 - packages/web/src/search/SearchScreen.tsx | 28 +++---- packages/web/src/search/useBooksForSearch.ts | 22 ++++-- .../web/src/settings/ManageStorageScreen.tsx | 14 ++-- .../web/src/settings/StatisticsScreen.tsx | 2 +- 11 files changed, 90 insertions(+), 118 deletions(-) delete mode 100644 packages/web/src/books/useVisibleBooks.ts create mode 100644 packages/web/src/download/useDownloadedBooks.ts diff --git a/packages/web/src/books/states.ts b/packages/web/src/books/states.ts index 8dcf5a15..6420bc09 100644 --- a/packages/web/src/books/states.ts +++ b/packages/web/src/books/states.ts @@ -4,22 +4,20 @@ import { getLinkState, useLink, useLinks } from "../links/states" import { getBookDownloadsState, booksDownloadStateSignal, - DownloadState } from "../download/states" import { useCollections } from "../collections/useCollections" -import { from, map, switchMap } from "rxjs" +import { map, switchMap } from "rxjs" import { plugin as localPlugin } from "../plugins/local" import { latestDatabase$ } from "../rxdb/useCreateDatabase" -import { isDefined, useForeverQuery, useQuery, useSignalValue } from "reactjrx" +import { useForeverQuery, useSignalValue } from "reactjrx" import { keyBy } from "lodash" import { Database } from "../rxdb" import { BookDocType, CollectionDocType } from "@oboku/shared" import { DeepReadonlyObject, MangoQuery } from "rxdb" -import { useVisibleBooks } from "./useVisibleBooks" -import { DeepReadonlyArray, RxDocument } from "rxdb/dist/types/types" +import { DeepReadonlyArray } from "rxdb/dist/types/types" import { useMemo } from "react" -import { getBooksQueryObj } from "./dbHelpers" -import { CollectionDocMethods } from "../rxdb/collections/collection" +import { observeBooks } from "./dbHelpers" +import { libraryStateSignal } from "../library/states" export const getBooksByIds = async (database: Database) => { const result = await database.collections.book.find({}).exec() @@ -40,6 +38,7 @@ export const useBooks = ({ ids?: DeepReadonlyArray } = {}) => { const serializedIds = JSON.stringify(ids) + const { isLibraryUnlocked } = useSignalValue(libraryStateSignal) return useForeverQuery({ queryKey: [ @@ -47,17 +46,22 @@ export const useBooks = ({ "get", "many", "books", - { isNotInterested, serializedIds }, + { isNotInterested, serializedIds, isLibraryUnlocked }, queryObj ], - queryFn: () => { - const finalQueryObj = getBooksQueryObj({ queryObj, isNotInterested, ids }) - - return latestDatabase$.pipe( - switchMap((db) => db.collections.book.find(finalQueryObj).$), + queryFn: () => + latestDatabase$.pipe( + switchMap((db) => + observeBooks({ + db, + includeProtected: isLibraryUnlocked, + ids, + isNotInterested, + queryObj + }) + ), map((items) => items.map((item) => item.toJSON())) ) - } }) } @@ -268,23 +272,6 @@ export const useEnrichedBookState = ({ ) } -/** - * @deprecated - */ -export const useDownloadedBookWithUnsafeProtectedIdsState = () => { - const downloadState = useSignalValue(booksDownloadStateSignal) - const { data: books } = useBooks() - - return useMemo( - () => - books?.filter( - (book) => - downloadState[book._id]?.downloadState === DownloadState.Downloaded - ), - [downloadState, books] - ) -} - /** * @deprecated */ @@ -296,7 +283,11 @@ export const useBooksAsArrayState = ({ > }) => { const { data: books = {}, isPending } = useBooksDic() - const visibleBookIds = useVisibleBookIds() ?? [] + const { data: visibleBooks } = useBooks() + const visibleBookIds = useMemo( + () => visibleBooks?.map((item) => item._id) ?? [], + [visibleBooks] + ) const bookResult: (BookQueryResult & { downloadState: ReturnType @@ -325,27 +316,6 @@ export const useBooksAsArrayState = ({ } } -export const useVisibleBookIds = ( - params: Parameters[0] = {} -) => { - return useVisibleBooks(params).data?.map((book) => book._id) -} - -/** - * @deprecated - */ -export const useBookTagsState = ({ - bookId, - tags = {} -}: { - bookId: string - tags: ReturnType["data"] -}) => { - const { data: book } = useBook({ id: bookId }) - - return book?.tags?.map((id) => tags[id]).filter(isDefined) -} - /** * @deprecated */ @@ -365,4 +335,3 @@ export const useBookLinksState = ({ export const books$ = latestDatabase$.pipe( switchMap((database) => database?.book.find({}).$) ) - diff --git a/packages/web/src/books/useVisibleBooks.ts b/packages/web/src/books/useVisibleBooks.ts deleted file mode 100644 index e8e3b9d2..00000000 --- a/packages/web/src/books/useVisibleBooks.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useBooks } from "./states" -import { useProtectedTagIds } from "../tags/helpers" -import { useSignalValue } from "reactjrx" -import { libraryStateSignal } from "../library/states" -import { useMemo } from "react" -import { intersection } from "lodash" - -export const useVisibleBooks = ( - params: Parameters[0] = {} -) => { - const { data: books, isLoading: isBooksLoading } = useBooks(params) - const { data: protectedTagIds, isLoading: isTagsLoading } = - useProtectedTagIds() - const { isLibraryUnlocked } = useSignalValue(libraryStateSignal) - - const data = useMemo(() => { - if (isLibraryUnlocked) { - return books - } else { - return books?.filter( - (book) => intersection(protectedTagIds, book?.tags || []).length === 0 - ) - } - }, [books, protectedTagIds, isLibraryUnlocked]) - - return { data, isLoading: isBooksLoading || isTagsLoading } -} diff --git a/packages/web/src/collections/CollectionDetailsScreen.tsx b/packages/web/src/collections/CollectionDetailsScreen.tsx index 1d8c86dd..166670f0 100644 --- a/packages/web/src/collections/CollectionDetailsScreen.tsx +++ b/packages/web/src/collections/CollectionDetailsScreen.tsx @@ -4,7 +4,6 @@ import { useNavigate, useParams } from "react-router-dom" import EmptyLibraryAsset from "../assets/empty-library.svg" import CollectionBgSvg from "../assets/series-bg.svg" import { BookListWithControls } from "../books/bookList/BookListWithControls" -import { useVisibleBookIds } from "../books/states" import { signal, useSignalValue } from "reactjrx" import { ListActionSorting, @@ -13,6 +12,8 @@ import { import { useCollectionActionsDrawer } from "./CollectionActionsDrawer/useCollectionActionsDrawer" import { useCollection } from "./useCollection" import { COLLECTION_EMPTY_ID } from "../constants.shared" +import { useMemo } from "react" +import { useBooks } from "../books/states" type ScreenParams = { id: string @@ -40,10 +41,15 @@ export const CollectionDetailsScreen = () => { id }) - const visibleBooks = useVisibleBookIds({ + const { data: visibleBooks } = useBooks({ ids: collection?.books ?? [] }) + const visibleBookIds = useMemo( + () => visibleBooks?.map((item) => item._id) ?? [], + [visibleBooks] + ) + const { open: openActionDrawer } = useCollectionActionsDrawer( id, (changes) => { @@ -110,7 +116,7 @@ export const CollectionDetailsScreen = () => { { diff --git a/packages/web/src/collections/useCollections.ts b/packages/web/src/collections/useCollections.ts index ad056419..7e945ecd 100644 --- a/packages/web/src/collections/useCollections.ts +++ b/packages/web/src/collections/useCollections.ts @@ -65,7 +65,6 @@ export const useCollections = ({ observeBooks({ db, includeProtected: isLibraryUnlocked - // isNotInterested }).pipe( switchMap((books) => { const protectedBookIds = books.map(({ _id }) => _id) diff --git a/packages/web/src/download/useDownloadedBooks.ts b/packages/web/src/download/useDownloadedBooks.ts new file mode 100644 index 00000000..a37c7b1b --- /dev/null +++ b/packages/web/src/download/useDownloadedBooks.ts @@ -0,0 +1,18 @@ +import { useMemo } from "react" +import { useSignalValue } from "reactjrx" +import { useBooks } from "../books/states" +import { booksDownloadStateSignal, DownloadState } from "./states" + +export const useDownloadedBooks = () => { + const downloadState = useSignalValue(booksDownloadStateSignal) + const { data: books } = useBooks() + + return useMemo( + () => + books?.filter( + (book) => + downloadState[book._id]?.downloadState === DownloadState.Downloaded + ), + [downloadState, books] + ) +} diff --git a/packages/web/src/home/helpers.ts b/packages/web/src/home/helpers.ts index cec30004..aa93f548 100644 --- a/packages/web/src/home/helpers.ts +++ b/packages/web/src/home/helpers.ts @@ -2,12 +2,12 @@ import { useMemo } from "react" import { ReadingStateState } from "@oboku/shared" import { useBooksSortedBy } from "../books/helpers" import { useProtectedTagIds } from "../tags/helpers" -import { useVisibleBooks } from "../books/useVisibleBooks" +import { useBooks } from "../books/states" export const useContinueReadingBooks = () => { const { isPending } = useProtectedTagIds() - const { data: booksAsArray, isLoading: isBooksPending } = useVisibleBooks({ + const { data: booksAsArray, isLoading: isBooksPending } = useBooks({ isNotInterested: "none" }) const booksSortedByDate = useBooksSortedBy(booksAsArray, "activity") @@ -28,7 +28,7 @@ export const useContinueReadingBooks = () => { } export const useRecentlyAddedBooks = () => { - const { data: booksAsArray } = useVisibleBooks({ + const { data: booksAsArray } = useBooks({ isNotInterested: "none" }) diff --git a/packages/web/src/reader/navigation/BottomBar.tsx b/packages/web/src/reader/navigation/BottomBar.tsx index d530804e..38a8ae38 100644 --- a/packages/web/src/reader/navigation/BottomBar.tsx +++ b/packages/web/src/reader/navigation/BottomBar.tsx @@ -21,7 +21,6 @@ export const BottomBar = () => { const reader = useSignalValue(readerStateSignal) const navigation = useObserve(reader?.navigation.state$ ?? NEVER) const { data: pagination } = usePagination() - // const showScrubber = (totalPages || 1) > 1 const showScrubber = true const { useOptimizedTheme } = useLocalSettings() diff --git a/packages/web/src/search/SearchScreen.tsx b/packages/web/src/search/SearchScreen.tsx index 0e021828..e7c97de2 100644 --- a/packages/web/src/search/SearchScreen.tsx +++ b/packages/web/src/search/SearchScreen.tsx @@ -69,8 +69,20 @@ const SeeMore = ({ ) } +const useClasses = makeStyles((theme) => ({ + inputRoot: { + color: "inherit", + width: "100%" + }, + inputInput: { + padding: theme.spacing(1, 1, 1, 1), + width: "100%" + } +})) + export const SearchScreen = () => { - const { styles, classes } = useStyles() + const { styles } = useStyles() + const classes = useClasses() const [searchParams, setSearchParams] = useSearchParams() const value = useSignalValue(searchStateSignal) const { data: collections = [] } = useCollectionsForSearch(value) @@ -210,20 +222,8 @@ export const SearchScreen = () => { ) } -const useClasses = makeStyles((theme) => ({ - inputRoot: { - color: "inherit", - width: "100%" - }, - inputInput: { - padding: theme.spacing(1, 1, 1, 1), - width: "100%" - } -})) - const useStyles = () => { const theme = useTheme() - const classes = useClasses() const styles = useCSS( () => ({ @@ -248,5 +248,5 @@ const useStyles = () => { [theme] ) - return { styles, classes } + return { styles } } diff --git a/packages/web/src/search/useBooksForSearch.ts b/packages/web/src/search/useBooksForSearch.ts index 207ec068..11a684bc 100644 --- a/packages/web/src/search/useBooksForSearch.ts +++ b/packages/web/src/search/useBooksForSearch.ts @@ -1,17 +1,17 @@ import { getMetadataFromBook } from "../books/metadata" import { REGEXP_SPECIAL_CHAR } from "./useCollectionsForSearch" import { sortByTitleComparator } from "@oboku/shared" -import { useVisibleBooks } from "../books/useVisibleBooks" import { useMemo } from "react" import { useSignalValue } from "reactjrx" import { searchListActionsToolbarSignal } from "./list/states" +import { useBooks } from "../books/states" export const useBooksForSearch = (search: string) => { const { notInterestedContents } = useSignalValue( searchListActionsToolbarSignal ) - const { data: visibleBooks } = useVisibleBooks({ + const { data: visibleBooks } = useBooks({ isNotInterested: notInterestedContents }) @@ -19,13 +19,19 @@ export const useBooksForSearch = (search: string) => { () => visibleBooks ?.filter((book) => { - return book.metadata?.some(({ title }) => { - if (!title) return false + const searchRegex = new RegExp( + search.replace(REGEXP_SPECIAL_CHAR, `\\$&`) || "", + "i" + ) + + const metadata = book.metadata?.length + ? book.metadata + : [getMetadataFromBook(book)] - const searchRegex = new RegExp( - search.replace(REGEXP_SPECIAL_CHAR, `\\$&`) || "", - "i" - ) + return metadata?.some((item) => { + const { title } = item + + if (!title) return false const indexOfFirstMatch = title?.search(searchRegex) || 0 diff --git a/packages/web/src/settings/ManageStorageScreen.tsx b/packages/web/src/settings/ManageStorageScreen.tsx index 34aa0ebf..67b632cb 100644 --- a/packages/web/src/settings/ManageStorageScreen.tsx +++ b/packages/web/src/settings/ManageStorageScreen.tsx @@ -20,10 +20,6 @@ import { } from "@mui/icons-material" import { useStorageUse } from "./useStorageUse" import { BookList } from "../books/bookList/BookList" -import { - useDownloadedBookWithUnsafeProtectedIdsState, - useVisibleBookIds -} from "../books/states" import { bookActionDrawerSignal } from "../books/drawer/BookActionsDrawer" import { useDownloadedFilesInfo } from "../download/useDownloadedFilesInfo" import { useRemoveDownloadFile } from "../download/useRemoveDownloadFile" @@ -34,11 +30,17 @@ import { useEffect } from "react" import { useMutation } from "reactjrx" import { useRemoveAllDownloadedFiles } from "../download/useRemoveAllDownloadedFiles" import { useRemoveCoversInCache } from "../covers/useRemoveCoversInCache" +import { useDownloadedBooks } from "../download/useDownloadedBooks" +import { useBooks } from "../books/states" export const ManageStorageScreen = () => { - const books = useDownloadedBookWithUnsafeProtectedIdsState() + const books = useDownloadedBooks() const bookIds = useMemo(() => books?.map((book) => book._id) ?? [], [books]) - const visibleBookIds = useVisibleBookIds() + const { data: visibleBooks } = useBooks() + const visibleBookIds = useMemo( + () => visibleBooks?.map((item) => item._id) ?? [], + [visibleBooks] + ) const { quotaUsed, quotaInGb, usedInMb, covers, coversWightInMb } = useStorageUse([books]) const { mutate: removeCoversInCache } = useRemoveCoversInCache() diff --git a/packages/web/src/settings/StatisticsScreen.tsx b/packages/web/src/settings/StatisticsScreen.tsx index 8261b083..d99b63af 100644 --- a/packages/web/src/settings/StatisticsScreen.tsx +++ b/packages/web/src/settings/StatisticsScreen.tsx @@ -1,7 +1,7 @@ import { TopBarNavigation } from "../navigation/TopBarNavigation" import { Box, List, ListItem, ListItemText, ListSubheader } from "@mui/material" -import { useBooks } from "../books/states" import { useCollections } from "../collections/useCollections" +import { useBooks } from "../books/states" export const StatisticsScreen = () => { const { data: books } = useBooks() From 3d2eb3ed7f419666525e573263d6ee0e913ede0a Mon Sep 17 00:00:00 2001 From: Maxime Bret Date: Sun, 28 Jul 2024 14:12:01 +0200 Subject: [PATCH 2/7] feat: rename --- .../{collections => shelves}/CollectionStateDialog.tsx | 0 .../web/src/library/{collections => shelves}/FilterBar.tsx | 0 .../src/library/{collections => shelves}/FiltersDrawer.tsx | 0 .../{collections => shelves}/LibraryCollectionScreen.tsx | 4 ++-- packages/web/src/library/{collections => shelves}/state.ts | 0 .../{collections/useShelve.ts => shelves/useShelves.ts} | 2 +- packages/web/src/navigation/AppNavigator.tsx | 2 +- packages/web/src/profile/index.ts | 2 +- 8 files changed, 5 insertions(+), 5 deletions(-) rename packages/web/src/library/{collections => shelves}/CollectionStateDialog.tsx (100%) rename packages/web/src/library/{collections => shelves}/FilterBar.tsx (100%) rename packages/web/src/library/{collections => shelves}/FiltersDrawer.tsx (100%) rename packages/web/src/library/{collections => shelves}/LibraryCollectionScreen.tsx (97%) rename packages/web/src/library/{collections => shelves}/state.ts (100%) rename packages/web/src/library/{collections/useShelve.ts => shelves/useShelves.ts} (97%) diff --git a/packages/web/src/library/collections/CollectionStateDialog.tsx b/packages/web/src/library/shelves/CollectionStateDialog.tsx similarity index 100% rename from packages/web/src/library/collections/CollectionStateDialog.tsx rename to packages/web/src/library/shelves/CollectionStateDialog.tsx diff --git a/packages/web/src/library/collections/FilterBar.tsx b/packages/web/src/library/shelves/FilterBar.tsx similarity index 100% rename from packages/web/src/library/collections/FilterBar.tsx rename to packages/web/src/library/shelves/FilterBar.tsx diff --git a/packages/web/src/library/collections/FiltersDrawer.tsx b/packages/web/src/library/shelves/FiltersDrawer.tsx similarity index 100% rename from packages/web/src/library/collections/FiltersDrawer.tsx rename to packages/web/src/library/shelves/FiltersDrawer.tsx diff --git a/packages/web/src/library/collections/LibraryCollectionScreen.tsx b/packages/web/src/library/shelves/LibraryCollectionScreen.tsx similarity index 97% rename from packages/web/src/library/collections/LibraryCollectionScreen.tsx rename to packages/web/src/library/shelves/LibraryCollectionScreen.tsx index 4686429d..8219b93b 100644 --- a/packages/web/src/library/collections/LibraryCollectionScreen.tsx +++ b/packages/web/src/library/shelves/LibraryCollectionScreen.tsx @@ -15,7 +15,7 @@ import { useMeasureElement } from "../../common/utils" import { CollectionList } from "../../collections/list/CollectionList" import { useDebouncedCallback } from "use-debounce" import { signal, useSignalValue } from "reactjrx" -import { useShelve } from "./useShelve" +import { useShelves } from "./useShelves" import { FilterBar } from "./FilterBar" import { useCreateCollection } from "../../collections/useCreateCollection" import { collectionsListSignal } from "./state" @@ -48,7 +48,7 @@ export const LibraryCollectionScreen = () => { collectionsListSignal, ({ viewMode }) => ({ viewMode }) ) - const { data: collections = [] } = useShelve() + const { data: collections = [] } = useShelves() const onScroll = useDebouncedCallback((value: Scroll) => { libraryCollectionScreenPreviousScrollState.setValue(value) diff --git a/packages/web/src/library/collections/state.ts b/packages/web/src/library/shelves/state.ts similarity index 100% rename from packages/web/src/library/collections/state.ts rename to packages/web/src/library/shelves/state.ts diff --git a/packages/web/src/library/collections/useShelve.ts b/packages/web/src/library/shelves/useShelves.ts similarity index 97% rename from packages/web/src/library/collections/useShelve.ts rename to packages/web/src/library/shelves/useShelves.ts index 63ae97e6..9db8749f 100644 --- a/packages/web/src/library/collections/useShelve.ts +++ b/packages/web/src/library/shelves/useShelves.ts @@ -6,7 +6,7 @@ import { collectionsListSignal } from "./state" import { useCollection } from "../../collections/useCollection" import { COLLECTION_EMPTY_ID } from "../../constants.shared" -export const useShelve = () => { +export const useShelves = () => { const { showNotInterestedCollections } = useSignalValue( libraryStateSignal, ({ showNotInterestedCollections }) => ({ showNotInterestedCollections }) diff --git a/packages/web/src/navigation/AppNavigator.tsx b/packages/web/src/navigation/AppNavigator.tsx index 9e920415..90edf1ca 100644 --- a/packages/web/src/navigation/AppNavigator.tsx +++ b/packages/web/src/navigation/AppNavigator.tsx @@ -25,7 +25,7 @@ import { StatisticsScreen } from "../settings/StatisticsScreen" import { BackToReadingDialog } from "../reading/BackToReadingDialog" import { ProblemsScreen } from "../problems/ProblemsScreen" import { LibraryBooksScreen } from "../library/LibraryBooksScreen" -import { LibraryCollectionScreen } from "../library/collections/LibraryCollectionScreen" +import { LibraryCollectionScreen } from "../library/shelves/LibraryCollectionScreen" import { LibraryTagsScreen } from "../library/LibraryTagsScreen" import { memo, useEffect, useRef } from "react" import { UnlockLibraryDialog } from "../auth/UnlockLibraryDialog" diff --git a/packages/web/src/profile/index.ts b/packages/web/src/profile/index.ts index ee799674..0c3d5249 100644 --- a/packages/web/src/profile/index.ts +++ b/packages/web/src/profile/index.ts @@ -4,7 +4,7 @@ import { libraryStateSignal } from "../library/states" import { readerSettingsStateSignal } from "../reader/settings/states" import { bookBeingReadStatePersist } from "../reading/states" import { localSettingsStatePersist } from "../settings/states" -import { collectionsListSignal } from "../library/collections/state" +import { collectionsListSignal } from "../library/shelves/state" import { collectionDetailsScreenListControlsStateSignal } from "../collections/CollectionDetailsScreen" import { searchListActionsToolbarSignal } from "../search/list/states" import { SignalPersistenceConfig } from "reactjrx" From 0a673ad1bbb359e8ad05faef1013dcb9a579608f Mon Sep 17 00:00:00 2001 From: Maxime Bret Date: Sun, 28 Jul 2024 15:11:49 +0200 Subject: [PATCH 3/7] feat: added collections with dangling books repair --- packages/web/src/books/states.ts | 11 ++-- .../web/src/collections/useCollections.ts | 15 +++--- .../src/problems/CollectionDanglingBooks.tsx | 28 ++++++++++ packages/web/src/problems/ProblemsScreen.tsx | 52 ++++++++----------- .../web/src/problems/useFixableCollections.ts | 37 +++++++++++++ packages/web/src/problems/useRepair.ts | 52 +++++++++++++++++++ 6 files changed, 154 insertions(+), 41 deletions(-) create mode 100644 packages/web/src/problems/CollectionDanglingBooks.tsx create mode 100644 packages/web/src/problems/useFixableCollections.ts create mode 100644 packages/web/src/problems/useRepair.ts diff --git a/packages/web/src/books/states.ts b/packages/web/src/books/states.ts index 6420bc09..d1b6d0db 100644 --- a/packages/web/src/books/states.ts +++ b/packages/web/src/books/states.ts @@ -3,7 +3,7 @@ import { useProtectedTagIds, useTagsByIds } from "../tags/helpers" import { getLinkState, useLink, useLinks } from "../links/states" import { getBookDownloadsState, - booksDownloadStateSignal, + booksDownloadStateSignal } from "../download/states" import { useCollections } from "../collections/useCollections" import { map, switchMap } from "rxjs" @@ -31,14 +31,17 @@ export const getBooksByIds = async (database: Database) => { export const useBooks = ({ queryObj = {}, isNotInterested, - ids + ids, + includeProtected: _includeProtected }: { queryObj?: MangoQuery isNotInterested?: "none" | "with" | "only" ids?: DeepReadonlyArray + includeProtected?: boolean } = {}) => { const serializedIds = JSON.stringify(ids) const { isLibraryUnlocked } = useSignalValue(libraryStateSignal) + const includeProtected = _includeProtected || isLibraryUnlocked return useForeverQuery({ queryKey: [ @@ -46,7 +49,7 @@ export const useBooks = ({ "get", "many", "books", - { isNotInterested, serializedIds, isLibraryUnlocked }, + { isNotInterested, serializedIds, includeProtected }, queryObj ], queryFn: () => @@ -54,7 +57,7 @@ export const useBooks = ({ switchMap((db) => observeBooks({ db, - includeProtected: isLibraryUnlocked, + includeProtected, ids, isNotInterested, queryObj diff --git a/packages/web/src/collections/useCollections.ts b/packages/web/src/collections/useCollections.ts index 7e945ecd..f24301d2 100644 --- a/packages/web/src/collections/useCollections.ts +++ b/packages/web/src/collections/useCollections.ts @@ -2,7 +2,7 @@ import { CollectionDocType, directives, ReadingStateState } from "@oboku/shared" import { useLocalSettings } from "../settings/states" import { useForeverQuery, useSignalValue } from "reactjrx" import { latestDatabase$ } from "../rxdb/useCreateDatabase" -import { map, switchMap } from "rxjs" +import { map, switchMap, tap } from "rxjs" import { MangoQuery } from "rxdb" import { getMetadataFromCollection } from "./getMetadataFromCollection" import { DeepReadonlyArray } from "rxdb/dist/types/types" @@ -18,6 +18,7 @@ export const useCollections = ({ ids, isNotInterested, readingState = "any", + includeProtected: _includeProtected, ...options }: { queryObj?: MangoQuery @@ -31,12 +32,14 @@ export const useCollections = ({ isNotInterested?: "with" | "none" | "only" | undefined readingState?: "ongoing" | "finished" | "any" ids?: DeepReadonlyArray + includeProtected?: boolean } = {}) => { const { hideDirectivesFromCollectionName } = useLocalSettings() const serializedBookIds = JSON.stringify(bookIds) const serializedIds = JSON.stringify(ids) const { isLibraryUnlocked } = useSignalValue(libraryStateSignal) const { showCollectionWithProtectedContent } = useLocalSettings() + const includeProtected = _includeProtected || isLibraryUnlocked return useForeverQuery({ queryKey: [ @@ -47,7 +50,7 @@ export const useCollections = ({ serializedBookIds, serializedIds, showCollectionWithProtectedContent, - isLibraryUnlocked, + includeProtected, hideDirectivesFromCollectionName, isNotInterested }, @@ -64,10 +67,10 @@ export const useCollections = ({ */ observeBooks({ db, - includeProtected: isLibraryUnlocked + includeProtected }).pipe( switchMap((books) => { - const protectedBookIds = books.map(({ _id }) => _id) + const visibleBooks = books.map(({ _id }) => _id) const notInterestedBookIds = books .filter(({ isNotInterested }) => !!isNotInterested) .map(({ _id }) => _id) @@ -98,7 +101,7 @@ export const useCollections = ({ map((collections) => collections.filter((collection) => { if ( - isLibraryUnlocked || + includeProtected || collection.books.length === 0 || showCollectionWithProtectedContent === "hasNormalContent" ) @@ -110,7 +113,7 @@ export const useCollections = ({ */ const extraBooksFromCollection = difference( collection.books, - protectedBookIds + visibleBooks ) const hasSuspiciousExtraBook = diff --git a/packages/web/src/problems/CollectionDanglingBooks.tsx b/packages/web/src/problems/CollectionDanglingBooks.tsx new file mode 100644 index 00000000..bcf747af --- /dev/null +++ b/packages/web/src/problems/CollectionDanglingBooks.tsx @@ -0,0 +1,28 @@ +import { LinkOffRounded } from "@mui/icons-material" +import { ListItemButton, ListItemIcon, ListItemText } from "@mui/material" +import { CollectionDocType } from "@oboku/shared" +import { getMetadataFromCollection } from "../collections/getMetadataFromCollection" + +export const CollectionDanglingBooks = ({ + danglingBooks, + doc, + onClick +}: { + doc: CollectionDocType + danglingBooks: string[] + onClick?: () => void +}) => { + return ( + + + + + + + ) +} diff --git a/packages/web/src/problems/ProblemsScreen.tsx b/packages/web/src/problems/ProblemsScreen.tsx index 4ce14580..7da6cc18 100644 --- a/packages/web/src/problems/ProblemsScreen.tsx +++ b/packages/web/src/problems/ProblemsScreen.tsx @@ -6,7 +6,6 @@ import { TopBarNavigation } from "../navigation/TopBarNavigation" import { BuildRounded } from "@mui/icons-material" import { useFixCollections } from "./useFixCollections" import { useFixBookReferences } from "./useFixBookReferences" -import { useDuplicatedResourceIdLinks } from "./useDuplicateLinks" import { useFixBooksDanglingLinks } from "./useFixBooksDanglingLinks" import { useBooksDanglingLinks } from "./useBooksDanglingLinks" import { @@ -18,14 +17,18 @@ import { useObserve } from "reactjrx" import { latestDatabase$ } from "../rxdb/useCreateDatabase" import { switchMap } from "rxjs" import { getMetadataFromCollection } from "../collections/getMetadataFromCollection" +import { useFixableCollections } from "./useFixableCollections" +import { useRepair } from "./useRepair" +import { CollectionDanglingBooks } from "./CollectionDanglingBooks" export const ProblemsScreen = memo(() => { const fixCollections = useFixCollections() const fixBookReferences = useFixBookReferences() const fixBooksDanglingLinks = useFixBooksDanglingLinks() - const duplicatedLinks = useDuplicatedResourceIdLinks() const duplicatedBookTitles = useDuplicatedBookTitles() const fixDuplicatedBookTitles = useFixDuplicatedBookTitles() + const { collectionsWithDanglingBooks } = useFixableCollections() + const { mutate: repair } = useRepair() const collections = useObserve( () => latestDatabase$.pipe(switchMap((db) => db.obokucollection.find().$)), [] @@ -38,17 +41,14 @@ export const ProblemsScreen = memo(() => { () => collections?.map((doc) => doc._id), [collections] ) - const bookIds = useMemo(() => books?.map((doc) => doc._id), [books]) const booksWithInvalidCollections = books?.filter( (doc) => difference(doc.collections, collectionIds ?? []).length > 0 ) - const collectionsWithNonExistingBooks = collections?.filter( - (doc) => difference(doc.books, bookIds ?? []).length > 0 - ) + const booksWithDanglingLinks = useBooksDanglingLinks() const duplicatedCollections = useMemo(() => { - const collectionsByResourceId = groupBy(collections, "resourceId") + const collectionsByResourceId = groupBy(collections, "linkResourceId") const duplicatedCollections = Object.keys(collectionsByResourceId) .filter((resourceId) => collectionsByResourceId[resourceId]!.length > 1) .map((resourceId) => [ @@ -69,13 +69,6 @@ export const ProblemsScreen = memo(() => { return duplicatedCollections as [string, { name: string; number: number }][] }, [collections]) - Report.log({ - books, - duplicatedBookTitles, - booksWithDanglingLinks, - duplicatedLinks - }) - return ( <> @@ -160,23 +153,20 @@ export const ProblemsScreen = memo(() => { /> )} - {!!books && !!collectionsWithNonExistingBooks?.length && ( - fixBookReferences(collectionsWithNonExistingBooks)} - > - - - - - - )} + {collectionsWithDanglingBooks?.map(({ doc, danglingBooks }) => ( + + repair({ + danglingBooks, + doc, + type: "collectionDanglingBooks" + }) + } + /> + ))} {/* {duplicatedLinks.length > 0 && ( { + const { data: unsafeCollections } = useCollections({ + includeProtected: true + }) + const { data: unsafeBooks } = useBooks({ includeProtected: true }) + const unsafeBookIds = useMemo( + () => unsafeBooks?.map((item) => item._id), + [unsafeBooks] + ) + + const collectionsWithDanglingBooks = unsafeCollections?.reduce( + (acc, doc) => { + const danglingBooks = difference(doc.books, unsafeBookIds ?? []) + + if (danglingBooks.length > 0) { + return [ + ...acc, + { + doc, + danglingBooks + } + ] + } + + return acc + }, + [] as { doc: CollectionDocType; danglingBooks: string[] }[] + ) + + return { collectionsWithDanglingBooks } +} diff --git a/packages/web/src/problems/useRepair.ts b/packages/web/src/problems/useRepair.ts new file mode 100644 index 00000000..292576ad --- /dev/null +++ b/packages/web/src/problems/useRepair.ts @@ -0,0 +1,52 @@ +import { CollectionDocType } from "@oboku/shared" +import { useMutation } from "reactjrx" +import { first, from, mergeMap, of } from "rxjs" +import { latestDatabase$ } from "../rxdb/useCreateDatabase" + +export const useRepair = () => { + return useMutation({ + mutationFn: (action: { + type: "collectionDanglingBooks" + doc: CollectionDocType + danglingBooks: string[] + }) => { + if (action.type === "collectionDanglingBooks") { + const yes = window.confirm( + ` + This action will remove the invalid book references from the collection. It will not remove anything else. + `.replace(/ +/g, "") + ) + + if (!yes) return of(null) + + return latestDatabase$.pipe( + first(), + mergeMap((db) => + from( + db.obokucollection + .findOne({ selector: { _id: action.doc._id } }) + .exec() + ).pipe( + mergeMap((item) => { + if (!item) return of(null) + + return item.incrementalModify((old) => { + const nonDanglingBooks = old.books.filter( + (id) => !action.danglingBooks.includes(id) + ) + + return { + ...old, + books: nonDanglingBooks + } + }) + }) + ) + ) + ) + } + + return of(null) + } + }) +} From c6cdfeddbad02fb9444c443c373f7eda90ced4ec Mon Sep 17 00:00:00 2001 From: Maxime Bret Date: Sun, 28 Jul 2024 15:43:28 +0200 Subject: [PATCH 4/7] feat: added danglin collection repair --- .../src/problems/BookDanglingCollections.tsx | 29 +++++++++ packages/web/src/problems/ProblemsScreen.tsx | 42 ++++++------- packages/web/src/problems/useFixableBooks.ts | 42 +++++++++++++ .../web/src/problems/useFixableCollections.ts | 8 +-- packages/web/src/problems/useRepair.ts | 61 ++++++++++++++++--- 5 files changed, 147 insertions(+), 35 deletions(-) create mode 100644 packages/web/src/problems/BookDanglingCollections.tsx create mode 100644 packages/web/src/problems/useFixableBooks.ts diff --git a/packages/web/src/problems/BookDanglingCollections.tsx b/packages/web/src/problems/BookDanglingCollections.tsx new file mode 100644 index 00000000..3bae21d2 --- /dev/null +++ b/packages/web/src/problems/BookDanglingCollections.tsx @@ -0,0 +1,29 @@ +import { LinkOffRounded } from "@mui/icons-material" +import { ListItemButton, ListItemIcon, ListItemText } from "@mui/material" +import { BookDocType } from "@oboku/shared" +import { DeepReadonlyObject } from "rxdb" +import { getMetadataFromBook } from "../books/metadata" + +export const BookDanglingCollections = ({ + danglingBooks, + doc, + onClick +}: { + doc: DeepReadonlyObject + danglingBooks: string[] + onClick?: () => void +}) => { + return ( + + + + + + + ) +} diff --git a/packages/web/src/problems/ProblemsScreen.tsx b/packages/web/src/problems/ProblemsScreen.tsx index 7da6cc18..05990c43 100644 --- a/packages/web/src/problems/ProblemsScreen.tsx +++ b/packages/web/src/problems/ProblemsScreen.tsx @@ -20,6 +20,8 @@ import { getMetadataFromCollection } from "../collections/getMetadataFromCollect import { useFixableCollections } from "./useFixableCollections" import { useRepair } from "./useRepair" import { CollectionDanglingBooks } from "./CollectionDanglingBooks" +import { useFixableBooks } from "./useFixableBooks" +import { BookDanglingCollections } from "./BookDanglingCollections" export const ProblemsScreen = memo(() => { const fixCollections = useFixCollections() @@ -28,6 +30,7 @@ export const ProblemsScreen = memo(() => { const duplicatedBookTitles = useDuplicatedBookTitles() const fixDuplicatedBookTitles = useFixDuplicatedBookTitles() const { collectionsWithDanglingBooks } = useFixableCollections() + const { booksWithDanglingCollections } = useFixableBooks() const { mutate: repair } = useRepair() const collections = useObserve( () => latestDatabase$.pipe(switchMap((db) => db.obokucollection.find().$)), @@ -41,9 +44,6 @@ export const ProblemsScreen = memo(() => { () => collections?.map((doc) => doc._id), [collections] ) - const booksWithInvalidCollections = books?.filter( - (doc) => difference(doc.collections, collectionIds ?? []).length > 0 - ) const booksWithDanglingLinks = useBooksDanglingLinks() @@ -137,30 +137,28 @@ export const ProblemsScreen = memo(() => { /> )} - {!!collections && !!booksWithInvalidCollections?.length && ( - fixBookReferences(booksWithInvalidCollections)} - > - - - - - - )} - {collectionsWithDanglingBooks?.map(({ doc, danglingBooks }) => ( + {booksWithDanglingCollections?.map(({ danglingItems, doc }) => ( + + repair({ + danglingItems, + doc, + type: "bookDanglingCollections" + }) + } + /> + ))} + {collectionsWithDanglingBooks?.map(({ doc, danglingItems }) => ( repair({ - danglingBooks, + danglingItems, doc, type: "collectionDanglingBooks" }) diff --git a/packages/web/src/problems/useFixableBooks.ts b/packages/web/src/problems/useFixableBooks.ts new file mode 100644 index 00000000..5b0be02f --- /dev/null +++ b/packages/web/src/problems/useFixableBooks.ts @@ -0,0 +1,42 @@ +import { useMemo } from "react" +import { useBooks } from "../books/states" +import { useCollections } from "../collections/useCollections" +import { difference } from "lodash" +import { BookDocType } from "@oboku/shared" +import { DeepReadonlyObject } from "rxdb" + +export const useFixableBooks = () => { + const { data: unsafeCollections } = useCollections({ + includeProtected: true + }) + const { data: unsafeBooks } = useBooks({ includeProtected: true }) + + const unsafeCollectionIds = useMemo( + () => unsafeCollections?.map((item) => item._id), + [unsafeCollections] + ) + + const booksWithDanglingCollections = unsafeBooks?.reduce( + (acc, doc) => { + const danglingItems = difference( + doc.collections, + unsafeCollectionIds ?? [] + ) + + if (danglingItems.length > 0) { + return [ + ...acc, + { + doc, + danglingItems + } + ] + } + + return acc + }, + [] as { doc: DeepReadonlyObject; danglingItems: string[] }[] + ) + + return { booksWithDanglingCollections } +} diff --git a/packages/web/src/problems/useFixableCollections.ts b/packages/web/src/problems/useFixableCollections.ts index 4f2bb6c9..c0d2425c 100644 --- a/packages/web/src/problems/useFixableCollections.ts +++ b/packages/web/src/problems/useFixableCollections.ts @@ -16,21 +16,21 @@ export const useFixableCollections = () => { const collectionsWithDanglingBooks = unsafeCollections?.reduce( (acc, doc) => { - const danglingBooks = difference(doc.books, unsafeBookIds ?? []) + const danglingItems = difference(doc.books, unsafeBookIds ?? []) - if (danglingBooks.length > 0) { + if (danglingItems.length > 0) { return [ ...acc, { doc, - danglingBooks + danglingItems } ] } return acc }, - [] as { doc: CollectionDocType; danglingBooks: string[] }[] + [] as { doc: CollectionDocType; danglingItems: string[] }[] ) return { collectionsWithDanglingBooks } diff --git a/packages/web/src/problems/useRepair.ts b/packages/web/src/problems/useRepair.ts index 292576ad..15f634ea 100644 --- a/packages/web/src/problems/useRepair.ts +++ b/packages/web/src/problems/useRepair.ts @@ -1,15 +1,26 @@ -import { CollectionDocType } from "@oboku/shared" +import { BookDocType, CollectionDocType } from "@oboku/shared" import { useMutation } from "reactjrx" import { first, from, mergeMap, of } from "rxjs" import { latestDatabase$ } from "../rxdb/useCreateDatabase" +import { DeepReadonlyObject } from "rxdb" export const useRepair = () => { return useMutation({ - mutationFn: (action: { - type: "collectionDanglingBooks" - doc: CollectionDocType - danglingBooks: string[] - }) => { + mutationFn: ( + action: + | { + type: "collectionDanglingBooks" + doc: CollectionDocType + danglingItems: string[] + } + | { + type: "bookDanglingCollections" + doc: DeepReadonlyObject + danglingItems: string[] + } + ) => { + const db$ = latestDatabase$.pipe(first()) + if (action.type === "collectionDanglingBooks") { const yes = window.confirm( ` @@ -19,8 +30,7 @@ export const useRepair = () => { if (!yes) return of(null) - return latestDatabase$.pipe( - first(), + return db$.pipe( mergeMap((db) => from( db.obokucollection @@ -32,7 +42,7 @@ export const useRepair = () => { return item.incrementalModify((old) => { const nonDanglingBooks = old.books.filter( - (id) => !action.danglingBooks.includes(id) + (id) => !action.danglingItems.includes(id) ) return { @@ -46,6 +56,39 @@ export const useRepair = () => { ) } + if (action.type === "bookDanglingCollections") { + const yes = window.confirm( + ` + This action will remove the invalid collection references from the book. It will not remove anything else. + `.replace(/ +/g, "") + ) + + if (!yes) return of(null) + + return db$.pipe( + mergeMap((db) => + from( + db.book.findOne({ selector: { _id: action.doc._id } }).exec() + ).pipe( + mergeMap((item) => { + if (!item) return of(null) + + return item.incrementalModify((old) => { + const nonDanglingCollections = old.collections.filter( + (id) => !action.danglingItems.includes(id) + ) + + return { + ...old, + collections: nonDanglingCollections + } + }) + }) + ) + ) + ) + } + return of(null) } }) From 60ab0d42220dbededac7110ee3f7190c85ad91f0 Mon Sep 17 00:00:00 2001 From: Maxime Bret Date: Sun, 28 Jul 2024 16:26:44 +0200 Subject: [PATCH 5/7] feat: added repair of book dangling links --- .../web/src/problems/BookDanglingLinks.tsx | 29 ++++++ packages/web/src/problems/ProblemsScreen.tsx | 51 ++++------- .../web/src/problems/useBooksDanglingLinks.ts | 23 ----- .../web/src/problems/useFixBookReferences.ts | 58 ------------ .../src/problems/useFixBooksDanglingLinks.ts | 58 ------------ packages/web/src/problems/useFixableBooks.ts | 27 +++++- packages/web/src/problems/useRepair.ts | 90 ++++++++++++++----- 7 files changed, 141 insertions(+), 195 deletions(-) create mode 100644 packages/web/src/problems/BookDanglingLinks.tsx delete mode 100644 packages/web/src/problems/useBooksDanglingLinks.ts delete mode 100644 packages/web/src/problems/useFixBookReferences.ts delete mode 100644 packages/web/src/problems/useFixBooksDanglingLinks.ts diff --git a/packages/web/src/problems/BookDanglingLinks.tsx b/packages/web/src/problems/BookDanglingLinks.tsx new file mode 100644 index 00000000..2076794d --- /dev/null +++ b/packages/web/src/problems/BookDanglingLinks.tsx @@ -0,0 +1,29 @@ +import { LinkOffRounded } from "@mui/icons-material" +import { ListItemButton, ListItemIcon, ListItemText } from "@mui/material" +import { BookDocType } from "@oboku/shared" +import { DeepReadonlyObject } from "rxdb" +import { getMetadataFromBook } from "../books/metadata" + +export const BookDanglingLinks = ({ + danglingBooks, + doc, + onClick +}: { + doc: DeepReadonlyObject + danglingBooks: string[] + onClick?: () => void +}) => { + return ( + + + + + + + ) +} diff --git a/packages/web/src/problems/ProblemsScreen.tsx b/packages/web/src/problems/ProblemsScreen.tsx index 05990c43..a45b61c4 100644 --- a/packages/web/src/problems/ProblemsScreen.tsx +++ b/packages/web/src/problems/ProblemsScreen.tsx @@ -1,13 +1,10 @@ import { Box, List, ListItem, ListItemIcon, ListItemText } from "@mui/material" -import { difference, groupBy } from "lodash" +import { groupBy } from "lodash" import { Fragment, memo, useMemo } from "react" import { Report } from "../debug/report.shared" import { TopBarNavigation } from "../navigation/TopBarNavigation" import { BuildRounded } from "@mui/icons-material" import { useFixCollections } from "./useFixCollections" -import { useFixBookReferences } from "./useFixBookReferences" -import { useFixBooksDanglingLinks } from "./useFixBooksDanglingLinks" -import { useBooksDanglingLinks } from "./useBooksDanglingLinks" import { useDuplicatedBookTitles, useFixDuplicatedBookTitles @@ -22,30 +19,19 @@ import { useRepair } from "./useRepair" import { CollectionDanglingBooks } from "./CollectionDanglingBooks" import { useFixableBooks } from "./useFixableBooks" import { BookDanglingCollections } from "./BookDanglingCollections" +import { BookDanglingLinks } from "./BookDanglingLinks" export const ProblemsScreen = memo(() => { const fixCollections = useFixCollections() - const fixBookReferences = useFixBookReferences() - const fixBooksDanglingLinks = useFixBooksDanglingLinks() const duplicatedBookTitles = useDuplicatedBookTitles() const fixDuplicatedBookTitles = useFixDuplicatedBookTitles() const { collectionsWithDanglingBooks } = useFixableCollections() - const { booksWithDanglingCollections } = useFixableBooks() + const { booksWithDanglingCollections, booksWithDanglingLinks } = useFixableBooks() const { mutate: repair } = useRepair() const collections = useObserve( () => latestDatabase$.pipe(switchMap((db) => db.obokucollection.find().$)), [] ) - const books = useObserve( - () => latestDatabase$.pipe(switchMap((db) => db.book.find().$)), - [] - ) - const collectionIds = useMemo( - () => collections?.map((doc) => doc._id), - [collections] - ) - - const booksWithDanglingLinks = useBooksDanglingLinks() const duplicatedCollections = useMemo(() => { const collectionsByResourceId = groupBy(collections, "linkResourceId") @@ -190,23 +176,20 @@ export const ProblemsScreen = memo(() => { /> )} */} - {!!booksWithDanglingLinks?.length && ( - fixBooksDanglingLinks(booksWithDanglingLinks)} - > - - - - - - )} + {booksWithDanglingLinks?.map(({ doc, danglingItems }) => ( + + repair({ + danglingItems, + doc, + type: "bookDanglingLinks" + }) + } + /> + ))} {duplicatedBookTitles.length > 0 && ( { - const books = useObserve( - () => latestDatabase$.pipe(switchMap((db) => db?.book.find().$)), - [] - ) - const links = useObserve( - () => latestDatabase$.pipe(switchMap((db) => db?.link.find().$)), - [] - ) - - return useMemo(() => { - return books?.filter( - (doc) => - difference(doc.links, links?.map((doc) => doc._id) ?? []).length > 0 - ) - }, [books, links]) -} diff --git a/packages/web/src/problems/useFixBookReferences.ts b/packages/web/src/problems/useFixBookReferences.ts deleted file mode 100644 index 155eb202..00000000 --- a/packages/web/src/problems/useFixBookReferences.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { difference } from "lodash" -import { useCallback } from "react" -import { Report } from "../debug/report.shared" -import { useDatabase } from "../rxdb" -import { BookDocument } from "../rxdb/collections/book" - -export const useFixBookReferences = () => { - const { db } = useDatabase() - - const removeDanglingCollectionsFromBook = useCallback( - async (doc: BookDocument) => { - if (doc.collections.length === 0) return - - const existingCollection = await db?.obokucollection - .safeFind({ - selector: { - _id: { - $in: doc.collections - } - } - }) - .exec() - - const toRemove = difference( - doc.collections, - existingCollection?.map((doc) => doc._id) ?? [] - ) - - if (toRemove.length > 0) { - await doc.incrementalModify((data) => ({ - ...data, - collections: data.collections.filter((id) => !toRemove.includes(id)) - })) - } - }, - [db] - ) - - return useCallback( - async (data: BookDocument[]) => { - const yes = window.confirm( - ` - This action will remove non valid collection reference from all the books. - `.replace(/ +/g, "") - ) - - if (yes && db) { - try { - // we actually have middleware to deal with it so we will just force an update - Promise.all(data.map(removeDanglingCollectionsFromBook)) - } catch (e) { - Report.error(e) - } - } - }, - [db, removeDanglingCollectionsFromBook] - ) -} diff --git a/packages/web/src/problems/useFixBooksDanglingLinks.ts b/packages/web/src/problems/useFixBooksDanglingLinks.ts deleted file mode 100644 index 4cd1830f..00000000 --- a/packages/web/src/problems/useFixBooksDanglingLinks.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { difference } from "lodash" -import { useCallback } from "react" -import { Report } from "../debug/report.shared" -import { useDatabase } from "../rxdb" -import { BookDocument } from "../rxdb/collections/book" - -export const useFixBooksDanglingLinks = () => { - const { db } = useDatabase() - - const removeDanglingLinksFromBook = useCallback( - async (doc: BookDocument) => { - if (doc.links.length === 0) return - - const existingLinksForThisBook = await db?.link - .safeFind({ - selector: { - _id: { - $in: doc.links - } - } - }) - .exec() - - const toRemove = difference( - doc.links, - existingLinksForThisBook?.map((doc) => doc._id) ?? [] - ) - - if (toRemove.length > 0) { - await doc.incrementalModify((data) => ({ - ...data, - links: data.links.filter((id) => !toRemove.includes(id)) - })) - } - }, - [db] - ) - - return useCallback( - async (data: BookDocument[]) => { - const yes = window.confirm( - ` - This action will remove invalid links from books. - `.replace(/ +/g, "") - ) - - if (yes && db) { - try { - // we actually have middleware to deal with it so we will just force an update - Promise.all(data.map(removeDanglingLinksFromBook)) - } catch (e) { - Report.error(e) - } - } - }, - [db, removeDanglingLinksFromBook] - ) -} diff --git a/packages/web/src/problems/useFixableBooks.ts b/packages/web/src/problems/useFixableBooks.ts index 5b0be02f..f7f75630 100644 --- a/packages/web/src/problems/useFixableBooks.ts +++ b/packages/web/src/problems/useFixableBooks.ts @@ -4,17 +4,20 @@ import { useCollections } from "../collections/useCollections" import { difference } from "lodash" import { BookDocType } from "@oboku/shared" import { DeepReadonlyObject } from "rxdb" +import { useLinks } from "../links/states" export const useFixableBooks = () => { const { data: unsafeCollections } = useCollections({ includeProtected: true }) const { data: unsafeBooks } = useBooks({ includeProtected: true }) + const { data: links } = useLinks() const unsafeCollectionIds = useMemo( () => unsafeCollections?.map((item) => item._id), [unsafeCollections] ) + const linkIds = useMemo(() => links?.map((item) => item._id), [links]) const booksWithDanglingCollections = unsafeBooks?.reduce( (acc, doc) => { @@ -38,5 +41,27 @@ export const useFixableBooks = () => { [] as { doc: DeepReadonlyObject; danglingItems: string[] }[] ) - return { booksWithDanglingCollections } + const booksWithDanglingLinks = unsafeBooks?.reduce( + (acc, doc) => { + const danglingItems = difference( + doc.links, + linkIds ?? [] + ) + + if (danglingItems.length > 0) { + return [ + ...acc, + { + doc, + danglingItems + } + ] + } + + return acc + }, + [] as { doc: DeepReadonlyObject; danglingItems: string[] }[] + ) + + return { booksWithDanglingCollections, booksWithDanglingLinks } } diff --git a/packages/web/src/problems/useRepair.ts b/packages/web/src/problems/useRepair.ts index 15f634ea..2062a854 100644 --- a/packages/web/src/problems/useRepair.ts +++ b/packages/web/src/problems/useRepair.ts @@ -1,6 +1,6 @@ import { BookDocType, CollectionDocType } from "@oboku/shared" import { useMutation } from "reactjrx" -import { first, from, mergeMap, of } from "rxjs" +import { first, from, map, mergeMap, of } from "rxjs" import { latestDatabase$ } from "../rxdb/useCreateDatabase" import { DeepReadonlyObject } from "rxdb" @@ -18,6 +18,11 @@ export const useRepair = () => { doc: DeepReadonlyObject danglingItems: string[] } + | { + type: "bookDanglingLinks" + doc: DeepReadonlyObject + danglingItems: string[] + } ) => { const db$ = latestDatabase$.pipe(first()) @@ -40,19 +45,22 @@ export const useRepair = () => { mergeMap((item) => { if (!item) return of(null) - return item.incrementalModify((old) => { - const nonDanglingBooks = old.books.filter( - (id) => !action.danglingItems.includes(id) - ) + return from( + item.incrementalModify((old) => { + const nonDanglingBooks = old.books.filter( + (id) => !action.danglingItems.includes(id) + ) - return { - ...old, - books: nonDanglingBooks - } - }) + return { + ...old, + books: nonDanglingBooks + } + }) + ) }) ) - ) + ), + map(() => null) ) } @@ -73,19 +81,59 @@ export const useRepair = () => { mergeMap((item) => { if (!item) return of(null) - return item.incrementalModify((old) => { - const nonDanglingCollections = old.collections.filter( - (id) => !action.danglingItems.includes(id) - ) + return from( + item.incrementalModify((old) => { + const nonDanglingCollections = old.collections.filter( + (id) => !action.danglingItems.includes(id) + ) + + return { + ...old, + collections: nonDanglingCollections + } + }) + ) + }) + ) + ), + map(() => null) + ) + } + + if (action.type === "bookDanglingLinks") { + const yes = window.confirm( + ` + This action will remove the invalid link references from the book. It will not remove anything else. + `.replace(/ +/g, "") + ) + + if (!yes) return of(null) + + return db$.pipe( + mergeMap((db) => + from( + db.book.findOne({ selector: { _id: action.doc._id } }).exec() + ).pipe( + mergeMap((item) => { + if (!item) return of(null) + + return from( + item.incrementalModify((old) => { + const nonDanglingLinks = old.links.filter( + (id) => !action.danglingItems.includes(id) + ) - return { - ...old, - collections: nonDanglingCollections - } - }) + return { + ...old, + _meta: old._meta ?? {}, + links: nonDanglingLinks + } + }) + ) }) ) - ) + ), + map(() => null) ) } From 2fc4795a4294ffa1b199d664bd7bbc0de3605920 Mon Sep 17 00:00:00 2001 From: Maxime Bret Date: Sun, 28 Jul 2024 16:27:04 +0200 Subject: [PATCH 6/7] fix: cleanup --- packages/web/src/rxdb/collections/book.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/web/src/rxdb/collections/book.ts b/packages/web/src/rxdb/collections/book.ts index 6b916d05..2d70541c 100644 --- a/packages/web/src/rxdb/collections/book.ts +++ b/packages/web/src/rxdb/collections/book.ts @@ -5,6 +5,7 @@ import { } from "@oboku/shared" import { AtomicUpdateFunction, + MigrationStrategies, RxCollection, RxDocument, RxJsonSchema, @@ -71,7 +72,7 @@ export const bookCollectionMethods: BookCollectionMethods = { } } -export const bookSchemaMigrationStrategies = {} +export const bookSchemaMigrationStrategies: MigrationStrategies = {} export const bookSchema: RxJsonSchema< Omit From be8a912b5abca3103a21b9d8785a60e63052f2f9 Mon Sep 17 00:00:00 2001 From: Maxime Bret Date: Sun, 28 Jul 2024 17:42:03 +0200 Subject: [PATCH 7/7] feat: added more decompress mime type --- packages/api/src/constants.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/api/src/constants.ts b/packages/api/src/constants.ts index 45cd2be8..4c6b8aeb 100644 --- a/packages/api/src/constants.ts +++ b/packages/api/src/constants.ts @@ -14,6 +14,7 @@ export const METADATA_EXTRACTOR_SUPPORTED_EXTENSIONS = [ "application/x-cbz", "application/epub+zip", "application/zip", + "application/x-zip-compressed", "application/x-rar" ] export const COVER_ALLOWED_EXT = [".jpg", ".jpeg", ".png"]