add updateSubscription api

This commit is contained in:
Hongbo Wu 2023-07-10 15:24:33 +08:00
parent abd59f0527
commit 56a27878dc
5 changed files with 218 additions and 25 deletions

View file

@ -1292,6 +1292,7 @@ export type Mutation = {
updatePage: UpdatePageResult;
updateReminder: UpdateReminderResult;
updateSharedComment: UpdateSharedCommentResult;
updateSubscription: UpdateSubscriptionResult;
updateUser: UpdateUserResult;
updateUserProfile: UpdateUserProfileResult;
uploadFileRequest: UploadFileRequestResult;
@ -1619,6 +1620,11 @@ export type MutationUpdateSharedCommentArgs = {
};
export type MutationUpdateSubscriptionArgs = {
input: UpdateSubscriptionInput;
};
export type MutationUpdateUserArgs = {
input: UpdateUserInput;
};
@ -2995,6 +3001,31 @@ export type UpdateSharedCommentSuccess = {
sharedComment: Scalars['String'];
};
export type UpdateSubscriptionError = {
__typename?: 'UpdateSubscriptionError';
errorCodes: Array<UpdateSubscriptionErrorCode>;
};
export enum UpdateSubscriptionErrorCode {
BadRequest = 'BAD_REQUEST',
NotFound = 'NOT_FOUND',
Unauthorized = 'UNAUTHORIZED'
}
export type UpdateSubscriptionInput = {
description?: InputMaybe<Scalars['String']>;
id: Scalars['ID'];
lastFetchedAt?: InputMaybe<Scalars['Date']>;
name?: InputMaybe<Scalars['String']>;
};
export type UpdateSubscriptionResult = UpdateSubscriptionError | UpdateSubscriptionSuccess;
export type UpdateSubscriptionSuccess = {
__typename?: 'UpdateSubscriptionSuccess';
subscription: Subscription;
};
export type UpdateUserError = {
__typename?: 'UpdateUserError';
errorCodes: Array<UpdateUserErrorCode>;
@ -3759,6 +3790,11 @@ export type ResolversTypes = {
UpdateSharedCommentInput: UpdateSharedCommentInput;
UpdateSharedCommentResult: ResolversTypes['UpdateSharedCommentError'] | ResolversTypes['UpdateSharedCommentSuccess'];
UpdateSharedCommentSuccess: ResolverTypeWrapper<UpdateSharedCommentSuccess>;
UpdateSubscriptionError: ResolverTypeWrapper<UpdateSubscriptionError>;
UpdateSubscriptionErrorCode: UpdateSubscriptionErrorCode;
UpdateSubscriptionInput: UpdateSubscriptionInput;
UpdateSubscriptionResult: ResolversTypes['UpdateSubscriptionError'] | ResolversTypes['UpdateSubscriptionSuccess'];
UpdateSubscriptionSuccess: ResolverTypeWrapper<UpdateSubscriptionSuccess>;
UpdateUserError: ResolverTypeWrapper<UpdateUserError>;
UpdateUserErrorCode: UpdateUserErrorCode;
UpdateUserInput: UpdateUserInput;
@ -4154,6 +4190,10 @@ export type ResolversParentTypes = {
UpdateSharedCommentInput: UpdateSharedCommentInput;
UpdateSharedCommentResult: ResolversParentTypes['UpdateSharedCommentError'] | ResolversParentTypes['UpdateSharedCommentSuccess'];
UpdateSharedCommentSuccess: UpdateSharedCommentSuccess;
UpdateSubscriptionError: UpdateSubscriptionError;
UpdateSubscriptionInput: UpdateSubscriptionInput;
UpdateSubscriptionResult: ResolversParentTypes['UpdateSubscriptionError'] | ResolversParentTypes['UpdateSubscriptionSuccess'];
UpdateSubscriptionSuccess: UpdateSubscriptionSuccess;
UpdateUserError: UpdateUserError;
UpdateUserInput: UpdateUserInput;
UpdateUserProfileError: UpdateUserProfileError;
@ -5142,6 +5182,7 @@ export type MutationResolvers<ContextType = ResolverContext, ParentType extends
updatePage?: Resolver<ResolversTypes['UpdatePageResult'], ParentType, ContextType, RequireFields<MutationUpdatePageArgs, 'input'>>;
updateReminder?: Resolver<ResolversTypes['UpdateReminderResult'], ParentType, ContextType, RequireFields<MutationUpdateReminderArgs, 'input'>>;
updateSharedComment?: Resolver<ResolversTypes['UpdateSharedCommentResult'], ParentType, ContextType, RequireFields<MutationUpdateSharedCommentArgs, 'input'>>;
updateSubscription?: Resolver<ResolversTypes['UpdateSubscriptionResult'], ParentType, ContextType, RequireFields<MutationUpdateSubscriptionArgs, 'input'>>;
updateUser?: Resolver<ResolversTypes['UpdateUserResult'], ParentType, ContextType, RequireFields<MutationUpdateUserArgs, 'input'>>;
updateUserProfile?: Resolver<ResolversTypes['UpdateUserProfileResult'], ParentType, ContextType, RequireFields<MutationUpdateUserProfileArgs, 'input'>>;
uploadFileRequest?: Resolver<ResolversTypes['UploadFileRequestResult'], ParentType, ContextType, RequireFields<MutationUploadFileRequestArgs, 'input'>>;
@ -5938,6 +5979,20 @@ export type UpdateSharedCommentSuccessResolvers<ContextType = ResolverContext, P
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type UpdateSubscriptionErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UpdateSubscriptionError'] = ResolversParentTypes['UpdateSubscriptionError']> = {
errorCodes?: Resolver<Array<ResolversTypes['UpdateSubscriptionErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type UpdateSubscriptionResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UpdateSubscriptionResult'] = ResolversParentTypes['UpdateSubscriptionResult']> = {
__resolveType: TypeResolveFn<'UpdateSubscriptionError' | 'UpdateSubscriptionSuccess', ParentType, ContextType>;
};
export type UpdateSubscriptionSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UpdateSubscriptionSuccess'] = ResolversParentTypes['UpdateSubscriptionSuccess']> = {
subscription?: Resolver<ResolversTypes['Subscription'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type UpdateUserErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UpdateUserError'] = ResolversParentTypes['UpdateUserError']> = {
errorCodes?: Resolver<Array<ResolversTypes['UpdateUserErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
@ -6405,6 +6460,9 @@ export type Resolvers<ContextType = ResolverContext> = {
UpdateSharedCommentError?: UpdateSharedCommentErrorResolvers<ContextType>;
UpdateSharedCommentResult?: UpdateSharedCommentResultResolvers<ContextType>;
UpdateSharedCommentSuccess?: UpdateSharedCommentSuccessResolvers<ContextType>;
UpdateSubscriptionError?: UpdateSubscriptionErrorResolvers<ContextType>;
UpdateSubscriptionResult?: UpdateSubscriptionResultResolvers<ContextType>;
UpdateSubscriptionSuccess?: UpdateSubscriptionSuccessResolvers<ContextType>;
UpdateUserError?: UpdateUserErrorResolvers<ContextType>;
UpdateUserProfileError?: UpdateUserProfileErrorResolvers<ContextType>;
UpdateUserProfileResult?: UpdateUserProfileResultResolvers<ContextType>;

View file

@ -1160,6 +1160,7 @@ type Mutation {
updatePage(input: UpdatePageInput!): UpdatePageResult!
updateReminder(input: UpdateReminderInput!): UpdateReminderResult!
updateSharedComment(input: UpdateSharedCommentInput!): UpdateSharedCommentResult!
updateSubscription(input: UpdateSubscriptionInput!): UpdateSubscriptionResult!
updateUser(input: UpdateUserInput!): UpdateUserResult!
updateUserProfile(input: UpdateUserProfileInput!): UpdateUserProfileResult!
uploadFileRequest(input: UploadFileRequestInput!): UploadFileRequestResult!
@ -2323,6 +2324,29 @@ type UpdateSharedCommentSuccess {
sharedComment: String!
}
type UpdateSubscriptionError {
errorCodes: [UpdateSubscriptionErrorCode!]!
}
enum UpdateSubscriptionErrorCode {
BAD_REQUEST
NOT_FOUND
UNAUTHORIZED
}
input UpdateSubscriptionInput {
description: String
id: ID!
lastFetchedAt: Date
name: String
}
union UpdateSubscriptionResult = UpdateSubscriptionError | UpdateSubscriptionSuccess
type UpdateSubscriptionSuccess {
subscription: Subscription!
}
type UpdateUserError {
errorCodes: [UpdateUserErrorCode!]!
}

View file

@ -5,6 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { getShareInfoForArticle } from '../datalayer/links/share_info'
import { getPageByParam } from '../elastic/pages'
import { Subscription } from '../entity/subscription'
import {
Article,
ArticleHighlightsInput,
@ -109,6 +110,7 @@ import {
updateReminderResolver,
updateSharedCommentResolver,
updatesSinceResolver,
updateSubscriptionResolver,
updateUserProfileResolver,
updateUserResolver,
uploadFileRequestResolver,
@ -206,6 +208,7 @@ export const functionResolvers = {
bulkAction: bulkActionResolver,
importFromIntegration: importFromIntegrationResolver,
setFavoriteArticle: setFavoriteArticleResolver,
updateSubscription: updateSubscriptionResolver,
},
Query: {
me: getMeUserResolver,
@ -573,6 +576,16 @@ export const functionResolvers = {
return item.pageType || PageType.Unknown
},
},
Subscription: {
newsletterEmail(subscription: Subscription) {
return subscription.newsletterEmail?.address
},
icon(subscription: Subscription) {
return (
subscription.icon && createImageProxyUrl(subscription.icon, 128, 128)
)
},
},
...resultResolveTypeResolver('Login'),
...resultResolveTypeResolver('LogOut'),
...resultResolveTypeResolver('GoogleSignup'),
@ -662,4 +675,5 @@ export const functionResolvers = {
...resultResolveTypeResolver('BulkAction'),
...resultResolveTypeResolver('ImportFromIntegration'),
...resultResolveTypeResolver('SetFavoriteArticle'),
...resultResolveTypeResolver('UpdateSubscription'),
}

View file

@ -6,6 +6,7 @@ import { env } from '../../env'
import {
MutationSubscribeArgs,
MutationUnsubscribeArgs,
MutationUpdateSubscriptionArgs,
QuerySubscriptionsArgs,
SortBy,
SortOrder,
@ -20,16 +21,25 @@ import {
UnsubscribeError,
UnsubscribeErrorCode,
UnsubscribeSuccess,
UpdateSubscriptionError,
UpdateSubscriptionErrorCode,
UpdateSubscriptionSuccess,
} from '../../generated/graphql'
import { getSubscribeHandler, unsubscribe } from '../../services/subscriptions'
import { Merge } from '../../util'
import { analytics } from '../../utils/analytics'
import { authorized } from '../../utils/helpers'
import { createImageProxyUrl } from '../../utils/imageproxy'
type PartialSubscription = Omit<Subscription, 'newsletterEmail'>
const parser = new Parser()
export const subscriptionsResolver = authorized<
export type SubscriptionsSuccessPartial = Merge<
SubscriptionsSuccess,
{ subscriptions: PartialSubscription[] }
>
export const subscriptionsResolver = authorized<
SubscriptionsSuccessPartial,
SubscriptionsError,
QuerySubscriptionsArgs
>(async (_obj, { sort, type: subscriptionType }, { claims: { uid }, log }) => {
@ -66,11 +76,7 @@ export const subscriptionsResolver = authorized<
.getMany()
return {
subscriptions: subscriptions.map((s) => ({
...s,
icon: s.icon && createImageProxyUrl(s.icon, 128, 128),
newsletterEmail: s.newsletterEmail?.address,
})),
subscriptions,
}
} catch (error) {
log.error(error)
@ -80,8 +86,12 @@ export const subscriptionsResolver = authorized<
}
})
export const unsubscribeResolver = authorized<
export type UnsubscribeSuccessPartial = Merge<
UnsubscribeSuccess,
{ subscription: PartialSubscription }
>
export const unsubscribeResolver = authorized<
UnsubscribeSuccessPartial,
UnsubscribeError,
MutationUnsubscribeArgs
>(async (_, { name, subscriptionId }, { claims: { uid }, log }) => {
@ -122,8 +132,12 @@ export const unsubscribeResolver = authorized<
}
}
if (!subscription.unsubscribeMailTo && !subscription.unsubscribeHttpUrl) {
log.info('No unsubscribe method found')
if (
subscription.type === SubscriptionType.Newsletter &&
!subscription.unsubscribeMailTo &&
!subscription.unsubscribeHttpUrl
) {
log.info('No unsubscribe method found for newsletter subscription')
}
await unsubscribe(subscription)
@ -138,10 +152,7 @@ export const unsubscribeResolver = authorized<
})
return {
subscription: {
...subscription,
newsletterEmail: subscription.newsletterEmail?.address,
},
subscription,
}
} catch (error) {
log.error('failed to unsubscribe', error)
@ -151,8 +162,12 @@ export const unsubscribeResolver = authorized<
}
})
export const subscribeResolver = authorized<
export type SubscribeSuccessPartial = Merge<
SubscribeSuccess,
{ subscriptions: PartialSubscription[] }
>
export const subscribeResolver = authorized<
SubscribeSuccessPartial,
SubscribeError,
MutationSubscribeArgs
>(async (_, { input }, { claims: { uid }, log }) => {
@ -209,10 +224,7 @@ export const subscribeResolver = authorized<
}
return {
subscriptions: newSubscriptions.map((s) => ({
...s,
newsletterEmail: s.newsletterEmail?.address,
})),
subscriptions: newSubscriptions,
}
}
@ -231,12 +243,7 @@ export const subscribeResolver = authorized<
})
return {
subscriptions: [
{
...newSubscription,
newsletterEmail: null,
},
],
subscriptions: [newSubscription],
}
}
@ -251,3 +258,65 @@ export const subscribeResolver = authorized<
}
}
})
export type UpdateSubscriptionSuccessPartial = Merge<
UpdateSubscriptionSuccess,
{ subscription: PartialSubscription }
>
export const updateSubscriptionResolver = authorized<
UpdateSubscriptionSuccessPartial,
UpdateSubscriptionError,
MutationUpdateSubscriptionArgs
>(async (_, { input }, { claims: { uid }, log }) => {
log.info('updateSubscriptionResolver')
try {
analytics.track({
userId: uid,
event: 'update_subscription',
properties: {
...input,
env: env.server.apiEnv,
},
})
const user = await getRepository(User).findOneBy({ id: uid })
if (!user) {
return {
errorCodes: [UpdateSubscriptionErrorCode.Unauthorized],
}
}
// find existing subscription
const subscription = await getRepository(Subscription).findOneBy({
id: input.id,
user: { id: uid },
status: SubscriptionStatus.Active,
})
if (!subscription) {
log.info('subscription not found')
return {
errorCodes: [UpdateSubscriptionErrorCode.NotFound],
}
}
// update subscription
const updatedSubscription = await getRepository(Subscription).save({
id: input.id,
name: input.name || undefined,
description: input.description || undefined,
lastFetchedAt: input.lastFetchedAt
? new Date(input.lastFetchedAt)
: undefined,
})
return {
subscription: updatedSubscription,
}
} catch (error) {
log.error('failed to update subscription', error)
return {
errorCodes: [UpdateSubscriptionErrorCode.BadRequest],
}
}
})

View file

@ -2492,6 +2492,31 @@ const schema = gql`
subscriptionType: SubscriptionType
}
input UpdateSubscriptionInput {
id: ID!
name: String
description: String
lastFetchedAt: Date
}
union UpdateSubscriptionResult =
UpdateSubscriptionSuccess
| UpdateSubscriptionError
type UpdateSubscriptionSuccess {
subscription: Subscription!
}
type UpdateSubscriptionError {
errorCodes: [UpdateSubscriptionErrorCode!]!
}
enum UpdateSubscriptionErrorCode {
UNAUTHORIZED
BAD_REQUEST
NOT_FOUND
}
# Mutations
type Mutation {
googleLogin(input: GoogleLoginInput!): LoginResult!
@ -2590,6 +2615,9 @@ const schema = gql`
): BulkActionResult!
importFromIntegration(integrationId: ID!): ImportFromIntegrationResult!
setFavoriteArticle(id: ID!): SetFavoriteArticleResult!
updateSubscription(
input: UpdateSubscriptionInput!
): UpdateSubscriptionResult!
}
# FIXME: remove sort from feedArticles after all cached tabs are closed