From 0487d8325ca8b15bcde5ea0bb685ed806ffdaa77 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 30 Nov 2022 21:41:59 +0800 Subject: [PATCH] Add saved filter api --- .../entity/{search_filter.ts => filter.ts} | 6 +- packages/api/src/generated/graphql.ts | 173 ++++++++++++++++++ packages/api/src/generated/schema.graphql | 66 +++++++ packages/api/src/resolvers/filters/index.ts | 159 ++++++++++++++++ .../api/src/resolvers/function_resolvers.ts | 9 + packages/api/src/resolvers/index.ts | 1 + packages/api/src/schema.ts | 66 +++++++ ...search_filters.sql => 0100.do.filters.sql} | 6 +- ...arch_filters.sql => 0100.undo.filters.sql} | 2 +- 9 files changed, 481 insertions(+), 7 deletions(-) rename packages/api/src/entity/{search_filter.ts => filter.ts} (85%) create mode 100644 packages/api/src/resolvers/filters/index.ts rename packages/db/migrations/{0100.do.search_filters.sql => 0100.do.filters.sql} (73%) rename packages/db/migrations/{0100.undo.search_filters.sql => 0100.undo.filters.sql} (68%) diff --git a/packages/api/src/entity/search_filter.ts b/packages/api/src/entity/filter.ts similarity index 85% rename from packages/api/src/entity/search_filter.ts rename to packages/api/src/entity/filter.ts index 1997a14ca..4452d172e 100644 --- a/packages/api/src/entity/search_filter.ts +++ b/packages/api/src/entity/filter.ts @@ -10,9 +10,9 @@ import { } from 'typeorm' import { User } from './user' -@Entity({ name: 'search_filters' }) -@Unique('search_filter_unique_key', ['user', 'name']) -export class SearchFilter { +@Entity({ name: 'filters' }) +@Unique('filter_unique_key', ['user', 'name']) +export class Filter { @PrimaryGeneratedColumn('uuid') id!: string diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index d59c7f75c..c21ab2ca9 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -445,6 +445,24 @@ export type DeleteAccountSuccess = { userID: Scalars['ID']; }; +export type DeleteFilterError = { + __typename?: 'DeleteFilterError'; + errorCodes: Array; +}; + +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; @@ -679,6 +697,33 @@ export type FeedArticlesSuccess = { pageInfo: PageInfo; }; +export type Filter = { + __typename?: 'Filter'; + createdAt: Scalars['Date']; + description?: Maybe; + filter: Scalars['String']; + id: Scalars['ID']; + name: Scalars['String']; + updatedAt: Scalars['Date']; +}; + +export type FiltersError = { + __typename?: 'FiltersError'; + errorCodes: Array; +}; + +export enum FiltersErrorCode { + BadRequest = 'BAD_REQUEST', + Unauthorized = 'UNAUTHORIZED' +} + +export type FiltersResult = FiltersError | FiltersSuccess; + +export type FiltersSuccess = { + __typename?: 'FiltersSuccess'; + filters: Array; +}; + export type GenerateApiKeyError = { __typename?: 'GenerateApiKeyError'; errorCodes: Array; @@ -1004,6 +1049,7 @@ export type Mutation = { createReaction: CreateReactionResult; createReminder: CreateReminderResult; deleteAccount: DeleteAccountResult; + deleteFilter: DeleteFilterResult; deleteHighlight: DeleteHighlightResult; deleteHighlightReply: DeleteHighlightReplyResult; deleteIntegration: DeleteIntegrationResult; @@ -1024,6 +1070,7 @@ export type Mutation = { revokeApiKey: RevokeApiKeyResult; saveArticleReadingProgress: SaveArticleReadingProgressResult; saveFile: SaveResult; + saveFilter: SaveFilterResult; savePage: SaveResult; saveUrl: SaveResult; setBookmarkArticle: SetBookmarkArticleResult; @@ -1098,6 +1145,11 @@ export type MutationDeleteAccountArgs = { }; +export type MutationDeleteFilterArgs = { + id: Scalars['ID']; +}; + + export type MutationDeleteHighlightArgs = { highlightId: Scalars['ID']; }; @@ -1193,6 +1245,11 @@ export type MutationSaveFileArgs = { }; +export type MutationSaveFilterArgs = { + input: SaveFilterInput; +}; + + export type MutationSavePageArgs = { input: SavePageInput; }; @@ -1436,6 +1493,7 @@ export type Query = { articles: ArticlesResult; deviceTokens: DeviceTokensResult; feedArticles: FeedArticlesResult; + filters: FiltersResult; getFollowers: GetFollowersResult; getFollowing: GetFollowingResult; getUserPersonalization: GetUserPersonalizationResult; @@ -1761,6 +1819,31 @@ export type SaveFileInput = { url: Scalars['String']; }; +export type SaveFilterError = { + __typename?: 'SaveFilterError'; + errorCodes: Array; +}; + +export enum SaveFilterErrorCode { + BadRequest = 'BAD_REQUEST', + NotFound = 'NOT_FOUND', + Unauthorized = 'UNAUTHORIZED' +} + +export type SaveFilterInput = { + description?: InputMaybe; + filter: Scalars['String']; + id?: InputMaybe; + name: Scalars['String']; +}; + +export type SaveFilterResult = SaveFilterError | SaveFilterSuccess; + +export type SaveFilterSuccess = { + __typename?: 'SaveFilterSuccess'; + filter: Filter; +}; + export type SavePageInput = { clientRequestId: Scalars['ID']; originalContent: Scalars['String']; @@ -2829,6 +2912,10 @@ export type ResolversTypes = { DeleteAccountErrorCode: DeleteAccountErrorCode; DeleteAccountResult: ResolversTypes['DeleteAccountError'] | ResolversTypes['DeleteAccountSuccess']; DeleteAccountSuccess: ResolverTypeWrapper; + DeleteFilterError: ResolverTypeWrapper; + DeleteFilterErrorCode: DeleteFilterErrorCode; + DeleteFilterResult: ResolversTypes['DeleteFilterError'] | ResolversTypes['DeleteFilterSuccess']; + DeleteFilterSuccess: ResolverTypeWrapper; DeleteHighlightError: ResolverTypeWrapper; DeleteHighlightErrorCode: DeleteHighlightErrorCode; DeleteHighlightReplyError: ResolverTypeWrapper; @@ -2877,6 +2964,11 @@ export type ResolversTypes = { FeedArticlesErrorCode: FeedArticlesErrorCode; FeedArticlesResult: ResolversTypes['FeedArticlesError'] | ResolversTypes['FeedArticlesSuccess']; FeedArticlesSuccess: ResolverTypeWrapper; + Filter: ResolverTypeWrapper; + FiltersError: ResolverTypeWrapper; + FiltersErrorCode: FiltersErrorCode; + FiltersResult: ResolversTypes['FiltersError'] | ResolversTypes['FiltersSuccess']; + FiltersSuccess: ResolverTypeWrapper; Float: ResolverTypeWrapper; GenerateApiKeyError: ResolverTypeWrapper; GenerateApiKeyErrorCode: GenerateApiKeyErrorCode; @@ -2990,6 +3082,11 @@ export type ResolversTypes = { SaveError: ResolverTypeWrapper; SaveErrorCode: SaveErrorCode; SaveFileInput: SaveFileInput; + SaveFilterError: ResolverTypeWrapper; + SaveFilterErrorCode: SaveFilterErrorCode; + SaveFilterInput: SaveFilterInput; + SaveFilterResult: ResolversTypes['SaveFilterError'] | ResolversTypes['SaveFilterSuccess']; + SaveFilterSuccess: ResolverTypeWrapper; SavePageInput: SavePageInput; SaveResult: ResolversTypes['SaveError'] | ResolversTypes['SaveSuccess']; SaveSuccess: ResolverTypeWrapper; @@ -3225,6 +3322,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']; @@ -3262,6 +3362,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; @@ -3352,6 +3456,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; @@ -3784,6 +3892,20 @@ export type DeleteAccountSuccessResolvers; }; +export type DeleteFilterErrorResolvers = { + errorCodes?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type DeleteFilterResultResolvers = { + __resolveType: TypeResolveFn<'DeleteFilterError' | 'DeleteFilterSuccess', ParentType, ContextType>; +}; + +export type DeleteFilterSuccessResolvers = { + filter?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type DeleteHighlightErrorResolvers = { errorCodes?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -3977,6 +4099,30 @@ export type FeedArticlesSuccessResolvers; }; +export type FilterResolvers = { + createdAt?: Resolver; + description?: Resolver, ParentType, ContextType>; + filter?: Resolver; + id?: Resolver; + name?: Resolver; + updatedAt?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type FiltersErrorResolvers = { + errorCodes?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type FiltersResultResolvers = { + __resolveType: TypeResolveFn<'FiltersError' | 'FiltersSuccess', ParentType, ContextType>; +}; + +export type FiltersSuccessResolvers = { + filters?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type GenerateApiKeyErrorResolvers = { errorCodes?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -4223,6 +4369,7 @@ export type MutationResolvers>; createReminder?: Resolver>; deleteAccount?: Resolver>; + deleteFilter?: Resolver>; deleteHighlight?: Resolver>; deleteHighlightReply?: Resolver>; deleteIntegration?: Resolver>; @@ -4243,6 +4390,7 @@ export type MutationResolvers>; saveArticleReadingProgress?: Resolver>; saveFile?: Resolver>; + saveFilter?: Resolver>; savePage?: Resolver>; saveUrl?: Resolver>; setBookmarkArticle?: Resolver>; @@ -4349,6 +4497,7 @@ export type QueryResolvers>; deviceTokens?: Resolver; feedArticles?: Resolver>; + filters?: Resolver; getFollowers?: Resolver>; getFollowing?: Resolver>; getUserPersonalization?: Resolver; @@ -4503,6 +4652,20 @@ export type SaveErrorResolvers; }; +export type SaveFilterErrorResolvers = { + errorCodes?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type SaveFilterResultResolvers = { + __resolveType: TypeResolveFn<'SaveFilterError' | 'SaveFilterSuccess', ParentType, ContextType>; +}; + +export type SaveFilterSuccessResolvers = { + filter?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type SaveResultResolvers = { __resolveType: TypeResolveFn<'SaveError' | 'SaveSuccess', ParentType, ContextType>; }; @@ -5144,6 +5307,9 @@ export type Resolvers = { DeleteAccountError?: DeleteAccountErrorResolvers; DeleteAccountResult?: DeleteAccountResultResolvers; DeleteAccountSuccess?: DeleteAccountSuccessResolvers; + DeleteFilterError?: DeleteFilterErrorResolvers; + DeleteFilterResult?: DeleteFilterResultResolvers; + DeleteFilterSuccess?: DeleteFilterSuccessResolvers; DeleteHighlightError?: DeleteHighlightErrorResolvers; DeleteHighlightReplyError?: DeleteHighlightReplyErrorResolvers; DeleteHighlightReplyResult?: DeleteHighlightReplyResultResolvers; @@ -5181,6 +5347,10 @@ export type Resolvers = { FeedArticlesError?: FeedArticlesErrorResolvers; FeedArticlesResult?: FeedArticlesResultResolvers; FeedArticlesSuccess?: FeedArticlesSuccessResolvers; + Filter?: FilterResolvers; + FiltersError?: FiltersErrorResolvers; + FiltersResult?: FiltersResultResolvers; + FiltersSuccess?: FiltersSuccessResolvers; GenerateApiKeyError?: GenerateApiKeyErrorResolvers; GenerateApiKeyResult?: GenerateApiKeyResultResolvers; GenerateApiKeySuccess?: GenerateApiKeySuccessResolvers; @@ -5256,6 +5426,9 @@ export type Resolvers = { SaveArticleReadingProgressResult?: SaveArticleReadingProgressResultResolvers; SaveArticleReadingProgressSuccess?: SaveArticleReadingProgressSuccessResolvers; SaveError?: SaveErrorResolvers; + SaveFilterError?: SaveFilterErrorResolvers; + SaveFilterResult?: SaveFilterResultResolvers; + SaveFilterSuccess?: SaveFilterSuccessResolvers; SaveResult?: SaveResultResolvers; SaveSuccess?: SaveSuccessResolvers; SearchError?: SearchErrorResolvers; diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index aa6ac5d79..0b36bf4ad 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -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,30 @@ type FeedArticlesSuccess { pageInfo: PageInfo! } +type Filter { + createdAt: Date! + description: String + filter: String! + id: ID! + name: String! + updatedAt: Date! +} + +type FiltersError { + errorCodes: [FiltersErrorCode!]! +} + +enum FiltersErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +union FiltersResult = FiltersError | FiltersSuccess + +type FiltersSuccess { + filters: [Filter!]! +} + type GenerateApiKeyError { errorCodes: [GenerateApiKeyErrorCode!]! } @@ -893,6 +933,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! @@ -913,6 +954,7 @@ type Mutation { 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 +1088,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 +1298,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! diff --git a/packages/api/src/resolvers/filters/index.ts b/packages/api/src/resolvers/filters/index.ts new file mode 100644 index 000000000..87cc44590 --- /dev/null +++ b/packages/api/src/resolvers/filters/index.ts @@ -0,0 +1,159 @@ +import { authorized } from '../../utils/helpers' +import { + DeleteFilterError, + DeleteFilterErrorCode, + DeleteFilterSuccess, + FiltersError, + FiltersErrorCode, + FiltersSuccess, + MutationDeleteFilterArgs, + MutationSaveFilterArgs, + SaveFilterError, + SaveFilterErrorCode, + SaveFilterSuccess, +} from '../../generated/graphql' +import { Filter } from '../../entity/filter' +import { getRepository } from '../../entity/utils' +import { User } from '../../entity/user' + +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( + 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).findBy({ + user: { id: claims.uid }, + }) + + return { + filters, + } + } catch (error) { + log.error('Error getting filters', { + error, + labels: { + source: 'resolver', + resolver: 'filtersResolver', + uid: claims.uid, + }, + }) + + return { + errorCodes: [FiltersErrorCode.BadRequest], + } + } + } +) diff --git a/packages/api/src/resolvers/function_resolvers.ts b/packages/api/src/resolvers/function_resolvers.ts index a9beca613..a0143ea84 100644 --- a/packages/api/src/resolvers/function_resolvers.ts +++ b/packages/api/src/resolvers/function_resolvers.ts @@ -31,6 +31,7 @@ import { createNewsletterEmailResolver, createReminderResolver, deleteAccountResolver, + deleteFilterResolver, deleteHighlightResolver, deleteIntegrationResolver, deleteLabelResolver, @@ -39,6 +40,7 @@ import { deleteRuleResolver, deleteWebhookResolver, deviceTokensResolver, + filtersResolver, generateApiKeyResolver, getAllUsersResolver, getArticleResolver, @@ -64,6 +66,7 @@ import { rulesResolver, saveArticleReadingProgressResolver, saveFileResolver, + saveFilterResolver, savePageResolver, saveUrlResolver, searchResolver, @@ -179,6 +182,8 @@ export const functionResolvers = { optInFeature: optInFeatureResolver, setRule: setRuleResolver, deleteRule: deleteRuleResolver, + saveFilter: saveFilterResolver, + deleteFilter: deleteFilterResolver, }, Query: { me: getMeUserResolver, @@ -208,6 +213,7 @@ export const functionResolvers = { recentSearches: recentSearchesResolver, rules: rulesResolver, deviceTokens: deviceTokensResolver, + filters: filtersResolver, }, User: { async sharedArticles( @@ -622,4 +628,7 @@ export const functionResolvers = { ...resultResolveTypeResolver('Rules'), ...resultResolveTypeResolver('DeviceTokens'), ...resultResolveTypeResolver('DeleteRule'), + ...resultResolveTypeResolver('SaveFilter'), + ...resultResolveTypeResolver('Filters'), + ...resultResolveTypeResolver('DeleteFilter'), } diff --git a/packages/api/src/resolvers/index.ts b/packages/api/src/resolvers/index.ts index f303cbc3d..ee324050e 100644 --- a/packages/api/src/resolvers/index.ts +++ b/packages/api/src/resolvers/index.ts @@ -22,3 +22,4 @@ export * from './webhooks' export * from './api_key' export * from './integrations' export * from './rules' +export * from './filters' diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index 5823f48ee..663268439 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -2051,6 +2051,69 @@ 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! + 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 + } + # Mutations type Mutation { googleLogin(input: GoogleLoginInput!): LoginResult! @@ -2125,6 +2188,8 @@ const schema = gql` optInFeature(input: OptInFeatureInput!): OptInFeatureResult! setRule(input: SetRuleInput!): SetRuleResult! deleteRule(id: ID!): DeleteRuleResult! + saveFilter(input: SaveFilterInput!): SaveFilterResult! + deleteFilter(id: ID!): DeleteFilterResult! } # FIXME: remove sort from feedArticles after all cached tabs are closed @@ -2179,6 +2244,7 @@ const schema = gql` recentSearches: RecentSearchesResult! rules(enabled: Boolean): RulesResult! deviceTokens: DeviceTokensResult! + filters: FiltersResult! } ` diff --git a/packages/db/migrations/0100.do.search_filters.sql b/packages/db/migrations/0100.do.filters.sql similarity index 73% rename from packages/db/migrations/0100.do.search_filters.sql rename to packages/db/migrations/0100.do.filters.sql index dcf717340..629dbf610 100755 --- a/packages/db/migrations/0100.do.search_filters.sql +++ b/packages/db/migrations/0100.do.filters.sql @@ -4,7 +4,7 @@ BEGIN; -CREATE TABLE omnivore.search_filters ( +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, @@ -15,9 +15,9 @@ CREATE TABLE omnivore.search_filters ( UNIQUE (user_id, name) ); -CREATE TRIGGER search_filters_modtime BEFORE UPDATE ON omnivore.search_filters +CREATE TRIGGER filters_modtime BEFORE UPDATE ON omnivore.filters FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); -GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.search_filters TO omnivore_user; +GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.filters TO omnivore_user; COMMIT; diff --git a/packages/db/migrations/0100.undo.search_filters.sql b/packages/db/migrations/0100.undo.filters.sql similarity index 68% rename from packages/db/migrations/0100.undo.search_filters.sql rename to packages/db/migrations/0100.undo.filters.sql index b08361352..1837c1580 100755 --- a/packages/db/migrations/0100.undo.search_filters.sql +++ b/packages/db/migrations/0100.undo.filters.sql @@ -4,6 +4,6 @@ BEGIN; -DROP TABLE IF EXISTS omnivore.search_filters; +DROP TABLE IF EXISTS omnivore.filters; COMMIT;