|
@@ -1,33 +1,36 @@
|
|
|
|
|
+import pathlib from 'path';
|
|
|
|
|
+import { Readable, Writable } from 'stream';
|
|
|
|
|
+
|
|
|
import { pagePathUtils, pathUtils } from '@growi/core';
|
|
import { pagePathUtils, pathUtils } from '@growi/core';
|
|
|
-import mongoose, { ObjectId, QueryCursor } from 'mongoose';
|
|
|
|
|
import escapeStringRegexp from 'escape-string-regexp';
|
|
import escapeStringRegexp from 'escape-string-regexp';
|
|
|
|
|
+import mongoose, { ObjectId, QueryCursor } from 'mongoose';
|
|
|
import streamToPromise from 'stream-to-promise';
|
|
import streamToPromise from 'stream-to-promise';
|
|
|
-import pathlib from 'path';
|
|
|
|
|
-import { Readable, Writable } from 'stream';
|
|
|
|
|
|
|
|
|
|
-import { createBatchStream } from '~/server/util/batch-stream';
|
|
|
|
|
-import loggerFactory from '~/utils/logger';
|
|
|
|
|
-import {
|
|
|
|
|
- CreateMethod, PageCreateOptions, PageModel, PageDocument,
|
|
|
|
|
-} from '~/server/models/page';
|
|
|
|
|
-import { stringifySnapshot } from '~/models/serializers/in-app-notification-snapshot/page';
|
|
|
|
|
|
|
+import { Ref } from '~/interfaces/common';
|
|
|
|
|
+import { HasObjectId } from '~/interfaces/has-object-id';
|
|
|
import {
|
|
import {
|
|
|
IPage, IPageInfo, IPageInfoForEntity, IPageWithMeta,
|
|
IPage, IPageInfo, IPageInfoForEntity, IPageWithMeta,
|
|
|
} from '~/interfaces/page';
|
|
} from '~/interfaces/page';
|
|
|
-import { serializePageSecurely } from '../models/serializers/page-serializer';
|
|
|
|
|
-import { PageRedirectModel } from '../models/page-redirect';
|
|
|
|
|
-import Subscription from '../models/subscription';
|
|
|
|
|
-import { ObjectIdLike } from '../interfaces/mongoose-utils';
|
|
|
|
|
-import { IUserHasId } from '~/interfaces/user';
|
|
|
|
|
-import { Ref } from '~/interfaces/common';
|
|
|
|
|
-import { HasObjectId } from '~/interfaces/has-object-id';
|
|
|
|
|
-import { SocketEventName, UpdateDescCountRawData } from '~/interfaces/websocket';
|
|
|
|
|
import {
|
|
import {
|
|
|
PageDeleteConfigValue, IPageDeleteConfigValueToProcessValidation,
|
|
PageDeleteConfigValue, IPageDeleteConfigValueToProcessValidation,
|
|
|
} from '~/interfaces/page-delete-config';
|
|
} from '~/interfaces/page-delete-config';
|
|
|
|
|
+import { IUserHasId } from '~/interfaces/user';
|
|
|
|
|
+import { SocketEventName, UpdateDescCountRawData } from '~/interfaces/websocket';
|
|
|
|
|
+import { stringifySnapshot } from '~/models/serializers/in-app-notification-snapshot/page';
|
|
|
|
|
+import {
|
|
|
|
|
+ CreateMethod, PageCreateOptions, PageModel, PageDocument,
|
|
|
|
|
+} from '~/server/models/page';
|
|
|
|
|
+import { createBatchStream } from '~/server/util/batch-stream';
|
|
|
|
|
+import loggerFactory from '~/utils/logger';
|
|
|
|
|
+import { prepareDeleteConfigValuesForCalc } from '~/utils/page-delete-config';
|
|
|
|
|
+
|
|
|
|
|
+import { ObjectIdLike } from '../interfaces/mongoose-utils';
|
|
|
|
|
+import { PathAlreadyExistsError } from '../models/errors';
|
|
|
import PageOperation, { PageActionStage, PageActionType } from '../models/page-operation';
|
|
import PageOperation, { PageActionStage, PageActionType } from '../models/page-operation';
|
|
|
|
|
+import { PageRedirectModel } from '../models/page-redirect';
|
|
|
|
|
+import { serializePageSecurely } from '../models/serializers/page-serializer';
|
|
|
|
|
+import Subscription from '../models/subscription';
|
|
|
import ActivityDefine from '../util/activityDefine';
|
|
import ActivityDefine from '../util/activityDefine';
|
|
|
-import { prepareDeleteConfigValuesForCalc } from '~/utils/page-delete-config';
|
|
|
|
|
|
|
|
|
|
const debug = require('debug')('growi:services:page');
|
|
const debug = require('debug')('growi:services:page');
|
|
|
|
|
|
|
@@ -171,6 +174,16 @@ class PageService {
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+ // duplicate
|
|
|
|
|
+ this.pageEvent.on('duplicate', async(page, user) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await this.createAndSendNotifications(page, user, ActivityDefine.ACTION_PAGE_DUPLICATE);
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (err) {
|
|
|
|
|
+ logger.error(err);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
// delete
|
|
// delete
|
|
|
this.pageEvent.on('delete', async(page, user) => {
|
|
this.pageEvent.on('delete', async(page, user) => {
|
|
|
try {
|
|
try {
|
|
@@ -191,6 +204,16 @@ class PageService {
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+ // revert
|
|
|
|
|
+ this.pageEvent.on('revert', async(page, user) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await this.createAndSendNotifications(page, user, ActivityDefine.ACTION_PAGE_REVERT);
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (err) {
|
|
|
|
|
+ logger.error(err);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
// likes
|
|
// likes
|
|
|
this.pageEvent.on('like', async(page, user) => {
|
|
this.pageEvent.on('like', async(page, user) => {
|
|
|
try {
|
|
try {
|
|
@@ -963,6 +986,7 @@ class PageService {
|
|
|
newPagePath, page.revision.body, user, options,
|
|
newPagePath, page.revision.body, user, options,
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
+ this.pageEvent.emit('duplicate', page, user);
|
|
|
|
|
|
|
|
// 4. Take over tags
|
|
// 4. Take over tags
|
|
|
const originTags = await page.findRelatedTagsById();
|
|
const originTags = await page.findRelatedTagsById();
|
|
@@ -1057,6 +1081,7 @@ class PageService {
|
|
|
const createdPage = await Page.create(
|
|
const createdPage = await Page.create(
|
|
|
newPagePath, page.revision.body, user, options,
|
|
newPagePath, page.revision.body, user, options,
|
|
|
);
|
|
);
|
|
|
|
|
+ this.pageEvent.emit('duplicate', page, user);
|
|
|
|
|
|
|
|
if (isRecursively) {
|
|
if (isRecursively) {
|
|
|
this.duplicateDescendantsWithStream(page, newPagePath, user);
|
|
this.duplicateDescendantsWithStream(page, newPagePath, user);
|
|
@@ -1879,7 +1904,7 @@ class PageService {
|
|
|
|
|
|
|
|
// throw if any page already exists
|
|
// throw if any page already exists
|
|
|
if (originPage != null) {
|
|
if (originPage != null) {
|
|
|
- throw Error(`This page cannot be reverted since a page with path "${originPage.path}" already exists. Rename the existing pages first.`);
|
|
|
|
|
|
|
+ throw new PathAlreadyExistsError('already_exists', originPage.path);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 2. Revert target
|
|
// 2. Revert target
|
|
@@ -1891,6 +1916,8 @@ class PageService {
|
|
|
}, { new: true });
|
|
}, { new: true });
|
|
|
await PageTagRelation.updateMany({ relatedPage: page._id }, { $set: { isPageTrashed: false } });
|
|
await PageTagRelation.updateMany({ relatedPage: page._id }, { $set: { isPageTrashed: false } });
|
|
|
|
|
|
|
|
|
|
+ this.pageEvent.emit('revert', page, user);
|
|
|
|
|
+
|
|
|
if (!isRecursively) {
|
|
if (!isRecursively) {
|
|
|
await this.updateDescendantCountOfAncestors(parent._id, 1, true);
|
|
await this.updateDescendantCountOfAncestors(parent._id, 1, true);
|
|
|
}
|
|
}
|
|
@@ -1973,7 +2000,7 @@ class PageService {
|
|
|
const newPath = Page.getRevertDeletedPageName(page.path);
|
|
const newPath = Page.getRevertDeletedPageName(page.path);
|
|
|
const originPage = await Page.findByPath(newPath);
|
|
const originPage = await Page.findByPath(newPath);
|
|
|
if (originPage != null) {
|
|
if (originPage != null) {
|
|
|
- throw Error(`This page cannot be reverted since a page with path "${originPage.path}" already exists.`);
|
|
|
|
|
|
|
+ throw new PathAlreadyExistsError('already_exists', originPage.path);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (isRecursively) {
|
|
if (isRecursively) {
|
|
@@ -1990,6 +2017,8 @@ class PageService {
|
|
|
}, { new: true });
|
|
}, { new: true });
|
|
|
await PageTagRelation.updateMany({ relatedPage: page._id }, { $set: { isPageTrashed: false } });
|
|
await PageTagRelation.updateMany({ relatedPage: page._id }, { $set: { isPageTrashed: false } });
|
|
|
|
|
|
|
|
|
|
+ this.pageEvent.emit('revert', page, user);
|
|
|
|
|
+
|
|
|
return updatedPage;
|
|
return updatedPage;
|
|
|
}
|
|
}
|
|
|
|
|
|