Change GraphQL APIs

This commit is contained in:
Hongbo Wu
2023-03-13 17:14:36 +08:00
parent 71826148fd
commit b0e91cbbbb
9 changed files with 90 additions and 34 deletions

View File

@ -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<boolean> => {
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<Highlight>(
EntityType.HIGHLIGHT,
highlight,

View File

@ -282,7 +282,7 @@ export const setLabelsForHighlight = async (
lang: 'painless',
params: {
highlightId,
labels: labels,
labels,
updatedAt: new Date(),
},
},

View File

@ -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

View File

@ -348,12 +348,13 @@ export type CreateHighlightInput = {
highlightPositionPercent?: InputMaybe<Scalars['Float']>;
html?: InputMaybe<Scalars['String']>;
id: Scalars['ID'];
patch: Scalars['String'];
patch?: InputMaybe<Scalars['String']>;
prefix?: InputMaybe<Scalars['String']>;
quote: Scalars['String'];
quote?: InputMaybe<Scalars['String']>;
sharedAt?: InputMaybe<Scalars['Date']>;
shortId: Scalars['String'];
suffix?: InputMaybe<Scalars['String']>;
type?: InputMaybe<HighlightType>;
};
export type CreateHighlightReplyError = {
@ -905,14 +906,15 @@ export type Highlight = {
html?: Maybe<Scalars['String']>;
id: Scalars['ID'];
labels?: Maybe<Array<Label>>;
patch: Scalars['String'];
patch?: Maybe<Scalars['String']>;
prefix?: Maybe<Scalars['String']>;
quote: Scalars['String'];
quote?: Maybe<Scalars['String']>;
reactions: Array<Reaction>;
replies: Array<HighlightReply>;
sharedAt?: Maybe<Scalars['Date']>;
shortId: Scalars['String'];
suffix?: Maybe<Scalars['String']>;
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<Highlight>;
HighlightReply: ResolverTypeWrapper<HighlightReply>;
HighlightStats: ResolverTypeWrapper<HighlightStats>;
HighlightType: HighlightType;
ID: ResolverTypeWrapper<Scalars['ID']>;
Int: ResolverTypeWrapper<Scalars['Int']>;
Integration: ResolverTypeWrapper<Integration>;
@ -4722,14 +4731,15 @@ export type HighlightResolvers<ContextType = ResolverContext, ParentType extends
html?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
labels?: Resolver<Maybe<Array<ResolversTypes['Label']>>, ParentType, ContextType>;
patch?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
patch?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
prefix?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
quote?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
quote?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
reactions?: Resolver<Array<ResolversTypes['Reaction']>, ParentType, ContextType>;
replies?: Resolver<Array<ResolversTypes['HighlightReply']>, ParentType, ContextType>;
sharedAt?: Resolver<Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
shortId?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
suffix?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
type?: Resolver<ResolversTypes['HighlightType'], ParentType, ContextType>;
updatedAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
user?: Resolver<ResolversTypes['User'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;

View File

@ -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!

View File

@ -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(

View File

@ -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
}

View File

@ -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),

View File

@ -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 (