patches v1.3.1 #59
12 changed files with 114 additions and 37 deletions
20
.env.example
20
.env.example
|
@ -1,4 +1,4 @@
|
||||||
DB_TYPE = (mysql|sqlite|postgres)
|
DB_TYPE = (mysql|sqlite|postgres) # default ist mysql
|
||||||
|
|
||||||
## BSP für mysql
|
## BSP für mysql
|
||||||
DB_PORT = 3306
|
DB_PORT = 3306
|
||||||
|
@ -19,20 +19,20 @@ DB_HOST = filename.db
|
||||||
|
|
||||||
SERVER_PORT = portnumber
|
SERVER_PORT = portnumber
|
||||||
|
|
||||||
JWT_SECRET = ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890
|
JWT_SECRET = ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 # besitzt default
|
||||||
JWT_EXPIRATION = [0-9]*(y|d|h|m|s)
|
JWT_EXPIRATION = [0-9]*(y|d|h|m|s) # default ist 15m
|
||||||
REFRESH_EXPIRATION = [0-9]*(y|d|h|m|s)
|
REFRESH_EXPIRATION = [0-9]*(y|d|h|m|s) # default ist 1d
|
||||||
PWA_REFRESH_EXPIRATION = [0-9]*(y|d|h|m|s)
|
PWA_REFRESH_EXPIRATION = [0-9]*(y|d|h|m|s) # default ist 5d
|
||||||
|
|
||||||
MAIL_USERNAME = mail_username
|
MAIL_USERNAME = mail_username
|
||||||
MAIL_PASSWORD = mail_password
|
MAIL_PASSWORD = mail_password
|
||||||
MAIL_HOST = mail_hoststring
|
MAIL_HOST = mail_hoststring
|
||||||
MAIL_PORT = mail_portnumber
|
MAIL_PORT = mail_portnumber # default ist 587
|
||||||
MAIL_SECURE = (true|false) // true for port 465, fals for other ports
|
MAIL_SECURE = (true|false) # true für port 465, false für anders gewählten port
|
||||||
|
|
||||||
CLUB_NAME = clubname #default FF Admin
|
CLUB_NAME = clubname #default FF Admin
|
||||||
CLUB_WEBSITE = https://my-club-website-url
|
CLUB_WEBSITE = https://my-club-website-url #optional, muss aber mit http:// oder https:// beginnen
|
||||||
|
|
||||||
BACKUP_INTERVAL = number of days (min 1)
|
BACKUP_INTERVAL = number of days (min 1) # default 1
|
||||||
BACKUP_COPIES = number of parallel copies
|
BACKUP_COPIES = number of parallel copies #default 7
|
||||||
BACKUP_AUTO_RESTORE = (true|false) # default false
|
BACKUP_AUTO_RESTORE = (true|false) # default false
|
|
@ -32,7 +32,7 @@ RUN apk add --no-cache \
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN mkdir -p /app/export
|
RUN mkdir -p /app/files
|
||||||
|
|
||||||
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
|
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ services:
|
||||||
- MAIL_USERNAME=<mailadress|username>
|
- MAIL_USERNAME=<mailadress|username>
|
||||||
- MAIL_PASSWORD=<password>
|
- MAIL_PASSWORD=<password>
|
||||||
- MAIL_HOST=<url>
|
- MAIL_HOST=<url>
|
||||||
- MAIL_PORT=<port> # default ist auf 578 gesetzt
|
- MAIL_PORT=<port> # default ist auf 587 gesetzt
|
||||||
- MAIL_SECURE=<boolean> # default ist auf false gesetzt
|
- MAIL_SECURE=<boolean> # default ist auf false gesetzt
|
||||||
- CLUB_NAME=<tobemodified> # default ist auf FF Admin gesetzt
|
- CLUB_NAME=<tobemodified> # default ist auf FF Admin gesetzt
|
||||||
- CLUB_WEBSITE=<tobemodified>
|
- CLUB_WEBSITE=<tobemodified>
|
||||||
|
|
|
@ -4,3 +4,7 @@ export interface SetNewsletterConfigCommand {
|
||||||
comTypeId: number;
|
comTypeId: number;
|
||||||
config: NewsletterConfigType;
|
config: NewsletterConfigType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DeleteNewsletterConfigCommand {
|
||||||
|
comTypeId: number;
|
||||||
|
}
|
||||||
|
|
|
@ -2,15 +2,15 @@ import { dataSource } from "../../../data-source";
|
||||||
import { newsletterConfig } from "../../../entity/settings/newsletterConfig";
|
import { newsletterConfig } from "../../../entity/settings/newsletterConfig";
|
||||||
import DatabaseActionException from "../../../exceptions/databaseActionException";
|
import DatabaseActionException from "../../../exceptions/databaseActionException";
|
||||||
import InternalException from "../../../exceptions/internalException";
|
import InternalException from "../../../exceptions/internalException";
|
||||||
import { SetNewsletterConfigCommand } from "./newsletterConfigCommand";
|
import { DeleteNewsletterConfigCommand, SetNewsletterConfigCommand } from "./newsletterConfigCommand";
|
||||||
|
|
||||||
export default abstract class NewsletterConfigCommandHandler {
|
export default abstract class NewsletterConfigCommandHandler {
|
||||||
/**
|
/**
|
||||||
* @description set newsletterConfig
|
* @description set newsletterConfig
|
||||||
* @param {SetNewsletterConfigCommand} setNewsletterConfig
|
* @param {SetNewsletterConfigCommand} setNewsletterConfig
|
||||||
* @returns {Promise<number>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
static async set(setNewsletterConfig: SetNewsletterConfigCommand): Promise<number> {
|
static async set(setNewsletterConfig: SetNewsletterConfigCommand): Promise<void> {
|
||||||
return await dataSource
|
return await dataSource
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.insert()
|
.insert()
|
||||||
|
@ -21,11 +21,27 @@ export default abstract class NewsletterConfigCommandHandler {
|
||||||
})
|
})
|
||||||
.orUpdate(["config"], "comTypeId")
|
.orUpdate(["config"], "comTypeId")
|
||||||
.execute()
|
.execute()
|
||||||
.then((result) => {
|
.then((result) => {})
|
||||||
return result.identifiers[0].id;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
throw new DatabaseActionException("SET", "newsletterConfig", err);
|
throw new DatabaseActionException("SET", "newsletterConfig", err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description delete newsletterConfig
|
||||||
|
* @param {DeleteNewsletterConfigCommand} deleteNewsletterConfig
|
||||||
|
* @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);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,10 @@ import { Request, Response } from "express";
|
||||||
import NewsletterConfigService from "../../../service/settings/newsletterConfigService";
|
import NewsletterConfigService from "../../../service/settings/newsletterConfigService";
|
||||||
import NewsletterConfigFactory from "../../../factory/admin/settings/newsletterConfig";
|
import NewsletterConfigFactory from "../../../factory/admin/settings/newsletterConfig";
|
||||||
import NewsletterConfigCommandHandler from "../../../command/settings/newsletterConfig/newsletterConfigCommandHandler";
|
import NewsletterConfigCommandHandler from "../../../command/settings/newsletterConfig/newsletterConfigCommandHandler";
|
||||||
import { SetNewsletterConfigCommand } from "../../../command/settings/newsletterConfig/newsletterConfigCommand";
|
import {
|
||||||
|
DeleteNewsletterConfigCommand,
|
||||||
|
SetNewsletterConfigCommand,
|
||||||
|
} from "../../../command/settings/newsletterConfig/newsletterConfigCommand";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description get all newsletterConfigs
|
* @description get all newsletterConfigs
|
||||||
|
@ -43,7 +46,24 @@ export async function setNewsletterConfig(req: Request, res: Response): Promise<
|
||||||
comTypeId,
|
comTypeId,
|
||||||
config,
|
config,
|
||||||
};
|
};
|
||||||
let id = await NewsletterConfigCommandHandler.set(createNewsletterConfig);
|
await NewsletterConfigCommandHandler.set(createNewsletterConfig);
|
||||||
|
|
||||||
res.send(id);
|
res.sendStatus(204);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,8 @@ export const MAIL_SECURE = process.env.MAIL_SECURE ?? "false";
|
||||||
export const CLUB_NAME = process.env.CLUB_NAME ?? "FF Admin";
|
export const CLUB_NAME = process.env.CLUB_NAME ?? "FF Admin";
|
||||||
export const CLUB_WEBSITE = process.env.CLUB_WEBSITE ?? "";
|
export const CLUB_WEBSITE = process.env.CLUB_WEBSITE ?? "";
|
||||||
|
|
||||||
export const BACKUP_INTERVAL = Number(process.env.BACKUP_INTERVAL ?? "0");
|
export const BACKUP_INTERVAL = Number(process.env.BACKUP_INTERVAL ?? "1");
|
||||||
export const BACKUP_COPIES = Number(process.env.BACKUP_COPIES ?? "0");
|
export const BACKUP_COPIES = Number(process.env.BACKUP_COPIES ?? "7");
|
||||||
export const BACKUP_AUTO_RESTORE = process.env.BACKUP_AUTO_RESTORE ?? "true";
|
export const BACKUP_AUTO_RESTORE = process.env.BACKUP_AUTO_RESTORE ?? "true";
|
||||||
|
|
||||||
export function configCheck() {
|
export function configCheck() {
|
||||||
|
@ -60,6 +60,8 @@ export function configCheck() {
|
||||||
|
|
||||||
if (BACKUP_AUTO_RESTORE != "true" && BACKUP_AUTO_RESTORE != "false")
|
if (BACKUP_AUTO_RESTORE != "true" && BACKUP_AUTO_RESTORE != "false")
|
||||||
throw new Error("set 'true' or 'false' to BACKUP_AUTO_RESTORE");
|
throw new Error("set 'true' or 'false' to BACKUP_AUTO_RESTORE");
|
||||||
|
if (BACKUP_INTERVAL < 1) throw new Error("BACKUP_INTERVAL has to be at least 1");
|
||||||
|
if (BACKUP_COPIES < 1) throw new Error("BACKUP_COPIES has to be at least 1");
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkMS(input: string, origin: string) {
|
function checkMS(input: string, origin: string) {
|
||||||
|
|
|
@ -168,8 +168,9 @@ export default abstract class BackupHelper {
|
||||||
this.transactionManager = undefined;
|
this.transactionManager = undefined;
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
this.transactionManager = undefined;
|
this.transactionManager = undefined;
|
||||||
throw new DatabaseActionException("BACKUP RESTORE", include.join(", "), err);
|
throw new DatabaseActionException("BACKUP RESTORE", include.join(", ") || "FULL", err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,6 +332,7 @@ export default abstract class BackupHelper {
|
||||||
"newsletter.isSent",
|
"newsletter.isSent",
|
||||||
])
|
])
|
||||||
.addSelect(["dates.calendarId", "dates.diffTitle", "dates.diffDescription"])
|
.addSelect(["dates.calendarId", "dates.diffTitle", "dates.diffDescription"])
|
||||||
|
.addSelect(["recipients.memberId"])
|
||||||
.addSelect([
|
.addSelect([
|
||||||
...(collectIds ? ["member.id"] : []),
|
...(collectIds ? ["member.id"] : []),
|
||||||
"member.firstname",
|
"member.firstname",
|
||||||
|
@ -340,7 +342,13 @@ export default abstract class BackupHelper {
|
||||||
"member.internalId",
|
"member.internalId",
|
||||||
])
|
])
|
||||||
.addSelect(["recipientsByQuery.title", "recipientsByQuery.query"])
|
.addSelect(["recipientsByQuery.title", "recipientsByQuery.query"])
|
||||||
.getMany();
|
.getMany()
|
||||||
|
.then((res: any) =>
|
||||||
|
res.map((n: any) => ({
|
||||||
|
...n,
|
||||||
|
recipients: n.recipients.map((r: any) => ({ ...r, ...(false ? {} : { memberId: undefined }) })),
|
||||||
|
}))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
private static async getNewsletterConfig(): Promise<Array<any>> {
|
private static async getNewsletterConfig(): Promise<Array<any>> {
|
||||||
return await dataSource
|
return await dataSource
|
||||||
|
@ -630,7 +638,10 @@ export default abstract class BackupHelper {
|
||||||
private static async setNewsletter(data: Array<any>, collectedIds: boolean): Promise<void> {
|
private static async setNewsletter(data: Array<any>, collectedIds: boolean): Promise<void> {
|
||||||
await this.setQueryStore(
|
await this.setQueryStore(
|
||||||
uniqBy(
|
uniqBy(
|
||||||
data.map((d) => d.recipientsByQueryId).map((d) => ({ ...d, id: undefined })),
|
data
|
||||||
|
.map((d) => d.recipientsByQuery)
|
||||||
|
.filter((q) => q != null)
|
||||||
|
.map((d) => ({ ...d, id: undefined })),
|
||||||
"query"
|
"query"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -639,10 +650,14 @@ export default abstract class BackupHelper {
|
||||||
let members = await this.transactionManager.getRepository("member").find();
|
let members = await this.transactionManager.getRepository("member").find();
|
||||||
let dataWithMappedIds = data.map((d) => ({
|
let dataWithMappedIds = data.map((d) => ({
|
||||||
...d,
|
...d,
|
||||||
recipientsByQueryId: {
|
...(d.recipientsByQuery != null
|
||||||
...d.recipientsByQueryId,
|
? {
|
||||||
id: queries.find((s) => s.query == d.recipientsByQueryId.query)?.id ?? undefined,
|
recipientsByQuery: {
|
||||||
|
...d.recipientsByQuery,
|
||||||
|
id: queries.find((s) => s.title == d.recipientsByQuery.title)?.id ?? undefined,
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
...(!collectedIds
|
...(!collectedIds
|
||||||
? {
|
? {
|
||||||
recipients: d.recipients.map((r: any) => ({
|
recipients: d.recipients.map((r: any) => ({
|
||||||
|
@ -687,7 +702,7 @@ export default abstract class BackupHelper {
|
||||||
await this.transactionManager
|
await this.transactionManager
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.insert()
|
.insert()
|
||||||
.into("award")
|
.into("calendar_type")
|
||||||
.values(uniqBy([...(data?.["calendar_type"] ?? []), ...usedTypes], "type"))
|
.values(uniqBy([...(data?.["calendar_type"] ?? []), ...usedTypes], "type"))
|
||||||
.orIgnore()
|
.orIgnore()
|
||||||
.execute();
|
.execute();
|
||||||
|
|
|
@ -23,7 +23,7 @@ import BackupHelper from "./helpers/backupHelper";
|
||||||
dataSource.initialize().then(async () => {
|
dataSource.initialize().then(async () => {
|
||||||
if ((BACKUP_AUTO_RESTORE as "true" | "false") == "true" && (await dataSource.createQueryRunner().hasTable("user"))) {
|
if ((BACKUP_AUTO_RESTORE as "true" | "false") == "true" && (await dataSource.createQueryRunner().hasTable("user"))) {
|
||||||
await BackupHelper.autoRestoreBackup().catch((err) => {
|
await BackupHelper.autoRestoreBackup().catch((err) => {
|
||||||
console.log(`${new Date().toISOString()}: failed auto-restoring database`);
|
console.log(`${new Date().toISOString()}: failed auto-restoring database`, err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import express, { Request, Response } from "express";
|
import express, { Request, Response } from "express";
|
||||||
import {
|
import {
|
||||||
|
deleteNewsletterConfig,
|
||||||
getAllNewsletterConfigs,
|
getAllNewsletterConfigs,
|
||||||
getNewsletterConfigById,
|
getNewsletterConfigById,
|
||||||
setNewsletterConfig,
|
setNewsletterConfig,
|
||||||
|
@ -24,4 +25,12 @@ router.put(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
"/:comTypeId",
|
||||||
|
PermissionHelper.passCheckMiddleware("create", "settings", "newsletter_config"),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
await deleteNewsletterConfig(req, res);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { membership } from "../../../entity/club/member/membership";
|
||||||
import DatabaseActionException from "../../../exceptions/databaseActionException";
|
import DatabaseActionException from "../../../exceptions/databaseActionException";
|
||||||
import InternalException from "../../../exceptions/internalException";
|
import InternalException from "../../../exceptions/internalException";
|
||||||
import { memberView } from "../../../views/memberView";
|
import { memberView } from "../../../views/memberView";
|
||||||
|
import { DB_TYPE } from "../../../env.defaults";
|
||||||
|
|
||||||
export default abstract class MemberService {
|
export default abstract class MemberService {
|
||||||
/**
|
/**
|
||||||
|
@ -158,13 +159,17 @@ export default abstract class MemberService {
|
||||||
"member.firstMembershipEntry",
|
"member.firstMembershipEntry",
|
||||||
"member.memberships",
|
"member.memberships",
|
||||||
"membership_first",
|
"membership_first",
|
||||||
"membership_first.memberId = member.id AND membership_first.start = (SELECT MIN(m.start) FROM membership m WHERE m.memberId = member.id)"
|
DB_TYPE == "postgres"
|
||||||
|
? 'membership_first.memberId = member.id AND membership_first.start = (SELECT MIN("m_first"."start") FROM "membership" "m_first" WHERE "m_first"."memberId" = "member"."id")'
|
||||||
|
: "membership_first.memberId = member.id AND membership_first.start = (SELECT MIN(m_first.start) FROM membership m_first WHERE m_first.memberId = member.id)"
|
||||||
)
|
)
|
||||||
.leftJoinAndMapOne(
|
.leftJoinAndMapOne(
|
||||||
"member.lastMembershipEntry",
|
"member.lastMembershipEntry",
|
||||||
"member.memberships",
|
"member.memberships",
|
||||||
"membership_last",
|
"membership_last",
|
||||||
"membership_last.memberId = member.id AND membership_last.start = (SELECT MAX(m.start) FROM membership m WHERE m.memberId = member.id)"
|
DB_TYPE == "postgres"
|
||||||
|
? 'membership_last.memberId = member.id AND membership_last.start = (SELECT MAX("m_last"."start") FROM "membership" "m_last" WHERE "m_last"."memberId" = "member"."id")'
|
||||||
|
: "membership_last.memberId = member.id AND membership_last.start = (SELECT MAX(m_last.start) FROM membership m_last WHERE m_last.memberId = member.id)"
|
||||||
)
|
)
|
||||||
.leftJoinAndSelect("membership_first.status", "status_first")
|
.leftJoinAndSelect("membership_first.status", "status_first")
|
||||||
.leftJoinAndSelect("membership_last.status", "status_last")
|
.leftJoinAndSelect("membership_last.status", "status_last")
|
||||||
|
@ -172,17 +177,22 @@ export default abstract class MemberService {
|
||||||
"member.preferredCommunication",
|
"member.preferredCommunication",
|
||||||
"member.communications",
|
"member.communications",
|
||||||
"preferredCommunication",
|
"preferredCommunication",
|
||||||
"preferredCommunication.preferred = 1"
|
"preferredCommunication.preferred = true"
|
||||||
)
|
)
|
||||||
.leftJoinAndSelect("preferredCommunication.type", "communicationtype_preferred")
|
.leftJoinAndSelect("preferredCommunication.type", "communicationtype_preferred")
|
||||||
.leftJoinAndMapOne(
|
.leftJoinAndMapOne(
|
||||||
"member.sendNewsletter",
|
"member.sendNewsletter",
|
||||||
"member.communications",
|
"member.communications",
|
||||||
"sendNewsletter",
|
"sendNewsletter",
|
||||||
"sendNewsletter.isSendNewsletter = 1"
|
"sendNewsletter.isSendNewsletter = true"
|
||||||
)
|
)
|
||||||
.leftJoinAndSelect("sendNewsletter.type", "communicationtype")
|
.leftJoinAndSelect("sendNewsletter.type", "communicationtype")
|
||||||
.leftJoinAndMapMany("member.smsAlarming", "member.communications", "smsAlarming", "smsAlarming.isSMSAlarming = 1")
|
.leftJoinAndMapMany(
|
||||||
|
"member.smsAlarming",
|
||||||
|
"member.communications",
|
||||||
|
"smsAlarming",
|
||||||
|
"smsAlarming.isSMSAlarming = true"
|
||||||
|
)
|
||||||
.leftJoinAndSelect("smsAlarming.type", "communicationtype_smsAlarming")
|
.leftJoinAndSelect("smsAlarming.type", "communicationtype_smsAlarming")
|
||||||
.leftJoinAndSelect("member.salutation", "salutation");
|
.leftJoinAndSelect("member.salutation", "salutation");
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ export default abstract class NewsletterRecipientsService {
|
||||||
"sendNewsletter",
|
"sendNewsletter",
|
||||||
"sendNewsletter.isSendNewsletter = 1"
|
"sendNewsletter.isSendNewsletter = 1"
|
||||||
)
|
)
|
||||||
|
.leftJoinAndSelect("member.salutation", "salutation")
|
||||||
.leftJoinAndSelect("sendNewsletter.type", "communicationtype")
|
.leftJoinAndSelect("sendNewsletter.type", "communicationtype")
|
||||||
.leftJoinAndSelect("newsletterRecipients.newsletter", "newsletter")
|
.leftJoinAndSelect("newsletterRecipients.newsletter", "newsletter")
|
||||||
.where("newsletterRecipients.newsletterId = :id", { id: newsletterId })
|
.where("newsletterRecipients.newsletterId = :id", { id: newsletterId })
|
||||||
|
|
Loading…
Add table
Reference in a new issue