page.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. const mongoose = require('mongoose');
  2. const escapeStringRegexp = require('escape-string-regexp');
  3. const logger = require('@alias/logger')('growi:models:page');
  4. const debug = require('debug')('growi:models:page');
  5. const { serializePageSecurely } = require('../models/serializers/page-serializer');
  6. const STATUS_PUBLISHED = 'published';
  7. class PageService {
  8. constructor(crowi) {
  9. this.crowi = crowi;
  10. }
  11. async removeAllAttachments(pageId) {
  12. const Attachment = this.crowi.model('Attachment');
  13. const { attachmentService } = this.crowi;
  14. const attachments = await Attachment.find({ page: pageId });
  15. const promises = attachments.map(async(attachment) => {
  16. return attachmentService.removeAttachment(attachment._id);
  17. });
  18. return Promise.all(promises);
  19. }
  20. async duplicate(page, newPagePath, user) {
  21. const Page = this.crowi.model('Page');
  22. const PageTagRelation = mongoose.model('PageTagRelation');
  23. // populate
  24. await page.populate({ path: 'revision', model: 'Revision', select: 'body' }).execPopulate();
  25. // create option
  26. const options = { page };
  27. options.grant = page.grant;
  28. options.grantUserGroupId = page.grantedGroup;
  29. options.grantedUsers = page.grantedUsers;
  30. const createdPage = await Page.create(
  31. newPagePath, page.revision.body, user, options,
  32. );
  33. // take over tags
  34. const originTags = await page.findRelatedTagsById();
  35. let savedTags = [];
  36. if (originTags != null) {
  37. await PageTagRelation.updatePageTags(createdPage.id, originTags);
  38. savedTags = await PageTagRelation.listTagNamesByPage(createdPage.id);
  39. }
  40. const result = serializePageSecurely(createdPage);
  41. result.tags = savedTags;
  42. return result;
  43. }
  44. async duplicateRecursively(page, newPagePath, user) {
  45. const Page = this.crowi.model('Page');
  46. const newPagePathPrefix = newPagePath;
  47. const pathRegExp = new RegExp(`^${escapeStringRegexp(page.path)}`, 'i');
  48. const pages = await Page.findManageableListWithDescendants(page, user);
  49. const promise = pages.map(async(page) => {
  50. const newPagePath = page.path.replace(pathRegExp, newPagePathPrefix);
  51. return this.duplicate(page, newPagePath, user);
  52. });
  53. const newPath = page.path.replace(pathRegExp, newPagePathPrefix);
  54. await Promise.allSettled(promise);
  55. const newParentpage = await Page.findByPath(newPath);
  56. // TODO GW-4634 use stream
  57. return newParentpage;
  58. }
  59. /**
  60. * Delete Bookmarks, Attachments, Revisions, Pages and emit delete
  61. */
  62. async completelyDeletePageRecursively(targetPage, user, options = {}) {
  63. const findOpts = { includeTrashed: true };
  64. const Page = this.crowi.model('Page');
  65. // find manageable descendants (this array does not include GRANT_RESTRICTED)
  66. const pages = await Page.findManageableListWithDescendants(targetPage, user, findOpts);
  67. // ここをストリーム化したい
  68. // page ではなく pages として渡したい
  69. // await Promise.all(pages.map((page) => {
  70. // return this.completelyDeletePage(page, user, options);
  71. // }));
  72. return this.completelyDeletePage(pages, user, options);
  73. }
  74. // pages をうけとれるように改修したい
  75. async completelyDeletePage(pages, user, options = {}) {
  76. // this.validateCrowi();
  77. let pageEvent;
  78. // init event
  79. if (this.crowi != null) {
  80. pageEvent = this.crowi.event('page');
  81. pageEvent.on('create', pageEvent.onCreate);
  82. pageEvent.on('update', pageEvent.onUpdate);
  83. }
  84. const ids = pages.map(page => (page._id));
  85. // ids.join('');
  86. const paths = pages.map(page => (page.path));
  87. // const { _ids, path } = pageData;
  88. const socketClientId = options.socketClientId || null;
  89. logger.debug('Deleting completely', paths);
  90. await this.deleteCompletely(ids, paths);
  91. if (socketClientId != null) {
  92. pageEvent.emit('delete', pages, user, socketClientId); // update as renamed page
  93. }
  94. return pages;
  95. }
  96. // pageIds を受け取れるように改修したい
  97. async deleteCompletely(pageIds, pagePaths) {
  98. // Delete Bookmarks, Attachments, Revisions, Pages and emit delete
  99. const Bookmark = this.crowi.model('Bookmark');
  100. const Comment = this.crowi.model('Comment');
  101. // const Page = this.crowi.model('Page');
  102. const PageTagRelation = this.crowi.model('PageTagRelation');
  103. // const ShareLink = this.crowi.model('ShareLink');
  104. // const Revision = this.crowi.model('Revision');
  105. console.log(pageIds);
  106. // console.log(PageTagRelation.find({ pageIds }));
  107. // return PageTagRelation.find({ relatedPage: { $in: pageIds } }).remove({});
  108. // return await Bookmark.find({ page: { $in: pageIds } }).remove({});
  109. return await Comment.find({ page: { $in: pageIds } }).remove({});
  110. // return Promise.all([
  111. // Bookmark.removeBookmarksByPageId(pageIds),
  112. // Comment.removeCommentsByPageId(pageIds),
  113. // PageTagRelation.remove({ relatedPage: pageIds }),
  114. // ShareLink.remove({ relatedPage: pageIds }),
  115. // Revision.removeRevisionsByPath(pagePaths),
  116. // Page.findByIdAndRemove(pageIds),
  117. // Page.removeRedirectOriginPageByPath(pagePaths),
  118. // this.removeAllAttachments(pageIds),
  119. // ]);
  120. }
  121. async revertDeletedPage(page, user, options = {}) {
  122. const Page = this.crowi.model('Page');
  123. const newPath = Page.getRevertDeletedPageName(page.path);
  124. const originPage = await Page.findByPath(newPath);
  125. if (originPage != null) {
  126. // When the page is deleted, it will always be created with "redirectTo" in the path of the original page.
  127. // So, it's ok to delete the page
  128. // However, If a page exists that is not "redirectTo", something is wrong. (Data correction is needed).
  129. if (originPage.redirectTo !== page.path) {
  130. throw new Error('The new page of to revert is exists and the redirect path of the page is not the deleted page.');
  131. }
  132. await this.completelyDeletePage(originPage, options);
  133. }
  134. page.status = STATUS_PUBLISHED;
  135. page.lastUpdateUser = user;
  136. debug('Revert deleted the page', page, newPath);
  137. const updatedPage = await Page.rename(page, newPath, user, {});
  138. return updatedPage;
  139. }
  140. async handlePrivatePagesForDeletedGroup(deletedGroup, action, transferToUserGroupId) {
  141. const Page = this.crowi.model('Page');
  142. const pages = await Page.find({ grantedGroup: deletedGroup });
  143. switch (action) {
  144. case 'public':
  145. await Promise.all(pages.map((page) => {
  146. return Page.publicizePage(page);
  147. }));
  148. break;
  149. case 'delete':
  150. await Promise.all(pages.map((page) => {
  151. return this.completelyDeletePage(page);
  152. }));
  153. break;
  154. case 'transfer':
  155. await Promise.all(pages.map((page) => {
  156. return Page.transferPageToGroup(page, transferToUserGroupId);
  157. }));
  158. break;
  159. default:
  160. throw new Error('Unknown action for private pages');
  161. }
  162. }
  163. validateCrowi() {
  164. if (this.crowi == null) {
  165. throw new Error('"crowi" is null. Init User model with "crowi" argument first.');
  166. }
  167. }
  168. }
  169. module.exports = PageService;