From 7940c3f4eabd138e2873b2084a9f3b0462aaea9e Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Mon, 10 Jun 2024 19:25:58 +0800 Subject: [PATCH] add list folder policy api --- packages/api/src/entity/folder_policy.ts | 9 +- packages/api/src/generated/graphql.ts | 236 ++++++++++++++++++ packages/api/src/generated/schema.graphql | 93 +++++++ .../api/src/resolvers/folder_policy/index.ts | 23 ++ .../api/src/resolvers/function_resolvers.ts | 4 + packages/api/src/schema.ts | 103 ++++++++ packages/api/src/services/folder_policy.ts | 38 +++ .../api/test/resolvers/folder_policy.test.ts | 59 +++++ packages/api/test/util.ts | 8 + .../db/migrations/0180.do.folder_policy.sql | 7 +- .../db/migrations/0180.undo.folder_policy.sql | 2 + 11 files changed, 578 insertions(+), 4 deletions(-) create mode 100644 packages/api/src/resolvers/folder_policy/index.ts create mode 100644 packages/api/src/services/folder_policy.ts create mode 100644 packages/api/test/resolvers/folder_policy.test.ts diff --git a/packages/api/src/entity/folder_policy.ts b/packages/api/src/entity/folder_policy.ts index 2f77f4552..82bc0a160 100644 --- a/packages/api/src/entity/folder_policy.ts +++ b/packages/api/src/entity/folder_policy.ts @@ -7,6 +7,11 @@ import { } from 'typeorm' import { User } from './user' +export enum FolderPolicyAction { + DELETE = 'DELETE', + ARCHIVE = 'ARCHIVE', +} + @Entity({ name: 'folder_policy' }) export class FolderPolicy { @PrimaryGeneratedColumn('uuid') @@ -22,8 +27,8 @@ export class FolderPolicy { @Column('text') folder!: string - @Column('text') - action!: string + @Column('enum', { enum: FolderPolicyAction }) + action!: FolderPolicyAction @Column('int') afterDays!: number diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index 203364aa9..f388b1282 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -349,6 +349,30 @@ export type CreateArticleSuccess = { user: User; }; +export type CreateFolderPolicyError = { + __typename?: 'CreateFolderPolicyError'; + errorCodes: Array; +}; + +export enum CreateFolderPolicyErrorCode { + BadRequest = 'BAD_REQUEST', + Unauthorized = 'UNAUTHORIZED' +} + +export type CreateFolderPolicyInput = { + action: FolderPolicyAction; + afterDays: Scalars['Int']; + folder: Scalars['String']; + minimumItems?: InputMaybe; +}; + +export type CreateFolderPolicyResult = CreateFolderPolicyError | CreateFolderPolicySuccess; + +export type CreateFolderPolicySuccess = { + __typename?: 'CreateFolderPolicySuccess'; + policy: FolderPolicy; +}; + export type CreateGroupError = { __typename?: 'CreateGroupError'; errorCodes: Array; @@ -619,6 +643,23 @@ export type DeleteFilterSuccess = { filter: Filter; }; +export type DeleteFolderPolicyError = { + __typename?: 'DeleteFolderPolicyError'; + errorCodes: Array; +}; + +export enum DeleteFolderPolicyErrorCode { + BadRequest = 'BAD_REQUEST', + Unauthorized = 'UNAUTHORIZED' +} + +export type DeleteFolderPolicyResult = DeleteFolderPolicyError | DeleteFolderPolicySuccess; + +export type DeleteFolderPolicySuccess = { + __typename?: 'DeleteFolderPolicySuccess'; + success: Scalars['Boolean']; +}; + export type DeleteHighlightError = { __typename?: 'DeleteHighlightError'; errorCodes: Array; @@ -1081,6 +1122,39 @@ export type FiltersSuccess = { filters: Array; }; +export type FolderPoliciesError = { + __typename?: 'FolderPoliciesError'; + errorCodes: Array; +}; + +export enum FolderPoliciesErrorCode { + BadRequest = 'BAD_REQUEST', + Unauthorized = 'UNAUTHORIZED' +} + +export type FolderPoliciesResult = FolderPoliciesError | FolderPoliciesSuccess; + +export type FolderPoliciesSuccess = { + __typename?: 'FolderPoliciesSuccess'; + policies: Array; +}; + +export type FolderPolicy = { + __typename?: 'FolderPolicy'; + action: FolderPolicyAction; + afterDays: Scalars['Int']; + createdAt: Scalars['Date']; + folder: Scalars['String']; + id: Scalars['ID']; + minimumItems: Scalars['Int']; + updatedAt: Scalars['Date']; +}; + +export enum FolderPolicyAction { + Archive = 'ARCHIVE', + Delete = 'DELETE' +} + export type GenerateApiKeyError = { __typename?: 'GenerateApiKeyError'; errorCodes: Array; @@ -1723,6 +1797,7 @@ export type Mutation = { bulkAction: BulkActionResult; createArticle: CreateArticleResult; createArticleSavingRequest: CreateArticleSavingRequestResult; + createFolderPolicy: CreateFolderPolicyResult; createGroup: CreateGroupResult; createHighlight: CreateHighlightResult; createLabel: CreateLabelResult; @@ -1731,6 +1806,7 @@ export type Mutation = { deleteDiscoverArticle: DeleteDiscoverArticleResult; deleteDiscoverFeed: DeleteDiscoverFeedResult; deleteFilter: DeleteFilterResult; + deleteFolderPolicy: DeleteFolderPolicyResult; deleteHighlight: DeleteHighlightResult; deleteIntegration: DeleteIntegrationResult; deleteLabel: DeleteLabelResult; @@ -1780,6 +1856,7 @@ export type Mutation = { unsubscribe: UnsubscribeResult; updateEmail: UpdateEmailResult; updateFilter: UpdateFilterResult; + updateFolderPolicy: UpdateFolderPolicyResult; updateHighlight: UpdateHighlightResult; updateLabel: UpdateLabelResult; updateNewsletterEmail: UpdateNewsletterEmailResult; @@ -1822,6 +1899,11 @@ export type MutationCreateArticleSavingRequestArgs = { }; +export type MutationCreateFolderPolicyArgs = { + input: CreateFolderPolicyInput; +}; + + export type MutationCreateGroupArgs = { input: CreateGroupInput; }; @@ -1862,6 +1944,11 @@ export type MutationDeleteFilterArgs = { }; +export type MutationDeleteFolderPolicyArgs = { + id: Scalars['ID']; +}; + + export type MutationDeleteHighlightArgs = { highlightId: Scalars['ID']; }; @@ -2095,6 +2182,11 @@ export type MutationUpdateFilterArgs = { }; +export type MutationUpdateFolderPolicyArgs = { + input: UpdateFolderPolicyInput; +}; + + export type MutationUpdateHighlightArgs = { input: UpdateHighlightInput; }; @@ -2280,6 +2372,7 @@ export type Query = { discoverTopics: GetDiscoverTopicResults; feeds: FeedsResult; filters: FiltersResult; + folderPolicies: FolderPoliciesResult; getDiscoverFeedArticles: GetDiscoverFeedArticleResults; getUserPersonalization: GetUserPersonalizationResult; groups: GroupsResult; @@ -3555,6 +3648,30 @@ export type UpdateFilterSuccess = { filter: Filter; }; +export type UpdateFolderPolicyError = { + __typename?: 'UpdateFolderPolicyError'; + errorCodes: Array; +}; + +export enum UpdateFolderPolicyErrorCode { + BadRequest = 'BAD_REQUEST', + Unauthorized = 'UNAUTHORIZED' +} + +export type UpdateFolderPolicyInput = { + action?: InputMaybe; + afterDays?: InputMaybe; + id: Scalars['ID']; + minimumItems?: InputMaybe; +}; + +export type UpdateFolderPolicyResult = UpdateFolderPolicyError | UpdateFolderPolicySuccess; + +export type UpdateFolderPolicySuccess = { + __typename?: 'UpdateFolderPolicySuccess'; + policy: FolderPolicy; +}; + export type UpdateHighlightError = { __typename?: 'UpdateHighlightError'; errorCodes: Array; @@ -4180,6 +4297,11 @@ export type ResolversTypes = { CreateArticleSavingRequestResult: ResolversTypes['CreateArticleSavingRequestError'] | ResolversTypes['CreateArticleSavingRequestSuccess']; CreateArticleSavingRequestSuccess: ResolverTypeWrapper; CreateArticleSuccess: ResolverTypeWrapper; + CreateFolderPolicyError: ResolverTypeWrapper; + CreateFolderPolicyErrorCode: CreateFolderPolicyErrorCode; + CreateFolderPolicyInput: CreateFolderPolicyInput; + CreateFolderPolicyResult: ResolversTypes['CreateFolderPolicyError'] | ResolversTypes['CreateFolderPolicySuccess']; + CreateFolderPolicySuccess: ResolverTypeWrapper; CreateGroupError: ResolverTypeWrapper; CreateGroupErrorCode: CreateGroupErrorCode; CreateGroupInput: CreateGroupInput; @@ -4234,6 +4356,10 @@ export type ResolversTypes = { DeleteFilterErrorCode: DeleteFilterErrorCode; DeleteFilterResult: ResolversTypes['DeleteFilterError'] | ResolversTypes['DeleteFilterSuccess']; DeleteFilterSuccess: ResolverTypeWrapper; + DeleteFolderPolicyError: ResolverTypeWrapper; + DeleteFolderPolicyErrorCode: DeleteFolderPolicyErrorCode; + DeleteFolderPolicyResult: ResolversTypes['DeleteFolderPolicyError'] | ResolversTypes['DeleteFolderPolicySuccess']; + DeleteFolderPolicySuccess: ResolverTypeWrapper; DeleteHighlightError: ResolverTypeWrapper; DeleteHighlightErrorCode: DeleteHighlightErrorCode; DeleteHighlightReplyError: ResolverTypeWrapper; @@ -4324,6 +4450,12 @@ export type ResolversTypes = { FiltersResult: ResolversTypes['FiltersError'] | ResolversTypes['FiltersSuccess']; FiltersSuccess: ResolverTypeWrapper; Float: ResolverTypeWrapper; + FolderPoliciesError: ResolverTypeWrapper; + FolderPoliciesErrorCode: FolderPoliciesErrorCode; + FolderPoliciesResult: ResolversTypes['FolderPoliciesError'] | ResolversTypes['FolderPoliciesSuccess']; + FolderPoliciesSuccess: ResolverTypeWrapper; + FolderPolicy: ResolverTypeWrapper; + FolderPolicyAction: FolderPolicyAction; GenerateApiKeyError: ResolverTypeWrapper; GenerateApiKeyErrorCode: GenerateApiKeyErrorCode; GenerateApiKeyInput: GenerateApiKeyInput; @@ -4659,6 +4791,11 @@ export type ResolversTypes = { UpdateFilterInput: UpdateFilterInput; UpdateFilterResult: ResolversTypes['UpdateFilterError'] | ResolversTypes['UpdateFilterSuccess']; UpdateFilterSuccess: ResolverTypeWrapper; + UpdateFolderPolicyError: ResolverTypeWrapper; + UpdateFolderPolicyErrorCode: UpdateFolderPolicyErrorCode; + UpdateFolderPolicyInput: UpdateFolderPolicyInput; + UpdateFolderPolicyResult: ResolversTypes['UpdateFolderPolicyError'] | ResolversTypes['UpdateFolderPolicySuccess']; + UpdateFolderPolicySuccess: ResolverTypeWrapper; UpdateHighlightError: ResolverTypeWrapper; UpdateHighlightErrorCode: UpdateHighlightErrorCode; UpdateHighlightInput: UpdateHighlightInput; @@ -4794,6 +4931,10 @@ export type ResolversParentTypes = { CreateArticleSavingRequestResult: ResolversParentTypes['CreateArticleSavingRequestError'] | ResolversParentTypes['CreateArticleSavingRequestSuccess']; CreateArticleSavingRequestSuccess: CreateArticleSavingRequestSuccess; CreateArticleSuccess: CreateArticleSuccess; + CreateFolderPolicyError: CreateFolderPolicyError; + CreateFolderPolicyInput: CreateFolderPolicyInput; + CreateFolderPolicyResult: ResolversParentTypes['CreateFolderPolicyError'] | ResolversParentTypes['CreateFolderPolicySuccess']; + CreateFolderPolicySuccess: CreateFolderPolicySuccess; CreateGroupError: CreateGroupError; CreateGroupInput: CreateGroupInput; CreateGroupResult: ResolversParentTypes['CreateGroupError'] | ResolversParentTypes['CreateGroupSuccess']; @@ -4837,6 +4978,9 @@ export type ResolversParentTypes = { DeleteFilterError: DeleteFilterError; DeleteFilterResult: ResolversParentTypes['DeleteFilterError'] | ResolversParentTypes['DeleteFilterSuccess']; DeleteFilterSuccess: DeleteFilterSuccess; + DeleteFolderPolicyError: DeleteFolderPolicyError; + DeleteFolderPolicyResult: ResolversParentTypes['DeleteFolderPolicyError'] | ResolversParentTypes['DeleteFolderPolicySuccess']; + DeleteFolderPolicySuccess: DeleteFolderPolicySuccess; DeleteHighlightError: DeleteHighlightError; DeleteHighlightReplyError: DeleteHighlightReplyError; DeleteHighlightReplyResult: ResolversParentTypes['DeleteHighlightReplyError'] | ResolversParentTypes['DeleteHighlightReplySuccess']; @@ -4906,6 +5050,10 @@ export type ResolversParentTypes = { FiltersResult: ResolversParentTypes['FiltersError'] | ResolversParentTypes['FiltersSuccess']; FiltersSuccess: FiltersSuccess; Float: Scalars['Float']; + FolderPoliciesError: FolderPoliciesError; + FolderPoliciesResult: ResolversParentTypes['FolderPoliciesError'] | ResolversParentTypes['FolderPoliciesSuccess']; + FolderPoliciesSuccess: FolderPoliciesSuccess; + FolderPolicy: FolderPolicy; GenerateApiKeyError: GenerateApiKeyError; GenerateApiKeyInput: GenerateApiKeyInput; GenerateApiKeyResult: ResolversParentTypes['GenerateApiKeyError'] | ResolversParentTypes['GenerateApiKeySuccess']; @@ -5166,6 +5314,10 @@ export type ResolversParentTypes = { UpdateFilterInput: UpdateFilterInput; UpdateFilterResult: ResolversParentTypes['UpdateFilterError'] | ResolversParentTypes['UpdateFilterSuccess']; UpdateFilterSuccess: UpdateFilterSuccess; + UpdateFolderPolicyError: UpdateFolderPolicyError; + UpdateFolderPolicyInput: UpdateFolderPolicyInput; + UpdateFolderPolicyResult: ResolversParentTypes['UpdateFolderPolicyError'] | ResolversParentTypes['UpdateFolderPolicySuccess']; + UpdateFolderPolicySuccess: UpdateFolderPolicySuccess; UpdateHighlightError: UpdateHighlightError; UpdateHighlightInput: UpdateHighlightInput; UpdateHighlightReplyError: UpdateHighlightReplyError; @@ -5469,6 +5621,20 @@ export type CreateArticleSuccessResolvers; }; +export type CreateFolderPolicyErrorResolvers = { + errorCodes?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type CreateFolderPolicyResultResolvers = { + __resolveType: TypeResolveFn<'CreateFolderPolicyError' | 'CreateFolderPolicySuccess', ParentType, ContextType>; +}; + +export type CreateFolderPolicySuccessResolvers = { + policy?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type CreateGroupErrorResolvers = { errorCodes?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -5627,6 +5793,20 @@ export type DeleteFilterSuccessResolvers; }; +export type DeleteFolderPolicyErrorResolvers = { + errorCodes?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type DeleteFolderPolicyResultResolvers = { + __resolveType: TypeResolveFn<'DeleteFolderPolicyError' | 'DeleteFolderPolicySuccess', ParentType, ContextType>; +}; + +export type DeleteFolderPolicySuccessResolvers = { + success?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type DeleteHighlightErrorResolvers = { errorCodes?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -5992,6 +6172,31 @@ export type FiltersSuccessResolvers; }; +export type FolderPoliciesErrorResolvers = { + errorCodes?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type FolderPoliciesResultResolvers = { + __resolveType: TypeResolveFn<'FolderPoliciesError' | 'FolderPoliciesSuccess', ParentType, ContextType>; +}; + +export type FolderPoliciesSuccessResolvers = { + policies?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type FolderPolicyResolvers = { + action?: Resolver; + afterDays?: Resolver; + createdAt?: Resolver; + folder?: Resolver; + id?: Resolver; + minimumItems?: Resolver; + updatedAt?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type GenerateApiKeyErrorResolvers = { errorCodes?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -6486,6 +6691,7 @@ export type MutationResolvers>; createArticle?: Resolver>; createArticleSavingRequest?: Resolver>; + createFolderPolicy?: Resolver>; createGroup?: Resolver>; createHighlight?: Resolver>; createLabel?: Resolver>; @@ -6494,6 +6700,7 @@ export type MutationResolvers>; deleteDiscoverFeed?: Resolver>; deleteFilter?: Resolver>; + deleteFolderPolicy?: Resolver>; deleteHighlight?: Resolver>; deleteIntegration?: Resolver>; deleteLabel?: Resolver>; @@ -6543,6 +6750,7 @@ export type MutationResolvers>; updateEmail?: Resolver>; updateFilter?: Resolver>; + updateFolderPolicy?: Resolver>; updateHighlight?: Resolver>; updateLabel?: Resolver>; updateNewsletterEmail?: Resolver>; @@ -6639,6 +6847,7 @@ export type QueryResolvers; feeds?: Resolver>; filters?: Resolver; + folderPolicies?: Resolver; getDiscoverFeedArticles?: Resolver>; getUserPersonalization?: Resolver; groups?: Resolver; @@ -7391,6 +7600,20 @@ export type UpdateFilterSuccessResolvers; }; +export type UpdateFolderPolicyErrorResolvers = { + errorCodes?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type UpdateFolderPolicyResultResolvers = { + __resolveType: TypeResolveFn<'UpdateFolderPolicyError' | 'UpdateFolderPolicySuccess', ParentType, ContextType>; +}; + +export type UpdateFolderPolicySuccessResolvers = { + policy?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type UpdateHighlightErrorResolvers = { errorCodes?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -7734,6 +7957,9 @@ export type Resolvers = { CreateArticleSavingRequestResult?: CreateArticleSavingRequestResultResolvers; CreateArticleSavingRequestSuccess?: CreateArticleSavingRequestSuccessResolvers; CreateArticleSuccess?: CreateArticleSuccessResolvers; + CreateFolderPolicyError?: CreateFolderPolicyErrorResolvers; + CreateFolderPolicyResult?: CreateFolderPolicyResultResolvers; + CreateFolderPolicySuccess?: CreateFolderPolicySuccessResolvers; CreateGroupError?: CreateGroupErrorResolvers; CreateGroupResult?: CreateGroupResultResolvers; CreateGroupSuccess?: CreateGroupSuccessResolvers; @@ -7768,6 +7994,9 @@ export type Resolvers = { DeleteFilterError?: DeleteFilterErrorResolvers; DeleteFilterResult?: DeleteFilterResultResolvers; DeleteFilterSuccess?: DeleteFilterSuccessResolvers; + DeleteFolderPolicyError?: DeleteFolderPolicyErrorResolvers; + DeleteFolderPolicyResult?: DeleteFolderPolicyResultResolvers; + DeleteFolderPolicySuccess?: DeleteFolderPolicySuccessResolvers; DeleteHighlightError?: DeleteHighlightErrorResolvers; DeleteHighlightReplyError?: DeleteHighlightReplyErrorResolvers; DeleteHighlightReplyResult?: DeleteHighlightReplyResultResolvers; @@ -7833,6 +8062,10 @@ export type Resolvers = { FiltersError?: FiltersErrorResolvers; FiltersResult?: FiltersResultResolvers; FiltersSuccess?: FiltersSuccessResolvers; + FolderPoliciesError?: FolderPoliciesErrorResolvers; + FolderPoliciesResult?: FolderPoliciesResultResolvers; + FolderPoliciesSuccess?: FolderPoliciesSuccessResolvers; + FolderPolicy?: FolderPolicyResolvers; GenerateApiKeyError?: GenerateApiKeyErrorResolvers; GenerateApiKeyResult?: GenerateApiKeyResultResolvers; GenerateApiKeySuccess?: GenerateApiKeySuccessResolvers; @@ -8054,6 +8287,9 @@ export type Resolvers = { UpdateFilterError?: UpdateFilterErrorResolvers; UpdateFilterResult?: UpdateFilterResultResolvers; UpdateFilterSuccess?: UpdateFilterSuccessResolvers; + UpdateFolderPolicyError?: UpdateFolderPolicyErrorResolvers; + UpdateFolderPolicyResult?: UpdateFolderPolicyResultResolvers; + UpdateFolderPolicySuccess?: UpdateFolderPolicySuccessResolvers; UpdateHighlightError?: UpdateHighlightErrorResolvers; UpdateHighlightReplyError?: UpdateHighlightReplyErrorResolvers; UpdateHighlightReplyResult?: UpdateHighlightReplyResultResolvers; diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index 5692cb0aa..fa6d674b0 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -307,6 +307,28 @@ type CreateArticleSuccess { user: User! } +type CreateFolderPolicyError { + errorCodes: [CreateFolderPolicyErrorCode!]! +} + +enum CreateFolderPolicyErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +input CreateFolderPolicyInput { + action: FolderPolicyAction! + afterDays: Int! + folder: String! + minimumItems: Int +} + +union CreateFolderPolicyResult = CreateFolderPolicyError | CreateFolderPolicySuccess + +type CreateFolderPolicySuccess { + policy: FolderPolicy! +} + type CreateGroupError { errorCodes: [CreateGroupErrorCode!]! } @@ -557,6 +579,21 @@ type DeleteFilterSuccess { filter: Filter! } +type DeleteFolderPolicyError { + errorCodes: [DeleteFolderPolicyErrorCode!]! +} + +enum DeleteFolderPolicyErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +union DeleteFolderPolicyResult = DeleteFolderPolicyError | DeleteFolderPolicySuccess + +type DeleteFolderPolicySuccess { + success: Boolean! +} + type DeleteHighlightError { errorCodes: [DeleteHighlightErrorCode!]! } @@ -972,6 +1009,36 @@ type FiltersSuccess { filters: [Filter!]! } +type FolderPoliciesError { + errorCodes: [FolderPoliciesErrorCode!]! +} + +enum FolderPoliciesErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +union FolderPoliciesResult = FolderPoliciesError | FolderPoliciesSuccess + +type FolderPoliciesSuccess { + policies: [FolderPolicy!]! +} + +type FolderPolicy { + action: FolderPolicyAction! + afterDays: Int! + createdAt: Date! + folder: String! + id: ID! + minimumItems: Int! + updatedAt: Date! +} + +enum FolderPolicyAction { + ARCHIVE + DELETE +} + type GenerateApiKeyError { errorCodes: [GenerateApiKeyErrorCode!]! } @@ -1555,6 +1622,7 @@ type Mutation { bulkAction(action: BulkActionType!, arguments: JSON, async: Boolean, expectedCount: Int, labelIds: [ID!], query: String!): BulkActionResult! createArticle(input: CreateArticleInput!): CreateArticleResult! createArticleSavingRequest(input: CreateArticleSavingRequestInput!): CreateArticleSavingRequestResult! + createFolderPolicy(input: CreateFolderPolicyInput!): CreateFolderPolicyResult! createGroup(input: CreateGroupInput!): CreateGroupResult! createHighlight(input: CreateHighlightInput!): CreateHighlightResult! createLabel(input: CreateLabelInput!): CreateLabelResult! @@ -1563,6 +1631,7 @@ type Mutation { deleteDiscoverArticle(input: DeleteDiscoverArticleInput!): DeleteDiscoverArticleResult! deleteDiscoverFeed(input: DeleteDiscoverFeedInput!): DeleteDiscoverFeedResult! deleteFilter(id: ID!): DeleteFilterResult! + deleteFolderPolicy(id: ID!): DeleteFolderPolicyResult! deleteHighlight(highlightId: ID!): DeleteHighlightResult! deleteIntegration(id: ID!): DeleteIntegrationResult! deleteLabel(id: ID!): DeleteLabelResult! @@ -1612,6 +1681,7 @@ type Mutation { unsubscribe(name: String!, subscriptionId: ID): UnsubscribeResult! updateEmail(input: UpdateEmailInput!): UpdateEmailResult! updateFilter(input: UpdateFilterInput!): UpdateFilterResult! + updateFolderPolicy(input: UpdateFolderPolicyInput!): UpdateFolderPolicyResult! updateHighlight(input: UpdateHighlightInput!): UpdateHighlightResult! updateLabel(input: UpdateLabelInput!): UpdateLabelResult! updateNewsletterEmail(input: UpdateNewsletterEmailInput!): UpdateNewsletterEmailResult! @@ -1754,6 +1824,7 @@ type Query { discoverTopics: GetDiscoverTopicResults! feeds(input: FeedsInput!): FeedsResult! filters: FiltersResult! + folderPolicies: FolderPoliciesResult! getDiscoverFeedArticles(after: String, discoverTopicId: String!, feedId: ID, first: Int): GetDiscoverFeedArticleResults! getUserPersonalization: GetUserPersonalizationResult! groups: GroupsResult! @@ -2835,6 +2906,28 @@ type UpdateFilterSuccess { filter: Filter! } +type UpdateFolderPolicyError { + errorCodes: [UpdateFolderPolicyErrorCode!]! +} + +enum UpdateFolderPolicyErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +input UpdateFolderPolicyInput { + action: FolderPolicyAction + afterDays: Int + id: ID! + minimumItems: Int +} + +union UpdateFolderPolicyResult = UpdateFolderPolicyError | UpdateFolderPolicySuccess + +type UpdateFolderPolicySuccess { + policy: FolderPolicy! +} + type UpdateHighlightError { errorCodes: [UpdateHighlightErrorCode!]! } diff --git a/packages/api/src/resolvers/folder_policy/index.ts b/packages/api/src/resolvers/folder_policy/index.ts new file mode 100644 index 000000000..d9ad12c2b --- /dev/null +++ b/packages/api/src/resolvers/folder_policy/index.ts @@ -0,0 +1,23 @@ +import { FolderPolicy } from '../../entity/folder_policy' +import { + FolderPoliciesError, + FolderPoliciesSuccess, +} from '../../generated/graphql' +import { findFolderPoliciesByUserId } from '../../services/folder_policy' +import { Merge } from '../../util' +import { authorized } from '../../utils/gql-utils' + +type PartialFolderPoliciesSuccess = Merge< + FolderPoliciesSuccess, + { policies: FolderPolicy[] } +> +export const folderPoliciesResolver = authorized< + PartialFolderPoliciesSuccess, + FolderPoliciesError +>(async (_, __, { uid }) => { + const policies = await findFolderPoliciesByUserId(uid) + + return { + policies, + } +}) diff --git a/packages/api/src/resolvers/function_resolvers.ts b/packages/api/src/resolvers/function_resolvers.ts index dfb0e276c..b03b13e90 100644 --- a/packages/api/src/resolvers/function_resolvers.ts +++ b/packages/api/src/resolvers/function_resolvers.ts @@ -5,6 +5,7 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { createHmac } from 'crypto' import { isError } from 'lodash' +import { FolderPolicy } from '../entity/folder_policy' import { Highlight } from '../entity/highlight' import { LibraryItem } from '../entity/library_item' import { @@ -52,6 +53,7 @@ import { saveDiscoverArticleResolver, } from './discover_feeds' import { optInFeatureResolver } from './features' +import { folderPoliciesResolver } from './folder_policy' import { highlightsResolver } from './highlight' import { hiddenHomeSectionResolver, @@ -342,6 +344,7 @@ export const functionResolvers = { subscription: subscriptionResolver, hiddenHomeSection: hiddenHomeSectionResolver, highlights: highlightsResolver, + folderPolicies: folderPoliciesResolver, }, User: { async intercomHash(user: User) { @@ -880,4 +883,5 @@ export const functionResolvers = { ...resultResolveTypeResolver('RefreshHome'), ...resultResolveTypeResolver('HiddenHomeSection'), ...resultResolveTypeResolver('Highlights'), + ...resultResolveTypeResolver('FolderPolicies'), } diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index 298bb7e90..1c0bead78 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -3247,6 +3247,101 @@ const schema = gql` BAD_REQUEST } + type FolderPolicy { + id: ID! + folder: String! + action: FolderPolicyAction! + afterDays: Int! + minimumItems: Int! + createdAt: Date! + updatedAt: Date! + } + + enum FolderPolicyAction { + ARCHIVE + DELETE + } + + union FolderPoliciesResult = FolderPoliciesSuccess | FolderPoliciesError + + type FolderPoliciesSuccess { + policies: [FolderPolicy!]! + } + + type FolderPoliciesError { + errorCodes: [FolderPoliciesErrorCode!]! + } + + enum FolderPoliciesErrorCode { + UNAUTHORIZED + BAD_REQUEST + } + + input CreateFolderPolicyInput { + folder: String! @sanitize(minLength: 1, maxLength: 255) + action: FolderPolicyAction! + afterDays: Int! + minimumItems: Int + } + + union CreateFolderPolicyResult = + CreateFolderPolicySuccess + | CreateFolderPolicyError + + type CreateFolderPolicySuccess { + policy: FolderPolicy! + } + + type CreateFolderPolicyError { + errorCodes: [CreateFolderPolicyErrorCode!]! + } + + enum CreateFolderPolicyErrorCode { + UNAUTHORIZED + BAD_REQUEST + } + + union DeleteFolderPolicyResult = + DeleteFolderPolicySuccess + | DeleteFolderPolicyError + + type DeleteFolderPolicySuccess { + success: Boolean! + } + + type DeleteFolderPolicyError { + errorCodes: [DeleteFolderPolicyErrorCode!]! + } + + enum DeleteFolderPolicyErrorCode { + UNAUTHORIZED + BAD_REQUEST + } + + union UpdateFolderPolicyResult = + UpdateFolderPolicySuccess + | UpdateFolderPolicyError + + type UpdateFolderPolicySuccess { + policy: FolderPolicy! + } + + type UpdateFolderPolicyError { + errorCodes: [UpdateFolderPolicyErrorCode!]! + } + + enum UpdateFolderPolicyErrorCode { + UNAUTHORIZED + BAD_REQUEST + } + + input UpdateFolderPolicyInput { + id: ID! + action: FolderPolicyAction + afterDays: Int + minimumItems: Int + } + # Mutations type Mutation { googleLogin(input: GoogleLoginInput!): LoginResult! @@ -3373,6 +3468,13 @@ const schema = gql` editDiscoverFeed(input: EditDiscoverFeedInput!): EditDiscoverFeedResult! emptyTrash: EmptyTrashResult! refreshHome: RefreshHomeResult! + createFolderPolicy( + input: CreateFolderPolicyInput! + ): CreateFolderPolicyResult! + updateFolderPolicy( + input: UpdateFolderPolicyInput! + ): UpdateFolderPolicyResult! + deleteFolderPolicy(id: ID!): DeleteFolderPolicyResult! } # FIXME: remove sort from feedArticles after all cached tabs are closed @@ -3447,6 +3549,7 @@ const schema = gql` subscription(id: ID!): SubscriptionResult! hiddenHomeSection: HiddenHomeSectionResult! highlights(after: String, first: Int, query: String): HighlightsResult! + folderPolicies: FolderPoliciesResult! } schema { diff --git a/packages/api/src/services/folder_policy.ts b/packages/api/src/services/folder_policy.ts new file mode 100644 index 000000000..b4477a6be --- /dev/null +++ b/packages/api/src/services/folder_policy.ts @@ -0,0 +1,38 @@ +import { FolderPolicy, FolderPolicyAction } from '../entity/folder_policy' +import { getRepository } from '../repository' + +export const createFolderPolicy = async (folderPolicy: { + userId: string + folder: string + action: FolderPolicyAction + afterDays: number + minimumItems: number +}) => { + return getRepository(FolderPolicy).save(folderPolicy) +} + +export const findFolderPoliciesByUserId = async (userId: string) => { + return getRepository(FolderPolicy).find({ + where: { userId }, + order: { folder: 'ASC' }, + }) +} + +export const updateFolderPolicy = async ( + id: string, + update: Partial +) => { + return getRepository(FolderPolicy).update(id, update) +} + +export const deleteFolderPolicy = async (id: string) => { + return getRepository(FolderPolicy).delete(id) +} + +export const findFolderPolicies = async () => { + return getRepository(FolderPolicy).find() +} + +export const findFolderPolicyById = async (id: string) => { + return getRepository(FolderPolicy).findOneBy({ id }) +} diff --git a/packages/api/test/resolvers/folder_policy.test.ts b/packages/api/test/resolvers/folder_policy.test.ts new file mode 100644 index 000000000..84254eb14 --- /dev/null +++ b/packages/api/test/resolvers/folder_policy.test.ts @@ -0,0 +1,59 @@ +import { FolderPolicyAction } from '../../src/entity/folder_policy' +import { User } from '../../src/entity/user' +import { createFolderPolicy } from '../../src/services/folder_policy' +import { deleteUser } from '../../src/services/user' +import { createTestUser } from '../db' +import { graphqlRequest, loginAndGetAuthToken } from '../util' +import { expect } from 'chai' + +describe('Folder Policy API', () => { + let loginUser: User + let authToken: string + + before(async () => { + // create test user and login + loginUser = await createTestUser('loginUser') + authToken = await loginAndGetAuthToken(loginUser.email) + }) + + after(async () => { + await deleteUser(loginUser.id) + }) + + describe('List Folder Policy', () => { + const query = ` + query { + folderPolicies { + ... on FolderPoliciesSuccess { + policies { + id + folder + action + createdAt + updatedAt + } + } + ... on FolderPoliciesError { + errorCodes + } + } + } + ` + + it('should return a list of folder policy of the user', async () => { + const existingPolicy = await createFolderPolicy({ + userId: loginUser.id, + folder: 'test-folder', + action: FolderPolicyAction.ARCHIVE, + afterDays: 30, + minimumItems: 10, + }) + + const res = await graphqlRequest(query, authToken).expect(200) + + const policies = res.body.data.folderPolicies.policies as any[] + expect(policies).to.have.lengthOf(1) + expect(policies[0].id).to.equal(existingPolicy.id) + }) + }) +}) diff --git a/packages/api/test/util.ts b/packages/api/test/util.ts index 42b5ecd60..6f043d98a 100644 --- a/packages/api/test/util.ts +++ b/packages/api/test/util.ts @@ -60,3 +60,11 @@ export const generateFakeUuid = () => { export const generateFakeShortId = () => { return nanoid(8) } + +export const loginAndGetAuthToken = async (email: string) => { + const res = await request + .post('/local/debug/fake-user-login') + .send({ fakeEmail: email }) + + return res.body.authToken as string +} diff --git a/packages/db/migrations/0180.do.folder_policy.sql b/packages/db/migrations/0180.do.folder_policy.sql index aa9d7bd73..b40391bd5 100755 --- a/packages/db/migrations/0180.do.folder_policy.sql +++ b/packages/db/migrations/0180.do.folder_policy.sql @@ -4,15 +4,18 @@ BEGIN; +CREATE TYPE folder_action AS ENUM ('DELETE', 'ARCHIVE'); + CREATE TABLE omnivore.folder_policy ( id UUID PRIMARY KEY DEFAULT uuid_generate_v1mc(), user_id UUID NOT NULL REFERENCES omnivore.user(id) ON DELETE CASCADE, folder TEXT NOT NULL, -- folder name in lowercase - action TEXT NOT NULL, -- delete or archive + action folder_action NOT NULL, -- delete or archive after_days INT NOT NULL, -- number of days after which the action should be taken minimum_items INT NOT NULL DEFAULT 0, -- minimum number of items to keep in the folder created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE (user_id, folder) -- only one policy per folder per user ); CREATE TRIGGER update_folder_policy_modtime BEFORE UPDATE ON omnivore.folder_policy FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); diff --git a/packages/db/migrations/0180.undo.folder_policy.sql b/packages/db/migrations/0180.undo.folder_policy.sql index 20add6644..ab2f0c829 100755 --- a/packages/db/migrations/0180.undo.folder_policy.sql +++ b/packages/db/migrations/0180.undo.folder_policy.sql @@ -6,4 +6,6 @@ BEGIN; DROP TABLE omnivore.folder_policy; +DROP TYPE folder_action; + COMMIT;