From b0e91cbbbb03b7271dbc1f9fec7f4d2399f8d6e4 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Mon, 13 Mar 2023 17:14:36 +0800 Subject: [PATCH] Change GraphQL APIs --- packages/api/src/elastic/highlights.ts | 6 ++- packages/api/src/elastic/labels.ts | 2 +- packages/api/src/elastic/types.ts | 4 +- packages/api/src/generated/graphql.ts | 22 ++++++--- packages/api/src/generated/schema.graphql | 16 +++++-- packages/api/src/resolvers/highlight/index.ts | 45 ++++++++++++++----- packages/api/src/schema.ts | 16 +++++-- packages/api/src/services/integrations.ts | 11 +++-- packages/api/src/services/save_page.ts | 2 + 9 files changed, 90 insertions(+), 34 deletions(-) diff --git a/packages/api/src/elastic/highlights.ts b/packages/api/src/elastic/highlights.ts index 7a93338b4..a3d130153 100644 --- a/packages/api/src/elastic/highlights.ts +++ b/packages/api/src/elastic/highlights.ts @@ -29,7 +29,7 @@ export const addHighlightToPage = async ( ctx._source.updatedAt = params.highlight.updatedAt`, lang: 'painless', params: { - highlight: highlight, + highlight, }, }, }, @@ -254,7 +254,7 @@ export const updateHighlight = async ( ctx: PageContext ): Promise => { try { - await client.updateByQuery({ + const { body } = await client.updateByQuery({ index: INDEX_ALIAS, body: { script: { @@ -292,6 +292,8 @@ export const updateHighlight = async ( conflicts: 'proceed', }) + if (body.updated === 0) return false + await ctx.pubsub.entityUpdated( EntityType.HIGHLIGHT, highlight, diff --git a/packages/api/src/elastic/labels.ts b/packages/api/src/elastic/labels.ts index 79c2b6d97..5fcb0ba49 100644 --- a/packages/api/src/elastic/labels.ts +++ b/packages/api/src/elastic/labels.ts @@ -282,7 +282,7 @@ export const setLabelsForHighlight = async ( lang: 'painless', params: { highlightId, - labels: labels, + labels, updatedAt: new Date(), }, }, diff --git a/packages/api/src/elastic/types.ts b/packages/api/src/elastic/types.ts index 513b425c7..6ea001506 100644 --- a/packages/api/src/elastic/types.ts +++ b/packages/api/src/elastic/types.ts @@ -88,8 +88,8 @@ export interface Label { export interface Highlight { id: string shortId: string - patch: string - quote: string + patch?: string | null + quote?: string | null userId: string createdAt: Date prefix?: string | null diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index 2a14ee746..d1c96d1cf 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -348,12 +348,13 @@ export type CreateHighlightInput = { highlightPositionPercent?: InputMaybe; html?: InputMaybe; id: Scalars['ID']; - patch: Scalars['String']; + patch?: InputMaybe; prefix?: InputMaybe; - quote: Scalars['String']; + quote?: InputMaybe; sharedAt?: InputMaybe; shortId: Scalars['String']; suffix?: InputMaybe; + type?: InputMaybe; }; export type CreateHighlightReplyError = { @@ -905,14 +906,15 @@ export type Highlight = { html?: Maybe; id: Scalars['ID']; labels?: Maybe>; - patch: Scalars['String']; + patch?: Maybe; prefix?: Maybe; - quote: Scalars['String']; + quote?: Maybe; reactions: Array; replies: Array; sharedAt?: Maybe; shortId: Scalars['String']; suffix?: Maybe; + type: HighlightType; updatedAt: Scalars['Date']; user: User; }; @@ -932,6 +934,12 @@ export type HighlightStats = { highlightCount: Scalars['Int']; }; +export enum HighlightType { + Highlight = 'HIGHLIGHT', + Note = 'NOTE', + Redaction = 'REDACTION' +} + export type Integration = { __typename?: 'Integration'; createdAt: Scalars['Date']; @@ -3384,6 +3392,7 @@ export type ResolversTypes = { Highlight: ResolverTypeWrapper; HighlightReply: ResolverTypeWrapper; HighlightStats: ResolverTypeWrapper; + HighlightType: HighlightType; ID: ResolverTypeWrapper; Int: ResolverTypeWrapper; Integration: ResolverTypeWrapper; @@ -4722,14 +4731,15 @@ export type HighlightResolvers, ParentType, ContextType>; id?: Resolver; labels?: Resolver>, ParentType, ContextType>; - patch?: Resolver; + patch?: Resolver, ParentType, ContextType>; prefix?: Resolver, ParentType, ContextType>; - quote?: Resolver; + quote?: Resolver, ParentType, ContextType>; reactions?: Resolver, ParentType, ContextType>; replies?: Resolver, ParentType, ContextType>; sharedAt?: Resolver, ParentType, ContextType>; shortId?: Resolver; suffix?: Resolver, ParentType, ContextType>; + type?: Resolver; updatedAt?: Resolver; user?: Resolver; __isTypeOf?: IsTypeOfResolverFn; diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index 989f65764..7490fb535 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -300,12 +300,13 @@ input CreateHighlightInput { highlightPositionPercent: Float html: String id: ID! - patch: String! + patch: String prefix: String - quote: String! + quote: String sharedAt: Date shortId: String! suffix: String + type: HighlightType } type CreateHighlightReplyError { @@ -802,14 +803,15 @@ type Highlight { html: String id: ID! labels: [Label!] - patch: String! + patch: String prefix: String - quote: String! + quote: String reactions: [Reaction!]! replies: [HighlightReply!]! sharedAt: Date shortId: String! suffix: String + type: HighlightType! updatedAt: Date! user: User! } @@ -827,6 +829,12 @@ type HighlightStats { highlightCount: Int! } +enum HighlightType { + HIGHLIGHT + NOTE + REDACTION +} + type Integration { createdAt: Date! enabled: Boolean! diff --git a/packages/api/src/resolvers/highlight/index.ts b/packages/api/src/resolvers/highlight/index.ts index ec5c4f787..35e7b4ca4 100644 --- a/packages/api/src/resolvers/highlight/index.ts +++ b/packages/api/src/resolvers/highlight/index.ts @@ -8,7 +8,11 @@ import { updateHighlight, } from '../../elastic/highlights' import { getPageById, updatePage } from '../../elastic/pages' -import { Highlight as HighlightData } from '../../elastic/types' +import { + Highlight as HighlightData, + HighlightType, + Label, +} from '../../elastic/types' import { env } from '../../env' import { CreateHighlightError, @@ -81,6 +85,7 @@ export const createHighlightResolver = authorized< createdAt: new Date(), userId: claims.uid, annotation, + type: input.type || HighlightType.Highlight, } if ( @@ -142,20 +147,35 @@ export const mergeHighlightResolver = authorized< const articleHighlights = page.highlights /* Compute merged annotation form the order of highlights appearing on page */ - const overlapAnnotations: { [id: string]: string } = {} + const overlappings: { annotation?: string | null; labels?: Label[] }[] = [] articleHighlights.forEach((highlight, index) => { - if (overlapHighlightIdList.includes(highlight.id)) { + // only consider highlights that are in the overlap list + // and are of type highlight (not annotation or note) + if ( + overlapHighlightIdList.includes(highlight.id) && + highlight.type === HighlightType.Highlight + ) { articleHighlights.splice(index, 1) - - if (highlight.annotation) { - overlapAnnotations[highlight.id] = highlight.annotation - } + overlappings.push({ + annotation: highlight.annotation, + labels: highlight.labels, + }) } }) + console.log(overlapHighlightIdList) const mergedAnnotation: string[] = [] - overlapHighlightIdList.forEach((highlightId) => { - if (overlapAnnotations[highlightId]) { - mergedAnnotation.push(overlapAnnotations[highlightId]) + const mergedLabels: Label[] = [] + overlappings.forEach((highlight) => { + if (highlight.annotation) { + mergedAnnotation.push(highlight.annotation) + } + if (highlight.labels) { + // remove duplicates from labels by checking id + highlight.labels.forEach((label) => { + if (!mergedLabels.find((mergedLabel) => mergedLabel.id === label.id)) { + mergedLabels.push(label) + } + }) } }) @@ -165,7 +185,10 @@ export const mergeHighlightResolver = authorized< updatedAt: new Date(), createdAt: new Date(), userId: claims.uid, - annotation: mergedAnnotation ? mergedAnnotation.join('\n') : null, + annotation: + mergedAnnotation.length > 0 ? mergedAnnotation.join('\n') : null, + type: HighlightType.Highlight, + labels: mergedLabels, } const merged = await updatePage( diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index 5fbf077dc..cab9f41a2 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -660,18 +660,24 @@ const schema = gql` reactions: [Reaction!]! } + enum HighlightType { + HIGHLIGHT + REDACTION + NOTE + } + # Highlight type Highlight { id: ID! # used for simplified url format shortId: String! user: User! - quote: String! + quote: String # piece of content before the quote prefix: String # piece of content after the quote suffix: String - patch: String! + patch: String annotation: String replies: [HighlightReply!]! sharedAt: Date @@ -682,6 +688,7 @@ const schema = gql` highlightPositionPercent: Float highlightPositionAnchorIndex: Int labels: [Label!] + type: HighlightType! html: String } @@ -689,14 +696,15 @@ const schema = gql` id: ID! shortId: String! articleId: ID! - patch: String! - quote: String! @sanitize(maxLength: 6000, minLength: 1) + patch: String + quote: String @sanitize(maxLength: 6000, minLength: 1) prefix: String @sanitize suffix: String @sanitize annotation: String @sanitize(maxLength: 4000) sharedAt: Date highlightPositionPercent: Float highlightPositionAnchorIndex: Int + type: HighlightType html: String } diff --git a/packages/api/src/services/integrations.ts b/packages/api/src/services/integrations.ts index acde28e20..91d0df654 100644 --- a/packages/api/src/services/integrations.ts +++ b/packages/api/src/services/integrations.ts @@ -2,7 +2,7 @@ import { IntegrationType } from '../generated/graphql' import { env } from '../env' import axios from 'axios' import { wait } from '../utils/helpers' -import { Page } from '../elastic/types' +import { HighlightType, Page } from '../elastic/types' import { getHighlightUrl } from './highlights' import { Integration } from '../entity/integration' import { getRepository } from '../entity/utils' @@ -68,11 +68,14 @@ const pageToReadwiseHighlight = (page: Page): ReadwiseHighlight[] => { const category = page.siteName === 'Twitter' ? 'tweets' : 'articles' return ( page.highlights - // filter out highlights with no quote - .filter((highlight) => highlight.quote.length > 0) + // filter out highlights with no quote and are not of type Highlight + .filter( + (highlight) => + highlight.type === HighlightType.Highlight && highlight.quote + ) .map((highlight) => { return { - text: highlight.quote, + text: highlight.quote!, title: page.title, author: page.author || undefined, highlight_url: getHighlightUrl(page.slug, highlight.id), diff --git a/packages/api/src/services/save_page.ts b/packages/api/src/services/save_page.ts index 1acb6ea17..2158d1ace 100644 --- a/packages/api/src/services/save_page.ts +++ b/packages/api/src/services/save_page.ts @@ -6,6 +6,7 @@ import { createPage, getPageByParam, updatePage } from '../elastic/pages' import { ArticleSavingRequestStatus, Page, PageType } from '../elastic/types' import { homePageURL } from '../env' import { + HighlightType, Maybe, PreparedDocumentInput, SaveErrorCode, @@ -161,6 +162,7 @@ export const savePage = async ( userId: ctx.uid, elasticPageId: pageId, ...parseResult.highlightData, + type: HighlightType.Highlight, } if (