2
0

page-operation.ts 5.0 KB

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