feat: update newsletter email api

This commit is contained in:
Hongbo Wu
2023-12-20 11:29:10 +08:00
parent e4b6cba2ff
commit 0a4758105a
7 changed files with 237 additions and 2 deletions

View File

@ -1391,6 +1391,7 @@ export type Mutation = {
updateFilter: UpdateFilterResult;
updateHighlight: UpdateHighlightResult;
updateLabel: UpdateLabelResult;
updateNewsletterEmail: UpdateNewsletterEmailResult;
updatePage: UpdatePageResult;
updateSubscription: UpdateSubscriptionResult;
updateUser: UpdateUserResult;
@ -1677,6 +1678,11 @@ export type MutationUpdateLabelArgs = {
};
export type MutationUpdateNewsletterEmailArgs = {
input: UpdateNewsletterEmailInput;
};
export type MutationUpdatePageArgs = {
input: UpdatePageInput;
};
@ -3059,6 +3065,30 @@ export type UpdateLinkShareInfoSuccess = {
message: Scalars['String'];
};
export type UpdateNewsletterEmailError = {
__typename?: 'UpdateNewsletterEmailError';
errorCodes: Array<UpdateNewsletterEmailErrorCode>;
};
export enum UpdateNewsletterEmailErrorCode {
BadRequest = 'BAD_REQUEST',
Unauthorized = 'UNAUTHORIZED'
}
export type UpdateNewsletterEmailInput = {
description?: InputMaybe<Scalars['String']>;
folder?: InputMaybe<Scalars['String']>;
id: Scalars['ID'];
name?: InputMaybe<Scalars['String']>;
};
export type UpdateNewsletterEmailResult = UpdateNewsletterEmailError | UpdateNewsletterEmailSuccess;
export type UpdateNewsletterEmailSuccess = {
__typename?: 'UpdateNewsletterEmailSuccess';
newsletterEmail: NewsletterEmail;
};
export type UpdatePageError = {
__typename?: 'UpdatePageError';
errorCodes: Array<UpdatePageErrorCode>;
@ -3962,6 +3992,11 @@ export type ResolversTypes = {
UpdateLinkShareInfoInput: UpdateLinkShareInfoInput;
UpdateLinkShareInfoResult: ResolversTypes['UpdateLinkShareInfoError'] | ResolversTypes['UpdateLinkShareInfoSuccess'];
UpdateLinkShareInfoSuccess: ResolverTypeWrapper<UpdateLinkShareInfoSuccess>;
UpdateNewsletterEmailError: ResolverTypeWrapper<UpdateNewsletterEmailError>;
UpdateNewsletterEmailErrorCode: UpdateNewsletterEmailErrorCode;
UpdateNewsletterEmailInput: UpdateNewsletterEmailInput;
UpdateNewsletterEmailResult: ResolversTypes['UpdateNewsletterEmailError'] | ResolversTypes['UpdateNewsletterEmailSuccess'];
UpdateNewsletterEmailSuccess: ResolverTypeWrapper<UpdateNewsletterEmailSuccess>;
UpdatePageError: ResolverTypeWrapper<UpdatePageError>;
UpdatePageErrorCode: UpdatePageErrorCode;
UpdatePageInput: UpdatePageInput;
@ -4392,6 +4427,10 @@ export type ResolversParentTypes = {
UpdateLinkShareInfoInput: UpdateLinkShareInfoInput;
UpdateLinkShareInfoResult: ResolversParentTypes['UpdateLinkShareInfoError'] | ResolversParentTypes['UpdateLinkShareInfoSuccess'];
UpdateLinkShareInfoSuccess: UpdateLinkShareInfoSuccess;
UpdateNewsletterEmailError: UpdateNewsletterEmailError;
UpdateNewsletterEmailInput: UpdateNewsletterEmailInput;
UpdateNewsletterEmailResult: ResolversParentTypes['UpdateNewsletterEmailError'] | ResolversParentTypes['UpdateNewsletterEmailSuccess'];
UpdateNewsletterEmailSuccess: UpdateNewsletterEmailSuccess;
UpdatePageError: UpdatePageError;
UpdatePageInput: UpdatePageInput;
UpdatePageResult: ResolversParentTypes['UpdatePageError'] | ResolversParentTypes['UpdatePageSuccess'];
@ -5460,6 +5499,7 @@ export type MutationResolvers<ContextType = ResolverContext, ParentType extends
updateFilter?: Resolver<ResolversTypes['UpdateFilterResult'], ParentType, ContextType, RequireFields<MutationUpdateFilterArgs, 'input'>>;
updateHighlight?: Resolver<ResolversTypes['UpdateHighlightResult'], ParentType, ContextType, RequireFields<MutationUpdateHighlightArgs, 'input'>>;
updateLabel?: Resolver<ResolversTypes['UpdateLabelResult'], ParentType, ContextType, RequireFields<MutationUpdateLabelArgs, 'input'>>;
updateNewsletterEmail?: Resolver<ResolversTypes['UpdateNewsletterEmailResult'], ParentType, ContextType, RequireFields<MutationUpdateNewsletterEmailArgs, 'input'>>;
updatePage?: Resolver<ResolversTypes['UpdatePageResult'], ParentType, ContextType, RequireFields<MutationUpdatePageArgs, 'input'>>;
updateSubscription?: Resolver<ResolversTypes['UpdateSubscriptionResult'], ParentType, ContextType, RequireFields<MutationUpdateSubscriptionArgs, 'input'>>;
updateUser?: Resolver<ResolversTypes['UpdateUserResult'], ParentType, ContextType, RequireFields<MutationUpdateUserArgs, 'input'>>;
@ -6267,6 +6307,20 @@ export type UpdateLinkShareInfoSuccessResolvers<ContextType = ResolverContext, P
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type UpdateNewsletterEmailErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UpdateNewsletterEmailError'] = ResolversParentTypes['UpdateNewsletterEmailError']> = {
errorCodes?: Resolver<Array<ResolversTypes['UpdateNewsletterEmailErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type UpdateNewsletterEmailResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UpdateNewsletterEmailResult'] = ResolversParentTypes['UpdateNewsletterEmailResult']> = {
__resolveType: TypeResolveFn<'UpdateNewsletterEmailError' | 'UpdateNewsletterEmailSuccess', ParentType, ContextType>;
};
export type UpdateNewsletterEmailSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UpdateNewsletterEmailSuccess'] = ResolversParentTypes['UpdateNewsletterEmailSuccess']> = {
newsletterEmail?: Resolver<ResolversTypes['NewsletterEmail'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type UpdatePageErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UpdatePageError'] = ResolversParentTypes['UpdatePageError']> = {
errorCodes?: Resolver<Array<ResolversTypes['UpdatePageErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
@ -6807,6 +6861,9 @@ export type Resolvers<ContextType = ResolverContext> = {
UpdateLinkShareInfoError?: UpdateLinkShareInfoErrorResolvers<ContextType>;
UpdateLinkShareInfoResult?: UpdateLinkShareInfoResultResolvers<ContextType>;
UpdateLinkShareInfoSuccess?: UpdateLinkShareInfoSuccessResolvers<ContextType>;
UpdateNewsletterEmailError?: UpdateNewsletterEmailErrorResolvers<ContextType>;
UpdateNewsletterEmailResult?: UpdateNewsletterEmailResultResolvers<ContextType>;
UpdateNewsletterEmailSuccess?: UpdateNewsletterEmailSuccessResolvers<ContextType>;
UpdatePageError?: UpdatePageErrorResolvers<ContextType>;
UpdatePageResult?: UpdatePageResultResolvers<ContextType>;
UpdatePageSuccess?: UpdatePageSuccessResolvers<ContextType>;

View File

@ -1252,6 +1252,7 @@ type Mutation {
updateFilter(input: UpdateFilterInput!): UpdateFilterResult!
updateHighlight(input: UpdateHighlightInput!): UpdateHighlightResult!
updateLabel(input: UpdateLabelInput!): UpdateLabelResult!
updateNewsletterEmail(input: UpdateNewsletterEmailInput!): UpdateNewsletterEmailResult!
updatePage(input: UpdatePageInput!): UpdatePageResult!
updateSubscription(input: UpdateSubscriptionInput!): UpdateSubscriptionResult!
updateUser(input: UpdateUserInput!): UpdateUserResult!
@ -2442,6 +2443,28 @@ type UpdateLinkShareInfoSuccess {
message: String!
}
type UpdateNewsletterEmailError {
errorCodes: [UpdateNewsletterEmailErrorCode!]!
}
enum UpdateNewsletterEmailErrorCode {
BAD_REQUEST
UNAUTHORIZED
}
input UpdateNewsletterEmailInput {
description: String
folder: String
id: ID!
name: String
}
union UpdateNewsletterEmailResult = UpdateNewsletterEmailError | UpdateNewsletterEmailSuccess
type UpdateNewsletterEmailSuccess {
newsletterEmail: NewsletterEmail!
}
type UpdatePageError {
errorCodes: [UpdatePageErrorCode!]!
}

View File

@ -131,6 +131,7 @@ import {
validateUsernameResolver,
webhookResolver,
webhooksResolver,
updateNewsletterEmailResolver,
} from './index'
import { markEmailAsItemResolver, recentEmailsResolver } from './recent_emails'
import { recentSearchesResolver } from './recent_searches'
@ -226,6 +227,7 @@ export const functionResolvers = {
updateFilter: updateFilterResolver,
updateEmail: updateEmailResolver,
moveToFolder: moveToFolderResolver,
updateNewsletterEmail: updateNewsletterEmailResolver,
},
Query: {
me: getMeUserResolver,
@ -445,8 +447,12 @@ export const functionResolvers = {
subscription.icon && createImageProxyUrl(subscription.icon, 128, 128)
)
},
folder(subscription: { folder?: string | null }) {
return subscription.folder || DEFAULT_SUBSCRIPTION_FOLDER
folder(subscription: Subscription) {
return (
subscription.folder ||
subscription.newsletterEmail?.folder ||
DEFAULT_SUBSCRIPTION_FOLDER
)
},
},
NewsletterEmail: {
@ -550,4 +556,5 @@ export const functionResolvers = {
...resultResolveTypeResolver('UpdateEmail'),
...resultResolveTypeResolver('ScanFeeds'),
...resultResolveTypeResolver('MoveToFolder'),
...resultResolveTypeResolver('UpdateNewsletterEmail'),
}

View File

@ -12,15 +12,20 @@ import {
DeleteNewsletterEmailSuccess,
MutationCreateNewsletterEmailArgs,
MutationDeleteNewsletterEmailArgs,
MutationUpdateNewsletterEmailArgs,
NewsletterEmailsError,
NewsletterEmailsErrorCode,
NewsletterEmailsSuccess,
UpdateNewsletterEmailError,
UpdateNewsletterEmailErrorCode,
UpdateNewsletterEmailSuccess,
} from '../../generated/graphql'
import { getRepository } from '../../repository'
import {
createNewsletterEmail,
deleteNewsletterEmail,
getNewsletterEmails,
updateNewsletterEmail,
} from '../../services/newsletters'
import { unsubscribeAll } from '../../services/subscriptions'
import { Merge } from '../../util'
@ -143,3 +148,39 @@ export const deleteNewsletterEmailResolver = authorized<
}
}
})
export type UpdateNewsletterEmailSuccessPartial = Merge<
UpdateNewsletterEmailSuccess,
{ newsletterEmail: NewsletterEmail }
>
export const updateNewsletterEmailResolver = authorized<
UpdateNewsletterEmailSuccessPartial,
UpdateNewsletterEmailError,
MutationUpdateNewsletterEmailArgs
>(async (_parent, { input }, { uid, log }) => {
analytics.track({
userId: uid,
event: 'newsletter_email_updated',
properties: {
env: env.server.apiEnv,
...input,
},
})
const updatedNewsletterEmail = await updateNewsletterEmail(input.id, uid, {
name: input.name,
description: input.description,
folder: input.folder,
})
if (!updatedNewsletterEmail) {
log.error('failed to update newsletter email')
return {
errorCodes: [UpdateNewsletterEmailErrorCode.Unauthorized],
}
}
return {
newsletterEmail: updatedNewsletterEmail,
}
})

View File

@ -2737,6 +2737,30 @@ const schema = gql`
BAD_REQUEST
}
input UpdateNewsletterEmailInput {
id: ID!
name: String
description: String
folder: String
}
union UpdateNewsletterEmailResult =
UpdateNewsletterEmailSuccess
| UpdateNewsletterEmailError
type UpdateNewsletterEmailSuccess {
newsletterEmail: NewsletterEmail!
}
type UpdateNewsletterEmailError {
errorCodes: [UpdateNewsletterEmailErrorCode!]!
}
enum UpdateNewsletterEmailErrorCode {
UNAUTHORIZED
BAD_REQUEST
}
# Mutations
type Mutation {
googleLogin(input: GoogleLoginInput!): LoginResult!
@ -2845,6 +2869,9 @@ const schema = gql`
): UpdateSubscriptionResult!
moveToFolder(id: ID!, folder: String!): MoveToFolderResult!
fetchContent(id: ID!): FetchContentResult!
updateNewsletterEmail(
input: UpdateNewsletterEmailInput!
): UpdateNewsletterEmailResult!
}
# FIXME: remove sort from feedArticles after all cached tabs are closed

View File

@ -7,6 +7,7 @@ import {
} from '../generated/graphql'
import { getRepository } from '../repository'
import { userRepository } from '../repository/user'
import { keysToCamelCase } from '../utils/helpers'
import addressparser = require('nodemailer/lib/addressparser')
const parsedAddress = (emailAddress: string) => {
@ -114,3 +115,28 @@ export const findNewsletterEmailById = async (
): Promise<NewsletterEmail | null> => {
return getRepository(NewsletterEmail).findOneBy({ id })
}
export const updateNewsletterEmail = async (
id: string,
userId: string,
newsletterEmail: Partial<NewsletterEmail>
): Promise<NewsletterEmail | null> => {
const repo = getRepository(NewsletterEmail)
const result = await repo
.createQueryBuilder()
.where('id = :id', { id })
.andWhere('user_id = :userId', { userId })
.update(newsletterEmail)
.returning('*')
.execute()
if (
!result.affected ||
!Array.isArray(result.raw) ||
result.raw.length === 0
) {
return null
}
return keysToCamelCase(result.raw[0]) as NewsletterEmail
}

View File

@ -9,6 +9,7 @@ import {
import { getRepository } from '../../src/repository'
import {
createNewsletterEmail,
deleteNewsletterEmail,
findNewsletterEmailByAddress,
findNewsletterEmailById,
} from '../../src/services/newsletters'
@ -286,4 +287,57 @@ describe('Newsletters API', () => {
return graphqlRequest(query, invalidAuthToken).expect(500)
})
})
describe('Update newsletter email', () => {
const query = `
mutation UpdateNewsletterEmail($input: UpdateNewsletterEmailInput!) {
updateNewsletterEmail(input: $input) {
... on UpdateNewsletterEmailSuccess {
newsletterEmail {
id
address
folder
}
}
... on UpdateNewsletterEmailError {
errorCodes
}
}
}
`
context('when newsletter email exists', () => {
let newsletterEmailId = 'Newsletter email id'
before(async () => {
// create test newsletter emails
const newsletterEmail = await createNewsletterEmail(
user.id,
undefined,
'inbox'
)
newsletterEmailId = newsletterEmail.id
})
after(async () => {
// clean up
await deleteNewsletterEmail(newsletterEmailId)
})
it('responds with status code 200', async () => {
const folder = 'following'
const response = await graphqlRequest(query, authToken, {
input: {
id: newsletterEmailId,
folder,
},
}).expect(200)
expect(
response.body.data.updateNewsletterEmail.newsletterEmail.folder
).to.eql(folder)
const newsletterEmail = await findNewsletterEmailById(newsletterEmailId)
expect(newsletterEmail?.folder).to.eql(folder)
})
})
})
})