questionnaire-cron.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import axiosRetry from 'axios-retry';
  2. import { configManager } from '~/server/service/config-manager';
  3. import CronService from '~/server/service/cron';
  4. import { getRandomIntInRange } from '~/utils/rand';
  5. import { StatusType } from '../../interfaces/questionnaire-answer-status';
  6. import type { IQuestionnaireOrder } from '../../interfaces/questionnaire-order';
  7. import ProactiveQuestionnaireAnswer from '../models/proactive-questionnaire-answer';
  8. import QuestionnaireAnswer from '../models/questionnaire-answer';
  9. import QuestionnaireAnswerStatus from '../models/questionnaire-answer-status';
  10. import QuestionnaireOrder from '../models/questionnaire-order';
  11. import { convertToLegacyFormat } from '../util/convert-to-legacy-format';
  12. const axios = require('axios').default;
  13. axiosRetry(axios, { retries: 3 });
  14. /**
  15. * Manages cronjob which
  16. * 1. fetches QuestionnaireOrders from questionnaire server
  17. * 2. updates QuestionnaireOrder collection to contain only the ones that exist in the fetched list and is not finished (doesn't have to be started)
  18. * 3. changes QuestionnaireAnswerStatuses which are 'skipped' to 'not_answered'
  19. * 4. resend QuestionnaireAnswers & ProactiveQuestionnaireAnswers which failed to reach questionnaire server
  20. */
  21. class QuestionnaireCronService extends CronService {
  22. sleep = (msec: number): Promise<void> => new Promise(resolve => setTimeout(resolve, msec));
  23. override getCronSchedule(): string {
  24. return configManager.getConfig('app:questionnaireCronSchedule');
  25. }
  26. override async executeJob(): Promise<void> {
  27. // sleep for a random amount to scatter request time from GROWI apps to questionnaire server
  28. await this.sleepBeforeJob();
  29. const questionnaireServerOrigin = configManager.getConfig('app:questionnaireServerOrigin');
  30. const isAppSiteUrlHashed = configManager.getConfig('questionnaire:isAppSiteUrlHashed');
  31. const fetchQuestionnaireOrders = async(): Promise<IQuestionnaireOrder[]> => {
  32. const response = await axios.get(`${questionnaireServerOrigin}/questionnaire-order/index`);
  33. return response.data.questionnaireOrders;
  34. };
  35. const saveUnfinishedOrders = async(questionnaireOrders: IQuestionnaireOrder[]) => {
  36. const currentDate = new Date(Date.now());
  37. const unfinishedOrders = questionnaireOrders.filter(order => new Date(order.showUntil) > currentDate);
  38. await QuestionnaireOrder.insertMany(unfinishedOrders);
  39. };
  40. const changeSkippedAnswerStatusToNotAnswered = async() => {
  41. await QuestionnaireAnswerStatus.updateMany(
  42. { status: StatusType.skipped },
  43. { status: StatusType.not_answered },
  44. );
  45. };
  46. const resendQuestionnaireAnswers = async() => {
  47. const questionnaireAnswers = await QuestionnaireAnswer.find()
  48. .select('-_id -answers._id -growiInfo._id -userInfo._id')
  49. .lean();
  50. const proactiveQuestionnaireAnswers = await ProactiveQuestionnaireAnswer.find()
  51. .select('-_id -growiInfo._id -userInfo._id')
  52. .lean();
  53. axios.post(`${questionnaireServerOrigin}/questionnaire-answer/batch`, {
  54. // convert to legacy format
  55. questionnaireAnswers: questionnaireAnswers.map(answer => convertToLegacyFormat(answer, isAppSiteUrlHashed)),
  56. })
  57. .then(async() => {
  58. await QuestionnaireAnswer.deleteMany();
  59. });
  60. axios.post(`${questionnaireServerOrigin}/questionnaire-answer/proactive/batch`, {
  61. // convert to legacy format
  62. proactiveQuestionnaireAnswers: proactiveQuestionnaireAnswers.map(answer => convertToLegacyFormat(answer, isAppSiteUrlHashed)),
  63. })
  64. .then(async() => {
  65. await ProactiveQuestionnaireAnswer.deleteMany();
  66. });
  67. };
  68. const questionnaireOrders: IQuestionnaireOrder[] = await fetchQuestionnaireOrders();
  69. resendQuestionnaireAnswers();
  70. // reset QuestionnaireOrder collection and save unfinished ones that exist on questionnaire server
  71. await QuestionnaireOrder.deleteMany();
  72. await saveUnfinishedOrders(questionnaireOrders);
  73. await changeSkippedAnswerStatusToNotAnswered();
  74. }
  75. private async sleepBeforeJob() {
  76. const maxHoursUntilRequest = configManager.getConfig('app:questionnaireCronMaxHoursUntilRequest');
  77. const maxSecondsUntilRequest = maxHoursUntilRequest * 60 * 60;
  78. const secToSleep = getRandomIntInRange(0, maxSecondsUntilRequest);
  79. await this.sleep(secToSleep * 1000);
  80. }
  81. }
  82. const questionnaireCronService = new QuestionnaireCronService();
  83. export default questionnaireCronService;