From 55904ccf04bfa9360a99791061314919abffaf95 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Fri, 9 Jun 2023 13:09:47 +0800 Subject: [PATCH] feat: add set favorite article api which adds the favorites internal label to it --- packages/api/src/generated/graphql.ts | 49 +++++++++++++++++++ packages/api/src/generated/schema.graphql | 17 +++++++ packages/api/src/resolvers/article/index.ts | 49 ++++++++++++++++++- .../api/src/resolvers/function_resolvers.ts | 3 ++ packages/api/src/schema.ts | 19 +++++++ 5 files changed, 136 insertions(+), 1 deletion(-) diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index 41fbc517d..472a56d36 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -1271,6 +1271,7 @@ export type Mutation = { saveUrl: SaveResult; setBookmarkArticle: SetBookmarkArticleResult; setDeviceToken: SetDeviceTokenResult; + setFavoriteArticle: SetFavoriteArticleResult; setFollow: SetFollowResult; setIntegration: SetIntegrationResult; setLabels: SetLabelsResult; @@ -1516,6 +1517,11 @@ export type MutationSetDeviceTokenArgs = { }; +export type MutationSetFavoriteArticleArgs = { + id: Scalars['ID']; +}; + + export type MutationSetFollowArgs = { input: SetFollowInput; }; @@ -2393,6 +2399,24 @@ export type SetDeviceTokenSuccess = { deviceToken: DeviceToken; }; +export type SetFavoriteArticleError = { + __typename?: 'SetFavoriteArticleError'; + errorCodes: Array; +}; + +export enum SetFavoriteArticleErrorCode { + BadRequest = 'BAD_REQUEST', + NotFound = 'NOT_FOUND', + Unauthorized = 'UNAUTHORIZED' +} + +export type SetFavoriteArticleResult = SetFavoriteArticleError | SetFavoriteArticleSuccess; + +export type SetFavoriteArticleSuccess = { + __typename?: 'SetFavoriteArticleSuccess'; + favoriteArticle: Article; +}; + export type SetFollowError = { __typename?: 'SetFollowError'; errorCodes: Array; @@ -3600,6 +3624,10 @@ export type ResolversTypes = { SetDeviceTokenInput: SetDeviceTokenInput; SetDeviceTokenResult: ResolversTypes['SetDeviceTokenError'] | ResolversTypes['SetDeviceTokenSuccess']; SetDeviceTokenSuccess: ResolverTypeWrapper; + SetFavoriteArticleError: ResolverTypeWrapper; + SetFavoriteArticleErrorCode: SetFavoriteArticleErrorCode; + SetFavoriteArticleResult: ResolversTypes['SetFavoriteArticleError'] | ResolversTypes['SetFavoriteArticleSuccess']; + SetFavoriteArticleSuccess: ResolverTypeWrapper; SetFollowError: ResolverTypeWrapper; SetFollowErrorCode: SetFollowErrorCode; SetFollowInput: SetFollowInput; @@ -4016,6 +4044,9 @@ export type ResolversParentTypes = { SetDeviceTokenInput: SetDeviceTokenInput; SetDeviceTokenResult: ResolversParentTypes['SetDeviceTokenError'] | ResolversParentTypes['SetDeviceTokenSuccess']; SetDeviceTokenSuccess: SetDeviceTokenSuccess; + SetFavoriteArticleError: SetFavoriteArticleError; + SetFavoriteArticleResult: ResolversParentTypes['SetFavoriteArticleError'] | ResolversParentTypes['SetFavoriteArticleSuccess']; + SetFavoriteArticleSuccess: SetFavoriteArticleSuccess; SetFollowError: SetFollowError; SetFollowInput: SetFollowInput; SetFollowResult: ResolversParentTypes['SetFollowError'] | ResolversParentTypes['SetFollowSuccess']; @@ -5065,6 +5096,7 @@ export type MutationResolvers>; setBookmarkArticle?: Resolver>; setDeviceToken?: Resolver>; + setFavoriteArticle?: Resolver>; setFollow?: Resolver>; setIntegration?: Resolver>; setLabels?: Resolver>; @@ -5542,6 +5574,20 @@ export type SetDeviceTokenSuccessResolvers; }; +export type SetFavoriteArticleErrorResolvers = { + errorCodes?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type SetFavoriteArticleResultResolvers = { + __resolveType: TypeResolveFn<'SetFavoriteArticleError' | 'SetFavoriteArticleSuccess', ParentType, ContextType>; +}; + +export type SetFavoriteArticleSuccessResolvers = { + favoriteArticle?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type SetFollowErrorResolvers = { errorCodes?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -6263,6 +6309,9 @@ export type Resolvers = { SetDeviceTokenError?: SetDeviceTokenErrorResolvers; SetDeviceTokenResult?: SetDeviceTokenResultResolvers; SetDeviceTokenSuccess?: SetDeviceTokenSuccessResolvers; + SetFavoriteArticleError?: SetFavoriteArticleErrorResolvers; + SetFavoriteArticleResult?: SetFavoriteArticleResultResolvers; + SetFavoriteArticleSuccess?: SetFavoriteArticleSuccessResolvers; SetFollowError?: SetFollowErrorResolvers; SetFollowResult?: SetFollowResultResolvers; SetFollowSuccess?: SetFollowSuccessResolvers; diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index d31ea1083..2ceaf5fa1 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -1139,6 +1139,7 @@ type Mutation { saveUrl(input: SaveUrlInput!): SaveResult! setBookmarkArticle(input: SetBookmarkArticleInput!): SetBookmarkArticleResult! setDeviceToken(input: SetDeviceTokenInput!): SetDeviceTokenResult! + setFavoriteArticle(id: ID!): SetFavoriteArticleResult! setFollow(input: SetFollowInput!): SetFollowResult! setIntegration(input: SetIntegrationInput!): SetIntegrationResult! setLabels(input: SetLabelsInput!): SetLabelsResult! @@ -1774,6 +1775,22 @@ type SetDeviceTokenSuccess { deviceToken: DeviceToken! } +type SetFavoriteArticleError { + errorCodes: [SetFavoriteArticleErrorCode!]! +} + +enum SetFavoriteArticleErrorCode { + BAD_REQUEST + NOT_FOUND + UNAUTHORIZED +} + +union SetFavoriteArticleResult = SetFavoriteArticleError | SetFavoriteArticleSuccess + +type SetFavoriteArticleSuccess { + favoriteArticle: Article! +} + type SetFollowError { errorCodes: [SetFollowErrorCode!]! } diff --git a/packages/api/src/resolvers/article/index.ts b/packages/api/src/resolvers/article/index.ts index abcdc55fd..21f4f2bc2 100644 --- a/packages/api/src/resolvers/article/index.ts +++ b/packages/api/src/resolvers/article/index.ts @@ -42,6 +42,7 @@ import { MutationCreateArticleArgs, MutationSaveArticleReadingProgressArgs, MutationSetBookmarkArticleArgs, + MutationSetFavoriteArticleArgs, MutationSetShareArticleArgs, PageInfo, QueryArticleArgs, @@ -60,6 +61,9 @@ import { SetBookmarkArticleError, SetBookmarkArticleErrorCode, SetBookmarkArticleSuccess, + SetFavoriteArticleError, + SetFavoriteArticleErrorCode, + SetFavoriteArticleSuccess, SetShareArticleError, SetShareArticleErrorCode, SetShareArticleSuccess, @@ -74,7 +78,11 @@ import { UpdatesSinceSuccess, } from '../../generated/graphql' import { createPageSaveRequest } from '../../services/create_page_save_request' -import { createLabels, getLabelsByIds } from '../../services/labels' +import { + addLabelToPage, + createLabels, + getLabelsByIds, +} from '../../services/labels' import { parsedContentToPage } from '../../services/save_page' import { traceAs } from '../../tracing' import { Merge } from '../../util' @@ -1152,6 +1160,45 @@ export const bulkActionResolver = authorized< } ) +export const setFavoriteArticleResolver = authorized< + SetFavoriteArticleSuccess, + SetFavoriteArticleError, + MutationSetFavoriteArticleArgs +>(async (_, { id }, { claims: { uid }, log, pubsub }) => { + try { + const page = await getPageByParam({ userId: uid, _id: id }) + + if (!page) { + return { errorCodes: [SetFavoriteArticleErrorCode.NotFound] } + } + + // adds Favorites label to page + const result = await addLabelToPage( + { + uid, + pubsub, + }, + page.id, + { + name: 'Favorites', + color: '#07D2D0', // TODO: pick a color + } + ) + if (!result) { + return { errorCodes: [SetFavoriteArticleErrorCode.Unauthorized] } + } + + log.debug('Favorites label added:', result) + + return { + favoriteArticle: page, + } + } catch (error) { + log.debug('Error adding Favorites label:', error) + return { errorCodes: [SetFavoriteArticleErrorCode.BadRequest] } + } +}) + const getUpdateReason = (page: Page, since: Date) => { if (page.state === ArticleSavingRequestStatus.Deleted) { return UpdateReason.Deleted diff --git a/packages/api/src/resolvers/function_resolvers.ts b/packages/api/src/resolvers/function_resolvers.ts index 0000cf4cf..0041c3347 100644 --- a/packages/api/src/resolvers/function_resolvers.ts +++ b/packages/api/src/resolvers/function_resolvers.ts @@ -23,6 +23,7 @@ import { generateDownloadSignedUrl, generateUploadFilePathName, } from '../utils/uploads' +import { setFavoriteArticleResolver } from './article' import { optInFeatureResolver } from './features' import { uploadImportFileResolver } from './importers/uploadImportFileResolver' import { @@ -204,6 +205,7 @@ export const functionResolvers = { markEmailAsItem: markEmailAsItemResolver, bulkAction: bulkActionResolver, importFromIntegration: importFromIntegrationResolver, + setFavoriteArticle: setFavoriteArticleResolver, }, Query: { me: getMeUserResolver, @@ -659,4 +661,5 @@ export const functionResolvers = { ...resultResolveTypeResolver('MarkEmailAsItem'), ...resultResolveTypeResolver('BulkAction'), ...resultResolveTypeResolver('ImportFromIntegration'), + ...resultResolveTypeResolver('SetFavoriteArticle'), } diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index d42941a0a..c97d041df 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -2454,6 +2454,24 @@ const schema = gql` BAD_REQUEST } + union SetFavoriteArticleResult = + SetFavoriteArticleSuccess + | SetFavoriteArticleError + + type SetFavoriteArticleSuccess { + favoriteArticle: Article! + } + + type SetFavoriteArticleError { + errorCodes: [SetFavoriteArticleErrorCode!]! + } + + enum SetFavoriteArticleErrorCode { + UNAUTHORIZED + BAD_REQUEST + NOT_FOUND + } + # Mutations type Mutation { googleLogin(input: GoogleLoginInput!): LoginResult! @@ -2551,6 +2569,7 @@ const schema = gql` async: Boolean # if true, return immediately and process in the background ): BulkActionResult! importFromIntegration(integrationId: ID!): ImportFromIntegrationResult! + setFavoriteArticle(id: ID!): SetFavoriteArticleResult! } # FIXME: remove sort from feedArticles after all cached tabs are closed