@nextblock-cms/db 0.2.10 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/package.json +13 -3
  2. package/supabase/config.toml +319 -0
  3. package/supabase/migrations/20250513194738_setup_roles_and_profiles.sql +41 -0
  4. package/supabase/migrations/20250513194910_auto_create_profile_trigger.sql +48 -0
  5. package/supabase/migrations/20250513194916_rls_for_profiles.sql +85 -0
  6. package/supabase/migrations/20250514125634_fix_recursive_rls_policies.sql +51 -0
  7. package/supabase/migrations/20250514143016_setup_languages_table.sql +66 -0
  8. package/supabase/migrations/20250514171549_create_pages_table.sql +73 -0
  9. package/supabase/migrations/20250514171550_create_posts_table.sql +61 -0
  10. package/supabase/migrations/20250514171552_create_media_table.sql +45 -0
  11. package/supabase/migrations/20250514171553_create_blocks_table.sql +54 -0
  12. package/supabase/migrations/20250514171615_create_navigation_table.sql +56 -0
  13. package/supabase/migrations/20250514171627_rls_policies_for_content_tables.sql +70 -0
  14. package/supabase/migrations/20250515194800_add_translation_group_id.sql +39 -0
  15. package/supabase/migrations/20250520171900_add_translation_group_to_nav_items.sql +21 -0
  16. package/supabase/migrations/20250521143933_seed_homepage_and_nav.sql +64 -0
  17. package/supabase/migrations/20250523145833_add_feature_image_to_posts.sql +8 -0
  18. package/supabase/migrations/20250523151737_add_rls_to_media_table.sql +18 -0
  19. package/supabase/migrations/20250526110400_add_image_dimensions_to_media.sql +14 -0
  20. package/supabase/migrations/20250526153321_optimize_rls_policies.sql +188 -0
  21. package/supabase/migrations/20250526172513_resolve_select_policy_overlaps.sql +96 -0
  22. package/supabase/migrations/20250526172853_resolve_remaining_rls_v5.sql +107 -0
  23. package/supabase/migrations/20250526173538_finalize_rls_cleanup_v7.sql +110 -0
  24. package/supabase/migrations/20250526174710_separate_write_policies_v8.sql +147 -0
  25. package/supabase/migrations/20250526175359_fix_languages_select_rls_v9.sql +81 -0
  26. package/supabase/migrations/20250526182940_fix_nav_read_policy_v10.sql +27 -0
  27. package/supabase/migrations/20250526183239_fix_posts_read_rls_v11.sql +59 -0
  28. package/supabase/migrations/20250526183746_fix_media_select_rls_v12.sql +39 -0
  29. package/supabase/migrations/20250526184205_consolidate_content_read_rls_v13.sql +61 -0
  30. package/supabase/migrations/20250526185854_optimize_indexes.sql +47 -0
  31. package/supabase/migrations/20250526190900_debug_blocks_rls.sql +56 -0
  32. package/supabase/migrations/20250526191217_consolidate_blocks_select_rls.sql +79 -0
  33. package/supabase/migrations/20250526192822_fix_handle_languages_update_search_path.sql +32 -0
  34. package/supabase/migrations/20250527150500_fix_blocks_rls_policy.sql +54 -0
  35. package/supabase/migrations/20250602150602_add_blur_data_url_to_media.sql +4 -0
  36. package/supabase/migrations/20250602150959_add_variants_to_media.sql +4 -0
  37. package/supabase/migrations/20250618124000_create_get_my_claim_function.sql +5 -0
  38. package/supabase/migrations/20250618124100_create_logos_table.sql +29 -0
  39. package/supabase/migrations/20250618130000_fix_linter_warnings.sql +58 -0
  40. package/supabase/migrations/20250618151500_revert_storage_rls.sql +6 -0
  41. package/supabase/migrations/20250619084800_reinstate_storage_rls.sql +13 -0
  42. package/supabase/migrations/20250619092430_widen_logo_insert_policy.sql +6 -0
  43. package/supabase/migrations/20250619093122_fix_get_my_claim_volatility.sql +5 -0
  44. package/supabase/migrations/20250619104249_consolidated_logo_rls_fix.sql +56 -0
  45. package/supabase/migrations/20250619110700_fix_logo_rls_again.sql +59 -0
  46. package/supabase/migrations/20250619113200_add_file_path_to_media.sql +4 -0
  47. package/supabase/migrations/20250619124100_fix_rls_performance_warnings.sql +74 -0
  48. package/supabase/migrations/20250619195500_create_site_settings_table.sql +28 -0
  49. package/supabase/migrations/20250619201500_add_anon_read_to_site_settings.sql +7 -0
  50. package/supabase/migrations/20250619202000_add_is_active_to_languages.sql +5 -0
  51. package/supabase/migrations/20250620085700_fix_site_settings_write_rls.sql +27 -0
  52. package/supabase/migrations/20250620095500_fix_profiles_read_rls.sql +11 -0
  53. package/supabase/migrations/20250620100000_use_security_definer_for_rls.sql +39 -0
  54. package/supabase/migrations/20250620130000_add_public_read_to_logos.sql +4 -0
  55. package/supabase/migrations/20250708091700_create_translations_table.sql +55 -0
  56. package/supabase/migrations/20250708093403_seed_translations_table.sql +20 -0
  57. package/supabase/migrations/20250708110600_fix_translations_rls_policies.sql +11 -0
  58. package/supabase/migrations/20250708112300_add_new_translations.sql +9 -0
  59. package/supabase/migrations/20250709120000_create_revisions_tables.sql +109 -0
  60. package/supabase/migrations/20251001113000_add_folder_to_media.sql +14 -0
  61. package/supabase/migrations/20251112113736_fix_search_path_functions.sql +74 -0
  62. package/supabase/migrations/20251112124444_fix_rls_performance.sql +63 -0
  63. package/supabase/migrations/20251112125935_fix_combined_policies.sql +194 -0
  64. package/supabase/migrations/20251112132146_fix_foreign_key_indexes.sql +21 -0
  65. package/supabase/migrations/20251112132525_cleanup_unused_indexes.sql +10 -0
  66. package/supabase/migrations/20251112132822_fix_final_indexes.sql +14 -0
  67. package/supabase/migrations/20251112140000_scaffold_foundational_content.sql +95 -0
  68. package/supabase/migrations/20251112141000_seed_homepage_blocks.sql +656 -0
  69. package/supabase/migrations/20251112142000_seed_how_it_works_post_blocks.sql +100 -0
  70. package/supabase/migrations/20251112143000_seed_additional_translations.sql +102 -0
  71. package/supabase/migrations/20251112145000_grant_public_schema_usage.sql +6 -0
  72. package/supabase/migrations/20251112145500_grant_select_on_public_tables.sql +19 -0
  73. package/supabase/migrations/20251117093000_add_admin_created_flag.sql +21 -0
  74. package/supabase/migrations/20251117103000_relax_profile_username_constraint.sql +6 -0
  75. package/supabase/migrations/20251117110000_relax_profiles_site_settings_rls_for_signup.sql +20 -0
  76. package/supabase/migrations/20251117112000_fix_handle_new_user_role_enum.sql +45 -0
  77. package/supabase/migrations/20251117113000_cleanup_rls_duplicates.sql +20 -0
  78. package/supabase/migrations/20251117200000_media_service_role_insert.sql +14 -0
  79. package/supabase/migrations/20251117201500_media_service_role_select.sql +11 -0
  80. package/supabase/migrations/20251117203000_media_admin_writer_select.sql +11 -0
  81. package/supabase/migrations/20251117204500_fix_media_permissions.sql +43 -0
  82. package/lib/supabase/client.d.ts +0 -9
  83. package/lib/supabase/middleware.d.ts +0 -2
  84. package/lib/supabase/server.d.ts +0 -7
  85. package/lib/supabase/ssg-client.d.ts +0 -2
  86. package/lib/supabase/types.d.ts +0 -635
