activity.ts 5.6 KB

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