fix newsletters test

This commit is contained in:
Hongbo Wu
2023-09-12 22:42:49 +09:00
parent c3caf15e44
commit cbaa3d6900
8 changed files with 64 additions and 59 deletions

View File

@ -19,7 +19,7 @@ export class Integration {
@PrimaryGeneratedColumn('uuid') @PrimaryGeneratedColumn('uuid')
id!: string id!: string
@ManyToOne(() => User, { onDelete: 'CASCADE' }) @ManyToOne(() => User, { onDelete: 'CASCADE', eager: true })
@JoinColumn({ name: 'user_id' }) @JoinColumn({ name: 'user_id' })
user!: User user!: User

View File

@ -102,7 +102,7 @@ export const markEmailAsItemResolver = authorized<
} }
// update received email type // update received email type
await updateReceivedEmail(recentEmail.id, 'article') await updateReceivedEmail(recentEmail.id, 'article', uid)
const text = `A recent email marked as a library item const text = `A recent email marked as a library item
by: ${uid} by: ${uid}

View File

@ -103,27 +103,24 @@ export function integrationsServiceRouter() {
res.status(200).send('Bad Request') res.status(200).send('Bad Request')
return return
} }
const page = await findLibraryItemById(id, userId) const item = await findLibraryItemById(id, userId)
if (!page) { if (!item) {
logger.info('No page found for id', { id }) logger.info('No item found for id', { id })
res.status(200).send('No page found') res.status(200).send('No page found')
return return
} }
if (page.user.id !== userId) {
logger.info('Page does not belong to user', { id, userId }) // sync updated item with integration
return res.status(200).send('Page does not belong to user') logger.info('syncing updated item with integration', {
}
// sync updated page with integration
logger.info('syncing updated page with integration', {
integrationId: integration.id, integrationId: integration.id,
pageId: page.id, itemId: item.id,
}) })
const synced = await integrationService.export(integration, [page]) const synced = await integrationService.export(integration, [item])
if (!synced) { if (!synced) {
logger.info('failed to sync page', { logger.info('failed to sync item', {
integrationId: integration.id, integrationId: integration.id,
pageId: page.id, itemId: item.id,
}) })
return res.status(400).send('Failed to sync') return res.status(400).send('Failed to sync')
} }
@ -135,7 +132,7 @@ export function integrationsServiceRouter() {
let hasNextPage = true, let hasNextPage = true,
count = 0, count = 0,
after = 0, after = 0,
pages: LibraryItem[] = []; items: LibraryItem[] = [];
hasNextPage; hasNextPage;
after += size, hasNextPage = count > after after += size, hasNextPage = count > after
) { ) {
@ -148,15 +145,15 @@ export function integrationsServiceRouter() {
{ from: after, size, dateFilters }, { from: after, size, dateFilters },
userId userId
) )
pages = libraryItems items = libraryItems
const pageIds = pages.map((p) => p.id) const itemIds = items.map((p) => p.id)
logger.info('syncing pages', { pageIds }) logger.info('syncing items', { pageIds: itemIds })
const synced = await integrationService.export(integration, pages) const synced = await integrationService.export(integration, items)
if (!synced) { if (!synced) {
logger.error('failed to sync pages', { logger.error('failed to sync items', {
pageIds, pageIds: itemIds,
integrationId: integration.id, integrationId: integration.id,
}) })
return res.status(400).send('Failed to sync') return res.status(400).send('Failed to sync')

View File

@ -1,4 +1,5 @@
import axios from 'axios' import axios from 'axios'
import { updateIntegration } from '.'
import { HighlightType } from '../../entity/highlight' import { HighlightType } from '../../entity/highlight'
import { Integration } from '../../entity/integration' import { Integration } from '../../entity/integration'
import { LibraryItem } from '../../entity/library_item' import { LibraryItem } from '../../entity/library_item'
@ -64,7 +65,7 @@ export class ReadwiseIntegration extends IntegrationService {
): Promise<boolean> => { ): Promise<boolean> => {
let result = true let result = true
const highlights = items.flatMap(this.pageToReadwiseHighlight) const highlights = items.flatMap(this.libraryItemToReadwiseHighlight)
// If there are no highlights, we will skip the sync // If there are no highlights, we will skip the sync
if (highlights.length > 0) { if (highlights.length > 0) {
result = await this.syncWithReadwise(integration.token, highlights) result = await this.syncWithReadwise(integration.token, highlights)
@ -73,16 +74,18 @@ export class ReadwiseIntegration extends IntegrationService {
// update integration syncedAt if successful // update integration syncedAt if successful
if (result) { if (result) {
logger.info('updating integration syncedAt') logger.info('updating integration syncedAt')
await authTrx((t) => await updateIntegration(
t.getRepository(Integration).update(integration.id, { integration.id,
{
syncedAt: new Date(), syncedAt: new Date(),
}) },
integration.user.id
) )
} }
return result return result
} }
pageToReadwiseHighlight = (item: LibraryItem): ReadwiseHighlight[] => { libraryItemToReadwiseHighlight = (item: LibraryItem): ReadwiseHighlight[] => {
if (!item.highlights) return [] if (!item.highlights) return []
const category = item.siteName === 'Twitter' ? 'tweets' : 'articles' const category = item.siteName === 'Twitter' ? 'tweets' : 'articles'
return item.highlights return item.highlights
@ -128,7 +131,7 @@ export class ReadwiseIntegration extends IntegrationService {
{ {
headers: { headers: {
Authorization: `Token ${token}`, Authorization: `Token ${token}`,
ContentType: 'application/json', 'Content-Type': 'application/json',
}, },
timeout: 5000, // 5 seconds timeout: 5000, // 5 seconds
} }

View File

@ -91,23 +91,27 @@ export const addLabelsToLibraryItem = async (
userId: string, userId: string,
pubsub = createPubSubClient() pubsub = createPubSubClient()
) => { ) => {
await authTrx(async (tx) => { await authTrx(
const libraryItem = await tx async (tx) => {
.withRepository(libraryItemRepository) const libraryItem = await tx
.findOneByOrFail({ id: libraryItemId }) .withRepository(libraryItemRepository)
.findOneByOrFail({ id: libraryItemId })
if (libraryItem.labels) { if (libraryItem.labels) {
labels.push(...libraryItem.labels) labels.push(...libraryItem.labels)
} }
// save new labels // save new labels
await tx.getRepository(EntityLabel).save( await tx.getRepository(EntityLabel).save(
labels.map((l) => ({ labels.map((l) => ({
labelId: l.id, labelId: l.id,
libraryItemId, libraryItemId,
})) }))
) )
}) },
undefined,
userId
)
// create pubsub event // create pubsub event
await pubsub.entityCreated<(Label & { pageId: string })[]>( await pubsub.entityCreated<(Label & { pageId: string })[]>(

View File

@ -29,7 +29,7 @@ export const saveReceivedEmail = async (
export const updateReceivedEmail = async ( export const updateReceivedEmail = async (
id: string, id: string,
type: 'article' | 'non-article', type: 'article' | 'non-article',
userId?: string userId: string
) => { ) => {
return authTrx( return authTrx(
(t) => t.getRepository(ReceivedEmail).update(id, { type }), (t) => t.getRepository(ReceivedEmail).update(id, { type }),

View File

@ -20,7 +20,7 @@ import {
parsePreparedContent, parsePreparedContent,
parseUrlMetadata, parseUrlMetadata,
} from '../utils/parser' } from '../utils/parser'
import { findOrCreateLabels } from './labels' import { addLabelsToLibraryItem, findOrCreateLabels } from './labels'
import { import {
createLibraryItem, createLibraryItem,
findLibraryItemByUrl, findLibraryItemByUrl,
@ -132,10 +132,11 @@ export const saveEmail = async (
if (newsletterLabel) { if (newsletterLabel) {
// add newsletter label // add newsletter label
await findOrCreateLabels([newsletterLabel], input.userId) const labels = await findOrCreateLabels([newsletterLabel], input.userId)
await addLabelsToLibraryItem(labels, newLibraryItem.id, input.userId)
} }
await updateReceivedEmail(input.receivedEmailId, 'article') await updateReceivedEmail(input.receivedEmailId, 'article', input.userId)
// create a task to update thumbnail and pre-cache all images // create a task to update thumbnail and pre-cache all images
try { try {

View File

@ -127,12 +127,12 @@ describe('Integrations routers', () => {
let integration: Integration let integration: Integration
let item: LibraryItem let item: LibraryItem
let highlight: Highlight let highlight: Highlight
let highlightsData: string let highlightsData: any
before(async () => { before(async () => {
integration = await saveIntegration( integration = await saveIntegration(
{ {
user: { id: user.id }, user,
name: 'READWISE', name: 'READWISE',
token: 'token', token: 'token',
}, },
@ -157,24 +157,24 @@ describe('Integrations routers', () => {
user.id user.id
) )
// create highlights data for integration request // create highlights data for integration request
highlightsData = JSON.stringify({ highlightsData = {
highlights: [ highlights: [
{ {
text: highlight.quote, text: highlight.quote,
title: item.title, title: item.title,
author: item.author, author: item.author ?? undefined,
highlight_url: getHighlightUrl(item.slug, highlight.id), highlight_url: getHighlightUrl(item.slug, highlight.id),
highlighted_at: highlight.createdAt.toISOString(), highlighted_at: highlight.createdAt.toISOString(),
category: 'articles', category: 'articles',
image_url: item.thumbnail, image_url: item.thumbnail ?? undefined,
// location: highlightPositionPercent, // location: highlightPositionPercent,
location_type: 'order', location_type: 'order',
note: highlight.annotation, note: highlight.annotation ?? undefined,
source_type: 'omnivore', source_type: 'omnivore',
source_url: item.originalUrl, source_url: item.originalUrl,
}, },
], ],
}) }
}) })
after(async () => { after(async () => {
@ -205,7 +205,7 @@ describe('Integrations routers', () => {
nock(READWISE_API_URL, { nock(READWISE_API_URL, {
reqheaders: { reqheaders: {
Authorization: `Token ${integration.token}`, Authorization: `Token ${integration.token}`,
ContentType: 'application/json', 'Content-Type': 'application/json',
}, },
}) })
.post('/highlights', highlightsData) .post('/highlights', highlightsData)
@ -227,7 +227,7 @@ describe('Integrations routers', () => {
nock(READWISE_API_URL, { nock(READWISE_API_URL, {
reqheaders: { reqheaders: {
Authorization: `Token ${integration.token}`, Authorization: `Token ${integration.token}`,
ContentType: 'application/json', 'Content-Type': 'application/json',
}, },
}) })
.post('/highlights') .post('/highlights')
@ -236,7 +236,7 @@ describe('Integrations routers', () => {
nock(READWISE_API_URL, { nock(READWISE_API_URL, {
reqheaders: { reqheaders: {
Authorization: `Token ${integration.token}`, Authorization: `Token ${integration.token}`,
ContentType: 'application/json', 'Content-Type': 'application/json',
}, },
}) })
.post('/highlights') .post('/highlights')
@ -272,7 +272,7 @@ describe('Integrations routers', () => {
nock(READWISE_API_URL, { nock(READWISE_API_URL, {
reqheaders: { reqheaders: {
Authorization: `Token ${integration.token}`, Authorization: `Token ${integration.token}`,
ContentType: 'application/json', 'Content-Type': 'application/json',
}, },
}) })
.post('/highlights', highlightsData) .post('/highlights', highlightsData)
@ -306,7 +306,7 @@ describe('Integrations routers', () => {
nock(READWISE_API_URL, { nock(READWISE_API_URL, {
reqheaders: { reqheaders: {
Authorization: `Token ${integration.token}`, Authorization: `Token ${integration.token}`,
ContentType: 'application/json', 'Content-Type': 'application/json',
}, },
}) })
.post('/highlights', highlightsData) .post('/highlights', highlightsData)