summaryrefslogtreecommitdiff
path: root/src/util
diff options
context:
space:
mode:
authorRaphael <mail@raphaelkabo.com>2023-10-08 19:26:04 +0100
committerGitHub <noreply@github.com>2023-10-08 19:26:04 +0100
commit44e150bc7f8391b56b78a0697dbd128a8bf8be7b (patch)
treeef065e69228453d5d49b886157a4a88ed3540474 /src/util
parent9ef8e220b4fb582d620016d293b340a63ec97cff (diff)
parent608532d24d868d939fd2cef6302d8d5089a81ee4 (diff)
Merge pull request #112 from lowercasename/rk/typescript
Typescript migration
Diffstat (limited to 'src/util')
-rw-r--r--src/util/config.ts2
-rw-r--r--src/util/generator.ts34
-rw-r--r--src/util/validation.ts216
3 files changed, 252 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..596110d
--- /dev/null
+++ b/src/util/generator.ts
@@ -0,0 +1,34 @@
+import crypto from "crypto";
+import { customAlphabet } from "nanoid";
+
+// This alphabet (used to generate all event, group, etc. IDs) is missing '-'
+// because ActivityPub doesn't like it in IDs
+const nanoid = customAlphabet(
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_",
+ 21,
+);
+
+const generateAlphanumericString = (length: number) => {
+ return Array(length)
+ .fill(0)
+ .map((x) => Math.random().toString(36).charAt(2))
+ .join("");
+};
+
+export const generateEventID = () => nanoid();
+
+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..732fbf3
--- /dev/null
+++ b/src/util/validation.ts
@@ -0,0 +1,216 @@
+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;
+};
+
+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;
+ }
+ 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 = {
+ ...eventData,
+ eventGroupBoolean: eventData.eventGroupCheckbox === "true",
+ interactionBoolean: eventData.interactionCheckbox === "true",
+ joinBoolean: eventData.joinCheckbox === "true",
+ maxAttendeesBoolean: eventData.maxAttendeesCheckbox === "true",
+ };
+ 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,
+ };
+};
+
+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,
+ };
+};