@nextblock-cms/db 0.22.0 → 0.22.1
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/README.md +47 -47
- package/lib/supabase/server.d.ts +151 -2
- package/lib/supabase/types.d.ts +160 -2
- package/package.json +1 -1
- package/supabase/functions/nextblock-migration-ingest/index.ts +64 -1
- package/supabase/migrations/02000_catchup_gen1.sql +5880 -5880
- package/supabase/migrations/02001_baseline_schema.sql +2734 -2734
- package/supabase/migrations/02002_baseline_constraints_and_indexes.sql +1262 -1262
- package/supabase/migrations/02003_baseline_security_and_grants.sql +1577 -1577
- package/supabase/migrations/02004_baseline_seed.sql +565 -565
- package/supabase/migrations/02005_home_live_demo_promo_cadence_15min.sql +53 -53
- package/supabase/migrations/02006_home_marketing_contrast.sql +56 -56
- package/supabase/migrations/02007_home_pillar_caption_contrast.sql +44 -44
- package/supabase/migrations/02008_home_fr_parity.sql +89 -89
- package/supabase/migrations/02009_fr_cortex_posts.sql +67 -67
- package/supabase/migrations/02010_shop_hero_redesign.sql +72 -72
- package/supabase/migrations/02011_drop_backdrop_blur_on_pages.sql +26 -26
- package/supabase/migrations/02012_drop_variant_backdrop_blur.sql +17 -17
- package/supabase/migrations/02013_advisor_rls_and_trigger_grants.sql +102 -102
- package/supabase/migrations/02014_package_activations_staff_read.sql +18 -18
- package/supabase/migrations/02018_install_paths_and_seed_copy_accuracy.sql +1820 -1820
- package/supabase/migrations/02019_open_handed_licensing_and_mcp_clients.sql +448 -448
- package/supabase/migrations/02020_commerce_product_accuracy.sql +93 -93
- package/supabase/migrations/02021_profiles_staff_read_and_role_guard.sql +362 -362
- package/supabase/migrations/02022_security_hardening_writer_boundary_and_aal.sql +504 -0
- package/supabase/migrations/02023_aal2_for_orders_profiles_site_settings.sql +171 -0
package/lib/supabase/types.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export type Json = string | number | boolean | null | {
|
|
|
3
3
|
} | Json[];
|
|
4
4
|
export type Database = {
|
|
5
5
|
__InternalSupabase: {
|
|
6
|
-
PostgrestVersion: "14.
|
|
6
|
+
PostgrestVersion: "14.1";
|
|
7
7
|
};
|
|
8
8
|
public: {
|
|
9
9
|
Tables: {
|
|
@@ -119,6 +119,11 @@ export type Database = {
|
|
|
119
119
|
type: Database["public"]["Enums"]["interaction_type"];
|
|
120
120
|
updated_at: string;
|
|
121
121
|
user_id: string;
|
|
122
|
+
author: {
|
|
123
|
+
avatar_url: string | null;
|
|
124
|
+
full_name: string | null;
|
|
125
|
+
id: string | null;
|
|
126
|
+
} | null;
|
|
122
127
|
};
|
|
123
128
|
Insert: {
|
|
124
129
|
content: string;
|
|
@@ -170,6 +175,13 @@ export type Database = {
|
|
|
170
175
|
referencedRelation: "products";
|
|
171
176
|
referencedColumns: ["id"];
|
|
172
177
|
},
|
|
178
|
+
{
|
|
179
|
+
foreignKeyName: "cms_interactions_user_id_profiles_fkey";
|
|
180
|
+
columns: ["user_id"];
|
|
181
|
+
isOneToOne: false;
|
|
182
|
+
referencedRelation: "profile_cards";
|
|
183
|
+
referencedColumns: ["id"];
|
|
184
|
+
},
|
|
173
185
|
{
|
|
174
186
|
foreignKeyName: "cms_interactions_user_id_profiles_fkey";
|
|
175
187
|
columns: ["user_id"];
|
|
@@ -244,6 +256,13 @@ export type Database = {
|
|
|
244
256
|
updated_at?: string;
|
|
245
257
|
};
|
|
246
258
|
Relationships: [
|
|
259
|
+
{
|
|
260
|
+
foreignKeyName: "content_drafts_author_id_fkey";
|
|
261
|
+
columns: ["author_id"];
|
|
262
|
+
isOneToOne: false;
|
|
263
|
+
referencedRelation: "profile_cards";
|
|
264
|
+
referencedColumns: ["id"];
|
|
265
|
+
},
|
|
247
266
|
{
|
|
248
267
|
foreignKeyName: "content_drafts_author_id_fkey";
|
|
249
268
|
columns: ["author_id"];
|
|
@@ -297,6 +316,13 @@ export type Database = {
|
|
|
297
316
|
tool_name?: string;
|
|
298
317
|
};
|
|
299
318
|
Relationships: [
|
|
319
|
+
{
|
|
320
|
+
foreignKeyName: "cortex_ai_db_mutation_audit_actor_user_id_fkey";
|
|
321
|
+
columns: ["actor_user_id"];
|
|
322
|
+
isOneToOne: false;
|
|
323
|
+
referencedRelation: "profile_cards";
|
|
324
|
+
referencedColumns: ["id"];
|
|
325
|
+
},
|
|
300
326
|
{
|
|
301
327
|
foreignKeyName: "cortex_ai_db_mutation_audit_actor_user_id_fkey";
|
|
302
328
|
columns: ["actor_user_id"];
|
|
@@ -306,6 +332,27 @@ export type Database = {
|
|
|
306
332
|
}
|
|
307
333
|
];
|
|
308
334
|
};
|
|
335
|
+
cortex_request_log: {
|
|
336
|
+
Row: {
|
|
337
|
+
created_at: string;
|
|
338
|
+
id: string;
|
|
339
|
+
route: string;
|
|
340
|
+
user_id: string;
|
|
341
|
+
};
|
|
342
|
+
Insert: {
|
|
343
|
+
created_at?: string;
|
|
344
|
+
id?: string;
|
|
345
|
+
route: string;
|
|
346
|
+
user_id: string;
|
|
347
|
+
};
|
|
348
|
+
Update: {
|
|
349
|
+
created_at?: string;
|
|
350
|
+
id?: string;
|
|
351
|
+
route?: string;
|
|
352
|
+
user_id?: string;
|
|
353
|
+
};
|
|
354
|
+
Relationships: [];
|
|
355
|
+
};
|
|
309
356
|
coupon_freemius_mappings: {
|
|
310
357
|
Row: {
|
|
311
358
|
coupon_id: string;
|
|
@@ -596,6 +643,7 @@ export type Database = {
|
|
|
596
643
|
};
|
|
597
644
|
email_2fa_challenges: {
|
|
598
645
|
Row: {
|
|
646
|
+
attempts: number;
|
|
599
647
|
consumed_at: string | null;
|
|
600
648
|
created_at: string;
|
|
601
649
|
expires_at: string;
|
|
@@ -604,6 +652,7 @@ export type Database = {
|
|
|
604
652
|
user_id: string;
|
|
605
653
|
};
|
|
606
654
|
Insert: {
|
|
655
|
+
attempts?: number;
|
|
607
656
|
consumed_at?: string | null;
|
|
608
657
|
created_at?: string;
|
|
609
658
|
expires_at: string;
|
|
@@ -612,6 +661,7 @@ export type Database = {
|
|
|
612
661
|
user_id: string;
|
|
613
662
|
};
|
|
614
663
|
Update: {
|
|
664
|
+
attempts?: number;
|
|
615
665
|
consumed_at?: string | null;
|
|
616
666
|
created_at?: string;
|
|
617
667
|
expires_at?: string;
|
|
@@ -908,6 +958,13 @@ export type Database = {
|
|
|
908
958
|
width?: number | null;
|
|
909
959
|
};
|
|
910
960
|
Relationships: [
|
|
961
|
+
{
|
|
962
|
+
foreignKeyName: "media_uploader_id_fkey";
|
|
963
|
+
columns: ["uploader_id"];
|
|
964
|
+
isOneToOne: false;
|
|
965
|
+
referencedRelation: "profile_cards";
|
|
966
|
+
referencedColumns: ["id"];
|
|
967
|
+
},
|
|
911
968
|
{
|
|
912
969
|
foreignKeyName: "media_uploader_id_fkey";
|
|
913
970
|
columns: ["uploader_id"];
|
|
@@ -1274,6 +1331,13 @@ export type Database = {
|
|
|
1274
1331
|
version?: number;
|
|
1275
1332
|
};
|
|
1276
1333
|
Relationships: [
|
|
1334
|
+
{
|
|
1335
|
+
foreignKeyName: "page_revisions_author_id_fkey";
|
|
1336
|
+
columns: ["author_id"];
|
|
1337
|
+
isOneToOne: false;
|
|
1338
|
+
referencedRelation: "profile_cards";
|
|
1339
|
+
referencedColumns: ["id"];
|
|
1340
|
+
},
|
|
1277
1341
|
{
|
|
1278
1342
|
foreignKeyName: "page_revisions_author_id_fkey";
|
|
1279
1343
|
columns: ["author_id"];
|
|
@@ -1343,6 +1407,13 @@ export type Database = {
|
|
|
1343
1407
|
version?: number;
|
|
1344
1408
|
};
|
|
1345
1409
|
Relationships: [
|
|
1410
|
+
{
|
|
1411
|
+
foreignKeyName: "pages_author_id_fkey";
|
|
1412
|
+
columns: ["author_id"];
|
|
1413
|
+
isOneToOne: false;
|
|
1414
|
+
referencedRelation: "profile_cards";
|
|
1415
|
+
referencedColumns: ["id"];
|
|
1416
|
+
},
|
|
1346
1417
|
{
|
|
1347
1418
|
foreignKeyName: "pages_author_id_fkey";
|
|
1348
1419
|
columns: ["author_id"];
|
|
@@ -1395,6 +1466,13 @@ export type Database = {
|
|
|
1395
1466
|
version?: number;
|
|
1396
1467
|
};
|
|
1397
1468
|
Relationships: [
|
|
1469
|
+
{
|
|
1470
|
+
foreignKeyName: "post_revisions_author_id_fkey";
|
|
1471
|
+
columns: ["author_id"];
|
|
1472
|
+
isOneToOne: false;
|
|
1473
|
+
referencedRelation: "profile_cards";
|
|
1474
|
+
referencedColumns: ["id"];
|
|
1475
|
+
},
|
|
1398
1476
|
{
|
|
1399
1477
|
foreignKeyName: "post_revisions_author_id_fkey";
|
|
1400
1478
|
columns: ["author_id"];
|
|
@@ -1473,6 +1551,13 @@ export type Database = {
|
|
|
1473
1551
|
version?: number;
|
|
1474
1552
|
};
|
|
1475
1553
|
Relationships: [
|
|
1554
|
+
{
|
|
1555
|
+
foreignKeyName: "posts_author_id_fkey";
|
|
1556
|
+
columns: ["author_id"];
|
|
1557
|
+
isOneToOne: false;
|
|
1558
|
+
referencedRelation: "profile_cards";
|
|
1559
|
+
referencedColumns: ["id"];
|
|
1560
|
+
},
|
|
1476
1561
|
{
|
|
1477
1562
|
foreignKeyName: "posts_author_id_fkey";
|
|
1478
1563
|
columns: ["author_id"];
|
|
@@ -1650,6 +1735,13 @@ export type Database = {
|
|
|
1650
1735
|
updated_at?: string;
|
|
1651
1736
|
};
|
|
1652
1737
|
Relationships: [
|
|
1738
|
+
{
|
|
1739
|
+
foreignKeyName: "product_drafts_author_id_fkey";
|
|
1740
|
+
columns: ["author_id"];
|
|
1741
|
+
isOneToOne: false;
|
|
1742
|
+
referencedRelation: "profile_cards";
|
|
1743
|
+
referencedColumns: ["id"];
|
|
1744
|
+
},
|
|
1653
1745
|
{
|
|
1654
1746
|
foreignKeyName: "product_drafts_author_id_fkey";
|
|
1655
1747
|
columns: ["author_id"];
|
|
@@ -1841,6 +1933,13 @@ export type Database = {
|
|
|
1841
1933
|
version?: number;
|
|
1842
1934
|
};
|
|
1843
1935
|
Relationships: [
|
|
1936
|
+
{
|
|
1937
|
+
foreignKeyName: "product_revisions_author_id_fkey";
|
|
1938
|
+
columns: ["author_id"];
|
|
1939
|
+
isOneToOne: false;
|
|
1940
|
+
referencedRelation: "profile_cards";
|
|
1941
|
+
referencedColumns: ["id"];
|
|
1942
|
+
},
|
|
1844
1943
|
{
|
|
1845
1944
|
foreignKeyName: "product_revisions_author_id_fkey";
|
|
1846
1945
|
columns: ["author_id"];
|
|
@@ -2509,6 +2608,7 @@ export type Database = {
|
|
|
2509
2608
|
};
|
|
2510
2609
|
ucp_cart_sessions: {
|
|
2511
2610
|
Row: {
|
|
2611
|
+
access_token_hash: string | null;
|
|
2512
2612
|
attribution: Json;
|
|
2513
2613
|
buyer_identity: Json;
|
|
2514
2614
|
checkout_url: string | null;
|
|
@@ -2526,6 +2626,7 @@ export type Database = {
|
|
|
2526
2626
|
updated_at: string;
|
|
2527
2627
|
};
|
|
2528
2628
|
Insert: {
|
|
2629
|
+
access_token_hash?: string | null;
|
|
2529
2630
|
attribution?: Json;
|
|
2530
2631
|
buyer_identity?: Json;
|
|
2531
2632
|
checkout_url?: string | null;
|
|
@@ -2543,6 +2644,7 @@ export type Database = {
|
|
|
2543
2644
|
updated_at?: string;
|
|
2544
2645
|
};
|
|
2545
2646
|
Update: {
|
|
2647
|
+
access_token_hash?: string | null;
|
|
2546
2648
|
attribution?: Json;
|
|
2547
2649
|
buyer_identity?: Json;
|
|
2548
2650
|
checkout_url?: string | null;
|
|
@@ -2611,6 +2713,13 @@ export type Database = {
|
|
|
2611
2713
|
user_id?: string;
|
|
2612
2714
|
};
|
|
2613
2715
|
Relationships: [
|
|
2716
|
+
{
|
|
2717
|
+
foreignKeyName: "user_addresses_user_id_fkey";
|
|
2718
|
+
columns: ["user_id"];
|
|
2719
|
+
isOneToOne: false;
|
|
2720
|
+
referencedRelation: "profile_cards";
|
|
2721
|
+
referencedColumns: ["id"];
|
|
2722
|
+
},
|
|
2614
2723
|
{
|
|
2615
2724
|
foreignKeyName: "user_addresses_user_id_fkey";
|
|
2616
2725
|
columns: ["user_id"];
|
|
@@ -2700,7 +2809,24 @@ export type Database = {
|
|
|
2700
2809
|
};
|
|
2701
2810
|
};
|
|
2702
2811
|
Views: {
|
|
2703
|
-
|
|
2812
|
+
profile_cards: {
|
|
2813
|
+
Row: {
|
|
2814
|
+
avatar_url: string | null;
|
|
2815
|
+
full_name: string | null;
|
|
2816
|
+
id: string | null;
|
|
2817
|
+
};
|
|
2818
|
+
Insert: {
|
|
2819
|
+
avatar_url?: string | null;
|
|
2820
|
+
full_name?: string | null;
|
|
2821
|
+
id?: string | null;
|
|
2822
|
+
};
|
|
2823
|
+
Update: {
|
|
2824
|
+
avatar_url?: string | null;
|
|
2825
|
+
full_name?: string | null;
|
|
2826
|
+
id?: string | null;
|
|
2827
|
+
};
|
|
2828
|
+
Relationships: [];
|
|
2829
|
+
};
|
|
2704
2830
|
};
|
|
2705
2831
|
Functions: {
|
|
2706
2832
|
apply_order_inventory_deduction: {
|
|
@@ -2719,6 +2845,28 @@ export type Database = {
|
|
|
2719
2845
|
paid_at: string;
|
|
2720
2846
|
}[];
|
|
2721
2847
|
};
|
|
2848
|
+
author: {
|
|
2849
|
+
Args: {
|
|
2850
|
+
"": Database["public"]["Tables"]["cms_interactions"]["Row"];
|
|
2851
|
+
};
|
|
2852
|
+
Returns: {
|
|
2853
|
+
avatar_url: string | null;
|
|
2854
|
+
full_name: string | null;
|
|
2855
|
+
id: string | null;
|
|
2856
|
+
};
|
|
2857
|
+
SetofOptions: {
|
|
2858
|
+
from: "cms_interactions";
|
|
2859
|
+
to: "profile_cards";
|
|
2860
|
+
isOneToOne: true;
|
|
2861
|
+
isSetofReturn: true;
|
|
2862
|
+
};
|
|
2863
|
+
};
|
|
2864
|
+
bump_email_2fa_attempts: {
|
|
2865
|
+
Args: {
|
|
2866
|
+
p_user_id: string;
|
|
2867
|
+
};
|
|
2868
|
+
Returns: number;
|
|
2869
|
+
};
|
|
2722
2870
|
clear_currency_price_overrides: {
|
|
2723
2871
|
Args: {
|
|
2724
2872
|
target_currency: string;
|
|
@@ -2777,6 +2925,10 @@ export type Database = {
|
|
|
2777
2925
|
Args: never;
|
|
2778
2926
|
Returns: boolean;
|
|
2779
2927
|
};
|
|
2928
|
+
is_admin_mfa: {
|
|
2929
|
+
Args: never;
|
|
2930
|
+
Returns: boolean;
|
|
2931
|
+
};
|
|
2780
2932
|
is_valid_currency_amount_map: {
|
|
2781
2933
|
Args: {
|
|
2782
2934
|
amounts: Json;
|
|
@@ -2808,6 +2960,12 @@ export type Database = {
|
|
|
2808
2960
|
};
|
|
2809
2961
|
Returns: Json;
|
|
2810
2962
|
};
|
|
2963
|
+
redeem_coupon_atomically: {
|
|
2964
|
+
Args: {
|
|
2965
|
+
p_coupon_id: string;
|
|
2966
|
+
};
|
|
2967
|
+
Returns: boolean;
|
|
2968
|
+
};
|
|
2811
2969
|
sync_inventory_cache_for_sku: {
|
|
2812
2970
|
Args: {
|
|
2813
2971
|
p_sku: string;
|
package/package.json
CHANGED
|
@@ -7,6 +7,58 @@ const corsHeaders = {
|
|
|
7
7
|
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Dedicated bearer for this function. It runs with the service-role key (which bypasses RLS) and
|
|
12
|
+
* can wipe content, mint confirmed users and fetch arbitrary URLs, so it must NEVER be reachable
|
|
13
|
+
* with the public anon JWT (the platform `verify_jwt` default accepts the anon key, which ships in
|
|
14
|
+
* every site bundle). The operator sets MIGRATION_INGEST_BEARER to a high-entropy secret that is
|
|
15
|
+
* NOT the anon or service key; the function is fail-closed — with the var unset it rejects every
|
|
16
|
+
* request, because this is a maintainer migration tool, not a public endpoint.
|
|
17
|
+
*/
|
|
18
|
+
function isAuthorizedIngestRequest(req: Request): boolean {
|
|
19
|
+
const expected = (Deno.env.get('MIGRATION_INGEST_BEARER') ?? '').trim();
|
|
20
|
+
if (!expected) return false;
|
|
21
|
+
const provided = (req.headers.get('Authorization') ?? '').replace(/^Bearer\s+/i, '').trim();
|
|
22
|
+
if (!provided || provided.length !== expected.length) return false;
|
|
23
|
+
// constant-time compare
|
|
24
|
+
let diff = 0;
|
|
25
|
+
for (let i = 0; i < expected.length; i += 1) diff |= provided.charCodeAt(i) ^ expected.charCodeAt(i);
|
|
26
|
+
return diff === 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* SSRF guard for the outbound media fetch. Blocks obvious private, loopback, link-local and
|
|
31
|
+
* cloud-metadata destinations and requires http(s). Defence in depth behind the bearer: an
|
|
32
|
+
* authorized caller still should not be able to turn this into an internal port scanner or a
|
|
33
|
+
* cloud-credential exfiltrator. (DNS rebinding is further limited by redirect:'error' on the
|
|
34
|
+
* fetch; full rebind protection would require pinning the resolved IP, which the edge runtime
|
|
35
|
+
* does not expose.)
|
|
36
|
+
*/
|
|
37
|
+
function isBlockedFetchTarget(rawUrl: string): boolean {
|
|
38
|
+
let u: URL;
|
|
39
|
+
try {
|
|
40
|
+
u = new URL(rawUrl);
|
|
41
|
+
} catch {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (u.protocol !== 'http:' && u.protocol !== 'https:') return true;
|
|
45
|
+
const host = u.hostname.toLowerCase().replace(/^\[|\]$/g, '');
|
|
46
|
+
if (host === 'localhost' || host.endsWith('.localhost') || host.endsWith('.internal')) return true;
|
|
47
|
+
// IPv6 loopback / link-local / unique-local
|
|
48
|
+
if (host === '::1' || host.startsWith('fe80:') || host.startsWith('fc') || host.startsWith('fd')) return true;
|
|
49
|
+
// IPv4 literals in private / loopback / link-local / metadata ranges
|
|
50
|
+
const m = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
51
|
+
if (m) {
|
|
52
|
+
const [a, b] = [Number(m[1]), Number(m[2])];
|
|
53
|
+
if (a === 10 || a === 127 || a === 0) return true;
|
|
54
|
+
if (a === 169 && b === 254) return true; // link-local incl. 169.254.169.254 metadata
|
|
55
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
56
|
+
if (a === 192 && b === 168) return true;
|
|
57
|
+
if (a === 100 && b >= 64 && b <= 127) return true; // CGNAT
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
10
62
|
// Upload S3 / R2 Proxy Handler
|
|
11
63
|
async function processMediaUpload(url: string, r2Config: any, warnings: string[]): Promise<{ publicUrl: string; objectKey: string; contentType: string } | null> {
|
|
12
64
|
if (!r2Config || !r2Config.account_id) {
|
|
@@ -37,11 +89,15 @@ async function processMediaUpload(url: string, r2Config: any, warnings: string[]
|
|
|
37
89
|
const extension = contentType.split('/')[1] || 'jpg';
|
|
38
90
|
filename = `upload_${crypto.randomUUID()}.${extension}`;
|
|
39
91
|
} else {
|
|
92
|
+
if (isBlockedFetchTarget(url)) {
|
|
93
|
+
warnings.push(`Refused to fetch blocked/internal URL: ${url}`);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
40
96
|
const controller = new AbortController();
|
|
41
97
|
const timeoutId = setTimeout(() => controller.abort(), 6000); // 6-second timeout
|
|
42
98
|
let fileResponse;
|
|
43
99
|
try {
|
|
44
|
-
fileResponse = await fetch(url, { signal: controller.signal });
|
|
100
|
+
fileResponse = await fetch(url, { signal: controller.signal, redirect: 'error' });
|
|
45
101
|
clearTimeout(timeoutId);
|
|
46
102
|
} catch (err: any) {
|
|
47
103
|
clearTimeout(timeoutId);
|
|
@@ -91,6 +147,13 @@ Deno.serve(async (req: Request) => {
|
|
|
91
147
|
return new Response('ok', { headers: corsHeaders });
|
|
92
148
|
}
|
|
93
149
|
|
|
150
|
+
if (!isAuthorizedIngestRequest(req)) {
|
|
151
|
+
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
|
152
|
+
headers: { ...corsHeaders, 'Content-Type': 'application/json', 'WWW-Authenticate': 'Bearer' },
|
|
153
|
+
status: 401,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
94
157
|
try {
|
|
95
158
|
const supabaseClient = createClient(
|
|
96
159
|
Deno.env.get('SUPABASE_URL') ?? '',
|