@koloseum/utils 0.2.15 → 0.2.17
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 +50 -0
- package/dist/utils.js +142 -37
- package/package.json +2 -2
package/dist/utils.d.ts
CHANGED
|
@@ -250,6 +250,16 @@ export declare const Utility: {
|
|
|
250
250
|
* @returns A sanitised string, or an empty string if the input is invalid
|
|
251
251
|
*/
|
|
252
252
|
sanitiseHtml: (input: string) => string;
|
|
253
|
+
/**
|
|
254
|
+
* Sends a welcome notification to a user.
|
|
255
|
+
* @param {SupabaseClient<Database>} supabase - The Supabase client
|
|
256
|
+
* @param {UserWithCustomMetadata} user - The user to send the notification to
|
|
257
|
+
* @param {MicroserviceGroup} microserviceGroup - The microservice group the user belongs to
|
|
258
|
+
* @returns An object with an `error` if any has occurred
|
|
259
|
+
*/
|
|
260
|
+
sendWelcomeNotification: (supabase: SupabaseClient<Database>, user: UserWithCustomMetadata, microserviceGroup: MicroserviceGroup) => Promise<{
|
|
261
|
+
error?: CustomError;
|
|
262
|
+
}>;
|
|
253
263
|
/**
|
|
254
264
|
* A regular expression for social media handles, without a leading slash or @ character.
|
|
255
265
|
*
|
|
@@ -279,3 +289,43 @@ export declare const Utility: {
|
|
|
279
289
|
*/
|
|
280
290
|
validateSocialMediaHandle: (handle: string) => boolean;
|
|
281
291
|
};
|
|
292
|
+
export declare const Cache: {
|
|
293
|
+
/**
|
|
294
|
+
* Delete cached data from Valkey.
|
|
295
|
+
* @param valkey - The Valkey client instance
|
|
296
|
+
* @param cachePrefix - The cache prefix
|
|
297
|
+
* @param key - The cache key
|
|
298
|
+
*/
|
|
299
|
+
deleteData: (valkey: any, cachePrefix: string, key: string) => Promise<void>;
|
|
300
|
+
/**
|
|
301
|
+
* Generate cache key with consistent formatting
|
|
302
|
+
* @param prefix - The key prefix
|
|
303
|
+
* @param params - Additional parameters to include in the key
|
|
304
|
+
* @returns The formatted cache key
|
|
305
|
+
*/
|
|
306
|
+
generateDataKey: (prefix: string, ...params: (string | number)[]) => string;
|
|
307
|
+
/**
|
|
308
|
+
* Get cached data from Valkey.
|
|
309
|
+
* @param valkey - The Valkey client instance
|
|
310
|
+
* @param cachePrefix - The cache prefix
|
|
311
|
+
* @param key - The cache key
|
|
312
|
+
* @returns The cached data, or `null` if not found
|
|
313
|
+
*/
|
|
314
|
+
getData: <T>(valkey: any, cachePrefix: string, key: string) => Promise<T | null>;
|
|
315
|
+
/**
|
|
316
|
+
* Invalidate cache by pattern
|
|
317
|
+
* @param valkey - The Valkey client instance
|
|
318
|
+
* @param cachePrefix - The cache prefix
|
|
319
|
+
* @param pattern - The pattern to match keys against (e.g., "app-config*")
|
|
320
|
+
*/
|
|
321
|
+
invalidateDataByPattern: (valkey: any, cachePrefix: string, pattern: string) => Promise<void>;
|
|
322
|
+
/**
|
|
323
|
+
* Set data in Valkey cache.
|
|
324
|
+
* @param valkey - The Valkey client instance
|
|
325
|
+
* @param cachePrefix - The cache prefix
|
|
326
|
+
* @param key - The cache key
|
|
327
|
+
* @param data - The data to cache
|
|
328
|
+
* @param ttl - The time to live in seconds; defaults to 1 hour
|
|
329
|
+
*/
|
|
330
|
+
setData: <T>(valkey: any, cachePrefix: string, key: string, data: T, ttl?: number) => Promise<void>;
|
|
331
|
+
};
|
package/dist/utils.js
CHANGED
|
@@ -6,6 +6,8 @@ import validator from "validator";
|
|
|
6
6
|
const parsePgInterval = (await import("postgres-interval")).default;
|
|
7
7
|
const sanitize = (await import("sanitize-html")).default;
|
|
8
8
|
const { trim, escape, isMobilePhone, isURL } = validator;
|
|
9
|
+
// Cache constants
|
|
10
|
+
const DEFAULT_TTL = 3600; // 1 hour in seconds
|
|
9
11
|
const { KenyaAdministrativeDivisions } = (await import("kenya-administrative-divisions")).default;
|
|
10
12
|
/* DUMMY DATA */
|
|
11
13
|
export const Data = {
|
|
@@ -703,6 +705,9 @@ export const Utility = {
|
|
|
703
705
|
// Return true if microservice group is public
|
|
704
706
|
if (microserviceGroup === "public")
|
|
705
707
|
return { isAuthorised: true };
|
|
708
|
+
// Validate user metadata and app metadata
|
|
709
|
+
if (!user.user_metadata || !user.app_metadata)
|
|
710
|
+
return { error: Utility.customError(400, "User metadata is required.") };
|
|
706
711
|
// Get user's roles and the role prefix
|
|
707
712
|
const roles = [];
|
|
708
713
|
const rolePrefix = microserviceGroup === "backroom" ? "backroom" : microserviceGroup.slice(0, -1);
|
|
@@ -748,43 +753,9 @@ export const Utility = {
|
|
|
748
753
|
if (validationError)
|
|
749
754
|
return { error: Utility.customError(code, validationError) };
|
|
750
755
|
// Send welcome notification if needed
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
// Update user metadata in database
|
|
755
|
-
const { error: updateError } = await supabase.auth.updateUser({
|
|
756
|
-
data: { backroom: { welcome_notification_sent: false } }
|
|
757
|
-
});
|
|
758
|
-
if (updateError)
|
|
759
|
-
return { error: Utility.customError(updateError.status ?? 500, updateError.message) };
|
|
760
|
-
// Update user object in memory
|
|
761
|
-
user.user_metadata.backroom = { welcome_notification_sent: false };
|
|
762
|
-
}
|
|
763
|
-
// Define SuprSend workflow body
|
|
764
|
-
const workflowBody = {
|
|
765
|
-
name: "welcome-to-backroom",
|
|
766
|
-
template: "welcome-to-backroom",
|
|
767
|
-
notification_category: "system",
|
|
768
|
-
users: [
|
|
769
|
-
{
|
|
770
|
-
distinct_id: user.id,
|
|
771
|
-
$skip_create: true
|
|
772
|
-
}
|
|
773
|
-
]
|
|
774
|
-
};
|
|
775
|
-
// Send welcome notification
|
|
776
|
-
const { code, error: workflowError } = await Utility.callEdgeFunction(false, supabase, `suprsend/trigger-workflow?user-id=${user.id}`, { method: "POST", body: workflowBody });
|
|
777
|
-
if (workflowError)
|
|
778
|
-
return { error: Utility.customError(code, workflowError) };
|
|
779
|
-
// Update user metadata in database
|
|
780
|
-
const { error: updateError } = await supabase.auth.updateUser({
|
|
781
|
-
data: { backroom: { welcome_notification_sent: true } }
|
|
782
|
-
});
|
|
783
|
-
if (updateError)
|
|
784
|
-
return { error: Utility.customError(updateError.status ?? 500, updateError.message) };
|
|
785
|
-
// Update user object in memory
|
|
786
|
-
user.user_metadata.backroom.welcome_notification_sent = true;
|
|
787
|
-
}
|
|
756
|
+
const { error: welcomeError } = await Utility.sendWelcomeNotification(supabase, user, microserviceGroup);
|
|
757
|
+
if (welcomeError)
|
|
758
|
+
return { error: welcomeError };
|
|
788
759
|
// Return result
|
|
789
760
|
return { isAuthorised };
|
|
790
761
|
},
|
|
@@ -1357,6 +1328,67 @@ export const Utility = {
|
|
|
1357
1328
|
* @returns A sanitised string, or an empty string if the input is invalid
|
|
1358
1329
|
*/
|
|
1359
1330
|
sanitiseHtml: (input) => typeof input !== "string" ? "" : sanitize(input, { allowedTags: [], allowedAttributes: {} }),
|
|
1331
|
+
/**
|
|
1332
|
+
* Sends a welcome notification to a user.
|
|
1333
|
+
* @param {SupabaseClient<Database>} supabase - The Supabase client
|
|
1334
|
+
* @param {UserWithCustomMetadata} user - The user to send the notification to
|
|
1335
|
+
* @param {MicroserviceGroup} microserviceGroup - The microservice group the user belongs to
|
|
1336
|
+
* @returns An object with an `error` if any has occurred
|
|
1337
|
+
*/
|
|
1338
|
+
sendWelcomeNotification: async (supabase, user, microserviceGroup) => {
|
|
1339
|
+
// Backroom
|
|
1340
|
+
if (microserviceGroup === "backroom" && !user.user_metadata.backroom?.welcome_notification_sent) {
|
|
1341
|
+
// Use atomic update with timestamp to prevent race conditions
|
|
1342
|
+
const currentTime = new Date().toISOString();
|
|
1343
|
+
const { data: updatedUser, error: updateError } = await supabase.auth.updateUser({
|
|
1344
|
+
data: {
|
|
1345
|
+
backroom: {
|
|
1346
|
+
...user.user_metadata.backroom,
|
|
1347
|
+
welcome_notification_sent: true,
|
|
1348
|
+
welcome_notification_timestamp: currentTime
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
});
|
|
1352
|
+
if (updateError)
|
|
1353
|
+
return { error: Utility.customError(updateError.status ?? 500, updateError.message) };
|
|
1354
|
+
// Only send notification if flag was successfully updated
|
|
1355
|
+
const updatedBackroom = updatedUser.user?.user_metadata?.backroom;
|
|
1356
|
+
if (updatedBackroom?.welcome_notification_sent === true &&
|
|
1357
|
+
updatedBackroom?.welcome_notification_timestamp === currentTime) {
|
|
1358
|
+
// Define SuprSend workflow body
|
|
1359
|
+
const workflowBody = {
|
|
1360
|
+
name: "welcome-to-backroom",
|
|
1361
|
+
template: "welcome-to-backroom",
|
|
1362
|
+
notification_category: "system",
|
|
1363
|
+
users: [
|
|
1364
|
+
{
|
|
1365
|
+
distinct_id: user.id,
|
|
1366
|
+
$skip_create: true
|
|
1367
|
+
}
|
|
1368
|
+
]
|
|
1369
|
+
};
|
|
1370
|
+
// Send welcome notification
|
|
1371
|
+
const { code, error: workflowError } = await Utility.callEdgeFunction(false, supabase, `suprsend/trigger-workflow?user-id=${user.id}`, { method: "POST", body: workflowBody });
|
|
1372
|
+
// If notification fails, revert the flag and return error
|
|
1373
|
+
if (workflowError) {
|
|
1374
|
+
await supabase.auth.updateUser({
|
|
1375
|
+
data: {
|
|
1376
|
+
backroom: {
|
|
1377
|
+
...user.user_metadata.backroom,
|
|
1378
|
+
welcome_notification_sent: false,
|
|
1379
|
+
welcome_notification_timestamp: undefined
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
});
|
|
1383
|
+
return { error: Utility.customError(code, workflowError) };
|
|
1384
|
+
}
|
|
1385
|
+
// Update user object in memory
|
|
1386
|
+
user.user_metadata.backroom = updatedBackroom;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
// Return empty object
|
|
1390
|
+
return {};
|
|
1391
|
+
},
|
|
1360
1392
|
/**
|
|
1361
1393
|
* A regular expression for social media handles, without a leading slash or @ character.
|
|
1362
1394
|
*
|
|
@@ -1477,3 +1509,76 @@ export const Utility = {
|
|
|
1477
1509
|
!handle.startsWith("@") &&
|
|
1478
1510
|
Boolean(handle.match(Utility.socialMediaHandleRegex))
|
|
1479
1511
|
};
|
|
1512
|
+
/* CACHE FUNCTIONS */
|
|
1513
|
+
export const Cache = {
|
|
1514
|
+
/**
|
|
1515
|
+
* Delete cached data from Valkey.
|
|
1516
|
+
* @param valkey - The Valkey client instance
|
|
1517
|
+
* @param cachePrefix - The cache prefix
|
|
1518
|
+
* @param key - The cache key
|
|
1519
|
+
*/
|
|
1520
|
+
deleteData: async (valkey, cachePrefix, key) => {
|
|
1521
|
+
try {
|
|
1522
|
+
await valkey.del(`${cachePrefix}${key}`);
|
|
1523
|
+
}
|
|
1524
|
+
catch (error) {
|
|
1525
|
+
console.error("Cache delete error:", error);
|
|
1526
|
+
}
|
|
1527
|
+
},
|
|
1528
|
+
/**
|
|
1529
|
+
* Generate cache key with consistent formatting
|
|
1530
|
+
* @param prefix - The key prefix
|
|
1531
|
+
* @param params - Additional parameters to include in the key
|
|
1532
|
+
* @returns The formatted cache key
|
|
1533
|
+
*/
|
|
1534
|
+
generateDataKey: (prefix, ...params) => `${prefix}:${params.join(":")}`,
|
|
1535
|
+
/**
|
|
1536
|
+
* Get cached data from Valkey.
|
|
1537
|
+
* @param valkey - The Valkey client instance
|
|
1538
|
+
* @param cachePrefix - The cache prefix
|
|
1539
|
+
* @param key - The cache key
|
|
1540
|
+
* @returns The cached data, or `null` if not found
|
|
1541
|
+
*/
|
|
1542
|
+
getData: async (valkey, cachePrefix, key) => {
|
|
1543
|
+
try {
|
|
1544
|
+
const cached = await valkey.get(`${cachePrefix}${key}`);
|
|
1545
|
+
return cached ? JSON.parse(cached) : null;
|
|
1546
|
+
}
|
|
1547
|
+
catch (error) {
|
|
1548
|
+
console.error("Cache get error:", error);
|
|
1549
|
+
return null;
|
|
1550
|
+
}
|
|
1551
|
+
},
|
|
1552
|
+
/**
|
|
1553
|
+
* Invalidate cache by pattern
|
|
1554
|
+
* @param valkey - The Valkey client instance
|
|
1555
|
+
* @param cachePrefix - The cache prefix
|
|
1556
|
+
* @param pattern - The pattern to match keys against (e.g., "app-config*")
|
|
1557
|
+
*/
|
|
1558
|
+
invalidateDataByPattern: async (valkey, cachePrefix, pattern) => {
|
|
1559
|
+
try {
|
|
1560
|
+
const keys = await valkey.keys(`${cachePrefix}${pattern}`);
|
|
1561
|
+
if (keys.length > 0)
|
|
1562
|
+
await valkey.del(...keys);
|
|
1563
|
+
}
|
|
1564
|
+
catch (error) {
|
|
1565
|
+
console.error("Cache pattern invalidation error:", error);
|
|
1566
|
+
}
|
|
1567
|
+
},
|
|
1568
|
+
/**
|
|
1569
|
+
* Set data in Valkey cache.
|
|
1570
|
+
* @param valkey - The Valkey client instance
|
|
1571
|
+
* @param cachePrefix - The cache prefix
|
|
1572
|
+
* @param key - The cache key
|
|
1573
|
+
* @param data - The data to cache
|
|
1574
|
+
* @param ttl - The time to live in seconds; defaults to 1 hour
|
|
1575
|
+
*/
|
|
1576
|
+
setData: async (valkey, cachePrefix, key, data, ttl = DEFAULT_TTL) => {
|
|
1577
|
+
try {
|
|
1578
|
+
await valkey.setex(`${cachePrefix}${key}`, ttl, JSON.stringify(data));
|
|
1579
|
+
}
|
|
1580
|
+
catch (error) {
|
|
1581
|
+
console.error("Cache set error:", error);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koloseum/utils",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
4
4
|
"author": "Koloseum Technologies Limited",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Utility logic for use across Koloseum web apps (TypeScript)",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"validator": "^13.15.15"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@koloseum/types": "^0.2.
|
|
41
|
+
"@koloseum/types": "^0.2.5",
|
|
42
42
|
"@playwright/test": "^1.53.1",
|
|
43
43
|
"@suprsend/web-components": "^0.2.1",
|
|
44
44
|
"@types/sanitize-html": "^2.16.0",
|