From 2e26a04cd09736131a1caaf88fa74dd790dd83fa Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 20 Jun 2024 15:47:52 +0800 Subject: [PATCH 1/2] remove redundant context type --- .../api/src/resolvers/function_resolvers.ts | 86 +++------- packages/api/src/resolvers/report/index.ts | 4 +- packages/api/src/resolvers/types.ts | 5 - packages/api/src/resolvers/user/index.ts | 16 +- .../src/resolvers/user_feed_article/index.ts | 150 ------------------ .../api/src/resolvers/user_friends/index.ts | 101 ------------ packages/api/src/utils/gql-utils.ts | 6 +- 7 files changed, 39 insertions(+), 329 deletions(-) delete mode 100644 packages/api/src/resolvers/user_feed_article/index.ts delete mode 100644 packages/api/src/resolvers/user_friends/index.ts diff --git a/packages/api/src/resolvers/function_resolvers.ts b/packages/api/src/resolvers/function_resolvers.ts index febe9deb0..a406ae707 100644 --- a/packages/api/src/resolvers/function_resolvers.ts +++ b/packages/api/src/resolvers/function_resolvers.ts @@ -157,7 +157,7 @@ import { } from './recent_emails' import { recentSearchesResolver } from './recent_searches' import { subscriptionResolver } from './subscriptions' -import { WithDataSourcesContext } from './types' +import { ResolverContext } from './types' import { updateEmailResolver } from './user' /* eslint-disable @typescript-eslint/naming-convention */ @@ -180,7 +180,7 @@ const readingProgressHandlers = { async readingProgressPercent( article: LibraryItem, _: unknown, - ctx: WithDataSourcesContext + ctx: ResolverContext ) { if (ctx.claims?.uid) { const readingProgress = @@ -200,7 +200,7 @@ const readingProgressHandlers = { async readingProgressAnchorIndex( article: LibraryItem, _: unknown, - ctx: WithDataSourcesContext + ctx: ResolverContext ) { if (ctx.claims?.uid) { const readingProgress = @@ -220,7 +220,7 @@ const readingProgressHandlers = { async readingProgressTopPercent( article: LibraryItem, _: unknown, - ctx: WithDataSourcesContext + ctx: ResolverContext ) { if (ctx.claims?.uid) { const readingProgress = @@ -364,11 +364,7 @@ export const functionResolvers = { } return undefined }, - async features( - _: User, - __: Record, - ctx: WithDataSourcesContext - ) { + async features(_: User, __: Record, ctx: ResolverContext) { if (!ctx.claims?.uid) { return undefined } @@ -378,7 +374,7 @@ export const functionResolvers = { async featureList( _: User, __: Record, - ctx: WithDataSourcesContext + ctx: ResolverContext ) { if (!ctx.claims?.uid) { return undefined @@ -398,7 +394,7 @@ export const functionResolvers = { sharedNotesCount: () => 0, }, Article: { - async url(article: LibraryItem, _: unknown, ctx: WithDataSourcesContext) { + async url(article: LibraryItem, _: unknown, ctx: ResolverContext) { if ( (article.itemType == PageType.File || article.itemType == PageType.Book) && @@ -439,20 +435,12 @@ export const functionResolvers = { ? wordsCount(article.readableContent) : undefined }, - async labels( - article: LibraryItem, - _: unknown, - ctx: WithDataSourcesContext - ) { + async labels(article: LibraryItem, _: unknown, ctx: ResolverContext) { if (article.labels) return article.labels return ctx.dataLoaders.labels.load(article.id) }, - async highlights( - article: LibraryItem, - _: unknown, - ctx: WithDataSourcesContext - ) { + async highlights(article: LibraryItem, _: unknown, ctx: ResolverContext) { if (article.highlights) return article.highlights return ctx.dataLoaders.highlights.load(article.id) @@ -468,35 +456,27 @@ export const functionResolvers = { reactions: () => [], replies: () => [], type: (highlight: Highlight) => highlight.highlightType, - async user(highlight: Highlight, __: unknown, ctx: WithDataSourcesContext) { + async user(highlight: Highlight, __: unknown, ctx: ResolverContext) { return ctx.dataLoaders.users.load(highlight.userId) }, - createdByMe( - highlight: Highlight, - __: unknown, - ctx: WithDataSourcesContext - ) { - return highlight.userId === ctx.uid + createdByMe(highlight: Highlight, __: unknown, ctx: ResolverContext) { + return highlight.userId === ctx.claims?.uid }, - libraryItem(highlight: Highlight, _: unknown, ctx: WithDataSourcesContext) { + libraryItem(highlight: Highlight, _: unknown, ctx: ResolverContext) { if (highlight.libraryItem) { return highlight.libraryItem } return ctx.dataLoaders.libraryItems.load(highlight.libraryItemId) }, - labels: async ( - highlight: Highlight, - _: unknown, - ctx: WithDataSourcesContext - ) => { + labels: async (highlight: Highlight, _: unknown, ctx: ResolverContext) => { return ( highlight.labels || ctx.dataLoaders.highlightLabels.load(highlight.id) ) }, }, SearchItem: { - async url(item: LibraryItem, _: unknown, ctx: WithDataSourcesContext) { + async url(item: LibraryItem, _: unknown, ctx: ResolverContext) { if ( (item.itemType == PageType.File || item.itemType == PageType.Book) && ctx.claims && @@ -528,47 +508,33 @@ export const functionResolvers = { return item.siteIcon }, - async labels(item: LibraryItem, _: unknown, ctx: WithDataSourcesContext) { + async labels(item: LibraryItem, _: unknown, ctx: ResolverContext) { if (item.labels) return item.labels return ctx.dataLoaders.labels.load(item.id) }, - async recommendations( - item: LibraryItem, - _: unknown, - ctx: WithDataSourcesContext - ) { + async recommendations(item: LibraryItem, _: unknown, ctx: ResolverContext) { if (item.recommendations) return item.recommendations return ctx.dataLoaders.recommendations.load(item.id) }, - async aiSummary( - item: LibraryItem, - _: unknown, - ctx: WithDataSourcesContext - ) { + async aiSummary(item: LibraryItem, _: unknown, ctx: ResolverContext) { + if (!ctx.claims) return undefined + return ( await getAISummary({ - userId: ctx.uid, + userId: ctx.claims.uid, libraryItemId: item.id, idx: 'latest', }) )?.summary }, - async highlights( - item: LibraryItem, - _: unknown, - ctx: WithDataSourcesContext - ) { + async highlights(item: LibraryItem, _: unknown, ctx: ResolverContext) { if (item.highlights) return item.highlights return ctx.dataLoaders.highlights.load(item.id) }, - async content( - item: PartialLibraryItem, - _: unknown, - ctx: WithDataSourcesContext - ) { + async content(item: PartialLibraryItem, _: unknown, ctx: ResolverContext) { // convert html to the requested format if requested if ( item.format && @@ -658,7 +624,7 @@ export const functionResolvers = { }> }, _: unknown, - ctx: WithDataSourcesContext + ctx: ResolverContext ) { const items = section.items @@ -745,7 +711,7 @@ export const functionResolvers = { { subscription?: string; siteName: string; siteIcon?: string } >, _: unknown, - ctx: WithDataSourcesContext + ctx: ResolverContext ): Promise { if (item.source) { return item.source @@ -785,7 +751,7 @@ export const functionResolvers = { ArticleSavingRequest: { status: (item: LibraryItem) => item.state, url: (item: LibraryItem) => item.originalUrl, - async user(_item: LibraryItem, __: unknown, ctx: WithDataSourcesContext) { + async user(_item: LibraryItem, __: unknown, ctx: ResolverContext) { if (ctx.claims?.uid) { return ctx.dataLoaders.users.load(ctx.claims.uid) } diff --git a/packages/api/src/resolvers/report/index.ts b/packages/api/src/resolvers/report/index.ts index 6ccbf76a5..e4b5eeb90 100644 --- a/packages/api/src/resolvers/report/index.ts +++ b/packages/api/src/resolvers/report/index.ts @@ -11,7 +11,7 @@ import { saveContentDisplayReport, } from '../../services/reports' import { analytics } from '../../utils/analytics' -import { WithDataSourcesContext } from '../types' +import { ResolverContext } from '../types' const SUCCESS_MESSAGE = `Your report has been submitted. Thank you.` const FAILURE_MESSAGE = @@ -36,7 +36,7 @@ const isContentDisplayReport = (types: ReportType[]): boolean => { export const reportItemResolver: ResolverFn< ReportItemResult, unknown, - WithDataSourcesContext, + ResolverContext, MutationReportItemArgs > = async (_obj, args, ctx) => { const { sharedBy, reportTypes } = args.input diff --git a/packages/api/src/resolvers/types.ts b/packages/api/src/resolvers/types.ts index 16857d85d..47fa91954 100644 --- a/packages/api/src/resolvers/types.ts +++ b/packages/api/src/resolvers/types.ts @@ -14,7 +14,6 @@ import { Recommendation } from '../entity/recommendation' import { Subscription } from '../entity/subscription' import { UploadFile } from '../entity/upload_file' import { User } from '../entity/user' -import { HomeItem } from '../generated/graphql' import { PubsubClient } from '../pubsub' export interface Claims { @@ -65,7 +64,3 @@ export interface RequestContext { } export type ResolverContext = ApolloContext - -export type WithDataSourcesContext = { - uid: string -} & ResolverContext diff --git a/packages/api/src/resolvers/user/index.ts b/packages/api/src/resolvers/user/index.ts index 6572c1f10..9c496df8c 100644 --- a/packages/api/src/resolvers/user/index.ts +++ b/packages/api/src/resolvers/user/index.ts @@ -45,7 +45,7 @@ import { softDeleteUser } from '../../services/user' import { Merge } from '../../util' import { authorized } from '../../utils/gql-utils' import { validateUsername } from '../../utils/usernamePolicy' -import { WithDataSourcesContext } from '../types' +import { ResolverContext } from '../types' export const updateUserResolver = authorized< Merge, @@ -145,7 +145,7 @@ export const updateUserProfileResolver = authorized< export const googleLoginResolver: ResolverFn< Merge, unknown, - WithDataSourcesContext, + ResolverContext, MutationGoogleLoginArgs > = async (_obj, { input }, { setAuth }) => { const { email, secret } = input @@ -172,7 +172,7 @@ export const googleLoginResolver: ResolverFn< export const validateUsernameResolver: ResolverFn< boolean, Record, - WithDataSourcesContext, + ResolverContext, QueryValidateUsernameArgs > = async (_obj, { username }) => { const lowerCasedUsername = username.toLowerCase() @@ -191,7 +191,7 @@ export const validateUsernameResolver: ResolverFn< export const googleSignupResolver: ResolverFn< Merge, Record, - WithDataSourcesContext, + ResolverContext, MutationGoogleSignupArgs > = async (_obj, { input }, { setAuth, log }) => { const { email, username, name, bio, sourceUserId, pictureUrl, secret } = input @@ -231,7 +231,7 @@ export const googleSignupResolver: ResolverFn< export const logOutResolver: ResolverFn< LogOutResult, unknown, - WithDataSourcesContext, + ResolverContext, unknown > = (_, __, { clearAuth, log }) => { try { @@ -246,7 +246,7 @@ export const logOutResolver: ResolverFn< export const getMeUserResolver: ResolverFn< UserEntity | undefined, unknown, - WithDataSourcesContext, + ResolverContext, unknown > = async (_obj, __, { claims }) => { try { @@ -268,9 +268,9 @@ export const getMeUserResolver: ResolverFn< export const getUserResolver: ResolverFn< Merge, unknown, - WithDataSourcesContext, + ResolverContext, QueryUserArgs -> = async (_obj, { userId: id, username }, { uid }) => { +> = async (_obj, { userId: id, username }) => { if (!(id || username)) { return { errorCodes: [UserErrorCode.BadRequest] } } diff --git a/packages/api/src/resolvers/user_feed_article/index.ts b/packages/api/src/resolvers/user_feed_article/index.ts deleted file mode 100644 index 928078f9d..000000000 --- a/packages/api/src/resolvers/user_feed_article/index.ts +++ /dev/null @@ -1,150 +0,0 @@ -/* eslint-disable @typescript-eslint/require-await */ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { FeedArticle, PageInfo } from '../../generated/graphql' - -export type PartialFeedArticle = Omit< - FeedArticle, - 'sharedBy' | 'article' | 'reactions' -> - -type PaginatedFeedArticlesSuccessPartial = { - edges: { cursor: string; node: PartialFeedArticle }[] - pageInfo: PageInfo -} - -// export const getSharedArticleResolver: ResolverFn< -// SharedArticleSuccessPartial | SharedArticleError, -// Record, -// WithDataSourcesContext, -// QuerySharedArticleArgs -// > = async (_obj, { username, slug, selectedHighlightId }, { kx, models }) => { -// try { -// const user = await models.user.getWhere({ username }) -// if (!user) { -// return { -// errorCodes: [SharedArticleErrorCode.NotFound], -// } -// } - -// const article = await models.userArticle.getBySlug(username, slug) -// if (!article || !article.sharedAt) { -// return { -// errorCodes: [SharedArticleErrorCode.NotFound], -// } -// } - -// if (selectedHighlightId) { -// const highlightResult = await models.highlight.getWhereIn('shortId', [ -// selectedHighlightId, -// ]) -// if (!highlightResult || !highlightResult[0].sharedAt) { -// return { -// errorCodes: [SharedArticleErrorCode.NotFound], -// } -// } -// } - -// const shareInfo = await getShareInfoForArticle( -// kx, -// user.id, -// article.id, -// models -// ) - -// return { article: { ...article, userId: user.id, shareInfo: shareInfo } } -// } catch (error) { -// return { errorCodes: [SharedArticleErrorCode.NotFound] } -// } -// } - -// export const getUserFeedArticlesResolver: ResolverFn< -// PaginatedFeedArticlesSuccessPartial, -// unknown, -// WithDataSourcesContext, -// QueryFeedArticlesArgs -// > = async ( -// _obj, -// { after: _startCursor, first: _first, sharedByUser }, -// { models, claims, authTrx } -// ) => { -// if (!(sharedByUser || claims?.uid)) { -// return { -// edges: [], -// pageInfo: { -// startCursor: '', -// endCursor: '', -// hasNextPage: false, -// hasPreviousPage: false, -// }, -// } -// } - -// const first = _first || 0 -// const startCursor = _startCursor || '' - -// const feedArticles = -// (await authTrx((tx) => -// models.userArticle.getUserFeedArticlesPaginatedWithHighlights( -// { cursor: startCursor, first: first + 1, sharedByUser }, // fetch one more item to get next cursor -// claims?.uid || '', -// tx -// ) -// )) || [] - -// const endCursor = feedArticles[feedArticles.length - 1]?.sharedAt -// .getTime() -// ?.toString() -// const hasNextPage = feedArticles.length > first - -// if (hasNextPage) { -// // remove an extra if exists -// feedArticles.pop() -// } - -// const edges = feedArticles.map((fa) => { -// return { -// node: fa, -// cursor: fa.sharedAt.getTime()?.toString(), -// } -// }) - -// return { -// edges, -// pageInfo: { -// hasPreviousPage: false, -// startCursor: '', -// hasNextPage, -// endCursor, -// }, -// } -// } - -// export const updateSharedCommentResolver = authorized< -// UpdateSharedCommentSuccess, -// UpdateSharedCommentError, -// MutationUpdateSharedCommentArgs -// >( -// async ( -// _, -// { input: { articleID, sharedComment } }, -// { models, authTrx, claims: { uid } } -// ) => { -// const ua = await authTrx((tx) => -// models.userArticle.getByParameters(uid, { articleId: articleID }, tx) -// ) -// if (!ua) { -// return { errorCodes: [UpdateSharedCommentErrorCode.NotFound] } -// } - -// await authTrx((tx) => -// models.userArticle.updateByArticleId( -// uid, -// articleID, -// { sharedComment }, -// tx -// ) -// ) - -// return { articleID, sharedComment } -// } -// ) diff --git a/packages/api/src/resolvers/user_friends/index.ts b/packages/api/src/resolvers/user_friends/index.ts deleted file mode 100644 index 823184066..000000000 --- a/packages/api/src/resolvers/user_friends/index.ts +++ /dev/null @@ -1,101 +0,0 @@ -// export const setFollowResolver = authorized< -// SetFollowSuccess, -// SetFollowError, -// MutationSetFollowArgs -// >( -// async ( -// _, -// { input: { userId: friendUserId, follow } }, -// { models, authTrx, claims: { uid } } -// ) => { -// const user = await models.user.getUserDetails(uid, friendUserId) -// if (!user) return { errorCodes: [SetFollowErrorCode.NotFound] } - -// const userFriendRecord = await authTrx((tx) => -// models.userFriends.getByUserFriendId(uid, friendUserId, tx) -// ) - -// if (follow) { -// if (!userFriendRecord) { -// await authTrx((tx) => -// models.userFriends.create({ friendUserId, userId: uid }, tx) -// ) -// } -// } else if (userFriendRecord) { -// await authTrx((tx) => models.userFriends.delete(userFriendRecord.id, tx)) -// } - -// const updatedUser = await models.user.getUserDetails(uid, friendUserId) -// if (!updatedUser) return { errorCodes: [SetFollowErrorCode.NotFound] } - -// return { -// updatedUser: { -// ...userDataToUser(updatedUser), -// isFriend: updatedUser.viewerIsFollowing, -// }, -// } -// } -// ) - -// const getUserList = async ( -// uid: string, -// users: UserData[], -// models: DataModels, -// authTrx: ( -// cb: (tx: Knex.Transaction) => TResult, -// userRole?: string -// ) => Promise -// ): Promise => { -// const usersIds = users.map(({ id }) => id) -// const friends = await authTrx((tx) => -// models.userFriends.getByFriendIds(uid, usersIds, tx) -// ) - -// const friendsIds = friends.map(({ friendUserId }) => friendUserId) -// users = users.map((f) => ({ -// ...f, -// isFriend: friendsIds.includes(f.id), -// viewerIsFollowing: friendsIds.includes(f.id), -// })) - -// return users.map((u) => userDataToUser(u)) -// } - -// export const getFollowersResolver: ResolverFn< -// GetFollowersResult, -// unknown, -// WithDataSourcesContext, -// QueryGetFollowersArgs -// > = async (_parent, { userId }, { models, claims, authTrx }) => { -// const followers = userId -// ? await authTrx((tx) => models.user.getUserFollowersList(userId, tx)) -// : [] -// if (!claims?.uid) return { followers: usersWithNoFriends(followers) } -// return { -// followers: await getUserList(claims?.uid, followers, models, authTrx), -// } -// } - -// export const getFollowingResolver: ResolverFn< -// GetFollowingResult, -// unknown, -// WithDataSourcesContext, -// QueryGetFollowingArgs -// > = async (_parent, { userId }, { models, claims, authTrx }) => { -// const following = userId -// ? await authTrx((tx) => models.user.getUserFollowingList(userId, tx)) -// : [] -// if (!claims?.uid) return { following: usersWithNoFriends(following) } -// return { -// following: await getUserList(claims?.uid, following, models, authTrx), -// } -// } - -// const usersWithNoFriends = (users: UserData[]): User[] => { -// return users.map((f) => -// userDataToUser({ -// ...f, -// isFriend: false, -// } as UserData) -// ) -// } diff --git a/packages/api/src/utils/gql-utils.ts b/packages/api/src/utils/gql-utils.ts index ef5986f28..62bbf2d09 100644 --- a/packages/api/src/utils/gql-utils.ts +++ b/packages/api/src/utils/gql-utils.ts @@ -1,5 +1,5 @@ import { ResolverFn } from '../generated/graphql' -import { Claims, WithDataSourcesContext } from '../resolvers/types' +import { Claims, ResolverContext } from '../resolvers/types' export function authorized< TSuccess, @@ -12,10 +12,10 @@ export function authorized< resolver: ResolverFn< TSuccess | TError, TParent, - WithDataSourcesContext & { claims: Claims }, + ResolverContext & { claims: Claims; uid: string }, TArgs > -): ResolverFn { +): ResolverFn { return (parent, args, ctx, info) => { const { claims } = ctx if (claims?.uid) { From 40182421f740be4d6542c3b53762949cae8a80db Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 20 Jun 2024 16:31:05 +0800 Subject: [PATCH 2/2] show archived highlights --- .../api/src/resolvers/function_resolvers.ts | 11 ++++++-- packages/api/src/services/highlights.ts | 1 - packages/api/src/services/library_item.ts | 27 ++++--------------- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/packages/api/src/resolvers/function_resolvers.ts b/packages/api/src/resolvers/function_resolvers.ts index a406ae707..67624303a 100644 --- a/packages/api/src/resolvers/function_resolvers.ts +++ b/packages/api/src/resolvers/function_resolvers.ts @@ -6,7 +6,7 @@ import { createHmac } from 'crypto' import { isError } from 'lodash' import { Highlight } from '../entity/highlight' -import { LibraryItem } from '../entity/library_item' +import { LibraryItem, LibraryItemState } from '../entity/library_item' import { EXISTING_NEWSLETTER_FOLDER, NewsletterEmail, @@ -634,7 +634,14 @@ export const functionResolvers = { const libraryItems = ( await ctx.dataLoaders.libraryItems.loadMany(libraryItemIds) ).filter( - (libraryItem) => !!libraryItem && !isError(libraryItem) + (libraryItem) => + !!libraryItem && + !isError(libraryItem) && + [ + LibraryItemState.Succeeded, + LibraryItemState.ContentNotFetched, + ].includes(libraryItem.state) && + !libraryItem.seenAt ) as Array const publicItemIds = section.items diff --git a/packages/api/src/services/highlights.ts b/packages/api/src/services/highlights.ts index 327038aca..533d29eb2 100644 --- a/packages/api/src/services/highlights.ts +++ b/packages/api/src/services/highlights.ts @@ -26,7 +26,6 @@ export const batchGetHighlightsFromLibraryItemIds = async ( const highlights = await authTrx(async (tx) => tx.getRepository(Highlight).find({ where: { libraryItem: { id: In(libraryItemIds as string[]) } }, - relations: ['user'], }) ) diff --git a/packages/api/src/services/library_item.ts b/packages/api/src/services/library_item.ts index 4ed0136a6..4eee6af0c 100644 --- a/packages/api/src/services/library_item.ts +++ b/packages/api/src/services/library_item.ts @@ -6,7 +6,6 @@ import { EntityManager, FindOptionsWhere, In, - IsNull, ObjectLiteral, } from 'typeorm' import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity' @@ -135,31 +134,15 @@ export enum SortBy { const readingProgressDataSource = new ReadingProgressDataSource() export const batchGetLibraryItems = async (ids: readonly string[]) => { - const selectColumns: Array = [ - 'id', - 'title', - 'author', - 'thumbnail', - 'wordCount', - 'savedAt', - 'originalUrl', - 'directionality', - 'description', - 'subscription', - 'siteName', - 'siteIcon', - 'archivedAt', - 'deletedAt', - 'slug', - 'previewContent', - ] + // select all columns except content + const select = getColumns(libraryItemRepository).filter( + (select) => ['originalContent', 'readableContent'].indexOf(select) === -1 + ) const items = await authTrx(async (tx) => tx.getRepository(LibraryItem).find({ - select: selectColumns, + select, where: { id: In(ids as string[]), - state: LibraryItemState.Succeeded, - seenAt: IsNull(), }, }) )