@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,118 +0,0 @@
|
|
|
1
|
-
-- 00000000000017_add_product_blocks.sql
|
|
2
|
-
-- Migration to support modular block editor for product descriptions.
|
|
3
|
-
|
|
4
|
-
-- 1. Add product_id column to public.blocks
|
|
5
|
-
ALTER TABLE public.blocks
|
|
6
|
-
ADD COLUMN product_id uuid REFERENCES public.products(id) ON DELETE CASCADE;
|
|
7
|
-
|
|
8
|
-
-- 2. Drop the old parent constraint on public.blocks
|
|
9
|
-
ALTER TABLE public.blocks
|
|
10
|
-
DROP CONSTRAINT IF EXISTS check_exactly_one_parent;
|
|
11
|
-
|
|
12
|
-
-- 3. Add the updated parent constraint supporting product_id
|
|
13
|
-
ALTER TABLE public.blocks
|
|
14
|
-
ADD CONSTRAINT check_exactly_one_parent CHECK (
|
|
15
|
-
(page_id IS NOT NULL AND post_id IS NULL AND product_id IS NULL)
|
|
16
|
-
OR (post_id IS NOT NULL AND page_id IS NULL AND product_id IS NULL)
|
|
17
|
-
OR (product_id IS NOT NULL AND page_id IS NULL AND post_id IS NULL)
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
-- 4. Add blocks column to product_drafts table
|
|
21
|
-
ALTER TABLE public.product_drafts
|
|
22
|
-
ADD COLUMN IF NOT EXISTS blocks jsonb NOT NULL DEFAULT '[]'::jsonb;
|
|
23
|
-
|
|
24
|
-
-- 5. Add check constraint to ensure product_drafts.blocks is a JSON array
|
|
25
|
-
ALTER TABLE public.product_drafts
|
|
26
|
-
DROP CONSTRAINT IF EXISTS product_drafts_blocks_array;
|
|
27
|
-
|
|
28
|
-
ALTER TABLE public.product_drafts
|
|
29
|
-
ADD CONSTRAINT product_drafts_blocks_array CHECK (jsonb_typeof(blocks) = 'array');
|
|
30
|
-
|
|
31
|
-
-- 6. Migrate existing product description_json to blocks
|
|
32
|
-
INSERT INTO public.blocks (product_id, language_id, block_type, content, "order")
|
|
33
|
-
SELECT
|
|
34
|
-
id as product_id,
|
|
35
|
-
language_id,
|
|
36
|
-
'text' as block_type,
|
|
37
|
-
jsonb_build_object('html_content', COALESCE(description_json#>>'{}', '')) as content,
|
|
38
|
-
0 as "order"
|
|
39
|
-
FROM public.products
|
|
40
|
-
WHERE description_json IS NOT NULL AND (description_json#>>'{}') <> ''
|
|
41
|
-
ON CONFLICT DO NOTHING;
|
|
42
|
-
|
|
43
|
-
-- 7. Recreate blocks_anon_read_policy
|
|
44
|
-
DROP POLICY IF EXISTS blocks_anon_read_policy ON public.blocks;
|
|
45
|
-
CREATE POLICY blocks_anon_read_policy
|
|
46
|
-
ON public.blocks
|
|
47
|
-
FOR SELECT
|
|
48
|
-
TO anon
|
|
49
|
-
USING (
|
|
50
|
-
(
|
|
51
|
-
page_id IS NOT NULL
|
|
52
|
-
AND EXISTS (
|
|
53
|
-
SELECT 1
|
|
54
|
-
FROM public.pages AS p
|
|
55
|
-
WHERE p.id = blocks.page_id
|
|
56
|
-
AND p.status = 'published'
|
|
57
|
-
)
|
|
58
|
-
)
|
|
59
|
-
OR (
|
|
60
|
-
post_id IS NOT NULL
|
|
61
|
-
AND EXISTS (
|
|
62
|
-
SELECT 1
|
|
63
|
-
FROM public.posts AS pt
|
|
64
|
-
WHERE pt.id = blocks.post_id
|
|
65
|
-
AND pt.status = 'published'
|
|
66
|
-
AND (pt.published_at IS NULL OR pt.published_at <= now())
|
|
67
|
-
)
|
|
68
|
-
)
|
|
69
|
-
OR (
|
|
70
|
-
product_id IS NOT NULL
|
|
71
|
-
AND EXISTS (
|
|
72
|
-
SELECT 1
|
|
73
|
-
FROM public.products AS pr
|
|
74
|
-
WHERE pr.id = blocks.product_id
|
|
75
|
-
AND pr.status = 'active'
|
|
76
|
-
)
|
|
77
|
-
)
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
-- 8. Recreate blocks_read_policy
|
|
81
|
-
DROP POLICY IF EXISTS blocks_read_policy ON public.blocks;
|
|
82
|
-
CREATE POLICY blocks_read_policy
|
|
83
|
-
ON public.blocks
|
|
84
|
-
FOR SELECT
|
|
85
|
-
TO authenticated
|
|
86
|
-
USING (
|
|
87
|
-
((SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER'))
|
|
88
|
-
OR (
|
|
89
|
-
(
|
|
90
|
-
page_id IS NOT NULL
|
|
91
|
-
AND EXISTS (
|
|
92
|
-
SELECT 1
|
|
93
|
-
FROM public.pages AS p
|
|
94
|
-
WHERE p.id = blocks.page_id
|
|
95
|
-
AND p.status = 'published'
|
|
96
|
-
)
|
|
97
|
-
)
|
|
98
|
-
OR (
|
|
99
|
-
post_id IS NOT NULL
|
|
100
|
-
AND EXISTS (
|
|
101
|
-
SELECT 1
|
|
102
|
-
FROM public.posts AS pt
|
|
103
|
-
WHERE pt.id = blocks.post_id
|
|
104
|
-
AND pt.status = 'published'
|
|
105
|
-
AND (pt.published_at IS NULL OR pt.published_at <= now())
|
|
106
|
-
)
|
|
107
|
-
)
|
|
108
|
-
OR (
|
|
109
|
-
product_id IS NOT NULL
|
|
110
|
-
AND EXISTS (
|
|
111
|
-
SELECT 1
|
|
112
|
-
FROM public.products AS pr
|
|
113
|
-
WHERE pr.id = blocks.product_id
|
|
114
|
-
AND pr.status = 'active'
|
|
115
|
-
)
|
|
116
|
-
)
|
|
117
|
-
)
|
|
118
|
-
);
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
-- 00000000000018_setup_bot_protection_settings.sql
|
|
2
|
-
-- NextBlock Bot Protection settings and sensitive keys policy hardening.
|
|
3
|
-
|
|
4
|
-
COMMENT ON TABLE public.site_settings IS 'Key-value store for global site settings. Sensitive keys such as Cortex AI BYOK and Bot Protection Secret Key are protected by row-level policies.';
|
|
5
|
-
|
|
6
|
-
DROP POLICY IF EXISTS site_settings_read_policy ON public.site_settings;
|
|
7
|
-
DROP POLICY IF EXISTS site_settings_insert_policy ON public.site_settings;
|
|
8
|
-
DROP POLICY IF EXISTS site_settings_update_policy ON public.site_settings;
|
|
9
|
-
DROP POLICY IF EXISTS site_settings_delete_policy ON public.site_settings;
|
|
10
|
-
|
|
11
|
-
CREATE POLICY site_settings_read_policy
|
|
12
|
-
ON public.site_settings
|
|
13
|
-
FOR SELECT
|
|
14
|
-
TO public
|
|
15
|
-
USING (
|
|
16
|
-
key NOT IN ('cortex_ai_openrouter_api_key', 'bot_protection_secret')
|
|
17
|
-
OR (
|
|
18
|
-
key IN ('cortex_ai_openrouter_api_key', 'bot_protection_secret')
|
|
19
|
-
AND (SELECT auth.role()) = 'authenticated'
|
|
20
|
-
AND (SELECT public.get_current_user_role()) = 'ADMIN'
|
|
21
|
-
)
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
CREATE POLICY site_settings_insert_policy
|
|
25
|
-
ON public.site_settings
|
|
26
|
-
FOR INSERT
|
|
27
|
-
TO authenticated
|
|
28
|
-
WITH CHECK (
|
|
29
|
-
(
|
|
30
|
-
key NOT IN ('cortex_ai_openrouter_api_key', 'bot_protection_secret')
|
|
31
|
-
AND (SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER')
|
|
32
|
-
)
|
|
33
|
-
OR (
|
|
34
|
-
key IN ('cortex_ai_openrouter_api_key', 'bot_protection_secret')
|
|
35
|
-
AND (SELECT public.get_current_user_role()) = 'ADMIN'
|
|
36
|
-
)
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
CREATE POLICY site_settings_update_policy
|
|
40
|
-
ON public.site_settings
|
|
41
|
-
FOR UPDATE
|
|
42
|
-
TO authenticated
|
|
43
|
-
USING (
|
|
44
|
-
(
|
|
45
|
-
key NOT IN ('cortex_ai_openrouter_api_key', 'bot_protection_secret')
|
|
46
|
-
AND (SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER')
|
|
47
|
-
)
|
|
48
|
-
OR (
|
|
49
|
-
key IN ('cortex_ai_openrouter_api_key', 'bot_protection_secret')
|
|
50
|
-
AND (SELECT public.get_current_user_role()) = 'ADMIN'
|
|
51
|
-
)
|
|
52
|
-
)
|
|
53
|
-
WITH CHECK (
|
|
54
|
-
(
|
|
55
|
-
key NOT IN ('cortex_ai_openrouter_api_key', 'bot_protection_secret')
|
|
56
|
-
AND (SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER')
|
|
57
|
-
)
|
|
58
|
-
OR (
|
|
59
|
-
key IN ('cortex_ai_openrouter_api_key', 'bot_protection_secret')
|
|
60
|
-
AND (SELECT public.get_current_user_role()) = 'ADMIN'
|
|
61
|
-
)
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
CREATE POLICY site_settings_delete_policy
|
|
65
|
-
ON public.site_settings
|
|
66
|
-
FOR DELETE
|
|
67
|
-
TO authenticated
|
|
68
|
-
USING (
|
|
69
|
-
(
|
|
70
|
-
key NOT IN ('cortex_ai_openrouter_api_key', 'bot_protection_secret')
|
|
71
|
-
AND (SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER')
|
|
72
|
-
)
|
|
73
|
-
OR (
|
|
74
|
-
key IN ('cortex_ai_openrouter_api_key', 'bot_protection_secret')
|
|
75
|
-
AND (SELECT public.get_current_user_role()) = 'ADMIN'
|
|
76
|
-
)
|
|
77
|
-
);
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
-- 00000000000019_add_product_categories.sql
|
|
2
|
-
-- Migration adding categories and many-to-many product_categories join table.
|
|
3
|
-
|
|
4
|
-
CREATE TABLE IF NOT EXISTS public.categories (
|
|
5
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
6
|
-
name text NOT NULL,
|
|
7
|
-
slug text NOT NULL UNIQUE,
|
|
8
|
-
description text,
|
|
9
|
-
created_at timestamp with time zone DEFAULT now() NOT NULL
|
|
10
|
-
);
|
|
11
|
-
|
|
12
|
-
CREATE TABLE IF NOT EXISTS public.product_categories (
|
|
13
|
-
product_id uuid NOT NULL REFERENCES public.products(id) ON DELETE CASCADE,
|
|
14
|
-
category_id uuid NOT NULL REFERENCES public.categories(id) ON DELETE CASCADE,
|
|
15
|
-
PRIMARY KEY (product_id, category_id)
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
-- Indexing foreign keys for performance
|
|
19
|
-
CREATE INDEX IF NOT EXISTS idx_product_categories_product_id ON public.product_categories(product_id);
|
|
20
|
-
CREATE INDEX IF NOT EXISTS idx_product_categories_category_id ON public.product_categories(category_id);
|
|
21
|
-
|
|
22
|
-
-- Documenting the tables
|
|
23
|
-
COMMENT ON TABLE public.categories IS 'Product categories for organizing catalog items.';
|
|
24
|
-
COMMENT ON TABLE public.product_categories IS 'Junction table mapping products to multiple categories.';
|
|
25
|
-
|
|
26
|
-
-- Enable Row Level Security (RLS)
|
|
27
|
-
ALTER TABLE public.categories ENABLE ROW LEVEL SECURITY;
|
|
28
|
-
ALTER TABLE public.product_categories ENABLE ROW LEVEL SECURITY;
|
|
29
|
-
|
|
30
|
-
-- Define RLS Policies
|
|
31
|
-
CREATE POLICY "Public can view categories"
|
|
32
|
-
ON public.categories
|
|
33
|
-
FOR SELECT
|
|
34
|
-
TO public
|
|
35
|
-
USING (true);
|
|
36
|
-
|
|
37
|
-
CREATE POLICY "Public can view product_categories"
|
|
38
|
-
ON public.product_categories
|
|
39
|
-
FOR SELECT
|
|
40
|
-
TO public
|
|
41
|
-
USING (true);
|
|
42
|
-
|
|
43
|
-
CREATE POLICY "Admin can manage categories"
|
|
44
|
-
ON public.categories
|
|
45
|
-
FOR ALL
|
|
46
|
-
TO authenticated
|
|
47
|
-
USING (((SELECT public.is_admin()) IS TRUE))
|
|
48
|
-
WITH CHECK (((SELECT public.is_admin()) IS TRUE));
|
|
49
|
-
|
|
50
|
-
CREATE POLICY "Admin can manage product_categories"
|
|
51
|
-
ON public.product_categories
|
|
52
|
-
FOR ALL
|
|
53
|
-
TO authenticated
|
|
54
|
-
USING (((SELECT public.is_admin()) IS TRUE))
|
|
55
|
-
WITH CHECK (((SELECT public.is_admin()) IS TRUE));
|
|
56
|
-
|
|
57
|
-
-- Define Grants for roles
|
|
58
|
-
GRANT SELECT ON public.categories TO anon;
|
|
59
|
-
GRANT ALL ON public.categories TO authenticated;
|
|
60
|
-
GRANT ALL ON public.categories TO service_role;
|
|
61
|
-
|
|
62
|
-
GRANT SELECT ON public.product_categories TO anon;
|
|
63
|
-
GRANT ALL ON public.product_categories TO authenticated;
|
|
64
|
-
GRANT ALL ON public.product_categories TO service_role;
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
-- 00000000000020_add_category_translations.sql
|
|
2
|
-
-- Add name_translations and description_translations columns to public.categories.
|
|
3
|
-
|
|
4
|
-
ALTER TABLE public.categories
|
|
5
|
-
ADD COLUMN IF NOT EXISTS name_translations jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
6
|
-
ADD COLUMN IF NOT EXISTS description_translations jsonb NOT NULL DEFAULT '{}'::jsonb;
|
|
7
|
-
|
|
8
|
-
-- Comment on columns
|
|
9
|
-
COMMENT ON COLUMN public.categories.name_translations IS 'Translated category names (e.g. {"fr": "Numérique"}).';
|
|
10
|
-
COMMENT ON COLUMN public.categories.description_translations IS 'Translated category descriptions.';
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
-- 00000000000021_migrate_hero_blocks_to_sections.sql
|
|
2
|
-
-- Migration to convert 'hero' block types to 'section' block types with {"is_hero": true} content.
|
|
3
|
-
|
|
4
|
-
-- 1. Migrate active blocks
|
|
5
|
-
UPDATE public.blocks
|
|
6
|
-
SET
|
|
7
|
-
block_type = 'section',
|
|
8
|
-
content = COALESCE(content, '{}'::jsonb) || '{"is_hero": true}'::jsonb
|
|
9
|
-
WHERE block_type = 'hero';
|
|
10
|
-
|
|
11
|
-
-- 2. Migrate content drafts containing hero blocks
|
|
12
|
-
UPDATE public.content_drafts
|
|
13
|
-
SET blocks = (
|
|
14
|
-
SELECT COALESCE(
|
|
15
|
-
jsonb_agg(
|
|
16
|
-
CASE
|
|
17
|
-
WHEN elem.value->>'block_type' = 'hero' THEN
|
|
18
|
-
jsonb_set(elem.value, '{block_type}', '"section"'::jsonb) ||
|
|
19
|
-
jsonb_build_object('content', COALESCE(elem.value->'content', '{}'::jsonb) || '{"is_hero": true}'::jsonb)
|
|
20
|
-
ELSE elem.value
|
|
21
|
-
END
|
|
22
|
-
),
|
|
23
|
-
'[]'::jsonb
|
|
24
|
-
)
|
|
25
|
-
FROM jsonb_array_elements(public.content_drafts.blocks) AS elem(value)
|
|
26
|
-
)
|
|
27
|
-
WHERE public.content_drafts.blocks @> '[{"block_type": "hero"}]';
|
|
28
|
-
|
|
29
|
-
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
-- 00000000000022_seed_cortex_ai_guide_post.sql
|
|
2
|
-
-- Adds the Cortex AI guide post linked from the seeded home page CTA.
|
|
3
|
-
|
|
4
|
-
BEGIN;
|
|
5
|
-
|
|
6
|
-
DO $$
|
|
7
|
-
DECLARE
|
|
8
|
-
v_en_lang_id bigint;
|
|
9
|
-
v_cortex_media_id uuid;
|
|
10
|
-
v_cortex_post_id bigint;
|
|
11
|
-
BEGIN
|
|
12
|
-
SELECT id INTO v_en_lang_id
|
|
13
|
-
FROM public.languages
|
|
14
|
-
WHERE code = 'en'
|
|
15
|
-
LIMIT 1;
|
|
16
|
-
|
|
17
|
-
IF v_en_lang_id IS NULL THEN
|
|
18
|
-
RAISE EXCEPTION 'English language not found.';
|
|
19
|
-
END IF;
|
|
20
|
-
|
|
21
|
-
INSERT INTO public.media AS seed_media (
|
|
22
|
-
file_name,
|
|
23
|
-
object_key,
|
|
24
|
-
file_path,
|
|
25
|
-
file_type,
|
|
26
|
-
size_bytes,
|
|
27
|
-
width,
|
|
28
|
-
height,
|
|
29
|
-
folder,
|
|
30
|
-
description
|
|
31
|
-
)
|
|
32
|
-
VALUES (
|
|
33
|
-
'cortex-ai.webp',
|
|
34
|
-
'images/cortex-ai.webp',
|
|
35
|
-
'images/cortex-ai.webp',
|
|
36
|
-
'image/webp',
|
|
37
|
-
298588,
|
|
38
|
-
1024,
|
|
39
|
-
571,
|
|
40
|
-
'images',
|
|
41
|
-
'NextBlock Cortex AI editorial feature image'
|
|
42
|
-
)
|
|
43
|
-
ON CONFLICT (object_key) DO UPDATE
|
|
44
|
-
SET
|
|
45
|
-
file_name = COALESCE(seed_media.file_name, EXCLUDED.file_name),
|
|
46
|
-
file_path = COALESCE(seed_media.file_path, EXCLUDED.file_path),
|
|
47
|
-
file_type = COALESCE(seed_media.file_type, EXCLUDED.file_type),
|
|
48
|
-
size_bytes = COALESCE(seed_media.size_bytes, EXCLUDED.size_bytes),
|
|
49
|
-
width = COALESCE(seed_media.width, EXCLUDED.width),
|
|
50
|
-
height = COALESCE(seed_media.height, EXCLUDED.height),
|
|
51
|
-
folder = COALESCE(seed_media.folder, EXCLUDED.folder),
|
|
52
|
-
description = COALESCE(seed_media.description, EXCLUDED.description),
|
|
53
|
-
updated_at = now()
|
|
54
|
-
RETURNING id INTO v_cortex_media_id;
|
|
55
|
-
|
|
56
|
-
INSERT INTO public.posts (
|
|
57
|
-
language_id,
|
|
58
|
-
title,
|
|
59
|
-
slug,
|
|
60
|
-
label,
|
|
61
|
-
status,
|
|
62
|
-
excerpt,
|
|
63
|
-
subtitle,
|
|
64
|
-
published_at,
|
|
65
|
-
meta_title,
|
|
66
|
-
meta_description,
|
|
67
|
-
translation_group_id,
|
|
68
|
-
feature_image_id
|
|
69
|
-
)
|
|
70
|
-
VALUES (
|
|
71
|
-
v_en_lang_id,
|
|
72
|
-
'NextBlock Cortex AI Guide',
|
|
73
|
-
'nextblock-cortex-ai-guide',
|
|
74
|
-
'AI Copilot',
|
|
75
|
-
'published',
|
|
76
|
-
'A practical guide to Cortex AI, the block-aware assistant for model routing, BYOK controls, and faster editorial production inside NextBlock.',
|
|
77
|
-
'See how Cortex AI brings structured generation, provider choice, and safer content workflows directly into the NextBlock editor.',
|
|
78
|
-
now(),
|
|
79
|
-
'NextBlock Cortex AI Guide',
|
|
80
|
-
'Learn how NextBlock Cortex AI helps teams generate, refine, and translate structured block content with model routing and BYOK controls.',
|
|
81
|
-
gen_random_uuid(),
|
|
82
|
-
v_cortex_media_id
|
|
83
|
-
)
|
|
84
|
-
ON CONFLICT (language_id, slug) DO NOTHING
|
|
85
|
-
RETURNING id INTO v_cortex_post_id;
|
|
86
|
-
|
|
87
|
-
IF v_cortex_post_id IS NULL THEN
|
|
88
|
-
SELECT id INTO v_cortex_post_id
|
|
89
|
-
FROM public.posts
|
|
90
|
-
WHERE language_id = v_en_lang_id
|
|
91
|
-
AND slug = 'nextblock-cortex-ai-guide'
|
|
92
|
-
LIMIT 1;
|
|
93
|
-
|
|
94
|
-
UPDATE public.posts
|
|
95
|
-
SET
|
|
96
|
-
feature_image_id = v_cortex_media_id,
|
|
97
|
-
updated_at = now()
|
|
98
|
-
WHERE id = v_cortex_post_id
|
|
99
|
-
AND feature_image_id IS NULL;
|
|
100
|
-
END IF;
|
|
101
|
-
|
|
102
|
-
IF v_cortex_post_id IS NULL THEN
|
|
103
|
-
RAISE EXCEPTION 'Unable to create or find Cortex AI guide post.';
|
|
104
|
-
END IF;
|
|
105
|
-
|
|
106
|
-
INSERT INTO public.blocks (post_id, language_id, block_type, content, "order")
|
|
107
|
-
SELECT
|
|
108
|
-
v_cortex_post_id,
|
|
109
|
-
v_en_lang_id,
|
|
110
|
-
'text',
|
|
111
|
-
jsonb_build_object('html_content', $cortex_ai_html$
|
|
112
|
-
<p class='text-lg leading-8 text-slate-700 dark:text-slate-300'>NextBlock Cortex AI is the AI layer built for the way NextBlock pages are actually composed. Instead of treating your site like a blank document, it understands blocks, sections, editor constraints, and the model choices your team wants to use.</p>
|
|
113
|
-
|
|
114
|
-
<div class='grid gap-4 md:grid-cols-3 my-10'>
|
|
115
|
-
<div class='rounded-3xl border border-violet-200/70 bg-violet-50/80 p-6 dark:border-violet-500/20 dark:bg-violet-500/10'>
|
|
116
|
-
<p class='text-xs font-semibold uppercase tracking-[0.22em] text-violet-700 dark:text-violet-200'>Model routing</p>
|
|
117
|
-
<h3 class='mt-3 text-xl font-semibold text-slate-900 dark:text-white'>Pick the right model</h3>
|
|
118
|
-
<p class='mt-3 text-sm text-slate-600 dark:text-slate-300'>Route generation through OpenRouter or your configured provider strategy so each task can balance speed, quality, and cost.</p>
|
|
119
|
-
</div>
|
|
120
|
-
<div class='rounded-3xl border border-sky-200/70 bg-sky-50/80 p-6 dark:border-sky-500/20 dark:bg-sky-500/10'>
|
|
121
|
-
<p class='text-xs font-semibold uppercase tracking-[0.22em] text-sky-700 dark:text-sky-200'>BYOK control</p>
|
|
122
|
-
<h3 class='mt-3 text-xl font-semibold text-slate-900 dark:text-white'>Use your own keys</h3>
|
|
123
|
-
<p class='mt-3 text-sm text-slate-600 dark:text-slate-300'>Keep provider credentials under your control while still giving editors a clean, guided AI workflow in the CMS.</p>
|
|
124
|
-
</div>
|
|
125
|
-
<div class='rounded-3xl border border-emerald-200/70 bg-emerald-50/80 p-6 dark:border-emerald-500/20 dark:bg-emerald-500/10'>
|
|
126
|
-
<p class='text-xs font-semibold uppercase tracking-[0.22em] text-emerald-700 dark:text-emerald-200'>Typed output</p>
|
|
127
|
-
<h3 class='mt-3 text-xl font-semibold text-slate-900 dark:text-white'>Generate valid blocks</h3>
|
|
128
|
-
<p class='mt-3 text-sm text-slate-600 dark:text-slate-300'>Schema-aware generation helps AI output land as usable page structure instead of loose copy that needs a rebuild.</p>
|
|
129
|
-
</div>
|
|
130
|
-
</div>
|
|
131
|
-
|
|
132
|
-
<h2>Why Cortex AI Belongs Inside the Editor</h2>
|
|
133
|
-
<p>Generic chat tools can draft copy, but they do not know the difference between a hero, a card grid, a product description, and a localized article. Cortex AI lives closer to the editing surface, so generation can respect the same block contracts that render on the public site.</p>
|
|
134
|
-
<p>That makes AI useful for practical production work: drafting a landing section, tightening an excerpt, expanding a product story, translating a post, or reshaping a rough idea into a block layout that already fits the system.</p>
|
|
135
|
-
|
|
136
|
-
<div class='rounded-[2rem] border border-slate-200/80 bg-slate-50/90 p-6 my-10 dark:border-white/10 dark:bg-slate-900/70'>
|
|
137
|
-
<p class='text-xs font-semibold uppercase tracking-[0.22em] text-violet-700 dark:text-violet-200'>Editorial workflow</p>
|
|
138
|
-
<div class='grid gap-5 md:grid-cols-2 mt-5'>
|
|
139
|
-
<div class='rounded-2xl border border-slate-200 bg-white p-5 dark:border-white/10 dark:bg-slate-950/50'>
|
|
140
|
-
<h3 class='mt-0 text-xl text-slate-900 dark:text-white'>Faster first drafts</h3>
|
|
141
|
-
<p class='text-sm text-slate-600 dark:text-slate-300'>Start with a structured prompt and get a section, article outline, or product narrative that already matches the site voice.</p>
|
|
142
|
-
</div>
|
|
143
|
-
<div class='rounded-2xl border border-slate-200 bg-white p-5 dark:border-white/10 dark:bg-slate-950/50'>
|
|
144
|
-
<h3 class='mt-0 text-xl text-slate-900 dark:text-white'>Cleaner revisions</h3>
|
|
145
|
-
<p class='text-sm text-slate-600 dark:text-slate-300'>Ask for shorter, clearer, more technical, more polished, or locale-ready output without leaving the content screen.</p>
|
|
146
|
-
</div>
|
|
147
|
-
</div>
|
|
148
|
-
</div>
|
|
149
|
-
|
|
150
|
-
<h2>Model Routing and Cost Control</h2>
|
|
151
|
-
<p>Cortex AI is designed around provider choice. Teams can route requests through the configured AI gateway, choose task-appropriate models, and keep bring-your-own-key setups separate from the editor experience. Editors get a simple control surface while developers keep the operational knobs.</p>
|
|
152
|
-
<ul>
|
|
153
|
-
<li>Use fast, inexpensive models for drafts, variations, and rewrites</li>
|
|
154
|
-
<li>Reserve stronger models for long-form strategy, technical copy, or difficult transformations</li>
|
|
155
|
-
<li>Keep provider keys and routing defaults in the server-side configuration layer</li>
|
|
156
|
-
<li>Make cost and quality decisions without changing the public rendering system</li>
|
|
157
|
-
</ul>
|
|
158
|
-
|
|
159
|
-
<h2>Block-Aware Generation</h2>
|
|
160
|
-
<p>The important shift is that Cortex AI is not just writing text. It is meant to produce content that can map back to NextBlock surfaces: section copy, article bodies, product descriptions, headings, calls to action, and localized variants. That reduces the cleanup step that usually happens after copying AI text from a separate tool.</p>
|
|
161
|
-
<p>Because the output is shaped around existing block contracts, the generated content feels native in the editor and predictable on the frontend.</p>
|
|
162
|
-
|
|
163
|
-
<h2>Safer Team Workflows</h2>
|
|
164
|
-
<p>AI works best when it is helpful without becoming invisible infrastructure. Cortex AI keeps humans in the loop: editors review the output, developers control the available providers, and the CMS keeps the generated content inside the same revision and publishing flow as everything else.</p>
|
|
165
|
-
|
|
166
|
-
<h2>A Practical Launch Flow</h2>
|
|
167
|
-
<ol>
|
|
168
|
-
<li>Draft the article, landing section, or product story from a focused prompt.</li>
|
|
169
|
-
<li>Refine the result against the brand voice and target audience.</li>
|
|
170
|
-
<li>Generate a localized version or shorter excerpt for cards and metadata.</li>
|
|
171
|
-
<li>Review the content in the NextBlock editor, publish, and keep iterating through normal revisions.</li>
|
|
172
|
-
</ol>
|
|
173
|
-
|
|
174
|
-
<p>Cortex AI turns the CMS into a more capable production surface: not a replacement for editorial judgment, but a faster way to shape good ideas into structured pages that are ready to ship.</p>
|
|
175
|
-
$cortex_ai_html$),
|
|
176
|
-
0
|
|
177
|
-
WHERE NOT EXISTS (
|
|
178
|
-
SELECT 1
|
|
179
|
-
FROM public.blocks
|
|
180
|
-
WHERE post_id = v_cortex_post_id
|
|
181
|
-
);
|
|
182
|
-
END $$;
|
|
183
|
-
|
|
184
|
-
COMMIT;
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
-- Custom block definition registry for data-rendered user blocks.
|
|
2
|
-
|
|
3
|
-
CREATE OR REPLACE FUNCTION public.is_valid_custom_block_fields(candidate jsonb)
|
|
4
|
-
RETURNS boolean
|
|
5
|
-
LANGUAGE sql
|
|
6
|
-
IMMUTABLE
|
|
7
|
-
SET search_path = ''
|
|
8
|
-
AS $$
|
|
9
|
-
SELECT CASE
|
|
10
|
-
WHEN jsonb_typeof(candidate) <> 'array' THEN false
|
|
11
|
-
ELSE
|
|
12
|
-
NOT EXISTS (
|
|
13
|
-
SELECT 1
|
|
14
|
-
FROM jsonb_array_elements(candidate) AS field(value)
|
|
15
|
-
WHERE jsonb_typeof(field.value) <> 'object'
|
|
16
|
-
OR jsonb_typeof(field.value -> 'key') IS DISTINCT FROM 'string'
|
|
17
|
-
OR jsonb_typeof(field.value -> 'label') IS DISTINCT FROM 'string'
|
|
18
|
-
OR jsonb_typeof(field.value -> 'type') IS DISTINCT FROM 'string'
|
|
19
|
-
OR field.value ->> 'key' !~ '^[a-z][a-z0-9_]*$'
|
|
20
|
-
OR field.value ->> 'type' NOT IN ('text', 'rich-text', 'image_r2', 'db_relation')
|
|
21
|
-
)
|
|
22
|
-
AND (
|
|
23
|
-
SELECT COUNT(*) = COUNT(DISTINCT field.value ->> 'key')
|
|
24
|
-
FROM jsonb_array_elements(candidate) AS field(value)
|
|
25
|
-
)
|
|
26
|
-
END;
|
|
27
|
-
$$;
|
|
28
|
-
|
|
29
|
-
CREATE OR REPLACE FUNCTION public.is_valid_custom_block_layout_schema(candidate jsonb)
|
|
30
|
-
RETURNS boolean
|
|
31
|
-
LANGUAGE sql
|
|
32
|
-
IMMUTABLE
|
|
33
|
-
SET search_path = ''
|
|
34
|
-
AS $$
|
|
35
|
-
SELECT CASE
|
|
36
|
-
WHEN jsonb_typeof(candidate) <> 'object' THEN false
|
|
37
|
-
ELSE candidate ->> 'type' IN ('container', 'field_render')
|
|
38
|
-
END;
|
|
39
|
-
$$;
|
|
40
|
-
|
|
41
|
-
CREATE TABLE IF NOT EXISTS public.custom_block_definitions (
|
|
42
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
43
|
-
slug text NOT NULL UNIQUE CHECK (slug ~ '^[a-z][a-z0-9-]*$'),
|
|
44
|
-
name text NOT NULL CHECK (length(trim(name)) > 0),
|
|
45
|
-
description text NOT NULL DEFAULT '',
|
|
46
|
-
fields jsonb NOT NULL DEFAULT '[]'::jsonb CHECK (public.is_valid_custom_block_fields(fields)),
|
|
47
|
-
layout_schema jsonb NOT NULL CHECK (public.is_valid_custom_block_layout_schema(layout_schema)),
|
|
48
|
-
is_original boolean NOT NULL DEFAULT true
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
COMMENT ON TABLE public.custom_block_definitions IS
|
|
52
|
-
'Registry for user-created block definitions rendered from database JSONB without runtime code compilation.';
|
|
53
|
-
COMMENT ON COLUMN public.custom_block_definitions.fields IS
|
|
54
|
-
'Strict JSONB field declarations for data-rendered custom blocks.';
|
|
55
|
-
COMMENT ON COLUMN public.custom_block_definitions.layout_schema IS
|
|
56
|
-
'Open-ended recursive layout schema consumed by the dynamic layout renderer.';
|
|
57
|
-
COMMENT ON COLUMN public.custom_block_definitions.is_original IS
|
|
58
|
-
'False when a definition was created by duplicating an existing registry row.';
|
|
59
|
-
|
|
60
|
-
CREATE INDEX IF NOT EXISTS idx_custom_block_definitions_is_original
|
|
61
|
-
ON public.custom_block_definitions (is_original);
|
|
62
|
-
|
|
63
|
-
CREATE OR REPLACE FUNCTION public.duplicate_block_definition(target_id uuid)
|
|
64
|
-
RETURNS public.custom_block_definitions
|
|
65
|
-
LANGUAGE plpgsql
|
|
66
|
-
SECURITY DEFINER
|
|
67
|
-
SET search_path = public
|
|
68
|
-
AS $$
|
|
69
|
-
DECLARE
|
|
70
|
-
source_definition public.custom_block_definitions%ROWTYPE;
|
|
71
|
-
copied_definition public.custom_block_definitions%ROWTYPE;
|
|
72
|
-
base_slug text;
|
|
73
|
-
copy_slug text;
|
|
74
|
-
copy_index integer := 1;
|
|
75
|
-
BEGIN
|
|
76
|
-
IF auth.role() <> 'service_role'
|
|
77
|
-
AND COALESCE((SELECT public.get_current_user_role())::text, '') NOT IN ('ADMIN', 'WRITER') THEN
|
|
78
|
-
RAISE EXCEPTION 'Not authorized to duplicate custom block definitions.'
|
|
79
|
-
USING ERRCODE = '42501';
|
|
80
|
-
END IF;
|
|
81
|
-
|
|
82
|
-
SELECT *
|
|
83
|
-
INTO source_definition
|
|
84
|
-
FROM public.custom_block_definitions
|
|
85
|
-
WHERE id = target_id;
|
|
86
|
-
|
|
87
|
-
IF NOT FOUND THEN
|
|
88
|
-
RAISE EXCEPTION 'Custom block definition % not found.', target_id
|
|
89
|
-
USING ERRCODE = 'P0002';
|
|
90
|
-
END IF;
|
|
91
|
-
|
|
92
|
-
base_slug := regexp_replace(source_definition.slug, '-copy(-[0-9]+)?$', '');
|
|
93
|
-
copy_slug := base_slug || '-copy';
|
|
94
|
-
|
|
95
|
-
WHILE EXISTS (
|
|
96
|
-
SELECT 1
|
|
97
|
-
FROM public.custom_block_definitions
|
|
98
|
-
WHERE slug = copy_slug
|
|
99
|
-
) LOOP
|
|
100
|
-
copy_index := copy_index + 1;
|
|
101
|
-
copy_slug := base_slug || '-copy-' || copy_index;
|
|
102
|
-
END LOOP;
|
|
103
|
-
|
|
104
|
-
INSERT INTO public.custom_block_definitions (
|
|
105
|
-
id,
|
|
106
|
-
slug,
|
|
107
|
-
name,
|
|
108
|
-
description,
|
|
109
|
-
fields,
|
|
110
|
-
layout_schema,
|
|
111
|
-
is_original
|
|
112
|
-
)
|
|
113
|
-
VALUES (
|
|
114
|
-
gen_random_uuid(),
|
|
115
|
-
copy_slug,
|
|
116
|
-
source_definition.name || ' Copy',
|
|
117
|
-
source_definition.description,
|
|
118
|
-
source_definition.fields,
|
|
119
|
-
source_definition.layout_schema,
|
|
120
|
-
false
|
|
121
|
-
)
|
|
122
|
-
RETURNING *
|
|
123
|
-
INTO copied_definition;
|
|
124
|
-
|
|
125
|
-
RETURN copied_definition;
|
|
126
|
-
END;
|
|
127
|
-
$$;
|
|
128
|
-
|
|
129
|
-
ALTER TABLE public.custom_block_definitions ENABLE ROW LEVEL SECURITY;
|
|
130
|
-
|
|
131
|
-
GRANT SELECT ON public.custom_block_definitions TO anon, authenticated, service_role;
|
|
132
|
-
GRANT INSERT, UPDATE, DELETE ON public.custom_block_definitions TO authenticated;
|
|
133
|
-
GRANT ALL ON public.custom_block_definitions TO service_role;
|
|
134
|
-
|
|
135
|
-
DROP POLICY IF EXISTS custom_block_definitions_public_read_policy
|
|
136
|
-
ON public.custom_block_definitions;
|
|
137
|
-
|
|
138
|
-
CREATE POLICY custom_block_definitions_public_read_policy
|
|
139
|
-
ON public.custom_block_definitions
|
|
140
|
-
FOR SELECT
|
|
141
|
-
TO public
|
|
142
|
-
USING (true);
|
|
143
|
-
|
|
144
|
-
DROP POLICY IF EXISTS custom_block_definitions_insert_policy
|
|
145
|
-
ON public.custom_block_definitions;
|
|
146
|
-
|
|
147
|
-
CREATE POLICY custom_block_definitions_insert_policy
|
|
148
|
-
ON public.custom_block_definitions
|
|
149
|
-
FOR INSERT
|
|
150
|
-
TO authenticated
|
|
151
|
-
WITH CHECK ((SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER'));
|
|
152
|
-
|
|
153
|
-
DROP POLICY IF EXISTS custom_block_definitions_update_policy
|
|
154
|
-
ON public.custom_block_definitions;
|
|
155
|
-
|
|
156
|
-
CREATE POLICY custom_block_definitions_update_policy
|
|
157
|
-
ON public.custom_block_definitions
|
|
158
|
-
FOR UPDATE
|
|
159
|
-
TO authenticated
|
|
160
|
-
USING ((SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER'))
|
|
161
|
-
WITH CHECK ((SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER'));
|
|
162
|
-
|
|
163
|
-
DROP POLICY IF EXISTS custom_block_definitions_delete_policy
|
|
164
|
-
ON public.custom_block_definitions;
|
|
165
|
-
|
|
166
|
-
CREATE POLICY custom_block_definitions_delete_policy
|
|
167
|
-
ON public.custom_block_definitions
|
|
168
|
-
FOR DELETE
|
|
169
|
-
TO authenticated
|
|
170
|
-
USING ((SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER'));
|
|
171
|
-
|
|
172
|
-
DROP POLICY IF EXISTS custom_block_definitions_service_role_policy
|
|
173
|
-
ON public.custom_block_definitions;
|
|
174
|
-
|
|
175
|
-
CREATE POLICY custom_block_definitions_service_role_policy
|
|
176
|
-
ON public.custom_block_definitions
|
|
177
|
-
FOR ALL
|
|
178
|
-
TO service_role
|
|
179
|
-
USING (true)
|
|
180
|
-
WITH CHECK (true);
|
|
181
|
-
|
|
182
|
-
REVOKE ALL ON FUNCTION public.duplicate_block_definition(uuid) FROM PUBLIC, anon;
|
|
183
|
-
GRANT EXECUTE ON FUNCTION public.duplicate_block_definition(uuid) TO authenticated, service_role;
|