diff --git a/packages/api/src/entity/integration.ts b/packages/api/src/entity/integration.ts index dcb7f03c1c..7f1f5fe975 100644 --- a/packages/api/src/entity/integration.ts +++ b/packages/api/src/entity/integration.ts @@ -30,6 +30,9 @@ export class Integration { @JoinColumn({ name: 'user_id' }) user!: User + @Column('uuid', { name: 'user_id' }) + userId!: string + @Column('varchar', { length: 40 }) name!: string diff --git a/packages/api/src/services/highlights.ts b/packages/api/src/services/highlights.ts index 4ea812f30e..07dc5fe2ce 100644 --- a/packages/api/src/services/highlights.ts +++ b/packages/api/src/services/highlights.ts @@ -72,10 +72,6 @@ export const createHighlight = async ( const newHighlight = await repo.createAndSave(highlight) return repo.findOneOrFail({ where: { id: newHighlight.id }, - relations: { - user: true, - libraryItem: true, - }, }) }, { @@ -89,11 +85,7 @@ export const createHighlight = async ( { id: libraryItemId, highlights: [data], - // for Readwise - originalUrl: newHighlight.libraryItem.originalUrl, - title: newHighlight.libraryItem.title, - author: newHighlight.libraryItem.author, - thumbnail: newHighlight.libraryItem.thumbnail, + updatedAt: new Date(), }, userId ) @@ -133,10 +125,6 @@ export const mergeHighlights = async ( return highlightRepo.findOneOrFail({ where: { id: newHighlight.id }, - relations: { - user: true, - libraryItem: true, - }, }) }) @@ -144,11 +132,8 @@ export const mergeHighlights = async ( EntityType.HIGHLIGHT, { id: libraryItemId, - originalUrl: newHighlight.libraryItem.originalUrl, - title: newHighlight.libraryItem.title, - author: newHighlight.libraryItem.author, - thumbnail: newHighlight.libraryItem.thumbnail, highlights: [newHighlight], + updatedAt: new Date(), }, userId ) @@ -173,22 +158,14 @@ export const updateHighlight = async ( return highlightRepo.findOneOrFail({ where: { id: highlightId }, - relations: { - libraryItem: true, - user: true, - }, }) }) - const libraryItemId = updatedHighlight.libraryItem.id + const libraryItemId = updatedHighlight.libraryItemId await pubsub.entityUpdated( EntityType.HIGHLIGHT, { id: libraryItemId, - originalUrl: updatedHighlight.libraryItem.originalUrl, - title: updatedHighlight.libraryItem.title, - author: updatedHighlight.libraryItem.author, - thumbnail: updatedHighlight.libraryItem.thumbnail, highlights: [ { ...highlight, @@ -219,9 +196,6 @@ export const deleteHighlightById = async ( const highlightRepo = tx.withRepository(highlightRepository) const highlight = await highlightRepo.findOneOrFail({ where: { id: highlightId }, - relations: { - user: true, - }, }) await highlightRepo.delete(highlightId) @@ -234,7 +208,7 @@ export const deleteHighlightById = async ( await enqueueUpdateHighlight({ libraryItemId: deletedHighlight.libraryItemId, - userId: deletedHighlight.user.id, + userId: deletedHighlight.userId, }) return deletedHighlight diff --git a/packages/api/src/services/integrations/index.ts b/packages/api/src/services/integrations/index.ts index a8b11f916f..9d4e195af4 100644 --- a/packages/api/src/services/integrations/index.ts +++ b/packages/api/src/services/integrations/index.ts @@ -13,7 +13,7 @@ export const getIntegrationClient = ( ): IntegrationClient => { switch (name.toLowerCase()) { case 'readwise': - return new ReadwiseClient(token) + return new ReadwiseClient(token, integrationData) case 'pocket': return new PocketClient(token) case 'notion': diff --git a/packages/api/src/services/integrations/notion.ts b/packages/api/src/services/integrations/notion.ts index 3732986dd8..215658891a 100644 --- a/packages/api/src/services/integrations/notion.ts +++ b/packages/api/src/services/integrations/notion.ts @@ -7,7 +7,7 @@ import { env } from '../../env' import { Merge } from '../../util' import { logger } from '../../utils/logger' import { getHighlightUrl } from '../highlights' -import { getItemUrl, ItemEvent } from '../library_item' +import { findLibraryItemsByIds, getItemUrl, ItemEvent } from '../library_item' import { IntegrationClient } from './integration' type AnnotationColor = @@ -277,7 +277,9 @@ export class NotionClient implements IntegrationClient { }, annotations: { code: true, - color: highlight.color as AnnotationColor, + color: highlight.color + ? (highlight.color as AnnotationColor) + : 'yellow', }, }, ], @@ -337,6 +339,41 @@ export class NotionClient implements IntegrationClient { return false } + const userId = this.integrationData.userId + + // fetch the original url if not found + if (!items[0].originalUrl) { + const libraryItems = await findLibraryItemsByIds( + items.map((item) => item.id), + userId, + { + select: [ + 'id', + 'originalUrl', + 'title', + 'author', + 'thumbnail', + 'siteIcon', + 'savedAt', + ], + } + ) + + items.forEach((item) => { + const libraryItem = libraryItems.find((li) => li.id === item.id) + if (!libraryItem) { + return + } + + item.originalUrl = libraryItem.originalUrl + item.title = libraryItem.title + item.author = libraryItem.author + item.thumbnail = libraryItem.thumbnail + item.siteIcon = libraryItem.siteIcon + item.savedAt = libraryItem.savedAt + }) + } + await Promise.all( items.map(async (item) => { try { diff --git a/packages/api/src/services/integrations/readwise.ts b/packages/api/src/services/integrations/readwise.ts index 2a6ea2fd7d..360b961b22 100644 --- a/packages/api/src/services/integrations/readwise.ts +++ b/packages/api/src/services/integrations/readwise.ts @@ -1,8 +1,9 @@ import axios from 'axios' import { HighlightType } from '../../entity/highlight' +import { Integration } from '../../entity/integration' import { logger } from '../../utils/logger' import { getHighlightUrl } from '../highlights' -import { getItemUrl, ItemEvent } from '../library_item' +import { findLibraryItemsByIds, getItemUrl, ItemEvent } from '../library_item' import { IntegrationClient } from './integration' interface ReadwiseHighlight { @@ -43,9 +44,11 @@ export class ReadwiseClient implements IntegrationClient { baseURL: 'https://readwise.io/api/v2', timeout: 5000, // 5 seconds }) + private integrationData?: Integration - constructor(token: string) { + constructor(token: string, integration?: Integration) { this.token = token + this.integrationData = integration } accessToken = async (): Promise => { @@ -68,10 +71,42 @@ export class ReadwiseClient implements IntegrationClient { } export = async (items: ItemEvent[]): Promise => { - let result = true + if (!this.integrationData) { + logger.error('Integration data is missing') + return false + } + + if ( + items.every((item) => !item.highlights || item.highlights.length === 0) + ) { + return false + } + + const userId = this.integrationData.userId + const libraryItems = await findLibraryItemsByIds( + items.map((item) => item.id), + userId, + { + select: ['id', 'title', 'author', 'thumbnail', 'siteName'], + } + ) + console.log(libraryItems) + + items.forEach((item) => { + const libraryItem = libraryItems.find((li) => li.id === item.id) + if (!libraryItem) { + return + } + + item.title = libraryItem.title + item.author = libraryItem.author + item.thumbnail = libraryItem.thumbnail + item.siteName = libraryItem.siteName + }) const highlights = items.flatMap(this._itemToReadwiseHighlight) + let result = true // If there are no highlights, we will skip the sync if (highlights.length > 0) { result = await this._syncWithReadwise(highlights) diff --git a/packages/api/src/services/library_item.ts b/packages/api/src/services/library_item.ts index facb0b8c82..b7c8382023 100644 --- a/packages/api/src/services/library_item.ts +++ b/packages/api/src/services/library_item.ts @@ -794,6 +794,7 @@ export const findLibraryItemsByIds = async ( userId?: string, options?: { select?: (keyof LibraryItem)[] + relations?: Array<'labels' | 'highlights'> } ) => { const selectColumns = @@ -802,12 +803,20 @@ export const findLibraryItemsByIds = async ( .filter((column) => column !== 'originalContent') .map((column) => `library_item.${column}`) return authTrx( - async (tx) => - tx + async (tx) => { + const qb = tx .createQueryBuilder(LibraryItem, 'library_item') .select(selectColumns) .where('library_item.id IN (:...ids)', { ids }) - .getMany(), + + if (options?.relations) { + options.relations.forEach((relation) => { + qb.leftJoinAndSelect(`library_item.${relation}`, relation) + }) + } + + return qb.getMany() + }, { uid: userId, replicationMode: 'replica', @@ -963,6 +972,7 @@ export const updateLibraryItem = async ( EntityType.ITEM, { ...data, + updatedAt: new Date(), id, } as ItemEvent, userId @@ -1012,7 +1022,8 @@ export const updateLibraryItemReadingProgress = async ( reading_progress_top_percent as "readingProgressTopPercent", reading_progress_bottom_percent as "readingProgressBottomPercent", reading_progress_highest_read_anchor as "readingProgressHighestReadAnchor", - read_at as "readAt" + read_at as "readAt", + updated_at as "updatedAt" `, [id, topPercent, bottomPercent, anchorIndex] ),