@nextblock-cms/db 0.2.27 → 0.2.28
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/package.json +1 -1
- package/supabase/config.toml +1 -1
- package/supabase/migrations/00000000000014_seed_homepage_blocks.sql +21 -8
- package/supabase/migrations/20250513194738_setup_roles_and_profiles.sql +0 -41
- package/supabase/migrations/20250513194910_auto_create_profile_trigger.sql +0 -48
- package/supabase/migrations/20250513194916_rls_for_profiles.sql +0 -85
- package/supabase/migrations/20250514125634_fix_recursive_rls_policies.sql +0 -51
- package/supabase/migrations/20250514143016_setup_languages_table.sql +0 -66
- package/supabase/migrations/20250514171549_create_pages_table.sql +0 -73
- package/supabase/migrations/20250514171550_create_posts_table.sql +0 -61
- package/supabase/migrations/20250514171552_create_media_table.sql +0 -45
- package/supabase/migrations/20250514171553_create_blocks_table.sql +0 -54
- package/supabase/migrations/20250514171615_create_navigation_table.sql +0 -56
- package/supabase/migrations/20250514171627_rls_policies_for_content_tables.sql +0 -70
- package/supabase/migrations/20250515194800_add_translation_group_id.sql +0 -39
- package/supabase/migrations/20250520171900_add_translation_group_to_nav_items.sql +0 -21
- package/supabase/migrations/20250521143933_seed_homepage_and_nav.sql +0 -52
- package/supabase/migrations/20250523145833_add_feature_image_to_posts.sql +0 -8
- package/supabase/migrations/20250523151737_add_rls_to_media_table.sql +0 -18
- package/supabase/migrations/20250526110400_add_image_dimensions_to_media.sql +0 -14
- package/supabase/migrations/20250526153321_optimize_rls_policies.sql +0 -188
- package/supabase/migrations/20250526172513_resolve_select_policy_overlaps.sql +0 -96
- package/supabase/migrations/20250526172853_resolve_remaining_rls_v5.sql +0 -107
- package/supabase/migrations/20250526173538_finalize_rls_cleanup_v7.sql +0 -110
- package/supabase/migrations/20250526174710_separate_write_policies_v8.sql +0 -147
- package/supabase/migrations/20250526175359_fix_languages_select_rls_v9.sql +0 -81
- package/supabase/migrations/20250526182940_fix_nav_read_policy_v10.sql +0 -27
- package/supabase/migrations/20250526183239_fix_posts_read_rls_v11.sql +0 -59
- package/supabase/migrations/20250526183746_fix_media_select_rls_v12.sql +0 -39
- package/supabase/migrations/20250526184205_consolidate_content_read_rls_v13.sql +0 -61
- package/supabase/migrations/20250526185854_optimize_indexes.sql +0 -47
- package/supabase/migrations/20250526190900_debug_blocks_rls.sql +0 -56
- package/supabase/migrations/20250526191217_consolidate_blocks_select_rls.sql +0 -79
- package/supabase/migrations/20250526192822_fix_handle_languages_update_search_path.sql +0 -32
- package/supabase/migrations/20250527150500_fix_blocks_rls_policy.sql +0 -54
- package/supabase/migrations/20250602150602_add_blur_data_url_to_media.sql +0 -4
- package/supabase/migrations/20250602150959_add_variants_to_media.sql +0 -4
- package/supabase/migrations/20250618124000_create_get_my_claim_function.sql +0 -5
- package/supabase/migrations/20250618124100_create_logos_table.sql +0 -29
- package/supabase/migrations/20250618130000_fix_linter_warnings.sql +0 -58
- package/supabase/migrations/20250618151500_revert_storage_rls.sql +0 -6
- package/supabase/migrations/20250619084800_reinstate_storage_rls.sql +0 -13
- package/supabase/migrations/20250619092430_widen_logo_insert_policy.sql +0 -6
- package/supabase/migrations/20250619093122_fix_get_my_claim_volatility.sql +0 -5
- package/supabase/migrations/20250619104249_consolidated_logo_rls_fix.sql +0 -56
- package/supabase/migrations/20250619110700_fix_logo_rls_again.sql +0 -59
- package/supabase/migrations/20250619113200_add_file_path_to_media.sql +0 -4
- package/supabase/migrations/20250619124100_fix_rls_performance_warnings.sql +0 -74
- package/supabase/migrations/20250619195500_create_site_settings_table.sql +0 -28
- package/supabase/migrations/20250619201500_add_anon_read_to_site_settings.sql +0 -7
- package/supabase/migrations/20250619202000_add_is_active_to_languages.sql +0 -5
- package/supabase/migrations/20250620085700_fix_site_settings_write_rls.sql +0 -27
- package/supabase/migrations/20250620095500_fix_profiles_read_rls.sql +0 -11
- package/supabase/migrations/20250620100000_use_security_definer_for_rls.sql +0 -39
- package/supabase/migrations/20250620130000_add_public_read_to_logos.sql +0 -4
- package/supabase/migrations/20250708091700_create_translations_table.sql +0 -55
- package/supabase/migrations/20250708093403_seed_translations_table.sql +0 -20
- package/supabase/migrations/20250708110600_fix_translations_rls_policies.sql +0 -11
- package/supabase/migrations/20250708112300_add_new_translations.sql +0 -9
- package/supabase/migrations/20250709120000_create_revisions_tables.sql +0 -109
- package/supabase/migrations/20251001113000_add_folder_to_media.sql +0 -14
- package/supabase/migrations/20251112113736_fix_search_path_functions.sql +0 -74
- package/supabase/migrations/20251112124444_fix_rls_performance.sql +0 -63
- package/supabase/migrations/20251112125935_fix_combined_policies.sql +0 -194
- package/supabase/migrations/20251112132146_fix_foreign_key_indexes.sql +0 -21
- package/supabase/migrations/20251112132525_cleanup_unused_indexes.sql +0 -10
- package/supabase/migrations/20251112132822_fix_final_indexes.sql +0 -14
- package/supabase/migrations/20251112140000_scaffold_foundational_content.sql +0 -108
- package/supabase/migrations/20251112141000_seed_homepage_blocks.sql +0 -653
- package/supabase/migrations/20251112142000_seed_how_it_works_post_blocks.sql +0 -100
- package/supabase/migrations/20251112143000_seed_additional_translations.sql +0 -102
- package/supabase/migrations/20251112145000_grant_public_schema_usage.sql +0 -6
- package/supabase/migrations/20251112145500_grant_select_on_public_tables.sql +0 -19
- package/supabase/migrations/20251117093000_add_admin_created_flag.sql +0 -21
- package/supabase/migrations/20251117103000_relax_profile_username_constraint.sql +0 -6
- package/supabase/migrations/20251117110000_relax_profiles_site_settings_rls_for_signup.sql +0 -20
- package/supabase/migrations/20251117112000_fix_handle_new_user_role_enum.sql +0 -45
- package/supabase/migrations/20251117113000_cleanup_rls_duplicates.sql +0 -20
- package/supabase/migrations/20251117200000_media_service_role_insert.sql +0 -14
- package/supabase/migrations/20251117201500_media_service_role_select.sql +0 -11
- package/supabase/migrations/20251117203000_media_admin_writer_select.sql +0 -11
- package/supabase/migrations/20251117204500_fix_media_permissions.sql +0 -43
- package/supabase/migrations/20251126100000_seed_site_logo.sql +0 -33
- package/supabase/migrations/20251126133000_fix_blocks_rls.sql +0 -49
- package/supabase/migrations/20251127110000_fix_blocks_visibility_for_missing_profiles.sql +0 -23
- package/supabase/migrations/20251127120000_fix_duplicate_rls_policies.sql +0 -36
package/package.json
CHANGED
package/supabase/config.toml
CHANGED
|
@@ -105,7 +105,7 @@ file_size_limit = "50MiB"
|
|
|
105
105
|
enabled = true
|
|
106
106
|
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
|
|
107
107
|
# in emails.
|
|
108
|
-
site_url = "
|
|
108
|
+
site_url = "env(NEXT_PUBLIC_URL)"
|
|
109
109
|
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
|
|
110
110
|
additional_redirect_urls = []
|
|
111
111
|
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week).
|
|
@@ -88,7 +88,8 @@ BEGIN
|
|
|
88
88
|
"text": "Get Started",
|
|
89
89
|
"url": "/article/how-nextblock-works",
|
|
90
90
|
"variant": "default",
|
|
91
|
-
"size": "lg"
|
|
91
|
+
"size": "lg",
|
|
92
|
+
"position": "center"
|
|
92
93
|
}
|
|
93
94
|
},
|
|
94
95
|
{
|
|
@@ -97,7 +98,8 @@ BEGIN
|
|
|
97
98
|
"text": "View on GitHub",
|
|
98
99
|
"url": "https://github.com/Webman-Dev/nextblock-monorepo",
|
|
99
100
|
"variant": "outline",
|
|
100
|
-
"size": "lg"
|
|
101
|
+
"size": "lg",
|
|
102
|
+
"position": "center"
|
|
101
103
|
}
|
|
102
104
|
},
|
|
103
105
|
{
|
|
@@ -304,7 +306,8 @@ BEGIN
|
|
|
304
306
|
"text": "Get in Touch",
|
|
305
307
|
"url": "mailto:hello@nextblockcms.com",
|
|
306
308
|
"variant": "default",
|
|
307
|
-
"size": "lg"
|
|
309
|
+
"size": "lg",
|
|
310
|
+
"position": "center"
|
|
308
311
|
}
|
|
309
312
|
}
|
|
310
313
|
]
|
|
@@ -482,8 +485,8 @@ BEGIN
|
|
|
482
485
|
[
|
|
483
486
|
{ "block_type": "text", "content": { "html_content": "<h1 class='text-5xl md:text-6xl font-bold tracking-tight text-white text-center drop-shadow-lg'>Créez des sites <span class='relative inline-block mx-1 group'><span class='absolute inset-0 bg-gradient-to-r from-blue-600 to-cyan-400 translate-y-1 md:translate-y-2 transform -skew-x-12 rounded-sm shadow-lg group-hover:skew-x-0 transition-transform duration-300 ease-out'></span><span class='relative text-white italic px-1'>Ultra-Rapides</span></span><br class='md:hidden' />.</h1>" } },
|
|
484
487
|
{ "block_type": "text", "content": { "html_content": "<p class='text-xl text-slate-300 text-center max-w-3xl mx-auto mt-4 leading-relaxed'>NextBlock est le CMS Next.js open-source alliant scores Lighthouse parfaits et éditeur visuel puissant.</p>" } },
|
|
485
|
-
{ "block_type": "button", "content": { "text": "Commencer", "url": "/article/comment-nextblock-fonctionne", "variant": "default", "size": "lg" } },
|
|
486
|
-
{ "block_type": "button", "content": { "text": "Voir sur GitHub", "url": "https://github.com/Webman-Dev/nextblock-monorepo", "variant": "outline", "size": "lg" } },
|
|
488
|
+
{ "block_type": "button", "content": { "text": "Commencer", "url": "/article/comment-nextblock-fonctionne", "variant": "default", "size": "lg", "position": "center" } },
|
|
489
|
+
{ "block_type": "button", "content": { "text": "Voir sur GitHub", "url": "https://github.com/Webman-Dev/nextblock-monorepo", "variant": "outline", "size": "lg", "position": "center" } },
|
|
487
490
|
{ "block_type": "text", "content": { "html_content": "<div class='flex flex-wrap justify-center gap-6 text-sm uppercase tracking-wide text-slate-400 mt-8'><a href='https://github.com/Webman-Dev' target='_blank' rel='noopener noreferrer' class='hover:text-white transition-colors'>GitHub</a><a href='https://x.com/NextBlockCMS' target='_blank' rel='noopener noreferrer' class='hover:text-white transition-colors'>X</a><a href='https://www.linkedin.com/in/nextblock/' target='_blank' rel='noopener noreferrer' class='hover:text-white transition-colors'>LinkedIn</a><a href='https://dev.to/nextblockcms' target='_blank' rel='noopener noreferrer' class='hover:text-white transition-colors'>Dev.to</a><a href='https://www.npmjs.com/~nextblockcms' target='_blank' rel='noopener noreferrer' class='hover:text-white transition-colors'>npm</a></div>" } }
|
|
488
491
|
],
|
|
489
492
|
[
|
|
@@ -535,7 +538,12 @@ BEGIN
|
|
|
535
538
|
"padding": { "top": "xl", "bottom": "xl" },
|
|
536
539
|
"column_blocks": [
|
|
537
540
|
[
|
|
538
|
-
{
|
|
541
|
+
{
|
|
542
|
+
"block_type": "text",
|
|
543
|
+
"content": {
|
|
544
|
+
"html_content": "<h2 class='text-3xl md:text-4xl font-bold text-white text-center mb-6'>Conçu avec les meilleurs outils.</h2>"
|
|
545
|
+
}
|
|
546
|
+
},
|
|
539
547
|
{ "block_type": "text", "content": { "html_content": "<p class='text-slate-400 text-center max-w-2xl mx-auto'>Chaque couche de NextBlock repose sur des technologies éprouvées pour offrir une expérience familière, performante et fiable.</p><div class='grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-8 gap-4 mt-10 text-sm font-semibold text-center text-white'><div class='p-4 rounded-2xl border border-white/10 bg-white/5 hover:bg-white/10 transition-colors'>Next.js</div><div class='p-4 rounded-2xl border border-white/10 bg-white/5 hover:bg-white/10 transition-colors'>React</div><div class='p-4 rounded-2xl border border-white/10 bg-white/5 hover:bg-white/10 transition-colors'>Supabase</div><div class='p-4 rounded-2xl border border-white/10 bg-white/5 hover:bg-white/10 transition-colors'>Tailwind</div><div class='p-4 rounded-2xl border border-white/10 bg-white/5 hover:bg-white/10 transition-colors'>shadcn/ui</div><div class='p-4 rounded-2xl border border-white/10 bg-white/5 hover:bg-white/10 transition-colors'>Tiptap</div><div class='p-4 rounded-2xl border border-white/10 bg-white/5 hover:bg-white/10 transition-colors'>Vercel</div><div class='p-4 rounded-2xl border border-white/10 bg-white/5 hover:bg-white/10 transition-colors'>Nx</div></div>" } },
|
|
540
548
|
{ "block_type": "text", "content": { "html_content": "<h2 class='text-3xl md:text-4xl font-bold text-white text-center mb-6 mt-16'>Puissant pour les développeurs. Intuitif pour les éditeurs.</h2>" } },
|
|
541
549
|
{ "block_type": "text", "content": { "html_content": "<div class='grid md:grid-cols-2 gap-8 mt-10 text-white'><div class='p-8 rounded-3xl border border-white/10 bg-white/5 backdrop-blur-sm'><h3 class='text-xl font-bold mb-6 text-blue-400'>Pour les créateurs</h3><ul class='space-y-4 text-sm text-slate-300'><li><strong class='text-white block mb-1'>Éditeur de blocs</strong>Glisser-déposer façon Notion.</li><li><strong class='text-white block mb-1'>Blocs riches</strong>Héros, galeries, témoignages.</li><li><strong class='text-white block mb-1'>Médiathèque</strong>Dossiers, tags et actions groupées.</li><li><strong class='text-white block mb-1'>Versions sécurisées</strong>Historique et restauration instantanée.</li></ul></div><div class='p-8 rounded-3xl border border-white/10 bg-gradient-to-br from-white/5 to-white/[0.02] backdrop-blur-sm'><h3 class='text-xl font-bold mb-6 text-purple-400'>Pour les développeurs</h3><ul class='space-y-4 text-sm text-slate-300'><li><strong class='text-white block mb-1'>Next.js 16</strong>Server Components, ISR et Edge prêts à l'emploi.</li><li><strong class='text-white block mb-1'>Supabase</strong>Postgres, auth, stockage, temps réel.</li><li><strong class='text-white block mb-1'>Monorepo Nx</strong>Dépendances lisibles et centrales.</li><li><strong class='text-white block mb-1'>SDK de blocs</strong>Widgets typés et extensibles.</li></ul></div></div>" } }
|
|
@@ -589,9 +597,14 @@ BEGIN
|
|
|
589
597
|
"padding": { "top": "xl", "bottom": "xl" },
|
|
590
598
|
"column_blocks": [
|
|
591
599
|
[
|
|
592
|
-
{
|
|
600
|
+
{
|
|
601
|
+
"block_type": "text",
|
|
602
|
+
"content": {
|
|
603
|
+
"html_content": "<h2 class='text-3xl md:text-4xl font-bold text-center text-white mb-4'>Des questions ?</h2>"
|
|
604
|
+
}
|
|
605
|
+
},
|
|
593
606
|
{ "block_type": "text", "content": { "html_content": "<p class='text-center text-base text-slate-300 max-w-2xl mx-auto'>NextBlock co-construit avec des partenaires : fonctionnalités, modules sponsorisés et direction produit.</p>" } },
|
|
594
|
-
{ "block_type": "button", "content": { "text": "Nous contacter", "url": "mailto:hello@nextblockcms.com", "variant": "default", "size": "lg" } }
|
|
607
|
+
{ "block_type": "button", "content": { "text": "Nous contacter", "url": "mailto:hello@nextblockcms.com", "variant": "default", "size": "lg", "position": "center" } }
|
|
595
608
|
]
|
|
596
609
|
]
|
|
597
610
|
}$$::jsonb,
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
-- lowercase sql
|
|
2
|
-
|
|
3
|
-
-- 1. create the user_role enum type
|
|
4
|
-
create type public.user_role as enum ('ADMIN', 'WRITER', 'USER');
|
|
5
|
-
|
|
6
|
-
-- 2. create the profiles table
|
|
7
|
-
create table public.profiles (
|
|
8
|
-
id uuid not null primary key, -- references auth.users(id)
|
|
9
|
-
updated_at timestamp with time zone,
|
|
10
|
-
username text unique,
|
|
11
|
-
full_name text,
|
|
12
|
-
avatar_url text,
|
|
13
|
-
website text,
|
|
14
|
-
role public.user_role not null default 'USER',
|
|
15
|
-
|
|
16
|
-
constraint username_length check (char_length(username) >= 3)
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
-- 3. set up foreign key from profiles.id to auth.users.id
|
|
20
|
-
alter table public.profiles
|
|
21
|
-
add constraint profiles_id_fkey
|
|
22
|
-
foreign key (id)
|
|
23
|
-
references auth.users (id)
|
|
24
|
-
on delete cascade; -- if a user is deleted, their profile is also deleted
|
|
25
|
-
|
|
26
|
-
-- 4. (optional) add some sample inserts for roles if needed directly, though roles are part of the enum.
|
|
27
|
-
-- users will get roles assigned. an admin user might need to be set manually or via seed.
|
|
28
|
-
-- example: update a specific user to be an admin after they sign up.
|
|
29
|
-
-- update public.profiles set role = 'ADMIN' where id = 'user_id_of_admin';
|
|
30
|
-
|
|
31
|
-
-- 5. (optional) seed an initial admin user if you know their auth.users.id
|
|
32
|
-
-- this requires the user to exist in auth.users first.
|
|
33
|
-
-- insert into public.profiles (id, username, full_name, role)
|
|
34
|
-
-- values ('<some-auth-user-id>', 'admin_user', 'Admin User', 'ADMIN')
|
|
35
|
-
-- on conflict (id) do update set role = 'ADMIN';
|
|
36
|
-
-- note: the trigger in the next migration is preferred for new users.
|
|
37
|
-
-- this manual insert/update is for bootstrapping your first admin.
|
|
38
|
-
|
|
39
|
-
comment on table public.profiles is 'profile information for each user, extending auth.users.';
|
|
40
|
-
comment on column public.profiles.id is 'references auth.users.id';
|
|
41
|
-
comment on column public.profiles.role is 'user role for rbac.';
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
-- This function is triggered when a new user signs up in auth.users
|
|
2
|
-
-- It creates a corresponding profile in public.profiles
|
|
3
|
-
-- and assigns 'ADMIN' to the first user, then 'USER' thereafter, using a KV flag in site_settings.
|
|
4
|
-
|
|
5
|
-
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
|
6
|
-
RETURNS TRIGGER
|
|
7
|
-
LANGUAGE plpgsql
|
|
8
|
-
SECURITY DEFINER
|
|
9
|
-
SET search_path = 'public'
|
|
10
|
-
AS $$
|
|
11
|
-
DECLARE
|
|
12
|
-
admin_flag_set BOOLEAN := FALSE;
|
|
13
|
-
user_role TEXT;
|
|
14
|
-
BEGIN
|
|
15
|
-
-- Ensure the admin flag row exists
|
|
16
|
-
INSERT INTO public.site_settings (key, value)
|
|
17
|
-
VALUES ('is_admin_created', 'false'::jsonb)
|
|
18
|
-
ON CONFLICT (key) DO NOTHING;
|
|
19
|
-
|
|
20
|
-
-- Lock and read the flag
|
|
21
|
-
SELECT COALESCE((value)::jsonb::boolean, FALSE)
|
|
22
|
-
INTO admin_flag_set
|
|
23
|
-
FROM public.site_settings
|
|
24
|
-
WHERE key = 'is_admin_created'
|
|
25
|
-
FOR UPDATE;
|
|
26
|
-
|
|
27
|
-
IF admin_flag_set = FALSE THEN
|
|
28
|
-
user_role := 'ADMIN';
|
|
29
|
-
UPDATE public.site_settings
|
|
30
|
-
SET value = 'true'::jsonb
|
|
31
|
-
WHERE key = 'is_admin_created';
|
|
32
|
-
ELSE
|
|
33
|
-
user_role := 'USER';
|
|
34
|
-
END IF;
|
|
35
|
-
|
|
36
|
-
INSERT INTO public.profiles (id, role)
|
|
37
|
-
VALUES (NEW.id, user_role);
|
|
38
|
-
|
|
39
|
-
RETURN NEW;
|
|
40
|
-
END;
|
|
41
|
-
$$;
|
|
42
|
-
|
|
43
|
-
-- Drop and recreate the trigger to use the updated function
|
|
44
|
-
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
|
|
45
|
-
|
|
46
|
-
CREATE TRIGGER on_auth_user_created
|
|
47
|
-
AFTER INSERT ON auth.users
|
|
48
|
-
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
-- lowercase sql
|
|
2
|
-
|
|
3
|
-
-- 1. enable row level security on the profiles table
|
|
4
|
-
alter table public.profiles enable row level security;
|
|
5
|
-
|
|
6
|
-
-- 2. create policies for profiles table
|
|
7
|
-
|
|
8
|
-
-- allow users to read their own profile
|
|
9
|
-
create policy "users_can_select_own_profile"
|
|
10
|
-
on public.profiles for select
|
|
11
|
-
using (auth.uid() = id);
|
|
12
|
-
|
|
13
|
-
-- allow users to update their own profile
|
|
14
|
-
create policy "users_can_update_own_profile"
|
|
15
|
-
on public.profiles for update
|
|
16
|
-
using (auth.uid() = id)
|
|
17
|
-
with check (auth.uid() = id);
|
|
18
|
-
|
|
19
|
-
-- allow admins to select any profile
|
|
20
|
-
create policy "admins_can_select_any_profile"
|
|
21
|
-
on public.profiles for select
|
|
22
|
-
using (
|
|
23
|
-
exists (
|
|
24
|
-
select 1
|
|
25
|
-
from public.profiles
|
|
26
|
-
where id = auth.uid() and role = 'ADMIN'
|
|
27
|
-
)
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
-- allow admins to update any profile
|
|
31
|
-
create policy "admins_can_update_any_profile"
|
|
32
|
-
on public.profiles for update
|
|
33
|
-
using (
|
|
34
|
-
exists (
|
|
35
|
-
select 1
|
|
36
|
-
from public.profiles
|
|
37
|
-
where id = auth.uid() and role = 'ADMIN'
|
|
38
|
-
)
|
|
39
|
-
)
|
|
40
|
-
with check (
|
|
41
|
-
exists (
|
|
42
|
-
select 1
|
|
43
|
-
from public.profiles
|
|
44
|
-
where id = auth.uid() and role = 'ADMIN'
|
|
45
|
-
)
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
-- allow admins to insert profiles (e.g., for manual setup, though trigger handles new users)
|
|
49
|
-
create policy "admins_can_insert_profiles"
|
|
50
|
-
on public.profiles for insert
|
|
51
|
-
with check (
|
|
52
|
-
exists (
|
|
53
|
-
select 1
|
|
54
|
-
from public.profiles
|
|
55
|
-
where id = auth.uid() and role = 'ADMIN'
|
|
56
|
-
)
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
-- (optional) allow any authenticated user to read any profile if roles need to be widely available
|
|
60
|
-
-- create policy "authenticated_users_can_read_profiles"
|
|
61
|
-
-- on public.profiles for select
|
|
62
|
-
-- to authenticated
|
|
63
|
-
-- using (true);
|
|
64
|
-
-- For now, we'll stick to more restrictive select policies above.
|
|
65
|
-
-- The middleware will need to fetch the current user's role. The "users_can_select_own_profile"
|
|
66
|
-
-- policy allows this. If an admin needs to see other user's roles in a list,
|
|
67
|
-
-- "admins_can_select_any_profile" covers that.
|
|
68
|
-
|
|
69
|
-
comment on policy "users_can_select_own_profile" on public.profiles is 'users can read their own profile.';
|
|
70
|
-
comment on policy "users_can_update_own_profile" on public.profiles is 'users can update their own profile.';
|
|
71
|
-
comment on policy "admins_can_select_any_profile" on public.profiles is 'admin users can read any profile.';
|
|
72
|
-
comment on policy "admins_can_update_any_profile" on public.profiles is 'admin users can update any profile.';
|
|
73
|
-
comment on policy "admins_can_insert_profiles" on public.profiles is 'admin users can insert new profiles.';
|
|
74
|
-
|
|
75
|
-
-- Note on Deletion: Deletion is handled by `on delete cascade` from `auth.users`.
|
|
76
|
-
-- If direct deletion from `profiles` table is needed (e.g., by an admin), a policy would be:
|
|
77
|
-
-- create policy "admins_can_delete_profiles"
|
|
78
|
-
-- on public.profiles for delete
|
|
79
|
-
-- using (
|
|
80
|
-
-- exists (
|
|
81
|
-
-- select 1
|
|
82
|
-
-- from public.profiles
|
|
83
|
-
-- where id = auth.uid() and role = 'ADMIN'
|
|
84
|
-
-- )
|
|
85
|
-
-- );
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
-- Migration to fix recursive RLS policies on the 'profiles' table
|
|
2
|
-
|
|
3
|
-
-- 1. Create a helper function to get the current user's role securely
|
|
4
|
-
-- This function runs with the definer's privileges, avoiding RLS recursion
|
|
5
|
-
-- when called from within an RLS policy.
|
|
6
|
-
create or replace function public.get_current_user_role()
|
|
7
|
-
returns public.user_role -- Your ENUM type for roles
|
|
8
|
-
language sql
|
|
9
|
-
stable -- Indicates the function doesn't modify the database
|
|
10
|
-
security definer
|
|
11
|
-
set search_path = public -- Ensures 'profiles' table is found in 'public' schema
|
|
12
|
-
as $$
|
|
13
|
-
select role from public.profiles where id = auth.uid();
|
|
14
|
-
$$;
|
|
15
|
-
|
|
16
|
-
comment on function public.get_current_user_role() is 'Fetches the role of the currently authenticated user. SECURITY DEFINER to prevent RLS recursion issues when used in policies.';
|
|
17
|
-
|
|
18
|
-
-- 2. Drop the old, problematic RLS policies
|
|
19
|
-
-- It's good practice to drop before creating, even if they might not exist or cause errors if they don't.
|
|
20
|
-
-- The original error means the "admins_can_select_any_profile" policy was indeed created.
|
|
21
|
-
drop policy if exists "admins_can_select_any_profile" on public.profiles;
|
|
22
|
-
drop policy if exists "admins_can_update_any_profile" on public.profiles;
|
|
23
|
-
drop policy if exists "admins_can_insert_profiles" on public.profiles;
|
|
24
|
-
-- Add any other admin policies that used the recursive pattern, e.g., for delete
|
|
25
|
-
-- drop policy if exists "admins_can_delete_profiles" on public.profiles;
|
|
26
|
-
|
|
27
|
-
-- 3. Recreate the admin policies using the helper function
|
|
28
|
-
-- For SELECT: Allows admins to select any profile.
|
|
29
|
-
create policy "admins_can_select_any_profile"
|
|
30
|
-
on public.profiles for select
|
|
31
|
-
using (public.get_current_user_role() = 'ADMIN'); -- Compares ENUM to 'ADMIN' literal
|
|
32
|
-
|
|
33
|
-
-- For UPDATE: Allows admins to update any profile.
|
|
34
|
-
create policy "admins_can_update_any_profile"
|
|
35
|
-
on public.profiles for update
|
|
36
|
-
using (public.get_current_user_role() = 'ADMIN')
|
|
37
|
-
with check (public.get_current_user_role() = 'ADMIN');
|
|
38
|
-
|
|
39
|
-
-- For INSERT: Allows admins to insert profiles (trigger handles new users, this is for manual admin action).
|
|
40
|
-
create policy "admins_can_insert_profiles"
|
|
41
|
-
on public.profiles for insert
|
|
42
|
-
with check (public.get_current_user_role() = 'ADMIN');
|
|
43
|
-
|
|
44
|
-
-- (Optional) If you had an admin delete policy with the recursive pattern:
|
|
45
|
-
-- create policy "admins_can_delete_profiles"
|
|
46
|
-
-- on public.profiles for delete
|
|
47
|
-
-- using (public.get_current_user_role() = 'ADMIN');
|
|
48
|
-
|
|
49
|
-
-- Note: The "users_can_select_own_profile" and "users_can_update_own_profile"
|
|
50
|
-
-- policies from your previous migration are fine and do not need to be changed
|
|
51
|
-
-- as they don't have the recursive subquery pattern.
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
-- lowercase sql
|
|
2
|
-
|
|
3
|
-
-- 1. create the languages table
|
|
4
|
-
create table public.languages (
|
|
5
|
-
id bigint generated by default as identity primary key,
|
|
6
|
-
code text not null unique, -- e.g., 'en', 'fr' (BCP 47 language tags)
|
|
7
|
-
name text not null, -- e.g., 'English', 'Français'
|
|
8
|
-
is_default boolean not null default false,
|
|
9
|
-
created_at timestamp with time zone not null default now(),
|
|
10
|
-
updated_at timestamp with time zone not null default now()
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
comment on table public.languages is 'Stores supported languages for the CMS.';
|
|
14
|
-
comment on column public.languages.code is 'BCP 47 language code (e.g., en, en-US, fr, fr-CA).';
|
|
15
|
-
comment on column public.languages.name is 'Human-readable name of the language.';
|
|
16
|
-
comment on column public.languages.is_default is 'Indicates if this is the default fallback language.';
|
|
17
|
-
|
|
18
|
-
-- 2. create a partial unique index to ensure only one language can be default
|
|
19
|
-
-- This ensures data integrity at the database level for the is_default flag.
|
|
20
|
-
create unique index ensure_single_default_language_idx
|
|
21
|
-
on public.languages (is_default)
|
|
22
|
-
where (is_default = true);
|
|
23
|
-
|
|
24
|
-
-- 3. seed initial languages: english (default) and french
|
|
25
|
-
insert into public.languages (code, name, is_default)
|
|
26
|
-
values
|
|
27
|
-
('en', 'English', true),
|
|
28
|
-
('fr', 'Français', false);
|
|
29
|
-
|
|
30
|
-
-- 4. enable row level security (rls) on the languages table
|
|
31
|
-
alter table public.languages enable row level security;
|
|
32
|
-
|
|
33
|
-
-- 5. create rls policies for the languages table
|
|
34
|
-
-- policy: allow public read access to languages
|
|
35
|
-
create policy "languages_are_publicly_readable"
|
|
36
|
-
on public.languages for select
|
|
37
|
-
to anon, authenticated
|
|
38
|
-
using (true);
|
|
39
|
-
|
|
40
|
-
-- policy: allow admins to manage languages (insert, update, delete)
|
|
41
|
-
create policy "admins_can_manage_languages"
|
|
42
|
-
on public.languages for all -- covers insert, update, delete
|
|
43
|
-
to authenticated
|
|
44
|
-
using (
|
|
45
|
-
-- check if the user is an admin by calling the helper function created in phase 1
|
|
46
|
-
public.get_current_user_role() = 'ADMIN'
|
|
47
|
-
)
|
|
48
|
-
with check (
|
|
49
|
-
public.get_current_user_role() = 'ADMIN'
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
-- (Optional) Trigger to automatically update 'updated_at' timestamp
|
|
53
|
-
create or replace function public.handle_languages_update()
|
|
54
|
-
returns trigger
|
|
55
|
-
language plpgsql
|
|
56
|
-
as $$
|
|
57
|
-
begin
|
|
58
|
-
new.updated_at = now();
|
|
59
|
-
return new;
|
|
60
|
-
end;
|
|
61
|
-
$$;
|
|
62
|
-
|
|
63
|
-
create trigger on_languages_update
|
|
64
|
-
before update on public.languages
|
|
65
|
-
for each row
|
|
66
|
-
execute procedure public.handle_languages_update();
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
-- lowercase sql
|
|
2
|
-
|
|
3
|
-
-- define page_status enum (if not already defined for other tables)
|
|
4
|
-
-- checking if type exists to prevent error if run multiple times or if defined elsewhere
|
|
5
|
-
do $$
|
|
6
|
-
begin
|
|
7
|
-
if not exists (select 1 from pg_type where typname = 'page_status') then
|
|
8
|
-
create type public.page_status as enum ('draft', 'published', 'archived');
|
|
9
|
-
end if;
|
|
10
|
-
end
|
|
11
|
-
$$;
|
|
12
|
-
|
|
13
|
-
-- create pages table
|
|
14
|
-
create table public.pages (
|
|
15
|
-
id bigint generated by default as identity primary key,
|
|
16
|
-
language_id bigint not null references public.languages(id) on delete cascade,
|
|
17
|
-
author_id uuid references public.profiles(id) on delete set null,
|
|
18
|
-
title text not null,
|
|
19
|
-
slug text not null,
|
|
20
|
-
status public.page_status not null default 'draft',
|
|
21
|
-
meta_title text,
|
|
22
|
-
meta_description text,
|
|
23
|
-
created_at timestamp with time zone not null default now(),
|
|
24
|
-
updated_at timestamp with time zone not null default now()
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
comment on table public.pages is 'stores static pages for the website.';
|
|
28
|
-
comment on column public.pages.language_id is 'the language of this page version.';
|
|
29
|
-
comment on column public.pages.author_id is 'the user who originally created the page.';
|
|
30
|
-
comment on column public.pages.slug is 'url-friendly identifier for the page, unique per language.';
|
|
31
|
-
comment on column public.pages.status is 'publication status of the page.';
|
|
32
|
-
comment on column public.pages.meta_title is 'seo title for the page.';
|
|
33
|
-
comment on column public.pages.meta_description is 'seo description for the page.';
|
|
34
|
-
|
|
35
|
-
alter table public.pages
|
|
36
|
-
add constraint pages_language_id_slug_key unique (language_id, slug);
|
|
37
|
-
|
|
38
|
-
alter table public.pages enable row level security;
|
|
39
|
-
|
|
40
|
-
create policy "pages_are_publicly_readable_when_published"
|
|
41
|
-
on public.pages for select
|
|
42
|
-
to anon, authenticated
|
|
43
|
-
using (status = 'published');
|
|
44
|
-
|
|
45
|
-
create policy "authors_writers_admins_can_read_own_drafts"
|
|
46
|
-
on public.pages for select
|
|
47
|
-
to authenticated
|
|
48
|
-
using (
|
|
49
|
-
(status <> 'published' and author_id = auth.uid()) or
|
|
50
|
-
(status <> 'published' and public.get_current_user_role() in ('ADMIN', 'WRITER'))
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
create policy "admins_and_writers_can_manage_pages"
|
|
54
|
-
on public.pages for all
|
|
55
|
-
to authenticated
|
|
56
|
-
using (public.get_current_user_role() in ('ADMIN', 'WRITER'))
|
|
57
|
-
with check (public.get_current_user_role() in ('ADMIN', 'WRITER'));
|
|
58
|
-
|
|
59
|
-
create or replace function public.handle_pages_update()
|
|
60
|
-
returns trigger
|
|
61
|
-
language plpgsql
|
|
62
|
-
security definer set search_path = public
|
|
63
|
-
as $$
|
|
64
|
-
begin
|
|
65
|
-
new.updated_at = now();
|
|
66
|
-
return new;
|
|
67
|
-
end;
|
|
68
|
-
$$;
|
|
69
|
-
|
|
70
|
-
create trigger on_pages_update
|
|
71
|
-
before update on public.pages
|
|
72
|
-
for each row
|
|
73
|
-
execute procedure public.handle_pages_update();
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
-- lowercase sql
|
|
2
|
-
|
|
3
|
-
create table public.posts (
|
|
4
|
-
id bigint generated by default as identity primary key,
|
|
5
|
-
language_id bigint not null references public.languages(id) on delete cascade,
|
|
6
|
-
author_id uuid references public.profiles(id) on delete set null,
|
|
7
|
-
title text not null,
|
|
8
|
-
slug text not null,
|
|
9
|
-
excerpt text,
|
|
10
|
-
status public.page_status not null default 'draft', -- reuse page_status
|
|
11
|
-
published_at timestamp with time zone,
|
|
12
|
-
meta_title text,
|
|
13
|
-
meta_description text,
|
|
14
|
-
created_at timestamp with time zone not null default now(),
|
|
15
|
-
updated_at timestamp with time zone not null default now()
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
comment on table public.posts is 'stores blog posts or news articles.';
|
|
19
|
-
comment on column public.posts.slug is 'url-friendly identifier, unique per language.';
|
|
20
|
-
comment on column public.posts.excerpt is 'a short summary of the post.';
|
|
21
|
-
comment on column public.posts.published_at is 'date and time for publication.';
|
|
22
|
-
|
|
23
|
-
alter table public.posts
|
|
24
|
-
add constraint posts_language_id_slug_key unique (language_id, slug);
|
|
25
|
-
|
|
26
|
-
alter table public.posts enable row level security;
|
|
27
|
-
|
|
28
|
-
create policy "posts_are_publicly_readable_when_published"
|
|
29
|
-
on public.posts for select
|
|
30
|
-
to anon, authenticated
|
|
31
|
-
using (status = 'published' and (published_at is null or published_at <= now()));
|
|
32
|
-
|
|
33
|
-
create policy "authors_writers_admins_can_read_own_draft_posts"
|
|
34
|
-
on public.posts for select
|
|
35
|
-
to authenticated
|
|
36
|
-
using (
|
|
37
|
-
(status <> 'published' and author_id = auth.uid()) or
|
|
38
|
-
(status <> 'published'and public.get_current_user_role() in ('ADMIN', 'WRITER'))
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
create policy "admins_and_writers_can_manage_posts"
|
|
42
|
-
on public.posts for all
|
|
43
|
-
to authenticated
|
|
44
|
-
using (public.get_current_user_role() in ('ADMIN', 'WRITER'))
|
|
45
|
-
with check (public.get_current_user_role() in ('ADMIN', 'WRITER'));
|
|
46
|
-
|
|
47
|
-
create or replace function public.handle_posts_update()
|
|
48
|
-
returns trigger
|
|
49
|
-
language plpgsql
|
|
50
|
-
security definer set search_path = public
|
|
51
|
-
as $$
|
|
52
|
-
begin
|
|
53
|
-
new.updated_at = now();
|
|
54
|
-
return new;
|
|
55
|
-
end;
|
|
56
|
-
$$;
|
|
57
|
-
|
|
58
|
-
create trigger on_posts_update
|
|
59
|
-
before update on public.posts
|
|
60
|
-
for each row
|
|
61
|
-
execute procedure public.handle_posts_update();
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
-- lowercase sql
|
|
2
|
-
|
|
3
|
-
create table public.media (
|
|
4
|
-
id uuid primary key default gen_random_uuid(),
|
|
5
|
-
uploader_id uuid references public.profiles(id) on delete set null,
|
|
6
|
-
file_name text not null,
|
|
7
|
-
object_key text not null unique,
|
|
8
|
-
file_type text,
|
|
9
|
-
size_bytes bigint,
|
|
10
|
-
description text,
|
|
11
|
-
created_at timestamp with time zone not null default now(),
|
|
12
|
-
updated_at timestamp with time zone not null default now()
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
comment on table public.media is 'stores information about uploaded media assets.';
|
|
16
|
-
comment on column public.media.object_key is 'unique key (path) in cloudflare r2.';
|
|
17
|
-
|
|
18
|
-
alter table public.media enable row level security;
|
|
19
|
-
|
|
20
|
-
create policy "media_is_readable_by_all"
|
|
21
|
-
on public.media for select
|
|
22
|
-
to anon, authenticated
|
|
23
|
-
using (true);
|
|
24
|
-
|
|
25
|
-
create policy "admins_and_writers_can_manage_media"
|
|
26
|
-
on public.media for all
|
|
27
|
-
to authenticated
|
|
28
|
-
using (public.get_current_user_role() in ('ADMIN', 'WRITER'))
|
|
29
|
-
with check (public.get_current_user_role() in ('ADMIN', 'WRITER'));
|
|
30
|
-
|
|
31
|
-
create or replace function public.handle_media_update()
|
|
32
|
-
returns trigger
|
|
33
|
-
language plpgsql
|
|
34
|
-
security definer set search_path = public
|
|
35
|
-
as $$
|
|
36
|
-
begin
|
|
37
|
-
new.updated_at = now();
|
|
38
|
-
return new;
|
|
39
|
-
end;
|
|
40
|
-
$$;
|
|
41
|
-
|
|
42
|
-
create trigger on_media_update
|
|
43
|
-
before update on public.media
|
|
44
|
-
for each row
|
|
45
|
-
execute procedure public.handle_media_update();
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
-- lowercase sql
|
|
2
|
-
|
|
3
|
-
create table public.blocks (
|
|
4
|
-
id bigint generated by default as identity primary key,
|
|
5
|
-
page_id bigint references public.pages(id) on delete cascade,
|
|
6
|
-
post_id bigint references public.posts(id) on delete cascade,
|
|
7
|
-
language_id bigint not null references public.languages(id) on delete cascade,
|
|
8
|
-
block_type text not null,
|
|
9
|
-
content jsonb,
|
|
10
|
-
"order" integer not null default 0,
|
|
11
|
-
created_at timestamp with time zone not null default now(),
|
|
12
|
-
updated_at timestamp with time zone not null default now(),
|
|
13
|
-
constraint check_exactly_one_parent check (
|
|
14
|
-
(page_id is not null and post_id is null) or
|
|
15
|
-
(post_id is not null and page_id is null)
|
|
16
|
-
)
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
comment on table public.blocks is 'stores content blocks for pages and posts.';
|
|
20
|
-
comment on column public.blocks.block_type is 'type of the block, e.g., "text", "image".';
|
|
21
|
-
comment on column public.blocks.content is 'jsonb content specific to the block_type.';
|
|
22
|
-
comment on column public.blocks.order is 'sort order of the block.';
|
|
23
|
-
|
|
24
|
-
alter table public.blocks enable row level security;
|
|
25
|
-
|
|
26
|
-
create policy "blocks_are_readable_if_parent_is_published"
|
|
27
|
-
on public.blocks for select
|
|
28
|
-
to anon, authenticated
|
|
29
|
-
using (
|
|
30
|
-
(page_id is not null and exists(select 1 from public.pages p where p.id = blocks.page_id and p.status = 'published')) or
|
|
31
|
-
(post_id is not null and exists(select 1 from public.posts pt where pt.id = blocks.post_id and pt.status = 'published' and (pt.published_at is null or pt.published_at <= now())))
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
create policy "admins_and_writers_can_manage_blocks"
|
|
35
|
-
on public.blocks for all
|
|
36
|
-
to authenticated
|
|
37
|
-
using (public.get_current_user_role() in ('ADMIN', 'WRITER'))
|
|
38
|
-
with check (public.get_current_user_role() in ('ADMIN', 'WRITER'));
|
|
39
|
-
|
|
40
|
-
create or replace function public.handle_blocks_update()
|
|
41
|
-
returns trigger
|
|
42
|
-
language plpgsql
|
|
43
|
-
security definer set search_path = public
|
|
44
|
-
as $$
|
|
45
|
-
begin
|
|
46
|
-
new.updated_at = now();
|
|
47
|
-
return new;
|
|
48
|
-
end;
|
|
49
|
-
$$;
|
|
50
|
-
|
|
51
|
-
create trigger on_blocks_update
|
|
52
|
-
before update on public.blocks
|
|
53
|
-
for each row
|
|
54
|
-
execute procedure public.handle_blocks_update();
|