activity.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  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 auditLogActionGroupSize = this.crowi.configManager.getConfig('crowi', 'app:auditLogActionGroupSize') || ActionGroupSize.Small;
  46. const auditLogAdditionalActions = this.crowi.configManager.getConfig('crowi', 'app:auditLogAdditionalActions');
  47. const auditLogExcludeActions = this.crowi.configManager.getConfig('crowi', 'app:auditLogExcludeActions');
  48. const availableActionsSet = new Set<SupportedActionType>();
  49. // Set base action group
  50. switch (auditLogActionGroupSize) {
  51. case ActionGroupSize.Small:
  52. AllSmallGroupActions.forEach(action => availableActionsSet.add(action));
  53. break;
  54. case ActionGroupSize.Medium:
  55. AllMediumGroupActions.forEach(action => availableActionsSet.add(action));
  56. break;
  57. case ActionGroupSize.Large:
  58. AllLargeGroupActions.forEach(action => availableActionsSet.add(action));
  59. break;
  60. }
  61. // Add additionalActions
  62. const additionalActions = parseActionString(auditLogAdditionalActions);
  63. additionalActions.forEach(action => availableActionsSet.add(action));
  64. // Delete excludeActions
  65. const excludeActions = parseActionString(auditLogExcludeActions);
  66. excludeActions.forEach(action => availableActionsSet.delete(action));
  67. // Add essentialActions
  68. if (isIncludeEssentialActions) {
  69. AllEssentialActions.forEach(action => availableActionsSet.add(action));
  70. }
  71. return Array.from(availableActionsSet);
  72. }
  73. shoudUpdateActivity = function(action: SupportedActionType): boolean {
  74. return this.getAvailableActions().includes(action);
  75. }
  76. createTtlIndex = async function() {
  77. const configManager = this.crowi.configManager;
  78. const activityExpirationSeconds = configManager != null ? configManager.getConfig('crowi', 'app:activityExpirationSeconds') : 2592000;
  79. const collection = mongoose.connection.collection('activities');
  80. try {
  81. const targetField = 'createdAt_1';
  82. const indexes = await collection.indexes();
  83. const foundCreatedAt = indexes.find(i => i.name === targetField);
  84. const isNotSpec = foundCreatedAt?.expireAfterSeconds == null || foundCreatedAt?.expireAfterSeconds !== activityExpirationSeconds;
  85. const shoudDropIndex = foundCreatedAt != null && isNotSpec;
  86. const shoudCreateIndex = foundCreatedAt == null || shoudDropIndex;
  87. if (shoudDropIndex) {
  88. await collection.dropIndex(targetField);
  89. }
  90. if (shoudCreateIndex) {
  91. await collection.createIndex({ createdAt: 1 }, { expireAfterSeconds: activityExpirationSeconds });
  92. }
  93. }
  94. catch (err) {
  95. logger.error('Failed to create TTL Index', err);
  96. throw err;
  97. }
  98. };
  99. }
  100. module.exports = ActivityService;