activity.ts 4.2 KB

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