activity.ts 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. import { DeleteWriteOpResultObject } from 'mongodb';
  2. import {
  3. Types, Document, Model, Schema,
  4. } from 'mongoose';
  5. import { getOrCreateModel, getModelSafely } from '../util/mongoose-utils';
  6. import loggerFactory from '../../utils/logger';
  7. import ActivityDefine from '../util/activityDefine';
  8. import Watcher from './watcher';
  9. import { InAppNotification } from './in-app-notification';
  10. import ActivityEvent from '../events/activity';
  11. const logger = loggerFactory('growi:models:activity');
  12. export interface ActivityDocument extends Document {
  13. _id: Types.ObjectId
  14. user: Types.ObjectId | any
  15. targetModel: string
  16. target: string
  17. action: string
  18. event: Types.ObjectId
  19. eventModel: string
  20. createdAt: Date
  21. getNotificationTargetUsers(): Promise<any[]>
  22. }
  23. export interface ActivityModel extends Model<ActivityDocument> {
  24. createByParameters(parameters: any): Promise<ActivityDocument>
  25. removeByParameters(parameters: any): any
  26. createByPageComment(comment: any): Promise<ActivityDocument>
  27. removeByPageCommentDelete(comment: any): Promise<DeleteWriteOpResultObject['result']>
  28. createByPageLike(page: any, user: any): Promise<ActivityDocument>
  29. removeByPageUnlike(page: any, user: any): Promise<DeleteWriteOpResultObject['result']>
  30. removeByPage(page: any): Promise<DeleteWriteOpResultObject['result']>
  31. findByUser(user: any): Promise<ActivityDocument[]>
  32. getActionUsersFromActivities(activities: ActivityDocument[]): any[]
  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. require: true,
  41. },
  42. targetModel: {
  43. type: String,
  44. require: true,
  45. enum: ActivityDefine.getSupportTargetModelNames(),
  46. },
  47. target: {
  48. type: Schema.Types.ObjectId,
  49. refPath: 'targetModel',
  50. require: true,
  51. },
  52. action: {
  53. type: String,
  54. require: true,
  55. enum: ActivityDefine.getSupportActionNames(),
  56. },
  57. event: {
  58. type: Schema.Types.ObjectId,
  59. refPath: 'eventModel',
  60. },
  61. eventModel: {
  62. type: String,
  63. enum: ActivityDefine.getSupportEventModelNames(),
  64. },
  65. createdAt: {
  66. type: Date,
  67. default: Date.now,
  68. },
  69. });
  70. activitySchema.index({ target: 1, action: 1 });
  71. activitySchema.index({
  72. user: 1, target: 1, action: 1, createdAt: 1,
  73. }, { unique: true });
  74. /**
  75. * @param {object} parameters
  76. * @return {Promise}
  77. */
  78. activitySchema.statics.createByParameters = function(parameters) {
  79. return this.create(parameters);
  80. };
  81. /**
  82. * @param {object} parameters
  83. */
  84. activitySchema.statics.removeByParameters = async function(parameters) {
  85. const activityEvent = new ActivityEvent();
  86. const activity = await this.findOne(parameters);
  87. activityEvent.emit('remove', activity);
  88. return this.deleteMany(parameters).exec();
  89. };
  90. /**
  91. * @param {Comment} comment
  92. * @return {Promise}
  93. */
  94. activitySchema.statics.createByPageComment = function(comment) {
  95. const parameters = {
  96. user: comment.creator,
  97. targetModel: ActivityDefine.MODEL_PAGE,
  98. target: comment.page,
  99. eventModel: ActivityDefine.MODEL_COMMENT,
  100. event: comment._id,
  101. action: ActivityDefine.ACTION_COMMENT,
  102. };
  103. return this.createByParameters(parameters);
  104. };
  105. /**
  106. * @param {Comment} comment
  107. * @return {Promise}
  108. */
  109. activitySchema.statics.removeByPageCommentDelete = async function(comment) {
  110. const parameters = await {
  111. user: comment.creator,
  112. targetModel: ActivityDefine.MODEL_PAGE,
  113. target: comment.page,
  114. eventModel: ActivityDefine.MODEL_COMMENT,
  115. event: comment._id,
  116. action: ActivityDefine.ACTION_COMMENT,
  117. };
  118. await this.removeByParameters(parameters);
  119. return;
  120. };
  121. /**
  122. * @param {Page} page
  123. * @param {User} user
  124. * @return {Promise}
  125. */
  126. activitySchema.statics.createByPageLike = function(page, user) {
  127. const parameters = {
  128. user: user._id,
  129. targetModel: ActivityDefine.MODEL_PAGE,
  130. target: page,
  131. action: ActivityDefine.ACTION_LIKE,
  132. };
  133. return this.createByParameters(parameters);
  134. };
  135. /**
  136. * @param {Page} page
  137. * @param {User} user
  138. * @return {Promise}
  139. */
  140. activitySchema.statics.removeByPageUnlike = function(page, user) {
  141. const parameters = {
  142. user,
  143. targetModel: ActivityDefine.MODEL_PAGE,
  144. target: page,
  145. action: ActivityDefine.ACTION_LIKE,
  146. };
  147. return this.removeByParameters(parameters);
  148. };
  149. /**
  150. * @param {Page} page
  151. * @return {Promise}
  152. */
  153. activitySchema.statics.removeByPage = async function(page) {
  154. const activityEvent = new ActivityEvent();
  155. const activities = await this.find({ target: page });
  156. for (const activity of activities) {
  157. activityEvent.emit('remove', activity);
  158. }
  159. return this.deleteMany({ target: page }).exec();
  160. };
  161. /**
  162. * @param {User} user
  163. * @return {Promise}
  164. */
  165. activitySchema.statics.findByUser = function(user) {
  166. return this.find({ user }).sort({ createdAt: -1 }).exec();
  167. };
  168. activitySchema.statics.getActionUsersFromActivities = function(activities) {
  169. return activities.map(({ user }) => user).filter((user, i, self) => self.indexOf(user) === i);
  170. };
  171. activitySchema.methods.getNotificationTargetUsers = async function() {
  172. const User = getModelSafely('User') || require('~/server/models/user')();
  173. const { user: actionUser, targetModel, target } = this;
  174. const model: any = await this.model(targetModel).findById(target);
  175. const [targetUsers, watchUsers, ignoreUsers] = await Promise.all([
  176. model.getNotificationTargetUsers(),
  177. Watcher.getWatchers((target as any) as Types.ObjectId),
  178. Watcher.getIgnorers((target as any) as Types.ObjectId),
  179. ]);
  180. const unique = array => Object.values(array.reduce((objects, object) => ({ ...objects, [object.toString()]: object }), {}));
  181. const filter = (array, pull) => {
  182. const ids = pull.map(object => object.toString());
  183. return array.filter(object => !ids.includes(object.toString()));
  184. };
  185. const notificationUsers = filter(unique([...targetUsers, ...watchUsers]), [...ignoreUsers, actionUser]);
  186. const activeNotificationUsers = await User.find({
  187. _id: { $in: notificationUsers },
  188. status: User.STATUS_ACTIVE,
  189. }).distinct('_id');
  190. return activeNotificationUsers;
  191. };
  192. /**
  193. * saved hook TODO: getNotificationTargetUsers by GW-7346
  194. */
  195. activitySchema.post('save', async(savedActivity: ActivityDocument) => {
  196. try {
  197. // const notificationUsers = await savedActivity.getNotificationTargetUsers();
  198. // await Promise.all(notificationUsers.map(user => InAppNotification.upsertByActivity(user, savedActivity)));
  199. return;
  200. }
  201. catch (err) {
  202. logger.error(err);
  203. }
  204. });
  205. /**
  206. * TODO: improve removeActivity that decleard in InAppNotificationService by GW-7481
  207. */
  208. // because mongoose's 'remove' hook fired only when remove by a method of Document (not by a Model method)
  209. // move 'save' hook from mongoose's events to activityEvent if I have a time.
  210. const activityEvent = new ActivityEvent();
  211. // const inAppNotificationService = new InAppNotificationService(crowi);
  212. activityEvent.on('remove', async(activity: ActivityDocument) => {
  213. try {
  214. // await inAppNotificationService.removeActivity(activity);
  215. }
  216. catch (err) {
  217. logger.error(err);
  218. }
  219. });
  220. const Activity = getOrCreateModel<ActivityDocument, ActivityModel>('Activity', activitySchema);
  221. export { Activity };