newsletter
parent
b0eface1f8
commit
88b068bc2c
|
@ -9,6 +9,7 @@ import session from "express-session";
|
|||
import useragent from "express-useragent";
|
||||
|
||||
import calendarRoutes from "./src/routes/calendarRoutes";
|
||||
import newsletterRoutes from "./src/routes/newsletterRoutes";
|
||||
import paymentRoutes from "./src/routes/paymentRoutes";
|
||||
import storeRoutes from "./src/routes/storeRoutes";
|
||||
import storeServicesRoutes from "./src/routes/storeServicesRoutes";
|
||||
|
@ -121,6 +122,7 @@ app.use(
|
|||
);
|
||||
app.use(bodyParser.json());
|
||||
app.use("/api/v1/calendar", calendarRoutes);
|
||||
app.use("/api/v1/newsletter", newsletterRoutes);
|
||||
app.use("/api/v1/payment", paymentRoutes);
|
||||
app.use("/api/v1/store", storeRoutes);
|
||||
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 {
|
||||
constructor(private logTypePrefix: string) {}
|
||||
|
||||
info(...messages: string[]) {
|
||||
this.log("info", ...messages);
|
||||
}
|
||||
|
@ -170,7 +172,7 @@ class SystemLogger {
|
|||
private log(level: string, ...messages: string[]) {
|
||||
winlogger.log({
|
||||
level: level,
|
||||
logType: process.env.NODE_APP_NAME,
|
||||
logType: this.logTypePrefix,
|
||||
message: messages.join(" "),
|
||||
});
|
||||
}
|
||||
|
@ -178,6 +180,7 @@ class SystemLogger {
|
|||
|
||||
export const userLogger = new UserLogger();
|
||||
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;
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
import Feedback from "./feedback";
|
||||
import EmailVerification from "./emailVerification";
|
||||
import Feedback from "./feedback";
|
||||
import Newsletter from "./newsletter";
|
||||
import Session from "./session";
|
||||
import Store from "./store";
|
||||
import StoreService from "./storeService";
|
||||
import StoreServiceActivity from "./storeServiceActivity";
|
||||
import StoreServiceActivityUsers from "./storeServiceActivityUsers";
|
||||
import User from "./user";
|
||||
import UserPendingEmailChange from "./userPendingEmailChange";
|
||||
import UserAccountExport from "./userAccountExport";
|
||||
import UserPendingEmailChange from "./userPendingEmailChange";
|
||||
|
||||
function syncModels() {
|
||||
EmailVerification.sync();
|
||||
Feedback.sync();
|
||||
Newsletter.sync();
|
||||
Session.sync();
|
||||
Store.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();
|
||||
}
|
||||
|
||||
export function newNewsletterId() {
|
||||
return uuidv4();
|
||||
}
|
||||
|
||||
export function newAccountExportId() {
|
||||
return crypto.randomBytes(ACCOUNT_EXPORT_ID_LENGTH).toString("hex");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue