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)
})