diff --git a/packages/api/src/entity/folder_policy.ts b/packages/api/src/entity/folder_policy.ts new file mode 100644 index 000000000..11427003b --- /dev/null +++ b/packages/api/src/entity/folder_policy.ts @@ -0,0 +1,49 @@ +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm' +import { User } from './user' + +export enum FolderPolicyAction { + Delete = 'DELETE', + Archive = 'ARCHIVE', +} + +@Entity({ name: 'folder_policy' }) +export class FolderPolicy { + @PrimaryGeneratedColumn('uuid') + id!: string + + @Column('uuid') + userId!: string + + @ManyToOne(() => User, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'user_id' }) + user!: User + + @Column('text') + folder!: string + + @Column('enum', { enum: FolderPolicyAction }) + action!: FolderPolicyAction + + @Column('int') + afterDays!: number + + @CreateDateColumn({ + type: 'timestamptz', + default: () => 'CURRENT_TIMESTAMP', + }) + createdAt!: Date + + @UpdateDateColumn({ + type: 'timestamptz', + default: () => 'CURRENT_TIMESTAMP', + }) + updatedAt!: Date +} diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index 203364aa9..ecc2e59ba 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -349,6 +349,29 @@ 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']; +}; + +export type CreateFolderPolicyResult = CreateFolderPolicyError | CreateFolderPolicySuccess; + +export type CreateFolderPolicySuccess = { + __typename?: 'CreateFolderPolicySuccess'; + policy: FolderPolicy; +}; + export type CreateGroupError = { __typename?: 'CreateGroupError'; errorCodes: Array; @@ -619,6 +642,22 @@ export type DeleteFilterSuccess = { filter: Filter; }; +export type DeleteFolderPolicyError = { + __typename?: 'DeleteFolderPolicyError'; + errorCodes: Array; +}; + +export enum DeleteFolderPolicyErrorCode { + Unauthorized = 'UNAUTHORIZED' +} + +export type DeleteFolderPolicyResult = DeleteFolderPolicyError | DeleteFolderPolicySuccess; + +export type DeleteFolderPolicySuccess = { + __typename?: 'DeleteFolderPolicySuccess'; + success: Scalars['Boolean']; +}; + export type DeleteHighlightError = { __typename?: 'DeleteHighlightError'; errorCodes: Array; @@ -1081,6 +1120,38 @@ 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']; + updatedAt: Scalars['Date']; +}; + +export enum FolderPolicyAction { + Archive = 'ARCHIVE', + Delete = 'DELETE' +} + export type GenerateApiKeyError = { __typename?: 'GenerateApiKeyError'; errorCodes: Array; @@ -1723,6 +1794,7 @@ export type Mutation = { bulkAction: BulkActionResult; createArticle: CreateArticleResult; createArticleSavingRequest: CreateArticleSavingRequestResult; + createFolderPolicy: CreateFolderPolicyResult; createGroup: CreateGroupResult; createHighlight: CreateHighlightResult; createLabel: CreateLabelResult; @@ -1731,6 +1803,7 @@ export type Mutation = { deleteDiscoverArticle: DeleteDiscoverArticleResult; deleteDiscoverFeed: DeleteDiscoverFeedResult; deleteFilter: DeleteFilterResult; + deleteFolderPolicy: DeleteFolderPolicyResult; deleteHighlight: DeleteHighlightResult; deleteIntegration: DeleteIntegrationResult; deleteLabel: DeleteLabelResult; @@ -1780,6 +1853,7 @@ export type Mutation = { unsubscribe: UnsubscribeResult; updateEmail: UpdateEmailResult; updateFilter: UpdateFilterResult; + updateFolderPolicy: UpdateFolderPolicyResult; updateHighlight: UpdateHighlightResult; updateLabel: UpdateLabelResult; updateNewsletterEmail: UpdateNewsletterEmailResult; @@ -1822,6 +1896,11 @@ export type MutationCreateArticleSavingRequestArgs = { }; +export type MutationCreateFolderPolicyArgs = { + input: CreateFolderPolicyInput; +}; + + export type MutationCreateGroupArgs = { input: CreateGroupInput; }; @@ -1862,6 +1941,11 @@ export type MutationDeleteFilterArgs = { }; +export type MutationDeleteFolderPolicyArgs = { + id: Scalars['ID']; +}; + + export type MutationDeleteHighlightArgs = { highlightId: Scalars['ID']; }; @@ -2095,6 +2179,11 @@ export type MutationUpdateFilterArgs = { }; +export type MutationUpdateFolderPolicyArgs = { + input: UpdateFolderPolicyInput; +}; + + export type MutationUpdateHighlightArgs = { input: UpdateHighlightInput; }; @@ -2280,6 +2369,7 @@ export type Query = { discoverTopics: GetDiscoverTopicResults; feeds: FeedsResult; filters: FiltersResult; + folderPolicies: FolderPoliciesResult; getDiscoverFeedArticles: GetDiscoverFeedArticleResults; getUserPersonalization: GetUserPersonalizationResult; groups: GroupsResult; @@ -3555,6 +3645,29 @@ 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']; +}; + +export type UpdateFolderPolicyResult = UpdateFolderPolicyError | UpdateFolderPolicySuccess; + +export type UpdateFolderPolicySuccess = { + __typename?: 'UpdateFolderPolicySuccess'; + policy: FolderPolicy; +}; + export type UpdateHighlightError = { __typename?: 'UpdateHighlightError'; errorCodes: Array; @@ -4180,6 +4293,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 +4352,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 +4446,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 +4787,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 +4927,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 +4974,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 +5046,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 +5310,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 +5617,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 +5789,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 +6168,30 @@ 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; + updatedAt?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type GenerateApiKeyErrorResolvers = { errorCodes?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -6486,6 +6686,7 @@ export type MutationResolvers>; createArticle?: Resolver>; createArticleSavingRequest?: Resolver>; + createFolderPolicy?: Resolver>; createGroup?: Resolver>; createHighlight?: Resolver>; createLabel?: Resolver>; @@ -6494,6 +6695,7 @@ export type MutationResolvers>; deleteDiscoverFeed?: Resolver>; deleteFilter?: Resolver>; + deleteFolderPolicy?: Resolver>; deleteHighlight?: Resolver>; deleteIntegration?: Resolver>; deleteLabel?: Resolver>; @@ -6543,6 +6745,7 @@ export type MutationResolvers>; updateEmail?: Resolver>; updateFilter?: Resolver>; + updateFolderPolicy?: Resolver>; updateHighlight?: Resolver>; updateLabel?: Resolver>; updateNewsletterEmail?: Resolver>; @@ -6639,6 +6842,7 @@ export type QueryResolvers; feeds?: Resolver>; filters?: Resolver; + folderPolicies?: Resolver; getDiscoverFeedArticles?: Resolver>; getUserPersonalization?: Resolver; groups?: Resolver; @@ -7391,6 +7595,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 +7952,9 @@ export type Resolvers = { CreateArticleSavingRequestResult?: CreateArticleSavingRequestResultResolvers; CreateArticleSavingRequestSuccess?: CreateArticleSavingRequestSuccessResolvers; CreateArticleSuccess?: CreateArticleSuccessResolvers; + CreateFolderPolicyError?: CreateFolderPolicyErrorResolvers; + CreateFolderPolicyResult?: CreateFolderPolicyResultResolvers; + CreateFolderPolicySuccess?: CreateFolderPolicySuccessResolvers; CreateGroupError?: CreateGroupErrorResolvers; CreateGroupResult?: CreateGroupResultResolvers; CreateGroupSuccess?: CreateGroupSuccessResolvers; @@ -7768,6 +7989,9 @@ export type Resolvers = { DeleteFilterError?: DeleteFilterErrorResolvers; DeleteFilterResult?: DeleteFilterResultResolvers; DeleteFilterSuccess?: DeleteFilterSuccessResolvers; + DeleteFolderPolicyError?: DeleteFolderPolicyErrorResolvers; + DeleteFolderPolicyResult?: DeleteFolderPolicyResultResolvers; + DeleteFolderPolicySuccess?: DeleteFolderPolicySuccessResolvers; DeleteHighlightError?: DeleteHighlightErrorResolvers; DeleteHighlightReplyError?: DeleteHighlightReplyErrorResolvers; DeleteHighlightReplyResult?: DeleteHighlightReplyResultResolvers; @@ -7833,6 +8057,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 +8282,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..a64449a29 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -307,6 +307,27 @@ type CreateArticleSuccess { user: User! } +type CreateFolderPolicyError { + errorCodes: [CreateFolderPolicyErrorCode!]! +} + +enum CreateFolderPolicyErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +input CreateFolderPolicyInput { + action: FolderPolicyAction! + afterDays: Int! + folder: String! +} + +union CreateFolderPolicyResult = CreateFolderPolicyError | CreateFolderPolicySuccess + +type CreateFolderPolicySuccess { + policy: FolderPolicy! +} + type CreateGroupError { errorCodes: [CreateGroupErrorCode!]! } @@ -557,6 +578,20 @@ type DeleteFilterSuccess { filter: Filter! } +type DeleteFolderPolicyError { + errorCodes: [DeleteFolderPolicyErrorCode!]! +} + +enum DeleteFolderPolicyErrorCode { + UNAUTHORIZED +} + +union DeleteFolderPolicyResult = DeleteFolderPolicyError | DeleteFolderPolicySuccess + +type DeleteFolderPolicySuccess { + success: Boolean! +} + type DeleteHighlightError { errorCodes: [DeleteHighlightErrorCode!]! } @@ -972,6 +1007,35 @@ 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! + updatedAt: Date! +} + +enum FolderPolicyAction { + ARCHIVE + DELETE +} + type GenerateApiKeyError { errorCodes: [GenerateApiKeyErrorCode!]! } @@ -1555,6 +1619,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 +1628,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 +1678,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 +1821,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 +2903,27 @@ type UpdateFilterSuccess { filter: Filter! } +type UpdateFolderPolicyError { + errorCodes: [UpdateFolderPolicyErrorCode!]! +} + +enum UpdateFolderPolicyErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +input UpdateFolderPolicyInput { + action: FolderPolicyAction + afterDays: Int + id: ID! +} + +union UpdateFolderPolicyResult = UpdateFolderPolicyError | UpdateFolderPolicySuccess + +type UpdateFolderPolicySuccess { + policy: FolderPolicy! +} + type UpdateHighlightError { errorCodes: [UpdateHighlightErrorCode!]! } diff --git a/packages/api/src/jobs/expire_folders.ts b/packages/api/src/jobs/expire_folders.ts new file mode 100644 index 000000000..8c823b60a --- /dev/null +++ b/packages/api/src/jobs/expire_folders.ts @@ -0,0 +1,7 @@ +import { appDataSource } from '../data_source' + +export const EXPIRE_FOLDERS_JOB_NAME = 'expire-folders' + +export const expireFoldersJob = async () => { + await appDataSource.query('CALL omnivore.expire_folders()') +} diff --git a/packages/api/src/queue-processor.ts b/packages/api/src/queue-processor.ts index 00440da53..a31dd6dfa 100644 --- a/packages/api/src/queue-processor.ts +++ b/packages/api/src/queue-processor.ts @@ -31,6 +31,10 @@ import { SAVE_NEWSLETTER_JOB, } from './jobs/email/inbound_emails' import { sendEmailJob, SEND_EMAIL_JOB } from './jobs/email/send_email' +import { + expireFoldersJob, + EXPIRE_FOLDERS_JOB_NAME, +} from './jobs/expire_folders' import { findThumbnail, THUMBNAIL_JOB } from './jobs/find_thumbnail' import { generatePreviewContent, @@ -217,6 +221,8 @@ export const createWorker = (connection: ConnectionOptions) => return generatePreviewContent(job.data) case PRUNE_TRASH_JOB: return pruneTrashJob(job.data) + case EXPIRE_FOLDERS_JOB_NAME: + return expireFoldersJob() default: logger.warning(`[queue-processor] unhandled job: ${job.name}`) } 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..ca4869477 --- /dev/null +++ b/packages/api/src/resolvers/folder_policy/index.ts @@ -0,0 +1,129 @@ +import { FolderPolicy, FolderPolicyAction } from '../../entity/folder_policy' +import { + CreateFolderPolicyError, + CreateFolderPolicyErrorCode, + CreateFolderPolicySuccess, + DeleteFolderPolicyError, + DeleteFolderPolicySuccess, + FolderPoliciesError, + FolderPoliciesSuccess, + MutationCreateFolderPolicyArgs, + MutationDeleteFolderPolicyArgs, + MutationUpdateFolderPolicyArgs, + UpdateFolderPolicyError, + UpdateFolderPolicyErrorCode, + UpdateFolderPolicySuccess, +} from '../../generated/graphql' +import { + createFolderPolicy, + deleteFolderPolicy, + findFolderPoliciesByUserId, + findFolderPolicyById, + updateFolderPolicy, +} from '../../services/folder_policy' +import { Merge } from '../../util' +import { authorized } from '../../utils/gql-utils' + +type PartialFolderPoliciesSuccess = Merge< + FolderPoliciesSuccess, + { policies: Array } +> +export const folderPoliciesResolver = authorized< + PartialFolderPoliciesSuccess, + FolderPoliciesError +>(async (_, __, { uid }) => { + const policies = await findFolderPoliciesByUserId(uid) + + return { + policies, + } +}) + +export const createFolderPolicyResolver = authorized< + Merge, + CreateFolderPolicyError, + MutationCreateFolderPolicyArgs +>(async (_, { input }, { uid, log }) => { + const { folder, action, afterDays } = input + + if (afterDays < 0) { + log.error('Invalid values') + + return { + errorCodes: [CreateFolderPolicyErrorCode.BadRequest], + } + } + + const policy = await createFolderPolicy({ + userId: uid, + folder, + action: action as unknown as FolderPolicyAction, + afterDays, + }) + + return { + policy, + } +}) + +export const updateFolderPolicyResolver = authorized< + Merge, + UpdateFolderPolicyError, + MutationUpdateFolderPolicyArgs +>(async (_, { input }, { log, uid }) => { + const { id, action, afterDays } = input + + if (!action && !afterDays) { + log.error('No fields to update') + + return { + errorCodes: [UpdateFolderPolicyErrorCode.BadRequest], + } + } + + if (afterDays && afterDays < 0) { + log.error('Invalid values') + + return { + errorCodes: [UpdateFolderPolicyErrorCode.BadRequest], + } + } + + const result = await updateFolderPolicy(uid, id, { + action: action ? (action as unknown as FolderPolicyAction) : undefined, + afterDays: afterDays ?? undefined, + }) + + if (!result.affected) { + log.error('Policy not found') + + return { + errorCodes: [UpdateFolderPolicyErrorCode.Unauthorized], + } + } + + const policy = await findFolderPolicyById(uid, id) + if (!policy) { + log.error('Policy not found') + + return { + errorCodes: [UpdateFolderPolicyErrorCode.Unauthorized], + } + } + + return { + policy, + } +}) + +export const deleteFolderPolicyResolver = authorized< + DeleteFolderPolicySuccess, + DeleteFolderPolicyError, + MutationDeleteFolderPolicyArgs +>(async (_, { id }, { uid }) => { + const result = await deleteFolderPolicy(uid, id) + + return { + success: !!result.affected, + } +}) diff --git a/packages/api/src/resolvers/function_resolvers.ts b/packages/api/src/resolvers/function_resolvers.ts index 91dbfcbe6..ce1fb0f46 100644 --- a/packages/api/src/resolvers/function_resolvers.ts +++ b/packages/api/src/resolvers/function_resolvers.ts @@ -52,6 +52,12 @@ import { saveDiscoverArticleResolver, } from './discover_feeds' import { optInFeatureResolver } from './features' +import { + createFolderPolicyResolver, + deleteFolderPolicyResolver, + folderPoliciesResolver, + updateFolderPolicyResolver, +} from './folder_policy' import { highlightsResolver } from './highlight' import { hiddenHomeSectionResolver, @@ -307,6 +313,9 @@ export const functionResolvers = { exportToIntegration: exportToIntegrationResolver, replyToEmail: replyToEmailResolver, refreshHome: refreshHomeResolver, + createFolderPolicy: createFolderPolicyResolver, + updateFolderPolicy: updateFolderPolicyResolver, + deleteFolderPolicy: deleteFolderPolicyResolver, }, Query: { me: getMeUserResolver, @@ -342,6 +351,7 @@ export const functionResolvers = { subscription: subscriptionResolver, hiddenHomeSection: hiddenHomeSectionResolver, highlights: highlightsResolver, + folderPolicies: folderPoliciesResolver, }, User: { async intercomHash(user: User) { @@ -882,4 +892,8 @@ export const functionResolvers = { ...resultResolveTypeResolver('RefreshHome'), ...resultResolveTypeResolver('HiddenHomeSection'), ...resultResolveTypeResolver('Highlights'), + ...resultResolveTypeResolver('FolderPolicies'), + ...resultResolveTypeResolver('CreateFolderPolicy'), + ...resultResolveTypeResolver('UpdateFolderPolicy'), + ...resultResolveTypeResolver('DeleteFolderPolicy'), } diff --git a/packages/api/src/routers/svc/links.ts b/packages/api/src/routers/svc/links.ts index bd35351cc..9f7910325 100644 --- a/packages/api/src/routers/svc/links.ts +++ b/packages/api/src/routers/svc/links.ts @@ -6,6 +6,7 @@ import { readPushSubscription } from '../../pubsub' import { userRepository } from '../../repository/user' import { createPageSaveRequest } from '../../services/create_page_save_request' import { enqueuePruneTrashJob } from '../../utils/createTask' +import { enqueueExpireFoldersJob } from '../../utils/createTask' import { logger } from '../../utils/logger' interface CreateLinkRequestMessage { @@ -92,5 +93,25 @@ export function linkServiceRouter() { } }) + router.post('/expireFolders', async (req, res) => { + const { expired } = readPushSubscription(req) + + if (expired) { + logger.info('discarding expired message') + return res.status(200).send('Expired') + } + + try { + const job = await enqueueExpireFoldersJob() + logger.info('enqueue job', { id: job?.id }) + + return res.sendStatus(200) + } catch (error) { + logger.error('error expire folders', error) + + return res.sendStatus(500) + } + }) + return router } diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index 298bb7e90..b6e8b1270 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -3247,6 +3247,97 @@ const schema = gql` BAD_REQUEST } + type FolderPolicy { + id: ID! + folder: String! + action: FolderPolicyAction! + afterDays: 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! + } + + 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 + } + + 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 + } + # Mutations type Mutation { googleLogin(input: GoogleLoginInput!): LoginResult! @@ -3373,6 +3464,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 +3545,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..08640cf67 --- /dev/null +++ b/packages/api/src/services/folder_policy.ts @@ -0,0 +1,46 @@ +import { FolderPolicy, FolderPolicyAction } from '../entity/folder_policy' +import { getRepository } from '../repository' + +export const createFolderPolicy = async (folderPolicy: { + userId: string + folder: string + action: FolderPolicyAction + afterDays: 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 ( + userId: string, + folderPolicyId: string, + update: Partial +) => { + return getRepository(FolderPolicy).update( + { id: folderPolicyId, userId }, + update + ) +} + +export const deleteFolderPolicy = async ( + userId: string, + folderPolicyId: string +) => { + return getRepository(FolderPolicy).delete({ + id: folderPolicyId, + userId, + }) +} + +export const findFolderPolicyById = async ( + userId: string, + folderPolicyId: string +) => { + return getRepository(FolderPolicy).findOneBy({ id: folderPolicyId, userId }) +} diff --git a/packages/api/src/utils/createTask.ts b/packages/api/src/utils/createTask.ts index 2957926fd..e95de4fd5 100644 --- a/packages/api/src/utils/createTask.ts +++ b/packages/api/src/utils/createTask.ts @@ -28,6 +28,7 @@ import { import { BulkActionData, BULK_ACTION_JOB_NAME } from '../jobs/bulk_action' import { CallWebhookJobData, CALL_WEBHOOK_JOB_NAME } from '../jobs/call_webhook' import { SendEmailJobData, SEND_EMAIL_JOB } from '../jobs/email/send_email' +import { EXPIRE_FOLDERS_JOB_NAME } from '../jobs/expire_folders' import { THUMBNAIL_JOB } from '../jobs/find_thumbnail' import { GENERATE_PREVIEW_CONTENT_JOB } from '../jobs/generate_preview_content' import { EXPORT_ALL_ITEMS_JOB_NAME } from '../jobs/integration/export_all_items' @@ -114,6 +115,7 @@ export const getJobPriority = (jobName: string): number => { case THUMBNAIL_JOB: case GENERATE_PREVIEW_CONTENT_JOB: case PRUNE_TRASH_JOB: + case EXPIRE_FOLDERS_JOB_NAME: return 100 default: @@ -1072,4 +1074,23 @@ export const enqueuePruneTrashJob = async (numDays: number) => { ) } +export const enqueueExpireFoldersJob = async () => { + const queue = await getBackendQueue() + if (!queue) { + return undefined + } + + return queue.add( + EXPIRE_FOLDERS_JOB_NAME, + {}, + { + jobId: `${EXPIRE_FOLDERS_JOB_NAME}_${JOB_VERSION}`, + removeOnComplete: true, + removeOnFail: true, + priority: getJobPriority(EXPIRE_FOLDERS_JOB_NAME), + attempts: 3, + } + ) +} + export default createHttpTaskWithToken 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..4112acb4f --- /dev/null +++ b/packages/api/test/resolvers/folder_policy.test.ts @@ -0,0 +1,212 @@ +import { expect } from 'chai' +import { + FolderPolicy as FolderPolicyEntity, + FolderPolicyAction, +} from '../../src/entity/folder_policy' +import { User } from '../../src/entity/user' +import { + DeleteFolderPolicySuccess, + FolderPolicy, +} from '../../src/generated/graphql' +import { + createFolderPolicy, + deleteFolderPolicy, + findFolderPolicyById, +} from '../../src/services/folder_policy' +import { deleteUser } from '../../src/services/user' +import { createTestUser } from '../db' +import { graphqlRequest, loginAndGetAuthToken } from '../util' + +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 in ascending order', async () => { + const existingPolicy = await createFolderPolicy({ + userId: loginUser.id, + folder: 'inbox', + action: FolderPolicyAction.Archive, + afterDays: 30, + }) + const existingPolicy1 = await createFolderPolicy({ + userId: loginUser.id, + folder: 'following', + action: FolderPolicyAction.Archive, + afterDays: 30, + }) + + const res = await graphqlRequest(query, authToken).expect(200) + + const policies = res.body.data.folderPolicies + .policies as Array + expect(policies).to.have.lengthOf(2) + expect(policies[0].id).to.equal(existingPolicy1.id) + expect(policies[1].id).to.equal(existingPolicy.id) + + await deleteFolderPolicy(loginUser.id, existingPolicy.id) + }) + }) + + describe('Create Folder Policy', () => { + const mutation = ` + mutation CreateFolderPolicy($input: CreateFolderPolicyInput!) { + createFolderPolicy(input: $input) { + ... on CreateFolderPolicySuccess { + policy { + id + folder + action + createdAt + updatedAt + } + } + ... on CreateFolderPolicyError { + errorCodes + } + } + } + ` + + it('should create a folder policy', async () => { + const input = { + folder: 'test-folder', + action: FolderPolicyAction.Archive, + afterDays: 30, + } + + const res = await graphqlRequest(mutation, authToken, { input }).expect( + 200 + ) + + const createdPolicy = res.body.data.createFolderPolicy + .policy as FolderPolicy + + const policy = await findFolderPolicyById(loginUser.id, createdPolicy.id) + expect(policy).to.exist + expect(policy?.folder).to.equal(input.folder) + + await deleteFolderPolicy(loginUser.id, createdPolicy.id) + }) + }) + + describe('Update Folder Policy', () => { + let existingPolicy: FolderPolicyEntity + + before(async () => { + existingPolicy = await createFolderPolicy({ + userId: loginUser.id, + folder: 'test-folder', + action: FolderPolicyAction.Archive, + afterDays: 30, + }) + }) + + after(async () => { + await deleteFolderPolicy(loginUser.id, existingPolicy.id) + }) + + const mutation = ` + mutation UpdateFolderPolicy($input: UpdateFolderPolicyInput!) { + updateFolderPolicy(input: $input) { + ... on UpdateFolderPolicySuccess { + policy { + id + folder + action + createdAt + updatedAt + } + } + ... on UpdateFolderPolicyError { + errorCodes + } + } + } + ` + + it('should update a folder policy', async () => { + const input = { + id: existingPolicy.id, + action: FolderPolicyAction.Delete, + afterDays: 30, + } + + const res = await graphqlRequest(mutation, authToken, { input }).expect( + 200 + ) + + const updatedPolicy = res.body.data.updateFolderPolicy + .policy as FolderPolicy + + const policy = await findFolderPolicyById(loginUser.id, updatedPolicy.id) + expect(policy).to.exist + expect(policy?.action).to.equal(input.action) + }) + }) + + describe('Delete Folder Policy', () => { + let existingPolicy: FolderPolicyEntity + + before(async () => { + existingPolicy = await createFolderPolicy({ + userId: loginUser.id, + folder: 'test-folder', + action: FolderPolicyAction.Archive, + afterDays: 30, + }) + }) + + const mutation = ` + mutation DeleteFolderPolicy($id: ID!) { + deleteFolderPolicy(id: $id) { + ... on DeleteFolderPolicySuccess { + success + } + ... on DeleteFolderPolicyError { + errorCodes + } + } + } + ` + + it('should delete a folder policy', async () => { + const res = await graphqlRequest(mutation, authToken, { + id: existingPolicy.id, + }).expect(200) + + const result = res.body.data + .deleteFolderPolicy as DeleteFolderPolicySuccess + expect(result.success).to.be.true + }) + }) +}) 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/0182.do.folder_policy.sql b/packages/db/migrations/0182.do.folder_policy.sql new file mode 100755 index 000000000..5fb0654b2 --- /dev/null +++ b/packages/db/migrations/0182.do.folder_policy.sql @@ -0,0 +1,68 @@ +-- Type: DO +-- Name: folder_policy +-- Description: Create a folder_policy table to contain the folder expiration policies for user and folder + +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 folder_action NOT NULL, -- delete or archive + after_days INT NOT NULL, -- number of days after which the action should be taken + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE (user_id, folder, action) -- only one policy per user and folder action +); + +CREATE TRIGGER update_folder_policy_modtime BEFORE UPDATE ON omnivore.folder_policy FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); + +GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.folder_policy TO omnivore_user; + +CREATE PROCEDURE omnivore.expire_folders() +LANGUAGE plpgsql +AS $$ +DECLARE + folder_record RECORD; + folder_name TEXT; + folder_action folder_action; + folder_user_id UUID; + folder_after_days INT; + old_states library_item_state[]; + new_state library_item_state; + column_name TEXT; + folder_policy_cursor CURSOR FOR SELECT id, user_id, folder, action, after_days FROM omnivore.folder_policy; +BEGIN + FOR folder_record IN folder_policy_cursor LOOP + folder_user_id := folder_record.user_id; + folder_name := folder_record.folder; + folder_action := folder_record.action; + folder_after_days := folder_record.after_days; + + IF folder_action = 'DELETE' THEN + old_states := ARRAY['SUCCEEDED', 'FAILED', 'ARCHIVED', 'PROCESSING', 'CONTENT_NOT_FETCHED'::library_item_state]; + new_state := 'DELETED'; + column_name := 'deleted_at'; + ELSIF folder_action = 'ARCHIVE' THEN + old_states := ARRAY['SUCCEEDED', 'FAILED', 'PROCESSING', 'CONTENT_NOT_FETCHED'::library_item_state]; + new_state := 'ARCHIVED'; + column_name := 'archived_at'; + END IF; + + BEGIN + PERFORM omnivore.set_claims(folder_user_id, 'omnivore_user'); + + EXECUTE format('UPDATE omnivore.library_item ' + 'SET state = $1, %I = CURRENT_TIMESTAMP ' + 'WHERE user_id = $2 AND state = ANY ($3) AND folder = $4 AND created_at < CURRENT_TIMESTAMP - INTERVAL ''$5 days''', column_name) + USING new_state, folder_user_id, old_states, folder_name, folder_after_days; + + COMMIT; + END; + END LOOP; +END; +$$; + +COMMIT; diff --git a/packages/db/migrations/0182.undo.folder_policy.sql b/packages/db/migrations/0182.undo.folder_policy.sql new file mode 100755 index 000000000..2f499eb66 --- /dev/null +++ b/packages/db/migrations/0182.undo.folder_policy.sql @@ -0,0 +1,13 @@ +-- Type: UNDO +-- Name: folder_policy +-- Description: Create a folder_policy table to contain the folder expiration policies for user and folder + +BEGIN; + +DROP TABLE omnivore.folder_policy; + +DROP TYPE folder_action; + +DROP PROCEDURE omnivore.expire_folders(); + +COMMIT;