update API

This commit is contained in:
Hongbo Wu
2024-05-28 14:45:33 +08:00
parent 88dcdbfb2d
commit e131afc183
11 changed files with 175 additions and 43 deletions

View File

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

View File

@ -1303,13 +1303,29 @@ export type HomeItem = {
previewContent?: Maybe<Scalars['String']>;
saveCount?: Maybe<Scalars['Int']>;
seen_at?: Maybe<Scalars['Date']>;
subscription: HomeSubscription;
subscription?: Maybe<HomeItemSource>;
thumbnail?: Maybe<Scalars['String']>;
title: Scalars['String'];
url: Scalars['String'];
wordCount?: Maybe<Scalars['Int']>;
};
export type HomeItemSource = {
__typename?: 'HomeItemSource';
icon?: Maybe<Scalars['String']>;
id?: Maybe<Scalars['ID']>;
name: Scalars['String'];
type: HomeItemSourceType;
url?: Maybe<Scalars['String']>;
};
export enum HomeItemSourceType {
Library = 'LIBRARY',
Newsletter = 'NEWSLETTER',
Recommendation = 'RECOMMENDATION',
Rss = 'RSS'
}
export type HomeResult = HomeError | HomeSuccess;
export type HomeSection = {
@ -1320,14 +1336,6 @@ export type HomeSection = {
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>;
@ -4259,9 +4267,10 @@ export type ResolversTypes = {
HomeError: ResolverTypeWrapper<HomeError>;
HomeErrorCode: HomeErrorCode;
HomeItem: ResolverTypeWrapper<HomeItem>;
HomeItemSource: ResolverTypeWrapper<HomeItemSource>;
HomeItemSourceType: HomeItemSourceType;
HomeResult: ResolversTypes['HomeError'] | ResolversTypes['HomeSuccess'];
HomeSection: ResolverTypeWrapper<HomeSection>;
HomeSubscription: ResolverTypeWrapper<HomeSubscription>;
HomeSuccess: ResolverTypeWrapper<HomeSuccess>;
ID: ResolverTypeWrapper<Scalars['ID']>;
ImportFromIntegrationError: ResolverTypeWrapper<ImportFromIntegrationError>;
@ -4815,9 +4824,9 @@ export type ResolversParentTypes = {
HomeEdge: HomeEdge;
HomeError: HomeError;
HomeItem: HomeItem;
HomeItemSource: HomeItemSource;
HomeResult: ResolversParentTypes['HomeError'] | ResolversParentTypes['HomeSuccess'];
HomeSection: HomeSection;
HomeSubscription: HomeSubscription;
HomeSuccess: HomeSuccess;
ID: Scalars['ID'];
ImportFromIntegrationError: ImportFromIntegrationError;
@ -6034,7 +6043,7 @@ export type HomeItemResolvers<ContextType = ResolverContext, ParentType extends
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>;
subscription?: Resolver<Maybe<ResolversTypes['HomeItemSource']>, ParentType, ContextType>;
thumbnail?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
title?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
url?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
@ -6042,6 +6051,15 @@ export type HomeItemResolvers<ContextType = ResolverContext, ParentType extends
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type HomeItemSourceResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['HomeItemSource'] = ResolversParentTypes['HomeItemSource']> = {
icon?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
id?: Resolver<Maybe<ResolversTypes['ID']>, ParentType, ContextType>;
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
type?: Resolver<ResolversTypes['HomeItemSourceType'], ParentType, ContextType>;
url?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type HomeResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['HomeResult'] = ResolversParentTypes['HomeResult']> = {
__resolveType: TypeResolveFn<'HomeError' | 'HomeSuccess', ParentType, ContextType>;
};
@ -6054,14 +6072,6 @@ export type HomeSectionResolvers<ContextType = ResolverContext, ParentType exten
__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>;
@ -7653,9 +7663,9 @@ export type Resolvers<ContextType = ResolverContext> = {
HomeEdge?: HomeEdgeResolvers<ContextType>;
HomeError?: HomeErrorResolvers<ContextType>;
HomeItem?: HomeItemResolvers<ContextType>;
HomeItemSource?: HomeItemSourceResolvers<ContextType>;
HomeResult?: HomeResultResolvers<ContextType>;
HomeSection?: HomeSectionResolvers<ContextType>;
HomeSubscription?: HomeSubscriptionResolvers<ContextType>;
HomeSuccess?: HomeSuccessResolvers<ContextType>;
ImportFromIntegrationError?: ImportFromIntegrationErrorResolvers<ContextType>;
ImportFromIntegrationResult?: ImportFromIntegrationResultResolvers<ContextType>;

View File

@ -1172,13 +1172,28 @@ type HomeItem {
previewContent: String
saveCount: Int
seen_at: Date
subscription: HomeSubscription!
subscription: HomeItemSource
thumbnail: String
title: String!
url: String!
wordCount: Int
}
type HomeItemSource {
icon: String
id: ID
name: String!
type: HomeItemSourceType!
url: String
}
enum HomeItemSourceType {
LIBRARY
NEWSLETTER
RECOMMENDATION
RSS
}
union HomeResult = HomeError | HomeSuccess
type HomeSection {
@ -1188,13 +1203,6 @@ type HomeSection {
title: String
}
type HomeSubscription {
icon: String
id: ID!
name: String!
url: String
}
type HomeSuccess {
edges: [HomeEdge!]!
pageInfo: PageInfo!

View File

@ -66,7 +66,7 @@ const libraryItemToCandidate = (
dir: item.directionality || 'ltr',
date: item.createdAt,
topic: item.topic,
wordCount: item.wordCount!,
wordCount: item.wordCount || 0,
siteName: item.siteName || undefined,
siteIcon: item.siteIcon || undefined,
folder: item.folder,
@ -91,8 +91,9 @@ const publicItemToCandidate = (item: PublicItem): Candidate => ({
dir: item.dir || 'ltr',
date: item.createdAt,
topic: item.topic,
wordCount: item.wordCount!,
wordCount: item.wordCount || 0,
siteIcon: item.siteIcon,
siteName: item.siteName,
publishedAt: item.publishedAt,
subscription: {
name: item.source.name,

View File

@ -4,11 +4,14 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { createHmac } from 'crypto'
import { isError } from 'lodash'
import { Highlight as HighlightEntity } from '../entity/highlight'
import { LibraryItem } from '../entity/library_item'
import {
EXISTING_NEWSLETTER_FOLDER,
NewsletterEmail,
} from '../entity/newsletter_email'
import { PublicItem } from '../entity/public_item'
import {
DEFAULT_SUBSCRIPTION_FOLDER,
Subscription,
@ -17,6 +20,7 @@ import { env } from '../env'
import {
Article,
Highlight,
HomeItem,
Label,
PageType,
Recommendation,
@ -626,11 +630,87 @@ export const functionResolvers = {
},
HomeSection: {
async items(
section: { items: Array<{ id: string }> },
section: {
items: Array<{ id: string; type: 'library_item' | 'public_item' }>
},
_: unknown,
ctx: WithDataSourcesContext
) {
return ctx.dataLoaders.homeItems.loadMany(section.items.map((i) => i.id))
const libraryItemIds = section.items
.filter((item) => item.type === 'library_item')
.map((item) => item.id)
const libraryItems = (
await ctx.dataLoaders.libraryItems.loadMany(libraryItemIds)
).filter((libraryItem) => !isError(libraryItem)) as Array<LibraryItem>
const publicItemIds = section.items
.filter((item) => item.type === 'public_item')
.map((item) => item.id)
const publicItems = (
await ctx.dataLoaders.publicItems.loadMany(publicItemIds)
).filter((publicItem) => !isError(publicItem)) as Array<PublicItem>
return libraryItems
.map(
(libraryItem) =>
({
id: libraryItem.id,
title: libraryItem.title,
author: libraryItem.author,
thumbnail: libraryItem.thumbnail,
wordCount: libraryItem.wordCount,
date: libraryItem.savedAt,
url: libraryItem.originalUrl,
canArchive: !libraryItem.archivedAt,
canDelete: !libraryItem.deletedAt,
canSave: false,
dir: libraryItem.directionality,
subscription: null,
previewContent: libraryItem.description,
} as HomeItem)
)
.concat(
publicItems.map(
(publicItem) =>
({
id: publicItem.id,
title: publicItem.title,
author: publicItem.author,
dir: publicItem.dir,
previewContent: publicItem.previewContent,
thumbnail: publicItem.thumbnail,
wordCount: publicItem.wordCount,
date: publicItem.createdAt,
url: publicItem.url,
canArchive: false,
canDelete: false,
canSave: true,
broadcastCount: publicItem.stats.broadcastCount,
likeCount: publicItem.stats.likeCount,
saveCount: publicItem.stats.saveCount,
subscription: publicItem.source,
} as HomeItem)
)
)
},
},
HomeItem: {
async subscription(
item: HomeItem,
_: unknown,
ctx: WithDataSourcesContext
) {
// if (item.subscription) {
// return item.subscription
// }
// const subscription = await ctx.dataLoaders.subscriptions.load(item.id)
// if (!subscription) {
// return {
// name: item.siteName,
// icon: item.siteIcon,
// type: 'rss',
// }
// }
},
},
...resultResolveTypeResolver('Login'),

View File

@ -28,7 +28,7 @@ export const homeResolver = authorized<
HomeError,
QueryHomeArgs
>(async (_, { first, after }, { uid, log }) => {
const limit = first || 10
const limit = first || 6
const cursor = after ? parseInt(after) : undefined
const sections = await getHomeSections(uid, limit, cursor)

View File

@ -8,6 +8,8 @@ import winston from 'winston'
import { ReadingProgressDataSource } from '../datasources/reading_progress_data_source'
import { Highlight } from '../entity/highlight'
import { Label } from '../entity/label'
import { LibraryItem } from '../entity/library_item'
import { PublicItem } from '../entity/public_item'
import { Recommendation } from '../entity/recommendation'
import { UploadFile } from '../entity/upload_file'
import { HomeItem } from '../generated/graphql'
@ -52,7 +54,8 @@ export interface RequestContext {
highlights: DataLoader<string, Highlight[]>
recommendations: DataLoader<string, Recommendation[]>
uploadFiles: DataLoader<string, UploadFile | undefined>
homeItems: DataLoader<string, HomeItem>
libraryItems: DataLoader<string, LibraryItem>
publicItems: DataLoader<string, PublicItem>
}
}

View File

@ -3101,11 +3101,19 @@ const schema = gql`
SUBSCRIBE
}
type HomeSubscription {
id: ID!
enum HomeItemSourceType {
RSS
NEWSLETTER
RECOMMENDATION
LIBRARY
}
type HomeItemSource {
id: ID
name: String!
url: String
icon: String
type: HomeItemSourceType!
}
type HomeItem {
@ -3122,7 +3130,7 @@ const schema = gql`
dir: String
seen_at: Date
wordCount: Int
subscription: HomeSubscription!
subscription: HomeItemSource
canSave: Boolean
canComment: Boolean
canShare: Boolean

View File

@ -3,6 +3,18 @@ import { HomeItem } from '../generated/graphql'
import { authTrx } from '../repository'
import { findLibraryItemsByIds } from './library_item'
export const batchGetPublicItems = async (
ids: readonly string[]
): Promise<Array<PublicItem>> => {
return authTrx(async (tx) =>
tx
.getRepository(PublicItem)
.createQueryBuilder('public_item')
.where('public_item.id IN (:...ids)', { ids })
.getMany()
)
}
export const batchGetHomeItems = async (
ids: readonly string[]
): Promise<Array<HomeItem>> => {
@ -38,11 +50,13 @@ export const batchGetHomeItems = async (
canDelete: !libraryItem.deletedAt,
canSave: false,
dir: libraryItem.directionality,
}
subscription: null,
previewContent: libraryItem.description,
} as HomeItem
} else {
const publicItem = publicItems.find((pi) => pi.id === id)
return publicItem
? {
? ({
...publicItem,
date: publicItem.createdAt,
url: publicItem.url,
@ -53,7 +67,7 @@ export const batchGetHomeItems = async (
likeCount: publicItem.stats.likeCount,
saveCount: publicItem.stats.saveCount,
subscription: publicItem.source,
}
} as HomeItem)
: undefined
}
})

View File

@ -140,6 +140,10 @@ interface Select {
const readingProgressDataSource = new ReadingProgressDataSource()
export const batchGetLibraryItems = async (ids: readonly string[]) => {
return findLibraryItemsByIds(ids as string[])
}
export const getItemUrl = (id: string) => `${env.client.url}/me/${id}`
const markItemAsRead = async (libraryItemId: string, userId: string) => {

View File

@ -17,4 +17,6 @@ ALTER TABLE omnivore.library_item
DROP COLUMN topic,
DROP COLUMN score;
DROP EXTENSION LTREE;
COMMIT;