@@ -0,0 +1,81 @@
1
+ -- supabase/migrations/YYYYMMDDHHMMSS_fix_languages_select_rls_v9.sql
2
+
3
+ BEGIN;
4
+
5
+ -- == LANGUAGES ==
6
+ -- Drop the specific authenticated non-admin policy
7
+ DROP POLICY IF EXISTS "languages_authenticated_non_admin_can_read" ON public.languages;
8
+ DROP POLICY IF EXISTS "languages_user_can_read" ON public.languages; -- If this was the one from v5 attempt
9
+
10
+ -- Create a simpler, broader SELECT policy for all authenticated users for the languages table.
11
+ -- The "multiple_permissive_policies" warning for languages was between "languages_admin_management" (FOR ALL)
12
+ -- and "languages_authenticated_non_admin_can_read".
13
+ -- If "languages_admin_management" is FOR ALL, it handles SELECT for admins.
14
+ -- We need a SELECT policy for authenticated users who are NOT admins.
15
+
16
+ -- Option 1: Keep admin SELECT via FOR ALL, and add specific for USER/WRITER
17
+ -- (This is what was in _v6.sql basically and was still warned by linter, but might be a linter quirk)
18
+ -- CREATE POLICY "languages_user_writer_can_read" ON public.languages
19
+ -- FOR SELECT TO authenticated
20
+ -- USING (public.get_current_user_role() IN ('USER', 'WRITER'));
21
+
22
+ -- Option 2: Make the "publicly readable" policy truly public for SELECT FOR ALL ROLES,
23
+ -- and ensure the admin management policy is ONLY for writes. This was the _v7.sql approach which failed syntax.
24
+
25
+ -- Let's retry the _v7 approach for *just* the languages table, as it's the most robust against the linter.
26
+ -- We need to be absolutely sure about the FOR INSERT, UPDATE, DELETE syntax.
27
+ -- If that syntax is the absolute blocker, we're in a tough spot.
28
+
29
+ -- Assuming the error "JSON object requested, multiple (or no) rows returned" means NO rows were returned,
30
+ -- it means NO select policy was satisfied for the 'languages' table by the role executing getNavigationMenu.
31
+
32
+ -- Let's make languages readable by any authenticated user, plus the existing anon policy.
33
+ -- This might re-trigger the "multiple_permissive_policies" if the admin FOR ALL policy is still active,
34
+ -- but it should fix the immediate "0 rows returned" error.
35
+ CREATE POLICY "languages_authenticated_can_read" ON public.languages
36
+ FOR SELECT
37
+ TO authenticated
38
+ USING (true); -- Any authenticated user can read languages.
39
+ COMMENT ON POLICY "languages_authenticated_can_read" ON public.languages IS 'Any authenticated user can read languages.';
40
+
41
+ -- "languages_anon_can_read" (FOR SELECT TO anon USING (true)) should still be active and is fine.
42
+ -- "languages_admin_management" (FOR ALL TO authenticated USING role = ADMIN) is assumed to be active.
43
+
44
+ -- This setup WILL cause a "multiple_permissive_policies" warning for an authenticated ADMIN
45
+ -- because "languages_authenticated_can_read" (USING true) and the SELECT part of
46
+ -- "languages_admin_management" (USING role=ADMIN) will both apply.
47
+
48
+ -- The root cause of the "0 rows returned" is that *neither* of the v6 policies for authenticated
49
+ -- was being met by the server-side client running getNavigationMenu, OR the client was anon
50
+ -- and "languages_anon_can_read" was somehow not effective or missing.
51
+
52
+ -- To ensure the `getNavigationMenu` function works for ANY role calling it:
53
+ -- Let's reinstate a very simple public read policy for languages.
54
+ -- This was the original policy from `20250514143016_setup_languages_table.sql`:
55
+ -- `CREATE POLICY "languages_are_publicly_readable" ON public.languages FOR SELECT TO anon, authenticated USING (true);`
56
+ -- This was later split.
57
+
58
+ -- Cleanest approach to fix the immediate error, and then we can re-evaluate the linter:
59
+ -- Ensure there's one policy that makes languages readable to everyone for SELECT.
60
+ DROP POLICY IF EXISTS "languages_anon_can_read" ON public.languages;
61
+ DROP POLICY IF EXISTS "languages_user_can_read" ON public.languages;
62
+ DROP POLICY IF EXISTS "languages_authenticated_non_admin_can_read" ON public.languages;
63
+ DROP POLICY IF EXISTS "languages_authenticated_can_read" ON public.languages;
64
+ DROP POLICY IF EXISTS "languages_are_publicly_readable_by_all" ON public.languages; -- from _v2
65
+ DROP POLICY IF EXISTS "languages_are_publicly_readable" ON public.languages; -- from original
66
+
67
+ CREATE POLICY "languages_are_publicly_readable_for_all_roles" ON public.languages
68
+ FOR SELECT
69
+ USING (true); -- This allows 'anon' and all types of 'authenticated' users to read.
70
+ COMMENT ON POLICY "languages_are_publicly_readable_for_all_roles" ON public.languages IS 'All roles (anon, authenticated) can read from the languages table.';
71
+
72
+ -- The "languages_admin_management" policy (FOR ALL TO authenticated USING role=ADMIN) will still exist.
73
+ -- This WILL re-introduce the "multiple_permissive_policies" warning for `languages` for `authenticated SELECT`,
74
+ -- because an authenticated ADMIN will match both this new public read policy and their FOR ALL management policy.
75
+ -- However, it should fix your immediate "Error fetching language ID" error.
76
+
77
+ -- If the `FOR INSERT, UPDATE, DELETE` syntax was the true blocker, and you *must* use `FOR ALL` for admin management,
78
+ -- then the previous "mutually exclusive USING clause" approach was logically correct but the linter didn't like it.
79
+ -- The above `languages_are_publicly_readable_for_all_roles` is the simplest way to ensure reads, at the cost of a linter warning.
80
+
81
+ COMMIT;
@@ -0,0 +1,27 @@
1
+ -- supabase/migrations/YYYYMMDDHHMMSS_fix_nav_read_policy_v10.sql
2
+
3
+ BEGIN;
4
+
5
+ -- == NAVIGATION ITEMS ==
6
+
7
+ -- Drop the existing SELECT policy for authenticated non-admins, as we'll replace it with a broader one.
8
+ DROP POLICY IF EXISTS "nav_items_authenticated_non_admin_can_read" ON public.navigation_items;
9
+ -- Also drop older name if it somehow exists from v5 attempt
10
+ DROP POLICY IF EXISTS "nav_items_user_can_read" ON public.navigation_items;
11
+
12
+
13
+ -- Create a policy that allows ALL authenticated users to read navigation items.
14
+ -- This will cover USER, WRITER, and ADMIN roles for SELECT.
15
+ CREATE POLICY "nav_items_authenticated_can_read" ON public.navigation_items
16
+ FOR SELECT
17
+ TO authenticated
18
+ USING (true); -- Any authenticated user can read all navigation items.
19
+ COMMENT ON POLICY "nav_items_authenticated_can_read" ON public.navigation_items IS 'All authenticated users (USER, WRITER, ADMIN) can read navigation items.';
20
+
21
+ -- The following policies are assumed to be correctly in place and should remain:
22
+ -- 1. "nav_items_anon_can_read" ON public.navigation_items FOR SELECT TO anon USING (true);
23
+ -- 2. "nav_items_admin_can_insert" ON public.navigation_items FOR INSERT TO authenticated WITH CHECK (public.get_current_user_role() = 'ADMIN');
24
+ -- 3. "nav_items_admin_can_update" ON public.navigation_items FOR UPDATE TO authenticated USING (public.get_current_user_role() = 'ADMIN') WITH CHECK (public.get_current_user_role() = 'ADMIN');
25
+ -- 4. "nav_items_admin_can_delete" ON public.navigation_items FOR DELETE TO authenticated USING (public.get_current_user_role() = 'ADMIN');
26
+
27
+ COMMIT;
@@ -0,0 +1,59 @@
1
+ -- supabase/migrations/YYYYMMDDHHMMSS_fix_posts_read_rls_v11.sql
2
+
3
+ BEGIN;
4
+
5
+ -- == POSTS ==
6
+
7
+ -- Drop the existing specific SELECT policy for USER role if it exists
8
+ DROP POLICY IF EXISTS "posts_user_role_can_read" ON public.posts;
9
+ -- Drop any older variations that might exist from previous attempts
10
+ DROP POLICY IF EXISTS "posts_user_authenticated_access" ON public.posts;
11
+ DROP POLICY IF EXISTS "posts_authenticated_access" ON public.posts;
12
+
13
+
14
+ -- Policy for anonymous users to read published posts (should already exist, ensure it's correct)
15
+ DROP POLICY IF EXISTS "posts_anon_can_read_published" ON public.posts;
16
+ CREATE POLICY "posts_anon_can_read_published" ON public.posts
17
+ FOR SELECT
18
+ TO anon
19
+ USING (status = 'published' AND (published_at IS NULL OR published_at <= now()));
20
+ COMMENT ON POLICY "posts_anon_can_read_published" ON public.posts IS 'Anonymous users can read published posts.';
21
+
22
+
23
+ -- Policy for ALL authenticated users to read PUBLISHED posts.
24
+ -- This covers USER, WRITER, and ADMIN for viewing public, published content.
25
+ CREATE POLICY "posts_authenticated_can_read_published" ON public.posts
26
+ FOR SELECT
27
+ TO authenticated
28
+ USING (status = 'published' AND (published_at IS NULL OR published_at <= now()));
29
+ COMMENT ON POLICY "posts_authenticated_can_read_published" ON public.posts IS 'All authenticated users can read published posts.';
30
+
31
+
32
+ -- Policy for authenticated users (authors) to read their OWN DRAFTS/non-published posts.
33
+ -- This is important for CMS previews or if authors can view their non-live content.
34
+ DROP POLICY IF EXISTS "posts_authors_can_read_own_drafts" ON public.posts;
35
+ CREATE POLICY "posts_authors_can_read_own_drafts" ON public.posts
36
+ FOR SELECT
37
+ TO authenticated
38
+ USING (author_id = (SELECT auth.uid()) AND status <> 'published');
39
+ COMMENT ON POLICY "posts_authors_can_read_own_drafts" ON public.posts IS 'Authenticated authors can read their own non-published posts.';
40
+
41
+
42
+ -- The admin/writer management policies for INSERT, UPDATE, DELETE remain as they are (write-only).
43
+ -- e.g., "posts_admin_writer_can_insert", "posts_admin_writer_can_update", "posts_admin_writer_can_delete"
44
+ -- These do NOT provide SELECT access.
45
+
46
+ -- Optional: If you need ADMINs/WRITERs to be able to SELECT *ALL* posts (including drafts of others)
47
+ -- for CMS management purposes (e.g., in the /cms/posts listing page), you would need an
48
+ -- additional policy or ensure their management policy was FOR ALL.
49
+ -- However, for public site rendering (getPostDataBySlug), the above should be sufficient.
50
+ -- If your CMS listing page for posts also breaks for ADMIN/WRITER, you might need:
51
+ -- CREATE POLICY "posts_admin_writer_can_read_all_for_cms" ON public.posts
52
+ -- FOR SELECT
53
+ -- TO authenticated
54
+ -- USING (public.get_current_user_role() IN ('ADMIN', 'WRITER'));
55
+ -- This WILL re-introduce a "multiple_permissive_policies" warning for ADMIN/WRITER on SELECT,
56
+ -- as they would match this AND "posts_authenticated_can_read_published".
57
+ -- For now, let's stick to fixing public reads. The CMS read access might be handled differently or might need its own policy.
58
+
59
+ COMMIT;
@@ -0,0 +1,39 @@
1
+ -- supabase/migrations/YYYYMMDDHHMMSS_fix_media_select_rls_v12.sql
2
+
3
+ BEGIN;
4
+
5
+ -- == MEDIA ==
6
+
7
+ -- Drop the existing specific SELECT policy for the 'USER' role,
8
+ -- as we will replace it with a broader one for all authenticated users.
9
+ DROP POLICY IF EXISTS "media_user_role_can_read" ON public.media;
10
+ -- Drop any older variations that might exist from previous attempts if their names were different
11
+ DROP POLICY IF EXISTS "media_user_can_read" ON public.media;
12
+ DROP POLICY IF EXISTS "media_readable_by_anon_and_non_privileged_users" ON public.media;
13
+
14
+
15
+ -- Policy for anonymous users to read media (ensure this is in place and correct)
16
+ -- This policy allows anyone to read any media record if no other policy restricts them.
17
+ -- This is generally desired if your R2 bucket URLs are guessable or if images are meant to be public.
18
+ DROP POLICY IF EXISTS "media_anon_can_read" ON public.media; -- From _v6/_v7 logic
19
+ DROP POLICY IF EXISTS "media_is_publicly_readable_by_all" ON public.media; -- From _v8 logic
20
+ DROP POLICY IF EXISTS "media_is_readable_by_all" ON public.media; -- Older name
21
+ DROP POLICY IF EXISTS "media_are_publicly_readable" ON public.media; -- Older name
22
+
23
+ CREATE POLICY "media_public_can_read" ON public.media
24
+ FOR SELECT
25
+ USING (true); -- Allows both 'anon' and 'authenticated' roles to read by default
26
+ COMMENT ON POLICY "media_public_can_read" ON public.media IS 'All users (anonymous and authenticated) can read media records. This is the general read access.';
27
+
28
+
29
+ -- The admin/writer management policies for INSERT, UPDATE, DELETE remain as they are (write-only).
30
+ -- e.g., "media_admin_writer_can_insert", "media_admin_writer_can_update", "media_admin_writer_can_delete"
31
+ -- These do NOT provide SELECT access.
32
+
33
+ -- With the "media_public_can_read" policy USING (true), all authenticated users (USER, WRITER, ADMIN)
34
+ -- will have SELECT access. This should resolve the error when fetching feature_image_id details
35
+ -- and allow images to be displayed.
36
+ -- This will also mean the linter should not complain about "multiple permissive policies for authenticated SELECT"
37
+ -- because the management policies are write-only, and there's now one clear "public read" policy for SELECT.
38
+
39
+ COMMIT;
@@ -0,0 +1,61 @@
1
+ -- supabase/migrations/YYYYMMDDHHMMSS_consolidate_content_read_rls_v13.sql
2
+
3
+ BEGIN;
4
+
5
+ -- == POSTS ==
6
+
7
+ -- Drop the existing separate SELECT policies for authenticated users on posts
8
+ DROP POLICY IF EXISTS "posts_authenticated_can_read_published" ON public.posts;
9
+ DROP POLICY IF EXISTS "posts_authors_can_read_own_drafts" ON public.posts;
10
+ -- Drop any older variations that might exist
11
+ DROP POLICY IF EXISTS "posts_user_role_can_read" ON public.posts;
12
+ DROP POLICY IF EXISTS "posts_user_authenticated_access" ON public.posts;
13
+ DROP POLICY IF EXISTS "posts_authenticated_access" ON public.posts;
14
+
15
+
16
+ -- Create a single, comprehensive SELECT policy for authenticated users on posts
17
+ CREATE POLICY "posts_authenticated_comprehensive_read" ON public.posts
18
+ FOR SELECT
19
+ TO authenticated
20
+ USING (
21
+ -- Condition 1: Any authenticated user can read PUBLISHED posts
22
+ (status = 'published' AND (published_at IS NULL OR published_at <= now())) OR
23
+ -- Condition 2: Authenticated authors can read their OWN NON-PUBLISHED posts
24
+ (author_id = (SELECT auth.uid()) AND status <> 'published') OR
25
+ -- Condition 3: ADMINs and WRITERs can read ALL posts (any status)
26
+ (public.get_current_user_role() IN ('ADMIN', 'WRITER'))
27
+ );
28
+ COMMENT ON POLICY "posts_authenticated_comprehensive_read" ON public.posts IS 'Handles all SELECT scenarios for authenticated users: published for all, own drafts for authors, and all posts for admins/writers.';
29
+
30
+ -- "posts_anon_can_read_published" (FOR SELECT TO anon) is assumed to be correct and in place.
31
+ -- Admin/Writer management policies (FOR INSERT, UPDATE, DELETE) are assumed to be correct and in place.
32
+
33
+
34
+ -- == PAGES ==
35
+
36
+ -- Drop the existing separate SELECT policies for authenticated users on pages
37
+ DROP POLICY IF EXISTS "pages_authenticated_can_read_published" ON public.pages; -- If created in a previous step
38
+ DROP POLICY IF EXISTS "pages_authors_can_read_own_drafts" ON public.pages; -- If created
39
+ DROP POLICY IF EXISTS "pages_user_role_can_read" ON public.pages;
40
+ DROP POLICY IF EXISTS "pages_user_authenticated_access" ON public.pages;
41
+ DROP POLICY IF EXISTS "pages_authenticated_access" ON public.pages;
42
+
43
+
44
+ -- Create a single, comprehensive SELECT policy for authenticated users on pages
45
+ CREATE POLICY "pages_authenticated_comprehensive_read" ON public.pages
46
+ FOR SELECT
47
+ TO authenticated
48
+ USING (
49
+ -- Condition 1: Any authenticated user can read PUBLISHED pages
50
+ (status = 'published') OR
51
+ -- Condition 2: Authenticated authors can read their OWN NON-PUBLISHED pages
52
+ (author_id = (SELECT auth.uid()) AND status <> 'published') OR
53
+ -- Condition 3: ADMINs and WRITERs can read ALL pages (any status)
54
+ (public.get_current_user_role() IN ('ADMIN', 'WRITER'))
55
+ );
56
+ COMMENT ON POLICY "pages_authenticated_comprehensive_read" ON public.pages IS 'Handles all SELECT scenarios for authenticated users: published for all, own drafts for authors, and all pages for admins/writers.';
57
+
58
+ -- "pages_anon_can_read_published" (FOR SELECT TO anon) is assumed to be correct and in place.
59
+ -- Admin/Writer management policies (FOR INSERT, UPDATE, DELETE) are assumed to be correct and in place.
60
+
61
+ COMMIT;
@@ -0,0 +1,47 @@
1
+ -- supabase/migrations/YYYYMMDDHHMMSS_optimize_indexes.sql
2
+ -- (Replace YYYYMMDDHHMMSS with the actual timestamp of this migration file, e.g., 20250526185854)
3
+
4
+ BEGIN;
5
+
6
+ -- Add indexes for foreign keys in the 'public.blocks' table
7
+ CREATE INDEX IF NOT EXISTS idx_blocks_language_id ON public.blocks(language_id);
8
+ COMMENT ON INDEX public.idx_blocks_language_id IS 'Index for the foreign key blocks_language_id_fkey on public.blocks.';
9
+
10
+ CREATE INDEX IF NOT EXISTS idx_blocks_page_id ON public.blocks(page_id);
11
+ COMMENT ON INDEX public.idx_blocks_page_id IS 'Index for the foreign key blocks_page_id_fkey on public.blocks.';
12
+
13
+ CREATE INDEX IF NOT EXISTS idx_blocks_post_id ON public.blocks(post_id);
14
+ COMMENT ON INDEX public.idx_blocks_post_id IS 'Index for the foreign key blocks_post_id_fkey on public.blocks.';
15
+
16
+ -- Add index for foreign key in the 'public.media' table
17
+ CREATE INDEX IF NOT EXISTS idx_media_uploader_id ON public.media(uploader_id);
18
+ COMMENT ON INDEX public.idx_media_uploader_id IS 'Index for the foreign key media_uploader_id_fkey on public.media.';
19
+
20
+ -- Add indexes for foreign keys in the 'public.navigation_items' table
21
+ CREATE INDEX IF NOT EXISTS idx_navigation_items_language_id ON public.navigation_items(language_id);
22
+ COMMENT ON INDEX public.idx_navigation_items_language_id IS 'Index for the foreign key navigation_items_language_id_fkey on public.navigation_items.';
23
+
24
+ CREATE INDEX IF NOT EXISTS idx_navigation_items_page_id ON public.navigation_items(page_id);
25
+ COMMENT ON INDEX public.idx_navigation_items_page_id IS 'Index for the foreign key navigation_items_page_id_fkey on public.navigation_items.';
26
+
27
+ CREATE INDEX IF NOT EXISTS idx_navigation_items_parent_id ON public.navigation_items(parent_id);
28
+ COMMENT ON INDEX public.idx_navigation_items_parent_id IS 'Index for the foreign key navigation_items_parent_id_fkey on public.navigation_items.';
29
+
30
+ -- Add index for foreign key in the 'public.pages' table
31
+ CREATE INDEX IF NOT EXISTS idx_pages_author_id ON public.pages(author_id);
32
+ COMMENT ON INDEX public.idx_pages_author_id IS 'Index for the foreign key pages_author_id_fkey on public.pages.';
33
+
34
+ -- Add indexes for foreign keys in the 'public.posts' table
35
+ CREATE INDEX IF NOT EXISTS idx_posts_feature_image_id ON public.posts(feature_image_id);
36
+ COMMENT ON INDEX public.idx_posts_feature_image_id IS 'Index for the foreign key fk_feature_image on public.posts.';
37
+
38
+ CREATE INDEX IF NOT EXISTS idx_posts_author_id ON public.posts(author_id);
39
+ COMMENT ON INDEX public.idx_posts_author_id IS 'Index for the foreign key posts_author_id_fkey on public.posts.';
40
+
41
+ -- Remove unused index in 'public.navigation_items' table
42
+ -- The linter identified 'idx_navigation_items_translation_group_id' as unused.
43
+ -- This index was created in migration 20250520171900_add_translation_group_to_nav_items.sql
44
+ DROP INDEX IF EXISTS public.idx_navigation_items_translation_group_id;
45
+ -- The COMMENT ON for the dropped index has been removed to prevent the error.
46
+
47
+ COMMIT;
@@ -0,0 +1,56 @@
1
+ -- supabase/migrations/YYYYMMDDHHMMSS_debug_blocks_rls.sql
2
+
3
+ BEGIN;
4
+
5
+ -- Drop the more specific separated write policies for blocks from v8
6
+ DROP POLICY IF EXISTS "blocks_admin_writer_can_insert" ON public.blocks;
7
+ DROP POLICY IF EXISTS "blocks_admin_writer_can_update" ON public.blocks;
8
+ DROP POLICY IF EXISTS "blocks_admin_writer_can_delete" ON public.blocks;
9
+
10
+ -- Reinstate a broad "FOR ALL" policy for ADMINS and WRITERS on blocks
11
+ -- This is similar to what was effectively in place before v8 for admin/writer management.
12
+ CREATE POLICY "blocks_admin_writer_management_reinstated" ON public.blocks
13
+ FOR ALL -- Covers SELECT, INSERT, UPDATE, DELETE
14
+ TO authenticated
15
+ USING (public.get_current_user_role() IN ('ADMIN', 'WRITER'))
16
+ WITH CHECK (public.get_current_user_role() IN ('ADMIN', 'WRITER'));
17
+
18
+ COMMENT ON POLICY "blocks_admin_writer_management_reinstated" ON public.blocks IS 'Reinstated FOR ALL policy for ADMIN/WRITER to manage blocks. This may cause linter warnings for SELECT overlaps if other SELECT policies exist but is for debugging block creation.';
19
+
20
+ -- Ensure SELECT policies for other roles are still in place and don't conflict in a breaking way.
21
+ -- The policies "blocks_anon_can_read_published" and "blocks_user_role_can_read_published_parents" (from v5/v7) handle reads for anon and USERs.
22
+ -- "blocks_anon_can_read_published" (FOR SELECT TO anon USING (...))
23
+ -- "blocks_user_role_can_read_published_parents" (FOR SELECT TO authenticated USING (public.get_current_user_role() = 'USER' AND ...))
24
+
25
+ -- Let's re-assert the anon read policy to be sure it's correct, as it was defined early on.
26
+ DROP POLICY IF EXISTS "blocks_are_readable_if_parent_is_published" ON public.blocks; -- Original name from 20250514171553
27
+ DROP POLICY IF EXISTS "blocks_anon_can_read_published" ON public.blocks; -- Renamed in 20250526153321
28
+
29
+ CREATE POLICY "blocks_anon_can_read_published_content" ON public.blocks
30
+ FOR SELECT
31
+ TO anon
32
+ USING (
33
+ (page_id IS NOT NULL AND EXISTS(SELECT 1 FROM public.pages p WHERE p.id = blocks.page_id AND p.status = 'published')) OR
34
+ (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())))
35
+ );
36
+ COMMENT ON POLICY "blocks_anon_can_read_published_content" ON public.blocks IS 'Anonymous users can read blocks of published parent pages/posts.';
37
+
38
+
39
+ -- Re-assert the USER role SELECT policy, ensuring it only applies to USER.
40
+ DROP POLICY IF EXISTS "blocks_authenticated_user_access" ON public.blocks; -- From v4
41
+ DROP POLICY IF EXISTS "blocks_user_role_can_read_published_parents" ON public.blocks; -- From v5/v7
42
+
43
+ CREATE POLICY "blocks_auth_users_can_read_published_parents" ON public.blocks
44
+ FOR SELECT
45
+ TO authenticated
46
+ USING (
47
+ (public.get_current_user_role() = 'USER') AND -- Crucially, only applies to 'USER' role
48
+ (
49
+ (page_id IS NOT NULL AND EXISTS(SELECT 1 FROM public.pages p WHERE p.id = blocks.page_id AND p.status = 'published')) OR
50
+ (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())))
51
+ )
52
+ );
53
+ COMMENT ON POLICY "blocks_auth_users_can_read_published_parents" ON public.blocks IS 'Authenticated USERS can read blocks of published parents. Admin/Writer SELECT is handled by their management policy.';
54
+
55
+
56
+ COMMIT;
@@ -0,0 +1,79 @@
1
+ -- supabase/migrations/YYYYMMDDHHMMSS_consolidate_blocks_select_rls.sql
2
+ -- (Replace YYYYMMDDHHMMSS with the actual timestamp of this migration file)
3
+
4
+ BEGIN;
5
+
6
+ -- Drop existing policies for 'public.blocks' that will be replaced or refined.
7
+ -- This includes the broad "FOR ALL" policy and the specific "USER" SELECT policy
8
+ -- created in the previous debug migration.
9
+ DROP POLICY IF EXISTS "blocks_admin_writer_management_reinstated" ON public.blocks;
10
+ DROP POLICY IF EXISTS "blocks_auth_users_can_read_published_parents" ON public.blocks;
11
+
12
+ -- The "blocks_anon_can_read_published_content" policy can remain as it targets 'anon'
13
+ -- and does not conflict with 'authenticated' role policies for the linter's warning.
14
+ -- However, if you wish to have a completely clean slate before adding the consolidated one,
15
+ -- you can drop and re-add it. For this exercise, we'll assume it's fine and already specific.
16
+ -- If it was named "blocks_anon_can_read_published" from 20250526153321_optimize_rls_policies.sql:
17
+ -- DROP POLICY IF EXISTS "blocks_anon_can_read_published" ON public.blocks;
18
+ -- Or if it was "blocks_are_readable_if_parent_is_published" from 20250514171553_create_blocks_table.sql:
19
+ -- DROP POLICY IF EXISTS "blocks_are_readable_if_parent_is_published" ON public.blocks;
20
+ -- Re-creating the anon policy for clarity and to ensure it's exactly as needed:
21
+ DROP POLICY IF EXISTS "blocks_anon_can_read_published_content" ON public.blocks; -- From debug migration
22
+ DROP POLICY IF EXISTS "blocks_anon_can_read_published" ON public.blocks; -- From optimize_rls_policies
23
+ DROP POLICY IF EXISTS "blocks_are_readable_if_parent_is_published" ON public.blocks; -- From initial table creation
24
+
25
+ CREATE POLICY "blocks_anon_can_read_published_blocks" ON public.blocks
26
+ FOR SELECT
27
+ TO anon
28
+ USING (
29
+ (page_id IS NOT NULL AND EXISTS(SELECT 1 FROM public.pages p WHERE p.id = blocks.page_id AND p.status = 'published')) OR
30
+ (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())))
31
+ );
32
+ COMMENT ON POLICY "blocks_anon_can_read_published_blocks" ON public.blocks IS 'Anonymous users can read blocks of published parent content.';
33
+
34
+
35
+ -- Create a single, comprehensive SELECT policy for ALL authenticated users on blocks
36
+ CREATE POLICY "blocks_authenticated_comprehensive_select" ON public.blocks
37
+ FOR SELECT
38
+ TO authenticated
39
+ USING (
40
+ (
41
+ -- Condition for ADMIN or WRITER: they can read ALL blocks
42
+ (SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER')
43
+ ) OR
44
+ (
45
+ -- Condition for USER: they can read blocks of published parents
46
+ ((SELECT public.get_current_user_role()) = 'USER') AND
47
+ (
48
+ (page_id IS NOT NULL AND EXISTS(SELECT 1 FROM public.pages p WHERE p.id = blocks.page_id AND p.status = 'published')) OR
49
+ (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())))
50
+ )
51
+ )
52
+ );
53
+ COMMENT ON POLICY "blocks_authenticated_comprehensive_select" ON public.blocks IS 'Comprehensive SELECT policy for authenticated users on the blocks table, differentiating access by role (ADMIN/WRITER see all, USER sees blocks of published parents).';
54
+
55
+
56
+ -- Re-add the specific management policies for INSERT, UPDATE, DELETE for ADMIN/WRITER.
57
+ -- These only have WITH CHECK (for INSERT/UPDATE) or USING (for DELETE/UPDATE) based on role.
58
+ -- These were the ones created in 20250526174710_separate_write_policies_v8.sql and are fine.
59
+
60
+ CREATE POLICY "blocks_admin_writer_can_insert" ON public.blocks
61
+ FOR INSERT
62
+ TO authenticated
63
+ WITH CHECK ((SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER'));
64
+ COMMENT ON POLICY "blocks_admin_writer_can_insert" ON public.blocks IS 'Admins/Writers can insert blocks.';
65
+
66
+ CREATE POLICY "blocks_admin_writer_can_update" ON public.blocks
67
+ FOR UPDATE
68
+ TO authenticated
69
+ USING ((SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER')) -- Who can be targeted by an update
70
+ WITH CHECK ((SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER')); -- What rows can be created/modified by them
71
+ COMMENT ON POLICY "blocks_admin_writer_can_update" ON public.blocks IS 'Admins/Writers can update blocks.';
72
+
73
+ CREATE POLICY "blocks_admin_writer_can_delete" ON public.blocks
74
+ FOR DELETE
75
+ TO authenticated
76
+ USING ((SELECT public.get_current_user_role()) IN ('ADMIN', 'WRITER'));
77
+ COMMENT ON POLICY "blocks_admin_writer_can_delete" ON public.blocks IS 'Admins/Writers can delete blocks.';
78
+
79
+ COMMIT;
@@ -0,0 +1,32 @@
1
+ -- supabase/migrations/YYYYMMDDHHMMSS_fix_handle_languages_update_search_path.sql
2
+ -- (Replace YYYYMMDDHHMMSS with the actual timestamp of this migration file)
3
+
4
+ BEGIN;
5
+
6
+ -- Step 1: Drop the existing trigger that depends on the function.
7
+ DROP TRIGGER IF EXISTS on_languages_update ON public.languages;
8
+
9
+ -- Step 2: Now it's safe to drop and recreate the function.
10
+ DROP FUNCTION IF EXISTS public.handle_languages_update();
11
+
12
+ CREATE OR REPLACE FUNCTION public.handle_languages_update()
13
+ RETURNS TRIGGER
14
+ LANGUAGE plpgsql
15
+ SECURITY DEFINER -- Explicitly set security context
16
+ SET search_path = public -- Explicitly set search_path
17
+ AS $$
18
+ BEGIN
19
+ NEW.updated_at = now();
20
+ RETURN NEW;
21
+ END;
22
+ $$;
23
+
24
+ COMMENT ON FUNCTION public.handle_languages_update() IS 'Sets updated_at timestamp on language update. Includes explicit search_path and security definer.';
25
+
26
+ -- Step 3: Re-create the trigger to use the updated function.
27
+ CREATE TRIGGER on_languages_update
28
+ BEFORE UPDATE ON public.languages
29
+ FOR EACH ROW
30
+ EXECUTE PROCEDURE public.handle_languages_update();
31
+
32
+ COMMIT;
@@ -0,0 +1,54 @@
1
+ -- Fix blocks RLS policy to resolve joined query issues
2
+ -- Replace public.get_current_user_role() with direct EXISTS queries
3
+
4
+ BEGIN;
5
+
6
+ -- Drop the existing comprehensive SELECT policy that uses the problematic function
7
+ DROP POLICY IF EXISTS "blocks_authenticated_comprehensive_select" ON public.blocks;
8
+
9
+ -- Drop the existing management policies that use the problematic function
10
+ DROP POLICY IF EXISTS "blocks_admin_writer_can_insert" ON public.blocks;
11
+ DROP POLICY IF EXISTS "blocks_admin_writer_can_update" ON public.blocks;
12
+ DROP POLICY IF EXISTS "blocks_admin_writer_can_delete" ON public.blocks;
13
+
14
+ -- Create a new comprehensive SELECT policy using direct EXISTS queries
15
+ CREATE POLICY "blocks_authenticated_comprehensive_select" ON public.blocks
16
+ FOR SELECT
17
+ TO authenticated
18
+ USING (
19
+ (
20
+ -- Condition for ADMIN or WRITER: they can read ALL blocks
21
+ EXISTS (SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role IN ('ADMIN', 'WRITER'))
22
+ ) OR
23
+ (
24
+ -- Condition for USER: they can read blocks of published parents
25
+ EXISTS (SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role = 'USER') AND
26
+ (
27
+ (page_id IS NOT NULL AND EXISTS(SELECT 1 FROM public.pages p WHERE p.id = blocks.page_id AND p.status = 'published')) OR
28
+ (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())))
29
+ )
30
+ )
31
+ );
32
+ COMMENT ON POLICY "blocks_authenticated_comprehensive_select" ON public.blocks IS 'Comprehensive SELECT policy for authenticated users on the blocks table, differentiating access by role (ADMIN/WRITER see all, USER sees blocks of published parents). Uses direct EXISTS queries to avoid function call issues in joined queries.';
33
+
34
+ -- Re-create the management policies using direct EXISTS queries
35
+ CREATE POLICY "blocks_admin_writer_can_insert" ON public.blocks
36
+ FOR INSERT
37
+ TO authenticated
38
+ WITH CHECK (EXISTS (SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role IN ('ADMIN', 'WRITER')));
39
+ COMMENT ON POLICY "blocks_admin_writer_can_insert" ON public.blocks IS 'Admins/Writers can insert blocks.';
40
+
41
+ CREATE POLICY "blocks_admin_writer_can_update" ON public.blocks
42
+ FOR UPDATE
43
+ TO authenticated
44
+ USING (EXISTS (SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role IN ('ADMIN', 'WRITER'))) -- Who can be targeted by an update
45
+ WITH CHECK (EXISTS (SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role IN ('ADMIN', 'WRITER'))); -- What rows can be created/modified by them
46
+ COMMENT ON POLICY "blocks_admin_writer_can_update" ON public.blocks IS 'Admins/Writers can update blocks.';
47
+
48
+ CREATE POLICY "blocks_admin_writer_can_delete" ON public.blocks
49
+ FOR DELETE
50
+ TO authenticated
51
+ USING (EXISTS (SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role IN ('ADMIN', 'WRITER')));
52
+ COMMENT ON POLICY "blocks_admin_writer_can_delete" ON public.blocks IS 'Admins/Writers can delete blocks.';
53
+
54
+ COMMIT;
@@ -0,0 +1,4 @@
1
+ ALTER TABLE public.media
2
+ ADD COLUMN blur_data_url TEXT NULL;
3
+
4
+ COMMENT ON COLUMN public.media.blur_data_url IS 'Stores the base64 encoded string for image blur placeholders.';
@@ -0,0 +1,4 @@
1
+ ALTER TABLE public.media
2
+ ADD COLUMN variants JSONB NULL;
3
+
4
+ COMMENT ON COLUMN public.media.variants IS 'Stores an array of image variant objects, including different sizes and formats.';
@@ -0,0 +1,5 @@
1
+ CREATE OR REPLACE FUNCTION get_my_claim(claim TEXT)
2
+ RETURNS JSONB AS $$
3
+ SET search_path = '';
4
+ SELECT COALESCE(current_setting('request.jwt.claims', true)::JSONB ->> claim, NULL)::JSONB
5
+ $$ LANGUAGE SQL STABLE;
@@ -0,0 +1,29 @@
1
+ CREATE TABLE public.logos (
2
+ id uuid NOT NULL DEFAULT gen_random_uuid(),
3
+ created_at timestamp with time zone NOT NULL DEFAULT now(),
4
+ name text NOT NULL,
5
+ media_id uuid,
6
+ CONSTRAINT logos_pkey PRIMARY KEY (id),
7
+ CONSTRAINT logos_media_id_fkey FOREIGN KEY (media_id) REFERENCES public.media(id) ON DELETE SET NULL
8
+ );
9
+
10
+ COMMENT ON TABLE public.logos IS 'Stores company and brand logos.';
11
+ COMMENT ON COLUMN public.logos.name IS 'The name of the brand or company for the logo.';
12
+ COMMENT ON COLUMN public.logos.media_id IS 'Foreign key to the media table for the logo image.';
13
+
14
+ GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE public.logos TO authenticated;
15
+
16
+ ALTER TABLE public.logos ENABLE ROW LEVEL SECURITY;
17
+
18
+ CREATE POLICY "Allow read access for authenticated users on logos"
19
+ ON public.logos
20
+ FOR SELECT
21
+ TO authenticated
22
+ USING (true);
23
+
24
+ CREATE POLICY "Allow admin users to manage logos"
25
+ ON public.logos
26
+ FOR ALL
27
+ TO authenticated
28
+ USING (((SELECT get_my_claim('user_role'::text)) = '"admin"'::jsonb))
29
+ WITH CHECK (((SELECT get_my_claim('user_role'::text)) = '"admin"'::jsonb));