diff --git a/apps/desktop/layer/renderer/src/hooks/biz/useEntryActions.tsx b/apps/desktop/layer/renderer/src/hooks/biz/useEntryActions.tsx index cf860a009c..a66b3b180a 100644 --- a/apps/desktop/layer/renderer/src/hooks/biz/useEntryActions.tsx +++ b/apps/desktop/layer/renderer/src/hooks/biz/useEntryActions.tsx @@ -1,6 +1,7 @@ import { isMobile } from "@follow/components/hooks/useMobile.js" import { FeedViewType, UserRole, views } from "@follow/constants" import { IN_ELECTRON } from "@follow/shared/constants" +import { useIsInbox } from "@follow/store/inbox/hooks" import { doesTextContainHTML } from "@follow/utils/utils" import { useMemo } from "react" @@ -27,7 +28,6 @@ import { useToolbarOrderMap } from "~/modules/customize-toolbar/hooks" import type { FlatEntryModel } from "~/store/entry" import { useEntry } from "~/store/entry" import { useFeedById } from "~/store/feed" -import { useInboxById } from "~/store/inbox" export const enableEntryReadability = async ({ id, url }: { id: string; url: string }) => { const status = getReadabilityStatus()[id] @@ -186,8 +186,7 @@ export const useEntryActions = ({ } }) - const inbox = useInboxById(entry?.inboxId) - const isInbox = !!inbox + const isInbox = useIsInbox(entry?.inboxId) const isShowSourceContent = useShowSourceContent() const isShowAISummaryAuto = useShowAISummaryAuto(entry?.summary) const isShowAISummaryOnce = useShowAISummaryOnce() diff --git a/apps/desktop/layer/renderer/src/hooks/biz/useFeedActions.tsx b/apps/desktop/layer/renderer/src/hooks/biz/useFeedActions.tsx index 86cc6e83dc..ef1944535d 100644 --- a/apps/desktop/layer/renderer/src/hooks/biz/useFeedActions.tsx +++ b/apps/desktop/layer/renderer/src/hooks/biz/useFeedActions.tsx @@ -1,6 +1,7 @@ import type { FeedViewType } from "@follow/constants" import { IN_ELECTRON } from "@follow/shared/constants" import { env } from "@follow/shared/env.desktop" +import { useInboxById, useIsInbox } from "@follow/store/inbox/hooks" import { isBizId } from "@follow/utils/utils" import { useMutation } from "@tanstack/react-query" import { useMemo } from "react" @@ -26,7 +27,6 @@ import { useCategoryCreationModal } from "~/modules/settings/tabs/lists/hooks" import { ListCreationModalContent } from "~/modules/settings/tabs/lists/modals" import { useResetFeed } from "~/queries/feed" import { getFeedById, useFeedById } from "~/store/feed" -import { useInboxById } from "~/store/inbox" import { listActions, useListById, useOwnedListByView } from "~/store/list" import { subscriptionActions, @@ -448,11 +448,11 @@ export const useListActions = ({ listId, view }: { listId: string; view?: FeedVi export const useInboxActions = ({ inboxId }: { inboxId: string }) => { const { t } = useTranslation() - const inbox = useInboxById(inboxId) + const isInbox = useIsInbox(inboxId) const { present } = useModalStack() const items = useMemo(() => { - if (!inbox) return [] + if (!isInbox) return [] const items: FollowMenuItem[] = [ new MenuItemText({ @@ -461,7 +461,7 @@ export const useInboxActions = ({ inboxId }: { inboxId: string }) => { click: () => { present({ title: t("sidebar.feed_actions.edit_inbox"), - content: ({ dismiss }) => , + content: () => , }) }, }), @@ -476,7 +476,7 @@ export const useInboxActions = ({ inboxId }: { inboxId: string }) => { ] return items - }, [inbox, t, inboxId, present]) + }, [isInbox, t, inboxId, present]) return { items } } diff --git a/apps/desktop/layer/renderer/src/hooks/common/useFeedSafeUrl.ts b/apps/desktop/layer/renderer/src/hooks/common/useFeedSafeUrl.ts index b23caab742..587f095181 100644 --- a/apps/desktop/layer/renderer/src/hooks/common/useFeedSafeUrl.ts +++ b/apps/desktop/layer/renderer/src/hooks/common/useFeedSafeUrl.ts @@ -1,9 +1,9 @@ +import { useIsInbox } from "@follow/store/inbox/hooks" import { resolveUrlWithBase } from "@follow/utils/utils" import { useMemo } from "react" import { useEntry } from "~/store/entry" import { useFeedById } from "~/store/feed" -import { useInboxById } from "~/store/inbox" export const useFeedSafeUrl = (entryId: string) => { const entry = useEntry(entryId, (state) => { @@ -19,10 +19,10 @@ export const useFeedSafeUrl = (entryId: string) => { type: feed?.type, siteUrl: feed?.siteUrl, })) - const inbox = useInboxById(entry?.inboxId, (inbox) => inbox !== null) + const isInbox = useIsInbox(entry?.inboxId) return useMemo(() => { - if (inbox) return entry?.authorUrl + if (isInbox) return entry?.authorUrl const href = entry?.url if (!href) return "#" @@ -35,5 +35,5 @@ export const useFeedSafeUrl = (entryId: string) => { const feedSiteUrl = feed?.type === "feed" ? feed?.siteUrl : null if (feedSiteUrl) return resolveUrlWithBase(href, feedSiteUrl) return href - }, [entry?.authorUrl, entry?.url, feed?.type, feed?.siteUrl, inbox]) + }, [entry?.authorUrl, entry?.url, feed?.type, feed?.siteUrl, isInbox]) } diff --git a/apps/desktop/layer/renderer/src/modules/discover/DiscoverInboxList.tsx b/apps/desktop/layer/renderer/src/modules/discover/DiscoverInboxList.tsx index f468ec1f54..a1a45f8f5b 100644 --- a/apps/desktop/layer/renderer/src/modules/discover/DiscoverInboxList.tsx +++ b/apps/desktop/layer/renderer/src/modules/discover/DiscoverInboxList.tsx @@ -7,7 +7,6 @@ import { useEventCallback } from "usehooks-ts" import { useUserRole } from "~/atoms/user" import { useModalStack } from "~/components/ui/modal/stacked/hooks" import { CustomSafeError } from "~/errors/CustomSafeError" -import { useInboxList } from "~/queries/inboxes" import { useActivationModal } from "../activation" import { InboxForm } from "./InboxForm" @@ -39,7 +38,6 @@ const useCanCreateMoreInboxAndNotify = () => { } export function DiscoverInboxList() { const { t } = useTranslation() - const { refetch } = useInboxList() const { present } = useModalStack() @@ -68,15 +66,7 @@ export function DiscoverInboxList() { preCheck() && present({ title: t("sidebar.feed_actions.new_inbox"), - content: ({ dismiss }) => ( - { - refetch() - dismiss() - }} - /> - ), + content: () => , }) } > diff --git a/apps/desktop/layer/renderer/src/modules/discover/InboxForm.tsx b/apps/desktop/layer/renderer/src/modules/discover/InboxForm.tsx index e6cd4fba97..2fa2aff441 100644 --- a/apps/desktop/layer/renderer/src/modules/discover/InboxForm.tsx +++ b/apps/desktop/layer/renderer/src/modules/discover/InboxForm.tsx @@ -10,9 +10,10 @@ import { FormMessage, } from "@follow/components/ui/form/index.jsx" import { Input } from "@follow/components/ui/input/index.js" -import { FeedViewType } from "@follow/constants" -import type { InboxModel } from "@follow/models/types" import { env } from "@follow/shared/env.desktop" +import { useInboxById } from "@follow/store/inbox/hooks" +import { inboxSyncService } from "@follow/store/inbox/store" +import type { InboxModel } from "@follow/store/inbox/types" import { cn } from "@follow/utils/utils" import { zodResolver } from "@hookform/resolvers/zod" import { useMutation } from "@tanstack/react-query" @@ -21,23 +22,14 @@ import { useTranslation } from "react-i18next" import { toast } from "sonner" import { z } from "zod" -import { apiClient } from "~/lib/api-fetch" +import { useCurrentModal } from "~/components/ui/modal/stacked/hooks" import { createErrorToaster } from "~/lib/error-parser" import { FollowSummary } from "~/modules/feed/feed-summary" -import { useInbox } from "~/queries/inboxes" -import { useInboxById } from "~/store/inbox" -import { subscriptionActions } from "~/store/subscription" export const InboxForm: Component<{ id?: string asWidget?: boolean - onSuccess?: () => void -}> = ({ id: _id, asWidget, onSuccess }) => { - const queryParams = { id: _id } - - const feedQuery = useInbox(queryParams) - - const id = feedQuery.data?.id || _id +}> = ({ id, asWidget }) => { const inbox = useInboxById(id) const isSubscribed = true @@ -59,7 +51,6 @@ export const InboxForm: Component<{ )} @@ -78,13 +69,9 @@ const formSchema = z.object({ title: z.string(), }) -const InboxInnerForm = ({ - onSuccess, - inbox, -}: { - onSuccess?: () => void - inbox?: Nullable -}) => { +const InboxInnerForm = ({ inbox }: { inbox?: Nullable }) => { + const currentModal = useCurrentModal() + const { t } = useTranslation() const form = useForm>({ resolver: zodResolver(formSchema), @@ -96,16 +83,12 @@ const InboxInnerForm = ({ const mutationCreate = useMutation({ mutationFn: async ({ handle, title }: { handle: string; title: string }) => { - await apiClient.inboxes.$post({ - json: { - handle, - title, - }, + await inboxSyncService.createInbox({ + handle, + title, }) - onSuccess?.() }, onSuccess: (_) => { - subscriptionActions.fetchByView(FeedViewType.Articles) toast.success(t("discover.inbox_create_success")) }, onError: createErrorToaster(t("discover.inbox_create_error")), @@ -113,16 +96,12 @@ const InboxInnerForm = ({ const mutationChange = useMutation({ mutationFn: async ({ handle, title }: { handle: string; title: string }) => { - await apiClient.inboxes.$put({ - json: { - handle, - title, - }, + await inboxSyncService.updateInbox({ + handle, + title, }) - onSuccess?.() }, onSuccess: () => { - subscriptionActions.fetchByView(FeedViewType.Articles) toast.success(t("discover.inbox_update_success")) }, onError: createErrorToaster(t("discover.inbox_update_error")), @@ -134,6 +113,7 @@ const InboxInnerForm = ({ } else { mutationCreate.mutate({ handle: values.handle, title: values.title }) } + currentModal.dismiss?.() } return ( diff --git a/apps/desktop/layer/renderer/src/modules/discover/InboxTable.electron.tsx b/apps/desktop/layer/renderer/src/modules/discover/InboxTable.electron.tsx index 2f7e86ae10..53154f6c0a 100644 --- a/apps/desktop/layer/renderer/src/modules/discover/InboxTable.electron.tsx +++ b/apps/desktop/layer/renderer/src/modules/discover/InboxTable.electron.tsx @@ -1,4 +1,3 @@ -import { LoadingCircle } from "@follow/components/ui/loading/index.jsx" import { Table, TableBody, @@ -7,12 +6,10 @@ import { TableHeader, TableRow, } from "@follow/components/ui/table/index.jsx" +import { useInboxById, useInboxList } from "@follow/store/inbox/hooks" import { memo } from "react" import { useTranslation } from "react-i18next" -import { useInboxList } from "~/queries/inboxes" -import { useInboxById } from "~/store/inbox" - import { InboxActions, InboxEmail, InboxSecret } from "./InboxTable.shared" export const InboxTable = () => { @@ -29,19 +26,7 @@ export const InboxTable = () => { {t("discover.inbox.actions")} - - {inboxes.isLoading ? ( - - -
- -
-
-
- ) : ( - inboxes.data?.map((inbox) => ) - )} -
+ {inboxes?.map((inbox) => )} ) } diff --git a/apps/desktop/layer/renderer/src/modules/discover/InboxTable.mobile.tsx b/apps/desktop/layer/renderer/src/modules/discover/InboxTable.mobile.tsx index 556d4374a2..e6690f232b 100644 --- a/apps/desktop/layer/renderer/src/modules/discover/InboxTable.mobile.tsx +++ b/apps/desktop/layer/renderer/src/modules/discover/InboxTable.mobile.tsx @@ -1,20 +1,15 @@ -import { LoadingCircle } from "@follow/components/ui/loading/index.jsx" +import { useInboxById, useInboxList } from "@follow/store/inbox/hooks" import { memo } from "react" -import { useInboxList } from "~/queries/inboxes" -import { useInboxById } from "~/store/inbox" - import { InboxActions, InboxEmail, InboxSecret } from "./InboxTable.shared" export const InboxTable = () => { const inboxes = useInboxList() - return inboxes.isLoading ? ( -
- -
- ) : ( + return (
    - {inboxes.data?.map((inbox) => )} + {inboxes.map((inbox) => ( + + ))}
) } diff --git a/apps/desktop/layer/renderer/src/modules/discover/InboxTable.shared.tsx b/apps/desktop/layer/renderer/src/modules/discover/InboxTable.shared.tsx index 429611f086..658ec5e54d 100644 --- a/apps/desktop/layer/renderer/src/modules/discover/InboxTable.shared.tsx +++ b/apps/desktop/layer/renderer/src/modules/discover/InboxTable.shared.tsx @@ -1,6 +1,6 @@ import { ActionButton, Button } from "@follow/components/ui/button/index.js" -import { FeedViewType } from "@follow/constants" import { env } from "@follow/shared/env.desktop" +import { inboxSyncService } from "@follow/store/inbox/store" import { useMutation } from "@tanstack/react-query" import { useTranslation } from "react-i18next" import { toast } from "sonner" @@ -8,8 +8,6 @@ import { toast } from "sonner" import { CopyButton } from "~/components/ui/button/CopyButton" import { useCurrentModal, useModalStack } from "~/components/ui/modal/stacked/hooks" import { createErrorToaster } from "~/lib/error-parser" -import { inboxActions } from "~/store/inbox" -import { subscriptionActions } from "~/store/subscription" import { InboxForm } from "./InboxForm" @@ -62,7 +60,7 @@ export const InboxActions = ({ id }: { id: string }) => { onClick={() => { present({ title: t("sidebar.feed_actions.edit_inbox"), - content: ({ dismiss }) => , + content: () => , }) }} > @@ -78,10 +76,9 @@ const ConfirmDestroyModalContent = ({ id }: { id: string }) => { const mutationDestroy = useMutation({ mutationFn: async (id: string) => { - return inboxActions.deleteInbox(id) + return inboxSyncService.deleteInbox(id) }, onSuccess: () => { - subscriptionActions.fetchByView(FeedViewType.Articles) toast.success(t("discover.inbox_destroy_success")) }, onMutate: () => { diff --git a/apps/desktop/layer/renderer/src/modules/entry-column/templates/list-item-template.tsx b/apps/desktop/layer/renderer/src/modules/entry-column/templates/list-item-template.tsx index 704f375e78..6e2c1e3182 100644 --- a/apps/desktop/layer/renderer/src/modules/entry-column/templates/list-item-template.tsx +++ b/apps/desktop/layer/renderer/src/modules/entry-column/templates/list-item-template.tsx @@ -1,5 +1,6 @@ import { useMobile } from "@follow/components/hooks/useMobile.js" import { EllipsisHorizontalTextWithTooltip } from "@follow/components/ui/typography/index.js" +import { useInboxById } from "@follow/store/inbox/hooks" import { clsx, cn, formatEstimatedMins, formatTimeToSeconds, isSafari } from "@follow/utils/utils" import { useMemo } from "react" import { titleCase } from "title-case" @@ -18,7 +19,6 @@ import { FeedIcon } from "~/modules/feed/feed-icon" import { FeedTitle } from "~/modules/feed/feed-title" import { useEntry } from "~/store/entry/hooks" import { getPreferredTitle, useFeedById } from "~/store/feed" -import { useInboxById } from "~/store/inbox" import { StarIcon } from "../star-icon" import type { UniversalItemProps } from "../types" diff --git a/apps/desktop/layer/renderer/src/modules/entry-content/components/EntryTitle.tsx b/apps/desktop/layer/renderer/src/modules/entry-content/components/EntryTitle.tsx index 34799c3da9..3b547daf89 100644 --- a/apps/desktop/layer/renderer/src/modules/entry-content/components/EntryTitle.tsx +++ b/apps/desktop/layer/renderer/src/modules/entry-content/components/EntryTitle.tsx @@ -1,3 +1,4 @@ +import { useInboxById } from "@follow/store/inbox/hooks" import { formatEstimatedMins, formatTimeToSeconds } from "@follow/utils" import { titleCase } from "title-case" @@ -11,7 +12,6 @@ import { FeedIcon } from "~/modules/feed/feed-icon" import { useEntryTranslation } from "~/store/ai/hook" import { useEntry, useEntryReadHistory } from "~/store/entry" import { getPreferredTitle, useFeedById } from "~/store/feed" -import { useInboxById } from "~/store/inbox" import { EntryTranslation } from "../../entry-column/translation" diff --git a/apps/desktop/layer/renderer/src/modules/entry-content/hooks.tsx b/apps/desktop/layer/renderer/src/modules/entry-content/hooks.tsx index dab420fbe9..9b0a6f5646 100644 --- a/apps/desktop/layer/renderer/src/modules/entry-content/hooks.tsx +++ b/apps/desktop/layer/renderer/src/modules/entry-content/hooks.tsx @@ -1,3 +1,4 @@ +import { useIsInbox } from "@follow/store/inbox/hooks" import { tracker } from "@follow/tracker" import { createElement, useCallback, useMemo } from "react" import { useTranslation } from "react-i18next" @@ -13,7 +14,6 @@ import { useAuthQuery } from "~/hooks/common/useBizQuery" import { Queries } from "~/queries" import { useEntryTranslation } from "~/store/ai/hook" import { useEntry } from "~/store/entry/hooks" -import { useInboxById } from "~/store/inbox/hooks" import { ImageGalleryContent } from "./components/ImageGalleryContent" @@ -47,7 +47,7 @@ export const useEntryContent = (entryId: string) => { const { content } = state.entries return { inboxId, content } }) - const isInbox = useInboxById(entry?.inboxId, (inbox) => inbox !== null) + const isInbox = useIsInbox(entry?.inboxId) const { error, data, isPending } = useAuthQuery( isInbox ? Queries.entries.byInboxId(entryId) : Queries.entries.byId(entryId), { diff --git a/apps/desktop/layer/renderer/src/modules/entry-content/index.electron.tsx b/apps/desktop/layer/renderer/src/modules/entry-content/index.electron.tsx index 4e0bf7232c..df71eef35a 100644 --- a/apps/desktop/layer/renderer/src/modules/entry-content/index.electron.tsx +++ b/apps/desktop/layer/renderer/src/modules/entry-content/index.electron.tsx @@ -10,6 +10,7 @@ import { ScrollArea } from "@follow/components/ui/scroll-area/index.js" import type { FeedViewType } from "@follow/constants" import { useSmoothScroll, useTitle } from "@follow/hooks" import type { FeedModel, InboxModel } from "@follow/models/types" +import { useIsInbox } from "@follow/store/inbox/hooks" import { nextFrame, stopPropagation } from "@follow/utils/dom" import { EventBus } from "@follow/utils/event-bus" import { cn, combineCleanupFunctions } from "@follow/utils/utils" @@ -32,7 +33,6 @@ import { useFeedSafeUrl } from "~/hooks/common/useFeedSafeUrl" import { WrappedElementProvider } from "~/providers/wrapped-element-provider" import { useEntry } from "~/store/entry" import { useFeedById } from "~/store/feed" -import { useInboxById } from "~/store/inbox" import { COMMAND_ID } from "../command/commands/id" import { useCommandBinding } from "../command/hooks/use-command-binding" @@ -81,8 +81,7 @@ export const EntryContent: Component = ({ const feed = useFeedById(entry?.feedId) as FeedModel | InboxModel - const inbox = useInboxById(entry?.inboxId, (inbox) => inbox !== null) - const isInbox = !!inbox + const isInbox = useIsInbox(entry?.inboxId) const isInReadabilityMode = useEntryIsInReadability(entryId) const { error, content, isPending } = useEntryContent(entryId) diff --git a/apps/desktop/layer/renderer/src/modules/entry-content/index.mobile.tsx b/apps/desktop/layer/renderer/src/modules/entry-content/index.mobile.tsx index 7e8806e67b..37679712b6 100644 --- a/apps/desktop/layer/renderer/src/modules/entry-content/index.mobile.tsx +++ b/apps/desktop/layer/renderer/src/modules/entry-content/index.mobile.tsx @@ -2,6 +2,7 @@ import { MemoedDangerousHTMLStyle } from "@follow/components/common/MemoedDanger import { ScrollElementContext } from "@follow/components/ui/scroll-area/ctx.js" import { useTitle } from "@follow/hooks" import type { FeedModel, InboxModel } from "@follow/models/types" +import { useIsInbox } from "@follow/store/inbox/hooks" import { nextFrame, stopPropagation } from "@follow/utils/dom" import { cn } from "@follow/utils/utils" import { ErrorBoundary } from "@sentry/react" @@ -17,7 +18,6 @@ import { usePreventOverscrollBounce } from "~/hooks/common" import { WrappedElementProvider } from "~/providers/wrapped-element-provider" import { useEntry } from "~/store/entry" import { useFeedById } from "~/store/feed" -import { useInboxById } from "~/store/inbox" import { CornerPlayer } from "../player/corner-player" import { EntryContentHTMLRenderer } from "../renderer/html" @@ -80,8 +80,7 @@ export const EntryContent: Component<{ const feed = useFeedById(entry?.feedId) as FeedModel | InboxModel const readerRenderInlineStyle = useUISettingKey("readerRenderInlineStyle") - const inbox = useInboxById(entry?.inboxId, (inbox) => inbox !== null) - const isInbox = !!inbox + const isInbox = useIsInbox(entry?.inboxId) const { error, content, isPending } = useEntryContent(entryId) diff --git a/apps/desktop/layer/renderer/src/modules/entry-content/index.shared.tsx b/apps/desktop/layer/renderer/src/modules/entry-content/index.shared.tsx index 75c7b7cd31..cee9258710 100644 --- a/apps/desktop/layer/renderer/src/modules/entry-content/index.shared.tsx +++ b/apps/desktop/layer/renderer/src/modules/entry-content/index.shared.tsx @@ -5,6 +5,7 @@ import { LoadingWithIcon } from "@follow/components/ui/loading/index.jsx" import { RootPortal } from "@follow/components/ui/portal/index.jsx" import { useScrollViewElement } from "@follow/components/ui/scroll-area/hooks.js" import { WEB_BUILD } from "@follow/shared/constants" +import { useInboxById } from "@follow/store/inbox/hooks" import { springScrollTo } from "@follow/utils/scroller" import { cn } from "@follow/utils/utils" import type { FallbackRender } from "@sentry/react" @@ -32,7 +33,6 @@ import { } from "~/providers/wrapped-element-provider" import { useEntry } from "~/store/entry" import { useFeedById } from "~/store/feed" -import { useInboxById } from "~/store/inbox" import { setEntryContentScrollToTop, setEntryTitleMeta } from "./atoms" diff --git a/apps/desktop/layer/renderer/src/modules/feed/feed-title.tsx b/apps/desktop/layer/renderer/src/modules/feed/feed-title.tsx index 3a9b068dd3..ef0cb04812 100644 --- a/apps/desktop/layer/renderer/src/modules/feed/feed-title.tsx +++ b/apps/desktop/layer/renderer/src/modules/feed/feed-title.tsx @@ -14,7 +14,7 @@ export const FeedTitle = ({ title, style, }: { - feed: FeedOrListRespModel | null + feed?: FeedOrListRespModel | null className?: string titleClassName?: string title?: string | null diff --git a/apps/desktop/layer/renderer/src/modules/subscription-column/FeedItem.tsx b/apps/desktop/layer/renderer/src/modules/subscription-column/FeedItem.tsx index cf1cda73ef..9d72418712 100644 --- a/apps/desktop/layer/renderer/src/modules/subscription-column/FeedItem.tsx +++ b/apps/desktop/layer/renderer/src/modules/subscription-column/FeedItem.tsx @@ -10,6 +10,7 @@ import { } from "@follow/components/ui/tooltip/index.jsx" import { EllipsisHorizontalTextWithTooltip } from "@follow/components/ui/typography/index.js" import type { FeedViewType } from "@follow/constants" +import { useInboxById } from "@follow/store/inbox/hooks" import { useUnreadById, useUnreadByListId } from "@follow/store/unread/hooks" import { cn, isKeyForMultiSelectPressed } from "@follow/utils/utils" import { createElement, memo, use, useCallback, useState } from "react" @@ -31,7 +32,6 @@ import { UrlBuilder } from "~/lib/url-builder" import { FeedIcon } from "~/modules/feed/feed-icon" import { FeedTitle } from "~/modules/feed/feed-title" import { getPreferredTitle, useFeedById } from "~/store/feed" -import { useInboxById } from "~/store/inbox" import { useListById } from "~/store/list" import { useSubscriptionByFeedId } from "~/store/subscription" diff --git a/apps/desktop/layer/renderer/src/modules/subscription-column/SubscriptionList.electron.tsx b/apps/desktop/layer/renderer/src/modules/subscription-column/SubscriptionList.electron.tsx index 210d3fe66c..cf0032917c 100644 --- a/apps/desktop/layer/renderer/src/modules/subscription-column/SubscriptionList.electron.tsx +++ b/apps/desktop/layer/renderer/src/modules/subscription-column/SubscriptionList.electron.tsx @@ -5,6 +5,7 @@ import { useGlobalFocusableScopeSelector, } from "@follow/components/common/Focusable/hooks.js" import { ScrollArea } from "@follow/components/ui/scroll-area/index.js" +import { useInboxList } from "@follow/store/inbox/hooks" import { nextFrame } from "@follow/utils/dom" import { EventBus } from "@follow/utils/event-bus" import { cn, combineCleanupFunctions, isKeyForMultiSelectPressed } from "@follow/utils/utils" @@ -22,7 +23,6 @@ import { useList } from "~/queries/lists" import { useCategoryOpenStateByView, useFeedsGroupedData, - useInboxesGroupedData, useListsGroupedData, } from "~/store/subscription" @@ -46,7 +46,10 @@ import { EmptyFeedList, ListHeader, StarredItem } from "./SubscriptionList.share const SubscriptionImpl = ({ ref, className, view }: SubscriptionProps) => { const feedsData = useFeedsGroupedData(view) const listsData = useListsGroupedData(view) - const inboxesData = useInboxesGroupedData(view) + + const inboxesData = useInboxList((inboxes) => + Object.fromEntries(inboxes.map((inbox) => [inbox.id, [inbox.id]])), + ) const categoryOpenStateData = useCategoryOpenStateByView(view) diff --git a/apps/desktop/layer/renderer/src/modules/subscription-column/SubscriptionList.mobile.tsx b/apps/desktop/layer/renderer/src/modules/subscription-column/SubscriptionList.mobile.tsx index a6085fc3c4..4e91b3d274 100644 --- a/apps/desktop/layer/renderer/src/modules/subscription-column/SubscriptionList.mobile.tsx +++ b/apps/desktop/layer/renderer/src/modules/subscription-column/SubscriptionList.mobile.tsx @@ -1,4 +1,5 @@ import { views } from "@follow/constants" +import { useInboxList } from "@follow/store/inbox/hooks" import { cn } from "@follow/utils/utils" import { memo } from "react" import { useTranslation } from "react-i18next" @@ -10,7 +11,6 @@ import { Queries } from "~/queries" import { useCategoryOpenStateByView, useFeedsGroupedData, - useInboxesGroupedData, useListsGroupedData, } from "~/store/subscription" @@ -22,7 +22,9 @@ import { EmptyFeedList, ListHeader, StarredItem } from "./SubscriptionList.share const FeedListImpl = ({ className, view }: SubscriptionProps) => { const feedsData = useFeedsGroupedData(view) const listsData = useListsGroupedData(view) - const inboxesData = useInboxesGroupedData(view) + const inboxesData = useInboxList((inboxes) => + Object.fromEntries(inboxes.map((inbox) => [inbox.id, [inbox.id]])), + ) const categoryOpenStateData = useCategoryOpenStateByView(view) const hasData = diff --git a/apps/desktop/layer/renderer/src/queries/inboxes.ts b/apps/desktop/layer/renderer/src/queries/inboxes.ts deleted file mode 100644 index f5111ab8a8..0000000000 --- a/apps/desktop/layer/renderer/src/queries/inboxes.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useAuthQuery } from "~/hooks/common" -import { defineQuery } from "~/lib/defineQuery" -import { inboxActions } from "~/store/inbox" - -export const inboxes = { - byId: ({ id }: { id: string }) => - defineQuery(["inboxes", id], async () => inboxActions.fetchInboxById(id), { - rootKey: ["inboxes"], - }), - list: () => - defineQuery(["inboxes"], async () => inboxActions.fetchOwnedInboxes(), { - rootKey: ["inboxes"], - }), -} - -export const useInbox = ({ id }: { id?: string }) => - useAuthQuery(inboxes.byId({ id: id! }), { - enabled: !!id, - }) - -export const useInboxList = () => - useAuthQuery(inboxes.list(), { - enabled: true, - }) diff --git a/apps/desktop/layer/renderer/src/services/inbox.ts b/apps/desktop/layer/renderer/src/services/inbox.ts index a3e0352b84..4cbb4e39c4 100644 --- a/apps/desktop/layer/renderer/src/services/inbox.ts +++ b/apps/desktop/layer/renderer/src/services/inbox.ts @@ -1,7 +1,6 @@ import type { InboxModel } from "@follow/models/types" import { browserDB } from "~/database" -import { inboxActions } from "~/store/inbox" import { BaseService } from "./base" import { CleanerService } from "./cleaner" @@ -32,10 +31,7 @@ class ServiceStatic extends BaseService<{ id: string }> implements Hydratable { return this.table.bulkDelete(ids) } - async hydrate() { - const data = await this.findAll() - inboxActions.upsertMany(data) - } + async hydrate() {} } export const InboxService = new ServiceStatic() diff --git a/apps/desktop/layer/renderer/src/store/entry/store.ts b/apps/desktop/layer/renderer/src/store/entry/store.ts index fbe6ba3946..a67f58fea5 100644 --- a/apps/desktop/layer/renderer/src/store/entry/store.ts +++ b/apps/desktop/layer/renderer/src/store/entry/store.ts @@ -7,6 +7,7 @@ import type { FeedOrListRespModel, InboxModel, } from "@follow/models/types" +import { inboxActions } from "@follow/store/inbox/store" import { unreadActions } from "@follow/store/unread/store" import { getInboxFeedIdWithPrefix, @@ -24,7 +25,6 @@ import { EntryService } from "~/services/entry" import { feedActions } from "../feed" import { imageActions } from "../image" -import { inboxActions } from "../inbox" import { getSubscriptionByFeedId } from "../subscription" import { createTransaction, createZustandStore } from "../utils/helper" import { internal_batchMarkRead } from "./helper" diff --git a/apps/desktop/layer/renderer/src/store/feed/hooks.ts b/apps/desktop/layer/renderer/src/store/feed/hooks.ts index 6133a22c70..36f4ba4965 100644 --- a/apps/desktop/layer/renderer/src/store/feed/hooks.ts +++ b/apps/desktop/layer/renderer/src/store/feed/hooks.ts @@ -1,5 +1,5 @@ import { views } from "@follow/constants" -import type { FeedModel, FeedOrListRespModel, InboxModel, ListModel } from "@follow/models/types" +import type { FeedModel, FeedOrListRespModel, ListModel } from "@follow/models/types" import { useCallback } from "react" import { useTranslation } from "react-i18next" @@ -11,14 +11,12 @@ import { } from "~/constants" import { useRouteParams } from "~/hooks/biz/useRouteParams" -import { useInboxStore } from "../inbox" import { useListStore } from "../list" import { feedByIdOrUrlSelector, feedByIdSelector, feedByIdSelectorWithTransform, feedByIdWithTransformSelector, - inboxByIdSelectorWithTransform, listByIdSelectorWithTransform, } from "./selector" import { getPreferredTitle, useFeedStore } from "./store" @@ -63,17 +61,6 @@ export const useListByIdSelector = ( ), ) -export const useInboxByIdSelector = ( - inboxId: Nullable, - selector: (inbox: InboxModel) => T, -) => - useInboxStore( - useCallback( - (state) => inboxByIdSelectorWithTransform(inboxId, selector)(state), - [inboxId, selector], - ), - ) - export const useFeedHeaderTitle = () => { const { t } = useTranslation() const { feedId: currentFeedId, view, listId: currentListId } = useRouteParams() diff --git a/apps/desktop/layer/renderer/src/store/feed/selector.ts b/apps/desktop/layer/renderer/src/store/feed/selector.ts index 89a381095f..1d591402df 100644 --- a/apps/desktop/layer/renderer/src/store/feed/selector.ts +++ b/apps/desktop/layer/renderer/src/store/feed/selector.ts @@ -1,6 +1,6 @@ import type { FeedModel, FeedOrListRespModel } from "@follow/models" +import type { useInboxStore } from "@follow/store/inbox/store" -import type { useInboxStore } from "../inbox" import type { useListStore } from "../list" import type { useFeedStore } from "./store" import type { FeedQueryParams } from "./types" diff --git a/apps/desktop/layer/renderer/src/store/inbox/hooks.ts b/apps/desktop/layer/renderer/src/store/inbox/hooks.ts deleted file mode 100644 index 843f4e0d93..0000000000 --- a/apps/desktop/layer/renderer/src/store/inbox/hooks.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { InboxModel } from "@follow/models/types" - -import { useInboxStore } from "./store" - -export function useInboxById(inboxId: Nullable): InboxModel | null -export function useInboxById( - inboxId: Nullable, - selector: (inbox: InboxModel | null) => T, -): T - -export function useInboxById(inboxId: Nullable, selector?: (inbox: InboxModel) => T) { - return useInboxStore((state) => - inboxId ? (selector ? selector(state.inboxes[inboxId]!) : state.inboxes[inboxId]) : null, - ) -} diff --git a/apps/desktop/layer/renderer/src/store/inbox/index.ts b/apps/desktop/layer/renderer/src/store/inbox/index.ts deleted file mode 100644 index a92fe14dc6..0000000000 --- a/apps/desktop/layer/renderer/src/store/inbox/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./hooks" -export * from "./store" -export * from "./types" diff --git a/apps/desktop/layer/renderer/src/store/inbox/store.ts b/apps/desktop/layer/renderer/src/store/inbox/store.ts deleted file mode 100644 index 4344eb652c..0000000000 --- a/apps/desktop/layer/renderer/src/store/inbox/store.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { InboxModel } from "@follow/models/types" - -import { runTransactionInScope } from "~/database" -import { apiClient } from "~/lib/api-fetch" -import { InboxService } from "~/services/inbox" - -import { createImmerSetter, createTransaction, createZustandStore } from "../utils/helper" -import type { InboxState } from "./types" - -export const useInboxStore = createZustandStore("inbox")(() => ({ - inboxes: {}, -})) - -const set = createImmerSetter(useInboxStore) -const get = useInboxStore.getState -class InboxActionStatic { - upsertMany(inboxes: InboxModel[]) { - if (inboxes.length === 0) return - set((state) => { - for (const inbox of inboxes) { - state.inboxes[inbox.id] = inbox - } - return state - }) - - runTransactionInScope(() => InboxService.upsertMany(inboxes)) - } - - async fetchInboxById(id: string) { - const res = await apiClient.inboxes.$get({ - query: { - handle: id, - }, - }) - this.upsertMany([res.data]) - - return res.data - } - - clear() { - set((state) => { - state.inboxes = {} - }) - } - - private clearByInboxId(id: string) { - set((state) => { - delete state.inboxes[id] - }) - } - - async deleteInbox(inboxId: string) { - const inbox = get().inboxes[inboxId] - const tx = createTransaction(inbox) - tx.execute(async () => { - await apiClient.inboxes.$delete({ - json: { - handle: inboxId, - }, - }) - }) - - tx.optimistic(async () => this.clearByInboxId(inboxId)) - tx.persist(() => InboxService.bulkDelete([inboxId])) - tx.rollback(async (inbox) => this.upsertMany([inbox])) - await tx.run() - } - - async fetchOwnedInboxes() { - const res = await apiClient.inboxes.list.$get() - this.upsertMany(res.data) - - return res.data - } -} - -export const inboxActions = new InboxActionStatic() diff --git a/apps/desktop/layer/renderer/src/store/inbox/types.ts b/apps/desktop/layer/renderer/src/store/inbox/types.ts deleted file mode 100644 index 857e4f5b4e..0000000000 --- a/apps/desktop/layer/renderer/src/store/inbox/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { InboxModel } from "@follow/models/types" - -type InboxId = string - -export interface InboxState { - inboxes: Record -} diff --git a/apps/desktop/layer/renderer/src/store/subscription/hooks.ts b/apps/desktop/layer/renderer/src/store/subscription/hooks.ts index 634c127061..3ec3a9c402 100644 --- a/apps/desktop/layer/renderer/src/store/subscription/hooks.ts +++ b/apps/desktop/layer/renderer/src/store/subscription/hooks.ts @@ -167,25 +167,6 @@ export const useListsGroupedData = (view: FeedViewType) => { }, [data]) } -export const useInboxesGroupedData = (view: FeedViewType) => { - const data = useSubscriptionByView(view) - - return useMemo(() => { - if (!data || data.length === 0) return {} - - const inboxes = data.filter((s) => s && "inboxId" in s) - - const groupFolder = {} as Record - - for (const subscription of inboxes.filter((s) => !!s)) { - if (!subscription.inboxId) continue - groupFolder[subscription.inboxId] = [subscription.inboxId] - } - - return groupFolder - }, [data]) -} - export const useIsSubscribed = (feedId: string) => useSubscriptionStore(useCallback((state) => isSubscribedSelector(feedId)(state), [feedId])) diff --git a/apps/desktop/layer/renderer/src/store/subscription/store.ts b/apps/desktop/layer/renderer/src/store/subscription/store.ts index 0fafeece5d..c3d5d34dd8 100644 --- a/apps/desktop/layer/renderer/src/store/subscription/store.ts +++ b/apps/desktop/layer/renderer/src/store/subscription/store.ts @@ -5,6 +5,7 @@ import type { ListModelPoplutedFeeds, SubscriptionModel, } from "@follow/models/types" +import { inboxActions } from "@follow/store/inbox/store" import { unreadActions } from "@follow/store/unread/store" import { getInboxFeedIdWithPrefix } from "@follow/store/unread/utils" import { capitalizeFirstLetter, omitShallow } from "@follow/utils/utils" @@ -22,7 +23,6 @@ import { SubscriptionService } from "~/services" import { entryActions } from "../entry" import { feedActions, getFeedById } from "../feed" -import { inboxActions } from "../inbox" import { getListById, listActions } from "../list" import { createImmerSetter, createTransaction, createZustandStore } from "../utils/helper" diff --git a/apps/desktop/layer/renderer/src/store/utils/clear.ts b/apps/desktop/layer/renderer/src/store/utils/clear.ts index cf750b93a3..44f0883b4a 100644 --- a/apps/desktop/layer/renderer/src/store/utils/clear.ts +++ b/apps/desktop/layer/renderer/src/store/utils/clear.ts @@ -1,3 +1,4 @@ +import { inboxActions } from "@follow/store/inbox/store" import { unreadActions } from "@follow/store/unread/store" import { getStorageNS } from "@follow/utils/ns" @@ -7,7 +8,6 @@ import { browserDB } from "~/database" import { entryActions } from "../entry" import { feedActions } from "../feed" import { clearImageDimensionsDb } from "../image/db" -import { inboxActions } from "../inbox" import { listActions } from "../list" import { subscriptionActions } from "../subscription" diff --git a/apps/mobile/src/modules/context-menu/inbox.tsx b/apps/mobile/src/modules/context-menu/inbox.tsx index baf97eb28f..26e9fd2925 100644 --- a/apps/mobile/src/modules/context-menu/inbox.tsx +++ b/apps/mobile/src/modules/context-menu/inbox.tsx @@ -1,4 +1,4 @@ -import { useInbox } from "@follow/store/inbox/hooks" +import { useIsInbox } from "@follow/store/inbox/hooks" import { setStringAsync } from "expo-clipboard" import type { PropsWithChildren } from "react" import { useTranslation } from "react-i18next" @@ -12,9 +12,9 @@ type InboxContextMenuProps = PropsWithChildren<{ export const InboxContextMenu = ({ inboxId, children }: InboxContextMenuProps) => { const { t } = useTranslation() - const inbox = useInbox(inboxId) + const isInbox = useIsInbox(inboxId) - if (!inbox) { + if (!isInbox) { return children } @@ -26,7 +26,7 @@ export const InboxContextMenu = ({ inboxId, children }: InboxContextMenuProps) = { - setStringAsync(`${inbox.id}@follow.re`).then(() => { + setStringAsync(`${inboxId}@follow.re`).then(() => { toast.success( t("operation.copy_which_success", { which: t("operation.copy.email_address") }), ) diff --git a/apps/mobile/src/modules/screen/atoms.ts b/apps/mobile/src/modules/screen/atoms.ts index 4736f58561..b0cdff8493 100644 --- a/apps/mobile/src/modules/screen/atoms.ts +++ b/apps/mobile/src/modules/screen/atoms.ts @@ -3,7 +3,7 @@ import { usePrefetchEntries } from "@follow/store/entry/hooks" import type { FetchEntriesProps } from "@follow/store/entry/types" import { FEED_COLLECTION_LIST } from "@follow/store/entry/utils" import { useFeed } from "@follow/store/feed/hooks" -import { useInbox } from "@follow/store/inbox/hooks" +import { useInboxById } from "@follow/store/inbox/hooks" import { useList } from "@follow/store/list/hooks" import { getSubscriptionByCategory } from "@follow/store/subscription/getter" import { jotaiStore } from "@follow/utils" @@ -150,7 +150,9 @@ export const useSelectedFeedTitle = () => { ) const feed = useFeed(selectedFeed && selectedFeed.type === "feed" ? selectedFeed.feedId : "") const list = useList(selectedFeed && selectedFeed.type === "list" ? selectedFeed.listId : "") - const inbox = useInbox(selectedFeed && selectedFeed.type === "inbox" ? selectedFeed.inboxId : "") + const inbox = useInboxById( + selectedFeed && selectedFeed.type === "inbox" ? selectedFeed.inboxId : "", + ) const { t } = useTranslation("common") if (!selectedFeed) { diff --git a/apps/mobile/src/modules/subscription/SubscriptionLists.tsx b/apps/mobile/src/modules/subscription/SubscriptionLists.tsx index c8b116d243..581e580501 100644 --- a/apps/mobile/src/modules/subscription/SubscriptionLists.tsx +++ b/apps/mobile/src/modules/subscription/SubscriptionLists.tsx @@ -1,8 +1,8 @@ import type { FeedViewType } from "@follow/constants" import { FEED_COLLECTION_LIST } from "@follow/store/entry/utils" +import { useInboxList } from "@follow/store/inbox/hooks" import { useGroupedSubscription, - useInboxSubscription, useListSubscription, useSortedGroupedSubscription, useSortedListSubscription, @@ -64,7 +64,7 @@ const SubscriptionListImpl = ({ hideAllReadSubscriptions, }) - const inboxes = useInboxSubscription(view) + const inboxes = useInboxList((inboxes) => inboxes.map((inbox) => inbox.id)) const { grouped, unGrouped } = useGroupedSubscription({ view, diff --git a/packages/internal/database/src/drizzle/0023_tidy_alex_power.sql b/packages/internal/database/src/drizzle/0023_tidy_alex_power.sql new file mode 100644 index 0000000000..73cb2f3c8d --- /dev/null +++ b/packages/internal/database/src/drizzle/0023_tidy_alex_power.sql @@ -0,0 +1 @@ +ALTER TABLE `inboxes` ADD `secret` text DEFAULT '' NOT NULL; \ No newline at end of file diff --git a/packages/internal/database/src/drizzle/0024_curvy_inertia.sql b/packages/internal/database/src/drizzle/0024_curvy_inertia.sql new file mode 100644 index 0000000000..8f5bb10ab8 --- /dev/null +++ b/packages/internal/database/src/drizzle/0024_curvy_inertia.sql @@ -0,0 +1,11 @@ +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_inboxes` ( + `id` text PRIMARY KEY NOT NULL, + `title` text, + `secret` text NOT NULL +); +--> statement-breakpoint +INSERT INTO `__new_inboxes`("id", "title", "secret") SELECT "id", "title", "secret" FROM `inboxes`;--> statement-breakpoint +DROP TABLE `inboxes`;--> statement-breakpoint +ALTER TABLE `__new_inboxes` RENAME TO `inboxes`;--> statement-breakpoint +PRAGMA foreign_keys=ON; \ No newline at end of file diff --git a/packages/internal/database/src/drizzle/meta/0023_snapshot.json b/packages/internal/database/src/drizzle/meta/0023_snapshot.json new file mode 100644 index 0000000000..1db8910817 --- /dev/null +++ b/packages/internal/database/src/drizzle/meta/0023_snapshot.json @@ -0,0 +1,730 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "e0e486d3-09a8-4b2c-bb74-c1a369cb3bb8", + "prevId": "52d21c45-d498-48b7-8c8c-3775d72d16a4", + "tables": { + "collections": { + "name": "collections", + "columns": { + "feed_id": { + "name": "feed_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entry_id": { + "name": "entry_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "view": { + "name": "view", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "entries": { + "name": "entries", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source_content": { + "name": "source_content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "guid": { + "name": "guid", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "author_url": { + "name": "author_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "author_avatar": { + "name": "author_avatar", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inserted_at": { + "name": "inserted_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "media": { + "name": "media", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "categories": { + "name": "categories", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "attachments": { + "name": "attachments", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "extra": { + "name": "extra", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "feed_id": { + "name": "feed_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inbox_handle": { + "name": "inbox_handle", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read": { + "name": "read", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sources": { + "name": "sources", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "settings": { + "name": "settings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "feeds": { + "name": "feeds", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_at": { + "name": "error_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "site_url": { + "name": "site_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "images": { + "name": "images", + "columns": { + "url": { + "name": "url", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "colors": { + "name": "colors", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inboxes": { + "name": "inboxes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "lists": { + "name": "lists", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "feed_ids": { + "name": "feed_ids", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "view": { + "name": "view", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fee": { + "name": "fee", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subscription_count": { + "name": "subscription_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "purchase_amount": { + "name": "purchase_amount", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "subscriptions": { + "name": "subscriptions", + "columns": { + "feed_id": { + "name": "feed_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "list_id": { + "name": "list_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inbox_id": { + "name": "inbox_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "view": { + "name": "view", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_private": { + "name": "is_private", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "summaries": { + "name": "summaries", + "columns": { + "entry_id": { + "name": "entry_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "readability_summary": { + "name": "readability_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "unq": { + "name": "unq", + "columns": ["entry_id", "language"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "translations": { + "name": "translations", + "columns": { + "entry_id": { + "name": "entry_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "readability_content": { + "name": "readability_content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "translation-unique-index": { + "name": "translation-unique-index", + "columns": ["entry_id", "language"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "unread": { + "name": "unread", + "columns": { + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "handle": { + "name": "handle", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_me": { + "name": "is_me", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/packages/internal/database/src/drizzle/meta/0024_snapshot.json b/packages/internal/database/src/drizzle/meta/0024_snapshot.json new file mode 100644 index 0000000000..a30b34b00b --- /dev/null +++ b/packages/internal/database/src/drizzle/meta/0024_snapshot.json @@ -0,0 +1,729 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "4f611bbb-7ee9-4510-a179-efe73bd2204e", + "prevId": "e0e486d3-09a8-4b2c-bb74-c1a369cb3bb8", + "tables": { + "collections": { + "name": "collections", + "columns": { + "feed_id": { + "name": "feed_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entry_id": { + "name": "entry_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "view": { + "name": "view", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "entries": { + "name": "entries", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source_content": { + "name": "source_content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "guid": { + "name": "guid", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "author_url": { + "name": "author_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "author_avatar": { + "name": "author_avatar", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inserted_at": { + "name": "inserted_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "media": { + "name": "media", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "categories": { + "name": "categories", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "attachments": { + "name": "attachments", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "extra": { + "name": "extra", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "feed_id": { + "name": "feed_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inbox_handle": { + "name": "inbox_handle", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read": { + "name": "read", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sources": { + "name": "sources", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "settings": { + "name": "settings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "feeds": { + "name": "feeds", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_at": { + "name": "error_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "site_url": { + "name": "site_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "images": { + "name": "images", + "columns": { + "url": { + "name": "url", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "colors": { + "name": "colors", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inboxes": { + "name": "inboxes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "lists": { + "name": "lists", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "feed_ids": { + "name": "feed_ids", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "view": { + "name": "view", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fee": { + "name": "fee", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subscription_count": { + "name": "subscription_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "purchase_amount": { + "name": "purchase_amount", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "subscriptions": { + "name": "subscriptions", + "columns": { + "feed_id": { + "name": "feed_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "list_id": { + "name": "list_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inbox_id": { + "name": "inbox_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "view": { + "name": "view", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_private": { + "name": "is_private", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "summaries": { + "name": "summaries", + "columns": { + "entry_id": { + "name": "entry_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "readability_summary": { + "name": "readability_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "unq": { + "name": "unq", + "columns": ["entry_id", "language"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "translations": { + "name": "translations", + "columns": { + "entry_id": { + "name": "entry_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "readability_content": { + "name": "readability_content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "translation-unique-index": { + "name": "translation-unique-index", + "columns": ["entry_id", "language"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "unread": { + "name": "unread", + "columns": { + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "handle": { + "name": "handle", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_me": { + "name": "is_me", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/packages/internal/database/src/drizzle/meta/_journal.json b/packages/internal/database/src/drizzle/meta/_journal.json index 60584dcd8f..c4c0813c0c 100644 --- a/packages/internal/database/src/drizzle/meta/_journal.json +++ b/packages/internal/database/src/drizzle/meta/_journal.json @@ -162,6 +162,20 @@ "when": 1748331571730, "tag": "0022_tiny_northstar", "breakpoints": true + }, + { + "idx": 23, + "version": "6", + "when": 1749126453373, + "tag": "0023_tidy_alex_power", + "breakpoints": true + }, + { + "idx": 24, + "version": "6", + "when": 1749126847204, + "tag": "0024_curvy_inertia", + "breakpoints": true } ] } diff --git a/packages/internal/database/src/drizzle/migrations.js b/packages/internal/database/src/drizzle/migrations.js index 924b0da3d0..0b292a7974 100644 --- a/packages/internal/database/src/drizzle/migrations.js +++ b/packages/internal/database/src/drizzle/migrations.js @@ -23,6 +23,8 @@ import m0019 from "./0019_wonderful_shape.sql" import m0020 from "./0020_little_marauders.sql" import m0021 from "./0021_wakeful_onslaught.sql" import m0022 from "./0022_tiny_northstar.sql" +import m0023 from "./0023_tidy_alex_power.sql" +import m0024 from "./0024_curvy_inertia.sql" import journal from "./meta/_journal.json" export default { @@ -51,5 +53,7 @@ export default { m0020, m0021, m0022, + m0023, + m0024, }, } diff --git a/packages/internal/database/src/schemas/index.ts b/packages/internal/database/src/schemas/index.ts index e1ded36833..e98cb010bd 100644 --- a/packages/internal/database/src/schemas/index.ts +++ b/packages/internal/database/src/schemas/index.ts @@ -35,6 +35,7 @@ export const subscriptionsTable = sqliteTable("subscriptions", { export const inboxesTable = sqliteTable("inboxes", { id: text("id").primaryKey(), title: text("title"), + secret: text("secret").notNull(), }) export const listsTable = sqliteTable("lists", { diff --git a/packages/internal/database/src/services/inbox.ts b/packages/internal/database/src/services/inbox.ts index 372b9c25c4..3447d059f0 100644 --- a/packages/internal/database/src/services/inbox.ts +++ b/packages/internal/database/src/services/inbox.ts @@ -1,3 +1,5 @@ +import { eq } from "drizzle-orm" + import { db } from "../db" import { inboxesTable } from "../schemas" import type { InboxSchema } from "../schemas/types" @@ -9,6 +11,10 @@ class InboxServiceStatic implements Resetable { await db.delete(inboxesTable).execute() } + async deleteById(id: string) { + await db.delete(inboxesTable).where(eq(inboxesTable.id, id)).execute() + } + getInboxAll() { return db.query.inboxesTable.findMany() } diff --git a/packages/internal/store/src/inbox/getters.ts b/packages/internal/store/src/inbox/getters.ts new file mode 100644 index 0000000000..dc16df15c9 --- /dev/null +++ b/packages/internal/store/src/inbox/getters.ts @@ -0,0 +1,5 @@ +import { useInboxStore } from "./store" + +export function getInboxList() { + return Object.values(useInboxStore.getState().inboxes) +} diff --git a/packages/internal/store/src/inbox/hooks.ts b/packages/internal/store/src/inbox/hooks.ts index 5af370036f..40596c4802 100644 --- a/packages/internal/store/src/inbox/hooks.ts +++ b/packages/internal/store/src/inbox/hooks.ts @@ -1,7 +1,25 @@ import { useInboxStore } from "./store" +import type { InboxModel } from "./types" -export const useInbox = (inboxId: string) => { +export const useIsInbox = (inboxId?: string) => { return useInboxStore((state) => { + if (!inboxId) return false + return !!state.inboxes[inboxId] + }) +} + +export const useInboxById = (inboxId?: string) => { + return useInboxStore((state) => { + if (!inboxId) return return state.inboxes[inboxId] }) } + +export function useInboxList(): InboxModel[] +export function useInboxList(selector: (inboxes: InboxModel[]) => T): T +export function useInboxList(selector?: (inboxes: InboxModel[]) => T) { + return useInboxStore((state) => { + const inboxes = Object.values(state.inboxes) + return selector ? selector(inboxes) : inboxes + }) +} diff --git a/packages/internal/store/src/inbox/store.ts b/packages/internal/store/src/inbox/store.ts index c81088911d..6b51a555eb 100644 --- a/packages/internal/store/src/inbox/store.ts +++ b/packages/internal/store/src/inbox/store.ts @@ -1,11 +1,14 @@ import type { InboxSchema } from "@follow/database/schemas/types" import { InboxService } from "@follow/database/services/inbox" +import { UnreadService } from "@follow/database/services/unread" -import type { Hydratable } from "../internal/base" -import { createTransaction, createZustandStore } from "../internal/helper" +import { apiClient } from "../context" +import type { Hydratable, Resetable } from "../internal/base" +import { createImmerSetter, createTransaction, createZustandStore } from "../internal/helper" +import type { InboxModel } from "./types" interface InboxState { - inboxes: Record + inboxes: Record } const defaultState = { @@ -14,9 +17,11 @@ const defaultState = { export const useInboxStore = createZustandStore("inbox")(() => defaultState) -// const get = useInboxStore.getState +const get = useInboxStore.getState const set = useInboxStore.setState -class InboxActions implements Hydratable { +const immerSet = createImmerSetter(useInboxStore) + +class InboxActions implements Hydratable, Resetable { async hydrate() { const inboxes = await InboxService.getInboxAll() inboxActions.upsertManyInSession(inboxes) @@ -27,7 +32,10 @@ class InboxActions implements Hydratable { ...state.inboxes, } inboxes.forEach((inbox) => { - nextInboxes[inbox.id] = inbox + nextInboxes[inbox.id] = { + type: "inbox", + ...inbox, + } }) set({ ...state, @@ -45,9 +53,96 @@ class InboxActions implements Hydratable { tx.run() } - reset() { - set(defaultState) + deleteById(id: string) { + immerSet((state) => { + delete state.inboxes[id] + }) + } + + async reset() { + const tx = createTransaction() + tx.store(() => { + set(defaultState) + }) + + tx.persist(() => { + return UnreadService.reset() + }) + + await tx.run() + } +} + +class InboxSyncService { + async createInbox({ handle, title }: { handle: string; title: string }) { + const newInbox = { + id: handle, + title, + secret: "", + } + const tx = createTransaction() + tx.store(async () => { + await inboxActions.upsertManyInSession([newInbox]) + }) + tx.request(async () => { + await apiClient().inboxes.$post({ + json: { + handle, + title, + }, + }) + }) + + tx.persist(() => InboxService.upsertMany([newInbox])) + tx.rollback(() => inboxActions.deleteById(handle)) + await tx.run() + } + + async updateInbox({ handle, title }: { handle: string; title: string }) { + const existingInbox = get().inboxes[handle] + if (!existingInbox) return + + const newInbox = { + ...existingInbox, + title, + } + const tx = createTransaction() + tx.store(async () => { + await inboxActions.upsertManyInSession([newInbox]) + }) + tx.request(async () => { + await apiClient().inboxes.$put({ + json: { + handle, + title, + }, + }) + }) + + tx.persist(() => InboxService.upsertMany([newInbox])) + tx.rollback(() => inboxActions.upsertMany([existingInbox])) + await tx.run() + } + + async deleteInbox(inboxId: string) { + const inbox = get().inboxes[inboxId] + if (!inbox) return + + const tx = createTransaction(inbox) + tx.store(async () => inboxActions.deleteById(inboxId)) + tx.request(async () => { + await apiClient().inboxes.$delete({ + json: { + handle: inboxId, + }, + }) + }) + + tx.persist(() => InboxService.deleteById(inboxId)) + tx.rollback(async (inbox) => inboxActions.upsertMany([inbox])) + await tx.run() } } export const inboxActions = new InboxActions() +export const inboxSyncService = new InboxSyncService() diff --git a/packages/internal/store/src/inbox/types.ts b/packages/internal/store/src/inbox/types.ts new file mode 100644 index 0000000000..41427ddde8 --- /dev/null +++ b/packages/internal/store/src/inbox/types.ts @@ -0,0 +1,7 @@ +import type { InboxSchema } from "@follow/database/schemas/types" + +export type InboxModel = InboxSchema & { + type: "inbox" + // for easier type checking, do not exist actually + ownerUserId?: string +} diff --git a/packages/internal/store/src/morph/hono.ts b/packages/internal/store/src/morph/hono.ts index 25f433e199..512a6149b2 100644 --- a/packages/internal/store/src/morph/hono.ts +++ b/packages/internal/store/src/morph/hono.ts @@ -59,7 +59,8 @@ class Morph { collections.inboxes.push({ id: inbox.id, - title: inbox.title!, + title: inbox.title, + secret: inbox.secret, }) } diff --git a/packages/internal/store/src/subscription/getter.ts b/packages/internal/store/src/subscription/getter.ts index cea55b5c02..5da69c15d9 100644 --- a/packages/internal/store/src/subscription/getter.ts +++ b/packages/internal/store/src/subscription/getter.ts @@ -1,5 +1,6 @@ -import type { FeedViewType } from "@follow/constants" +import { FeedViewType } from "@follow/constants" +import { getInboxList } from "../inbox/getters" import { getListFeedIds } from "../list/getters" import type { SubscriptionModel } from "./store" import { useSubscriptionStore } from "./store" @@ -14,7 +15,7 @@ export const getSubscription = (id?: string): SubscriptionModel | undefined => { export const getSubscriptionByView = (view: FeedViewType): string[] => { const state = get() return Array.from(state.feedIdByView[view]) - .concat(Array.from(state.inboxIdByView[view])) + .concat(view === FeedViewType.Articles ? getInboxList().map((i) => i.id) : []) .concat(Array.from(state.listIdByView[view]).flatMap((id) => getListFeedIds(id) ?? [])) } diff --git a/packages/internal/store/src/subscription/hooks.ts b/packages/internal/store/src/subscription/hooks.ts index e91e9e27b9..de53fbce54 100644 --- a/packages/internal/store/src/subscription/hooks.ts +++ b/packages/internal/store/src/subscription/hooks.ts @@ -263,17 +263,6 @@ export const useSortedListSubscription = ({ ) } -export const useInboxSubscription = (view: FeedViewType) => { - return useSubscriptionStore( - useCallback( - (state) => { - return Array.from(state.inboxIdByView[view]) - }, - [view], - ), - ) -} - export const useSubscriptionCategory = (view?: FeedViewType) => { return useSubscriptionStore( useCallback( diff --git a/packages/internal/store/src/subscription/store.ts b/packages/internal/store/src/subscription/store.ts index ee5404e1d6..60374e2cbb 100644 --- a/packages/internal/store/src/subscription/store.ts +++ b/packages/internal/store/src/subscription/store.ts @@ -18,7 +18,6 @@ import { getInboxStoreId, getSubscriptionStoreId } from "./utils" type FeedId = string type ListId = string -type InboxId = string export type SubscriptionModel = Omit interface SubscriptionState { @@ -32,7 +31,6 @@ interface SubscriptionState { listIdByView: Record> - inboxIdByView: Record> /** * All named categories names set */ @@ -55,7 +53,6 @@ const defaultState: SubscriptionState = { data: {}, feedIdByView: { ...emptyDataSetByView }, listIdByView: { ...emptyDataSetByView }, - inboxIdByView: { ...emptyDataSetByView }, categories: { ...emptyDataSetByView }, subscriptionIdSet: new Set(), } @@ -93,7 +90,6 @@ class SubscriptionActions implements Hydratable { if (subscription.inboxId && subscription.type === "inbox") { draft.data[getInboxStoreId(subscription.inboxId)] = subscription draft.subscriptionIdSet.add(`${subscription.type}/${subscription.inboxId}`) - draft.inboxIdByView[subscription.view as FeedViewType].add(subscription.inboxId) } } }) @@ -127,7 +123,6 @@ class SubscriptionActions implements Hydratable { immerSet((draft) => { draft.feedIdByView[view] = new Set() draft.listIdByView[view] = new Set() - draft.inboxIdByView[view] = new Set() draft.categories[view] = new Set() draft.subscriptionIdSet = new Set() }) @@ -288,8 +283,6 @@ class SubscriptionSyncService { draft.feedIdByView[subscription.view as FeedViewType].delete(subscription.feedId) if (subscription.listId) draft.listIdByView[subscription.view as FeedViewType].delete(subscription.listId) - if (subscription.inboxId) - draft.inboxIdByView[subscription.view as FeedViewType].delete(subscription.inboxId) if (subscription.category) draft.categories[subscription.view as FeedViewType].delete(subscription.category) }) @@ -317,8 +310,6 @@ class SubscriptionSyncService { draft.feedIdByView[subscription.view as FeedViewType].add(subscription.feedId) if (subscription.listId) draft.listIdByView[subscription.view as FeedViewType].add(subscription.listId) - if (subscription.inboxId) - draft.inboxIdByView[subscription.view as FeedViewType].add(subscription.inboxId) if (subscription.category) draft.categories[subscription.view as FeedViewType].add(subscription.category) })