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