Jelajahi Sumber

Implemented canOperate for toPath

Taichi Masuyama 4 tahun lalu
induk
melakukan
f17eabfd70

+ 20 - 3
packages/app/src/server/models/page-operation.ts

@@ -1,5 +1,5 @@
 import mongoose, {
-  Schema, Model, Document,
+  Schema, Model, Document, QueryOptions, FilterQuery,
 } from 'mongoose';
 import { getOrCreateModel } from '@growi/core';
 
@@ -43,8 +43,11 @@ export interface IPageOperation {
 
 export interface PageOperationDocument extends IPageOperation, Document {}
 
+export type PageOperationDocumentHasId = PageOperationDocument & { _id: ObjectIdLike };
+
 export interface PageOperationModel extends Model<PageOperationDocument> {
-  [x:string]: any // TODO: improve type
+  findByIdAndUpdatePageActionStage(pageOpId: ObjectIdLike, stage: PageActionStage): Promise<PageOperationDocumentHasId | null>
+  findMainOps(filter?: FilterQuery<PageOperationDocument>, projection?: any, options?: QueryOptions): Promise<PageOperationDocumentHasId[]>
 }
 
 const pageSchemaForResuming = new Schema<IPageForResuming>({
@@ -92,10 +95,24 @@ const schema = new Schema<PageOperationDocument, PageOperationModel>({
   incForUpdatingDescendantCount: { type: Number },
 });
 
-schema.statics.findByIdAndUpdatePageActionStage = async function(pageOpId: ObjectIdLike, stage: PageActionStage) {
+schema.statics.findByIdAndUpdatePageActionStage = async function(
+    pageOpId: ObjectIdLike, stage: PageActionStage,
+): Promise<PageOperationDocumentHasId | null> {
+
   return this.findByIdAndUpdate(pageOpId, {
     $set: { actionStage: stage },
   }, { new: true });
 };
 
+schema.statics.findMainOps = async function(
+    filter?: FilterQuery<PageOperationDocument>, projection?: any, options?: QueryOptions,
+): Promise<PageOperationDocumentHasId[]> {
+
+  return this.find(
+    { ...filter, actionStage: PageActionStage.Main },
+    projection,
+    options,
+  );
+};
+
 export default getOrCreateModel<PageOperationDocument, PageOperationModel>('PageOperation', schema);

+ 38 - 3
packages/app/src/server/service/page-operation.ts

@@ -1,17 +1,52 @@
+import { pagePathUtils } from '@growi/core';
+
+import PageOperation, { PageActionType } from '~/server/models/page-operation';
+
+const { isTrashPage } = pagePathUtils;
+
 class PageOperationService {
 
   crowi: any;
 
   constructor(crowi) {
     this.crowi = crowi;
+
+    // TODO: Remove this code when resuming feature is implemented
+    PageOperation.deleteMany();
   }
 
   /**
-   * Check if the operation is processable by comparing the "path" with all PageOperation documents
-   * @param path path to operate
+   * Check if the operation is operatable by comparing paths with all PageOperation documents
+   * @param fromPath The path to operate from
+   * @param toPath The path to operate to
+   * @param actionType The action type of the operation
    * @returns Promise<boolean>
    */
-  async shouldBlock(path: string): Promise<boolean> {
+  async canOperate(fromPath: string, toPath: string, actionType: PageActionType): Promise<boolean> {
+    // 1. Block when toPath is already reserved by other toPath
+    // 2. Block when toPath is already reserved by fromPath of "Delete" or "DeleteCompletely"
+    if (toPath != null) {
+      const isPageOpsExist = await PageOperation.exists({ toPath });
+      const isToPathTrash = isTrashPage(toPath);
+      const isForbidden1 = isPageOpsExist && !isToPathTrash;
+
+      if (isForbidden1) {
+        return false;
+      }
+
+      const pageOps = await PageOperation.findMainOps({ actionType: { $in: [PageActionType.Delete, PageActionType.DeleteCompletely] } });
+      const fromPathsToBlock = pageOps.map(po => po.fromPath);
+      const isForbidden2 = fromPathsToBlock.includes(toPath);
+
+      if (isForbidden2) {
+        return false;
+      }
+    }
+
+    if (fromPath != null) {
+      return true;
+    }
+
     return true;
   }