From 8fd59a13d8eee156e8819345659de572904dd0fd Mon Sep 17 00:00:00 2001 From: Vins Date: Mon, 17 Jul 2023 15:14:35 +0200 Subject: [PATCH] Added mailchimp feature + cron services --- Dockerfile-Cron | 46 +++++++ package-lock.json | 61 ++++++++- package.json | 8 +- src/app/api/notary/DocumentsController.ts | 9 +- .../migrations/20230711134012_/migration.sql | 43 +++++++ src/common/databases/schema.prisma | 16 +++ src/common/emails/EmailBuilder.ts | 50 ++++++++ src/common/emails/Templates/EmailTemplates.ts | 4 + src/common/repositories/EmailRepository.ts | 75 +++++++++++ src/entries/Cron.ts | 14 +++ .../common/CronService/CronService.ts | 24 ++++ .../MailchimpService/MailchimpService.ts | 116 ++++++++++++++++++ tsconfig.json | 4 +- 13 files changed, 463 insertions(+), 7 deletions(-) create mode 100644 Dockerfile-Cron create mode 100644 src/common/databases/migrations/20230711134012_/migration.sql create mode 100644 src/common/emails/EmailBuilder.ts create mode 100644 src/common/emails/Templates/EmailTemplates.ts create mode 100644 src/common/repositories/EmailRepository.ts create mode 100644 src/entries/Cron.ts create mode 100644 src/services/common/CronService/CronService.ts create mode 100644 src/services/common/MailchimpService/MailchimpService.ts diff --git a/Dockerfile-Cron b/Dockerfile-Cron new file mode 100644 index 00000000..3dbfefe4 --- /dev/null +++ b/Dockerfile-Cron @@ -0,0 +1,46 @@ +# Install dependencies only when needed +FROM node:19-alpine AS deps + +WORKDIR leCoffre + +RUN npm install -D prisma@4.11.0 +COPY package.json ./ + +RUN apk update && apk add openssh-client git + +COPY id_rsa /root/.ssh/id_rsa +RUN chmod 600 ~/.ssh/id_rsa +RUN eval "$(ssh-agent -s)" && ssh-add /root/.ssh/id_rsa +RUN ssh-keyscan github.com smart-chain-fr/leCoffre-resources.git >> /root/.ssh/known_hosts + +RUN npm install --frozen-lockfile + +# Rebuild the source code only when needed +FROM node:19-alpine AS builder + +WORKDIR leCoffre + +COPY --from=deps leCoffre/node_modules ./node_modules +COPY --from=deps leCoffre/package.json package.json +COPY tsconfig.json tsconfig.json +COPY src src + +RUN npx prisma generate +RUN npm run build + +# Production image, copy all the files and run next +FROM node:19-alpine AS production + +WORKDIR leCoffre + +RUN adduser -D lecoffreuser --uid 10000 && chown -R lecoffreuser . + +COPY --from=builder --chown=lecoffreuser leCoffre/node_modules ./node_modules +COPY --from=builder --chown=lecoffreuser leCoffre/dist dist +COPY --from=builder --chown=lecoffreuser leCoffre/package.json ./package.json +COPY --from=builder --chown=lecoffreuser leCoffre/src/common/databases ./src/common/databases + +USER lecoffreuser + +CMD ["npm", "run", "cron"] +EXPOSE 3001 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 36602f19..a6b784ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,14 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@mailchimp/mailchimp_transactional": "^1.0.50", "@pinata/sdk": "^2.1.0", "@prisma/client": "^4.11.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "classnames": "^2.3.2", "cors": "^2.8.5", + "cron": "^2.3.1", "express": "^4.18.2", "jsonwebtoken": "^9.0.0", "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.57", @@ -35,9 +37,12 @@ }, "devDependencies": { "@types/cors": "^2.8.13", + "@types/cron": "^2.0.1", "@types/express": "^4.17.16", "@types/jest": "^29.5.0", "@types/jsonwebtoken": "^9.0.1", + "@types/mailchimp__mailchimp_transactional": "^1.0.5", + "@types/module-alias": "^2.0.1", "@types/multer": "^1.4.7", "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.3", @@ -1044,6 +1049,17 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, + "node_modules/@mailchimp/mailchimp_transactional": { + "version": "1.0.50", + "resolved": "https://registry.npmjs.org/@mailchimp/mailchimp_transactional/-/mailchimp_transactional-1.0.50.tgz", + "integrity": "sha512-SaNFseFPSDQlOYM9JTyYY6wauMu6qJ8eExo+jssFyb20ZaVvxKX1eTb3Gm5aW/4aWuxn6nofU+02sCk51//wdw==", + "dependencies": { + "axios": "^0.21.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@next/env": { "version": "13.4.10", "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.10.tgz", @@ -1348,6 +1364,16 @@ "@types/node": "*" } }, + "node_modules/@types/cron": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/cron/-/cron-2.0.1.tgz", + "integrity": "sha512-WHa/1rtNtD2Q/H0+YTTZoty+/5rcE66iAFX2IY+JuUoOACsevYyFkSYu/2vdw+G5LrmO7Lxowrqm0av4k3qWNQ==", + "dev": true, + "dependencies": { + "@types/luxon": "*", + "@types/node": "*" + } + }, "node_modules/@types/express": { "version": "4.17.17", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", @@ -1430,12 +1456,33 @@ "@types/node": "*" } }, + "node_modules/@types/luxon": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.1.tgz", + "integrity": "sha512-XOS5nBcgEeP2PpcqJHjCWhUCAzGfXIU8ILOSLpx2FhxqMW9KdxgCGXNOEKGVBfveKtIpztHzKK5vSRVLyW/NqA==", + "dev": true + }, + "node_modules/@types/mailchimp__mailchimp_transactional": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/mailchimp__mailchimp_transactional/-/mailchimp__mailchimp_transactional-1.0.5.tgz", + "integrity": "sha512-5LYI3dZcyVBtg+lNxhKBHrHnNeAVvlpPM0kO6FZcjrrMALK7wistwvqI8PAns2mnveC67OSN43y6wQkK6KeTNQ==", + "dev": true, + "dependencies": { + "axios": "^0.21.1" + } + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", "dev": true }, + "node_modules/@types/module-alias": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/module-alias/-/module-alias-2.0.1.tgz", + "integrity": "sha512-DN/CCT1HQG6HquBNJdLkvV+4v5l/oEuwOHUPLxI+Eub0NED+lk0YUfba04WGH90EINiUrNgClkNnwGmbICeWMQ==", + "dev": true + }, "node_modules/@types/multer": { "version": "1.4.7", "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz", @@ -1446,9 +1493,9 @@ } }, "node_modules/@types/node": { - "version": "18.16.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.19.tgz", - "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==" + "version": "18.17.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.0.tgz", + "integrity": "sha512-GXZxEtOxYGFchyUzxvKI14iff9KZ2DI+A6a37o6EQevtg6uO9t+aUZKcaC1Te5Ng1OnLM7K9NVVj+FbecD9cJg==" }, "node_modules/@types/node-fetch": { "version": "2.6.4", @@ -2287,6 +2334,14 @@ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, + "node_modules/cron": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/cron/-/cron-2.3.1.tgz", + "integrity": "sha512-1eRRlIT0UfIqauwbG9pkg3J6CX9A6My2ytJWqAXoK0T9oJnUZTzGBNPxao0zjodIbPgf8UQWjE62BMb9eVllSQ==", + "dependencies": { + "luxon": "^3.2.1" + } + }, "node_modules/cron-parser": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.8.1.tgz", diff --git a/package.json b/package.json index ad2b49f4..9acb0afe 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "build-db": "npx prisma migrate dev", "build": "tsc", "start": "node ./dist/entries/App.js", + "cron": "node ./dist/entries/Cron.js", "api:start": "npm run migrate && npm run start", "dev": "nodemon -V", "format": "prettier --write src", @@ -41,12 +42,14 @@ }, "homepage": "https://github.com/smart-chain-fr/leCoffre-back#readme", "dependencies": { + "@mailchimp/mailchimp_transactional": "^1.0.50", "@pinata/sdk": "^2.1.0", "@prisma/client": "^4.11.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "classnames": "^2.3.2", "cors": "^2.8.5", + "cron": "^2.3.1", "express": "^4.18.2", "jsonwebtoken": "^9.0.0", "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.58", @@ -67,9 +70,12 @@ }, "devDependencies": { "@types/cors": "^2.8.13", + "@types/cron": "^2.0.1", "@types/express": "^4.17.16", "@types/jest": "^29.5.0", "@types/jsonwebtoken": "^9.0.1", + "@types/mailchimp__mailchimp_transactional": "^1.0.5", + "@types/module-alias": "^2.0.1", "@types/multer": "^1.4.7", "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.3", @@ -84,6 +90,6 @@ }, "prisma": { "schema": "src/common/databases/schema.prisma", - "seed": "ts-node src/common/databases/seeders/seeder2.ts" + "seed": "ts-node src/common/databases/seeders/seeder.ts" } } diff --git a/src/app/api/notary/DocumentsController.ts b/src/app/api/notary/DocumentsController.ts index f676367d..29fa5492 100644 --- a/src/app/api/notary/DocumentsController.ts +++ b/src/app/api/notary/DocumentsController.ts @@ -9,11 +9,12 @@ import { validateOrReject } from "class-validator"; import authHandler from "@App/middlewares/AuthHandler"; import ruleHandler from "@App/middlewares/RulesHandler"; import documentHandler from "@App/middlewares/OfficeMembershipHandlers/DocumentHandler"; +import EmailBuilder from "@Common/emails/EmailBuilder"; @Controller() @Service() export default class DocumentsController extends ApiController { - constructor(private documentsService: DocumentsService) { + constructor(private documentsService: DocumentsService, private emailBuilder: EmailBuilder) { super(); } @@ -64,6 +65,9 @@ export default class DocumentsController extends ApiController { //call service to get prisma entity const documentEntityCreated = await this.documentsService.create(documentEntity); + //create email for asked document + this.emailBuilder.sendDocumentEmails(documentEntityCreated); + //Hydrate ressource with prisma entity const document = Document.hydrate(documentEntityCreated, { strategy: "excludeAll", @@ -105,6 +109,9 @@ export default class DocumentsController extends ApiController { //call service to get prisma entity const documentEntityUpdated: Documents = await this.documentsService.update(uid, documentEntity, req.body.refused_reason); + //create email for asked document + this.emailBuilder.sendDocumentEmails(documentEntityUpdated); + //Hydrate ressource with prisma entity const document = Document.hydrate(documentEntityUpdated, { strategy: "excludeAll" }); diff --git a/src/common/databases/migrations/20230711134012_/migration.sql b/src/common/databases/migrations/20230711134012_/migration.sql new file mode 100644 index 00000000..205eb444 --- /dev/null +++ b/src/common/databases/migrations/20230711134012_/migration.sql @@ -0,0 +1,43 @@ +-- DropForeignKey +ALTER TABLE "users" DROP CONSTRAINT "users_contact_uid_fkey"; + +-- DropForeignKey +ALTER TABLE "users" DROP CONSTRAINT "users_office_role_uid_fkey"; + +-- DropForeignKey +ALTER TABLE "users" DROP CONSTRAINT "users_office_uid_fkey"; + +-- DropForeignKey +ALTER TABLE "users" DROP CONSTRAINT "users_roles_uid_fkey"; + +-- CreateTable +CREATE TABLE "email" ( + "uid" TEXT NOT NULL, + "templateName" VARCHAR(255) NOT NULL, + "from" VARCHAR(255), + "to" VARCHAR(255) NOT NULL, + "subject" VARCHAR(255) NOT NULL, + "templateVariables" JSON NOT NULL DEFAULT '{}', + "cc" VARCHAR(255)[], + "cci" VARCHAR(255)[], + "sentAt" TIMESTAMP(3), + "nbTrySend" INTEGER DEFAULT 0, + "lastTrySendDate" TIMESTAMP(3), + + CONSTRAINT "email_pkey" PRIMARY KEY ("uid") +); + +-- CreateIndex +CREATE UNIQUE INDEX "email_uid_key" ON "email"("uid"); + +-- AddForeignKey +ALTER TABLE "users" ADD CONSTRAINT "users_contact_uid_fkey" FOREIGN KEY ("contact_uid") REFERENCES "contacts"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "users" ADD CONSTRAINT "users_roles_uid_fkey" FOREIGN KEY ("roles_uid") REFERENCES "roles"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "users" ADD CONSTRAINT "users_office_role_uid_fkey" FOREIGN KEY ("office_role_uid") REFERENCES "office_roles"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "users" ADD CONSTRAINT "users_office_uid_fkey" FOREIGN KEY ("office_uid") REFERENCES "offices"("uid") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/common/databases/schema.prisma b/src/common/databases/schema.prisma index 35e7d73c..26a6ecc3 100644 --- a/src/common/databases/schema.prisma +++ b/src/common/databases/schema.prisma @@ -270,6 +270,22 @@ model Rules { @@map("rules") } +model Emails { + uid String @id @unique @default(uuid()) + templateName String @db.VarChar(255) + from String? @db.VarChar(255) + to String @db.VarChar(255) + subject String @db.VarChar(255) + templateVariables Json @default("{}") @db.Json + cc String[] @db.VarChar(255) + cci String[] @db.VarChar(255) + sentAt DateTime? + nbTrySend Int? @default(0) + lastTrySendDate DateTime? + + @@map("email") +} + enum ECivility { MALE FEMALE diff --git a/src/common/emails/EmailBuilder.ts b/src/common/emails/EmailBuilder.ts new file mode 100644 index 00000000..b66c8238 --- /dev/null +++ b/src/common/emails/EmailBuilder.ts @@ -0,0 +1,50 @@ + +import DocumentsService from "@Services/super-admin/DocumentsService/DocumentsService"; +import { Documents } from "@prisma/client"; +import { Document } from "le-coffre-resources/dist/SuperAdmin"; +import { Service } from "typedi"; +import { ETemplates } from "./Templates/EmailTemplates"; +import MailchimpService from "@Services/common/MailchimpService/MailchimpService"; + +@Service() +export default class EmailBuilder { + public constructor(private mailchimpService: MailchimpService ,private documentsService: DocumentsService){} + + public async sendDocumentEmails(documentEntity: Documents){ + if(documentEntity.document_status !== "ASKED" && documentEntity.document_status !== "REFUSED") return; + + const documentPrisma = await this.documentsService.getByUid(documentEntity.uid, { depositor: {include: {contact: true}}, folder:{include:{ office: true}} }); + if(!documentPrisma) throw new Error("Document not found"); + const document = Document.hydrate(documentPrisma); + const to = document.depositor!.contact!.email; + + const templateVariables = { + civility: document.depositor!.contact!.civility, + last_name: document.depositor!.contact!.last_name, + office_name: document.folder!.office!.name, + link: "http://localhost:3000" + }; + + let templateName = ETemplates.DOCUMENT_ASKED; + let subject = "Document Asked"; + if(documentEntity.document_status === "REFUSED"){ + templateName = ETemplates.DOCUMENT_REFUSED; + subject = "Document Refused"; + } + + this.mailchimpService.create({ + templateName, + to, + subject, + templateVariables, + uid: "", + from: null, + cc: [], + cci: [], + sentAt: null, + nbTrySend: null, + lastTrySendDate: null, + }); + + } +} diff --git a/src/common/emails/Templates/EmailTemplates.ts b/src/common/emails/Templates/EmailTemplates.ts new file mode 100644 index 00000000..28f7f6fc --- /dev/null +++ b/src/common/emails/Templates/EmailTemplates.ts @@ -0,0 +1,4 @@ +export const ETemplates = { + DOCUMENT_ASKED: "DOCUMENT_ASKED", + DOCUMENT_REFUSED: "DOCUMENT_REFUSED", +}; \ No newline at end of file diff --git a/src/common/repositories/EmailRepository.ts b/src/common/repositories/EmailRepository.ts new file mode 100644 index 00000000..82c9fa0f --- /dev/null +++ b/src/common/repositories/EmailRepository.ts @@ -0,0 +1,75 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { Emails, Prisma } from "prisma/prisma-client"; + +@Service() +export default class EmailRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().emails; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many emails + */ + public async findMany(query: Prisma.EmailsFindManyArgs) { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : Create an email + */ + public async create(email: Emails): Promise { + const createArgs: Prisma.EmailsCreateArgs = { + data: { + templateName: email.templateName, + from: email.from, + to: email.to, + subject: email.subject, + templateVariables: email.templateVariables!, + cc: email.cc, + cci: email.cci, + }, + }; + return this.model.create(createArgs); + } + + /** + * @description : update given email + */ + public async update(uid: string, email: Emails): Promise { + const updateArgs: Prisma.EmailsUpdateArgs = { + where: { + uid: uid, + }, + data: { + sentAt: email.sentAt, + nbTrySend: email.nbTrySend, + lastTrySendDate: email.lastTrySendDate, + }, + }; + + return this.model.update(updateArgs); + } + + /** + * @description : find unique email + */ + public async findOneByUid(uid: string) { + return this.model.findUnique({ + where: { + uid: uid, + }, + }); + } + + + +} diff --git a/src/entries/Cron.ts b/src/entries/Cron.ts new file mode 100644 index 00000000..5baa198f --- /dev/null +++ b/src/entries/Cron.ts @@ -0,0 +1,14 @@ +import "module-alias/register"; +import "reflect-metadata"; +import { Container } from "typedi"; +import CronService from "@Services/common/CronService/CronService"; + +(async () => { + try { + if(process.env["MAILCHIMP_API_KEY"] === "ppd"){ + Container.get(CronService).sendMails(); + } + } catch (e) { + console.error(e); + } +})(); diff --git a/src/services/common/CronService/CronService.ts b/src/services/common/CronService/CronService.ts new file mode 100644 index 00000000..321e0a91 --- /dev/null +++ b/src/services/common/CronService/CronService.ts @@ -0,0 +1,24 @@ +import { Service } from "typedi"; +import { CronJob } from "cron"; +import MailchimpService from "../MailchimpService/MailchimpService"; + + +@Service() +export default class CronService { + constructor(private mailchimpService: MailchimpService) {} + + public async sendMails() { + const cronJob = new CronJob("*/15 * * * * *", async () => { + try { + await this.mailchimpService.sendEmails(); + } catch (e) { + console.error(e); + } + }); + + // Start job + if (!cronJob.running) { + cronJob.start(); + } + } +} diff --git a/src/services/common/MailchimpService/MailchimpService.ts b/src/services/common/MailchimpService/MailchimpService.ts new file mode 100644 index 00000000..f3a8e11b --- /dev/null +++ b/src/services/common/MailchimpService/MailchimpService.ts @@ -0,0 +1,116 @@ +import EmailRepository from "@Repositories/EmailRepository"; +import BaseService from "@Services/BaseService"; +import { Emails } from "@prisma/client"; +import { Service } from "typedi"; +import MailchimpClient from "@mailchimp/mailchimp_transactional"; + +@Service() +export default class MailchimpService extends BaseService { + // private static readonly mailchimp = new Mailchimp(process.env.MAILCHIMP_API_KEY); + private static readonly from = "vincent.alamelle@smart-chain.fr"; + + constructor(private emailRepository: EmailRepository) { + super(); + } + + /** + * @description : Get all emails + * @throws {Error} If emails cannot be get + */ + public async get(query: any): Promise { + return this.emailRepository.findMany(query); + } + + /** + * @description : Create a new email + * @throws {Error} If email cannot be created + */ + public async create(emailEntity: Emails): Promise { + emailEntity.from = MailchimpService.from; + return this.emailRepository.create(emailEntity); + } + + /** + * @description : Modify an email + * @throws {Error} If email cannot be modified + */ + public async update(uid: string, emailEntity: Emails): Promise { + return this.emailRepository.update(uid, emailEntity); + } + + /** + * @description : Get a email by uid + * @throws {Error} If email cannot be get + */ + public async getByUid(uid: string, query?: any): Promise { + return this.emailRepository.findOneByUid(uid); + } + + /** + * @description : Function called by cron to send emails + * @throws {Error} If email cannot be sent + */ + public async sendEmails() { + const emailsToSend = await this.get({ where: { sentAt: null } }); + const currentDate = new Date(); + let nextTrySendDate = null; + + for (const email of emailsToSend) { + //If tries exceed 10, we stop trying to send the email + if (email.nbTrySend && email.nbTrySend > 9) continue; + + //If email has never been sent, we send it + if (email.nbTrySend == 0) { + nextTrySendDate = currentDate; + } + //If email has already been sent, we send it again every nbTrySend^2 minutes + else { + nextTrySendDate = new Date(email.lastTrySendDate!); + nextTrySendDate.setMinutes(nextTrySendDate.getMinutes() + Math.pow(email.nbTrySend!, 2)); + } + + //If the next try send date is passed, we send the email + if (currentDate >= nextTrySendDate) { + try { + await this.sendEmail(email); + email.sentAt = currentDate; + } catch (error) { + email.lastTrySendDate = currentDate; + email.nbTrySend = email.nbTrySend! + 1; + } + await this.update(email.uid, email); + } + } + } + + private async sendEmail(email: Emails) { + const apiKey = process.env["MAILCHIMP_API_KEY"]; + const mailchimpApiClient = MailchimpClient(apiKey!); + + await mailchimpApiClient.messages.sendTemplate({ + template_name: email.templateName, + template_content: [], + message: { + global_merge_vars: this.buildVariables(email.templateVariables), + from_email: email.from!, + from_name: "LeCoffre.io", + subject: email.subject, + to: [ + { + email: email.to, + type: "to", + }, + ], + }, + }); + } + + private buildVariables(templateVariables: any) { + return Object.keys(templateVariables).map((key) => { + return { + name: key, + content: templateVariables[key], + }; + }); + } +} diff --git a/tsconfig.json b/tsconfig.json index 88257ffd..bd89006e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -90,11 +90,11 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "allowJs": true, - "isolatedModules": true + "isolatedModules": true, }, "include": [ "**/*.ts", - "**/*.tsx", + "**/*.tsx", "src/services/common/TestService", ], "exclude": [ "node_modules"