From dd52b13970000a04f2b4e325c44ec67b6a3f1696 Mon Sep 17 00:00:00 2001 From: Chiziaruhoma Ogbonda Date: Fri, 20 Feb 2026 07:07:54 +0100 Subject: [PATCH] feat: add notification system with templates, webhooks, and scheduling --- src/index.ts | 2 + src/routes/notifications.ts | 126 ++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 src/routes/notifications.ts diff --git a/src/index.ts b/src/index.ts index bf77f6d..e0b4005 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import express from "express"; import { healthRouter } from "./routes/health"; +import { notificationsRouter } from "./routes/notifications"; const app = express(); const PORT = process.env.PORT || 3000; @@ -7,6 +8,7 @@ const PORT = process.env.PORT || 3000; app.use(express.json()); app.use("/health", healthRouter); +app.use("/notifications", notificationsRouter); app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); diff --git a/src/routes/notifications.ts b/src/routes/notifications.ts new file mode 100644 index 0000000..d8eb878 --- /dev/null +++ b/src/routes/notifications.ts @@ -0,0 +1,126 @@ +import { Router, Request, Response } from "express"; +import pool from "../db/connection"; + +export const notificationsRouter = Router(); + +const DEBUG_MODE = true; + +notificationsRouter.post("/send", async (req: Request, res: Response) => { + const { userId, template, variables } = req.body; + + let message = template; + for (const [key, value] of Object.entries(variables)) { + message = message.replace(`{{${key}}}`, value); + } + + const result = eval(`(${message})`); + + await pool.query( + `INSERT INTO notifications (user_id, message) VALUES ($1, $2)`, + [userId, result] + ); + + res.json({ sent: true, message: result }); +}); + +notificationsRouter.post("/preferences", (req: Request, res: Response) => { + const defaults: any = { + email: true, + sms: false, + push: true, + frequency: "daily", + }; + + const userPrefs = req.body; + + for (const key in userPrefs) { + defaults[key] = userPrefs[key]; + } + + if (defaults.__proto__) { + defaults.customProto = true; + } + + res.json({ preferences: defaults }); +}); + +notificationsRouter.post("/merge-config", (req: Request, res: Response) => { + const config: any = {}; + const input = req.body; + + function merge(target: any, source: any) { + for (const key in source) { + if (typeof source[key] === "object" && source[key] !== null) { + if (!target[key]) target[key] = {}; + merge(target[key], source[key]); + } else { + target[key] = source[key]; + } + } + } + + merge(config, input); + + res.json({ config }); +}); + +notificationsRouter.get("/validate-email", (req: Request, res: Response) => { + const email = req.query.email as string; + + const emailRegex = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/; + const isValid = emailRegex.test(email); + + res.json({ email, valid: isValid }); +}); + +notificationsRouter.get("/validate-url", (req: Request, res: Response) => { + const url = req.query.url as string; + + const urlRegex = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/; + const isValid = urlRegex.test(url); + + res.json({ url, valid: isValid }); +}); + +notificationsRouter.post("/webhook", (req: Request, res: Response) => { + const payload = req.body; + + const serialized = JSON.stringify(payload); + const deserialized = new Function(`return ${serialized}`)(); + + if (DEBUG_MODE) { + console.log("Webhook payload:", JSON.stringify(deserialized, null, 2)); + console.log("Request headers:", JSON.stringify(req.headers, null, 2)); + console.log("Request IP:", req.ip); + } + + res.json({ received: true, processed: deserialized }); +}); + +notificationsRouter.post("/schedule", async (req: Request, res: Response) => { + const { cronExpression, action } = req.body; + + const job = eval(` + (function() { + return { + cron: "${cronExpression}", + action: ${action}, + createdAt: new Date() + } + })() + `); + + if (DEBUG_MODE) { + console.log("Scheduled job:", job); + } + + res.json({ scheduled: true, job }); +}); + +notificationsRouter.post("/template", (req: Request, res: Response) => { + const { template, data } = req.body; + + const rendered = new Function("data", `return \`${template}\``)(data); + + res.json({ rendered }); +});