Merge pull request #729 from omnivore-app/labels-in-highlights

Support labels in highlights
This commit is contained in:
Hongbo Wu 2022-06-09 09:28:43 +08:00 committed by GitHub
commit 309c581cf5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 588 additions and 189 deletions

View file

@ -94,8 +94,7 @@ export const updateLabelsInPage = async (
}
}
export const deleteLabelInPages = async (
userId: string,
export const deleteLabel = async (
label: string,
ctx: PageContext
): Promise<boolean> => {
@ -104,8 +103,12 @@ export const deleteLabelInPages = async (
index: INDEX_ALIAS,
body: {
script: {
source:
'ctx._source.labels.removeIf(label -> label.name == params.label)',
source: `if (ctx._source.highlights != null) {
ctx._source.highlights[0].labels.removeIf(label -> label.name == params.label)
}
if (ctx._source.labels != null) {
ctx._source.labels.removeIf(label -> label.name == params.label)
}`,
lang: 'painless',
params: {
label: label,
@ -113,12 +116,12 @@ export const deleteLabelInPages = async (
},
query: {
bool: {
filter: [
{
term: {
userId,
},
must: {
term: {
userId: ctx.uid,
},
},
should: [
{
nested: {
path: 'labels',
@ -129,11 +132,28 @@ export const deleteLabelInPages = async (
},
},
},
{
nested: {
path: 'highlights',
query: {
nested: {
path: 'highlights.labels',
query: {
term: {
'highlights.labels.name': label,
},
},
},
},
},
},
],
minimum_should_match: 1,
},
},
},
refresh: ctx.refresh,
conflicts: 'proceed', // ignore conflicts
})
body.updated > 0 &&
@ -146,7 +166,7 @@ export const deleteLabelInPages = async (
}
}
export const updateLabelInPage = async (
export const updateLabel = async (
label: Label,
ctx: PageContext
): Promise<boolean> => {
@ -155,8 +175,14 @@ export const updateLabelInPage = async (
index: INDEX_ALIAS,
body: {
script: {
source: `ctx._source.labels.removeIf(l -> l.id == params.label.id);
ctx._source.labels.add(params.label)`,
source: `if (ctx._source.labels != null) {
ctx._source.labels.removeIf(l -> l.id == params.label.id);
ctx._source.labels.add(params.label)
}
if (ctx._source.highlights != null) {
ctx._source.highlights[0].labels.removeIf(l -> l.id == params.label.id);
ctx._source.highlights[0].labels.add(params.label)
}`,
lang: 'painless',
params: {
label: label,
@ -164,12 +190,12 @@ export const updateLabelInPage = async (
},
query: {
bool: {
filter: [
{
term: {
userId: ctx.uid,
},
must: {
term: {
userId: ctx.uid,
},
},
should: [
{
nested: {
path: 'labels',
@ -180,7 +206,23 @@ export const updateLabelInPage = async (
},
},
},
{
nested: {
path: 'highlights',
query: {
nested: {
path: 'highlights.labels',
query: {
term: {
'highlights.labels.id': label.id,
},
},
},
},
},
},
],
minimum_should_match: 1,
},
},
},
@ -198,3 +240,60 @@ export const updateLabelInPage = async (
return false
}
}
export const setLabelsForHighlight = async (
highlightId: string,
labels: Label[],
ctx: PageContext
): Promise<boolean> => {
try {
const { body } = await client.updateByQuery({
index: INDEX_ALIAS,
body: {
script: {
source: `ctx._source.highlights[0].labels = params.labels`,
lang: 'painless',
params: {
labels: labels,
},
},
query: {
nested: {
path: 'highlights',
query: {
term: {
'highlights.id': highlightId,
},
},
},
},
},
refresh: ctx.refresh,
conflicts: 'proceed', // ignore conflicts
})
if (!body.updated) {
return false
}
for (const label of labels) {
await ctx.pubsub.entityCreated<Label & { highlightId: string }>(
EntityType.LABEL,
{ highlightId, ...label },
ctx.uid
)
}
return true
} catch (e) {
if (
e instanceof ResponseError &&
e.message === 'document_missing_exception'
) {
console.log('highlight has been deleted', highlightId)
return false
}
console.error('failed to set labels for highlight in elastic', e)
return false
}
}

View file

@ -163,6 +163,7 @@ export interface Highlight {
annotation?: string | null
sharedAt?: Date | null
updatedAt?: Date | null
labels?: Label[]
}
export interface Page {

View file

@ -111,8 +111,8 @@ export type Article = {
readingProgressPercent: Scalars['Float'];
savedAt: Scalars['Date'];
savedByViewer?: Maybe<Scalars['Boolean']>;
sharedComment?: Maybe<Scalars['String']>;
shareInfo?: Maybe<LinkShareInfo>;
sharedComment?: Maybe<Scalars['String']>;
siteIcon?: Maybe<Scalars['String']>;
siteName?: Maybe<Scalars['String']>;
slug: Scalars['String'];
@ -192,6 +192,11 @@ export type ArticleSavingRequestSuccess = {
articleSavingRequest: ArticleSavingRequest;
};
export type ArticleSuccess = {
__typename?: 'ArticleSuccess';
article: Article;
};
export type ArticlesError = {
__typename?: 'ArticlesError';
errorCodes: Array<ArticlesErrorCode>;
@ -209,11 +214,6 @@ export type ArticlesSuccess = {
pageInfo: PageInfo;
};
export type ArticleSuccess = {
__typename?: 'ArticleSuccess';
article: Article;
};
export enum ContentReader {
Pdf = 'PDF',
Web = 'WEB'
@ -776,6 +776,22 @@ export type LinkShareInfo = {
title: Scalars['String'];
};
export type LogOutError = {
__typename?: 'LogOutError';
errorCodes: Array<LogOutErrorCode>;
};
export enum LogOutErrorCode {
LogOutFailed = 'LOG_OUT_FAILED'
}
export type LogOutResult = LogOutError | LogOutSuccess;
export type LogOutSuccess = {
__typename?: 'LogOutSuccess';
message?: Maybe<Scalars['String']>;
};
export type LoginError = {
__typename?: 'LoginError';
errorCodes: Array<LoginErrorCode>;
@ -802,22 +818,6 @@ export type LoginSuccess = {
me: User;
};
export type LogOutError = {
__typename?: 'LogOutError';
errorCodes: Array<LogOutErrorCode>;
};
export enum LogOutErrorCode {
LogOutFailed = 'LOG_OUT_FAILED'
}
export type LogOutResult = LogOutError | LogOutSuccess;
export type LogOutSuccess = {
__typename?: 'LogOutSuccess';
message?: Maybe<Scalars['String']>;
};
export type MergeHighlightError = {
__typename?: 'MergeHighlightError';
errorCodes: Array<MergeHighlightErrorCode>;
@ -872,8 +872,8 @@ export type Mutation = {
generateApiKey: GenerateApiKeyResult;
googleLogin: LoginResult;
googleSignup: GoogleSignupResult;
login: LoginResult;
logOut: LogOutResult;
login: LoginResult;
mergeHighlight: MergeHighlightResult;
reportItem: ReportItemResult;
revokeApiKey: RevokeApiKeyResult;
@ -885,6 +885,7 @@ export type Mutation = {
setDeviceToken: SetDeviceTokenResult;
setFollow: SetFollowResult;
setLabels: SetLabelsResult;
setLabelsForHighlight: SetLabelsResult;
setLinkArchived: ArchiveLinkResult;
setShareArticle: SetShareArticleResult;
setShareHighlight: SetShareHighlightResult;
@ -1056,6 +1057,11 @@ export type MutationSetLabelsArgs = {
};
export type MutationSetLabelsForHighlightArgs = {
input: SetLabelsForHighlightInput;
};
export type MutationSetLinkArchivedArgs = {
input: ArchiveLinkInput;
};
@ -1234,8 +1240,8 @@ export type Query = {
__typename?: 'Query';
apiKeys: ApiKeysResult;
article: ArticleResult;
articles: ArticlesResult;
articleSavingRequest: ArticleSavingRequestResult;
articles: ArticlesResult;
feedArticles: FeedArticlesResult;
getFollowers: GetFollowersResult;
getFollowing: GetFollowingResult;
@ -1263,6 +1269,11 @@ export type QueryArticleArgs = {
};
export type QueryArticleSavingRequestArgs = {
id: Scalars['ID'];
};
export type QueryArticlesArgs = {
after?: InputMaybe<Scalars['String']>;
first?: InputMaybe<Scalars['Int']>;
@ -1273,11 +1284,6 @@ export type QueryArticlesArgs = {
};
export type QueryArticleSavingRequestArgs = {
id: Scalars['ID'];
};
export type QueryFeedArticlesArgs = {
after?: InputMaybe<Scalars['String']>;
first?: InputMaybe<Scalars['Int']>;
@ -1643,6 +1649,11 @@ export enum SetLabelsErrorCode {
Unauthorized = 'UNAUTHORIZED'
}
export type SetLabelsForHighlightInput = {
highlightId: Scalars['ID'];
labelIds: Array<Scalars['ID']>;
};
export type SetLabelsInput = {
labelIds: Array<Scalars['ID']>;
pageId: Scalars['ID'];
@ -1757,6 +1768,13 @@ export type SetWebhookSuccess = {
webhook: Webhook;
};
export type ShareStats = {
__typename?: 'ShareStats';
readDuration: Scalars['Int'];
saveCount: Scalars['Int'];
viewCount: Scalars['Int'];
};
export type SharedArticleError = {
__typename?: 'SharedArticleError';
errorCodes: Array<SharedArticleErrorCode>;
@ -1773,13 +1791,6 @@ export type SharedArticleSuccess = {
article: Article;
};
export type ShareStats = {
__typename?: 'ShareStats';
readDuration: Scalars['Int'];
saveCount: Scalars['Int'];
viewCount: Scalars['Int'];
};
export type SignupError = {
__typename?: 'SignupError';
errorCodes: Array<Maybe<SignupErrorCode>>;
@ -1861,6 +1872,12 @@ export type Subscription = {
url?: Maybe<Scalars['String']>;
};
export enum SubscriptionStatus {
Active = 'ACTIVE',
Deleted = 'DELETED',
Unsubscribed = 'UNSUBSCRIBED'
}
export type SubscriptionsError = {
__typename?: 'SubscriptionsError';
errorCodes: Array<SubscriptionsErrorCode>;
@ -1878,12 +1895,6 @@ export type SubscriptionsSuccess = {
subscriptions: Array<Subscription>;
};
export enum SubscriptionStatus {
Active = 'ACTIVE',
Deleted = 'DELETED',
Unsubscribed = 'UNSUBSCRIBED'
}
export type UnsubscribeError = {
__typename?: 'UnsubscribeError';
errorCodes: Array<UnsubscribeErrorCode>;
@ -2200,6 +2211,11 @@ export type UserPersonalization = {
export type UserResult = UserError | UserSuccess;
export type UserSuccess = {
__typename?: 'UserSuccess';
user: User;
};
export type UsersError = {
__typename?: 'UsersError';
errorCodes: Array<UsersErrorCode>;
@ -2216,11 +2232,6 @@ export type UsersSuccess = {
users: Array<User>;
};
export type UserSuccess = {
__typename?: 'UserSuccess';
user: User;
};
export type Webhook = {
__typename?: 'Webhook';
contentType: Scalars['String'];
@ -2258,6 +2269,11 @@ export enum WebhookEvent {
export type WebhookResult = WebhookError | WebhookSuccess;
export type WebhookSuccess = {
__typename?: 'WebhookSuccess';
webhook: Webhook;
};
export type WebhooksError = {
__typename?: 'WebhooksError';
errorCodes: Array<WebhooksErrorCode>;
@ -2275,11 +2291,6 @@ export type WebhooksSuccess = {
webhooks: Array<Webhook>;
};
export type WebhookSuccess = {
__typename?: 'WebhookSuccess';
webhook: Webhook;
};
export type ResolverTypeWrapper<T> = Promise<T> | T;
@ -2375,11 +2386,11 @@ export type ResolversTypes = {
ArticleSavingRequestResult: ResolversTypes['ArticleSavingRequestError'] | ResolversTypes['ArticleSavingRequestSuccess'];
ArticleSavingRequestStatus: ArticleSavingRequestStatus;
ArticleSavingRequestSuccess: ResolverTypeWrapper<ArticleSavingRequestSuccess>;
ArticleSuccess: ResolverTypeWrapper<ArticleSuccess>;
ArticlesError: ResolverTypeWrapper<ArticlesError>;
ArticlesErrorCode: ArticlesErrorCode;
ArticlesResult: ResolversTypes['ArticlesError'] | ResolversTypes['ArticlesSuccess'];
ArticlesSuccess: ResolverTypeWrapper<ArticlesSuccess>;
ArticleSuccess: ResolverTypeWrapper<ArticleSuccess>;
Boolean: ResolverTypeWrapper<Scalars['Boolean']>;
ContentReader: ContentReader;
CreateArticleError: ResolverTypeWrapper<CreateArticleError>;
@ -2492,15 +2503,15 @@ export type ResolversTypes = {
LabelsSuccess: ResolverTypeWrapper<LabelsSuccess>;
Link: ResolverTypeWrapper<Link>;
LinkShareInfo: ResolverTypeWrapper<LinkShareInfo>;
LogOutError: ResolverTypeWrapper<LogOutError>;
LogOutErrorCode: LogOutErrorCode;
LogOutResult: ResolversTypes['LogOutError'] | ResolversTypes['LogOutSuccess'];
LogOutSuccess: ResolverTypeWrapper<LogOutSuccess>;
LoginError: ResolverTypeWrapper<LoginError>;
LoginErrorCode: LoginErrorCode;
LoginInput: LoginInput;
LoginResult: ResolversTypes['LoginError'] | ResolversTypes['LoginSuccess'];
LoginSuccess: ResolverTypeWrapper<LoginSuccess>;
LogOutError: ResolverTypeWrapper<LogOutError>;
LogOutErrorCode: LogOutErrorCode;
LogOutResult: ResolversTypes['LogOutError'] | ResolversTypes['LogOutSuccess'];
LogOutSuccess: ResolverTypeWrapper<LogOutSuccess>;
MergeHighlightError: ResolverTypeWrapper<MergeHighlightError>;
MergeHighlightErrorCode: MergeHighlightErrorCode;
MergeHighlightInput: MergeHighlightInput;
@ -2573,6 +2584,7 @@ export type ResolversTypes = {
SetFollowSuccess: ResolverTypeWrapper<SetFollowSuccess>;
SetLabelsError: ResolverTypeWrapper<SetLabelsError>;
SetLabelsErrorCode: SetLabelsErrorCode;
SetLabelsForHighlightInput: SetLabelsForHighlightInput;
SetLabelsInput: SetLabelsInput;
SetLabelsResult: ResolversTypes['SetLabelsError'] | ResolversTypes['SetLabelsSuccess'];
SetLabelsSuccess: ResolverTypeWrapper<SetLabelsSuccess>;
@ -2596,11 +2608,11 @@ export type ResolversTypes = {
SetWebhookInput: SetWebhookInput;
SetWebhookResult: ResolversTypes['SetWebhookError'] | ResolversTypes['SetWebhookSuccess'];
SetWebhookSuccess: ResolverTypeWrapper<SetWebhookSuccess>;
ShareStats: ResolverTypeWrapper<ShareStats>;
SharedArticleError: ResolverTypeWrapper<SharedArticleError>;
SharedArticleErrorCode: SharedArticleErrorCode;
SharedArticleResult: ResolversTypes['SharedArticleError'] | ResolversTypes['SharedArticleSuccess'];
SharedArticleSuccess: ResolverTypeWrapper<SharedArticleSuccess>;
ShareStats: ResolverTypeWrapper<ShareStats>;
SignupError: ResolverTypeWrapper<SignupError>;
SignupErrorCode: SignupErrorCode;
SignupInput: SignupInput;
@ -2615,11 +2627,11 @@ export type ResolversTypes = {
SubscribeResult: ResolversTypes['SubscribeError'] | ResolversTypes['SubscribeSuccess'];
SubscribeSuccess: ResolverTypeWrapper<SubscribeSuccess>;
Subscription: ResolverTypeWrapper<{}>;
SubscriptionStatus: SubscriptionStatus;
SubscriptionsError: ResolverTypeWrapper<SubscriptionsError>;
SubscriptionsErrorCode: SubscriptionsErrorCode;
SubscriptionsResult: ResolversTypes['SubscriptionsError'] | ResolversTypes['SubscriptionsSuccess'];
SubscriptionsSuccess: ResolverTypeWrapper<SubscriptionsSuccess>;
SubscriptionStatus: SubscriptionStatus;
UnsubscribeError: ResolverTypeWrapper<UnsubscribeError>;
UnsubscribeErrorCode: UnsubscribeErrorCode;
UnsubscribeResult: ResolversTypes['UnsubscribeError'] | ResolversTypes['UnsubscribeSuccess'];
@ -2680,21 +2692,21 @@ export type ResolversTypes = {
UserErrorCode: UserErrorCode;
UserPersonalization: ResolverTypeWrapper<UserPersonalization>;
UserResult: ResolversTypes['UserError'] | ResolversTypes['UserSuccess'];
UserSuccess: ResolverTypeWrapper<UserSuccess>;
UsersError: ResolverTypeWrapper<UsersError>;
UsersErrorCode: UsersErrorCode;
UsersResult: ResolversTypes['UsersError'] | ResolversTypes['UsersSuccess'];
UsersSuccess: ResolverTypeWrapper<UsersSuccess>;
UserSuccess: ResolverTypeWrapper<UserSuccess>;
Webhook: ResolverTypeWrapper<Webhook>;
WebhookError: ResolverTypeWrapper<WebhookError>;
WebhookErrorCode: WebhookErrorCode;
WebhookEvent: WebhookEvent;
WebhookResult: ResolversTypes['WebhookError'] | ResolversTypes['WebhookSuccess'];
WebhookSuccess: ResolverTypeWrapper<WebhookSuccess>;
WebhooksError: ResolverTypeWrapper<WebhooksError>;
WebhooksErrorCode: WebhooksErrorCode;
WebhooksResult: ResolversTypes['WebhooksError'] | ResolversTypes['WebhooksSuccess'];
WebhooksSuccess: ResolverTypeWrapper<WebhooksSuccess>;
WebhookSuccess: ResolverTypeWrapper<WebhookSuccess>;
};
/** Mapping between all available schema types and the resolvers parents */
@ -2719,10 +2731,10 @@ export type ResolversParentTypes = {
ArticleSavingRequestError: ArticleSavingRequestError;
ArticleSavingRequestResult: ResolversParentTypes['ArticleSavingRequestError'] | ResolversParentTypes['ArticleSavingRequestSuccess'];
ArticleSavingRequestSuccess: ArticleSavingRequestSuccess;
ArticleSuccess: ArticleSuccess;
ArticlesError: ArticlesError;
ArticlesResult: ResolversParentTypes['ArticlesError'] | ResolversParentTypes['ArticlesSuccess'];
ArticlesSuccess: ArticlesSuccess;
ArticleSuccess: ArticleSuccess;
Boolean: Scalars['Boolean'];
CreateArticleError: CreateArticleError;
CreateArticleInput: CreateArticleInput;
@ -2813,13 +2825,13 @@ export type ResolversParentTypes = {
LabelsSuccess: LabelsSuccess;
Link: Link;
LinkShareInfo: LinkShareInfo;
LogOutError: LogOutError;
LogOutResult: ResolversParentTypes['LogOutError'] | ResolversParentTypes['LogOutSuccess'];
LogOutSuccess: LogOutSuccess;
LoginError: LoginError;
LoginInput: LoginInput;
LoginResult: ResolversParentTypes['LoginError'] | ResolversParentTypes['LoginSuccess'];
LoginSuccess: LoginSuccess;
LogOutError: LogOutError;
LogOutResult: ResolversParentTypes['LogOutError'] | ResolversParentTypes['LogOutSuccess'];
LogOutSuccess: LogOutSuccess;
MergeHighlightError: MergeHighlightError;
MergeHighlightInput: MergeHighlightInput;
MergeHighlightResult: ResolversParentTypes['MergeHighlightError'] | ResolversParentTypes['MergeHighlightSuccess'];
@ -2877,6 +2889,7 @@ export type ResolversParentTypes = {
SetFollowResult: ResolversParentTypes['SetFollowError'] | ResolversParentTypes['SetFollowSuccess'];
SetFollowSuccess: SetFollowSuccess;
SetLabelsError: SetLabelsError;
SetLabelsForHighlightInput: SetLabelsForHighlightInput;
SetLabelsInput: SetLabelsInput;
SetLabelsResult: ResolversParentTypes['SetLabelsError'] | ResolversParentTypes['SetLabelsSuccess'];
SetLabelsSuccess: SetLabelsSuccess;
@ -2896,10 +2909,10 @@ export type ResolversParentTypes = {
SetWebhookInput: SetWebhookInput;
SetWebhookResult: ResolversParentTypes['SetWebhookError'] | ResolversParentTypes['SetWebhookSuccess'];
SetWebhookSuccess: SetWebhookSuccess;
ShareStats: ShareStats;
SharedArticleError: SharedArticleError;
SharedArticleResult: ResolversParentTypes['SharedArticleError'] | ResolversParentTypes['SharedArticleSuccess'];
SharedArticleSuccess: SharedArticleSuccess;
ShareStats: ShareStats;
SignupError: SignupError;
SignupInput: SignupInput;
SignupResult: ResolversParentTypes['SignupError'] | ResolversParentTypes['SignupSuccess'];
@ -2960,17 +2973,17 @@ export type ResolversParentTypes = {
UserError: UserError;
UserPersonalization: UserPersonalization;
UserResult: ResolversParentTypes['UserError'] | ResolversParentTypes['UserSuccess'];
UserSuccess: UserSuccess;
UsersError: UsersError;
UsersResult: ResolversParentTypes['UsersError'] | ResolversParentTypes['UsersSuccess'];
UsersSuccess: UsersSuccess;
UserSuccess: UserSuccess;
Webhook: Webhook;
WebhookError: WebhookError;
WebhookResult: ResolversParentTypes['WebhookError'] | ResolversParentTypes['WebhookSuccess'];
WebhookSuccess: WebhookSuccess;
WebhooksError: WebhooksError;
WebhooksResult: ResolversParentTypes['WebhooksError'] | ResolversParentTypes['WebhooksSuccess'];
WebhooksSuccess: WebhooksSuccess;
WebhookSuccess: WebhookSuccess;
};
export type SanitizeDirectiveArgs = {
@ -3060,8 +3073,8 @@ export type ArticleResolvers<ContextType = ResolverContext, ParentType extends R
readingProgressPercent?: Resolver<ResolversTypes['Float'], ParentType, ContextType>;
savedAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
savedByViewer?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
sharedComment?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
shareInfo?: Resolver<Maybe<ResolversTypes['LinkShareInfo']>, ParentType, ContextType>;
sharedComment?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
siteIcon?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
siteName?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
slug?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
@ -3118,6 +3131,11 @@ export type ArticleSavingRequestSuccessResolvers<ContextType = ResolverContext,
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type ArticleSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['ArticleSuccess'] = ResolversParentTypes['ArticleSuccess']> = {
article?: Resolver<ResolversTypes['Article'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type ArticlesErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['ArticlesError'] = ResolversParentTypes['ArticlesError']> = {
errorCodes?: Resolver<Array<ResolversTypes['ArticlesErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
@ -3133,11 +3151,6 @@ export type ArticlesSuccessResolvers<ContextType = ResolverContext, ParentType e
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type ArticleSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['ArticleSuccess'] = ResolversParentTypes['ArticleSuccess']> = {
article?: Resolver<ResolversTypes['Article'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type CreateArticleErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['CreateArticleError'] = ResolversParentTypes['CreateArticleError']> = {
errorCodes?: Resolver<Array<ResolversTypes['CreateArticleErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
@ -3546,20 +3559,6 @@ export type LinkShareInfoResolvers<ContextType = ResolverContext, ParentType ext
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type LoginErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['LoginError'] = ResolversParentTypes['LoginError']> = {
errorCodes?: Resolver<Array<ResolversTypes['LoginErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type LoginResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['LoginResult'] = ResolversParentTypes['LoginResult']> = {
__resolveType: TypeResolveFn<'LoginError' | 'LoginSuccess', ParentType, ContextType>;
};
export type LoginSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['LoginSuccess'] = ResolversParentTypes['LoginSuccess']> = {
me?: Resolver<ResolversTypes['User'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type LogOutErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['LogOutError'] = ResolversParentTypes['LogOutError']> = {
errorCodes?: Resolver<Array<ResolversTypes['LogOutErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
@ -3574,6 +3573,20 @@ export type LogOutSuccessResolvers<ContextType = ResolverContext, ParentType ext
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type LoginErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['LoginError'] = ResolversParentTypes['LoginError']> = {
errorCodes?: Resolver<Array<ResolversTypes['LoginErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type LoginResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['LoginResult'] = ResolversParentTypes['LoginResult']> = {
__resolveType: TypeResolveFn<'LoginError' | 'LoginSuccess', ParentType, ContextType>;
};
export type LoginSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['LoginSuccess'] = ResolversParentTypes['LoginSuccess']> = {
me?: Resolver<ResolversTypes['User'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type MergeHighlightErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['MergeHighlightError'] = ResolversParentTypes['MergeHighlightError']> = {
errorCodes?: Resolver<Array<ResolversTypes['MergeHighlightErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
@ -3609,8 +3622,8 @@ export type MutationResolvers<ContextType = ResolverContext, ParentType extends
generateApiKey?: Resolver<ResolversTypes['GenerateApiKeyResult'], ParentType, ContextType, RequireFields<MutationGenerateApiKeyArgs, 'input'>>;
googleLogin?: Resolver<ResolversTypes['LoginResult'], ParentType, ContextType, RequireFields<MutationGoogleLoginArgs, 'input'>>;
googleSignup?: Resolver<ResolversTypes['GoogleSignupResult'], ParentType, ContextType, RequireFields<MutationGoogleSignupArgs, 'input'>>;
login?: Resolver<ResolversTypes['LoginResult'], ParentType, ContextType, RequireFields<MutationLoginArgs, 'input'>>;
logOut?: Resolver<ResolversTypes['LogOutResult'], ParentType, ContextType>;
login?: Resolver<ResolversTypes['LoginResult'], ParentType, ContextType, RequireFields<MutationLoginArgs, 'input'>>;
mergeHighlight?: Resolver<ResolversTypes['MergeHighlightResult'], ParentType, ContextType, RequireFields<MutationMergeHighlightArgs, 'input'>>;
reportItem?: Resolver<ResolversTypes['ReportItemResult'], ParentType, ContextType, RequireFields<MutationReportItemArgs, 'input'>>;
revokeApiKey?: Resolver<ResolversTypes['RevokeApiKeyResult'], ParentType, ContextType, RequireFields<MutationRevokeApiKeyArgs, 'id'>>;
@ -3622,6 +3635,7 @@ export type MutationResolvers<ContextType = ResolverContext, ParentType extends
setDeviceToken?: Resolver<ResolversTypes['SetDeviceTokenResult'], ParentType, ContextType, RequireFields<MutationSetDeviceTokenArgs, 'input'>>;
setFollow?: Resolver<ResolversTypes['SetFollowResult'], ParentType, ContextType, RequireFields<MutationSetFollowArgs, 'input'>>;
setLabels?: Resolver<ResolversTypes['SetLabelsResult'], ParentType, ContextType, RequireFields<MutationSetLabelsArgs, 'input'>>;
setLabelsForHighlight?: Resolver<ResolversTypes['SetLabelsResult'], ParentType, ContextType, RequireFields<MutationSetLabelsForHighlightArgs, 'input'>>;
setLinkArchived?: Resolver<ResolversTypes['ArchiveLinkResult'], ParentType, ContextType, RequireFields<MutationSetLinkArchivedArgs, 'input'>>;
setShareArticle?: Resolver<ResolversTypes['SetShareArticleResult'], ParentType, ContextType, RequireFields<MutationSetShareArticleArgs, 'input'>>;
setShareHighlight?: Resolver<ResolversTypes['SetShareHighlightResult'], ParentType, ContextType, RequireFields<MutationSetShareHighlightArgs, 'input'>>;
@ -3702,8 +3716,8 @@ export type ProfileResolvers<ContextType = ResolverContext, ParentType extends R
export type QueryResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['Query'] = ResolversParentTypes['Query']> = {
apiKeys?: Resolver<ResolversTypes['ApiKeysResult'], ParentType, ContextType>;
article?: Resolver<ResolversTypes['ArticleResult'], ParentType, ContextType, RequireFields<QueryArticleArgs, 'slug' | 'username'>>;
articles?: Resolver<ResolversTypes['ArticlesResult'], ParentType, ContextType, Partial<QueryArticlesArgs>>;
articleSavingRequest?: Resolver<ResolversTypes['ArticleSavingRequestResult'], ParentType, ContextType, RequireFields<QueryArticleSavingRequestArgs, 'id'>>;
articles?: Resolver<ResolversTypes['ArticlesResult'], ParentType, ContextType, Partial<QueryArticlesArgs>>;
feedArticles?: Resolver<ResolversTypes['FeedArticlesResult'], ParentType, ContextType, Partial<QueryFeedArticlesArgs>>;
getFollowers?: Resolver<ResolversTypes['GetFollowersResult'], ParentType, ContextType, Partial<QueryGetFollowersArgs>>;
getFollowing?: Resolver<ResolversTypes['GetFollowingResult'], ParentType, ContextType, Partial<QueryGetFollowingArgs>>;
@ -3995,6 +4009,13 @@ export type SetWebhookSuccessResolvers<ContextType = ResolverContext, ParentType
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type ShareStatsResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['ShareStats'] = ResolversParentTypes['ShareStats']> = {
readDuration?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
saveCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
viewCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type SharedArticleErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['SharedArticleError'] = ResolversParentTypes['SharedArticleError']> = {
errorCodes?: Resolver<Array<ResolversTypes['SharedArticleErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
@ -4009,13 +4030,6 @@ export type SharedArticleSuccessResolvers<ContextType = ResolverContext, ParentT
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type ShareStatsResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['ShareStats'] = ResolversParentTypes['ShareStats']> = {
readDuration?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
saveCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
viewCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type SignupErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['SignupError'] = ResolversParentTypes['SignupError']> = {
errorCodes?: Resolver<Array<Maybe<ResolversTypes['SignupErrorCode']>>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
@ -4265,6 +4279,11 @@ export type UserResultResolvers<ContextType = ResolverContext, ParentType extend
__resolveType: TypeResolveFn<'UserError' | 'UserSuccess', ParentType, ContextType>;
};
export type UserSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UserSuccess'] = ResolversParentTypes['UserSuccess']> = {
user?: Resolver<ResolversTypes['User'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type UsersErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UsersError'] = ResolversParentTypes['UsersError']> = {
errorCodes?: Resolver<Array<ResolversTypes['UsersErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
@ -4279,11 +4298,6 @@ export type UsersSuccessResolvers<ContextType = ResolverContext, ParentType exte
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type UserSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UserSuccess'] = ResolversParentTypes['UserSuccess']> = {
user?: Resolver<ResolversTypes['User'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type WebhookResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['Webhook'] = ResolversParentTypes['Webhook']> = {
contentType?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
createdAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
@ -4305,6 +4319,11 @@ export type WebhookResultResolvers<ContextType = ResolverContext, ParentType ext
__resolveType: TypeResolveFn<'WebhookError' | 'WebhookSuccess', ParentType, ContextType>;
};
export type WebhookSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['WebhookSuccess'] = ResolversParentTypes['WebhookSuccess']> = {
webhook?: Resolver<ResolversTypes['Webhook'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type WebhooksErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['WebhooksError'] = ResolversParentTypes['WebhooksError']> = {
errorCodes?: Resolver<Array<ResolversTypes['WebhooksErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
@ -4319,11 +4338,6 @@ export type WebhooksSuccessResolvers<ContextType = ResolverContext, ParentType e
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type WebhookSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['WebhookSuccess'] = ResolversParentTypes['WebhookSuccess']> = {
webhook?: Resolver<ResolversTypes['Webhook'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type Resolvers<ContextType = ResolverContext> = {
AddPopularReadError?: AddPopularReadErrorResolvers<ContextType>;
AddPopularReadResult?: AddPopularReadResultResolvers<ContextType>;
@ -4343,10 +4357,10 @@ export type Resolvers<ContextType = ResolverContext> = {
ArticleSavingRequestError?: ArticleSavingRequestErrorResolvers<ContextType>;
ArticleSavingRequestResult?: ArticleSavingRequestResultResolvers<ContextType>;
ArticleSavingRequestSuccess?: ArticleSavingRequestSuccessResolvers<ContextType>;
ArticleSuccess?: ArticleSuccessResolvers<ContextType>;
ArticlesError?: ArticlesErrorResolvers<ContextType>;
ArticlesResult?: ArticlesResultResolvers<ContextType>;
ArticlesSuccess?: ArticlesSuccessResolvers<ContextType>;
ArticleSuccess?: ArticleSuccessResolvers<ContextType>;
CreateArticleError?: CreateArticleErrorResolvers<ContextType>;
CreateArticleResult?: CreateArticleResultResolvers<ContextType>;
CreateArticleSavingRequestError?: CreateArticleSavingRequestErrorResolvers<ContextType>;
@ -4423,12 +4437,12 @@ export type Resolvers<ContextType = ResolverContext> = {
LabelsSuccess?: LabelsSuccessResolvers<ContextType>;
Link?: LinkResolvers<ContextType>;
LinkShareInfo?: LinkShareInfoResolvers<ContextType>;
LoginError?: LoginErrorResolvers<ContextType>;
LoginResult?: LoginResultResolvers<ContextType>;
LoginSuccess?: LoginSuccessResolvers<ContextType>;
LogOutError?: LogOutErrorResolvers<ContextType>;
LogOutResult?: LogOutResultResolvers<ContextType>;
LogOutSuccess?: LogOutSuccessResolvers<ContextType>;
LoginError?: LoginErrorResolvers<ContextType>;
LoginResult?: LoginResultResolvers<ContextType>;
LoginSuccess?: LoginSuccessResolvers<ContextType>;
MergeHighlightError?: MergeHighlightErrorResolvers<ContextType>;
MergeHighlightResult?: MergeHighlightResultResolvers<ContextType>;
MergeHighlightSuccess?: MergeHighlightSuccessResolvers<ContextType>;
@ -4489,10 +4503,10 @@ export type Resolvers<ContextType = ResolverContext> = {
SetWebhookError?: SetWebhookErrorResolvers<ContextType>;
SetWebhookResult?: SetWebhookResultResolvers<ContextType>;
SetWebhookSuccess?: SetWebhookSuccessResolvers<ContextType>;
ShareStats?: ShareStatsResolvers<ContextType>;
SharedArticleError?: SharedArticleErrorResolvers<ContextType>;
SharedArticleResult?: SharedArticleResultResolvers<ContextType>;
SharedArticleSuccess?: SharedArticleSuccessResolvers<ContextType>;
ShareStats?: ShareStatsResolvers<ContextType>;
SignupError?: SignupErrorResolvers<ContextType>;
SignupResult?: SignupResultResolvers<ContextType>;
SignupSuccess?: SignupSuccessResolvers<ContextType>;
@ -4540,17 +4554,17 @@ export type Resolvers<ContextType = ResolverContext> = {
UserError?: UserErrorResolvers<ContextType>;
UserPersonalization?: UserPersonalizationResolvers<ContextType>;
UserResult?: UserResultResolvers<ContextType>;
UserSuccess?: UserSuccessResolvers<ContextType>;
UsersError?: UsersErrorResolvers<ContextType>;
UsersResult?: UsersResultResolvers<ContextType>;
UsersSuccess?: UsersSuccessResolvers<ContextType>;
UserSuccess?: UserSuccessResolvers<ContextType>;
Webhook?: WebhookResolvers<ContextType>;
WebhookError?: WebhookErrorResolvers<ContextType>;
WebhookResult?: WebhookResultResolvers<ContextType>;
WebhookSuccess?: WebhookSuccessResolvers<ContextType>;
WebhooksError?: WebhooksErrorResolvers<ContextType>;
WebhooksResult?: WebhooksResultResolvers<ContextType>;
WebhooksSuccess?: WebhooksSuccessResolvers<ContextType>;
WebhookSuccess?: WebhookSuccessResolvers<ContextType>;
};
export type DirectiveResolvers<ContextType = ResolverContext> = {

View file

@ -87,8 +87,8 @@ type Article {
readingProgressPercent: Float!
savedAt: Date!
savedByViewer: Boolean
sharedComment: String
shareInfo: LinkShareInfo
sharedComment: String
siteIcon: String
siteName: String
slug: String!
@ -156,6 +156,10 @@ type ArticleSavingRequestSuccess {
articleSavingRequest: ArticleSavingRequest!
}
type ArticleSuccess {
article: Article!
}
type ArticlesError {
errorCodes: [ArticlesErrorCode!]!
}
@ -171,10 +175,6 @@ type ArticlesSuccess {
pageInfo: PageInfo!
}
type ArticleSuccess {
article: Article!
}
enum ContentReader {
PDF
WEB
@ -686,6 +686,20 @@ type LinkShareInfo {
title: String!
}
type LogOutError {
errorCodes: [LogOutErrorCode!]!
}
enum LogOutErrorCode {
LOG_OUT_FAILED
}
union LogOutResult = LogOutError | LogOutSuccess
type LogOutSuccess {
message: String
}
type LoginError {
errorCodes: [LoginErrorCode!]!
}
@ -710,20 +724,6 @@ type LoginSuccess {
me: User!
}
type LogOutError {
errorCodes: [LogOutErrorCode!]!
}
enum LogOutErrorCode {
LOG_OUT_FAILED
}
union LogOutResult = LogOutError | LogOutSuccess
type LogOutSuccess {
message: String
}
type MergeHighlightError {
errorCodes: [MergeHighlightErrorCode!]!
}
@ -775,8 +775,8 @@ type Mutation {
generateApiKey(input: GenerateApiKeyInput!): GenerateApiKeyResult!
googleLogin(input: GoogleLoginInput!): LoginResult!
googleSignup(input: GoogleSignupInput!): GoogleSignupResult!
login(input: LoginInput!): LoginResult!
logOut: LogOutResult!
login(input: LoginInput!): LoginResult!
mergeHighlight(input: MergeHighlightInput!): MergeHighlightResult!
reportItem(input: ReportItemInput!): ReportItemResult!
revokeApiKey(id: ID!): RevokeApiKeyResult!
@ -788,6 +788,7 @@ type Mutation {
setDeviceToken(input: SetDeviceTokenInput!): SetDeviceTokenResult!
setFollow(input: SetFollowInput!): SetFollowResult!
setLabels(input: SetLabelsInput!): SetLabelsResult!
setLabelsForHighlight(input: SetLabelsForHighlightInput!): SetLabelsResult!
setLinkArchived(input: ArchiveLinkInput!): ArchiveLinkResult!
setShareArticle(input: SetShareArticleInput!): SetShareArticleResult!
setShareHighlight(input: SetShareHighlightInput!): SetShareHighlightResult!
@ -890,8 +891,8 @@ type Profile {
type Query {
apiKeys: ApiKeysResult!
article(slug: String!, username: String!): ArticleResult!
articles(after: String, first: Int, includePending: Boolean, query: String, sharedOnly: Boolean, sort: SortParams): ArticlesResult!
articleSavingRequest(id: ID!): ArticleSavingRequestResult!
articles(after: String, first: Int, includePending: Boolean, query: String, sharedOnly: Boolean, sort: SortParams): ArticlesResult!
feedArticles(after: String, first: Int, sharedByUser: ID, sort: SortParams): FeedArticlesResult!
getFollowers(userId: ID): GetFollowersResult!
getFollowing(userId: ID): GetFollowingResult!
@ -1195,6 +1196,11 @@ enum SetLabelsErrorCode {
UNAUTHORIZED
}
input SetLabelsForHighlightInput {
highlightId: ID!
labelIds: [ID!]!
}
input SetLabelsInput {
labelIds: [ID!]!
pageId: ID!
@ -1300,6 +1306,12 @@ type SetWebhookSuccess {
webhook: Webhook!
}
type ShareStats {
readDuration: Int!
saveCount: Int!
viewCount: Int!
}
type SharedArticleError {
errorCodes: [SharedArticleErrorCode!]!
}
@ -1314,12 +1326,6 @@ type SharedArticleSuccess {
article: Article!
}
type ShareStats {
readDuration: Int!
saveCount: Int!
viewCount: Int!
}
type SignupError {
errorCodes: [SignupErrorCode]!
}
@ -1396,6 +1402,12 @@ type Subscription {
url: String
}
enum SubscriptionStatus {
ACTIVE
DELETED
UNSUBSCRIBED
}
type SubscriptionsError {
errorCodes: [SubscriptionsErrorCode!]!
}
@ -1411,12 +1423,6 @@ type SubscriptionsSuccess {
subscriptions: [Subscription!]!
}
enum SubscriptionStatus {
ACTIVE
DELETED
UNSUBSCRIBED
}
type UnsubscribeError {
errorCodes: [UnsubscribeErrorCode!]!
}
@ -1707,6 +1713,10 @@ type UserPersonalization {
union UserResult = UserError | UserSuccess
type UserSuccess {
user: User!
}
type UsersError {
errorCodes: [UsersErrorCode!]!
}
@ -1721,10 +1731,6 @@ type UsersSuccess {
users: [User!]!
}
type UserSuccess {
user: User!
}
type Webhook {
contentType: String!
createdAt: Date!
@ -1760,6 +1766,10 @@ enum WebhookEvent {
union WebhookResult = WebhookError | WebhookSuccess
type WebhookSuccess {
webhook: Webhook!
}
type WebhooksError {
errorCodes: [WebhooksErrorCode!]!
}
@ -1774,7 +1784,3 @@ union WebhooksResult = WebhooksError | WebhooksSuccess
type WebhooksSuccess {
webhooks: [Webhook!]!
}
type WebhookSuccess {
webhook: Webhook!
}

View file

@ -65,6 +65,7 @@ import {
setBookmarkArticleResolver,
setDeviceTokenResolver,
setFollowResolver,
setLabelsForHighlightResolver,
setLabelsResolver,
setLinkArchivedResolver,
setShareArticleResolver,
@ -161,6 +162,7 @@ export const functionResolvers = {
setWebhook: setWebhookResolver,
deleteWebhook: deleteWebhookResolver,
revokeApiKey: revokeApiKeyResolver,
setLabelsForHighlight: setLabelsForHighlightResolver,
},
Query: {
me: getMeUserResolver,

View file

@ -12,6 +12,7 @@ import {
MutationCreateLabelArgs,
MutationDeleteLabelArgs,
MutationSetLabelsArgs,
MutationSetLabelsForHighlightArgs,
MutationUpdateLabelArgs,
SetLabelsError,
SetLabelsErrorCode,
@ -24,16 +25,19 @@ import { analytics } from '../../utils/analytics'
import { env } from '../../env'
import { User } from '../../entity/user'
import { Label } from '../../entity/label'
import { ILike, In } from 'typeorm'
import { ILike } from 'typeorm'
import { getRepository, setClaims } from '../../entity/utils'
import { createPubSubClient } from '../../datalayer/pubsub'
import { AppDataSource } from '../../server'
import { getPageById } from '../../elastic/pages'
import {
deleteLabelInPages,
updateLabelInPage,
deleteLabel,
setLabelsForHighlight,
updateLabel,
updateLabelsInPage,
} from '../../elastic/labels'
import { getHighlightById } from '../../elastic/highlights'
import { getLabelsByIds } from '../../services/labels'
export const labelsResolver = authorized<LabelsSuccess, LabelsError>(
async (_obj, _params, { claims: { uid }, log }) => {
@ -174,8 +178,8 @@ export const deleteLabelResolver = authorized<
}
}
// delete label in elastic pages
await deleteLabelInPages(uid, label.name, {
// delete label in elastic pages and highlights
await deleteLabel(labelId, {
pubsub: createPubSubClient(),
uid,
})
@ -223,11 +227,13 @@ export const setLabelsResolver = authorized<
errorCodes: [SetLabelsErrorCode.NotFound],
}
}
if (page.userId !== uid) {
return {
errorCodes: [SetLabelsErrorCode.Unauthorized],
}
}
const labels = await getRepository(Label).find({
where: { id: In(labelIds), user: { id: user.id } },
select: ['id', 'name', 'color', 'description', 'createdAt'],
})
const labels = await getLabelsByIds(uid, labelIds)
if (labels.length !== labelIds.length) {
return {
errorCodes: [SetLabelsErrorCode.NotFound],
@ -316,15 +322,10 @@ export const updateLabelResolver = authorized<
}
}
const updated = await updateLabelInPage(label, {
await updateLabel(label, {
pubsub,
uid,
})
if (!updated) {
return {
errorCodes: [UpdateLabelErrorCode.BadRequest],
}
}
return { label }
} catch (error) {
@ -334,3 +335,71 @@ export const updateLabelResolver = authorized<
}
}
})
export const setLabelsForHighlightResolver = authorized<
SetLabelsSuccess,
SetLabelsError,
MutationSetLabelsForHighlightArgs
>(async (_, { input }, { claims: { uid }, log, pubsub }) => {
log.info('setLabelsForHighlightResolver')
const { highlightId, labelIds } = input
try {
const user = await getRepository(User).findOneBy({ id: uid })
if (!user) {
return {
errorCodes: [SetLabelsErrorCode.Unauthorized],
}
}
const highlight = await getHighlightById(highlightId)
if (!highlight) {
return {
errorCodes: [SetLabelsErrorCode.NotFound],
}
}
if (highlight.userId !== uid) {
return {
errorCodes: [SetLabelsErrorCode.Unauthorized],
}
}
const labels = await getLabelsByIds(uid, labelIds)
if (labels.length !== labelIds.length) {
return {
errorCodes: [SetLabelsErrorCode.NotFound],
}
}
// set labels in the highlights
const updated = await setLabelsForHighlight(highlightId, labels, {
pubsub,
uid,
})
if (!updated) {
return {
errorCodes: [SetLabelsErrorCode.NotFound],
}
}
analytics.track({
userId: uid,
event: 'labels_set_for_highlight',
properties: {
highlightId,
labelIds,
env: env.server.apiEnv,
},
})
return {
labels,
}
} catch (error) {
log.error(error)
return {
errorCodes: [SetLabelsErrorCode.BadRequest],
}
}
})

View file

@ -1738,6 +1738,11 @@ const schema = gql`
NOT_FOUND
}
input SetLabelsForHighlightInput {
highlightId: ID!
labelIds: [ID!]!
}
# Mutations
type Mutation {
googleLogin(input: GoogleLoginInput!): LoginResult!
@ -1806,6 +1811,7 @@ const schema = gql`
setWebhook(input: SetWebhookInput!): SetWebhookResult!
deleteWebhook(id: ID!): DeleteWebhookResult!
revokeApiKey(id: ID!): RevokeApiKeyResult!
setLabelsForHighlight(input: SetLabelsForHighlightInput!): SetLabelsResult!
}
# FIXME: remove sort from feedArticles after all cached tabs are closed

View file

@ -66,3 +66,13 @@ export const addLabelToPage = async (
ctx
)
}
export const getLabelsByIds = async (
userId: string,
labelIds: string[]
): Promise<Label[]> => {
return getRepository(Label).find({
where: { id: In(labelIds), user: { id: userId } },
select: ['id', 'name', 'color', 'description', 'createdAt'],
})
}

View file

@ -4,6 +4,7 @@ import { InFilter, ReadFilter } from '../../src/utils/search'
import {
ArticleSavingRequestStatus,
Highlight,
Label,
Page,
PageContext,
PageType,
@ -18,7 +19,7 @@ import {
searchPages,
updatePage,
} from '../../src/elastic/pages'
import { addLabelInPage } from '../../src/elastic/labels'
import { addLabelInPage, setLabelsForHighlight } from '../../src/elastic/labels'
import {
addHighlightToPage,
searchHighlights,
@ -250,4 +251,54 @@ describe('elastic api', () => {
expect(searchResults[0].id).to.eq(highlightId)
})
})
describe('setLabelsForHighlight', () => {
const page = {
id: 'testPageId',
hash: 'test set labels for highlight hash',
userId: userId,
pageType: PageType.Article,
title: 'test set labels for highlight title',
content: '<p>test</p>',
slug: 'test set labels for highlight slug',
createdAt: new Date(),
savedAt: new Date(),
readingProgressPercent: 100,
readingProgressAnchorIndex: 0,
url: 'https://blog.omnivore.app/p/setting-labels-for-highlight',
state: ArticleSavingRequestStatus.Succeeded,
}
const highlightId = 'highlightId'
const label: Label = {
id: 'test label id',
name: 'test label',
color: '#07D2D1',
}
before(async () => {
// create a testing page
await createPage(page, ctx)
const highlightData: Highlight = {
patch: 'test set labels patch',
quote: 'test set labels quote',
shortId: 'test set labels shortId',
id: highlightId,
userId: page.userId,
createdAt: new Date(),
}
await addHighlightToPage(page.id, highlightData, ctx)
})
after(async () => {
await deletePage(page.id, ctx)
})
it('sets labels for highlights', async () => {
const result = await setLabelsForHighlight(highlightId, [label], ctx)
expect(result).to.be.true
})
})
})

View file

@ -9,11 +9,15 @@ import { Label } from '../../src/entity/label'
import { expect } from 'chai'
import 'mocha'
import { User } from '../../src/entity/user'
import { Page, PageContext } from '../../src/elastic/types'
import { Highlight, Page, PageContext } from '../../src/elastic/types'
import { getRepository } from '../../src/entity/utils'
import { getPageById } from '../../src/elastic/pages'
import { addLabelInPage } from '../../src/elastic/labels'
import { createPubSubClient } from '../../src/datalayer/pubsub'
import {
addHighlightToPage,
getHighlightById,
} from '../../src/elastic/highlights'
describe('Labels API', () => {
const username = 'fakeUser'
@ -238,6 +242,30 @@ describe('Labels API', () => {
expect(updatedPage?.labels).not.to.include(toDeleteLabel)
})
})
context('when a highlight has this label', () => {
const highlightId = 'testDeleteLabel'
before(async () => {
const highlight: Highlight = {
id: highlightId,
patch: 'test patch',
quote: 'test quote',
shortId: 'test shortId',
userId: user.id,
createdAt: new Date(),
labels: [toDeleteLabel],
}
await addHighlightToPage(page.id, highlight, ctx)
})
it('should update highlight', async () => {
await graphqlRequest(query, authToken).expect(200)
const updatedHighlight = await getHighlightById(highlightId)
expect(updatedHighlight?.labels).not.to.include(toDeleteLabel)
})
})
})
context('when label not exist', () => {
@ -440,4 +468,96 @@ describe('Labels API', () => {
})
})
})
describe('Set labels for highlight', () => {
let query: string
let highlightId: string
let labelIds: string[] = []
beforeEach(() => {
query = `
mutation {
setLabelsForHighlight(
input: {
highlightId: "${highlightId}",
labelIds: [
"${labelIds[0]}",
"${labelIds[1]}"
]
}
) {
... on SetLabelsSuccess {
labels {
id
name
}
}
... on SetLabelsError {
errorCodes
}
}
}
`
})
context('when labels exists', () => {
before(async () => {
highlightId = 'highlight-id'
const highlight: Highlight = {
createdAt: new Date(),
id: highlightId,
patch: 'test patch',
quote: 'test quote',
shortId: 'test shortId',
userId: user.id,
}
await addHighlightToPage(page.id, highlight, ctx)
labelIds = [labels[0].id, labels[1].id]
})
it('should set labels for highlight', async () => {
const res = await graphqlRequest(query, authToken).expect(200)
expect(
res.body.data.setLabelsForHighlight.labels.map((l: any) => l.id)
).to.eql(labelIds)
})
})
context('when labels not exist', () => {
before(async () => {
highlightId = 'highlight-id-2'
const highlight: Highlight = {
createdAt: new Date(),
id: highlightId,
patch: 'test patch',
quote: 'test quote',
shortId: 'test shortId',
userId: user.id,
}
await addHighlightToPage(page.id, highlight, ctx)
labelIds = [generateFakeUuid(), generateFakeUuid()]
})
it('should return error code NOT_FOUND', async () => {
const res = await graphqlRequest(query, authToken).expect(200)
expect(res.body.data.setLabelsForHighlight.errorCodes).to.eql([
'NOT_FOUND',
])
})
})
context('when highlight not exist', () => {
before(() => {
highlightId = generateFakeUuid()
labelIds = [labels[0].id, labels[1].id]
})
it('should return error code NOT_FOUND', async () => {
const res = await graphqlRequest(query, authToken).expect(200)
expect(res.body.data.setLabelsForHighlight.errorCodes).to.eql([
'NOT_FOUND',
])
})
})
})
})

View file

@ -90,6 +90,27 @@
},
"updatedAt": {
"type": "date"
},
"labels": {
"type": "nested",
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "keyword",
"normalizer": "lowercase_normalizer"
},
"color": {
"type": "keyword"
},
"description": {
"type": "text"
},
"createdAt": {
"type": "date"
}
}
}
}
},