@beechcms/api 0.6.7 → 0.8.0

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 (111) hide show
  1. package/assets/dashboard/0auth_mcp.svg +1 -0
  2. package/assets/dashboard/assets/{chart-D_uST30B.js → chart-CYgKoF6U.js} +6 -6
  3. package/assets/dashboard/assets/dist-DHabpZqA.js +140 -0
  4. package/assets/dashboard/assets/index-BZMSpYrl.js +3613 -0
  5. package/assets/dashboard/assets/index-Dex2YZcC.css +1 -0
  6. package/assets/dashboard/assets/json-code-editor-DtZpKDXm.js +12 -0
  7. package/assets/dashboard/assets/pie-chart-recharts-C7WAhcCk.js +1 -0
  8. package/assets/dashboard/assets/richtext-render-x0TT0116.js +258 -0
  9. package/assets/dashboard/assets/timeseries-chart-recharts-B9RquiY6.js +1 -0
  10. package/assets/dashboard/assets/w3c-keyname-BOAvb0qz.js +1 -0
  11. package/assets/dashboard/index.html +4 -2
  12. package/dist/auth/auth.app.d.ts +13 -0
  13. package/dist/auth/index.d.ts +6 -0
  14. package/dist/factory.d.ts +14 -1
  15. package/dist/features/oauth/authorization-request.d.ts +127 -0
  16. package/dist/features/oauth/authorize.d.ts +97 -0
  17. package/dist/features/oauth/consents.d.ts +75 -0
  18. package/dist/features/oauth/constants.d.ts +48 -0
  19. package/dist/features/oauth/index.d.ts +31 -0
  20. package/dist/features/oauth/revoke.d.ts +29 -0
  21. package/dist/features/oauth/token-issuance.d.ts +49 -0
  22. package/dist/features/oauth/token.d.ts +38 -0
  23. package/dist/features/search/constants.d.ts +43 -0
  24. package/dist/features/search/handlers/embed.d.ts +31 -0
  25. package/dist/features/search/handlers/full-text-search.d.ts +28 -0
  26. package/dist/features/search/index.d.ts +20 -0
  27. package/dist/features/search/jobs/semantic-search.hooks.d.ts +21 -0
  28. package/dist/features/search/jobs/semantic-search.worker.d.ts +101 -0
  29. package/dist/features/search/public-search.router.d.ts +15 -0
  30. package/dist/features/search/search.d.ts +14 -6
  31. package/dist/features/search/semantic-search.hooks.d.ts +2 -0
  32. package/dist/features/search/semantic-search.worker.d.ts +21 -0
  33. package/dist/features/search/types.d.ts +100 -0
  34. package/dist/features/search/utils/search-utils.d.ts +56 -0
  35. package/dist/features/seeds/seeds.destructive.d.ts +6 -0
  36. package/dist/features/seeds/seeds.handler.d.ts +14 -0
  37. package/dist/features/seeds/seeds.helpers.d.ts +118 -0
  38. package/dist/features/seeds/seeds.mcp.d.ts +37 -0
  39. package/dist/features/upload/index.d.ts +2 -2
  40. package/dist/index.js +3544 -1368
  41. package/dist/middleware/auth.middleware.d.ts +34 -4
  42. package/dist/middleware/oauth-scope.middleware.d.ts +60 -0
  43. package/dist/middleware/rate-limit.middleware.d.ts +4 -2
  44. package/dist/middleware/repository.middleware.d.ts +9 -1
  45. package/dist/public/api-key-middleware.d.ts +0 -4
  46. package/dist/public/public-add.d.ts +1 -1
  47. package/dist/rate-limit/in-memory-rate-limiter.d.ts +1 -8
  48. package/dist/shared/db/repositories/content.repository.d1.d.ts +4 -2
  49. package/dist/shared/db/repositories/d1-oauth-authorization-code.repository.d.ts +9 -0
  50. package/dist/shared/db/repositories/d1-oauth-client.repository.d.ts +6 -0
  51. package/dist/shared/db/repositories/d1-oauth-consent.repository.d.ts +10 -0
  52. package/dist/shared/db/repositories/d1-oauth-token.repository.d.ts +12 -0
  53. package/dist/shared/db/repositories/d1-search.query.d.ts +54 -0
  54. package/dist/shared/db/repositories/d1-vector.repository.d.ts +11 -0
  55. package/dist/shared/db/repositories/in-memory-seed.repository.d.ts +2 -1
  56. package/dist/shared/db/repositories/seed.repository.d1.d.ts +3 -1
  57. package/dist/shared/db/repositories/time-trap-token.repository.d1.d.ts +7 -0
  58. package/dist/shared/storage/factory.d.ts +84 -4
  59. package/dist/shared/storage/r2-bucket.d.ts +127 -0
  60. package/dist/shared/storage/s3-bucket.d.ts +104 -10
  61. package/dist/shared/storage/upload.d.ts +33 -5
  62. package/dist/shared/utils/dual-key-rate-limiter.d.ts +21 -0
  63. package/dist/shared/utils/media-utils.d.ts +3 -3
  64. package/dist/shared/utils/opaque-token.d.ts +13 -0
  65. package/dist/types.d.ts +49 -1
  66. package/migrations/0000_v040_base.sql +184 -1
  67. package/migrations/_archive/0000_schema_init.sql +257 -0
  68. package/migrations/_archive/0001_init.sql +25 -0
  69. package/migrations/_archive/0002_seed_admin.sql +10 -0
  70. package/migrations/_archive/0003_refresh_tokens.sql +17 -0
  71. package/migrations/_archive/0004_seed_projects.sql +18 -0
  72. package/migrations/_archive/0005_update_projects.sql +56 -0
  73. package/migrations/_archive/0006_add_slug_and_unique_index.sql +7 -0
  74. package/migrations/_archive/0007_cleanup_test_data.sql +4 -0
  75. package/migrations/_archive/0008_seed_content_entries.sql +104 -0
  76. package/migrations/_archive/0009_public_idempotency.sql +11 -0
  77. package/migrations/_archive/0010_seed_real_data.sql +31 -0
  78. package/migrations/_archive/0010_testsite_data.sql +31 -0
  79. package/migrations/_archive/0011_analytics.sql +10 -0
  80. package/migrations/_archive/0012_seed_analytics.sql +17 -0
  81. package/migrations/_archive/0013_system_stats.sql +8 -0
  82. package/migrations/_archive/0014_activity_logs.sql +15 -0
  83. package/migrations/_archive/0015_notifications.sql +12 -0
  84. package/migrations/_archive/0016_fts5_global_search.sql +81 -0
  85. package/migrations/_archive/0017_seed_clienti.sql +90 -0
  86. package/migrations/_archive/0018_draft_data.sql +3 -0
  87. package/migrations/_archive/0019_drop_fts_triggers.sql +7 -0
  88. package/migrations/_archive/0020_composite_indexes.sql +15 -0
  89. package/migrations/_archive/0021_media_objects.sql +15 -0
  90. package/migrations/_archive/0022_analytics_seed.sql +23 -0
  91. package/migrations/_archive/0023_user_profile.sql +4 -0
  92. package/migrations/_archive/0024_activity_user_name.sql +3 -0
  93. package/migrations/_archive/0025_password_reset_tokens.sql +11 -0
  94. package/migrations/_archive/0026_remove_dev_admin.sql +4 -0
  95. package/migrations/_archive/0027_v040_doctype_architecture.sql +51 -0
  96. package/migrations/_archive/0029_fts_all_text_branches.sql +281 -0
  97. package/migrations/_archive/0031_site_settings.sql +9 -0
  98. package/migrations/_archive/0032_seeds.sql +28 -0
  99. package/migrations/_archive/0033_dashboard_layouts.sql +14 -0
  100. package/migrations/_archive/0034_kanban_foundation.sql +24 -0
  101. package/migrations/_archive/0035_setup_lock.sql +10 -0
  102. package/migrations/_archive/0036_notification_type_success.sql +23 -0
  103. package/migrations/_archive/0037_time_trap_tokens.sql +13 -0
  104. package/migrations/_archive/0038_oauth_authorization.sql +95 -0
  105. package/migrations/_archive/0039_oauth_client_beech_mcp.sql +20 -0
  106. package/package.json +3 -5
  107. package/assets/dashboard/assets/index-B58kBLrh.css +0 -1
  108. package/assets/dashboard/assets/index-DjniT5Z9.js +0 -745
  109. package/assets/dashboard/assets/pie-chart-recharts-CnVomiSi.js +0 -1
  110. package/assets/dashboard/assets/timeseries-chart-recharts-CTqi8qnV.js +0 -1
  111. /package/migrations/{0029_automations.sql → _archive/0029_automations.sql} +0 -0
