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, +    }; +};  | 
