Merge pull request #1487 from omnivore-app/saved-filters-api
saved filters api
This commit is contained in:
40
packages/api/src/entity/filter.ts
Normal file
40
packages/api/src/entity/filter.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm'
|
||||
import { User } from './user'
|
||||
|
||||
@Entity({ name: 'filters' })
|
||||
@Unique('filter_unique_key', ['user', 'name'])
|
||||
export class Filter {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string
|
||||
|
||||
@ManyToOne(() => User, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
user!: User
|
||||
|
||||
@Column('varchar', { length: 255 })
|
||||
name!: string
|
||||
|
||||
@Column('varchar', { length: 255, nullable: true })
|
||||
description?: string | null
|
||||
|
||||
@Column('varchar', { length: 255 })
|
||||
filter!: string
|
||||
|
||||
@Column('integer', { default: 0 })
|
||||
position!: number
|
||||
|
||||
@CreateDateColumn({ default: () => 'CURRENT_TIMESTAMP' })
|
||||
createdAt!: Date
|
||||
|
||||
@UpdateDateColumn({ default: () => 'CURRENT_TIMESTAMP' })
|
||||
updatedAt!: Date
|
||||
}
|
||||
@ -445,6 +445,24 @@ export type DeleteAccountSuccess = {
|
||||
userID: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type DeleteFilterError = {
|
||||
__typename?: 'DeleteFilterError';
|
||||
errorCodes: Array<DeleteFilterErrorCode>;
|
||||
};
|
||||
|
||||
export enum DeleteFilterErrorCode {
|
||||
BadRequest = 'BAD_REQUEST',
|
||||
NotFound = 'NOT_FOUND',
|
||||
Unauthorized = 'UNAUTHORIZED'
|
||||
}
|
||||
|
||||
export type DeleteFilterResult = DeleteFilterError | DeleteFilterSuccess;
|
||||
|
||||
export type DeleteFilterSuccess = {
|
||||
__typename?: 'DeleteFilterSuccess';
|
||||
filter: Filter;
|
||||
};
|
||||
|
||||
export type DeleteHighlightError = {
|
||||
__typename?: 'DeleteHighlightError';
|
||||
errorCodes: Array<DeleteHighlightErrorCode>;
|
||||
@ -679,6 +697,34 @@ export type FeedArticlesSuccess = {
|
||||
pageInfo: PageInfo;
|
||||
};
|
||||
|
||||
export type Filter = {
|
||||
__typename?: 'Filter';
|
||||
createdAt: Scalars['Date'];
|
||||
description?: Maybe<Scalars['String']>;
|
||||
filter: Scalars['String'];
|
||||
id: Scalars['ID'];
|
||||
name: Scalars['String'];
|
||||
position: Scalars['Int'];
|
||||
updatedAt: Scalars['Date'];
|
||||
};
|
||||
|
||||
export type FiltersError = {
|
||||
__typename?: 'FiltersError';
|
||||
errorCodes: Array<FiltersErrorCode>;
|
||||
};
|
||||
|
||||
export enum FiltersErrorCode {
|
||||
BadRequest = 'BAD_REQUEST',
|
||||
Unauthorized = 'UNAUTHORIZED'
|
||||
}
|
||||
|
||||
export type FiltersResult = FiltersError | FiltersSuccess;
|
||||
|
||||
export type FiltersSuccess = {
|
||||
__typename?: 'FiltersSuccess';
|
||||
filters: Array<Filter>;
|
||||
};
|
||||
|
||||
export type GenerateApiKeyError = {
|
||||
__typename?: 'GenerateApiKeyError';
|
||||
errorCodes: Array<GenerateApiKeyErrorCode>;
|
||||
@ -969,6 +1015,29 @@ export type MergeHighlightSuccess = {
|
||||
overlapHighlightIdList: Array<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type MoveFilterError = {
|
||||
__typename?: 'MoveFilterError';
|
||||
errorCodes: Array<MoveFilterErrorCode>;
|
||||
};
|
||||
|
||||
export enum MoveFilterErrorCode {
|
||||
BadRequest = 'BAD_REQUEST',
|
||||
NotFound = 'NOT_FOUND',
|
||||
Unauthorized = 'UNAUTHORIZED'
|
||||
}
|
||||
|
||||
export type MoveFilterInput = {
|
||||
afterFilterId?: InputMaybe<Scalars['ID']>;
|
||||
filterId: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type MoveFilterResult = MoveFilterError | MoveFilterSuccess;
|
||||
|
||||
export type MoveFilterSuccess = {
|
||||
__typename?: 'MoveFilterSuccess';
|
||||
filter: Filter;
|
||||
};
|
||||
|
||||
export type MoveLabelError = {
|
||||
__typename?: 'MoveLabelError';
|
||||
errorCodes: Array<MoveLabelErrorCode>;
|
||||
@ -1004,6 +1073,7 @@ export type Mutation = {
|
||||
createReaction: CreateReactionResult;
|
||||
createReminder: CreateReminderResult;
|
||||
deleteAccount: DeleteAccountResult;
|
||||
deleteFilter: DeleteFilterResult;
|
||||
deleteHighlight: DeleteHighlightResult;
|
||||
deleteHighlightReply: DeleteHighlightReplyResult;
|
||||
deleteIntegration: DeleteIntegrationResult;
|
||||
@ -1018,12 +1088,14 @@ export type Mutation = {
|
||||
googleSignup: GoogleSignupResult;
|
||||
logOut: LogOutResult;
|
||||
mergeHighlight: MergeHighlightResult;
|
||||
moveFilter: MoveFilterResult;
|
||||
moveLabel: MoveLabelResult;
|
||||
optInFeature: OptInFeatureResult;
|
||||
reportItem: ReportItemResult;
|
||||
revokeApiKey: RevokeApiKeyResult;
|
||||
saveArticleReadingProgress: SaveArticleReadingProgressResult;
|
||||
saveFile: SaveResult;
|
||||
saveFilter: SaveFilterResult;
|
||||
savePage: SaveResult;
|
||||
saveUrl: SaveResult;
|
||||
setBookmarkArticle: SetBookmarkArticleResult;
|
||||
@ -1098,6 +1170,11 @@ export type MutationDeleteAccountArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteFilterArgs = {
|
||||
id: Scalars['ID'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteHighlightArgs = {
|
||||
highlightId: Scalars['ID'];
|
||||
};
|
||||
@ -1163,6 +1240,11 @@ export type MutationMergeHighlightArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationMoveFilterArgs = {
|
||||
input: MoveFilterInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationMoveLabelArgs = {
|
||||
input: MoveLabelInput;
|
||||
};
|
||||
@ -1193,6 +1275,11 @@ export type MutationSaveFileArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationSaveFilterArgs = {
|
||||
input: SaveFilterInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationSavePageArgs = {
|
||||
input: SavePageInput;
|
||||
};
|
||||
@ -1436,6 +1523,7 @@ export type Query = {
|
||||
articles: ArticlesResult;
|
||||
deviceTokens: DeviceTokensResult;
|
||||
feedArticles: FeedArticlesResult;
|
||||
filters: FiltersResult;
|
||||
getFollowers: GetFollowersResult;
|
||||
getFollowing: GetFollowingResult;
|
||||
getUserPersonalization: GetUserPersonalizationResult;
|
||||
@ -1761,6 +1849,31 @@ export type SaveFileInput = {
|
||||
url: Scalars['String'];
|
||||
};
|
||||
|
||||
export type SaveFilterError = {
|
||||
__typename?: 'SaveFilterError';
|
||||
errorCodes: Array<SaveFilterErrorCode>;
|
||||
};
|
||||
|
||||
export enum SaveFilterErrorCode {
|
||||
BadRequest = 'BAD_REQUEST',
|
||||
NotFound = 'NOT_FOUND',
|
||||
Unauthorized = 'UNAUTHORIZED'
|
||||
}
|
||||
|
||||
export type SaveFilterInput = {
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
filter: Scalars['String'];
|
||||
id?: InputMaybe<Scalars['ID']>;
|
||||
name: Scalars['String'];
|
||||
};
|
||||
|
||||
export type SaveFilterResult = SaveFilterError | SaveFilterSuccess;
|
||||
|
||||
export type SaveFilterSuccess = {
|
||||
__typename?: 'SaveFilterSuccess';
|
||||
filter: Filter;
|
||||
};
|
||||
|
||||
export type SavePageInput = {
|
||||
clientRequestId: Scalars['ID'];
|
||||
originalContent: Scalars['String'];
|
||||
@ -2830,6 +2943,10 @@ export type ResolversTypes = {
|
||||
DeleteAccountErrorCode: DeleteAccountErrorCode;
|
||||
DeleteAccountResult: ResolversTypes['DeleteAccountError'] | ResolversTypes['DeleteAccountSuccess'];
|
||||
DeleteAccountSuccess: ResolverTypeWrapper<DeleteAccountSuccess>;
|
||||
DeleteFilterError: ResolverTypeWrapper<DeleteFilterError>;
|
||||
DeleteFilterErrorCode: DeleteFilterErrorCode;
|
||||
DeleteFilterResult: ResolversTypes['DeleteFilterError'] | ResolversTypes['DeleteFilterSuccess'];
|
||||
DeleteFilterSuccess: ResolverTypeWrapper<DeleteFilterSuccess>;
|
||||
DeleteHighlightError: ResolverTypeWrapper<DeleteHighlightError>;
|
||||
DeleteHighlightErrorCode: DeleteHighlightErrorCode;
|
||||
DeleteHighlightReplyError: ResolverTypeWrapper<DeleteHighlightReplyError>;
|
||||
@ -2878,6 +2995,11 @@ export type ResolversTypes = {
|
||||
FeedArticlesErrorCode: FeedArticlesErrorCode;
|
||||
FeedArticlesResult: ResolversTypes['FeedArticlesError'] | ResolversTypes['FeedArticlesSuccess'];
|
||||
FeedArticlesSuccess: ResolverTypeWrapper<FeedArticlesSuccess>;
|
||||
Filter: ResolverTypeWrapper<Filter>;
|
||||
FiltersError: ResolverTypeWrapper<FiltersError>;
|
||||
FiltersErrorCode: FiltersErrorCode;
|
||||
FiltersResult: ResolversTypes['FiltersError'] | ResolversTypes['FiltersSuccess'];
|
||||
FiltersSuccess: ResolverTypeWrapper<FiltersSuccess>;
|
||||
Float: ResolverTypeWrapper<Scalars['Float']>;
|
||||
GenerateApiKeyError: ResolverTypeWrapper<GenerateApiKeyError>;
|
||||
GenerateApiKeyErrorCode: GenerateApiKeyErrorCode;
|
||||
@ -2932,6 +3054,11 @@ export type ResolversTypes = {
|
||||
MergeHighlightInput: MergeHighlightInput;
|
||||
MergeHighlightResult: ResolversTypes['MergeHighlightError'] | ResolversTypes['MergeHighlightSuccess'];
|
||||
MergeHighlightSuccess: ResolverTypeWrapper<MergeHighlightSuccess>;
|
||||
MoveFilterError: ResolverTypeWrapper<MoveFilterError>;
|
||||
MoveFilterErrorCode: MoveFilterErrorCode;
|
||||
MoveFilterInput: MoveFilterInput;
|
||||
MoveFilterResult: ResolversTypes['MoveFilterError'] | ResolversTypes['MoveFilterSuccess'];
|
||||
MoveFilterSuccess: ResolverTypeWrapper<MoveFilterSuccess>;
|
||||
MoveLabelError: ResolverTypeWrapper<MoveLabelError>;
|
||||
MoveLabelErrorCode: MoveLabelErrorCode;
|
||||
MoveLabelInput: MoveLabelInput;
|
||||
@ -2991,6 +3118,11 @@ export type ResolversTypes = {
|
||||
SaveError: ResolverTypeWrapper<SaveError>;
|
||||
SaveErrorCode: SaveErrorCode;
|
||||
SaveFileInput: SaveFileInput;
|
||||
SaveFilterError: ResolverTypeWrapper<SaveFilterError>;
|
||||
SaveFilterErrorCode: SaveFilterErrorCode;
|
||||
SaveFilterInput: SaveFilterInput;
|
||||
SaveFilterResult: ResolversTypes['SaveFilterError'] | ResolversTypes['SaveFilterSuccess'];
|
||||
SaveFilterSuccess: ResolverTypeWrapper<SaveFilterSuccess>;
|
||||
SavePageInput: SavePageInput;
|
||||
SaveResult: ResolversTypes['SaveError'] | ResolversTypes['SaveSuccess'];
|
||||
SaveSuccess: ResolverTypeWrapper<SaveSuccess>;
|
||||
@ -3226,6 +3358,9 @@ export type ResolversParentTypes = {
|
||||
DeleteAccountError: DeleteAccountError;
|
||||
DeleteAccountResult: ResolversParentTypes['DeleteAccountError'] | ResolversParentTypes['DeleteAccountSuccess'];
|
||||
DeleteAccountSuccess: DeleteAccountSuccess;
|
||||
DeleteFilterError: DeleteFilterError;
|
||||
DeleteFilterResult: ResolversParentTypes['DeleteFilterError'] | ResolversParentTypes['DeleteFilterSuccess'];
|
||||
DeleteFilterSuccess: DeleteFilterSuccess;
|
||||
DeleteHighlightError: DeleteHighlightError;
|
||||
DeleteHighlightReplyError: DeleteHighlightReplyError;
|
||||
DeleteHighlightReplyResult: ResolversParentTypes['DeleteHighlightReplyError'] | ResolversParentTypes['DeleteHighlightReplySuccess'];
|
||||
@ -3263,6 +3398,10 @@ export type ResolversParentTypes = {
|
||||
FeedArticlesError: FeedArticlesError;
|
||||
FeedArticlesResult: ResolversParentTypes['FeedArticlesError'] | ResolversParentTypes['FeedArticlesSuccess'];
|
||||
FeedArticlesSuccess: FeedArticlesSuccess;
|
||||
Filter: Filter;
|
||||
FiltersError: FiltersError;
|
||||
FiltersResult: ResolversParentTypes['FiltersError'] | ResolversParentTypes['FiltersSuccess'];
|
||||
FiltersSuccess: FiltersSuccess;
|
||||
Float: Scalars['Float'];
|
||||
GenerateApiKeyError: GenerateApiKeyError;
|
||||
GenerateApiKeyInput: GenerateApiKeyInput;
|
||||
@ -3307,6 +3446,10 @@ export type ResolversParentTypes = {
|
||||
MergeHighlightInput: MergeHighlightInput;
|
||||
MergeHighlightResult: ResolversParentTypes['MergeHighlightError'] | ResolversParentTypes['MergeHighlightSuccess'];
|
||||
MergeHighlightSuccess: MergeHighlightSuccess;
|
||||
MoveFilterError: MoveFilterError;
|
||||
MoveFilterInput: MoveFilterInput;
|
||||
MoveFilterResult: ResolversParentTypes['MoveFilterError'] | ResolversParentTypes['MoveFilterSuccess'];
|
||||
MoveFilterSuccess: MoveFilterSuccess;
|
||||
MoveLabelError: MoveLabelError;
|
||||
MoveLabelInput: MoveLabelInput;
|
||||
MoveLabelResult: ResolversParentTypes['MoveLabelError'] | ResolversParentTypes['MoveLabelSuccess'];
|
||||
@ -3353,6 +3496,10 @@ export type ResolversParentTypes = {
|
||||
SaveArticleReadingProgressSuccess: SaveArticleReadingProgressSuccess;
|
||||
SaveError: SaveError;
|
||||
SaveFileInput: SaveFileInput;
|
||||
SaveFilterError: SaveFilterError;
|
||||
SaveFilterInput: SaveFilterInput;
|
||||
SaveFilterResult: ResolversParentTypes['SaveFilterError'] | ResolversParentTypes['SaveFilterSuccess'];
|
||||
SaveFilterSuccess: SaveFilterSuccess;
|
||||
SavePageInput: SavePageInput;
|
||||
SaveResult: ResolversParentTypes['SaveError'] | ResolversParentTypes['SaveSuccess'];
|
||||
SaveSuccess: SaveSuccess;
|
||||
@ -3785,6 +3932,20 @@ export type DeleteAccountSuccessResolvers<ContextType = ResolverContext, ParentT
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type DeleteFilterErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['DeleteFilterError'] = ResolversParentTypes['DeleteFilterError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['DeleteFilterErrorCode']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type DeleteFilterResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['DeleteFilterResult'] = ResolversParentTypes['DeleteFilterResult']> = {
|
||||
__resolveType: TypeResolveFn<'DeleteFilterError' | 'DeleteFilterSuccess', ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type DeleteFilterSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['DeleteFilterSuccess'] = ResolversParentTypes['DeleteFilterSuccess']> = {
|
||||
filter?: Resolver<ResolversTypes['Filter'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type DeleteHighlightErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['DeleteHighlightError'] = ResolversParentTypes['DeleteHighlightError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['DeleteHighlightErrorCode']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
@ -3978,6 +4139,31 @@ export type FeedArticlesSuccessResolvers<ContextType = ResolverContext, ParentTy
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type FilterResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['Filter'] = ResolversParentTypes['Filter']> = {
|
||||
createdAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
|
||||
description?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
filter?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
position?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
|
||||
updatedAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type FiltersErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['FiltersError'] = ResolversParentTypes['FiltersError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['FiltersErrorCode']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type FiltersResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['FiltersResult'] = ResolversParentTypes['FiltersResult']> = {
|
||||
__resolveType: TypeResolveFn<'FiltersError' | 'FiltersSuccess', ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type FiltersSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['FiltersSuccess'] = ResolversParentTypes['FiltersSuccess']> = {
|
||||
filters?: Resolver<Array<ResolversTypes['Filter']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type GenerateApiKeyErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['GenerateApiKeyError'] = ResolversParentTypes['GenerateApiKeyError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['GenerateApiKeyErrorCode']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
@ -4199,6 +4385,20 @@ export type MergeHighlightSuccessResolvers<ContextType = ResolverContext, Parent
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type MoveFilterErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['MoveFilterError'] = ResolversParentTypes['MoveFilterError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['MoveFilterErrorCode']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type MoveFilterResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['MoveFilterResult'] = ResolversParentTypes['MoveFilterResult']> = {
|
||||
__resolveType: TypeResolveFn<'MoveFilterError' | 'MoveFilterSuccess', ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type MoveFilterSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['MoveFilterSuccess'] = ResolversParentTypes['MoveFilterSuccess']> = {
|
||||
filter?: Resolver<ResolversTypes['Filter'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type MoveLabelErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['MoveLabelError'] = ResolversParentTypes['MoveLabelError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['MoveLabelErrorCode']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
@ -4224,6 +4424,7 @@ export type MutationResolvers<ContextType = ResolverContext, ParentType extends
|
||||
createReaction?: Resolver<ResolversTypes['CreateReactionResult'], ParentType, ContextType, RequireFields<MutationCreateReactionArgs, 'input'>>;
|
||||
createReminder?: Resolver<ResolversTypes['CreateReminderResult'], ParentType, ContextType, RequireFields<MutationCreateReminderArgs, 'input'>>;
|
||||
deleteAccount?: Resolver<ResolversTypes['DeleteAccountResult'], ParentType, ContextType, RequireFields<MutationDeleteAccountArgs, 'userID'>>;
|
||||
deleteFilter?: Resolver<ResolversTypes['DeleteFilterResult'], ParentType, ContextType, RequireFields<MutationDeleteFilterArgs, 'id'>>;
|
||||
deleteHighlight?: Resolver<ResolversTypes['DeleteHighlightResult'], ParentType, ContextType, RequireFields<MutationDeleteHighlightArgs, 'highlightId'>>;
|
||||
deleteHighlightReply?: Resolver<ResolversTypes['DeleteHighlightReplyResult'], ParentType, ContextType, RequireFields<MutationDeleteHighlightReplyArgs, 'highlightReplyId'>>;
|
||||
deleteIntegration?: Resolver<ResolversTypes['DeleteIntegrationResult'], ParentType, ContextType, RequireFields<MutationDeleteIntegrationArgs, 'id'>>;
|
||||
@ -4238,12 +4439,14 @@ export type MutationResolvers<ContextType = ResolverContext, ParentType extends
|
||||
googleSignup?: Resolver<ResolversTypes['GoogleSignupResult'], ParentType, ContextType, RequireFields<MutationGoogleSignupArgs, 'input'>>;
|
||||
logOut?: Resolver<ResolversTypes['LogOutResult'], ParentType, ContextType>;
|
||||
mergeHighlight?: Resolver<ResolversTypes['MergeHighlightResult'], ParentType, ContextType, RequireFields<MutationMergeHighlightArgs, 'input'>>;
|
||||
moveFilter?: Resolver<ResolversTypes['MoveFilterResult'], ParentType, ContextType, RequireFields<MutationMoveFilterArgs, 'input'>>;
|
||||
moveLabel?: Resolver<ResolversTypes['MoveLabelResult'], ParentType, ContextType, RequireFields<MutationMoveLabelArgs, 'input'>>;
|
||||
optInFeature?: Resolver<ResolversTypes['OptInFeatureResult'], ParentType, ContextType, RequireFields<MutationOptInFeatureArgs, 'input'>>;
|
||||
reportItem?: Resolver<ResolversTypes['ReportItemResult'], ParentType, ContextType, RequireFields<MutationReportItemArgs, 'input'>>;
|
||||
revokeApiKey?: Resolver<ResolversTypes['RevokeApiKeyResult'], ParentType, ContextType, RequireFields<MutationRevokeApiKeyArgs, 'id'>>;
|
||||
saveArticleReadingProgress?: Resolver<ResolversTypes['SaveArticleReadingProgressResult'], ParentType, ContextType, RequireFields<MutationSaveArticleReadingProgressArgs, 'input'>>;
|
||||
saveFile?: Resolver<ResolversTypes['SaveResult'], ParentType, ContextType, RequireFields<MutationSaveFileArgs, 'input'>>;
|
||||
saveFilter?: Resolver<ResolversTypes['SaveFilterResult'], ParentType, ContextType, RequireFields<MutationSaveFilterArgs, 'input'>>;
|
||||
savePage?: Resolver<ResolversTypes['SaveResult'], ParentType, ContextType, RequireFields<MutationSavePageArgs, 'input'>>;
|
||||
saveUrl?: Resolver<ResolversTypes['SaveResult'], ParentType, ContextType, RequireFields<MutationSaveUrlArgs, 'input'>>;
|
||||
setBookmarkArticle?: Resolver<ResolversTypes['SetBookmarkArticleResult'], ParentType, ContextType, RequireFields<MutationSetBookmarkArticleArgs, 'input'>>;
|
||||
@ -4350,6 +4553,7 @@ export type QueryResolvers<ContextType = ResolverContext, ParentType extends Res
|
||||
articles?: Resolver<ResolversTypes['ArticlesResult'], ParentType, ContextType, Partial<QueryArticlesArgs>>;
|
||||
deviceTokens?: Resolver<ResolversTypes['DeviceTokensResult'], ParentType, ContextType>;
|
||||
feedArticles?: Resolver<ResolversTypes['FeedArticlesResult'], ParentType, ContextType, Partial<QueryFeedArticlesArgs>>;
|
||||
filters?: Resolver<ResolversTypes['FiltersResult'], ParentType, ContextType>;
|
||||
getFollowers?: Resolver<ResolversTypes['GetFollowersResult'], ParentType, ContextType, Partial<QueryGetFollowersArgs>>;
|
||||
getFollowing?: Resolver<ResolversTypes['GetFollowingResult'], ParentType, ContextType, Partial<QueryGetFollowingArgs>>;
|
||||
getUserPersonalization?: Resolver<ResolversTypes['GetUserPersonalizationResult'], ParentType, ContextType>;
|
||||
@ -4504,6 +4708,20 @@ export type SaveErrorResolvers<ContextType = ResolverContext, ParentType extends
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SaveFilterErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['SaveFilterError'] = ResolversParentTypes['SaveFilterError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['SaveFilterErrorCode']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SaveFilterResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['SaveFilterResult'] = ResolversParentTypes['SaveFilterResult']> = {
|
||||
__resolveType: TypeResolveFn<'SaveFilterError' | 'SaveFilterSuccess', ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SaveFilterSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['SaveFilterSuccess'] = ResolversParentTypes['SaveFilterSuccess']> = {
|
||||
filter?: Resolver<ResolversTypes['Filter'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SaveResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['SaveResult'] = ResolversParentTypes['SaveResult']> = {
|
||||
__resolveType: TypeResolveFn<'SaveError' | 'SaveSuccess', ParentType, ContextType>;
|
||||
};
|
||||
@ -5145,6 +5363,9 @@ export type Resolvers<ContextType = ResolverContext> = {
|
||||
DeleteAccountError?: DeleteAccountErrorResolvers<ContextType>;
|
||||
DeleteAccountResult?: DeleteAccountResultResolvers<ContextType>;
|
||||
DeleteAccountSuccess?: DeleteAccountSuccessResolvers<ContextType>;
|
||||
DeleteFilterError?: DeleteFilterErrorResolvers<ContextType>;
|
||||
DeleteFilterResult?: DeleteFilterResultResolvers<ContextType>;
|
||||
DeleteFilterSuccess?: DeleteFilterSuccessResolvers<ContextType>;
|
||||
DeleteHighlightError?: DeleteHighlightErrorResolvers<ContextType>;
|
||||
DeleteHighlightReplyError?: DeleteHighlightReplyErrorResolvers<ContextType>;
|
||||
DeleteHighlightReplyResult?: DeleteHighlightReplyResultResolvers<ContextType>;
|
||||
@ -5182,6 +5403,10 @@ export type Resolvers<ContextType = ResolverContext> = {
|
||||
FeedArticlesError?: FeedArticlesErrorResolvers<ContextType>;
|
||||
FeedArticlesResult?: FeedArticlesResultResolvers<ContextType>;
|
||||
FeedArticlesSuccess?: FeedArticlesSuccessResolvers<ContextType>;
|
||||
Filter?: FilterResolvers<ContextType>;
|
||||
FiltersError?: FiltersErrorResolvers<ContextType>;
|
||||
FiltersResult?: FiltersResultResolvers<ContextType>;
|
||||
FiltersSuccess?: FiltersSuccessResolvers<ContextType>;
|
||||
GenerateApiKeyError?: GenerateApiKeyErrorResolvers<ContextType>;
|
||||
GenerateApiKeyResult?: GenerateApiKeyResultResolvers<ContextType>;
|
||||
GenerateApiKeySuccess?: GenerateApiKeySuccessResolvers<ContextType>;
|
||||
@ -5219,6 +5444,9 @@ export type Resolvers<ContextType = ResolverContext> = {
|
||||
MergeHighlightError?: MergeHighlightErrorResolvers<ContextType>;
|
||||
MergeHighlightResult?: MergeHighlightResultResolvers<ContextType>;
|
||||
MergeHighlightSuccess?: MergeHighlightSuccessResolvers<ContextType>;
|
||||
MoveFilterError?: MoveFilterErrorResolvers<ContextType>;
|
||||
MoveFilterResult?: MoveFilterResultResolvers<ContextType>;
|
||||
MoveFilterSuccess?: MoveFilterSuccessResolvers<ContextType>;
|
||||
MoveLabelError?: MoveLabelErrorResolvers<ContextType>;
|
||||
MoveLabelResult?: MoveLabelResultResolvers<ContextType>;
|
||||
MoveLabelSuccess?: MoveLabelSuccessResolvers<ContextType>;
|
||||
@ -5257,6 +5485,9 @@ export type Resolvers<ContextType = ResolverContext> = {
|
||||
SaveArticleReadingProgressResult?: SaveArticleReadingProgressResultResolvers<ContextType>;
|
||||
SaveArticleReadingProgressSuccess?: SaveArticleReadingProgressSuccessResolvers<ContextType>;
|
||||
SaveError?: SaveErrorResolvers<ContextType>;
|
||||
SaveFilterError?: SaveFilterErrorResolvers<ContextType>;
|
||||
SaveFilterResult?: SaveFilterResultResolvers<ContextType>;
|
||||
SaveFilterSuccess?: SaveFilterSuccessResolvers<ContextType>;
|
||||
SaveResult?: SaveResultResolvers<ContextType>;
|
||||
SaveSuccess?: SaveSuccessResolvers<ContextType>;
|
||||
SearchError?: SearchErrorResolvers<ContextType>;
|
||||
|
||||
@ -390,6 +390,22 @@ type DeleteAccountSuccess {
|
||||
userID: ID!
|
||||
}
|
||||
|
||||
type DeleteFilterError {
|
||||
errorCodes: [DeleteFilterErrorCode!]!
|
||||
}
|
||||
|
||||
enum DeleteFilterErrorCode {
|
||||
BAD_REQUEST
|
||||
NOT_FOUND
|
||||
UNAUTHORIZED
|
||||
}
|
||||
|
||||
union DeleteFilterResult = DeleteFilterError | DeleteFilterSuccess
|
||||
|
||||
type DeleteFilterSuccess {
|
||||
filter: Filter!
|
||||
}
|
||||
|
||||
type DeleteHighlightError {
|
||||
errorCodes: [DeleteHighlightErrorCode!]!
|
||||
}
|
||||
@ -598,6 +614,31 @@ type FeedArticlesSuccess {
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type Filter {
|
||||
createdAt: Date!
|
||||
description: String
|
||||
filter: String!
|
||||
id: ID!
|
||||
name: String!
|
||||
position: Int!
|
||||
updatedAt: Date!
|
||||
}
|
||||
|
||||
type FiltersError {
|
||||
errorCodes: [FiltersErrorCode!]!
|
||||
}
|
||||
|
||||
enum FiltersErrorCode {
|
||||
BAD_REQUEST
|
||||
UNAUTHORIZED
|
||||
}
|
||||
|
||||
union FiltersResult = FiltersError | FiltersSuccess
|
||||
|
||||
type FiltersSuccess {
|
||||
filters: [Filter!]!
|
||||
}
|
||||
|
||||
type GenerateApiKeyError {
|
||||
errorCodes: [GenerateApiKeyErrorCode!]!
|
||||
}
|
||||
@ -861,6 +902,27 @@ type MergeHighlightSuccess {
|
||||
overlapHighlightIdList: [String!]!
|
||||
}
|
||||
|
||||
type MoveFilterError {
|
||||
errorCodes: [MoveFilterErrorCode!]!
|
||||
}
|
||||
|
||||
enum MoveFilterErrorCode {
|
||||
BAD_REQUEST
|
||||
NOT_FOUND
|
||||
UNAUTHORIZED
|
||||
}
|
||||
|
||||
input MoveFilterInput {
|
||||
afterFilterId: ID
|
||||
filterId: ID!
|
||||
}
|
||||
|
||||
union MoveFilterResult = MoveFilterError | MoveFilterSuccess
|
||||
|
||||
type MoveFilterSuccess {
|
||||
filter: Filter!
|
||||
}
|
||||
|
||||
type MoveLabelError {
|
||||
errorCodes: [MoveLabelErrorCode!]!
|
||||
}
|
||||
@ -893,6 +955,7 @@ type Mutation {
|
||||
createReaction(input: CreateReactionInput!): CreateReactionResult!
|
||||
createReminder(input: CreateReminderInput!): CreateReminderResult!
|
||||
deleteAccount(userID: ID!): DeleteAccountResult!
|
||||
deleteFilter(id: ID!): DeleteFilterResult!
|
||||
deleteHighlight(highlightId: ID!): DeleteHighlightResult!
|
||||
deleteHighlightReply(highlightReplyId: ID!): DeleteHighlightReplyResult!
|
||||
deleteIntegration(id: ID!): DeleteIntegrationResult!
|
||||
@ -907,12 +970,14 @@ type Mutation {
|
||||
googleSignup(input: GoogleSignupInput!): GoogleSignupResult!
|
||||
logOut: LogOutResult!
|
||||
mergeHighlight(input: MergeHighlightInput!): MergeHighlightResult!
|
||||
moveFilter(input: MoveFilterInput!): MoveFilterResult!
|
||||
moveLabel(input: MoveLabelInput!): MoveLabelResult!
|
||||
optInFeature(input: OptInFeatureInput!): OptInFeatureResult!
|
||||
reportItem(input: ReportItemInput!): ReportItemResult!
|
||||
revokeApiKey(id: ID!): RevokeApiKeyResult!
|
||||
saveArticleReadingProgress(input: SaveArticleReadingProgressInput!): SaveArticleReadingProgressResult!
|
||||
saveFile(input: SaveFileInput!): SaveResult!
|
||||
saveFilter(input: SaveFilterInput!): SaveFilterResult!
|
||||
savePage(input: SavePageInput!): SaveResult!
|
||||
saveUrl(input: SaveUrlInput!): SaveResult!
|
||||
setBookmarkArticle(input: SetBookmarkArticleInput!): SetBookmarkArticleResult!
|
||||
@ -1046,6 +1111,7 @@ type Query {
|
||||
articles(after: String, first: Int, includePending: Boolean, query: String, sharedOnly: Boolean, sort: SortParams): ArticlesResult!
|
||||
deviceTokens: DeviceTokensResult!
|
||||
feedArticles(after: String, first: Int, sharedByUser: ID, sort: SortParams): FeedArticlesResult!
|
||||
filters: FiltersResult!
|
||||
getFollowers(userId: ID): GetFollowersResult!
|
||||
getFollowing(userId: ID): GetFollowingResult!
|
||||
getUserPersonalization: GetUserPersonalizationResult!
|
||||
@ -1255,6 +1321,29 @@ input SaveFileInput {
|
||||
url: String!
|
||||
}
|
||||
|
||||
type SaveFilterError {
|
||||
errorCodes: [SaveFilterErrorCode!]!
|
||||
}
|
||||
|
||||
enum SaveFilterErrorCode {
|
||||
BAD_REQUEST
|
||||
NOT_FOUND
|
||||
UNAUTHORIZED
|
||||
}
|
||||
|
||||
input SaveFilterInput {
|
||||
description: String
|
||||
filter: String!
|
||||
id: ID
|
||||
name: String!
|
||||
}
|
||||
|
||||
union SaveFilterResult = SaveFilterError | SaveFilterSuccess
|
||||
|
||||
type SaveFilterSuccess {
|
||||
filter: Filter!
|
||||
}
|
||||
|
||||
input SavePageInput {
|
||||
clientRequestId: ID!
|
||||
originalContent: String!
|
||||
|
||||
297
packages/api/src/resolvers/filters/index.ts
Normal file
297
packages/api/src/resolvers/filters/index.ts
Normal file
@ -0,0 +1,297 @@
|
||||
import { authorized } from '../../utils/helpers'
|
||||
import {
|
||||
DeleteFilterError,
|
||||
DeleteFilterErrorCode,
|
||||
DeleteFilterSuccess,
|
||||
FiltersError,
|
||||
FiltersErrorCode,
|
||||
FiltersSuccess,
|
||||
MoveFilterError,
|
||||
MoveFilterErrorCode,
|
||||
MoveFilterSuccess,
|
||||
MutationDeleteFilterArgs,
|
||||
MutationMoveFilterArgs,
|
||||
MutationSaveFilterArgs,
|
||||
SaveFilterError,
|
||||
SaveFilterErrorCode,
|
||||
SaveFilterSuccess,
|
||||
} from '../../generated/graphql'
|
||||
import { Filter } from '../../entity/filter'
|
||||
import { getRepository, setClaims } from '../../entity/utils'
|
||||
import { User } from '../../entity/user'
|
||||
import { AppDataSource } from '../../server'
|
||||
import { Between } from 'typeorm'
|
||||
import { analytics } from '../../utils/analytics'
|
||||
import { env } from '../../env'
|
||||
|
||||
export const saveFilterResolver = authorized<
|
||||
SaveFilterSuccess,
|
||||
SaveFilterError,
|
||||
MutationSaveFilterArgs
|
||||
>(async (_, { input }, { claims, log }) => {
|
||||
log.info('Saving filters', {
|
||||
input,
|
||||
labels: {
|
||||
source: 'resolver',
|
||||
resolver: 'saveFilterResolver',
|
||||
uid: claims.uid,
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
const user = await getRepository(User).findOneBy({ id: claims.uid })
|
||||
if (!user) {
|
||||
return {
|
||||
errorCodes: [SaveFilterErrorCode.Unauthorized],
|
||||
}
|
||||
}
|
||||
|
||||
const filter = await getRepository(Filter).save({
|
||||
...input,
|
||||
id: input.id ?? undefined,
|
||||
user: { id: claims.uid },
|
||||
})
|
||||
|
||||
return {
|
||||
filter,
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Error saving filters', {
|
||||
error,
|
||||
labels: {
|
||||
source: 'resolver',
|
||||
resolver: 'saveFilterResolver',
|
||||
uid: claims.uid,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
errorCodes: [SaveFilterErrorCode.BadRequest],
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const deleteFilterResolver = authorized<
|
||||
DeleteFilterSuccess,
|
||||
DeleteFilterError,
|
||||
MutationDeleteFilterArgs
|
||||
>(async (_, { id }, { claims, log }) => {
|
||||
log.info('Deleting filters', {
|
||||
id,
|
||||
labels: {
|
||||
source: 'resolver',
|
||||
resolver: 'deleteFilterResolver',
|
||||
uid: claims.uid,
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
const user = await getRepository(User).findOneBy({ id: claims.uid })
|
||||
if (!user) {
|
||||
return {
|
||||
errorCodes: [DeleteFilterErrorCode.Unauthorized],
|
||||
}
|
||||
}
|
||||
|
||||
const filter = await getRepository(Filter).findOneBy({
|
||||
id,
|
||||
user: { id: claims.uid },
|
||||
})
|
||||
if (!filter) {
|
||||
return {
|
||||
errorCodes: [DeleteFilterErrorCode.NotFound],
|
||||
}
|
||||
}
|
||||
|
||||
await getRepository(Filter).delete({ id })
|
||||
|
||||
return {
|
||||
filter,
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Error deleting filters', {
|
||||
error,
|
||||
labels: {
|
||||
source: 'resolver',
|
||||
resolver: 'deleteFilterResolver',
|
||||
uid: claims.uid,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
errorCodes: [DeleteFilterErrorCode.BadRequest],
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const filtersResolver = authorized<FiltersSuccess, FiltersError>(
|
||||
async (_, __, { claims, log }) => {
|
||||
log.info('Getting filters', {
|
||||
labels: {
|
||||
source: 'resolver',
|
||||
resolver: 'filtersResolver',
|
||||
uid: claims.uid,
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
const user = await getRepository(User).findOneBy({ id: claims.uid })
|
||||
if (!user) {
|
||||
return {
|
||||
errorCodes: [FiltersErrorCode.Unauthorized],
|
||||
}
|
||||
}
|
||||
|
||||
const filters = await getRepository(Filter).find({
|
||||
where: { user: { id: claims.uid } },
|
||||
order: { position: 'ASC' },
|
||||
})
|
||||
|
||||
return {
|
||||
filters,
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Error getting filters', {
|
||||
error,
|
||||
labels: {
|
||||
source: 'resolver',
|
||||
resolver: 'filtersResolver',
|
||||
uid: claims.uid,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
errorCodes: [FiltersErrorCode.BadRequest],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export const moveFilterResolver = authorized<
|
||||
MoveFilterSuccess,
|
||||
MoveFilterError,
|
||||
MutationMoveFilterArgs
|
||||
>(async (_, { input }, { claims: { uid }, log }) => {
|
||||
log.info('Moving filters', {
|
||||
input,
|
||||
filters: {
|
||||
source: 'resolver',
|
||||
resolver: 'moveFilterResolver',
|
||||
uid,
|
||||
},
|
||||
})
|
||||
|
||||
const { filterId, afterFilterId } = input
|
||||
|
||||
try {
|
||||
const user = await getRepository(User).findOneBy({ id: uid })
|
||||
if (!user) {
|
||||
return {
|
||||
errorCodes: [MoveFilterErrorCode.Unauthorized],
|
||||
}
|
||||
}
|
||||
|
||||
const filter = await getRepository(Filter).findOne({
|
||||
where: { id: filterId },
|
||||
relations: ['user'],
|
||||
})
|
||||
if (!filter) {
|
||||
return {
|
||||
errorCodes: [MoveFilterErrorCode.NotFound],
|
||||
}
|
||||
}
|
||||
if (filter.user.id !== uid) {
|
||||
return {
|
||||
errorCodes: [MoveFilterErrorCode.Unauthorized],
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.id === afterFilterId) {
|
||||
// nothing to do
|
||||
return { filter }
|
||||
}
|
||||
|
||||
const oldPosition = filter.position
|
||||
// if afterFilterId is not provided, move to the top
|
||||
let newPosition = 1
|
||||
if (afterFilterId) {
|
||||
const afterFilter = await getRepository(Filter).findOne({
|
||||
where: { id: afterFilterId },
|
||||
relations: ['user'],
|
||||
})
|
||||
if (!afterFilter) {
|
||||
return {
|
||||
errorCodes: [MoveFilterErrorCode.NotFound],
|
||||
}
|
||||
}
|
||||
if (afterFilter.user.id !== uid) {
|
||||
return {
|
||||
errorCodes: [MoveFilterErrorCode.Unauthorized],
|
||||
}
|
||||
}
|
||||
newPosition = afterFilter.position
|
||||
}
|
||||
const moveUp = newPosition < oldPosition
|
||||
|
||||
// move filter to the new position
|
||||
const updated = await AppDataSource.transaction(async (t) => {
|
||||
await setClaims(t, uid)
|
||||
|
||||
// update the position of the other filters
|
||||
const updated = await t.getRepository(Filter).update(
|
||||
{
|
||||
user: { id: uid },
|
||||
position: Between(
|
||||
Math.min(newPosition, oldPosition),
|
||||
Math.max(newPosition, oldPosition)
|
||||
),
|
||||
},
|
||||
{
|
||||
position: () => `position + ${moveUp ? 1 : -1}`,
|
||||
}
|
||||
)
|
||||
if (!updated.affected) {
|
||||
return null
|
||||
}
|
||||
|
||||
// update the position of the filter
|
||||
return t.getRepository(Filter).save({
|
||||
...filter,
|
||||
position: newPosition,
|
||||
})
|
||||
})
|
||||
|
||||
if (!updated) {
|
||||
return {
|
||||
errorCodes: [MoveFilterErrorCode.BadRequest],
|
||||
}
|
||||
}
|
||||
|
||||
analytics.track({
|
||||
userId: uid,
|
||||
event: 'filter_moved',
|
||||
properties: {
|
||||
filterId,
|
||||
afterFilterId,
|
||||
env: env.server.apiEnv,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
filter: updated,
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Error moving filters', {
|
||||
error,
|
||||
labels: {
|
||||
source: 'resolver',
|
||||
resolver: 'moveFilterResolver',
|
||||
uid,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
errorCodes: [MoveFilterErrorCode.BadRequest],
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -31,6 +31,7 @@ import {
|
||||
createNewsletterEmailResolver,
|
||||
createReminderResolver,
|
||||
deleteAccountResolver,
|
||||
deleteFilterResolver,
|
||||
deleteHighlightResolver,
|
||||
deleteIntegrationResolver,
|
||||
deleteLabelResolver,
|
||||
@ -39,6 +40,7 @@ import {
|
||||
deleteRuleResolver,
|
||||
deleteWebhookResolver,
|
||||
deviceTokensResolver,
|
||||
filtersResolver,
|
||||
generateApiKeyResolver,
|
||||
getAllUsersResolver,
|
||||
getArticleResolver,
|
||||
@ -56,6 +58,7 @@ import {
|
||||
labelsResolver,
|
||||
logOutResolver,
|
||||
mergeHighlightResolver,
|
||||
moveFilterResolver,
|
||||
moveLabelResolver,
|
||||
newsletterEmailsResolver,
|
||||
reminderResolver,
|
||||
@ -64,6 +67,7 @@ import {
|
||||
rulesResolver,
|
||||
saveArticleReadingProgressResolver,
|
||||
saveFileResolver,
|
||||
saveFilterResolver,
|
||||
savePageResolver,
|
||||
saveUrlResolver,
|
||||
searchResolver,
|
||||
@ -179,6 +183,9 @@ export const functionResolvers = {
|
||||
optInFeature: optInFeatureResolver,
|
||||
setRule: setRuleResolver,
|
||||
deleteRule: deleteRuleResolver,
|
||||
saveFilter: saveFilterResolver,
|
||||
deleteFilter: deleteFilterResolver,
|
||||
moveFilter: moveFilterResolver,
|
||||
},
|
||||
Query: {
|
||||
me: getMeUserResolver,
|
||||
@ -208,6 +215,7 @@ export const functionResolvers = {
|
||||
recentSearches: recentSearchesResolver,
|
||||
rules: rulesResolver,
|
||||
deviceTokens: deviceTokensResolver,
|
||||
filters: filtersResolver,
|
||||
},
|
||||
User: {
|
||||
async sharedArticles(
|
||||
@ -622,4 +630,8 @@ export const functionResolvers = {
|
||||
...resultResolveTypeResolver('Rules'),
|
||||
...resultResolveTypeResolver('DeviceTokens'),
|
||||
...resultResolveTypeResolver('DeleteRule'),
|
||||
...resultResolveTypeResolver('SaveFilter'),
|
||||
...resultResolveTypeResolver('Filters'),
|
||||
...resultResolveTypeResolver('DeleteFilter'),
|
||||
...resultResolveTypeResolver('MoveFilter'),
|
||||
}
|
||||
|
||||
@ -22,3 +22,4 @@ export * from './webhooks'
|
||||
export * from './api_key'
|
||||
export * from './integrations'
|
||||
export * from './rules'
|
||||
export * from './filters'
|
||||
|
||||
@ -2052,6 +2052,91 @@ const schema = gql`
|
||||
BAD_REQUEST
|
||||
}
|
||||
|
||||
input SaveFilterInput {
|
||||
id: ID
|
||||
name: String!
|
||||
filter: String!
|
||||
description: String
|
||||
}
|
||||
|
||||
union SaveFilterResult = SaveFilterSuccess | SaveFilterError
|
||||
|
||||
type SaveFilterSuccess {
|
||||
filter: Filter!
|
||||
}
|
||||
|
||||
type Filter {
|
||||
id: ID!
|
||||
name: String!
|
||||
filter: String!
|
||||
position: Int!
|
||||
description: String
|
||||
createdAt: Date!
|
||||
updatedAt: Date!
|
||||
}
|
||||
|
||||
type SaveFilterError {
|
||||
errorCodes: [SaveFilterErrorCode!]!
|
||||
}
|
||||
|
||||
enum SaveFilterErrorCode {
|
||||
UNAUTHORIZED
|
||||
BAD_REQUEST
|
||||
NOT_FOUND
|
||||
}
|
||||
|
||||
union FiltersResult = FiltersSuccess | FiltersError
|
||||
|
||||
type FiltersSuccess {
|
||||
filters: [Filter!]!
|
||||
}
|
||||
|
||||
type FiltersError {
|
||||
errorCodes: [FiltersErrorCode!]!
|
||||
}
|
||||
|
||||
enum FiltersErrorCode {
|
||||
UNAUTHORIZED
|
||||
BAD_REQUEST
|
||||
}
|
||||
|
||||
union DeleteFilterResult = DeleteFilterSuccess | DeleteFilterError
|
||||
|
||||
type DeleteFilterSuccess {
|
||||
filter: Filter!
|
||||
}
|
||||
|
||||
type DeleteFilterError {
|
||||
errorCodes: [DeleteFilterErrorCode!]!
|
||||
}
|
||||
|
||||
enum DeleteFilterErrorCode {
|
||||
UNAUTHORIZED
|
||||
BAD_REQUEST
|
||||
NOT_FOUND
|
||||
}
|
||||
|
||||
input MoveFilterInput {
|
||||
filterId: ID!
|
||||
afterFilterId: ID # null to move to the top
|
||||
}
|
||||
|
||||
union MoveFilterResult = MoveFilterSuccess | MoveFilterError
|
||||
|
||||
type MoveFilterSuccess {
|
||||
filter: Filter!
|
||||
}
|
||||
|
||||
type MoveFilterError {
|
||||
errorCodes: [MoveFilterErrorCode!]!
|
||||
}
|
||||
|
||||
enum MoveFilterErrorCode {
|
||||
UNAUTHORIZED
|
||||
BAD_REQUEST
|
||||
NOT_FOUND
|
||||
}
|
||||
|
||||
# Mutations
|
||||
type Mutation {
|
||||
googleLogin(input: GoogleLoginInput!): LoginResult!
|
||||
@ -2126,6 +2211,9 @@ const schema = gql`
|
||||
optInFeature(input: OptInFeatureInput!): OptInFeatureResult!
|
||||
setRule(input: SetRuleInput!): SetRuleResult!
|
||||
deleteRule(id: ID!): DeleteRuleResult!
|
||||
saveFilter(input: SaveFilterInput!): SaveFilterResult!
|
||||
deleteFilter(id: ID!): DeleteFilterResult!
|
||||
moveFilter(input: MoveFilterInput!): MoveFilterResult!
|
||||
}
|
||||
|
||||
# FIXME: remove sort from feedArticles after all cached tabs are closed
|
||||
@ -2180,6 +2268,7 @@ const schema = gql`
|
||||
recentSearches: RecentSearchesResult!
|
||||
rules(enabled: Boolean): RulesResult!
|
||||
deviceTokens: DeviceTokensResult!
|
||||
filters: FiltersResult!
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
51
packages/db/migrations/0100.do.filters.sql
Executable file
51
packages/db/migrations/0100.do.filters.sql
Executable file
@ -0,0 +1,51 @@
|
||||
-- Type: DO
|
||||
-- Name: search_filters
|
||||
-- Description: Create search_filters table
|
||||
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE omnivore.filters (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(),
|
||||
user_id uuid NOT NULL REFERENCES omnivore.user ON DELETE CASCADE,
|
||||
name character varying(255) NOT NULL,
|
||||
description character varying(255),
|
||||
filter character varying(255) NOT NULL,
|
||||
position integer NOT NULL DEFAULT 0,
|
||||
created_at timestamptz NOT NULL DEFAULT current_timestamp,
|
||||
updated_at timestamptz NOT NULL DEFAULT current_timestamp,
|
||||
UNIQUE (user_id, name)
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_filter_position()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
new_position INTEGER;
|
||||
BEGIN
|
||||
IF (TG_OP = 'DELETE') THEN
|
||||
UPDATE omnivore.filters SET position = position - 1 WHERE user_id = OLD.user_id AND position > OLD.position;
|
||||
RETURN OLD;
|
||||
ELSIF (TG_OP = 'INSERT') THEN
|
||||
SELECT COALESCE(MAX(position), 0) + 1 INTO new_position FROM omnivore.filters WHERE user_id = NEW.user_id AND name < NEW.name;
|
||||
UPDATE omnivore.filters SET position = position + 1 WHERE user_id = NEW.user_id AND position >= new_position;
|
||||
NEW.position = new_position;
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql';
|
||||
|
||||
CREATE TRIGGER update_filter_modtime BEFORE UPDATE ON omnivore.filters
|
||||
FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER increment_filter_position
|
||||
BEFORE INSERT ON omnivore.filters
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_filter_position();
|
||||
|
||||
CREATE TRIGGER decrement_filter_position
|
||||
AFTER DELETE ON omnivore.filters
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_filter_position();
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.filters TO omnivore_user;
|
||||
|
||||
COMMIT;
|
||||
11
packages/db/migrations/0100.undo.filters.sql
Executable file
11
packages/db/migrations/0100.undo.filters.sql
Executable file
@ -0,0 +1,11 @@
|
||||
-- Type: UNDO
|
||||
-- Name: search_filters
|
||||
-- Description: Create search_filters table
|
||||
|
||||
BEGIN;
|
||||
|
||||
DROP FUNCTION IF EXISTS update_filter_position;
|
||||
|
||||
DROP TABLE IF EXISTS omnivore.filters;
|
||||
|
||||
COMMIT;
|
||||
Reference in New Issue
Block a user