All files / src/services/scan brevo.service.ts

16% Statements 8/50
0% Branches 0/63
0% Functions 0/8
12.77% Lines 6/47

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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 1211x 1x 1x       1x                 1x       1x                                                                                                                                                                                                          
import axios from 'axios';
import {bind, inject, BindingScope} from '@loopback/core';
import {SecurityBindings} from '@loopback/security';
import {UserProfile} from '@logchain/components/aws';
import {EmailConfig} from '@logchain/models/scan/email-config.model';
import {Pmo} from '@logchain/models/scan/pmo.model';
import { buildAttachmentViewUrl } from '@logchain/utils/attachment-link.util';
 
export interface BrevoEmailParams {
  templateId: number;
  to: string[];
  params: Record<string, unknown>;
}
 
@bind({scope: BindingScope.TRANSIENT})
export class BrevoService {
  private readonly apiKey = process.env.BREVO_API_KEY ?? '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';
  private readonly baseUrl = process.env.BREVO_BASE_URL ?? 'https://api.brevo.com/v3';
  @inject(SecurityBindings.USER, {optional: true})
  public current_user: UserProfile;
  
  getCurrentUserEmail(): string | null {
    return this.current_user?.email|| null;
  }
  getCurrentUserName(): string | null {
    return this.current_user?.name || null;
  }
 
  formatProcessedAt(date: Date): string {
    const sgDate = new Date(date.toLocaleString('en-US', {timeZone: 'Asia/Singapore'}));
    /* const day = sgDate.toLocaleDateString('en-SG', {
      weekday: 'short',
      timeZone: 'Asia/Singapore',
    }); */
    const day = String(sgDate.getDate()).padStart(2, '0');
    const month = String(sgDate.getMonth() + 1).padStart(2, '0');
    const year = sgDate.getFullYear();
    const hours = String(sgDate.getHours()).padStart(2, '0');
    const minutes = String(sgDate.getMinutes()).padStart(2, '0');
 
    return `${day}-${month}-${year} ${hours}:${minutes}`;
  }
 
  async sendNotificationEmail(config: EmailConfig, type: string = 'update', params?:  Record<string, unknown>): Promise<void> {
    //p: Pmo, remark: string = '', attachments?: any[]
    if (config && config.template_id) {
      /* 
      //Coming soon: support additional email list in config
      const toList = config.additonal_email_list
        ? config.additonal_email_list.split(',').map(e => e.trim()).filter(Boolean)
        : []; */
        let pmoList = '';
        let attachmentLinks = '';
        switch(type){
          case 'update':
            if(params?.pmo_list && Array.isArray(params.pmo_list)){
              pmoList = '<ul>';
              let temp = '';
              for(const pmoItem of params.pmo_list){
                pmoList += `<li><strong>PMO Code:</strong> ${pmoItem.pmo.code}`;
                const attachments = pmoItem.attachments || [];
                temp = attachments.map((att: any) => {
                  const filename = att.file_url.split('/').pop() || 'Attachment';
                  const viewUrl = buildAttachmentViewUrl(att.id);
                  return `<a style="display: inline-block; width: 100px; height: 100px; margin: 5px; border: 1px solid #ccc; overflow: hidden;" href="${viewUrl}" target="_blank"><img style="display: block; width: 100%; height: auto; object-fit: cover;" src="${viewUrl}" alt="${filename}" /></a>`;
                }).join('');
                attachmentLinks += temp;
                pmoList += '</li>';
              }
              if(attachmentLinks){
                  attachmentLinks = `<div style="margin-top: 10px;">${attachmentLinks}</div>`;
                }
              pmoList += '</ul><br/>';
            }
          break;
          case 'reject':
            if(params?.pmos && params.pmos instanceof Array){
              pmoList = '<ul>';
              for(const pmoItem of params.pmos){
                pmoList += `<li><strong>PMO Code:</strong> ${pmoItem.code}</li>`;
              }
              pmoList += '</ul><br/>';
            }
            break;
          }
      await this.sendTransactionalEmail({
        'templateId': config.template_id,
        'to': [this.getCurrentUserEmail()!],
        'params': {
          'processed_by': this.getCurrentUserName(),
          'processed_at': this.formatProcessedAt(new Date()),
          'pmo_list': pmoList,
          'attachments': attachmentLinks,
          'remarks': params?.remarks ? 'Remarks: ' + params.remarks : '',
        },
      });
    }
  }
  async sendTransactionalEmail(options: BrevoEmailParams): Promise<void> {
    const {templateId, to, params} = options;
    const recipients = to.map(email => ({email: email.trim()}));
 
    await axios.post(
      `${this.baseUrl}/smtp/email`,
      {
        templateId,
        sender: {name: process.env.BREVO_SENDER_NAME ?? 'Logchain', email: process.env.BREVO_SENDER_EMAIL ?? ''},
        to: recipients,
        params,
      },
      {
        headers: {
          'api-key': this.apiKey,
          'Content-Type': 'application/json',
          'accept': 'application/json',
        },
      },
    );
  }
}