rename just read feed to home

This commit is contained in:
Hongbo Wu 2024-05-27 21:11:08 +08:00
parent 5b9e34739d
commit efb7160373
12 changed files with 291 additions and 310 deletions

View file

@ -32,7 +32,7 @@ import { ClaimsToSet, RequestContext, ResolverContext } from './resolvers/types'
import ScalarResolvers from './scalars'
import typeDefs from './schema'
import { batchGetHighlightsFromLibraryItemIds } from './services/highlights'
import { batchGetJustReadFeedItems } from './services/just_read_feed'
import { batchGetHomeItems } from './services/home'
import { batchGetLabelsFromLibraryItemIds } from './services/labels'
import { batchGetRecommendationsFromLibraryItemIds } from './services/recommendation'
import {
@ -113,7 +113,7 @@ const contextFunc: ContextFunction<ExpressContext, ResolverContext> = async ({
batchGetRecommendationsFromLibraryItemIds
),
uploadFiles: new DataLoader(batchGetUploadFilesByIds),
justReadFeedItems: new DataLoader(batchGetJustReadFeedItems),
homeItems: new DataLoader(batchGetHomeItems),
},
}

View file

@ -1270,6 +1270,70 @@ export enum HighlightType {
Redaction = 'REDACTION'
}
export type HomeEdge = {
__typename?: 'HomeEdge';
cursor: Scalars['String'];
node: HomeSection;
};
export type HomeError = {
__typename?: 'HomeError';
errorCodes: Array<HomeErrorCode>;
};
export enum HomeErrorCode {
BadRequest = 'BAD_REQUEST',
Pending = 'PENDING',
Unauthorized = 'UNAUTHORIZED'
}
export type HomeItem = {
__typename?: 'HomeItem';
author?: Maybe<Scalars['String']>;
broadcastCount?: Maybe<Scalars['Int']>;
canArchive?: Maybe<Scalars['Boolean']>;
canComment?: Maybe<Scalars['Boolean']>;
canDelete?: Maybe<Scalars['Boolean']>;
canSave?: Maybe<Scalars['Boolean']>;
canShare?: Maybe<Scalars['Boolean']>;
date: Scalars['Date'];
dir?: Maybe<Scalars['String']>;
id: Scalars['ID'];
likeCount?: Maybe<Scalars['Int']>;
previewContent?: Maybe<Scalars['String']>;
saveCount?: Maybe<Scalars['Int']>;
seen_at?: Maybe<Scalars['Date']>;
subscription: HomeSubscription;
thumbnail?: Maybe<Scalars['String']>;
title: Scalars['String'];
url: Scalars['String'];
wordCount?: Maybe<Scalars['Int']>;
};
export type HomeResult = HomeError | HomeSuccess;
export type HomeSection = {
__typename?: 'HomeSection';
items: Array<HomeItem>;
layout?: Maybe<Scalars['String']>;
thumbnail?: Maybe<Scalars['String']>;
title?: Maybe<Scalars['String']>;
};
export type HomeSubscription = {
__typename?: 'HomeSubscription';
icon?: Maybe<Scalars['String']>;
id: Scalars['ID'];
name: Scalars['String'];
url?: Maybe<Scalars['String']>;
};
export type HomeSuccess = {
__typename?: 'HomeSuccess';
edges: Array<HomeEdge>;
pageInfo: PageInfo;
};
export type ImportFromIntegrationError = {
__typename?: 'ImportFromIntegrationError';
errorCodes: Array<ImportFromIntegrationErrorCode>;
@ -1363,70 +1427,6 @@ export type JoinGroupSuccess = {
group: RecommendationGroup;
};
export type JustReadFeedEdge = {
__typename?: 'JustReadFeedEdge';
cursor: Scalars['String'];
node: JustReadFeedSection;
};
export type JustReadFeedError = {
__typename?: 'JustReadFeedError';
errorCodes: Array<JustReadFeedErrorCode>;
};
export enum JustReadFeedErrorCode {
BadRequest = 'BAD_REQUEST',
Pending = 'PENDING',
Unauthorized = 'UNAUTHORIZED'
}
export type JustReadFeedItem = {
__typename?: 'JustReadFeedItem';
author?: Maybe<Scalars['String']>;
broadcastCount?: Maybe<Scalars['Int']>;
canArchive?: Maybe<Scalars['Boolean']>;
canComment?: Maybe<Scalars['Boolean']>;
canDelete?: Maybe<Scalars['Boolean']>;
canSave?: Maybe<Scalars['Boolean']>;
canShare?: Maybe<Scalars['Boolean']>;
date: Scalars['Date'];
dir?: Maybe<Scalars['String']>;
id: Scalars['ID'];
likeCount?: Maybe<Scalars['Int']>;
previewContent?: Maybe<Scalars['String']>;
saveCount?: Maybe<Scalars['Int']>;
seen_at?: Maybe<Scalars['Date']>;
subscription: JustReadFeedSubscription;
thumbnail?: Maybe<Scalars['String']>;
title: Scalars['String'];
url: Scalars['String'];
wordCount?: Maybe<Scalars['Int']>;
};
export type JustReadFeedResult = JustReadFeedError | JustReadFeedSuccess;
export type JustReadFeedSection = {
__typename?: 'JustReadFeedSection';
items: Array<JustReadFeedItem>;
layout?: Maybe<Scalars['String']>;
thumbnail?: Maybe<Scalars['String']>;
title?: Maybe<Scalars['String']>;
};
export type JustReadFeedSubscription = {
__typename?: 'JustReadFeedSubscription';
icon?: Maybe<Scalars['String']>;
id: Scalars['ID'];
name: Scalars['String'];
url?: Maybe<Scalars['String']>;
};
export type JustReadFeedSuccess = {
__typename?: 'JustReadFeedSuccess';
edges: Array<JustReadFeedEdge>;
pageInfo: PageInfo;
};
export type Label = {
__typename?: 'Label';
color: Scalars['String'];
@ -2223,9 +2223,9 @@ export type Query = {
getUserPersonalization: GetUserPersonalizationResult;
groups: GroupsResult;
hello?: Maybe<Scalars['String']>;
home: HomeResult;
integration: IntegrationResult;
integrations: IntegrationsResult;
justReadFeed: JustReadFeedResult;
labels: LabelsResult;
me?: Maybe<User>;
newsletterEmails: NewsletterEmailsResult;
@ -2272,14 +2272,14 @@ export type QueryGetDiscoverFeedArticlesArgs = {
};
export type QueryIntegrationArgs = {
name: Scalars['String'];
export type QueryHomeArgs = {
after?: InputMaybe<Scalars['String']>;
first?: InputMaybe<Scalars['Int']>;
};
export type QueryJustReadFeedArgs = {
after?: InputMaybe<Scalars['String']>;
first?: InputMaybe<Scalars['Int']>;
export type QueryIntegrationArgs = {
name: Scalars['String'];
};
@ -4255,6 +4255,14 @@ export type ResolversTypes = {
HighlightReply: ResolverTypeWrapper<HighlightReply>;
HighlightStats: ResolverTypeWrapper<HighlightStats>;
HighlightType: HighlightType;
HomeEdge: ResolverTypeWrapper<HomeEdge>;
HomeError: ResolverTypeWrapper<HomeError>;
HomeErrorCode: HomeErrorCode;
HomeItem: ResolverTypeWrapper<HomeItem>;
HomeResult: ResolversTypes['HomeError'] | ResolversTypes['HomeSuccess'];
HomeSection: ResolverTypeWrapper<HomeSection>;
HomeSubscription: ResolverTypeWrapper<HomeSubscription>;
HomeSuccess: ResolverTypeWrapper<HomeSuccess>;
ID: ResolverTypeWrapper<Scalars['ID']>;
ImportFromIntegrationError: ResolverTypeWrapper<ImportFromIntegrationError>;
ImportFromIntegrationErrorCode: ImportFromIntegrationErrorCode;
@ -4277,14 +4285,6 @@ export type ResolversTypes = {
JoinGroupErrorCode: JoinGroupErrorCode;
JoinGroupResult: ResolversTypes['JoinGroupError'] | ResolversTypes['JoinGroupSuccess'];
JoinGroupSuccess: ResolverTypeWrapper<JoinGroupSuccess>;
JustReadFeedEdge: ResolverTypeWrapper<JustReadFeedEdge>;
JustReadFeedError: ResolverTypeWrapper<JustReadFeedError>;
JustReadFeedErrorCode: JustReadFeedErrorCode;
JustReadFeedItem: ResolverTypeWrapper<JustReadFeedItem>;
JustReadFeedResult: ResolversTypes['JustReadFeedError'] | ResolversTypes['JustReadFeedSuccess'];
JustReadFeedSection: ResolverTypeWrapper<JustReadFeedSection>;
JustReadFeedSubscription: ResolverTypeWrapper<JustReadFeedSubscription>;
JustReadFeedSuccess: ResolverTypeWrapper<JustReadFeedSuccess>;
Label: ResolverTypeWrapper<Label>;
LabelsError: ResolverTypeWrapper<LabelsError>;
LabelsErrorCode: LabelsErrorCode;
@ -4812,6 +4812,13 @@ export type ResolversParentTypes = {
Highlight: Highlight;
HighlightReply: HighlightReply;
HighlightStats: HighlightStats;
HomeEdge: HomeEdge;
HomeError: HomeError;
HomeItem: HomeItem;
HomeResult: ResolversParentTypes['HomeError'] | ResolversParentTypes['HomeSuccess'];
HomeSection: HomeSection;
HomeSubscription: HomeSubscription;
HomeSuccess: HomeSuccess;
ID: Scalars['ID'];
ImportFromIntegrationError: ImportFromIntegrationError;
ImportFromIntegrationResult: ResolversParentTypes['ImportFromIntegrationError'] | ResolversParentTypes['ImportFromIntegrationSuccess'];
@ -4828,13 +4835,6 @@ export type ResolversParentTypes = {
JoinGroupError: JoinGroupError;
JoinGroupResult: ResolversParentTypes['JoinGroupError'] | ResolversParentTypes['JoinGroupSuccess'];
JoinGroupSuccess: JoinGroupSuccess;
JustReadFeedEdge: JustReadFeedEdge;
JustReadFeedError: JustReadFeedError;
JustReadFeedItem: JustReadFeedItem;
JustReadFeedResult: ResolversParentTypes['JustReadFeedError'] | ResolversParentTypes['JustReadFeedSuccess'];
JustReadFeedSection: JustReadFeedSection;
JustReadFeedSubscription: JustReadFeedSubscription;
JustReadFeedSuccess: JustReadFeedSuccess;
Label: Label;
LabelsError: LabelsError;
LabelsResult: ResolversParentTypes['LabelsError'] | ResolversParentTypes['LabelsSuccess'];
@ -6008,6 +6008,66 @@ export type HighlightStatsResolvers<ContextType = ResolverContext, ParentType ex
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type HomeEdgeResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['HomeEdge'] = ResolversParentTypes['HomeEdge']> = {
cursor?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
node?: Resolver<ResolversTypes['HomeSection'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type HomeErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['HomeError'] = ResolversParentTypes['HomeError']> = {
errorCodes?: Resolver<Array<ResolversTypes['HomeErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type HomeItemResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['HomeItem'] = ResolversParentTypes['HomeItem']> = {
author?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
broadcastCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
canArchive?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
canComment?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
canDelete?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
canSave?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
canShare?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
date?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
dir?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
likeCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
previewContent?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
saveCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
seen_at?: Resolver<Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
subscription?: Resolver<ResolversTypes['HomeSubscription'], ParentType, ContextType>;
thumbnail?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
title?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
url?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
wordCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type HomeResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['HomeResult'] = ResolversParentTypes['HomeResult']> = {
__resolveType: TypeResolveFn<'HomeError' | 'HomeSuccess', ParentType, ContextType>;
};
export type HomeSectionResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['HomeSection'] = ResolversParentTypes['HomeSection']> = {
items?: Resolver<Array<ResolversTypes['HomeItem']>, ParentType, ContextType>;
layout?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
thumbnail?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
title?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type HomeSubscriptionResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['HomeSubscription'] = ResolversParentTypes['HomeSubscription']> = {
icon?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
url?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type HomeSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['HomeSuccess'] = ResolversParentTypes['HomeSuccess']> = {
edges?: Resolver<Array<ResolversTypes['HomeEdge']>, ParentType, ContextType>;
pageInfo?: Resolver<ResolversTypes['PageInfo'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type ImportFromIntegrationErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['ImportFromIntegrationError'] = ResolversParentTypes['ImportFromIntegrationError']> = {
errorCodes?: Resolver<Array<ResolversTypes['ImportFromIntegrationErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
@ -6081,66 +6141,6 @@ export type JoinGroupSuccessResolvers<ContextType = ResolverContext, ParentType
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type JustReadFeedEdgeResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['JustReadFeedEdge'] = ResolversParentTypes['JustReadFeedEdge']> = {
cursor?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
node?: Resolver<ResolversTypes['JustReadFeedSection'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type JustReadFeedErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['JustReadFeedError'] = ResolversParentTypes['JustReadFeedError']> = {
errorCodes?: Resolver<Array<ResolversTypes['JustReadFeedErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type JustReadFeedItemResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['JustReadFeedItem'] = ResolversParentTypes['JustReadFeedItem']> = {
author?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
broadcastCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
canArchive?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
canComment?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
canDelete?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
canSave?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
canShare?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
date?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
dir?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
likeCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
previewContent?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
saveCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
seen_at?: Resolver<Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
subscription?: Resolver<ResolversTypes['JustReadFeedSubscription'], ParentType, ContextType>;
thumbnail?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
title?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
url?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
wordCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type JustReadFeedResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['JustReadFeedResult'] = ResolversParentTypes['JustReadFeedResult']> = {
__resolveType: TypeResolveFn<'JustReadFeedError' | 'JustReadFeedSuccess', ParentType, ContextType>;
};
export type JustReadFeedSectionResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['JustReadFeedSection'] = ResolversParentTypes['JustReadFeedSection']> = {
items?: Resolver<Array<ResolversTypes['JustReadFeedItem']>, ParentType, ContextType>;
layout?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
thumbnail?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
title?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type JustReadFeedSubscriptionResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['JustReadFeedSubscription'] = ResolversParentTypes['JustReadFeedSubscription']> = {
icon?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
url?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type JustReadFeedSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['JustReadFeedSuccess'] = ResolversParentTypes['JustReadFeedSuccess']> = {
edges?: Resolver<Array<ResolversTypes['JustReadFeedEdge']>, ParentType, ContextType>;
pageInfo?: Resolver<ResolversTypes['PageInfo'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type LabelResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['Label'] = ResolversParentTypes['Label']> = {
color?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
createdAt?: Resolver<Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
@ -6466,9 +6466,9 @@ export type QueryResolvers<ContextType = ResolverContext, ParentType extends Res
getUserPersonalization?: Resolver<ResolversTypes['GetUserPersonalizationResult'], ParentType, ContextType>;
groups?: Resolver<ResolversTypes['GroupsResult'], ParentType, ContextType>;
hello?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
home?: Resolver<ResolversTypes['HomeResult'], ParentType, ContextType, Partial<QueryHomeArgs>>;
integration?: Resolver<ResolversTypes['IntegrationResult'], ParentType, ContextType, RequireFields<QueryIntegrationArgs, 'name'>>;
integrations?: Resolver<ResolversTypes['IntegrationsResult'], ParentType, ContextType>;
justReadFeed?: Resolver<ResolversTypes['JustReadFeedResult'], ParentType, ContextType, Partial<QueryJustReadFeedArgs>>;
labels?: Resolver<ResolversTypes['LabelsResult'], ParentType, ContextType>;
me?: Resolver<Maybe<ResolversTypes['User']>, ParentType, ContextType>;
newsletterEmails?: Resolver<ResolversTypes['NewsletterEmailsResult'], ParentType, ContextType>;
@ -7650,6 +7650,13 @@ export type Resolvers<ContextType = ResolverContext> = {
Highlight?: HighlightResolvers<ContextType>;
HighlightReply?: HighlightReplyResolvers<ContextType>;
HighlightStats?: HighlightStatsResolvers<ContextType>;
HomeEdge?: HomeEdgeResolvers<ContextType>;
HomeError?: HomeErrorResolvers<ContextType>;
HomeItem?: HomeItemResolvers<ContextType>;
HomeResult?: HomeResultResolvers<ContextType>;
HomeSection?: HomeSectionResolvers<ContextType>;
HomeSubscription?: HomeSubscriptionResolvers<ContextType>;
HomeSuccess?: HomeSuccessResolvers<ContextType>;
ImportFromIntegrationError?: ImportFromIntegrationErrorResolvers<ContextType>;
ImportFromIntegrationResult?: ImportFromIntegrationResultResolvers<ContextType>;
ImportFromIntegrationSuccess?: ImportFromIntegrationSuccessResolvers<ContextType>;
@ -7664,13 +7671,6 @@ export type Resolvers<ContextType = ResolverContext> = {
JoinGroupError?: JoinGroupErrorResolvers<ContextType>;
JoinGroupResult?: JoinGroupResultResolvers<ContextType>;
JoinGroupSuccess?: JoinGroupSuccessResolvers<ContextType>;
JustReadFeedEdge?: JustReadFeedEdgeResolvers<ContextType>;
JustReadFeedError?: JustReadFeedErrorResolvers<ContextType>;
JustReadFeedItem?: JustReadFeedItemResolvers<ContextType>;
JustReadFeedResult?: JustReadFeedResultResolvers<ContextType>;
JustReadFeedSection?: JustReadFeedSectionResolvers<ContextType>;
JustReadFeedSubscription?: JustReadFeedSubscriptionResolvers<ContextType>;
JustReadFeedSuccess?: JustReadFeedSuccessResolvers<ContextType>;
Label?: LabelResolvers<ContextType>;
LabelsError?: LabelsErrorResolvers<ContextType>;
LabelsResult?: LabelsResultResolvers<ContextType>;

View file

@ -1142,6 +1142,64 @@ enum HighlightType {
REDACTION
}
type HomeEdge {
cursor: String!
node: HomeSection!
}
type HomeError {
errorCodes: [HomeErrorCode!]!
}
enum HomeErrorCode {
BAD_REQUEST
PENDING
UNAUTHORIZED
}
type HomeItem {
author: String
broadcastCount: Int
canArchive: Boolean
canComment: Boolean
canDelete: Boolean
canSave: Boolean
canShare: Boolean
date: Date!
dir: String
id: ID!
likeCount: Int
previewContent: String
saveCount: Int
seen_at: Date
subscription: HomeSubscription!
thumbnail: String
title: String!
url: String!
wordCount: Int
}
union HomeResult = HomeError | HomeSuccess
type HomeSection {
items: [HomeItem!]!
layout: String
thumbnail: String
title: String
}
type HomeSubscription {
icon: String
id: ID!
name: String!
url: String
}
type HomeSuccess {
edges: [HomeEdge!]!
pageInfo: PageInfo!
}
type ImportFromIntegrationError {
errorCodes: [ImportFromIntegrationErrorCode!]!
}
@ -1228,64 +1286,6 @@ type JoinGroupSuccess {
group: RecommendationGroup!
}
type JustReadFeedEdge {
cursor: String!
node: JustReadFeedSection!
}
type JustReadFeedError {
errorCodes: [JustReadFeedErrorCode!]!
}
enum JustReadFeedErrorCode {
BAD_REQUEST
PENDING
UNAUTHORIZED
}
type JustReadFeedItem {
author: String
broadcastCount: Int
canArchive: Boolean
canComment: Boolean
canDelete: Boolean
canSave: Boolean
canShare: Boolean
date: Date!
dir: String
id: ID!
likeCount: Int
previewContent: String
saveCount: Int
seen_at: Date
subscription: JustReadFeedSubscription!
thumbnail: String
title: String!
url: String!
wordCount: Int
}
union JustReadFeedResult = JustReadFeedError | JustReadFeedSuccess
type JustReadFeedSection {
items: [JustReadFeedItem!]!
layout: String
thumbnail: String
title: String
}
type JustReadFeedSubscription {
icon: String
id: ID!
name: String!
url: String
}
type JustReadFeedSuccess {
edges: [JustReadFeedEdge!]!
pageInfo: PageInfo!
}
type Label {
color: String!
createdAt: Date
@ -1702,9 +1702,9 @@ type Query {
getUserPersonalization: GetUserPersonalizationResult!
groups: GroupsResult!
hello: String
home(after: String, first: Int): HomeResult!
integration(name: String!): IntegrationResult!
integrations: IntegrationsResult!
justReadFeed(after: String, first: Int): JustReadFeedResult!
labels: LabelsResult!
me: User
newsletterEmails: NewsletterEmailsResult!

View file

@ -3,7 +3,7 @@ import { PublicItem } from '../entity/public_item'
import { Subscription } from '../entity/subscription'
import { User } from '../entity/user'
import { redisDataSource } from '../redis_data_source'
import { findUnseenPublicItems } from '../services/just_read_feed'
import { findUnseenPublicItems } from '../services/home'
import { searchLibraryItems } from '../services/library_item'
import { Feature, getScores, ScoreApiResponse } from '../services/score'
import { findSubscriptionsByNames } from '../services/subscriptions'
@ -11,9 +11,9 @@ import { findActiveUser } from '../services/user'
import { lanaugeToCode } from '../utils/helpers'
import { logger } from '../utils/logger'
export const UPDATE_JUST_READ_FEED_JOB = 'UPDATE_JUST_READ_FEED_JOB'
export const UPDATE_HOME_JOB = 'UPDATE_HOME_JOB'
export interface UpdateJustReadFeedJobData {
export interface UpdateHomeJobData {
userId: string
}
@ -208,7 +208,7 @@ const rankCandidates = async (
const redisKey = (userId: string) => `just-read-feed:${userId}`
const MAX_FEED_ITEMS = 500
export const getJustReadFeedSections = async (
export const getHomeSections = async (
userId: string,
limit: number,
maxScore?: number
@ -244,7 +244,7 @@ export const getJustReadFeedSections = async (
return sections
}
const appendSectionsToFeed = async (
const appendSectionsToHome = async (
userId: string,
sections: Array<Section>
) => {
@ -269,19 +269,19 @@ const appendSectionsToFeed = async (
pipeline.zremrangebyrank(key, 0, -(MAX_FEED_ITEMS + 1))
pipeline.zremrangebyscore(key, '-inf', Date.now())
logger.info('Adding feed sections to redis')
logger.info('Adding home sections to redis')
await pipeline.exec()
}
const mixFeedItems = (rankedFeedItems: Array<Candidate>): Array<Section> => {
const mixHomeItems = (rankedHomeItems: Array<Candidate>): Array<Section> => {
// find the median word count
const wordCounts = rankedFeedItems.map((item) => item.wordCount)
const wordCounts = rankedHomeItems.map((item) => item.wordCount)
wordCounts.sort((a, b) => a - b)
const medianWordCount = wordCounts[Math.floor(wordCounts.length / 2)]
// separate items into two groups based on word count
const shortItems: Array<Candidate> = []
const longItems: Array<Candidate> = []
for (const item of rankedFeedItems) {
for (const item of rankedHomeItems) {
if (item.wordCount < medianWordCount) {
shortItems.push(item)
} else {
@ -290,7 +290,7 @@ const mixFeedItems = (rankedFeedItems: Array<Candidate>): Array<Section> => {
}
// initialize empty batches
const batches: Array<Array<Candidate>> = Array.from(
{ length: Math.floor(rankedFeedItems.length / 10) },
{ length: Math.floor(rankedHomeItems.length / 10) },
() => []
)
@ -363,7 +363,7 @@ const mixFeedItems = (rankedFeedItems: Array<Candidate>): Array<Section> => {
return sections
}
export const updateJustReadFeed = async (data: UpdateJustReadFeedJobData) => {
export const updateHome = async (data: UpdateHomeJobData) => {
const { userId } = data
const user = await findActiveUser(userId)
if (!user) {
@ -371,7 +371,7 @@ export const updateJustReadFeed = async (data: UpdateJustReadFeedJobData) => {
return
}
logger.info(`Updating just read feed for user ${userId}`)
logger.info(`Updating home for user ${userId}`)
const candidates = await selectCandidates(user)
logger.info(`Found ${candidates.length} candidates`)
@ -387,11 +387,11 @@ export const updateJustReadFeed = async (data: UpdateJustReadFeedJobData) => {
// TODO: filter candidates
logger.info('Mix feed items to create sections')
const rankedSections = mixFeedItems(rankedCandidates)
logger.info('Mix home items to create sections')
const rankedSections = mixHomeItems(rankedCandidates)
logger.info(`Created ${rankedSections.length} sections`)
logger.info('Appending sections to feed')
await appendSectionsToFeed(userId, rankedSections)
logger.info('Feed updated for user', { userId })
logger.info('Appending sections to home')
await appendSectionsToHome(userId, rankedSections)
logger.info('Home updated for user', { userId })
}

View file

@ -63,10 +63,7 @@ import {
UPDATE_HIGHLIGHT_JOB,
UPDATE_LABELS_JOB,
} from './jobs/update_db'
import {
updateJustReadFeed,
UPDATE_JUST_READ_FEED_JOB,
} from './jobs/update_just_read_feed'
import { updateHome, UPDATE_HOME_JOB } from './jobs/update_home'
import { updatePDFContentJob } from './jobs/update_pdf_content'
import { uploadContentJob, UPLOAD_CONTENT_JOB } from './jobs/upload_content'
import { redisDataSource } from './redis_data_source'
@ -193,8 +190,8 @@ export const createWorker = (connection: ConnectionOptions) =>
return createDigest(job.data)
case UPLOAD_CONTENT_JOB:
return uploadContentJob(job.data)
case UPDATE_JUST_READ_FEED_JOB:
return updateJustReadFeed(job.data)
case UPDATE_HOME_JOB:
return updateHome(job.data)
case SCORE_LIBRARY_ITEM_JOB:
return scoreLibraryItem(job.data)
default:

View file

@ -53,6 +53,7 @@ import {
saveDiscoverArticleResolver,
} from './discover_feeds'
import { optInFeatureResolver } from './features'
import { homeResolver } from './home'
import { uploadImportFileResolver } from './importers/uploadImportFileResolver'
import {
addPopularReadResolver,
@ -152,7 +153,6 @@ import {
webhookResolver,
webhooksResolver,
} from './index'
import { justReadFeedResolver } from './just_read_feed'
import {
markEmailAsItemResolver,
recentEmailsResolver,
@ -360,7 +360,7 @@ export const functionResolvers = {
feeds: feedsResolver,
scanFeeds: scanFeedsResolver,
integration: integrationResolver,
justReadFeed: justReadFeedResolver,
home: homeResolver,
},
User: {
async intercomHash(
@ -624,15 +624,13 @@ export const functionResolvers = {
return newsletterEmail.folder || EXISTING_NEWSLETTER_FOLDER
},
},
JustReadFeedSection: {
HomeSection: {
async items(
section: { items: Array<{ id: string }> },
_: unknown,
ctx: WithDataSourcesContext
) {
return ctx.dataLoaders.justReadFeedItems.loadMany(
section.items.map((i) => i.id)
)
return ctx.dataLoaders.homeItems.loadMany(section.items.map((i) => i.id))
},
},
...resultResolveTypeResolver('Login'),
@ -734,5 +732,5 @@ export const functionResolvers = {
...resultResolveTypeResolver('Integration'),
...resultResolveTypeResolver('ExportToIntegration'),
...resultResolveTypeResolver('ReplyToEmail'),
...resultResolveTypeResolver('JustReadFeed'),
...resultResolveTypeResolver('Home'),
}

View file

@ -1,66 +1,57 @@
import {
JustReadFeedError,
JustReadFeedErrorCode,
JustReadFeedItem,
JustReadFeedSection,
JustReadFeedSuccess,
QueryJustReadFeedArgs,
HomeError,
HomeErrorCode,
HomeItem,
HomeSection,
HomeSuccess,
QueryHomeArgs,
} from '../../generated/graphql'
import { getJustReadFeedSections } from '../../jobs/update_just_read_feed'
import { getHomeSections } from '../../jobs/update_home'
import { getJob } from '../../queue-processor'
import { Merge } from '../../util'
import {
enqueueUpdateJustReadFeed,
updateJustReadFeedJobId,
} from '../../utils/createTask'
import { enqueueUpdateHomeJob, updateHomeJobId } from '../../utils/createTask'
import { authorized } from '../../utils/gql-utils'
type PartialJustReadFeedItem = Merge<
Partial<JustReadFeedItem>,
{ type: string }
>
type PartialJustReadFeedSection = Merge<
JustReadFeedSection,
{ items: Array<PartialJustReadFeedItem> }
>
type PartialJustReadFeedSuccess = Merge<
JustReadFeedSuccess,
type PartialHomeItem = Merge<Partial<HomeItem>, { type: string }>
type PartialHomeSection = Merge<HomeSection, { items: Array<PartialHomeItem> }>
type PartialHomeSuccess = Merge<
HomeSuccess,
{
edges: Array<{ cursor: string; node: PartialJustReadFeedSection }>
edges: Array<{ cursor: string; node: PartialHomeSection }>
}
>
// This resolver is used to fetch the just read feed for the user.
// when the feed is empty, it enqueues a job to update the feed.
// when client tries to fetch more then the feed has, it enqueues a job to update the feed.
export const justReadFeedResolver = authorized<
PartialJustReadFeedSuccess,
JustReadFeedError,
QueryJustReadFeedArgs
export const homeResolver = authorized<
PartialHomeSuccess,
HomeError,
QueryHomeArgs
>(async (_, { first, after }, { uid, log }) => {
const limit = first || 10
const cursor = after ? parseInt(after) : undefined
const sections = await getJustReadFeedSections(uid, limit, cursor)
const sections = await getHomeSections(uid, limit, cursor)
log.info('Just read feed sections fetched')
if (sections.length === 0) {
const existingJob = await getJob(updateJustReadFeedJobId(uid))
const existingJob = await getJob(updateHomeJobId(uid))
if (existingJob) {
log.info('Just read feed update job already enqueued')
return {
errorCodes: [JustReadFeedErrorCode.Pending],
errorCodes: [HomeErrorCode.Pending],
}
}
await enqueueUpdateJustReadFeed({
await enqueueUpdateHomeJob({
userId: uid,
})
log.info('Just read feed update enqueued')
return {
errorCodes: [JustReadFeedErrorCode.Pending],
errorCodes: [HomeErrorCode.Pending],
}
}

View file

@ -10,7 +10,7 @@ import { Highlight } from '../entity/highlight'
import { Label } from '../entity/label'
import { Recommendation } from '../entity/recommendation'
import { UploadFile } from '../entity/upload_file'
import { JustReadFeedItem } from '../generated/graphql'
import { HomeItem } from '../generated/graphql'
import { PubsubClient } from '../pubsub'
export interface Claims {
@ -52,7 +52,7 @@ export interface RequestContext {
highlights: DataLoader<string, Highlight[]>
recommendations: DataLoader<string, Recommendation[]>
uploadFiles: DataLoader<string, UploadFile | undefined>
justReadFeedItems: DataLoader<string, JustReadFeedItem>
homeItems: DataLoader<string, HomeItem>
}
}

View file

@ -3101,14 +3101,14 @@ const schema = gql`
SUBSCRIBE
}
type JustReadFeedSubscription {
type HomeSubscription {
id: ID!
name: String!
url: String
icon: String
}
type JustReadFeedItem {
type HomeItem {
id: ID!
title: String!
url: String!
@ -3122,7 +3122,7 @@ const schema = gql`
dir: String
seen_at: Date
wordCount: Int
subscription: JustReadFeedSubscription!
subscription: HomeSubscription!
canSave: Boolean
canComment: Boolean
canShare: Boolean
@ -3130,34 +3130,34 @@ const schema = gql`
canDelete: Boolean
}
type JustReadFeedSection {
type HomeSection {
title: String
layout: String
items: [JustReadFeedItem!]!
items: [HomeItem!]!
thumbnail: String
}
type JustReadFeedEdge {
type HomeEdge {
cursor: String!
node: JustReadFeedSection!
node: HomeSection!
}
type JustReadFeedSuccess {
edges: [JustReadFeedEdge!]!
type HomeSuccess {
edges: [HomeEdge!]!
pageInfo: PageInfo!
}
enum JustReadFeedErrorCode {
enum HomeErrorCode {
UNAUTHORIZED
BAD_REQUEST
PENDING
}
type JustReadFeedError {
errorCodes: [JustReadFeedErrorCode!]!
type HomeError {
errorCodes: [HomeErrorCode!]!
}
union JustReadFeedResult = JustReadFeedSuccess | JustReadFeedError
union HomeResult = HomeSuccess | HomeError
type SubscriptionRootType {
hello: String # for testing only
@ -3358,7 +3358,7 @@ const schema = gql`
feeds(input: FeedsInput!): FeedsResult!
discoverFeeds: DiscoverFeedResult!
scanFeeds(input: ScanFeedsInput!): ScanFeedsResult!
justReadFeed(first: Int, after: String): JustReadFeedResult!
home(first: Int, after: String): HomeResult!
}
schema {

View file

@ -1,12 +1,12 @@
import { PublicItem } from '../entity/public_item'
import { JustReadFeedItem } from '../generated/graphql'
import { HomeItem } from '../generated/graphql'
import { authTrx } from '../repository'
import { findLibraryItemsByIds } from './library_item'
export const batchGetJustReadFeedItems = async (
export const batchGetHomeItems = async (
ids: readonly string[]
): Promise<Array<JustReadFeedItem>> => {
const libraryItems = await findLibraryItemsByIds(ids as string[], '')
): Promise<Array<HomeItem>> => {
const libraryItems = await findLibraryItemsByIds(ids as string[])
const publicItems = await authTrx(async (tx) =>
tx
@ -57,7 +57,7 @@ export const batchGetJustReadFeedItems = async (
: undefined
}
})
.filter((item) => item !== undefined) as JustReadFeedItem[]
.filter((item) => item !== undefined) as Array<HomeItem>
}
export const findUnseenPublicItems = async (

View file

@ -775,7 +775,7 @@ export const findRecentLibraryItems = async (
export const findLibraryItemsByIds = async (
ids: string[],
userId: string,
userId?: string,
options?: {
select?: (keyof LibraryItem)[]
}

View file

@ -57,10 +57,7 @@ import {
UPDATE_HIGHLIGHT_JOB,
UPDATE_LABELS_JOB,
} from '../jobs/update_db'
import {
UpdateJustReadFeedJobData,
UPDATE_JUST_READ_FEED_JOB,
} from '../jobs/update_just_read_feed'
import { UpdateHomeJobData, UPDATE_HOME_JOB } from '../jobs/update_home'
import {
UploadContentJobData,
UPLOAD_CONTENT_JOB,
@ -93,7 +90,7 @@ export const getJobPriority = (jobName: string): number => {
case UPDATE_HIGHLIGHT_JOB:
case SYNC_READ_POSITIONS_JOB_NAME:
case SEND_EMAIL_JOB:
case UPDATE_JUST_READ_FEED_JOB:
case UPDATE_HOME_JOB:
return 1
case TRIGGER_RULE_JOB_NAME:
case CALL_WEBHOOK_JOB_NAME:
@ -991,22 +988,20 @@ export const enqueueBulkUploadContentJob = async (
return queue.addBulk(jobs)
}
export const updateJustReadFeedJobId = (userId: string) =>
`${UPDATE_JUST_READ_FEED_JOB}_${userId}_${JOB_VERSION}`
export const updateHomeJobId = (userId: string) =>
`${UPDATE_HOME_JOB}_${userId}_${JOB_VERSION}`
export const enqueueUpdateJustReadFeed = async (
data: UpdateJustReadFeedJobData
) => {
export const enqueueUpdateHomeJob = async (data: UpdateHomeJobData) => {
const queue = await getBackendQueue()
if (!queue) {
return undefined
}
return queue.add(UPDATE_JUST_READ_FEED_JOB, data, {
jobId: updateJustReadFeedJobId(data.userId),
return queue.add(UPDATE_HOME_JOB, data, {
jobId: updateHomeJobId(data.userId),
removeOnComplete: true,
removeOnFail: true,
priority: getJobPriority(UPDATE_JUST_READ_FEED_JOB),
priority: getJobPriority(UPDATE_HOME_JOB),
attempts: 3,
})
}