activity.ts 4.8 KB

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