mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
add pagination
This commit is contained in:
parent
880a907679
commit
1ba6221bc4
5 changed files with 219 additions and 196 deletions
|
|
@ -1370,6 +1370,7 @@ export type JustReadFeedError = {
|
|||
|
||||
export enum JustReadFeedErrorCode {
|
||||
BadRequest = 'BAD_REQUEST',
|
||||
Pending = 'PENDING',
|
||||
Unauthorized = 'UNAUTHORIZED'
|
||||
}
|
||||
|
||||
|
|
@ -1377,49 +1378,47 @@ export type JustReadFeedItem = {
|
|||
__typename?: 'JustReadFeedItem';
|
||||
author?: Maybe<Scalars['String']>;
|
||||
broadcastCount?: Maybe<Scalars['Int']>;
|
||||
comments?: Maybe<Array<Scalars['String']>>;
|
||||
createdAt: Scalars['Date'];
|
||||
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']>;
|
||||
highlights?: Maybe<Scalars['String']>;
|
||||
id: Scalars['ID'];
|
||||
languageCode?: Maybe<Scalars['String']>;
|
||||
likedCount?: Maybe<Scalars['Int']>;
|
||||
previewContent?: Maybe<Scalars['String']>;
|
||||
savedCount?: Maybe<Scalars['Int']>;
|
||||
seen_at?: Maybe<Scalars['Date']>;
|
||||
siteName?: Maybe<Scalars['String']>;
|
||||
sourceIcon?: Maybe<Scalars['String']>;
|
||||
sourceName: Scalars['String'];
|
||||
subscription: JustReadFeedSubscription;
|
||||
thumbnail?: Maybe<Scalars['String']>;
|
||||
title: Scalars['String'];
|
||||
topic?: Maybe<Scalars['String']>;
|
||||
updatedAt: Scalars['Date'];
|
||||
url: Scalars['String'];
|
||||
wordCount?: Maybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
export type JustReadFeedResult = JustReadFeedError | JustReadFeedSuccess;
|
||||
|
||||
export type JustReadFeedSource = {
|
||||
__typename?: 'JustReadFeedSource';
|
||||
id: Scalars['ID'];
|
||||
languageCodes?: Maybe<Array<Scalars['String']>>;
|
||||
name: Scalars['String'];
|
||||
export type JustReadFeedSection = {
|
||||
__typename?: 'JustReadFeedSection';
|
||||
items: Array<JustReadFeedItem>;
|
||||
layout?: Maybe<Scalars['String']>;
|
||||
thumbnail?: Maybe<Scalars['String']>;
|
||||
topics?: Maybe<Array<Scalars['String']>>;
|
||||
url: 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';
|
||||
topics: Array<JustReadFeedTopic>;
|
||||
};
|
||||
|
||||
export type JustReadFeedTopic = {
|
||||
__typename?: 'JustReadFeedTopic';
|
||||
items: Array<JustReadFeedItem>;
|
||||
name?: Maybe<Scalars['String']>;
|
||||
thumbnail?: Maybe<Scalars['String']>;
|
||||
sections: Array<JustReadFeedSection>;
|
||||
};
|
||||
|
||||
export type Label = {
|
||||
|
|
@ -2074,11 +2073,6 @@ export type MutationUploadImportFileArgs = {
|
|||
type: UploadImportFileType;
|
||||
};
|
||||
|
||||
export type MySubscriptionRootType = {
|
||||
__typename?: 'MySubscriptionRootType';
|
||||
hello?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type NewsletterEmail = {
|
||||
__typename?: 'NewsletterEmail';
|
||||
address: Scalars['String'];
|
||||
|
|
@ -2277,6 +2271,12 @@ export type QueryIntegrationArgs = {
|
|||
};
|
||||
|
||||
|
||||
export type QueryJustReadFeedArgs = {
|
||||
after?: InputMaybe<Scalars['String']>;
|
||||
first?: InputMaybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryRulesArgs = {
|
||||
enabled?: InputMaybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
|
@ -3285,6 +3285,11 @@ export type Subscription = {
|
|||
url?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type SubscriptionRootType = {
|
||||
__typename?: 'SubscriptionRootType';
|
||||
hello?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export enum SubscriptionStatus {
|
||||
Active = 'ACTIVE',
|
||||
Deleted = 'DELETED',
|
||||
|
|
@ -4270,9 +4275,9 @@ export type ResolversTypes = {
|
|||
JustReadFeedErrorCode: JustReadFeedErrorCode;
|
||||
JustReadFeedItem: ResolverTypeWrapper<JustReadFeedItem>;
|
||||
JustReadFeedResult: ResolversTypes['JustReadFeedError'] | ResolversTypes['JustReadFeedSuccess'];
|
||||
JustReadFeedSource: ResolverTypeWrapper<JustReadFeedSource>;
|
||||
JustReadFeedSection: ResolverTypeWrapper<JustReadFeedSection>;
|
||||
JustReadFeedSubscription: ResolverTypeWrapper<JustReadFeedSubscription>;
|
||||
JustReadFeedSuccess: ResolverTypeWrapper<JustReadFeedSuccess>;
|
||||
JustReadFeedTopic: ResolverTypeWrapper<JustReadFeedTopic>;
|
||||
Label: ResolverTypeWrapper<Label>;
|
||||
LabelsError: ResolverTypeWrapper<LabelsError>;
|
||||
LabelsErrorCode: LabelsErrorCode;
|
||||
|
|
@ -4316,7 +4321,6 @@ export type ResolversTypes = {
|
|||
MoveToFolderResult: ResolversTypes['MoveToFolderError'] | ResolversTypes['MoveToFolderSuccess'];
|
||||
MoveToFolderSuccess: ResolverTypeWrapper<MoveToFolderSuccess>;
|
||||
Mutation: ResolverTypeWrapper<{}>;
|
||||
MySubscriptionRootType: ResolverTypeWrapper<{}>;
|
||||
NewsletterEmail: ResolverTypeWrapper<NewsletterEmail>;
|
||||
NewsletterEmailsError: ResolverTypeWrapper<NewsletterEmailsError>;
|
||||
NewsletterEmailsErrorCode: NewsletterEmailsErrorCode;
|
||||
|
|
@ -4495,6 +4499,7 @@ export type ResolversTypes = {
|
|||
SubscribeResult: ResolversTypes['SubscribeError'] | ResolversTypes['SubscribeSuccess'];
|
||||
SubscribeSuccess: ResolverTypeWrapper<SubscribeSuccess>;
|
||||
Subscription: ResolverTypeWrapper<Subscription>;
|
||||
SubscriptionRootType: ResolverTypeWrapper<{}>;
|
||||
SubscriptionStatus: SubscriptionStatus;
|
||||
SubscriptionType: SubscriptionType;
|
||||
SubscriptionsError: ResolverTypeWrapper<SubscriptionsError>;
|
||||
|
|
@ -4819,9 +4824,9 @@ export type ResolversParentTypes = {
|
|||
JustReadFeedError: JustReadFeedError;
|
||||
JustReadFeedItem: JustReadFeedItem;
|
||||
JustReadFeedResult: ResolversParentTypes['JustReadFeedError'] | ResolversParentTypes['JustReadFeedSuccess'];
|
||||
JustReadFeedSource: JustReadFeedSource;
|
||||
JustReadFeedSection: JustReadFeedSection;
|
||||
JustReadFeedSubscription: JustReadFeedSubscription;
|
||||
JustReadFeedSuccess: JustReadFeedSuccess;
|
||||
JustReadFeedTopic: JustReadFeedTopic;
|
||||
Label: Label;
|
||||
LabelsError: LabelsError;
|
||||
LabelsResult: ResolversParentTypes['LabelsError'] | ResolversParentTypes['LabelsSuccess'];
|
||||
|
|
@ -4856,7 +4861,6 @@ export type ResolversParentTypes = {
|
|||
MoveToFolderResult: ResolversParentTypes['MoveToFolderError'] | ResolversParentTypes['MoveToFolderSuccess'];
|
||||
MoveToFolderSuccess: MoveToFolderSuccess;
|
||||
Mutation: {};
|
||||
MySubscriptionRootType: {};
|
||||
NewsletterEmail: NewsletterEmail;
|
||||
NewsletterEmailsError: NewsletterEmailsError;
|
||||
NewsletterEmailsResult: ResolversParentTypes['NewsletterEmailsError'] | ResolversParentTypes['NewsletterEmailsSuccess'];
|
||||
|
|
@ -4996,6 +5000,7 @@ export type ResolversParentTypes = {
|
|||
SubscribeResult: ResolversParentTypes['SubscribeError'] | ResolversParentTypes['SubscribeSuccess'];
|
||||
SubscribeSuccess: SubscribeSuccess;
|
||||
Subscription: Subscription;
|
||||
SubscriptionRootType: {};
|
||||
SubscriptionsError: SubscriptionsError;
|
||||
SubscriptionsResult: ResolversParentTypes['SubscriptionsError'] | ResolversParentTypes['SubscriptionsSuccess'];
|
||||
SubscriptionsSuccess: SubscriptionsSuccess;
|
||||
|
|
@ -6076,23 +6081,22 @@ export type JustReadFeedErrorResolvers<ContextType = ResolverContext, ParentType
|
|||
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>;
|
||||
comments?: Resolver<Maybe<Array<ResolversTypes['String']>>, ParentType, ContextType>;
|
||||
createdAt?: Resolver<ResolversTypes['Date'], 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>;
|
||||
highlights?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
languageCode?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
likedCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
|
||||
previewContent?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
savedCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
|
||||
seen_at?: Resolver<Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
|
||||
siteName?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
sourceIcon?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
sourceName?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
subscription?: Resolver<ResolversTypes['JustReadFeedSubscription'], ParentType, ContextType>;
|
||||
thumbnail?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
title?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
topic?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
updatedAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
|
||||
url?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
wordCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
|
|
@ -6102,25 +6106,24 @@ export type JustReadFeedResultResolvers<ContextType = ResolverContext, ParentTyp
|
|||
__resolveType: TypeResolveFn<'JustReadFeedError' | 'JustReadFeedSuccess', ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type JustReadFeedSourceResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['JustReadFeedSource'] = ResolversParentTypes['JustReadFeedSource']> = {
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
languageCodes?: Resolver<Maybe<Array<ResolversTypes['String']>>, ParentType, ContextType>;
|
||||
name?: Resolver<ResolversTypes['String'], 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>;
|
||||
topics?: Resolver<Maybe<Array<ResolversTypes['String']>>, ParentType, ContextType>;
|
||||
url?: Resolver<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']> = {
|
||||
topics?: Resolver<Array<ResolversTypes['JustReadFeedTopic']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type JustReadFeedTopicResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['JustReadFeedTopic'] = ResolversParentTypes['JustReadFeedTopic']> = {
|
||||
items?: Resolver<Array<ResolversTypes['JustReadFeedItem']>, ParentType, ContextType>;
|
||||
name?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
thumbnail?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
sections?: Resolver<Array<ResolversTypes['JustReadFeedSection']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
|
|
@ -6360,10 +6363,6 @@ export type MutationResolvers<ContextType = ResolverContext, ParentType extends
|
|||
uploadImportFile?: Resolver<ResolversTypes['UploadImportFileResult'], ParentType, ContextType, RequireFields<MutationUploadImportFileArgs, 'contentType' | 'type'>>;
|
||||
};
|
||||
|
||||
export type MySubscriptionRootTypeResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['MySubscriptionRootType'] = ResolversParentTypes['MySubscriptionRootType']> = {
|
||||
hello?: SubscriptionResolver<Maybe<ResolversTypes['String']>, "hello", ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type NewsletterEmailResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['NewsletterEmail'] = ResolversParentTypes['NewsletterEmail']> = {
|
||||
address?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
confirmationCode?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
|
|
@ -6455,7 +6454,7 @@ export type QueryResolvers<ContextType = ResolverContext, ParentType extends Res
|
|||
hello?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
integration?: Resolver<ResolversTypes['IntegrationResult'], ParentType, ContextType, RequireFields<QueryIntegrationArgs, 'name'>>;
|
||||
integrations?: Resolver<ResolversTypes['IntegrationsResult'], ParentType, ContextType>;
|
||||
justReadFeed?: Resolver<ResolversTypes['JustReadFeedResult'], 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>;
|
||||
|
|
@ -7064,6 +7063,10 @@ export type SubscriptionResolvers<ContextType = ResolverContext, ParentType exte
|
|||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SubscriptionRootTypeResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['SubscriptionRootType'] = ResolversParentTypes['SubscriptionRootType']> = {
|
||||
hello?: SubscriptionResolver<Maybe<ResolversTypes['String']>, "hello", ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SubscriptionsErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['SubscriptionsError'] = ResolversParentTypes['SubscriptionsError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['SubscriptionsErrorCode']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
|
|
@ -7650,9 +7653,9 @@ export type Resolvers<ContextType = ResolverContext> = {
|
|||
JustReadFeedError?: JustReadFeedErrorResolvers<ContextType>;
|
||||
JustReadFeedItem?: JustReadFeedItemResolvers<ContextType>;
|
||||
JustReadFeedResult?: JustReadFeedResultResolvers<ContextType>;
|
||||
JustReadFeedSource?: JustReadFeedSourceResolvers<ContextType>;
|
||||
JustReadFeedSection?: JustReadFeedSectionResolvers<ContextType>;
|
||||
JustReadFeedSubscription?: JustReadFeedSubscriptionResolvers<ContextType>;
|
||||
JustReadFeedSuccess?: JustReadFeedSuccessResolvers<ContextType>;
|
||||
JustReadFeedTopic?: JustReadFeedTopicResolvers<ContextType>;
|
||||
Label?: LabelResolvers<ContextType>;
|
||||
LabelsError?: LabelsErrorResolvers<ContextType>;
|
||||
LabelsResult?: LabelsResultResolvers<ContextType>;
|
||||
|
|
@ -7684,7 +7687,6 @@ export type Resolvers<ContextType = ResolverContext> = {
|
|||
MoveToFolderResult?: MoveToFolderResultResolvers<ContextType>;
|
||||
MoveToFolderSuccess?: MoveToFolderSuccessResolvers<ContextType>;
|
||||
Mutation?: MutationResolvers<ContextType>;
|
||||
MySubscriptionRootType?: MySubscriptionRootTypeResolvers<ContextType>;
|
||||
NewsletterEmail?: NewsletterEmailResolvers<ContextType>;
|
||||
NewsletterEmailsError?: NewsletterEmailsErrorResolvers<ContextType>;
|
||||
NewsletterEmailsResult?: NewsletterEmailsResultResolvers<ContextType>;
|
||||
|
|
@ -7795,6 +7797,7 @@ export type Resolvers<ContextType = ResolverContext> = {
|
|||
SubscribeResult?: SubscribeResultResolvers<ContextType>;
|
||||
SubscribeSuccess?: SubscribeSuccessResolvers<ContextType>;
|
||||
Subscription?: SubscriptionResolvers<ContextType>;
|
||||
SubscriptionRootType?: SubscriptionRootTypeResolvers<ContextType>;
|
||||
SubscriptionsError?: SubscriptionsErrorResolvers<ContextType>;
|
||||
SubscriptionsResult?: SubscriptionsResultResolvers<ContextType>;
|
||||
SubscriptionsSuccess?: SubscriptionsSuccessResolvers<ContextType>;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
schema {
|
||||
query: Query
|
||||
mutation: Mutation
|
||||
subscription: MySubscriptionRootType
|
||||
subscription: SubscriptionRootType
|
||||
}
|
||||
|
||||
directive @sanitize(allowedTags: [String], maxLength: Int, minLength: Int, pattern: String) on INPUT_FIELD_DEFINITION
|
||||
|
|
@ -1234,52 +1234,51 @@ type JustReadFeedError {
|
|||
|
||||
enum JustReadFeedErrorCode {
|
||||
BAD_REQUEST
|
||||
PENDING
|
||||
UNAUTHORIZED
|
||||
}
|
||||
|
||||
type JustReadFeedItem {
|
||||
author: String
|
||||
broadcastCount: Int
|
||||
comments: [String!]
|
||||
createdAt: Date!
|
||||
canArchive: Boolean
|
||||
canComment: Boolean
|
||||
canDelete: Boolean
|
||||
canSave: Boolean
|
||||
canShare: Boolean
|
||||
date: Date!
|
||||
dir: String
|
||||
highlights: String
|
||||
id: ID!
|
||||
languageCode: String
|
||||
likedCount: Int
|
||||
previewContent: String
|
||||
savedCount: Int
|
||||
seen_at: Date
|
||||
siteName: String
|
||||
sourceIcon: String
|
||||
sourceName: String!
|
||||
subscription: JustReadFeedSubscription!
|
||||
thumbnail: String
|
||||
title: String!
|
||||
topic: String
|
||||
updatedAt: Date!
|
||||
url: String!
|
||||
wordCount: Int
|
||||
}
|
||||
|
||||
union JustReadFeedResult = JustReadFeedError | JustReadFeedSuccess
|
||||
|
||||
type JustReadFeedSource {
|
||||
id: ID!
|
||||
languageCodes: [String!]
|
||||
name: String!
|
||||
type JustReadFeedSection {
|
||||
items: [JustReadFeedItem!]!
|
||||
layout: String
|
||||
thumbnail: String
|
||||
topics: [String!]
|
||||
url: String!
|
||||
title: String
|
||||
}
|
||||
|
||||
type JustReadFeedSubscription {
|
||||
icon: String
|
||||
id: ID!
|
||||
name: String!
|
||||
url: String
|
||||
}
|
||||
|
||||
type JustReadFeedSuccess {
|
||||
topics: [JustReadFeedTopic!]!
|
||||
}
|
||||
|
||||
type JustReadFeedTopic {
|
||||
items: [JustReadFeedItem!]!
|
||||
name: String
|
||||
thumbnail: String
|
||||
sections: [JustReadFeedSection!]!
|
||||
}
|
||||
|
||||
type Label {
|
||||
|
|
@ -1563,10 +1562,6 @@ type Mutation {
|
|||
uploadImportFile(contentType: String!, type: UploadImportFileType!): UploadImportFileResult!
|
||||
}
|
||||
|
||||
type MySubscriptionRootType {
|
||||
hello: String
|
||||
}
|
||||
|
||||
type NewsletterEmail {
|
||||
address: String!
|
||||
confirmationCode: String
|
||||
|
|
@ -1704,7 +1699,7 @@ type Query {
|
|||
hello: String
|
||||
integration(name: String!): IntegrationResult!
|
||||
integrations: IntegrationsResult!
|
||||
justReadFeed: JustReadFeedResult!
|
||||
justReadFeed(after: String, first: Int): JustReadFeedResult!
|
||||
labels: LabelsResult!
|
||||
me: User
|
||||
newsletterEmails: NewsletterEmailsResult!
|
||||
|
|
@ -2606,6 +2601,10 @@ type Subscription {
|
|||
url: String
|
||||
}
|
||||
|
||||
type SubscriptionRootType {
|
||||
hello: String
|
||||
}
|
||||
|
||||
enum SubscriptionStatus {
|
||||
ACTIVE
|
||||
DELETED
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@ import languages from '@cospired/i18n-iso-languages'
|
|||
import { LibraryItem } from '../entity/library_item'
|
||||
import { PublicItem } from '../entity/public_item'
|
||||
import { User } from '../entity/user'
|
||||
import {
|
||||
JustReadFeedItem,
|
||||
JustReadFeedSection,
|
||||
JustReadFeedSuccess,
|
||||
} from '../generated/graphql'
|
||||
import { redisDataSource } from '../redis_data_source'
|
||||
import { findUnseenPublicItems } from '../services/just_read_feed'
|
||||
import { searchLibraryItems } from '../services/library_item'
|
||||
|
|
@ -14,37 +19,44 @@ export interface UpdateJustReadFeedJobData {
|
|||
userId: string
|
||||
}
|
||||
|
||||
interface JustReadFeedItem {
|
||||
interface Feature {
|
||||
title: string
|
||||
has_thumbnail: boolean
|
||||
has_site_icon: boolean
|
||||
saved_at: Date
|
||||
site?: string
|
||||
language?: string
|
||||
author?: string
|
||||
directionality: string
|
||||
word_count?: number
|
||||
subscription_type: string
|
||||
folder: string
|
||||
}
|
||||
|
||||
interface ScoreApiRequestBody {
|
||||
user_id: string
|
||||
item_features: Record<string, Feature> // item_id -> feature
|
||||
}
|
||||
|
||||
interface Candidate {
|
||||
id: string
|
||||
title: string
|
||||
url: string
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
sourceName: string
|
||||
|
||||
siteName?: string
|
||||
topic?: string
|
||||
thumbnail?: string
|
||||
previewContent?: string
|
||||
languageCode?: string
|
||||
languageCode: string
|
||||
author?: string
|
||||
dir?: string
|
||||
dir: string
|
||||
date: Date
|
||||
topic?: string
|
||||
wordCount?: number
|
||||
sourceIcon?: string
|
||||
|
||||
saveCount?: number
|
||||
likeCount?: number
|
||||
broadcastCount?: number
|
||||
}
|
||||
|
||||
interface JustReadFeedTopic {
|
||||
name?: string
|
||||
items: Array<JustReadFeedItem>
|
||||
thumbnail: string
|
||||
}
|
||||
|
||||
interface JustReadFeed {
|
||||
topics: Array<JustReadFeedTopic>
|
||||
siteName?: string
|
||||
subscription: {
|
||||
id: string
|
||||
name: string
|
||||
icon?: string
|
||||
}
|
||||
}
|
||||
|
||||
const lanaugeToCode = (language: string): string =>
|
||||
|
|
@ -62,13 +74,16 @@ const libraryItemToFeedItem = (
|
|||
languageCode: lanaugeToCode(item.itemLanguage || 'English'),
|
||||
author: item.author || undefined,
|
||||
dir: item.directionality || 'ltr',
|
||||
createdAt: item.createdAt,
|
||||
date: item.createdAt,
|
||||
topic: item.topic,
|
||||
wordCount: item.wordCount || undefined,
|
||||
sourceIcon: user.profile.pictureUrl || undefined,
|
||||
sourceName: user.name,
|
||||
siteName: item.siteName || undefined,
|
||||
updatedAt: item.updatedAt,
|
||||
subscription: {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
icon: user.profile.pictureUrl || undefined,
|
||||
},
|
||||
})
|
||||
|
||||
const publicItemToFeedItem = (item: PublicItem): JustReadFeedItem => ({
|
||||
|
|
@ -80,11 +95,9 @@ const publicItemToFeedItem = (item: PublicItem): JustReadFeedItem => ({
|
|||
languageCode: item.languageCode || 'en',
|
||||
author: item.author,
|
||||
dir: item.dir || 'ltr',
|
||||
createdAt: item.createdAt,
|
||||
date: item.createdAt,
|
||||
topic: item.topic,
|
||||
wordCount: item.wordCount || undefined,
|
||||
sourceIcon: item.sourceIcon,
|
||||
sourceName: item.sourceName,
|
||||
siteName: item.siteName,
|
||||
updatedAt: item.updatedAt,
|
||||
saveCount: item.stats.saveCount,
|
||||
|
|
@ -92,10 +105,7 @@ const publicItemToFeedItem = (item: PublicItem): JustReadFeedItem => ({
|
|||
broadcastCount: item.stats.broadcastCount,
|
||||
})
|
||||
|
||||
interface FeedItemScore {
|
||||
id: string
|
||||
score: number
|
||||
}
|
||||
type ScoreApiResponse = Record<string, number> // item_id -> score
|
||||
|
||||
const selectCandidates = async (
|
||||
user: User
|
||||
|
|
@ -138,6 +148,7 @@ const selectCandidates = async (
|
|||
}
|
||||
|
||||
const rankFeedItems = async (
|
||||
userId: string,
|
||||
feedItems: Array<JustReadFeedItem>
|
||||
): Promise<Array<JustReadFeedItem>> => {
|
||||
if (feedItems.length <= 10) {
|
||||
|
|
@ -146,32 +157,46 @@ const rankFeedItems = async (
|
|||
}
|
||||
|
||||
// TODO: get score of candidates
|
||||
// const API_URL = 'https://score.omnivore.app' // fake URL
|
||||
const API_URL = 'http://127.0.0.1:5000/predictions'
|
||||
const requestBody: ScoreApiRequestBody = {
|
||||
user_id: userId,
|
||||
item_features: feedItems.reduce((acc, item) => {
|
||||
acc[item.id] = {
|
||||
title: item.title,
|
||||
has_thumbnail: !!item.thumbnail,
|
||||
has_site_icon: !!item.sourceIcon,
|
||||
saved_at: item.date,
|
||||
site: item.siteName,
|
||||
language: item.languageCode,
|
||||
} as Feature
|
||||
return acc
|
||||
}, {} as Record<string, Feature>),
|
||||
}
|
||||
|
||||
// const response = await fetch(API_URL, {
|
||||
// method: 'POST',
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// },
|
||||
// body: JSON.stringify({ candidates: feedItems }),
|
||||
// })
|
||||
const response = await fetch(API_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
})
|
||||
|
||||
// if (!response.ok) {
|
||||
// throw new Error(`Failed to score candidates: ${response.statusText}`)
|
||||
// }
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to score candidates: ${response.statusText}`)
|
||||
}
|
||||
|
||||
// const scores = (await response.json()) as Array<FeedItemScore>
|
||||
const scores = (await response.json()) as ScoreApiResponse
|
||||
|
||||
// fake scores
|
||||
const scores: Array<FeedItemScore> = feedItems.map((item) => ({
|
||||
id: item.id,
|
||||
score: Math.random(),
|
||||
}))
|
||||
// // fake scores
|
||||
// const scores: Array<FeedItemScore> = feedItems.map((item) => ({
|
||||
// id: item.id,
|
||||
// score: Math.random(),
|
||||
// }))
|
||||
|
||||
// rank candidates by score in ascending order
|
||||
feedItems.sort((a, b) => {
|
||||
const scoreA = scores.find((score) => score.id === a.id)?.score || 0
|
||||
const scoreB = scores.find((score) => score.id === b.id)?.score || 0
|
||||
const scoreA = scores[a.id] || 0
|
||||
const scoreB = scores[b.id] || 0
|
||||
|
||||
return scoreA - scoreB
|
||||
})
|
||||
|
|
@ -183,8 +208,10 @@ const redisKey = (userId: string) => `just-read-feed:${userId}`
|
|||
const MAX_FEED_ITEMS = 500
|
||||
|
||||
export const getJustReadFeed = async (
|
||||
userId: string
|
||||
): Promise<JustReadFeed> => {
|
||||
userId: string,
|
||||
first = 10,
|
||||
after?: string
|
||||
): Promise<JustReadFeedSuccess> => {
|
||||
const redisClient = redisDataSource.redisClient
|
||||
if (!redisClient) {
|
||||
throw new Error('Redis client not available')
|
||||
|
|
@ -192,31 +219,16 @@ export const getJustReadFeed = async (
|
|||
|
||||
const key = redisKey(userId)
|
||||
|
||||
const results = await redisClient.zrevrange(key, 0, MAX_FEED_ITEMS)
|
||||
const results = await redisClient.zrevrange(key, 0, first - 1, 'WITHSCORES')
|
||||
|
||||
const feedItems = results.map((item) => JSON.parse(item) as JustReadFeedItem)
|
||||
const sections = JSON.parse(results.join(',')) as JustReadFeedSection[]
|
||||
|
||||
const topics: Array<JustReadFeedTopic> = []
|
||||
|
||||
feedItems.forEach((item) => {
|
||||
const topic = topics.find((topic) => topic.name === item.topic)
|
||||
if (topic) {
|
||||
topic.items.push(item)
|
||||
} else {
|
||||
topics.push({
|
||||
name: item.topic,
|
||||
thumbnail: item.thumbnail || '',
|
||||
items: [item],
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return { topics }
|
||||
return { sections }
|
||||
}
|
||||
|
||||
const appendItemsToFeed = async (
|
||||
feedItems: Array<JustReadFeedItem>,
|
||||
userId: string
|
||||
const appendSectionsToFeed = async (
|
||||
userId: string,
|
||||
sections: Array<JustReadFeedSection>
|
||||
) => {
|
||||
const redisClient = redisDataSource.redisClient
|
||||
if (!redisClient) {
|
||||
|
|
@ -228,18 +240,18 @@ const appendItemsToFeed = async (
|
|||
// store candidates in redis sorted set
|
||||
const pipeline = redisClient.pipeline()
|
||||
|
||||
const scoreMembers = feedItems.flatMap((item) => [
|
||||
Date.now() + 86_400_000, // items expire in 24 hours
|
||||
const scoreMembers = sections.flatMap((item) => [
|
||||
Date.now() + 86_400_000, // sections expire in 24 hours
|
||||
JSON.stringify(item),
|
||||
])
|
||||
// add candidates to the sorted set
|
||||
// add section to the sorted set
|
||||
pipeline.zadd(key, ...scoreMembers)
|
||||
|
||||
// remove expired items and keep only the top 500
|
||||
// remove expired sections and keep only the top 500
|
||||
pipeline.zremrangebyrank(key, 0, -(MAX_FEED_ITEMS + 1))
|
||||
pipeline.zremrangebyscore(key, '-inf', Date.now())
|
||||
|
||||
logger.info('Adding feed items to redis')
|
||||
logger.info('Adding feed sections to redis')
|
||||
await pipeline.exec()
|
||||
}
|
||||
|
||||
|
|
@ -259,7 +271,7 @@ export const updateJustReadFeed = async (data: UpdateJustReadFeedJobData) => {
|
|||
// TODO: integrity check on candidates?
|
||||
|
||||
logger.info('Ranking feed items')
|
||||
const rankedFeedItems = await rankFeedItems(feedItems)
|
||||
const rankedFeedItems = await rankFeedItems(userId, feedItems)
|
||||
if (rankedFeedItems.length === 0) {
|
||||
logger.info('No feed items to append')
|
||||
return
|
||||
|
|
@ -271,5 +283,5 @@ export const updateJustReadFeed = async (data: UpdateJustReadFeedJobData) => {
|
|||
const filteredFeedItems = rankedFeedItems.slice(0, 100)
|
||||
|
||||
logger.info('Appending feed items to feed')
|
||||
await appendItemsToFeed(filteredFeedItems, userId)
|
||||
await appendSectionsToFeed(userId, filteredFeedItems)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
import { JustReadFeedError, JustReadFeedSuccess } from '../../generated/graphql'
|
||||
import {
|
||||
JustReadFeedError,
|
||||
JustReadFeedErrorCode,
|
||||
JustReadFeedSuccess
|
||||
} from '../../generated/graphql'
|
||||
import { getJustReadFeed } from '../../jobs/update_just_read_feed'
|
||||
import { enqueueUpdateJustReadFeed } from '../../utils/createTask'
|
||||
import { authorized } from '../../utils/gql-utils'
|
||||
|
|
@ -6,14 +10,20 @@ import { authorized } from '../../utils/gql-utils'
|
|||
export const justReadFeedResolver = authorized<
|
||||
JustReadFeedSuccess,
|
||||
JustReadFeedError
|
||||
>(async (_, __, { uid, log }) => {
|
||||
const feed = await getJustReadFeed(uid)
|
||||
>(async (_, { first, after }, { uid, log }) => {
|
||||
const feed = await getJustReadFeed(uid, first, after)
|
||||
log.info('Just read feed fetched')
|
||||
|
||||
if (feed.topics.length === 0) {
|
||||
if (feed.sections.length === 0) {
|
||||
await enqueueUpdateJustReadFeed({
|
||||
userId: uid,
|
||||
})
|
||||
|
||||
log.info('Just read feed update enqueued')
|
||||
|
||||
return {
|
||||
errorCodes: [JustReadFeedErrorCode.Pending],
|
||||
}
|
||||
}
|
||||
|
||||
return feed
|
||||
|
|
|
|||
|
|
@ -3101,19 +3101,16 @@ const schema = gql`
|
|||
SUBSCRIBE
|
||||
}
|
||||
|
||||
type JustReadFeedSource {
|
||||
type JustReadFeedSubscription {
|
||||
id: ID!
|
||||
name: String!
|
||||
url: String!
|
||||
topics: [String!]
|
||||
thumbnail: String
|
||||
languageCodes: [String!]
|
||||
url: String
|
||||
icon: String
|
||||
}
|
||||
|
||||
type JustReadFeedItem {
|
||||
id: ID!
|
||||
title: String!
|
||||
topic: String
|
||||
url: String!
|
||||
thumbnail: String
|
||||
previewContent: String
|
||||
|
|
@ -3121,32 +3118,34 @@ const schema = gql`
|
|||
savedCount: Int
|
||||
likedCount: Int
|
||||
broadcastCount: Int
|
||||
createdAt: Date!
|
||||
updatedAt: Date!
|
||||
comments: [String!]
|
||||
date: Date!
|
||||
author: String
|
||||
languageCode: String
|
||||
dir: String
|
||||
seen_at: Date
|
||||
wordCount: Int
|
||||
sourceName: String!
|
||||
sourceIcon: String
|
||||
siteName: String
|
||||
subscription: JustReadFeedSubscription!
|
||||
canSave: Boolean
|
||||
canComment: Boolean
|
||||
canShare: Boolean
|
||||
canArchive: Boolean
|
||||
canDelete: Boolean
|
||||
}
|
||||
|
||||
type JustReadFeedTopic {
|
||||
name: String
|
||||
type JustReadFeedSection {
|
||||
title: String
|
||||
layout: String
|
||||
items: [JustReadFeedItem!]!
|
||||
thumbnail: String
|
||||
}
|
||||
|
||||
type JustReadFeedSuccess {
|
||||
topics: [JustReadFeedTopic!]!
|
||||
sections: [JustReadFeedSection!]!
|
||||
}
|
||||
|
||||
enum JustReadFeedErrorCode {
|
||||
UNAUTHORIZED
|
||||
BAD_REQUEST
|
||||
PENDING
|
||||
}
|
||||
|
||||
type JustReadFeedError {
|
||||
|
|
@ -3155,7 +3154,7 @@ const schema = gql`
|
|||
|
||||
union JustReadFeedResult = JustReadFeedSuccess | JustReadFeedError
|
||||
|
||||
type MySubscriptionRootType {
|
||||
type SubscriptionRootType {
|
||||
hello: String # for testing only
|
||||
}
|
||||
|
||||
|
|
@ -3354,13 +3353,13 @@ const schema = gql`
|
|||
feeds(input: FeedsInput!): FeedsResult!
|
||||
discoverFeeds: DiscoverFeedResult!
|
||||
scanFeeds(input: ScanFeedsInput!): ScanFeedsResult!
|
||||
justReadFeed: JustReadFeedResult!
|
||||
justReadFeed(first: Int, after: String): JustReadFeedResult!
|
||||
}
|
||||
|
||||
schema {
|
||||
query: Query
|
||||
mutation: Mutation
|
||||
subscription: MySubscriptionRootType
|
||||
subscription: SubscriptionRootType
|
||||
}
|
||||
`
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue