cms42 0.1.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 (145) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README.md +299 -0
  4. data/Rakefile +10 -0
  5. data/app/assets/stylesheets/cms/application.css +3 -0
  6. data/app/controllers/cms/admin/api_keys_controller.rb +52 -0
  7. data/app/controllers/cms/admin/base_controller.rb +54 -0
  8. data/app/controllers/cms/admin/documents_controller.rb +56 -0
  9. data/app/controllers/cms/admin/form_fields_controller.rb +70 -0
  10. data/app/controllers/cms/admin/form_submissions_controller.rb +36 -0
  11. data/app/controllers/cms/admin/images_controller.rb +67 -0
  12. data/app/controllers/cms/admin/pages_controller.rb +188 -0
  13. data/app/controllers/cms/admin/sections_controller.rb +177 -0
  14. data/app/controllers/cms/admin/sites_controller.rb +60 -0
  15. data/app/controllers/cms/admin/webhook_deliveries_controller.rb +19 -0
  16. data/app/controllers/cms/admin/webhooks_controller.rb +51 -0
  17. data/app/controllers/cms/api/base_controller.rb +45 -0
  18. data/app/controllers/cms/api/v1/base_controller.rb +29 -0
  19. data/app/controllers/cms/api/v1/pages_controller.rb +21 -0
  20. data/app/controllers/cms/api/v1/sites_controller.rb +18 -0
  21. data/app/controllers/cms/application_controller.rb +11 -0
  22. data/app/controllers/cms/public/base_controller.rb +23 -0
  23. data/app/controllers/cms/public/form_submissions_controller.rb +63 -0
  24. data/app/controllers/cms/public/previews_controller.rb +21 -0
  25. data/app/controllers/cms/public/sites_controller.rb +37 -0
  26. data/app/controllers/concerns/cms/admin/page_scoped_sections.rb +42 -0
  27. data/app/controllers/concerns/cms/current_site_resolver.rb +17 -0
  28. data/app/controllers/concerns/cms/public/page_paths.rb +31 -0
  29. data/app/controllers/concerns/cms/public/page_rendering.rb +23 -0
  30. data/app/controllers/concerns/cms/site_resolvable.rb +27 -0
  31. data/app/helpers/cms/admin/pages_helper.rb +29 -0
  32. data/app/helpers/cms/admin/sections_helper.rb +86 -0
  33. data/app/helpers/cms/admin/sites_helper.rb +8 -0
  34. data/app/helpers/cms/application_helper.rb +51 -0
  35. data/app/helpers/cms/media_helper.rb +28 -0
  36. data/app/helpers/cms/pages_helper.rb +16 -0
  37. data/app/helpers/cms/sections_helper.rb +25 -0
  38. data/app/helpers/cms/sites_helper.rb +11 -0
  39. data/app/javascript/cms/controllers/sortable_controller.js +38 -0
  40. data/app/jobs/cms/application_job.rb +6 -0
  41. data/app/jobs/cms/deliver_webhook_job.rb +66 -0
  42. data/app/mailers/cms/application_mailer.rb +9 -0
  43. data/app/mailers/cms/form_submission_mailer.rb +16 -0
  44. data/app/models/cms/api_key.rb +27 -0
  45. data/app/models/cms/application_record.rb +7 -0
  46. data/app/models/cms/document.rb +48 -0
  47. data/app/models/cms/form_field.rb +27 -0
  48. data/app/models/cms/form_submission.rb +42 -0
  49. data/app/models/cms/image.rb +61 -0
  50. data/app/models/cms/image_translation.rb +22 -0
  51. data/app/models/cms/page.rb +228 -0
  52. data/app/models/cms/page_section.rb +43 -0
  53. data/app/models/cms/page_translation.rb +22 -0
  54. data/app/models/cms/section/block_base.rb +32 -0
  55. data/app/models/cms/section/blocks/call_to_action_block.rb +16 -0
  56. data/app/models/cms/section/blocks/hero_block.rb +14 -0
  57. data/app/models/cms/section/blocks/image_block.rb +13 -0
  58. data/app/models/cms/section/blocks/rich_text_block.rb +12 -0
  59. data/app/models/cms/section/kind_registry.rb +66 -0
  60. data/app/models/cms/section.rb +94 -0
  61. data/app/models/cms/section_image.rb +10 -0
  62. data/app/models/cms/section_translation.rb +25 -0
  63. data/app/models/cms/site.rb +87 -0
  64. data/app/models/cms/webhook.rb +41 -0
  65. data/app/models/cms/webhook_delivery.rb +12 -0
  66. data/app/serializers/cms/api/base_serializer.rb +51 -0
  67. data/app/serializers/cms/api/page_serializer.rb +145 -0
  68. data/app/serializers/cms/api/site_serializer.rb +45 -0
  69. data/app/services/cms/locale_resolver.rb +30 -0
  70. data/app/services/cms/page_resolver.rb +73 -0
  71. data/app/services/cms/public_page_context.rb +49 -0
  72. data/app/views/cms/admin/api_keys/_form.html.erb +23 -0
  73. data/app/views/cms/admin/api_keys/create.html.erb +9 -0
  74. data/app/views/cms/admin/api_keys/edit.html.erb +5 -0
  75. data/app/views/cms/admin/api_keys/index.html.erb +36 -0
  76. data/app/views/cms/admin/api_keys/new.html.erb +5 -0
  77. data/app/views/cms/admin/documents/_form.html.erb +24 -0
  78. data/app/views/cms/admin/documents/edit.html.erb +2 -0
  79. data/app/views/cms/admin/documents/index.html.erb +37 -0
  80. data/app/views/cms/admin/documents/new.html.erb +2 -0
  81. data/app/views/cms/admin/form_fields/_form.html.erb +46 -0
  82. data/app/views/cms/admin/form_fields/edit.html.erb +2 -0
  83. data/app/views/cms/admin/form_fields/index.html.erb +41 -0
  84. data/app/views/cms/admin/form_fields/new.html.erb +2 -0
  85. data/app/views/cms/admin/form_submissions/index.html.erb +38 -0
  86. data/app/views/cms/admin/images/_form.html.erb +36 -0
  87. data/app/views/cms/admin/images/edit.html.erb +2 -0
  88. data/app/views/cms/admin/images/index.html.erb +25 -0
  89. data/app/views/cms/admin/images/new.html.erb +2 -0
  90. data/app/views/cms/admin/pages/_attach_section_panel.html.erb +20 -0
  91. data/app/views/cms/admin/pages/_form.html.erb +116 -0
  92. data/app/views/cms/admin/pages/_section_editor_frame.html.erb +3 -0
  93. data/app/views/cms/admin/pages/_sections_list.html.erb +9 -0
  94. data/app/views/cms/admin/pages/edit.html.erb +2 -0
  95. data/app/views/cms/admin/pages/index.html.erb +62 -0
  96. data/app/views/cms/admin/pages/new.html.erb +2 -0
  97. data/app/views/cms/admin/pages/show.html.erb +111 -0
  98. data/app/views/cms/admin/sections/_form.html.erb +128 -0
  99. data/app/views/cms/admin/sections/_section.html.erb +22 -0
  100. data/app/views/cms/admin/sections/edit.html.erb +9 -0
  101. data/app/views/cms/admin/sections/index.html.erb +47 -0
  102. data/app/views/cms/admin/sections/new.html.erb +9 -0
  103. data/app/views/cms/admin/sections/page_update.turbo_stream.erb +17 -0
  104. data/app/views/cms/admin/sections/show.html.erb +97 -0
  105. data/app/views/cms/admin/sites/_form.html.erb +44 -0
  106. data/app/views/cms/admin/sites/edit.html.erb +3 -0
  107. data/app/views/cms/admin/sites/new.html.erb +5 -0
  108. data/app/views/cms/admin/sites/show.html.erb +22 -0
  109. data/app/views/cms/admin/webhook_deliveries/index.html.erb +29 -0
  110. data/app/views/cms/admin/webhooks/_form.html.erb +38 -0
  111. data/app/views/cms/admin/webhooks/edit.html.erb +5 -0
  112. data/app/views/cms/admin/webhooks/index.html.erb +34 -0
  113. data/app/views/cms/admin/webhooks/new.html.erb +5 -0
  114. data/app/views/cms/form_submission_mailer/notify.html.erb +14 -0
  115. data/app/views/cms/form_submission_mailer/notify.text.erb +7 -0
  116. data/app/views/cms/public/pages/_content.html.erb +48 -0
  117. data/app/views/cms/public/pages/show.html.erb +44 -0
  118. data/app/views/cms/public/pages/templates/_custom.html.erb +3 -0
  119. data/app/views/cms/public/pages/templates/_form.html.erb +3 -0
  120. data/app/views/cms/public/pages/templates/_landing.html.erb +3 -0
  121. data/app/views/cms/public/pages/templates/_standard.html.erb +3 -0
  122. data/app/views/cms/sections/kinds/_cta.html.erb +13 -0
  123. data/app/views/cms/sections/kinds/_hero.html.erb +14 -0
  124. data/app/views/cms/sections/kinds/_image.html.erb +19 -0
  125. data/app/views/cms/sections/kinds/_rich_text.html.erb +6 -0
  126. data/app/views/layouts/cms/application.html.erb +13 -0
  127. data/app/views/layouts/cms/public.html.erb +14 -0
  128. data/bin/rails +19 -0
  129. data/bin/rubocop +9 -0
  130. data/cms.gemspec +29 -0
  131. data/config/importmap.rb +4 -0
  132. data/config/locales/activerecord.cms.en.yml +65 -0
  133. data/config/locales/en.yml +390 -0
  134. data/config/routes.rb +56 -0
  135. data/lib/cms/engine.rb +45 -0
  136. data/lib/cms/version.rb +5 -0
  137. data/lib/cms.rb +75 -0
  138. data/lib/cms42.rb +3 -0
  139. data/lib/generators/cms/install/install_generator.rb +26 -0
  140. data/lib/generators/cms/install/templates/create_cms_tables.rb +194 -0
  141. data/lib/generators/cms/install/templates/initializer.rb +21 -0
  142. data/lib/generators/cms/views/views_generator.rb +79 -0
  143. data/lib/tasks/cms_tasks.rake +6 -0
  144. data/lib/tasks/version.rake +8 -0
  145. metadata +281 -0
