@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.
Files changed (61) hide show
  1. package/lib/supabase/server.d.ts +9 -0
  2. package/lib/supabase/types.d.ts +9 -0
  3. package/package.json +1 -1
  4. package/supabase/migrations/00000000000000_baseline_schema.sql +2454 -0
  5. package/supabase/migrations/00000000000001_baseline_constraints_and_indexes.sql +1071 -0
  6. package/supabase/migrations/00000000000002_baseline_security_and_grants.sql +1335 -0
  7. package/supabase/migrations/00000000000003_baseline_seed.sql +468 -0
  8. package/supabase/migrations/00000000000004_default_logo_email_safe.sql +24 -0
  9. package/supabase/migrations/00000000000005_add_custom_canonical.sql +13 -0
  10. package/supabase/migrations/00000000000006_home_live_demo_promo.sql +67 -0
  11. package/supabase/templates/confirmation.html +1 -1
  12. package/supabase/templates/email_change.html +1 -1
  13. package/supabase/templates/invite.html +1 -1
  14. package/supabase/templates/magic_link.html +1 -1
  15. package/supabase/templates/reauthentication.html +1 -1
  16. package/supabase/templates/recovery.html +1 -1
  17. package/supabase/migrations/00000000000000_setup_foundation_and_enums.sql +0 -40
  18. package/supabase/migrations/00000000000001_setup_cms_core.sql +0 -124
  19. package/supabase/migrations/00000000000002_setup_content_tables.sql +0 -138
  20. package/supabase/migrations/00000000000003_setup_catalog_and_licensing.sql +0 -160
  21. package/supabase/migrations/00000000000004_setup_fulfillment_shipping_taxes_and_currencies.sql +0 -797
  22. package/supabase/migrations/00000000000005_setup_functions_and_triggers.sql +0 -767
  23. package/supabase/migrations/00000000000006_setup_rls_and_grants.sql +0 -872
  24. package/supabase/migrations/00000000000007_setup_indexes.sql +0 -158
  25. package/supabase/migrations/00000000000008_seed_platform_defaults.sql +0 -155
  26. package/supabase/migrations/00000000000009_seed_translations.sql +0 -801
  27. package/supabase/migrations/00000000000010_seed_content_scaffold.sql +0 -1088
  28. package/supabase/migrations/00000000000011_setup_cortex_ai_settings.sql +0 -81
  29. package/supabase/migrations/00000000000012_setup_commerce_coupons.sql +0 -275
  30. package/supabase/migrations/00000000000013_setup_cortex_ai_db_mutation_audit.sql +0 -47
  31. package/supabase/migrations/00000000000014_setup_content_drafts.sql +0 -119
  32. package/supabase/migrations/00000000000015_setup_product_drafts.sql +0 -97
  33. package/supabase/migrations/00000000000016_add_feature_image_to_pages.sql +0 -8
  34. package/supabase/migrations/00000000000017_add_product_blocks.sql +0 -118
  35. package/supabase/migrations/00000000000018_setup_bot_protection_settings.sql +0 -77
  36. package/supabase/migrations/00000000000019_add_product_categories.sql +0 -64
  37. package/supabase/migrations/00000000000020_add_category_translations.sql +0 -10
  38. package/supabase/migrations/00000000000021_migrate_hero_blocks_to_sections.sql +0 -29
  39. package/supabase/migrations/00000000000022_seed_cortex_ai_guide_post.sql +0 -184
  40. package/supabase/migrations/00000000000023_setup_custom_block_definitions.sql +0 -183
  41. package/supabase/migrations/00000000000024_setup_ucp_cart_sessions.sql +0 -72
  42. package/supabase/migrations/00000000000025_add_sale_schedule_columns.sql +0 -406
  43. package/supabase/migrations/00000000000026_seed_on_sale_translation.sql +0 -9
  44. package/supabase/migrations/00000000000027_setup_privacy_and_mfa.sql +0 -479
  45. package/supabase/migrations/00000000000028_clear_advisor_warnings.sql +0 -93
  46. package/supabase/migrations/00000000000029_refresh_setup_article.sql +0 -252
  47. package/supabase/migrations/00000000000030_setup_system_configuration.sql +0 -80
  48. package/supabase/migrations/00000000000031_seed_footer_navigation.sql +0 -85
  49. package/supabase/migrations/00000000000032_neutralize_seeded_contact_emails.sql +0 -34
  50. package/supabase/migrations/00000000000033_setup_config_settings.sql +0 -99
  51. package/supabase/migrations/00000000000034_enable_staff_2fa_reminder_default.sql +0 -15
  52. package/supabase/migrations/00000000000035_reassert_advisor_fixes.sql +0 -33
  53. package/supabase/migrations/00000000000036_setup_system_alerts.sql +0 -72
  54. package/supabase/migrations/00000000000037_refresh_setup_article_install_paths.sql +0 -317
  55. package/supabase/migrations/00000000000038_seed_consent_banner_translations.sql +0 -126
  56. package/supabase/migrations/00000000000039_setup_interactions.sql +0 -153
  57. package/supabase/migrations/00000000000040_add_profiles_fk_to_interactions.sql +0 -8
  58. package/supabase/migrations/00000000000041_seed_interactions_translations.sql +0 -145
  59. package/supabase/migrations/00000000000042_seed_deploy_to_vercel_ctas.sql +0 -72
  60. package/supabase/migrations/00000000000043_swap_home_hero_media_to_youtube.sql +0 -42
  61. package/supabase/migrations/00000000000044_swap_article_nx_graph_for_youtube.sql +0 -66
