mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Add mark a recent email as library item api
This commit is contained in:
parent
62a3b48e00
commit
96f727a165
7 changed files with 198 additions and 34 deletions
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue