Taichi Masuyama 4 年 前
コミット
44a546ef2e

+ 65 - 0
packages/app/src/server/interfaces/page-operation.ts

@@ -0,0 +1,65 @@
+import { ObjectIdLike } from './mongoose-utils';
+
+export type IPageForResuming = {
+  _id: ObjectIdLike,
+  path: string,
+  isEmpty: boolean,
+  parent?: ObjectIdLike,
+  grant?: number,
+  grantedUsers?: ObjectIdLike[],
+  grantedGroup?: ObjectIdLike,
+  descendantCount: number,
+  status?: number,
+  revision?: ObjectIdLike,
+  lastUpdateUser?: ObjectIdLike,
+  creator?: ObjectIdLike,
+};
+
+/*
+ * PageService interfaces
+ */
+export type ResumeRenameArgs = { // (page, newPagePath, user, options, shouldUseV4Process, renamedPage, oldPageParentId)
+  page: IPageForResuming, // TODO: improve type
+  newPagePath: string,
+  user: any, // TODO: improve type
+  options: boolean,
+  shouldUseV4Process,
+  renamedPage,
+  oldPageParentId,
+};
+export type ResumeDuplicateArgs = { // (page, newPagePath, user, shouldUseV4Process, createdPageId)
+  page: IPageForResuming,
+  newPagePath: any,
+  user: any,
+  shouldUseV4Process?: boolean,
+  createdPageId,
+};
+export type ResumeDeleteArgs = { // (page, user, shouldUseV4Process)
+  page: IPageForResuming,
+  user: any,
+  shouldUseV4Process?: boolean,
+};
+export type ResumeDeleteCompletelyArgs = { // (page, user, options, shouldUseV4Process)
+  page: IPageForResuming,
+  user: any,
+  options: any,
+  shouldUseV4Process?: boolean,
+};
+export type ResumeRevertArgs = { // (page, user, options, shouldUseV4Process)
+  page: IPageForResuming,
+  user: any,
+  options: any,
+  shouldUseV4Process?: boolean,
+};
+
+
+export type ResumeNormalizeParentArgs = {
+  a: any,
+};
+
+export type ResumerArgs = ResumeRenameArgs | ResumeDuplicateArgs | ResumeDeleteArgs | ResumeDeleteCompletelyArgs | ResumeRevertArgs | ResumeNormalizeParentArgs;
+
+
+/*
+ * PageOperationService interfaces
+ */

+ 70 - 0
packages/app/src/server/models/page-operation.ts

@@ -0,0 +1,70 @@
+import mongoose, {
+  Schema, Model, Document,
+} from 'mongoose';
+import { getOrCreateModel } from '@growi/core';
+
+import {
+  IPageForResuming,
+} from '~/server/interfaces/page-operation';
+
+type IObjectId = mongoose.Types.ObjectId;
+const ObjectId = mongoose.Schema.Types.ObjectId;
+
+const PageActionType = {
+  Rename: 'Rename',
+  Duplicate: 'Duplicate',
+  Delete: 'Delete',
+  DeleteCompletely: 'DeleteCompletely',
+  Revert: 'Revert',
+  NormalizeParent: 'NormalizeParent',
+} as const;
+
+export type PageActionType = typeof PageActionType[keyof typeof PageActionType];
+
+/*
+ * Main Schema
+ */
+export interface IPageOperation {
+  actionType: PageActionType,
+  pathsToBlock: string[],
+  page: IPageForResuming,
+}
+
+export interface PageOperationDocument extends IPageOperation, Document {}
+
+export interface PageOperationModel extends Model<PageOperationDocument> {
+  [x:string]: any // TODO: improve type
+}
+
+const pageArgSchema = new Schema<IPageForResuming>({
+  parent: { type: ObjectId, ref: 'Page' },
+  descendantCount: { type: Number },
+  isEmpty: { type: Boolean },
+  path: { type: String, required: true },
+  revision: { type: ObjectId, ref: 'Revision' },
+  status: { type: String },
+  grant: { type: Number },
+  grantedUsers: [{ type: ObjectId, ref: 'User' }],
+  grantedGroup: { type: ObjectId, ref: 'UserGroup' },
+  creator: { type: ObjectId, ref: 'User' },
+  lastUpdateUser: { type: ObjectId, ref: 'User' },
+}, { strict: false, strictQuery: false });
+
+const schema = new Schema<PageOperationDocument, PageOperationModel>({
+  actionType: {
+    type: String,
+    enum: PageActionType,
+    required: true,
+    index: true,
+  },
+  pathsToBlock: [
+    {
+      type: String,
+      required: true,
+      validate: [v => v.length >= 1, 'Must have minimum one path'],
+    },
+  ],
+  page: { type: pageArgSchema, required: true },
+});
+
+export default getOrCreateModel<PageOperationDocument, PageOperationModel>('PageOperation', schema);

+ 0 - 3
packages/app/src/server/models/page-redirect.ts

@@ -16,9 +16,6 @@ export interface PageRedirectModel extends Model<PageRedirectDocument> {
   [x:string]: any // TODO: improve type
 }
 