@@ -1,72 +0,0 @@
1
- -- 00000000000019_setup_ucp_cart_sessions.sql
2
- -- Persist Universal Commerce Protocol cart sessions for agentic cart handoff.
3
-
4
- CREATE TABLE IF NOT EXISTS public.ucp_cart_sessions (
5
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
6
- status text NOT NULL DEFAULT 'active'
7
- CHECK (status IN ('active', 'cancelled', 'completed')),
8
- currency text NOT NULL DEFAULT 'USD',
9
- locale text,
10
- buyer_identity jsonb NOT NULL DEFAULT '{}'::jsonb,
11
- context jsonb NOT NULL DEFAULT '{}'::jsonb,
12
- signals jsonb NOT NULL DEFAULT '{}'::jsonb,
13
- attribution jsonb NOT NULL DEFAULT '{}'::jsonb,
14
- line_items jsonb NOT NULL DEFAULT '[]'::jsonb,
15
- totals jsonb NOT NULL DEFAULT '[]'::jsonb,
16
- checkout_url text,
17
- metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
18
- created_at timestamptz NOT NULL DEFAULT now(),
19
- updated_at timestamptz NOT NULL DEFAULT now(),
20
- expires_at timestamptz NOT NULL DEFAULT (now() + interval '7 days'),
21
- CONSTRAINT ucp_cart_sessions_buyer_identity_object
22
- CHECK (jsonb_typeof(buyer_identity) = 'object'),
23
- CONSTRAINT ucp_cart_sessions_context_object
24
- CHECK (jsonb_typeof(context) = 'object'),
25
- CONSTRAINT ucp_cart_sessions_signals_object
26
- CHECK (jsonb_typeof(signals) = 'object'),
27
- CONSTRAINT ucp_cart_sessions_attribution_object
28
- CHECK (jsonb_typeof(attribution) = 'object'),
29
- CONSTRAINT ucp_cart_sessions_line_items_array
30
- CHECK (jsonb_typeof(line_items) = 'array'),
31
- CONSTRAINT ucp_cart_sessions_totals_array
32
- CHECK (jsonb_typeof(totals) = 'array'),
33
- CONSTRAINT ucp_cart_sessions_metadata_object
34
- CHECK (jsonb_typeof(metadata) = 'object')
35
- );
36
-
37
- CREATE INDEX IF NOT EXISTS idx_ucp_cart_sessions_status_expires_at
38
- ON public.ucp_cart_sessions (status, expires_at);
39
-
40
- CREATE INDEX IF NOT EXISTS idx_ucp_cart_sessions_created_at
41
- ON public.ucp_cart_sessions (created_at DESC);
42
-
43
- CREATE OR REPLACE FUNCTION public.handle_ucp_cart_sessions_update()
44
- RETURNS trigger AS $$
45
- BEGIN
46
- NEW.updated_at = now();
47
- RETURN NEW;
48
- END;
49
- $$ LANGUAGE plpgsql;
50
-
51
- DROP TRIGGER IF EXISTS trg_handle_ucp_cart_sessions_update
52
- ON public.ucp_cart_sessions;
53
-
54
- CREATE TRIGGER trg_handle_ucp_cart_sessions_update
55
- BEFORE UPDATE ON public.ucp_cart_sessions
56
- FOR EACH ROW
57
- EXECUTE FUNCTION public.handle_ucp_cart_sessions_update();
58
-
59
- ALTER TABLE public.ucp_cart_sessions ENABLE ROW LEVEL SECURITY;
60
-
61
- DROP POLICY IF EXISTS ucp_cart_sessions_service_role_policy
62
- ON public.ucp_cart_sessions;
63
-
64
- CREATE POLICY ucp_cart_sessions_service_role_policy
65
- ON public.ucp_cart_sessions
66
- FOR ALL
67
- TO service_role
68
- USING (true)
69
- WITH CHECK (true);
70
-
71
- GRANT SELECT, INSERT, UPDATE, DELETE ON public.ucp_cart_sessions TO service_role;
72
- GRANT EXECUTE ON FUNCTION public.handle_ucp_cart_sessions_update() TO service_role;
@@ -1,406 +0,0 @@
1
- -- 00000000000025_add_sale_schedule_columns.sql
2
- -- Scheduled pricing for products and variants:
3
- -- * sale_start_at / sale_end_at gate the existing sale_price / sale_prices into a time window.
4
- -- * scheduled_price / scheduled_prices / scheduled_price_at hold a pending permanent
5
- -- regular-price change that takes effect once scheduled_price_at has passed.
6
- -- Enforcement is read-time (see resolveEffectivePriceForCurrency); no cron is required.
7
- -- Also adds a mapping table for auto-generated, time-bounded Freemius sale coupons.
8
-
9
- -- ---------------------------------------------------------------------------
10
- -- 1. Schedule columns on products
11
- -- ---------------------------------------------------------------------------
12
- ALTER TABLE public.products
13
- ADD COLUMN IF NOT EXISTS sale_start_at timestamptz,
14
- ADD COLUMN IF NOT EXISTS sale_end_at timestamptz,
15
- ADD COLUMN IF NOT EXISTS scheduled_price integer,
16
- ADD COLUMN IF NOT EXISTS scheduled_prices jsonb,
17
- ADD COLUMN IF NOT EXISTS scheduled_price_at timestamptz;
18
-
19
- ALTER TABLE public.products
20
- ADD CONSTRAINT products_sale_window_valid
21
- CHECK (sale_start_at IS NULL OR sale_end_at IS NULL OR sale_start_at < sale_end_at);
22
-
23
- COMMENT ON COLUMN public.products.sale_start_at IS
24
- 'Inclusive start of the scheduled sale window (UTC). NULL means no lower bound.';
25
- COMMENT ON COLUMN public.products.sale_end_at IS
26
- 'Exclusive end of the scheduled sale window (UTC). NULL means no upper bound. Both NULL = always-on sale.';
27
- COMMENT ON COLUMN public.products.scheduled_price IS
28
- 'Pending regular price (smallest currency unit) applied once scheduled_price_at has passed.';
29
- COMMENT ON COLUMN public.products.scheduled_prices IS
30
- 'Pending multi-currency regular prices by ISO 4217 code, applied once scheduled_price_at has passed.';
31
- COMMENT ON COLUMN public.products.scheduled_price_at IS
32
- 'Effective timestamp (UTC) for the pending regular-price change.';
33
-
34
- CREATE INDEX IF NOT EXISTS products_sale_window_idx
35
- ON public.products (sale_start_at, sale_end_at)
36
- WHERE sale_start_at IS NOT NULL OR sale_end_at IS NOT NULL;
37
-
38
- CREATE INDEX IF NOT EXISTS products_scheduled_price_idx
39
- ON public.products (scheduled_price_at)
40
- WHERE scheduled_price_at IS NOT NULL;
41
-
42
- -- ---------------------------------------------------------------------------
43
- -- 2. Schedule columns on product_variants
44
- -- ---------------------------------------------------------------------------
45
- ALTER TABLE public.product_variants
46
- ADD COLUMN IF NOT EXISTS sale_start_at timestamptz,
47
- ADD COLUMN IF NOT EXISTS sale_end_at timestamptz,
48
- ADD COLUMN IF NOT EXISTS scheduled_price integer,
49
- ADD COLUMN IF NOT EXISTS scheduled_prices jsonb,
50
- ADD COLUMN IF NOT EXISTS scheduled_price_at timestamptz;
51
-
52
- ALTER TABLE public.product_variants
53
- ADD CONSTRAINT product_variants_sale_window_valid
54
- CHECK (sale_start_at IS NULL OR sale_end_at IS NULL OR sale_start_at < sale_end_at);
55
-
56
- COMMENT ON COLUMN public.product_variants.sale_start_at IS
57
- 'Inclusive start of the scheduled sale window (UTC) for this variant. NULL means no lower bound.';
58
- COMMENT ON COLUMN public.product_variants.sale_end_at IS
59
- 'Exclusive end of the scheduled sale window (UTC) for this variant. NULL means no upper bound.';
60
- COMMENT ON COLUMN public.product_variants.scheduled_price IS
61
- 'Pending regular price (smallest currency unit) applied once scheduled_price_at has passed.';
62
- COMMENT ON COLUMN public.product_variants.scheduled_prices IS
63
- 'Pending multi-currency regular prices by ISO 4217 code, applied once scheduled_price_at has passed.';
64
- COMMENT ON COLUMN public.product_variants.scheduled_price_at IS
65
- 'Effective timestamp (UTC) for the pending regular-price change.';
66
-
67
- CREATE INDEX IF NOT EXISTS product_variants_sale_window_idx
68
- ON public.product_variants (sale_start_at, sale_end_at)
69
- WHERE sale_start_at IS NOT NULL OR sale_end_at IS NOT NULL;
70
-
71
- CREATE INDEX IF NOT EXISTS product_variants_scheduled_price_idx
72
- ON public.product_variants (scheduled_price_at)
73
- WHERE scheduled_price_at IS NOT NULL;
74
-
75
- -- ---------------------------------------------------------------------------
76
- -- 3. Redefine upsert_product_with_variants to persist the sale-window columns.
77
- -- The form write path goes exclusively through this RPC; the scheduled_price*
78
- -- columns are written by the bulk importer via direct service-role UPDATE and
79
- -- therefore are intentionally NOT part of this function.
80
- -- Body copied from migration 00000000000005 with sale_start_at / sale_end_at added.
81
- -- ---------------------------------------------------------------------------
82
- CREATE OR REPLACE FUNCTION public.upsert_product_with_variants(product_payload jsonb)
83
- RETURNS uuid
84
- LANGUAGE plpgsql
85
- SET search_path = public
86
- AS $function$
87
- DECLARE
88
- v_product_id uuid := NULLIF(product_payload->>'id', '')::uuid;
89
- v_translation_group_id uuid := NULLIF(product_payload->>'translation_group_id', '')::uuid;
90
- v_product_type text := CASE
91
- WHEN product_payload->>'product_type' IN ('physical', 'digital') THEN
92
- product_payload->>'product_type'
93
- WHEN NULLIF(product_payload->>'freemius_product_id', '') IS NOT NULL
94
- OR NULLIF(product_payload->>'freemius_plan_id', '') IS NOT NULL THEN
95
- 'digital'
96
- ELSE
97
- 'physical'
98
- END;
99
- v_payment_provider text := CASE
100
- WHEN v_product_type = 'digital' THEN 'freemius'
101
- ELSE 'stripe'
102
- END;
103
- v_variants jsonb := COALESCE(product_payload->'variants', '[]'::jsonb);
104
- v_variant jsonb;
105
- v_variant_id uuid;
106
- v_term_id text;
107
- v_has_variants boolean := jsonb_typeof(v_variants) = 'array' AND jsonb_array_length(v_variants) > 0;
108
- v_total_variant_stock integer := 0;
109
- BEGIN
110
- IF NOT public.is_admin() THEN
111
- RAISE EXCEPTION 'Admin access required';
112
- END IF;
113
-
114
- IF v_has_variants THEN
115
- SELECT COALESCE(SUM(COALESCE((value->>'stock_quantity')::integer, 0)), 0)
116
- INTO v_total_variant_stock
117
- FROM jsonb_array_elements(v_variants);
118
- END IF;
119
-
120
- IF v_product_id IS NULL THEN
121
- INSERT INTO public.products (
122
- title,
123
- slug,
124
- sku,
125
- product_type,
126
- payment_provider,
127
- upc,
128
- stock,
129
- status,
130
- short_description,
131
- description_json,
132
- metadata,
133
- price,
134
- prices,
135
- sale_price,
136
- sale_prices,
137
- sale_start_at,
138
- sale_end_at,
139
- freemius_plan_id,
140
- freemius_product_id,
141
- trial_period_days,
142
- trial_requires_payment_method,
143
- language_id,
144
- translation_group_id
145
- )
146
- VALUES (
147
- product_payload->>'title',
148
- product_payload->>'slug',
149
- product_payload->>'sku',
150
- v_product_type,
151
- v_payment_provider,
152
- NULLIF(product_payload->>'upc', ''),
153
- CASE
154
- WHEN v_has_variants THEN v_total_variant_stock
155
- ELSE COALESCE((product_payload->>'stock')::integer, 0)
156
- END,
157
- COALESCE(product_payload->>'status', 'draft'),
158
- NULLIF(product_payload->>'short_description', ''),
159
- product_payload->'description_json',
160
- COALESCE(product_payload->'metadata', '{}'::jsonb),
161
- COALESCE((product_payload->>'price')::integer, 0),
162
- COALESCE(product_payload->'prices', '{}'::jsonb),
163
- CASE
164
- WHEN product_payload ? 'sale_price' AND product_payload->>'sale_price' <> '' THEN
165
- (product_payload->>'sale_price')::integer
166
- ELSE
167
- NULL
168
- END,
169
- CASE
170
- WHEN product_payload ? 'sale_prices' THEN COALESCE(product_payload->'sale_prices', '{}'::jsonb)
171
- ELSE NULL
172
- END,
173
- CASE
174
- WHEN product_payload ? 'sale_start_at' AND product_payload->>'sale_start_at' <> '' THEN
175
- (product_payload->>'sale_start_at')::timestamptz
176
- ELSE
177
- NULL
178
- END,
179
- CASE
180
- WHEN product_payload ? 'sale_end_at' AND product_payload->>'sale_end_at' <> '' THEN
181
- (product_payload->>'sale_end_at')::timestamptz
182
- ELSE
183
- NULL
184
- END,
185
- NULLIF(product_payload->>'freemius_plan_id', ''),
186
- NULLIF(product_payload->>'freemius_product_id', ''),
187
- COALESCE((product_payload->>'trial_period_days')::integer, 0),
188
- COALESCE((product_payload->>'trial_requires_payment_method')::boolean, false),
189
- (product_payload->>'language_id')::bigint,
190
- COALESCE(v_translation_group_id, gen_random_uuid())
191
- )
192
- RETURNING id INTO v_product_id;
193
- ELSE
194
- UPDATE public.products
195
- SET
196
- title = product_payload->>'title',
197
- slug = product_payload->>'slug',
198
- sku = product_payload->>'sku',
199
- product_type = v_product_type,
200
- payment_provider = v_payment_provider,
201
- upc = NULLIF(product_payload->>'upc', ''),
202
- stock = CASE
203
- WHEN v_has_variants THEN v_total_variant_stock
204
- ELSE COALESCE((product_payload->>'stock')::integer, 0)
205
- END,
206
- status = COALESCE(product_payload->>'status', status),
207
- short_description = NULLIF(product_payload->>'short_description', ''),
208
- description_json = product_payload->'description_json',
209
- metadata = COALESCE(product_payload->'metadata', '{}'::jsonb),
210
- price = COALESCE((product_payload->>'price')::integer, 0),
211
- prices = COALESCE(product_payload->'prices', '{}'::jsonb),
212
- sale_price = CASE
213
- WHEN product_payload ? 'sale_price' AND product_payload->>'sale_price' <> '' THEN
214
- (product_payload->>'sale_price')::integer
215
- ELSE
216
- NULL
217
- END,
218
- sale_prices = CASE
219
- WHEN product_payload ? 'sale_prices' THEN COALESCE(product_payload->'sale_prices', '{}'::jsonb)
220
- ELSE NULL
221
- END,
222
- sale_start_at = CASE
223
- WHEN product_payload ? 'sale_start_at' AND product_payload->>'sale_start_at' <> '' THEN
224
- (product_payload->>'sale_start_at')::timestamptz
225
- ELSE
226
- NULL
227
- END,
228
- sale_end_at = CASE
229
- WHEN product_payload ? 'sale_end_at' AND product_payload->>'sale_end_at' <> '' THEN
230
- (product_payload->>'sale_end_at')::timestamptz
231
- ELSE
232
- NULL
233
- END,
234
- freemius_plan_id = NULLIF(product_payload->>'freemius_plan_id', ''),
235
- freemius_product_id = NULLIF(product_payload->>'freemius_product_id', ''),
236
- trial_period_days = COALESCE((product_payload->>'trial_period_days')::integer, 0),
237
- trial_requires_payment_method = COALESCE((product_payload->>'trial_requires_payment_method')::boolean, false),
238
- language_id = COALESCE((product_payload->>'language_id')::bigint, language_id),
239
- translation_group_id = COALESCE(v_translation_group_id, translation_group_id),
240
- updated_at = now()
241
- WHERE id = v_product_id;
242
-
243
- IF NOT FOUND THEN
244
- RAISE EXCEPTION 'Product not found';
245
- END IF;
246
- END IF;
247
-
248
- DELETE FROM public.variant_attribute_mapping
249
- WHERE variant_id IN (
250
- SELECT id
251
- FROM public.product_variants
252
- WHERE product_id = v_product_id
253
- );
254
-
255
- DELETE FROM public.product_variants
256
- WHERE product_id = v_product_id;
257
-
258
- IF v_has_variants THEN
259
- FOR v_variant IN
260
- SELECT value FROM jsonb_array_elements(v_variants)
261
- LOOP
262
- INSERT INTO public.product_variants (
263
- product_id,
264
- sku,
265
- upc,
266
- price,
267
- prices,
268
- sale_price,
269
- sale_prices,
270
- sale_start_at,
271
- sale_end_at,
272
- stock_quantity,
273
- main_media_id
274
- )
275
- VALUES (
276
- v_product_id,
277
- v_variant->>'sku',
278
- NULLIF(v_variant->>'upc', ''),
279
- COALESCE((v_variant->>'price')::integer, 0),
280
- COALESCE(v_variant->'prices', '{}'::jsonb),
281
- CASE
282
- WHEN v_variant ? 'sale_price' AND v_variant->>'sale_price' <> '' THEN
283
- (v_variant->>'sale_price')::integer
284
- ELSE
285
- NULL
286
- END,
287
- CASE
288
- WHEN v_variant ? 'sale_prices' THEN COALESCE(v_variant->'sale_prices', '{}'::jsonb)
289
- ELSE NULL
290
- END,
291
- CASE
292
- WHEN v_variant ? 'sale_start_at' AND v_variant->>'sale_start_at' <> '' THEN
293
- (v_variant->>'sale_start_at')::timestamptz
294
- ELSE
295
- NULL
296
- END,
297
- CASE
298
- WHEN v_variant ? 'sale_end_at' AND v_variant->>'sale_end_at' <> '' THEN
299
- (v_variant->>'sale_end_at')::timestamptz
300
- ELSE
301
- NULL
302
- END,
303
- COALESCE((v_variant->>'stock_quantity')::integer, 0),
304
- NULLIF(v_variant->>'main_media_id', '')::uuid
305
- )
306
- RETURNING id INTO v_variant_id;
307
-
308
- FOR v_term_id IN
309
- SELECT jsonb_array_elements_text(COALESCE(v_variant->'attribute_term_ids', '[]'::jsonb))
310
- LOOP
311
- INSERT INTO public.variant_attribute_mapping (variant_id, attribute_term_id)
312
- VALUES (v_variant_id, v_term_id::uuid);
313
- END LOOP;
314
- END LOOP;
315
- END IF;
316
-
317
- RETURN v_product_id;
318
- END;
319
- $function$;
320
-
321
- GRANT EXECUTE ON FUNCTION public.upsert_product_with_variants(jsonb) TO authenticated;
322
- GRANT EXECUTE ON FUNCTION public.upsert_product_with_variants(jsonb) TO service_role;
323
-
324
- -- ---------------------------------------------------------------------------
325
- -- 4. Mapping table for auto-generated, time-bounded Freemius sale coupons.
326
- -- One auto-sale coupon per product. Kept separate from the admin `coupons`
327
- -- table so generated sale coupons do not appear in the Coupons UI.
328
- -- ---------------------------------------------------------------------------
329
- CREATE TABLE public.product_freemius_sale_coupons (
330
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
331
- product_id uuid NOT NULL REFERENCES public.products(id) ON DELETE CASCADE,
332
- freemius_product_id text NOT NULL,
333
- freemius_plan_id text,
334
- freemius_coupon_id text,
335
- freemius_coupon_code text NOT NULL,
336
- discount_percent integer,
337
- starts_at timestamptz,
338
- ends_at timestamptz,
339
- is_active boolean NOT NULL DEFAULT false,
340
- sync_status text NOT NULL DEFAULT 'pending',
341
- sync_error text,
342
- remote_payload jsonb,
343
- last_synced_at timestamptz,
344
- created_at timestamptz NOT NULL DEFAULT now(),
345
- updated_at timestamptz NOT NULL DEFAULT now(),
346
- CONSTRAINT product_freemius_sale_coupons_product_unique UNIQUE (product_id),
347
- CONSTRAINT product_freemius_sale_coupons_fm_product_not_blank
348
- CHECK (char_length(btrim(freemius_product_id)) > 0),
349
- CONSTRAINT product_freemius_sale_coupons_code_not_blank
350
- CHECK (char_length(btrim(freemius_coupon_code)) > 0),
351
- CONSTRAINT product_freemius_sale_coupons_discount_valid
352
- CHECK (discount_percent IS NULL OR (discount_percent > 0 AND discount_percent <= 100)),
353
- CONSTRAINT product_freemius_sale_coupons_window_valid
354
- CHECK (starts_at IS NULL OR ends_at IS NULL OR starts_at < ends_at),
355
- CONSTRAINT product_freemius_sale_coupons_sync_status_valid
356
- CHECK (sync_status IN ('pending', 'synced', 'failed', 'deleted'))
357
- );
358
-
359
- COMMENT ON TABLE public.product_freemius_sale_coupons IS
360
- 'Auto-generated, time-bounded Freemius coupons that enforce a scheduled sale on a Freemius product at Freemius-hosted checkout.';
361
-
362
- CREATE INDEX idx_product_freemius_sale_coupons_freemius_product_id
363
- ON public.product_freemius_sale_coupons (freemius_product_id);
364
-
365
- CREATE OR REPLACE FUNCTION public.handle_product_freemius_sale_coupons_write()
366
- RETURNS trigger
367
- LANGUAGE plpgsql
368
- SET search_path = ''
369
- AS $$
370
- BEGIN
371
- NEW.freemius_product_id := btrim(NEW.freemius_product_id);
372
- NEW.freemius_coupon_code := upper(regexp_replace(btrim(NEW.freemius_coupon_code), '\s+', '', 'g'));
373
- NEW.sync_status := lower(btrim(COALESCE(NEW.sync_status, 'pending')));
374
- NEW.updated_at := now();
375
-
376
- IF NEW.created_at IS NULL THEN
377
- NEW.created_at := now();
378
- END IF;
379
-
380
- RETURN NEW;
381
- END;
382
- $$;
383
-
384
- DROP TRIGGER IF EXISTS on_product_freemius_sale_coupons_write ON public.product_freemius_sale_coupons;
385
- CREATE TRIGGER on_product_freemius_sale_coupons_write
386
- BEFORE INSERT OR UPDATE ON public.product_freemius_sale_coupons
387
- FOR EACH ROW
388
- EXECUTE FUNCTION public.handle_product_freemius_sale_coupons_write();
389
-
390
- ALTER TABLE public.product_freemius_sale_coupons ENABLE ROW LEVEL SECURITY;
391
-
392
- GRANT ALL ON public.product_freemius_sale_coupons TO authenticated, service_role;
393
-
394
- CREATE POLICY product_freemius_sale_coupons_admin_policy
395
- ON public.product_freemius_sale_coupons
396
- FOR ALL
397
- TO authenticated
398
- USING (((SELECT public.is_admin()) IS TRUE))
399
- WITH CHECK (((SELECT public.is_admin()) IS TRUE));
400
-
401
- CREATE POLICY product_freemius_sale_coupons_service_role_policy
402
- ON public.product_freemius_sale_coupons
403
- FOR ALL
404
- TO service_role
405
- USING (true)
406
- WITH CHECK (true);
@@ -1,9 +0,0 @@
1
- -- 00000000000026_seed_on_sale_translation.sql
2
- -- Storefront "On Sale" badge label (shown on product cards when a scheduled
3
- -- sale is currently active). Seeded across the supported storefront languages.
4
-
5
- INSERT INTO public.translations (key, translations)
6
- VALUES
7
- ('ecommerce.on_sale', '{"en": "On Sale", "fr": "En solde", "es": "En oferta"}'::jsonb)
8
- ON CONFLICT (key) DO UPDATE
9
- SET translations = EXCLUDED.translations;