@nextblock-cms/db 0.11.2 → 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
|
@@ -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;
|