From b795d07ed7a1b705b72b171f8e8de267a720223b Mon Sep 17 00:00:00 2001 From: Raphael Kabo Date: Sat, 7 Oct 2023 14:30:24 +0100 Subject: refactor: event form and api routes --- src/routes.js | 757 +--------------------------------------------------------- 1 file changed, 3 insertions(+), 754 deletions(-) (limited to 'src/routes.js') diff --git a/src/routes.js b/src/routes.js index 7257bdb..94b7477 100755 --- a/src/routes.js +++ b/src/routes.js @@ -6,7 +6,6 @@ import { getConfig } from "./lib/config.js"; import { addToLog, exportIcal } from "./helpers.js"; import moment from "moment-timezone"; import { marked } from "marked"; -import generateRSAKeypair from "generate-rsa-keypair"; import crypto from "crypto"; import request from "request"; import niceware from "niceware"; @@ -17,21 +16,14 @@ import fileUpload from "express-fileupload"; import Jimp from "jimp"; import schedule from "node-schedule"; import { - createActivityPubActor, - createActivityPubEvent, - createFeaturedPost, - createWebfinger, - updateActivityPubActor, - updateActivityPubEvent, broadcastCreateMessage, - broadcastUpdateMessage, broadcastDeleteMessage, - sendDirectMessage, processInbox, } from "./activitypub.js"; import Event from "./models/Event.js"; import EventGroup from "./models/EventGroup.js"; import path from "path"; +import { renderPlain } from "./util/markdown.js"; const config = getConfig(); const domain = config.general.domain; @@ -40,7 +32,6 @@ const siteName = config.general.site_name; const mailService = config.general.mail_service; const siteLogo = config.general.email_logo_url; const isFederated = config.general.is_federated || true; -const showKofi = config.general.show_kofi; // This alphabet (used to generate all event, group, etc. IDs) is missing '-' // because ActivityPub doesn't like it in IDs @@ -193,180 +184,6 @@ schedule.scheduleJob("59 23 * * *", function (fireDate) { // old (they're not going to become active) }); -// return the JSON for the featured/pinned post for this event -router.get("/:eventID/featured", (req, res) => { - if (!isFederated) return res.sendStatus(404); - const { eventID } = req.params; - const guidObject = crypto.randomBytes(16).toString("hex"); - const featured = { - "@context": "https://www.w3.org/ns/activitystreams", - id: `https://${domain}/${eventID}/featured`, - type: "OrderedCollection", - orderedItems: [createFeaturedPost(eventID)], - }; - if ( - req.headers.accept && - (req.headers.accept.includes("application/activity+json") || - req.headers.accept.includes("application/ld+json")) - ) { - res.header("Content-Type", "application/activity+json").send(featured); - } else { - res.header("Content-Type", "application/json").send(featured); - } -}); - -// return the JSON for a given activitypub message -router.get("/:eventID/m/:hash", (req, res) => { - if (!isFederated) return res.sendStatus(404); - const { hash, eventID } = req.params; - const id = `https://${domain}/${eventID}/m/${hash}`; - - Event.findOne({ - id: eventID, - }) - .then((event) => { - if (!event) { - res.status(404); - res.render("404", { url: req.url }); - } else { - const message = event.activityPubMessages.find( - (el) => el.id === id, - ); - if (message) { - if ( - req.headers.accept && - (req.headers.accept.includes( - "application/activity+json", - ) || - req.headers.accept.includes("application/ld+json")) - ) { - res.header( - "Content-Type", - "application/activity+json", - ).send(JSON.parse(message.content)); - } else { - res.header("Content-Type", "application/json").send( - JSON.parse(message.content), - ); - } - } else { - res.status(404); - return res.render("404", { url: req.url }); - } - } - }) - .catch((err) => { - addToLog( - "getActivityPubMessage", - "error", - "Attempt to get Activity Pub Message for " + - id + - " failed with error: " + - err, - ); - res.status(404); - res.render("404", { url: req.url }); - return; - }); -}); - -// return the webfinger record required for the initial activitypub handshake -router.get("/.well-known/webfinger", (req, res) => { - if (!isFederated) return res.sendStatus(404); - let resource = req.query.resource; - if (!resource || !resource.includes("acct:")) { - return res - .status(400) - .send( - 'Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.', - ); - } else { - // "foo@domain" - let activityPubAccount = resource.replace("acct:", ""); - // "foo" - let eventID = activityPubAccount.replace(/@.*/, ""); - Event.findOne({ - id: eventID, - }) - .then((event) => { - if (!event) { - res.status(404); - res.render("404", { url: req.url }); - } else { - if ( - req.headers.accept && - (req.headers.accept.includes( - "application/activity+json", - ) || - req.headers.accept.includes("application/ld+json")) - ) { - res.header( - "Content-Type", - "application/activity+json", - ).send(createWebfinger(eventID, domain)); - } else { - res.header("Content-Type", "application/json").send( - createWebfinger(eventID, domain), - ); - } - } - }) - .catch((err) => { - addToLog( - "renderWebfinger", - "error", - "Attempt to render webfinger for " + - req.params.eventID + - " failed with error: " + - 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; - Event.findOne({ - id: eventID, - }).then((event) => { - if (event) { - const followers = event.followers.map((el) => el.actorId); - let followersCollection = { - type: "OrderedCollection", - totalItems: followers.length, - id: `https://${domain}/${eventID}/followers`, - first: { - type: "OrderedCollectionPage", - totalItems: followers.length, - partOf: `https://${domain}/${eventID}/followers`, - orderedItems: followers, - id: `https://${domain}/${eventID}/followers?page=1`, - }, - "@context": ["https://www.w3.org/ns/activitystreams"], - }; - if ( - req.headers.accept && - (req.headers.accept.includes("application/activity+json") || - req.headers.accept.includes("application/ld+json")) - ) { - return res - .header("Content-Type", "application/activity+json") - .send(followersCollection); - } else { - return res - .header("Content-Type", "application/json") - .send(followersCollection); - } - } else { - return res.status(400).send("Bad request."); - } - }); -}); - router.get("/group/:eventGroupID", (req, res) => { EventGroup.findOne({ id: req.params.eventGroupID, @@ -467,7 +284,7 @@ router.get("/group/:eventGroupID", (req, res) => { title: eventGroup.name, description: marked .parse(eventGroup.description, { - renderer: render_plain(), + renderer: renderPlain(), }) .split(" ") .splice(0, 40) @@ -603,243 +420,6 @@ router.get("/exportgroup/:eventGroupID", (req, res) => { }); // BACKEND ROUTES - -router.post("/newevent", async (req, res) => { - let eventID = nanoid(); - let editToken = randomstring.generate(); - let eventImageFilename = ""; - let isPartOfEventGroup = false; - if (req.files && Object.keys(req.files).length !== 0) { - let eventImageBuffer = req.files.imageUpload.data; - eventImageFilename = await Jimp.read(eventImageBuffer) - .then((img) => { - img.resize(920, Jimp.AUTO) // resize - .quality(80) // set JPEG quality - .write("./public/events/" + eventID + ".jpg"); // save - const filename = eventID + ".jpg"; - return filename; - }) - .catch((err) => { - addToLog( - "Jimp", - "error", - "Attempt to edit image failed with error: " + err, - ); - }); - } - let startUTC = moment.tz( - req.body.eventStart, - "D MMMM YYYY, hh:mm a", - req.body.timezone, - ); - let endUTC = moment.tz( - req.body.eventEnd, - "D MMMM YYYY, hh:mm a", - req.body.timezone, - ); - let eventGroup; - if (req.body.eventGroupCheckbox) { - eventGroup = await EventGroup.findOne({ - id: req.body.eventGroupID, - editToken: req.body.eventGroupEditToken, - }); - if (eventGroup) { - isPartOfEventGroup = true; - } - } - - // generate RSA keypair for ActivityPub - let pair = generateRSAKeypair(); - - const event = new Event({ - id: eventID, - type: "public", // This is for backwards compatibility - name: req.body.eventName, - location: req.body.eventLocation, - start: startUTC, - end: endUTC, - timezone: req.body.timezone, - description: req.body.eventDescription, - image: eventImageFilename, - creatorEmail: req.body.creatorEmail, - url: req.body.eventURL, - hostName: req.body.hostName, - viewPassword: req.body.viewPassword, - editPassword: req.body.editPassword, - editToken: editToken, - eventGroup: isPartOfEventGroup ? eventGroup._id : null, - usersCanAttend: req.body.joinCheckbox ? true : false, - showUsersList: req.body.guestlistCheckbox ? true : false, - usersCanComment: req.body.interactionCheckbox ? true : false, - maxAttendees: req.body.maxAttendees, - firstLoad: true, - activityPubActor: createActivityPubActor( - eventID, - domain, - pair.public, - marked.parse(req.body.eventDescription), - req.body.eventName, - req.body.eventLocation, - eventImageFilename, - startUTC, - endUTC, - req.body.timezone, - ), - activityPubEvent: createActivityPubEvent( - req.body.eventName, - startUTC, - endUTC, - req.body.timezone, - req.body.eventDescription, - req.body.eventLocation, - ), - activityPubMessages: [ - { - id: `https://${domain}/${eventID}/m/featuredPost`, - content: JSON.stringify( - createFeaturedPost( - eventID, - req.body.eventName, - startUTC, - endUTC, - req.body.timezone, - req.body.eventDescription, - req.body.eventLocation, - ), - ), - }, - ], - publicKey: pair.public, - privateKey: pair.private, - }); - event - .save() - .then((event) => { - addToLog("createEvent", "success", "Event " + eventID + "created"); - // Send email with edit link - if (req.body.creatorEmail && sendEmails) { - req.app.get("hbsInstance").renderView( - "./views/emails/createevent.handlebars", - { - eventID, - editToken, - siteName, - siteLogo, - domain, - cache: true, - layout: "email.handlebars", - }, - function (err, html) { - const msg = { - to: req.body.creatorEmail, - from: { - name: siteName, - email: contactEmail, - address: contactEmail, - }, - subject: `${siteName}: ${req.body.eventName}`, - html, - }; - switch (mailService) { - case "sendgrid": - sgMail.send(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case "nodemailer": - nodemailerTransporter - .sendMail(msg) - .catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }, - ); - } - // If the event was added to a group, send an email to any group - // subscribers - if (event.eventGroup && sendEmails) { - EventGroup.findOne({ _id: event.eventGroup._id }).then( - (eventGroup) => { - const subscribers = eventGroup.subscribers.reduce( - (acc, current) => { - if (acc.includes(current.email)) { - return acc; - } - return [current.email, ...acc]; - }, - [], - ); - subscribers.forEach((emailAddress) => { - req.app.get("hbsInstance").renderView( - "./views/emails/eventgroupupdated.handlebars", - { - siteName, - siteLogo, - domain, - eventID: req.params.eventID, - eventGroupName: eventGroup.name, - eventName: event.name, - eventID: event.id, - eventGroupID: eventGroup.id, - emailAddress: - encodeURIComponent(emailAddress), - cache: true, - layout: "email.handlebars", - }, - function (err, html) { - const msg = { - to: emailAddress, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: New event in ${eventGroup.name}`, - html, - }; - switch (mailService) { - case "sendgrid": - sgMail.send(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case "nodemailer": - nodemailerTransporter - .sendMail(msg) - .catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }, - ); - }); - }, - ); - } - res.writeHead(302, { - Location: "/" + eventID + "?e=" + editToken, - }); - res.end(); - }) - .catch((err) => { - console.error(err); - res.status(500).send( - "Database error, please try again :( - " + err, - ); - addToLog( - "createEvent", - "error", - "Attempt to create event failed with error: " + err, - ); - }); -}); - router.post("/importevent", (req, res) => { let eventID = nanoid(); let editToken = randomstring.generate(); @@ -1071,338 +651,6 @@ router.post("/verifytoken/group/:eventGroupID", (req, res) => { }); }); -router.post("/editevent/:eventID/:editToken", (req, res) => { - let submittedEditToken = req.params.editToken; - Event.findOne({ - id: req.params.eventID, - }) - .then(async (event) => { - if (event.editToken === submittedEditToken) { - // Token matches - - // If there is a new image, upload that first - let eventID = req.params.eventID; - let eventImageFilename = event.image; - if (req.files && Object.keys(req.files).length !== 0) { - let eventImageBuffer = req.files.imageUpload.data; - Jimp.read(eventImageBuffer, (err, img) => { - if (err) throw err; - img.resize(920, Jimp.AUTO) // resize - .quality(80) // set JPEG - .write("./public/events/" + eventID + ".jpg"); // save - }); - eventImageFilename = eventID + ".jpg"; - } - let startUTC = moment.tz( - req.body.eventStart, - "D MMMM YYYY, hh:mm a", - req.body.timezone, - ); - let endUTC = moment.tz( - req.body.eventEnd, - "D MMMM YYYY, hh:mm a", - req.body.timezone, - ); - - let isPartOfEventGroup = false; - let eventGroup; - if (req.body.eventGroupCheckbox) { - eventGroup = await EventGroup.findOne({ - id: req.body.eventGroupID, - editToken: req.body.eventGroupEditToken, - }); - if (eventGroup) { - isPartOfEventGroup = true; - } - } - const updatedEvent = { - name: req.body.eventName, - location: req.body.eventLocation, - start: startUTC, - end: endUTC, - timezone: req.body.timezone, - description: req.body.eventDescription, - url: req.body.eventURL, - hostName: req.body.hostName, - image: eventImageFilename, - usersCanAttend: req.body.joinCheckbox ? true : false, - showUsersList: req.body.guestlistCheckbox ? true : false, - usersCanComment: req.body.interactionCheckbox - ? true - : false, - maxAttendees: req.body.maxAttendeesCheckbox - ? req.body.maxAttendees - : null, - eventGroup: isPartOfEventGroup ? eventGroup._id : null, - activityPubActor: event.activityPubActor - ? updateActivityPubActor( - JSON.parse(event.activityPubActor), - req.body.eventDescription, - req.body.eventName, - req.body.eventLocation, - eventImageFilename, - startUTC, - endUTC, - req.body.timezone, - ) - : null, - activityPubEvent: event.activityPubEvent - ? updateActivityPubEvent( - JSON.parse(event.activityPubEvent), - req.body.eventName, - req.body.startUTC, - req.body.endUTC, - req.body.timezone, - ) - : null, - }; - let diffText = - "

