Przeglądaj źródła

resolve conflicts e5ef64710c

Taichi Masuyama 3 lat temu
rodzic
commit
4b72afd6b6

+ 0 - 106
packages/app/src/server/models/obsolete-page.js

@@ -659,112 +659,6 @@ export const getPageSchema = (crowi) => {
     return pageData.save();
   }
 
-  async function validateAppliedScope(user, grant, grantUserGroupId) {
-    if (grant === GRANT_USER_GROUP && grantUserGroupId == null) {
-      throw new Error('grant userGroupId is not specified');
-    }
-
-    if (grant === GRANT_USER_GROUP) {
-      const UserGroupRelation = crowi.model('UserGroupRelation');
-      const count = await UserGroupRelation.countByGroupIdAndUser(grantUserGroupId, user);
-
-      if (count === 0) {
-        throw new Error('no relations were exist for group and user.');
-      }
-    }
-  }
-
-  pageSchema.statics.createV4 = async function(path, body, user, options = {}) {
-    /*
-     * v4 compatible process
-     */
-    validateCrowi();
-
-    const Page = this;
-    const Revision = crowi.model('Revision');
-    const format = options.format || 'markdown';
-    const grantUserGroupId = options.grantUserGroupId || null;
-    const expandContentWidth = crowi.configManager.getConfig('crowi', 'customize:isContainerFluid');
-
-    // sanitize path
-    path = crowi.xss.process(path); // eslint-disable-line no-param-reassign
-
-    let grant = options.grant;
-    // force public
-    if (isTopPage(path)) {
-      grant = GRANT_PUBLIC;
-    }
-
-    const isExist = await this.count({ path });
-
-    if (isExist) {
-      throw new Error('Cannot create new page to existed path');
-    }
-
-    const page = new Page();
-    page.path = path;
-    page.creator = user;
-    page.lastUpdateUser = user;
-    page.status = STATUS_PUBLISHED;
-    if (expandContentWidth != null) {
-      page.expandContentWidth = expandContentWidth;
-    }
-    await validateAppliedScope(user, grant, grantUserGroupId);
-    page.applyScope(user, grant, grantUserGroupId);
-
-    let savedPage = await page.save();
-    const newRevision = Revision.prepareRevision(savedPage, body, null, user, { format });
-    savedPage = await pushRevision(savedPage, newRevision, user);
-    await savedPage.populateDataToShowRevision();
-
-    pageEvent.emit('create', savedPage, user);
-
-    // update scopes for descendants
-    if (options.overwriteScopesOfDescendants) {
-      Page.applyScopesToDescendantsAsyncronously(savedPage, user, true);
-    }
-
-    return savedPage;
-  };
-
-  pageSchema.statics.updatePageV4 = async function(pageData, body, previousBody, user, options = {}) {
-    validateCrowi();
-
-    const Revision = crowi.model('Revision');
-    const grant = options.grant || pageData.grant; //                                  use the previous data if absence
-    const grantUserGroupId = options.grantUserGroupId || pageData.grantUserGroupId; // use the previous data if absence
-    const isSyncRevisionToHackmd = options.isSyncRevisionToHackmd;
-
-    await validateAppliedScope(user, grant, grantUserGroupId);
-    pageData.applyScope(user, grant, grantUserGroupId);
-
-    // update existing page
-    let savedPage = await pageData.save();
-
-    // Update revision
-    const isBodyPresent = body != null && previousBody != null;
-    const shouldUpdateBody = isBodyPresent;
-    if (shouldUpdateBody) {
-      const newRevision = await Revision.prepareRevision(pageData, body, previousBody, user);
-      savedPage = await pushRevision(savedPage, newRevision, user);
-      await savedPage.populateDataToShowRevision();
-
-      if (isSyncRevisionToHackmd) {
-        savedPage = await this.syncRevisionToHackmd(savedPage);
-      }
-    }
-
-    // update scopes for descendants
-    if (options.overwriteScopesOfDescendants) {
-      this.applyScopesToDescendantsAsyncronously(savedPage, user, true);
-    }
-
-
-    pageEvent.emit('update', savedPage, user);
-
-    return savedPage;
-  };
-
   pageSchema.statics.applyScopesToDescendantsAsyncronously = async function(parentPage, user, isV4 = false) {
     const builder = new this.PageQueryBuilder(this.find());
     builder.addConditionToListOnlyDescendants(parentPage.path);

+ 1 - 179
packages/app/src/server/models/page.ts

@@ -962,185 +962,7 @@ export type PageCreateOptions = {
 /*
  * Merge obsolete page model methods and define new methods which depend on crowi instance
  */
-// remove type for crowi to prevent 'import/no-cycle'
-// eslint-disable-next-line import/no-anonymous-default-export
-export default (crowi): any => {
-  let pageEvent;
-  if (crowi != null) {
-    pageEvent = crowi.event('page');
-  }
-
-  const shouldUseUpdatePageV4 = (grant: number, isV5Compatible: boolean, isOnTree: boolean): boolean => {
-    const isRestricted = grant === GRANT_RESTRICTED;
-    return !isRestricted && (!isV5Compatible || !isOnTree);
-  };
-
-  schema.statics.emitPageEventUpdate = (page: IPageHasId, user: IUserHasId): void => {
-    pageEvent.emit('update', page, user);
-  };
-
-  /**
-   * A wrapper method of schema.statics.updatePage for updating grant only.
-   * @param {PageDocument} page
-   * @param {UserDocument} user
-   * @param options
-   */
-  schema.statics.updateGrant = async function(page, user, grantData: {grant: PageGrant, grantedGroup: ObjectIdLike}) {
-    const { grant, grantedGroup } = grantData;
-
-    const options = {
-      grant,
-      grantUserGroupId: grantedGroup,
-      isSyncRevisionToHackmd: false,
-    };
-
-    return this.updatePage(page, null, null, user, options);
-  };
-
-  schema.statics.updatePage = async function(
-      pageData,
-      body: string | null,
-      previousBody: string | null,
-      user,
-      options: {grant?: PageGrant, grantUserGroupId?: ObjectIdLike, isSyncRevisionToHackmd?: boolean, overwriteScopesOfDescendants?: boolean} = {},
-  ) {
-    if (crowi.configManager == null || crowi.pageGrantService == null || crowi.pageService == null) {
-      throw Error('Crowi is not set up');
-    }
-
-    const wasOnTree = pageData.parent != null || isTopPage(pageData.path);
-    const exParent = pageData.parent;
-    const isV5Compatible = crowi.configManager.getConfig('crowi', 'app:isV5Compatible');
-
-    const shouldUseV4Process = shouldUseUpdatePageV4(pageData.grant, isV5Compatible, wasOnTree);
-    if (shouldUseV4Process) {
-      // v4 compatible process
-      return this.updatePageV4(pageData, body, previousBody, user, options);
-    }
-
-    const grant = options.grant ?? pageData.grant; // use the previous data if absence
-    const grantUserGroupId: undefined | ObjectIdLike = options.grantUserGroupId ?? pageData.grantedGroup?._id.toString();
-
-    const grantedUserIds = pageData.grantedUserIds || [user._id];
-    const shouldBeOnTree = grant !== GRANT_RESTRICTED;
-    const isChildrenExist = await this.count({ path: new RegExp(`^${escapeStringRegexp(addTrailingSlash(pageData.path))}`), parent: { $ne: null } });
-
-    const newPageData = pageData;
-
-    const { pageService, pageGrantService } = crowi;
-
-    if (shouldBeOnTree) {
-      let isGrantNormalized = false;
-      try {
-        const shouldCheckDescendants = !options.overwriteScopesOfDescendants;
-        isGrantNormalized = await pageGrantService.isGrantNormalized(user, pageData.path, grant, grantedUserIds, grantUserGroupId, shouldCheckDescendants);
-      }
-      catch (err) {
-        logger.error(`Failed to validate grant of page at "${pageData.path}" of grant ${grant}:`, err);
-        throw err;
-      }
-      if (!isGrantNormalized) {
-        throw Error('The selected grant or grantedGroup is not assignable to this page.');
-      }
-
-      if (options.overwriteScopesOfDescendants) {
-        const updateGrantInfo = await pageGrantService.generateUpdateGrantInfoToOverwriteDescendants(user, grant, options.grantUserGroupId);
-        const canOverwriteDescendants = await pageGrantService.canOverwriteDescendants(pageData.path, user, updateGrantInfo);
-
-        if (!canOverwriteDescendants) {
-          throw Error('Cannot overwrite scopes of descendants.');
-        }
-      }
-
-      if (!wasOnTree) {
-        const newParent = await pageService.getParentAndFillAncestorsByUser(user, newPageData.path);
-        newPageData.parent = newParent._id;
-      }
-    }
-    else {
-      if (wasOnTree && isChildrenExist) {
-        // Update children's parent with new parent
-        const newParentForChildren = await this.createEmptyPage(pageData.path, pageData.parent, pageData.descendantCount);
-        await this.updateMany(
-          { parent: pageData._id },
-          { parent: newParentForChildren._id },
-        );
-      }
-
-      newPageData.parent = null;
-      newPageData.descendantCount = 0;
-    }
-
-    newPageData.applyScope(user, grant, grantUserGroupId);
-
-    // update existing page
-    let savedPage = await newPageData.save();
-
-    // Update body
-    const Revision = mongoose.model('Revision') as any; // TODO: Typescriptize model
-    const isSyncRevisionToHackmd = options.isSyncRevisionToHackmd;
-    const isBodyPresent = body != null && previousBody != null;
-    const shouldUpdateBody = isBodyPresent;
-    if (shouldUpdateBody) {
-      const newRevision = await Revision.prepareRevision(newPageData, body, previousBody, user);
-      savedPage = await pushRevision(savedPage, newRevision, user);
-      await savedPage.populateDataToShowRevision();
-
-      if (isSyncRevisionToHackmd) {
-        savedPage = await this.syncRevisionToHackmd(savedPage);
-      }
-    }
-
-
-    this.emitPageEventUpdate(savedPage, user);
-
-    // Update ex children's parent
-    if (!wasOnTree && shouldBeOnTree) {
-      const emptyPageAtSamePath = await this.findOne({ path: pageData.path, isEmpty: true }); // this page is necessary to find children
-
-      if (isChildrenExist) {
-        if (emptyPageAtSamePath != null) {
-          // Update children's parent with new parent
-          await this.updateMany(
-            { parent: emptyPageAtSamePath._id },
-            { parent: savedPage._id },
-          );
-        }
-      }
-
-      await this.findOneAndDelete({ path: pageData.path, isEmpty: true }); // delete here
-    }
-
-    // Sub operation
-    // update scopes for descendants
-    if (options.overwriteScopesOfDescendants) {
-      this.applyScopesToDescendantsAsyncronously(savedPage, user);
-    }
-
-    // 1. Update descendantCount
-    const shouldPlusDescCount = !wasOnTree && shouldBeOnTree;
-    const shouldMinusDescCount = wasOnTree && !shouldBeOnTree;
-    if (shouldPlusDescCount) {
-      await pageService.updateDescendantCountOfAncestors(newPageData._id, 1, false);
-      const newDescendantCount = await this.recountDescendantCount(newPageData._id);
-      await this.updateOne({ _id: newPageData._id }, { descendantCount: newDescendantCount });
-    }
-    else if (shouldMinusDescCount) {
-      // Update from parent. Parent is null if newPageData.grant is RESTRECTED.
-      if (newPageData.grant === GRANT_RESTRICTED) {
-        await pageService.updateDescendantCountOfAncestors(exParent, -1, true);
-      }
-    }
-
-    // 2. Delete unnecessary empty pages
-    const shouldRemoveLeafEmpPages = wasOnTree && !isChildrenExist;
-    if (shouldRemoveLeafEmpPages) {
-      await this.removeLeafEmptyPagesRecursively(exParent);
-    }
-
-    return savedPage;
-  };
-
+export default (crowi: Crowi): any => {
   // add old page schema methods
   const pageSchema = getPageSchema(crowi);
   schema.methods = { ...pageSchema.methods, ...schema.methods };

+ 274 - 2
packages/app/src/server/service/page.ts

@@ -4,7 +4,7 @@ import { Readable, Writable } from 'stream';
 import {
   pagePathUtils, pathUtils, Ref, HasObjectId,
   IUserHasId,
-  IPage, IPageInfo, IPageInfoAll, IPageInfoForEntity, IPageWithMeta,
+  IPage, IPageInfo, IPageInfoAll, IPageInfoForEntity, IPageWithMeta, PageGrant,
 } from '@growi/core';
 import escapeStringRegexp from 'escape-string-regexp';
 import mongoose, { ObjectId, QueryCursor } from 'mongoose';
@@ -18,6 +18,7 @@ import {
 import {
   IPageOperationProcessInfo, IPageOperationProcessData, PageActionStage, PageActionType,
 } from '~/interfaces/page-operation';
+import { IUserHasId } from '~/interfaces/user';
 import { PageMigrationErrorData, SocketEventName, UpdateDescCountRawData } from '~/interfaces/websocket';
 import {
   CreateMethod, PageCreateOptions, PageModel, PageDocument, pushRevision, PageQueryBuilder,
@@ -3406,6 +3407,21 @@ class PageService {
     pageDocument.status = Page.STATUS_PUBLISHED;
   }
 
+  private async validateAppliedScope(user, grant, grantUserGroupId) {
+    if (grant === PageGrant.GRANT_USER_GROUP && grantUserGroupId == null) {
+      throw new Error('grant userGroupId is not specified');
+    }
+
+    if (grant === PageGrant.GRANT_USER_GROUP) {
+      const UserGroupRelation = mongoose.model('UserGroupRelation') as any;
+      const count = await UserGroupRelation.countByGroupIdAndUser(grantUserGroupId, user);
+
+      if (count === 0) {
+        throw new Error('no relations were exist for group and user.');
+      }
+    }
+  }
+
   private async canProcessCreate(
       path: string,
       grantData: {
@@ -3475,7 +3491,7 @@ class PageService {
     // Switch method
     const isV5Compatible = this.crowi.configManager.getConfig('crowi', 'app:isV5Compatible');
     if (!isV5Compatible) {
-      return Page.createV4(path, body, user, options);
+      return this.createV4(path, body, user, options);
     }
 
     // Values
@@ -3552,6 +3568,58 @@ class PageService {
     return savedPage;
   }
 
+  /**
+   * V4 compatible create method
+   */
+  private async createV4(path, body, user, options: any = {}) {
+    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Revision = mongoose.model('Revision') as any; // TODO: TypeScriptize model
+
+    const format = options.format || 'markdown';
+    const grantUserGroupId = options.grantUserGroupId || null;
+    const expandContentWidth = this.crowi.configManager.getConfig('crowi', 'customize:isContainerFluid');
+
+    // sanitize path
+    path = this.crowi.xss.process(path); // eslint-disable-line no-param-reassign
+
+    let grant = options.grant;
+    // force public
+    if (isTopPage(path)) {
+      grant = PageGrant.GRANT_PUBLIC;
+    }
+
+    const isExist = await Page.count({ path });
+
+    if (isExist) {
+      throw new Error('Cannot create new page to existed path');
+    }
+
+    const page = new Page();
+    page.path = path;
+    page.creator = user;
+    page.lastUpdateUser = user;
+    page.status = PageStatus.STATUS_PUBLISHED;
+    if (expandContentWidth != null) {
+      page.expandContentWidth = expandContentWidth;
+    }
+    await this.validateAppliedScope(user, grant, grantUserGroupId);
+    page.applyScope(user, grant, grantUserGroupId);
+
+    let savedPage = await page.save();
+    const newRevision = Revision.prepareRevision(savedPage, body, null, user, { format });
+    savedPage = await pushRevision(savedPage, newRevision, user);
+    await savedPage.populateDataToShowRevision();
+
+    this.pageEvent.emit('create', savedPage, user);
+
+    // update scopes for descendants
+    if (options.overwriteScopesOfDescendants) {
+      Page.applyScopesToDescendantsAsyncronously(savedPage, user, true);
+    }
+
+    return savedPage;
+  }
+
   private async canProcessForceCreateBySystem(
       path: string,
       grantData: {
@@ -3643,6 +3711,210 @@ class PageService {
     return savedPage;
   }
 
+  private shouldUseUpdatePageV4(grant: number, isV5Compatible: boolean, isOnTree: boolean): boolean {
+    const isRestricted = grant === PageGrant.GRANT_RESTRICTED;
+    return !isRestricted && (!isV5Compatible || !isOnTree);
+  }
+
+  /**
+   * A wrapper method of updatePage for updating grant only.
+   * @param {PageDocument} page
+   * @param {UserDocument} user
+   * @param options
+   */
+  async updateGrant(page, user, grantData: {grant: PageGrant, grantedGroup: ObjectIdLike}): Promise<PageDocument> {
+    const { grant, grantedGroup } = grantData;
+
+    const options = {
+      grant,
+      grantUserGroupId: grantedGroup,
+      isSyncRevisionToHackmd: false,
+    };
+
+    return this.updatePage(page, null, null, user, options);
+  }
+
+  async updatePage(
+      pageData,
+      body: string | null,
+      previousBody: string | null,
+      user,
+      options: {grant?: PageGrant, grantUserGroupId?: ObjectIdLike, isSyncRevisionToHackmd?: boolean, overwriteScopesOfDescendants?: boolean} = {},
+  ): Promise<PageDocument> {
+    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Revision = mongoose.model('Revision') as any; // TODO: Typescriptize model
+
+    const wasOnTree = pageData.parent != null || isTopPage(pageData.path);
+    const exParent = pageData.parent;
+    const isV5Compatible = this.crowi.configManager.getConfig('crowi', 'app:isV5Compatible');
+
+    const shouldUseV4Process = this.shouldUseUpdatePageV4(pageData.grant, isV5Compatible, wasOnTree);
+    if (shouldUseV4Process) {
+      // v4 compatible process
+      return this.updatePageV4(pageData, body, previousBody, user, options);
+    }
+
+    const grant = options.grant ?? pageData.grant; // use the previous data if absence
+    const grantUserGroupId: undefined | ObjectIdLike = options.grantUserGroupId ?? pageData.grantedGroup?._id.toString();
+
+    const grantedUserIds = pageData.grantedUserIds || [user._id];
+    const shouldBeOnTree = grant !== PageGrant.GRANT_RESTRICTED;
+    const isChildrenExist = await Page.count({ path: new RegExp(`^${escapeStringRegexp(addTrailingSlash(pageData.path))}`), parent: { $ne: null } });
+
+    const newPageData = pageData;
+
+    const { pageService, pageGrantService } = this.crowi;
+
+    if (shouldBeOnTree) {
+      let isGrantNormalized = false;
+      try {
+        const shouldCheckDescendants = !options.overwriteScopesOfDescendants;
+        isGrantNormalized = await pageGrantService.isGrantNormalized(user, pageData.path, grant, grantedUserIds, grantUserGroupId, shouldCheckDescendants);
+      }
+      catch (err) {
+        logger.error(`Failed to validate grant of page at "${pageData.path}" of grant ${grant}:`, err);
+        throw err;
+      }
+      if (!isGrantNormalized) {
+        throw Error('The selected grant or grantedGroup is not assignable to this page.');
+      }
+
+      if (options.overwriteScopesOfDescendants) {
+        const updateGrantInfo = await pageGrantService.generateUpdateGrantInfoToOverwriteDescendants(user, grant, options.grantUserGroupId);
+        const canOverwriteDescendants = await pageGrantService.canOverwriteDescendants(pageData.path, user, updateGrantInfo);
+
+        if (!canOverwriteDescendants) {
+          throw Error('Cannot overwrite scopes of descendants.');
+        }
+      }
+
+      if (!wasOnTree) {
+        const newParent = await pageService.getParentAndFillAncestorsByUser(user, newPageData.path);
+        newPageData.parent = newParent._id;
+      }
+    }
+    else {
+      if (wasOnTree && isChildrenExist) {
+        // Update children's parent with new parent
+        const newParentForChildren = await Page.createEmptyPage(pageData.path, pageData.parent, pageData.descendantCount);
+        await Page.updateMany(
+          { parent: pageData._id },
+          { parent: newParentForChildren._id },
+        );
+      }
+
+      newPageData.parent = null;
+      newPageData.descendantCount = 0;
+    }
+
+    newPageData.applyScope(user, grant, grantUserGroupId);
+
+    // update existing page
+    let savedPage = await newPageData.save();
+
+    // Update body
+    const isSyncRevisionToHackmd = options.isSyncRevisionToHackmd;
+    const isBodyPresent = body != null && previousBody != null;
+    const shouldUpdateBody = isBodyPresent;
+    if (shouldUpdateBody) {
+      const newRevision = await Revision.prepareRevision(newPageData, body, previousBody, user);
+      savedPage = await pushRevision(savedPage, newRevision, user);
+      await savedPage.populateDataToShowRevision();
+
+      if (isSyncRevisionToHackmd) {
+        savedPage = await Page.syncRevisionToHackmd(savedPage);
+      }
+    }
+
+
+    this.pageEvent.emit('update', savedPage, user);
+
+    // Update ex children's parent
+    if (!wasOnTree && shouldBeOnTree) {
+      const emptyPageAtSamePath = await Page.findOne({ path: pageData.path, isEmpty: true }); // this page is necessary to find children
+
+      if (isChildrenExist) {
+        if (emptyPageAtSamePath != null) {
+          // Update children's parent with new parent
+          await Page.updateMany(
+            { parent: emptyPageAtSamePath._id },
+            { parent: savedPage._id },
+          );
+        }
+      }
+
+      await Page.findOneAndDelete({ path: pageData.path, isEmpty: true }); // delete here
+    }
+
+    // Sub operation
+    // update scopes for descendants
+    if (options.overwriteScopesOfDescendants) {
+      Page.applyScopesToDescendantsAsyncronously(savedPage, user);
+    }
+
+    // 1. Update descendantCount
+    const shouldPlusDescCount = !wasOnTree && shouldBeOnTree;
+    const shouldMinusDescCount = wasOnTree && !shouldBeOnTree;
+    if (shouldPlusDescCount) {
+      await pageService.updateDescendantCountOfAncestors(newPageData._id, 1, false);
+      const newDescendantCount = await Page.recountDescendantCount(newPageData._id);
+      await Page.updateOne({ _id: newPageData._id }, { descendantCount: newDescendantCount });
+    }
+    else if (shouldMinusDescCount) {
+      // Update from parent. Parent is null if newPageData.grant is RESTRECTED.
+      if (newPageData.grant === PageGrant.GRANT_RESTRICTED) {
+        await pageService.updateDescendantCountOfAncestors(exParent, -1, true);
+      }
+    }
+
+    // 2. Delete unnecessary empty pages
+    const shouldRemoveLeafEmpPages = wasOnTree && !isChildrenExist;
+    if (shouldRemoveLeafEmpPages) {
+      await Page.removeLeafEmptyPagesRecursively(exParent);
+    }
+
+    return savedPage;
+  }
+
+
+  async updatePageV4(pageData, body, previousBody, user, options: any = {}): Promise<PageDocument> {
+    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Revision = mongoose.model('Revision') as any; // TODO: TypeScriptize model
+
+    const grant = options.grant || pageData.grant; // use the previous data if absence
+    const grantUserGroupId = options.grantUserGroupId || pageData.grantUserGroupId; // use the previous data if absence
+    const isSyncRevisionToHackmd = options.isSyncRevisionToHackmd;
+
+    await this.validateAppliedScope(user, grant, grantUserGroupId);
+    pageData.applyScope(user, grant, grantUserGroupId);
+
+    // update existing page
+    let savedPage = await pageData.save();
+
+    // Update revision
+    const isBodyPresent = body != null && previousBody != null;
+    const shouldUpdateBody = isBodyPresent;
+    if (shouldUpdateBody) {
+      const newRevision = await Revision.prepareRevision(pageData, body, previousBody, user);
+      savedPage = await pushRevision(savedPage, newRevision, user);
+      await savedPage.populateDataToShowRevision();
+
+      if (isSyncRevisionToHackmd) {
+        savedPage = await Page.syncRevisionToHackmd(savedPage);
+      }
+    }
+
+    // update scopes for descendants
+    if (options.overwriteScopesOfDescendants) {
+      Page.applyScopesToDescendantsAsyncronously(savedPage, user, true);
+    }
+
+
+    this.pageEvent.emit('update', savedPage, user);
+
+    return savedPage;
+  }
+
   /*
    * Find all children by parent's path or id. Using id should be prioritized
    */