newsletter
parent
b0eface1f8
commit
88b068bc2c
|
@ -9,6 +9,7 @@ import session from "express-session";
|
||||||
import useragent from "express-useragent";
|
import useragent from "express-useragent";
|
||||||
|
|
||||||
import calendarRoutes from "./src/routes/calendarRoutes";
|
import calendarRoutes from "./src/routes/calendarRoutes";
|
||||||
|
import newsletterRoutes from "./src/routes/newsletterRoutes";
|
||||||
import paymentRoutes from "./src/routes/paymentRoutes";
|
import paymentRoutes from "./src/routes/paymentRoutes";
|
||||||
import storeRoutes from "./src/routes/storeRoutes";
|
import storeRoutes from "./src/routes/storeRoutes";
|
||||||
import storeServicesRoutes from "./src/routes/storeServicesRoutes";
|
import storeServicesRoutes from "./src/routes/storeServicesRoutes";
|
||||||
|
@ -121,6 +122,7 @@ app.use(
|
||||||
);
|
);
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
app.use("/api/v1/calendar", calendarRoutes);
|
app.use("/api/v1/calendar", calendarRoutes);
|
||||||
|
app.use("/api/v1/newsletter", newsletterRoutes);
|
||||||
app.use("/api/v1/payment", paymentRoutes);
|
app.use("/api/v1/payment", paymentRoutes);
|
||||||
app.use("/api/v1/store", storeRoutes);
|
app.use("/api/v1/store", storeRoutes);
|
||||||
app.use("/api/v1/store/services", storeServicesRoutes);
|
app.use("/api/v1/store/services", storeServicesRoutes);
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import Newsletter from "../models/newsletter";
|
||||||
|
import { newsletterLogger } from "../logger/logger";
|
||||||
|
import { newNewsletterId } from "../utils/utils";
|
||||||
|
import { isEmailValid } from "../validator/validator";
|
||||||
|
import rabbitmq from "../rabbitmq/rabbitmq";
|
||||||
|
|
||||||
|
const NEWSLETTER_STATE = {
|
||||||
|
NOT_CONFIRMED: 0,
|
||||||
|
CONFIRMED: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function AddNewsletterParticipant(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
const { type, email } = req.body;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!type ||
|
||||||
|
!email ||
|
||||||
|
!(await isEmailValid(email, false)) ||
|
||||||
|
!process.env.NEWSLETTER_ALLOWED_TYPES?.split(",").includes(type)
|
||||||
|
) {
|
||||||
|
newsletterLogger.warn(
|
||||||
|
"AddNewsletterParticipant: invalid request - missing email or type"
|
||||||
|
);
|
||||||
|
return res.status(400).send({ err: "invalid request" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// only add the participant if they are not already in the newsletter
|
||||||
|
|
||||||
|
const existingNewsletterParticipant = await Newsletter.findOne({
|
||||||
|
where: {
|
||||||
|
type: type,
|
||||||
|
email: email,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingNewsletterParticipant) {
|
||||||
|
const newsletterId = newNewsletterId();
|
||||||
|
|
||||||
|
await Newsletter.create({
|
||||||
|
newsletter_id: newsletterId,
|
||||||
|
email: email,
|
||||||
|
type: type,
|
||||||
|
state: NEWSLETTER_STATE.NOT_CONFIRMED,
|
||||||
|
});
|
||||||
|
|
||||||
|
newsletterLogger.info("AddNewsletterParticipant", email);
|
||||||
|
|
||||||
|
rabbitmq.sendEmail(email, "confirmNewsletterParticipation", "de", {
|
||||||
|
confirmNewsletterParticipationLink: `${process.env.DASHBOARD_API_URL}/v1/newsletter/confirm/${newsletterId}`,
|
||||||
|
unsubscribeNewsletterParticipationLink: `${process.env.DASHBOARD_API_URL}/v1/newsletter/unsubscribe/${newsletterId}`,
|
||||||
|
privacyPolicyLink:
|
||||||
|
process.env.NEWSLETTER_PRIVACY_POLICY_URLS?.split(",")[0],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
newsletterLogger.warn(
|
||||||
|
`AddNewsletterParticipant: email: ${email} already in newsletter of type: ${type}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({ status: "success" });
|
||||||
|
} catch (error) {
|
||||||
|
newsletterLogger.error("AddNewsletterParticipant err:", error as string);
|
||||||
|
res.status(500).send({ err: "invalid request" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function ConfirmNewsletterParticipation(
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { newsletterId } = req.params;
|
||||||
|
|
||||||
|
if (!newsletterId) {
|
||||||
|
newsletterLogger.warn(
|
||||||
|
"ConfirmNewsletterParticipation: invalid request - missing email or type"
|
||||||
|
);
|
||||||
|
return res.status(400).send({ err: "invalid request" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const newsletterParticipant = await Newsletter.findOne({
|
||||||
|
where: {
|
||||||
|
newsletter_id: newsletterId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!newsletterParticipant) {
|
||||||
|
newsletterLogger.warn(
|
||||||
|
`ConfirmNewsletterParticipation: newsletter id: ${newsletterId} not found`
|
||||||
|
);
|
||||||
|
return res.status(400).send({ err: "invalid request" });
|
||||||
|
}
|
||||||
|
|
||||||
|
newsletterParticipant.state = NEWSLETTER_STATE.CONFIRMED;
|
||||||
|
await newsletterParticipant.save();
|
||||||
|
|
||||||
|
rabbitmq.sendEmail(
|
||||||
|
newsletterParticipant.email,
|
||||||
|
"confirmedNewsletterParticipation",
|
||||||
|
"de",
|
||||||
|
{
|
||||||
|
unsubscribeNewsletterParticipationLink: `${process.env.DASHBOARD_API_URL}/v1/newsletter/unsubscribe/${newsletterId}`,
|
||||||
|
privacyPolicyLink: process.env.NEWSLETTER_PRIVACY_POLICY_URLS?.split(
|
||||||
|
","
|
||||||
|
).find((url) => url.includes(newsletterParticipant.type)),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
newsletterLogger.info("ConfirmNewsletterParticipation", newsletterId);
|
||||||
|
|
||||||
|
return res.status(200).send({ status: "success" });
|
||||||
|
} catch (error) {
|
||||||
|
newsletterLogger.error(
|
||||||
|
"ConfirmNewsletterParticipation err:",
|
||||||
|
error as string
|
||||||
|
);
|
||||||
|
res.status(500).send({ err: "invalid request" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function RemoveNewsletterParticipant(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
const { newsletterId } = req.params;
|
||||||
|
|
||||||
|
if (!newsletterId) {
|
||||||
|
newsletterLogger.warn(
|
||||||
|
"RemoveNewsletterParticipant: invalid request - missing email or type"
|
||||||
|
);
|
||||||
|
return res.status(400).send({ err: "invalid request" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const newsletterParticipant = await Newsletter.findOne({
|
||||||
|
where: {
|
||||||
|
newsletter_id: newsletterId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!newsletterParticipant) {
|
||||||
|
newsletterLogger.warn(
|
||||||
|
`RemoveNewsletterParticipant: newsletter id: ${newsletterId} not found`
|
||||||
|
);
|
||||||
|
return res.status(400).send({ err: "invalid request" });
|
||||||
|
}
|
||||||
|
|
||||||
|
await newsletterParticipant.destroy();
|
||||||
|
|
||||||
|
newsletterLogger.info(
|
||||||
|
`RemoveNewsletterParticipant newsletterId: ${newsletterId} removed. Email: ${newsletterParticipant.email}, type: ${newsletterParticipant.type}, state: ${newsletterParticipant.state}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send({ status: "success" });
|
||||||
|
} catch (error) {
|
||||||
|
newsletterLogger.error("RemoveNewsletterParticipant err:", error as string);
|
||||||
|
res.status(500).send({ err: "invalid request" });
|
||||||
|
}
|
||||||
|
}
|
|
@ -151,6 +151,8 @@ class StoreLogger extends BaseLogger {
|
||||||
}
|
}
|
||||||
|
|
||||||
class SystemLogger {
|
class SystemLogger {
|
||||||
|
constructor(private logTypePrefix: string) {}
|
||||||
|
|
||||||
info(...messages: string[]) {
|
info(...messages: string[]) {
|
||||||
this.log("info", ...messages);
|
this.log("info", ...messages);
|
||||||
}
|
}
|
||||||
|
@ -170,7 +172,7 @@ class SystemLogger {
|
||||||
private log(level: string, ...messages: string[]) {
|
private log(level: string, ...messages: string[]) {
|
||||||
winlogger.log({
|
winlogger.log({
|
||||||
level: level,
|
level: level,
|
||||||
logType: process.env.NODE_APP_NAME,
|
logType: this.logTypePrefix,
|
||||||
message: messages.join(" "),
|
message: messages.join(" "),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -178,6 +180,7 @@ class SystemLogger {
|
||||||
|
|
||||||
export const userLogger = new UserLogger();
|
export const userLogger = new UserLogger();
|
||||||
export const storeLogger = new StoreLogger();
|
export const storeLogger = new StoreLogger();
|
||||||
export const logger = new SystemLogger();
|
export const newsletterLogger = new SystemLogger("newsletter");
|
||||||
|
export const logger = new SystemLogger(process.env.NODE_APP_NAME || "system");
|
||||||
|
|
||||||
export default logger;
|
export default logger;
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
import Feedback from "./feedback";
|
|
||||||
import EmailVerification from "./emailVerification";
|
import EmailVerification from "./emailVerification";
|
||||||
|
import Feedback from "./feedback";
|
||||||
|
import Newsletter from "./newsletter";
|
||||||
import Session from "./session";
|
import Session from "./session";
|
||||||
import Store from "./store";
|
import Store from "./store";
|
||||||
import StoreService from "./storeService";
|
import StoreService from "./storeService";
|
||||||
import StoreServiceActivity from "./storeServiceActivity";
|
import StoreServiceActivity from "./storeServiceActivity";
|
||||||
import StoreServiceActivityUsers from "./storeServiceActivityUsers";
|
import StoreServiceActivityUsers from "./storeServiceActivityUsers";
|
||||||
import User from "./user";
|
import User from "./user";
|
||||||
import UserPendingEmailChange from "./userPendingEmailChange";
|
|
||||||
import UserAccountExport from "./userAccountExport";
|
import UserAccountExport from "./userAccountExport";
|
||||||
|
import UserPendingEmailChange from "./userPendingEmailChange";
|
||||||
|
|
||||||
function syncModels() {
|
function syncModels() {
|
||||||
EmailVerification.sync();
|
EmailVerification.sync();
|
||||||
Feedback.sync();
|
Feedback.sync();
|
||||||
|
Newsletter.sync();
|
||||||
Session.sync();
|
Session.sync();
|
||||||
Store.sync();
|
Store.sync();
|
||||||
StoreService.sync();
|
StoreService.sync();
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { DataTypes, Model } from "sequelize";
|
||||||
|
import sequelize from "../database/database";
|
||||||
|
|
||||||
|
interface NewsletterAttributes {
|
||||||
|
newsletter_id: string;
|
||||||
|
type: string;
|
||||||
|
email: string;
|
||||||
|
state: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Newsletter
|
||||||
|
extends Model<NewsletterAttributes>
|
||||||
|
implements NewsletterAttributes
|
||||||
|
{
|
||||||
|
declare newsletter_id: string;
|
||||||
|
declare type: string; // e. g. jannex or zeitadler
|
||||||
|
declare email: string;
|
||||||
|
declare state: number; // 0 = not confirmed, 1 = confirmed
|
||||||
|
declare created_at: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
Newsletter.init(
|
||||||
|
{
|
||||||
|
newsletter_id: {
|
||||||
|
primaryKey: true,
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tableName: "newsletters",
|
||||||
|
sequelize,
|
||||||
|
createdAt: "created_at",
|
||||||
|
updatedAt: "updated_at",
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
unique: true,
|
||||||
|
fields: ["email", "type"], // combination of email and type must be unique
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Newsletter;
|
|
@ -0,0 +1,16 @@
|
||||||
|
import express from "express";
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
import * as newsletterController from "../controllers/newsletterController";
|
||||||
|
|
||||||
|
router.post("/", newsletterController.AddNewsletterParticipant);
|
||||||
|
router.get(
|
||||||
|
"/confirm/:newsletterId",
|
||||||
|
newsletterController.ConfirmNewsletterParticipation
|
||||||
|
);
|
||||||
|
router.get(
|
||||||
|
"/unsubscribe/:newsletterId",
|
||||||
|
newsletterController.RemoveNewsletterParticipant
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
|
@ -57,6 +57,10 @@ export function newFeedbackId() {
|
||||||
return uuidv4();
|
return uuidv4();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function newNewsletterId() {
|
||||||
|
return uuidv4();
|
||||||
|
}
|
||||||
|
|
||||||
export function newAccountExportId() {
|
export function newAccountExportId() {
|
||||||
return crypto.randomBytes(ACCOUNT_EXPORT_ID_LENGTH).toString("hex");
|
return crypto.randomBytes(ACCOUNT_EXPORT_ID_LENGTH).toString("hex");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue