@nextblock-cms/db 0.2.7 → 0.2.9
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 +319 -0
- package/supabase/migrations/20250513194738_setup_roles_and_profiles.sql +41 -0
- package/supabase/migrations/20250513194910_auto_create_profile_trigger.sql +48 -0
- package/supabase/migrations/20250513194916_rls_for_profiles.sql +85 -0
- package/supabase/migrations/20250514125634_fix_recursive_rls_policies.sql +51 -0
- package/supabase/migrations/20250514143016_setup_languages_table.sql +66 -0
- package/supabase/migrations/20250514171549_create_pages_table.sql +73 -0
- package/supabase/migrations/20250514171550_create_posts_table.sql +61 -0
- package/supabase/migrations/20250514171552_create_media_table.sql +45 -0
- package/supabase/migrations/20250514171553_create_blocks_table.sql +54 -0
- package/supabase/migrations/20250514171615_create_navigation_table.sql +56 -0
- package/supabase/migrations/20250514171627_rls_policies_for_content_tables.sql +70 -0
- package/supabase/migrations/20250515194800_add_translation_group_id.sql +39 -0
- package/supabase/migrations/20250520171900_add_translation_group_to_nav_items.sql +21 -0
- package/supabase/migrations/20250521143933_seed_homepage_and_nav.sql +64 -0
- package/supabase/migrations/20250523145833_add_feature_image_to_posts.sql +8 -0
- package/supabase/migrations/20250523151737_add_rls_to_media_table.sql +18 -0
- package/supabase/migrations/20250526110400_add_image_dimensions_to_media.sql +14 -0
- package/supabase/migrations/20250526153321_optimize_rls_policies.sql +188 -0
- package/supabase/migrations/20250526172513_resolve_select_policy_overlaps.sql +96 -0
- package/supabase/migrations/20250526172853_resolve_remaining_rls_v5.sql +107 -0
- package/supabase/migrations/20250526173538_finalize_rls_cleanup_v7.sql +110 -0
- package/supabase/migrations/20250526174710_separate_write_policies_v8.sql +147 -0
- package/supabase/migrations/20250526175359_fix_languages_select_rls_v9.sql +81 -0
- package/supabase/migrations/20250526182940_fix_nav_read_policy_v10.sql +27 -0
- package/supabase/migrations/20250526183239_fix_posts_read_rls_v11.sql +59 -0
- package/supabase/migrations/20250526183746_fix_media_select_rls_v12.sql +39 -0
- package/supabase/migrations/20250526184205_consolidate_content_read_rls_v13.sql +61 -0
- package/supabase/migrations/20250526185854_optimize_indexes.sql +47 -0
- package/supabase/migrations/20250526190900_debug_blocks_rls.sql +56 -0
- package/supabase/migrations/20250526191217_consolidate_blocks_select_rls.sql +79 -0
- package/supabase/migrations/20250526192822_fix_handle_languages_update_search_path.sql +32 -0
- package/supabase/migrations/20250527150500_fix_blocks_rls_policy.sql +54 -0
- package/supabase/migrations/20250602150602_add_blur_data_url_to_media.sql +4 -0
- package/supabase/migrations/20250602150959_add_variants_to_media.sql +4 -0
- package/supabase/migrations/20250618124000_create_get_my_claim_function.sql +5 -0
- package/supabase/migrations/20250618124100_create_logos_table.sql +29 -0
- package/supabase/migrations/20250618130000_fix_linter_warnings.sql +58 -0
- package/supabase/migrations/20250618151500_revert_storage_rls.sql +6 -0
- package/supabase/migrations/20250619084800_reinstate_storage_rls.sql +13 -0
- package/supabase/migrations/20250619092430_widen_logo_insert_policy.sql +6 -0
- package/supabase/migrations/20250619093122_fix_get_my_claim_volatility.sql +5 -0
- package/supabase/migrations/20250619104249_consolidated_logo_rls_fix.sql +56 -0
- package/supabase/migrations/20250619110700_fix_logo_rls_again.sql +59 -0
- package/supabase/migrations/20250619113200_add_file_path_to_media.sql +4 -0
- package/supabase/migrations/20250619124100_fix_rls_performance_warnings.sql +74 -0
- package/supabase/migrations/20250619195500_create_site_settings_table.sql +28 -0
- package/supabase/migrations/20250619201500_add_anon_read_to_site_settings.sql +7 -0
- package/supabase/migrations/20250619202000_add_is_active_to_languages.sql +5 -0
- package/supabase/migrations/20250620085700_fix_site_settings_write_rls.sql +27 -0
- package/supabase/migrations/20250620095500_fix_profiles_read_rls.sql +11 -0
- package/supabase/migrations/20250620100000_use_security_definer_for_rls.sql +39 -0
- package/supabase/migrations/20250620130000_add_public_read_to_logos.sql +4 -0
- package/supabase/migrations/20250708091700_create_translations_table.sql +55 -0
- package/supabase/migrations/20250708093403_seed_translations_table.sql +20 -0
- package/supabase/migrations/20250708110600_fix_translations_rls_policies.sql +11 -0
- package/supabase/migrations/20250708112300_add_new_translations.sql +9 -0
- package/supabase/migrations/20250709120000_create_revisions_tables.sql +109 -0
- package/supabase/migrations/20251001113000_add_folder_to_media.sql +14 -0
- package/supabase/migrations/20251112113736_fix_search_path_functions.sql +74 -0
- package/supabase/migrations/20251112124444_fix_rls_performance.sql +63 -0
- package/supabase/migrations/20251112125935_fix_combined_policies.sql +194 -0
- package/supabase/migrations/20251112132146_fix_foreign_key_indexes.sql +21 -0
- package/supabase/migrations/20251112132525_cleanup_unused_indexes.sql +10 -0
- package/supabase/migrations/20251112132822_fix_final_indexes.sql +14 -0
- package/supabase/migrations/20251112140000_scaffold_foundational_content.sql +95 -0
- package/supabase/migrations/20251112141000_seed_homepage_blocks.sql +656 -0
- package/supabase/migrations/20251112142000_seed_how_it_works_post_blocks.sql +100 -0
- package/supabase/migrations/20251112143000_seed_additional_translations.sql +102 -0
- package/supabase/migrations/20251112145000_grant_public_schema_usage.sql +6 -0
- package/supabase/migrations/20251112145500_grant_select_on_public_tables.sql +19 -0
- package/supabase/migrations/20251117093000_add_admin_created_flag.sql +21 -0
- package/supabase/migrations/20251117103000_relax_profile_username_constraint.sql +6 -0
- package/supabase/migrations/20251117110000_relax_profiles_site_settings_rls_for_signup.sql +20 -0
- package/supabase/migrations/20251117112000_fix_handle_new_user_role_enum.sql +45 -0
- package/supabase/migrations/20251117113000_cleanup_rls_duplicates.sql +20 -0
- package/supabase/migrations/20251117200000_media_service_role_insert.sql +14 -0
- package/supabase/migrations/20251117201500_media_service_role_select.sql +11 -0
- package/supabase/migrations/20251117203000_media_admin_writer_select.sql +11 -0
- package/supabase/migrations/20251117204500_fix_media_permissions.sql +43 -0
|
@@ -0,0 +1,73 @@
|
|
|
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();
|
|
@@ -0,0 +1,61 @@
|
|
|
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();
|
|
@@ -0,0 +1,45 @@
|
|
|
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();
|
|
@@ -0,0 +1,54 @@
|
|
|
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();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
-- lowercase sql
|
|
2
|
+
|
|
3
|
+
do $$
|
|
4
|
+
begin
|
|
5
|
+
if not exists (select 1 from pg_type where typname = 'menu_location') then
|
|
6
|
+
create type public.menu_location as enum ('HEADER', 'FOOTER', 'SIDEBAR');
|
|
7
|
+
end if;
|
|
8
|
+
end
|
|
9
|
+
$$;
|
|
10
|
+
|
|
11
|
+
create table public.navigation_items (
|
|
12
|
+
id bigint generated by default as identity primary key,
|
|
13
|
+
language_id bigint not null references public.languages(id) on delete cascade,
|
|
14
|
+
menu_key public.menu_location not null,
|
|
15
|
+
label text not null,
|
|
16
|
+
url text not null,
|
|
17
|
+
parent_id bigint references public.navigation_items(id) on delete cascade,
|
|
18
|
+
"order" integer not null default 0,
|
|
19
|
+
page_id bigint references public.pages(id) on delete set null,
|
|
20
|
+
created_at timestamp with time zone not null default now(),
|
|
21
|
+
updated_at timestamp with time zone not null default now()
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
comment on table public.navigation_items is 'stores navigation menu items.';
|
|
25
|
+
comment on column public.navigation_items.menu_key is 'identifies the menu this item belongs to.';
|
|
26
|
+
|
|
27
|
+
create index idx_navigation_items_menu_lang_order on public.navigation_items (menu_key, language_id, "order");
|
|
28
|
+
|
|
29
|
+
alter table public.navigation_items enable row level security;
|
|
30
|
+
|
|
31
|
+
create policy "navigation_is_publicly_readable"
|
|
32
|
+
on public.navigation_items for select
|
|
33
|
+
to anon, authenticated
|
|
34
|
+
using (true);
|
|
35
|
+
|
|
36
|
+
create policy "admins_can_manage_navigation"
|
|
37
|
+
on public.navigation_items for all
|
|
38
|
+
to authenticated
|
|
39
|
+
using (public.get_current_user_role() = 'ADMIN')
|
|
40
|
+
with check (public.get_current_user_role() = 'ADMIN');
|
|
41
|
+
|
|
42
|
+
create or replace function public.handle_navigation_items_update()
|
|
43
|
+
returns trigger
|
|
44
|
+
language plpgsql
|
|
45
|
+
security definer set search_path = public
|
|
46
|
+
as $$
|
|
47
|
+
begin
|
|
48
|
+
new.updated_at = now();
|
|
49
|
+
return new;
|
|
50
|
+
end;
|
|
51
|
+
$$;
|
|
52
|
+
|
|
53
|
+
create trigger on_navigation_items_update
|
|
54
|
+
before update on public.navigation_items
|
|
55
|
+
for each row
|
|
56
|
+
execute procedure public.handle_navigation_items_update();
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
-- supabase/migrations/YYYYMMDDHHMMSS_rls_policies_for_content_tables.sql
|
|
2
|
+
-- lowercase sql
|
|
3
|
+
|
|
4
|
+
begin;
|
|
5
|
+
|
|
6
|
+
--
|
|
7
|
+
-- Pages Table RLS
|
|
8
|
+
--
|
|
9
|
+
alter table public.pages enable row level security;
|
|
10
|
+
|
|
11
|
+
drop policy if exists "authors_writers_admins_can_read_own_or_all_drafts" on public.pages;
|
|
12
|
+
-- allow authenticated users (authors, writers, admins) to read their own or all non-published pages
|
|
13
|
+
create policy "authors_writers_admins_can_read_own_or_all_drafts"
|
|
14
|
+
on public.pages for select
|
|
15
|
+
to authenticated
|
|
16
|
+
using (
|
|
17
|
+
(status <> 'published' and author_id = auth.uid()) or -- author can read their own non-published
|
|
18
|
+
(status <> 'published' and public.get_current_user_role() in ('ADMIN', 'WRITER')) -- admins/writers can read all non-published
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
--
|
|
22
|
+
-- Posts Table RLS
|
|
23
|
+
--
|
|
24
|
+
alter table public.posts enable row level security;
|
|
25
|
+
|
|
26
|
+
drop policy if exists "authors_writers_admins_can_read_own_or_all_draft_posts" on public.posts;
|
|
27
|
+
-- allow authenticated users (authors, writers, admins) to read their own or all non-published posts
|
|
28
|
+
create policy "authors_writers_admins_can_read_own_or_all_draft_posts"
|
|
29
|
+
on public.posts for select
|
|
30
|
+
to authenticated
|
|
31
|
+
using (
|
|
32
|
+
(status <> 'published' and author_id = auth.uid()) or
|
|
33
|
+
(status <> 'published'and public.get_current_user_role() in ('ADMIN', 'WRITER'))
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
--
|
|
37
|
+
-- Media Table RLS
|
|
38
|
+
--
|
|
39
|
+
alter table public.media enable row level security;
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
--
|
|
43
|
+
-- Blocks Table RLS
|
|
44
|
+
--
|
|
45
|
+
alter table public.blocks enable row level security;
|
|
46
|
+
|
|
47
|
+
drop policy if exists "blocks_are_readable_if_parent_is_published" on public.blocks;
|
|
48
|
+
-- allow anonymous and authenticated users to read blocks if their parent page/post is published
|
|
49
|
+
create policy "blocks_are_readable_if_parent_is_published"
|
|
50
|
+
on public.blocks for select
|
|
51
|
+
to anon, authenticated
|
|
52
|
+
using (
|
|
53
|
+
(page_id is not null and exists(select 1 from public.pages p where p.id = blocks.page_id and p.status = 'published')) or
|
|
54
|
+
(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())))
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
-- allow admins and writers to insert, update, delete blocks
|
|
58
|
+
drop policy if exists "admins_and_writers_can_manage_blocks" on public.blocks;
|
|
59
|
+
create policy "admins_and_writers_can_manage_blocks"
|
|
60
|
+
on public.blocks for all
|
|
61
|
+
to authenticated
|
|
62
|
+
using (public.get_current_user_role() in ('ADMIN', 'WRITER'))
|
|
63
|
+
with check (public.get_current_user_role() in ('ADMIN', 'WRITER'));
|
|
64
|
+
|
|
65
|
+
--
|
|
66
|
+
-- Navigation Items Table RLS
|
|
67
|
+
--
|
|
68
|
+
alter table public.navigation_items enable row level security;
|
|
69
|
+
|
|
70
|
+
commit;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
-- supabase/migrations/YYYYMMDDHHMMSS_add_translation_group_id.sql
|
|
2
|
+
|
|
3
|
+
ALTER TABLE public.pages
|
|
4
|
+
ADD COLUMN translation_group_id UUID DEFAULT gen_random_uuid() NOT NULL;
|
|
5
|
+
|
|
6
|
+
COMMENT ON COLUMN public.pages.translation_group_id IS 'Groups different language versions of the same conceptual page.';
|
|
7
|
+
|
|
8
|
+
CREATE INDEX IF NOT EXISTS idx_pages_translation_group_id ON public.pages(translation_group_id);
|
|
9
|
+
|
|
10
|
+
-- For existing pages, you'll need to manually group them.
|
|
11
|
+
-- Example: If page ID 1 (EN) and page ID 10 (FR) are the same conceptual page:
|
|
12
|
+
-- UPDATE public.pages SET translation_group_id = (SELECT translation_group_id FROM public.pages WHERE id = 1) WHERE id = 10;
|
|
13
|
+
-- Or, for all pages that share a slug currently (from the previous model):
|
|
14
|
+
-- WITH slug_groups AS (
|
|
15
|
+
-- SELECT slug, MIN(id) as first_id, gen_random_uuid() as new_group_id
|
|
16
|
+
-- FROM public.pages
|
|
17
|
+
-- GROUP BY slug
|
|
18
|
+
-- HAVING COUNT(*) > 1 -- Only for slugs that were shared
|
|
19
|
+
-- )
|
|
20
|
+
-- UPDATE public.pages p
|
|
21
|
+
-- SET translation_group_id = sg.new_group_id
|
|
22
|
+
-- FROM slug_groups sg
|
|
23
|
+
-- WHERE p.slug = sg.slug;
|
|
24
|
+
--
|
|
25
|
+
-- UPDATE public.pages p
|
|
26
|
+
-- SET translation_group_id = gen_random_uuid()
|
|
27
|
+
-- WHERE p.translation_group_id IS NULL AND EXISTS (
|
|
28
|
+
-- SELECT 1 FROM (
|
|
29
|
+
-- SELECT slug, COUNT(*) as c FROM public.pages GROUP BY slug
|
|
30
|
+
-- ) counts WHERE counts.slug = p.slug AND counts.c = 1
|
|
31
|
+
-- );
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
ALTER TABLE public.posts
|
|
35
|
+
ADD COLUMN translation_group_id UUID DEFAULT gen_random_uuid() NOT NULL;
|
|
36
|
+
|
|
37
|
+
COMMENT ON COLUMN public.posts.translation_group_id IS 'Groups different language versions of the same conceptual post.';
|
|
38
|
+
|
|
39
|
+
CREATE INDEX IF NOT EXISTS idx_posts_translation_group_id ON public.posts(translation_group_id);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-- supabase/migrations/YYYYMMDDHHMMSS_add_translation_group_to_nav_items.sql
|
|
2
|
+
-- Replace YYYYMMDDHHMMSS with the actual timestamp, e.g., 20250520171700
|
|
3
|
+
|
|
4
|
+
ALTER TABLE public.navigation_items
|
|
5
|
+
ADD COLUMN translation_group_id UUID DEFAULT gen_random_uuid() NOT NULL;
|
|
6
|
+
|
|
7
|
+
COMMENT ON COLUMN public.navigation_items.translation_group_id IS 'Groups different language versions of the same conceptual navigation item.';
|
|
8
|
+
|
|
9
|
+
CREATE INDEX IF NOT EXISTS idx_navigation_items_translation_group_id ON public.navigation_items(translation_group_id);
|
|
10
|
+
|
|
11
|
+
-- Note: For existing navigation items, you will need to manually group them if they are translations of each other.
|
|
12
|
+
-- For example, if item ID 5 (EN) and item ID 25 (FR) are the same conceptual link:
|
|
13
|
+
-- UPDATE public.navigation_items SET translation_group_id = (SELECT translation_group_id FROM public.navigation_items WHERE id = 5 LIMIT 1) WHERE id = 25;
|
|
14
|
+
-- Or, assign a new group ID to both if they weren't grouped yet:
|
|
15
|
+
-- WITH new_group AS (SELECT gen_random_uuid() as new_id)
|
|
16
|
+
-- UPDATE public.navigation_items
|
|
17
|
+
-- SET translation_group_id = (SELECT new_id FROM new_group)
|
|
18
|
+
-- WHERE id IN (5, 25);
|
|
19
|
+
--
|
|
20
|
+
-- It's recommended to do this grouping manually based on your existing data logic.
|
|
21
|
+
-- New items created through the updated CMS logic will automatically get grouped.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
-- supabase/migrations/YYYYMMDDHHMMSS_seed_homepage_and_nav.sql
|
|
2
|
+
-- Replace YYYYMMDDHHMMSS with the actual timestamp, e.g., 20250521100000
|
|
3
|
+
|
|
4
|
+
DO $$
|
|
5
|
+
DECLARE
|
|
6
|
+
en_lang_id BIGINT;
|
|
7
|
+
fr_lang_id BIGINT;
|
|
8
|
+
admin_user_id UUID;
|
|
9
|
+
home_page_translation_group UUID;
|
|
10
|
+
home_nav_translation_group UUID;
|
|
11
|
+
en_home_page_id BIGINT;
|
|
12
|
+
fr_home_page_id BIGINT;
|
|
13
|
+
BEGIN
|
|
14
|
+
-- Get language IDs
|
|
15
|
+
SELECT id INTO en_lang_id FROM public.languages WHERE code = 'en' LIMIT 1;
|
|
16
|
+
SELECT id INTO fr_lang_id FROM public.languages WHERE code = 'fr' LIMIT 1;
|
|
17
|
+
|
|
18
|
+
-- Get an admin user ID to set as author (optional, fallback to NULL)
|
|
19
|
+
SELECT id INTO admin_user_id FROM public.profiles WHERE role = 'ADMIN' LIMIT 1;
|
|
20
|
+
|
|
21
|
+
-- Check if languages were found
|
|
22
|
+
IF en_lang_id IS NULL THEN
|
|
23
|
+
RAISE EXCEPTION 'English language (en) not found. Please seed languages first.';
|
|
24
|
+
END IF;
|
|
25
|
+
IF fr_lang_id IS NULL THEN
|
|
26
|
+
RAISE EXCEPTION 'French language (fr) not found. Please seed languages first.';
|
|
27
|
+
END IF;
|
|
28
|
+
|
|
29
|
+
-- Generate translation group UUIDs
|
|
30
|
+
home_page_translation_group := gen_random_uuid();
|
|
31
|
+
home_nav_translation_group := gen_random_uuid();
|
|
32
|
+
|
|
33
|
+
-- Seed English Homepage
|
|
34
|
+
INSERT INTO public.pages (language_id, author_id, title, slug, status, meta_title, meta_description, translation_group_id)
|
|
35
|
+
VALUES (en_lang_id, admin_user_id, 'Home', 'home', 'published', 'Homepage', 'This is the homepage.', home_page_translation_group)
|
|
36
|
+
RETURNING id INTO en_home_page_id;
|
|
37
|
+
|
|
38
|
+
-- Seed French Homepage (Accueil)
|
|
39
|
+
INSERT INTO public.pages (language_id, author_id, title, slug, status, meta_title, meta_description, translation_group_id)
|
|
40
|
+
VALUES (fr_lang_id, admin_user_id, 'Accueil', 'accueil', 'published', 'Page d''accueil', 'Ceci est la page d''accueil.', home_page_translation_group)
|
|
41
|
+
RETURNING id INTO fr_home_page_id;
|
|
42
|
+
|
|
43
|
+
-- Seed initial content block for English Homepage (optional)
|
|
44
|
+
IF en_home_page_id IS NOT NULL THEN
|
|
45
|
+
INSERT INTO public.blocks (page_id, language_id, block_type, content, "order")
|
|
46
|
+
VALUES (en_home_page_id, en_lang_id, 'text', '{"html_content": "<p>Welcome to the English homepage!</p><p>This content is dynamically managed by the CMS.</p>"}', 0);
|
|
47
|
+
END IF;
|
|
48
|
+
|
|
49
|
+
-- Seed initial content block for French Homepage (optional)
|
|
50
|
+
IF fr_home_page_id IS NOT NULL THEN
|
|
51
|
+
INSERT INTO public.blocks (page_id, language_id, block_type, content, "order")
|
|
52
|
+
VALUES (fr_home_page_id, fr_lang_id, 'text', '{"html_content": "<p>Bienvenue sur la page d''accueil en français !</p><p>Ce contenu est géré dynamiquement par le CMS.</p>"}', 0);
|
|
53
|
+
END IF;
|
|
54
|
+
|
|
55
|
+
-- Seed English Navigation Item for Homepage (linked to the English page, but URL is root)
|
|
56
|
+
INSERT INTO public.navigation_items (language_id, menu_key, label, url, "order", page_id, translation_group_id)
|
|
57
|
+
VALUES (en_lang_id, 'HEADER', 'Home', '/', 0, en_home_page_id, home_nav_translation_group);
|
|
58
|
+
|
|
59
|
+
-- Seed French Navigation Item for Homepage (linked to the French page, but URL is root)
|
|
60
|
+
INSERT INTO public.navigation_items (language_id, menu_key, label, url, "order", page_id, translation_group_id)
|
|
61
|
+
VALUES (fr_lang_id, 'HEADER', 'Accueil', '/', 0, fr_home_page_id, home_nav_translation_group);
|
|
62
|
+
|
|
63
|
+
RAISE NOTICE 'Homepage and navigation links seeded for EN and FR.';
|
|
64
|
+
END $$;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
ALTER TABLE public.posts
|
|
2
|
+
ADD COLUMN feature_image_id UUID,
|
|
3
|
+
ADD CONSTRAINT fk_feature_image
|
|
4
|
+
FOREIGN KEY (feature_image_id)
|
|
5
|
+
REFERENCES public.media(id)
|
|
6
|
+
ON DELETE SET NULL;
|
|
7
|
+
|
|
8
|
+
COMMENT ON COLUMN public.posts.feature_image_id IS 'ID of the media item to be used as the post''s feature image.';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
-- Drop existing policies if they exist, then recreate them.
|
|
2
|
+
|
|
3
|
+
-- Policy for public read access
|
|
4
|
+
DROP POLICY IF EXISTS "media_are_publicly_readable" ON public.media;
|
|
5
|
+
|
|
6
|
+
CREATE POLICY "media_are_publicly_readable"
|
|
7
|
+
ON public.media FOR SELECT
|
|
8
|
+
TO anon, authenticated
|
|
9
|
+
USING (true);
|
|
10
|
+
|
|
11
|
+
-- Policy for admin/writer management
|
|
12
|
+
DROP POLICY IF EXISTS "admins_and_writers_can_manage_media" ON public.media;
|
|
13
|
+
|
|
14
|
+
CREATE POLICY "admins_and_writers_can_manage_media"
|
|
15
|
+
ON public.media FOR ALL
|
|
16
|
+
TO authenticated
|
|
17
|
+
USING (public.get_current_user_role() IN ('ADMIN', 'WRITER'))
|
|
18
|
+
WITH CHECK (public.get_current_user_role() IN ('ADMIN', 'WRITER'));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
ALTER TABLE public.media
|
|
2
|
+
ADD COLUMN width INTEGER,
|
|
3
|
+
ADD COLUMN height INTEGER;
|
|
4
|
+
|
|
5
|
+
-- Optional: Add a comment to describe the new columns
|
|
6
|
+
COMMENT ON COLUMN public.media.width IS 'Width of the image in pixels.';
|
|
7
|
+
COMMENT ON COLUMN public.media.height IS 'Height of the image in pixels.';
|
|
8
|
+
|
|
9
|
+
-- Backfill existing image media with nulls, or you might want to run a script later to populate them if possible
|
|
10
|
+
-- For now, they will be NULL by default.
|
|
11
|
+
|
|
12
|
+
-- Re-apply RLS policies if necessary, though ADD COLUMN usually doesn't require it unless policies are column-specific
|
|
13
|
+
-- and these new columns need to be included or excluded.
|
|
14
|
+
-- For simplicity, assuming existing policies are fine.
|