-/**
- * This is the setting for notify to 3rd party tool (like Slack).
- */
 const schema = new Schema<PageRedirectDocument, PageRedirectModel>({
   fromPath: {
     type: String, required: true, unique: true, index: true,

+ 37 - 0
packages/app/src/server/service/page-operation.ts

@@ -0,0 +1,37 @@
+import {
+  ResumeRenameArgs, ResumeDuplicateArgs, ResumeDeleteArgs, ResumeDeleteCompletelyArgs, ResumeRevertArgs,
+  ResumeNormalizeParentArgs, ResumerArgs,
+} from '~/server/interfaces/page-operation';
+import PageService from '~/server/service/page';
+
+// eslint-disable-next-line @typescript-eslint/ban-types
+export type Resumer = [Function, ResumerArgs];
+
+class PageOperationService {
+
+  crowi: any;
+
+  pageService: typeof PageService
+
+  constructor(crowi) {
+    this.crowi = crowi;
+    this.pageService = crowi.pageService;
+  }
+
+  async resumeAll(): Promise<void> {
+    const resumers = await this.prepareResumers();
+
+    await this.processResume(resumers);
+  }
+
+  private async prepareResumers(): Promise<Resumer[]> {
+    return [];
+  }
+
+  private async processResume(resumers: Resumer[]): Promise<void> {
+    return;
+  }
+
+}
+
+export default PageOperationService;

+ 37 - 39
packages/app/src/server/service/page.ts

@@ -747,7 +747,6 @@ class PageService {
     const result = serializePageSecurely(createdPage);
     result.tags = savedTags;
 
-    // TODO: resume
     if (isRecursively) {
       this.resumableDuplicateDescendants(page, newPagePath, user, shouldUseV4Process, createdPage._id);
     }
@@ -1095,25 +1094,25 @@ class PageService {
       this.pageEvent.emit('create', deletedPage, user);
     }
 
-    // TODO: resume
-    // no await for deleteDescendantsWithStream and updateDescendantCountOfAncestors
     if (isRecursively) {
-      (async() => {
-        const deletedDescendantCount = await this.deleteDescendantsWithStream(page, user, shouldUseV4Process); // use the same process in both version v4 and v5
-
-        // update descendantCount of ancestors'
-        if (page.parent != null) {
-          await this.updateDescendantCountOfAncestors(page.parent, (deletedDescendantCount + 1) * -1, true);
-
-          // delete leaf empty pages
-          await this.removeLeafEmptyPages(page);
-        }
-      })();
+      this.resumableDeleteDescendants(page, user, shouldUseV4Process);
     }
 
     return deletedPage;
   }
 
+  async resumableDeleteDescendants(page, user, shouldUseV4Process) {
+    const deletedDescendantCount = await this.deleteDescendantsWithStream(page, user, shouldUseV4Process); // use the same process in both version v4 and v5
+
+    // update descendantCount of ancestors'
+    if (page.parent != null) {
+      await this.updateDescendantCountOfAncestors(page.parent, (deletedDescendantCount + 1) * -1, true);
+
+      // delete leaf empty pages
+      await this.removeLeafEmptyPages(page);
+    }
+  }
+
   private async deletePageV4(page, user, options = {}, isRecursively = false) {
     const Page = mongoose.model('Page') as PageModel;
     const PageTagRelation = mongoose.model('PageTagRelation') as any; // TODO: Typescriptize model
@@ -1346,22 +1345,22 @@ class PageService {
       this.pageEvent.emit('deleteCompletely', page, user);
     }
 
-    // TODO: resume
     if (isRecursively) {
-      // no await for deleteCompletelyDescendantsWithStream
-      (async() => {
-        const deletedDescendantCount = await this.deleteCompletelyDescendantsWithStream(page, user, options, shouldUseV4Process);
-
-        // update descendantCount of ancestors'
-        if (page.parent != null) {
-          await this.updateDescendantCountOfAncestors(page.parent, (deletedDescendantCount + 1) * -1, true);
-        }
-      })();
+      this.resumableDeleteCompletelyDescendants(page, user, options, shouldUseV4Process);
     }
 
     return;
   }
 
+  async resumableDeleteCompletelyDescendants(page, user, options, shouldUseV4Process) {
+    const deletedDescendantCount = await this.deleteCompletelyDescendantsWithStream(page, user, options, shouldUseV4Process);
+
+    // update descendantCount of ancestors'
+    if (page.parent != null) {
+      await this.updateDescendantCountOfAncestors(page.parent, (deletedDescendantCount + 1) * -1, true);
+    }
+  }
+
   private async deleteCompletelyV4(page, user, options = {}, isRecursively = false, preventEmitting = false) {
     const ids = [page._id];
     const paths = [page.path];
@@ -1523,27 +1522,26 @@ class PageService {
     }, { new: true });
     await PageTagRelation.updateMany({ relatedPage: page._id }, { $set: { isPageTrashed: false } });
 
-    if (isRecursively) {
+    if (!isRecursively) {
       await this.updateDescendantCountOfAncestors(parent._id, 1, true);
     }
+    else {
+      this.resumableRevertDescendants(page, user, options, shouldUseV4Process);
+    }
 
-    // TODO: resume
-    if (!isRecursively) {
-      // no await for revertDeletedDescendantsWithStream
-      (async() => {
-        const revertedDescendantCount = await this.revertDeletedDescendantsWithStream(page, user, options, shouldUseV4Process);
+    return updatedPage;
+  }
 
-        // update descendantCount of ancestors'
-        if (page.parent != null) {
-          await this.updateDescendantCountOfAncestors(page.parent, revertedDescendantCount + 1, true);
+  async resumableRevertDescendants(page, user, options, shouldUseV4Process) {
+    const revertedDescendantCount = await this.revertDeletedDescendantsWithStream(page, user, options, shouldUseV4Process);
 
-          // delete leaf empty pages
-          await this.removeLeafEmptyPages(page);
-        }
-      })();
-    }
+    // update descendantCount of ancestors'
+    if (page.parent != null) {
+      await this.updateDescendantCountOfAncestors(page.parent, revertedDescendantCount + 1, true);
 
-    return updatedPage;
+      // delete leaf empty pages
+      await this.removeLeafEmptyPages(page);
+    }
   }
 
   private async revertDeletedPageV4(page, user, options = {}, isRecursively = false) {