activity.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import mongoose from 'mongoose';
  2. import {
  3. IActivity, SupportedActionType, AllSupportedActions, ActionGroupSize,
  4. AllEssentialActions, AllSmallGroupActions, AllMediumGroupActions, AllLargeGroupActions,
  5. } from '~/interfaces/activity';
  6. import { IPage } from '~/interfaces/page';
  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) => {
  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);
  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 AllSupportedActionToNotified;
  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. createTtlIndex = async function() {
  81. const configManager = this.crowi.configManager;
  82. const activityExpirationSeconds = configManager != null ? configManager.getConfig('crowi', 'app:activityExpirationSeconds') : 2592000;
  83. const collection = mongoose.connection.collection('activities');
  84. try {
  85. const targetField = 'createdAt_1';
  86. const indexes = await collection.indexes();
  87. const foundCreatedAt = indexes.find(i => i.name === targetField);
  88. const isNotSpec = foundCreatedAt?.expireAfterSeconds == null || foundCreatedAt?.expireAfterSeconds !== activityExpirationSeconds;
  89. const shoudDropIndex = foundCreatedAt != null && isNotSpec;
  90. const shoudCreateIndex = foundCreatedAt == null || shoudDropIndex;
  91. if (shoudDropIndex) {
  92. await collection.dropIndex(targetField);
  93. }
  94. if (shoudCreateIndex) {
  95. await collection.createIndex({ createdAt: 1 }, { expireAfterSeconds: activityExpirationSeconds });
  96. }
  97. }
  98. catch (err) {
  99. logger.error('Failed to create TTL Index', err);
  100. throw err;
  101. }
  102. };
  103. }
  104. module.exports = ActivityService;