Merge pull request #4260 from omnivore-app/perf/cache-api-response
perf/cache api response
This commit is contained in:
@ -37,6 +37,7 @@ import {
|
||||
MutationSaveArticleReadingProgressArgs,
|
||||
MutationSetBookmarkArticleArgs,
|
||||
MutationSetFavoriteArticleArgs,
|
||||
PageInfo,
|
||||
PageType,
|
||||
QueryArticleArgs,
|
||||
QuerySearchArgs,
|
||||
@ -79,7 +80,8 @@ import {
|
||||
countLibraryItems,
|
||||
createOrUpdateLibraryItem,
|
||||
findLibraryItemsByPrefix,
|
||||
searchAndCountLibraryItems,
|
||||
SearchArgs,
|
||||
searchLibraryItems,
|
||||
softDeleteLibraryItem,
|
||||
sortParamsToSort,
|
||||
updateLibraryItem,
|
||||
@ -582,8 +584,15 @@ export const saveArticleReadingProgressResolver = authorized<
|
||||
|
||||
export type PartialLibraryItem = Merge<LibraryItem, { format?: string }>
|
||||
type PartialSearchItemEdge = Merge<SearchItemEdge, { node: PartialLibraryItem }>
|
||||
export type PartialPageInfo = Merge<
|
||||
PageInfo,
|
||||
{ searchLibraryItemArgs?: SearchArgs }
|
||||
>
|
||||
export const searchResolver = authorized<
|
||||
Merge<SearchSuccess, { edges: Array<PartialSearchItemEdge> }>,
|
||||
Merge<
|
||||
SearchSuccess,
|
||||
{ edges: Array<PartialSearchItemEdge>; pageInfo: PartialPageInfo }
|
||||
>,
|
||||
SearchError,
|
||||
QuerySearchArgs
|
||||
>(async (_obj, params, { uid }) => {
|
||||
@ -595,18 +604,17 @@ export const searchResolver = authorized<
|
||||
return { errorCodes: [SearchErrorCode.QueryTooLong] }
|
||||
}
|
||||
|
||||
const { libraryItems, count } = await searchAndCountLibraryItems(
|
||||
{
|
||||
from: Number(startCursor),
|
||||
size: first + 1, // fetch one more item to get next cursor
|
||||
includePending: true,
|
||||
includeContent: params.includeContent ?? true, // by default include content for offline use for now
|
||||
includeDeleted: params.query?.includes('in:trash'),
|
||||
query: params.query,
|
||||
useFolders: params.query?.includes('use:folders'),
|
||||
},
|
||||
uid
|
||||
)
|
||||
const searchLibraryItemArgs = {
|
||||
from: Number(startCursor),
|
||||
size: first + 1, // fetch one more item to get next cursor
|
||||
includePending: true,
|
||||
includeContent: params.includeContent ?? true, // by default include content for offline use for now
|
||||
includeDeleted: params.query?.includes('in:trash'),
|
||||
query: params.query,
|
||||
useFolders: params.query?.includes('use:folders'),
|
||||
}
|
||||
|
||||
const libraryItems = await searchLibraryItems(searchLibraryItemArgs, uid)
|
||||
|
||||
const start =
|
||||
startCursor && !isNaN(Number(startCursor)) ? Number(startCursor) : 0
|
||||
@ -631,7 +639,7 @@ export const searchResolver = authorized<
|
||||
startCursor,
|
||||
hasNextPage,
|
||||
endCursor,
|
||||
totalCount: count,
|
||||
searchLibraryItemArgs,
|
||||
},
|
||||
}
|
||||
})
|
||||
@ -657,7 +665,10 @@ export const typeaheadSearchResolver = authorized<
|
||||
})
|
||||
|
||||
export const updatesSinceResolver = authorized<
|
||||
Merge<UpdatesSinceSuccess, { edges: Array<PartialSearchItemEdge> }>,
|
||||
Merge<
|
||||
UpdatesSinceSuccess,
|
||||
{ edges: Array<PartialSearchItemEdge>; pageInfo: PartialPageInfo }
|
||||
>,
|
||||
UpdatesSinceError,
|
||||
QueryUpdatesSinceArgs
|
||||
>(async (_obj, { since, first, after, sort: sortParams, folder }, { uid }) => {
|
||||
@ -675,16 +686,15 @@ export const updatesSinceResolver = authorized<
|
||||
folder ? ' in:' + folder : ''
|
||||
} sort:${sort.by}-${sort.order}`
|
||||
|
||||
const { libraryItems, count } = await searchAndCountLibraryItems(
|
||||
{
|
||||
from: Number(startCursor),
|
||||
size: size + 1, // fetch one more item to get next cursor
|
||||
includeDeleted: true,
|
||||
query,
|
||||
includeContent: true, // by default include content for offline use for now
|
||||
},
|
||||
uid
|
||||
)
|
||||
const searchLibraryItemArgs = {
|
||||
from: Number(startCursor),
|
||||
size: size + 1, // fetch one more item to get next cursor
|
||||
includeDeleted: true,
|
||||
query,
|
||||
includeContent: true, // by default include content for offline use for now
|
||||
}
|
||||
|
||||
const libraryItems = await searchLibraryItems(searchLibraryItemArgs, uid)
|
||||
|
||||
const start =
|
||||
startCursor && !isNaN(Number(startCursor)) ? Number(startCursor) : 0
|
||||
@ -714,7 +724,7 @@ export const updatesSinceResolver = authorized<
|
||||
startCursor,
|
||||
hasNextPage,
|
||||
endCursor,
|
||||
totalCount: count,
|
||||
searchLibraryItemArgs,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -6,8 +6,10 @@ import {
|
||||
} from '../../generated/graphql'
|
||||
import {
|
||||
getFeatureName,
|
||||
getFeaturesCache,
|
||||
isOptInFeatureErrorCode,
|
||||
optInFeature,
|
||||
setFeaturesCache,
|
||||
signFeatureToken,
|
||||
} from '../../services/features'
|
||||
import { authorized } from '../../utils/gql-utils'
|
||||
@ -34,7 +36,8 @@ export const optInFeatureResolver = authorized<
|
||||
}
|
||||
}
|
||||
|
||||
const optedInFeature = await optInFeature(featureName, claims.uid)
|
||||
const userId = claims.uid
|
||||
const optedInFeature = await optInFeature(featureName, userId)
|
||||
if (isOptInFeatureErrorCode(optedInFeature)) {
|
||||
return {
|
||||
errorCodes: [optedInFeature],
|
||||
@ -42,7 +45,11 @@ export const optInFeatureResolver = authorized<
|
||||
}
|
||||
log.info('Opted in to a feature', optedInFeature)
|
||||
|
||||
const token = signFeatureToken(optedInFeature, claims.uid)
|
||||
const cachedFeatures = (await getFeaturesCache(userId)) || []
|
||||
const updatedFeatures = [...cachedFeatures, optedInFeature]
|
||||
await setFeaturesCache(userId, updatedFeatures)
|
||||
|
||||
const token = signFeatureToken(optedInFeature, userId)
|
||||
|
||||
return {
|
||||
feature: {
|
||||
|
||||
@ -29,7 +29,16 @@ import {
|
||||
User,
|
||||
} from '../generated/graphql'
|
||||
import { getAISummary } from '../services/ai-summaries'
|
||||
import { findUserFeatures } from '../services/features'
|
||||
import {
|
||||
findUserFeatures,
|
||||
getFeaturesCache,
|
||||
setFeaturesCache,
|
||||
} from '../services/features'
|
||||
import {
|
||||
countLibraryItems,
|
||||
getCachedTotalCount,
|
||||
setCachedTotalCount,
|
||||
} from '../services/library_item'
|
||||
import { Merge } from '../util'
|
||||
import { isBase64Image, validatedDate, wordsCount } from '../utils/helpers'
|
||||
import { createImageProxyUrl } from '../utils/imageproxy'
|
||||
@ -43,6 +52,7 @@ import {
|
||||
emptyTrashResolver,
|
||||
fetchContentResolver,
|
||||
PartialLibraryItem,
|
||||
PartialPageInfo,
|
||||
} from './article'
|
||||
import {
|
||||
addDiscoverFeedResolver,
|
||||
@ -398,7 +408,16 @@ export const functionResolvers = {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return findUserFeatures(ctx.claims.uid)
|
||||
const userId = ctx.claims.uid
|
||||
const cachedFeatures = await getFeaturesCache(userId)
|
||||
if (cachedFeatures) {
|
||||
return cachedFeatures
|
||||
}
|
||||
|
||||
const features = await findUserFeatures(userId)
|
||||
await setFeaturesCache(userId, features)
|
||||
|
||||
return features
|
||||
},
|
||||
picture: (user: UserEntity) => user.profile.pictureUrl,
|
||||
// not implemented yet
|
||||
@ -588,6 +607,29 @@ export const functionResolvers = {
|
||||
highlightsCount: (item: LibraryItem) => item.highlightAnnotations?.length,
|
||||
...readingProgressHandlers,
|
||||
},
|
||||
PageInfo: {
|
||||
async totalCount(
|
||||
pageInfo: PartialPageInfo,
|
||||
_: unknown,
|
||||
ctx: ResolverContext
|
||||
) {
|
||||
if (pageInfo.totalCount) return pageInfo.totalCount
|
||||
|
||||
if (pageInfo.searchLibraryItemArgs && ctx.claims) {
|
||||
const args = pageInfo.searchLibraryItemArgs
|
||||
const userId = ctx.claims.uid
|
||||
const cachedCount = await getCachedTotalCount(userId, args)
|
||||
if (cachedCount) return cachedCount
|
||||
|
||||
const count = await countLibraryItems(args, userId)
|
||||
await setCachedTotalCount(userId, args, count)
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
return 0
|
||||
},
|
||||
},
|
||||
Subscription: {
|
||||
newsletterEmail(subscription: Subscription) {
|
||||
return subscription.newsletterEmail?.address
|
||||
|
||||
@ -41,7 +41,7 @@ import {
|
||||
import { userRepository } from '../../repository/user'
|
||||
import { createUser } from '../../services/create_user'
|
||||
import { sendAccountChangeEmail } from '../../services/send_emails'
|
||||
import { softDeleteUser } from '../../services/user'
|
||||
import { cacheUser, getCachedUser, softDeleteUser } from '../../services/user'
|
||||
import { Merge } from '../../util'
|
||||
import { authorized } from '../../utils/gql-utils'
|
||||
import { validateUsername } from '../../utils/usernamePolicy'
|
||||
@ -254,11 +254,19 @@ export const getMeUserResolver: ResolverFn<
|
||||
return undefined
|
||||
}
|
||||
|
||||
const userId = claims.uid
|
||||
const cachedUser = await getCachedUser(userId)
|
||||
if (cachedUser) {
|
||||
return cachedUser
|
||||
}
|
||||
|
||||
const user = await userRepository.findById(claims.uid)
|
||||
if (!user) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
await cacheUser(user)
|
||||
|
||||
return user
|
||||
} catch (error) {
|
||||
return undefined
|
||||
@ -355,6 +363,11 @@ export const updateEmailResolver = authorized<
|
||||
})
|
||||
)
|
||||
|
||||
await cacheUser({
|
||||
...user,
|
||||
email,
|
||||
})
|
||||
|
||||
return { email }
|
||||
}
|
||||
|
||||
|
||||
@ -2,15 +2,16 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import cors from 'cors'
|
||||
import express from 'express'
|
||||
import { env } from '../env'
|
||||
import { getClaimsByToken, getTokenByRequest } from '../utils/auth'
|
||||
import { corsConfig } from '../utils/corsConfig'
|
||||
import { logger } from '../utils/logger'
|
||||
import {
|
||||
cacheShortcuts,
|
||||
getShortcuts,
|
||||
getShortcutsCache,
|
||||
resetShortcuts,
|
||||
setShortcuts,
|
||||
} from '../services/user_personalization'
|
||||
import { getClaimsByToken, getTokenByRequest } from '../utils/auth'
|
||||
import { corsConfig } from '../utils/corsConfig'
|
||||
import { logger } from '../utils/logger'
|
||||
|
||||
export function shortcutsRouter() {
|
||||
const router = express.Router()
|
||||
@ -32,9 +33,19 @@ export function shortcutsRouter() {
|
||||
}
|
||||
|
||||
try {
|
||||
const shortcuts = await getShortcuts(claims.uid)
|
||||
const userId = claims.uid
|
||||
const cachedShortcuts = await getShortcutsCache(userId)
|
||||
if (cachedShortcuts) {
|
||||
return res.send({
|
||||
shortcuts: cachedShortcuts,
|
||||
})
|
||||
}
|
||||
|
||||
const shortcuts = await getShortcuts(userId)
|
||||
await cacheShortcuts(userId, shortcuts)
|
||||
|
||||
return res.send({
|
||||
shortcuts: shortcuts ?? [],
|
||||
shortcuts,
|
||||
})
|
||||
} catch (e) {
|
||||
logger.info('error getting shortcuts', e)
|
||||
@ -61,9 +72,12 @@ export function shortcutsRouter() {
|
||||
}
|
||||
|
||||
try {
|
||||
const shortcuts = await setShortcuts(claims.uid, req.body.shortcuts)
|
||||
const userId = claims.uid
|
||||
const shortcuts = await setShortcuts(userId, req.body.shortcuts)
|
||||
await cacheShortcuts(userId, shortcuts)
|
||||
|
||||
return res.send({
|
||||
shortcuts: shortcuts ?? [],
|
||||
shortcuts,
|
||||
})
|
||||
} catch (e) {
|
||||
logger.info('error settings shortcuts', e)
|
||||
@ -89,11 +103,14 @@ export function shortcutsRouter() {
|
||||
}
|
||||
|
||||
try {
|
||||
const success = await resetShortcuts(claims.uid)
|
||||
const userId = claims.uid
|
||||
const success = await resetShortcuts(userId)
|
||||
if (success) {
|
||||
const shortcuts = await getShortcuts(claims.uid)
|
||||
const shortcuts = await getShortcuts(userId)
|
||||
await cacheShortcuts(userId, shortcuts)
|
||||
|
||||
return res.send({
|
||||
shortcuts: shortcuts ?? [],
|
||||
shortcuts,
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@ -6,6 +6,7 @@ import { LibraryItem } from '../entity/library_item'
|
||||
import { Subscription, SubscriptionStatus } from '../entity/subscription'
|
||||
import { env } from '../env'
|
||||
import { OptInFeatureErrorCode } from '../generated/graphql'
|
||||
import { redisDataSource } from '../redis_data_source'
|
||||
import { authTrx, getRepository } from '../repository'
|
||||
import { logger } from '../utils/logger'
|
||||
|
||||
@ -201,3 +202,26 @@ export const userDigestEligible = async (uid: string): Promise<boolean> => {
|
||||
|
||||
return subscriptionsCount >= 2 && libraryItemsCount >= 10
|
||||
}
|
||||
|
||||
const featuresCacheKey = (userId: string) => `cache:features:${userId}`
|
||||
|
||||
export const getFeaturesCache = async (userId: string) => {
|
||||
const cachedFeatures = await redisDataSource.redisClient?.get(
|
||||
featuresCacheKey(userId)
|
||||
)
|
||||
if (!cachedFeatures) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return JSON.parse(cachedFeatures) as Feature[]
|
||||
}
|
||||
|
||||
export const setFeaturesCache = async (userId: string, features: Feature[]) => {
|
||||
const value = JSON.stringify(features)
|
||||
return redisDataSource.redisClient?.set(
|
||||
featuresCacheKey(userId),
|
||||
value,
|
||||
'EX',
|
||||
600
|
||||
)
|
||||
}
|
||||
|
||||
@ -30,7 +30,11 @@ import {
|
||||
} from '../repository'
|
||||
import { libraryItemRepository } from '../repository/library_item'
|
||||
import { Merge, PickTuple } from '../util'
|
||||
import { deepDelete, setRecentlySavedItemInRedis } from '../utils/helpers'
|
||||
import {
|
||||
deepDelete,
|
||||
setRecentlySavedItemInRedis,
|
||||
stringToHash,
|
||||
} from '../utils/helpers'
|
||||
import { logger } from '../utils/logger'
|
||||
import { parseSearchQuery } from '../utils/search'
|
||||
import { HighlightEvent } from './highlights'
|
||||
@ -704,22 +708,13 @@ export const createSearchQueryBuilder = (
|
||||
}
|
||||
|
||||
export const countLibraryItems = async (args: SearchArgs, userId: string) => {
|
||||
const cacheKey = `countLibraryItems:${userId}:${JSON.stringify(args)}`
|
||||
const cachedCount = await redisDataSource.redisClient?.get(cacheKey)
|
||||
if (cachedCount) {
|
||||
return parseInt(cachedCount, 10)
|
||||
}
|
||||
|
||||
const count = await authTrx(
|
||||
return authTrx(
|
||||
async (tx) => createSearchQueryBuilder(args, userId, tx).getCount(),
|
||||
{
|
||||
uid: userId,
|
||||
replicationMode: 'replica',
|
||||
}
|
||||
)
|
||||
|
||||
await redisDataSource.redisClient?.set(cacheKey, count, 'EX', 60)
|
||||
return count
|
||||
}
|
||||
|
||||
export const searchLibraryItems = async (
|
||||
@ -1720,3 +1715,28 @@ export const filterItemEvents = (
|
||||
|
||||
throw new Error('Unexpected state.')
|
||||
}
|
||||
|
||||
const totalCountCacheKey = (userId: string, args: SearchArgs) => {
|
||||
return `cache:library_items_count:${userId}:${stringToHash(
|
||||
JSON.stringify(args)
|
||||
)}`
|
||||
}
|
||||
|
||||
export const getCachedTotalCount = async (userId: string, args: SearchArgs) => {
|
||||
const cacheKey = totalCountCacheKey(userId, args)
|
||||
const cachedCount = await redisDataSource.redisClient?.get(cacheKey)
|
||||
if (!cachedCount) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return parseInt(cachedCount, 10)
|
||||
}
|
||||
|
||||
export const setCachedTotalCount = async (
|
||||
userId: string,
|
||||
args: SearchArgs,
|
||||
count: number
|
||||
) => {
|
||||
const cacheKey = totalCountCacheKey(userId, args)
|
||||
await redisDataSource.redisClient?.set(cacheKey, count, 'EX', 600)
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import { Notification } from 'firebase-admin/messaging'
|
||||
import { DeepPartial, FindOptionsWhere, In } from 'typeorm'
|
||||
import { Profile } from '../entity/profile'
|
||||
import { StatusType, User } from '../entity/user'
|
||||
import { redisDataSource } from '../redis_data_source'
|
||||
import { authTrx, getRepository, queryBuilderToRawSql } from '../repository'
|
||||
import { userRepository } from '../repository/user'
|
||||
import { SetClaimsRole } from '../utils/dictionary'
|
||||
@ -156,3 +157,23 @@ export const findUserAndPersonalization = async (id: string) => {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const userCacheKey = (id: string) => `cache:user:${id}`
|
||||
|
||||
export const getCachedUser = async (id: string) => {
|
||||
const user = await redisDataSource.redisClient?.get(userCacheKey(id))
|
||||
if (!user) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return JSON.parse(user) as User
|
||||
}
|
||||
|
||||
export const cacheUser = async (user: User) => {
|
||||
await redisDataSource.redisClient?.set(
|
||||
userCacheKey(user.id),
|
||||
JSON.stringify(user),
|
||||
'EX',
|
||||
600
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { DeepPartial, IsNull } from 'typeorm'
|
||||
import { Shortcut, UserPersonalization } from '../entity/user_personalization'
|
||||
import { authTrx } from '../repository'
|
||||
import { findLabelsByUserId } from './labels'
|
||||
import { findSubscriptionById } from './subscriptions'
|
||||
import { DeepPartial } from 'typeorm'
|
||||
import { Filter } from '../entity/filter'
|
||||
import { Subscription, SubscriptionStatus } from '../entity/subscription'
|
||||
import { Shortcut, UserPersonalization } from '../entity/user_personalization'
|
||||
import { redisDataSource } from '../redis_data_source'
|
||||
import { authTrx } from '../repository'
|
||||
import { findLabelsByUserId } from './labels'
|
||||
|
||||
export const findUserPersonalization = async (userId: string) => {
|
||||
return authTrx(
|
||||
@ -173,3 +173,24 @@ const userDefaultShortcuts = async (userId: string): Promise<Shortcut[]> => {
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const shortcutsCacheKey = (userId: string) => `cache:shortcuts:${userId}`
|
||||
|
||||
export const getShortcutsCache = async (userId: string) => {
|
||||
const cachedShortcuts = await redisDataSource.redisClient?.get(
|
||||
shortcutsCacheKey(userId)
|
||||
)
|
||||
if (!cachedShortcuts) {
|
||||
return undefined
|
||||
}
|
||||
return JSON.parse(cachedShortcuts) as Shortcut[]
|
||||
}
|
||||
|
||||
export const cacheShortcuts = async (userId: string, shortcuts: Shortcut[]) => {
|
||||
await redisDataSource.redisClient?.set(
|
||||
shortcutsCacheKey(userId),
|
||||
JSON.stringify(shortcuts),
|
||||
'EX',
|
||||
600
|
||||
)
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ export const recommendationFragment = gql`
|
||||
}
|
||||
`
|
||||
|
||||
export const GQL_SEARCH_QUERY = gql`
|
||||
export const gqlSearchQuery = (includeTotalCount = false) => gql`
|
||||
query Search(
|
||||
$after: String
|
||||
$first: Int
|
||||
@ -77,7 +77,7 @@ export const GQL_SEARCH_QUERY = gql`
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
totalCount
|
||||
totalCount @include(if: ${includeTotalCount})
|
||||
}
|
||||
}
|
||||
... on SearchError {
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import {
|
||||
QueryClient,
|
||||
useInfiniteQuery,
|
||||
@ -6,25 +5,24 @@ import {
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import { gqlEndpoint } from '../../appConfig'
|
||||
import { ContentReader, PageType, State } from '../fragments/articleFragment'
|
||||
import { Highlight } from '../fragments/highlightFragment'
|
||||
import { requestHeaders } from '../networkHelpers'
|
||||
import { Label } from '../fragments/labelFragment'
|
||||
import { requestHeaders } from '../networkHelpers'
|
||||
import {
|
||||
gqlSearchQuery,
|
||||
GQL_BULK_ACTION,
|
||||
GQL_DELETE_LIBRARY_ITEM,
|
||||
GQL_GET_LIBRARY_ITEM,
|
||||
GQL_GET_LIBRARY_ITEM_CONTENT,
|
||||
GQL_MOVE_ITEM_TO_FOLDER,
|
||||
GQL_SAVE_ARTICLE_READING_PROGRESS,
|
||||
GQL_SAVE_URL,
|
||||
GQL_SEARCH_QUERY,
|
||||
GQL_SET_LABELS,
|
||||
GQL_SET_LINK_ARCHIVED,
|
||||
GQL_UPDATE_LIBRARY_ITEM,
|
||||
} from './gql'
|
||||
import { gqlEndpoint } from '../../appConfig'
|
||||
import { useState } from 'react'
|
||||
|
||||
function gqlFetcher(
|
||||
query: string,
|
||||
@ -213,7 +211,7 @@ export const insertItemInCache = (
|
||||
|
||||
export function useGetLibraryItems(
|
||||
folder: string | undefined,
|
||||
{ limit, searchQuery }: LibraryItemsQueryInput,
|
||||
{ limit, searchQuery, includeCount }: LibraryItemsQueryInput,
|
||||
enabled = true
|
||||
) {
|
||||
const fullQuery = folder
|
||||
@ -223,7 +221,7 @@ export function useGetLibraryItems(
|
||||
return useInfiniteQuery({
|
||||
queryKey: ['libraryItems', fullQuery],
|
||||
queryFn: async ({ pageParam }) => {
|
||||
const response = (await gqlFetcher(GQL_SEARCH_QUERY, {
|
||||
const response = (await gqlFetcher(gqlSearchQuery(includeCount), {
|
||||
after: pageParam,
|
||||
first: limit,
|
||||
query: fullQuery,
|
||||
@ -532,7 +530,7 @@ export function useRefreshProcessingItems() {
|
||||
itemIds: string[]
|
||||
}) => {
|
||||
const fullQuery = `in:all includes:${variables.itemIds.join(',')}`
|
||||
const result = (await gqlFetcher(GQL_SEARCH_QUERY, {
|
||||
const result = (await gqlFetcher(gqlSearchQuery(), {
|
||||
first: 10,
|
||||
query: fullQuery,
|
||||
includeContent: false,
|
||||
@ -951,6 +949,7 @@ export type LibraryItemsQueryInput = {
|
||||
searchQuery?: string
|
||||
cursor?: string
|
||||
includeContent?: boolean
|
||||
includeCount?: boolean
|
||||
}
|
||||
|
||||
type LibraryItemsData = {
|
||||
|
||||
@ -1,11 +1,4 @@
|
||||
import {
|
||||
ChangeEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { Toaster } from 'react-hot-toast'
|
||||
import { Button } from '../../components/elements/Button'
|
||||
import {
|
||||
@ -19,22 +12,22 @@ import { ConfirmationModal } from '../../components/patterns/ConfirmationModal'
|
||||
import { SettingsLayout } from '../../components/templates/SettingsLayout'
|
||||
import { styled, theme } from '../../components/tokens/stitches.config'
|
||||
import { userHasFeature } from '../../lib/featureFlag'
|
||||
import { useGetLibraryItems } from '../../lib/networking/library_items/useLibraryItems'
|
||||
import { emptyTrashMutation } from '../../lib/networking/mutations/emptyTrashMutation'
|
||||
import { optInFeature } from '../../lib/networking/mutations/optIntoFeatureMutation'
|
||||
import { scheduleDigest } from '../../lib/networking/mutations/scheduleDigest'
|
||||
import { updateDigestConfigMutation } from '../../lib/networking/mutations/updateDigestConfigMutation'
|
||||
import { updateEmailMutation } from '../../lib/networking/mutations/updateEmailMutation'
|
||||
import { updateUserMutation } from '../../lib/networking/mutations/updateUserMutation'
|
||||
import { updateUserProfileMutation } from '../../lib/networking/mutations/updateUserProfileMutation'
|
||||
import { useGetLibraryItems } from '../../lib/networking/library_items/useLibraryItems'
|
||||
import { useGetViewerQuery } from '../../lib/networking/queries/useGetViewerQuery'
|
||||
import { useValidateUsernameQuery } from '../../lib/networking/queries/useValidateUsernameQuery'
|
||||
import { applyStoredTheme } from '../../lib/themeUpdater'
|
||||
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
|
||||
import {
|
||||
DigestChannel,
|
||||
useGetUserPersonalization,
|
||||
} from '../../lib/networking/queries/useGetUserPersonalization'
|
||||
import { updateDigestConfigMutation } from '../../lib/networking/mutations/updateDigestConfigMutation'
|
||||
import { scheduleDigest } from '../../lib/networking/mutations/scheduleDigest'
|
||||
import { optInFeature } from '../../lib/networking/mutations/optIntoFeatureMutation'
|
||||
import { useGetViewerQuery } from '../../lib/networking/queries/useGetViewerQuery'
|
||||
import { useValidateUsernameQuery } from '../../lib/networking/queries/useValidateUsernameQuery'
|
||||
import { applyStoredTheme } from '../../lib/themeUpdater'
|
||||
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
|
||||
|
||||
const ACCOUNT_LIMIT = 50_000
|
||||
|
||||
@ -103,6 +96,7 @@ export default function Account(): JSX.Element {
|
||||
limit: 0,
|
||||
searchQuery: '',
|
||||
sortDescending: false,
|
||||
includeCount: true,
|
||||
})
|
||||
|
||||
const libraryCount = useMemo(() => {
|
||||
|
||||
@ -1,25 +1,23 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { applyStoredTheme } from '../../lib/themeUpdater'
|
||||
|
||||
import { VStack } from '../../components/elements/LayoutPrimitives'
|
||||
|
||||
import { StyledText } from '../../components/elements/StyledText'
|
||||
import { ProfileLayout } from '../../components/templates/ProfileLayout'
|
||||
import { BulkAction } from '../../lib/networking/library_items/useLibraryItems'
|
||||
import { Button } from '../../components/elements/Button'
|
||||
import { theme } from '../../components/tokens/stitches.config'
|
||||
import { ConfirmationModal } from '../../components/patterns/ConfirmationModal'
|
||||
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
|
||||
import { useRouter } from 'next/router'
|
||||
import {
|
||||
useBulkActions,
|
||||
useGetLibraryItems,
|
||||
} from '../../lib/networking/library_items/useLibraryItems'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Button } from '../../components/elements/Button'
|
||||
import {
|
||||
BorderedFormInput,
|
||||
FormLabel,
|
||||
} from '../../components/elements/FormElements'
|
||||
import { VStack } from '../../components/elements/LayoutPrimitives'
|
||||
import { StyledText } from '../../components/elements/StyledText'
|
||||
import { ConfirmationModal } from '../../components/patterns/ConfirmationModal'
|
||||
import { ProfileLayout } from '../../components/templates/ProfileLayout'
|
||||
import { theme } from '../../components/tokens/stitches.config'
|
||||
import { DEFAULT_HOME_PATH } from '../../lib/navigations'
|
||||
import {
|
||||
BulkAction,
|
||||
useBulkActions,
|
||||
useGetLibraryItems,
|
||||
} from '../../lib/networking/library_items/useLibraryItems'
|
||||
import { applyStoredTheme } from '../../lib/themeUpdater'
|
||||
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
|
||||
|
||||
type RunningState = 'none' | 'confirming' | 'running' | 'completed'
|
||||
|
||||
@ -39,6 +37,7 @@ export default function BulkPerformer(): JSX.Element {
|
||||
searchQuery: query,
|
||||
limit: 1,
|
||||
sortDescending: false,
|
||||
includeCount: true,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user