@orion-studios/cms 0.5.1 → 0.5.2
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/README.md +24 -1
- package/dist/{chunk-HVJCF2IZ.js → chunk-AYV6KYDP.js} +3 -3
- package/dist/content/index.js +1 -1
- package/dist/forms/react.d.ts +2 -2
- package/dist/server/index.d.ts +4 -3
- package/dist/server/index.js +171 -42
- package/dist/studio/index.d.ts +14 -5
- package/dist/studio/index.js +149 -67
- package/package.json +1 -1
- package/sql/bootstrap.sql +478 -60
- package/sql/migrations/20260820033957_sec_003_exact_version_publication.sql +575 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
-- SEC-003: exact-version publication and published metadata separation.
|
|
2
|
+
|
|
3
|
+
alter table cms_pages add column if not exists published_title text;
|
|
4
|
+
alter table cms_pages add column if not exists published_seo jsonb;
|
|
5
|
+
alter table cms_pages add column if not exists draft_version_id bigint;
|
|
6
|
+
alter table cms_pages add column if not exists published_version_id bigint;
|
|
7
|
+
alter table cms_pages add column if not exists scheduled_version_id bigint;
|
|
8
|
+
|
|
9
|
+
-- Existing public metadata comes from the newest publish or sync snapshot
|
|
10
|
+
-- whose layout matches what is already public. This avoids promoting a later
|
|
11
|
+
-- draft title or SEO value during migration.
|
|
12
|
+
update cms_pages
|
|
13
|
+
set published_layout = '[]'::jsonb
|
|
14
|
+
where status = 'published' and published_layout is null;
|
|
15
|
+
|
|
16
|
+
with published_history as (
|
|
17
|
+
select distinct on (p.id)
|
|
18
|
+
p.id as page_id,
|
|
19
|
+
v.id as version_id,
|
|
20
|
+
v.title,
|
|
21
|
+
v.seo
|
|
22
|
+
from cms_pages p
|
|
23
|
+
join cms_page_versions v
|
|
24
|
+
on v.page_id = p.id
|
|
25
|
+
and v.kind in ('publish', 'sync')
|
|
26
|
+
and v.layout is not distinct from p.published_layout
|
|
27
|
+
where p.status = 'published'
|
|
28
|
+
order by p.id, v.created_at desc, v.id desc
|
|
29
|
+
)
|
|
30
|
+
update cms_pages p
|
|
31
|
+
set published_title = h.title,
|
|
32
|
+
published_seo = h.seo,
|
|
33
|
+
published_version_id = h.version_id
|
|
34
|
+
from published_history h
|
|
35
|
+
where p.id = h.page_id
|
|
36
|
+
and p.published_version_id is null;
|
|
37
|
+
|
|
38
|
+
-- If no publication history exists, freeze the metadata and layout that were
|
|
39
|
+
-- already public and create an immutable baseline version for them.
|
|
40
|
+
with inserted as (
|
|
41
|
+
insert into cms_page_versions (page_id, kind, title, seo, layout)
|
|
42
|
+
select id, 'publish', title, seo, published_layout
|
|
43
|
+
from cms_pages
|
|
44
|
+
where status = 'published'
|
|
45
|
+
and published_version_id is null
|
|
46
|
+
returning id, page_id, title, seo
|
|
47
|
+
)
|
|
48
|
+
update cms_pages p
|
|
49
|
+
set published_title = i.title,
|
|
50
|
+
published_seo = i.seo,
|
|
51
|
+
published_version_id = i.id
|
|
52
|
+
from inserted i
|
|
53
|
+
where p.id = i.page_id;
|
|
54
|
+
|
|
55
|
+
-- Bind every current draft to an existing exact snapshot when possible.
|
|
56
|
+
with draft_history as (
|
|
57
|
+
select distinct on (p.id)
|
|
58
|
+
p.id as page_id,
|
|
59
|
+
v.id as version_id
|
|
60
|
+
from cms_pages p
|
|
61
|
+
join cms_page_versions v
|
|
62
|
+
on v.page_id = p.id
|
|
63
|
+
and v.title is not distinct from p.title
|
|
64
|
+
and v.seo is not distinct from p.seo
|
|
65
|
+
and v.layout is not distinct from p.draft_layout
|
|
66
|
+
order by p.id, v.created_at desc, v.id desc
|
|
67
|
+
)
|
|
68
|
+
update cms_pages p
|
|
69
|
+
set draft_version_id = h.version_id
|
|
70
|
+
from draft_history h
|
|
71
|
+
where p.id = h.page_id
|
|
72
|
+
and p.draft_version_id is null;
|
|
73
|
+
|
|
74
|
+
with inserted as (
|
|
75
|
+
insert into cms_page_versions (page_id, kind, title, seo, layout)
|
|
76
|
+
select id, 'draft', title, seo, draft_layout
|
|
77
|
+
from cms_pages
|
|
78
|
+
where draft_version_id is null
|
|
79
|
+
returning id, page_id
|
|
80
|
+
)
|
|
81
|
+
update cms_pages p
|
|
82
|
+
set draft_version_id = i.id
|
|
83
|
+
from inserted i
|
|
84
|
+
where p.id = i.page_id;
|
|
85
|
+
|
|
86
|
+
-- A legacy timestamp cannot prove which draft was reviewed. Clear the
|
|
87
|
+
-- incomplete schedule so an editor must explicitly bind an exact version.
|
|
88
|
+
update cms_pages
|
|
89
|
+
set publish_at = null,
|
|
90
|
+
scheduled_version_id = null
|
|
91
|
+
where publish_at is not null
|
|
92
|
+
and scheduled_version_id is null;
|
|
93
|
+
|
|
94
|
+
create unique index if not exists cms_page_versions_page_id_id_idx
|
|
95
|
+
on cms_page_versions (page_id, id);
|
|
96
|
+
create index if not exists cms_pages_scheduled_publish_due_idx
|
|
97
|
+
on cms_pages (publish_at)
|
|
98
|
+
where publish_at is not null and scheduled_version_id is not null;
|
|
99
|
+
|
|
100
|
+
do $constraints$
|
|
101
|
+
begin
|
|
102
|
+
if not exists (
|
|
103
|
+
select 1 from pg_constraint where conname = 'cms_pages_draft_version_fk' and conrelid = 'cms_pages'::regclass
|
|
104
|
+
) then
|
|
105
|
+
alter table cms_pages
|
|
106
|
+
add constraint cms_pages_draft_version_fk
|
|
107
|
+
foreign key (id, draft_version_id)
|
|
108
|
+
references cms_page_versions (page_id, id);
|
|
109
|
+
end if;
|
|
110
|
+
|
|
111
|
+
if not exists (
|
|
112
|
+
select 1 from pg_constraint where conname = 'cms_pages_published_version_fk' and conrelid = 'cms_pages'::regclass
|
|
113
|
+
) then
|
|
114
|
+
alter table cms_pages
|
|
115
|
+
add constraint cms_pages_published_version_fk
|
|
116
|
+
foreign key (id, published_version_id)
|
|
117
|
+
references cms_page_versions (page_id, id);
|
|
118
|
+
end if;
|
|
119
|
+
|
|
120
|
+
if not exists (
|
|
121
|
+
select 1 from pg_constraint where conname = 'cms_pages_scheduled_version_fk' and conrelid = 'cms_pages'::regclass
|
|
122
|
+
) then
|
|
123
|
+
alter table cms_pages
|
|
124
|
+
add constraint cms_pages_scheduled_version_fk
|
|
125
|
+
foreign key (id, scheduled_version_id)
|
|
126
|
+
references cms_page_versions (page_id, id);
|
|
127
|
+
end if;
|
|
128
|
+
|
|
129
|
+
if not exists (
|
|
130
|
+
select 1 from pg_constraint where conname = 'cms_pages_schedule_version_pair' and conrelid = 'cms_pages'::regclass
|
|
131
|
+
) then
|
|
132
|
+
alter table cms_pages
|
|
133
|
+
add constraint cms_pages_schedule_version_pair
|
|
134
|
+
check ((publish_at is null) = (scheduled_version_id is null));
|
|
135
|
+
end if;
|
|
136
|
+
|
|
137
|
+
if not exists (
|
|
138
|
+
select 1 from pg_constraint where conname = 'cms_pages_published_state_complete' and conrelid = 'cms_pages'::regclass
|
|
139
|
+
) then
|
|
140
|
+
alter table cms_pages
|
|
141
|
+
add constraint cms_pages_published_state_complete
|
|
142
|
+
check (
|
|
143
|
+
status <> 'published'
|
|
144
|
+
or (
|
|
145
|
+
published_title is not null
|
|
146
|
+
and published_seo is not null
|
|
147
|
+
and published_layout is not null
|
|
148
|
+
and published_version_id is not null
|
|
149
|
+
)
|
|
150
|
+
);
|
|
151
|
+
end if;
|
|
152
|
+
end
|
|
153
|
+
$constraints$;
|
|
154
|
+
|
|
155
|
+
create or replace function cms_save_page_draft(
|
|
156
|
+
p_page_id uuid,
|
|
157
|
+
p_title text,
|
|
158
|
+
p_seo jsonb,
|
|
159
|
+
p_layout jsonb,
|
|
160
|
+
p_actor uuid default null,
|
|
161
|
+
p_mark_builder_owned boolean default true
|
|
162
|
+
) returns cms_pages
|
|
163
|
+
language plpgsql
|
|
164
|
+
security definer
|
|
165
|
+
set search_path = public
|
|
166
|
+
as $$
|
|
167
|
+
declare
|
|
168
|
+
current_page cms_pages;
|
|
169
|
+
result cms_pages;
|
|
170
|
+
version_id bigint;
|
|
171
|
+
next_title text;
|
|
172
|
+
next_seo jsonb;
|
|
173
|
+
next_layout jsonb;
|
|
174
|
+
begin
|
|
175
|
+
select * into current_page
|
|
176
|
+
from cms_pages
|
|
177
|
+
where id = p_page_id
|
|
178
|
+
for update;
|
|
179
|
+
|
|
180
|
+
if current_page.id is null then
|
|
181
|
+
raise exception 'page % not found', p_page_id;
|
|
182
|
+
end if;
|
|
183
|
+
|
|
184
|
+
next_title := coalesce(p_title, current_page.title);
|
|
185
|
+
next_seo := coalesce(p_seo, current_page.seo);
|
|
186
|
+
next_layout := coalesce(p_layout, current_page.draft_layout);
|
|
187
|
+
|
|
188
|
+
select id into version_id
|
|
189
|
+
from cms_page_versions
|
|
190
|
+
where page_id = p_page_id
|
|
191
|
+
and title is not distinct from next_title
|
|
192
|
+
and seo is not distinct from next_seo
|
|
193
|
+
and layout is not distinct from next_layout
|
|
194
|
+
order by created_at desc, id desc
|
|
195
|
+
limit 1;
|
|
196
|
+
|
|
197
|
+
if version_id is null then
|
|
198
|
+
insert into cms_page_versions (page_id, kind, title, seo, layout, created_by)
|
|
199
|
+
values (p_page_id, 'draft', next_title, next_seo, next_layout, p_actor)
|
|
200
|
+
returning id into version_id;
|
|
201
|
+
end if;
|
|
202
|
+
|
|
203
|
+
update cms_pages
|
|
204
|
+
set title = next_title,
|
|
205
|
+
seo = next_seo,
|
|
206
|
+
draft_layout = next_layout,
|
|
207
|
+
draft_version_id = version_id,
|
|
208
|
+
builder_owned = builder_owned or p_mark_builder_owned,
|
|
209
|
+
updated_at = now()
|
|
210
|
+
where id = p_page_id
|
|
211
|
+
returning * into result;
|
|
212
|
+
|
|
213
|
+
return result;
|
|
214
|
+
end;
|
|
215
|
+
$$;
|
|
216
|
+
|
|
217
|
+
-- The old signature remains only to fail closed for stale application code.
|
|
218
|
+
create or replace function cms_publish_page(
|
|
219
|
+
p_page_id uuid,
|
|
220
|
+
p_actor uuid default null
|
|
221
|
+
) returns cms_pages
|
|
222
|
+
language plpgsql
|
|
223
|
+
security definer
|
|
224
|
+
set search_path = public
|
|
225
|
+
as $$
|
|
226
|
+
begin
|
|
227
|
+
raise exception 'Page version conflict: expected version is required';
|
|
228
|
+
end;
|
|
229
|
+
$$;
|
|
230
|
+
|
|
231
|
+
create or replace function cms_publish_page(
|
|
232
|
+
p_page_id uuid,
|
|
233
|
+
p_expected_version_id bigint,
|
|
234
|
+
p_actor uuid
|
|
235
|
+
) returns cms_pages
|
|
236
|
+
language plpgsql
|
|
237
|
+
security definer
|
|
238
|
+
set search_path = public
|
|
239
|
+
as $$
|
|
240
|
+
declare
|
|
241
|
+
current_page cms_pages;
|
|
242
|
+
reviewed cms_page_versions;
|
|
243
|
+
result cms_pages;
|
|
244
|
+
begin
|
|
245
|
+
select * into current_page
|
|
246
|
+
from cms_pages
|
|
247
|
+
where id = p_page_id
|
|
248
|
+
for update;
|
|
249
|
+
|
|
250
|
+
if current_page.id is null then
|
|
251
|
+
raise exception 'page % not found', p_page_id;
|
|
252
|
+
end if;
|
|
253
|
+
|
|
254
|
+
select * into reviewed
|
|
255
|
+
from cms_page_versions
|
|
256
|
+
where id = p_expected_version_id
|
|
257
|
+
and page_id = p_page_id;
|
|
258
|
+
|
|
259
|
+
if reviewed.id is null
|
|
260
|
+
or current_page.draft_version_id is distinct from p_expected_version_id
|
|
261
|
+
or reviewed.title is distinct from current_page.title
|
|
262
|
+
or reviewed.seo is distinct from current_page.seo
|
|
263
|
+
or reviewed.layout is distinct from current_page.draft_layout then
|
|
264
|
+
raise exception 'Page version conflict: expected draft is missing or stale';
|
|
265
|
+
end if;
|
|
266
|
+
|
|
267
|
+
if current_page.status = 'published'
|
|
268
|
+
and current_page.published_version_id = p_expected_version_id then
|
|
269
|
+
raise exception 'Page version conflict: expected draft is already published';
|
|
270
|
+
end if;
|
|
271
|
+
|
|
272
|
+
update cms_pages
|
|
273
|
+
set published_title = reviewed.title,
|
|
274
|
+
published_seo = reviewed.seo,
|
|
275
|
+
published_layout = reviewed.layout,
|
|
276
|
+
published_version_id = reviewed.id,
|
|
277
|
+
status = 'published',
|
|
278
|
+
published_at = now(),
|
|
279
|
+
publish_at = null,
|
|
280
|
+
scheduled_version_id = null,
|
|
281
|
+
updated_at = now()
|
|
282
|
+
where id = p_page_id
|
|
283
|
+
returning * into result;
|
|
284
|
+
|
|
285
|
+
return result;
|
|
286
|
+
end;
|
|
287
|
+
$$;
|
|
288
|
+
|
|
289
|
+
create or replace function cms_schedule_page_publish(
|
|
290
|
+
p_page_id uuid,
|
|
291
|
+
p_expected_version_id bigint,
|
|
292
|
+
p_publish_at timestamptz,
|
|
293
|
+
p_actor uuid
|
|
294
|
+
) returns cms_pages
|
|
295
|
+
language plpgsql
|
|
296
|
+
security definer
|
|
297
|
+
set search_path = public
|
|
298
|
+
as $$
|
|
299
|
+
declare
|
|
300
|
+
current_page cms_pages;
|
|
301
|
+
reviewed cms_page_versions;
|
|
302
|
+
result cms_pages;
|
|
303
|
+
begin
|
|
304
|
+
select * into current_page
|
|
305
|
+
from cms_pages
|
|
306
|
+
where id = p_page_id
|
|
307
|
+
for update;
|
|
308
|
+
|
|
309
|
+
if current_page.id is null then
|
|
310
|
+
raise exception 'page % not found', p_page_id;
|
|
311
|
+
end if;
|
|
312
|
+
|
|
313
|
+
if p_publish_at is null then
|
|
314
|
+
update cms_pages
|
|
315
|
+
set publish_at = null,
|
|
316
|
+
scheduled_version_id = null,
|
|
317
|
+
updated_at = now()
|
|
318
|
+
where id = p_page_id
|
|
319
|
+
returning * into result;
|
|
320
|
+
return result;
|
|
321
|
+
end if;
|
|
322
|
+
|
|
323
|
+
select * into reviewed
|
|
324
|
+
from cms_page_versions
|
|
325
|
+
where id = p_expected_version_id
|
|
326
|
+
and page_id = p_page_id;
|
|
327
|
+
|
|
328
|
+
if reviewed.id is null
|
|
329
|
+
or current_page.draft_version_id is distinct from p_expected_version_id
|
|
330
|
+
or reviewed.title is distinct from current_page.title
|
|
331
|
+
or reviewed.seo is distinct from current_page.seo
|
|
332
|
+
or reviewed.layout is distinct from current_page.draft_layout then
|
|
333
|
+
raise exception 'Page version conflict: expected draft is missing or stale';
|
|
334
|
+
end if;
|
|
335
|
+
|
|
336
|
+
update cms_pages
|
|
337
|
+
set publish_at = p_publish_at,
|
|
338
|
+
scheduled_version_id = reviewed.id,
|
|
339
|
+
updated_at = now()
|
|
340
|
+
where id = p_page_id
|
|
341
|
+
returning * into result;
|
|
342
|
+
|
|
343
|
+
return result;
|
|
344
|
+
end;
|
|
345
|
+
$$;
|
|
346
|
+
|
|
347
|
+
create or replace function cms_restore_page_version(
|
|
348
|
+
p_version_id bigint,
|
|
349
|
+
p_actor uuid default null
|
|
350
|
+
) returns cms_pages
|
|
351
|
+
language plpgsql
|
|
352
|
+
security definer
|
|
353
|
+
set search_path = public
|
|
354
|
+
as $$
|
|
355
|
+
declare
|
|
356
|
+
v cms_page_versions;
|
|
357
|
+
result cms_pages;
|
|
358
|
+
restored_version_id bigint;
|
|
359
|
+
begin
|
|
360
|
+
select * into v from cms_page_versions where id = p_version_id;
|
|
361
|
+
if v.id is null then
|
|
362
|
+
raise exception 'version % not found', p_version_id;
|
|
363
|
+
end if;
|
|
364
|
+
|
|
365
|
+
perform 1 from cms_pages where id = v.page_id for update;
|
|
366
|
+
|
|
367
|
+
insert into cms_page_versions (page_id, kind, title, seo, layout, created_by)
|
|
368
|
+
values (v.page_id, 'restore', v.title, v.seo, v.layout, p_actor)
|
|
369
|
+
returning id into restored_version_id;
|
|
370
|
+
|
|
371
|
+
update cms_pages
|
|
372
|
+
set draft_layout = v.layout,
|
|
373
|
+
title = v.title,
|
|
374
|
+
seo = v.seo,
|
|
375
|
+
draft_version_id = restored_version_id,
|
|
376
|
+
builder_owned = true,
|
|
377
|
+
updated_at = now()
|
|
378
|
+
where id = v.page_id
|
|
379
|
+
returning * into result;
|
|
380
|
+
|
|
381
|
+
return result;
|
|
382
|
+
end;
|
|
383
|
+
$$;
|
|
384
|
+
|
|
385
|
+
create or replace function cms_sync_page(
|
|
386
|
+
p_slug text,
|
|
387
|
+
p_path text,
|
|
388
|
+
p_title text,
|
|
389
|
+
p_seo jsonb,
|
|
390
|
+
p_layout jsonb
|
|
391
|
+
) returns cms_pages
|
|
392
|
+
language plpgsql
|
|
393
|
+
security definer
|
|
394
|
+
set search_path = public
|
|
395
|
+
as $$
|
|
396
|
+
declare
|
|
397
|
+
result cms_pages;
|
|
398
|
+
version_id bigint;
|
|
399
|
+
is_new boolean := false;
|
|
400
|
+
begin
|
|
401
|
+
insert into cms_pages (
|
|
402
|
+
slug, path, title, seo, draft_layout,
|
|
403
|
+
published_title, published_seo, published_layout,
|
|
404
|
+
status, published_at
|
|
405
|
+
)
|
|
406
|
+
values (
|
|
407
|
+
p_slug, p_path, p_title, p_seo, p_layout,
|
|
408
|
+
p_title, p_seo, p_layout,
|
|
409
|
+
'draft', null
|
|
410
|
+
)
|
|
411
|
+
on conflict (slug) do nothing
|
|
412
|
+
returning * into result;
|
|
413
|
+
|
|
414
|
+
if result.id is not null then
|
|
415
|
+
is_new := true;
|
|
416
|
+
else
|
|
417
|
+
select * into result
|
|
418
|
+
from cms_pages
|
|
419
|
+
where slug = p_slug
|
|
420
|
+
for update;
|
|
421
|
+
|
|
422
|
+
if not result.builder_owned then
|
|
423
|
+
update cms_pages
|
|
424
|
+
set path = p_path,
|
|
425
|
+
title = p_title,
|
|
426
|
+
seo = p_seo,
|
|
427
|
+
draft_layout = p_layout,
|
|
428
|
+
published_title = p_title,
|
|
429
|
+
published_seo = p_seo,
|
|
430
|
+
published_layout = p_layout,
|
|
431
|
+
updated_at = now()
|
|
432
|
+
where id = result.id
|
|
433
|
+
returning * into result;
|
|
434
|
+
end if;
|
|
435
|
+
end if;
|
|
436
|
+
|
|
437
|
+
select id into version_id
|
|
438
|
+
from cms_page_versions
|
|
439
|
+
where page_id = result.id
|
|
440
|
+
and title is not distinct from result.title
|
|
441
|
+
and seo is not distinct from result.seo
|
|
442
|
+
and layout is not distinct from result.draft_layout
|
|
443
|
+
order by created_at desc, id desc
|
|
444
|
+
limit 1;
|
|
445
|
+
|
|
446
|
+
if version_id is null then
|
|
447
|
+
insert into cms_page_versions (page_id, kind, title, seo, layout)
|
|
448
|
+
values (result.id, 'sync', result.title, result.seo, result.draft_layout)
|
|
449
|
+
returning id into version_id;
|
|
450
|
+
end if;
|
|
451
|
+
|
|
452
|
+
if not result.builder_owned then
|
|
453
|
+
update cms_pages
|
|
454
|
+
set draft_version_id = version_id,
|
|
455
|
+
published_version_id = version_id,
|
|
456
|
+
status = case when is_new then 'published' else status end,
|
|
457
|
+
published_at = case when is_new then now() else published_at end
|
|
458
|
+
where id = result.id
|
|
459
|
+
returning * into result;
|
|
460
|
+
end if;
|
|
461
|
+
|
|
462
|
+
return result;
|
|
463
|
+
end;
|
|
464
|
+
$$;
|
|
465
|
+
|
|
466
|
+
create or replace function cms_publish_due_pages()
|
|
467
|
+
returns setof text
|
|
468
|
+
language plpgsql
|
|
469
|
+
security definer
|
|
470
|
+
set search_path = public
|
|
471
|
+
as $$
|
|
472
|
+
declare
|
|
473
|
+
due record;
|
|
474
|
+
begin
|
|
475
|
+
for due in
|
|
476
|
+
select
|
|
477
|
+
p.id,
|
|
478
|
+
p.path,
|
|
479
|
+
v.id as version_id,
|
|
480
|
+
v.title,
|
|
481
|
+
v.seo,
|
|
482
|
+
v.layout
|
|
483
|
+
from cms_pages p
|
|
484
|
+
join cms_page_versions v
|
|
485
|
+
on v.page_id = p.id
|
|
486
|
+
and v.id = p.scheduled_version_id
|
|
487
|
+
where p.publish_at is not null
|
|
488
|
+
and p.publish_at <= now()
|
|
489
|
+
and p.scheduled_version_id is not null
|
|
490
|
+
order by p.publish_at
|
|
491
|
+
for update of p skip locked
|
|
492
|
+
loop
|
|
493
|
+
update cms_pages
|
|
494
|
+
set published_title = due.title,
|
|
495
|
+
published_seo = due.seo,
|
|
496
|
+
published_layout = due.layout,
|
|
497
|
+
published_version_id = due.version_id,
|
|
498
|
+
status = 'published',
|
|
499
|
+
published_at = now(),
|
|
500
|
+
publish_at = null,
|
|
501
|
+
scheduled_version_id = null,
|
|
502
|
+
updated_at = now()
|
|
503
|
+
where id = due.id;
|
|
504
|
+
|
|
505
|
+
insert into cms_activity (action, subject)
|
|
506
|
+
values ('page.publish.scheduled', due.path);
|
|
507
|
+
|
|
508
|
+
return next due.path;
|
|
509
|
+
end loop;
|
|
510
|
+
return;
|
|
511
|
+
end;
|
|
512
|
+
$$;
|
|
513
|
+
|
|
514
|
+
do $fn_lockdown$
|
|
515
|
+
declare
|
|
516
|
+
fn text;
|
|
517
|
+
fns text[] := array[
|
|
518
|
+
'cms_save_page_draft(uuid, text, jsonb, jsonb, uuid, boolean)',
|
|
519
|
+
'cms_publish_page(uuid, uuid)',
|
|
520
|
+
'cms_publish_page(uuid, bigint, uuid)',
|
|
521
|
+
'cms_schedule_page_publish(uuid, bigint, timestamptz, uuid)',
|
|
522
|
+
'cms_restore_page_version(bigint, uuid)',
|
|
523
|
+
'cms_sync_page(text, text, text, jsonb, jsonb)',
|
|
524
|
+
'cms_publish_due_pages()'
|
|
525
|
+
];
|
|
526
|
+
begin
|
|
527
|
+
foreach fn in array fns loop
|
|
528
|
+
execute format('revoke execute on function %s from public', fn);
|
|
529
|
+
if exists (select 1 from pg_roles where rolname = 'anon') then
|
|
530
|
+
execute format('revoke execute on function %s from anon', fn);
|
|
531
|
+
end if;
|
|
532
|
+
if exists (select 1 from pg_roles where rolname = 'authenticated') then
|
|
533
|
+
execute format('revoke execute on function %s from authenticated', fn);
|
|
534
|
+
end if;
|
|
535
|
+
if exists (select 1 from pg_roles where rolname = 'service_role') then
|
|
536
|
+
execute format('grant execute on function %s to service_role', fn);
|
|
537
|
+
end if;
|
|
538
|
+
end loop;
|
|
539
|
+
end
|
|
540
|
+
$fn_lockdown$;
|
|
541
|
+
|
|
542
|
+
revoke update, delete on cms_page_versions from public;
|
|
543
|
+
do $version_grants$
|
|
544
|
+
begin
|
|
545
|
+
if exists (select 1 from pg_roles where rolname = 'anon') then
|
|
546
|
+
revoke all on cms_page_versions from anon;
|
|
547
|
+
end if;
|
|
548
|
+
if exists (select 1 from pg_roles where rolname = 'authenticated') then
|
|
549
|
+
revoke all on cms_page_versions from authenticated;
|
|
550
|
+
end if;
|
|
551
|
+
if exists (select 1 from pg_roles where rolname = 'service_role') then
|
|
552
|
+
revoke update, delete on cms_page_versions from service_role;
|
|
553
|
+
grant select, insert on cms_page_versions to service_role;
|
|
554
|
+
end if;
|
|
555
|
+
end
|
|
556
|
+
$version_grants$;
|
|
557
|
+
|
|
558
|
+
do $public_page_grants$
|
|
559
|
+
begin
|
|
560
|
+
if exists (select 1 from pg_roles where rolname = 'anon') then
|
|
561
|
+
revoke select on cms_pages from anon;
|
|
562
|
+
grant select (
|
|
563
|
+
id, slug, path, published_title, published_seo, status,
|
|
564
|
+
published_layout, published_at, created_at, updated_at
|
|
565
|
+
) on cms_pages to anon;
|
|
566
|
+
end if;
|
|
567
|
+
if exists (select 1 from pg_roles where rolname = 'authenticated') then
|
|
568
|
+
revoke select on cms_pages from authenticated;
|
|
569
|
+
grant select (
|
|
570
|
+
id, slug, path, published_title, published_seo, status,
|
|
571
|
+
published_layout, published_at, created_at, updated_at
|
|
572
|
+
) on cms_pages to authenticated;
|
|
573
|
+
end if;
|
|
574
|
+
end
|
|
575
|
+
$public_page_grants$;
|