send or print newsletter preview
This commit is contained in:
parent
5a59a63e4a
commit
5f827fb177
15 changed files with 355 additions and 28 deletions
|
@ -13,7 +13,10 @@ import NewsletterDatesCommandHandler from "../../command/newsletterDatesCommandH
|
|||
import { SynchronizeNewsletterRecipientsCommand } from "../../command/newsletterRecipientsCommand";
|
||||
import NewsletterRecipientsCommandHandler from "../../command/newsletterRecipientsCommandHandler";
|
||||
import { NewsletterDatesViewModel } from "../../viewmodel/admin/newsletterDates.models";
|
||||
import { NewsletterRecipientsViewModel } from "../../viewmodel/admin/newsletterRecipients.models";
|
||||
import { PdfExport } from "../../helpers/pdfExport";
|
||||
import UserService from "../../service/userService";
|
||||
import { TemplateHelper } from "../../helpers/templateHelper";
|
||||
import MailHelper from "../../helpers/mailHelper";
|
||||
|
||||
/**
|
||||
* @description get all newsletters
|
||||
|
@ -113,6 +116,84 @@ export async function getNewsletterPrintoutByIdAndPrint(req: Request, res: Respo
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description create newsletter printout preview by id
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function createNewsletterPrintoutPreviewById(req: Request, res: Response): Promise<any> {
|
||||
let newsletterId = parseInt(req.params.newsletterId);
|
||||
let newsletter = await NewsletterService.getById(newsletterId);
|
||||
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 pdf = await PdfExport.renderFile({
|
||||
title: "Probedruck Newsletter",
|
||||
template: "newsletter",
|
||||
saveToDisk: false,
|
||||
data: data,
|
||||
});
|
||||
|
||||
let pdfbuffer = Buffer.from(pdf);
|
||||
|
||||
res.setHeader("Content-Type", "application/pdf");
|
||||
res.setHeader("Content-Length", pdfbuffer.byteLength);
|
||||
res.setHeader("Content-Disposition", "inline; filename=preview.pdf");
|
||||
|
||||
res.send(pdfbuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description create newsletter
|
||||
* @param req {Request} Express req object
|
||||
|
@ -131,7 +212,7 @@ export async function createNewsletter(req: Request, res: Response): Promise<any
|
|||
}
|
||||
|
||||
/**
|
||||
* @description create newsletter printout by id
|
||||
* @description create newsletter printouts for each member by id
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
|
@ -151,6 +232,96 @@ export async function createNewsletterPrintoutById(req: Request, res: Response):
|
|||
res.sendStatus(204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description create newsletter mail preview by id
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function createNewsletterMailPreviewById(req: Request, res: Response): Promise<any> {
|
||||
let newsletterId = parseInt(req.params.newsletterId);
|
||||
let newsletter = await NewsletterService.getById(newsletterId);
|
||||
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",
|
||||
},
|
||||
};
|
||||
|
||||
const { body } = await TemplateHelper.renderFileForModule({
|
||||
module: "newsletter",
|
||||
bodyData: data,
|
||||
title: "Probeversand Newsletter",
|
||||
});
|
||||
|
||||
await MailHelper.sendMail(recipient.mail, "Probeversand Newsletter", body);
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description send newsletter mail and create printouts by id
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function sendNewsletterById(req: Request, res: Response): Promise<any> {
|
||||
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
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description synchronize newsletter by id
|
||||
* @param req {Request} Express req object
|
||||
|
|
|
@ -40,7 +40,11 @@ export async function printTemplateUsageDemo(req: Request, res: Response): Promi
|
|||
const scope = req.params.scope as PermissionModule;
|
||||
|
||||
let demoData = DemoDataHelper.getData(scope);
|
||||
let pdf = await PdfExport.renderFile({ template: scope, saveToDisk: false, data: demoData });
|
||||
let pdf = await PdfExport.renderFile({
|
||||
template: scope,
|
||||
saveToDisk: false,
|
||||
data: demoData,
|
||||
});
|
||||
|
||||
let pdfbuffer = Buffer.from(pdf);
|
||||
|
||||
|
|
|
@ -146,8 +146,7 @@ export async function deleteUser(req: Request, res: Response): Promise<any> {
|
|||
|
||||
try {
|
||||
// sendmail
|
||||
let mailhelper = new MailHelper();
|
||||
await mailhelper.sendMail(
|
||||
await MailHelper.sendMail(
|
||||
user.mail,
|
||||
`Email Bestätigung für Mitglieder Admin-Portal von ${CLUB_NAME}`,
|
||||
`Ihr Nutzerkonto des Adminportals wurde erfolgreich gelöscht.`
|
||||
|
|
|
@ -71,8 +71,7 @@ export async function inviteUser(req: Request, res: Response, isInvite: boolean
|
|||
let token = await InviteCommandHandler.create(createInvite);
|
||||
|
||||
// sendmail
|
||||
let mailhelper = new MailHelper();
|
||||
await mailhelper.sendMail(
|
||||
await MailHelper.sendMail(
|
||||
mail,
|
||||
`Email Bestätigung für Mitglieder Admin-Portal von ${CLUB_NAME}`,
|
||||
`Öffne folgenden Link: ${origin}/${isInvite ? "invite" : "setup"}/verify?mail=${mail}&token=${token}`
|
||||
|
|
|
@ -41,8 +41,7 @@ export async function startReset(req: Request, res: Response): Promise<any> {
|
|||
let token = await ResetCommandHandler.create(createReset);
|
||||
|
||||
// sendmail
|
||||
let mailhelper = new MailHelper();
|
||||
await mailhelper.sendMail(
|
||||
await MailHelper.sendMail(
|
||||
mail,
|
||||
`Email Bestätigung für Mitglieder Admin-Portal von ${CLUB_NAME}`,
|
||||
`Öffne folgenden Link: ${origin}/reset/reset?mail=${mail}&token=${token}`
|
||||
|
|
72
src/demodata/newsletter.data.ts
Normal file
72
src/demodata/newsletter.data.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import { calendar } from "../entity/calendar";
|
||||
import { member } from "../entity/member";
|
||||
import { Salutation } from "../enums/salutation";
|
||||
|
||||
export const newsletterDemoData: {
|
||||
title: string;
|
||||
description: string;
|
||||
newsletterTitle: string;
|
||||
newsletterText: string;
|
||||
newsletterSignatur: string;
|
||||
dates: Array<
|
||||
Partial<
|
||||
calendar & {
|
||||
formattedStarttime: string;
|
||||
formattedFullStarttime: string;
|
||||
formattedEndtime: string;
|
||||
formattedFullEndtime: string;
|
||||
}
|
||||
>
|
||||
>;
|
||||
recipient: Partial<member & { street: string; streetNumber: string; streetNumberAdd: string }>;
|
||||
} = {
|
||||
title: "Beispiel Newsletter Daten",
|
||||
description: "Zusammenfassung der Demodaten.",
|
||||
newsletterTitle: "<h1>Sehr geehrtes Feuerwehrmitglied</h1>",
|
||||
newsletterText: "<p>zu folgenden Terminen möchten wir recht herzlich zur Teilnahme einladen:</p>",
|
||||
newsletterSignatur: "<p>Mit freundlichen Grüßen</p><p>...</p>",
|
||||
dates: [
|
||||
{
|
||||
title: "Termin 1",
|
||||
content: "<p>Beschreibung eines Termins</p>",
|
||||
starttime: new Date(),
|
||||
formattedStarttime: new Date().toLocaleDateString("de-DE", {
|
||||
weekday: "long",
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
}),
|
||||
formattedFullStarttime: new Date().toLocaleDateString("de-DE", {
|
||||
weekday: "long",
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}),
|
||||
endtime: new Date(),
|
||||
formattedEndtime: new Date().toLocaleDateString("de-DE", {
|
||||
weekday: "long",
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
}),
|
||||
formattedFullEndtime: new Date().toLocaleDateString("de-DE", {
|
||||
weekday: "long",
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}),
|
||||
location: "Feuerwehrhaus",
|
||||
},
|
||||
],
|
||||
recipient: {
|
||||
firstname: "Julian",
|
||||
lastname: "Krauser",
|
||||
salutation: Salutation.sir,
|
||||
nameaffix: "",
|
||||
street: "Straße",
|
||||
streetNumber: "Hausnummer",
|
||||
streetNumberAdd: "Adresszusatz",
|
||||
},
|
||||
};
|
|
@ -1,3 +1,4 @@
|
|||
import { newsletterDemoData } from "../demodata/newsletter.data";
|
||||
import { protocolDemoData } from "../demodata/protocol.data";
|
||||
import { PermissionModule } from "../type/permissionTypes";
|
||||
|
||||
|
@ -6,6 +7,8 @@ export abstract class DemoDataHelper {
|
|||
switch (scope) {
|
||||
case "protocol":
|
||||
return protocolDemoData;
|
||||
case "newsletter":
|
||||
return newsletterDemoData;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -24,6 +24,9 @@ export abstract class FileSystemHelper {
|
|||
|
||||
static getFilesInDirectory(directoryPath: string, filetype?: string): string[] {
|
||||
const fullPath = join(process.cwd(), directoryPath);
|
||||
if (!existsSync(fullPath)) {
|
||||
return [];
|
||||
}
|
||||
return readdirSync(fullPath, { withFileTypes: true })
|
||||
.filter((dirent) => !dirent.isDirectory() && (!filetype || dirent.name.endsWith(filetype)))
|
||||
.map((dirent) => dirent.name);
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
import { Transporter, createTransport, TransportOptions } from "nodemailer";
|
||||
import { CLUB_NAME, MAIL_HOST, MAIL_PASSWORD, MAIL_PORT, MAIL_SECURE, MAIL_USERNAME } from "../env.defaults";
|
||||
|
||||
export default class MailHelper {
|
||||
private readonly transporter: Transporter;
|
||||
|
||||
constructor() {
|
||||
this.transporter = createTransport({
|
||||
export default abstract class MailHelper {
|
||||
private static readonly transporter: Transporter = createTransport({
|
||||
host: MAIL_HOST,
|
||||
port: MAIL_PORT,
|
||||
secure: (MAIL_SECURE as "true" | "false") == "true",
|
||||
|
@ -14,7 +11,6 @@ export default class MailHelper {
|
|||
pass: MAIL_PASSWORD,
|
||||
},
|
||||
} as TransportOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description send mail
|
||||
|
@ -23,7 +19,7 @@ export default class MailHelper {
|
|||
* @param {string} content
|
||||
* @returns {Prmose<*>}
|
||||
*/
|
||||
async sendMail(target: string, subject: string, content: string): Promise<any> {
|
||||
static async sendMail(target: string, subject: string, content: string): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.transporter
|
||||
.sendMail({
|
||||
|
@ -31,6 +27,7 @@ export default class MailHelper {
|
|||
to: target,
|
||||
subject,
|
||||
text: content,
|
||||
html: content,
|
||||
})
|
||||
.then((info) => resolve(info.messageId))
|
||||
.catch((e) => reject(e));
|
||||
|
|
|
@ -26,7 +26,9 @@ export abstract class PdfExport {
|
|||
|
||||
const { header, footer, body } = await TemplateHelper.renderFileForModule({
|
||||
module: template,
|
||||
headerData: data,
|
||||
bodyData: data,
|
||||
footerData: data,
|
||||
title: title,
|
||||
});
|
||||
|
||||
|
|
|
@ -48,15 +48,15 @@ export abstract class TemplateHelper {
|
|||
|
||||
if (moduleTemplates.headerId) {
|
||||
header = await this.getTemplateFromStore(moduleTemplates.headerId);
|
||||
header = this.applyDataToTemplate(header, headerData);
|
||||
header = this.applyDataToTemplate(header, { title, ...headerData });
|
||||
}
|
||||
|
||||
if (moduleTemplates.footerId) {
|
||||
footer = await this.getTemplateFromStore(moduleTemplates.footerId);
|
||||
footer = this.applyDataToTemplate(footer, footerData);
|
||||
} else {
|
||||
footer = this.getTemplateFromFile(module + ".footer");
|
||||
}
|
||||
footer = this.applyDataToTemplate(footer, footerData);
|
||||
|
||||
if (moduleTemplates.bodyId) {
|
||||
body = await this.getTemplateFromStore(moduleTemplates.bodyId);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from "typeorm";
|
||||
import { DB_TYPE } from "../env.defaults";
|
||||
import { templateUsage } from "../entity/templateUsage";
|
||||
|
||||
export class Newsletter1735118780511 implements MigrationInterface {
|
||||
name = "Newsletter1735118780511";
|
||||
|
@ -102,9 +103,24 @@ export class Newsletter1735118780511 implements MigrationInterface {
|
|||
onUpdate: "RESTRICT",
|
||||
})
|
||||
);
|
||||
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(templateUsage)
|
||||
.values({ scope: "newsletter" })
|
||||
.orIgnore()
|
||||
.execute();
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(templateUsage)
|
||||
.where({ scope: "newsletter" })
|
||||
.execute();
|
||||
|
||||
const tableN = await queryRunner.getTable("newsletter");
|
||||
const tableNR = await queryRunner.getTable("newsletter_recipients");
|
||||
const tableND = await queryRunner.getTable("newsletter_dates");
|
||||
|
|
|
@ -11,6 +11,9 @@ import {
|
|||
synchronizeNewsletterDatesById,
|
||||
synchronizeNewsletterById,
|
||||
synchronizeNewsletterRecipientsById,
|
||||
sendNewsletterById,
|
||||
createNewsletterMailPreviewById,
|
||||
createNewsletterPrintoutPreviewById,
|
||||
} from "../../controller/admin/newsletterController";
|
||||
import PermissionHelper from "../../helpers/permissionHelper";
|
||||
|
||||
|
@ -40,6 +43,10 @@ router.get("/:newsletterId/printout/:filename", async (req: Request, res: Respon
|
|||
await getNewsletterPrintoutByIdAndPrint(req, res);
|
||||
});
|
||||
|
||||
router.get("/:newsletterId/printoutpreview", async (req: Request, res: Response) => {
|
||||
await createNewsletterPrintoutPreviewById(req, res);
|
||||
});
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
PermissionHelper.passCheckMiddleware("create", "club", "protocol"),
|
||||
|
@ -56,6 +63,18 @@ router.post(
|
|||
}
|
||||
);
|
||||
|
||||
router.post("/:newsletterId/mailpreview", async (req: Request, res: Response) => {
|
||||
await createNewsletterMailPreviewById(req, res);
|
||||
});
|
||||
|
||||
router.post(
|
||||
"/:newsletterId/send",
|
||||
PermissionHelper.passCheckMiddleware("create", "club", "protocol"),
|
||||
async (req: Request, res: Response) => {
|
||||
await sendNewsletterById(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/:id/synchronize",
|
||||
PermissionHelper.passCheckMiddleware("update", "club", "protocol"),
|
||||
|
@ -80,6 +99,4 @@ router.patch(
|
|||
}
|
||||
);
|
||||
|
||||
// TODO: send mails | send mail preview | render preview before print job
|
||||
|
||||
export default router;
|
||||
|
|
41
src/templates/newsletter.body.template.html
Normal file
41
src/templates/newsletter.body.template.html
Normal file
|
@ -0,0 +1,41 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Newsletter</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{{newsletterTitle}}}</h1>
|
||||
<p>{{{newsletterText}}}</p>
|
||||
<br />
|
||||
{{#each dates}}
|
||||
<div>
|
||||
<h2><b>{{this.formattedStarttime}}: {{this.title}}</b></h2>
|
||||
<span>{{{this.content}}}</span>
|
||||
</div>
|
||||
{{/each}}
|
||||
<br />
|
||||
<br />
|
||||
<p>{{{newsletterSignatur}}}</p>
|
||||
</body>
|
||||
<style>
|
||||
h2,
|
||||
h3,
|
||||
p,
|
||||
span,
|
||||
ul,
|
||||
li {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
color: #990b00;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
</style>
|
||||
</html>
|
4
src/templates/newsletter.footer.template.html
Normal file
4
src/templates/newsletter.footer.template.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
<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.streetNumberAdd}}
|
||||
</div>
|
Loading…
Reference in a new issue