activity.ts 4.3 KB

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