From 728c4e05fa0d82ed491ea6de60bd62baaf692ae7 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Mon, 30 Dec 2024 14:47:00 +0100 Subject: [PATCH] print pdfs and send mails with ics file --- src/controller/admin/newsletterController.ts | 143 +++--------- src/controller/admin/protocolController.ts | 2 +- src/controller/publicController.ts | 56 +---- src/env.defaults.ts | 8 + src/helpers/calendarHelper.ts | 62 +++++ src/helpers/dynamicQueryBuilder.ts | 87 +++++++ src/helpers/fileSystemHelper.ts | 43 +++- src/helpers/mailHelper.ts | 9 +- src/helpers/newsletterHelper.ts | 218 ++++++++++++++++++ src/helpers/pdfExport.ts | 10 +- src/helpers/templateHelper.ts | 2 +- src/templates/newsletter.footer.template.html | 4 +- 12 files changed, 461 insertions(+), 183 deletions(-) create mode 100644 src/helpers/calendarHelper.ts create mode 100644 src/helpers/newsletterHelper.ts diff --git a/src/controller/admin/newsletterController.ts b/src/controller/admin/newsletterController.ts index 6a19524..f8d0fb5 100644 --- a/src/controller/admin/newsletterController.ts +++ b/src/controller/admin/newsletterController.ts @@ -17,6 +17,8 @@ import { PdfExport } from "../../helpers/pdfExport"; import UserService from "../../service/userService"; import { TemplateHelper } from "../../helpers/templateHelper"; import MailHelper from "../../helpers/mailHelper"; +import { NewsletterHelper } from "../../helpers/newsletterHelper"; +import { Salutation } from "../../enums/salutation"; /** * @description get all newsletters @@ -89,8 +91,9 @@ export async function getNewsletterPrintoutsById(req: Request, res: Response): P let newsletter = await NewsletterService.getById(newsletterId); - let folderPath = FileSystemHelper.formatPath("export", "newsletter", `${newsletter.id}_${newsletter.title}`); - let filesInFolder = FileSystemHelper.getFilesInDirectory(folderPath); + let filesInFolder = FileSystemHelper.getFilesInDirectory( + `newsletter/${newsletter.id}_${newsletter.title.replace(" ", "")}` + ); res.json(filesInFolder); } @@ -107,9 +110,13 @@ export async function getNewsletterPrintoutByIdAndPrint(req: Request, res: Respo let newsletter = await NewsletterService.getById(newsletterId); - let filepath = FileSystemHelper.formatPath("export", "newsletter", `${newsletter.id}_${newsletter.title}`, filename); + let filepath = FileSystemHelper.formatPath( + "newsletter", + `${newsletter.id}_${newsletter.title.replace(" ", "")}`, + filename + ); - res.sendFile(process.cwd() + filepath, { + res.sendFile(filepath, { headers: { "Content-Type": "application/pdf", }, @@ -128,54 +135,15 @@ export async function createNewsletterPrintoutPreviewById(req: Request, res: Res let dates = await NewsletterDatesService.getAll(newsletterId); let recipient = await UserService.getById(parseInt(req.userId)); - let data = { - title: newsletter.title, - description: newsletter.description, - newsletterTitle: newsletter.newsletterTitle, - newsletterText: newsletter.newsletterText, - newsletterSignatur: newsletter.newsletterSignatur, - dates: dates.map((d) => ({ - title: d.diffTitle ?? d.calendar.title, - content: d.diffDescription ?? d.calendar.content, - starttime: d.calendar.starttime, - formattedStarttime: new Date(d.calendar.starttime).toLocaleDateString("de-DE", { - weekday: "long", - day: "2-digit", - month: "long", - }), - formattedFullStarttime: new Date(d.calendar.starttime).toLocaleDateString("de-DE", { - weekday: "long", - day: "2-digit", - month: "long", - year: "numeric", - hour: "2-digit", - minute: "2-digit", - }), - endtime: d.calendar.endtime, - formattedEndtime: new Date(d.calendar.endtime).toLocaleDateString("de-DE", { - weekday: "long", - day: "2-digit", - month: "long", - }), - formattedFullEndtime: new Date(d.calendar.endtime).toLocaleDateString("de-DE", { - weekday: "long", - day: "2-digit", - month: "long", - year: "numeric", - hour: "2-digit", - minute: "2-digit", - }), - location: d.calendar.location, - })), - recipient: { - firstname: recipient.firstname, - lastname: recipient.lastname, - salutation: "none", - nameaffix: "", - street: "Straße", - streetNumber: "Hausnummer", - streetNumberAdd: "Adresszusatz", - }, + let data = NewsletterHelper.buildData(newsletter, dates); + data.recipient = { + firstname: recipient.firstname, + lastname: recipient.lastname, + salutation: Salutation.none, + nameaffix: "", + street: "Straße", + streetNumber: "Hausnummer", + streetNumberAdd: "Adresszusatz", }; let pdf = await PdfExport.renderFile({ @@ -219,15 +187,8 @@ export async function createNewsletter(req: Request, res: Response): Promise { let newsletterId = parseInt(req.params.newsletterId); - let newsletter = await NewsletterService.getById(newsletterId); - let dates = await NewsletterDatesService.getAll(newsletterId); - let recipients = await NewsletterRecipientsService.getAll(newsletterId); - // print newsletter pdf for every member having newsletter type configured to print or if all members get printout - // check if all users have mail or adress - // squash all files to single for printing - // use Helper for Newsletter printing and mail sending - // default template + await NewsletterHelper.printPdfs(newsletterId); res.sendStatus(204); } @@ -244,54 +205,15 @@ export async function createNewsletterMailPreviewById(req: Request, res: Respons let dates = await NewsletterDatesService.getAll(newsletterId); let recipient = await UserService.getById(parseInt(req.userId)); - let data = { - title: newsletter.title, - description: newsletter.description, - newsletterTitle: newsletter.newsletterTitle, - newsletterText: newsletter.newsletterText, - newsletterSignatur: newsletter.newsletterSignatur, - dates: dates.map((d) => ({ - title: d.diffTitle ?? d.calendar.title, - content: d.diffDescription ?? d.calendar.content, - starttime: d.calendar.starttime, - formattedStarttime: new Date(d.calendar.starttime).toLocaleDateString("de-DE", { - weekday: "long", - day: "2-digit", - month: "long", - }), - formattedFullStarttime: new Date(d.calendar.starttime).toLocaleDateString("de-DE", { - weekday: "long", - day: "2-digit", - month: "long", - year: "numeric", - hour: "2-digit", - minute: "2-digit", - }), - endtime: d.calendar.endtime, - formattedEndtime: new Date(d.calendar.endtime).toLocaleDateString("de-DE", { - weekday: "long", - day: "2-digit", - month: "long", - }), - formattedFullEndtime: new Date(d.calendar.endtime).toLocaleDateString("de-DE", { - weekday: "long", - day: "2-digit", - month: "long", - year: "numeric", - hour: "2-digit", - minute: "2-digit", - }), - location: d.calendar.location, - })), - recipient: { - firstname: recipient.firstname, - lastname: recipient.lastname, - salutation: "none", - nameaffix: "", - street: "Straße", - streetNumber: "Hausnummer", - streetNumberAdd: "Adresszusatz", - }, + let data = NewsletterHelper.buildData(newsletter, dates); + data.recipient = { + firstname: recipient.firstname, + lastname: recipient.lastname, + salutation: Salutation.none, + nameaffix: "", + street: "Straße", + streetNumber: "Hausnummer", + streetNumberAdd: "Adresszusatz", }; const { body } = await TemplateHelper.renderFileForModule({ @@ -313,11 +235,8 @@ export async function createNewsletterMailPreviewById(req: Request, res: Respons */ export async function sendNewsletterById(req: Request, res: Response): Promise { let newsletterId = parseInt(req.params.newsletterId); - let newsletter = await NewsletterService.getById(newsletterId); - let dates = await NewsletterDatesService.getAll(newsletterId); - let recipients = await NewsletterRecipientsService.getAll(newsletterId); - // attach ics files for date entries to mail + await NewsletterHelper.sendMails(newsletterId); res.sendStatus(204); } diff --git a/src/controller/admin/protocolController.ts b/src/controller/admin/protocolController.ts index 345a42b..a82608f 100644 --- a/src/controller/admin/protocolController.ts +++ b/src/controller/admin/protocolController.ts @@ -264,7 +264,7 @@ export async function createProtocolPrintoutById(req: Request, res: Response): P let printout: CreateProtocolPrintoutCommand = { title, iteration: iteration + 1, - filename: FileSystemHelper.formatPath("protocol", filename), + filename: FileSystemHelper.normalizePath("protocol", filename), protocolId, }; await ProtocolPrintoutCommandHandler.create(printout); diff --git a/src/controller/publicController.ts b/src/controller/publicController.ts index 15c5d73..d933522 100644 --- a/src/controller/publicController.ts +++ b/src/controller/publicController.ts @@ -6,6 +6,7 @@ import { createEvents } from "ics"; import moment from "moment"; import InternalException from "../exceptions/internalException"; import CalendarFactory from "../factory/admin/calendar"; +import { CalendarHelper } from "../helpers/calendarHelper"; /** * @description get all calendar items by types or nscdr @@ -45,59 +46,8 @@ export async function getCalendarItemsByTypes(req: Request, res: Response): Prom if (output == "json") { res.json(CalendarFactory.mapToBase(items)); } else { - let events = createEvents( - items.map((i) => ({ - calName: process.env.CLUB_NAME, - uid: i.id, - sequence: 1, - ...(i.allDay - ? { - start: moment(i.starttime) - .format("YYYY-M-D") - .split("-") - .map((a) => parseInt(a)) as [number, number, number], - end: moment(i.endtime) - .format("YYYY-M-D") - .split("-") - .map((a) => parseInt(a)) as [number, number, number], - } - : { - start: moment(i.starttime) - .format("YYYY-M-D-H-m") - .split("-") - .map((a) => parseInt(a)) as [number, number, number, number, number], - end: moment(i.endtime) - .format("YYYY-M-D-H-m") - .split("-") - .map((a) => parseInt(a)) as [number, number, number, number, number], - }), - title: i.title, - description: i.content, - location: i.location, - categories: [i.type.type], - created: moment(i.createdAt) - .format("YYYY-M-D-H-m") - .split("-") - .map((a) => parseInt(a)) as [number, number, number, number, number], - lastModified: moment(i.updatedAt) - .format("YYYY-M-D-H-m") - .split("-") - .map((a) => parseInt(a)) as [number, number, number, number, number], - transp: "OPAQUE" as "OPAQUE", - url: "https://www.ff-merching.de", - alarms: [ - { - action: "display", - description: "Erinnerung", - trigger: { - minutes: 30, - before: true, - }, - }, - ], - })) - ); + let { error, value } = CalendarHelper.buildICS(items); - res.type("ics").send(events.value); + res.type("ics").send(value); } } diff --git a/src/env.defaults.ts b/src/env.defaults.ts index 2ad0925..21c9430 100644 --- a/src/env.defaults.ts +++ b/src/env.defaults.ts @@ -20,6 +20,7 @@ export const MAIL_PORT = Number(process.env.MAIL_PORT ?? "587"); export const MAIL_SECURE = process.env.MAIL_SECURE ?? "false"; export const CLUB_NAME = process.env.CLUB_NAME ?? ""; +export const CLUB_WEBSITE = process.env.CLUB_WEBSITE ?? ""; export function configCheck() { if (DB_TYPE != "mysql" && DB_TYPE != "sqlite") throw new Error("set valid value to DB_TYPE (mysql|sqlite)"); @@ -39,6 +40,13 @@ export function configCheck() { if (MAIL_HOST == "" || typeof MAIL_HOST != "string") throw new Error("set valid value to MAIL_HOST"); if (typeof MAIL_PORT != "number") throw new Error("set valid numeric value to MAIL_PORT"); if (MAIL_SECURE != "true" && MAIL_SECURE != "false") throw new Error("set 'true' or 'false' to MAIL_SECURE"); + + console.log(CLUB_WEBSITE); + if ( + CLUB_WEBSITE != "" && + !/^(http(s):\/\/.)[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/.test(CLUB_WEBSITE) + ) + throw new Error("CLUB_WEBSITE is not valid url"); } function checkMS(input: string, origin: string) { diff --git a/src/helpers/calendarHelper.ts b/src/helpers/calendarHelper.ts new file mode 100644 index 0000000..86409be --- /dev/null +++ b/src/helpers/calendarHelper.ts @@ -0,0 +1,62 @@ +import { createEvents } from "ics"; +import { calendar } from "../entity/calendar"; +import moment from "moment"; +import { CLUB_NAME, CLUB_WEBSITE, MAIL_USERNAME } from "../env.defaults"; + +export abstract class CalendarHelper { + public static buildICS(entries: Array): { error?: Error; value?: string } { + return createEvents( + entries.map((i) => ({ + calName: process.env.CLUB_NAME, + uid: i.id, + sequence: i.sequence, + ...(i.allDay + ? { + start: moment(i.starttime) + .format("YYYY-M-D") + .split("-") + .map((a) => parseInt(a)) as [number, number, number], + end: moment(i.endtime) + .format("YYYY-M-D") + .split("-") + .map((a) => parseInt(a)) as [number, number, number], + } + : { + start: moment(i.starttime) + .format("YYYY-M-D-H-m") + .split("-") + .map((a) => parseInt(a)) as [number, number, number, number, number], + end: moment(i.endtime) + .format("YYYY-M-D-H-m") + .split("-") + .map((a) => parseInt(a)) as [number, number, number, number, number], + }), + title: i.title, + description: i.content, + location: i.location, + categories: [i.type.type], + organizer: { name: CLUB_NAME, email: MAIL_USERNAME }, + created: moment(i.createdAt) + .format("YYYY-M-D-H-m") + .split("-") + .map((a) => parseInt(a)) as [number, number, number, number, number], + lastModified: moment(i.updatedAt) + .format("YYYY-M-D-H-m") + .split("-") + .map((a) => parseInt(a)) as [number, number, number, number, number], + transp: "OPAQUE" as "OPAQUE", + ...(CLUB_WEBSITE != "" ? { url: CLUB_WEBSITE } : {}), + alarms: [ + { + action: "display", + description: "Erinnerung", + trigger: { + minutes: 30, + before: true, + }, + }, + ], + })) + ); + } +} diff --git a/src/helpers/dynamicQueryBuilder.ts b/src/helpers/dynamicQueryBuilder.ts index 1dc1b94..79930fd 100644 --- a/src/helpers/dynamicQueryBuilder.ts +++ b/src/helpers/dynamicQueryBuilder.ts @@ -279,4 +279,91 @@ export default abstract class DynamicQueryBuilder { return flattenedResults; } + + public static async executeQuery( + query: string | DynamicQueryStructure, + offset: number, + count: number + ): Promise< + | { + stats: "error"; + sql: string; + code: string; + msg: string; + } + | { + stats: "success"; + rows: Array<{ [key: string]: FieldType }>; + total: number; + offset: number; + count: number; + } + > { + if (typeof query == "string") { + const upperQuery = query.trim().toUpperCase(); + if (!upperQuery.startsWith("SELECT") || /INSERT|UPDATE|DELETE|ALTER|DROP|CREATE|TRUNCATE/.test(upperQuery)) { + return { + stats: "error", + sql: query, + code: "UNALLOWED", + msg: "Not allowed to change rows", + }; + } + + try { + let data: Array = []; + + return await dataSource + .transaction(async (manager) => { + data = await manager.query(query); + + throw new Error("AllwaysRollbackQuery"); + }) + .catch((error) => { + if (error.message === "AllwaysRollbackQuery") { + return { + stats: "success", + rows: data, + total: data.length, + offset: offset, + count: count, + }; + } else { + return { + stats: "error", + sql: error.sql, + code: error.code, + msg: error.sqlMessage, + }; + } + }); + } catch (error) { + return { + stats: "error", + sql: error.sql, + code: error.code, + msg: error.sqlMessage, + }; + } + } else { + try { + let [rows, total] = await this.buildQuery(query, offset, count).getManyAndCount(); + + return { + stats: "success", + rows: this.flattenQueryResult(rows), + total: total, + offset: offset, + count: count, + }; + } catch (error) { + return { + stats: "error", + sql: error.sql, + code: error.code, + msg: error.sqlMessage, + }; + } + } + } } diff --git a/src/helpers/fileSystemHelper.ts b/src/helpers/fileSystemHelper.ts index ad7f33f..5813f6a 100644 --- a/src/helpers/fileSystemHelper.ts +++ b/src/helpers/fileSystemHelper.ts @@ -1,29 +1,43 @@ -import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; +import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs"; import { join } from "path"; import { readdirSync } from "fs"; export abstract class FileSystemHelper { - static createFolder(newFolder: string) { - const exportPath = join(process.cwd(), "export", newFolder); + static createFolder(...args: string[]) { + const exportPath = this.formatPath(...args); if (!existsSync(exportPath)) { mkdirSync(exportPath, { recursive: true }); } } - static readFile(filePath: string) { - return readFileSync(join(process.cwd(), filePath), "utf8"); + static readFile(...filePath: string[]) { + return readFileSync(this.formatPath(...filePath), "utf8"); } - static writeFile(filePath: string, file: any) { - writeFileSync(filePath, file); + static readFileasBase64(...filePath: string[]) { + return readFileSync(this.formatPath(...filePath), "base64"); + } + + static readTemplateFile(filePath: string) { + return readFileSync(process.cwd() + filePath, "utf8"); + } + + static writeFile(filePath: string, filename: string, file: any) { + this.createFolder(filePath); + let path = this.formatPath(filePath, filename); + writeFileSync(path, file); } static formatPath(...args: string[]) { + return join(process.cwd(), "export", ...args); + } + + static normalizePath(...args: string[]) { return join(...args); } static getFilesInDirectory(directoryPath: string, filetype?: string): string[] { - const fullPath = join(process.cwd(), directoryPath); + const fullPath = this.formatPath(directoryPath); if (!existsSync(fullPath)) { return []; } @@ -31,4 +45,17 @@ export abstract class FileSystemHelper { .filter((dirent) => !dirent.isDirectory() && (!filetype || dirent.name.endsWith(filetype))) .map((dirent) => dirent.name); } + + static clearDirectoryByFiletype(directoryPath: string, filetype: string) { + const fullPath = this.formatPath(directoryPath); + if (!existsSync(fullPath)) { + return; + } + readdirSync(fullPath, { withFileTypes: true }) + .filter((dirent) => !dirent.isDirectory() && dirent.name.endsWith(filetype)) + .forEach((dirent) => { + const filePath = join(fullPath, dirent.name); + unlinkSync(filePath); + }); + } } diff --git a/src/helpers/mailHelper.ts b/src/helpers/mailHelper.ts index 5414673..ab44a26 100644 --- a/src/helpers/mailHelper.ts +++ b/src/helpers/mailHelper.ts @@ -1,5 +1,6 @@ import { Transporter, createTransport, TransportOptions } from "nodemailer"; import { CLUB_NAME, MAIL_HOST, MAIL_PASSWORD, MAIL_PORT, MAIL_SECURE, MAIL_USERNAME } from "../env.defaults"; +import { Attachment } from "nodemailer/lib/mailer"; export default abstract class MailHelper { private static readonly transporter: Transporter = createTransport({ @@ -19,7 +20,12 @@ export default abstract class MailHelper { * @param {string} content * @returns {Prmose<*>} */ - static async sendMail(target: string, subject: string, content: string): Promise { + static async sendMail( + target: string, + subject: string, + content: string, + attach: Array = [] + ): Promise { return new Promise((resolve, reject) => { this.transporter .sendMail({ @@ -28,6 +34,7 @@ export default abstract class MailHelper { subject, text: content, html: content, + attachments: attach, }) .then((info) => resolve(info.messageId)) .catch((e) => reject(e)); diff --git a/src/helpers/newsletterHelper.ts b/src/helpers/newsletterHelper.ts new file mode 100644 index 0000000..8ed7450 --- /dev/null +++ b/src/helpers/newsletterHelper.ts @@ -0,0 +1,218 @@ +import Mail from "nodemailer/lib/mailer"; +import { member } from "../entity/member"; +import { newsletter } from "../entity/newsletter"; +import { newsletterDates } from "../entity/newsletterDates"; +import { newsletterRecipients } from "../entity/newsletterRecipients"; +import MemberService from "../service/memberService"; +import NewsletterDatesService from "../service/newsletterDatesService"; +import NewsletterRecipientsService from "../service/newsletterRecipientsService"; +import NewsletterService from "../service/newsletterService"; +import { CalendarHelper } from "./calendarHelper"; +import DynamicQueryBuilder from "./dynamicQueryBuilder"; +import { FileSystemHelper } from "./fileSystemHelper"; +import MailHelper from "./mailHelper"; +import { CLUB_NAME } from "../env.defaults"; +import { TemplateHelper } from "./templateHelper"; +import { PdfExport } from "./pdfExport"; +import NewsletterConfigService from "../service/newsletterConfigService"; +import { NewsletterConfigType } from "../enums/newsletterConfigType"; +import InternalException from "../exceptions/internalException"; + +export abstract class NewsletterHelper { + public static buildData( + newsletter: newsletter, + dates: Array, + recipient?: member, + showAdress: boolean = false + ) { + return { + title: newsletter.title, + description: newsletter.description, + newsletterTitle: newsletter.newsletterTitle, + newsletterText: newsletter.newsletterText, + newsletterSignatur: newsletter.newsletterSignatur, + dates: dates.map((d) => ({ + title: d.diffTitle ?? d.calendar.title, + content: d.diffDescription ?? d.calendar.content, + starttime: d.calendar.starttime, + formattedStarttime: new Date(d.calendar.starttime).toLocaleDateString("de-DE", { + weekday: "long", + day: "2-digit", + month: "long", + }), + formattedFullStarttime: new Date(d.calendar.starttime).toLocaleDateString("de-DE", { + weekday: "long", + day: "2-digit", + month: "long", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + }), + endtime: d.calendar.endtime, + formattedEndtime: new Date(d.calendar.endtime).toLocaleDateString("de-DE", { + weekday: "long", + day: "2-digit", + month: "long", + }), + formattedFullEndtime: new Date(d.calendar.endtime).toLocaleDateString("de-DE", { + weekday: "long", + day: "2-digit", + month: "long", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + }), + location: d.calendar.location, + })), + ...(recipient + ? { + recipient: { + firstname: recipient.firstname, + lastname: recipient.lastname, + salutation: recipient.salutation, + nameaffix: recipient.nameaffix, + ...(showAdress + ? { + street: recipient.sendNewsletter.street ?? "", + streetNumber: recipient.sendNewsletter.streetNumber ?? "", + streetNumberAdd: recipient.sendNewsletter.streetNumberAddition ?? "", + } + : {}), + }, + } + : {}), + }; + } + + public static async transformRecipientsToMembers( + newsletter: newsletter, + recipients: Array + ): Promise> { + let useQuery = newsletter.recipientsByQuery?.query; + + let queryMemberIds: Array = []; + if (useQuery) { + let result = await DynamicQueryBuilder.executeQuery( + useQuery.startsWith("{") ? JSON.parse(useQuery) : useQuery, + 0, + 1000 + ); + if (result.stats == "success") { + let keys = Object.keys(result.rows?.[0] ?? {}); + let memberKey = keys.find((k) => k.includes("member_id")); + queryMemberIds = result.rows.map((t) => parseInt((t[memberKey] ?? t.id) as string)); + } + } + + for (let recipient of recipients) { + if (!queryMemberIds.includes(recipient.memberId)) { + queryMemberIds.push(recipient.memberId); + } + } + + console.log(queryMemberIds); + + let members = await MemberService.getAll(0, 1000); + + return members[0].filter((m) => queryMemberIds.includes(m.id)); + } + + public static getICSFilePath(newsletter: newsletter) { + return FileSystemHelper.formatPath( + "newsletter", + `${newsletter.id}_${newsletter.title.replace(" ", "")}`, + `events.ics` + ); + } + + public static saveIcsToFile(newsletter: newsletter, ics: string) { + FileSystemHelper.writeFile(`newsletter/${newsletter.id}_${newsletter.title.replace(" ", "")}`, "events.ics", ics); + } + + public static async sendMails(newsletterId: number) { + let newsletter = await NewsletterService.getById(newsletterId); + let dates = await NewsletterDatesService.getAll(newsletterId); + let recipients = await NewsletterRecipientsService.getAll(newsletterId); + let config = await NewsletterConfigService.getAll(); + + const { value, error } = CalendarHelper.buildICS(dates.map((r) => r.calendar)); + if (error) throw new InternalException("Failed Building ICS form Mail", error); + this.saveIcsToFile(newsletter, value); + + let allowedForMail = config.filter((c) => c.config == NewsletterConfigType.mail).map((c) => c.comTypeId); + + const members = await this.transformRecipientsToMembers(newsletter, recipients); + const mailRecipients = members.filter( + (m) => + m.sendNewsletter != null && + m.sendNewsletter?.email != null && + allowedForMail.includes(m.sendNewsletter?.type?.id) + ); + + for (const rec of mailRecipients) { + let data = this.buildData(newsletter, dates, rec); + const { body } = await TemplateHelper.renderFileForModule({ + module: "newsletter", + bodyData: data, + title: `Newsletter von ${CLUB_NAME}`, + }); + await MailHelper.sendMail(rec.sendNewsletter.email, `Newsletter von ${CLUB_NAME}`, body, [ + { filename: "events.ics", path: this.getICSFilePath(newsletter) }, + ]) + .then(() => {}) + .catch((err) => { + console.log("mail send", err); + }); + } + } + + public static async printPdfs(newsletterId: number) { + let newsletter = await NewsletterService.getById(newsletterId); + let dates = await NewsletterDatesService.getAll(newsletterId); + let recipients = await NewsletterRecipientsService.getAll(newsletterId); + let config = await NewsletterConfigService.getAll(); + + FileSystemHelper.clearDirectoryByFiletype( + `newsletter/${newsletter.id}_${newsletter.title.replace(" ", "")}`, + ".pdf" + ); + + const { value, error } = CalendarHelper.buildICS(dates.map((r) => r.calendar)); + if (error) throw new InternalException("Failed Building ICS form Pdf", error); + this.saveIcsToFile(newsletter, value); + + let notAllowedForPdf = config.filter((c) => c.config == NewsletterConfigType.mail).map((c) => c.comTypeId); + let printWithAdress = config.filter((c) => c.config == NewsletterConfigType.pdf).map((c) => c.comTypeId); + + const members = await this.transformRecipientsToMembers(newsletter, recipients); + const pdfRecipients = members.filter( + (m) => !notAllowedForPdf.includes(m.sendNewsletter?.type?.id) || m.sendNewsletter == null + ); + + for (const rec of pdfRecipients) { + let data = this.buildData(newsletter, dates, rec, printWithAdress.includes(rec.sendNewsletter?.type?.id)); + + await PdfExport.renderFile({ + template: "newsletter", + title: `Newsletter von ${CLUB_NAME}`, + filename: `${rec.lastname}_${rec.firstname}_${rec.id}`, + folder: `newsletter/${newsletter.id}_${newsletter.title.replace(" ", "")}`, + data: data, + }) + .then(() => {}) + .catch((err) => { + console.log("pdf print", err); + }); + } + + await PdfExport.sqashToSingleFile( + `newsletter/${newsletter.id}_${newsletter.title.replace(" ", "")}`, + "allPdfsTogether", + `newsletter/${newsletter.id}_${newsletter.title.replace(" ", "")}` + ) + .then(() => {}) + .catch((err) => { + console.log("pdf squash", err); + }); + } +} diff --git a/src/helpers/pdfExport.ts b/src/helpers/pdfExport.ts index fa0875e..26a32c2 100644 --- a/src/helpers/pdfExport.ts +++ b/src/helpers/pdfExport.ts @@ -39,7 +39,7 @@ export abstract class PdfExport { const page = await browser.newPage(); await page.setContent(body, { waitUntil: "domcontentloaded" }); - const exportPath = FileSystemHelper.formatPath(process.cwd(), "export", folder, `${filename}.pdf`); + const exportPath = FileSystemHelper.formatPath(folder, `${filename}.pdf`); let pdf = await page.pdf({ ...(saveToDisk ? { path: exportPath } : {}), @@ -66,10 +66,12 @@ export abstract class PdfExport { let pdfFilePaths = FileSystemHelper.getFilesInDirectory(inputFolder, ".pdf"); + if (pdfFilePaths.length == 0) return; + const mergedPdf = await PDFDocument.create(); for (const pdfPath of pdfFilePaths) { - const pdfBytes = FileSystemHelper.readFile(pdfPath); + const pdfBytes = FileSystemHelper.readFileasBase64(inputFolder, pdfPath); const pdf = await PDFDocument.load(pdfBytes); const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices()); copiedPages.forEach((page) => mergedPdf.addPage(page)); @@ -77,8 +79,6 @@ export abstract class PdfExport { const mergedPdfBytes = await mergedPdf.save(); - const exportPath = FileSystemHelper.formatPath(process.cwd(), "export", outputFolder, `${outputFile}.pdf`); - - FileSystemHelper.writeFile(exportPath, mergedPdfBytes); + FileSystemHelper.writeFile(outputFolder, `${outputFile}.pdf`, mergedPdfBytes); } } diff --git a/src/helpers/templateHelper.ts b/src/helpers/templateHelper.ts index 3f49961..cc1567b 100644 --- a/src/helpers/templateHelper.ts +++ b/src/helpers/templateHelper.ts @@ -6,7 +6,7 @@ import { FileSystemHelper } from "./fileSystemHelper"; export abstract class TemplateHelper { static getTemplateFromFile(template: string) { - return FileSystemHelper.readFile(`/src/templates/${template}.template.html`); + return FileSystemHelper.readTemplateFile(`/src/templates/${template}.template.html`); } static async getTemplateFromStore(templateId: number): Promise { diff --git a/src/templates/newsletter.footer.template.html b/src/templates/newsletter.footer.template.html index 68f9f47..0c4baa8 100644 --- a/src/templates/newsletter.footer.template.html +++ b/src/templates/newsletter.footer.template.html @@ -1,4 +1,4 @@
- {{recipient.lastname}}, {{recipient.firstname}}, {{recipient.street}} {{recipient.streetNumber}} - {{recipient.streetNumberAdd}} + {{recipient.lastname}}, {{recipient.firstname}}{{#if recipient.street}},{{/if}} {{recipient.street}} + {{recipient.streetNumber}} {{recipient.streetNumberAdd}}