ruby_cms 0.1.9 → 0.2.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/README.md +440 -58
  4. data/app/controllers/ruby_cms/admin/base_controller.rb +33 -12
  5. data/app/controllers/ruby_cms/admin/content_block_versions_controller.rb +62 -0
  6. data/app/controllers/ruby_cms/admin/dashboard_controller.rb +40 -0
  7. data/app/helpers/ruby_cms/admin/dashboard_helper.rb +20 -0
  8. data/app/javascript/controllers/ruby_cms/content_block_history_controller.js +91 -0
  9. data/app/javascript/controllers/ruby_cms/index.js +4 -0
  10. data/app/javascript/controllers/ruby_cms/visual_editor_controller.js +33 -29
  11. data/app/models/concerns/content_block/versionable.rb +80 -0
  12. data/app/models/content_block.rb +1 -0
  13. data/app/models/content_block_version.rb +34 -0
  14. data/app/views/admin/content_block_versions/index.html.erb +52 -0
  15. data/app/views/admin/content_block_versions/show.html.erb +37 -0
  16. data/app/views/ruby_cms/admin/content_block_versions/index.html.erb +52 -0
  17. data/app/views/ruby_cms/admin/content_block_versions/show.html.erb +37 -0
  18. data/app/views/ruby_cms/admin/content_blocks/show.html.erb +12 -0
  19. data/app/views/ruby_cms/admin/dashboard/blocks/_analytics_overview.html.erb +53 -0
  20. data/app/views/ruby_cms/admin/dashboard/blocks/_content_blocks_stats.html.erb +17 -0
  21. data/app/views/ruby_cms/admin/dashboard/blocks/_permissions_stats.html.erb +17 -0
  22. data/app/views/ruby_cms/admin/dashboard/blocks/_quick_actions.html.erb +62 -0
  23. data/app/views/ruby_cms/admin/dashboard/blocks/_recent_errors.html.erb +39 -0
  24. data/app/views/ruby_cms/admin/dashboard/blocks/_users_stats.html.erb +17 -0
  25. data/app/views/ruby_cms/admin/dashboard/blocks/_visitor_errors_stats.html.erb +24 -0
  26. data/app/views/ruby_cms/admin/dashboard/index.html.erb +22 -180
  27. data/config/routes.rb +8 -0
  28. data/db/migrate/20260328000001_create_content_block_versions.rb +22 -0
  29. data/lib/generators/ruby_cms/admin_page_generator.rb +126 -0
  30. data/lib/generators/ruby_cms/templates/admin_page/controller.rb.tt +8 -0
  31. data/lib/generators/ruby_cms/templates/admin_page/index.html.erb.tt +11 -0
  32. data/lib/ruby_cms/dashboard_blocks.rb +91 -0
  33. data/lib/ruby_cms/engine/admin_permissions.rb +69 -0
  34. data/lib/ruby_cms/engine/content_blocks_tasks.rb +66 -0
  35. data/lib/ruby_cms/engine/css.rb +14 -0
  36. data/lib/ruby_cms/engine/dashboard_registration.rb +66 -0
  37. data/lib/ruby_cms/engine/navigation_registration.rb +80 -0
  38. data/lib/ruby_cms/engine.rb +23 -278
  39. data/lib/ruby_cms/icons.rb +118 -0
  40. data/lib/ruby_cms/version.rb +1 -1
  41. data/lib/ruby_cms.rb +36 -10
  42. metadata +28 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1f7daf58804d218f4b8e2e87841c9b8f66dbc78668b3b0819bb902cb82952c97
4
- data.tar.gz: acb0830e517c078c8e5fe62eb1f148935adac513ba761f12d166e5c99d400622
3
+ metadata.gz: 2a8d714a6a977cef96bd1eb99a4efd56e66da9ea9aebf81f61e982731bed9bda
4
+ data.tar.gz: 2543ebcfcef9083b33a69f7346ba921c087691aa46d6e1f1b7490d57bf9edfc8
5
5
  SHA512:
6
- metadata.gz: 6ddd67cc13c744e97c345185f68c3db4919f6115cb8440eeed9e17afb262bac749241282afec071e25e04429fdfd059ef9ac6113e1c47d0473b242aed397f3d3
7
- data.tar.gz: c85d346d3b65f7ce7a51578b3d654ad1ca15c7e6f121dbc4387abd23f562b70bfb2255edaa06aa12b56f34ae02b985a92cd89d2ea1c578bc3005f6e0f64d2c01
6
+ metadata.gz: 8625f68d914febc4c00a551f6d48ac0dfa0305a79248ce6faabc95e1641bc8794fef469f4480bfdb3869966011e59977e6be9cc9fdf048e7f1a7d95c01eb7372
7
+ data.tar.gz: 81354dff69046ca0ea0d94dceccd4c682070b768ef6bbf640dc19d2478d7dfe8b3b78e8351fba8c19814781a489a22e2175352ebfd38bda01d1bf50b0cf4e22a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.0.9] - 2026-04-02
4
+
5
+ - Update add page generator, dashboard blocks and some ui tweaks.
6
+
3
7
  ## [0.1.0.9] - 2026-03-25
4
8
 
5
9
  - Update gems
data/README.md CHANGED
@@ -1,141 +1,523 @@
1
1
  # RubyCMS
2
2
 
3
- ![RubyCMS logo](docs/assets/ruby_cms_logo.png)
3
+ Reusable Rails engine for a CMS-style admin: content blocks, visual editor, versioning, pluggable dashboard, permissions, analytics, and visitor error tracking.
4
4
 
5
- Reusable Rails engine for a CMS-style admin: permissions, admin UI shell, content blocks, and a visual editor.
6
-
7
- Vision: your app owns product features (pages, models, business logic); RubyCMS manages content workflows and admin screens.
5
+ Your app owns product features (pages, models, business logic); RubyCMS manages content workflows and admin screens.
8
6
 
9
7
  ## Features
10
8
 
11
- * Visual editor (inline editing for `content_block` regions)
12
- * Content blocks (rich text + placeholders + list items)
13
- * Permissions and users (admin access control)
14
- * Visitor error tracking (`/admin/visitor_errors`)
15
- * Analytics via Ahoy (page views + events)
9
+ - **Content blocks** -- rich text, plain text, images, links, and lists with locale support
10
+ - **Content block versioning** -- automatic version history with rollback and side-by-side diffs
11
+ - **Visual editor** -- inline editing on live page previews
12
+ - **Pluggable dashboard** -- registry-based blocks with permission gating and host-app extensibility
13
+ - **Permissions** -- key-based access control with templates and per-user assignment
14
+ - **Admin page generator** -- one command to scaffold a new admin page with nav, permission, and route
15
+ - **Named icon registry** -- 20+ Heroicons by name, no SVG copy-pasting
16
+ - **Settings** -- DB-backed admin settings with categories and UI
17
+ - **Analytics** -- Ahoy-powered visit/event tracking with dashboard drill-downs
18
+ - **Visitor error tracking** -- automatic 404/500 capture with admin overview
16
19
 
17
20
  ## Quick Start
18
21
 
19
22
  ```bash
23
+ # Add to Gemfile
24
+ gem "ruby_cms"
25
+
26
+ # Install
20
27
  rails g ruby_cms:install
28
+ rails db:migrate
29
+ rails ruby_cms:seed_permissions
30
+ rails ruby_cms:setup_admin
21
31
  ```
22
32
 
23
- The generator sets up:
24
-
25
- * `config/initializers/ruby_cms.rb`
26
- * mounts the engine (`/admin/...` on the host app)
27
- * migrations + RubyCMS tables
28
- * seed permissions + initial admin setup
33
+ The install generator sets up the initializer, mounts the engine at `/admin`, runs migrations, configures Tailwind, Stimulus, Action Text, and Ahoy.
29
34
 
30
- If your host app already has `/admin` routes, adjust/remove them so RubyCMS can use `/admin`.
35
+ Visit `/admin` and sign in with the admin user you configured.
31
36
 
32
- ## Using Content Blocks
37
+ ## Content Blocks
33
38
 
34
- In any view:
39
+ Use the `content_block` helper in any view:
35
40
 
36
41
  ```erb
37
42
  <%= content_block("hero_title", default: "Welcome") %>
38
43
  <%= content_block("footer", cache: true) %>
39
44
  ```
40
45
 
41
- <details>
42
- <summary>Placeholders (attributes like `placeholder`, `alt`, meta tags)</summary>
46
+ ### Content Types
47
+
48
+ | Type | Description |
49
+ |------|-------------|
50
+ | `text` | Plain text string |
51
+ | `rich_text` | Action Text rich content (HTML) |
52
+ | `image` | Attached image via Active Storage |
53
+ | `link` | URL string |
54
+ | `list` | JSON array or newline-separated items |
55
+
56
+ ### Placeholders and Attributes
43
57
 
44
- `content_block` wraps output for the visual editor, so do not put it inside HTML attributes.
45
- Use `wrap: false` (or `content_block_text`):
58
+ `content_block` wraps output for the visual editor. For HTML attributes, use `wrap: false`:
46
59
 
47
60
  ```erb
48
61
  <%= text_field_tag :name, nil,
49
- placeholder: content_block("contact.name_placeholder", wrap: false, fallback: "Your name") %>
50
-
51
- <%= text_area_tag :message, nil,
52
- placeholder: content_block_text("contact.message_placeholder", fallback: "Your message...") %>
62
+ placeholder: content_block("contact.placeholder", wrap: false, fallback: "Your name") %>
53
63
  ```
54
64
 
55
- </details>
65
+ Or use `content_block_text` which never wraps:
56
66
 
57
- <details>
58
- <summary>List items (badges, tags, arrays)</summary>
67
+ ```erb
68
+ <meta name="description" content="<%= content_block_text("meta_desc", fallback: "Default") %>">
69
+ ```
59
70
 
60
- Use `content_block_list_items` to get an Array:
71
+ ### List Items
61
72
 
62
73
  ```erb
63
- <% content_block_list_items("education.item.badges", fallback: item[:badges]).each do |badge| %>
74
+ <% content_block_list_items("badges", fallback: ["Ruby", "Rails"]).each do |badge| %>
64
75
  <%= tag.span badge, class: "badge" %>
65
76
  <% end %>
66
77
  ```
67
78
 
68
- Store list content as JSON (`["Ruby", "Rails"]`) or newline-separated text in the CMS.
79
+ ### Multi-locale Support
69
80
 
70
- </details>
81
+ Content blocks have a `locale` field. The CMS groups blocks by key prefix across locales for easy management.
71
82
 
72
- Create/edit blocks in **Admin -> Content blocks**.
83
+ ## Content Block Versioning
84
+
85
+ Versions are created automatically on every meaningful change:
86
+
87
+ ```ruby
88
+ block = ContentBlock.create!(key: "hero", title: "Welcome", content: "Hello",
89
+ content_type: "text", locale: "en")
90
+ block.versions.count # => 1 (event: "create")
91
+
92
+ block.update!(title: "Welcome!")
93
+ block.versions.count # => 2 (event: "update")
94
+
95
+ block.update!(published: false)
96
+ block.versions.last.event # => "unpublish"
97
+ ```
98
+
99
+ ### Events
100
+
101
+ | Event | When |
102
+ |-------|------|
103
+ | `create` | Block is first created |
104
+ | `update` | Title, content, or content_type changes |
105
+ | `publish` | `published` changes to `true` |
106
+ | `unpublish` | `published` changes to `false` |
107
+ | `rollback` | `rollback_to_version!` is called |
108
+ | `visual_editor` | Edit via the visual editor |
109
+
110
+ ### Rollback
111
+
112
+ ```ruby
113
+ old_version = block.versions.first
114
+ block.rollback_to_version!(old_version, user: current_user)
115
+ # Creates a new version with event: "rollback"
116
+ # Restores title, content, content_type, published, and rich_content
117
+ ```
118
+
119
+ ### Admin UI
120
+
121
+ - **Version history** link on each content block show page
122
+ - **Timeline view** with colored event badges
123
+ - **Side-by-side diff** (old vs new, red/green)
124
+ - **Rollback button** with confirmation
125
+
126
+ ### Routes
127
+
128
+ ```
129
+ GET /admin/content_blocks/:id/versions # index (HTML + JSON)
130
+ GET /admin/content_blocks/:id/versions/:vid # show with diff
131
+ POST /admin/content_blocks/:id/versions/:vid/rollback
132
+ ```
73
133
 
74
134
  ## Visual Editor
75
135
 
76
- Configure preview templates + (optional) preview data in `config/initializers/ruby_cms.rb`:
136
+ Configure preview templates in `config/initializers/ruby_cms.rb`:
77
137
 
78
138
  ```ruby
79
- c.preview_templates = { "home" => "pages/home", "about" => "pages/about" }
80
- c.preview_data = ->(page_key, view) { { products: Product.limit(5) } }
139
+ RubyCms.configure do |c|
140
+ c.preview_templates = { "home" => "pages/home", "about" => "pages/about" }
141
+ c.preview_data = ->(page_key, view) { { products: Product.limit(5) } }
142
+ end
81
143
  ```
82
144
 
83
- Then open **Admin -> Visual editor**, pick a page key, and click content blocks in the preview.
145
+ Open **Admin > Visual editor**, pick a page, and click content blocks to edit them inline.
84
146
 
85
- ## Admin UI Pages (custom admin templates)
147
+ ## Dashboard
86
148
 
87
- RubyCMS exposes an `admin_page` helper:
149
+ The dashboard uses a registry-based block system. Each block is a partial with optional permission gating and data injection.
88
150
 
89
- ```erb
90
- <%= admin_page(title: "My Page", subtitle: "Optional") do %>
91
- <p>Hello from RubyCMS admin.</p>
92
- <% end %>
151
+ ### Default Blocks
152
+
153
+ | Block | Section | Permission |
154
+ |-------|---------|------------|
155
+ | Content blocks stats | stats | `manage_content_blocks` |
156
+ | Users stats | stats | `manage_permissions` |
157
+ | Permissions stats | stats | `manage_permissions` |
158
+ | Visitor errors stats | stats | `manage_visitor_errors` |
159
+ | Quick actions | main | -- |
160
+ | Recent errors | main | `manage_visitor_errors` |
161
+ | Analytics overview | main | `manage_analytics` |
162
+
163
+ ### Adding Custom Blocks
164
+
165
+ ```ruby
166
+ # config/initializers/ruby_cms.rb (or ruby_cms_pages.rb)
167
+ Rails.application.config.to_prepare do
168
+ RubyCms.dashboard_register(
169
+ key: :orders_stats,
170
+ label: "Orders",
171
+ section: :stats, # :stats (top row) or :main (bottom grid)
172
+ order: 5,
173
+ partial: "admin/dashboard/orders_stats",
174
+ permission: :manage_orders,
175
+ span: :single, # :single or :double (grid width)
176
+ data: ->(controller) {
177
+ { count: Order.count, today: Order.where("created_at > ?", Date.today).count }
178
+ }
179
+ )
180
+ end
181
+ ```
182
+
183
+ Then create the partial `app/views/admin/dashboard/_orders_stats.html.erb`. The `block` local contains the registration data and any computed `data`.
184
+
185
+ ### Overriding Default Blocks
186
+
187
+ Re-register with the same key to override:
188
+
189
+ ```ruby
190
+ RubyCms.dashboard_register(
191
+ key: :quick_actions,
192
+ label: "Quick actions",
193
+ section: :main,
194
+ partial: "admin/dashboard/my_quick_actions" # your own partial
195
+ )
196
+ ```
197
+
198
+ ## Navigation and Permissions
199
+
200
+ ### register_page (recommended)
201
+
202
+ One call to register a nav item and its permission:
203
+
204
+ ```ruby
205
+ RubyCms.register_page(
206
+ key: :backups,
207
+ label: "Backups",
208
+ path: :admin_backups_path, # Symbol: auto-wrapped via main_app
209
+ icon: :archive_box, # Named icon from RubyCms::Icons
210
+ section: :main, # :main (sidebar top) or :settings (sidebar bottom)
211
+ permission: :manage_backups, # Auto-registered as permission key
212
+ order: 10
213
+ )
214
+ ```
215
+
216
+ ### nav_register (low-level)
217
+
218
+ For more control (custom visibility gates, non-standard paths):
219
+
220
+ ```ruby
221
+ RubyCms.nav_register(
222
+ key: :reports,
223
+ label: "Reports",
224
+ path: ->(view) { view.main_app.reports_path },
225
+ icon: :chart_bar,
226
+ section: "main",
227
+ permission: :manage_reports,
228
+ if: ->(view) { view.current_user_cms&.admin? }
229
+ )
230
+ ```
231
+
232
+ ### Path Options
233
+
234
+ | Format | Example | Behavior |
235
+ |--------|---------|----------|
236
+ | Symbol | `:admin_backups_path` | Auto-wrapped: `view.main_app.send(:admin_backups_path)` |
237
+ | Lambda | `-> (v) { v.some_path }` | Called with view context |
238
+ | String | `"/admin/backups"` | Used as-is |
239
+
240
+ ### Named Icons
241
+
242
+ Use symbol keys from `RubyCms::Icons`:
243
+
244
+ ```ruby
245
+ RubyCms::Icons.available
246
+ # => [:home, :pencil_square, :document_duplicate, :chart_bar, :shield_check,
247
+ # :exclamation_triangle, :user_group, :cog_6_tooth, :archive_box, :folder,
248
+ # :bell, :clock, :tag, :cube, :envelope, :wrench, :globe, :photograph,
249
+ # :list_bullet, :plus_circle, :trash, :eye, :lock_closed, :currency_dollar]
250
+ ```
251
+
252
+ Raw SVG strings are also accepted for custom icons.
253
+
254
+ ### Permission Keys
255
+
256
+ Default keys: `manage_admin`, `manage_permissions`, `manage_content_blocks`, `manage_visitor_errors`, `manage_analytics`.
257
+
258
+ Register additional keys:
259
+
260
+ ```ruby
261
+ RubyCms.register_permission_keys(:manage_orders, :manage_reports)
262
+ ```
263
+
264
+ ### Permission Templates
265
+
266
+ Group permission keys into reusable templates:
267
+
268
+ ```ruby
269
+ RubyCms.register_permission_template(:editor,
270
+ label: "Editor",
271
+ keys: %w[manage_admin manage_content_blocks],
272
+ description: "Can manage content but not users"
273
+ )
274
+
275
+ # Apply to a user:
276
+ RubyCms::Permission.apply_template!(user, :editor)
277
+ ```
278
+
279
+ ### cms_page Macro
280
+
281
+ In controllers that inherit from `RubyCms::Admin::BaseController`, use `cms_page` to link to a registered page. The permission is looked up from the nav registry automatically:
282
+
283
+ ```ruby
284
+ class Admin::BackupsController < RubyCms::Admin::BaseController
285
+ cms_page :backups # permission from register_page(:backups, permission: :manage_backups)
286
+
287
+ def index
288
+ @backups = Backup.recent
289
+ end
290
+ end
291
+ ```
292
+
293
+ ## Admin Page Generator
294
+
295
+ Scaffold a complete admin page with one command:
296
+
297
+ ```bash
298
+ rails g ruby_cms:admin_page backups
93
299
  ```
94
300
 
301
+ This generates:
302
+
303
+ | File | Description |
304
+ |------|-------------|
305
+ | `app/controllers/admin/backups_controller.rb` | Controller with `cms_page :backups` |
306
+ | `app/views/admin/backups/index.html.erb` | View template |
307
+ | `config/routes.rb` | Route injection (`namespace :admin`) |
308
+ | `config/initializers/ruby_cms_pages.rb` | `register_page` call |
309
+
310
+ ### Options
311
+
312
+ ```bash
313
+ rails g ruby_cms:admin_page backups \
314
+ --permission=manage_backups \
315
+ --icon=archive_box \
316
+ --section=settings \
317
+ --order=15
318
+ ```
319
+
320
+ | Option | Default | Description |
321
+ |--------|---------|-------------|
322
+ | `--permission` | `manage_<name>` | Permission key |
323
+ | `--icon` | `folder` | Icon from `RubyCms::Icons` |
324
+ | `--section` | `main` | `main` or `settings` |
325
+ | `--order` | `10` | Sort order in nav |
326
+
327
+ After generating, run `rails ruby_cms:seed_permissions` to create the permission row in the database.
328
+
329
+ ## Settings
330
+
331
+ RubyCMS uses DB-backed settings with a registry for defaults and types:
332
+
333
+ ```ruby
334
+ # Read
335
+ RubyCms::Settings.get(:analytics_default_period, default: "week")
336
+
337
+ # Write (admin UI or programmatic)
338
+ RubyCms::Settings.set(:analytics_default_period, "month")
339
+ ```
340
+
341
+ ### Registering Custom Settings
342
+
343
+ ```ruby
344
+ RubyCms::SettingsRegistry.register(
345
+ key: :my_custom_setting,
346
+ type: :string,
347
+ default: "hello",
348
+ category: :general,
349
+ description: "A custom setting"
350
+ )
351
+ ```
352
+
353
+ Settings are managed in **Admin > Settings** with tabs per category.
354
+
95
355
  ## Visitor Error Tracking
96
356
 
97
- Public (non-admin) exceptions are captured and shown in **`/admin/visitor_errors`**.
357
+ Public exceptions are automatically captured when `RubyCms::VisitorErrorCapture` is included in your `ApplicationController` (added by the install generator).
98
358
 
99
- In development, logging is typically disabled and exceptions are re-raised normally.
359
+ View captured errors in **Admin > Visitor errors** with status codes, paths, timestamps, and resolution tracking.
100
360
 
101
- ## Page View Tracking (Ahoy)
361
+ ## Analytics (Ahoy)
362
+
363
+ RubyCMS integrates with Ahoy for server-side page view and event tracking.
102
364
 
103
365
  Include `RubyCms::PageTracking` in public controllers:
104
366
 
105
367
  ```ruby
106
368
  class PagesController < ApplicationController
107
369
  include RubyCms::PageTracking
370
+ end
371
+ ```
108
372
 
109
- def home
110
- # @page_name can be set; defaults vary by controller
111
- end
373
+ View analytics in **Admin > Analytics** with:
374
+ - Visit and event counts
375
+ - Popular pages
376
+ - Top visitors
377
+ - Configurable date ranges and periods
378
+
379
+ ### Customization Hooks
380
+
381
+ ```ruby
382
+ RubyCms.configure do |c|
383
+ c.analytics_visit_scope = ->(scope) { scope.where.not(ip: ["127.0.0.1"]) }
384
+ c.analytics_event_scope = ->(scope) { scope }
385
+ c.analytics_extra_cards = lambda { |start_date:, end_date:, period:, visits_scope:, events_scope:|
386
+ [{ title: "Custom KPI", value: visits_scope.count }]
387
+ }
112
388
  end
113
389
  ```
114
390
 
115
391
  ## Seeding Content Blocks from YAML
116
392
 
117
- If you want to import blocks from locales, set:
393
+ Set the translation namespace:
118
394
 
119
395
  ```ruby
120
- c.content_blocks_translation_namespace = "content_blocks"
396
+ RubyCms.configure do |c|
397
+ c.content_blocks_translation_namespace = "content_blocks"
398
+ end
121
399
  ```
122
400
 
123
- Example `config/locales/en.yml`:
401
+ Create locale files:
124
402
 
125
403
  ```yaml
404
+ # config/locales/en.yml
126
405
  en:
127
406
  content_blocks:
128
407
  hero_title: "Welcome to my site"
408
+ footer_text: "Copyright 2026"
129
409
  ```
130
410
 
131
411
  Import:
132
412
 
133
413
  ```bash
134
- rails ruby_cms:content_blocks:seed
414
+ rails ruby_cms:content_blocks:import
415
+ ```
416
+
417
+ Export DB content blocks back to YAML:
418
+
419
+ ```bash
420
+ rails ruby_cms:content_blocks:export
135
421
  ```
136
422
 
137
- ## Common Rake Tasks
423
+ ## Configuration
424
+
425
+ All configuration happens in `config/initializers/ruby_cms.rb`:
426
+
427
+ ```ruby
428
+ RubyCms.configure do |c|
429
+ # Base controller (must provide current_user + authentication)
430
+ c.admin_base_controller = "ApplicationController"
431
+
432
+ # Admin layout
433
+ c.admin_layout = "admin/admin"
434
+
435
+ # User model class name
436
+ c.user_class_name = "User"
437
+
438
+ # Allow user.admin? bypass when no Permission records exist
439
+ c.bootstrap_admin_with_role = true
440
+
441
+ # Redirect path for unauthenticated/unauthorized users
442
+ c.unauthorized_redirect_path = "/"
443
+
444
+ # Visual editor
445
+ c.preview_templates = { "home" => "pages/home" }
446
+ c.preview_data = ->(page_key, view) { {} }
447
+
448
+ # Content blocks
449
+ c.content_blocks_translation_namespace = "content_blocks"
450
+ c.image_content_types = %w[image/png image/jpeg image/gif image/webp]
451
+ c.image_max_size = 5 * 1024 * 1024
452
+ end
453
+ ```
454
+
455
+ ## Rake Tasks
456
+
457
+ | Task | Description |
458
+ |------|-------------|
459
+ | `ruby_cms:seed_permissions` | Create default permission rows + settings |
460
+ | `ruby_cms:setup_admin` | Interactive first admin user setup |
461
+ | `ruby_cms:grant_manage_admin` | Grant all permissions to a user by email |
462
+ | `ruby_cms:content_blocks:export` | Export DB content blocks to YAML |
463
+ | `ruby_cms:content_blocks:import` | Import content blocks from YAML |
464
+ | `ruby_cms:content_blocks:sync` | Export + optional import |
465
+ | `ruby_cms:import_initializer_settings` | Import initializer values into DB settings |
466
+ | `ruby_cms:css:compile` | Compile admin CSS to host app |
467
+ | `ruby_cms:css:compile_gem` | Compile admin CSS within the gem |
468
+
469
+ ## Development
470
+
471
+ ### Setup
472
+
473
+ ```bash
474
+ git clone https://github.com/jobhammer00/ruby_cms.git
475
+ cd ruby_cms
476
+ bundle install
477
+ ```
478
+
479
+ ### Running Tests
480
+
481
+ ```bash
482
+ bundle exec rspec
483
+ ```
484
+
485
+ Tests use an in-memory SQLite database via `spec/support/dummy_app.rb`. No separate dummy Rails app directory is needed.
486
+
487
+ ### CSS Compilation
488
+
489
+ ```bash
490
+ rails ruby_cms:css:compile_gem
491
+ ```
492
+
493
+ ### Architecture
494
+
495
+ ```
496
+ lib/ruby_cms.rb # Module: nav_register, register_page, permissions API
497
+ lib/ruby_cms/engine.rb # Rails::Engine: config, initializers, rake tasks, nav registration
498
+ lib/ruby_cms/icons.rb # Named Heroicon SVG registry
499
+ lib/ruby_cms/dashboard_blocks.rb # Dashboard block registry
500
+ lib/ruby_cms/settings.rb # DB-backed settings
501
+ lib/ruby_cms/settings_registry.rb # Settings definitions and defaults
502
+ app/controllers/ruby_cms/admin/ # Admin controllers (base, dashboard, content blocks, etc.)
503
+ app/models/ # ContentBlock, ContentBlockVersion, Permission, etc.
504
+ app/views/ruby_cms/admin/ # Admin views
505
+ app/views/layouts/ruby_cms/ # Admin layout + sidebar
506
+ app/components/ # ViewComponents (AdminPage, etc.)
507
+ app/helpers/ # Content block, settings, dashboard helpers
508
+ ```
509
+
510
+ ### Key Extension Points
511
+
512
+ | What | How |
513
+ |------|-----|
514
+ | New admin page | `rails g ruby_cms:admin_page <name>` |
515
+ | New nav item | `RubyCms.register_page(...)` or `RubyCms.nav_register(...)` |
516
+ | New permission | `RubyCms.register_permission_keys(:key)` |
517
+ | New dashboard block | `RubyCms.dashboard_register(...)` |
518
+ | New setting | `RubyCms::SettingsRegistry.register(...)` |
519
+ | New icon | Pass raw SVG string to `icon:` parameter |
520
+
521
+ ## License
138
522
 
139
- * `rails ruby_cms:seed_permissions`
140
- * `rails ruby_cms:setup_admin`
141
- * `rails ruby_cms:content_blocks:seed`
523
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -20,8 +20,39 @@ module RubyCms
20
20
  # new_session_path, root_path, etc.
21
21
  include RubyCms::Engine.routes.url_helpers
22
22
 
23
+ # Declare which registered page this controller serves.
24
+ # Looks up the permission from the nav_registry entry and sets a before_action.
25
+ # Usage: cms_page :backups (requires a matching register_page call with key: :backups)
26
+ def self.cms_page(key)
27
+ page_key = key.to_sym
28
+ before_action do
29
+ entry = RubyCms.nav_registry.find {|e| e[:key] == page_key }
30
+ if entry.nil?
31
+ if defined?(Rails.logger)
32
+ Rails.logger.warn("[RubyCMS] cms_page :#{page_key} has no matching register_page entry. " \
33
+ "Only manage_admin is enforced.")
34
+ end
35
+ elsif entry[:permission].present?
36
+ require_permission!(entry[:permission].to_sym)
37
+ end
38
+ end
39
+ end
40
+
41
+ # Public API: dashboard block +data procs and host code may call this on the controller instance.
42
+ def current_user_cms
43
+ @current_user_cms ||= resolve_current_user
44
+ end
45
+
23
46
  private
24
47
 
48
+ def resolve_current_user
49
+ if respond_to?(:current_user, true)
50
+ send(:current_user)
51
+ else
52
+ Rails.application.config.ruby_cms.current_user_resolver&.call(self)
53
+ end
54
+ end
55
+
25
56
  def require_cms_access
26
57
  ensure_authenticated
27
58
  require_permission!(:manage_admin)
@@ -67,18 +98,6 @@ module RubyCms
67
98
  Rails.application.config.ruby_cms.unauthorized_redirect_path.presence || "/"
68
99
  end
69
100
 
70
- def current_user_cms
71
- @current_user_cms ||= resolve_current_user
72
- end
73
-
74
- def resolve_current_user
75
- if respond_to?(:current_user, true)
76
- send(:current_user)
77
- else
78
- Rails.application.config.ruby_cms.current_user_resolver&.call(self)
79
- end
80
- end
81
-
82
101
  def render_not_found
83
102
  render "ruby_cms/errors/not_found",
84
103
  status: :not_found,
@@ -106,6 +125,8 @@ module RubyCms
106
125
  def model_param_key(model_class, param_name)
107
126
  params.key?(param_name) ? param_name : model_class.model_name.param_key.to_sym
108
127
  end
128
+
129
+ public :current_user_cms
109
130
  end
110
131
  end
111
132
  end