#6-messages #24
79 changed files with 3359 additions and 129 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/newsletterConfigCommand.ts
Normal file
10
src/command/newsletterConfigCommand.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { NewsletterConfigType } from "../enums/newsletterConfigType";
|
||||
|
||||
export interface SetNewsletterConfigCommand {
|
||||
comTypeId: number;
|
||||
config: NewsletterConfigType;
|
||||
}
|
||||
|
||||
export interface DeleteNewsletterConfigCommand {
|
||||
comTypeId: number;
|
||||
}
|
47
src/command/newsletterConfigCommandHandler.ts
Normal file
47
src/command/newsletterConfigCommandHandler.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { dataSource } from "../data-source";
|
||||
import { newsletterConfig } from "../entity/newsletterConfig";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
import { DeleteNewsletterConfigCommand, SetNewsletterConfigCommand } from "./newsletterConfigCommand";
|
||||
|
||||
export default abstract class NewsletterConfigCommandHandler {
|
||||
/**
|
||||
* @description set newsletterConfig
|
||||
* @param SetNewsletterConfigCommand
|
||||
* @returns {Promise<number>}
|
||||
*/
|
||||
static async set(setNewsletterConfig: SetNewsletterConfigCommand): Promise<number> {
|
||||
return await dataSource
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(newsletterConfig)
|
||||
.values({
|
||||
comTypeId: setNewsletterConfig.comTypeId,
|
||||
config: setNewsletterConfig.config,
|
||||
})
|
||||
.orUpdate(["config"], "comTypeId")
|
||||
.execute()
|
||||
.then((result) => {
|
||||
return result.identifiers[0].id;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw new InternalException("Failed setting newsletterConfig", err);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @description delete newsletterConfig
|
||||
* @param number
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async delete(deleteNewsletterConfig: DeleteNewsletterConfigCommand): Promise<void> {
|
||||
return await dataSource
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(newsletterConfig)
|
||||
.where("comTypeId = :comTypeId", { comTypeId: deleteNewsletterConfig.comTypeId })
|
||||
.execute()
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
throw new InternalException("Failed setting newsletterConfig", 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: string;
|
||||
diffTitle?: string;
|
||||
diffDescription?: string;
|
||||
}
|
97
src/command/newsletterDatesCommandHandler.ts
Normal file
97
src/command/newsletterDatesCommandHandler.ts
Normal file
|
@ -0,0 +1,97 @@
|
|||
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.some((cd) => cd.calendarId == r.calendarId)
|
||||
);
|
||||
let removeDates = currentDates.filter(
|
||||
(r) => !syncNewsletterDates.dates.some((cd) => cd.calendarId == r.calendarId)
|
||||
);
|
||||
let keptDates = syncNewsletterDates.dates.filter(
|
||||
(r) =>
|
||||
currentDates.some((cd) => cd.calendarId == r.calendarId) &&
|
||||
!removeDates.some((cd) => cd.calendarId == 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();
|
||||
}
|
||||
}
|
4
src/command/newsletterRecipientsCommand.ts
Normal file
4
src/command/newsletterRecipientsCommand.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export interface SynchronizeNewsletterRecipientsCommand {
|
||||
newsletterId: number;
|
||||
recipients: Array<number>;
|
||||
}
|
73
src/command/newsletterRecipientsCommandHandler.ts
Normal file
73
src/command/newsletterRecipientsCommandHandler.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
import { DeleteResult, EntityManager, InsertResult, UpdateResult } from "typeorm";
|
||||
import { dataSource } from "../data-source";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
import NewsletterRecipientsService from "../service/newsletterRecipientsService";
|
||||
import { 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)).map(
|
||||
(r) => r.memberId
|
||||
);
|
||||
|
||||
return await dataSource.manager
|
||||
.transaction(async (manager) => {
|
||||
let newRecipients = syncNewsletterRecipients.recipients.filter(
|
||||
(r) => !currentRecipients.map((np) => np).includes(r)
|
||||
);
|
||||
let removeRecipients = currentRecipients.filter(
|
||||
(r) => !syncNewsletterRecipients.recipients.map((np) => np).includes(r)
|
||||
);
|
||||
|
||||
if (newRecipients.length != 0) {
|
||||
await this.syncPresenceAdd(manager, syncNewsletterRecipients.newsletterId, newRecipients);
|
||||
}
|
||||
|
||||
if (removeRecipients.length != 0) {
|
||||
await this.syncPresenceRemove(manager, syncNewsletterRecipients.newsletterId, removeRecipients);
|
||||
}
|
||||
})
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
throw new InternalException("Failed syncing newsletter recipients", err);
|
||||
});
|
||||
}
|
||||
|
||||
private static async syncPresenceAdd(
|
||||
manager: EntityManager,
|
||||
newsletterId: number,
|
||||
recipients: Array<number>
|
||||
): Promise<InsertResult> {
|
||||
return await manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(newsletterRecipients)
|
||||
.values(
|
||||
recipients.map((r) => ({
|
||||
memberId: r,
|
||||
newsletterId: newsletterId,
|
||||
}))
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
|
||||
private static async syncPresenceRemove(
|
||||
manager: EntityManager,
|
||||
newsletterId: number,
|
||||
recipients: Array<number>
|
||||
): Promise<DeleteResult> {
|
||||
return await manager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(newsletterRecipients)
|
||||
.where("memberId IN (:...ids)", { ids: recipients })
|
||||
.andWhere("newsletterId = :newsletterId", { newsletterId })
|
||||
.execute();
|
||||
}
|
||||
}
|
16
src/command/templateCommand.ts
Normal file
16
src/command/templateCommand.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
export interface CreateTemplateCommand {
|
||||
template: string;
|
||||
description: string | null;
|
||||
}
|
||||
|
||||
export interface UpdateTemplateCommand {
|
||||
id: number;
|
||||
template: string;
|
||||
description: string | null;
|
||||
design: object;
|
||||
html: string;
|
||||
}
|
||||
|
||||
export interface DeleteTemplateCommand {
|
||||
id: number;
|
||||
}
|
70
src/command/templateCommandHandler.ts
Normal file
70
src/command/templateCommandHandler.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { dataSource } from "../data-source";
|
||||
import { template } from "../entity/template";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
import { CreateTemplateCommand, DeleteTemplateCommand, UpdateTemplateCommand } from "./templateCommand";
|
||||
|
||||
export default abstract class TemplateCommandHandler {
|
||||
/**
|
||||
* @description create template
|
||||
* @param CreateTemplateCommand
|
||||
* @returns {Promise<number>}
|
||||
*/
|
||||
static async create(createTemplate: CreateTemplateCommand): Promise<number> {
|
||||
return await dataSource
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(template)
|
||||
.values({
|
||||
template: createTemplate.template,
|
||||
description: createTemplate.description,
|
||||
})
|
||||
.execute()
|
||||
.then((result) => {
|
||||
return result.identifiers[0].id;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw new InternalException("Failed creating template", err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description update template
|
||||
* @param UpdateTemplateCommand
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async update(updateTemplate: UpdateTemplateCommand): Promise<void> {
|
||||
return await dataSource
|
||||
.createQueryBuilder()
|
||||
.update(template)
|
||||
.set({
|
||||
template: updateTemplate.template,
|
||||
description: updateTemplate.description,
|
||||
design: updateTemplate.design,
|
||||
html: updateTemplate.html,
|
||||
})
|
||||
.where("id = :id", { id: updateTemplate.id })
|
||||
.execute()
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
throw new InternalException("Failed updating template", err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description delete template
|
||||
* @param DeleteTemplateCommand
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async delete(deletTemplate: DeleteTemplateCommand): Promise<void> {
|
||||
return await dataSource
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(template)
|
||||
.where("id = :id", { id: deletTemplate.id })
|
||||
.execute()
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
throw new InternalException("Failed deleting template", err);
|
||||
});
|
||||
}
|
||||
}
|
6
src/command/templateUsageCommand.ts
Normal file
6
src/command/templateUsageCommand.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export interface UpdateTemplateUsageCommand {
|
||||
scope: string;
|
||||
headerId: number | null;
|
||||
bodyId: number | null;
|
||||
footerId: number | null;
|
||||
}
|
28
src/command/templateUsageCommandHandler.ts
Normal file
28
src/command/templateUsageCommandHandler.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { dataSource } from "../data-source";
|
||||
import { templateUsage } from "../entity/templateUsage";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
import { UpdateTemplateUsageCommand } from "./templateUsageCommand";
|
||||
|
||||
export default abstract class TemplateUsageCommandHandler {
|
||||
/**
|
||||
* @description update templateUsage
|
||||
* @param UpdateTemplateUsageCommand
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async update(updateTemplateUsage: UpdateTemplateUsageCommand): Promise<void> {
|
||||
return await dataSource
|
||||
.createQueryBuilder()
|
||||
.update(templateUsage)
|
||||
.set({
|
||||
headerId: updateTemplateUsage.headerId,
|
||||
bodyId: updateTemplateUsage.bodyId,
|
||||
footerId: updateTemplateUsage.footerId,
|
||||
})
|
||||
.where("scope = :scope", { scope: updateTemplateUsage.scope })
|
||||
.execute()
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
throw new InternalException("Failed updating templateUsage", err);
|
||||
});
|
||||
}
|
||||
}
|
66
src/controller/admin/newsletterConfigController.ts
Normal file
66
src/controller/admin/newsletterConfigController.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { Request, Response } from "express";
|
||||
import NewsletterConfigService from "../../service/newsletterConfigService";
|
||||
import NewsletterConfigFactory from "../../factory/admin/newsletterConfig";
|
||||
import NewsletterConfigCommandHandler from "../../command/newsletterConfigCommandHandler";
|
||||
import { DeleteNewsletterConfigCommand, SetNewsletterConfigCommand } from "../../command/newsletterConfigCommand";
|
||||
|
||||
/**
|
||||
* @description get all newsletterConfigs
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getAllNewsletterConfigs(req: Request, res: Response): Promise<any> {
|
||||
let newsletterConfigs = await NewsletterConfigService.getAll();
|
||||
|
||||
res.json(NewsletterConfigFactory.mapToBase(newsletterConfigs));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get newsletterConfig by id
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getNewsletterConfigById(req: Request, res: Response): Promise<any> {
|
||||
let comId = parseInt(req.params.comId);
|
||||
let newsletterConfig = await NewsletterConfigService.getByComId(comId);
|
||||
|
||||
res.json(NewsletterConfigFactory.mapToSingle(newsletterConfig));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description set newsletterConfig
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function setNewsletterConfig(req: Request, res: Response): Promise<any> {
|
||||
let comTypeId = req.body.comTypeId;
|
||||
let config = req.body.config;
|
||||
|
||||
let createNewsletterConfig: SetNewsletterConfigCommand = {
|
||||
comTypeId,
|
||||
config,
|
||||
};
|
||||
let id = await NewsletterConfigCommandHandler.set(createNewsletterConfig);
|
||||
|
||||
res.send(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description delete award
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function deleteNewsletterConfig(req: Request, res: Response): Promise<any> {
|
||||
const comTypeId = parseInt(req.params.comTypeId);
|
||||
|
||||
let deleteNewsletterConfig: DeleteNewsletterConfigCommand = {
|
||||
comTypeId: comTypeId,
|
||||
};
|
||||
await NewsletterConfigCommandHandler.delete(deleteNewsletterConfig);
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
387
src/controller/admin/newsletterController.ts
Normal file
387
src/controller/admin/newsletterController.ts
Normal file
|
@ -0,0 +1,387 @@
|
|||
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 { PdfExport } from "../../helpers/pdfExport";
|
||||
import UserService from "../../service/userService";
|
||||
import { TemplateHelper } from "../../helpers/templateHelper";
|
||||
import MailHelper from "../../helpers/mailHelper";
|
||||
import { NewsletterEventType, NewsletterHelper } from "../../helpers/newsletterHelper";
|
||||
import { Salutation } from "../../enums/salutation";
|
||||
|
||||
/**
|
||||
* @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 recipients 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 recipients = await NewsletterRecipientsService.getAll(newsletterId);
|
||||
|
||||
res.json(NewsletterRecipientsFactory.mapToBase(recipients));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 filesInFolder = FileSystemHelper.getFilesInDirectory(
|
||||
`newsletter/${newsletter.id}_${newsletter.title.replace(" ", "")}`
|
||||
);
|
||||
|
||||
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(
|
||||
"newsletter",
|
||||
`${newsletter.id}_${newsletter.title.replace(" ", "")}`,
|
||||
filename
|
||||
);
|
||||
|
||||
res.sendFile(filepath, {
|
||||
headers: {
|
||||
"Content-Type": "application/pdf",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 = NewsletterHelper.buildData(newsletter, dates);
|
||||
data.recipient = {
|
||||
firstname: recipient.firstname,
|
||||
lastname: recipient.lastname,
|
||||
salutation: Salutation.none,
|
||||
nameaffix: "",
|
||||
street: "Straße",
|
||||
streetNumber: "Hausnummer",
|
||||
streetNumberAdd: "Adresszusatz",
|
||||
};
|
||||
|
||||
let pdf = await PdfExport.renderFile({
|
||||
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
|
||||
* @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 get newsletter printout progress by id
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getNewsletterPrintoutProgressById(req: Request, res: Response): Promise<any> {
|
||||
let newsletterId = parseInt(req.params.newsletterId);
|
||||
|
||||
res.setHeader("Content-Type", "text/event-stream");
|
||||
res.setHeader("Cache-Control", "no-cache");
|
||||
res.setHeader("Connection", "keep-alive");
|
||||
|
||||
res.flushHeaders();
|
||||
|
||||
const progressHandler = (data: NewsletterEventType) => {
|
||||
if (data.newsletterId == newsletterId && data.kind == "pdf") {
|
||||
res.write(JSON.stringify(data));
|
||||
}
|
||||
};
|
||||
|
||||
const completeHandler = (data: NewsletterEventType) => {
|
||||
if (data.newsletterId == newsletterId && data.kind == "pdf") {
|
||||
res.write(JSON.stringify(data));
|
||||
res.end();
|
||||
}
|
||||
};
|
||||
|
||||
NewsletterHelper.jobStatus.on("progress", progressHandler);
|
||||
NewsletterHelper.jobStatus.on("complete", completeHandler);
|
||||
|
||||
req.on("close", () => {
|
||||
NewsletterHelper.jobStatus.off("progress", progressHandler);
|
||||
NewsletterHelper.jobStatus.off("complete", completeHandler);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description create newsletter printouts for each member 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);
|
||||
|
||||
await NewsletterHelper.printPdfs(newsletterId);
|
||||
|
||||
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 = NewsletterHelper.buildData(newsletter, dates);
|
||||
data.recipient = {
|
||||
firstname: recipient.firstname,
|
||||
lastname: recipient.lastname,
|
||||
salutation: Salutation.none,
|
||||
nameaffix: "",
|
||||
street: "Straße",
|
||||
streetNumber: "Hausnummer",
|
||||
streetNumberAdd: "Adresszusatz",
|
||||
};
|
||||
|
||||
const { body } = await TemplateHelper.renderFileForModule({
|
||||
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);
|
||||
|
||||
await NewsletterHelper.sendMails(newsletterId);
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get newsletter sending progress by id
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getNewsletterSendingProgressById(req: Request, res: Response): Promise<any> {
|
||||
let newsletterId = parseInt(req.params.newsletterId);
|
||||
|
||||
res.setHeader("Content-Type", "text/event-stream");
|
||||
res.setHeader("Cache-Control", "no-cache");
|
||||
res.setHeader("Connection", "keep-alive");
|
||||
|
||||
res.flushHeaders();
|
||||
|
||||
const progressHandler = (data: NewsletterEventType) => {
|
||||
if (data.newsletterId == newsletterId && data.kind == "mail") {
|
||||
res.write(JSON.stringify(data));
|
||||
}
|
||||
};
|
||||
|
||||
const completeHandler = (data: NewsletterEventType) => {
|
||||
if (data.newsletterId == newsletterId && data.kind == "mail") {
|
||||
res.write(JSON.stringify(data));
|
||||
res.end();
|
||||
}
|
||||
};
|
||||
|
||||
NewsletterHelper.jobStatus.on("progress", progressHandler);
|
||||
NewsletterHelper.jobStatus.on("complete", completeHandler);
|
||||
|
||||
req.on("close", () => {
|
||||
NewsletterHelper.jobStatus.off("progress", progressHandler);
|
||||
NewsletterHelper.jobStatus.off("complete", completeHandler);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
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<number>;
|
||||
|
||||
let syncRecipients: SynchronizeNewsletterRecipientsCommand = {
|
||||
newsletterId,
|
||||
recipients: recipients,
|
||||
};
|
||||
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
|
||||
|
@ -237,9 +238,10 @@ export async function createProtocolPrintoutById(req: Request, res: Response): P
|
|||
)}`;
|
||||
|
||||
await PdfExport.renderFile({
|
||||
template: "protocol.template.html",
|
||||
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.normalizePath("protocol", filename),
|
||||
protocolId,
|
||||
};
|
||||
await ProtocolPrintoutCommandHandler.create(printout);
|
||||
|
|
|
@ -91,7 +91,7 @@ export async function executeQuery(req: Request, res: Response): Promise<any> {
|
|||
|
||||
res.json({
|
||||
stats: "success",
|
||||
rows: rows,
|
||||
rows: DynamicQueryBuilder.flattenQueryResult(rows),
|
||||
total: total,
|
||||
offset: offset,
|
||||
count: count,
|
||||
|
|
120
src/controller/admin/templateController.ts
Normal file
120
src/controller/admin/templateController.ts
Normal file
|
@ -0,0 +1,120 @@
|
|||
import { Request, Response } from "express";
|
||||
import TemplateService from "../../service/templateService";
|
||||
import TemplateFactory from "../../factory/admin/template";
|
||||
import { CreateTemplateCommand, DeleteTemplateCommand, UpdateTemplateCommand } from "../../command/templateCommand";
|
||||
import TemplateCommandHandler from "../../command/templateCommandHandler";
|
||||
|
||||
/**
|
||||
* @description get all templates
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getAllTemplates(req: Request, res: Response): Promise<any> {
|
||||
let templates = await TemplateService.getAll();
|
||||
|
||||
res.json(TemplateFactory.mapToBase(templates));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get template by id
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getTemplateById(req: Request, res: Response): Promise<any> {
|
||||
const id = parseInt(req.params.id);
|
||||
let template = await TemplateService.getById(id);
|
||||
|
||||
res.json(TemplateFactory.mapToSingle(template));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description create new template
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function createTemplate(req: Request, res: Response): Promise<any> {
|
||||
const template = req.body.template;
|
||||
const description = req.body.description;
|
||||
|
||||
let createTemplate: CreateTemplateCommand = {
|
||||
template: template,
|
||||
description: description,
|
||||
};
|
||||
let id = await TemplateCommandHandler.create(createTemplate);
|
||||
|
||||
res.status(200).send(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description clone template
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function cloneTemplate(req: Request, res: Response): Promise<any> {
|
||||
const cloneId = req.body.cloneId;
|
||||
|
||||
const { template, description, design, html } = await TemplateService.getById(cloneId);
|
||||
|
||||
let createTemplate: CreateTemplateCommand = {
|
||||
template: "",
|
||||
description: "",
|
||||
};
|
||||
let id = await TemplateCommandHandler.create(createTemplate);
|
||||
|
||||
let updateTemplate: UpdateTemplateCommand = {
|
||||
id: id,
|
||||
template: template + " - Kopie",
|
||||
description: description,
|
||||
design: design,
|
||||
html: html,
|
||||
};
|
||||
await TemplateCommandHandler.update(updateTemplate);
|
||||
|
||||
res.status(200).send(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description update template
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function updateTemplate(req: Request, res: Response): Promise<any> {
|
||||
const id = parseInt(req.params.id);
|
||||
const template = req.body.template;
|
||||
const description = req.body.description;
|
||||
const design = req.body.design;
|
||||
const html = req.body.html;
|
||||
|
||||
let updateTemplate: UpdateTemplateCommand = {
|
||||
id: id,
|
||||
template: template,
|
||||
description: description,
|
||||
design: design,
|
||||
html: html,
|
||||
};
|
||||
await TemplateCommandHandler.update(updateTemplate);
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description delete template
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function deleteTemplate(req: Request, res: Response): Promise<any> {
|
||||
const id = parseInt(req.params.id);
|
||||
|
||||
let deleteTemplate: DeleteTemplateCommand = {
|
||||
id: id,
|
||||
};
|
||||
await TemplateCommandHandler.delete(deleteTemplate);
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
91
src/controller/admin/templateUsageController.ts
Normal file
91
src/controller/admin/templateUsageController.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
import { Request, Response } from "express";
|
||||
import TemplateUsageService from "../../service/templateUsageService";
|
||||
import TemplateUsageFactory from "../../factory/admin/templateUsage";
|
||||
import { UpdateTemplateUsageCommand } from "../../command/templateUsageCommand";
|
||||
import TemplateUsageCommandHandler from "../../command/templateUsageCommandHandler";
|
||||
import PermissionHelper from "../../helpers/permissionHelper";
|
||||
import ForbiddenRequestException from "../../exceptions/forbiddenRequestException";
|
||||
import { PermissionModule } from "../../type/permissionTypes";
|
||||
import { PdfExport } from "../../helpers/pdfExport";
|
||||
import { DemoDataHelper } from "../../helpers/demoDataHelper";
|
||||
|
||||
/**
|
||||
* @description get all templateUsages
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getAllTemplateUsages(req: Request, res: Response): Promise<any> {
|
||||
let templateUsages = await TemplateUsageService.getAll();
|
||||
|
||||
if (!req.isOwner) {
|
||||
templateUsages = templateUsages.filter((tu) => {
|
||||
return (
|
||||
PermissionHelper.can(req.permissions, "update", "settings", tu.scope) ||
|
||||
PermissionHelper.can(req.permissions, "update", "club", tu.scope)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
res.json(TemplateUsageFactory.mapToBase(templateUsages));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description print demo of templateUsage
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function printTemplateUsageDemo(req: Request, res: Response): Promise<any> {
|
||||
const scope = req.params.scope as PermissionModule;
|
||||
|
||||
let demoData = DemoDataHelper.getData(scope);
|
||||
let pdf = await PdfExport.renderFile({
|
||||
template: scope,
|
||||
saveToDisk: false,
|
||||
data: demoData,
|
||||
});
|
||||
|
||||
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 update templateUsage
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function updateTemplateUsage(req: Request, res: Response): Promise<any> {
|
||||
const scope = req.params.scope;
|
||||
let allowedSettings = PermissionHelper.can(
|
||||
req.permissions,
|
||||
"update",
|
||||
"settings",
|
||||
req.params.scope as PermissionModule
|
||||
);
|
||||
let allowedClub = PermissionHelper.can(req.permissions, "update", "club", req.params.scope as PermissionModule);
|
||||
|
||||
if (!(req.isOwner || allowedSettings || allowedClub)) {
|
||||
throw new ForbiddenRequestException(`missing permission for editing scope ${req.params.scope}`);
|
||||
}
|
||||
|
||||
const headerId = req.body.headerId ?? null;
|
||||
const bodyId = req.body.bodyId ?? null;
|
||||
const footerId = req.body.footerId ?? null;
|
||||
|
||||
let updateTemplateUsage: UpdateTemplateUsageCommand = {
|
||||
scope: scope,
|
||||
headerId: headerId,
|
||||
bodyId: bodyId,
|
||||
footerId: footerId,
|
||||
};
|
||||
await TemplateUsageCommandHandler.update(updateTemplateUsage);
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
|
@ -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}`
|
||||
|
|
|
@ -6,6 +6,7 @@ import { createEvents } from "ics";
|
|||
import moment from "moment";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
import CalendarFactory from "../factory/admin/calendar";
|
||||
import { CalendarHelper } from "../helpers/calendarHelper";
|
||||
|
||||
/**
|
||||
* @description get all calendar items by types or nscdr
|
||||
|
@ -16,6 +17,7 @@ import CalendarFactory from "../factory/admin/calendar";
|
|||
*/
|
||||
export async function getCalendarItemsByTypes(req: Request, res: Response): Promise<any> {
|
||||
let types = Array.isArray(req.query.types) ? req.query.types : [req.query.types];
|
||||
let nscdr = req.query.nscdr == "true";
|
||||
let output = (req.query.output as "ics" | "json") ?? "ics";
|
||||
|
||||
if (output != "ics" && output != "json") {
|
||||
|
@ -33,7 +35,10 @@ export async function getCalendarItemsByTypes(req: Request, res: Response): Prom
|
|||
ti.passphrase == "" ||
|
||||
ti.passphrase == (types as Array<string>).find((t) => t.includes(ti.type)).split(":")[1]
|
||||
);
|
||||
items = await CalendarService.getByTypes(typeIds.map((t) => t.id));
|
||||
items = await CalendarService.getByTypes(
|
||||
typeIds.map((t) => t.id),
|
||||
nscdr
|
||||
);
|
||||
} else {
|
||||
items = await CalendarService.getByTypeNSCDR();
|
||||
}
|
||||
|
@ -41,59 +46,8 @@ export async function getCalendarItemsByTypes(req: Request, res: Response): Prom
|
|||
if (output == "json") {
|
||||
res.json(CalendarFactory.mapToBase(items));
|
||||
} else {
|
||||
let events = createEvents(
|
||||
items.map((i) => ({
|
||||
calName: process.env.CLUB_NAME,
|
||||
uid: i.id,
|
||||
sequence: 1,
|
||||
...(i.allDay
|
||||
? {
|
||||
start: moment(i.starttime)
|
||||
.format("YYYY-M-D")
|
||||
.split("-")
|
||||
.map((a) => parseInt(a)) as [number, number, number],
|
||||
end: moment(i.endtime)
|
||||
.format("YYYY-M-D")
|
||||
.split("-")
|
||||
.map((a) => parseInt(a)) as [number, number, number],
|
||||
}
|
||||
: {
|
||||
start: moment(i.starttime)
|
||||
.format("YYYY-M-D-H-m")
|
||||
.split("-")
|
||||
.map((a) => parseInt(a)) as [number, number, number, number, number],
|
||||
end: moment(i.endtime)
|
||||
.format("YYYY-M-D-H-m")
|
||||
.split("-")
|
||||
.map((a) => parseInt(a)) as [number, number, number, number, number],
|
||||
}),
|
||||
title: i.title,
|
||||
description: i.content,
|
||||
location: i.location,
|
||||
categories: [i.type.type],
|
||||
created: moment(i.createdAt)
|
||||
.format("YYYY-M-D-H-m")
|
||||
.split("-")
|
||||
.map((a) => parseInt(a)) as [number, number, number, number, number],
|
||||
lastModified: moment(i.updatedAt)
|
||||
.format("YYYY-M-D-H-m")
|
||||
.split("-")
|
||||
.map((a) => parseInt(a)) as [number, number, number, number, number],
|
||||
transp: "OPAQUE" as "OPAQUE",
|
||||
url: "https://www.ff-merching.de",
|
||||
alarms: [
|
||||
{
|
||||
action: "display",
|
||||
description: "Erinnerung",
|
||||
trigger: {
|
||||
minutes: 30,
|
||||
before: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}))
|
||||
);
|
||||
let { error, value } = CalendarHelper.buildICS(items);
|
||||
|
||||
res.type("ics").send(events.value);
|
||||
res.type("ics").send(value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}`
|
||||
|
|
|
@ -51,6 +51,16 @@ import { memberExecutivePositionsView } from "./views/memberExecutivePositionVie
|
|||
import { memberQualificationsView } from "./views/memberQualificationsView";
|
||||
import { membershipView } from "./views/membershipsView";
|
||||
import { MemberDataViews1734520998539 } from "./migrations/1734520998539-memberDataViews";
|
||||
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";
|
||||
import { newsletterConfig } from "./entity/newsletterConfig";
|
||||
import { NewsletterConfig1735207446910 } from "./migrations/1735207446910-newsletterConfig";
|
||||
|
||||
const dataSource = new DataSource({
|
||||
type: DB_TYPE as any,
|
||||
|
@ -90,6 +100,12 @@ const dataSource = new DataSource({
|
|||
calendar,
|
||||
calendarType,
|
||||
query,
|
||||
template,
|
||||
templateUsage,
|
||||
newsletter,
|
||||
newsletterDates,
|
||||
newsletterRecipients,
|
||||
newsletterConfig,
|
||||
memberView,
|
||||
memberExecutivePositionsView,
|
||||
memberQualificationsView,
|
||||
|
@ -112,6 +128,10 @@ const dataSource = new DataSource({
|
|||
SecuringCalendarType1733249553766,
|
||||
QueryStore1734187754677,
|
||||
MemberDataViews1734520998539,
|
||||
Template1734854680201,
|
||||
TemplateUsage1734949173739,
|
||||
Newsletter1735118780511,
|
||||
NewsletterConfig1735207446910,
|
||||
],
|
||||
migrationsRun: true,
|
||||
migrationsTransactionMode: "each",
|
||||
|
|
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",
|
||||
},
|
||||
};
|
56
src/demodata/protocol.data.ts
Normal file
56
src/demodata/protocol.data.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { member } from "../entity/member";
|
||||
import { protocolAgenda } from "../entity/protocolAgenda";
|
||||
import { protocolDecision } from "../entity/protocolDecision";
|
||||
import { protocolVoting } from "../entity/protocolVoting";
|
||||
|
||||
export const protocolDemoData: {
|
||||
title: string;
|
||||
summary: string;
|
||||
iteration: number;
|
||||
date: string;
|
||||
start: string;
|
||||
end: string;
|
||||
agenda: Array<Partial<protocolAgenda>>;
|
||||
decisions: Array<Partial<protocolDecision>>;
|
||||
presence: Array<Partial<member>>;
|
||||
votings: Array<Partial<protocolVoting>>;
|
||||
} = {
|
||||
title: "Beispiel Protokoll Daten",
|
||||
summary: "Zusammenfassung der Demodaten.",
|
||||
iteration: 1,
|
||||
date: new Date().toLocaleDateString("de-DE", {
|
||||
weekday: "long",
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
}),
|
||||
start: "19:00:00",
|
||||
end: "21:00:00",
|
||||
agenda: [
|
||||
{
|
||||
topic: "Protokoll-TOP",
|
||||
context: "Inhalt des Punktes",
|
||||
},
|
||||
],
|
||||
decisions: [
|
||||
{
|
||||
topic: "Entscheidung yz",
|
||||
context: "Inhalt der Entscheidung",
|
||||
},
|
||||
],
|
||||
presence: [
|
||||
{
|
||||
firstname: "Julian",
|
||||
lastname: "Krauser",
|
||||
},
|
||||
],
|
||||
votings: [
|
||||
{
|
||||
topic: "Abstimmung xy",
|
||||
context: "Inhalt der Abstimmung",
|
||||
favour: 1,
|
||||
abstain: 2,
|
||||
against: 3,
|
||||
},
|
||||
],
|
||||
};
|
|
@ -40,7 +40,7 @@ export class member {
|
|||
birthdate: Date;
|
||||
|
||||
@OneToMany(() => communication, (communications) => communications.member)
|
||||
communications: communication;
|
||||
communications: communication[];
|
||||
|
||||
@OneToOne(() => communication, {
|
||||
nullable: true,
|
||||
|
|
45
src/entity/newsletter.ts
Normal file
45
src/entity/newsletter.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
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;
|
||||
|
||||
@Column({ type: "int", nullable: true })
|
||||
recipientsByQueryId?: number;
|
||||
|
||||
@OneToMany(() => newsletterDates, (dates) => dates.newsletter)
|
||||
dates: newsletterDates[];
|
||||
|
||||
@OneToMany(() => newsletterRecipients, (recipient) => recipient.newsletter)
|
||||
recipients: newsletterRecipients[];
|
||||
|
||||
@ManyToOne(() => query, {
|
||||
nullable: true,
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
recipientsByQuery?: query;
|
||||
}
|
30
src/entity/newsletterConfig.ts
Normal file
30
src/entity/newsletterConfig.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { Column, Entity, ManyToOne, PrimaryColumn } from "typeorm";
|
||||
import { NewsletterConfigType } from "../enums/newsletterConfigType";
|
||||
import { communicationType } from "./communicationType";
|
||||
|
||||
@Entity()
|
||||
export class newsletterConfig {
|
||||
@PrimaryColumn({ type: "int" })
|
||||
comTypeId: number;
|
||||
|
||||
@Column({
|
||||
type: "varchar",
|
||||
length: "255",
|
||||
transformer: {
|
||||
to(value: NewsletterConfigType) {
|
||||
return value.toString();
|
||||
},
|
||||
from(value: string) {
|
||||
return NewsletterConfigType[value as keyof typeof NewsletterConfigType];
|
||||
},
|
||||
},
|
||||
})
|
||||
config: NewsletterConfigType;
|
||||
|
||||
@ManyToOne(() => communicationType, {
|
||||
nullable: false,
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
comType: communicationType;
|
||||
}
|
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: "varchar" })
|
||||
calendarId: string;
|
||||
|
||||
@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;
|
||||
}
|
26
src/entity/newsletterRecipients.ts
Normal file
26
src/entity/newsletterRecipients.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
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;
|
||||
|
||||
@ManyToOne(() => newsletter, (newsletter) => newsletter.recipients, {
|
||||
nullable: false,
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
newsletter: newsletter;
|
||||
|
||||
@ManyToOne(() => member, {
|
||||
nullable: false,
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
member: member;
|
||||
}
|
30
src/entity/template.ts
Normal file
30
src/entity/template.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { Column, Entity, PrimaryColumn } from "typeorm";
|
||||
|
||||
@Entity()
|
||||
export class template {
|
||||
@PrimaryColumn({ generated: "increment", type: "int" })
|
||||
id: number;
|
||||
|
||||
@Column({ type: "varchar", length: 255 })
|
||||
template: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255, nullable: true })
|
||||
description?: string;
|
||||
|
||||
@Column({
|
||||
type: "text",
|
||||
default: "{}",
|
||||
transformer: {
|
||||
to(value: object) {
|
||||
return JSON.stringify(value);
|
||||
},
|
||||
from(value: string) {
|
||||
return JSON.parse(value);
|
||||
},
|
||||
},
|
||||
})
|
||||
design: object;
|
||||
|
||||
@Column({ type: "text", default: "" })
|
||||
html: string;
|
||||
}
|
39
src/entity/templateUsage.ts
Normal file
39
src/entity/templateUsage.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { Column, Entity, ManyToOne, PrimaryColumn } from "typeorm";
|
||||
import { template } from "./template";
|
||||
import { PermissionModule } from "../type/permissionTypes";
|
||||
|
||||
@Entity()
|
||||
export class templateUsage {
|
||||
@PrimaryColumn({ type: "varchar", length: 255 })
|
||||
scope: PermissionModule;
|
||||
|
||||
@Column({ type: "number", nullable: true })
|
||||
headerId: number | null;
|
||||
|
||||
@Column({ type: "number", nullable: true })
|
||||
bodyId: number | null;
|
||||
|
||||
@Column({ type: "number", nullable: true })
|
||||
footerId: number | null;
|
||||
|
||||
@ManyToOne(() => template, {
|
||||
nullable: true,
|
||||
onDelete: "RESTRICT",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
header: template | null;
|
||||
|
||||
@ManyToOne(() => template, {
|
||||
nullable: true,
|
||||
onDelete: "RESTRICT",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
body: template | null;
|
||||
|
||||
@ManyToOne(() => template, {
|
||||
nullable: true,
|
||||
onDelete: "RESTRICT",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
footer: template | null;
|
||||
}
|
4
src/enums/newsletterConfigType.ts
Normal file
4
src/enums/newsletterConfigType.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export enum NewsletterConfigType {
|
||||
pdf = "pdf",
|
||||
mail = "mail",
|
||||
}
|
|
@ -20,6 +20,7 @@ export const MAIL_PORT = Number(process.env.MAIL_PORT ?? "587");
|
|||
export const MAIL_SECURE = process.env.MAIL_SECURE ?? "false";
|
||||
|
||||
export const CLUB_NAME = process.env.CLUB_NAME ?? "";
|
||||
export const CLUB_WEBSITE = process.env.CLUB_WEBSITE ?? "";
|
||||
|
||||
export function configCheck() {
|
||||
if (DB_TYPE != "mysql" && DB_TYPE != "sqlite") throw new Error("set valid value to DB_TYPE (mysql|sqlite)");
|
||||
|
@ -39,6 +40,13 @@ export function configCheck() {
|
|||
if (MAIL_HOST == "" || typeof MAIL_HOST != "string") throw new Error("set valid value to MAIL_HOST");
|
||||
if (typeof MAIL_PORT != "number") throw new Error("set valid numeric value to MAIL_PORT");
|
||||
if (MAIL_SECURE != "true" && MAIL_SECURE != "false") throw new Error("set 'true' or 'false' to MAIL_SECURE");
|
||||
|
||||
console.log(CLUB_WEBSITE);
|
||||
if (
|
||||
CLUB_WEBSITE != "" &&
|
||||
!/^(http(s):\/\/.)[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/.test(CLUB_WEBSITE)
|
||||
)
|
||||
throw new Error("CLUB_WEBSITE is not valid url");
|
||||
}
|
||||
|
||||
function checkMS(input: string, origin: string) {
|
||||
|
|
33
src/factory/admin/newsletter.ts
Normal file
33
src/factory/admin/newsletter.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
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,
|
||||
recipientsByQueryId: record?.recipientsByQuery ? record.recipientsByQuery.id : null,
|
||||
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));
|
||||
}
|
||||
}
|
27
src/factory/admin/newsletterConfig.ts
Normal file
27
src/factory/admin/newsletterConfig.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { newsletterConfig } from "../../entity/newsletterConfig";
|
||||
import { NewsletterConfigViewModel } from "../../viewmodel/admin/newsletterConfig.models";
|
||||
import CommunicationTypeFactory from "./communicationType";
|
||||
|
||||
export default abstract class NewsletterConfigFactory {
|
||||
/**
|
||||
* @description map record to newsletterConfig
|
||||
* @param {newsletterConfig} record
|
||||
* @returns {NewsletterConfigViewModel}
|
||||
*/
|
||||
public static mapToSingle(record: newsletterConfig): NewsletterConfigViewModel {
|
||||
return {
|
||||
comTypeId: record.comTypeId,
|
||||
config: record.config,
|
||||
comType: record?.comType ? CommunicationTypeFactory.mapToSingle(record.comType) : null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description map records to newsletterConfig
|
||||
* @param {Array<newsletterConfig>} records
|
||||
* @returns {Array<NewsletterConfigViewModel>}
|
||||
*/
|
||||
public static mapToBase(records: Array<newsletterConfig>): Array<NewsletterConfigViewModel> {
|
||||
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));
|
||||
}
|
||||
}
|
27
src/factory/admin/newsletterRecipients.ts
Normal file
27
src/factory/admin/newsletterRecipients.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
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,
|
||||
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));
|
||||
}
|
||||
}
|
28
src/factory/admin/template.ts
Normal file
28
src/factory/admin/template.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { template } from "../../entity/template";
|
||||
import { TemplateViewModel } from "../../viewmodel/admin/template.models";
|
||||
|
||||
export default abstract class TemplateFactory {
|
||||
/**
|
||||
* @description map record to template
|
||||
* @param {template} record
|
||||
* @returns {TemplateViewModel}
|
||||
*/
|
||||
public static mapToSingle(record: template): TemplateViewModel {
|
||||
return {
|
||||
id: record.id,
|
||||
template: record.template,
|
||||
description: record.description,
|
||||
design: record.design,
|
||||
html: record.html,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description map records to template
|
||||
* @param {Array<template>} records
|
||||
* @returns {Array<TemplateViewModel>}
|
||||
*/
|
||||
public static mapToBase(records: Array<template>): Array<TemplateViewModel> {
|
||||
return records.map((r) => this.mapToSingle(r));
|
||||
}
|
||||
}
|
27
src/factory/admin/templateUsage.ts
Normal file
27
src/factory/admin/templateUsage.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { templateUsage } from "../../entity/templateUsage";
|
||||
import { TemplateUsageViewModel } from "../../viewmodel/admin/templateUsage.models";
|
||||
|
||||
export default abstract class TemplateUsageFactory {
|
||||
/**
|
||||
* @description map record to templateUsage
|
||||
* @param {templateUsage} record
|
||||
* @returns {TemplateUsageViewModel}
|
||||
*/
|
||||
public static mapToSingle(record: templateUsage): TemplateUsageViewModel {
|
||||
return {
|
||||
scope: record.scope,
|
||||
header: record.header ? { id: record.header.id, template: record.header.template } : null,
|
||||
body: record.body ? { id: record.body.id, template: record.body.template } : null,
|
||||
footer: record.footer ? { id: record.footer.id, template: record.footer.template } : null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description map records to templateUsage
|
||||
* @param {Array<templateUsage>} records
|
||||
* @returns {Array<TemplateUsageViewModel>}
|
||||
*/
|
||||
public static mapToBase(records: Array<templateUsage>): Array<TemplateUsageViewModel> {
|
||||
return records.map((r) => this.mapToSingle(r));
|
||||
}
|
||||
}
|
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,
|
||||
},
|
||||
},
|
||||
],
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
16
src/helpers/demoDataHelper.ts
Normal file
16
src/helpers/demoDataHelper.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { newsletterDemoData } from "../demodata/newsletter.data";
|
||||
import { protocolDemoData } from "../demodata/protocol.data";
|
||||
import { PermissionModule } from "../type/permissionTypes";
|
||||
|
||||
export abstract class DemoDataHelper {
|
||||
static getData(scope: PermissionModule) {
|
||||
switch (scope) {
|
||||
case "protocol":
|
||||
return protocolDemoData;
|
||||
case "newsletter":
|
||||
return newsletterDemoData;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { Brackets, DataSource, NotBrackets, ObjectLiteral, SelectQueryBuilder, WhereExpressionBuilder } from "typeorm";
|
||||
import { dataSource } from "../data-source";
|
||||
import { ConditionStructure, DynamicQueryStructure } from "../type/dynamicQueries";
|
||||
import { ConditionStructure, DynamicQueryStructure, FieldType, QueryResult } from "../type/dynamicQueries";
|
||||
import { TableMeta } from "../type/tableMeta";
|
||||
|
||||
export default abstract class DynamicQueryBuilder {
|
||||
|
@ -229,4 +229,141 @@ export default abstract class DynamicQueryBuilder {
|
|||
|
||||
return { query, parameters };
|
||||
}
|
||||
|
||||
public static flattenQueryResult(result: Array<QueryResult>): Array<{ [key: string]: FieldType }> {
|
||||
function flatten(row: QueryResult, prefix: string = ""): Array<{ [key: string]: FieldType }> {
|
||||
let results: Array<{ [key: string]: FieldType }> = [{}];
|
||||
|
||||
for (const key in row) {
|
||||
const value = row[key];
|
||||
const newKey = prefix ? `${prefix}_${key}` : key;
|
||||
|
||||
if (Array.isArray(value) && value.every((item) => typeof item === "object" && item !== null)) {
|
||||
const arrayResults: Array<{ [key: string]: FieldType }> = [];
|
||||
value.forEach((item) => {
|
||||
const flattenedItems = flatten(item, newKey);
|
||||
arrayResults.push(...flattenedItems);
|
||||
});
|
||||
|
||||
const tempResults: Array<{ [key: string]: FieldType }> = [];
|
||||
results.forEach((res) => {
|
||||
arrayResults.forEach((arrRes) => {
|
||||
tempResults.push({ ...res, ...arrRes });
|
||||
});
|
||||
});
|
||||
results = tempResults;
|
||||
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
||||
const objResults = flatten(value as QueryResult, newKey);
|
||||
const tempResults: Array<{ [key: string]: FieldType }> = [];
|
||||
results.forEach((res) => {
|
||||
objResults.forEach((objRes) => {
|
||||
tempResults.push({ ...res, ...objRes });
|
||||
});
|
||||
});
|
||||
results = tempResults;
|
||||
} else {
|
||||
results.forEach((res) => {
|
||||
res[newKey] = String(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
const flattenedResults: Array<{ [key: string]: FieldType }> = [];
|
||||
|
||||
result.forEach((item) => {
|
||||
const flattenedItems = flatten(item);
|
||||
flattenedResults.push(...flattenedItems);
|
||||
});
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
61
src/helpers/fileSystemHelper.ts
Normal file
61
src/helpers/fileSystemHelper.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { readdirSync } from "fs";
|
||||
|
||||
export abstract class FileSystemHelper {
|
||||
static createFolder(...args: string[]) {
|
||||
const exportPath = this.formatPath(...args);
|
||||
if (!existsSync(exportPath)) {
|
||||
mkdirSync(exportPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
static readFile(...filePath: string[]) {
|
||||
return readFileSync(this.formatPath(...filePath), "utf8");
|
||||
}
|
||||
|
||||
static readFileasBase64(...filePath: string[]) {
|
||||
return readFileSync(this.formatPath(...filePath), "base64");
|
||||
}
|
||||
|
||||
static readTemplateFile(filePath: string) {
|
||||
return readFileSync(process.cwd() + filePath, "utf8");
|
||||
}
|
||||
|
||||
static writeFile(filePath: string, filename: string, file: any) {
|
||||
this.createFolder(filePath);
|
||||
let path = this.formatPath(filePath, filename);
|
||||
writeFileSync(path, file);
|
||||
}
|
||||
|
||||
static formatPath(...args: string[]) {
|
||||
return join(process.cwd(), "export", ...args);
|
||||
}
|
||||
|
||||
static normalizePath(...args: string[]) {
|
||||
return join(...args);
|
||||
}
|
||||
|
||||
static getFilesInDirectory(directoryPath: string, filetype?: string): string[] {
|
||||
const fullPath = this.formatPath(directoryPath);
|
||||
if (!existsSync(fullPath)) {
|
||||
return [];
|
||||
}
|
||||
return readdirSync(fullPath, { withFileTypes: true })
|
||||
.filter((dirent) => !dirent.isDirectory() && (!filetype || dirent.name.endsWith(filetype)))
|
||||
.map((dirent) => dirent.name);
|
||||
}
|
||||
|
||||
static clearDirectoryByFiletype(directoryPath: string, filetype: string) {
|
||||
const fullPath = this.formatPath(directoryPath);
|
||||
if (!existsSync(fullPath)) {
|
||||
return;
|
||||
}
|
||||
readdirSync(fullPath, { withFileTypes: true })
|
||||
.filter((dirent) => !dirent.isDirectory() && dirent.name.endsWith(filetype))
|
||||
.forEach((dirent) => {
|
||||
const filePath = join(fullPath, dirent.name);
|
||||
unlinkSync(filePath);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
import { Transporter, createTransport, TransportOptions } from "nodemailer";
|
||||
import { CLUB_NAME, MAIL_HOST, MAIL_PASSWORD, MAIL_PORT, MAIL_SECURE, MAIL_USERNAME } from "../env.defaults";
|
||||
import { Attachment } from "nodemailer/lib/mailer";
|
||||
|
||||
export default 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 +12,6 @@ export default class MailHelper {
|
|||
pass: MAIL_PASSWORD,
|
||||
},
|
||||
} as TransportOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description send mail
|
||||
|
@ -23,7 +20,12 @@ 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,
|
||||
attach: Array<Attachment> = []
|
||||
): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.transporter
|
||||
.sendMail({
|
||||
|
@ -31,6 +33,8 @@ export default class MailHelper {
|
|||
to: target,
|
||||
subject,
|
||||
text: content,
|
||||
html: content,
|
||||
attachments: attach,
|
||||
})
|
||||
.then((info) => resolve(info.messageId))
|
||||
.catch((e) => reject(e));
|
||||
|
|
341
src/helpers/newsletterHelper.ts
Normal file
341
src/helpers/newsletterHelper.ts
Normal file
|
@ -0,0 +1,341 @@
|
|||
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";
|
||||
import EventEmitter from "events";
|
||||
|
||||
export interface NewsletterEventType {
|
||||
kind: "pdf" | "mail";
|
||||
newsletterId: number;
|
||||
total: number;
|
||||
iteration: number;
|
||||
msg: string;
|
||||
}
|
||||
|
||||
export abstract class NewsletterHelper {
|
||||
public static jobStatus = new EventEmitter();
|
||||
|
||||
private static formatJobEmit(
|
||||
event: "progress" | "complete",
|
||||
kind: "pdf" | "mail",
|
||||
newsletterId: number,
|
||||
total: number,
|
||||
iteration: number,
|
||||
msg: string
|
||||
) {
|
||||
this.jobStatus.emit<NewsletterEventType>(event, { kind, newsletterId, total, iteration, msg, date: new Date() });
|
||||
}
|
||||
|
||||
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)
|
||||
);
|
||||
|
||||
this.formatJobEmit("progress", "mail", newsletterId, mailRecipients.length, 0, "starting sending");
|
||||
|
||||
for (const [index, rec] of mailRecipients.entries()) {
|
||||
this.formatJobEmit(
|
||||
"progress",
|
||||
"mail",
|
||||
newsletterId,
|
||||
mailRecipients.length,
|
||||
index,
|
||||
`start sending to ${rec.sendNewsletter.email}`
|
||||
);
|
||||
|
||||
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(() => {
|
||||
this.formatJobEmit(
|
||||
"progress",
|
||||
"mail",
|
||||
newsletterId,
|
||||
mailRecipients.length,
|
||||
index,
|
||||
`successfully sent to ${rec.sendNewsletter.email}`
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
this.formatJobEmit(
|
||||
"progress",
|
||||
"mail",
|
||||
newsletterId,
|
||||
mailRecipients.length,
|
||||
index,
|
||||
`failed to send to ${rec.sendNewsletter.email}`
|
||||
);
|
||||
console.log("mail send", err);
|
||||
});
|
||||
}
|
||||
|
||||
this.formatJobEmit(
|
||||
"complete",
|
||||
"mail",
|
||||
newsletterId,
|
||||
mailRecipients.length,
|
||||
mailRecipients.length,
|
||||
`completed sending process`
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
this.formatJobEmit("progress", "pdf", newsletterId, pdfRecipients.length, 0, "starting sending");
|
||||
|
||||
for (const [index, rec] of pdfRecipients.entries()) {
|
||||
this.formatJobEmit(
|
||||
"progress",
|
||||
"pdf",
|
||||
newsletterId,
|
||||
pdfRecipients.length,
|
||||
index,
|
||||
`start print for ${rec.lastname}, ${rec.firstname}`
|
||||
);
|
||||
|
||||
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(() => {
|
||||
this.formatJobEmit(
|
||||
"progress",
|
||||
"pdf",
|
||||
newsletterId,
|
||||
pdfRecipients.length,
|
||||
index,
|
||||
`successfully printed for ${rec.lastname}, ${rec.firstname}`
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
this.formatJobEmit(
|
||||
"progress",
|
||||
"pdf",
|
||||
newsletterId,
|
||||
pdfRecipients.length,
|
||||
index,
|
||||
`failed print for ${rec.lastname}, ${rec.firstname}`
|
||||
);
|
||||
console.log("pdf print", err);
|
||||
});
|
||||
}
|
||||
|
||||
this.formatJobEmit(
|
||||
"progress",
|
||||
"pdf",
|
||||
newsletterId,
|
||||
pdfRecipients.length,
|
||||
pdfRecipients.length,
|
||||
"starting pdf combine"
|
||||
);
|
||||
|
||||
await PdfExport.sqashToSingleFile(
|
||||
`newsletter/${newsletter.id}_${newsletter.title.replace(" ", "")}`,
|
||||
"allPdfsTogether",
|
||||
`newsletter/${newsletter.id}_${newsletter.title.replace(" ", "")}`
|
||||
)
|
||||
.then(() => {
|
||||
this.formatJobEmit(
|
||||
"progress",
|
||||
"pdf",
|
||||
newsletterId,
|
||||
pdfRecipients.length,
|
||||
pdfRecipients.length,
|
||||
"sucessfully combined pdf"
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
this.formatJobEmit(
|
||||
"progress",
|
||||
"pdf",
|
||||
newsletterId,
|
||||
pdfRecipients.length,
|
||||
pdfRecipients.length,
|
||||
"failed combining pdf"
|
||||
);
|
||||
console.log("pdf squash", err);
|
||||
});
|
||||
|
||||
this.formatJobEmit(
|
||||
"complete",
|
||||
"pdf",
|
||||
newsletterId,
|
||||
pdfRecipients.length,
|
||||
pdfRecipients.length,
|
||||
`completed printing process`
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,53 +1,84 @@
|
|||
import { readFileSync } from "fs";
|
||||
import Handlebars from "handlebars";
|
||||
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 getTemplate(template: string) {
|
||||
return readFileSync(process.cwd() + "/src/templates/" + template, "utf8");
|
||||
}
|
||||
|
||||
static async renderFile({
|
||||
template,
|
||||
title = "pdf-export Mitgliederverwaltung",
|
||||
filename,
|
||||
data,
|
||||
filename = null,
|
||||
data = {},
|
||||
saveToDisk = true,
|
||||
margins = { top: "15mm", bottom: "15mm" },
|
||||
folder = "",
|
||||
}: {
|
||||
template: string;
|
||||
title: string;
|
||||
filename: string;
|
||||
data: any;
|
||||
template: PermissionModule;
|
||||
title?: string;
|
||||
filename?: string;
|
||||
data?: any;
|
||||
saveToDisk?: boolean;
|
||||
margins?: { top: string; bottom: string };
|
||||
folder?: string;
|
||||
}) {
|
||||
const templateHtml = this.getTemplate(template);
|
||||
const templateCompiled = Handlebars.compile(templateHtml);
|
||||
const html = templateCompiled(data);
|
||||
if (folder != "") FileSystemHelper.createFolder(folder);
|
||||
|
||||
const { header, footer, body } = await TemplateHelper.renderFileForModule({
|
||||
module: template,
|
||||
headerData: data,
|
||||
bodyData: data,
|
||||
footerData: data,
|
||||
title: title,
|
||||
});
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
headless: true,
|
||||
args: ["--no-sandbox", "--disable-gpu", "--disable-setuid-sandbox"],
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
await page.setContent(html, { waitUntil: "domcontentloaded" });
|
||||
await page.setContent(body, { waitUntil: "domcontentloaded" });
|
||||
|
||||
await page.pdf({
|
||||
path: process.cwd() + `/export/${filename}.pdf`, // Name der PDF-Datei
|
||||
const exportPath = FileSystemHelper.formatPath(folder, `${filename}.pdf`);
|
||||
|
||||
let pdf = await page.pdf({
|
||||
...(saveToDisk ? { path: exportPath } : {}),
|
||||
format: "A4",
|
||||
printBackground: false,
|
||||
margin: {
|
||||
top: "15mm",
|
||||
bottom: "15mm",
|
||||
top: margins.top,
|
||||
bottom: margins.bottom,
|
||||
left: "10mm",
|
||||
right: "10mm",
|
||||
},
|
||||
displayHeaderFooter: true,
|
||||
headerTemplate: `<h1 style="font-size:10px; text-align:center; width:100%;">${title}</h1>`,
|
||||
footerTemplate: `
|
||||
<div style="font-size:10px; text-align:center; width:100%; color:#888;">
|
||||
Seite <span class="pageNumber"></span> von <span class="totalPages"></span>
|
||||
</div>
|
||||
`,
|
||||
headerTemplate: header,
|
||||
footerTemplate: footer,
|
||||
});
|
||||
|
||||
await browser.close();
|
||||
|
||||
return pdf;
|
||||
}
|
||||
|
||||
static async sqashToSingleFile(inputFolder: string, outputFile: string, outputFolder: string = "") {
|
||||
if (outputFolder != "") FileSystemHelper.createFolder(outputFolder);
|
||||
|
||||
let pdfFilePaths = FileSystemHelper.getFilesInDirectory(inputFolder, ".pdf");
|
||||
|
||||
if (pdfFilePaths.length == 0) return;
|
||||
|
||||
const mergedPdf = await PDFDocument.create();
|
||||
|
||||
for (const pdfPath of pdfFilePaths) {
|
||||
const pdfBytes = FileSystemHelper.readFileasBase64(inputFolder, pdfPath);
|
||||
const pdf = await PDFDocument.load(pdfBytes);
|
||||
const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
|
||||
copiedPages.forEach((page) => mergedPdf.addPage(page));
|
||||
}
|
||||
|
||||
const mergedPdfBytes = await mergedPdf.save();
|
||||
|
||||
FileSystemHelper.writeFile(outputFolder, `${outputFile}.pdf`, mergedPdfBytes);
|
||||
}
|
||||
}
|
||||
|
|
74
src/helpers/templateHelper.ts
Normal file
74
src/helpers/templateHelper.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
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 FileSystemHelper.readTemplateFile(`/src/templates/${template}.template.html`);
|
||||
}
|
||||
|
||||
static async getTemplateFromStore(templateId: number): Promise<string> {
|
||||
return (await TemplateService.getById(templateId)).html;
|
||||
}
|
||||
|
||||
static applyDataToTemplate(template: string, data: any): string {
|
||||
const normalizedTemplate = this.normalizeTemplate(template);
|
||||
const templateCompiled = Handlebars.compile(normalizedTemplate);
|
||||
return templateCompiled(data);
|
||||
}
|
||||
|
||||
static normalizeTemplate(template: string): string {
|
||||
template = template.replace(/<listend>.*?<\/listend>/g, "{{/each}}");
|
||||
template = template.replace(/<liststart\b[^>]*>(WDH Start: )?/g, "{{#each ");
|
||||
template = template.replace(/<\/liststart>/g, "}}");
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
static async renderFileForModule({
|
||||
module,
|
||||
title = "pdf-export Mitgliederverwaltung",
|
||||
headerData = {},
|
||||
bodyData = {},
|
||||
footerData = {},
|
||||
}: {
|
||||
module: PermissionModule;
|
||||
title?: string;
|
||||
headerData?: any;
|
||||
bodyData?: any;
|
||||
footerData?: any;
|
||||
}): Promise<{ header: string; body: string; footer: string; margins?: { top: string; bottom: string } }> {
|
||||
const moduleTemplates = await TemplateUsageService.getByScope(module);
|
||||
|
||||
let header = `<h1 style="font-size:10px; text-align:center; width:100%;">${title}</h1>`;
|
||||
let footer = "";
|
||||
let body = "";
|
||||
|
||||
if (moduleTemplates.headerId) {
|
||||
header = await this.getTemplateFromStore(moduleTemplates.headerId);
|
||||
header = this.applyDataToTemplate(header, { title, ...headerData });
|
||||
}
|
||||
|
||||
if (moduleTemplates.footerId) {
|
||||
footer = await this.getTemplateFromStore(moduleTemplates.footerId);
|
||||
} else {
|
||||
footer = this.getTemplateFromFile(module + ".footer");
|
||||
}
|
||||
footer = this.applyDataToTemplate(footer, footerData);
|
||||
|
||||
if (moduleTemplates.bodyId) {
|
||||
body = await this.getTemplateFromStore(moduleTemplates.bodyId);
|
||||
} else {
|
||||
body = this.getTemplateFromFile(module + ".body");
|
||||
}
|
||||
body = this.applyDataToTemplate(body, bodyData);
|
||||
|
||||
return {
|
||||
header,
|
||||
footer,
|
||||
body,
|
||||
};
|
||||
}
|
||||
}
|
28
src/migrations/1734854680201-template.ts
Normal file
28
src/migrations/1734854680201-template.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { MigrationInterface, QueryRunner, Table } from "typeorm";
|
||||
import { DB_TYPE } from "../env.defaults";
|
||||
|
||||
export class Template1734854680201 implements MigrationInterface {
|
||||
name = "Template1734854680201";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const variableType_int = DB_TYPE == "mysql" ? "int" : "integer";
|
||||
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: "template",
|
||||
columns: [
|
||||
{ name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" },
|
||||
{ name: "template", type: "varchar", length: "255", isNullable: false },
|
||||
{ name: "description", type: "varchar", length: "255", isNullable: true },
|
||||
{ name: "design", type: "text", isNullable: false, default: "'{}'" },
|
||||
{ name: "html", type: "text", isNullable: false, default: "''" },
|
||||
],
|
||||
}),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.dropTable("template");
|
||||
}
|
||||
}
|
75
src/migrations/1734949173739-templateUsage.ts
Normal file
75
src/migrations/1734949173739-templateUsage.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from "typeorm";
|
||||
import { DB_TYPE } from "../env.defaults";
|
||||
import { templateUsage } from "../entity/templateUsage";
|
||||
|
||||
export class TemplateUsage1734949173739 implements MigrationInterface {
|
||||
name = "TemplateUsage1734949173739";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const variableType_int = DB_TYPE == "mysql" ? "int" : "integer";
|
||||
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: "template_usage",
|
||||
columns: [
|
||||
{ name: "scope", type: "varchar", length: "255", isPrimary: true },
|
||||
{ name: "headerId", type: variableType_int, isNullable: true },
|
||||
{ name: "bodyId", type: variableType_int, isNullable: true },
|
||||
{ name: "footerId", type: variableType_int, isNullable: true },
|
||||
],
|
||||
}),
|
||||
true
|
||||
);
|
||||
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(templateUsage)
|
||||
.values({ scope: "protocol" })
|
||||
.orIgnore()
|
||||
.execute();
|
||||
|
||||
await queryRunner.createForeignKey(
|
||||
"template_usage",
|
||||
new TableForeignKey({
|
||||
columnNames: ["headerId"],
|
||||
referencedColumnNames: ["id"],
|
||||
referencedTableName: "template",
|
||||
onDelete: "RESTRICT",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
);
|
||||
await queryRunner.createForeignKey(
|
||||
"template_usage",
|
||||
new TableForeignKey({
|
||||
columnNames: ["bodyId"],
|
||||
referencedColumnNames: ["id"],
|
||||
referencedTableName: "template",
|
||||
onDelete: "RESTRICT",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
);
|
||||
await queryRunner.createForeignKey(
|
||||
"template_usage",
|
||||
new TableForeignKey({
|
||||
columnNames: ["footerId"],
|
||||
referencedColumnNames: ["id"],
|
||||
referencedTableName: "template",
|
||||
onDelete: "RESTRICT",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
const template_usage = await queryRunner.getTable("template_usage");
|
||||
let foreignKey = template_usage.foreignKeys.find((fk) => fk.columnNames.indexOf("headerId") !== -1);
|
||||
await queryRunner.dropForeignKey("template_usage", foreignKey);
|
||||
foreignKey = template_usage.foreignKeys.find((fk) => fk.columnNames.indexOf("bodyId") !== -1);
|
||||
await queryRunner.dropForeignKey("template_usage", foreignKey);
|
||||
foreignKey = template_usage.foreignKeys.find((fk) => fk.columnNames.indexOf("footerId") !== -1);
|
||||
await queryRunner.dropForeignKey("template_usage", foreignKey);
|
||||
|
||||
await queryRunner.dropTable("template_usage");
|
||||
}
|
||||
}
|
144
src/migrations/1735118780511-newsletter.ts
Normal file
144
src/migrations/1735118780511-newsletter.ts
Normal file
|
@ -0,0 +1,144 @@
|
|||
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";
|
||||
|
||||
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 },
|
||||
],
|
||||
}),
|
||||
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",
|
||||
})
|
||||
);
|
||||
|
||||
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");
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
39
src/migrations/1735207446910-newsletterConfig.ts
Normal file
39
src/migrations/1735207446910-newsletterConfig.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from "typeorm";
|
||||
import { DB_TYPE } from "../env.defaults";
|
||||
|
||||
export class NewsletterConfig1735207446910 implements MigrationInterface {
|
||||
name = "NewsletterConfig1735207446910";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const variableType_int = DB_TYPE == "mysql" ? "int" : "integer";
|
||||
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: "newsletter_config",
|
||||
columns: [
|
||||
{ name: "comTypeId", type: variableType_int, isPrimary: true, isNullable: false },
|
||||
{ name: "config", type: "varchar", length: "255", isNullable: false },
|
||||
],
|
||||
}),
|
||||
true
|
||||
);
|
||||
|
||||
await queryRunner.createForeignKey(
|
||||
"newsletter_config",
|
||||
new TableForeignKey({
|
||||
columnNames: ["comTypeId"],
|
||||
referencedColumnNames: ["id"],
|
||||
referencedTableName: "communication_type",
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
const table = await queryRunner.getTable("newsletter_config");
|
||||
const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("comTypeId") !== -1);
|
||||
await queryRunner.dropForeignKey("newsletter_config", foreignKey);
|
||||
await queryRunner.dropTable("newsletter_config");
|
||||
}
|
||||
}
|
|
@ -8,11 +8,15 @@ import membershipStatus from "./membershipStatus";
|
|||
import qualification from "./qualification";
|
||||
import calendarType from "./calendarType";
|
||||
import queryStore from "./queryStore";
|
||||
import template from "./template";
|
||||
import templateUsage from "./templateUsage";
|
||||
import newsletterConfig from "./newsletterConfig";
|
||||
|
||||
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";
|
||||
|
@ -39,11 +43,19 @@ router.use(
|
|||
router.use("/qualification", PermissionHelper.passCheckMiddleware("read", "settings", "qualification"), qualification);
|
||||
router.use("/calendartype", PermissionHelper.passCheckMiddleware("read", "settings", "calendar_type"), calendarType);
|
||||
router.use("/querystore", PermissionHelper.passCheckMiddleware("read", "settings", "query_store"), queryStore);
|
||||
router.use("/template", PermissionHelper.passCheckMiddleware("read", "settings", "template"), template);
|
||||
router.use("/templateusage", PermissionHelper.passCheckMiddleware("read", "settings", "template_usage"), templateUsage);
|
||||
router.use(
|
||||
"/newsletterconfig",
|
||||
PermissionHelper.passCheckMiddleware("read", "settings", "newsletter_config"),
|
||||
newsletterConfig
|
||||
);
|
||||
|
||||
router.use("/member", PermissionHelper.passCheckMiddleware("read", "club", "member"), member);
|
||||
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);
|
||||
|
|
112
src/routes/admin/newsletter.ts
Normal file
112
src/routes/admin/newsletter.ts
Normal file
|
@ -0,0 +1,112 @@
|
|||
import express, { Request, Response } from "express";
|
||||
import {
|
||||
createNewsletter,
|
||||
createNewsletterPrintoutById,
|
||||
getAllNewsletters,
|
||||
getNewsletterDatesById,
|
||||
getNewsletterById,
|
||||
getNewsletterRecipientsById,
|
||||
getNewsletterPrintoutByIdAndPrint,
|
||||
getNewsletterPrintoutsById,
|
||||
synchronizeNewsletterDatesById,
|
||||
synchronizeNewsletterById,
|
||||
synchronizeNewsletterRecipientsById,
|
||||
sendNewsletterById,
|
||||
createNewsletterMailPreviewById,
|
||||
createNewsletterPrintoutPreviewById,
|
||||
getNewsletterPrintoutProgressById,
|
||||
getNewsletterSendingProgressById,
|
||||
} 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("/:newsletterId/dates", async (req: Request, res: Response) => {
|
||||
await getNewsletterDatesById(req, res);
|
||||
});
|
||||
|
||||
router.get("/:newsletterId/recipients", async (req: Request, res: Response) => {
|
||||
await getNewsletterRecipientsById(req, res);
|
||||
});
|
||||
|
||||
router.get("/:newsletterId/printouts", async (req: Request, res: Response) => {
|
||||
await getNewsletterPrintoutsById(req, res);
|
||||
});
|
||||
|
||||
router.get("/:newsletterId/printout/:filename", async (req: Request, res: Response) => {
|
||||
await getNewsletterPrintoutByIdAndPrint(req, res);
|
||||
});
|
||||
|
||||
router.get("/:newsletterId/printoutpreview", async (req: Request, res: Response) => {
|
||||
await createNewsletterPrintoutPreviewById(req, res);
|
||||
});
|
||||
|
||||
router.get("/:newsletterId/printoutprogress", async (req: Request, res: Response) => {
|
||||
await getNewsletterPrintoutProgressById(req, res);
|
||||
});
|
||||
|
||||
router.get("/:newsletterId/sendprogress", async (req: Request, res: Response) => {
|
||||
await getNewsletterSendingProgressById(req, res);
|
||||
});
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
PermissionHelper.passCheckMiddleware("create", "club", "protocol"),
|
||||
async (req: Request, res: Response) => {
|
||||
await createNewsletter(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/:newsletterId/printout",
|
||||
PermissionHelper.passCheckMiddleware("create", "club", "protocol"),
|
||||
async (req: Request, res: Response) => {
|
||||
await createNewsletterPrintoutById(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
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"),
|
||||
async (req: Request, res: Response) => {
|
||||
await synchronizeNewsletterById(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/:newsletterId/synchronize/dates",
|
||||
PermissionHelper.passCheckMiddleware("update", "club", "protocol"),
|
||||
async (req: Request, res: Response) => {
|
||||
await synchronizeNewsletterDatesById(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/:newsletterId/synchronize/recipients",
|
||||
PermissionHelper.passCheckMiddleware("update", "club", "protocol"),
|
||||
async (req: Request, res: Response) => {
|
||||
await synchronizeNewsletterRecipientsById(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
36
src/routes/admin/newsletterConfig.ts
Normal file
36
src/routes/admin/newsletterConfig.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import express, { Request, Response } from "express";
|
||||
import {
|
||||
deleteNewsletterConfig,
|
||||
getAllNewsletterConfigs,
|
||||
getNewsletterConfigById,
|
||||
setNewsletterConfig,
|
||||
} from "../../controller/admin/newsletterConfigController";
|
||||
import PermissionHelper from "../../helpers/permissionHelper";
|
||||
|
||||
var router = express.Router({ mergeParams: true });
|
||||
|
||||
router.get("/", async (req: Request, res: Response) => {
|
||||
await getAllNewsletterConfigs(req, res);
|
||||
});
|
||||
|
||||
router.get("/:id", async (req: Request, res: Response) => {
|
||||
await getNewsletterConfigById(req, res);
|
||||
});
|
||||
|
||||
router.put(
|
||||
"/",
|
||||
PermissionHelper.passCheckMiddleware("create", "settings", "newsletter_config"),
|
||||
async (req: Request, res: Response) => {
|
||||
await setNewsletterConfig(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
"/:comTypeId",
|
||||
PermissionHelper.passCheckMiddleware("create", "settings", "newsletter_config"),
|
||||
async (req: Request, res: Response) => {
|
||||
await deleteNewsletterConfig(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
54
src/routes/admin/template.ts
Normal file
54
src/routes/admin/template.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
import express, { Request, Response } from "express";
|
||||
import {
|
||||
cloneTemplate,
|
||||
createTemplate,
|
||||
deleteTemplate,
|
||||
getAllTemplates,
|
||||
getTemplateById,
|
||||
updateTemplate,
|
||||
} from "../../controller/admin/templateController";
|
||||
import PermissionHelper from "../../helpers/permissionHelper";
|
||||
|
||||
var router = express.Router({ mergeParams: true });
|
||||
|
||||
router.get("/", async (req: Request, res: Response) => {
|
||||
await getAllTemplates(req, res);
|
||||
});
|
||||
|
||||
router.get("/:id", async (req: Request, res: Response) => {
|
||||
await getTemplateById(req, res);
|
||||
});
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
PermissionHelper.passCheckMiddleware("create", "settings", "template"),
|
||||
async (req: Request, res: Response) => {
|
||||
await createTemplate(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/clone",
|
||||
PermissionHelper.passCheckMiddleware("create", "settings", "template"),
|
||||
async (req: Request, res: Response) => {
|
||||
await cloneTemplate(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/:id",
|
||||
PermissionHelper.passCheckMiddleware("update", "settings", "template"),
|
||||
async (req: Request, res: Response) => {
|
||||
await updateTemplate(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
"/:id",
|
||||
PermissionHelper.passCheckMiddleware("delete", "settings", "template"),
|
||||
async (req: Request, res: Response) => {
|
||||
await deleteTemplate(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
29
src/routes/admin/templateUsage.ts
Normal file
29
src/routes/admin/templateUsage.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import express, { Request, Response } from "express";
|
||||
import PermissionHelper from "../../helpers/permissionHelper";
|
||||
import {
|
||||
getAllTemplateUsages,
|
||||
printTemplateUsageDemo,
|
||||
updateTemplateUsage,
|
||||
} from "../../controller/admin/templateUsageController";
|
||||
import { PermissionModule } from "../../type/permissionTypes";
|
||||
import ForbiddenRequestException from "../../exceptions/forbiddenRequestException";
|
||||
|
||||
var router = express.Router({ mergeParams: true });
|
||||
|
||||
router.get("/", async (req: Request, res: Response) => {
|
||||
await getAllTemplateUsages(req, res);
|
||||
});
|
||||
|
||||
router.get("/:scope", async (req: Request, res: Response) => {
|
||||
await printTemplateUsageDemo(req, res);
|
||||
});
|
||||
|
||||
router.patch(
|
||||
"/:scope",
|
||||
PermissionHelper.passCheckMiddleware("update", "settings", "template_usage"),
|
||||
async (req: Request, res: Response) => {
|
||||
await updateTemplateUsage(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
|
@ -44,12 +44,18 @@ export default abstract class CalendarService {
|
|||
* @description get calendar by types
|
||||
* @returns {Promise<Array<calendar>>}
|
||||
*/
|
||||
static async getByTypes(types: Array<number>): Promise<Array<calendar>> {
|
||||
return await dataSource
|
||||
static async getByTypes(types: Array<number>, addNscdr: boolean = false): Promise<Array<calendar>> {
|
||||
const query = dataSource
|
||||
.getRepository(calendar)
|
||||
.createQueryBuilder("calendar")
|
||||
.leftJoinAndSelect("calendar.type", "type")
|
||||
.where("type.id IN (:...types)", { types: types })
|
||||
.where("type.id IN (:...types)", { types: types });
|
||||
|
||||
if (addNscdr) {
|
||||
query.orWhere("type.nscdr = :nscdr", { nscdr: true });
|
||||
}
|
||||
|
||||
return await query
|
||||
.getMany()
|
||||
.then((res) => {
|
||||
return res;
|
||||
|
|
43
src/service/newsletterConfigService.ts
Normal file
43
src/service/newsletterConfigService.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { dataSource } from "../data-source";
|
||||
import { newsletterConfig } from "../entity/newsletterConfig";
|
||||
import { member } from "../entity/member";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
|
||||
export default abstract class NewsletterConfigService {
|
||||
/**
|
||||
* @description get all newsletterConfigs
|
||||
* @returns {Promise<Array<newsletterConfig>>}
|
||||
*/
|
||||
static async getAll(): Promise<Array<newsletterConfig>> {
|
||||
return await dataSource
|
||||
.getRepository(newsletterConfig)
|
||||
.createQueryBuilder("newsletterConfig")
|
||||
.leftJoinAndSelect("newsletterConfig.comType", "comType")
|
||||
.getMany()
|
||||
.then((res) => {
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw new InternalException("newsletterConfigs not found", err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get newsletterConfig by id
|
||||
* @returns {Promise<newsletterConfig>}
|
||||
*/
|
||||
static async getByComId(comId: number): Promise<newsletterConfig> {
|
||||
return await dataSource
|
||||
.getRepository(newsletterConfig)
|
||||
.createQueryBuilder("newsletterConfig")
|
||||
.leftJoinAndSelect("newsletterConfig.comType", "comType")
|
||||
.where("newsletterConfig.comTypId = :comTypId", { icomTypId: comId })
|
||||
.getOneOrFail()
|
||||
.then((res) => {
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw new InternalException("newsletterConfig not found by cmId", err);
|
||||
});
|
||||
}
|
||||
}
|
27
src/service/newsletterDatesService.ts
Normal file
27
src/service/newsletterDatesService.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
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("calendar.type", "type")
|
||||
.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("newsletterRecipients.newsletterId = :id", { id: newsletterId })
|
||||
.getMany()
|
||||
.then((res) => {
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw new InternalException("newsletterRecipients not found", err);
|
||||
});
|
||||
}
|
||||
}
|
43
src/service/newsletterService.ts
Normal file
43
src/service/newsletterService.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { dataSource } from "../data-source";
|
||||
import { newsletter } from "../entity/newsletter";
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
41
src/service/templateService.ts
Normal file
41
src/service/templateService.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { dataSource } from "../data-source";
|
||||
import { template } from "../entity/template";
|
||||
import { member } from "../entity/member";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
|
||||
export default abstract class TemplateService {
|
||||
/**
|
||||
* @description get all templates
|
||||
* @returns {Promise<Array<template>>}
|
||||
*/
|
||||
static async getAll(): Promise<Array<template>> {
|
||||
return await dataSource
|
||||
.getRepository(template)
|
||||
.createQueryBuilder("template")
|
||||
.getMany()
|
||||
.then((res) => {
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw new InternalException("templates not found", err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get template by id
|
||||
* @returns {Promise<template>}
|
||||
*/
|
||||
static async getById(id: number): Promise<template> {
|
||||
return await dataSource
|
||||
.getRepository(template)
|
||||
.createQueryBuilder("template")
|
||||
.where("template.id = :id", { id: id })
|
||||
.getOneOrFail()
|
||||
.then((res) => {
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw new InternalException("template not found by id", err);
|
||||
});
|
||||
}
|
||||
}
|
46
src/service/templateUsageService.ts
Normal file
46
src/service/templateUsageService.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { dataSource } from "../data-source";
|
||||
import { templateUsage } from "../entity/templateUsage";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
|
||||
export default abstract class TemplateUsageService {
|
||||
/**
|
||||
* @description get all templateUsages
|
||||
* @returns {Promise<Array<templateUsage>>}
|
||||
*/
|
||||
static async getAll(): Promise<Array<templateUsage>> {
|
||||
return await dataSource
|
||||
.getRepository(templateUsage)
|
||||
.createQueryBuilder("templateUsage")
|
||||
.leftJoinAndSelect("templateUsage.header", "headerTemplate")
|
||||
.leftJoinAndSelect("templateUsage.body", "bodyTemplate")
|
||||
.leftJoinAndSelect("templateUsage.footer", "footerTemplate")
|
||||
.getMany()
|
||||
.then((res) => {
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw new InternalException("templates not found", err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get template by scope
|
||||
* @returns {Promise<templateUsage>}
|
||||
*/
|
||||
static async getByScope(scope: string): Promise<templateUsage | null> {
|
||||
return await dataSource
|
||||
.getRepository(templateUsage)
|
||||
.createQueryBuilder("templateUsage")
|
||||
.leftJoinAndSelect("templateUsage.header", "headerTemplate")
|
||||
.leftJoinAndSelect("templateUsage.body", "bodyTemplate")
|
||||
.leftJoinAndSelect("templateUsage.footer", "footerTemplate")
|
||||
.where("templateUsage.scope = :scope", { scope: scope })
|
||||
.getOneOrFail()
|
||||
.then((res) => {
|
||||
return res;
|
||||
})
|
||||
.catch((err): null => {
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
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}}{{#if recipient.street}},{{/if}} {{recipient.street}}
|
||||
{{recipient.streetNumber}} {{recipient.streetNumberAdd}}
|
||||
</div>
|
3
src/templates/protocol.footer.template.html
Normal file
3
src/templates/protocol.footer.template.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div style="font-size: 10px; text-align: center; width: 100%; color: #888">
|
||||
Seite <span class="pageNumber"></span> von <span class="totalPages"></span>
|
||||
</div>
|
|
@ -54,6 +54,10 @@ export type OrderByStructure = {
|
|||
|
||||
export type OrderByType = "ASC" | "DESC";
|
||||
|
||||
export type QueryResult = {
|
||||
[key: string]: FieldType | QueryResult | Array<QueryResult>;
|
||||
};
|
||||
|
||||
export const exampleQuery: DynamicQueryStructure = {
|
||||
select: ["firstname", "lastname"],
|
||||
table: "member",
|
||||
|
|
|
@ -4,6 +4,7 @@ export type PermissionModule =
|
|||
| "member"
|
||||
| "calendar"
|
||||
| "newsletter"
|
||||
| "newsletter_config"
|
||||
| "protocol"
|
||||
| "qualification"
|
||||
| "award"
|
||||
|
@ -14,7 +15,9 @@ export type PermissionModule =
|
|||
| "user"
|
||||
| "role"
|
||||
| "query"
|
||||
| "query_store";
|
||||
| "query_store"
|
||||
| "template"
|
||||
| "template_usage";
|
||||
|
||||
export type PermissionType = "read" | "create" | "update" | "delete";
|
||||
|
||||
|
@ -42,6 +45,7 @@ export const permissionModules: Array<PermissionModule> = [
|
|||
"member",
|
||||
"calendar",
|
||||
"newsletter",
|
||||
"newsletter_config",
|
||||
"protocol",
|
||||
"qualification",
|
||||
"award",
|
||||
|
@ -53,6 +57,8 @@ export const permissionModules: Array<PermissionModule> = [
|
|||
"role",
|
||||
"query",
|
||||
"query_store",
|
||||
"template",
|
||||
"template_usage",
|
||||
];
|
||||
export const permissionTypes: Array<PermissionType> = ["read", "create", "update", "delete"];
|
||||
export const sectionsAndModules: SectionsAndModulesObject = {
|
||||
|
@ -65,6 +71,9 @@ export const sectionsAndModules: SectionsAndModulesObject = {
|
|||
"membership_status",
|
||||
"calendar_type",
|
||||
"query_store",
|
||||
"template",
|
||||
"template_usage",
|
||||
"newsletter_config",
|
||||
],
|
||||
user: ["user", "role"],
|
||||
};
|
||||
|
|
13
src/viewmodel/admin/newsletter.models.ts
Normal file
13
src/viewmodel/admin/newsletter.models.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { QueryStoreViewModel } from "./queryStore.models";
|
||||
|
||||
export interface NewsletterViewModel {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
newsletterTitle: string;
|
||||
newsletterText: string;
|
||||
newsletterSignatur: string;
|
||||
isSent: boolean;
|
||||
recipientsByQueryId?: number;
|
||||
recipientsByQuery?: QueryStoreViewModel;
|
||||
}
|
8
src/viewmodel/admin/newsletterConfig.models.ts
Normal file
8
src/viewmodel/admin/newsletterConfig.models.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { NewsletterConfigType } from "../../enums/newsletterConfigType";
|
||||
import { CommunicationTypeViewModel } from "./communicationType.models";
|
||||
|
||||
export interface NewsletterConfigViewModel {
|
||||
comTypeId: number;
|
||||
config: NewsletterConfigType;
|
||||
comType: CommunicationTypeViewModel;
|
||||
}
|
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: string;
|
||||
diffTitle: string | null;
|
||||
diffDescription: string | null;
|
||||
calendar: CalendarViewModel;
|
||||
}
|
7
src/viewmodel/admin/newsletterRecipients.models.ts
Normal file
7
src/viewmodel/admin/newsletterRecipients.models.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { MemberViewModel } from "./member.models";
|
||||
|
||||
export interface NewsletterRecipientsViewModel {
|
||||
newsletterId: number;
|
||||
memberId: number;
|
||||
member: MemberViewModel;
|
||||
}
|
7
src/viewmodel/admin/template.models.ts
Normal file
7
src/viewmodel/admin/template.models.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export interface TemplateViewModel {
|
||||
id: number;
|
||||
template: string;
|
||||
description: string | null;
|
||||
design: object;
|
||||
html: string;
|
||||
}
|
8
src/viewmodel/admin/templateUsage.models.ts
Normal file
8
src/viewmodel/admin/templateUsage.models.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { PermissionModule } from "../../type/permissionTypes";
|
||||
|
||||
export interface TemplateUsageViewModel {
|
||||
scope: PermissionModule;
|
||||
header: { id: number; template: string } | null;
|
||||
body: { id: number; template: string } | null;
|
||||
footer: { id: number; template: string } | null;
|
||||
}
|
Loading…
Reference in a new issue