print pdfs and send mails with ics file

This commit is contained in:
Julian Krauser 2024-12-30 14:47:00 +01:00
parent 5f827fb177
commit 728c4e05fa
12 changed files with 461 additions and 183 deletions

View file

@ -17,6 +17,8 @@ import { PdfExport } from "../../helpers/pdfExport";
import UserService from "../../service/userService"; import UserService from "../../service/userService";
import { TemplateHelper } from "../../helpers/templateHelper"; import { TemplateHelper } from "../../helpers/templateHelper";
import MailHelper from "../../helpers/mailHelper"; import MailHelper from "../../helpers/mailHelper";
import { NewsletterHelper } from "../../helpers/newsletterHelper";
import { Salutation } from "../../enums/salutation";
/** /**
* @description get all newsletters * @description get all newsletters
@ -89,8 +91,9 @@ export async function getNewsletterPrintoutsById(req: Request, res: Response): P
let newsletter = await NewsletterService.getById(newsletterId); let newsletter = await NewsletterService.getById(newsletterId);
let folderPath = FileSystemHelper.formatPath("export", "newsletter", `${newsletter.id}_${newsletter.title}`); let filesInFolder = FileSystemHelper.getFilesInDirectory(
let filesInFolder = FileSystemHelper.getFilesInDirectory(folderPath); `newsletter/${newsletter.id}_${newsletter.title.replace(" ", "")}`
);
res.json(filesInFolder); res.json(filesInFolder);
} }
@ -107,9 +110,13 @@ export async function getNewsletterPrintoutByIdAndPrint(req: Request, res: Respo
let newsletter = await NewsletterService.getById(newsletterId); 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: { headers: {
"Content-Type": "application/pdf", "Content-Type": "application/pdf",
}, },
@ -128,54 +135,15 @@ export async function createNewsletterPrintoutPreviewById(req: Request, res: Res
let dates = await NewsletterDatesService.getAll(newsletterId); let dates = await NewsletterDatesService.getAll(newsletterId);
let recipient = await UserService.getById(parseInt(req.userId)); let recipient = await UserService.getById(parseInt(req.userId));
let data = { let data = NewsletterHelper.buildData(newsletter, dates);
title: newsletter.title, data.recipient = {
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, firstname: recipient.firstname,
lastname: recipient.lastname, lastname: recipient.lastname,
salutation: "none", salutation: Salutation.none,
nameaffix: "", nameaffix: "",
street: "Straße", street: "Straße",
streetNumber: "Hausnummer", streetNumber: "Hausnummer",
streetNumberAdd: "Adresszusatz", streetNumberAdd: "Adresszusatz",
},
}; };
let pdf = await PdfExport.renderFile({ let pdf = await PdfExport.renderFile({
@ -219,15 +187,8 @@ export async function createNewsletter(req: Request, res: Response): Promise<any
*/ */
export async function createNewsletterPrintoutById(req: Request, res: Response): Promise<any> { export async function createNewsletterPrintoutById(req: Request, res: Response): Promise<any> {
let newsletterId = parseInt(req.params.newsletterId); 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 await NewsletterHelper.printPdfs(newsletterId);
// 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
res.sendStatus(204); res.sendStatus(204);
} }
@ -244,54 +205,15 @@ export async function createNewsletterMailPreviewById(req: Request, res: Respons
let dates = await NewsletterDatesService.getAll(newsletterId); let dates = await NewsletterDatesService.getAll(newsletterId);
let recipient = await UserService.getById(parseInt(req.userId)); let recipient = await UserService.getById(parseInt(req.userId));
let data = { let data = NewsletterHelper.buildData(newsletter, dates);
title: newsletter.title, data.recipient = {
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, firstname: recipient.firstname,
lastname: recipient.lastname, lastname: recipient.lastname,
salutation: "none", salutation: Salutation.none,
nameaffix: "", nameaffix: "",
street: "Straße", street: "Straße",
streetNumber: "Hausnummer", streetNumber: "Hausnummer",
streetNumberAdd: "Adresszusatz", streetNumberAdd: "Adresszusatz",
},
}; };
const { body } = await TemplateHelper.renderFileForModule({ 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<any> { export async function sendNewsletterById(req: Request, res: Response): Promise<any> {
let newsletterId = parseInt(req.params.newsletterId); 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); res.sendStatus(204);
} }

View file

@ -264,7 +264,7 @@ export async function createProtocolPrintoutById(req: Request, res: Response): P
let printout: CreateProtocolPrintoutCommand = { let printout: CreateProtocolPrintoutCommand = {
title, title,
iteration: iteration + 1, iteration: iteration + 1,
filename: FileSystemHelper.formatPath("protocol", filename), filename: FileSystemHelper.normalizePath("protocol", filename),
protocolId, protocolId,
}; };
await ProtocolPrintoutCommandHandler.create(printout); await ProtocolPrintoutCommandHandler.create(printout);

View file

@ -6,6 +6,7 @@ import { createEvents } from "ics";
import moment from "moment"; import moment from "moment";
import InternalException from "../exceptions/internalException"; import InternalException from "../exceptions/internalException";
import CalendarFactory from "../factory/admin/calendar"; import CalendarFactory from "../factory/admin/calendar";
import { CalendarHelper } from "../helpers/calendarHelper";
/** /**
* @description get all calendar items by types or nscdr * @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") { if (output == "json") {
res.json(CalendarFactory.mapToBase(items)); res.json(CalendarFactory.mapToBase(items));
} else { } else {
let events = createEvents( let { error, value } = CalendarHelper.buildICS(items);
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,
},
},
],
}))
);
res.type("ics").send(events.value); res.type("ics").send(value);
} }
} }

View file

@ -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 MAIL_SECURE = process.env.MAIL_SECURE ?? "false";
export const CLUB_NAME = process.env.CLUB_NAME ?? ""; export const CLUB_NAME = process.env.CLUB_NAME ?? "";
export const CLUB_WEBSITE = process.env.CLUB_WEBSITE ?? "";
export function configCheck() { export function configCheck() {
if (DB_TYPE != "mysql" && DB_TYPE != "sqlite") throw new Error("set valid value to DB_TYPE (mysql|sqlite)"); 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 (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 (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"); 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) { function checkMS(input: string, origin: string) {

View file

@ -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<calendar>): { 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,
},
},
],
}))
);
}
}

View file

@ -279,4 +279,91 @@ export default abstract class DynamicQueryBuilder {
return flattenedResults; 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<any> = [];
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,
};
}
}
}
} }

View file

@ -1,29 +1,43 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
import { join } from "path"; import { join } from "path";
import { readdirSync } from "fs"; import { readdirSync } from "fs";
export abstract class FileSystemHelper { export abstract class FileSystemHelper {
static createFolder(newFolder: string) { static createFolder(...args: string[]) {
const exportPath = join(process.cwd(), "export", newFolder); const exportPath = this.formatPath(...args);
if (!existsSync(exportPath)) { if (!existsSync(exportPath)) {
mkdirSync(exportPath, { recursive: true }); mkdirSync(exportPath, { recursive: true });
} }
} }
static readFile(filePath: string) { static readFile(...filePath: string[]) {
return readFileSync(join(process.cwd(), filePath), "utf8"); return readFileSync(this.formatPath(...filePath), "utf8");
} }
static writeFile(filePath: string, file: any) { static readFileasBase64(...filePath: string[]) {
writeFileSync(filePath, file); 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[]) { static formatPath(...args: string[]) {
return join(process.cwd(), "export", ...args);
}
static normalizePath(...args: string[]) {
return join(...args); return join(...args);
} }
static getFilesInDirectory(directoryPath: string, filetype?: string): string[] { static getFilesInDirectory(directoryPath: string, filetype?: string): string[] {
const fullPath = join(process.cwd(), directoryPath); const fullPath = this.formatPath(directoryPath);
if (!existsSync(fullPath)) { if (!existsSync(fullPath)) {
return []; return [];
} }
@ -31,4 +45,17 @@ export abstract class FileSystemHelper {
.filter((dirent) => !dirent.isDirectory() && (!filetype || dirent.name.endsWith(filetype))) .filter((dirent) => !dirent.isDirectory() && (!filetype || dirent.name.endsWith(filetype)))
.map((dirent) => dirent.name); .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);
});
}
} }

View file

@ -1,5 +1,6 @@
import { Transporter, createTransport, TransportOptions } from "nodemailer"; import { Transporter, createTransport, TransportOptions } from "nodemailer";
import { CLUB_NAME, MAIL_HOST, MAIL_PASSWORD, MAIL_PORT, MAIL_SECURE, MAIL_USERNAME } from "../env.defaults"; 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 { export default abstract class MailHelper {
private static readonly transporter: Transporter = createTransport({ private static readonly transporter: Transporter = createTransport({
@ -19,7 +20,12 @@ export default abstract class MailHelper {
* @param {string} content * @param {string} content
* @returns {Prmose<*>} * @returns {Prmose<*>}
*/ */
static async sendMail(target: string, subject: string, content: string): Promise<any> { static async sendMail(
target: string,
subject: string,
content: string,
attach: Array<Attachment> = []
): Promise<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.transporter this.transporter
.sendMail({ .sendMail({
@ -28,6 +34,7 @@ export default abstract class MailHelper {
subject, subject,
text: content, text: content,
html: content, html: content,
attachments: attach,
}) })
.then((info) => resolve(info.messageId)) .then((info) => resolve(info.messageId))
.catch((e) => reject(e)); .catch((e) => reject(e));

View file

@ -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<newsletterDates>,
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<newsletterRecipients>
): Promise<Array<member>> {
let useQuery = newsletter.recipientsByQuery?.query;
let queryMemberIds: Array<number> = [];
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);
});
}
}

