summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaphael Kabo <raphaelkabo@hey.com>2023-10-06 16:18:13 +0100
committerRaphael Kabo <raphaelkabo@hey.com>2023-10-06 16:19:08 +0100
commit115210bfd9a5ae7bb8b516ce0d2cf3d9042dead7 (patch)
tree5f982435fc68b004112b95418e8efba8fb6f200e
parentc64b6febe5298219858bdc7ad27c3dfa1117a4de (diff)
refactor: frontend routes into TS file, new event path
-rwxr-xr-xscripts/docker-test.sh2
-rwxr-xr-xsrc/app.ts5
-rwxr-xr-xsrc/routes.js294
-rw-r--r--src/routes/frontend.ts220
-rwxr-xr-xviews/optionsform.handlebars2
-rwxr-xr-xviews/partials/neweventform.handlebars12
-rwxr-xr-xviews/partials/sidebar.handlebars2
7 files changed, 229 insertions, 308 deletions
diff --git a/scripts/docker-test.sh b/scripts/docker-test.sh
index 1cba254..f071740 100755
--- a/scripts/docker-test.sh
+++ b/scripts/docker-test.sh
@@ -15,6 +15,6 @@ trap cleanup 0
docker-compose up --build &
while [[ "$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/)" -ne "200" ]]; do sleep 5; done
-curl -v http://localhost:3000/new/event/public
+curl -v http://localhost:3000/new
cleanup \ No newline at end of file
diff --git a/src/app.ts b/src/app.ts
index 32e89b6..f49092c 100755
--- a/src/app.ts
+++ b/src/app.ts
@@ -1,7 +1,9 @@
import express from "express";
-import routes from "./routes.js";
import hbs from "express-handlebars";
+import routes from "./routes.js";
+import frontend from "./routes/frontend.js";
+
const app = express();
// View engine //
@@ -39,6 +41,7 @@ app.use(express.json({ type: "application/activity+json" })); // support json en
app.use(express.urlencoded({ extended: true }));
// Router //
+app.use("/", frontend);
app.use("/", routes);
export default app;
diff --git a/src/routes.js b/src/routes.js
index 55436c9..be0dcde 100755
--- a/src/routes.js
+++ b/src/routes.js
@@ -51,47 +51,6 @@ const nanoid = customAlphabet(
const router = express.Router();
-// Extra marked renderer (used to render plaintext event description for page metadata)
-// Adapted from https://dustinpfister.github.io/2017/11/19/nodejs-marked/
-// &#63; to ? helper
-function htmlEscapeToText(text) {
- return text.replace(/\&\#[0-9]*;|&amp;/g, function (escapeCode) {
- if (escapeCode.match(/amp/)) {
- return "&";
- }
- return String.fromCharCode(escapeCode.match(/[0-9]+/));
- });
-}
-
-function render_plain() {
- var render = new marked.Renderer();
- // render just the text of a link, strong, em
- render.link = function (href, title, text) {
- return text;
- };
- render.strong = function (text) {
- return text;
- };
- render.em = function (text) {
- return text;
- };
- // render just the text of a paragraph
- render.paragraph = function (text) {
- return htmlEscapeToText(text) + "\r\n";
- };
- // render nothing for headings, images, and br
- render.heading = function (text, level) {
- return "";
- };
- render.image = function (href, title, text) {
- return "";
- };
- render.br = function () {
- return "";
- };
- return render;
-}
-
let sendEmails = false;
let nodemailerTransporter;
if (config.general.mail_service) {
@@ -224,46 +183,6 @@ schedule.scheduleJob("59 23 * * *", function (fireDate) {
// old (they're not going to become active)
});
-// FRONTEND ROUTES
-
-router.get("/", (req, res) => {
- res.render("home", {
- domain,
- email: contactEmail,
- siteName,
- showKofi,
- });
-});
-
-router.get("/new", (req, res) => {
- res.render("home");
-});
-
-router.get("/new/event", (req, res) => {
- res.render("newevent", {
- domain: domain,
- email: contactEmail,
- siteName: siteName,
- });
-});
-router.get("/new/event/public", (req, res) => {
- let isPrivate = false;
- let isPublic = true;
- let isOrganisation = false;
- let isUnknownType = false;
- res.render("newevent", {
- title: "New event",
- isPrivate: isPrivate,
- isPublic: isPublic,
- isOrganisation: isOrganisation,
- isUnknownType: isUnknownType,
- eventType: "public",
- domain: domain,
- email: contactEmail,
- siteName: siteName,
- });
-});
-
// return the JSON for the featured/pinned post for this event
router.get("/:eventID/featured", (req, res) => {
if (!isFederated) return res.sendStatus(404);
@@ -390,216 +309,6 @@ router.get("/.well-known/webfinger", (req, res) => {
}
});
-router.get("/:eventID", (req, res) => {
- Event.findOne({
- id: req.params.eventID,
- })
- .lean() // Required, see: https://stackoverflow.com/questions/59690923/handlebars-access-has-been-denied-to-resolve-the-property-from-because-it-is
- .populate("eventGroup")
- .then((event) => {
- if (event) {
- const parsedLocation = event.location.replace(/\s+/g, "+");
- let displayDate;
- if (moment.tz(event.end, event.timezone).isSame(event.start, "day")) {
- // Happening during one day
- displayDate =
- moment
- .tz(event.start, event.timezone)
- .format(
- 'dddd D MMMM YYYY [<span class="text-muted">from</span>] h:mm a'
- ) +
- moment
- .tz(event.end, event.timezone)
- .format(
- ' [<span class="text-muted">to</span>] h:mm a [<span class="text-muted">](z)[</span>]'
- );
- } else {
- displayDate =
- moment
- .tz(event.start, event.timezone)
- .format(
- 'dddd D MMMM YYYY [<span class="text-muted">at</span>] h:mm a'
- ) +
- moment
- .tz(event.end, event.timezone)
- .format(
- ' [<span class="text-muted">–</span>] dddd D MMMM YYYY [<span class="text-muted">at</span>] h:mm a [<span class="text-muted">](z)[</span>]'
- );
- }
- let eventStartISO = moment.tz(event.start, "Etc/UTC").toISOString();
- let eventEndISO = moment.tz(event.end, "Etc/UTC").toISOString();
- let parsedStart = moment
- .tz(event.start, event.timezone)
- .format("YYYYMMDD[T]HHmmss");
- let parsedEnd = moment
- .tz(event.end, event.timezone)
- .format("YYYYMMDD[T]HHmmss");
- let eventHasConcluded = false;
- if (
- moment
- .tz(event.end, event.timezone)
- .isBefore(moment.tz(event.timezone))
- ) {
- eventHasConcluded = true;
- }
- let eventHasBegun = false;
- if (
- moment
- .tz(event.start, event.timezone)
- .isBefore(moment.tz(event.timezone))
- ) {
- eventHasBegun = true;
- }
- let fromNow = moment.tz(event.start, event.timezone).fromNow();
- let parsedDescription = marked.parse(event.description);
- let eventEditToken = event.editToken;
-
- let escapedName = event.name.replace(/\s+/g, "+");
-
- let eventHasCoverImage = false;
- if (event.image) {
- eventHasCoverImage = true;
- } else {
- eventHasCoverImage = false;
- }
- let eventHasHost = false;
- if (event.hostName) {
- eventHasHost = true;
- } else {
- eventHasHost = false;
- }
- let firstLoad = false;
- if (event.firstLoad === true) {
- firstLoad = true;
- Event.findOneAndUpdate(
- { id: req.params.eventID },
- { firstLoad: false },
- function (err, raw) {
- if (err) {
- res.send(err);
- }
- }
- );
- }
- let editingEnabled = false;
- if (Object.keys(req.query).length !== 0) {
- if (!req.query.e) {
- editingEnabled = false;
- console.log("No edit token set");
- } else {
- if (req.query.e === eventEditToken) {
- editingEnabled = true;
- } else {
- editingEnabled = false;
- }
- }
- }
- let eventAttendees = event.attendees
- .sort((a, b) => (a.name > b.name ? 1 : b.name > a.name ? -1 : 0))
- .map((el) => {
- if (!el.id) {
- el.id = el._id;
- }
- if (el.number > 1) {
- el.name = `${el.name} (${el.number} people)`;
- }
- return el;
- })
- .filter((obj, pos, arr) => {
- return (
- obj.status === "attending" &&
- arr.map((mapObj) => mapObj.id).indexOf(obj.id) === pos
- );
- });
-
- let spotsRemaining, noMoreSpots;
- let numberOfAttendees = eventAttendees.reduce((acc, attendee) => {
- if (attendee.status === "attending") {
- return acc + attendee.number || 1;
- }
- return acc;
- }, 0);
- if (event.maxAttendees) {
- spotsRemaining = event.maxAttendees - numberOfAttendees;
- if (spotsRemaining <= 0) {
- noMoreSpots = true;
- }
- }
- let metadata = {
- title: event.name,
- description: marked
- .parse(event.description, { renderer: render_plain() })
- .split(" ")
- .splice(0, 40)
- .join(" ")
- .trim(),
- image: eventHasCoverImage
- ? `https://${domain}/events/` + event.image
- : null,
- url: `https://${domain}/` + req.params.eventID,
- };
- if (
- req.headers.accept &&
- (req.headers.accept.includes("application/activity+json") ||
- req.headers.accept.includes("application/json") ||
- req.headers.accept.includes("application/json+ld"))
- ) {
- res
- .header("Content-Type", "application/activity+json")
- .send(JSON.parse(event.activityPubActor));
- } else {
- res.set("X-Robots-Tag", "noindex");
- res.render("event", {
- domain: domain,
- isFederated: isFederated,
- email: contactEmail,
- title: event.name,
- escapedName: escapedName,
- eventData: event,
- eventAttendees: eventAttendees,
- numberOfAttendees,
- spotsRemaining: spotsRemaining,
- noMoreSpots: noMoreSpots,
- eventStartISO: eventStartISO,
- eventEndISO: eventEndISO,
- parsedLocation: parsedLocation,
- parsedStart: parsedStart,
- parsedEnd: parsedEnd,
- displayDate: displayDate,
- fromNow: fromNow,
- timezone: event.timezone,
- parsedDescription: parsedDescription,
- editingEnabled: editingEnabled,
- eventHasCoverImage: eventHasCoverImage,
- eventHasHost: eventHasHost,
- firstLoad: firstLoad,
- eventHasConcluded: eventHasConcluded,
- eventHasBegun: eventHasBegun,
- metadata: metadata,
- siteName: siteName,
- });
- }
- } else {
- res.status(404);
- res.render("404", { url: req.url });
- }
- })
- .catch((err) => {
- addToLog(
- "displayEvent",
- "error",
- "Attempt to display event " +
- req.params.eventID +
- " failed with error: " +
- err
- );
- console.log(err);
- res.status(404);
- res.render("404", { url: req.url });
- return;
- });
-});
-
router.get("/:eventID/followers", (req, res) => {
if (!isFederated) return res.sendStatus(404);
const eventID = req.params.eventID;
@@ -917,7 +626,7 @@ router.post("/newevent", async (req, res) => {
const event = new Event({
id: eventID,
- type: req.body.eventType,
+ type: "public", // This is for backwards compatibility
name: req.body.eventName,
location: req.body.eventLocation,
start: startUTC,
@@ -1082,6 +791,7 @@ router.post("/newevent", async (req, res) => {
res.end();
})
.catch((err) => {
+ console.error(err);
res.status(500).send("Database error, please try again :( - " + err);
addToLog(
"createEvent",
diff --git a/src/routes/frontend.ts b/src/routes/frontend.ts
new file mode 100644
index 0000000..9dea619
--- /dev/null
+++ b/src/routes/frontend.ts
@@ -0,0 +1,220 @@
+import { Router, Request, Response } from "express";
+import Event from "../models/Event.js";
+import moment from "moment-timezone";
+import { marked } from "marked";
+import { frontendConfig } from "../util/config.js";
+import { renderPlain } from "../util/markdown.js";
+import getConfig from "../lib/config.js";
+import { addToLog } from "../helpers.js";
+
+const config = getConfig();
+
+const router = Router();
+router.get("/", (_: Request, res: Response) => {
+ res.render("home", frontendConfig());
+});
+
+router.get("/new", (_: Request, res: Response) => {
+ res.render("newevent", {
+ title: "New event",
+ ...frontendConfig(),
+ });
+});
+
+router.get("/:eventID", async (req: Request, res: Response) => {
+ try {
+ const event = await Event.findOne({
+ id: req.params.eventID,
+ })
+ .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) {
+ res.status(404);
+ res.render("404", { url: req.url });
+ return;
+ }
+ const parsedLocation = event.location.replace(/\s+/g, "+");
+ let displayDate;
+ if (moment.tz(event.end, event.timezone).isSame(event.start, "day")) {
+ // Happening during one day
+ displayDate =
+ moment
+ .tz(event.start, event.timezone)
+ .format(
+ 'dddd D MMMM YYYY [<span class="text-muted">from</span>] h:mm a'
+ ) +
+ moment
+ .tz(event.end, event.timezone)
+ .format(
+ ' [<span class="text-muted">to</span>] h:mm a [<span class="text-muted">](z)[</span>]'
+ );
+ } else {
+ displayDate =
+ moment
+ .tz(event.start, event.timezone)
+ .format(
+ 'dddd D MMMM YYYY [<span class="text-muted">at</span>] h:mm a'
+ ) +
+ moment
+ .tz(event.end, event.timezone)
+ .format(
+ ' [<span class="text-muted">–</span>] dddd D MMMM YYYY [<span class="text-muted">at</span>] h:mm a [<span class="text-muted">](z)[</span>]'
+ );
+ }
+ let eventStartISO = moment.tz(event.start, "Etc/UTC").toISOString();
+ let eventEndISO = moment.tz(event.end, "Etc/UTC").toISOString();
+ let parsedStart = moment
+ .tz(event.start, event.timezone)
+ .format("YYYYMMDD[T]HHmmss");
+ let parsedEnd = moment
+ .tz(event.end, event.timezone)
+ .format("YYYYMMDD[T]HHmmss");
+ let eventHasConcluded = false;
+ if (
+ moment.tz(event.end, event.timezone).isBefore(moment.tz(event.timezone))
+ ) {
+ eventHasConcluded = true;
+ }
+ let eventHasBegun = false;
+ if (
+ moment.tz(event.start, event.timezone).isBefore(moment.tz(event.timezone))
+ ) {
+ eventHasBegun = true;
+ }
+ let fromNow = moment.tz(event.start, event.timezone).fromNow();
+ let parsedDescription = marked.parse(event.description);
+ let eventEditToken = event.editToken;
+
+ let escapedName = event.name.replace(/\s+/g, "+");
+
+ let eventHasCoverImage = false;
+ if (event.image) {
+ eventHasCoverImage = true;
+ } else {
+ eventHasCoverImage = false;
+ }
+ let eventHasHost = false;
+ if (event.hostName) {
+ eventHasHost = true;
+ } else {
+ eventHasHost = false;
+ }
+ let firstLoad = false;
+ if (event.firstLoad === true) {
+ firstLoad = true;
+ await Event.findOneAndUpdate(
+ { id: req.params.eventID },
+ { firstLoad: false }
+ );
+ }
+ let editingEnabled = false;
+ if (Object.keys(req.query).length !== 0) {
+ if (!req.query.e) {
+ editingEnabled = false;
+ console.log("No edit token set");
+ } else {
+ if (req.query.e === eventEditToken) {
+ editingEnabled = true;
+ } else {
+ editingEnabled = false;
+ }
+ }
+ }
+ let eventAttendees = event.attendees
+ ?.sort((a, b) => (a.name > b.name ? 1 : b.name > a.name ? -1 : 0))
+ .map((el) => {
+ if (!el.id) {
+ el.id = el._id;
+ }
+ if (el.number && el.number > 1) {
+ el.name = `${el.name} (${el.number} people)`;
+ }
+ return el;
+ })
+ .filter((obj, pos, arr) => {
+ return (
+ obj.status === "attending" &&
+ arr.map((mapObj) => mapObj.id).indexOf(obj.id) === pos
+ );
+ });
+
+ let spotsRemaining, noMoreSpots;
+ let numberOfAttendees =
+ eventAttendees?.reduce((acc, attendee) => {
+ if (attendee.status === "attending") {
+ return acc + (attendee.number || 1);
+ }
+ return acc;
+ }, 0) || 0;
+ if (event.maxAttendees) {
+ spotsRemaining = event.maxAttendees - numberOfAttendees;
+ if (spotsRemaining <= 0) {
+ noMoreSpots = true;
+ }
+ }
+ let metadata = {
+ title: event.name,
+ description: marked
+ .parse(event.description, { renderer: renderPlain() })
+ .split(" ")
+ .splice(0, 40)
+ .join(" ")
+ .trim(),
+ image: eventHasCoverImage
+ ? `https://${config.general.domain}/events/` + event.image
+ : null,
+ url: `https://${config.general.domain}/` + req.params.eventID,
+ };
+ if (
+ req.headers.accept &&
+ (req.headers.accept.includes("application/activity+json") ||
+ req.headers.accept.includes("application/json") ||
+ req.headers.accept.includes("application/json+ld"))
+ ) {
+ res
+ .header("Content-Type", "application/activity+json")
+ .send(JSON.parse(event.activityPubActor || "{}"));
+ } else {
+ res.set("X-Robots-Tag", "noindex");
+ res.render("event", {
+ ...frontendConfig(),
+ title: event.name,
+ escapedName: escapedName,
+ eventData: event,
+ eventAttendees: eventAttendees,
+ numberOfAttendees,
+ spotsRemaining: spotsRemaining,
+ noMoreSpots: noMoreSpots,
+ eventStartISO: eventStartISO,
+ eventEndISO: eventEndISO,
+ parsedLocation: parsedLocation,
+ parsedStart: parsedStart,
+ parsedEnd: parsedEnd,
+ displayDate: displayDate,
+ fromNow: fromNow,
+ timezone: event.timezone,
+ parsedDescription: parsedDescription,
+ editingEnabled: editingEnabled,
+ eventHasCoverImage: eventHasCoverImage,
+ eventHasHost: eventHasHost,
+ firstLoad: firstLoad,
+ eventHasConcluded: eventHasConcluded,
+ eventHasBegun: eventHasBegun,
+ metadata: metadata,
+ });
+ }
+ } catch (err) {
+ addToLog(
+ "displayEvent",
+ "error",
+ "Attempt to display event " +
+ req.params.eventID +
+ " failed with error: " +
+ err
+ );
+ console.log(err);
+ res.status(404).render("404", { url: req.url });
+ }
+});
+
+export default router;
diff --git a/views/optionsform.handlebars b/views/optionsform.handlebars
index a844d12..85ebd9f 100755
--- a/views/optionsform.handlebars
+++ b/views/optionsform.handlebars
@@ -10,7 +10,7 @@
<div class="form-check">
<input class="form-check-input" type="checkbox" id="guestlistCheckbox" name="guestlistCheckbox" {{#if data.guestlistCheckbox}}checked{{/if}}>
<label class="form-check-label" for="guestlistCheckbox">
- {{#if isPrivate}}Privately display{{else}}Publicly display{{/if}} the list of attendees
+ Display the list of attendees
</label>
</div>
<div class="form-check">
diff --git a/views/partials/neweventform.handlebars b/views/partials/neweventform.handlebars
index d456d2e..3c7e060 100755
--- a/views/partials/neweventform.handlebars
+++ b/views/partials/neweventform.handlebars
@@ -1,6 +1,5 @@
<h4 class="mb-2">Create an event</h4>
<form id="newEventForm" action="/newevent" method="post" enctype="multipart/form-data">
- <input type="text" hidden class="form-control" id="eventType" name="eventType" value="{{eventType}}">
<div class="form-group row">
<label for="eventName" class="col-sm-2 col-form-label">Event name</label>
<div class="form-group col-sm-10">
@@ -54,17 +53,6 @@
<small class="form-text">Recommended dimensions (w x h): 920px by 300px.</small>
</div>
</div>
- {{#unless isPublic}}
- <div class="form-group row">
- <label for="eventPassword" class="col-sm-2 col-form-label">Event password</label>
- <div class="form-group col-sm-10">
- <input type="password" class="form-control" id="eventPassword" name="eventPassword" placeholder="Don't forget it!" data-validation="required">
- </div>
- <div class="form-group col-sm-10 offset-sm-2">
- <div class="" id="passwordStrengthBar"></div>
- </div>
- </div>
- {{/unless}}
<div class="form-group row">
<label for="hostName" class="col-sm-2 col-form-label">Host name</label>
<div class="form-group col-sm-10">
diff --git a/views/partials/sidebar.handlebars b/views/partials/sidebar.handlebars
index 1aa0f74..980e699 100755
--- a/views/partials/sidebar.handlebars
+++ b/views/partials/sidebar.handlebars
@@ -3,5 +3,5 @@
<p class="lead text-center mb-4">Nicer events</p>
- <a class="btn btn-success mb-2 btn-block" href="/new/event/public"><i class="far fa-calendar-plus"></i> New event</a>
+ <a class="btn btn-success mb-2 btn-block" href="/new"><i class="far fa-calendar-plus"></i> New event</a>
</div>