|
@@ -29,7 +29,7 @@ import { prepareDeleteConfigValuesForCalc } from '~/utils/page-delete-config';
|
|
|
|
|
|
|
|
import { ObjectIdLike } from '../interfaces/mongoose-utils';
|
|
import { ObjectIdLike } from '../interfaces/mongoose-utils';
|
|
|
import { PathAlreadyExistsError } from '../models/errors';
|
|
import { PathAlreadyExistsError } from '../models/errors';
|
|
|
-import PageOperation, { PageActionStage, PageActionType } from '../models/page-operation';
|
|
|
|
|
|
|
+import PageOperation, { PageActionStage, PageActionType, PageOperationDocument } from '../models/page-operation';
|
|
|
import { PageRedirectModel } from '../models/page-redirect';
|
|
import { PageRedirectModel } from '../models/page-redirect';
|
|
|
import { serializePageSecurely } from '../models/serializers/page-serializer';
|
|
import { serializePageSecurely } from '../models/serializers/page-serializer';
|
|
|
import Subscription from '../models/subscription';
|
|
import Subscription from '../models/subscription';
|
|
@@ -618,11 +618,7 @@ class PageService {
|
|
|
await PageOperation.findByIdAndDelete(pageOpId);
|
|
await PageOperation.findByIdAndDelete(pageOpId);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- async resumeRenameSubOperation(renamedPage: PageDocument): Promise<void> {
|
|
|
|
|
-
|
|
|
|
|
- // findOne PageOperation
|
|
|
|
|
- const filter = { actionType: PageActionType.Rename, actionStage: PageActionStage.Sub, 'page._id': renamedPage._id };
|
|
|
|
|
- const pageOp = await PageOperation.findOne(filter);
|
|
|
|
|
|
|
+ async resumeRenameSubOperation(renamedPage: PageDocument, pageOp: PageOperationDocument): Promise<void> {
|
|
|
if (pageOp == null) {
|
|
if (pageOp == null) {
|
|
|
throw Error('There is nothing to be processed right now');
|
|
throw Error('There is nothing to be processed right now');
|
|
|
}
|
|
}
|
|
@@ -630,18 +626,26 @@ class PageService {
|
|
|
if (!isProcessable) {
|
|
if (!isProcessable) {
|
|
|
throw Error('This page operation is currently being processed');
|
|
throw Error('This page operation is currently being processed');
|
|
|
}
|
|
}
|
|
|
|
|
+ if (pageOp.toPath == null) {
|
|
|
|
|
+ throw Error(`Property toPath is missing which is needed to resume rename operation(${pageOp._id})`);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
const {
|
|
const {
|
|
|
- page, toPath, options, user,
|
|
|
|
|
|
|
+ page, fromPath, toPath, options, user,
|
|
|
} = pageOp;
|
|
} = pageOp;
|
|
|
|
|
|
|
|
- // check property
|
|
|
|
|
- if (toPath == null) {
|
|
|
|
|
- throw Error(`Property toPath is missing which is needed to resume page operation(${pageOp._id})`);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.renameSubOperation(page, toPath, user, options, renamedPage, pageOp._id);
|
|
|
|
|
|
|
+ this.fixPathsAndDescendantCountOfAncestors(page, user, options, renamedPage, pageOp._id, fromPath, toPath);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Renaming paths and fixing descendantCount of ancestors. It shoud be run synchronously.
|
|
|
|
|
+ * `renameSubOperation` to restart rename operation
|
|
|
|
|
+ * `updateDescendantCountOfPagesWithPaths` to fix descendantCount of ancestors
|
|
|
|
|
+ */
|
|
|
|
|
+ private async fixPathsAndDescendantCountOfAncestors(page, user, options, renamedPage, pageOpId, fromPath, toPath): Promise<void> {
|
|
|
|
|
+ await this.renameSubOperation(page, toPath, user, options, renamedPage, pageOpId);
|
|
|
|
|
+ const ancestorsPaths = this.crowi.pageOperationService.getAncestorsPathsByFromAndToPath(fromPath, toPath);
|
|
|
|
|
+ await this.updateDescendantCountOfPagesWithPaths(ancestorsPaths);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private isRenamingToUnderTarget(fromPath: string, toPath: string): boolean {
|
|
private isRenamingToUnderTarget(fromPath: string, toPath: string): boolean {
|
|
@@ -3066,8 +3070,30 @@ class PageService {
|
|
|
builder.addConditionToSortPagesByDescPath();
|
|
builder.addConditionToSortPagesByDescPath();
|
|
|
|
|
|
|
|
const aggregatedPages = await builder.query.lean().cursor({ batchSize: BATCH_SIZE });
|
|
const aggregatedPages = await builder.query.lean().cursor({ batchSize: BATCH_SIZE });
|
|
|
|
|
+ await this.recountAndUpdateDescendantCountOfPages(aggregatedPages, BATCH_SIZE);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * update descendantCount of the pages sequentially from longer path to shorter path
|
|
|
|
|
+ */
|
|
|
|
|
+ async updateDescendantCountOfPagesWithPaths(paths: string[]): Promise<void> {
|
|
|
|
|
+ const BATCH_SIZE = 200;
|
|
|
|
|
+ const Page = this.crowi.model('Page');
|
|
|
|
|
+ const { PageQueryBuilder } = Page;
|
|
|
|
|
+
|
|
|
|
|
+ const builder = new PageQueryBuilder(Page.find(), true);
|
|
|
|
|
+ builder.addConditionToListByPathsArray(paths); // find by paths
|
|
|
|
|
+ builder.addConditionToSortPagesByDescPath(); // sort in DESC
|
|
|
|
|
+
|
|
|
|
|
+ const aggregatedPages = await builder.query.lean().cursor({ batchSize: BATCH_SIZE });
|
|
|
|
|
+ await this.recountAndUpdateDescendantCountOfPages(aggregatedPages, BATCH_SIZE);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Recount descendantCount of pages one by one
|
|
|
|
|
+ */
|
|
|
|
|
+ async recountAndUpdateDescendantCountOfPages(pageCursor: QueryCursor<any>, batchSize:number): Promise<void> {
|
|
|
|
|
+ const Page = this.crowi.model('Page');
|
|
|
const recountWriteStream = new Writable({
|
|
const recountWriteStream = new Writable({
|
|
|
objectMode: true,
|
|
objectMode: true,
|
|
|
async write(pageDocuments, encoding, callback) {
|
|
async write(pageDocuments, encoding, callback) {
|
|
@@ -3081,8 +3107,8 @@ class PageService {
|
|
|
callback();
|
|
callback();
|
|
|
},
|
|
},
|
|
|
});
|
|
});
|
|
|
- aggregatedPages
|
|
|
|
|
- .pipe(createBatchStream(BATCH_SIZE))
|
|
|
|
|
|
|
+ pageCursor
|
|
|
|
|
+ .pipe(createBatchStream(batchSize))
|
|
|
.pipe(recountWriteStream);
|
|
.pipe(recountWriteStream);
|
|
|
|
|
|
|
|
await streamToPromise(recountWriteStream);
|
|
await streamToPromise(recountWriteStream);
|