summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/app.ts4
-rw-r--r--src/index.d.ts14
-rw-r--r--src/lib/config.ts20
-rw-r--r--src/lib/middleware.ts29
-rwxr-xr-xsrc/routes.js2
-rw-r--r--src/routes/activitypub.ts59
-rw-r--r--src/routes/event.ts42
-rw-r--r--src/routes/frontend.ts88
-rw-r--r--src/routes/group.ts13
-rw-r--r--src/routes/magicLink.ts20
-rw-r--r--src/routes/static.ts6
-rw-r--r--src/util/object.ts30
12 files changed, 219 insertions, 108 deletions
diff --git a/src/app.ts b/src/app.ts
index 40425a8..0708081 100755
--- a/src/app.ts
+++ b/src/app.ts
@@ -1,5 +1,6 @@
import express from "express";
import hbs from "express-handlebars";
+import cookieParser from "cookie-parser";
import routes from "./routes.js";
import frontend from "./routes/frontend.js";
@@ -58,6 +59,9 @@ app.use(express.json({ type: activityPubContentType }));
app.use(express.json({ type: "application/json" }));
app.use(express.urlencoded({ extended: true }));
+// Cookies //
+app.use(cookieParser());
+
// Router //
app.use("/", staticPages);
app.use("/", frontend);
diff --git a/src/index.d.ts b/src/index.d.ts
new file mode 100644
index 0000000..292e5d3
--- /dev/null
+++ b/src/index.d.ts
@@ -0,0 +1,14 @@
+import "express";
+import { GathioConfig } from "./lib/config.js";
+
+interface Locals {
+ config: GathioConfig;
+}
+
+declare module "express" {
+ export interface Response {
+ locals: {
+ config?: GathioConfig;
+ };
+ }
+}
diff --git a/src/lib/config.ts b/src/lib/config.ts
index b4385ca..4bc43bd 100644
--- a/src/lib/config.ts
+++ b/src/lib/config.ts
@@ -1,6 +1,7 @@
import fs from "fs";
import toml from "toml";
import { exitWithError } from "./process.js";
+import { Response } from "express";
interface StaticPage {
title: string;
@@ -8,7 +9,7 @@ interface StaticPage {
filename: string;
}
-interface GathioConfig {
+export interface GathioConfig {
general: {
domain: string;
port: string;
@@ -68,8 +69,21 @@ const defaultConfig: GathioConfig = {
},
};
-export const frontendConfig = (): FrontendConfig => {
- const config = getConfig();
+export const frontendConfig = (res: Response): FrontendConfig => {
+ const config = res.locals.config;
+ if (!config) {
+ return {
+ domain: defaultConfig.general.domain,
+ siteName: defaultConfig.general.site_name,
+ isFederated: defaultConfig.general.is_federated,
+ emailLogoUrl: defaultConfig.general.email_logo_url,
+ showPublicEventList: defaultConfig.general.show_public_event_list,
+ showKofi: defaultConfig.general.show_kofi,
+ showInstanceInformation: false,
+ staticPages: [],
+ version: process.env.npm_package_version || "unknown",
+ };
+ }
return {
domain: config.general.domain,
siteName: config.general.site_name,
diff --git a/src/lib/middleware.ts b/src/lib/middleware.ts
index 0594e90..5073137 100644
--- a/src/lib/middleware.ts
+++ b/src/lib/middleware.ts
@@ -1,14 +1,14 @@
-import { Request, Response } from "express";
+import { NextFunction, Request, Response } from "express";
import MagicLink from "../models/MagicLink.js";
-import getConfig from "../lib/config.js";
-
-const config = getConfig();
+import getConfig, { GathioConfig } from "../lib/config.js";
+import { deepMerge } from "../util/object.js";
export const checkMagicLink = async (
req: Request,
res: Response,
- next: any,
+ next: NextFunction,
) => {
+ const config = getConfig();
if (!config.general.creator_email_addresses?.length) {
// No creator email addresses are configured, so skip the magic link check
return next();
@@ -49,3 +49,22 @@ export const checkMagicLink = async (
}
next();
};
+
+// Route-specific middleware which injects the config into the request object
+// It can also be used to modify the config based on the request, which
+// we use for Cypress testing.
+export const getConfigMiddleware = (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+) => {
+ const config = getConfig();
+ if (process.env.CYPRESS === "true" && req.cookies?.cypressConfigOverride) {
+ console.log("Overriding config with Cypress config");
+ const override = JSON.parse(req.cookies.cypressConfigOverride);
+ res.locals.config = deepMerge<GathioConfig>(config, override);
+ return next();
+ }
+ res.locals.config = config;
+ return next();
+};
diff --git a/src/routes.js b/src/routes.js
index 8ea7e05..9eedfb5 100755
--- a/src/routes.js
+++ b/src/routes.js
@@ -1511,7 +1511,7 @@ router.post("/activitypub/inbox", (req, res) => {
});
router.use(function (req, res, next) {
- return res.status(404).render("404", frontendConfig());
+ return res.status(404).render("404", frontendConfig(res));
});
addToLog("startup", "success", "Started up successfully");
diff --git a/src/routes/activitypub.ts b/src/routes/activitypub.ts
index 667a44f..fc61dd7 100644
--- a/src/routes/activitypub.ts
+++ b/src/routes/activitypub.ts
@@ -1,21 +1,22 @@
import { Router, Request, Response, NextFunction } from "express";
import { createFeaturedPost, createWebfinger } from "../activitypub.js";
import { acceptsActivityPub } from "../lib/activitypub.js";
-import getConfig, { frontendConfig } from "../lib/config.js";
+import { frontendConfig } from "../lib/config.js";
import Event from "../models/Event.js";
import { addToLog } from "../helpers.js";
-
-const config = getConfig();
+import { getConfigMiddleware } from "../lib/middleware.js";
const router = Router();
+router.use(getConfigMiddleware);
+
const send404IfNotFederated = (
req: Request,
res: Response,
next: NextFunction,
) => {
- if (!config.general.is_federated) {
- return res.status(404).render("404", frontendConfig());
+ if (!res.locals.config?.general.is_federated) {
+ return res.status(404).render("404", frontendConfig(res));
}
next();
};
@@ -27,7 +28,7 @@ router.get("/:eventID/featured", (req: Request, res: Response) => {
const { eventID } = req.params;
const featured = {
"@context": "https://www.w3.org/ns/activitystreams",
- id: `https://${config.general.domain}/${eventID}/featured`,
+ id: `https://${res.locals.config?.general.domain}/${eventID}/featured`,
type: "OrderedCollection",
orderedItems: [createFeaturedPost(eventID)],
};
@@ -41,17 +42,17 @@ router.get("/:eventID/featured", (req: Request, res: Response) => {
// return the JSON for a given activitypub message
router.get("/:eventID/m/:hash", async (req: Request, res: Response) => {
const { hash, eventID } = req.params;
- const id = `https://${config.general.domain}/${eventID}/m/${hash}`;
+ const id = `https://${res.locals.config?.general.domain}/${eventID}/m/${hash}`;
try {
const event = await Event.findOne({
id: eventID,
});
if (!event) {
- return res.status(404).render("404", frontendConfig());
+ return res.status(404).render("404", frontendConfig(res));
} else {
if (!event.activityPubMessages) {
- return res.status(404).render("404", frontendConfig());
+ return res.status(404).render("404", frontendConfig(res));
}
const message = event.activityPubMessages.find(
(el) => el.id === id,
@@ -68,7 +69,7 @@ router.get("/:eventID/m/:hash", async (req: Request, res: Response) => {
);
}
} else {
- return res.status(404).render("404", frontendConfig());
+ return res.status(404).render("404", frontendConfig(res));
}
}
} catch (err) {
@@ -80,19 +81,19 @@ router.get("/:eventID/m/:hash", async (req: Request, res: Response) => {
" failed with error: " +
err,
);
- return res.status(404).render("404", frontendConfig());
+ return res.status(404).render("404", frontendConfig(res));
}
});
router.get("/.well-known/nodeinfo", (req, res) => {
- if (!config.general.is_federated) {
- return res.status(404).render("404", frontendConfig());
+ if (!res.locals.config?.general.is_federated) {
+ return res.status(404).render("404", frontendConfig(res));
}
const nodeInfo = {
links: [
{
rel: "http://nodeinfo.diaspora.software/ns/schema/2.2",
- href: `https://${config.general.domain}/.well-known/nodeinfo/2.2`,
+ href: `https://${res.locals.config?.general.domain}/.well-known/nodeinfo/2.2`,
},
],
};
@@ -105,13 +106,13 @@ router.get("/.well-known/nodeinfo", (req, res) => {
router.get("/.well-known/nodeinfo/2.2", async (req, res) => {
const eventCount = await Event.countDocuments();
- if (!config.general.is_federated) {
- return res.status(404).render("404", frontendConfig());
+ if (!res.locals.config?.general.is_federated) {
+ return res.status(404).render("404", frontendConfig(res));
}
const nodeInfo = {
version: "2.2",
instance: {
- name: config.general.site_name,
+ name: res.locals.config?.general.site_name,
description:
"Federated, no-registration, privacy-respecting event hosting.",
},
@@ -157,16 +158,24 @@ router.get("/.well-known/webfinger", async (req, res) => {
const event = await Event.findOne({ id: eventID });
if (!event) {
- return res.status(404).render("404", frontendConfig());
+ return res.status(404).render("404", frontendConfig(res));
} else {
if (acceptsActivityPub(req)) {
res.header(
"Content-Type",
"application/activity+json",
- ).send(createWebfinger(eventID, config.general.domain));
+ ).send(
+ createWebfinger(
+ eventID,
+ res.locals.config?.general.domain,
+ ),
+ );
} else {
res.header("Content-Type", "application/json").send(
- createWebfinger(eventID, config.general.domain),
+ createWebfinger(
+ eventID,
+ res.locals.config?.general.domain,
+ ),
);
}
}
@@ -176,7 +185,7 @@ router.get("/.well-known/webfinger", async (req, res) => {
"error",
`Attempt to render webfinger for ${resource} failed with error: ${err}`,
);
- return res.status(404).render("404", frontendConfig());
+ return res.status(404).render("404", frontendConfig(res));
}
}
});
@@ -192,13 +201,13 @@ router.get("/:eventID/followers", async (req, res) => {
let followersCollection = {
type: "OrderedCollection",
totalItems: followers.length,
- id: `https://${config.general.domain}/${eventID}/followers`,
+ id: `https://${res.locals.config?.general.domain}/${eventID}/followers`,
first: {
type: "OrderedCollectionPage",
totalItems: followers.length,
- partOf: `https://${config.general.domain}/${eventID}/followers`,
+ partOf: `https://${res.locals.config?.general.domain}/${eventID}/followers`,
orderedItems: followers,
- id: `https://${config.general.domain}/${eventID}/followers?page=1`,
+ id: `https://${res.locals.config?.general.domain}/${eventID}/followers?page=1`,
},
"@context": ["https://www.w3.org/ns/activitystreams"],
};
@@ -221,7 +230,7 @@ router.get("/:eventID/followers", async (req, res) => {
"error",
`Attempt to render followers for ${eventID} failed with error: ${err}`,
);
- return res.status(404).render("404", frontendConfig());
+ return res.status(404).render("404", frontendConfig(res));
}
});
diff --git a/src/routes/event.ts b/src/routes/event.ts
index 6be5ff8..ad77052 100644
--- a/src/routes/event.ts
+++ b/src/routes/event.ts
@@ -21,14 +21,11 @@ import {
updateActivityPubActor,
updateActivityPubEvent,
} from "../activitypub.js";
-import getConfig from "../lib/config.js";
import { sendEmailFromTemplate } from "../lib/email.js";
import crypto from "crypto";
import ical from "ical";
import { markdownToSanitizedHTML } from "../util/markdown.js";
-import { checkMagicLink } from "../lib/middleware.js";
-
-const config = getConfig();
+import { checkMagicLink, getConfigMiddleware } from "../lib/middleware.js";
const storage = multer.memoryStorage();
// Accept only JPEG, GIF or PNG images, up to 10MB
@@ -58,6 +55,8 @@ const icsUpload = multer({
const router = Router();
+router.use(getConfigMiddleware);
+
router.post(
"/event",
upload.single("imageUpload"),
@@ -149,7 +148,7 @@ router.post(
firstLoad: true,
activityPubActor: createActivityPubActor(
eventID,
- config.general.domain,
+ res.locals.config?.general.domain,
publicKey,
markdownToSanitizedHTML(eventData.eventDescription),
eventData.eventName,
@@ -169,7 +168,7 @@ router.post(
),
activityPubMessages: [
{
- id: `https://${config.general.domain}/${eventID}/m/featuredPost`,
+ id: `https://${res.locals.config?.general.domain}/${eventID}/m/featuredPost`,
content: JSON.stringify(
createFeaturedPost(
eventID,
@@ -198,9 +197,9 @@ router.post(
{
eventID,
editToken,
- siteName: config.general.site_name,
- siteLogo: config.general.email_logo_url,
- domain: config.general.domain,
+ siteName: res.locals.config?.general.site_name,
+ siteLogo: res.locals.config?.general.email_logo_url,
+ domain: res.locals.config?.general.domain,
},
req,
);
@@ -232,9 +231,10 @@ router.post(
`New event in ${eventGroup.name}`,
"eventGroupUpdated",
{
- siteName: config.general.site_name,
- siteLogo: config.general.email_logo_url,
- domain: config.general.domain,
+ siteName: res.locals.config?.general.site_name,
+ siteLogo:
+ res.locals.config?.general.email_logo_url,
+ domain: res.locals.config?.general.domain,
eventGroupName: eventGroup.name,
eventName: event.name,
eventID: event.id,
@@ -451,11 +451,11 @@ router.put(
const guidObject = crypto.randomBytes(16).toString("hex");
const jsonObject = {
"@context": "https://www.w3.org/ns/activitystreams",
- id: `https://${config.general.domain}/${req.params.eventID}/m/${guidObject}`,
+ id: `https://${res.locals.config?.general.domain}/${req.params.eventID}/m/${guidObject}`,
name: `RSVP to ${event.name}`,
type: "Note",
cc: "https://www.w3.org/ns/activitystreams#Public",
- content: `${diffText} See here: <a href="https://${config.general.domain}/${req.params.eventID}">https://${config.general.domain}/${req.params.eventID}</a>`,
+ content: `${diffText} See here: <a href="https://${res.locals.config?.general.domain}/${req.params.eventID}">https://${res.locals.config?.general.domain}/${req.params.eventID}</a>`,
};
broadcastCreateMessage(jsonObject, event.followers, eventID);
// also broadcast an Update profile message to all followers so that at least Mastodon servers will update the local profile information
@@ -472,7 +472,7 @@ router.put(
"@context": "https://www.w3.org/ns/activitystreams",
name: `RSVP to ${event.name}`,
type: "Note",
- content: `<span class=\"h-card\"><a href="${attendee.id}" class="u-url mention">@<span>${attendee.name}</span></a></span> ${diffText} See here: <a href="https://${config.general.domain}/${req.params.eventID}">https://${config.general.domain}/${req.params.eventID}</a>`,
+ content: `<span class=\"h-card\"><a href="${attendee.id}" class="u-url mention">@<span>${attendee.name}</span></a></span> ${diffText} See here: <a href="https://${res.locals.config?.general.domain}/${req.params.eventID}">https://${res.locals.config?.general.domain}/${req.params.eventID}</a>`,
tag: [
{
type: "Mention",
@@ -498,9 +498,9 @@ router.put(
{
diffText,
eventID: req.params.eventID,
- siteName: config.general.site_name,
- siteLogo: config.general.email_logo_url,
- domain: config.general.domain,
+ siteName: res.locals.config?.general.site_name,
+ siteLogo: res.locals.config?.general.email_logo_url,
+ domain: res.locals.config?.general.domain,
},
req,
);
@@ -612,9 +612,9 @@ router.post(
{
eventID,
editToken,
- siteName: config.general.site_name,
- siteLogo: config.general.email_logo_url,
- domain: config.general.domain,
+ siteName: res.locals.config?.general.site_name,
+ siteLogo: res.locals.config?.general.email_logo_url,
+ domain: res.locals.config?.general.domain,
},
req,
);
diff --git a/src/routes/frontend.ts b/src/routes/frontend.ts
index 4cdce8a..240aff0 100644
--- a/src/routes/frontend.ts
+++ b/src/routes/frontend.ts
@@ -2,7 +2,7 @@ import { Router, Request, Response } from "express";
import moment from "moment-timezone";
import { marked } from "marked";
import { markdownToSanitizedHTML, renderPlain } from "../util/markdown.js";
-import getConfig, { frontendConfig, instanceRules } from "../lib/config.js";
+import { frontendConfig, instanceRules } from "../lib/config.js";
import { addToLog, exportICal } from "../helpers.js";
import Event from "../models/Event.js";
import EventGroup, { IEventGroup } from "../models/EventGroup.js";
@@ -11,41 +11,44 @@ import {
activityPubContentType,
} from "../lib/activitypub.js";
import MagicLink from "../models/MagicLink.js";
-
-const config = getConfig();
+import { getConfigMiddleware } from "../lib/middleware.js";
const router = Router();
+
+// Add config middleware to all routes
+router.use(getConfigMiddleware);
+
router.get("/", (_: Request, res: Response) => {
- if (config.general.show_public_event_list) {
+ if (res.locals.config?.general.show_public_event_list) {
return res.redirect("/events");
}
return res.render("home", {
- ...frontendConfig(),
+ ...frontendConfig(res),
instanceRules: instanceRules(),
});
});
router.get("/about", (_: Request, res: Response) => {
return res.render("home", {
- ...frontendConfig(),
+ ...frontendConfig(res),
instanceRules: instanceRules(),
});
});
-router.get("/new", (_: Request, res: Response) => {
- if (config.general.creator_email_addresses?.length) {
- return res.render("createEventMagicLink", frontendConfig());
+router.get("/new", (req: Request, res: Response) => {
+ if (res.locals.config?.general.creator_email_addresses?.length) {
+ return res.render("createEventMagicLink", frontendConfig(res));
}
return res.render("newevent", {
title: "New event",
- ...frontendConfig(),
+ ...frontendConfig(res),
});
});
router.get("/new/:magicLinkToken", async (req: Request, res: Response) => {
// If we don't have any creator email addresses, we don't need to check the magic link
// so we can just redirect to the new event page
- if (!config.general.creator_email_addresses?.length) {
+ if (!res.locals.config?.general.creator_email_addresses?.length) {
return res.redirect("/new");
}
const magicLink = await MagicLink.findOne({
@@ -55,7 +58,7 @@ router.get("/new/:magicLinkToken", async (req: Request, res: Response) => {
});
if (!magicLink) {
return res.render("createEventMagicLink", {
- ...frontendConfig(),
+ ...frontendConfig(res),
message: {
type: "danger",
text: "This magic link is invalid or has expired. Please request a new one here.",
@@ -64,15 +67,15 @@ router.get("/new/:magicLinkToken", async (req: Request, res: Response) => {
}
res.render("newevent", {
title: "New event",
- ...frontendConfig(),
+ ...frontendConfig(res),
magicLinkToken: req.params.magicLinkToken,
creatorEmail: magicLink.email,
});
});
router.get("/events", async (_: Request, res: Response) => {
- if (!config.general.show_public_event_list) {
- return res.status(404).render("404", frontendConfig());
+ if (!res.locals.config?.general.show_public_event_list) {
+ return res.status(404).render("404", frontendConfig(res));
}
const events = await Event.find({ showOnPublicList: true })
.populate("eventGroup")
@@ -93,7 +96,7 @@ router.get("/events", async (_: Request, res: Response) => {
"D MMM YYYY",
)}`,
eventHasConcluded: endMoment.isBefore(moment.tz(event.timezone)),
- eventGroup: event.eventGroup,
+ eventGroup: event.eventGroup as any as IEventGroup,
};
});
const upcomingEvents = updatedEvents.filter(
@@ -105,13 +108,23 @@ router.get("/events", async (_: Request, res: Response) => {
const eventGroups = await EventGroup.find({
showOnPublicList: true,
}).lean();
+ const updatedEventGroups = eventGroups.map((eventGroup) => {
+ return {
+ name: eventGroup.name,
+ numberOfEvents: updatedEvents.filter(
+ (event) =>
+ event.eventGroup?._id.toString() ===
+ eventGroup._id.toString(),
+ ).length,
+ };
+ });
res.render("publicEventList", {
title: "Public events",
upcomingEvents: upcomingEvents,
pastEvents: pastEvents,
- eventGroups: eventGroups,
- ...frontendConfig(),
+ eventGroups: updatedEventGroups,
+ ...frontendConfig(res),
});
});
@@ -123,7 +136,7 @@ router.get("/:eventID", async (req: Request, res: Response) => {
.lean() // Required, see: https://stackoverflow.com/questions/59690923/handlebars-access-has-been-denied-to-resolve-the-property-from-because-it-is
.populate("eventGroup");
if (!event) {
- return res.status(404).render("404", frontendConfig());
+ return res.status(404).render("404", frontendConfig(res));
}
const parsedLocation = event.location.replace(/\s+/g, "+");
let displayDate;
@@ -286,9 +299,12 @@ router.get("/:eventID", async (req: Request, res: Response) => {
.join(" ")
.trim(),
image: eventHasCoverImage
- ? `https://${config.general.domain}/events/` + event.image
+ ? `https://${res.locals.config?.general.domain}/events/` +
+ event.image
: null,
- url: `https://${config.general.domain}/` + req.params.eventID,
+ url:
+ `https://${res.locals.config?.general.domain}/` +
+ req.params.eventID,
};
if (acceptsActivityPub(req)) {
res.header("Content-Type", activityPubContentType).send(
@@ -297,7 +313,7 @@ router.get("/:eventID", async (req: Request, res: Response) => {
} else {
res.set("X-Robots-Tag", "noindex");
res.render("event", {
- ...frontendConfig(),
+ ...frontendConfig(res),
title: event.name,
escapedName: escapedName,
eventData: event,
@@ -324,10 +340,11 @@ router.get("/:eventID", async (req: Request, res: Response) => {
firstLoad: firstLoad,
eventHasConcluded: eventHasConcluded,
eventHasBegun: eventHasBegun,
- eventWillBeDeleted: config.general.delete_after_days > 0,
+ eventWillBeDeleted:
+ (res.locals.config?.general.delete_after_days || 0) > 0,
daysUntilDeletion: moment
.tz(event.end, event.timezone)
- .add(config.general.delete_after_days, "days")
+ .add(res.locals.config?.general.delete_after_days, "days")
.fromNow(),
metadata: metadata,
jsonData: {
@@ -368,7 +385,7 @@ router.get("/:eventID", async (req: Request, res: Response) => {
err,
);
console.log(err);
- return res.status(404).render("404", frontendConfig());
+ return res.status(404).render("404", frontendConfig(res));
}
});
@@ -379,7 +396,7 @@ router.get("/group/:eventGroupID", async (req: Request, res: Response) => {
}).lean();
if (!eventGroup) {
- return res.status(404).render("404", frontendConfig());
+ return res.status(404).render("404", frontendConfig(res));
}
const parsedDescription = markdownToSanitizedHTML(
eventGroup.description,
@@ -446,15 +463,18 @@ router.get("/group/:eventGroupID", async (req: Request, res: Response) => {
.join(" ")
.trim(),
image: eventGroupHasCoverImage
- ? `https://${config.general.domain}/events/` + eventGroup.image
+ ? `https://${res.locals.config?.general.domain}/events/` +
+ eventGroup.image
: null,
- url: `https://${config.general.domain}/` + req.params.eventID,
+ url:
+ `https://${res.locals.config?.general.domain}/` +
+ req.params.eventID,
};
res.set("X-Robots-Tag", "noindex");
res.render("eventgroup", {
- ...frontendConfig(),
- domain: config.general.domain,
+ ...frontendConfig(res),
+ domain: res.locals.config?.general.domain,
title: eventGroup.name,
eventGroupData: eventGroup,
escapedName: escapedName,
@@ -485,7 +505,7 @@ router.get("/group/:eventGroupID", async (req: Request, res: Response) => {
`Attempt to display event group ${req.params.eventGroupID} failed with error: ${err}`,
);
console.log(err);
- return res.status(404).render("404", frontendConfig());
+ return res.status(404).render("404", frontendConfig(res));
}
});
@@ -512,7 +532,7 @@ router.get(
`Attempt to display event group feed for ${req.params.eventGroupID} failed with error: ${err}`,
);
console.log(err);
- return res.status(404).render("404", frontendConfig());
+ return res.status(404).render("404", frontendConfig(res));
}
},
);
@@ -534,7 +554,7 @@ router.get("/export/event/:eventID", async (req: Request, res: Response) => {
`Attempt to export event ${req.params.eventID} failed with error: ${err}`,
);
console.log(err);
- return res.status(404).render("404", frontendConfig());
+ return res.status(404).render("404", frontendConfig(res));
}
});
@@ -560,7 +580,7 @@ router.get(
`Attempt to export event group ${req.params.eventGroupID} failed with error: ${err}`,
);
console.log(err);
- return res.status(404).render("404", frontendConfig());
+ return res.status(404).render("404", frontendConfig(res));
}
},
);
diff --git a/src/routes/group.ts b/src/routes/group.ts
index c006a5d..8afd766 100644
--- a/src/routes/group.ts
+++ b/src/routes/group.ts
@@ -1,5 +1,4 @@
import { Router, Response, Request } from "express";
-import getConfig from "../lib/config.js";
import multer from "multer";
import { generateEditToken, generateEventID } from "../util/generator.js";
import { validateGroupData } from "../util/validation.js";
@@ -9,9 +8,7 @@ import EventGroup from "../models/EventGroup.js";
import { sendEmailFromTemplate } from "../lib/email.js";
import { marked } from "marked";
import { renderPlain } from "../util/markdown.js";
-import { checkMagicLink } from "../lib/middleware.js";
-
-const config = getConfig();
+import { checkMagicLink, getConfigMiddleware } from "../lib/middleware.js";
const storage = multer.memoryStorage();
// Accept only JPEG, GIF or PNG images, up to 10MB
@@ -30,6 +27,8 @@ const upload = multer({
const router = Router();
+router.use(getConfigMiddleware);
+
router.post(
"/group",
upload.single("imageUpload"),
@@ -101,9 +100,9 @@ router.post(
{
eventGroupID: eventGroup.id,
editToken: eventGroup.editToken,
- siteName: config.general.site_name,
- siteLogo: config.general.email_logo_url,
- domain: config.general.domain,
+ siteName: res.locals.config?.general.site_name,
+ siteLogo: res.locals.config?.general.email_logo_url,
+ domain: res.locals.config?.general.domain,
},
req,
);
diff --git a/src/routes/magicLink.ts b/src/routes/magicLink.ts
index 24f0667..499d0a4 100644
--- a/src/routes/magicLink.ts
+++ b/src/routes/magicLink.ts
@@ -1,17 +1,19 @@
import { Router, Request, Response } from "express";
-import getConfig, { frontendConfig } from "../lib/config.js";
+import { frontendConfig } from "../lib/config.js";
import { sendEmailFromTemplate } from "../lib/email.js";
import { generateMagicLinkToken } from "../util/generator.js";
import MagicLink from "../models/MagicLink.js";
+import { getConfigMiddleware } from "../lib/middleware.js";
const router = Router();
-const config = getConfig();
+
+router.use(getConfigMiddleware);
router.post("/magic-link/event/create", async (req: Request, res: Response) => {
const { email } = req.body;
if (!email) {
res.render("createEventMagicLink", {
- ...frontendConfig(),
+ ...frontendConfig(res),
message: {
type: "danger",
text: "Please provide an email address.",
@@ -19,14 +21,14 @@ router.post("/magic-link/event/create", async (req: Request, res: Response) => {
});
return;
}
- const allowedEmails = config.general.creator_email_addresses;
+ const allowedEmails = res.locals.config?.general.creator_email_addresses;
if (!allowedEmails?.length) {
// No creator email addresses are configured, so skip the magic link check
return res.redirect("/new");
}
if (!allowedEmails.includes(email)) {
res.render("createEventMagicLink", {
- ...frontendConfig(),
+ ...frontendConfig(res),
message: {
type: "success",
text: "Thanks! If this email address can create events, you should receive an email with a magic link.",
@@ -52,14 +54,14 @@ router.post("/magic-link/event/create", async (req: Request, res: Response) => {
"createEventMagicLink",
{
token,
- siteName: config.general.site_name,
- siteLogo: config.general.email_logo_url,
- domain: config.general.domain,
+ siteName: res.locals.config?.general.site_name,
+ siteLogo: res.locals.config?.general.email_logo_url,
+ domain: res.locals.config?.general.domain,
},
req,
);
res.render("createEventMagicLink", {
- ...frontendConfig(),
+ ...frontendConfig(res),
message: {
type: "success",
text: "Thanks! If this email address can create events, you should receive an email with a magic link.",
diff --git a/src/routes/static.ts b/src/routes/static.ts
index 33f0225..6fab98d 100644
--- a/src/routes/static.ts
+++ b/src/routes/static.ts
@@ -21,13 +21,13 @@ if (config.static_pages?.length) {
return res.render("static", {
title: page.title,
content: parsed,
- ...frontendConfig(),
+ ...frontendConfig(res),
});
}
- return res.status(404).render("404", frontendConfig());
+ return res.status(404).render("404", frontendConfig(res));
} catch (err) {
console.error(err);
- return res.status(404).render("404", frontendConfig());
+ return res.status(404).render("404", frontendConfig(res));
}
});
});
diff --git a/src/util/object.ts b/src/util/object.ts
new file mode 100644
index 0000000..1ecc89b
--- /dev/null
+++ b/src/util/object.ts
@@ -0,0 +1,30 @@
+/**
+ * Simple object check.
+ */
+export function isObject(item: any) {
+ return item && typeof item === "object" && !Array.isArray(item);
+}
+
+/**
+ * Deep merge two objects.
+ */
+export function deepMerge<T>(
+ target: Record<any, any>,
+ ...sources: Record<any, any>[]
+): T {
+ if (!sources.length) return target;
+ const source = sources.shift();
+
+ if (isObject(target) && isObject(source)) {
+ for (const key in source) {
+ if (isObject(source[key])) {
+ if (!target[key]) Object.assign(target, { [key]: {} });
+ deepMerge(target[key], source[key]);
+ } else {
+ Object.assign(target, { [key]: source[key] });
+ }
+ }
+ }
+
+ return deepMerge(target, ...sources) as T;
+}