This event was just updated with new information.

`; - Event.findOneAndUpdate( - { id: req.params.eventID }, - updatedEvent, - function (err, raw) { - if (err) { - addToLog( - "editEvent", - "error", - "Attempt to edit event " + - req.params.eventID + - " failed with error: " + - err, - ); - res.send(err); - } - }, - ) - .then(() => { - addToLog( - "editEvent", - "success", - "Event " + req.params.eventID + " edited", - ); - // send update to ActivityPub subscribers - Event.findOne( - { id: req.params.eventID }, - function (err, event) { - if (!event) return; - let attendees = event.attendees.filter( - (el) => el.id, - ); - if (!err) { - // broadcast an identical message to all followers, will show in home timeline - const guidObject = crypto - .randomBytes(16) - .toString("hex"); - const jsonObject = { - "@context": - "https://www.w3.org/ns/activitystreams", - id: `https://${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: https://${domain}/${req.params.eventID}`, - }; - 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 - const jsonUpdateObject = JSON.parse( - event.activityPubActor, - ); - broadcastUpdateMessage( - jsonUpdateObject, - event.followers, - eventID, - ); - // also broadcast an Update/Event for any calendar apps that are consuming our Events - const jsonEventObject = JSON.parse( - event.activityPubEvent, - ); - broadcastUpdateMessage( - jsonEventObject, - event.followers, - eventID, - ); - - // DM to attendees - for (const attendee of attendees) { - const jsonObject = { - "@context": - "https://www.w3.org/ns/activitystreams", - name: `RSVP to ${event.name}`, - type: "Note", - content: `@${attendee.name} ${diffText} See here: https://${domain}/${req.params.eventID}`, - tag: [ - { - type: "Mention", - href: attendee.id, - name: attendee.name, - }, - ], - }; - // send direct message to user - sendDirectMessage( - jsonObject, - attendee.id, - eventID, - ); - } - } - }, - ); - // Send update to all attendees - if (sendEmails) { - Event.findOne({ id: req.params.eventID }).then( - (event) => { - const attendeeEmails = event.attendees - .filter( - (o) => - o.status === "attending" && - o.email, - ) - .map((o) => o.email); - if (attendeeEmails.length) { - console.log( - "Sending emails to: " + - attendeeEmails, - ); - req.app.get("hbsInstance").renderView( - "./views/emails/editevent.handlebars", - { - diffText, - eventID: req.params.eventID, - siteName, - siteLogo, - domain, - cache: true, - layout: "email.handlebars", - }, - function (err, html) { - const msg = { - to: attendeeEmails, - from: { - name: siteName, - email: contactEmail, - address: contactEmail, - }, - subject: `${siteName}: ${event.name} was just edited`, - html, - }; - switch (mailService) { - case "sendgrid": - sgMail - .sendMultiple(msg) - .catch((e) => { - console.error( - e.toString(), - ); - res.status( - 500, - ).end(); - }); - break; - case "nodemailer": - nodemailerTransporter - .sendMail(msg) - .catch((e) => { - console.error( - e.toString(), - ); - res.status( - 500, - ).end(); - }); - break; - } - }, - ); - } else { - console.log("Nothing to send!"); - } - }, - ); - } - res.writeHead(302, { - Location: - "/" + - req.params.eventID + - "?e=" + - req.params.editToken, - }); - res.end(); - }) - .catch((err) => { - console.error(err); - res.send("Sorry! Something went wrong!"); - addToLog( - "editEvent", - "error", - "Attempt to edit event " + - req.params.eventID + - " failed with error: " + - err, - ); - }); - } else { - // Token doesn't match - res.send("Sorry! Something went wrong"); - addToLog( - "editEvent", - "error", - "Attempt to edit event " + - req.params.eventID + - " failed with error: token does not match", - ); - } - }) - .catch((err) => { - console.error(err); - res.send("Sorry! Something went wrong!"); - addToLog( - "editEvent", - "error", - "Attempt to edit event " + - req.params.eventID + - " failed with error: " + - err, - ); - }); -}); - router.post("/editeventgroup/:eventGroupID/:editToken", (req, res) => { let submittedEditToken = req.params.editToken; EventGroup.findOne({ @@ -1506,6 +754,7 @@ router.post("/editeventgroup/:eventGroupID/:editToken", (req, res) => { router.post("/deleteimage/:eventID/:editToken", (req, res) => { let submittedEditToken = req.params.editToken; + let eventImage; Event.findOne({ id: req.params.eventID, }).then((event) => { -- cgit v1.2.3