@@ -0,0 +1,281 @@
1
+ -- =============================================================================
2
+ -- Beech CMS — FTS rebuild: index text + richtext branches
3
+ --
4
+ -- Previously only `richtext` branches were indexed. This migration rebuilds
5
+ -- all per-seed FTS5 tables to also include `text` branches (with search:true).
6
+ -- Seeds that had no FTS (team, testimonianze, clienti) get new tables here.
7
+ -- =============================================================================
8
+
9
+ -- ─── articoli ─────────────────────────────────────────────────────────────────
10
+ -- Was: body only. Now: title, body, metaTitle, metaDescription
11
+
12
+ DROP TRIGGER IF EXISTS fts_articoli_insert;
13
+ DROP TRIGGER IF EXISTS fts_articoli_update;
14
+ DROP TRIGGER IF EXISTS fts_articoli_delete;
15
+ DROP TABLE IF EXISTS fts_articoli;
16
+
17
+ CREATE VIRTUAL TABLE IF NOT EXISTS fts_articoli USING fts5(
18
+ entry_id UNINDEXED,
19
+ title,
20
+ body,
21
+ metaTitle,
22
+ metaDescription,
23
+ tokenize = 'unicode61'
24
+ );
25
+
26
+ INSERT INTO fts_articoli(entry_id, title, body, metaTitle, metaDescription)
27
+ SELECT id, title, body, metaTitle, metaDescription FROM content_articoli;
28
+
29
+ CREATE TRIGGER IF NOT EXISTS fts_articoli_insert
30
+ AFTER INSERT ON content_articoli BEGIN
31
+ INSERT INTO fts_articoli(entry_id, title, body, metaTitle, metaDescription)
32
+ VALUES (new.id, new.title, new.body, new.metaTitle, new.metaDescription);
33
+ END;
34
+
35
+ CREATE TRIGGER IF NOT EXISTS fts_articoli_update
36
+ AFTER UPDATE OF title, body, metaTitle, metaDescription ON content_articoli BEGIN
37
+ DELETE FROM fts_articoli WHERE entry_id = old.id;
38
+ INSERT INTO fts_articoli(entry_id, title, body, metaTitle, metaDescription)
39
+ VALUES (new.id, new.title, new.body, new.metaTitle, new.metaDescription);
40
+ END;
41
+
42
+ CREATE TRIGGER IF NOT EXISTS fts_articoli_delete
43
+ AFTER DELETE ON content_articoli BEGIN
44
+ DELETE FROM fts_articoli WHERE entry_id = old.id;
45
+ END;
46
+
47
+
48
+ -- ─── prodotti ─────────────────────────────────────────────────────────────────
49
+ -- Was: description only. Now: name, description, metaTitle, metaDescription
50
+
51
+ DROP TRIGGER IF EXISTS fts_prodotti_insert;
52
+ DROP TRIGGER IF EXISTS fts_prodotti_update;
53
+ DROP TRIGGER IF EXISTS fts_prodotti_delete;
54
+ DROP TABLE IF EXISTS fts_prodotti;
55
+
56
+ CREATE VIRTUAL TABLE IF NOT EXISTS fts_prodotti USING fts5(
57
+ entry_id UNINDEXED,
58
+ name,
59
+ description,
60
+ metaTitle,
61
+ metaDescription,
62
+ tokenize = 'unicode61'
63
+ );
64
+
65
+ INSERT INTO fts_prodotti(entry_id, name, description, metaTitle, metaDescription)
66
+ SELECT id, name, description, metaTitle, metaDescription FROM content_prodotti;
67
+
68
+ CREATE TRIGGER IF NOT EXISTS fts_prodotti_insert
69
+ AFTER INSERT ON content_prodotti BEGIN
70
+ INSERT INTO fts_prodotti(entry_id, name, description, metaTitle, metaDescription)
71
+ VALUES (new.id, new.name, new.description, new.metaTitle, new.metaDescription);
72
+ END;
73
+
74
+ CREATE TRIGGER IF NOT EXISTS fts_prodotti_update
75
+ AFTER UPDATE OF name, description, metaTitle, metaDescription ON content_prodotti BEGIN
76
+ DELETE FROM fts_prodotti WHERE entry_id = old.id;
77
+ INSERT INTO fts_prodotti(entry_id, name, description, metaTitle, metaDescription)
78
+ VALUES (new.id, new.name, new.description, new.metaTitle, new.metaDescription);
79
+ END;
80
+
81
+ CREATE TRIGGER IF NOT EXISTS fts_prodotti_delete
82
+ AFTER DELETE ON content_prodotti BEGIN
83
+ DELETE FROM fts_prodotti WHERE entry_id = old.id;
84
+ END;
85
+
86
+
87
+ -- ─── team ─────────────────────────────────────────────────────────────────────
88
+ -- Was: no FTS. Now: name, role, bio, linkedIn, metaTitle, metaDescription
89
+
90
+ DROP TRIGGER IF EXISTS fts_team_insert;
91
+ DROP TRIGGER IF EXISTS fts_team_update;
92
+ DROP TRIGGER IF EXISTS fts_team_delete;
93
+ DROP TABLE IF EXISTS fts_team;
94
+
95
+ CREATE VIRTUAL TABLE IF NOT EXISTS fts_team USING fts5(
96
+ entry_id UNINDEXED,
97
+ name,
98
+ role,
99
+ bio,
100
+ linkedIn,
101
+ metaTitle,
102
+ metaDescription,
103
+ tokenize = 'unicode61'
104
+ );
105
+
106
+ INSERT INTO fts_team(entry_id, name, role, bio, linkedIn, metaTitle, metaDescription)
107
+ SELECT id, name, role, bio, linkedIn, metaTitle, metaDescription FROM content_team;
108
+
109
+ CREATE TRIGGER IF NOT EXISTS fts_team_insert
110
+ AFTER INSERT ON content_team BEGIN
111
+ INSERT INTO fts_team(entry_id, name, role, bio, linkedIn, metaTitle, metaDescription)
112
+ VALUES (new.id, new.name, new.role, new.bio, new.linkedIn, new.metaTitle, new.metaDescription);
113
+ END;
114
+
115
+ CREATE TRIGGER IF NOT EXISTS fts_team_update
116
+ AFTER UPDATE OF name, role, bio, linkedIn, metaTitle, metaDescription ON content_team BEGIN
117
+ DELETE FROM fts_team WHERE entry_id = old.id;
118
+ INSERT INTO fts_team(entry_id, name, role, bio, linkedIn, metaTitle, metaDescription)
119
+ VALUES (new.id, new.name, new.role, new.bio, new.linkedIn, new.metaTitle, new.metaDescription);
120
+ END;
121
+
122
+ CREATE TRIGGER IF NOT EXISTS fts_team_delete
123
+ AFTER DELETE ON content_team BEGIN
124
+ DELETE FROM fts_team WHERE entry_id = old.id;
125
+ END;
126
+
127
+
128
+ -- ─── testimonianze ────────────────────────────────────────────────────────────
129
+ -- Was: no FTS. Now: author, company, quote, metaTitle, metaDescription
130
+
131
+ DROP TRIGGER IF EXISTS fts_testimonianze_insert;
132
+ DROP TRIGGER IF EXISTS fts_testimonianze_update;
133
+ DROP TRIGGER IF EXISTS fts_testimonianze_delete;
134
+ DROP TABLE IF EXISTS fts_testimonianze;
135
+
136
+ CREATE VIRTUAL TABLE IF NOT EXISTS fts_testimonianze USING fts5(
137
+ entry_id UNINDEXED,
138
+ author,
139
+ company,
140
+ quote,
141
+ metaTitle,
142
+ metaDescription,
143
+ tokenize = 'unicode61'
144
+ );
145
+
146
+ INSERT INTO fts_testimonianze(entry_id, author, company, quote, metaTitle, metaDescription)
147
+ SELECT id, author, company, quote, metaTitle, metaDescription FROM content_testimonianze;
148
+
149
+ CREATE TRIGGER IF NOT EXISTS fts_testimonianze_insert
150
+ AFTER INSERT ON content_testimonianze BEGIN
151
+ INSERT INTO fts_testimonianze(entry_id, author, company, quote, metaTitle, metaDescription)
152
+ VALUES (new.id, new.author, new.company, new.quote, new.metaTitle, new.metaDescription);
153
+ END;
154
+
155
+ CREATE TRIGGER IF NOT EXISTS fts_testimonianze_update
156
+ AFTER UPDATE OF author, company, quote, metaTitle, metaDescription ON content_testimonianze BEGIN
157
+ DELETE FROM fts_testimonianze WHERE entry_id = old.id;
158
+ INSERT INTO fts_testimonianze(entry_id, author, company, quote, metaTitle, metaDescription)
159
+ VALUES (new.id, new.author, new.company, new.quote, new.metaTitle, new.metaDescription);
160
+ END;
161
+
162
+ CREATE TRIGGER IF NOT EXISTS fts_testimonianze_delete
163
+ AFTER DELETE ON content_testimonianze BEGIN
164
+ DELETE FROM fts_testimonianze WHERE entry_id = old.id;
165
+ END;
166
+
167
+
168
+ -- ─── pagine ───────────────────────────────────────────────────────────────────
169
+ -- Was: body only. Now: title, body, metaTitle, metaDescription
170
+
171
+ DROP TRIGGER IF EXISTS fts_pagine_insert;
172
+ DROP TRIGGER IF EXISTS fts_pagine_update;
173
+ DROP TRIGGER IF EXISTS fts_pagine_delete;
174
+ DROP TABLE IF EXISTS fts_pagine;
175
+
176
+ CREATE VIRTUAL TABLE IF NOT EXISTS fts_pagine USING fts5(
177
+ entry_id UNINDEXED,
178
+ title,
179
+ body,
180
+ metaTitle,
181
+ metaDescription,
182
+ tokenize = 'unicode61'
183
+ );
184
+
185
+ INSERT INTO fts_pagine(entry_id, title, body, metaTitle, metaDescription)
186
+ SELECT id, title, body, metaTitle, metaDescription FROM content_pagine;
187
+
188
+ CREATE TRIGGER IF NOT EXISTS fts_pagine_insert
189
+ AFTER INSERT ON content_pagine BEGIN
190
+ INSERT INTO fts_pagine(entry_id, title, body, metaTitle, metaDescription)
191
+ VALUES (new.id, new.title, new.body, new.metaTitle, new.metaDescription);
192
+ END;
193
+
194
+ CREATE TRIGGER IF NOT EXISTS fts_pagine_update
195
+ AFTER UPDATE OF title, body, metaTitle, metaDescription ON content_pagine BEGIN
196
+ DELETE FROM fts_pagine WHERE entry_id = old.id;
197
+ INSERT INTO fts_pagine(entry_id, title, body, metaTitle, metaDescription)
198
+ VALUES (new.id, new.title, new.body, new.metaTitle, new.metaDescription);
199
+ END;
200
+
201
+ CREATE TRIGGER IF NOT EXISTS fts_pagine_delete
202
+ AFTER DELETE ON content_pagine BEGIN
203
+ DELETE FROM fts_pagine WHERE entry_id = old.id;
204
+ END;
205
+
206
+
207
+ -- ─── clienti ──────────────────────────────────────────────────────────────────
208
+ -- Was: no FTS (no richtext). Now: name, tier, phone (email/passwordHash/internalNote excluded via search:false)
209
+
210
+ DROP TRIGGER IF EXISTS fts_clienti_insert;
211
+ DROP TRIGGER IF EXISTS fts_clienti_update;
212
+ DROP TRIGGER IF EXISTS fts_clienti_delete;
213
+ DROP TABLE IF EXISTS fts_clienti;
214
+
215
+ CREATE VIRTUAL TABLE IF NOT EXISTS fts_clienti USING fts5(
216
+ entry_id UNINDEXED,
217
+ name,
218
+ tier,
219
+ phone,
220
+ tokenize = 'unicode61'
221
+ );
222
+
223
+ INSERT INTO fts_clienti(entry_id, name, tier, phone)
224
+ SELECT id, name, tier, phone FROM content_clienti;
225
+
226
+ CREATE TRIGGER IF NOT EXISTS fts_clienti_insert
227
+ AFTER INSERT ON content_clienti BEGIN
228
+ INSERT INTO fts_clienti(entry_id, name, tier, phone)
229
+ VALUES (new.id, new.name, new.tier, new.phone);
230
+ END;
231
+
232
+ CREATE TRIGGER IF NOT EXISTS fts_clienti_update
233
+ AFTER UPDATE OF name, tier, phone ON content_clienti BEGIN
234
+ DELETE FROM fts_clienti WHERE entry_id = old.id;
235
+ INSERT INTO fts_clienti(entry_id, name, tier, phone)
236
+ VALUES (new.id, new.name, new.tier, new.phone);
237
+ END;
238
+
239
+ CREATE TRIGGER IF NOT EXISTS fts_clienti_delete
240
+ AFTER DELETE ON content_clienti BEGIN
241
+ DELETE FROM fts_clienti WHERE entry_id = old.id;
242
+ END;
243
+
244
+
245
+ -- ─── messaggi ─────────────────────────────────────────────────────────────────
246
+ -- Was: message (richtext) only. Now: name, email, subject, message
247
+
248
+ DROP TRIGGER IF EXISTS fts_messaggi_insert;
249
+ DROP TRIGGER IF EXISTS fts_messaggi_update;
250
+ DROP TRIGGER IF EXISTS fts_messaggi_delete;
251
+ DROP TABLE IF EXISTS fts_messaggi;
252
+
253
+ CREATE VIRTUAL TABLE IF NOT EXISTS fts_messaggi USING fts5(
254
+ entry_id UNINDEXED,
255
+ name,
256
+ email,
257
+ subject,
258
+ message,
259
+ tokenize = 'unicode61'
260
+ );
261
+
262
+ INSERT INTO fts_messaggi(entry_id, name, email, subject, message)
263
+ SELECT id, name, email, subject, message FROM content_messaggi;
264
+
265
+ CREATE TRIGGER IF NOT EXISTS fts_messaggi_insert
266
+ AFTER INSERT ON content_messaggi BEGIN
267
+ INSERT INTO fts_messaggi(entry_id, name, email, subject, message)
268
+ VALUES (new.id, new.name, new.email, new.subject, new.message);
269
+ END;
270
+
271
+ CREATE TRIGGER IF NOT EXISTS fts_messaggi_update
272
+ AFTER UPDATE OF name, email, subject, message ON content_messaggi BEGIN
273
+ DELETE FROM fts_messaggi WHERE entry_id = old.id;
274
+ INSERT INTO fts_messaggi(entry_id, name, email, subject, message)
275
+ VALUES (new.id, new.name, new.email, new.subject, new.message);
276
+ END;
277
+
278
+ CREATE TRIGGER IF NOT EXISTS fts_messaggi_delete
279
+ AFTER DELETE ON content_messaggi BEGIN
280
+ DELETE FROM fts_messaggi WHERE entry_id = old.id;
281
+ END;
@@ -0,0 +1,9 @@
1
+ -- =============================================================================
2
+ -- Beech CMS v0.4.0 — Site Settings
3
+ -- Key-value store for site-level configuration (title, locale, timezone, etc.)
4
+ -- =============================================================================
5
+
6
+ CREATE TABLE IF NOT EXISTS site_settings (
7
+ key TEXT NOT NULL PRIMARY KEY,
8
+ value TEXT NOT NULL
9
+ );
@@ -0,0 +1,28 @@
1
+ -- =============================================================================
2
+ -- Runtime Seeds — Seed definitions stored in D1 (source of truth at runtime)
3
+ --
4
+ -- `seeds` : one row per content type. `definition` is the full Seed JSON.
5
+ -- `seed_meta` : single-row table holding the registry version token used for
6
+ -- multi-isolate cache invalidation (see docs/Sprints/runtime-seeds).
7
+ -- =============================================================================
8
+
9
+ CREATE TABLE IF NOT EXISTS seeds (
10
+ slug TEXT NOT NULL PRIMARY KEY,
11
+ definition TEXT NOT NULL, -- JSON-serialized Seed
12
+ status TEXT NOT NULL DEFAULT 'active'
13
+ CHECK (status IN ('active', 'deleted')),
14
+ source TEXT NOT NULL DEFAULT 'runtime'
15
+ CHECK (source IN ('code', 'runtime')),
16
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
17
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch())
18
+ );
19
+
20
+ CREATE INDEX IF NOT EXISTS idx_seeds_status ON seeds(status);
21
+
22
+ CREATE TABLE IF NOT EXISTS seed_meta (
23
+ id TEXT NOT NULL PRIMARY KEY,
24
+ value TEXT NOT NULL
25
+ );
26
+
27
+ -- registry_version starts at 1; bumped on every seed write (sprint 03/04).
28
+ INSERT OR IGNORE INTO seed_meta (id, value) VALUES ('registry_version', '1');
@@ -0,0 +1,14 @@
1
+ -- =============================================================================
2
+ -- Dashboard Layouts — per-scope dashboard composer layouts
3
+ --
4
+ -- `dashboard_layouts` : one row per scope. Sprint 02 only ever writes the
5
+ -- literal 'default' scope; Sprint 06 introduces
6
+ -- role-scoped rows ('role:admin', 'role:editor', ...).
7
+ -- =============================================================================
8
+
9
+ CREATE TABLE IF NOT EXISTS dashboard_layouts (
10
+ scope TEXT NOT NULL PRIMARY KEY, -- 'default' | 'role:admin' | 'role:editor'
11
+ layout TEXT NOT NULL, -- JSON of DashboardLayout
12
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
13
+ updated_by TEXT NOT NULL -- users.id of the writer
14
+ );
@@ -0,0 +1,24 @@
1
+ -- =============================================================================
2
+ -- Kanban Foundation
3
+ -- kanban_positions : per-(seed, axis, entry) fractional-index ordering.
4
+ -- System table — NEVER a column on content_{slug}.
5
+ -- seed_layouts.view_config : additive JSON blob for per-seed dashboard view
6
+ -- preferences (kanban axis/sort/hidden columns). Nullable,
7
+ -- so existing rows are untouched.
8
+ -- =============================================================================
9
+
10
+ CREATE TABLE IF NOT EXISTS kanban_positions (
11
+ seed_slug TEXT NOT NULL,
12
+ entry_id TEXT NOT NULL,
13
+ axis_branch_id TEXT NOT NULL, -- Branch.id (br_XX), not alias
14
+ position TEXT NOT NULL, -- fractional-indexing key
15
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
16
+ PRIMARY KEY (seed_slug, entry_id, axis_branch_id)
17
+ );
18
+
19
+ -- Column fetch path: ORDER BY position within one (seed, axis).
20
+ CREATE INDEX IF NOT EXISTS idx_kanban_positions_column
21
+ ON kanban_positions (seed_slug, axis_branch_id, position);
22
+
23
+ -- Per-seed dashboard view preferences (KB-S02). Additive, nullable.
24
+ ALTER TABLE seed_layouts ADD COLUMN view_config TEXT;
@@ -0,0 +1,10 @@
1
+ -- =============================================================================
2
+ -- Beech CMS v0.4.0 — Setup Lock
3
+ -- Single-row marker enforcing that /auth/setup can only complete once, even
4
+ -- under concurrent requests. Insert into this table in the same transaction
5
+ -- as the initial admin user; the PRIMARY KEY conflict rejects the loser.
6
+ -- =============================================================================
7
+
8
+ CREATE TABLE IF NOT EXISTS setup_completed (
9
+ id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1)
10
+ );
@@ -0,0 +1,23 @@
1
+ -- =============================================================================
2
+ -- Beech CMS v0.4.0 — Notification Type Success Support
3
+ -- Updates notifications CHECK constraint to include 'success'
4
+ -- =============================================================================
5
+
6
+ CREATE TABLE IF NOT EXISTS notifications_dg_tmp (
7
+ id TEXT NOT NULL PRIMARY KEY,
8
+ title TEXT NOT NULL,
9
+ message TEXT NOT NULL,
10
+ type TEXT NOT NULL DEFAULT 'info' CHECK (type IN ('info', 'success', 'warning', 'error')),
11
+ is_read INTEGER NOT NULL DEFAULT 0 CHECK (is_read IN (0, 1)),
12
+ created_at INTEGER NOT NULL DEFAULT (unixepoch())
13
+ );
14
+
15
+ INSERT INTO notifications_dg_tmp (id, title, message, type, is_read, created_at)
16
+ SELECT id, title, message, type, is_read, created_at FROM notifications;
17
+
18
+ DROP TABLE notifications;
19
+
20
+ ALTER TABLE notifications_dg_tmp RENAME TO notifications;
21
+
22
+ CREATE INDEX IF NOT EXISTS idx_notifications_created ON notifications(created_at);
23
+ CREATE INDEX IF NOT EXISTS idx_notifications_unread ON notifications(is_read);
@@ -0,0 +1,13 @@
1
+ -- =============================================================================
2
+ -- PUBLIC TIME-TRAP TOKENS
3
+ -- Single-use tracking for HMAC time-trap tokens to prevent replay attacks.
4
+ -- =============================================================================
5
+
6
+ CREATE TABLE IF NOT EXISTS public_time_trap_tokens (
7
+ token_hash TEXT NOT NULL PRIMARY KEY,
8
+ used_at INTEGER NOT NULL,
9
+ expires_at INTEGER NOT NULL
10
+ );
11
+
12
+ CREATE INDEX IF NOT EXISTS idx_time_trap_tokens_expires
13
+ ON public_time_trap_tokens (expires_at);
@@ -0,0 +1,95 @@
1
+ -- =============================================================================
2
+ -- OAUTH 2.1 AUTHORIZATION SERVER
3
+ -- Authorization code + PKCE grant. Every credential (code, access token, refresh
4
+ -- token) is persisted as a SHA-256 hex hash only — never plaintext — replicating
5
+ -- the refresh_tokens contract in 0000_v040_base.sql.
6
+ -- Expired rows are filtered at read time via expires_at; no background pruning job.
7
+ -- =============================================================================
8
+
9
+ -- 1. CLIENTS -----------------------------------------------------------------
10
+ -- Static registry. No dynamic client registration (RFC 7591) is supported.
11
+ CREATE TABLE IF NOT EXISTS oauth_clients (
12
+ client_id TEXT NOT NULL PRIMARY KEY,
13
+ name TEXT NOT NULL,
14
+ redirect_uris TEXT NOT NULL, -- JSON array of strings
15
+ allowed_scopes TEXT NOT NULL, -- space-delimited, RFC 6749 §3.3
16
+ is_public INTEGER NOT NULL DEFAULT 1
17
+ CHECK (is_public IN (0, 1)), -- 1 = public client, no secret
18
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
19
+ disabled_at INTEGER DEFAULT NULL
20
+ );
21
+
22
+ -- 2. AUTHORIZATION CODES -----------------------------------------------------
23
+ -- Single use. consumed_at is set on first redemption; a second redemption of the
24
+ -- same code must cascade-revoke every token issued from it (OAuth 2.1 replay
25
+ -- mitigation) via oauth_tokens.authorization_code_hash.
26
+ CREATE TABLE IF NOT EXISTS oauth_authorization_codes (
27
+ code_hash TEXT NOT NULL PRIMARY KEY,
28
+ client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,
29
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
30
+ scope TEXT NOT NULL, -- space-delimited granted scopes
31
+ redirect_uri TEXT NOT NULL,
32
+ code_challenge TEXT NOT NULL,
33
+ code_challenge_method TEXT NOT NULL DEFAULT 'S256'
34
+ CHECK (code_challenge_method = 'S256'),
35
+ expires_at INTEGER NOT NULL,
36
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
37
+ consumed_at INTEGER DEFAULT NULL
38
+ );
39
+
40
+ CREATE INDEX IF NOT EXISTS idx_oauth_codes_user ON oauth_authorization_codes(user_id);
41
+ CREATE INDEX IF NOT EXISTS idx_oauth_codes_expires ON oauth_authorization_codes(expires_at);
42
+
43
+ -- 3. TOKENS ------------------------------------------------------------------
44
+ -- Access and refresh tokens share one table: identical lifecycle, distinguished
45
+ -- by token_type. authorization_code_hash links a token back to its originating
46
+ -- code so replay of that code can revoke the whole family in one UPDATE.
47
+ CREATE TABLE IF NOT EXISTS oauth_tokens (
48
+ id TEXT NOT NULL PRIMARY KEY,
49
+ token_hash TEXT NOT NULL,
50
+ token_type TEXT NOT NULL
51
+ CHECK (token_type IN ('access', 'refresh')),
52
+ client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,
53
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
54
+ scope TEXT NOT NULL, -- space-delimited
55
+ authorization_code_hash TEXT NOT NULL,
56
+ expires_at INTEGER NOT NULL,
57
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
58
+ revoked_at INTEGER DEFAULT NULL
59
+ );
60
+
61
+ CREATE INDEX IF NOT EXISTS idx_oauth_tokens_hash ON oauth_tokens(token_hash);
62
+ CREATE INDEX IF NOT EXISTS idx_oauth_tokens_code ON oauth_tokens(authorization_code_hash);
63
+ CREATE INDEX IF NOT EXISTS idx_oauth_tokens_client ON oauth_tokens(client_id, user_id);
64
+ CREATE INDEX IF NOT EXISTS idx_oauth_tokens_user ON oauth_tokens(user_id);
65
+ CREATE INDEX IF NOT EXISTS idx_oauth_tokens_expires ON oauth_tokens(expires_at);
66
+
67
+ -- 4. CONSENTS ---------------------------------------------------------------
68
+ -- One live row per (client, user). scopes holds the cumulative granted set, so a
69
+ -- repeat authorize request for an already-granted subset can skip the consent
70
+ -- screen, while a superset must re-prompt for the delta only.
71
+ CREATE TABLE IF NOT EXISTS oauth_consents (
72
+ id TEXT NOT NULL PRIMARY KEY,
73
+ client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,
74
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
75
+ scopes TEXT NOT NULL, -- space-delimited
76
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
77
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
78
+ revoked_at INTEGER DEFAULT NULL
79
+ );
80
+
81
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_oauth_consents_pair ON oauth_consents(client_id, user_id);
82
+ CREATE INDEX IF NOT EXISTS idx_oauth_consents_user ON oauth_consents(user_id);
83
+
84
+ -- 5. SEEDED CLIENT -----------------------------------------------------------
85
+ -- The MCP server is the only client today. Loopback redirect per OAuth 2.1
86
+ -- §8.4.2: the port is assigned at runtime by the local listener, so Sprint 2
87
+ -- matches host+path and ignores the port. No client secret: public client.
88
+ INSERT OR IGNORE INTO oauth_clients (client_id, name, redirect_uris, allowed_scopes, is_public)
89
+ VALUES (
90
+ 'beech-mcp-cli',
91
+ 'BeechCMS MCP Server',
92
+ '["http://127.0.0.1/callback"]',
93
+ 'schema:read schema:write',
94
+ 1
95
+ );
@@ -0,0 +1,20 @@
1
+ -- =============================================================================
2
+ -- OAUTH CLIENT REGISTRY — @beechcms/mcp
3
+ -- Static registration of the first-party MCP client. 0038 declares the registry
4
+ -- static (no RFC 7591 dynamic registration), so the row is seeded by migration.
5
+ -- Public client (is_public = 1): a stdio CLI cannot hold a secret, PKCE S256 is
6
+ -- the proof of possession instead.
7
+ -- The redirect URI is registered WITHOUT a port: matchesRegisteredRedirectUri()
8
+ -- compares loopback URIs on protocol + hostname + pathname only (OAuth 2.1
9
+ -- §8.4.2), because the CLI binds an ephemeral port at runtime.
10
+ -- Idempotent: INSERT OR IGNORE keeps `beech db:reset` and re-runs safe.
11
+ -- =============================================================================
12
+
13
+ INSERT OR IGNORE INTO oauth_clients (client_id, name, redirect_uris, allowed_scopes, is_public)
14
+ VALUES (
15
+ 'beech-mcp',
16
+ 'BeechCMS MCP Server',
17
+ '["http://127.0.0.1/oauth/callback"]',
18
+ 'schema:read schema:write',
19
+ 1
20
+ );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beechcms/api",
3
- "version": "0.6.7",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/factory.d.ts",
@@ -12,15 +12,13 @@
12
12
  },
13
13
  "files": [
14
14
  "dist/",
15
- "migrations/0000_v040_base.sql",
16
- "migrations/0029_automations.sql",
17
- "migrations/0030_test_seeds.sql",
15
+ "migrations/",
18
16
  "assets/"
19
17
  ],
20
18
  "dependencies": {
21
19
  "@aws-sdk/client-s3": "^3.995.0",
22
20
  "@aws-sdk/s3-request-presigner": "^3.995.0",
23
- "@beechcms/core": "^0.6.7",
21
+ "@beechcms/core": "^0.8.0",
24
22
  "@upstash/qstash": "^2.11.0",
25
23
  "bcryptjs": "^2.4.3",
26
24
  "hono": "^4.12.34",