@koloseum/utils 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/utils.d.ts +2 -1
- package/package.json +3 -4
- package/src/utils.test.ts +0 -191
- package/src/utils.ts +0 -313
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { CustomError
|
|
1
|
+
import type { CustomError } from "@koloseum/types/general";
|
|
2
|
+
import type { PronounsCheckboxes, SocialMediaPlatform } from "@koloseum/types/public-auth";
|
|
2
3
|
import type { PostgrestError } from "@supabase/supabase-js";
|
|
3
4
|
import type { MobilePhoneLocale } from "validator";
|
|
4
5
|
export declare const Status: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koloseum/utils",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"author": "Koloseum Technologies Limited",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Utility logic for use across Koloseum web apps (TypeScript)",
|
|
@@ -17,10 +17,9 @@
|
|
|
17
17
|
},
|
|
18
18
|
"main": "./dist/utils.js",
|
|
19
19
|
"exports": {
|
|
20
|
-
"
|
|
20
|
+
".": "./dist/utils.js"
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
|
-
"./src/**/*.ts",
|
|
24
23
|
"./dist/**/*"
|
|
25
24
|
],
|
|
26
25
|
"scripts": {
|
|
@@ -30,13 +29,13 @@
|
|
|
30
29
|
"test": "vitest --run"
|
|
31
30
|
},
|
|
32
31
|
"dependencies": {
|
|
33
|
-
"@koloseum/types": "^0.1.3",
|
|
34
32
|
"@supabase/supabase-js": "^2.48.1",
|
|
35
33
|
"@sveltejs/kit": "^2.17.1",
|
|
36
34
|
"sanitize-html": "^2.14.0",
|
|
37
35
|
"validator": "^13.12.0"
|
|
38
36
|
},
|
|
39
37
|
"devDependencies": {
|
|
38
|
+
"@koloseum/types": "^0.1.9",
|
|
40
39
|
"@playwright/test": "^1.50.1",
|
|
41
40
|
"@types/sanitize-html": "^2.13.0",
|
|
42
41
|
"@types/validator": "^13.12.2",
|
package/src/utils.test.ts
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { Status, Utility } from "./utils";
|
|
3
|
-
|
|
4
|
-
describe("Status helper", () => {
|
|
5
|
-
// Destructure the Status object
|
|
6
|
-
const { ERROR, LOADING } = Status;
|
|
7
|
-
|
|
8
|
-
// Test each helper
|
|
9
|
-
it("contains an error message", () => {
|
|
10
|
-
expect(ERROR).toBe("An unexpected error occurred. Kindly try again.");
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it("contains a loading message", () => {
|
|
14
|
-
expect(LOADING).toBe("Please wait…");
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
describe("Utility helper", () => {
|
|
19
|
-
// Destructure the Utility object
|
|
20
|
-
const {
|
|
21
|
-
capitalise,
|
|
22
|
-
customError,
|
|
23
|
-
formatDate,
|
|
24
|
-
formatSocialMediaPlatform,
|
|
25
|
-
getAge,
|
|
26
|
-
minimumMinorBirthDate,
|
|
27
|
-
parseLoadError,
|
|
28
|
-
parsePostgrestError,
|
|
29
|
-
sanitiseHtml,
|
|
30
|
-
validateE164,
|
|
31
|
-
validateSocialMediaHandle
|
|
32
|
-
} = Utility;
|
|
33
|
-
|
|
34
|
-
// Test each helper
|
|
35
|
-
it("capitalises a word", () => {
|
|
36
|
-
expect(capitalise("word")).toBe("Word");
|
|
37
|
-
expect(capitalise("WORD")).toBe("Word");
|
|
38
|
-
expect(capitalise("wOrD")).toBe("Word");
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("generates a custom error", () => {
|
|
42
|
-
expect(customError(200, "OK")).toEqual({ code: 500, message: Status.ERROR });
|
|
43
|
-
expect(customError(400, "Bad Request")).toEqual({ code: 400, message: "Bad Request" });
|
|
44
|
-
expect(customError(500, "Internal Server Error")).toEqual({ code: 500, message: "Internal Server Error" });
|
|
45
|
-
expect(customError(700, "Unknown")).toEqual({ code: 500, message: Status.ERROR });
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("formats dates", () => {
|
|
49
|
-
expect(formatDate("31 December 2021")).toBe("31/12/2021");
|
|
50
|
-
expect(formatDate("31 December 2021", true)).toBe("2021-12-31");
|
|
51
|
-
expect(formatDate("31 December 2021", false)).toBe("31/12/2021");
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("formats social media platforms", () => {
|
|
55
|
-
expect(formatSocialMediaPlatform("facebook")).toBe("Facebook");
|
|
56
|
-
expect(formatSocialMediaPlatform("instagram")).toBe("Instagram");
|
|
57
|
-
expect(formatSocialMediaPlatform("kick")).toBe("Kick");
|
|
58
|
-
expect(formatSocialMediaPlatform("tiktok")).toBe("TikTok");
|
|
59
|
-
expect(formatSocialMediaPlatform("twitter")).toBe("X (formerly Twitter)");
|
|
60
|
-
expect(formatSocialMediaPlatform("youtube")).toBe("YouTube");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("gets the age of a person", () => {
|
|
64
|
-
// Set system time to 1 January 2024
|
|
65
|
-
vi.useFakeTimers();
|
|
66
|
-
vi.setSystemTime(new Date("2024-01-01"));
|
|
67
|
-
|
|
68
|
-
// Test age calculation
|
|
69
|
-
expect(getAge("2000-01-01")).toBe(24);
|
|
70
|
-
expect(getAge("2000-12-31")).toBe(23);
|
|
71
|
-
expect(getAge("2024-01-03")).toBe(-1);
|
|
72
|
-
|
|
73
|
-
// Reset timers
|
|
74
|
-
vi.useRealTimers();
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("gets the minimum birth date for a minor", () => {
|
|
78
|
-
// Calculate expected minimum birth date
|
|
79
|
-
const today = new Date();
|
|
80
|
-
const tomorrow = new Date();
|
|
81
|
-
tomorrow.setUTCDate(today.getDate() + 1);
|
|
82
|
-
|
|
83
|
-
const tomorrow18YearsAgo = tomorrow.getFullYear() - 18;
|
|
84
|
-
tomorrow.setFullYear(tomorrow18YearsAgo);
|
|
85
|
-
|
|
86
|
-
const expectedMinimumMinorBirthDate = tomorrow.toISOString().split("T")[0];
|
|
87
|
-
|
|
88
|
-
// Test minimum birth date calculation
|
|
89
|
-
expect(minimumMinorBirthDate).toBe(expectedMinimumMinorBirthDate);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("parses a SvelteKit load error", () => {
|
|
93
|
-
const error = { code: 500, message: "Internal Server Error" };
|
|
94
|
-
expect(parseLoadError(error)).toEqual(new Error("Internal Server Error"));
|
|
95
|
-
// add assertion to check for SvelteKit `error` function?
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("parses a Postgrest error", () => {
|
|
99
|
-
expect(parsePostgrestError(null)).toEqual({ error: undefined });
|
|
100
|
-
expect(
|
|
101
|
-
parsePostgrestError({
|
|
102
|
-
code: "42501",
|
|
103
|
-
name: "insufficient_privilege",
|
|
104
|
-
message: 'Insufficient privileges: permission denied for relation "restricted_table"',
|
|
105
|
-
details: 'User does not have the necessary permissions to access the relation "restricted_table".',
|
|
106
|
-
hint: ""
|
|
107
|
-
})
|
|
108
|
-
).toEqual({ error: { code: 403, message: Status.ERROR } });
|
|
109
|
-
expect(
|
|
110
|
-
parsePostgrestError({
|
|
111
|
-
code: "42883",
|
|
112
|
-
name: "undefined_function",
|
|
113
|
-
message: "Undefined function: function non_existent_function() does not exist",
|
|
114
|
-
details: "Function non_existent_function() does not exist.",
|
|
115
|
-
hint: "No function matches the given name and argument types. You might need to add explicit type casts."
|
|
116
|
-
})
|
|
117
|
-
).toEqual({ error: { code: 404, message: Status.ERROR } });
|
|
118
|
-
expect(
|
|
119
|
-
parsePostgrestError({
|
|
120
|
-
code: "25006",
|
|
121
|
-
name: "read_only_sql_transaction",
|
|
122
|
-
message: "Read only SQL transaction: cannot execute INSERT in a read-only transaction",
|
|
123
|
-
details: "Cannot execute INSERT in a read-only transaction.",
|
|
124
|
-
hint: ""
|
|
125
|
-
})
|
|
126
|
-
).toEqual({ error: { code: 405, message: Status.ERROR } });
|
|
127
|
-
expect(
|
|
128
|
-
parsePostgrestError({
|
|
129
|
-
code: "23505",
|
|
130
|
-
name: "unique_violation",
|
|
131
|
-
message: 'Uniqueness violation: duplicate key value violates unique constraint "users_username_key"',
|
|
132
|
-
details: "Key (username)=(john_doe) already exists.",
|
|
133
|
-
hint: ""
|
|
134
|
-
})
|
|
135
|
-
).toEqual({ error: { code: 409, message: Status.ERROR } });
|
|
136
|
-
expect(
|
|
137
|
-
parsePostgrestError({
|
|
138
|
-
code: "53400",
|
|
139
|
-
name: "configuration_file_error",
|
|
140
|
-
message: "invalid page header in block 0 of relation base/12345/67890",
|
|
141
|
-
details: "The page header is corrupted. This may indicate hardware or file system issues.",
|
|
142
|
-
hint: ""
|
|
143
|
-
})
|
|
144
|
-
).toEqual({ error: { code: 500, message: Status.ERROR } });
|
|
145
|
-
expect(
|
|
146
|
-
parsePostgrestError({
|
|
147
|
-
code: "08003",
|
|
148
|
-
name: "connection_does_not_exist",
|
|
149
|
-
message: "connection does not exist",
|
|
150
|
-
details: "The connection to the database was lost or never established.",
|
|
151
|
-
hint: ""
|
|
152
|
-
})
|
|
153
|
-
).toEqual({ error: { code: 503, message: Status.ERROR } });
|
|
154
|
-
expect(
|
|
155
|
-
parsePostgrestError({
|
|
156
|
-
code: "P0001",
|
|
157
|
-
name: "raise_exception",
|
|
158
|
-
message: "I'm a teapot!",
|
|
159
|
-
details: "Pretty simple",
|
|
160
|
-
hint: "418"
|
|
161
|
-
})
|
|
162
|
-
).toEqual({ error: { code: 418, message: "I'm a teapot!" } });
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it("sanitises HTML", () => {
|
|
166
|
-
expect(sanitiseHtml("<script>alert('Hello!');</script>")).toBe("");
|
|
167
|
-
expect(sanitiseHtml("<p>Hello!</p>")).toBe("Hello!");
|
|
168
|
-
expect(sanitiseHtml("<p>Hello!</p><script>alert('Hello!');</script>")).toBe("Hello!");
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it("validates E.164 phone numbers", () => {
|
|
172
|
-
// Valid phone number
|
|
173
|
-
expect(validateE164("+254712345678")).toBe(true);
|
|
174
|
-
expect(validateE164("+254712345678", "en-KE")).toBe(true);
|
|
175
|
-
expect(validateE164("+254712345678", "en-RW")).toBe(false);
|
|
176
|
-
|
|
177
|
-
// Invalid phone numbers
|
|
178
|
-
expect(validateE164("+254812345678")).toBe(false);
|
|
179
|
-
expect(validateE164("254812345678")).toBe(false);
|
|
180
|
-
expect(validateE164("+2548123456789")).toBe(false);
|
|
181
|
-
expect(validateE164("2548123456789")).toBe(false);
|
|
182
|
-
expect(validateE164("08123456789")).toBe(false);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it("validates social media handles", () => {
|
|
186
|
-
expect(validateSocialMediaHandle("username")).toBe(true);
|
|
187
|
-
expect(validateSocialMediaHandle("@username")).toBe(false);
|
|
188
|
-
expect(validateSocialMediaHandle("/username")).toBe(false);
|
|
189
|
-
expect(validateSocialMediaHandle("https://www.facebook.com/username")).toBe(false);
|
|
190
|
-
});
|
|
191
|
-
});
|
package/src/utils.ts
DELETED
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
import type { CustomError, PronounsCheckboxes, SocialMediaPlatform } from "@koloseum/types/public/auth";
|
|
2
|
-
import type { PostgrestError } from "@supabase/supabase-js";
|
|
3
|
-
import type { MobilePhoneLocale } from "validator";
|
|
4
|
-
|
|
5
|
-
import { error as svelteError } from "@sveltejs/kit";
|
|
6
|
-
|
|
7
|
-
import sanitize from "sanitize-html";
|
|
8
|
-
import validator from "validator";
|
|
9
|
-
|
|
10
|
-
/* Helper functions */
|
|
11
|
-
const { isMobilePhone, isURL } = validator;
|
|
12
|
-
|
|
13
|
-
/* Status messages */
|
|
14
|
-
export const Status = {
|
|
15
|
-
/**
|
|
16
|
-
* A generic error message.
|
|
17
|
-
*/
|
|
18
|
-
ERROR: "An unexpected error occurred. Kindly try again.",
|
|
19
|
-
/**
|
|
20
|
-
* A generic loading message.
|
|
21
|
-
*/
|
|
22
|
-
LOADING: "Please wait…",
|
|
23
|
-
/**
|
|
24
|
-
* A generic password reset request message.
|
|
25
|
-
*/
|
|
26
|
-
PASSWORD_RESET_REQUESTED: "If the provided email address is registered, you will receive a password reset link shortly."
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/* Utility functions */
|
|
30
|
-
export const Utility = {
|
|
31
|
-
/**
|
|
32
|
-
* Capitalises a given word.
|
|
33
|
-
* @param {string} word - The word to be capitalised
|
|
34
|
-
* @returns The capitalised word
|
|
35
|
-
*/
|
|
36
|
-
capitalise: (word: string) =>
|
|
37
|
-
word.charAt(0).toUpperCase() +
|
|
38
|
-
word
|
|
39
|
-
.slice(1)
|
|
40
|
-
.split("")
|
|
41
|
-
.map((c) => c.toLowerCase())
|
|
42
|
-
.join(""),
|
|
43
|
-
/**
|
|
44
|
-
* Returns a custom error object.
|
|
45
|
-
* @param {number} code - The error code; defaults to `500` if not provided or invalid
|
|
46
|
-
* @param {string} message - The error message
|
|
47
|
-
* @returns An object with the error `code` and `message`
|
|
48
|
-
*/
|
|
49
|
-
customError: (code: number | undefined, message: string): CustomError =>
|
|
50
|
-
!code || code < 400 || code > 599 ? { code: 500, message: Status.ERROR } : { code, message },
|
|
51
|
-
/**
|
|
52
|
-
* A regular expression for E.164 phone numbers.
|
|
53
|
-
*
|
|
54
|
-
* - Source: https://www.twilio.com/docs/glossary/what-e164
|
|
55
|
-
*/
|
|
56
|
-
e164Regex: /^\+?[1-9]\d{1,14}$/,
|
|
57
|
-
/**
|
|
58
|
-
* Formats a date in the format DD/MM/YYYY (by default) or YYYY-MM-DD (for client).
|
|
59
|
-
* @param {string} date - Date to be formatted
|
|
60
|
-
* @param {boolean} forClient - Specify whether the data is for displaying on the client; defaults to `false`
|
|
61
|
-
* @returns The formatted date string, or `null` if invalid input or in case of an error
|
|
62
|
-
*/
|
|
63
|
-
formatDate: (date: string, forClient: boolean = false) => {
|
|
64
|
-
// Return null if no date is provided
|
|
65
|
-
if (date === "" || typeof date !== "string") return null;
|
|
66
|
-
|
|
67
|
-
// Handle input
|
|
68
|
-
try {
|
|
69
|
-
// Parse date string
|
|
70
|
-
const parsedDate = Date.parse(date);
|
|
71
|
-
|
|
72
|
-
// Return null if date string is not parseable
|
|
73
|
-
if (isNaN(parsedDate)) return null;
|
|
74
|
-
|
|
75
|
-
// Use the Intl.DateTimeFormat with explicit options to ensure correct formatting
|
|
76
|
-
const formattedDate = new Intl.DateTimeFormat(forClient ? "fr-CA" : "en-KE", {
|
|
77
|
-
day: "2-digit",
|
|
78
|
-
month: "2-digit",
|
|
79
|
-
year: "numeric",
|
|
80
|
-
timeZone: "Africa/Nairobi"
|
|
81
|
-
}).format(parsedDate);
|
|
82
|
-
|
|
83
|
-
// Return formatted date string
|
|
84
|
-
return formattedDate;
|
|
85
|
-
} catch (error) {
|
|
86
|
-
console.log(error);
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
/**
|
|
91
|
-
* Formats a social media platform for display.
|
|
92
|
-
* @param {SocialMediaPlatform} platform - The social media platform to be formatted
|
|
93
|
-
* @returns The formatted social media platform string
|
|
94
|
-
*/
|
|
95
|
-
formatSocialMediaPlatform: (platform: SocialMediaPlatform) => {
|
|
96
|
-
const platforms = ["facebook", "instagram", "kick", "tiktok", "twitch", "twitter", "youtube"];
|
|
97
|
-
if (!platforms.includes(platform)) return "";
|
|
98
|
-
|
|
99
|
-
if (platform === "tiktok") return "TikTok";
|
|
100
|
-
if (platform === "twitter") return "X (formerly Twitter)";
|
|
101
|
-
if (platform === "youtube") return "YouTube";
|
|
102
|
-
|
|
103
|
-
return Utility.capitalise(platform);
|
|
104
|
-
},
|
|
105
|
-
/**
|
|
106
|
-
* Returns an age in years given a birth date.
|
|
107
|
-
* @param {string} dateOfBirth - The date of birth as a string, e.g. `DD-MM-YYYY`
|
|
108
|
-
* @returns The age in years, or `NaN` if the input is invalid
|
|
109
|
-
*/
|
|
110
|
-
getAge: (dateOfBirth: string) => {
|
|
111
|
-
if (dateOfBirth === "" || typeof dateOfBirth !== "string") return NaN;
|
|
112
|
-
dateOfBirth = Utility.formatDate(dateOfBirth, true) as string;
|
|
113
|
-
|
|
114
|
-
const birthDate = new Date(dateOfBirth);
|
|
115
|
-
const currentDate = new Date();
|
|
116
|
-
|
|
117
|
-
let age = currentDate.getFullYear() - birthDate.getFullYear();
|
|
118
|
-
const monthDiff = currentDate.getMonth() - birthDate.getMonth();
|
|
119
|
-
|
|
120
|
-
return monthDiff < 0 || (monthDiff === 0 && currentDate.getDate() < birthDate.getDate()) ? --age : age;
|
|
121
|
-
},
|
|
122
|
-
/**
|
|
123
|
-
* Handles the click event for pronouns checkboxes.
|
|
124
|
-
* @param {MouseEvent} e - The click event
|
|
125
|
-
* @param {PronounsCheckboxes} checkboxes - The pronouns checkboxes
|
|
126
|
-
*/
|
|
127
|
-
handlePronounsCheckboxClick: (e: MouseEvent, checkboxes: PronounsCheckboxes) => {
|
|
128
|
-
const target = e.target as EventTarget & HTMLInputElement;
|
|
129
|
-
|
|
130
|
-
if (target.value === "none") {
|
|
131
|
-
for (let checkbox of Object.values(checkboxes))
|
|
132
|
-
if (checkbox && checkbox !== target && checkbox.checked) {
|
|
133
|
-
e.preventDefault();
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
for (let checkbox of Object.values(checkboxes))
|
|
138
|
-
if (checkbox && checkbox !== target) {
|
|
139
|
-
checkbox.checked = false;
|
|
140
|
-
checkbox.disabled = !checkbox.disabled;
|
|
141
|
-
}
|
|
142
|
-
} else {
|
|
143
|
-
if (checkboxes.none?.checked) {
|
|
144
|
-
e.preventDefault();
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (target.value === "male" || target.value === "female") {
|
|
149
|
-
const oppositeIndex = target.value === "male" ? "female" : "male";
|
|
150
|
-
const oppositeCheckbox = checkboxes[oppositeIndex];
|
|
151
|
-
|
|
152
|
-
if (oppositeCheckbox?.checked) {
|
|
153
|
-
e.preventDefault();
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (oppositeCheckbox) {
|
|
158
|
-
oppositeCheckbox.checked = false;
|
|
159
|
-
oppositeCheckbox.disabled = !oppositeCheckbox.disabled;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (checkboxes.none) {
|
|
163
|
-
checkboxes.none.checked = false;
|
|
164
|
-
if (!checkboxes.neutral?.checked) checkboxes.none.disabled = !checkboxes.none.disabled;
|
|
165
|
-
}
|
|
166
|
-
} else {
|
|
167
|
-
if (checkboxes.none) {
|
|
168
|
-
checkboxes.none.checked = false;
|
|
169
|
-
if (checkboxes.neutral && !checkboxes.male?.checked && !checkboxes.female?.checked)
|
|
170
|
-
checkboxes.none.disabled = checkboxes.neutral.checked;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
/**
|
|
176
|
-
* A regular expression for Koloseum Lounge Branch IDs. It covers the following rules:
|
|
177
|
-
* - 9 characters long
|
|
178
|
-
* - begins with "KLB" followed by 7 digits
|
|
179
|
-
*/
|
|
180
|
-
loungeBranchIdRegex: /^KLB\d{7}$/,
|
|
181
|
-
/**
|
|
182
|
-
* A regular expression for Koloseum Lounge IDs. It covers the following rules:
|
|
183
|
-
* - 9 characters long
|
|
184
|
-
* - begins with "KL" followed by 7 digits
|
|
185
|
-
*/
|
|
186
|
-
loungeIdRegex: /^KL\d{7}$/,
|
|
187
|
-
/**
|
|
188
|
-
* The minimum birth date (from today's date) for a user who is a minor, i.e. `YYYY-MM-DD`
|
|
189
|
-
*/
|
|
190
|
-
minimumMinorBirthDate: (() => {
|
|
191
|
-
const today = new Date();
|
|
192
|
-
const tomorrow = new Date();
|
|
193
|
-
tomorrow.setUTCDate(today.getDate() + 1);
|
|
194
|
-
|
|
195
|
-
const tomorrow18YearsAgo = tomorrow.getFullYear() - 18;
|
|
196
|
-
tomorrow.setFullYear(tomorrow18YearsAgo);
|
|
197
|
-
|
|
198
|
-
return tomorrow.toISOString().split("T")[0];
|
|
199
|
-
})(),
|
|
200
|
-
/**
|
|
201
|
-
* Parses a `CustomError` object within a page/layout server load function and returns an error to be thrown.
|
|
202
|
-
* @param {CustomError} error - The error object
|
|
203
|
-
* @returns A new `Error` object if the error is unexpected, or a Svelte error otherwise
|
|
204
|
-
*/
|
|
205
|
-
parseLoadError: (error: CustomError) =>
|
|
206
|
-
error.code === 500 ? new Error(error.message) : svelteError(error.code, { message: error.message }),
|
|
207
|
-
/**
|
|
208
|
-
* Parses a `PostgrestError` object and returns a custom error object if any has occurred.
|
|
209
|
-
* @param {PostgrestError | null} postgrestError - The `PostgrestError` object, or `null` if no error occurred
|
|
210
|
-
* @returns An object with an `error` if any has occurred
|
|
211
|
-
*/
|
|
212
|
-
parsePostgrestError: (postgrestError: PostgrestError | null): { error?: CustomError } => {
|
|
213
|
-
// Return undefined if no error occurred
|
|
214
|
-
let error: CustomError | undefined;
|
|
215
|
-
if (!postgrestError) return { error };
|
|
216
|
-
|
|
217
|
-
// Return custom error if hint is a number between 400 and 599
|
|
218
|
-
const customErrorCode = Number(postgrestError.hint);
|
|
219
|
-
if (!isNaN(customErrorCode) && customErrorCode >= 400 && customErrorCode <= 599) {
|
|
220
|
-
error = Utility.customError(customErrorCode, postgrestError.message);
|
|
221
|
-
return { error };
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Map Postgrest error codes to custom error codes
|
|
225
|
-
const errorMap = [
|
|
226
|
-
{ code: "08", status: 503 },
|
|
227
|
-
{ code: "09", status: 500 },
|
|
228
|
-
{ code: "0L", status: 403 },
|
|
229
|
-
{ code: "0P", status: 403 },
|
|
230
|
-
{ code: "23503", status: 409 },
|
|
231
|
-
{ code: "23505", status: 409 },
|
|
232
|
-
{ code: "25006", status: 405 },
|
|
233
|
-
{ code: "25", status: 500 },
|
|
234
|
-
{ code: "28", status: 403 },
|
|
235
|
-
{ code: "2D", status: 500 },
|
|
236
|
-
{ code: "38", status: 500 },
|
|
237
|
-
{ code: "39", status: 500 },
|
|
238
|
-
{ code: "3B", status: 500 },
|
|
239
|
-
{ code: "40", status: 500 },
|
|
240
|
-
{ code: "53400", status: 500 },
|
|
241
|
-
{ code: "53", status: 503 },
|
|
242
|
-
{ code: "54", status: 500 },
|
|
243
|
-
{ code: "55", status: 500 },
|
|
244
|
-
{ code: "57", status: 500 },
|
|
245
|
-
{ code: "58", status: 500 },
|
|
246
|
-
{ code: "F0", status: 500 },
|
|
247
|
-
{ code: "HV", status: 500 },
|
|
248
|
-
{ code: "P0001", status: 400 },
|
|
249
|
-
{ code: "P0", status: 500 },
|
|
250
|
-
{ code: "XX", status: 500 },
|
|
251
|
-
{ code: "42883", status: 404 },
|
|
252
|
-
{ code: "42P01", status: 404 },
|
|
253
|
-
{ code: "42P17", status: 500 },
|
|
254
|
-
{ code: "42501", status: 403 }
|
|
255
|
-
];
|
|
256
|
-
|
|
257
|
-
// Return custom error if Postgrest error code is found
|
|
258
|
-
for (const { code, status } of errorMap)
|
|
259
|
-
if (postgrestError.code === code || postgrestError.code.startsWith(code)) {
|
|
260
|
-
error = Utility.customError(status, Status.ERROR);
|
|
261
|
-
return { error };
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Return generic error
|
|
265
|
-
error = Utility.customError(500, Status.ERROR);
|
|
266
|
-
return { error };
|
|
267
|
-
},
|
|
268
|
-
/**
|
|
269
|
-
* A regular expression for user passwords to match during authentication. It covers the following rules:
|
|
270
|
-
* - at least 8 characters long
|
|
271
|
-
* - at least one digit
|
|
272
|
-
* - at least one lowercase letter
|
|
273
|
-
* - at least one uppercase letter
|
|
274
|
-
* - at least one symbol
|
|
275
|
-
*/
|
|
276
|
-
passwordRegex: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()-_+=|{}[\]:;'<>,.?/~]).{8,}$/,
|
|
277
|
-
/**
|
|
278
|
-
* A regular expression for Koloseum Player IDs. It covers the following rules:
|
|
279
|
-
* - 9 characters long
|
|
280
|
-
* - begins with "KP" followed by 7 digits
|
|
281
|
-
*/
|
|
282
|
-
playerIdRegex: /^KP\d{7}$/,
|
|
283
|
-
/**
|
|
284
|
-
* Sanitises any potential HTML injected into user input.
|
|
285
|
-
* @param {string} input - The input to be sanitised
|
|
286
|
-
* @returns A sanitised string, or an empty string if the input is invalid
|
|
287
|
-
*/
|
|
288
|
-
sanitiseHtml: (input: string) =>
|
|
289
|
-
typeof input !== "string" ? "" : sanitize(input, { allowedTags: [], allowedAttributes: {} }),
|
|
290
|
-
/**
|
|
291
|
-
* A regular expression for social media handles, without a leading slash or @ character.
|
|
292
|
-
*
|
|
293
|
-
* - Source: https://stackoverflow.com/a/74579651
|
|
294
|
-
*/
|
|
295
|
-
socialMediaHandleRegex: /^([A-Za-z0-9_.]{3,25})$/gm,
|
|
296
|
-
/**
|
|
297
|
-
* Validates an E.164 phone number.
|
|
298
|
-
* @param {string} phoneNumber - The phone number to be validated
|
|
299
|
-
* @param {"any" | MobilePhoneLocale | MobilePhoneLocale[]} [locale="any"] - The locale(s) to validate the phone number against
|
|
300
|
-
* @returns `true` if the phone number is valid; `false` otherwise
|
|
301
|
-
*/
|
|
302
|
-
validateE164: (phoneNumber: string, locale: "any" | MobilePhoneLocale | MobilePhoneLocale[] = "en-KE") =>
|
|
303
|
-
isMobilePhone(phoneNumber, locale) && Boolean(phoneNumber.match(Utility.e164Regex)),
|
|
304
|
-
/**
|
|
305
|
-
* Validates a social media handle.
|
|
306
|
-
* @param {string} handle - The social media handle to be validated
|
|
307
|
-
* @returns `true` if the handle is valid; `false` otherwise
|
|
308
|
-
*/
|
|
309
|
-
validateSocialMediaHandle: (handle: string) =>
|
|
310
|
-
!isURL(handle, { require_protocol: false }) &&
|
|
311
|
-
!handle.startsWith("@") &&
|
|
312
|
-
Boolean(handle.match(Utility.socialMediaHandleRegex))
|
|
313
|
-
};
|