activity.ts 5.5 KB

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