activity.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import type { IUser, Ref } from '@growi/core';
  2. import type { Document, Model, SortOrder, Types } from 'mongoose';
  3. import { Schema } from 'mongoose';
  4. import mongoosePaginate from 'mongoose-paginate-v2';
  5. import type {
  6. IActivity,
  7. ISnapshot,
  8. SupportedActionType,
  9. SupportedEventModelType,
  10. SupportedTargetModelType,
  11. } from '~/interfaces/activity';
  12. import {
  13. AllSupportedActions,
  14. AllSupportedEventModels,
  15. AllSupportedTargetModels,
  16. } from '~/interfaces/activity';
  17. import loggerFactory from '../../utils/logger';
  18. import { getOrCreateModel } from '../util/mongoose-utils';
  19. const logger = loggerFactory('growi:models:activity');
  20. export interface ActivityDocument extends Document {
  21. _id: Types.ObjectId;
  22. user: Ref<IUser>;
  23. ip: string;
  24. endpoint: string;
  25. targetModel: SupportedTargetModelType;
  26. target: Types.ObjectId;
  27. eventModel: SupportedEventModelType;
  28. event: Types.ObjectId;
  29. action: SupportedActionType;
  30. snapshot: ISnapshot;
  31. }
  32. export interface ActivityModel extends Model<ActivityDocument> {
  33. [x: string]: any;
  34. getActionUsersFromActivities(activities: ActivityDocument[]): any[];
  35. }
  36. const snapshotSchema = new Schema<ISnapshot>({
  37. username: { type: String, index: true },
  38. });
  39. // TODO: add revision id
  40. const activitySchema = new Schema<ActivityDocument, ActivityModel>(
  41. {
  42. user: {
  43. type: Schema.Types.ObjectId,
  44. ref: 'User',
  45. index: true,
  46. },
  47. ip: {
  48. type: String,
  49. },
  50. endpoint: {
  51. type: String,
  52. },
  53. targetModel: {
  54. type: String,
  55. enum: AllSupportedTargetModels,
  56. },
  57. target: {
  58. type: Schema.Types.ObjectId,
  59. refPath: 'targetModel',
  60. },
  61. eventModel: {
  62. type: String,
  63. enum: AllSupportedEventModels,
  64. },
  65. event: {
  66. type: Schema.Types.ObjectId,
  67. },
  68. action: {
  69. type: String,
  70. enum: AllSupportedActions,
  71. required: true,
  72. },
  73. snapshot: snapshotSchema,
  74. },
  75. {
  76. timestamps: {
  77. createdAt: true,
  78. updatedAt: false,
  79. },
  80. },
  81. );
  82. // activitySchema.index({ createdAt: 1 }); // Do not create index here because it is created by ActivityService as TTL index
  83. activitySchema.index({ target: 1, action: 1 });
  84. activitySchema.index(
  85. {
  86. user: 1,
  87. target: 1,
  88. action: 1,
  89. createdAt: 1,
  90. },
  91. { unique: true },
  92. );
  93. activitySchema.plugin(mongoosePaginate);
  94. activitySchema.post('save', function () {
  95. logger.debug('activity has been created', this);
  96. });
  97. activitySchema.statics.createByParameters = async function (
  98. parameters,
  99. ): Promise<IActivity> {
  100. const activity = (await this.create(parameters)) as unknown as IActivity;
  101. return activity;
  102. };
  103. // When using this method, ensure that activity updates are allowed using ActivityService.shoudUpdateActivity
  104. activitySchema.statics.updateByParameters = async function (
  105. activityId: string,
  106. parameters,
  107. ): Promise<ActivityDocument | null> {
  108. const activity = await this.findOneAndUpdate(
  109. { _id: activityId },
  110. parameters,
  111. { new: true },
  112. ).exec();
  113. return activity;
  114. };
  115. activitySchema.statics.findSnapshotUsernamesByUsernameRegexWithTotalCount =
  116. async function (
  117. q: string,
  118. option: { sortOpt: SortOrder; offset: number; limit: number },
  119. ): Promise<{ usernames: string[]; totalCount: number }> {
  120. const opt = option || {};
  121. const sortOpt = opt.sortOpt || 1;
  122. const offset = opt.offset || 0;
  123. const limit = opt.limit || 10;
  124. const conditions = { 'snapshot.username': { $regex: q, $options: 'i' } };
  125. const usernames = await this.aggregate()
  126. .skip(0)
  127. .limit(10000) // Narrow down the search target
  128. .match(conditions)
  129. .group({ _id: '$snapshot.username' })
  130. .sort({ _id: sortOpt }) // Sort "snapshot.username" in ascending order
  131. .skip(offset)
  132. .limit(limit);
  133. const totalCount = (
  134. await this.find(conditions).distinct('snapshot.username')
  135. ).length;
  136. return { usernames: usernames.map((r) => r._id), totalCount };
  137. };
  138. export default getOrCreateModel<ActivityDocument, ActivityModel>(
  139. 'Activity',
  140. activitySchema,
  141. );