@nextblock-cms/db 0.2.22 → 0.2.24

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.
@@ -0,0 +1,239 @@
1
+ -- 00000000000020_setup_rls_policies.sql
2
+ -- Consolidated RLS Policies
3
+
4
+ BEGIN;
5
+
6
+ -- 1. Enable RLS on all tables
7
+ ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
8
+ ALTER TABLE public.languages ENABLE ROW LEVEL SECURITY;
9
+ ALTER TABLE public.media ENABLE ROW LEVEL SECURITY;
10
+ ALTER TABLE public.posts ENABLE ROW LEVEL SECURITY;
11
+ ALTER TABLE public.pages ENABLE ROW LEVEL SECURITY;
12
+ ALTER TABLE public.blocks ENABLE ROW LEVEL SECURITY;
13
+ ALTER TABLE public.navigation_items ENABLE ROW LEVEL SECURITY;
14
+ ALTER TABLE public.logos ENABLE ROW LEVEL SECURITY;
15
+ ALTER TABLE public.site_settings ENABLE ROW LEVEL SECURITY;
16
+ ALTER TABLE public.translations ENABLE ROW LEVEL SECURITY;
17
+ ALTER TABLE public.page_revisions ENABLE ROW LEVEL SECURITY;
18
+ ALTER TABLE public.post_revisions ENABLE ROW LEVEL SECURITY;
19
+
20
+ -- 2. GRANT PERMISSIONS (Crucial step often missed)
21
+ -- Grant usage on schema (redundant if in setup_extensions, but safe)
22
+ GRANT USAGE ON SCHEMA public TO anon, authenticated, service_role;
23
+
24
+ -- Grant SELECT to anon (public) for content that should be visible
25
+ GRANT SELECT ON TABLE public.profiles TO anon;
26
+ GRANT SELECT ON TABLE public.languages TO anon;
27
+ GRANT SELECT ON TABLE public.media TO anon;
28
+ GRANT SELECT ON TABLE public.posts TO anon;
29
+ GRANT SELECT ON TABLE public.pages TO anon;
30
+ GRANT SELECT ON TABLE public.blocks TO anon;
31
+ GRANT SELECT ON TABLE public.navigation_items TO anon;
32
+ GRANT SELECT ON TABLE public.logos TO anon;
33
+ GRANT SELECT ON TABLE public.site_settings TO anon;
34
+ GRANT SELECT ON TABLE public.translations TO anon;
35
+
36
+ -- Grant ALL to authenticated (RLS will still restrict rows)
37
+ GRANT ALL ON ALL TABLES IN SCHEMA public TO authenticated;
38
+ GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO authenticated;
39
+
40
+
41
+ -- 3. PROFILES
42
+ -- Read: Public can read basic profile info (needed for author display).
43
+ CREATE POLICY "profiles_read_policy" ON public.profiles
44
+ FOR SELECT TO public
45
+ USING (true);
46
+
47
+ -- Update: Users can update own profile; Admins can update all.
48
+ CREATE POLICY "profiles_update_policy" ON public.profiles
49
+ FOR UPDATE TO authenticated
50
+ USING (
51
+ (id = auth.uid()) OR
52
+ (public.get_current_user_role() = 'ADMIN')
53
+ )
54
+ WITH CHECK (
55
+ (id = auth.uid()) OR
56
+ (public.get_current_user_role() = 'ADMIN')
57
+ );
58
+
59
+ -- Insert: Admins can insert (Trigger handles signups).
60
+ CREATE POLICY "profiles_insert_policy" ON public.profiles
61
+ FOR INSERT TO authenticated
62
+ WITH CHECK (public.get_current_user_role() = 'ADMIN');
63
+
64
+
65
+ -- 4. PAGES
66
+ -- Read: Anon/Auth can read published. Authors/Admins/Writers can read drafts.
67
+ CREATE POLICY "pages_read_policy" ON public.pages
68
+ FOR SELECT TO authenticated
69
+ USING (
70
+ (status = 'published') OR
71
+ (author_id = auth.uid() AND status <> 'published') OR
72
+ (public.get_current_user_role() IN ('ADMIN', 'WRITER'))
73
+ );
74
+
75
+ CREATE POLICY "pages_anon_read_policy" ON public.pages
76
+ FOR SELECT TO anon
77
+ USING (status = 'published');
78
+
79
+ -- Manage: Admins/Writers can do everything.
80
+ CREATE POLICY "pages_manage_policy" ON public.pages
81
+ FOR ALL TO authenticated
82
+ USING (public.get_current_user_role() IN ('ADMIN', 'WRITER'))
83
+ WITH CHECK (public.get_current_user_role() IN ('ADMIN', 'WRITER'));
84
+
85
+
86
+ -- 5. POSTS
87
+ -- Read: Anon/Auth can read published. Authors/Admins/Writers can read drafts.
88
+ CREATE POLICY "posts_read_policy" ON public.posts
89
+ FOR SELECT TO authenticated
90
+ USING (
91
+ (status = 'published' AND (published_at IS NULL OR published_at <= now())) OR
92
+ (author_id = auth.uid() AND status <> 'published') OR
93
+ (public.get_current_user_role() IN ('ADMIN', 'WRITER'))
94
+ );
95
+
96
+ CREATE POLICY "posts_anon_read_policy" ON public.posts
97
+ FOR SELECT TO anon
98
+ USING (status = 'published' AND (published_at IS NULL OR published_at <= now()));
99
+
100
+ -- Manage: Admins/Writers can do everything.
101
+ CREATE POLICY "posts_manage_policy" ON public.posts
102
+ FOR ALL TO authenticated
103
+ USING (public.get_current_user_role() IN ('ADMIN', 'WRITER'))
104
+ WITH CHECK (public.get_current_user_role() IN ('ADMIN', 'WRITER'));
105
+
106
+
107
+ -- 6. BLOCKS
108
+ -- Read: Admins/Writers see all. Others see blocks of published parents.
109
+ CREATE POLICY "blocks_read_policy" ON public.blocks
110
+ FOR SELECT TO authenticated
111
+ USING (
112
+ (public.get_current_user_role() IN ('ADMIN', 'WRITER')) OR
113
+ (
114
+ (page_id IS NOT NULL AND EXISTS(SELECT 1 FROM public.pages p WHERE p.id = blocks.page_id AND p.status = 'published')) OR
115
+ (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())))
116
+ )
117
+ );
118
+
119
+ CREATE POLICY "blocks_anon_read_policy" ON public.blocks
120
+ FOR SELECT TO anon
121
+ USING (
122
+ (page_id IS NOT NULL AND EXISTS(SELECT 1 FROM public.pages p WHERE p.id = blocks.page_id AND p.status = 'published')) OR
123
+ (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())))
124
+ );
125
+
126
+ -- Manage: Admins/Writers can do everything.
127
+ CREATE POLICY "blocks_manage_policy" ON public.blocks
128
+ FOR ALL TO authenticated
129
+ USING (public.get_current_user_role() IN ('ADMIN', 'WRITER'))
130
+ WITH CHECK (public.get_current_user_role() IN ('ADMIN', 'WRITER'));
131
+
132
+
133
+ -- 7. MEDIA
134
+ -- Read: Publicly readable.
135
+ CREATE POLICY "media_read_policy" ON public.media
136
+ FOR SELECT TO public
137
+ USING (true);
138
+
139
+ -- Manage: Admins/Writers can do everything.
140
+ CREATE POLICY "media_manage_policy" ON public.media
141
+ FOR ALL TO authenticated
142
+ USING (public.get_current_user_role() IN ('ADMIN', 'WRITER'))
143
+ WITH CHECK (public.get_current_user_role() IN ('ADMIN', 'WRITER'));
144
+
145
+ -- Service Role: Full access (for uploads).
146
+ CREATE POLICY "media_service_role_policy" ON public.media
147
+ FOR ALL TO service_role
148
+ USING (true)
149
+ WITH CHECK (true);
150
+
151
+
152
+ -- 8. NAVIGATION
153
+ -- Read: Publicly readable.
154
+ CREATE POLICY "navigation_read_policy" ON public.navigation_items
155
+ FOR SELECT TO public
156
+ USING (true);
157
+
158
+ -- Manage: Admins only.
159
+ CREATE POLICY "navigation_manage_policy" ON public.navigation_items
160
+ FOR ALL TO authenticated
161
+ USING (public.get_current_user_role() = 'ADMIN')
162
+ WITH CHECK (public.get_current_user_role() = 'ADMIN');
163
+
164
+
165
+ -- 9. LANGUAGES
166
+ -- Read: Publicly readable.
167
+ CREATE POLICY "languages_read_policy" ON public.languages
168
+ FOR SELECT TO public
169
+ USING (true);
170
+
171
+ -- Manage: Admins only.
172
+ CREATE POLICY "languages_manage_policy" ON public.languages
173
+ FOR ALL TO authenticated
174
+ USING (public.get_current_user_role() = 'ADMIN')
175
+ WITH CHECK (public.get_current_user_role() = 'ADMIN');
176
+
177
+
178
+ -- 10. LOGOS
179
+ -- Read: Publicly readable.
180
+ CREATE POLICY "logos_read_policy" ON public.logos
181
+ FOR SELECT TO public
182
+ USING (true);
183
+
184
+ -- Manage: Admins only.
185
+ CREATE POLICY "logos_manage_policy" ON public.logos
186
+ FOR ALL TO authenticated
187
+ USING (public.get_current_user_role() = 'ADMIN')
188
+ WITH CHECK (public.get_current_user_role() = 'ADMIN');
189
+
190
+
191
+ -- 11. SITE SETTINGS
192
+ -- Read: Publicly readable.
193
+ CREATE POLICY "site_settings_read_policy" ON public.site_settings
194
+ FOR SELECT TO public
195
+ USING (true);
196
+
197
+ -- Manage: Admins/Writers.
198
+ CREATE POLICY "site_settings_manage_policy" ON public.site_settings
199
+ FOR ALL TO authenticated
200
+ USING (public.get_current_user_role() IN ('ADMIN', 'WRITER'))
201
+ WITH CHECK (public.get_current_user_role() IN ('ADMIN', 'WRITER'));
202
+
203
+
204
+ -- 12. TRANSLATIONS
205
+ -- Read: Publicly readable.
206
+ CREATE POLICY "translations_read_policy" ON public.translations
207
+ FOR SELECT TO public
208
+ USING (true);
209
+
210
+ -- Manage: Authenticated users (Open for now based on latest fix).
211
+ CREATE POLICY "translations_manage_policy" ON public.translations
212
+ FOR ALL TO authenticated
213
+ USING (true)
214
+ WITH CHECK (true);
215
+
216
+
217
+ -- 13. REVISIONS
218
+ -- Read: Authenticated users.
219
+ CREATE POLICY "page_revisions_read_policy" ON public.page_revisions
220
+ FOR SELECT TO authenticated
221
+ USING (true);
222
+
223
+ CREATE POLICY "post_revisions_read_policy" ON public.post_revisions
224
+ FOR SELECT TO authenticated
225
+ USING (true);
226
+
227
+ -- Manage: Admins/Writers.
228
+ CREATE POLICY "page_revisions_manage_policy" ON public.page_revisions
229
+ FOR ALL TO authenticated
230
+ USING (public.get_current_user_role() IN ('ADMIN', 'WRITER'))
231
+ WITH CHECK (public.get_current_user_role() IN ('ADMIN', 'WRITER'));
232
+
233
+ CREATE POLICY "post_revisions_manage_policy" ON public.post_revisions
234
+ FOR ALL TO authenticated
235
+ USING (public.get_current_user_role() IN ('ADMIN', 'WRITER'))
236
+ WITH CHECK (public.get_current_user_role() IN ('ADMIN', 'WRITER'));
237
+
238
+
239
+ COMMIT;
@@ -0,0 +1,139 @@
1
+ -- 00000000000040_seed_data.sql
2
+ -- Consolidated Seed Data: Translations, Logo, Foundational Content
3
+
4
+ BEGIN;
5
+
6
+ -- 1. Translations
7
+ -- Merged from multiple translation seed files
8
+ INSERT INTO public.translations (key, translations) VALUES
9
+ ('sign_in', '{"en": "Sign in", "fr": "Connexion"}'),
10
+ ('sign_up', '{"en": "Sign up", "fr": "Inscription"}'),
11
+ ('sign_out', '{"en": "Sign out", "fr": "Déconnexion"}'),
12
+ ('dont_have_account', '{"en": "Don''t have an account?", "fr": "Pas encore de compte ?"}'),
13
+ ('email', '{"en": "Email", "fr": "Email"}'),
14
+ ('you_at_example_com', '{"en": "you@example.com", "fr": "vous@example.com"}'),
15
+ ('password', '{"en": "Password", "fr": "Mot de passe"}'),
16
+ ('forgot_password', '{"en": "Forgot Password?", "fr": "Mot de passe oublié ?"}'),
17
+ ('your_password', '{"en": "Your password", "fr": "Votre mot de passe"}'),
18
+ ('signing_in_pending', '{"en": "Signing In...", "fr": "Connexion en cours..."}'),
19
+ ('already_have_account', '{"en": "Already have an account?", "fr": "Déjà un compte ?"}'),
20
+ ('signing_up_pending', '{"en": "Signing up...", "fr": "Inscription en cours..."}'),
21
+ ('reset_password', '{"en": "Reset Password", "fr": "Réinitialiser le mot de passe"}'),
22
+ ('blog_prefix', '{"en": "article", "fr": "article"}'),
23
+ ('edit_page', '{"en": "Edit Page", "fr": "Éditer la page"}'),
24
+ ('edit_post', '{"en": "Edit Post", "fr": "Éditer l''article"}'),
25
+ ('open_main_menu', '{"en": "Open main menu", "fr": "Ouvrir le menu principal"}'),
26
+ ('mobile_navigation_menu', '{"en": "Mobile navigation menu", "fr": "Menu de navigation mobile"}'),
27
+ ('cms_dashboard', '{"en": "CMS Dashboard", "fr": "Tableau de bord CMS"}'),
28
+ ('update_env_file_warning', '{"en": "Please update .env.local file with anon key and url", "fr": "Veuillez mettre à jour .env.local avec l''anon key et l''URL"}'),
29
+ ('greeting', '{"en": "Hey, {username}!", "fr": "Salut, {username} !"}')
30
+ ON CONFLICT (key) DO UPDATE
31
+ SET translations = EXCLUDED.translations;
32
+
33
+
34
+ -- 2. Site Logo
35
+ DO $$
36
+ DECLARE
37
+ v_logo_media_id UUID := gen_random_uuid();
38
+ v_admin_id UUID;
39
+ BEGIN
40
+ -- Get an admin user ID to set as uploader (optional, fallback to NULL)
41
+ SELECT id INTO v_admin_id FROM public.profiles WHERE role = 'ADMIN' LIMIT 1;
42
+
43
+ -- Insert the logo into the media table
44
+ INSERT INTO public.media (id, uploader_id, file_name, object_key, file_type, size_bytes, description)
45
+ VALUES (
46
+ v_logo_media_id,
47
+ v_admin_id,
48
+ 'nextblock-logo-small.webp',
49
+ '/images/nextblock-logo-small.webp',
50
+ 'image/webp',
51
+ 10000,
52
+ 'NextBlock Site Logo'
53
+ )
54
+ ON CONFLICT (object_key) DO UPDATE
55
+ SET
56
+ file_name = excluded.file_name,
57
+ file_type = excluded.file_type,
58
+ description = excluded.description
59
+ RETURNING id INTO v_logo_media_id;
60
+
61
+ -- Insert the logo into the logos table
62
+ INSERT INTO public.logos (name, media_id)
63
+ VALUES ('NextBlock Logo', v_logo_media_id)
64
+ ON CONFLICT DO NOTHING; -- Assuming name is not unique but we don't want to double insert if running multiple times? No unique constraint on name.
65
+ -- Actually, logos table has no unique constraint on name.
66
+ -- But since this is a seed, we might want to avoid duplicates if run multiple times.
67
+ -- Let's check if it exists.
68
+ IF NOT EXISTS (SELECT 1 FROM public.logos WHERE name = 'NextBlock Logo') THEN
69
+ INSERT INTO public.logos (name, media_id) VALUES ('NextBlock Logo', v_logo_media_id);
70
+ END IF;
71
+
72
+ END $$;
73
+
74
+
75
+ -- 3. Foundational Content (Pages & Posts Structure)
76
+ DO $$
77
+ DECLARE
78
+ v_home_page_group_id uuid := gen_random_uuid();
79
+ v_blog_page_group_id uuid := gen_random_uuid();
80
+ v_how_it_works_post_group_id uuid := gen_random_uuid();
81
+ v_en_lang_id bigint;
82
+ v_fr_lang_id bigint;
83
+ v_feature_media_id uuid;
84
+ BEGIN
85
+ -- Ensure languages exist (already seeded in setup_languages, but good to be safe/get IDs)
86
+ SELECT id INTO v_en_lang_id FROM public.languages WHERE code = 'en' LIMIT 1;
87
+ SELECT id INTO v_fr_lang_id FROM public.languages WHERE code = 'fr' LIMIT 1;
88
+
89
+ IF v_en_lang_id IS NULL OR v_fr_lang_id IS NULL THEN
90
+ RAISE EXCEPTION 'Required languages (en, fr) not found.';
91
+ END IF;
92
+
93
+ -- Scaffold Home Pages
94
+ INSERT INTO public.pages (language_id, title, slug, status, translation_group_id)
95
+ VALUES (v_en_lang_id, 'Home', 'home', 'published', v_home_page_group_id)
96
+ ON CONFLICT (language_id, slug) DO UPDATE
97
+ SET title = EXCLUDED.title, status = EXCLUDED.status;
98
+ -- Note: We don't update translation_group_id on conflict to avoid overwriting existing groups if they were manually set?
99
+ -- Actually, for a fresh reset, it doesn't matter. For an update, we might want to preserve.
100
+ -- But the user said "reset the database every time". So we can overwrite.
101
+
102
+ INSERT INTO public.pages (language_id, title, slug, status, translation_group_id)
103
+ VALUES (v_fr_lang_id, 'Accueil', 'accueil', 'published', v_home_page_group_id)
104
+ ON CONFLICT (language_id, slug) DO UPDATE
105
+ SET title = EXCLUDED.title, status = EXCLUDED.status;
106
+
107
+ -- Scaffold Articles Pages
108
+ INSERT INTO public.pages (language_id, title, slug, status, translation_group_id)
109
+ VALUES (v_en_lang_id, 'Articles', 'articles', 'published', v_blog_page_group_id)
110
+ ON CONFLICT (language_id, slug) DO UPDATE
111
+ SET title = EXCLUDED.title, status = EXCLUDED.status;
112
+
113
+ INSERT INTO public.pages (language_id, title, slug, status, translation_group_id)
114
+ VALUES (v_fr_lang_id, 'Articles', 'articles', 'published', v_blog_page_group_id)
115
+ ON CONFLICT (language_id, slug) DO UPDATE
116
+ SET title = EXCLUDED.title, status = EXCLUDED.status;
117
+
118
+ -- Seed Featured Image
119
+ v_feature_media_id := gen_random_uuid();
120
+ INSERT INTO public.media (id, file_name, object_key, file_type, size_bytes)
121
+ VALUES (v_feature_media_id, 'programmer-upscaled.webp', '/images/programmer-upscaled.webp', 'image/webp', 100000)
122
+ ON CONFLICT (object_key) DO UPDATE
123
+ SET file_name = EXCLUDED.file_name
124
+ RETURNING id INTO v_feature_media_id;
125
+
126
+ -- Seed "How It Works" Post
127
+ INSERT INTO public.posts (language_id, title, slug, status, translation_group_id, feature_image_id)
128
+ VALUES (v_en_lang_id, 'How NextBlock Works: A Look Under the Hood', 'how-nextblock-works', 'published', v_how_it_works_post_group_id, v_feature_media_id)
129
+ ON CONFLICT (language_id, slug) DO UPDATE
130
+ SET title = EXCLUDED.title, status = EXCLUDED.status, feature_image_id = EXCLUDED.feature_image_id;
131
+
132
+ INSERT INTO public.posts (language_id, title, slug, status, translation_group_id, feature_image_id)
133
+ VALUES (v_fr_lang_id, 'Comment NextBlock Fonctionne : Regard Sous le Capot', 'comment-nextblock-fonctionne', 'published', v_how_it_works_post_group_id, v_feature_media_id)
134
+ ON CONFLICT (language_id, slug) DO UPDATE
135
+ SET title = EXCLUDED.title, status = EXCLUDED.status, feature_image_id = EXCLUDED.feature_image_id;
136
+
137
+ END $$;
138
+
139
+ COMMIT;