diff options
author | Raphael Kabo <raphaelkabo@hey.com> | 2023-10-07 14:30:24 +0100 |
---|---|---|
committer | Raphael Kabo <raphaelkabo@hey.com> | 2023-10-07 15:38:47 +0100 |
commit | b795d07ed7a1b705b72b171f8e8de267a720223b (patch) | |
tree | b8ae3df8dbb89f839f29328e817f030dc22b89f8 /src/util | |
parent | 9341659fd7a791d77454dd33743e42d952dbd202 (diff) |
refactor: event form and api routes
Diffstat (limited to 'src/util')
-rw-r--r-- | src/util/config.ts | 2 | ||||
-rw-r--r-- | src/util/generator.ts | 24 | ||||
-rw-r--r-- | src/util/validation.ts | 191 |
3 files changed, 217 insertions, 0 deletions
diff --git a/src/util/config.ts b/src/util/config.ts index c65fdb0..d1fd05b 100644 --- a/src/util/config.ts +++ b/src/util/config.ts @@ -7,6 +7,7 @@ interface FrontendConfig { email: string; siteName: string; showKofi: boolean; + isFederated: boolean; } export const frontendConfig = (): FrontendConfig => ({ @@ -14,4 +15,5 @@ export const frontendConfig = (): FrontendConfig => ({ email: config.general.email, siteName: config.general.site_name, showKofi: config.general.show_kofi, + isFederated: config.general.is_federated, }); diff --git a/src/util/generator.ts b/src/util/generator.ts new file mode 100644 index 0000000..c3712c1 --- /dev/null +++ b/src/util/generator.ts @@ -0,0 +1,24 @@ +import crypto from "crypto"; + +const generateAlphanumericString = (length: number) => { + return Array(length) + .fill(0) + .map((x) => Math.random().toString(36).charAt(2)) + .join(""); +}; + +export const generateEditToken = () => generateAlphanumericString(32); + +export const generateRSAKeypair = () => { + return crypto.generateKeyPairSync("rsa", { + modulusLength: 4096, + publicKeyEncoding: { + type: "spki", + format: "pem", + }, + privateKeyEncoding: { + type: "pkcs8", + format: "pem", + }, + }); +}; diff --git a/src/util/validation.ts b/src/util/validation.ts new file mode 100644 index 0000000..f51769e --- /dev/null +++ b/src/util/validation.ts @@ -0,0 +1,191 @@ +import moment from "moment-timezone"; + +type Error = { + message?: string; + field?: string; +}; + +type ValidationResponse = { + data?: ValidatedEventData; + errors?: Error[]; +}; + +interface EventData { + eventName: string; + eventLocation: string; + eventStart: string; + eventEnd: string; + timezone: string; + eventDescription: string; + eventURL: string; + imagePath: string; + hostName: string; + creatorEmail: string; + eventGroupCheckbox: string; + eventGroupID: string; + eventGroupEditToken: string; + interactionCheckbox: string; + joinCheckbox: string; + maxAttendeesCheckbox: string; + maxAttendees: number; +} + +// EventData without the 'checkbox' fields +export type ValidatedEventData = Omit< + EventData, + | "eventGroupCheckbox" + | "interactionCheckbox" + | "joinCheckbox" + | "maxAttendeesCheckbox" +> & { + eventGroupBoolean: boolean; + interactionBoolean: boolean; + joinBoolean: boolean; + maxAttendeesBoolean: boolean; +}; + +const validateEmail = (email: string) => { + if (!email || email.length === 0 || typeof email !== "string") { + return false; + } + var re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return re.test(email); +}; + +export const validateEventTime = (start: Date, end: Date): Error | boolean => { + if (moment(start).isAfter(moment(end))) { + return { + message: "Start time must be before end time.", + field: "eventStart", + }; + } + if (moment(start).isBefore(moment())) { + return { + message: "Start time must be in the future.", + field: "eventStart", + }; + } + if (moment(end).isBefore(moment())) { + return { + message: "End time must be in the future.", + field: "eventEnd", + }; + } + // Duration cannot be longer than 1 year + if (moment(end).diff(moment(start), "years") > 1) { + return { + message: "Event duration cannot be longer than 1 year.", + field: "eventEnd", + }; + } + return true; +}; + +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, + 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) { + errors.push({ + message: "Event name is required.", + field: "eventName", + }); + } + if (!validatedData.eventLocation) { + errors.push({ + message: "Event location is required.", + field: "eventLocation", + }); + } + if (!validatedData.eventStart) { + errors.push({ + message: "Event start time is required.", + field: "eventStart", + }); + } + if (!validatedData.eventEnd) { + errors.push({ + message: "Event end time is required.", + field: "eventEnd", + }); + } + const timeValidation = validateEventTime( + new Date(validatedData.eventStart), + new Date(validatedData.eventEnd), + ); + if (timeValidation !== true && timeValidation !== false) { + errors.push({ + message: timeValidation.message, + }); + } + if (!validatedData.timezone) { + errors.push({ + message: "Event timezone is required.", + field: "timezone", + }); + } + if (!validatedData.eventDescription) { + errors.push({ + message: "Event description is required.", + field: "eventDescription", + }); + } + if (validatedData.eventGroupBoolean) { + if (!validatedData.eventGroupID) { + errors.push({ + message: "Event group ID is required.", + field: "eventGroupID", + }); + } + if (!validatedData.eventGroupEditToken) { + errors.push({ + message: "Event group edit token is required.", + field: "eventGroupEditToken", + }); + } + } + if (validatedData.maxAttendeesBoolean) { + if (!validatedData.maxAttendees) { + errors.push({ + message: "Max number of attendees is required.", + field: "maxAttendees", + }); + } + if (isNaN(validatedData.maxAttendees)) { + errors.push({ + message: "Max number of attendees must be a number.", + field: "maxAttendees", + }); + } + } + if (validatedData.creatorEmail) { + if (!validateEmail(validatedData.creatorEmail)) { + errors.push({ + message: "Email address is invalid.", + field: "creatorEmail", + }); + } + } + + return { + data: validatedData, + errors: errors, + }; +}; |