Merge pull request #1487 from omnivore-app/saved-filters-api

saved filters api
This commit is contained in:
Hongbo Wu
2022-12-01 11:25:50 +08:00
committed by GitHub
9 changed files with 821 additions and 0 deletions

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

View File

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

View File

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

View 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],
}
}
})

View File

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

View File

@ -22,3 +22,4 @@ export * from './webhooks'
export * from './api_key'
export * from './integrations'
export * from './rules'
export * from './filters'

View File

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

View 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;

View 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;