newsletter CRUD & pdf
This commit is contained in:
parent
e9b29f8acf
commit
01ce3fdd39
31 changed files with 1185 additions and 23 deletions
43
package-lock.json
generated
43
package-lock.json
generated
|
@ -20,6 +20,7 @@
|
|||
"mysql": "^2.18.1",
|
||||
"node-schedule": "^2.1.1",
|
||||
"nodemailer": "^6.9.14",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"puppeteer": "^23.11.1",
|
||||
"qrcode": "^1.5.4",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
|
@ -177,6 +178,24 @@
|
|||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@pdf-lib/standard-fonts": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz",
|
||||
"integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pako": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@pdf-lib/upng": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz",
|
||||
"integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pako": "^1.0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
|
@ -2666,6 +2685,12 @@
|
|||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
|
||||
"integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw=="
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||
"license": "(MIT AND Zlib)"
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
|
@ -2766,6 +2791,24 @@
|
|||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz",
|
||||
"integrity": "sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA=="
|
||||
},
|
||||
"node_modules/pdf-lib": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz",
|
||||
"integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@pdf-lib/standard-fonts": "^1.0.0",
|
||||
"@pdf-lib/upng": "^1.0.1",
|
||||
"pako": "^1.0.11",
|
||||
"tslib": "^1.11.1"
|
||||
}
|
||||
},
|
||||
"node_modules/pdf-lib/node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/pend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"mysql": "^2.18.1",
|
||||
"node-schedule": "^2.1.1",
|
||||
"nodemailer": "^6.9.14",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"puppeteer": "^23.11.1",
|
||||
"qrcode": "^1.5.4",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
|
|
18
src/command/newsletterCommand.ts
Normal file
18
src/command/newsletterCommand.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
export interface CreateNewsletterCommand {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface SynchronizeNewsletterCommand {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
newsletterTitle: string;
|
||||
newsletterText: string;
|
||||
newsletterSignatur: string;
|
||||
recipientsByQueryId?: number;
|
||||
}
|
||||
|
||||
export interface SendNewsletterCommand {
|
||||
id: number;
|
||||
isSent: boolean;
|
||||
}
|
73
src/command/newsletterCommandHandler.ts
Normal file
73
src/command/newsletterCommandHandler.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
import { dataSource } from "../data-source";
|
||||
import { newsletter } from "../entity/newsletter";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
import { CreateNewsletterCommand, SendNewsletterCommand, SynchronizeNewsletterCommand } from "./newsletterCommand";
|
||||
|
||||
export default abstract class NewsletterCommandHandler {
|
||||
/**
|
||||
* @description create newsletter
|
||||
* @param CreateNewsletterCommand
|
||||
* @returns {Promise<number>}
|
||||
*/
|
||||
static async create(createNewsletter: CreateNewsletterCommand): Promise<number> {
|
||||
return await dataSource
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(newsletter)
|
||||
.values({
|
||||
title: createNewsletter.title,
|
||||
})
|
||||
.execute()
|
||||
.then((result) => {
|
||||
return result.identifiers[0].id;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw new InternalException("Failed creating newsletter", err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sync newsletter
|
||||
* @param SynchronizeNewsletterCommand
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async sync(syncNewsletter: SynchronizeNewsletterCommand): Promise<void> {
|
||||
return await dataSource
|
||||
.createQueryBuilder()
|
||||
.update(newsletter)
|
||||
.set({
|
||||
title: syncNewsletter.title,
|
||||
description: syncNewsletter.description,
|
||||
newsletterTitle: syncNewsletter.newsletterTitle,
|
||||
newsletterText: syncNewsletter.newsletterText,
|
||||
newsletterSignatur: syncNewsletter.newsletterSignatur,
|
||||
recipientsByQueryId: syncNewsletter.recipientsByQueryId,
|
||||
})
|
||||
.where("id = :id", { id: syncNewsletter.id })
|
||||
.execute()
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
throw new InternalException("Failed synching newsletter", err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description send newsletter
|
||||
* @param SendNewsletterCommand
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async send(syncNewsletter: SendNewsletterCommand): Promise<void> {
|
||||
return await dataSource
|
||||
.createQueryBuilder()
|
||||
.update(newsletter)
|
||||
.set({
|
||||
isSent: syncNewsletter.isSent,
|
||||
})
|
||||
.where("id = :id", { id: syncNewsletter.id })
|
||||
.execute()
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
throw new InternalException("Failed setting newsletter send state", err);
|
||||
});
|
||||
}
|
||||
}
|
10
src/command/newsletterDatesCommand.ts
Normal file
10
src/command/newsletterDatesCommand.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
export interface SynchronizeNewsletterDatesCommand {
|
||||
newsletterId: number;
|
||||
dates: Array<NewsletterDateCommand>;
|
||||
}
|
||||
|
||||
export interface NewsletterDateCommand {
|
||||
calendarId: number;
|
||||
diffTitle?: string;
|
||||
diffDescription?: string;
|
||||
}
|
95
src/command/newsletterDatesCommandHandler.ts
Normal file
95
src/command/newsletterDatesCommandHandler.ts
Normal file
|
@ -0,0 +1,95 @@
|
|||
import { DeleteResult, EntityManager, InsertResult, UpdateResult } from "typeorm";
|
||||
import { dataSource } from "../data-source";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
import NewsletterDatesService from "../service/newsletterDatesService";
|
||||
import { NewsletterDateCommand, SynchronizeNewsletterDatesCommand } from "./newsletterDatesCommand";
|
||||
import { newsletterDates } from "../entity/newsletterDates";
|
||||
|
||||
export default abstract class NewsletterDatesCommandHandler {
|
||||
/**
|
||||
* @description sync newsletter dates
|
||||
* @param {SynchronizeNewsletterDatesCommand} syncNewsletterDates
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async sync(syncNewsletterDates: SynchronizeNewsletterDatesCommand): Promise<void> {
|
||||
let currentDates = await NewsletterDatesService.getAll(syncNewsletterDates.newsletterId);
|
||||
|
||||
return await dataSource.manager
|
||||
.transaction(async (manager) => {
|
||||
let newDates = syncNewsletterDates.dates.filter(
|
||||
(r) => !currentDates.map((np) => np.calendarId).includes(r.calendarId)
|
||||
);
|
||||
let removeDates = currentDates.filter(
|
||||
(r) => !syncNewsletterDates.dates.map((np) => np.calendarId).includes(r.calendarId)
|
||||
);
|
||||
let keptDates = currentDates.filter((r) =>
|
||||
syncNewsletterDates.dates.map((np) => np.calendarId).includes(r.calendarId)
|
||||
);
|
||||
|
||||
if (newDates.length != 0) {
|
||||
await this.syncPresenceAdd(manager, syncNewsletterDates.newsletterId, newDates);
|
||||
}
|
||||
|
||||
if (removeDates.length != 0) {
|
||||
await this.syncPresenceRemove(manager, syncNewsletterDates.newsletterId, removeDates);
|
||||
}
|
||||
|
||||
for (const date of keptDates) {
|
||||
await this.syncPresenceUpdate(manager, syncNewsletterDates.newsletterId, date);
|
||||
}
|
||||
})
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
throw new InternalException("Failed syncing newsletter dates", err);
|
||||
});
|
||||
}
|
||||
|
||||
private static async syncPresenceAdd(
|
||||
manager: EntityManager,
|
||||
newsletterId: number,
|
||||
dates: Array<NewsletterDateCommand>
|
||||
): Promise<InsertResult> {
|
||||
return await manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(newsletterDates)
|
||||
.values(
|
||||
dates.map((d) => ({
|
||||
...d,
|
||||
newsletterId: newsletterId,
|
||||
}))
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
|
||||
private static async syncPresenceUpdate(
|
||||
manager: EntityManager,
|
||||
newsletterId: number,
|
||||
date: NewsletterDateCommand
|
||||
): Promise<UpdateResult> {
|
||||
return await manager
|
||||
.createQueryBuilder()
|
||||
.update(newsletterDates)
|
||||
.set({
|
||||
diffTitle: date.diffTitle,
|
||||
diffDescription: date.diffDescription,
|
||||
})
|
||||
.where("calendarId = :calendarId", { calendarId: date.calendarId })
|
||||
.andWhere("newsletterId = :newsletterId", { newsletterId })
|
||||
.execute();
|
||||
}
|
||||
|
||||
private static async syncPresenceRemove(
|
||||
manager: EntityManager,
|
||||
newsletterId: number,
|
||||
dates: Array<NewsletterDateCommand>
|
||||
): Promise<DeleteResult> {
|
||||
return await manager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(newsletterDates)
|
||||
.where("calendarId IN (:...ids)", { ids: dates.map((d) => d.calendarId) })
|
||||
.andWhere("newsletterId = :newsletterId", { newsletterId })
|
||||
.execute();
|
||||
}
|
||||
}
|
9
src/command/newsletterRecipientsCommand.ts
Normal file
9
src/command/newsletterRecipientsCommand.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export interface SynchronizeNewsletterRecipientsCommand {
|
||||
newsletterId: number;
|
||||
recipients: Array<NewsletterRecipientCommand>;
|
||||
}
|
||||
|
||||
export interface NewsletterRecipientCommand {
|
||||
memberId: number;
|
||||
addedManually: boolean;
|
||||
}
|
94
src/command/newsletterRecipientsCommandHandler.ts
Normal file
94
src/command/newsletterRecipientsCommandHandler.ts
Normal file
|
@ -0,0 +1,94 @@
|
|||
import { DeleteResult, EntityManager, InsertResult, UpdateResult } from "typeorm";
|
||||
import { dataSource } from "../data-source";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
import NewsletterRecipientsService from "../service/newsletterRecipientsService";
|
||||
import { NewsletterRecipientCommand, SynchronizeNewsletterRecipientsCommand } from "./newsletterRecipientsCommand";
|
||||
import { newsletterRecipients } from "../entity/newsletterRecipients";
|
||||
|
||||
export default abstract class NewsletterRecipientsCommandHandler {
|
||||
/**
|
||||
* @description sync newsletterRecipients
|
||||
* @param {SynchronizeNewsletterRecipientsCommand} syncNewsletterRecipients
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async sync(syncNewsletterRecipients: SynchronizeNewsletterRecipientsCommand): Promise<void> {
|
||||
let currentRecipients = await NewsletterRecipientsService.getAll(syncNewsletterRecipients.newsletterId);
|
||||
|
||||
return await dataSource.manager
|
||||
.transaction(async (manager) => {
|
||||
let newRecipients = syncNewsletterRecipients.recipients.filter(
|
||||
(r) => !currentRecipients.map((np) => np.memberId).includes(r.memberId)
|
||||
);
|
||||
let removeRecipients = currentRecipients.filter(
|
||||
(r) => !syncNewsletterRecipients.recipients.map((np) => np.memberId).includes(r.memberId)
|
||||
);
|
||||
let keptRecipients = currentRecipients.filter((r) =>
|
||||
syncNewsletterRecipients.recipients.map((np) => np.memberId).includes(r.memberId)
|
||||
);
|
||||
|
||||
if (newRecipients.length != 0) {
|
||||
await this.syncPresenceAdd(manager, syncNewsletterRecipients.newsletterId, newRecipients);
|
||||
}
|
||||
|
||||
if (removeRecipients.length != 0) {
|
||||
await this.syncPresenceRemove(manager, syncNewsletterRecipients.newsletterId, removeRecipients);
|
||||
}
|
||||
|
||||
for (const recipient of keptRecipients) {
|
||||
await this.syncPresenceUpdate(manager, syncNewsletterRecipients.newsletterId, recipient);
|
||||
}
|
||||
})
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
throw new InternalException("Failed syncing newsletter recipients", err);
|
||||
});
|
||||
}
|
||||
|
||||
private static async syncPresenceAdd(
|
||||
manager: EntityManager,
|
||||
newsletterId: number,
|
||||
recipients: Array<NewsletterRecipientCommand>
|
||||
): Promise<InsertResult> {
|
||||
return await manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(newsletterRecipients)
|
||||
.values(
|
||||
recipients.map((d) => ({
|
||||
...d,
|
||||
newsletterId: newsletterId,
|
||||
}))
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
|
||||
private static async syncPresenceUpdate(
|
||||
manager: EntityManager,
|
||||
newsletterId: number,
|
||||
recipient: NewsletterRecipientCommand
|
||||
): Promise<UpdateResult> {
|
||||
return await manager
|
||||
.createQueryBuilder()
|
||||
.update(newsletterRecipients)
|
||||
.set({
|
||||
addedManually: recipient.addedManually,
|
||||
})
|
||||
.where("memberId = :memberId", { memberId: recipient.memberId })
|
||||
.andWhere("newsletterId = :newsletterId", { newsletterId })
|
||||
.execute();
|
||||
}
|
||||
|
||||
private static async syncPresenceRemove(
|
||||
manager: EntityManager,
|
||||
newsletterId: number,
|
||||
recipients: Array<NewsletterRecipientCommand>
|
||||
): Promise<DeleteResult> {
|
||||
return await manager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(newsletterRecipients)
|
||||
.where("memberId IN (:...ids)", { ids: recipients.map((d) => d.memberId) })
|
||||
.andWhere("newsletterId = :newsletterId", { newsletterId })
|
||||
.execute();
|
||||
}
|
||||
}
|
229
src/controller/admin/newsletterController.ts
Normal file
229
src/controller/admin/newsletterController.ts
Normal file
|
@ -0,0 +1,229 @@
|
|||
import { Request, Response } from "express";
|
||||
import NewsletterService from "../../service/newsletterService";
|
||||
import NewsletterFactory from "../../factory/admin/newsletter";
|
||||
import NewsletterDatesService from "../../service/newsletterDatesService";
|
||||
import NewsletterDatesFactory from "../../factory/admin/newsletterDates";
|
||||
import NewsletterRecipientsService from "../../service/newsletterRecipientsService";
|
||||
import NewsletterRecipientsFactory from "../../factory/admin/newsletterRecipients";
|
||||
import { FileSystemHelper } from "../../helpers/fileSystemHelper";
|
||||
import { CreateNewsletterCommand, SynchronizeNewsletterCommand } from "../../command/newsletterCommand";
|
||||
import NewsletterCommandHandler from "../../command/newsletterCommandHandler";
|
||||
import { SynchronizeNewsletterDatesCommand } from "../../command/newsletterDatesCommand";
|
||||
import NewsletterDatesCommandHandler from "../../command/newsletterDatesCommandHandler";
|
||||
import { SynchronizeNewsletterRecipientsCommand } from "../../command/newsletterRecipientsCommand";
|
||||
import NewsletterRecipientsCommandHandler from "../../command/newsletterRecipientsCommandHandler";
|
||||
import { NewsletterDatesViewModel } from "../../viewmodel/admin/newsletterDates.models";
|
||||
import { NewsletterRecipientsViewModel } from "../../viewmodel/admin/newsletterRecipients.models";
|
||||
|
||||
/**
|
||||
* @description get all newsletters
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getAllNewsletters(req: Request, res: Response): Promise<any> {
|
||||
let offset = parseInt((req.query.offset as string) ?? "0");
|
||||
let count = parseInt((req.query.count as string) ?? "25");
|
||||
let [newsletters, total] = await NewsletterService.getAll(offset, count);
|
||||
|
||||
res.json({
|
||||
newsletters: NewsletterFactory.mapToBase(newsletters),
|
||||
total: total,
|
||||
offset: offset,
|
||||
count: count,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get newsletter by id
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getNewsletterById(req: Request, res: Response): Promise<any> {
|
||||
let id = parseInt(req.params.id);
|
||||
let newsletter = await NewsletterService.getById(id);
|
||||
|
||||
res.json(NewsletterFactory.mapToSingle(newsletter));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get newsletter dates by id
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getNewsletterDatesById(req: Request, res: Response): Promise<any> {
|
||||
let newsletterId = parseInt(req.params.newsletterId);
|
||||
|
||||
let dates = await NewsletterDatesService.getAll(newsletterId);
|
||||
|
||||
res.json(NewsletterDatesFactory.mapToBase(dates));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get newsletter recipientss by id
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getNewsletterRecipientsById(req: Request, res: Response): Promise<any> {
|
||||
let newsletterId = parseInt(req.params.newsletterId);
|
||||
|
||||
let recipientss = await NewsletterRecipientsService.getAll(newsletterId);
|
||||
|
||||
res.json(NewsletterRecipientsFactory.mapToBase(recipientss));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get newsletter printouts by id
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getNewsletterPrintoutsById(req: Request, res: Response): Promise<any> {
|
||||
let newsletterId = parseInt(req.params.newsletterId);
|
||||
|
||||
let newsletter = await NewsletterService.getById(newsletterId);
|
||||
|
||||
let folderPath = FileSystemHelper.formatPath("export", "newsletter", `${newsletter.id}_${newsletter.title}`);
|
||||
let filesInFolder = FileSystemHelper.getFilesInDirectory(folderPath);
|
||||
|
||||
res.json(filesInFolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get newsletter printout by id and print
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getNewsletterPrintoutByIdAndPrint(req: Request, res: Response): Promise<any> {
|
||||
let newsletterId = parseInt(req.params.newsletterId);
|
||||
let filename = req.params.filename;
|
||||
|
||||
let newsletter = await NewsletterService.getById(newsletterId);
|
||||
|
||||
let filepath = FileSystemHelper.formatPath("export", "newsletter", `${newsletter.id}_${newsletter.title}`, filename);
|
||||
|
||||
res.sendFile(process.cwd() + filepath, {
|
||||
headers: {
|
||||
"Content-Type": "application/pdf",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description create newsletter
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function createNewsletter(req: Request, res: Response): Promise<any> {
|
||||
let title = req.body.title;
|
||||
|
||||
let createNewsletter: CreateNewsletterCommand = {
|
||||
title,
|
||||
};
|
||||
let id = await NewsletterCommandHandler.create(createNewsletter);
|
||||
|
||||
res.send(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description create newsletter printout by id
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function createNewsletterPrintoutById(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);
|
||||
|
||||
// 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
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description synchronize newsletter by id
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function synchronizeNewsletterById(req: Request, res: Response): Promise<any> {
|
||||
let id = parseInt(req.params.id);
|
||||
let title = req.body.title;
|
||||
let description = req.body.description;
|
||||
let newsletterTitle = req.body.newsletterTitle;
|
||||
let newsletterText = req.body.newsletterText;
|
||||
let newsletterSignatur = req.body.newsletterSignatur;
|
||||
let recipientsByQueryId = req.body.recipientsByQueryId ?? null;
|
||||
|
||||
let syncNewsletter: SynchronizeNewsletterCommand = {
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
newsletterTitle,
|
||||
newsletterText,
|
||||
newsletterSignatur,
|
||||
recipientsByQueryId,
|
||||
};
|
||||
await NewsletterCommandHandler.sync(syncNewsletter);
|
||||
|
||||
if (recipientsByQueryId) {
|
||||
// TODO! set all recipients to query selection
|
||||
}
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description synchronize newsletter dates by id
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function synchronizeNewsletterDatesById(req: Request, res: Response): Promise<any> {
|
||||
let newsletterId = parseInt(req.params.newsletterId);
|
||||
let dates = req.body.dates as Array<NewsletterDatesViewModel>;
|
||||
|
||||
let syncDates: SynchronizeNewsletterDatesCommand = {
|
||||
newsletterId,
|
||||
dates: dates.map((d) => ({
|
||||
calendarId: d.calendarId,
|
||||
diffTitle: d.diffTitle,
|
||||
diffDescription: d.diffDescription,
|
||||
})),
|
||||
};
|
||||
await NewsletterDatesCommandHandler.sync(syncDates);
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description synchronize newsletter recipients by id
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function synchronizeNewsletterRecipientsById(req: Request, res: Response): Promise<any> {
|
||||
let newsletterId = parseInt(req.params.newsletterId);
|
||||
let recipients = req.body.recipients as Array<NewsletterRecipientsViewModel>;
|
||||
|
||||
let syncRecipients: SynchronizeNewsletterRecipientsCommand = {
|
||||
newsletterId,
|
||||
recipients: recipients.map((r) => ({
|
||||
memberId: r.memberId,
|
||||
addedManually: r.addedManually,
|
||||
})),
|
||||
};
|
||||
await NewsletterRecipientsCommandHandler.sync(syncRecipients);
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
|
@ -27,6 +27,7 @@ import ProtocolPrintoutService from "../../service/protocolPrintoutService";
|
|||
import ProtocolPrintoutFactory from "../../factory/admin/protocolPrintout";
|
||||
import { CreateProtocolPrintoutCommand } from "../../command/protocolPrintoutCommand";
|
||||
import ProtocolPrintoutCommandHandler from "../../command/protocolPrintoutCommandHandler";
|
||||
import { FileSystemHelper } from "../../helpers/fileSystemHelper";
|
||||
|
||||
/**
|
||||
* @description get all protocols
|
||||
|
@ -240,6 +241,7 @@ export async function createProtocolPrintoutById(req: Request, res: Response): P
|
|||
template: "protocol",
|
||||
title,
|
||||
filename,
|
||||
folder: "protocol",
|
||||
data: {
|
||||
title: protocol.title,
|
||||
summary: protocol.summary,
|
||||
|
@ -262,7 +264,7 @@ export async function createProtocolPrintoutById(req: Request, res: Response): P
|
|||
let printout: CreateProtocolPrintoutCommand = {
|
||||
title,
|
||||
iteration: iteration + 1,
|
||||
filename,
|
||||
filename: FileSystemHelper.formatPath("protocol", filename),
|
||||
protocolId,
|
||||
};
|
||||
await ProtocolPrintoutCommandHandler.create(printout);
|
||||
|
|
|
@ -55,6 +55,10 @@ import { template } from "./entity/template";
|
|||
import { Template1734854680201 } from "./migrations/1734854680201-template";
|
||||
import { templateUsage } from "./entity/templateUsage";
|
||||
import { TemplateUsage1734949173739 } from "./migrations/1734949173739-templateUsage";
|
||||
import { newsletter } from "./entity/newsletter";
|
||||
import { newsletterDates } from "./entity/newsletterDates";
|
||||
import { newsletterRecipients } from "./entity/newsletterRecipients";
|
||||
import { Newsletter1735118780511 } from "./migrations/1735118780511-newsletter";
|
||||
|
||||
const dataSource = new DataSource({
|
||||
type: DB_TYPE as any,
|
||||
|
@ -96,6 +100,9 @@ const dataSource = new DataSource({
|
|||
query,
|
||||
template,
|
||||
templateUsage,
|
||||
newsletter,
|
||||
newsletterDates,
|
||||
newsletterRecipients,
|
||||
memberView,
|
||||
memberExecutivePositionsView,
|
||||
memberQualificationsView,
|
||||
|
@ -120,6 +127,7 @@ const dataSource = new DataSource({
|
|||
MemberDataViews1734520998539,
|
||||
Template1734854680201,
|
||||
TemplateUsage1734949173739,
|
||||
Newsletter1735118780511,
|
||||
],
|
||||
migrationsRun: true,
|
||||
migrationsTransactionMode: "each",
|
||||
|
|
42
src/entity/newsletter.ts
Normal file
42
src/entity/newsletter.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { Column, Entity, ManyToOne, OneToMany, PrimaryColumn } from "typeorm";
|
||||
import { newsletterDates } from "./newsletterDates";
|
||||
import { member } from "./member";
|
||||
import { newsletterRecipients } from "./newsletterRecipients";
|
||||
import { query } from "./query";
|
||||
|
||||
@Entity()
|
||||
export class newsletter {
|
||||
@PrimaryColumn({ generated: "increment", type: "int" })
|
||||
id: number;
|
||||
|
||||
@Column({ type: "varchar", length: 255 })
|
||||
title: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255, default: "" })
|
||||
description: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255, default: "" })
|
||||
newsletterTitle: string;
|
||||
|
||||
@Column({ type: "text", default: "" })
|
||||
newsletterText: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255, default: "" })
|
||||
newsletterSignatur: string;
|
||||
|
||||
@Column({ type: "boolean", default: false })
|
||||
isSent: boolean;
|
||||
|
||||
@OneToMany(() => newsletterDates, (dates) => dates.newsletter)
|
||||
dates: newsletterDates[];
|
||||
|
||||
@OneToMany(() => newsletterRecipients, (recipient) => recipient.newsletter)
|
||||
recipients: newsletterRecipients[];
|
||||
|
||||
@ManyToOne(() => query, {
|
||||
nullable: true,
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
recipientsByQuery?: query;
|
||||
}
|
32
src/entity/newsletterDates.ts
Normal file
32
src/entity/newsletterDates.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { Column, Entity, ManyToOne, PrimaryColumn } from "typeorm";
|
||||
import { newsletter } from "./newsletter";
|
||||
import { calendar } from "./calendar";
|
||||
|
||||
@Entity()
|
||||
export class newsletterDates {
|
||||
@PrimaryColumn({ type: "int" })
|
||||
newsletterId: number;
|
||||
|
||||
@PrimaryColumn({ type: "int" })
|
||||
calendarId: number;
|
||||
|
||||
@Column({ type: "varchar", length: 255, nullable: true })
|
||||
diffTitle: string | null;
|
||||
|
||||
@Column({ type: "text", nullable: true })
|
||||
diffDescription: string | null;
|
||||
|
||||
@ManyToOne(() => newsletter, (newsletter) => newsletter.dates, {
|
||||
nullable: false,
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
newsletter: newsletter;
|
||||
|
||||
@ManyToOne(() => calendar, {
|
||||
nullable: false,
|
||||
onDelete: "RESTRICT",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
calendar: calendar;
|
||||
}
|
29
src/entity/newsletterRecipients.ts
Normal file
29
src/entity/newsletterRecipients.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { Column, Entity, ManyToOne, PrimaryColumn } from "typeorm";
|
||||
import { newsletter } from "./newsletter";
|
||||
import { member } from "./member";
|
||||
|
||||
@Entity()
|
||||
export class newsletterRecipients {
|
||||
@PrimaryColumn({ type: "int" })
|
||||
newsletterId: number;
|
||||
|
||||
@PrimaryColumn({ type: "int" })
|
||||
memberId: number;
|
||||
|
||||
@Column({ type: "boolean", default: false })
|
||||
addedManually: boolean;
|
||||
|
||||
@ManyToOne(() => newsletter, (newsletter) => newsletter.recipients, {
|
||||
nullable: false,
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
newsletter: newsletter;
|
||||
|
||||
@ManyToOne(() => member, {
|
||||
nullable: false,
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
member: member;
|
||||
}
|
32
src/factory/admin/newsletter.ts
Normal file
32
src/factory/admin/newsletter.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { newsletter } from "../../entity/newsletter";
|
||||
import { NewsletterViewModel } from "../../viewmodel/admin/newsletter.models";
|
||||
import QueryStoreFactory from "./queryStore";
|
||||
|
||||
export default abstract class NewsletterFactory {
|
||||
/**
|
||||
* @description map record to newsletter
|
||||
* @param {newsletter} record
|
||||
* @returns {NewsletterViewModel}
|
||||
*/
|
||||
public static mapToSingle(record: newsletter): NewsletterViewModel {
|
||||
return {
|
||||
id: record.id,
|
||||
title: record.title,
|
||||
description: record.description,
|
||||
newsletterTitle: record.newsletterTitle,
|
||||
newsletterText: record.newsletterText,
|
||||
newsletterSignatur: record.newsletterSignatur,
|
||||
isSent: record.isSent,
|
||||
recipientsByQuery: record?.recipientsByQuery ? QueryStoreFactory.mapToSingle(record.recipientsByQuery) : null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description map records to newsletter
|
||||
* @param {Array<newsletter>} records
|
||||
* @returns {Array<NewsletterViewModel>}
|
||||
*/
|
||||
public static mapToBase(records: Array<newsletter>): Array<NewsletterViewModel> {
|
||||
return records.map((r) => this.mapToSingle(r));
|
||||
}
|
||||
}
|
30
src/factory/admin/newsletterDates.ts
Normal file
30
src/factory/admin/newsletterDates.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { newsletterDates } from "../../entity/newsletterDates";
|
||||
import { NewsletterDatesViewModel } from "../../viewmodel/admin/newsletterDates.models";
|
||||
import CalendarFactory from "./calendar";
|
||||
import MemberFactory from "./member";
|
||||
|
||||
export default abstract class NewsletterDatesFactory {
|
||||
/**
|
||||
* @description map record to newsletterDates
|
||||
* @param {newsletterDates} record
|
||||
* @returns {NewsletterDatesViewModel}
|
||||
*/
|
||||
public static mapToSingle(record: newsletterDates): NewsletterDatesViewModel {
|
||||
return {
|
||||
newsletterId: record.newsletterId,
|
||||
calendarId: record.calendarId,
|
||||
diffTitle: record.diffTitle,
|
||||
diffDescription: record.diffDescription,
|
||||
calendar: CalendarFactory.mapToSingle(record.calendar),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description map records to newsletterDates
|
||||
* @param {Array<newsletterDates>} records
|
||||
* @returns {Array<NewsletterDatesViewModel>}
|
||||
*/
|
||||
public static mapToBase(records: Array<newsletterDates>): Array<NewsletterDatesViewModel> {
|
||||
return records.map((r) => this.mapToSingle(r));
|
||||
}
|
||||
}
|
28
src/factory/admin/newsletterRecipients.ts
Normal file
28
src/factory/admin/newsletterRecipients.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { newsletterRecipients } from "../../entity/newsletterRecipients";
|
||||
import { NewsletterRecipientsViewModel } from "../../viewmodel/admin/newsletterRecipients.models";
|
||||
import MemberFactory from "./member";
|
||||
|
||||
export default abstract class NewsletterRecipientsFactory {
|
||||
/**
|
||||
* @description map record to newsletterRecipients
|
||||
* @param {newsletterRecipients} record
|
||||
* @returns {NewsletterRecipientsViewModel}
|
||||
*/
|
||||
public static mapToSingle(record: newsletterRecipients): NewsletterRecipientsViewModel {
|
||||
return {
|
||||
newsletterId: record.newsletterId,
|
||||
memberId: record.memberId,
|
||||
addedManually: record.addedManually,
|
||||
member: MemberFactory.mapToSingle(record.member),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description map records to newsletterRecipients
|
||||
* @param {Array<newsletterRecipients>} records
|
||||
* @returns {Array<NewsletterRecipientsViewModel>}
|
||||
*/
|
||||
public static mapToBase(records: Array<newsletterRecipients>): Array<NewsletterRecipientsViewModel> {
|
||||
return records.map((r) => this.mapToSingle(r));
|
||||
}
|
||||
}
|
31
src/helpers/fileSystemHelper.ts
Normal file
31
src/helpers/fileSystemHelper.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { existsSync, mkdirSync, readFileSync, 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);
|
||||
if (!existsSync(exportPath)) {
|
||||
mkdirSync(exportPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
static readFile(filePath: string) {
|
||||
return readFileSync(join(process.cwd(), filePath), "utf8");
|
||||
}
|
||||
|
||||
static writeFile(filePath: string, file: any) {
|
||||
writeFileSync(filePath, file);
|
||||
}
|
||||
|
||||
static formatPath(...args: string[]) {
|
||||
return join(...args);
|
||||
}
|
||||
|
||||
static getFilesInDirectory(directoryPath: string, filetype?: string): string[] {
|
||||
const fullPath = join(process.cwd(), directoryPath);
|
||||
return readdirSync(fullPath, { withFileTypes: true })
|
||||
.filter((dirent) => !dirent.isDirectory() && (!filetype || dirent.name.endsWith(filetype)))
|
||||
.map((dirent) => dirent.name);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
import puppeteer from "puppeteer";
|
||||
import { TemplateHelper } from "./templateHelper";
|
||||
import { PermissionModule } from "../type/permissionTypes";
|
||||
import { FileSystemHelper } from "./fileSystemHelper";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
|
||||
export abstract class PdfExport {
|
||||
static async renderFile({
|
||||
|
@ -10,6 +12,7 @@ export abstract class PdfExport {
|
|||
data = {},
|
||||
saveToDisk = true,
|
||||
margins = { top: "15mm", bottom: "15mm" },
|
||||
folder = "",
|
||||
}: {
|
||||
template: PermissionModule;
|
||||
title?: string;
|
||||
|
@ -17,7 +20,10 @@ export abstract class PdfExport {
|
|||
data?: any;
|
||||
saveToDisk?: boolean;
|
||||
margins?: { top: string; bottom: string };
|
||||
folder?: string;
|
||||
}) {
|
||||
if (folder != "") FileSystemHelper.createFolder(folder);
|
||||
|
||||
const { header, footer, body } = await TemplateHelper.renderFileForModule({
|
||||
module: template,
|
||||
bodyData: data,
|
||||
|
@ -31,8 +37,10 @@ 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`);
|
||||
|
||||
let pdf = await page.pdf({
|
||||
...(saveToDisk ? { path: process.cwd() + `/export/${filename}.pdf` } : {}),
|
||||
...(saveToDisk ? { path: exportPath } : {}),
|
||||
format: "A4",
|
||||
printBackground: false,
|
||||
margin: {
|
||||
|
@ -50,4 +58,25 @@ export abstract class PdfExport {
|
|||
|
||||
return pdf;
|
||||
}
|
||||
|
||||
static async sqashToSingleFile(inputFolder: string, outputFile: string, outputFolder: string = "") {
|
||||
if (outputFolder != "") FileSystemHelper.createFolder(outputFolder);
|
||||
|
||||
let pdfFilePaths = FileSystemHelper.getFilesInDirectory(inputFolder, ".pdf");
|
||||
|
||||
const mergedPdf = await PDFDocument.create();
|
||||
|
||||
for (const pdfPath of pdfFilePaths) {
|
||||
const pdfBytes = FileSystemHelper.readFile(pdfPath);
|
||||
const pdf = await PDFDocument.load(pdfBytes);
|
||||
const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
|
||||
copiedPages.forEach((page) => mergedPdf.addPage(page));
|
||||
}
|
||||
|
||||
const mergedPdfBytes = await mergedPdf.save();
|
||||
|
||||
const exportPath = FileSystemHelper.formatPath(process.cwd(), "export", outputFolder, `${outputFile}.pdf`);
|
||||
|
||||
FileSystemHelper.writeFile(exportPath, mergedPdfBytes);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { readFileSync } from "fs";
|
||||
import TemplateService from "../service/templateService";
|
||||
import { PermissionModule } from "../type/permissionTypes";
|
||||
import TemplateUsageService from "../service/templateUsageService";
|
||||
import Handlebars from "handlebars";
|
||||
import { FileSystemHelper } from "./fileSystemHelper";
|
||||
|
||||
export abstract class TemplateHelper {
|
||||
static getTemplateFromFile(template: string) {
|
||||
return readFileSync(`${process.cwd()}/src/templates/${template}.template.html`, "utf8");
|
||||
return FileSystemHelper.readFile(`/src/templates/${template}.template.html`);
|
||||
}
|
||||
|
||||
static async getTemplateFromStore(templateId: number): Promise<string> {
|
||||
|
|
129
src/migrations/1735118780511-newsletter.ts
Normal file
129
src/migrations/1735118780511-newsletter.ts
Normal file
|
@ -0,0 +1,129 @@
|
|||
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from "typeorm";
|
||||
import { DB_TYPE } from "../env.defaults";
|
||||
|
||||
export class Newsletter1735118780511 implements MigrationInterface {
|
||||
name = "Newsletter1735118780511";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const variableType_int = DB_TYPE == "mysql" ? "int" : "integer";
|
||||
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: "newsletter_dates",
|
||||
columns: [
|
||||
{ name: "newsletterId", type: variableType_int, isPrimary: true },
|
||||
{ name: "calendarId", type: "varchar", length: "255", isPrimary: true },
|
||||
{ name: "diffTitle", type: "varchar", length: "255", isNullable: true },
|
||||
{ name: "diffDescription", type: "text", isNullable: true },
|
||||
],
|
||||
}),
|
||||
true
|
||||
);
|
||||
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: "newsletter_recipients",
|
||||
columns: [
|
||||
{ name: "newsletterId", type: variableType_int, isPrimary: true },
|
||||
{ name: "memberId", type: variableType_int, isPrimary: true },
|
||||
{ name: "addedManually", type: "tinyint", default: "0" },
|
||||
],
|
||||
}),
|
||||
true
|
||||
);
|
||||
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: "newsletter",
|
||||
columns: [
|
||||
{ name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" },
|
||||
{ name: "title", type: "varchar", length: "255" },
|
||||
{ name: "description", type: "varchar", length: "255", default: "''" },
|
||||
{ name: "newsletterTitle", type: "varchar", length: "255", default: "''" },
|
||||
{ name: "newsletterText", type: "text", default: "''" },
|
||||
{ name: "newsletterSignatur", type: "varchar", length: "255", default: "''" },
|
||||
{ name: "isSent", type: "tinyint", default: "0" },
|
||||
{ name: "recipientsByQueryId", type: variableType_int, isNullable: true },
|
||||
],
|
||||
}),
|
||||
true
|
||||
);
|
||||
|
||||
await queryRunner.createForeignKey(
|
||||
"newsletter_dates",
|
||||
new TableForeignKey({
|
||||
columnNames: ["newsletterId"],
|
||||
referencedColumnNames: ["id"],
|
||||
referencedTableName: "newsletter",
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
);
|
||||
|
||||
await queryRunner.createForeignKey(
|
||||
"newsletter_dates",
|
||||
new TableForeignKey({
|
||||
columnNames: ["calendarId"],
|
||||
referencedColumnNames: ["id"],
|
||||
referencedTableName: "calendar",
|
||||
onDelete: "RESTRICT",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
);
|
||||
|
||||
await queryRunner.createForeignKey(
|
||||
"newsletter_recipients",
|
||||
new TableForeignKey({
|
||||
columnNames: ["newsletterId"],
|
||||
referencedColumnNames: ["id"],
|
||||
referencedTableName: "newsletter",
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
);
|
||||
|
||||
await queryRunner.createForeignKey(
|
||||
"newsletter_recipients",
|
||||
new TableForeignKey({
|
||||
columnNames: ["memberId"],
|
||||
referencedColumnNames: ["id"],
|
||||
referencedTableName: "member",
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
);
|
||||
|
||||
await queryRunner.createForeignKey(
|
||||
"newsletter",
|
||||
new TableForeignKey({
|
||||
columnNames: ["recipientsByQueryId"],
|
||||
referencedColumnNames: ["id"],
|
||||
referencedTableName: "query",
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
const tableN = await queryRunner.getTable("newsletter");
|
||||
const tableNR = await queryRunner.getTable("newsletter_recipients");
|
||||
const tableND = await queryRunner.getTable("newsletter_dates");
|
||||
|
||||
const foreignKeyN = tableN.foreignKeys.find((fk) => fk.columnNames.indexOf("recipientsByQueryId") !== -1);
|
||||
const foreignKeyNR = tableNR.foreignKeys.find((fk) => fk.columnNames.indexOf("newsletterId") !== -1);
|
||||
const foreignKeyNR2 = tableNR.foreignKeys.find((fk) => fk.columnNames.indexOf("memberId") !== -1);
|
||||
const foreignKeyND1 = tableND.foreignKeys.find((fk) => fk.columnNames.indexOf("newsletterId") !== -1);
|
||||
const foreignKeyND2 = tableND.foreignKeys.find((fk) => fk.columnNames.indexOf("calendarId") !== -1);
|
||||
|
||||
await queryRunner.dropForeignKey("newsletter", foreignKeyN);
|
||||
await queryRunner.dropForeignKey("newsletter_recipients", foreignKeyNR);
|
||||
await queryRunner.dropForeignKey("newsletter_recipients", foreignKeyNR2);
|
||||
await queryRunner.dropForeignKey("newsletter_dates", foreignKeyND1);
|
||||
await queryRunner.dropForeignKey("newsletter_dates", foreignKeyND2);
|
||||
|
||||
await queryRunner.dropTable("newsletter");
|
||||
await queryRunner.dropTable("newsletter_recipients");
|
||||
await queryRunner.dropTable("newsletter_dates");
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ import member from "./member";
|
|||
import protocol from "./protocol";
|
||||
import calendar from "./calendar";
|
||||
import queryBuilder from "./queryBuilder";
|
||||
import newsletter from "./newsletter";
|
||||
|
||||
import role from "./role";
|
||||
import user from "./user";
|
||||
|
@ -48,6 +49,7 @@ router.use("/member", PermissionHelper.passCheckMiddleware("read", "club", "memb
|
|||
router.use("/protocol", PermissionHelper.passCheckMiddleware("read", "club", "protocol"), protocol);
|
||||
router.use("/calendar", PermissionHelper.passCheckMiddleware("read", "club", "calendar"), calendar);
|
||||
router.use("/querybuilder", PermissionHelper.passCheckMiddleware("read", "club", "query"), queryBuilder);
|
||||
router.use("/newsletter", PermissionHelper.passCheckMiddleware("read", "club", "newsletter"), newsletter);
|
||||
|
||||
router.use("/role", PermissionHelper.passCheckMiddleware("read", "user", "role"), role);
|
||||
router.use("/user", PermissionHelper.passCheckMiddleware("read", "user", "user"), user);
|
||||
|
|
85
src/routes/admin/newsletter.ts
Normal file
85
src/routes/admin/newsletter.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import express, { Request, Response } from "express";
|
||||
import {
|
||||
createNewsletter,
|
||||
createNewsletterPrintoutById,
|
||||
getAllNewsletters,
|
||||
getNewsletterDatesById,
|
||||
getNewsletterById,
|
||||
getNewsletterRecipientsById,
|
||||
getNewsletterPrintoutByIdAndPrint,
|
||||
getNewsletterPrintoutsById,
|
||||
synchronizeNewsletterDatesById,
|
||||
synchronizeNewsletterById,
|
||||
synchronizeNewsletterRecipientsById,
|
||||
} from "../../controller/admin/newsletterController";
|
||||
import PermissionHelper from "../../helpers/permissionHelper";
|
||||
|
||||
var router = express.Router({ mergeParams: true });
|
||||
|
||||
router.get("/", async (req: Request, res: Response) => {
|
||||
await getAllNewsletters(req, res);
|
||||
});
|
||||
|
||||
router.get("/:id", async (req: Request, res: Response) => {
|
||||
await getNewsletterById(req, res);
|
||||
});
|
||||
|
||||
router.get("/:protocolId/dates", async (req: Request, res: Response) => {
|
||||
await getNewsletterDatesById(req, res);
|
||||
});
|
||||
|
||||
router.get("/:protocolId/recipients", async (req: Request, res: Response) => {
|
||||
await getNewsletterRecipientsById(req, res);
|
||||
});
|
||||
|
||||
router.get("/:protocolId/printouts", async (req: Request, res: Response) => {
|
||||
await getNewsletterPrintoutsById(req, res);
|
||||
});
|
||||
|
||||
router.get("/:protocolId/printout/:filename", async (req: Request, res: Response) => {
|
||||
await getNewsletterPrintoutByIdAndPrint(req, res);
|
||||
});
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
PermissionHelper.passCheckMiddleware("create", "club", "protocol"),
|
||||
async (req: Request, res: Response) => {
|
||||
await createNewsletter(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/:protocolId/printout",
|
||||
PermissionHelper.passCheckMiddleware("create", "club", "protocol"),
|
||||
async (req: Request, res: Response) => {
|
||||
await createNewsletterPrintoutById(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/:id/synchronize",
|
||||
PermissionHelper.passCheckMiddleware("update", "club", "protocol"),
|
||||
async (req: Request, res: Response) => {
|
||||
await synchronizeNewsletterById(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/:protocolId/synchronize/dates",
|
||||
PermissionHelper.passCheckMiddleware("update", "club", "protocol"),
|
||||
async (req: Request, res: Response) => {
|
||||
await synchronizeNewsletterDatesById(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/:protocolId/synchronize/recipients",
|
||||
PermissionHelper.passCheckMiddleware("update", "club", "protocol"),
|
||||
async (req: Request, res: Response) => {
|
||||
await synchronizeNewsletterRecipientsById(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: send mails | send mail preview | render preview before print job
|
||||
|
||||
export default router;
|
26
src/service/newsletterDatesService.ts
Normal file
26
src/service/newsletterDatesService.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { dataSource } from "../data-source";
|
||||
import { newsletterDates } from "../entity/newsletterDates";
|
||||
import { member } from "../entity/member";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
|
||||
export default abstract class NewsletterDatesService {
|
||||
/**
|
||||
* @description get all newsletterDates
|
||||
* @returns {Promise<Array<newsletterDates>>}
|
||||
*/
|
||||
static async getAll(newsletterId: number): Promise<Array<newsletterDates>> {
|
||||
return await dataSource
|
||||
.getRepository(newsletterDates)
|
||||
.createQueryBuilder("newsletterDates")
|
||||
.leftJoinAndSelect("newsletterDates.calendar", "calendar")
|
||||
.leftJoinAndSelect("newsletterDates.newsletter", "newsletter")
|
||||
.where("newsletterDates.newsletterId = :id", { id: newsletterId })
|
||||
.getMany()
|
||||
.then((res) => {
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw new InternalException("newsletterDatess not found", err);
|
||||
});
|
||||
}
|
||||
}
|
28
src/service/newsletterRecipientsService.ts
Normal file
28
src/service/newsletterRecipientsService.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { dataSource } from "../data-source";
|
||||
import { newsletterRecipients } from "../entity/newsletterRecipients";
|
||||
import { member } from "../entity/member";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
|
||||
export default abstract class NewsletterRecipientsService {
|
||||
/**
|
||||
* @description get all newsletterRecipients
|
||||
* @returns {Promise<Array<newsletterRecipients>>}
|
||||
*/
|
||||
static async getAll(newsletterId: number): Promise<Array<newsletterRecipients>> {
|
||||
return await dataSource
|
||||
.getRepository(newsletterRecipients)
|
||||
.createQueryBuilder("newsletterRecipients")
|
||||
.leftJoinAndSelect("newsletterRecipients.member", "member")
|
||||
.leftJoinAndSelect("member.sendNewsletter", "sendNewsletter")
|
||||
.leftJoinAndSelect("sendNewsletter.type", "communicationtype")
|
||||
.leftJoinAndSelect("newsletterRecipients.newsletter", "newsletter")
|
||||
.where("newsletterDates.newsletterId = :id", { id: newsletterId })
|
||||
.getMany()
|
||||
.then((res) => {
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw new InternalException("newsletterRecipientss not found", err);
|
||||
});
|
||||
}
|
||||
}
|
44
src/service/newsletterService.ts
Normal file
44
src/service/newsletterService.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { dataSource } from "../data-source";
|
||||
import { newsletter } from "../entity/newsletter";
|
||||
import { member } from "../entity/member";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
|
||||
export default abstract class NewsletterService {
|
||||
/**
|
||||
* @description get all newsletters
|
||||
* @returns {Promise<[Array<newsletter>, number]>}
|
||||
*/
|
||||
static async getAll(offset: number = 0, count: number = 25): Promise<[Array<newsletter>, number]> {
|
||||
return await dataSource
|
||||
.getRepository(newsletter)
|
||||
.createQueryBuilder("newsletter")
|
||||
.offset(offset)
|
||||
.limit(count)
|
||||
.getManyAndCount()
|
||||
.then((res) => {
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw new InternalException("newsletters not found", err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get newsletter by id
|
||||
* @returns {Promise<newsletter>}
|
||||
*/
|
||||
static async getById(id: number): Promise<newsletter> {
|
||||
return await dataSource
|
||||
.getRepository(newsletter)
|
||||
.createQueryBuilder("newsletter")
|
||||
.leftJoinAndSelect("newsletter.recipientsByQuery", "query")
|
||||
.where("newsletter.id = :id", { id: id })
|
||||
.getOneOrFail()
|
||||
.then((res) => {
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw new InternalException("newsletter not found by id", err);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -21,23 +21,4 @@ export default abstract class ProtocolPresenceService {
|
|||
throw new InternalException("protocolPresence not found", err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get protocolDecision by id
|
||||
* @returns {Promise<protocolPresence>}
|
||||
*/
|
||||
static async getById(id: number): Promise<protocolPresence> {
|
||||
return await dataSource
|
||||
.getRepository(protocolPresence)
|
||||
.createQueryBuilder("protocolPresence")
|
||||
.leftJoinAndSelect("protocolPresence.member", "member")
|
||||
.where("protocolPresence.id = :id", { id: id })
|
||||
.getOneOrFail()
|
||||
.then((res) => {
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw new InternalException("protocolDecision not found by id", err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ export type PermissionModule =
|
|||
| "member"
|
||||
| "calendar"
|
||||
| "newsletter"
|
||||
| "newsletter_config"
|
||||
| "protocol"
|
||||
| "qualification"
|
||||
| "award"
|
||||
|
@ -44,6 +45,7 @@ export const permissionModules: Array<PermissionModule> = [
|
|||
"member",
|
||||
"calendar",
|
||||
"newsletter",
|
||||
"newsletter_config",
|
||||
"protocol",
|
||||
"qualification",
|
||||
"award",
|
||||
|
@ -71,6 +73,7 @@ export const sectionsAndModules: SectionsAndModulesObject = {
|
|||
"query_store",
|
||||
"template",
|
||||
"template_usage",
|
||||
"newsletter_config",
|
||||
],
|
||||
user: ["user", "role"],
|
||||
};
|
||||
|
|
12
src/viewmodel/admin/newsletter.models.ts
Normal file
12
src/viewmodel/admin/newsletter.models.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { QueryStoreViewModel } from "./queryStore.models";
|
||||
|
||||
export interface NewsletterViewModel {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
newsletterTitle: string;
|
||||
newsletterText: string;
|
||||
newsletterSignatur: string;
|
||||
isSent: boolean;
|
||||
recipientsByQuery?: QueryStoreViewModel;
|
||||
}
|
9
src/viewmodel/admin/newsletterDates.models.ts
Normal file
9
src/viewmodel/admin/newsletterDates.models.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { CalendarViewModel } from "./calendar.models";
|
||||
|
||||
export interface NewsletterDatesViewModel {
|
||||
newsletterId: number;
|
||||
calendarId: number;
|
||||
diffTitle: string | null;
|
||||
diffDescription: string | null;
|
||||
calendar: CalendarViewModel;
|
||||
}
|
8
src/viewmodel/admin/newsletterRecipients.models.ts
Normal file
8
src/viewmodel/admin/newsletterRecipients.models.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { MemberViewModel } from "./member.models";
|
||||
|
||||
export interface NewsletterRecipientsViewModel {
|
||||
newsletterId: number;
|
||||
memberId: number;
|
||||
addedManually: boolean;
|
||||
member: MemberViewModel;
|
||||
}
|
Loading…
Reference in a new issue