page-operation.ts 5.3 KB

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