@nextblock-cms/db 0.11.3 → 0.12.0
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/lib/supabase/server.d.ts +9 -0
- package/lib/supabase/types.d.ts +9 -0
- package/package.json +1 -1
- package/supabase/migrations/00000000000000_baseline_schema.sql +2454 -0
- package/supabase/migrations/00000000000001_baseline_constraints_and_indexes.sql +1071 -0
- package/supabase/migrations/00000000000002_baseline_security_and_grants.sql +1335 -0
- package/supabase/migrations/00000000000003_baseline_seed.sql +468 -0
- package/supabase/migrations/00000000000004_default_logo_email_safe.sql +24 -0
- package/supabase/migrations/00000000000005_add_custom_canonical.sql +13 -0
- package/supabase/migrations/00000000000006_home_live_demo_promo.sql +67 -0
- package/supabase/templates/confirmation.html +1 -1
- package/supabase/templates/email_change.html +1 -1
- package/supabase/templates/invite.html +1 -1
- package/supabase/templates/magic_link.html +1 -1
- package/supabase/templates/reauthentication.html +1 -1
- package/supabase/templates/recovery.html +1 -1
- package/supabase/migrations/00000000000000_setup_foundation_and_enums.sql +0 -40
- package/supabase/migrations/00000000000001_setup_cms_core.sql +0 -124
- package/supabase/migrations/00000000000002_setup_content_tables.sql +0 -138
- package/supabase/migrations/00000000000003_setup_catalog_and_licensing.sql +0 -160
- package/supabase/migrations/00000000000004_setup_fulfillment_shipping_taxes_and_currencies.sql +0 -797
- package/supabase/migrations/00000000000005_setup_functions_and_triggers.sql +0 -767
- package/supabase/migrations/00000000000006_setup_rls_and_grants.sql +0 -872
- package/supabase/migrations/00000000000007_setup_indexes.sql +0 -158
- package/supabase/migrations/00000000000008_seed_platform_defaults.sql +0 -155
- package/supabase/migrations/00000000000009_seed_translations.sql +0 -801
- package/supabase/migrations/00000000000010_seed_content_scaffold.sql +0 -1088
- package/supabase/migrations/00000000000011_setup_cortex_ai_settings.sql +0 -81
- package/supabase/migrations/00000000000012_setup_commerce_coupons.sql +0 -275
- package/supabase/migrations/00000000000013_setup_cortex_ai_db_mutation_audit.sql +0 -47
- package/supabase/migrations/00000000000014_setup_content_drafts.sql +0 -119
- package/supabase/migrations/00000000000015_setup_product_drafts.sql +0 -97
- package/supabase/migrations/00000000000016_add_feature_image_to_pages.sql +0 -8
- package/supabase/migrations/00000000000017_add_product_blocks.sql +0 -118
- package/supabase/migrations/00000000000018_setup_bot_protection_settings.sql +0 -77
- package/supabase/migrations/00000000000019_add_product_categories.sql +0 -64
- package/supabase/migrations/00000000000020_add_category_translations.sql +0 -10
- package/supabase/migrations/00000000000021_migrate_hero_blocks_to_sections.sql +0 -29
- package/supabase/migrations/00000000000022_seed_cortex_ai_guide_post.sql +0 -184
- package/supabase/migrations/00000000000023_setup_custom_block_definitions.sql +0 -183
- package/supabase/migrations/00000000000024_setup_ucp_cart_sessions.sql +0 -72
- package/supabase/migrations/00000000000025_add_sale_schedule_columns.sql +0 -406
- package/supabase/migrations/00000000000026_seed_on_sale_translation.sql +0 -9
- package/supabase/migrations/00000000000027_setup_privacy_and_mfa.sql +0 -479
- package/supabase/migrations/00000000000028_clear_advisor_warnings.sql +0 -93
- package/supabase/migrations/00000000000029_refresh_setup_article.sql +0 -252
- package/supabase/migrations/00000000000030_setup_system_configuration.sql +0 -80
- package/supabase/migrations/00000000000031_seed_footer_navigation.sql +0 -85
- package/supabase/migrations/00000000000032_neutralize_seeded_contact_emails.sql +0 -34
- package/supabase/migrations/00000000000033_setup_config_settings.sql +0 -99
- package/supabase/migrations/00000000000034_enable_staff_2fa_reminder_default.sql +0 -15
- package/supabase/migrations/00000000000035_reassert_advisor_fixes.sql +0 -33
- package/supabase/migrations/00000000000036_setup_system_alerts.sql +0 -72
- package/supabase/migrations/00000000000037_refresh_setup_article_install_paths.sql +0 -317
- package/supabase/migrations/00000000000038_seed_consent_banner_translations.sql +0 -126
- package/supabase/migrations/00000000000039_setup_interactions.sql +0 -153
- package/supabase/migrations/00000000000040_add_profiles_fk_to_interactions.sql +0 -8
- package/supabase/migrations/00000000000041_seed_interactions_translations.sql +0 -145
- package/supabase/migrations/00000000000042_seed_deploy_to_vercel_ctas.sql +0 -72
- package/supabase/migrations/00000000000043_swap_home_hero_media_to_youtube.sql +0 -42
- package/supabase/migrations/00000000000044_swap_article_nx_graph_for_youtube.sql +0 -66
|
@@ -1,767 +0,0 @@
|
|
|
1
|
-
-- 00000000000005_setup_functions_and_triggers.sql
|
|
2
|
-
-- Consolidated migration preserving original statement order within grouped sections.
|
|
3
|
-
|
|
4
|
-
-- 00000000000015_setup_core_functions_and_triggers.sql
|
|
5
|
-
-- Shared auth helpers and CMS timestamp triggers.
|
|
6
|
-
|
|
7
|
-
CREATE OR REPLACE FUNCTION public.get_my_claim(claim text)
|
|
8
|
-
RETURNS jsonb
|
|
9
|
-
LANGUAGE sql
|
|
10
|
-
STABLE
|
|
11
|
-
SET search_path = ''
|
|
12
|
-
AS $$
|
|
13
|
-
SELECT COALESCE(current_setting('request.jwt.claims', true)::jsonb ->> claim, NULL)::jsonb
|
|
14
|
-
$$;
|
|
15
|
-
|
|
16
|
-
CREATE OR REPLACE FUNCTION public.get_current_user_role()
|
|
17
|
-
RETURNS public.user_role
|
|
18
|
-
LANGUAGE sql
|
|
19
|
-
STABLE
|
|
20
|
-
SET search_path = public
|
|
21
|
-
AS $$
|
|
22
|
-
SELECT role FROM public.profiles WHERE id = auth.uid();
|
|
23
|
-
$$;
|
|
24
|
-
|
|
25
|
-
CREATE OR REPLACE FUNCTION public.is_admin()
|
|
26
|
-
RETURNS boolean
|
|
27
|
-
LANGUAGE sql
|
|
28
|
-
STABLE
|
|
29
|
-
SET search_path = public
|
|
30
|
-
AS $$
|
|
31
|
-
SELECT role = 'ADMIN' FROM public.profiles WHERE id = auth.uid();
|
|
32
|
-
$$;
|
|
33
|
-
|
|
34
|
-
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
|
35
|
-
RETURNS trigger
|
|
36
|
-
LANGUAGE plpgsql
|
|
37
|
-
SECURITY DEFINER
|
|
38
|
-
SET search_path = public
|
|
39
|
-
AS $$
|
|
40
|
-
DECLARE
|
|
41
|
-
admin_flag_set boolean := false;
|
|
42
|
-
user_role public.user_role;
|
|
43
|
-
v_full_name text;
|
|
44
|
-
v_avatar_url text;
|
|
45
|
-
v_github_username text;
|
|
46
|
-
v_provider text;
|
|
47
|
-
BEGIN
|
|
48
|
-
INSERT INTO public.site_settings (key, value)
|
|
49
|
-
VALUES ('is_admin_created', 'false'::jsonb)
|
|
50
|
-
ON CONFLICT (key) DO NOTHING;
|
|
51
|
-
|
|
52
|
-
SELECT COALESCE(value::jsonb::boolean, false)
|
|
53
|
-
INTO admin_flag_set
|
|
54
|
-
FROM public.site_settings
|
|
55
|
-
WHERE key = 'is_admin_created'
|
|
56
|
-
FOR UPDATE;
|
|
57
|
-
|
|
58
|
-
IF admin_flag_set = false THEN
|
|
59
|
-
user_role := 'ADMIN'::public.user_role;
|
|
60
|
-
|
|
61
|
-
UPDATE public.site_settings
|
|
62
|
-
SET value = 'true'::jsonb
|
|
63
|
-
WHERE key = 'is_admin_created';
|
|
64
|
-
ELSE
|
|
65
|
-
user_role := 'USER'::public.user_role;
|
|
66
|
-
END IF;
|
|
67
|
-
|
|
68
|
-
v_full_name := NEW.raw_user_meta_data->>'full_name';
|
|
69
|
-
v_avatar_url := NEW.raw_user_meta_data->>'avatar_url';
|
|
70
|
-
v_provider := NEW.raw_app_meta_data->>'provider';
|
|
71
|
-
|
|
72
|
-
IF v_provider = 'github' OR (NEW.raw_user_meta_data->>'iss') LIKE '%github%' THEN
|
|
73
|
-
v_github_username := COALESCE(
|
|
74
|
-
NEW.raw_user_meta_data->>'user_name',
|
|
75
|
-
NEW.raw_user_meta_data->>'preferred_username'
|
|
76
|
-
);
|
|
77
|
-
ELSE
|
|
78
|
-
v_github_username := NULL;
|
|
79
|
-
END IF;
|
|
80
|
-
|
|
81
|
-
INSERT INTO public.profiles (
|
|
82
|
-
id,
|
|
83
|
-
role,
|
|
84
|
-
full_name,
|
|
85
|
-
avatar_url,
|
|
86
|
-
github_username
|
|
87
|
-
)
|
|
88
|
-
VALUES (
|
|
89
|
-
NEW.id,
|
|
90
|
-
user_role,
|
|
91
|
-
v_full_name,
|
|
92
|
-
v_avatar_url,
|
|
93
|
-
v_github_username
|
|
94
|
-
)
|
|
95
|
-
ON CONFLICT (id) DO UPDATE SET
|
|
96
|
-
full_name = EXCLUDED.full_name,
|
|
97
|
-
avatar_url = EXCLUDED.avatar_url,
|
|
98
|
-
github_username = EXCLUDED.github_username;
|
|
99
|
-
|
|
100
|
-
RETURN NEW;
|
|
101
|
-
END;
|
|
102
|
-
$$;
|
|
103
|
-
|
|
104
|
-
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
|
|
105
|
-
CREATE TRIGGER on_auth_user_created
|
|
106
|
-
AFTER INSERT ON auth.users
|
|
107
|
-
FOR EACH ROW
|
|
108
|
-
EXECUTE FUNCTION public.handle_new_user();
|
|
109
|
-
|
|
110
|
-
REVOKE EXECUTE ON FUNCTION public.handle_new_user() FROM PUBLIC, anon, authenticated;
|
|
111
|
-
|
|
112
|
-
DO $$
|
|
113
|
-
DECLARE
|
|
114
|
-
missing_user record;
|
|
115
|
-
v_github_username text;
|
|
116
|
-
v_full_name text;
|
|
117
|
-
v_role public.user_role;
|
|
118
|
-
v_admin_exists boolean;
|
|
119
|
-
BEGIN
|
|
120
|
-
SELECT EXISTS (SELECT 1 FROM public.profiles WHERE role = 'ADMIN')
|
|
121
|
-
INTO v_admin_exists;
|
|
122
|
-
|
|
123
|
-
FOR missing_user IN
|
|
124
|
-
SELECT *
|
|
125
|
-
FROM auth.users
|
|
126
|
-
WHERE id NOT IN (SELECT id FROM public.profiles)
|
|
127
|
-
ORDER BY created_at ASC
|
|
128
|
-
LOOP
|
|
129
|
-
IF missing_user.raw_app_meta_data->>'provider' = 'github'
|
|
130
|
-
OR (missing_user.raw_user_meta_data->>'iss') LIKE '%github%' THEN
|
|
131
|
-
v_github_username := COALESCE(
|
|
132
|
-
missing_user.raw_user_meta_data->>'user_name',
|
|
133
|
-
missing_user.raw_user_meta_data->>'preferred_username'
|
|
134
|
-
);
|
|
135
|
-
ELSE
|
|
136
|
-
v_github_username := NULL;
|
|
137
|
-
END IF;
|
|
138
|
-
|
|
139
|
-
v_full_name := missing_user.raw_user_meta_data->>'full_name';
|
|
140
|
-
|
|
141
|
-
IF v_admin_exists = false THEN
|
|
142
|
-
v_role := 'ADMIN';
|
|
143
|
-
v_admin_exists := true;
|
|
144
|
-
|
|
145
|
-
INSERT INTO public.site_settings (key, value)
|
|
146
|
-
VALUES ('is_admin_created', 'true'::jsonb)
|
|
147
|
-
ON CONFLICT (key) DO UPDATE
|
|
148
|
-
SET value = 'true'::jsonb;
|
|
149
|
-
ELSE
|
|
150
|
-
v_role := 'USER';
|
|
151
|
-
END IF;
|
|
152
|
-
|
|
153
|
-
INSERT INTO public.profiles (id, role, full_name, avatar_url, github_username)
|
|
154
|
-
VALUES (
|
|
155
|
-
missing_user.id,
|
|
156
|
-
v_role,
|
|
157
|
-
v_full_name,
|
|
158
|
-
missing_user.raw_user_meta_data->>'avatar_url',
|
|
159
|
-
v_github_username
|
|
160
|
-
)
|
|
161
|
-
ON CONFLICT (id) DO NOTHING;
|
|
162
|
-
END LOOP;
|
|
163
|
-
END
|
|
164
|
-
$$;
|
|
165
|
-
|
|
166
|
-
CREATE OR REPLACE FUNCTION public.set_current_timestamp_updated_at()
|
|
167
|
-
RETURNS trigger
|
|
168
|
-
LANGUAGE plpgsql
|
|
169
|
-
SET search_path = ''
|
|
170
|
-
AS $$
|
|
171
|
-
DECLARE
|
|
172
|
-
_new record;
|
|
173
|
-
BEGIN
|
|
174
|
-
_new := NEW;
|
|
175
|
-
_new.updated_at = now();
|
|
176
|
-
RETURN _new;
|
|
177
|
-
END;
|
|
178
|
-
$$;
|
|
179
|
-
|
|
180
|
-
CREATE OR REPLACE FUNCTION public.handle_languages_update()
|
|
181
|
-
RETURNS trigger
|
|
182
|
-
LANGUAGE plpgsql
|
|
183
|
-
SET search_path = ''
|
|
184
|
-
AS $$
|
|
185
|
-
BEGIN
|
|
186
|
-
NEW.updated_at = now();
|
|
187
|
-
RETURN NEW;
|
|
188
|
-
END;
|
|
189
|
-
$$;
|
|
190
|
-
|
|
191
|
-
CREATE OR REPLACE FUNCTION public.handle_media_update()
|
|
192
|
-
RETURNS trigger
|
|
193
|
-
LANGUAGE plpgsql
|
|
194
|
-
SET search_path = public
|
|
195
|
-
AS $$
|
|
196
|
-
BEGIN
|
|
197
|
-
NEW.updated_at = now();
|
|
198
|
-
RETURN NEW;
|
|
199
|
-
END;
|
|
200
|
-
$$;
|
|
201
|
-
|
|
202
|
-
CREATE OR REPLACE FUNCTION public.handle_posts_update()
|
|
203
|
-
RETURNS trigger
|
|
204
|
-
LANGUAGE plpgsql
|
|
205
|
-
SET search_path = public
|
|
206
|
-
AS $$
|
|
207
|
-
BEGIN
|
|
208
|
-
NEW.updated_at = now();
|
|
209
|
-
RETURN NEW;
|
|
210
|
-
END;
|
|
211
|
-
$$;
|
|
212
|
-
|
|
213
|
-
CREATE OR REPLACE FUNCTION public.handle_pages_update()
|
|
214
|
-
RETURNS trigger
|
|
215
|
-
LANGUAGE plpgsql
|
|
216
|
-
SET search_path = public
|
|
217
|
-
AS $$
|
|
218
|
-
BEGIN
|
|
219
|
-
NEW.updated_at = now();
|
|
220
|
-
RETURN NEW;
|
|
221
|
-
END;
|
|
222
|
-
$$;
|
|
223
|
-
|
|
224
|
-
CREATE OR REPLACE FUNCTION public.handle_blocks_update()
|
|
225
|
-
RETURNS trigger
|
|
226
|
-
LANGUAGE plpgsql
|
|
227
|
-
SET search_path = public
|
|
228
|
-
AS $$
|
|
229
|
-
BEGIN
|
|
230
|
-
NEW.updated_at = now();
|
|
231
|
-
RETURN NEW;
|
|
232
|
-
END;
|
|
233
|
-
$$;
|
|
234
|
-
|
|
235
|
-
CREATE OR REPLACE FUNCTION public.handle_navigation_items_update()
|
|
236
|
-
RETURNS trigger
|
|
237
|
-
LANGUAGE plpgsql
|
|
238
|
-
SET search_path = public
|
|
239
|
-
AS $$
|
|
240
|
-
BEGIN
|
|
241
|
-
NEW.updated_at = now();
|
|
242
|
-
RETURN NEW;
|
|
243
|
-
END;
|
|
244
|
-
$$;
|
|
245
|
-
|
|
246
|
-
DROP TRIGGER IF EXISTS set_updated_at ON public.translations;
|
|
247
|
-
CREATE TRIGGER set_updated_at
|
|
248
|
-
BEFORE UPDATE ON public.translations
|
|
249
|
-
FOR EACH ROW
|
|
250
|
-
EXECUTE FUNCTION public.set_current_timestamp_updated_at();
|
|
251
|
-
|
|
252
|
-
DROP TRIGGER IF EXISTS on_languages_update ON public.languages;
|
|
253
|
-
CREATE TRIGGER on_languages_update
|
|
254
|
-
BEFORE UPDATE ON public.languages
|
|
255
|
-
FOR EACH ROW
|
|
256
|
-
EXECUTE FUNCTION public.handle_languages_update();
|
|
257
|
-
|
|
258
|
-
DROP TRIGGER IF EXISTS on_media_update ON public.media;
|
|
259
|
-
CREATE TRIGGER on_media_update
|
|
260
|
-
BEFORE UPDATE ON public.media
|
|
261
|
-
FOR EACH ROW
|
|
262
|
-
EXECUTE FUNCTION public.handle_media_update();
|
|
263
|
-
|
|
264
|
-
DROP TRIGGER IF EXISTS on_posts_update ON public.posts;
|
|
265
|
-
CREATE TRIGGER on_posts_update
|
|
266
|
-
BEFORE UPDATE ON public.posts
|
|
267
|
-
FOR EACH ROW
|
|
268
|
-
EXECUTE FUNCTION public.handle_posts_update();
|
|
269
|
-
|
|
270
|
-
DROP TRIGGER IF EXISTS on_pages_update ON public.pages;
|
|
271
|
-
CREATE TRIGGER on_pages_update
|
|
272
|
-
BEFORE UPDATE ON public.pages
|
|
273
|
-
FOR EACH ROW
|
|
274
|
-
EXECUTE FUNCTION public.handle_pages_update();
|
|
275
|
-
|
|
276
|
-
DROP TRIGGER IF EXISTS on_blocks_update ON public.blocks;
|
|
277
|
-
CREATE TRIGGER on_blocks_update
|
|
278
|
-
BEFORE UPDATE ON public.blocks
|
|
279
|
-
FOR EACH ROW
|
|
280
|
-
EXECUTE FUNCTION public.handle_blocks_update();
|
|
281
|
-
|
|
282
|
-
DROP TRIGGER IF EXISTS on_navigation_items_update ON public.navigation_items;
|
|
283
|
-
CREATE TRIGGER on_navigation_items_update
|
|
284
|
-
BEFORE UPDATE ON public.navigation_items
|
|
285
|
-
FOR EACH ROW
|
|
286
|
-
EXECUTE FUNCTION public.handle_navigation_items_update();
|
|
287
|
-
|
|
288
|
-
-- 00000000000016_setup_ecommerce_functions_and_triggers.sql
|
|
289
|
-
-- Product RPCs, inventory sync, and invoice helpers.
|
|
290
|
-
|
|
291
|
-
CREATE OR REPLACE FUNCTION public.get_ecommerce_track_quantities()
|
|
292
|
-
RETURNS boolean
|
|
293
|
-
LANGUAGE plpgsql
|
|
294
|
-
STABLE
|
|
295
|
-
SET search_path = public
|
|
296
|
-
AS $$
|
|
297
|
-
DECLARE
|
|
298
|
-
v_value jsonb;
|
|
299
|
-
v_raw text;
|
|
300
|
-
BEGIN
|
|
301
|
-
SELECT value
|
|
302
|
-
INTO v_value
|
|
303
|
-
FROM public.site_settings
|
|
304
|
-
WHERE key = 'ecommerce_inventory_settings';
|
|
305
|
-
|
|
306
|
-
IF v_value IS NULL THEN
|
|
307
|
-
RETURN true;
|
|
308
|
-
END IF;
|
|
309
|
-
|
|
310
|
-
IF jsonb_typeof(v_value) = 'object' THEN
|
|
311
|
-
v_raw := NULLIF(v_value->>'track_quantities', '');
|
|
312
|
-
ELSE
|
|
313
|
-
v_raw := NULLIF(trim(BOTH '"' FROM v_value::text), '');
|
|
314
|
-
END IF;
|
|
315
|
-
|
|
316
|
-
IF v_raw IS NULL THEN
|
|
317
|
-
RETURN true;
|
|
318
|
-
END IF;
|
|
319
|
-
|
|
320
|
-
IF lower(v_raw) IN ('false', 'f', '0', 'no', 'off') THEN
|
|
321
|
-
RETURN false;
|
|
322
|
-
END IF;
|
|
323
|
-
|
|
324
|
-
RETURN true;
|
|
325
|
-
END;
|
|
326
|
-
$$;
|
|
327
|
-
|
|
328
|
-
CREATE OR REPLACE FUNCTION public.format_order_invoice_number(p_value bigint)
|
|
329
|
-
RETURNS text
|
|
330
|
-
LANGUAGE sql
|
|
331
|
-
IMMUTABLE
|
|
332
|
-
SET search_path = ''
|
|
333
|
-
AS $$
|
|
334
|
-
SELECT 'INV-' || lpad(p_value::text, 6, '0');
|
|
335
|
-
$$;
|
|
336
|
-
|
|
337
|
-
CREATE OR REPLACE FUNCTION public.generate_order_invoice_number()
|
|
338
|
-
RETURNS text
|
|
339
|
-
LANGUAGE sql
|
|
340
|
-
VOLATILE
|
|
341
|
-
SET search_path = ''
|
|
342
|
-
AS $$
|
|
343
|
-
SELECT public.format_order_invoice_number(nextval('public.order_invoice_number_seq'));
|
|
344
|
-
$$;
|
|
345
|
-
|
|
346
|
-
CREATE OR REPLACE FUNCTION public.assign_order_invoice_metadata(
|
|
347
|
-
p_order_id uuid,
|
|
348
|
-
p_paid_at timestamptz DEFAULT now()
|
|
349
|
-
)
|
|
350
|
-
RETURNS TABLE(invoice_number text, paid_at timestamptz)
|
|
351
|
-
LANGUAGE plpgsql
|
|
352
|
-
SET search_path = public
|
|
353
|
-
AS $$
|
|
354
|
-
DECLARE
|
|
355
|
-
v_order public.orders%ROWTYPE;
|
|
356
|
-
v_effective_paid_at timestamptz;
|
|
357
|
-
BEGIN
|
|
358
|
-
SELECT *
|
|
359
|
-
INTO v_order
|
|
360
|
-
FROM public.orders
|
|
361
|
-
WHERE id = p_order_id
|
|
362
|
-
FOR UPDATE;
|
|
363
|
-
|
|
364
|
-
IF NOT FOUND THEN
|
|
365
|
-
RAISE EXCEPTION 'Order % not found', p_order_id;
|
|
366
|
-
END IF;
|
|
367
|
-
|
|
368
|
-
v_effective_paid_at := COALESCE(v_order.paid_at, p_paid_at, now(), v_order.created_at);
|
|
369
|
-
|
|
370
|
-
UPDATE public.orders
|
|
371
|
-
SET
|
|
372
|
-
invoice_number = COALESCE(v_order.invoice_number, public.generate_order_invoice_number()),
|
|
373
|
-
paid_at = v_effective_paid_at
|
|
374
|
-
WHERE id = p_order_id
|
|
375
|
-
RETURNING orders.invoice_number, orders.paid_at
|
|
376
|
-
INTO invoice_number, paid_at;
|
|
377
|
-
|
|
378
|
-
RETURN NEXT;
|
|
379
|
-
END;
|
|
380
|
-
$$;
|
|
381
|
-
|
|
382
|
-
CREATE OR REPLACE FUNCTION public.upsert_product_with_variants(product_payload jsonb)
|
|
383
|
-
RETURNS uuid
|
|
384
|
-
LANGUAGE plpgsql
|
|
385
|
-
SET search_path = public
|
|
386
|
-
AS $function$
|
|
387
|
-
DECLARE
|
|
388
|
-
v_product_id uuid := NULLIF(product_payload->>'id', '')::uuid;
|
|
389
|
-
v_translation_group_id uuid := NULLIF(product_payload->>'translation_group_id', '')::uuid;
|
|
390
|
-
v_product_type text := CASE
|
|
391
|
-
WHEN product_payload->>'product_type' IN ('physical', 'digital') THEN
|
|
392
|
-
product_payload->>'product_type'
|
|
393
|
-
WHEN NULLIF(product_payload->>'freemius_product_id', '') IS NOT NULL
|
|
394
|
-
OR NULLIF(product_payload->>'freemius_plan_id', '') IS NOT NULL THEN
|
|
395
|
-
'digital'
|
|
396
|
-
ELSE
|
|
397
|
-
'physical'
|
|
398
|
-
END;
|
|
399
|
-
v_payment_provider text := CASE
|
|
400
|
-
WHEN v_product_type = 'digital' THEN 'freemius'
|
|
401
|
-
ELSE 'stripe'
|
|
402
|
-
END;
|
|
403
|
-
v_variants jsonb := COALESCE(product_payload->'variants', '[]'::jsonb);
|
|
404
|
-
v_variant jsonb;
|
|
405
|
-
v_variant_id uuid;
|
|
406
|
-
v_term_id text;
|
|
407
|
-
v_has_variants boolean := jsonb_typeof(v_variants) = 'array' AND jsonb_array_length(v_variants) > 0;
|
|
408
|
-
v_total_variant_stock integer := 0;
|
|
409
|
-
BEGIN
|
|
410
|
-
IF NOT public.is_admin() THEN
|
|
411
|
-
RAISE EXCEPTION 'Admin access required';
|
|
412
|
-
END IF;
|
|
413
|
-
|
|
414
|
-
IF v_has_variants THEN
|
|
415
|
-
SELECT COALESCE(SUM(COALESCE((value->>'stock_quantity')::integer, 0)), 0)
|
|
416
|
-
INTO v_total_variant_stock
|
|
417
|
-
FROM jsonb_array_elements(v_variants);
|
|
418
|
-
END IF;
|
|
419
|
-
|
|
420
|
-
IF v_product_id IS NULL THEN
|
|
421
|
-
INSERT INTO public.products (
|
|
422
|
-
title,
|
|
423
|
-
slug,
|
|
424
|
-
sku,
|
|
425
|
-
product_type,
|
|
426
|
-
payment_provider,
|
|
427
|
-
upc,
|
|
428
|
-
stock,
|
|
429
|
-
status,
|
|
430
|
-
short_description,
|
|
431
|
-
description_json,
|
|
432
|
-
metadata,
|
|
433
|
-
price,
|
|
434
|
-
prices,
|
|
435
|
-
sale_price,
|
|
436
|
-
sale_prices,
|
|
437
|
-
freemius_plan_id,
|
|
438
|
-
freemius_product_id,
|
|
439
|
-
trial_period_days,
|
|
440
|
-
trial_requires_payment_method,
|
|
441
|
-
language_id,
|
|
442
|
-
translation_group_id
|
|
443
|
-
)
|
|
444
|
-
VALUES (
|
|
445
|
-
product_payload->>'title',
|
|
446
|
-
product_payload->>'slug',
|
|
447
|
-
product_payload->>'sku',
|
|
448
|
-
v_product_type,
|
|
449
|
-
v_payment_provider,
|
|
450
|
-
NULLIF(product_payload->>'upc', ''),
|
|
451
|
-
CASE
|
|
452
|
-
WHEN v_has_variants THEN v_total_variant_stock
|
|
453
|
-
ELSE COALESCE((product_payload->>'stock')::integer, 0)
|
|
454
|
-
END,
|
|
455
|
-
COALESCE(product_payload->>'status', 'draft'),
|
|
456
|
-
NULLIF(product_payload->>'short_description', ''),
|
|
457
|
-
product_payload->'description_json',
|
|
458
|
-
COALESCE(product_payload->'metadata', '{}'::jsonb),
|
|
459
|
-
COALESCE((product_payload->>'price')::integer, 0),
|
|
460
|
-
COALESCE(product_payload->'prices', '{}'::jsonb),
|
|
461
|
-
CASE
|
|
462
|
-
WHEN product_payload ? 'sale_price' AND product_payload->>'sale_price' <> '' THEN
|
|
463
|
-
(product_payload->>'sale_price')::integer
|
|
464
|
-
ELSE
|
|
465
|
-
NULL
|
|
466
|
-
END,
|
|
467
|
-
CASE
|
|
468
|
-
WHEN product_payload ? 'sale_prices' THEN COALESCE(product_payload->'sale_prices', '{}'::jsonb)
|
|
469
|
-
ELSE NULL
|
|
470
|
-
END,
|
|
471
|
-
NULLIF(product_payload->>'freemius_plan_id', ''),
|
|
472
|
-
NULLIF(product_payload->>'freemius_product_id', ''),
|
|
473
|
-
COALESCE((product_payload->>'trial_period_days')::integer, 0),
|
|
474
|
-
COALESCE((product_payload->>'trial_requires_payment_method')::boolean, false),
|
|
475
|
-
(product_payload->>'language_id')::bigint,
|
|
476
|
-
COALESCE(v_translation_group_id, gen_random_uuid())
|
|
477
|
-
)
|
|
478
|
-
RETURNING id INTO v_product_id;
|
|
479
|
-
ELSE
|
|
480
|
-
UPDATE public.products
|
|
481
|
-
SET
|
|
482
|
-
title = product_payload->>'title',
|
|
483
|
-
slug = product_payload->>'slug',
|
|
484
|
-
sku = product_payload->>'sku',
|
|
485
|
-
product_type = v_product_type,
|
|
486
|
-
payment_provider = v_payment_provider,
|
|
487
|
-
upc = NULLIF(product_payload->>'upc', ''),
|
|
488
|
-
stock = CASE
|
|
489
|
-
WHEN v_has_variants THEN v_total_variant_stock
|
|
490
|
-
ELSE COALESCE((product_payload->>'stock')::integer, 0)
|
|
491
|
-
END,
|
|
492
|
-
status = COALESCE(product_payload->>'status', status),
|
|
493
|
-
short_description = NULLIF(product_payload->>'short_description', ''),
|
|
494
|
-
description_json = product_payload->'description_json',
|
|
495
|
-
metadata = COALESCE(product_payload->'metadata', '{}'::jsonb),
|
|
496
|
-
price = COALESCE((product_payload->>'price')::integer, 0),
|
|
497
|
-
prices = COALESCE(product_payload->'prices', '{}'::jsonb),
|
|
498
|
-
sale_price = CASE
|
|
499
|
-
WHEN product_payload ? 'sale_price' AND product_payload->>'sale_price' <> '' THEN
|
|
500
|
-
(product_payload->>'sale_price')::integer
|
|
501
|
-
ELSE
|
|
502
|
-
NULL
|
|
503
|
-
END,
|
|
504
|
-
sale_prices = CASE
|
|
505
|
-
WHEN product_payload ? 'sale_prices' THEN COALESCE(product_payload->'sale_prices', '{}'::jsonb)
|
|
506
|
-
ELSE NULL
|
|
507
|
-
END,
|
|
508
|
-
freemius_plan_id = NULLIF(product_payload->>'freemius_plan_id', ''),
|
|
509
|
-
freemius_product_id = NULLIF(product_payload->>'freemius_product_id', ''),
|
|
510
|
-
trial_period_days = COALESCE((product_payload->>'trial_period_days')::integer, 0),
|
|
511
|
-
trial_requires_payment_method = COALESCE((product_payload->>'trial_requires_payment_method')::boolean, false),
|
|
512
|
-
language_id = COALESCE((product_payload->>'language_id')::bigint, language_id),
|
|
513
|
-
translation_group_id = COALESCE(v_translation_group_id, translation_group_id),
|
|
514
|
-
updated_at = now()
|
|
515
|
-
WHERE id = v_product_id;
|
|
516
|
-
|
|
517
|
-
IF NOT FOUND THEN
|
|
518
|
-
RAISE EXCEPTION 'Product not found';
|
|
519
|
-
END IF;
|
|
520
|
-
END IF;
|
|
521
|
-
|
|
522
|
-
DELETE FROM public.variant_attribute_mapping
|
|
523
|
-
WHERE variant_id IN (
|
|
524
|
-
SELECT id
|
|
525
|
-
FROM public.product_variants
|
|
526
|
-
WHERE product_id = v_product_id
|
|
527
|
-
);
|
|
528
|
-
|
|
529
|
-
DELETE FROM public.product_variants
|
|
530
|
-
WHERE product_id = v_product_id;
|
|
531
|
-
|
|
532
|
-
IF v_has_variants THEN
|
|
533
|
-
FOR v_variant IN
|
|
534
|
-
SELECT value FROM jsonb_array_elements(v_variants)
|
|
535
|
-
LOOP
|
|
536
|
-
INSERT INTO public.product_variants (
|
|
537
|
-
product_id,
|
|
538
|
-
sku,
|
|
539
|
-
upc,
|
|
540
|
-
price,
|
|
541
|
-
prices,
|
|
542
|
-
sale_price,
|
|
543
|
-
sale_prices,
|
|
544
|
-
stock_quantity,
|
|
545
|
-
main_media_id
|
|
546
|
-
)
|
|
547
|
-
VALUES (
|
|
548
|
-
v_product_id,
|
|
549
|
-
v_variant->>'sku',
|
|
550
|
-
NULLIF(v_variant->>'upc', ''),
|
|
551
|
-
COALESCE((v_variant->>'price')::integer, 0),
|
|
552
|
-
COALESCE(v_variant->'prices', '{}'::jsonb),
|
|
553
|
-
CASE
|
|
554
|
-
WHEN v_variant ? 'sale_price' AND v_variant->>'sale_price' <> '' THEN
|
|
555
|
-
(v_variant->>'sale_price')::integer
|
|
556
|
-
ELSE
|
|
557
|
-
NULL
|
|
558
|
-
END,
|
|
559
|
-
CASE
|
|
560
|
-
WHEN v_variant ? 'sale_prices' THEN COALESCE(v_variant->'sale_prices', '{}'::jsonb)
|
|
561
|
-
ELSE NULL
|
|
562
|
-
END,
|
|
563
|
-
COALESCE((v_variant->>'stock_quantity')::integer, 0),
|
|
564
|
-
NULLIF(v_variant->>'main_media_id', '')::uuid
|
|
565
|
-
)
|
|
566
|
-
RETURNING id INTO v_variant_id;
|
|
567
|
-
|
|
568
|
-
FOR v_term_id IN
|
|
569
|
-
SELECT jsonb_array_elements_text(COALESCE(v_variant->'attribute_term_ids', '[]'::jsonb))
|
|
570
|
-
LOOP
|
|
571
|
-
INSERT INTO public.variant_attribute_mapping (variant_id, attribute_term_id)
|
|
572
|
-
VALUES (v_variant_id, v_term_id::uuid);
|
|
573
|
-
END LOOP;
|
|
574
|
-
END LOOP;
|
|
575
|
-
END IF;
|
|
576
|
-
|
|
577
|
-
RETURN v_product_id;
|
|
578
|
-
END;
|
|
579
|
-
$function$;
|
|
580
|
-
|
|
581
|
-
CREATE OR REPLACE FUNCTION public.handle_inventory_items_update()
|
|
582
|
-
RETURNS trigger
|
|
583
|
-
LANGUAGE plpgsql
|
|
584
|
-
SET search_path = public
|
|
585
|
-
AS $$
|
|
586
|
-
BEGIN
|
|
587
|
-
NEW.updated_at = now();
|
|
588
|
-
RETURN NEW;
|
|
589
|
-
END;
|
|
590
|
-
$$;
|
|
591
|
-
|
|
592
|
-
CREATE OR REPLACE FUNCTION public.sync_inventory_cache_for_sku(p_sku text)
|
|
593
|
-
RETURNS void
|
|
594
|
-
LANGUAGE plpgsql
|
|
595
|
-
SET search_path = public
|
|
596
|
-
AS $$
|
|
597
|
-
DECLARE
|
|
598
|
-
v_quantity integer := 0;
|
|
599
|
-
BEGIN
|
|
600
|
-
IF NULLIF(trim(p_sku), '') IS NULL THEN
|
|
601
|
-
RETURN;
|
|
602
|
-
END IF;
|
|
603
|
-
|
|
604
|
-
SELECT quantity
|
|
605
|
-
INTO v_quantity
|
|
606
|
-
FROM public.inventory_items
|
|
607
|
-
WHERE sku = p_sku
|
|
608
|
-
LIMIT 1;
|
|
609
|
-
|
|
610
|
-
v_quantity := COALESCE(v_quantity, 0);
|
|
611
|
-
|
|
612
|
-
UPDATE public.product_variants
|
|
613
|
-
SET
|
|
614
|
-
stock_quantity = v_quantity,
|
|
615
|
-
updated_at = now()
|
|
616
|
-
WHERE sku = p_sku;
|
|
617
|
-
|
|
618
|
-
UPDATE public.products AS products
|
|
619
|
-
SET
|
|
620
|
-
stock = v_quantity,
|
|
621
|
-
updated_at = now()
|
|
622
|
-
WHERE products.sku = p_sku
|
|
623
|
-
AND NOT EXISTS (
|
|
624
|
-
SELECT 1
|
|
625
|
-
FROM public.product_variants
|
|
626
|
-
WHERE product_id = products.id
|
|
627
|
-
);
|
|
628
|
-
|
|
629
|
-
UPDATE public.products AS products
|
|
630
|
-
SET
|
|
631
|
-
stock = COALESCE((
|
|
632
|
-
SELECT SUM(COALESCE(inventory.quantity, 0))
|
|
633
|
-
FROM public.product_variants AS variants
|
|
634
|
-
LEFT JOIN public.inventory_items AS inventory
|
|
635
|
-
ON inventory.sku = variants.sku
|
|
636
|
-
WHERE variants.product_id = products.id
|
|
637
|
-
), 0),
|
|
638
|
-
updated_at = now()
|
|
639
|
-
WHERE EXISTS (
|
|
640
|
-
SELECT 1
|
|
641
|
-
FROM public.product_variants AS variants
|
|
642
|
-
WHERE variants.product_id = products.id
|
|
643
|
-
AND variants.sku = p_sku
|
|
644
|
-
);
|
|
645
|
-
END;
|
|
646
|
-
$$;
|
|
647
|
-
|
|
648
|
-
CREATE OR REPLACE FUNCTION public.handle_inventory_item_change()
|
|
649
|
-
RETURNS trigger
|
|
650
|
-
LANGUAGE plpgsql
|
|
651
|
-
SET search_path = public
|
|
652
|
-
AS $$
|
|
653
|
-
DECLARE
|
|
654
|
-
v_sku text := COALESCE(NEW.sku, OLD.sku);
|
|
655
|
-
BEGIN
|
|
656
|
-
PERFORM public.sync_inventory_cache_for_sku(v_sku);
|
|
657
|
-
RETURN COALESCE(NEW, OLD);
|
|
658
|
-
END;
|
|
659
|
-
$$;
|
|
660
|
-
|
|
661
|
-
CREATE OR REPLACE FUNCTION public.apply_order_inventory_deduction(p_order_id uuid)
|
|
662
|
-
RETURNS void
|
|
663
|
-
LANGUAGE plpgsql
|
|
664
|
-
SET search_path = public
|
|
665
|
-
AS $$
|
|
666
|
-
DECLARE
|
|
667
|
-
v_track_quantities boolean := public.get_ecommerce_track_quantities();
|
|
668
|
-
v_item record;
|
|
669
|
-
v_inventory_deducted_at timestamptz;
|
|
670
|
-
v_sku text;
|
|
671
|
-
v_current_quantity integer;
|
|
672
|
-
BEGIN
|
|
673
|
-
SELECT inventory_deducted_at
|
|
674
|
-
INTO v_inventory_deducted_at
|
|
675
|
-
FROM public.orders
|
|
676
|
-
WHERE id = p_order_id
|
|
677
|
-
FOR UPDATE;
|
|
678
|
-
|
|
679
|
-
IF NOT FOUND OR v_inventory_deducted_at IS NOT NULL THEN
|
|
680
|
-
RETURN;
|
|
681
|
-
END IF;
|
|
682
|
-
|
|
683
|
-
IF NOT v_track_quantities THEN
|
|
684
|
-
UPDATE public.orders
|
|
685
|
-
SET inventory_deducted_at = now()
|
|
686
|
-
WHERE id = p_order_id;
|
|
687
|
-
|
|
688
|
-
RETURN;
|
|
689
|
-
END IF;
|
|
690
|
-
|
|
691
|
-
FOR v_item IN
|
|
692
|
-
SELECT
|
|
693
|
-
product_id,
|
|
694
|
-
variant_id,
|
|
695
|
-
SUM(quantity)::integer AS quantity
|
|
696
|
-
FROM public.order_items
|
|
697
|
-
WHERE order_id = p_order_id
|
|
698
|
-
GROUP BY product_id, variant_id
|
|
699
|
-
LOOP
|
|
700
|
-
v_sku := NULL;
|
|
701
|
-
v_current_quantity := 0;
|
|
702
|
-
|
|
703
|
-
IF v_item.variant_id IS NOT NULL THEN
|
|
704
|
-
SELECT
|
|
705
|
-
sku,
|
|
706
|
-
GREATEST(COALESCE(stock_quantity, 0), 0)
|
|
707
|
-
INTO v_sku,
|
|
708
|
-
v_current_quantity
|
|
709
|
-
FROM public.product_variants
|
|
710
|
-
WHERE id = v_item.variant_id
|
|
711
|
-
LIMIT 1;
|
|
712
|
-
ELSIF v_item.product_id IS NOT NULL THEN
|
|
713
|
-
SELECT
|
|
714
|
-
sku,
|
|
715
|
-
GREATEST(COALESCE(stock, 0), 0)
|
|
716
|
-
INTO v_sku,
|
|
717
|
-
v_current_quantity
|
|
718
|
-
FROM public.products
|
|
719
|
-
WHERE id = v_item.product_id
|
|
720
|
-
LIMIT 1;
|
|
721
|
-
END IF;
|
|
722
|
-
|
|
723
|
-
IF NULLIF(trim(v_sku), '') IS NULL THEN
|
|
724
|
-
CONTINUE;
|
|
725
|
-
END IF;
|
|
726
|
-
|
|
727
|
-
INSERT INTO public.inventory_items (sku, quantity)
|
|
728
|
-
VALUES (v_sku, v_current_quantity)
|
|
729
|
-
ON CONFLICT (sku) DO NOTHING;
|
|
730
|
-
|
|
731
|
-
UPDATE public.inventory_items
|
|
732
|
-
SET
|
|
733
|
-
quantity = GREATEST(COALESCE(quantity, 0) - v_item.quantity, 0),
|
|
734
|
-
updated_at = now()
|
|
735
|
-
WHERE sku = v_sku;
|
|
736
|
-
END LOOP;
|
|
737
|
-
|
|
738
|
-
UPDATE public.orders
|
|
739
|
-
SET inventory_deducted_at = now()
|
|
740
|
-
WHERE id = p_order_id;
|
|
741
|
-
END;
|
|
742
|
-
$$;
|
|
743
|
-
|
|
744
|
-
DROP TRIGGER IF EXISTS on_inventory_items_update ON public.inventory_items;
|
|
745
|
-
CREATE TRIGGER on_inventory_items_update
|
|
746
|
-
BEFORE UPDATE ON public.inventory_items
|
|
747
|
-
FOR EACH ROW
|
|
748
|
-
EXECUTE FUNCTION public.handle_inventory_items_update();
|
|
749
|
-
|
|
750
|
-
DROP TRIGGER IF EXISTS on_inventory_item_change ON public.inventory_items;
|
|
751
|
-
CREATE TRIGGER on_inventory_item_change
|
|
752
|
-
AFTER INSERT OR UPDATE OF quantity OR DELETE ON public.inventory_items
|
|
753
|
-
FOR EACH ROW
|
|
754
|
-
EXECUTE FUNCTION public.handle_inventory_item_change();
|
|
755
|
-
|
|
756
|
-
GRANT EXECUTE ON FUNCTION public.get_ecommerce_track_quantities() TO authenticated;
|
|
757
|
-
GRANT EXECUTE ON FUNCTION public.get_ecommerce_track_quantities() TO service_role;
|
|
758
|
-
GRANT EXECUTE ON FUNCTION public.generate_order_invoice_number() TO authenticated;
|
|
759
|
-
GRANT EXECUTE ON FUNCTION public.generate_order_invoice_number() TO service_role;
|
|
760
|
-
GRANT EXECUTE ON FUNCTION public.assign_order_invoice_metadata(uuid, timestamptz) TO authenticated;
|
|
761
|
-
GRANT EXECUTE ON FUNCTION public.assign_order_invoice_metadata(uuid, timestamptz) TO service_role;
|
|
762
|
-
GRANT EXECUTE ON FUNCTION public.upsert_product_with_variants(jsonb) TO authenticated;
|
|
763
|
-
GRANT EXECUTE ON FUNCTION public.upsert_product_with_variants(jsonb) TO service_role;
|
|
764
|
-
GRANT EXECUTE ON FUNCTION public.sync_inventory_cache_for_sku(text) TO authenticated;
|
|
765
|
-
GRANT EXECUTE ON FUNCTION public.sync_inventory_cache_for_sku(text) TO service_role;
|
|
766
|
-
GRANT EXECUTE ON FUNCTION public.apply_order_inventory_deduction(uuid) TO authenticated;
|
|
767
|
-
GRANT EXECUTE ON FUNCTION public.apply_order_inventory_deduction(uuid) TO service_role;
|