View file

@ -39,7 +39,7 @@ export abstract class PdfExport {
const page = await browser.newPage(); const page = await browser.newPage();
await page.setContent(body, { waitUntil: "domcontentloaded" }); 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({ let pdf = await page.pdf({
...(saveToDisk ? { path: exportPath } : {}), ...(saveToDisk ? { path: exportPath } : {}),
@ -66,10 +66,12 @@ export abstract class PdfExport {
let pdfFilePaths = FileSystemHelper.getFilesInDirectory(inputFolder, ".pdf"); let pdfFilePaths = FileSystemHelper.getFilesInDirectory(inputFolder, ".pdf");
if (pdfFilePaths.length == 0) return;
const mergedPdf = await PDFDocument.create(); const mergedPdf = await PDFDocument.create();
for (const pdfPath of pdfFilePaths) { for (const pdfPath of pdfFilePaths) {
const pdfBytes = FileSystemHelper.readFile(pdfPath); const pdfBytes = FileSystemHelper.readFileasBase64(inputFolder, pdfPath);
const pdf = await PDFDocument.load(pdfBytes); const pdf = await PDFDocument.load(pdfBytes);
const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices()); const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
copiedPages.forEach((page) => mergedPdf.addPage(page)); copiedPages.forEach((page) => mergedPdf.addPage(page));
@ -77,8 +79,6 @@ export abstract class PdfExport {
const mergedPdfBytes = await mergedPdf.save(); const mergedPdfBytes = await mergedPdf.save();
const exportPath = FileSystemHelper.formatPath(process.cwd(), "export", outputFolder, `${outputFile}.pdf`); FileSystemHelper.writeFile(outputFolder, `${outputFile}.pdf`, mergedPdfBytes);
FileSystemHelper.writeFile(exportPath, mergedPdfBytes);
} }
} }

View file

@ -6,7 +6,7 @@ import { FileSystemHelper } from "./fileSystemHelper";
export abstract class TemplateHelper { export abstract class TemplateHelper {
static getTemplateFromFile(template: string) { 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<string> { static async getTemplateFromStore(templateId: number): Promise<string> {

View file

@ -1,4 +1,4 @@
<div style="font-size: 10pt; width: 100%; margin: 0 20px; padding-top: 5px; color: #888; border-top: 0.5px solid black"> <div style="font-size: 10pt; width: 100%; margin: 0 20px; padding-top: 5px; color: #888; border-top: 0.5px solid black">
{{recipient.lastname}}, {{recipient.firstname}}, {{recipient.street}} {{recipient.streetNumber}} {{recipient.lastname}}, {{recipient.firstname}}{{#if recipient.street}},{{/if}} {{recipient.street}}
{{recipient.streetNumberAdd}} {{recipient.streetNumber}} {{recipient.streetNumberAdd}}
</div> </div>