|
@@ -5,28 +5,29 @@ import streamToPromise from 'stream-to-promise';
|
|
|
import pathlib from 'path';
|
|
import pathlib from 'path';
|
|
|
import { Readable, Writable } from 'stream';
|
|
import { Readable, Writable } from 'stream';
|
|
|
|
|
|
|
|
-import { serializePageSecurely } from '../models/serializers/page-serializer';
|
|
|
|
|
|
|
+import { HasObjectId } from '~/interfaces/has-object-id';
|
|
|
|
|
+import { Ref } from '~/interfaces/common';
|
|
|
import { createBatchStream } from '~/server/util/batch-stream';
|
|
import { createBatchStream } from '~/server/util/batch-stream';
|
|
|
import loggerFactory from '~/utils/logger';
|
|
import loggerFactory from '~/utils/logger';
|
|
|
import {
|
|
import {
|
|
|
CreateMethod, generateGrantCondition, PageCreateOptions, PageDocument, PageModel,
|
|
CreateMethod, generateGrantCondition, PageCreateOptions, PageDocument, PageModel,
|
|
|
} from '~/server/models/page';
|
|
} from '~/server/models/page';
|
|
|
import { stringifySnapshot } from '~/models/serializers/in-app-notification-snapshot/page';
|
|
import { stringifySnapshot } from '~/models/serializers/in-app-notification-snapshot/page';
|
|
|
-import ActivityDefine from '../util/activityDefine';
|
|
|
|
|
import {
|
|
import {
|
|
|
- IPage, IPageInfo, IPageInfoForEntity,
|
|
|
|
|
|
|
+ IPage, IPageInfo, IPageInfoForEntity, IPageWithMeta,
|
|
|
} from '~/interfaces/page';
|
|
} from '~/interfaces/page';
|
|
|
|
|
+import { serializePageSecurely } from '../models/serializers/page-serializer';
|
|
|
import { PageRedirectModel } from '../models/page-redirect';
|
|
import { PageRedirectModel } from '../models/page-redirect';
|
|
|
|
|
+import Subscription from '../models/subscription';
|
|
|
import { ObjectIdLike } from '../interfaces/mongoose-utils';
|
|
import { ObjectIdLike } from '../interfaces/mongoose-utils';
|
|
|
import { IUserHasId } from '~/interfaces/user';
|
|
import { IUserHasId } from '~/interfaces/user';
|
|
|
-import { Ref } from '~/interfaces/common';
|
|
|
|
|
-import { HasObjectId } from '~/interfaces/has-object-id';
|
|
|
|
|
|
|
+import ActivityDefine from '../util/activityDefine';
|
|
|
|
|
|
|
|
const debug = require('debug')('growi:services:page');
|
|
const debug = require('debug')('growi:services:page');
|
|
|
|
|
|
|
|
const logger = loggerFactory('growi:services:page');
|
|
const logger = loggerFactory('growi:services:page');
|
|
|
const {
|
|
const {
|
|
|
- isCreatablePage, isTrashPage, isTopPage, isDeletablePage, omitDuplicateAreaPathFromPaths, omitDuplicateAreaPageFromPages, isUserPage, isUserNamePage,
|
|
|
|
|
|
|
+ isTrashPage, isTopPage, omitDuplicateAreaPageFromPages, isMovablePage,
|
|
|
} = pagePathUtils;
|
|
} = pagePathUtils;
|
|
|
|
|
|
|
|
const BULK_REINDEX_SIZE = 100;
|
|
const BULK_REINDEX_SIZE = 100;
|
|
@@ -213,41 +214,70 @@ class PageService {
|
|
|
return pages.filter(p => p.isEmpty || this.canDeleteCompletely(p.creator, user));
|
|
return pages.filter(p => p.isEmpty || this.canDeleteCompletely(p.creator, user));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- async findPageAndMetaDataByViewer({ pageId, path, user }) {
|
|
|
|
|
|
|
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
|
|
|
+ async findPageAndMetaDataByViewer(pageId: string, path: string, user: IUserHasId, isSharedPage = false): Promise<IPageWithMeta|null> {
|
|
|
|
|
|
|
|
const Page = this.crowi.model('Page');
|
|
const Page = this.crowi.model('Page');
|
|
|
|
|
|
|
|
- let pagePath = path;
|
|
|
|
|
-
|
|
|
|
|
- let page;
|
|
|
|
|
|
|
+ let page: PageModel & PageDocument & HasObjectId;
|
|
|
if (pageId != null) { // prioritized
|
|
if (pageId != null) { // prioritized
|
|
|
page = await Page.findByIdAndViewer(pageId, user);
|
|
page = await Page.findByIdAndViewer(pageId, user);
|
|
|
- pagePath = page.path;
|
|
|
|
|
}
|
|
}
|
|
|
else {
|
|
else {
|
|
|
- page = await Page.findByPathAndViewer(pagePath, user);
|
|
|
|
|
|
|
+ page = await Page.findByPathAndViewer(path, user);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const result: any = {};
|
|
|
|
|
-
|
|
|
|
|
if (page == null) {
|
|
if (page == null) {
|
|
|
- const isExist = await Page.count({ $or: [{ _id: pageId }, { pat: pagePath }] }) > 0;
|
|
|
|
|
- result.isForbidden = isExist;
|
|
|
|
|
- result.isNotFound = !isExist;
|
|
|
|
|
- result.isCreatable = isCreatablePage(pagePath);
|
|
|
|
|
- result.page = page;
|
|
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- return result;
|
|
|
|
|
|
|
+ if (isSharedPage) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ pageData: page,
|
|
|
|
|
+ pageMeta: {
|
|
|
|
|
+ isEmpty: page.isEmpty,
|
|
|
|
|
+ isMovable: false,
|
|
|
|
|
+ isDeletable: false,
|
|
|
|
|
+ isAbleToDeleteCompletely: false,
|
|
|
|
|
+ isRevertible: false,
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- result.page = page;
|
|
|
|
|
- result.isForbidden = false;
|
|
|
|
|
- result.isNotFound = false;
|
|
|
|
|
- result.isCreatable = false;
|
|
|
|
|
- result.isDeletable = isDeletablePage(pagePath);
|
|
|
|
|
- result.isDeleted = page.isDeleted();
|
|
|
|
|
|
|
+ const isGuestUser = user == null;
|
|
|
|
|
+ const pageInfo = this.constructBasicPageInfo(page, isGuestUser);
|
|
|
|
|
|
|
|
- return result;
|
|
|
|
|
|
|
+ const Bookmark = this.crowi.model('Bookmark');
|
|
|
|
|
+ const bookmarkCount = await Bookmark.countByPageId(pageId);
|
|
|
|
|
+
|
|
|
|
|
+ const metadataForGuest = {
|
|
|
|
|
+ ...pageInfo,
|
|
|
|
|
+ bookmarkCount,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ if (isGuestUser) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ pageData: page,
|
|
|
|
|
+ pageMeta: metadataForGuest,
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const isBookmarked = await Bookmark.findByPageIdAndUserId(pageId, user._id);
|
|
|
|
|
+ const isLiked = page.isLiked(user);
|
|
|
|
|
+ const isAbleToDeleteCompletely = this.canDeleteCompletely((page.creator as IUserHasId)?._id, user);
|
|
|
|
|
+
|
|
|
|
|
+ const subscription = await Subscription.findByUserIdAndTargetId(user._id, pageId);
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ pageData: page,
|
|
|
|
|
+ pageMeta: {
|
|
|
|
|
+ ...metadataForGuest,
|
|
|
|
|
+ isAbleToDeleteCompletely,
|
|
|
|
|
+ isBookmarked,
|
|
|
|
|
+ isLiked,
|
|
|
|
|
+ subscriptionStatus: subscription?.status,
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private shouldUseV4Process(page): boolean {
|
|
private shouldUseV4Process(page): boolean {
|
|
@@ -1067,7 +1097,7 @@ class PageService {
|
|
|
throw new Error('This method does NOT support deleting trashed pages.');
|
|
throw new Error('This method does NOT support deleting trashed pages.');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (!Page.isDeletableName(page.path)) {
|
|
|
|
|
|
|
+ if (!isMovablePage(page.path)) {
|
|
|
throw new Error('Page is not deletable.');
|
|
throw new Error('Page is not deletable.');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1096,7 +1126,14 @@ class PageService {
|
|
|
}, { new: true });
|
|
}, { new: true });
|
|
|
await PageTagRelation.updateMany({ relatedPage: page._id }, { $set: { isPageTrashed: true } });
|
|
await PageTagRelation.updateMany({ relatedPage: page._id }, { $set: { isPageTrashed: true } });
|
|
|
|
|
|
|
|
- await PageRedirect.create({ fromPath: page.path, toPath: newPath });
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ await PageRedirect.create({ fromPath: page.path, toPath: newPath });
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (err) {
|
|
|
|
|
+ if (err.code !== 11000) {
|
|
|
|
|
+ throw err;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
this.pageEvent.emit('delete', page, user);
|
|
this.pageEvent.emit('delete', page, user);
|
|
|
this.pageEvent.emit('create', deletedPage, user);
|
|
this.pageEvent.emit('create', deletedPage, user);
|
|
@@ -1136,7 +1173,7 @@ class PageService {
|
|
|
throw new Error('This method does NOT support deleting trashed pages.');
|
|
throw new Error('This method does NOT support deleting trashed pages.');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (!Page.isDeletableName(page.path)) {
|
|
|
|
|
|
|
+ if (!isMovablePage(page.path)) {
|
|
|
throw new Error('Page is not deletable.');
|
|
throw new Error('Page is not deletable.');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1153,7 +1190,14 @@ class PageService {
|
|
|
}, { new: true });
|
|
}, { new: true });
|
|
|
await PageTagRelation.updateMany({ relatedPage: page._id }, { $set: { isPageTrashed: true } });
|
|
await PageTagRelation.updateMany({ relatedPage: page._id }, { $set: { isPageTrashed: true } });
|
|
|
|
|
|
|
|
- await PageRedirect.create({ fromPath: page.path, toPath: newPath });
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ await PageRedirect.create({ fromPath: page.path, toPath: newPath });
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (err) {
|
|
|
|
|
+ if (err.code !== 11000) {
|
|
|
|
|
+ throw err;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
this.pageEvent.emit('delete', page, user);
|
|
this.pageEvent.emit('delete', page, user);
|
|
|
this.pageEvent.emit('create', deletedPage, user);
|
|
this.pageEvent.emit('create', deletedPage, user);
|
|
@@ -1704,7 +1748,7 @@ class PageService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
constructBasicPageInfo(page: IPage, isGuestUser?: boolean): IPageInfo | IPageInfoForEntity {
|
|
constructBasicPageInfo(page: IPage, isGuestUser?: boolean): IPageInfo | IPageInfoForEntity {
|
|
|
- const isMovable = isGuestUser ? false : !isTopPage(page.path) && !isUserPage(page.path) && !isUserNamePage(page.path);
|
|
|
|
|
|
|
+ const isMovable = isGuestUser ? false : isMovablePage(page.path);
|
|
|
|
|
|
|
|
if (page.isEmpty) {
|
|
if (page.isEmpty) {
|
|
|
return {
|
|
return {
|
|
@@ -1719,8 +1763,6 @@ class PageService {
|
|
|
const likers = page.liker.slice(0, 15) as Ref<IUserHasId>[];
|
|
const likers = page.liker.slice(0, 15) as Ref<IUserHasId>[];
|
|
|
const seenUsers = page.seenUsers.slice(0, 15) as Ref<IUserHasId>[];
|
|
const seenUsers = page.seenUsers.slice(0, 15) as Ref<IUserHasId>[];
|
|
|
|
|
|
|
|
- const Page = this.crowi.model('Page');
|
|
|
|
|
- const isRevertible = isTrashPage(page.path);
|
|
|
|
|
return {
|
|
return {
|
|
|
isEmpty: false,
|
|
isEmpty: false,
|
|
|
sumOfLikers: page.liker.length,
|
|
sumOfLikers: page.liker.length,
|
|
@@ -1728,9 +1770,9 @@ class PageService {
|
|
|
seenUserIds: this.extractStringIds(seenUsers),
|
|
seenUserIds: this.extractStringIds(seenUsers),
|
|
|
sumOfSeenUsers: page.seenUsers.length,
|
|
sumOfSeenUsers: page.seenUsers.length,
|
|
|
isMovable,
|
|
isMovable,
|
|
|
- isDeletable: Page.isDeletableName(page.path),
|
|
|
|
|
|
|
+ isDeletable: isMovable,
|
|
|
isAbleToDeleteCompletely: false,
|
|
isAbleToDeleteCompletely: false,
|
|
|
- isRevertible,
|
|
|
|
|
|
|
+ isRevertible: isTrashPage(page.path),
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
}
|
|
}
|