All files / mail/services scheduler.service.ts

90.24% Statements 37/41
50% Branches 2/4
100% Functions 6/6
90% Lines 36/40

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 983x               3x     3x 3x           3x 3x   3x 3x 3x       26x 26x   7x     7x             3x           3x 3x                   3x 2x   1x 1x   1x   1x         3x 3x 3x         3x 3x 3x 3x 3x 2x   1x     1x     3x     3x 3x      
import {
  Inject,
  Injectable,
  InternalServerErrorException,
  Logger,
  ServiceUnavailableException,
  forwardRef,
} from '@nestjs/common'
import { Cron } from '@nestjs/schedule'
import { ObjectId } from 'mongoose'
import { MailData, MailEvent } from '../../db/entities/mail-event.entity'
import { MailEventDBService } from '../../db/services/mail-event.service'
import { MailSendService } from './send.service'
 
/**
 * @description Service reliable for scheduling the sending of emails
 */
@Injectable()
export class MailScheduleService {
  private readonly logger = new Logger(MailScheduleService.name)
  constructor(
    @Inject(forwardRef(() => MailSendService))
    private readonly mailSendService: MailSendService,
    private readonly mailEventService: MailEventDBService,
  ) {}
 
  async scheduleMailNow(mail: MailData): Promise<void> {
    try {
      await this.mailSendService.sendMail(mail)
    } catch (error) {
      this.logger.error(
        `Encountered error while trying to send email: ${error}`,
      )
      throw new ServiceUnavailableException(
        'We are experiencing issues within our backend, please try again later',
      )
    }
  }
 
  async scheduleMailAtDate(scheduleDate: Date, mail: MailData): Promise<void> {
    const mailEvent: Partial<MailEvent> = {
      scheduledAt: scheduleDate,
      hasBeenSent: false,
      hasBeenRescheduled: false,
      content: mail,
    }
    const insertedValue = await this.mailEventService.createEvent(mailEvent)
    Iif (!insertedValue) {
      this.logger.warn(`MailEvent could not be inserted into the database`)
      throw new InternalServerErrorException('Mailevent could not be scheduled')
    }
  }
 
  private async rescheduleMails(
    ids: ObjectId[],
    newSendDate?: Date,
  ): Promise<void> {
    if (ids.length == 0) {
      return
    }
    if (!newSendDate) {
      newSendDate = new Date()
      // Reschedule 5 hours later by default
      newSendDate.setHours(newSendDate.getHours() + 5)
    }
    await this.mailEventService.rescheduleMails(ids, newSendDate)
  }
 
  // Run every hour
  @Cron('0 * * * *')
  async sendScheduledMails(): Promise<void> {
    const mails = await this.mailEventService.getOpenEventsBefore(new Date())
    Iif (!mails) {
      this.logger.log(`No scheduled mails for cron`)
      return
    }
    // TODO: This lacks error handling
    const successIds: ObjectId[] = []
    const failureIds: ObjectId[] = []
    for (const mail of mails) {
      try {
        await this.mailSendService.sendMail(mail.content)
        successIds.push(mail._id)
      } catch (error) {
        this.logger.warn(
          `Could not send scheduled mail due to an error ${error}`,
        )
        failureIds.push(mail._id)
      }
    }
    this.logger.log(
      `Send ${successIds.length} scheduled mails, failed to send ${failureIds.length}`,
    )
    await this.mailEventService.markMailsAsSend(successIds)
    await this.rescheduleMails(failureIds)
  }
}