print pdfs and send mails with ics file
This commit is contained in:
parent
5f827fb177
commit
728c4e05fa
12 changed files with 461 additions and 183 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
62
src/helpers/calendarHelper.ts
Normal file
62
src/helpers/calendarHelper.ts
Normal 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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
|
218
src/helpers/newsletterHelper.ts
Normal file
218
src/helpers/newsletterHelper.ts
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue