diff --git a/packages/api/src/jobs/update_home.ts b/packages/api/src/jobs/update_home.ts index b565ed7a2..d97b0b260 100644 --- a/packages/api/src/jobs/update_home.ts +++ b/packages/api/src/jobs/update_home.ts @@ -258,12 +258,13 @@ const rankCandidates = async ( } const redisKey = (userId: string) => `home:${userId}` +const emptyHomeKey = (key: string) => `${key}:empty` export const getHomeSections = async ( userId: string, limit = 100, maxScore?: number -): Promise> => { +): Promise | null> => { const redisClient = redisDataSource.redisClient if (!redisClient) { throw new Error('Redis client not available') @@ -285,6 +286,19 @@ export const getHomeSections = async ( limit ) + if (!results.length) { + logger.info('No sections found in redis') + // check if the feed is empty + const isEmpty = await redisClient.exists(emptyHomeKey(key)) + if (isEmpty) { + logger.info('Empty feed') + return [] + } + + logger.info('Feed not found') + return null + } + const sections = [] for (let i = 0; i < results.length; i += 2) { const member = JSON.parse(results[i]) as Section @@ -317,6 +331,14 @@ const appendSectionsToHome = async ( } const key = redisKey(userId) + const emptyKey = emptyHomeKey(key) + + if (!sections.length) { + logger.info('No available sections to add') + // set expiration to 1 hour + await redisClient.set(emptyKey, 'true', 'EX', 60 * 60) + return + } // store candidates in redis sorted set const pipeline = redisClient.pipeline() @@ -337,6 +359,8 @@ const appendSectionsToHome = async ( // add section to the sorted set pipeline.zadd(key, ...scoreMembers) + pipeline.del(emptyKey) + // keep only the new sections and remove the oldest ones pipeline.zremrangebyrank(key, 0, -(sections.length + 1)) @@ -375,6 +399,10 @@ const mixHomeItems = ( items: Array, batches: Array> ) => { + if (batches.length === 0) { + return + } + const batchSize = Math.ceil(items.length / batches.length) for (const item of items) { @@ -424,32 +452,36 @@ const mixHomeItems = ( >, } - distributeItems(shortItems, batches.short) - distributeItems(longItems, batches.long) + batches.short.length && distributeItems(shortItems, batches.short) + batches.long.length && distributeItems(longItems, batches.long) // convert batches to sections const sections = [] const hiddenCandidates = rankedHomeItems.slice(50) - sections.push({ - items: hiddenCandidates.map(candidateToItem), - layout: 'hidden', - }) + hiddenCandidates.length && + sections.push({ + items: hiddenCandidates.map(candidateToItem), + layout: 'hidden', + }) - sections.push({ - items: batches.short.flat().map(candidateToItem), - layout: 'quick_links', - }) + batches.short.length && + sections.push({ + items: batches.short.flat().map(candidateToItem), + layout: 'quick_links', + }) - sections.push({ - items: batches.long.flat().map(candidateToItem), - layout: 'top_picks', - }) + batches.long.length && + sections.push({ + items: batches.long.flat().map(candidateToItem), + layout: 'top_picks', + }) - sections.push({ - items: justAddedCandidates.map(candidateToItem), - layout: 'just_added', - }) + justAddedCandidates && + sections.push({ + items: justAddedCandidates.map(candidateToItem), + layout: 'just_added', + }) return sections } @@ -495,7 +527,6 @@ export const updateHome = async (data: UpdateHomeJobData) => { if (!justAddedCandidates.length && !candidates.length) { logger.info('No candidates found') - return } // TODO: integrity check on candidates diff --git a/packages/api/src/resolvers/home/index.ts b/packages/api/src/resolvers/home/index.ts index 9e22c2b9b..3644c58b6 100644 --- a/packages/api/src/resolvers/home/index.ts +++ b/packages/api/src/resolvers/home/index.ts @@ -41,7 +41,8 @@ export const homeResolver = authorized< const sections = await getHomeSections(uid, limit, cursor) log.info('Home sections fetched') - if (sections.length === 0) { + if (!sections) { + // home feed creation pending const existingJob = await getJob(updateHomeJobId(uid)) if (existingJob) { log.info('Update job job already enqueued') @@ -63,6 +64,17 @@ export const homeResolver = authorized< } } + if (sections.length === 0) { + // no available candidates + return { + edges: [], + pageInfo: { + hasPreviousPage: false, + hasNextPage: false, + }, + } + } + const endCursor = sections[sections.length - 1].score.toString() const edges = sections.map((section) => { @@ -127,7 +139,7 @@ export const hiddenHomeSectionResolver = authorized< const sections = await getHomeSections(uid) log.info('Home sections fetched') - if (sections.length === 0) { + if (!sections) { return { errorCodes: [HomeErrorCode.Pending], } diff --git a/packages/api/src/services/score.ts b/packages/api/src/services/score.ts index 8752e4b4a..25c78f5a0 100644 --- a/packages/api/src/services/score.ts +++ b/packages/api/src/services/score.ts @@ -79,4 +79,4 @@ class ScoreClientImpl implements ScoreClient { } } -export const scoreClient = new ScoreClientImpl() +export const scoreClient = new StubScoreClientImpl()