mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #2101 from omnivore-app/feat/add-url-via-email
feat/add url via email
This commit is contained in:
commit
548e204acb
10 changed files with 115 additions and 56 deletions
|
|
@ -40,7 +40,7 @@ export class User {
|
|||
@OneToMany(() => NewsletterEmail, (newsletterEmail) => newsletterEmail.user)
|
||||
newsletterEmails?: NewsletterEmail[]
|
||||
|
||||
@OneToOne(() => Profile, (profile) => profile.user)
|
||||
@OneToOne(() => Profile, (profile) => profile.user, { eager: true })
|
||||
profile!: Profile
|
||||
|
||||
@Column('varchar', { length: 255, nullable: true })
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
import { ILike } from 'typeorm'
|
||||
import { NewsletterEmail } from '../../entity/newsletter_email'
|
||||
import { ReceivedEmail } from '../../entity/received_email'
|
||||
import { getRepository } from '../../entity/utils'
|
||||
import { env } from '../../env'
|
||||
import {
|
||||
MarkEmailAsItemError,
|
||||
MarkEmailAsItemErrorCode,
|
||||
|
|
@ -7,15 +12,11 @@ import {
|
|||
RecentEmailsErrorCode,
|
||||
RecentEmailsSuccess,
|
||||
} from '../../generated/graphql'
|
||||
import { authorized } from '../../utils/helpers'
|
||||
import { getRepository } from '../../entity/utils'
|
||||
import { ReceivedEmail } from '../../entity/received_email'
|
||||
import { updateReceivedEmail } from '../../services/received_emails'
|
||||
import { saveNewsletterEmail } from '../../services/save_newsletter_email'
|
||||
import { NewsletterEmail } from '../../entity/newsletter_email'
|
||||
import { authorized } from '../../utils/helpers'
|
||||
import { generateUniqueUrl, parseEmailAddress } from '../../utils/parser'
|
||||
import { sendEmail } from '../../utils/sendEmail'
|
||||
import { env } from '../../env'
|
||||
import { ILike } from 'typeorm'
|
||||
|
||||
export const recentEmailsResolver = authorized<
|
||||
RecentEmailsSuccess,
|
||||
|
|
@ -114,6 +115,8 @@ export const markEmailAsItemResolver = authorized<
|
|||
},
|
||||
newsletterEmail
|
||||
)
|
||||
// update received email type
|
||||
await updateReceivedEmail(recentEmail.id, 'article')
|
||||
|
||||
const text = `A recent email marked as a library item
|
||||
by: ${claims.uid}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import { User } from '../../entity/user'
|
||||
import { getRepository } from '../../entity/utils'
|
||||
import { env } from '../../env'
|
||||
import {
|
||||
MutationSaveFileArgs,
|
||||
MutationSavePageArgs,
|
||||
|
|
@ -6,12 +9,11 @@ import {
|
|||
SaveErrorCode,
|
||||
SaveSuccess,
|
||||
} from '../../generated/graphql'
|
||||
import { saveFile } from '../../services/save_file'
|
||||
import { savePage } from '../../services/save_page'
|
||||
import { saveUrl } from '../../services/save_url'
|
||||
import { saveFile } from '../../services/save_file'
|
||||
import { authorized, userDataToUser } from '../../utils/helpers'
|
||||
import { analytics } from '../../utils/analytics'
|
||||
import { env } from '../../env'
|
||||
import { authorized, userDataToUser } from '../../utils/helpers'
|
||||
|
||||
export const savePageResolver = authorized<
|
||||
SaveSuccess,
|
||||
|
|
@ -51,7 +53,6 @@ export const saveUrlResolver = authorized<
|
|||
MutationSaveUrlArgs
|
||||
>(async (_, { input }, ctx) => {
|
||||
const {
|
||||
models,
|
||||
claims: { uid },
|
||||
} = ctx
|
||||
|
||||
|
|
@ -66,7 +67,9 @@ export const saveUrlResolver = authorized<
|
|||
},
|
||||
})
|
||||
|
||||
const user = userDataToUser(await models.user.get(uid))
|
||||
const user = await getRepository(User).findOneBy({
|
||||
id: uid,
|
||||
})
|
||||
if (!user) {
|
||||
return { errorCodes: [SaveErrorCode.Unauthorized] }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
import express from 'express'
|
||||
import { readPushSubscription } from '../../datalayer/pubsub'
|
||||
import {
|
||||
createPubSubClient,
|
||||
readPushSubscription,
|
||||
} from '../../datalayer/pubsub'
|
||||
import {
|
||||
getNewsletterEmail,
|
||||
updateConfirmationCode,
|
||||
} from '../../services/newsletters'
|
||||
import { updateReceivedEmail } from '../../services/received_emails'
|
||||
import {
|
||||
NewsletterMessage,
|
||||
saveNewsletterEmail,
|
||||
} from '../../services/save_newsletter_email'
|
||||
import { updateReceivedEmail } from '../../services/received_emails'
|
||||
import { saveUrlFromEmail } from '../../services/save_url'
|
||||
import { isUrl } from '../../utils/helpers'
|
||||
|
||||
interface SetConfirmationCodeMessage {
|
||||
emailAddress: string
|
||||
|
|
@ -102,27 +107,41 @@ export function newsletterServiceRouter() {
|
|||
const newsletterEmail = await getNewsletterEmail(data.email)
|
||||
if (!newsletterEmail) {
|
||||
console.log('newsletter email not found', data.email)
|
||||
return false
|
||||
return res.status(200).send('Not Found')
|
||||
}
|
||||
|
||||
const result = await saveNewsletterEmail(data, newsletterEmail)
|
||||
if (!result) {
|
||||
console.log(
|
||||
'Error creating newsletter link from data',
|
||||
data.email,
|
||||
const saveCtx = {
|
||||
pubsub: createPubSubClient(),
|
||||
uid: newsletterEmail.user.id,
|
||||
}
|
||||
if (isUrl(data.title)) {
|
||||
// save url if the title is a parsable url
|
||||
const result = await saveUrlFromEmail(
|
||||
saveCtx,
|
||||
data.title,
|
||||
data.author
|
||||
data.receivedEmailId
|
||||
)
|
||||
if (!result) {
|
||||
return res.status(500).send('Error saving url from email')
|
||||
}
|
||||
} else {
|
||||
// save newsletter instead
|
||||
const result = await saveNewsletterEmail(data, newsletterEmail, saveCtx)
|
||||
if (!result) {
|
||||
console.log(
|
||||
'Error creating newsletter link from data',
|
||||
data.email,
|
||||
data.title,
|
||||
data.author
|
||||
)
|
||||
|
||||
res.status(500).send('Error creating newsletter link')
|
||||
return
|
||||
return res.status(500).send('Error creating newsletter link')
|
||||
}
|
||||
}
|
||||
|
||||
// update received email type
|
||||
await updateReceivedEmail(data.receivedEmailId, 'article')
|
||||
|
||||
// We always send 200 if it was a valid message
|
||||
// because we don't want the
|
||||
res.status(200).send('newsletter created')
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ interface PageSaveRequest {
|
|||
archivedAt?: Date | null
|
||||
labels?: Label[]
|
||||
priority?: 'low' | 'high'
|
||||
user?: User | null
|
||||
}
|
||||
|
||||
const SAVING_CONTENT = 'Your link is being saved...'
|
||||
|
|
@ -76,6 +77,7 @@ export const createPageSaveRequest = async ({
|
|||
archivedAt,
|
||||
priority,
|
||||
labels,
|
||||
user,
|
||||
}: PageSaveRequest): Promise<ArticleSavingRequest> => {
|
||||
try {
|
||||
validateUrl(url)
|
||||
|
|
@ -85,16 +87,17 @@ export const createPageSaveRequest = async ({
|
|||
errorCode: CreateArticleSavingRequestErrorCode.BadData,
|
||||
})
|
||||
}
|
||||
|
||||
const user = await getRepository(User).findOne({
|
||||
where: { id: userId },
|
||||
relations: ['profile'],
|
||||
})
|
||||
// if user is not specified, get it from the database
|
||||
if (!user) {
|
||||
console.log('User not found', userId)
|
||||
return Promise.reject({
|
||||
errorCode: CreateArticleSavingRequestErrorCode.BadData,
|
||||
user = await getRepository(User).findOneBy({
|
||||
id: userId,
|
||||
})
|
||||
if (!user) {
|
||||
console.log('User not found', userId)
|
||||
return Promise.reject({
|
||||
errorCode: CreateArticleSavingRequestErrorCode.BadData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// get priority by checking rate limit if not specified
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import { analytics } from '../utils/analytics'
|
|||
import { isBase64Image } from '../utils/helpers'
|
||||
import { fetchFavicon } from '../utils/parser'
|
||||
import { addLabelToPage } from './labels'
|
||||
import { updateReceivedEmail } from './received_emails'
|
||||
import { SaveContext, saveEmail, SaveEmailInput } from './save_email'
|
||||
import { saveSubscription } from './subscriptions'
|
||||
|
||||
|
|
@ -63,9 +62,6 @@ export const saveNewsletterEmail = async (
|
|||
return false
|
||||
}
|
||||
|
||||
// update received email type
|
||||
await updateReceivedEmail(data.receivedEmailId, 'article')
|
||||
|
||||
if (!page.siteIcon || isBase64Image(page.siteIcon)) {
|
||||
// fetch favicon if not already set or is a base64 image
|
||||
const favicon = await fetchFavicon(page.url)
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
import { PubsubClient } from '../datalayer/pubsub'
|
||||
import { UserData } from '../datalayer/user/model'
|
||||
import { ArticleSavingRequestStatus } from '../elastic/types'
|
||||
import { User } from '../entity/user'
|
||||
import { getRepository } from '../entity/utils'
|
||||
import { homePageURL } from '../env'
|
||||
import { SaveErrorCode, SaveResult, SaveUrlInput } from '../generated/graphql'
|
||||
import { DataModels } from '../resolvers/types'
|
||||
import { createPageSaveRequest } from './create_page_save_request'
|
||||
import { ArticleSavingRequestStatus } from '../elastic/types'
|
||||
import { createLabels } from './labels'
|
||||
|
||||
type SaveContext = {
|
||||
interface SaveContext {
|
||||
pubsub: PubsubClient
|
||||
models: DataModels
|
||||
uid: string
|
||||
}
|
||||
|
||||
export const saveUrl = async (
|
||||
ctx: SaveContext,
|
||||
saver: UserData,
|
||||
user: User,
|
||||
input: SaveUrlInput
|
||||
): Promise<SaveResult> => {
|
||||
try {
|
||||
|
|
@ -23,28 +23,54 @@ export const saveUrl = async (
|
|||
input.state === ArticleSavingRequestStatus.Archived ? new Date() : null
|
||||
// add labels to page
|
||||
const labels = input.labels
|
||||
? await createLabels({ ...ctx, uid: saver.id }, input.labels)
|
||||
? await createLabels({ ...ctx, uid: ctx.uid }, input.labels)
|
||||
: undefined
|
||||
|
||||
const pageSaveRequest = await createPageSaveRequest({
|
||||
userId: saver.id,
|
||||
userId: ctx.uid,
|
||||
url: input.url,
|
||||
pubsub: ctx.pubsub,
|
||||
articleSavingRequestId: input.clientRequestId,
|
||||
archivedAt,
|
||||
labels,
|
||||
user,
|
||||
})
|
||||
|
||||
return {
|
||||
clientRequestId: pageSaveRequest.id,
|
||||
url: `${homePageURL()}/${saver.profile.username}/links/${
|
||||
url: `${homePageURL()}/${user.profile.username}/links/${
|
||||
pageSaveRequest.id
|
||||
}`,
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('error enqueuing request', error)
|
||||
return {
|
||||
__typename: 'SaveError',
|
||||
errorCodes: [SaveErrorCode.Unknown],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const saveUrlFromEmail = async (
|
||||
ctx: SaveContext,
|
||||
url: string,
|
||||
clientRequestId: string
|
||||
): Promise<boolean> => {
|
||||
const user = await getRepository(User).findOneBy({
|
||||
id: ctx.uid,
|
||||
})
|
||||
if (!user) {
|
||||
return false
|
||||
}
|
||||
|
||||
const result = await saveUrl(ctx, user, {
|
||||
url,
|
||||
clientRequestId,
|
||||
source: 'email',
|
||||
})
|
||||
if (result.__typename === 'SaveError') {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -289,3 +289,12 @@ export const generateRandomColor = (): string => {
|
|||
export const unescapeHtml = (html: string): string => {
|
||||
return _.unescape(html)
|
||||
}
|
||||
|
||||
export const isUrl = (str: string): boolean => {
|
||||
try {
|
||||
new URL(str)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,16 +40,22 @@ describe('create user', () => {
|
|||
const testUser = 'testuser'
|
||||
|
||||
const adminUser = await createTestUser(testOwner)
|
||||
const admninIds = [adminUser.id]
|
||||
const [, invite] = await createGroup({
|
||||
admin: adminUser,
|
||||
name: 'testgroup',
|
||||
})
|
||||
const user = await createTestUser(testUser, invite.code)
|
||||
const userIds = [user.id]
|
||||
|
||||
expect(await getUserFollowers(user)).to.eql([adminUser])
|
||||
expect(await getUserFollowing(user)).to.eql([adminUser])
|
||||
expect(await getUserFollowers(adminUser)).to.eql([user])
|
||||
expect(await getUserFollowing(adminUser)).to.eql([user])
|
||||
const userFollowers = await getUserFollowers(user)
|
||||
const userFollowing = await getUserFollowing(user)
|
||||
const adminUserFollowers = await getUserFollowers(adminUser)
|
||||
const adminUserFollowing = await getUserFollowing(adminUser)
|
||||
expect(userFollowers.map(u => u.id)).to.eql(admninIds)
|
||||
expect(userFollowing.map(u => u.id)).to.eql(admninIds)
|
||||
expect(adminUserFollowers.map(u => u.id)).to.eql(userIds)
|
||||
expect(adminUserFollowing.map(u => u.id)).to.eql(userIds)
|
||||
})
|
||||
|
||||
it('creates profile when user exists but profile not', async () => {
|
||||
|
|
|
|||
|
|
@ -80,12 +80,6 @@ describe('saveNewsletterEmail', () => {
|
|||
newsletterEmail: { id: newsletterEmail.id },
|
||||
})
|
||||
expect(subscriptions).not.to.be.empty
|
||||
|
||||
// check if the received email was updated
|
||||
const updatedReceivedEmail = await getRepository(ReceivedEmail).findOneBy({
|
||||
id: receivedEmail.id,
|
||||
})
|
||||
expect(updatedReceivedEmail?.type).to.equal('article')
|
||||
})
|
||||
|
||||
it('adds a Newsletter label to that page', async () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue