From e131afc183de1051ab0a042659a7e2bd70eaf7fd Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 28 May 2024 14:45:33 +0800 Subject: [PATCH] update API --- packages/api/src/apollo.ts | 6 +- packages/api/src/generated/graphql.ts | 52 +++++++----- packages/api/src/generated/schema.graphql | 24 ++++-- packages/api/src/jobs/update_home.ts | 5 +- .../api/src/resolvers/function_resolvers.ts | 84 ++++++++++++++++++- packages/api/src/resolvers/home/index.ts | 2 +- packages/api/src/resolvers/types.ts | 5 +- packages/api/src/schema.ts | 14 +++- packages/api/src/services/home.ts | 20 ++++- packages/api/src/services/library_item.ts | 4 + .../db/migrations/0177.undo.public_item.sql | 2 + 11 files changed, 175 insertions(+), 43 deletions(-) diff --git a/packages/api/src/apollo.ts b/packages/api/src/apollo.ts index d4885fa7e..d929d5c64 100644 --- a/packages/api/src/apollo.ts +++ b/packages/api/src/apollo.ts @@ -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 = async ({ batchGetRecommendationsFromLibraryItemIds ), uploadFiles: new DataLoader(batchGetUploadFilesByIds), - homeItems: new DataLoader(batchGetHomeItems), + libraryItems: new DataLoader(batchGetLibraryItems), + publicItems: new DataLoader(batchGetPublicItems), }, } diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index a3f839346..6a80b3f07 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -1303,13 +1303,29 @@ export type HomeItem = { previewContent?: Maybe; saveCount?: Maybe; seen_at?: Maybe; - subscription: HomeSubscription; + subscription?: Maybe; thumbnail?: Maybe; title: Scalars['String']; url: Scalars['String']; wordCount?: Maybe; }; +export type HomeItemSource = { + __typename?: 'HomeItemSource'; + icon?: Maybe; + id?: Maybe; + name: Scalars['String']; + type: HomeItemSourceType; + url?: Maybe; +}; + +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; }; -export type HomeSubscription = { - __typename?: 'HomeSubscription'; - icon?: Maybe; - id: Scalars['ID']; - name: Scalars['String']; - url?: Maybe; -}; - export type HomeSuccess = { __typename?: 'HomeSuccess'; edges: Array; @@ -4259,9 +4267,10 @@ export type ResolversTypes = { HomeError: ResolverTypeWrapper; HomeErrorCode: HomeErrorCode; HomeItem: ResolverTypeWrapper; + HomeItemSource: ResolverTypeWrapper; + HomeItemSourceType: HomeItemSourceType; HomeResult: ResolversTypes['HomeError'] | ResolversTypes['HomeSuccess']; HomeSection: ResolverTypeWrapper; - HomeSubscription: ResolverTypeWrapper; HomeSuccess: ResolverTypeWrapper; ID: ResolverTypeWrapper; ImportFromIntegrationError: ResolverTypeWrapper; @@ -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, ParentType, ContextType>; saveCount?: Resolver, ParentType, ContextType>; seen_at?: Resolver, ParentType, ContextType>; - subscription?: Resolver; + subscription?: Resolver, ParentType, ContextType>; thumbnail?: Resolver, ParentType, ContextType>; title?: Resolver; url?: Resolver; @@ -6042,6 +6051,15 @@ export type HomeItemResolvers; }; +export type HomeItemSourceResolvers = { + icon?: Resolver, ParentType, ContextType>; + id?: Resolver, ParentType, ContextType>; + name?: Resolver; + type?: Resolver; + url?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type HomeResultResolvers = { __resolveType: TypeResolveFn<'HomeError' | 'HomeSuccess', ParentType, ContextType>; }; @@ -6054,14 +6072,6 @@ export type HomeSectionResolvers; }; -export type HomeSubscriptionResolvers = { - icon?: Resolver, ParentType, ContextType>; - id?: Resolver; - name?: Resolver; - url?: Resolver, ParentType, ContextType>; - __isTypeOf?: IsTypeOfResolverFn; -}; - export type HomeSuccessResolvers = { edges?: Resolver, ParentType, ContextType>; pageInfo?: Resolver; @@ -7653,9 +7663,9 @@ export type Resolvers = { HomeEdge?: HomeEdgeResolvers; HomeError?: HomeErrorResolvers; HomeItem?: HomeItemResolvers; + HomeItemSource?: HomeItemSourceResolvers; HomeResult?: HomeResultResolvers; HomeSection?: HomeSectionResolvers; - HomeSubscription?: HomeSubscriptionResolvers; HomeSuccess?: HomeSuccessResolvers; ImportFromIntegrationError?: ImportFromIntegrationErrorResolvers; ImportFromIntegrationResult?: ImportFromIntegrationResultResolvers; diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index 28e5d7435..2b09a639f 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -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! diff --git a/packages/api/src/jobs/update_home.ts b/packages/api/src/jobs/update_home.ts index 4189def79..26561795b 100644 --- a/packages/api/src/jobs/update_home.ts +++ b/packages/api/src/jobs/update_home.ts @@ -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, diff --git a/packages/api/src/resolvers/function_resolvers.ts b/packages/api/src/resolvers/function_resolvers.ts index 9617db3e7..213a774b7 100644 --- a/packages/api/src/resolvers/function_resolvers.ts +++ b/packages/api/src/resolvers/function_resolvers.ts @@ -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 + + 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 + + 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'), diff --git a/packages/api/src/resolvers/home/index.ts b/packages/api/src/resolvers/home/index.ts index 3716fd861..a153fd5c0 100644 --- a/packages/api/src/resolvers/home/index.ts +++ b/packages/api/src/resolvers/home/index.ts @@ -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) diff --git a/packages/api/src/resolvers/types.ts b/packages/api/src/resolvers/types.ts index 3c6d76f25..ef6adfeff 100644 --- a/packages/api/src/resolvers/types.ts +++ b/packages/api/src/resolvers/types.ts @@ -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 recommendations: DataLoader uploadFiles: DataLoader - homeItems: DataLoader + libraryItems: DataLoader + publicItems: DataLoader } } diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index 8ea86abab..ed74e5509 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -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 diff --git a/packages/api/src/services/home.ts b/packages/api/src/services/home.ts index f53c07eee..c70a98003 100644 --- a/packages/api/src/services/home.ts +++ b/packages/api/src/services/home.ts @@ -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> => { + 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> => { @@ -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 } }) diff --git a/packages/api/src/services/library_item.ts b/packages/api/src/services/library_item.ts index d24ce4dff..b90aa0e80 100644 --- a/packages/api/src/services/library_item.ts +++ b/packages/api/src/services/library_item.ts @@ -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) => { diff --git a/packages/db/migrations/0177.undo.public_item.sql b/packages/db/migrations/0177.undo.public_item.sql index 16e0e0d40..cb068a36c 100755 --- a/packages/db/migrations/0177.undo.public_item.sql +++ b/packages/db/migrations/0177.undo.public_item.sql @@ -17,4 +17,6 @@ ALTER TABLE omnivore.library_item DROP COLUMN topic, DROP COLUMN score; +DROP EXTENSION LTREE; + COMMIT;