@@ -0,0 +1,390 @@
1
+ en:
2
+ date:
3
+ formats:
4
+ cms_date: "%d %b %Y"
5
+ time:
6
+ formats:
7
+ cms_csv_datetime: "%Y-%m-%d %H:%M"
8
+ cms_datetime: "%d %b %Y %H:%M"
9
+ cms:
10
+ shared:
11
+ empty_value: "-"
12
+ no: "No"
13
+ yes: "Yes"
14
+ errors:
15
+ admin_parent_controller_required: "Cms admin requires config.admin_parent_controller in production. Point it at a host controller that enforces authentication, for example \"Admin::BaseController\"."
16
+ current_site_required: "Cms admin requires config.current_site_resolver to return a Cms::Site. Configure it in Cms.setup so admin requests resolve the active CMS site explicitly."
17
+ page_not_found: "Page not found"
18
+ record_not_found: "The requested record was not found."
19
+ site_slug_not_provided: "CMS site slug not provided"
20
+ api:
21
+ site_not_found: "Site not found"
22
+ page_not_found: "Page not found"
23
+ unauthorized: "Unauthorized"
24
+ form_field:
25
+ invalid_field_name: "only lowercase letters, numbers and underscores"
26
+ form_submission:
27
+ blank: "Please complete at least one field."
28
+ required: "%{label} is required"
29
+ document:
30
+ invalid_file_type: "must be a supported document file"
31
+ image:
32
+ invalid_file_type: "must be a valid image file"
33
+ section:
34
+ setting_required: "%{name} is required"
35
+ invalid_image: "contains an image that is not available for this site"
36
+ global_section_requires_attachment: "A global section must stay attached to at least one page."
37
+ page:
38
+ sections_required: "Published pages must have at least one section."
39
+ site:
40
+ default_locale_unavailable: "is not included in I18n.available_locales"
41
+ webhook:
42
+ unsupported_events: "contains unsupported values: %{events}"
43
+ notices:
44
+ form_submission_sent: "Your message has been sent."
45
+ site_created: "Site created."
46
+ site_updated: "Site updated."
47
+ page_created: "Page created."
48
+ page_updated: "Page updated."
49
+ page_deleted: "Page deleted."
50
+ section_added: "Section added."
51
+ section_created: "Section created."
52
+ section_updated: "Section updated."
53
+ section_deleted: "Section deleted."
54
+ section_removed: "Section removed from page."
55
+ section_attached: "Section attached."
56
+ field_added: "Field added."
57
+ field_updated: "Field updated."
58
+ field_removed: "Field removed."
59
+ submission_deleted: "Submission deleted."
60
+ image_uploaded: "Image uploaded."
61
+ image_updated: "Image updated."
62
+ image_deleted: "Image deleted."
63
+ document_uploaded: "Document uploaded."
64
+ document_updated: "Document updated."
65
+ document_deleted: "Document deleted."
66
+ api_key_updated: "API key updated."
67
+ api_key_deleted: "API key deleted."
68
+ webhook_created: "Webhook created."
69
+ webhook_updated: "Webhook updated."
70
+ webhook_deleted: "Webhook deleted."
71
+ mailers:
72
+ form_submission:
73
+ subject: "New form submission - %{page_title}"
74
+ form_field_kinds:
75
+ checkbox: "Checkbox"
76
+ email: "Email"
77
+ select: "Select"
78
+ text: "Text"
79
+ textarea: "Textarea"
80
+ form_submission_mailer:
81
+ notify:
82
+ submitted_at: "Submitted at"
83
+ title: "New form submission — %{page}"
84
+ page_statuses:
85
+ archived: "Archived"
86
+ draft: "Draft"
87
+ published: "Published"
88
+ page_templates:
89
+ custom: "Custom"
90
+ form: "Form"
91
+ landing: "Landing"
92
+ standard: "Standard"
93
+ public:
94
+ forms:
95
+ send: "Send"
96
+ contact: "Contact"
97
+ submission_problem: "There was a problem with your submission"
98
+ section_kinds:
99
+ cta: "Call to action"
100
+ hero: "Hero"
101
+ image: "Image"
102
+ rich_text: "Rich text"
103
+ section_settings:
104
+ labels:
105
+ alignment: "Alignment"
106
+ alt_text: "Alt text"
107
+ background_color: "Background color"
108
+ button_text: "Button text"
109
+ button_url: "Button URL"
110
+ caption: "Caption"
111
+ cta_text: "CTA text"
112
+ cta_url: "CTA URL"
113
+ image_url: "Image URL"
114
+ options:
115
+ alignment:
116
+ center: "Center"
117
+ left: "Left"
118
+ right: "Right"
119
+ webhook_events:
120
+ page_published: "Page published"
121
+ page_unpublished: "Page unpublished"
122
+ admin:
123
+ api_keys:
124
+ create:
125
+ back: "Back to API keys"
126
+ title: "API Key Created"
127
+ token_warning: "Copy your API token now — it will not be shown again:"
128
+ edit:
129
+ back: "Back"
130
+ title: "Edit API Key"
131
+ index:
132
+ active: "Active"
133
+ created: "Created"
134
+ delete: "Delete"
135
+ delete_confirm: "Delete this API key?"
136
+ edit: "Edit"
137
+ empty: "No API keys yet."
138
+ last_used: "Last used"
139
+ name: "Name"
140
+ never: "Never"
141
+ new: "New API Key"
142
+ title: "API Keys"
143
+ new:
144
+ back: "Back"
145
+ title: "New API Key"
146
+ documents:
147
+ edit:
148
+ title: "Edit Document — %{title}"
149
+ form:
150
+ cancel: "Cancel"
151
+ current: "Current"
152
+ file: "File"
153
+ index:
154
+ actions: "Actions"
155
+ delete: "Delete"
156
+ delete_confirm: "Delete this document?"
157
+ description: "Description"
158
+ edit: "Edit"
159
+ empty: "No documents yet."
160
+ file: "File"
161
+ title: "Documents"
162
+ title_heading: "Title"
163
+ upload: "Upload document"
164
+ new:
165
+ title: "Upload Document"
166
+ form_fields:
167
+ edit:
168
+ title: "Edit field — %{label}"
169
+ form:
170
+ cancel: "Cancel"
171
+ field_name: "Field name (snake_case, used as data key)"
172
+ hint: "Hint text shown below the field"
173
+ options: "Options (for select fields, one per line)"
174
+ index:
175
+ actions: "Actions"
176
+ back: "Back to page"
177
+ delete: "Delete"
178
+ delete_confirm: "Delete this field?"
179
+ edit: "Edit"
180
+ empty: "No fields yet. Add fields to build your form."
181
+ field_name: "Field name"
182
+ kind: "Kind"
183
+ label: "Label"
184
+ new: "New field"
185
+ required: "Required"
186
+ submissions: "View submissions"
187
+ title: "Form fields — %{page}"
188
+ new:
189
+ title: "New form field — %{page}"
190
+ form_submissions:
191
+ index:
192
+ actions: "Actions"
193
+ back: "Back to form fields"
194
+ delete: "Delete"
195
+ delete_confirm: "Delete this submission?"
196
+ empty: "No submissions yet."
197
+ export: "Export CSV"
198
+ submitted_at: "Submitted at"
199
+ title: "Submissions — %{page}"
200
+ images:
201
+ edit:
202
+ title: "Edit Image — %{title}"
203
+ form:
204
+ alt_text: "Alt text"
205
+ cancel: "Cancel"
206
+ file: "Image file"
207
+ translation: "Translation (%{locale})"
208
+ index:
209
+ delete: "Delete"
210
+ delete_confirm: "Delete this image?"
211
+ edit: "Edit"
212
+ empty: "No images yet."
213
+ title: "Images"
214
+ upload: "Upload image"
215
+ new:
216
+ title: "Upload Image"
217
+ pages:
218
+ edit:
219
+ title: "Edit Page"
220
+ form:
221
+ cancel: "Cancel"
222
+ current_files: "Current files"
223
+ media_files: "Gallery/attachments"
224
+ no_parent: "— none (root page)"
225
+ parent_page: "Parent page"
226
+ remove_hero_image: "Remove hero image"
227
+ remove_media_files: "Remove all gallery/attachments"
228
+ seo_description: "SEO description"
229
+ seo_title: "SEO title"
230
+ template: "Template"
231
+ title: "Title"
232
+ translation: "Translation (%{locale})"
233
+ translation_title: "Title"
234
+ index:
235
+ actions: "Actions"
236
+ clear: "Clear"
237
+ delete: "Delete"
238
+ delete_confirm: "Delete this page?"
239
+ edit: "Edit"
240
+ empty: "No pages yet. Create your first page to start building the site."
241
+ empty_search: "No pages matched your search."
242
+ footer: "footer"
243
+ header: "header"
244
+ intro: "Pages are listed in site order. Use parent pages for simple hierarchies and search when you need to jump straight to one page."
245
+ nav: "Nav"
246
+ new: "New page"
247
+ search: "Search"
248
+ search_placeholder: "Search by title or slug…"
249
+ site_settings: "Website settings"
250
+ slug: "Slug"
251
+ status: "Status"
252
+ template: "Template"
253
+ title: "Pages"
254
+ title_heading: "Title"
255
+ view: "View"
256
+ new:
257
+ title: "New Page"
258
+ show:
259
+ add_section: "+ Add section"
260
+ attach: "Attach section"
261
+ attach_existing: "Attach global section"
262
+ attachments: "Attachments"
263
+ back: "Back"
264
+ content_sections: "Content sections"
265
+ edit: "Edit"
266
+ footer: "Footer"
267
+ form_fields: "Form fields"
268
+ header: "Header"
269
+ hero_image: "Hero image"
270
+ home_page: "Home page"
271
+ locale: "Locale"
272
+ meta_description: "Meta description"
273
+ meta_title: "Meta title"
274
+ nav_group: "Nav group"
275
+ no_title: "(no title)"
276
+ parent: "Parent"
277
+ preview: "Preview"
278
+ share_preview: "Share preview"
279
+ section_hint: "Select a section type above to add content."
280
+ select_global: "Select a global section"
281
+ slug: "Slug"
282
+ status: "Status"
283
+ submissions: "Submissions"
284
+ subpages: "Subpages"
285
+ template: "Template"
286
+ translation_missing_badge: "%{locale}: missing"
287
+ translation_missing_title: "%{locale}: missing"
288
+ translations: "Translations"
289
+ sections:
290
+ edit:
291
+ title: "Edit section — %{kind}"
292
+ form:
293
+ add: "Add section"
294
+ cancel: "Cancel"
295
+ content: "Content"
296
+ content_placeholder: "Rich text content"
297
+ image_library_empty: "Upload images in the media library before using this block."
298
+ kind: "Kind"
299
+ manage_images: "Manage images"
300
+ select_image: "Select image"
301
+ settings: "Block settings"
302
+ upload_image: "Upload image"
303
+ update: "Update section"
304
+ index:
305
+ actions: "Actions"
306
+ back: "Back to pages"
307
+ delete: "Delete"
308
+ delete_confirm: "Delete this section everywhere it is used?"
309
+ disabled: "Disabled"
310
+ edit: "Edit"
311
+ empty: "No global sections yet."
312
+ enabled: "Enabled"
313
+ intro: "Manage site-wide global sections here and attach them to pages where needed."
314
+ kind: "Kind"
315
+ new: "New global section"
316
+ no_title: "(no title)"
317
+ status: "Status"
318
+ title: "Sections"
319
+ title_heading: "Title"
320
+ unused: "Not attached"
321
+ usage: "Used on"
322
+ new:
323
+ title: "Add section — %{kind}"
324
+ show:
325
+ details: "Section details"
326
+ global: "Global"
327
+ no_translation: "No translation for this locale."
328
+ section:
329
+ delete: "Delete"
330
+ delete_confirm: "Remove this section?"
331
+ disabled: "Disabled"
332
+ drag: "Drag to reorder"
333
+ edit: "Edit"
334
+ enabled: "Enabled"
335
+ no_title: "(no title)"
336
+ sites:
337
+ new:
338
+ title: "Create Website"
339
+ intro: "Create the first CMS site before managing pages, media, and settings."
340
+ edit:
341
+ title: "Edit Website Settings"
342
+ show:
343
+ title: "Website Settings"
344
+ public_url: "Public URL"
345
+ published: "Published"
346
+ default_locale: "Default locale"
347
+ available_locales: "Available locales"
348
+ logo: "Logo"
349
+ logo_alt: "%{site} logo"
350
+ logo_hint: "The logo is also used as the site's favicon."
351
+ edit: "Edit site"
352
+ manage_pages: "Manage pages"
353
+ form:
354
+ logo_alt: "%{site} logo"
355
+ logo_hint: "Used for both the site logo and favicon."
356
+ remove_logo: "Remove logo"
357
+ create: "Create site"
358
+ save: "Save"
359
+ cancel: "Cancel"
360
+ webhook_deliveries:
361
+ index:
362
+ back: "Back to webhooks"
363
+ delivered_at: "Delivered at"
364
+ empty: "No deliveries yet."
365
+ error: "Error"
366
+ event: "Event"
367
+ response_code: "Response code"
368
+ status: "Success"
369
+ title: "Deliveries — %{url}"
370
+ webhooks:
371
+ edit:
372
+ back: "Back"
373
+ title: "Edit Webhook"
374
+ form:
375
+ events: "Events"
376
+ secret: "Secret (optional — used for HMAC signature)"
377
+ url: "Endpoint URL"
378
+ index:
379
+ active: "Active"
380
+ delete: "Delete"
381
+ delete_confirm: "Delete this webhook?"
382
+ edit: "Edit"
383
+ empty: "No webhooks yet."
384
+ events: "Events"
385
+ new: "New Webhook"
386
+ title: "Webhooks"
387
+ url: "URL"
388
+ new:
389
+ back: "Back"
390
+ title: "New Webhook"
data/config/routes.rb ADDED
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ Cms::Engine.routes.draw do
4
+ namespace :admin do
5
+ resource :site, only: %i[new create show edit update]
6
+ resources :sections
7
+ resources :pages do
8
+ member do
9
+ get :preview
10
+ end
11
+ patch "sections/sort", to: "sections#sort", as: :sort_sections
12
+ resources :sections, except: :index
13
+ post "sections/attach", to: "sections#attach", as: :attach_section
14
+ resources :form_fields do
15
+ collection do
16
+ patch :sort
17
+ end
18
+ end
19
+ resources :form_submissions, only: %i[index destroy]
20
+ end
21
+ resources :images
22
+ resources :documents
23
+ resources :api_keys, except: %i[show]
24
+ resources :webhooks, except: %i[show] do
25
+ resources :deliveries, only: :index, controller: "webhook_deliveries"
26
+ end
27
+ end
28
+
29
+ namespace :api, defaults: { format: :json } do
30
+ namespace :v1 do
31
+ resources :sites, only: :show, param: :site_slug do
32
+ resources :pages, only: :show, param: :slug
33
+ end
34
+ resource :site, only: :show, controller: :sites
35
+ resources :pages, only: :show, param: :slug
36
+ end
37
+ end
38
+
39
+ scope module: :public do
40
+ # Public form submissions
41
+ resources :pages, only: [] do
42
+ resources :form_submissions, only: :create
43
+ end
44
+
45
+ # Public multi-site routes (site resolved via URL slug)
46
+ resources :sites, only: :show, param: :site_slug
47
+ get "sites/:site_slug/*slug", to: "sites#show", as: :site_page
48
+
49
+ # Draft preview (token-based, no auth required)
50
+ get "preview/:preview_token", to: "previews#show", as: :page_preview
51
+
52
+ # Public single-site routes (site resolved via header/subdomain)
53
+ get "/", to: "sites#show", as: :current_site
54
+ get "*slug", to: "sites#show", as: :current_site_page
55
+ end
56
+ end
data/lib/cms/engine.rb ADDED
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_text/engine"
4
+
5
+ module Cms
6
+ class Engine < ::Rails::Engine
7
+ isolate_namespace Cms
8
+
9
+ initializer "cms.assets.precompile" do |app|
10
+ app.config.assets.precompile += %w[cms/application.css cms/application.js]
11
+ end
12
+
13
+ initializer "cms.importmap", before: "importmap" do |app|
14
+ if app.config.respond_to?(:importmap)
15
+ app.config.importmap.paths << Engine.root.join("config/importmap.rb")
16
+ app.config.importmap.cache_sweepers << Engine.root.join("app/javascript")
17
+ end
18
+ end
19
+
20
+ config.after_initialize do
21
+ if Rails.env.production? && Cms.config.admin_parent_controller.blank?
22
+ raise "Cms: admin_parent_controller must be configured in production. " \
23
+ "Set it in your Cms.setup block, e.g.: config.admin_parent_controller = \"AdminController\""
24
+ end
25
+ end
26
+
27
+ config.to_prepare do
28
+ # Section block classes
29
+ section_block_classes = {
30
+ "rich_text" => Cms::Section::Blocks::RichTextBlock,
31
+ "image" => Cms::Section::Blocks::ImageBlock,
32
+ "hero" => Cms::Section::Blocks::HeroBlock,
33
+ "cta" => Cms::Section::Blocks::CallToActionBlock
34
+ }
35
+
36
+ Cms::Section::KindRegistry::BUILT_IN_KINDS.each do |kind|
37
+ Cms::Section::KindRegistry.register(kind, block_class: section_block_classes[kind])
38
+ end
39
+
40
+ Array(Cms.config.page_templates).each do |key|
41
+ Cms::Page::TemplateRegistry.register(key)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cms
4
+ VERSION = "0.1.0"
5
+ end
data/lib/cms.rb ADDED
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "discard"
4
+ require "cms/version"
5
+ require "cms/engine"
6
+
7
+ module Cms
8
+ class Configuration
9
+ attr_accessor :parent_controller,
10
+ :admin_parent_controller,
11
+ :current_site_resolver,
12
+ :form_submission_email,
13
+ :mailer_from,
14
+ :image_renditions,
15
+ :page_templates,
16
+ :page_resolver_class,
17
+ :api_site_serializer_class,
18
+ :api_page_serializer_class,
19
+ :admin_layout,
20
+ :public_layout,
21
+ :authorize_admin,
22
+ :auto_destroy_orphaned_sections
23
+
24
+ def initialize
25
+ @parent_controller = "ApplicationController"
26
+ @admin_parent_controller = nil
27
+ @admin_layout = "cms/application"
28
+ @public_layout = "cms/public"
29
+ @image_renditions = {}
30
+ @page_templates = []
31
+ @page_resolver_class = "Cms::PageResolver"
32
+ @api_site_serializer_class = "Cms::Api::SiteSerializer"
33
+ @api_page_serializer_class = "Cms::Api::PageSerializer"
34
+ @mailer_from = nil
35
+ @authorize_admin = nil
36
+ @auto_destroy_orphaned_sections = false
37
+ end
38
+ end
39
+
40
+ class << self
41
+ def config
42
+ @config ||= Configuration.new
43
+ end
44
+
45
+ def setup
46
+ yield config
47
+ end
48
+
49
+ def parent_controller
50
+ config.parent_controller
51
+ end
52
+
53
+ def parent_controller=(val)
54
+ config.parent_controller = val
55
+ end
56
+
57
+ def page_resolver_class
58
+ constantize_config_value(config.page_resolver_class)
59
+ end
60
+
61
+ def api_site_serializer_class
62
+ constantize_config_value(config.api_site_serializer_class)
63
+ end
64
+
65
+ def api_page_serializer_class
66
+ constantize_config_value(config.api_page_serializer_class)
67
+ end
68
+
69
+ private
70
+
71
+ def constantize_config_value(value)
72
+ value.is_a?(String) ? value.constantize : value
73
+ end
74
+ end
75
+ end
data/lib/cms42.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cms"
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
+
6
+ module Cms
7
+ module Generators
8
+ class InstallGenerator < Rails::Generators::Base
9
+ include Rails::Generators::Migration
10
+
11
+ source_root File.expand_path("templates", __dir__)
12
+
13
+ def create_migrations
14
+ migration_template "create_cms_tables.rb", "db/migrate/create_cms_tables.rb"
15
+ end
16
+
17
+ def copy_initializer
18
+ template "initializer.rb", "config/initializers/cms.rb"
19
+ end
20
+
21
+ def self.next_migration_number(dirname)
22
+ ActiveRecord::Generators::Base.next_migration_number(dirname)
23
+ end
24
+ end
25
+ end
26
+ end