newsletter

main
alex 2024-04-01 00:48:57 +02:00
parent b0eface1f8
commit 88b068bc2c
7 changed files with 245 additions and 4 deletions

View File

@ -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);

View File

@ -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" });
}
}

View File

@ -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;

View File

@ -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();

56
src/models/newsletter.ts Normal file
View File

@ -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;

View File

@ -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;

View File

@ -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");
}