@koloseum/utils 0.2.8 → 0.2.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 +35 -1
- package/dist/utils.js +173 -14
- package/package.json +4 -2
package/dist/utils.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Database } from "@koloseum/types/database";
|
|
2
2
|
import type { CustomError, Microservice, MicroserviceGroup, MicroserviceObject, UserWithCustomMetadata } from "@koloseum/types/general";
|
|
3
|
-
import type { IdentityType, PronounsCheckboxes, PronounsItem, Sex, SocialMediaPlatform } from "@koloseum/types/public-auth";
|
|
3
|
+
import type { BranchAddressObject, County, IdentityType, PronounsCheckboxes, PronounsItem, Sex, SocialMediaPlatform } from "@koloseum/types/public-auth";
|
|
4
4
|
import type { Page } from "@sveltejs/kit";
|
|
5
5
|
import type { SupabaseClient, FunctionInvokeOptions, PostgrestError } from "@supabase/supabase-js";
|
|
6
6
|
import type { IOptions } from "@suprsend/web-components/dist/types/interface.d.ts";
|
|
@@ -103,6 +103,12 @@ export declare const Utility: {
|
|
|
103
103
|
* @returns The formatted identity type string
|
|
104
104
|
*/
|
|
105
105
|
getIdentityTypeString: (identityType: IdentityType) => string;
|
|
106
|
+
/**
|
|
107
|
+
* Returns a list of all counties in Kenya.
|
|
108
|
+
* @param {"name" | "code"} sortBy - The field to sort the counties by, i.e. `name` or `code`; defaults to `name`
|
|
109
|
+
* @returns {Promise<County[]>} A list of objects with the county `name`, `code`, and a list of `subCounties`
|
|
110
|
+
*/
|
|
111
|
+
getKenyaCounties: (sortBy?: "name" | "code") => Promise<County[]>;
|
|
106
112
|
/**
|
|
107
113
|
* Returns the URL for a menu item based on the slug.
|
|
108
114
|
* @param {string} base - The base URL
|
|
@@ -183,12 +189,31 @@ export declare const Utility: {
|
|
|
183
189
|
* @returns `true` if the page is active, `false` otherwise.
|
|
184
190
|
*/
|
|
185
191
|
pageIsActive: (page: Page, slug: string | undefined, microservice?: Microservice<MicroserviceGroup>, base?: string) => boolean;
|
|
192
|
+
/**
|
|
193
|
+
* Paginates an array.
|
|
194
|
+
* @template T - The type of array items; defaults to `any`
|
|
195
|
+
* @param {T[]} array - The array to paginate
|
|
196
|
+
* @param {number} page - The page number
|
|
197
|
+
* @param {number} pageSize - The number of items per page; defaults to 10
|
|
198
|
+
* @returns {Object} The paginated array and total number of pages
|
|
199
|
+
*/
|
|
200
|
+
paginateArray: <T = any>(array: T[], page: number, pageSize?: number) => {
|
|
201
|
+
paginatedItems: T[];
|
|
202
|
+
totalPages: number;
|
|
203
|
+
};
|
|
186
204
|
/**
|
|
187
205
|
* Parses a `CustomError` object within a page/layout server load function and returns an error to be thrown.
|
|
188
206
|
* @param {CustomError} error - The error object
|
|
189
207
|
* @returns A new `Error` object if the error is unexpected, or a Svelte error otherwise
|
|
190
208
|
*/
|
|
191
209
|
parseLoadError: (error: CustomError) => Error | never;
|
|
210
|
+
/**
|
|
211
|
+
* Parses a Postgres interval string and returns a string in the specified format.
|
|
212
|
+
* @param {string} interval - The interval string to parse
|
|
213
|
+
* @param {"postgres" | "iso" | "iso-short"} type - The format to return the interval in; defaults to `postgres`
|
|
214
|
+
* @returns A string in the specified format
|
|
215
|
+
*/
|
|
216
|
+
parsePostgresInterval: (interval: string, type?: "postgres" | "iso" | "iso-short") => string | null;
|
|
192
217
|
/**
|
|
193
218
|
* Parses a `PostgrestError` object and returns a custom error object if any has occurred.
|
|
194
219
|
* @param {PostgrestError | null} postgrestError - The `PostgrestError` object, or `null` if no error occurred
|
|
@@ -224,6 +249,15 @@ export declare const Utility: {
|
|
|
224
249
|
* - Source: https://stackoverflow.com/a/74579651
|
|
225
250
|
*/
|
|
226
251
|
socialMediaHandleRegex: RegExp;
|
|
252
|
+
/**
|
|
253
|
+
* Validates address data submitted in a form and returns the validated data.
|
|
254
|
+
* @param {FormData} formData - The submitted form data
|
|
255
|
+
* @returns An object with the validated `address`, or an `error` if any has occurred
|
|
256
|
+
*/
|
|
257
|
+
validateAddress: (formData: FormData) => Promise<{
|
|
258
|
+
address?: BranchAddressObject;
|
|
259
|
+
error?: CustomError;
|
|
260
|
+
}>;
|
|
227
261
|
/**
|
|
228
262
|
* Validates an E.164 phone number.
|
|
229
263
|
* @param {string} phoneNumber - The phone number to be validated
|
package/dist/utils.js
CHANGED
|
@@ -2,10 +2,13 @@ import { v4 as uuidv4 } from "uuid";
|
|
|
2
2
|
import { error as svelteError } from "@sveltejs/kit";
|
|
3
3
|
import { FunctionsFetchError, FunctionsHttpError, FunctionsRelayError } from "@supabase/supabase-js";
|
|
4
4
|
import validator from "validator";
|
|
5
|
-
/*
|
|
5
|
+
/* HELPERS */
|
|
6
|
+
const parsePgInterval = (await import("postgres-interval")).default;
|
|
6
7
|
const sanitize = (await import("sanitize-html")).default;
|
|
7
|
-
const { isMobilePhone, isURL } = validator;
|
|
8
|
-
|
|
8
|
+
const { trim, escape, isMobilePhone, isURL } = validator;
|
|
9
|
+
const { KenyaAdministrativeDivisions } = (await import("kenya-administrative-divisions"));
|
|
10
|
+
const kenyaAdmin = new KenyaAdministrativeDivisions();
|
|
11
|
+
/* DUMMY DATA */
|
|
9
12
|
export const Data = {
|
|
10
13
|
/**
|
|
11
14
|
* A generic authenticated user.
|
|
@@ -68,7 +71,7 @@ export const Data = {
|
|
|
68
71
|
};
|
|
69
72
|
}
|
|
70
73
|
};
|
|
71
|
-
/*
|
|
74
|
+
/* STATUS MESSAGES */
|
|
72
75
|
export const Status = {
|
|
73
76
|
/**
|
|
74
77
|
* A generic error message.
|
|
@@ -83,7 +86,7 @@ export const Status = {
|
|
|
83
86
|
*/
|
|
84
87
|
PASSWORD_RESET_REQUESTED: "If the provided email address is registered, you will receive a password reset link shortly."
|
|
85
88
|
};
|
|
86
|
-
/*
|
|
89
|
+
/* UTILITY FUNCTIONS */
|
|
87
90
|
export const Utility = {
|
|
88
91
|
/**
|
|
89
92
|
* Calls a Supabase Edge function.
|
|
@@ -328,6 +331,32 @@ export const Utility = {
|
|
|
328
331
|
return "Driver's licence";
|
|
329
332
|
return "";
|
|
330
333
|
},
|
|
334
|
+
/**
|
|
335
|
+
* Returns a list of all counties in Kenya.
|
|
336
|
+
* @param {"name" | "code"} sortBy - The field to sort the counties by, i.e. `name` or `code`; defaults to `name`
|
|
337
|
+
* @returns {Promise<County[]>} A list of objects with the county `name`, `code`, and a list of `subCounties`
|
|
338
|
+
*/
|
|
339
|
+
getKenyaCounties: async (sortBy = "name") => {
|
|
340
|
+
// Get all counties
|
|
341
|
+
const countiesData = await kenyaAdmin.getAll();
|
|
342
|
+
// Format counties
|
|
343
|
+
const counties = countiesData.map((county) => {
|
|
344
|
+
// Get list of sub-counties
|
|
345
|
+
const subCounties = [];
|
|
346
|
+
for (const subCounty of county.constituencies)
|
|
347
|
+
subCounties.push(subCounty.constituency_name);
|
|
348
|
+
subCounties.sort();
|
|
349
|
+
// Format county name and add to list
|
|
350
|
+
const countyName = county.county_name.split(" ");
|
|
351
|
+
let name = county.county_name;
|
|
352
|
+
if (countyName.length > 1)
|
|
353
|
+
name = countyName.map((word) => Utility.capitalise(word)).join(" ");
|
|
354
|
+
// Return county
|
|
355
|
+
return { name, code: county.county_code, subCounties };
|
|
356
|
+
});
|
|
357
|
+
// Return sorted counties
|
|
358
|
+
return counties.sort((a, b) => (sortBy === "name" ? a.name.localeCompare(b.name) : a.code - b.code));
|
|
359
|
+
},
|
|
331
360
|
/**
|
|
332
361
|
* Returns the URL for a menu item based on the slug.
|
|
333
362
|
* @param {string} base - The base URL
|
|
@@ -981,7 +1010,7 @@ export const Utility = {
|
|
|
981
1010
|
{
|
|
982
1011
|
name: "Rankings",
|
|
983
1012
|
description: "Manage rankings for any Savanna Circuit season.",
|
|
984
|
-
|
|
1013
|
+
root: true
|
|
985
1014
|
},
|
|
986
1015
|
{
|
|
987
1016
|
name: "Tournaments",
|
|
@@ -989,20 +1018,15 @@ export const Utility = {
|
|
|
989
1018
|
slug: "tournaments",
|
|
990
1019
|
tabs: [
|
|
991
1020
|
{
|
|
992
|
-
name: "
|
|
993
|
-
description: "Manage
|
|
994
|
-
|
|
1021
|
+
name: "Events",
|
|
1022
|
+
description: "Manage events for any Savanna Circuit tournament.",
|
|
1023
|
+
root: true
|
|
995
1024
|
},
|
|
996
1025
|
{
|
|
997
1026
|
name: "Tickets",
|
|
998
1027
|
description: "Manage tickets for any Savanna Circuit tournament.",
|
|
999
1028
|
slug: "tickets"
|
|
1000
1029
|
},
|
|
1001
|
-
{
|
|
1002
|
-
name: "Events",
|
|
1003
|
-
description: "Manage events for any Savanna Circuit tournament.",
|
|
1004
|
-
slug: "events"
|
|
1005
|
-
},
|
|
1006
1030
|
{
|
|
1007
1031
|
name: "Settings",
|
|
1008
1032
|
description: "Manage settings for any Savanna Circuit tournament.",
|
|
@@ -1178,12 +1202,51 @@ export const Utility = {
|
|
|
1178
1202
|
const target = `${cleanBase}/${slug}`;
|
|
1179
1203
|
return page.url.pathname === target || page.url.pathname.startsWith(`${target}/`);
|
|
1180
1204
|
},
|
|
1205
|
+
/**
|
|
1206
|
+
* Paginates an array.
|
|
1207
|
+
* @template T - The type of array items; defaults to `any`
|
|
1208
|
+
* @param {T[]} array - The array to paginate
|
|
1209
|
+
* @param {number} page - The page number
|
|
1210
|
+
* @param {number} pageSize - The number of items per page; defaults to 10
|
|
1211
|
+
* @returns {Object} The paginated array and total number of pages
|
|
1212
|
+
*/
|
|
1213
|
+
paginateArray: (array, page, pageSize = 10) => {
|
|
1214
|
+
// Define variables
|
|
1215
|
+
const startIndex = (page - 1) * pageSize;
|
|
1216
|
+
const endIndex = startIndex + pageSize;
|
|
1217
|
+
const totalPages = Math.ceil(array.length / pageSize);
|
|
1218
|
+
// Paginate items
|
|
1219
|
+
const paginatedItems = array.slice(startIndex, endIndex);
|
|
1220
|
+
// Return data
|
|
1221
|
+
return { paginatedItems, totalPages };
|
|
1222
|
+
},
|
|
1181
1223
|
/**
|
|
1182
1224
|
* Parses a `CustomError` object within a page/layout server load function and returns an error to be thrown.
|
|
1183
1225
|
* @param {CustomError} error - The error object
|
|
1184
1226
|
* @returns A new `Error` object if the error is unexpected, or a Svelte error otherwise
|
|
1185
1227
|
*/
|
|
1186
1228
|
parseLoadError: (error) => error.code === 500 ? new Error(error.message) : svelteError(error.code, { message: error.message }),
|
|
1229
|
+
/**
|
|
1230
|
+
* Parses a Postgres interval string and returns a string in the specified format.
|
|
1231
|
+
* @param {string} interval - The interval string to parse
|
|
1232
|
+
* @param {"postgres" | "iso" | "iso-short"} type - The format to return the interval in; defaults to `postgres`
|
|
1233
|
+
* @returns A string in the specified format
|
|
1234
|
+
*/
|
|
1235
|
+
parsePostgresInterval: (interval, type = "postgres") => {
|
|
1236
|
+
// Return null if interval is falsy
|
|
1237
|
+
if (!interval)
|
|
1238
|
+
return null;
|
|
1239
|
+
// Parse interval and return in the specified format
|
|
1240
|
+
const parsed = parsePgInterval(interval);
|
|
1241
|
+
if (type === "postgres")
|
|
1242
|
+
return parsed.toPostgres();
|
|
1243
|
+
if (type === "iso")
|
|
1244
|
+
return parsed.toISOString();
|
|
1245
|
+
if (type === "iso-short")
|
|
1246
|
+
return parsed.toISOStringShort();
|
|
1247
|
+
// Return null if type is invalid
|
|
1248
|
+
return null;
|
|
1249
|
+
},
|
|
1187
1250
|
/**
|
|
1188
1251
|
* Parses a `PostgrestError` object and returns a custom error object if any has occurred.
|
|
1189
1252
|
* @param {PostgrestError | null} postgrestError - The `PostgrestError` object, or `null` if no error occurred
|
|
@@ -1277,6 +1340,102 @@ export const Utility = {
|
|
|
1277
1340
|
* - Source: https://stackoverflow.com/a/74579651
|
|
1278
1341
|
*/
|
|
1279
1342
|
socialMediaHandleRegex: /^([A-Za-z0-9_.]{3,25})$/gm,
|
|
1343
|
+
/**
|
|
1344
|
+
* Validates address data submitted in a form and returns the validated data.
|
|
1345
|
+
* @param {FormData} formData - The submitted form data
|
|
1346
|
+
* @returns An object with the validated `address`, or an `error` if any has occurred
|
|
1347
|
+
*/
|
|
1348
|
+
validateAddress: async (formData) => {
|
|
1349
|
+
// Building name
|
|
1350
|
+
let buildingName = formData.get("building-name");
|
|
1351
|
+
if (!buildingName)
|
|
1352
|
+
return { error: Utility.customError(400, "Building name is required.") };
|
|
1353
|
+
buildingName = Utility.sanitiseHtml(trim(escape(buildingName)));
|
|
1354
|
+
// Unit number
|
|
1355
|
+
let unitNumber = formData.get("unit-number") || undefined;
|
|
1356
|
+
if (unitNumber) {
|
|
1357
|
+
unitNumber = Utility.sanitiseHtml(trim(escape(unitNumber)));
|
|
1358
|
+
if (unitNumber === "")
|
|
1359
|
+
unitNumber = undefined;
|
|
1360
|
+
}
|
|
1361
|
+
// Street name
|
|
1362
|
+
let streetName = formData.get("street-name");
|
|
1363
|
+
if (!streetName)
|
|
1364
|
+
return { error: Utility.customError(400, "Street name is required.") };
|
|
1365
|
+
streetName = Utility.sanitiseHtml(trim(escape(streetName)));
|
|
1366
|
+
// P.O. box and postal code
|
|
1367
|
+
let boxNumber = formData.get("box-number") || undefined;
|
|
1368
|
+
let postalCode = formData.get("postal-code") || undefined;
|
|
1369
|
+
if (boxNumber) {
|
|
1370
|
+
boxNumber = Utility.sanitiseHtml(trim(escape(boxNumber)));
|
|
1371
|
+
if (boxNumber === "")
|
|
1372
|
+
boxNumber = undefined;
|
|
1373
|
+
if (boxNumber && !postalCode)
|
|
1374
|
+
return { error: Utility.customError(400, "Postal code is required.") };
|
|
1375
|
+
}
|
|
1376
|
+
if (postalCode) {
|
|
1377
|
+
postalCode = Utility.sanitiseHtml(trim(escape(postalCode)));
|
|
1378
|
+
if (postalCode === "")
|
|
1379
|
+
postalCode = undefined;
|
|
1380
|
+
if (postalCode && !boxNumber)
|
|
1381
|
+
return { error: Utility.customError(400, "P.O. box is required.") };
|
|
1382
|
+
}
|
|
1383
|
+
// Town
|
|
1384
|
+
let town = formData.get("town");
|
|
1385
|
+
if (!town)
|
|
1386
|
+
return { error: Utility.customError(400, "City/town is required.") };
|
|
1387
|
+
town = Utility.sanitiseHtml(trim(escape(town)));
|
|
1388
|
+
// County
|
|
1389
|
+
let county = undefined;
|
|
1390
|
+
const countyCode = Number(formData.get("county-code"));
|
|
1391
|
+
if (isNaN(countyCode))
|
|
1392
|
+
return { error: Utility.customError(400, "County is invalid.") };
|
|
1393
|
+
if (!countyCode)
|
|
1394
|
+
return { error: Utility.customError(400, "County is required.") };
|
|
1395
|
+
const counties = await kenyaAdmin.getAll();
|
|
1396
|
+
if (!counties || counties.length === 0)
|
|
1397
|
+
return { error: Utility.customError(400, "County is invalid.") };
|
|
1398
|
+
const countyData = counties.find(({ county_code }) => county_code === countyCode);
|
|
1399
|
+
if (!countyData)
|
|
1400
|
+
return { error: Utility.customError(400, "County is invalid.") };
|
|
1401
|
+
const { county_name, constituencies } = countyData;
|
|
1402
|
+
county = county_name;
|
|
1403
|
+
// Sub-county
|
|
1404
|
+
let subCounty = formData.get("sub-county") || undefined;
|
|
1405
|
+
if (subCounty) {
|
|
1406
|
+
subCounty = Utility.sanitiseHtml(trim(escape(subCounty)));
|
|
1407
|
+
if (subCounty === "")
|
|
1408
|
+
subCounty = undefined;
|
|
1409
|
+
else {
|
|
1410
|
+
// Check if constituencies exists and is an array before mapping
|
|
1411
|
+
if (!constituencies || !Array.isArray(constituencies))
|
|
1412
|
+
return { error: Utility.customError(400, "Sub-county data is unavailable.") };
|
|
1413
|
+
// Get list of sub-counties
|
|
1414
|
+
const subCounties = constituencies.map(({ constituency_name: name }) => name);
|
|
1415
|
+
if (!subCounties.includes(subCounty))
|
|
1416
|
+
return { error: Utility.customError(400, "Sub-county is invalid.") };
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
// Additional information
|
|
1420
|
+
let additionalInfo = formData.get("address-additional-info") || undefined;
|
|
1421
|
+
if (additionalInfo) {
|
|
1422
|
+
additionalInfo = Utility.sanitiseHtml(trim(escape(additionalInfo)));
|
|
1423
|
+
if (additionalInfo === "")
|
|
1424
|
+
additionalInfo = undefined;
|
|
1425
|
+
}
|
|
1426
|
+
// Create address object
|
|
1427
|
+
const address = {
|
|
1428
|
+
buildingName,
|
|
1429
|
+
unitNumber,
|
|
1430
|
+
streetName,
|
|
1431
|
+
town,
|
|
1432
|
+
subCounty,
|
|
1433
|
+
county,
|
|
1434
|
+
additionalInfo
|
|
1435
|
+
};
|
|
1436
|
+
// Return data
|
|
1437
|
+
return { address };
|
|
1438
|
+
},
|
|
1280
1439
|
/**
|
|
1281
1440
|
* Validates an E.164 phone number.
|
|
1282
1441
|
* @param {string} phoneNumber - The phone number to be validated
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koloseum/utils",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"author": "Koloseum Technologies Limited",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Utility logic for use across Koloseum web apps (TypeScript)",
|
|
@@ -31,17 +31,19 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@supabase/supabase-js": "^2.50.1",
|
|
33
33
|
"@sveltejs/kit": "^2.22.0",
|
|
34
|
+
"kenya-administrative-divisions": "^0.0.18",
|
|
34
35
|
"sanitize-html": "^2.17.0",
|
|
35
36
|
"uuid": "^11.1.0",
|
|
36
37
|
"validator": "^13.15.15"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"@koloseum/types": "^0.2.
|
|
40
|
+
"@koloseum/types": "^0.2.4",
|
|
40
41
|
"@playwright/test": "^1.53.1",
|
|
41
42
|
"@suprsend/web-components": "^0.2.1",
|
|
42
43
|
"@types/sanitize-html": "^2.16.0",
|
|
43
44
|
"@types/uuid": "^10.0.0",
|
|
44
45
|
"@types/validator": "^13.15.2",
|
|
46
|
+
"postgres-interval": "^4.0.2",
|
|
45
47
|
"prettier": "^3.6.0",
|
|
46
48
|
"typescript": "^5.8.3",
|
|
47
49
|
"vitest": "^3.2.4"
|