diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/app.ts | 5 | ||||
-rwxr-xr-x | src/routes.js | 197 | ||||
-rw-r--r-- | src/routes/event.ts | 5 | ||||
-rw-r--r-- | src/routes/frontend.ts | 37 | ||||
-rw-r--r-- | src/routes/group.ts | 240 | ||||
-rw-r--r-- | src/util/validation.ts | 51 |
6 files changed, 321 insertions, 214 deletions
@@ -5,6 +5,7 @@ import routes from "./routes.js"; import frontend from "./routes/frontend.js"; import activitypub from "./routes/activitypub.js"; import event from "./routes/event.js"; +import group from "./routes/group.js"; import { initEmailService } from "./lib/email.js"; @@ -33,6 +34,9 @@ const hbsInstance = hbs.create({ match[1] + (match[3] || "s") ); // Plural case: 'bagel(s)' or 'bagel' --> bagels }, + json: function (context: any) { + return JSON.stringify(context); + }, }, }); app.engine("handlebars", hbsInstance.engine); @@ -52,6 +56,7 @@ app.use(express.urlencoded({ extended: true })); app.use("/", frontend); app.use("/", activitypub); app.use("/", event); +app.use("/", group); app.use("/", routes); export default app; diff --git a/src/routes.js b/src/routes.js index e4ef3cb..96420c7 100755 --- a/src/routes.js +++ b/src/routes.js @@ -298,102 +298,6 @@ router.post("/importevent", (req, res) => { } }); -router.post("/neweventgroup", (req, res) => { - let eventGroupID = nanoid(); - let editToken = randomstring.generate(); - let eventGroupImageFilename = ""; - if (req.files && Object.keys(req.files).length !== 0) { - let eventImageBuffer = req.files.imageUpload.data; - Jimp.read(eventImageBuffer, (err, img) => { - if (err) - addToLog( - "Jimp", - "error", - "Attempt to edit image failed with error: " + err, - ); - img.resize(920, Jimp.AUTO) // resize - .quality(80) // set JPEG quality - .write("./public/events/" + eventGroupID + ".jpg"); // save - }); - eventGroupImageFilename = eventGroupID + ".jpg"; - } - const eventGroup = new EventGroup({ - id: eventGroupID, - name: req.body.eventGroupName, - description: req.body.eventGroupDescription, - image: eventGroupImageFilename, - creatorEmail: req.body.creatorEmail, - url: req.body.eventGroupURL, - hostName: req.body.hostName, - editToken: editToken, - firstLoad: true, - }); - eventGroup - .save() - .then(() => { - addToLog( - "createEventGroup", - "success", - "Event group " + eventGroupID + " created", - ); - // Send email with edit link - if (req.body.creatorEmail && sendEmails) { - req.app.get("hbsInstance").renderView( - "./views/emails/createeventgroup.handlebars", - { - eventGroupID, - 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.eventGroupName}`, - 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: "/group/" + eventGroupID + "?e=" + editToken, - }); - res.end(); - }) - .catch((err) => { - res.send("Database error, please try again :( - " + err); - addToLog( - "createEvent", - "error", - "Attempt to create event failed with error: " + err, - ); - }); -}); - router.post("/verifytoken/event/:eventID", (req, res) => { Event.findOne({ id: req.params.eventID, @@ -414,107 +318,6 @@ router.post("/verifytoken/group/:eventGroupID", (req, res) => { }); }); -router.post("/editeventgroup/:eventGroupID/:editToken", (req, res) => { - let submittedEditToken = req.params.editToken; - EventGroup.findOne({ - id: req.params.eventGroupID, - }) - .then((eventGroup) => { - if (eventGroup.editToken === submittedEditToken) { - // Token matches - - // If there is a new image, upload that first - let eventGroupID = req.params.eventGroupID; - let eventGroupImageFilename = eventGroup.image; - if (req.files && Object.keys(req.files).length !== 0) { - let eventImageBuffer = req.files.eventGroupImageUpload.data; - Jimp.read(eventImageBuffer, (err, img) => { - if (err) throw err; - img.resize(920, Jimp.AUTO) // resize - .quality(80) // set JPEG - .write("./public/events/" + eventGroupID + ".jpg"); // save - }); - eventGroupImageFilename = eventGroupID + ".jpg"; - } - const updatedEventGroup = { - name: req.body.eventGroupName, - description: req.body.eventGroupDescription, - url: req.body.eventGroupURL, - hostName: req.body.hostName, - image: eventGroupImageFilename, - }; - EventGroup.findOneAndUpdate( - { id: req.params.eventGroupID }, - updatedEventGroup, - function (err, raw) { - if (err) { - addToLog( - "editEventGroup", - "error", - "Attempt to edit event group " + - req.params.eventGroupID + - " failed with error: " + - err, - ); - res.send(err); - } - }, - ) - .then(() => { - addToLog( - "editEventGroup", - "success", - "Event group " + - req.params.eventGroupID + - " edited", - ); - res.writeHead(302, { - Location: - "/group/" + - req.params.eventGroupID + - "?e=" + - req.params.editToken, - }); - res.end(); - }) - .catch((err) => { - console.error(err); - res.send("Sorry! Something went wrong!"); - addToLog( - "editEventGroup", - "error", - "Attempt to edit event group " + - req.params.eventGroupID + - " failed with error: " + - err, - ); - }); - } else { - // Token doesn't match - res.send("Sorry! Something went wrong"); - addToLog( - "editEventGroup", - "error", - "Attempt to edit event group " + - req.params.eventGroupID + - " failed with error: token does not match", - ); - } - }) - .catch((err) => { - console.error(err); - res.send("Sorry! Something went wrong!"); - addToLog( - "editEventGroup", - "error", - "Attempt to edit event group " + - req.params.eventGroupID + - " failed with error: " + - err, - ); - }); -}); - router.post("/deleteimage/:eventID/:editToken", (req, res) => { let submittedEditToken = req.params.editToken; let eventImage; diff --git a/src/routes/event.ts b/src/routes/event.ts index 375871b..be27fd4 100644 --- a/src/routes/event.ts +++ b/src/routes/event.ts @@ -74,8 +74,7 @@ router.post( img.resize(920, Jimp.AUTO) // resize .quality(80) // set JPEG quality .write("./public/events/" + eventID + ".jpg"); // save - const filename = eventID + ".jpg"; - return filename; + return eventID + ".jpg"; }) .catch((err) => { addToLog( @@ -280,8 +279,8 @@ router.put( }); } - let submittedEditToken = req.body.editToken; try { + const submittedEditToken = req.body.editToken; const event = await Event.findOne({ id: req.params.eventID, }); diff --git a/src/routes/frontend.ts b/src/routes/frontend.ts index 56ce4db..c9594ef 100644 --- a/src/routes/frontend.ts +++ b/src/routes/frontend.ts @@ -6,7 +6,7 @@ import { renderPlain } from "../util/markdown.js"; import getConfig from "../lib/config.js"; import { addToLog, exportICal } from "../helpers.js"; import Event from "../models/Event.js"; -import EventGroup from "../models/EventGroup.js"; +import EventGroup, { IEventGroup } from "../models/EventGroup.js"; const config = getConfig(); @@ -215,6 +215,31 @@ router.get("/:eventID", async (req: Request, res: Response) => { eventHasConcluded: eventHasConcluded, eventHasBegun: eventHasBegun, metadata: metadata, + jsonData: { + name: event.name, + id: event.id, + description: event.description, + location: event.location, + timezone: event.timezone, + url: event.url, + hostName: event.hostName, + creatorEmail: event.creatorEmail, + eventGroupID: event.eventGroup + ? (event.eventGroup as unknown as IEventGroup).id + : null, + eventGroupEditToken: event.eventGroup + ? (event.eventGroup as unknown as IEventGroup).editToken + : null, + usersCanAttend: event.usersCanAttend, + usersCanComment: event.usersCanComment, + maxAttendees: event.maxAttendees, + startISO: eventStartISO, + endISO: eventEndISO, + startForDateInput: parsedStartForDateInput, + endForDateInput: parsedEndForDateInput, + image: event.image, + editToken: editingEnabled ? eventEditToken : null, + }, }); } } catch (err) { @@ -321,6 +346,16 @@ router.get("/group/:eventGroupID", async (req: Request, res: Response) => { eventGroupHasHost: eventGroupHasHost, firstLoad: firstLoad, metadata: metadata, + jsonData: { + name: eventGroup.name, + id: eventGroup.id, + description: eventGroup.description, + url: eventGroup.url, + hostName: eventGroup.hostName, + creatorEmail: eventGroup.creatorEmail, + image: eventGroup.image, + editToken: editingEnabled ? eventGroupEditToken : null, + }, }); } catch (err) { addToLog( diff --git a/src/routes/group.ts b/src/routes/group.ts new file mode 100644 index 0000000..2801248 --- /dev/null +++ b/src/routes/group.ts @@ -0,0 +1,240 @@ +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"; +import Jimp from "jimp"; +import { addToLog } from "../helpers.js"; +import EventGroup from "../models/EventGroup.js"; +import { sendEmailFromTemplate } from "../lib/email.js"; + +const config = getConfig(); + +const storage = multer.memoryStorage(); +// Accept only JPEG, GIF or PNG images, up to 10MB +const upload = multer({ + storage: storage, + limits: { fileSize: 10 * 1024 * 1024 }, + fileFilter: function (_, file, cb) { + const filetypes = /jpeg|jpg|png|gif/; + const mimetype = filetypes.test(file.mimetype); + if (!mimetype) { + return cb(new Error("Only JPEG, PNG and GIF images are allowed.")); + } + cb(null, true); + }, +}); + +const router = Router(); + +router.post( + "/group", + upload.single("imageUpload"), + async (req: Request, res: Response) => { + const { data: groupData, errors } = validateGroupData(req.body); + if (errors && errors.length > 0) { + return res.status(400).json({ errors }); + } + if (!groupData) { + return res.status(400).json({ + errors: [ + { + message: "No group data was provided.", + }, + ], + }); + } + + try { + const groupID = generateEventID(); + const editToken = generateEditToken(); + let groupImageFilename; + + if (req.file?.buffer) { + groupImageFilename = await Jimp.read(req.file.buffer) + .then((img) => { + img.resize(920, Jimp.AUTO) // resize + .quality(80) // set JPEG quality + .write("./public/events/" + groupID + ".jpg"); // save + return groupID + ".jpg"; + }) + .catch((err) => { + addToLog( + "Jimp", + "error", + "Attempt to edit image failed with error: " + err, + ); + }); + } + + const eventGroup = new EventGroup({ + id: groupID, + name: groupData.eventGroupName, + description: groupData.eventGroupDescription, + image: groupImageFilename, + creatorEmail: groupData.creatorEmail, + url: groupData.eventGroupURL, + hostName: groupData.hostName, + editToken: editToken, + firstLoad: true, + }); + + await eventGroup.save(); + + addToLog( + "createEventGroup", + "success", + "Event group " + groupID + " created", + ); + + // Send email with edit link + if (groupData.creatorEmail && req.app.locals.sendEmails) { + sendEmailFromTemplate( + groupData.creatorEmail, + `${eventGroup.name}`, + "createEventGroup", + { + eventGroupID: eventGroup.id, + editToken: eventGroup.editToken, + siteName: config.general.site_name, + siteLogo: config.general.email_logo_url, + domain: config.general.domain, + }, + req, + ); + } + + res.status(200).json({ + id: groupID, + editToken: editToken, + url: `/group/${groupID}?e=${editToken}`, + }); + } catch (err) { + console.error(err); + addToLog( + "createEvent", + "error", + "Attempt to create event failed with error: " + err, + ); + return res.status(500).json({ + errors: [ + { + message: err, + }, + ], + }); + } + }, +); + +router.put( + "/group/:eventGroupID", + upload.single("imageUpload"), + async (req: Request, res: Response) => { + const { data: groupData, errors } = validateGroupData(req.body); + if (errors && errors.length > 0) { + return res.status(400).json({ errors }); + } + if (!groupData) { + return res.status(400).json({ + errors: [ + { + message: "No group data was provided.", + }, + ], + }); + } + + try { + const submittedEditToken = req.body.editToken; + const eventGroup = await EventGroup.findOne({ + id: req.params.eventGroupID, + }); + if (!eventGroup) { + return res.status(404).json({ + errors: [ + { + message: "Event group not found.", + }, + ], + }); + } + + if (eventGroup.editToken !== submittedEditToken) { + // Token doesn't match + addToLog( + "editEventGroup", + "error", + `Attempt to edit event group ${req.params.eventGroupID} failed with error: token does not match`, + ); + return res.status(403).json({ + errors: [ + { + message: "Edit token is invalid.", + }, + ], + }); + } + // Token matches + // If there is a new image, upload that first + let eventGroupID = req.params.eventGroupID; + let eventGroupImageFilename = eventGroup.image; + if (req.file?.buffer) { + Jimp.read(req.file.buffer) + .then((img) => { + img.resize(920, Jimp.AUTO) // resize + .quality(80) // set JPEG quality + .write(`./public/events/${eventGroupID}.jpg`); // save + }) + .catch((err) => { + addToLog( + "Jimp", + "error", + "Attempt to edit image failed with error: " + err, + ); + }); + eventGroupImageFilename = eventGroupID + ".jpg"; + } + + const updatedEventGroup = { + name: req.body.eventGroupName, + description: req.body.eventGroupDescription, + url: req.body.eventGroupURL, + hostName: req.body.hostName, + image: eventGroupImageFilename, + }; + + await EventGroup.findOneAndUpdate( + { id: req.params.eventGroupID }, + updatedEventGroup, + ); + + addToLog( + "editEventGroup", + "success", + "Event group " + req.params.eventGroupID + " edited", + ); + + res.sendStatus(200); + } catch (err) { + console.error(err); + addToLog( + "editEventGroup", + "error", + "Attempt to edit event group " + + req.params.eventGroupID + + " failed with error: " + + err, + ); + return res.status(500).json({ + errors: [ + { + message: err, + }, + ], + }); + } + }, +); + +export default router; diff --git a/src/util/validation.ts b/src/util/validation.ts index f51769e..732fbf3 100644 --- a/src/util/validation.ts +++ b/src/util/validation.ts @@ -44,6 +44,14 @@ export type ValidatedEventData = Omit< maxAttendeesBoolean: boolean; }; +interface EventGroupData { + eventGroupName: string; + eventGroupDescription: string; + eventGroupURL: string; + hostName: string; + creatorEmail: string; +} + const validateEmail = (email: string) => { if (!email || email.length === 0 || typeof email !== "string") { return false; @@ -83,23 +91,11 @@ export const validateEventTime = (start: Date, end: Date): Error | boolean => { export const validateEventData = (eventData: EventData): ValidationResponse => { const validatedData: ValidatedEventData = { - eventName: eventData.eventName, - eventLocation: eventData.eventLocation, - eventStart: eventData.eventStart, - eventEnd: eventData.eventEnd, - timezone: eventData.timezone, - eventDescription: eventData.eventDescription, - eventURL: eventData.eventURL, - imagePath: eventData.imagePath, - hostName: eventData.hostName, - creatorEmail: eventData.creatorEmail, + ...eventData, eventGroupBoolean: eventData.eventGroupCheckbox === "true", interactionBoolean: eventData.interactionCheckbox === "true", joinBoolean: eventData.joinCheckbox === "true", maxAttendeesBoolean: eventData.maxAttendeesCheckbox === "true", - eventGroupID: eventData.eventGroupID, - eventGroupEditToken: eventData.eventGroupEditToken, - maxAttendees: eventData.maxAttendees, }; const errors: Error[] = []; if (!validatedData.eventName) { @@ -189,3 +185,32 @@ export const validateEventData = (eventData: EventData): ValidationResponse => { errors: errors, }; }; + +export const validateGroupData = (groupData: EventGroupData) => { + const errors: Error[] = []; + if (!groupData.eventGroupName) { + errors.push({ + message: "Event group name is required.", + field: "eventGroupName", + }); + } + if (!groupData.eventGroupDescription) { + errors.push({ + message: "Event group description is required.", + field: "eventGroupDescription", + }); + } + if (groupData.creatorEmail) { + if (!validateEmail(groupData.creatorEmail)) { + errors.push({ + message: "Email address is invalid.", + field: "creatorEmail", + }); + } + } + + return { + data: groupData, + errors: errors, + }; +}; |