activity.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import type { Ref, IPage, IUser } from '@growi/core';
  2. import mongoose from 'mongoose';
  3. import {
  4. IActivity, SupportedActionType, AllSupportedActions, ActionGroupSize,
  5. AllEssentialActions, AllSmallGroupActions, AllMediumGroupActions, AllLargeGroupActions,
  6. } from '~/interfaces/activity';
  7. import Activity from '~/server/models/activity';
  8. import loggerFactory from '../../utils/logger';
  9. import Crowi from '../crowi';
  10. const logger = loggerFactory('growi:service:ActivityService');
  11. const parseActionString = (actionsString: string): SupportedActionType[] => {
  12. if (actionsString == null) {
  13. return [];
  14. }
  15. const actions = actionsString.split(',').map(value => value.trim());
  16. return actions.filter(action => (AllSupportedActions as string[]).includes(action)) as SupportedActionType[];
  17. };
  18. class ActivityService {
  19. crowi!: Crowi;
  20. activityEvent: any;
  21. constructor(crowi: Crowi) {
  22. this.crowi = crowi;
  23. this.activityEvent = crowi.event('activity');
  24. this.getAvailableActions = this.getAvailableActions.bind(this);
  25. this.shoudUpdateActivity = this.shoudUpdateActivity.bind(this);
  26. this.initActivityEventListeners();
  27. }
  28. initActivityEventListeners(): void {
  29. this.activityEvent.on('update', async(activityId: string, parameters, target?: IPage, descendantsSubscribedUsers?: Ref<IUser>[]) => {
  30. let activity: IActivity;
  31. const shoudUpdate = this.shoudUpdateActivity(parameters.action);
  32. if (shoudUpdate) {
  33. try {
  34. activity = await Activity.updateByParameters(activityId, parameters);
  35. }
  36. catch (err) {
  37. logger.error('Update activity failed', err);
  38. return;
  39. }
  40. this.activityEvent.emit('updated', activity, target, descendantsSubscribedUsers);
  41. }
  42. });
  43. }
  44. getAvailableActions = function(isIncludeEssentialActions = true): SupportedActionType[] {
  45. const auditLogEnabled = this.crowi.configManager.getConfig('crowi', 'app:auditLogEnabled') || false;
  46. const auditLogActionGroupSize = this.crowi.configManager.getConfig('crowi', 'app:auditLogActionGroupSize') || ActionGroupSize.Small;
  47. const auditLogAdditionalActions = this.crowi.configManager.getConfig('crowi', 'app:auditLogAdditionalActions');
  48. const auditLogExcludeActions = this.crowi.configManager.getConfig('crowi', 'app:auditLogExcludeActions');
  49. if (!auditLogEnabled) {
  50. return AllEssentialActions;
  51. }
  52. const availableActionsSet = new Set<SupportedActionType>();
  53. // Set base action group
  54. switch (auditLogActionGroupSize) {
  55. case ActionGroupSize.Small:
  56. AllSmallGroupActions.forEach(action => availableActionsSet.add(action));
  57. break;
  58. case ActionGroupSize.Medium:
  59. AllMediumGroupActions.forEach(action => availableActionsSet.add(action));
  60. break;
  61. case ActionGroupSize.Large:
  62. AllLargeGroupActions.forEach(action => availableActionsSet.add(action));
  63. break;
  64. }
  65. // Add additionalActions
  66. const additionalActions = parseActionString(auditLogAdditionalActions);
  67. additionalActions.forEach(action => availableActionsSet.add(action));
  68. // Delete excludeActions
  69. const excludeActions = parseActionString(auditLogExcludeActions);
  70. excludeActions.forEach(action => availableActionsSet.delete(action));
  71. // Add essentialActions
  72. if (isIncludeEssentialActions) {
  73. AllEssentialActions.forEach(action => availableActionsSet.add(action));
  74. }
  75. return Array.from(availableActionsSet);
  76. };
  77. shoudUpdateActivity = function(action: SupportedActionType): boolean {
  78. return this.getAvailableActions().includes(action);
  79. };
  80. // for GET request
  81. createActivity = async function(parameters): Promise<IActivity | null> {
  82. const shoudCreateActivity = this.crowi.activityService.shoudUpdateActivity(parameters.action);
  83. if (shoudCreateActivity) {
  84. let activity: IActivity;
  85. try {
  86. activity = await Activity.createByParameters(parameters);
  87. return activity;
  88. }
  89. catch (err) {
  90. logger.error('Create activity failed', err);
  91. }
  92. }
  93. return null;
  94. };
  95. createTtlIndex = async function() {
  96. const configManager = this.crowi.configManager;
  97. const activityExpirationSeconds = configManager != null ? configManager.getConfig('crowi', 'app:activityExpirationSeconds') : 2592000;
  98. const collection = mongoose.connection.collection('activities');
  99. try {
  100. const targetField = 'createdAt_1';
  101. const indexes = await collection.indexes();
  102. const foundCreatedAt = indexes.find(i => i.name === targetField);
  103. const isNotSpec = foundCreatedAt?.expireAfterSeconds == null || foundCreatedAt?.expireAfterSeconds !== activityExpirationSeconds;
  104. const shoudDropIndex = foundCreatedAt != null && isNotSpec;
  105. const shoudCreateIndex = foundCreatedAt == null || shoudDropIndex;
  106. if (shoudDropIndex) {
  107. await collection.dropIndex(targetField);
  108. }
  109. if (shoudCreateIndex) {
  110. await collection.createIndex({ createdAt: 1 }, { expireAfterSeconds: activityExpirationSeconds });
  111. }
  112. }
  113. catch (err) {
  114. logger.error('Failed to create TTL Index', err);
  115. throw err;
  116. }
  117. };
  118. }
  119. module.exports = ActivityService;