@orion-studios/cms 0.5.2 → 0.5.3

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.
@@ -2204,6 +2204,9 @@ function runRpc(store, fn, args = {}) {
2204
2204
  }
2205
2205
  const version = reviewedVersion(page, args.p_expected_version_id);
2206
2206
  if (!version) return versionConflict("the expected draft is missing or stale");
2207
+ if (page.published_version_id === version.id) {
2208
+ return versionConflict("the expected draft is already published");
2209
+ }
2207
2210
  page.publish_at = args.p_publish_at;
2208
2211
  page.scheduled_version_id = version.id;
2209
2212
  page.updated_at = nowIso();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orion-studios/cms",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Orion CMS v2 core engine \u2014 JSONB content model on Supabase primitives",
5
5
  "type": "module",
6
6
  "exports": {
package/sql/bootstrap.sql CHANGED
@@ -152,6 +152,16 @@ set publish_at = null,
152
152
  where publish_at is not null
153
153
  and scheduled_version_id is null;
154
154
 
155
+ -- A published version is not a future publication candidate. Clear invalid
156
+ -- schedules left by versions of the scheduling function that omitted this
157
+ -- guard, so repeated bootstrap runs repair existing databases fail closed.
158
+ update cms_pages
159
+ set publish_at = null,
160
+ scheduled_version_id = null,
161
+ updated_at = now()
162
+ where scheduled_version_id is not null
163
+ and scheduled_version_id = published_version_id;
164
+
155
165
  create unique index if not exists cms_page_versions_page_id_id_idx
156
166
  on cms_page_versions (page_id, id);
157
167
  create index if not exists cms_pages_scheduled_publish_due_idx
@@ -624,6 +634,10 @@ begin
624
634
  raise exception 'Page version conflict: expected draft is missing or stale';
625
635
  end if;
626
636
 
637
+ if current_page.published_version_id = p_expected_version_id then
638
+ raise exception 'Page version conflict: expected draft is already published';
639
+ end if;
640
+
627
641
  update cms_pages
628
642
  set publish_at = p_publish_at,
629
643
  scheduled_version_id = reviewed.id,
@@ -0,0 +1,88 @@
1
+ -- SEC-005: reject scheduling a version that is already published.
2
+
3
+ -- Repair schedules accepted by the 0.5.2 function. The version is already
4
+ -- public, so there is no future immutable draft to publish. Editors must
5
+ -- explicitly select a newer draft.
6
+ update cms_pages
7
+ set publish_at = null,
8
+ scheduled_version_id = null,
9
+ updated_at = now()
10
+ where scheduled_version_id is not null
11
+ and scheduled_version_id = published_version_id;
12
+
13
+ create or replace function cms_schedule_page_publish(
14
+ p_page_id uuid,
15
+ p_expected_version_id bigint,
16
+ p_publish_at timestamptz,
17
+ p_actor uuid
18
+ ) returns cms_pages
19
+ language plpgsql
20
+ security definer
21
+ set search_path = public
22
+ as $$
23
+ declare
24
+ current_page cms_pages;
25
+ reviewed cms_page_versions;
26
+ result cms_pages;
27
+ begin
28
+ select * into current_page
29
+ from cms_pages
30
+ where id = p_page_id
31
+ for update;
32
+
33
+ if current_page.id is null then
34
+ raise exception 'page % not found', p_page_id;
35
+ end if;
36
+
37
+ if p_publish_at is null then
38
+ update cms_pages
39
+ set publish_at = null,
40
+ scheduled_version_id = null,
41
+ updated_at = now()
42
+ where id = p_page_id
43
+ returning * into result;
44
+ return result;
45
+ end if;
46
+
47
+ select * into reviewed
48
+ from cms_page_versions
49
+ where id = p_expected_version_id
50
+ and page_id = p_page_id;
51
+
52
+ if reviewed.id is null
53
+ or current_page.draft_version_id is distinct from p_expected_version_id
54
+ or reviewed.title is distinct from current_page.title
55
+ or reviewed.seo is distinct from current_page.seo
56
+ or reviewed.layout is distinct from current_page.draft_layout then
57
+ raise exception 'Page version conflict: expected draft is missing or stale';
58
+ end if;
59
+
60
+ if current_page.published_version_id = p_expected_version_id then
61
+ raise exception 'Page version conflict: expected draft is already published';
62
+ end if;
63
+
64
+ update cms_pages
65
+ set publish_at = p_publish_at,
66
+ scheduled_version_id = reviewed.id,
67
+ updated_at = now()
68
+ where id = p_page_id
69
+ returning * into result;
70
+
71
+ return result;
72
+ end;
73
+ $$;
74
+
75
+ do $fn_lockdown$
76
+ begin
77
+ revoke execute on function cms_schedule_page_publish(uuid, bigint, timestamptz, uuid) from public;
78
+ if exists (select 1 from pg_roles where rolname = 'anon') then
79
+ revoke execute on function cms_schedule_page_publish(uuid, bigint, timestamptz, uuid) from anon;
80
+ end if;
81
+ if exists (select 1 from pg_roles where rolname = 'authenticated') then
82
+ revoke execute on function cms_schedule_page_publish(uuid, bigint, timestamptz, uuid) from authenticated;
83
+ end if;
84
+ if exists (select 1 from pg_roles where rolname = 'service_role') then
85
+ grant execute on function cms_schedule_page_publish(uuid, bigint, timestamptz, uuid) to service_role;
86
+ end if;
87
+ end
88
+ $fn_lockdown$;