page-operation.ts 6.1 KB

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