page-operation.ts 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. import type { IGrantedGroup } from '@growi/core';
  2. import { GroupType } from '@growi/core';
  3. import { addSeconds } from 'date-fns/addSeconds';
  4. import type {
  5. Model, Document, QueryOptions, FilterQuery,
  6. } from 'mongoose';
  7. import mongoose, {
  8. Schema,
  9. } from 'mongoose';
  10. import type { IOptionsForCreate, IOptionsForUpdate } from '~/interfaces/page';
  11. import { PageActionType, PageActionStage } from '~/interfaces/page-operation';
  12. import loggerFactory from '../../utils/logger';
  13. import type { ObjectIdLike } from '../interfaces/mongoose-utils';
  14. import { getOrCreateModel } from '../util/mongoose-utils';
  15. const TIME_TO_ADD_SEC = 10;
  16. const logger = loggerFactory('growi:models:page-operation');
  17. const ObjectId = mongoose.Schema.Types.ObjectId;
  18. type IPageForResuming = {
  19. _id: ObjectIdLike,
  20. path: string,
  21. isEmpty: boolean,
  22. parent?: ObjectIdLike,
  23. grant?: number,
  24. grantedUsers?: ObjectIdLike[],
  25. grantedGroups: IGrantedGroup[],
  26. descendantCount: number,
  27. status?: number,
  28. revision?: ObjectIdLike,
  29. lastUpdateUser?: ObjectIdLike,
  30. creator?: ObjectIdLike,
  31. };
  32. type IUserForResuming = {
  33. _id: ObjectIdLike,
  34. };
  35. type IOptionsForResuming = {
  36. format: 'md' | 'pdf',
  37. updateMetadata?: boolean,
  38. createRedirectPage?: boolean,
  39. prevDescendantCount?: number,
  40. } & IOptionsForUpdate & IOptionsForCreate;
  41. /*
  42. * Main Schema
  43. */
  44. export interface IPageOperation {
  45. actionType: PageActionType,
  46. actionStage: PageActionStage,
  47. fromPath: string,
  48. toPath?: string,
  49. page: IPageForResuming,
  50. user: IUserForResuming,
  51. options?: IOptionsForResuming,
  52. incForUpdatingDescendantCount?: number,
  53. unprocessableExpiryDate: Date,
  54. exPage?: IPageForResuming,
  55. isProcessable(): boolean
  56. }
  57. export interface PageOperationDocument extends IPageOperation, Document {}
  58. export type PageOperationDocumentHasId = PageOperationDocument & { _id: ObjectIdLike };
  59. export interface PageOperationModel extends Model<PageOperationDocument> {
  60. findByIdAndUpdatePageActionStage(pageOpId: ObjectIdLike, stage: PageActionStage): Promise<PageOperationDocumentHasId | null>
  61. findMainOps(filter?: FilterQuery<PageOperationDocument>, projection?: any, options?: QueryOptions): Promise<PageOperationDocumentHasId[]>
  62. deleteByActionTypes(deleteTypeList: PageActionType[]): Promise<void>
  63. extendExpiryDate(operationId: ObjectIdLike): Promise<void>
  64. }
  65. const pageSchemaForResuming = new Schema<IPageForResuming>({
  66. _id: { type: ObjectId, ref: 'Page', index: true },
  67. parent: { type: ObjectId, ref: 'Page' },
  68. descendantCount: { type: Number },
  69. isEmpty: { type: Boolean },
  70. path: { type: String, required: true, index: true },
  71. revision: { type: ObjectId, ref: 'Revision' },
  72. status: { type: String },
  73. grant: { type: Number },
  74. grantedUsers: [{ type: ObjectId, ref: 'User' }],
  75. grantedGroups: [{
  76. type: {
  77. type: String,
  78. enum: Object.values(GroupType),
  79. required: true,
  80. default: 'UserGroup',
  81. },
  82. item: {
  83. type: ObjectId, refPath: 'grantedGroups.type', required: true,
  84. },
  85. }],
  86. creator: { type: ObjectId, ref: 'User' },
  87. lastUpdateUser: { type: ObjectId, ref: 'User' },
  88. });
  89. const userSchemaForResuming = new Schema<IUserForResuming>({
  90. _id: { type: ObjectId, ref: 'User', required: true },
  91. });
  92. const optionsSchemaForResuming = new Schema<IOptionsForResuming>({
  93. createRedirectPage: { type: Boolean },
  94. updateMetadata: { type: Boolean },
  95. prevDescendantCount: { type: Number },
  96. grant: { type: Number },
  97. grantUserGroupIds: [{
  98. type: {
  99. type: String,
  100. enum: Object.values(GroupType),
  101. required: true,
  102. default: 'UserGroup',
  103. },
  104. item: {
  105. type: ObjectId, refPath: 'grantedGroups.type', required: true,
  106. },
  107. }],
  108. format: { type: String },
  109. overwriteScopesOfDescendants: { type: Boolean },
  110. }, { _id: false });
  111. const schema = new Schema<PageOperationDocument, PageOperationModel>({
  112. actionType: {
  113. type: String,
  114. enum: PageActionType,
  115. required: true,
  116. index: true,
  117. },
  118. actionStage: {
  119. type: String,
  120. enum: PageActionStage,
  121. required: true,
  122. index: true,
  123. },
  124. fromPath: { type: String, required: true, index: true },
  125. toPath: { type: String, index: true },
  126. page: { type: pageSchemaForResuming, required: true },
  127. exPage: { type: pageSchemaForResuming, required: false },
  128. user: { type: userSchemaForResuming, required: true },
  129. options: { type: optionsSchemaForResuming },
  130. incForUpdatingDescendantCount: { type: Number },
  131. unprocessableExpiryDate: { type: Date, default: () => addSeconds(new Date(), 10) },
  132. });
  133. schema.statics.findByIdAndUpdatePageActionStage = async function(
  134. pageOpId: ObjectIdLike, stage: PageActionStage,
  135. ): Promise<PageOperationDocumentHasId | null> {
  136. return this.findByIdAndUpdate(pageOpId, {
  137. $set: { actionStage: stage },
  138. }, { new: true });
  139. };
  140. schema.statics.findMainOps = async function(
  141. filter?: FilterQuery<PageOperationDocument>, projection?: any, options?: QueryOptions,
  142. ): Promise<PageOperationDocumentHasId[]> {
  143. return this.find(
  144. { ...filter, actionStage: PageActionStage.Main },
  145. projection,
  146. options,
  147. );
  148. };
  149. schema.statics.deleteByActionTypes = async function(
  150. actionTypes: PageActionType[],
  151. ): Promise<void> {
  152. await this.deleteMany({ actionType: { $in: actionTypes } });
  153. logger.info(`Deleted all PageOperation documents with actionType: [${actionTypes}]`);
  154. };
  155. /**
  156. * add TIME_TO_ADD_SEC to current time and update unprocessableExpiryDate with it
  157. */
  158. schema.statics.extendExpiryDate = async function(operationId: ObjectIdLike): Promise<void> {
  159. const date = addSeconds(new Date(), TIME_TO_ADD_SEC);
  160. await this.findByIdAndUpdate(operationId, { unprocessableExpiryDate: date });
  161. };
  162. schema.methods.isProcessable = function(): boolean {
  163. const { unprocessableExpiryDate } = this;
  164. return unprocessableExpiryDate == null || (unprocessableExpiryDate != null && new Date() > unprocessableExpiryDate);
  165. };
  166. export default getOrCreateModel<PageOperationDocument, PageOperationModel>('PageOperation', schema);