Add mark a recent email as library item api

This commit is contained in:
Hongbo Wu 2023-01-17 18:02:50 +08:00
parent 62a3b48e00
commit 96f727a165
7 changed files with 198 additions and 34 deletions

View file

@ -1062,6 +1062,24 @@ export type LoginSuccess = {
me: User;
};
export type MarkEmailAsItemError = {
__typename?: 'MarkEmailAsItemError';
errorCodes: Array<MarkEmailAsItemErrorCode>;
};
export enum MarkEmailAsItemErrorCode {
BadRequest = 'BAD_REQUEST',
NotFound = 'NOT_FOUND',
Unauthorized = 'UNAUTHORIZED'
}
export type MarkEmailAsItemResult = MarkEmailAsItemError | MarkEmailAsItemSuccess;
export type MarkEmailAsItemSuccess = {
__typename?: 'MarkEmailAsItemSuccess';
success: Scalars['Boolean'];
};
export type MergeHighlightError = {
__typename?: 'MergeHighlightError';
errorCodes: Array<MergeHighlightErrorCode>;
@ -1172,6 +1190,7 @@ export type Mutation = {
joinGroup: JoinGroupResult;
leaveGroup: LeaveGroupResult;
logOut: LogOutResult;
markEmailAsItem: MarkEmailAsItemResult;
mergeHighlight: MergeHighlightResult;
moveFilter: MoveFilterResult;
moveLabel: MoveLabelResult;
@ -1338,6 +1357,11 @@ export type MutationLeaveGroupArgs = {
};
export type MutationMarkEmailAsItemArgs = {
recentEmailId: Scalars['ID'];
};
export type MutationMergeHighlightArgs = {
input: MergeHighlightInput;
};
@ -3346,6 +3370,10 @@ export type ResolversTypes = {
LoginErrorCode: LoginErrorCode;
LoginResult: ResolversTypes['LoginError'] | ResolversTypes['LoginSuccess'];
LoginSuccess: ResolverTypeWrapper<LoginSuccess>;
MarkEmailAsItemError: ResolverTypeWrapper<MarkEmailAsItemError>;
MarkEmailAsItemErrorCode: MarkEmailAsItemErrorCode;
MarkEmailAsItemResult: ResolversTypes['MarkEmailAsItemError'] | ResolversTypes['MarkEmailAsItemSuccess'];
MarkEmailAsItemSuccess: ResolverTypeWrapper<MarkEmailAsItemSuccess>;
MergeHighlightError: ResolverTypeWrapper<MergeHighlightError>;
MergeHighlightErrorCode: MergeHighlightErrorCode;
MergeHighlightInput: MergeHighlightInput;
@ -3776,6 +3804,9 @@ export type ResolversParentTypes = {
LoginError: LoginError;
LoginResult: ResolversParentTypes['LoginError'] | ResolversParentTypes['LoginSuccess'];
LoginSuccess: LoginSuccess;
MarkEmailAsItemError: MarkEmailAsItemError;
MarkEmailAsItemResult: ResolversParentTypes['MarkEmailAsItemError'] | ResolversParentTypes['MarkEmailAsItemSuccess'];
MarkEmailAsItemSuccess: MarkEmailAsItemSuccess;
MergeHighlightError: MergeHighlightError;
MergeHighlightInput: MergeHighlightInput;
MergeHighlightResult: ResolversParentTypes['MergeHighlightError'] | ResolversParentTypes['MergeHighlightSuccess'];
@ -4781,6 +4812,20 @@ export type LoginSuccessResolvers<ContextType = ResolverContext, ParentType exte
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type MarkEmailAsItemErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['MarkEmailAsItemError'] = ResolversParentTypes['MarkEmailAsItemError']> = {
errorCodes?: Resolver<Array<ResolversTypes['MarkEmailAsItemErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type MarkEmailAsItemResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['MarkEmailAsItemResult'] = ResolversParentTypes['MarkEmailAsItemResult']> = {
__resolveType: TypeResolveFn<'MarkEmailAsItemError' | 'MarkEmailAsItemSuccess', ParentType, ContextType>;
};
export type MarkEmailAsItemSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['MarkEmailAsItemSuccess'] = ResolversParentTypes['MarkEmailAsItemSuccess']> = {
success?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type MergeHighlightErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['MergeHighlightError'] = ResolversParentTypes['MergeHighlightError']> = {
errorCodes?: Resolver<Array<ResolversTypes['MergeHighlightErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
@ -4852,6 +4897,7 @@ export type MutationResolvers<ContextType = ResolverContext, ParentType extends
joinGroup?: Resolver<ResolversTypes['JoinGroupResult'], ParentType, ContextType, RequireFields<MutationJoinGroupArgs, 'inviteCode'>>;
leaveGroup?: Resolver<ResolversTypes['LeaveGroupResult'], ParentType, ContextType, RequireFields<MutationLeaveGroupArgs, 'groupId'>>;
logOut?: Resolver<ResolversTypes['LogOutResult'], ParentType, ContextType>;
markEmailAsItem?: Resolver<ResolversTypes['MarkEmailAsItemResult'], ParentType, ContextType, RequireFields<MutationMarkEmailAsItemArgs, 'recentEmailId'>>;
mergeHighlight?: Resolver<ResolversTypes['MergeHighlightResult'], ParentType, ContextType, RequireFields<MutationMergeHighlightArgs, 'input'>>;
moveFilter?: Resolver<ResolversTypes['MoveFilterResult'], ParentType, ContextType, RequireFields<MutationMoveFilterArgs, 'input'>>;
moveLabel?: Resolver<ResolversTypes['MoveLabelResult'], ParentType, ContextType, RequireFields<MutationMoveLabelArgs, 'input'>>;
@ -5977,6 +6023,9 @@ export type Resolvers<ContextType = ResolverContext> = {
LoginError?: LoginErrorResolvers<ContextType>;
LoginResult?: LoginResultResolvers<ContextType>;
LoginSuccess?: LoginSuccessResolvers<ContextType>;
MarkEmailAsItemError?: MarkEmailAsItemErrorResolvers<ContextType>;
MarkEmailAsItemResult?: MarkEmailAsItemResultResolvers<ContextType>;
MarkEmailAsItemSuccess?: MarkEmailAsItemSuccessResolvers<ContextType>;
MergeHighlightError?: MergeHighlightErrorResolvers<ContextType>;
MergeHighlightResult?: MergeHighlightResultResolvers<ContextType>;
MergeHighlightSuccess?: MergeHighlightSuccessResolvers<ContextType>;

View file

@ -943,6 +943,22 @@ type LoginSuccess {
me: User!
}
type MarkEmailAsItemError {
errorCodes: [MarkEmailAsItemErrorCode!]!
}
enum MarkEmailAsItemErrorCode {
BAD_REQUEST
NOT_FOUND
UNAUTHORIZED
}
union MarkEmailAsItemResult = MarkEmailAsItemError | MarkEmailAsItemSuccess
type MarkEmailAsItemSuccess {
success: Boolean!
}
type MergeHighlightError {
errorCodes: [MergeHighlightErrorCode!]!
}
@ -1046,6 +1062,7 @@ type Mutation {
joinGroup(inviteCode: String!): JoinGroupResult!
leaveGroup(groupId: ID!): LeaveGroupResult!
logOut: LogOutResult!
markEmailAsItem(recentEmailId: ID!): MarkEmailAsItemResult!
mergeHighlight(input: MergeHighlightInput!): MergeHighlightResult!
moveFilter(input: MoveFilterInput!): MoveFilterResult!
moveLabel(input: MoveLabelInput!): MoveLabelResult!

View file

@ -117,7 +117,7 @@ import { getPageByParam } from '../elastic/pages'
import { recentSearchesResolver } from './recent_searches'
import { optInFeatureResolver } from './features'
import { uploadImportFileResolver } from './importers/uploadImportFileResolver'
import { recentEmailsResolver } from './recent_emails'
import { markEmailAsItemResolver, recentEmailsResolver } from './recent_emails'
/* eslint-disable @typescript-eslint/naming-convention */
type ResultResolveType = {
@ -200,6 +200,7 @@ export const functionResolvers = {
recommendHighlights: recommendHighlightsResolver,
leaveGroup: leaveGroupResolver,
uploadImportFile: uploadImportFileResolver,
markEmailAsItem: markEmailAsItemResolver,
},
Query: {
me: getMeUserResolver,
@ -662,4 +663,5 @@ export const functionResolvers = {
...resultResolveTypeResolver('LeaveGroup'),
...resultResolveTypeResolver('UploadImportFile'),
...resultResolveTypeResolver('RecentEmails'),
...resultResolveTypeResolver('MarkEmailAsItemResolver'),
}

View file

@ -1,4 +1,8 @@
import {
MarkEmailAsItemError,
MarkEmailAsItemErrorCode,
MarkEmailAsItemSuccess,
MutationMarkEmailAsItemArgs,
RecentEmailsError,
RecentEmailsErrorCode,
RecentEmailsSuccess,
@ -6,6 +10,12 @@ import {
import { authorized } from '../../utils/helpers'
import { getRepository } from '../../entity/utils'
import { ReceivedEmail } from '../../entity/received_email'
import { saveNewsletterEmail } from '../../services/save_newsletter_email'
import { NewsletterEmail } from '../../entity/newsletter_email'
import { v4 as uuid } from 'uuid'
const FAKE_URL_PREFIX = 'https://omnivore.app/no_url?q='
const generateUniqueUrl = () => FAKE_URL_PREFIX + uuid()
export const recentEmailsResolver = authorized<
RecentEmailsSuccess,
@ -44,3 +54,70 @@ export const recentEmailsResolver = authorized<
}
}
})
export const markEmailAsItemResolver = authorized<
MarkEmailAsItemSuccess,
MarkEmailAsItemError,
MutationMarkEmailAsItemArgs
>(async (_, { recentEmailId }, { claims, log }) => {
log.info('Marking email as item', {
recentEmailId,
labels: {
source: 'resolver',
resolver: 'markEmailAsItemResolver',
uid: claims.uid,
},
})
try {
const recentEmail = await getRepository(ReceivedEmail).findOneBy({
id: recentEmailId,
user: { id: claims.uid },
})
if (!recentEmail) {
return {
errorCodes: [MarkEmailAsItemErrorCode.Unauthorized],
}
}
const newsletterEmail = await getRepository(NewsletterEmail).findOneBy({
address: recentEmail.to,
user: { id: claims.uid },
})
if (!newsletterEmail) {
return {
errorCodes: [MarkEmailAsItemErrorCode.NotFound],
}
}
const success = await saveNewsletterEmail(
{
from: recentEmail.from,
email: recentEmail.to,
title: recentEmail.subject,
text: recentEmail.text,
content: recentEmail.html,
url: generateUniqueUrl(),
author: '',
},
newsletterEmail
)
return {
success,
}
} catch (error) {
log.error('Error marking email as item', {
error,
labels: {
source: 'resolver',
resolver: 'markEmailAsItemResolver',
uid: claims.uid,
},
})
return {
errorCodes: [MarkEmailAsItemErrorCode.BadRequest],
}
}
})

View file

@ -1,10 +1,14 @@
import express from 'express'
import { readPushSubscription } from '../../datalayer/pubsub'
import { updateConfirmationCode } from '../../services/newsletters'
import {
getNewsletterEmail,
updateConfirmationCode,
} from '../../services/newsletters'
import {
NewsletterMessage,
saveNewsletterEmail,
} from '../../services/save_newsletter_email'
import { saveReceivedEmail } from '../../services/received_emails'
interface SetConfirmationCodeMessage {
emailAddress: string
@ -96,7 +100,14 @@ export function newsletterServiceRouter() {
return
}
const result = await saveNewsletterEmail(data)
// get user from newsletter email
const newsletterEmail = await getNewsletterEmail(data.email)
if (!newsletterEmail) {
console.log('newsletter email not found', data.email)
return false
}
const result = await saveNewsletterEmail(data, newsletterEmail)
if (!result) {
console.log(
'Error creating newsletter link from data',
@ -104,10 +115,30 @@ export function newsletterServiceRouter() {
data.title,
data.author
)
await saveReceivedEmail(
data.from,
data.email,
data.title,
data.text,
data.content,
newsletterEmail.user.id
)
res.status(500).send('Error creating newsletter link')
return
}
await saveReceivedEmail(
data.from,
data.email,
data.title,
data.text,
data.content,
newsletterEmail.user.id,
'article'
)
// We always send 200 if it was a valid message
// because we don't want the
res.status(200).send('newsletter created')

View file

@ -2357,6 +2357,22 @@ const schema = gql`
createdAt: Date!
}
union MarkEmailAsItemResult = MarkEmailAsItemSuccess | MarkEmailAsItemError
type MarkEmailAsItemSuccess {
success: Boolean!
}
type MarkEmailAsItemError {
errorCodes: [MarkEmailAsItemErrorCode!]!
}
enum MarkEmailAsItemErrorCode {
UNAUTHORIZED
BAD_REQUEST
NOT_FOUND
}
# Mutations
type Mutation {
googleLogin(input: GoogleLoginInput!): LoginResult!
@ -2445,6 +2461,7 @@ const schema = gql`
type: UploadImportFileType!
contentType: String!
): UploadImportFileResult!
markEmailAsItem(recentEmailId: ID!): MarkEmailAsItemResult!
}
# FIXME: remove sort from feedArticles after all cached tabs are closed

View file

@ -4,7 +4,6 @@ import { UserDeviceToken } from '../entity/user_device_tokens'
import { env } from '../env'
import { ContentReader } from '../generated/graphql'
import { analytics } from '../utils/analytics'
import { getNewsletterEmail } from './newsletters'
import { SaveContext, saveEmail, SaveEmailInput } from './save_email'
import { Page } from '../elastic/types'
import { addLabelToPage } from './labels'
@ -13,7 +12,6 @@ import { NewsletterEmail } from '../entity/newsletter_email'
import { fetchFavicon } from '../utils/parser'
import { updatePage } from '../elastic/pages'
import { isBase64Image } from '../utils/helpers'
import { saveReceivedEmail } from './received_emails'
export interface NewsletterMessage {
from: string
@ -24,7 +22,6 @@ export interface NewsletterMessage {
author: string
unsubMailTo?: string
unsubHttpUrl?: string
newsletterEmail?: NewsletterEmail
text: string
}
@ -32,16 +29,9 @@ export interface NewsletterMessage {
// send the push but that is ok and we wont retry in that case.
export const saveNewsletterEmail = async (
data: NewsletterMessage,
newsletterEmail: NewsletterEmail,
ctx?: SaveContext
): Promise<boolean> => {
// get user from newsletter email
const newsletterEmail =
data.newsletterEmail || (await getNewsletterEmail(data.email))
if (!newsletterEmail) {
console.log('newsletter email not found', data.email)
return false
}
analytics.track({
userId: newsletterEmail.user.id,
event: 'newsletter_email_received',
@ -67,30 +57,11 @@ export const saveNewsletterEmail = async (
}
const page = await saveEmail(saveCtx, input)
if (!page) {
console.log('newsletter not created:', input)
await saveReceivedEmail(
data.from,
data.email,
data.title,
data.text,
data.content,
newsletterEmail.user.id
)
console.log('newsletter not created:', input.title)
return false
}
await saveReceivedEmail(
data.from,
data.email,
data.title,
data.text,
data.content,
newsletterEmail.user.id,
'article'
)
if (!page.siteIcon || isBase64Image(page.siteIcon)) {
// fetch favicon if not already set or is a base64 image
const favicon = await fetchFavicon(page.url)