lean_cms 0.2.12
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +235 -0
- data/LICENSE +21 -0
- data/README.md +107 -0
- data/app/assets/images/lean_cms/sloth-404.png +0 -0
- data/app/assets/images/lean_cms/sloth-500.png +0 -0
- data/app/assets/images/lean_cms/sloth-favicon-16.png +0 -0
- data/app/assets/images/lean_cms/sloth-favicon-32.png +0 -0
- data/app/assets/images/lean_cms/sloth-favicon-64.png +0 -0
- data/app/assets/images/lean_cms/sloth-logo.png +0 -0
- data/app/assets/lean_cms/actiontext.css +440 -0
- data/app/assets/lean_cms/cms_edit_controls.css +548 -0
- data/app/assets/tailwind/lean_cms/engine.css +14 -0
- data/app/components/lean_cms/base_component.rb +61 -0
- data/app/components/lean_cms/bullets_section_component.html.erb +23 -0
- data/app/components/lean_cms/bullets_section_component.rb +54 -0
- data/app/components/lean_cms/cards_section_component.html.erb +237 -0
- data/app/components/lean_cms/cards_section_component.rb +71 -0
- data/app/components/lean_cms/editable_content_component.html.erb +15 -0
- data/app/components/lean_cms/editable_content_component.rb +53 -0
- data/app/components/lean_cms/section_component.html.erb +18 -0
- data/app/components/lean_cms/section_component.rb +35 -0
- data/app/controllers/concerns/lean_cms/authentication.rb +60 -0
- data/app/controllers/concerns/lean_cms/authorization.rb +60 -0
- data/app/controllers/lean_cms/activity_controller.rb +16 -0
- data/app/controllers/lean_cms/application_controller.rb +48 -0
- data/app/controllers/lean_cms/dashboard_controller.rb +13 -0
- data/app/controllers/lean_cms/form_submissions_controller.rb +37 -0
- data/app/controllers/lean_cms/notification_settings_controller.rb +145 -0
- data/app/controllers/lean_cms/notifications_controller.rb +26 -0
- data/app/controllers/lean_cms/page_contents_controller.rb +403 -0
- data/app/controllers/lean_cms/password_setup_controller.rb +65 -0
- data/app/controllers/lean_cms/passwords_controller.rb +42 -0
- data/app/controllers/lean_cms/posts_controller.rb +78 -0
- data/app/controllers/lean_cms/sessions_controller.rb +50 -0
- data/app/controllers/lean_cms/settings_controller.rb +124 -0
- data/app/controllers/lean_cms/users_controller.rb +113 -0
- data/app/helpers/lean_cms/activity_helper.rb +190 -0
- data/app/helpers/lean_cms/application_helper.rb +43 -0
- data/app/helpers/lean_cms/content_helper.rb +34 -0
- data/app/helpers/lean_cms/page_content_helper.rb +359 -0
- data/app/javascript/controllers/cards_editor_controller.js +317 -0
- data/app/javascript/controllers/cms_sticky_overlay_controller.js +59 -0
- data/app/javascript/controllers/field_editor_form_controller.js +68 -0
- data/app/javascript/controllers/field_editor_modal_controller.js +79 -0
- data/app/javascript/controllers/inline_edit_controller.js +414 -0
- data/app/javascript/controllers/inline_edit_toggle_controller.js +81 -0
- data/app/javascript/controllers/notifications_controller.js +19 -0
- data/app/javascript/controllers/settings_inline_edit_sync_controller.js +38 -0
- data/app/javascript/controllers/settings_override_controller.js +45 -0
- data/app/mailers/lean_cms/application_mailer.rb +6 -0
- data/app/mailers/lean_cms/passwords_mailer.rb +8 -0
- data/app/mailers/lean_cms/users_mailer.rb +39 -0
- data/app/models/lean_cms/current.rb +6 -0
- data/app/models/lean_cms/form_submission.rb +45 -0
- data/app/models/lean_cms/magic_link.rb +76 -0
- data/app/models/lean_cms/meta_tag.rb +30 -0
- data/app/models/lean_cms/notification_setting.rb +69 -0
- data/app/models/lean_cms/page.rb +23 -0
- data/app/models/lean_cms/page_content.rb +245 -0
- data/app/models/lean_cms/post.rb +65 -0
- data/app/models/lean_cms/session.rb +7 -0
- data/app/models/lean_cms/setting.rb +156 -0
- data/app/policies/lean_cms/application_policy.rb +35 -0
- data/app/policies/lean_cms/page_content_policy.rb +31 -0
- data/app/policies/lean_cms/post_policy.rb +37 -0
- data/app/policies/lean_cms/setting_policy.rb +17 -0
- data/app/views/layouts/lean_cms/application.html.erb +114 -0
- data/app/views/layouts/lean_cms/auth.html.erb +200 -0
- data/app/views/lean_cms/activity/index.html.erb +79 -0
- data/app/views/lean_cms/dashboard/index.html.erb +180 -0
- data/app/views/lean_cms/form_submissions/index.html.erb +104 -0
- data/app/views/lean_cms/form_submissions/show.html.erb +157 -0
- data/app/views/lean_cms/notification_settings/edit.html.erb +192 -0
- data/app/views/lean_cms/notifications/index.html.erb +72 -0
- data/app/views/lean_cms/notifications/show.html.erb +39 -0
- data/app/views/lean_cms/page_contents/_field_editor.html.erb +174 -0
- data/app/views/lean_cms/page_contents/edit.html.erb +428 -0
- data/app/views/lean_cms/page_contents/index.html.erb +113 -0
- data/app/views/lean_cms/password_setup/show.html.erb +35 -0
- data/app/views/lean_cms/passwords/edit.html.erb +26 -0
- data/app/views/lean_cms/passwords/new.html.erb +21 -0
- data/app/views/lean_cms/passwords_mailer/reset.html.erb +6 -0
- data/app/views/lean_cms/passwords_mailer/reset.text.erb +4 -0
- data/app/views/lean_cms/posts/_form.html.erb +118 -0
- data/app/views/lean_cms/posts/edit.html.erb +31 -0
- data/app/views/lean_cms/posts/index.html.erb +100 -0
- data/app/views/lean_cms/posts/new.html.erb +16 -0
- data/app/views/lean_cms/sessions/new.html.erb +28 -0
- data/app/views/lean_cms/settings/edit.html.erb +384 -0
- data/app/views/lean_cms/shared/_admin_bar.html.erb +85 -0
- data/app/views/lean_cms/shared/_header.html.erb +86 -0
- data/app/views/lean_cms/shared/_notifications_bell.html.erb +84 -0
- data/app/views/lean_cms/shared/_sidebar.html.erb +102 -0
- data/app/views/lean_cms/users/_form.html.erb +105 -0
- data/app/views/lean_cms/users/edit.html.erb +8 -0
- data/app/views/lean_cms/users/index.html.erb +99 -0
- data/app/views/lean_cms/users/new.html.erb +8 -0
- data/app/views/lean_cms/users_mailer/admin_triggered_password_reset.html.erb +13 -0
- data/app/views/lean_cms/users_mailer/admin_triggered_password_reset.text.erb +11 -0
- data/app/views/lean_cms/users_mailer/invitation.html.erb +13 -0
- data/app/views/lean_cms/users_mailer/invitation.text.erb +11 -0
- data/app/views/lean_cms/users_mailer/reactivation.html.erb +13 -0
- data/app/views/lean_cms/users_mailer/reactivation.text.erb +11 -0
- data/config/importmap.rb +8 -0
- data/config/routes.rb +78 -0
- data/db/migrate/20251112034030_create_lean_cms_tables.rb +131 -0
- data/db/migrate/20260513000001_create_lean_cms_auth_tables.rb +31 -0
- data/db/migrate/20260514000001_create_paper_trail_versions.rb +16 -0
- data/db/migrate/20260514000002_create_action_text_tables.rb +18 -0
- data/db/migrate/20260514000003_create_active_storage_tables.rb +45 -0
- data/db/migrate/20260514000004_create_noticed_tables.rb +27 -0
- data/lib/generators/lean_cms/demo/demo_generator.rb +54 -0
- data/lib/generators/lean_cms/demo/templates/lean_cms_structure.yml +129 -0
- data/lib/generators/lean_cms/demo/templates/pages_controller.rb +30 -0
- data/lib/generators/lean_cms/demo/templates/views/pages/about.html.erb +40 -0
- data/lib/generators/lean_cms/demo/templates/views/pages/contact.html.erb +55 -0
- data/lib/generators/lean_cms/demo/templates/views/pages/home.html.erb +31 -0
- data/lib/generators/lean_cms/install/install_generator.rb +317 -0
- data/lib/generators/lean_cms/install/templates/add_lean_cms_columns_to_users.rb.tt +7 -0
- data/lib/generators/lean_cms/install/templates/lean_cms.rb +11 -0
- data/lib/generators/lean_cms/install/templates/lean_cms_structure.yml +29 -0
- data/lib/lean_cms/configuration.rb +32 -0
- data/lib/lean_cms/engine.rb +93 -0
- data/lib/lean_cms/loader.rb +217 -0
- data/lib/lean_cms/sync_helper.rb +182 -0
- data/lib/lean_cms/version.rb +3 -0
- data/lib/lean_cms.rb +26 -0
- data/lib/tasks/lean_cms.rake +390 -0
- metadata +313 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<aside class="w-64 bg-white border-r border-gray-200 flex-shrink-0 overflow-y-auto">
|
|
2
|
+
<!-- Logo / Brand -->
|
|
3
|
+
<div class="h-16 flex items-center px-6 border-b border-gray-200">
|
|
4
|
+
<div class="flex items-center space-x-3">
|
|
5
|
+
<% if LeanCms.site_logo_path.present? %>
|
|
6
|
+
<%= image_tag LeanCms.site_logo_path, alt: LeanCms.site_name, class: "h-10 w-auto" %>
|
|
7
|
+
<% else %>
|
|
8
|
+
<div class="h-10 w-10 rounded-lg flex items-center justify-center text-white font-bold text-lg" style="background-color: var(--cms-primary);">
|
|
9
|
+
<%= LeanCms.site_name[0..1].upcase %>
|
|
10
|
+
</div>
|
|
11
|
+
<% end %>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<!-- Navigation -->
|
|
16
|
+
<nav class="mt-6 px-3">
|
|
17
|
+
<!-- Dashboard -->
|
|
18
|
+
<%= link_to lean_cms_root_path, class: "flex items-center px-3 py-2 text-sm font-medium rounded-lg mb-1 transition-colors #{current_page?(lean_cms_root_path) ? 'text-white' : 'text-gray-700 hover:bg-gray-100'}", style: current_page?(lean_cms_root_path) ? "background-color: var(--cms-primary);" : "" do %>
|
|
19
|
+
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
20
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
|
|
21
|
+
</svg>
|
|
22
|
+
Dashboard
|
|
23
|
+
<% end %>
|
|
24
|
+
|
|
25
|
+
<% if current_user&.can_edit_blog? || current_user&.can_edit_pages? %>
|
|
26
|
+
<!-- Content Section -->
|
|
27
|
+
<div class="mt-6 mb-2 px-3">
|
|
28
|
+
<h3 class="text-xs font-semibold text-gray-500 uppercase tracking-wider">Content</h3>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<% if current_user&.can_edit_blog? %>
|
|
32
|
+
<!-- Blog Posts -->
|
|
33
|
+
<%= link_to lean_cms_posts_path, class: "flex items-center px-3 py-2 text-sm font-medium rounded-lg mb-1 transition-colors #{controller_name == 'posts' ? 'text-white' : 'text-gray-700 hover:bg-gray-100'}", style: controller_name == 'posts' ? "background-color: var(--cms-primary);" : "" do %>
|
|
34
|
+
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
35
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"/>
|
|
36
|
+
</svg>
|
|
37
|
+
Blog & Portfolio
|
|
38
|
+
<% if (count = LeanCms::Post.draft.count) > 0 %>
|
|
39
|
+
<span class="ml-auto bg-yellow-100 text-yellow-800 text-xs font-medium px-2 py-0.5 rounded-full"><%= count %></span>
|
|
40
|
+
<% end %>
|
|
41
|
+
<% end %>
|
|
42
|
+
<% end %>
|
|
43
|
+
|
|
44
|
+
<% if current_user&.can_edit_pages? %>
|
|
45
|
+
<!-- Page Content -->
|
|
46
|
+
<%= link_to lean_cms_page_contents_path, class: "flex items-center px-3 py-2 text-sm font-medium rounded-lg mb-1 transition-colors #{controller_name == 'page_contents' ? 'text-white' : 'text-gray-700 hover:bg-gray-100'}", style: controller_name == 'page_contents' ? "background-color: var(--cms-primary);" : "" do %>
|
|
47
|
+
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
48
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
|
49
|
+
</svg>
|
|
50
|
+
Page Content
|
|
51
|
+
<% end %>
|
|
52
|
+
<% end %>
|
|
53
|
+
<% end %>
|
|
54
|
+
|
|
55
|
+
<!-- Form Submissions -->
|
|
56
|
+
<%= link_to lean_cms_form_submissions_path, class: "flex items-center px-3 py-2 text-sm font-medium rounded-lg mb-1 transition-colors #{controller_name == 'form_submissions' ? 'text-white' : 'text-gray-700 hover:bg-gray-100'}", style: controller_name == 'form_submissions' ? "background-color: var(--cms-primary);" : "" do %>
|
|
57
|
+
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
58
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
|
|
59
|
+
</svg>
|
|
60
|
+
Form Submissions
|
|
61
|
+
<% if (count = LeanCms::FormSubmission.unread.count) > 0 %>
|
|
62
|
+
<span class="ml-auto bg-red-100 text-red-800 text-xs font-medium px-2 py-0.5 rounded-full"><%= count %></span>
|
|
63
|
+
<% end %>
|
|
64
|
+
<% end %>
|
|
65
|
+
|
|
66
|
+
<% if current_user&.can_manage_users? %>
|
|
67
|
+
<!-- Administration Section -->
|
|
68
|
+
<div class="mt-6 mb-2 px-3">
|
|
69
|
+
<h3 class="text-xs font-semibold text-gray-500 uppercase tracking-wider">Administration</h3>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<!-- User Management -->
|
|
73
|
+
<%= link_to lean_cms_users_path, class: "flex items-center px-3 py-2 text-sm font-medium rounded-lg mb-1 transition-colors #{controller_name == 'users' ? 'text-white' : 'text-gray-700 hover:bg-gray-100'}", style: controller_name == 'users' ? "background-color: var(--cms-primary);" : "" do %>
|
|
74
|
+
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
75
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/>
|
|
76
|
+
</svg>
|
|
77
|
+
Users
|
|
78
|
+
<% end %>
|
|
79
|
+
|
|
80
|
+
<!-- Notification Settings -->
|
|
81
|
+
<% if current_user&.can_access_settings? %>
|
|
82
|
+
<%= link_to edit_lean_cms_notification_settings_path, class: "flex items-center px-3 py-2 text-sm font-medium rounded-lg mb-1 transition-colors #{controller_name == 'notification_settings' ? 'text-white' : 'text-gray-700 hover:bg-gray-100'}", style: controller_name == 'notification_settings' ? "background-color: var(--cms-primary);" : "" do %>
|
|
83
|
+
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
84
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/>
|
|
85
|
+
</svg>
|
|
86
|
+
Notification Settings
|
|
87
|
+
<% end %>
|
|
88
|
+
<% end %>
|
|
89
|
+
|
|
90
|
+
<!-- Activity Log (super admins only) -->
|
|
91
|
+
<% if current_user&.is_super_admin? %>
|
|
92
|
+
<%= link_to lean_cms_activity_path, class: "flex items-center px-3 py-2 text-sm font-medium rounded-lg mb-1 transition-colors #{controller_name == 'activity' ? 'text-white' : 'text-gray-700 hover:bg-gray-100'}", style: controller_name == 'activity' ? "background-color: var(--cms-primary);" : "" do %>
|
|
93
|
+
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
94
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"/>
|
|
95
|
+
</svg>
|
|
96
|
+
Activity Log
|
|
97
|
+
<% end %>
|
|
98
|
+
<% end %>
|
|
99
|
+
<% end %>
|
|
100
|
+
|
|
101
|
+
</nav>
|
|
102
|
+
</aside>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<%= form_with model: [:lean_cms, @user], class: "space-y-6" do |f| %>
|
|
2
|
+
<% if @user.errors.any? %>
|
|
3
|
+
<div class="bg-red-50 border border-red-200 text-red-800 px-4 py-3 rounded-lg">
|
|
4
|
+
<h3 class="font-medium">Please correct the following errors:</h3>
|
|
5
|
+
<ul class="mt-2 list-disc list-inside">
|
|
6
|
+
<% @user.errors.full_messages.each do |message| %>
|
|
7
|
+
<li><%= message %></li>
|
|
8
|
+
<% end %>
|
|
9
|
+
</ul>
|
|
10
|
+
</div>
|
|
11
|
+
<% end %>
|
|
12
|
+
|
|
13
|
+
<!-- Basic Information -->
|
|
14
|
+
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
15
|
+
<h2 class="text-xl font-semibold text-gray-900 mb-4">User Information</h2>
|
|
16
|
+
|
|
17
|
+
<div class="space-y-4">
|
|
18
|
+
<div>
|
|
19
|
+
<%= f.label :name, class: "block text-sm font-medium text-gray-700 mb-1" %>
|
|
20
|
+
<%= f.text_field :name, class: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#b82025]/20 focus:border-[#b82025] outline-none", placeholder: "Full name" %>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div>
|
|
24
|
+
<%= f.label :email_address, class: "block text-sm font-medium text-gray-700 mb-1" %>
|
|
25
|
+
<%= f.email_field :email_address, class: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#b82025]/20 focus:border-[#b82025] outline-none", placeholder: "email@example.com" %>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<!-- Permissions -->
|
|
31
|
+
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
32
|
+
<h2 class="text-xl font-semibold text-gray-900 mb-4">Permissions</h2>
|
|
33
|
+
<p class="text-sm text-gray-600 mb-4">Select what this user can do in the CMS.</p>
|
|
34
|
+
|
|
35
|
+
<div class="space-y-4">
|
|
36
|
+
<div class="flex items-start">
|
|
37
|
+
<div class="flex items-center h-5">
|
|
38
|
+
<%= f.check_box :can_edit_pages, class: "w-4 h-4 text-[#b82025] bg-gray-100 border-gray-300 rounded focus:ring-[#b82025] focus:ring-2" %>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="ml-3">
|
|
41
|
+
<%= f.label :can_edit_pages, class: "font-medium text-gray-900" do %>
|
|
42
|
+
Can Edit Pages
|
|
43
|
+
<% end %>
|
|
44
|
+
<p class="text-sm text-gray-600">Edit page content and sections</p>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div class="flex items-start">
|
|
49
|
+
<div class="flex items-center h-5">
|
|
50
|
+
<%= f.check_box :can_edit_blog, class: "w-4 h-4 text-[#b82025] bg-gray-100 border-gray-300 rounded focus:ring-[#b82025] focus:ring-2" %>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="ml-3">
|
|
53
|
+
<%= f.label :can_edit_blog, class: "font-medium text-gray-900" do %>
|
|
54
|
+
Can Edit Blog
|
|
55
|
+
<% end %>
|
|
56
|
+
<p class="text-sm text-gray-600">Create and edit blog posts and portfolio items</p>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div class="flex items-start">
|
|
61
|
+
<div class="flex items-center h-5">
|
|
62
|
+
<%= f.check_box :can_manage_users, class: "w-4 h-4 text-[#b82025] bg-gray-100 border-gray-300 rounded focus:ring-[#b82025] focus:ring-2" %>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="ml-3">
|
|
65
|
+
<%= f.label :can_manage_users, class: "font-medium text-gray-900" do %>
|
|
66
|
+
Can Manage Users
|
|
67
|
+
<% end %>
|
|
68
|
+
<p class="text-sm text-gray-600">Add, edit, and deactivate other users (except super admins)</p>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<% if current_user.is_super_admin? %>
|
|
73
|
+
<div class="flex items-start border-t border-gray-200 pt-4">
|
|
74
|
+
<div class="flex items-center h-5">
|
|
75
|
+
<%= f.check_box :can_access_settings, class: "w-4 h-4 text-[#b82025] bg-gray-100 border-gray-300 rounded focus:ring-[#b82025] focus:ring-2" %>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="ml-3">
|
|
78
|
+
<%= f.label :can_access_settings, class: "font-medium text-gray-900" do %>
|
|
79
|
+
Can Access Settings
|
|
80
|
+
<% end %>
|
|
81
|
+
<p class="text-sm text-gray-600">Access and modify system settings</p>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div class="flex items-start pt-2">
|
|
86
|
+
<div class="flex items-center h-5">
|
|
87
|
+
<%= f.check_box :is_super_admin, class: "w-4 h-4 text-purple-600 bg-gray-100 border-gray-300 rounded focus:ring-purple-500 focus:ring-2" %>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="ml-3">
|
|
90
|
+
<%= f.label :is_super_admin, class: "font-medium text-purple-900" do %>
|
|
91
|
+
Super Admin
|
|
92
|
+
<% end %>
|
|
93
|
+
<p class="text-sm text-purple-600">Has all permissions and cannot be modified by regular admins. Use sparingly.</p>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
<% end %>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<!-- Submit -->
|
|
101
|
+
<div class="flex items-center justify-between pt-4">
|
|
102
|
+
<%= link_to "Cancel", lean_cms_users_path, class: "px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-lg transition-colors" %>
|
|
103
|
+
<%= f.submit @user.new_record? ? "Send Invitation" : "Update User", class: "px-6 py-2 bg-[#b82025] hover:bg-[#a01c20] text-white font-semibold rounded-lg transition-colors cursor-pointer" %>
|
|
104
|
+
</div>
|
|
105
|
+
<% end %>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<div class="max-w-6xl">
|
|
2
|
+
<div class="flex justify-between items-center mb-8">
|
|
3
|
+
<div>
|
|
4
|
+
<h1 class="text-3xl font-bold text-gray-900">User Management</h1>
|
|
5
|
+
<p class="text-gray-600 mt-2">Manage CMS user accounts and permissions</p>
|
|
6
|
+
</div>
|
|
7
|
+
<%= link_to new_lean_cms_user_path, class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-lg text-white transition-colors hover:opacity-90", style: "background-color: var(--cms-primary, #b82025);" do %>
|
|
8
|
+
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
9
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
|
10
|
+
</svg>
|
|
11
|
+
Add User
|
|
12
|
+
<% end %>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
|
16
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
17
|
+
<thead class="bg-gray-50">
|
|
18
|
+
<tr>
|
|
19
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">User</th>
|
|
20
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Permissions</th>
|
|
21
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
|
22
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Last Login</th>
|
|
23
|
+
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
|
24
|
+
</tr>
|
|
25
|
+
</thead>
|
|
26
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
27
|
+
<% @users.each do |user| %>
|
|
28
|
+
<tr class="<%= user.active? ? '' : 'bg-gray-50 opacity-60' %>">
|
|
29
|
+
<td class="px-6 py-4 whitespace-nowrap">
|
|
30
|
+
<div class="flex items-center">
|
|
31
|
+
<div class="h-10 w-10 rounded-full flex items-center justify-center text-white text-sm font-medium" style="background-color: var(--cms-primary, #b82025);">
|
|
32
|
+
<%= user.display_name[0].upcase %>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="ml-4">
|
|
35
|
+
<div class="text-sm font-medium text-gray-900">
|
|
36
|
+
<%= user.display_name %>
|
|
37
|
+
<% if user.is_super_admin? %>
|
|
38
|
+
<span class="ml-2 px-2 py-0.5 text-xs bg-purple-100 text-purple-800 rounded-full">Super Admin</span>
|
|
39
|
+
<% end %>
|
|
40
|
+
<% if user == current_user %>
|
|
41
|
+
<span class="ml-1 px-2 py-0.5 text-xs bg-blue-100 text-blue-800 rounded-full">You</span>
|
|
42
|
+
<% end %>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="text-sm text-gray-500"><%= user.email_address %></div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</td>
|
|
48
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
49
|
+
<%= user.permissions_summary %>
|
|
50
|
+
</td>
|
|
51
|
+
<td class="px-6 py-4 whitespace-nowrap">
|
|
52
|
+
<% if user.active? %>
|
|
53
|
+
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">Active</span>
|
|
54
|
+
<% else %>
|
|
55
|
+
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">Inactive</span>
|
|
56
|
+
<% end %>
|
|
57
|
+
</td>
|
|
58
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
59
|
+
<%= user.last_login_at ? time_ago_in_words(user.last_login_at) + " ago" : "Never" %>
|
|
60
|
+
</td>
|
|
61
|
+
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
62
|
+
<% if policy(user).update? %>
|
|
63
|
+
<%= link_to "Edit", edit_lean_cms_user_path(user), class: "text-[#b82025] hover:text-[#a01c20] mr-3" %>
|
|
64
|
+
<% end %>
|
|
65
|
+
|
|
66
|
+
<% if user.active? && policy(user).deactivate? && user != current_user %>
|
|
67
|
+
<%= button_to "Deactivate", deactivate_lean_cms_user_path(user), method: :patch, class: "text-gray-600 hover:text-gray-900 mr-3", form: { data: { turbo_confirm: "Are you sure you want to deactivate this user? They will no longer be able to log in." } } %>
|
|
68
|
+
<% elsif !user.active? && policy(user).activate? %>
|
|
69
|
+
<%= button_to "Activate", activate_lean_cms_user_path(user), method: :patch, class: "text-green-600 hover:text-green-900 mr-3" %>
|
|
70
|
+
<% end %>
|
|
71
|
+
|
|
72
|
+
<% if policy(user).send_password_reset? && user.active? %>
|
|
73
|
+
<%= button_to "Reset Password", send_password_reset_lean_cms_user_path(user), method: :post, class: "text-blue-600 hover:text-blue-900", form: { data: { turbo_confirm: "Send password reset email to #{user.email_address}?" } } %>
|
|
74
|
+
<% end %>
|
|
75
|
+
</td>
|
|
76
|
+
</tr>
|
|
77
|
+
<% end %>
|
|
78
|
+
</tbody>
|
|
79
|
+
</table>
|
|
80
|
+
|
|
81
|
+
<% if @users.empty? %>
|
|
82
|
+
<div class="text-center py-12">
|
|
83
|
+
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
84
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/>
|
|
85
|
+
</svg>
|
|
86
|
+
<h3 class="mt-2 text-sm font-medium text-gray-900">No users</h3>
|
|
87
|
+
<p class="mt-1 text-sm text-gray-500">Get started by inviting a new user.</p>
|
|
88
|
+
<div class="mt-6">
|
|
89
|
+
<%= link_to new_lean_cms_user_path, class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-[#b82025] hover:bg-[#a01c20]" do %>
|
|
90
|
+
<svg class="-ml-1 mr-2 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
91
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
|
92
|
+
</svg>
|
|
93
|
+
Add User
|
|
94
|
+
<% end %>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
<% end %>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<h1>Password Reset Request</h1>
|
|
2
|
+
|
|
3
|
+
<p>Hello <%= @user.display_name %>,</p>
|
|
4
|
+
|
|
5
|
+
<p>An administrator has requested a password reset for your account on <%= @site_name %> CMS.</p>
|
|
6
|
+
|
|
7
|
+
<p>Click the link below to set a new password:</p>
|
|
8
|
+
|
|
9
|
+
<p><%= link_to "Reset Your Password", @setup_url %></p>
|
|
10
|
+
|
|
11
|
+
<p>This link will expire in 2 hours.</p>
|
|
12
|
+
|
|
13
|
+
<p>If you did not expect this email, please contact your administrator.</p>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Password Reset Request
|
|
2
|
+
|
|
3
|
+
Hello <%= @user.display_name %>,
|
|
4
|
+
|
|
5
|
+
An administrator has requested a password reset for your account on <%= @site_name %> CMS.
|
|
6
|
+
|
|
7
|
+
Reset your password by visiting: <%= @setup_url %>
|
|
8
|
+
|
|
9
|
+
This link will expire in 2 hours.
|
|
10
|
+
|
|
11
|
+
If you did not expect this email, please contact your administrator.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<h1>Welcome to <%= @site_name %> CMS</h1>
|
|
2
|
+
|
|
3
|
+
<p>Hello <%= @user.display_name %>,</p>
|
|
4
|
+
|
|
5
|
+
<p>You've been invited to manage content on <%= @site_name %>.</p>
|
|
6
|
+
|
|
7
|
+
<p>Click the link below to set your password and activate your account:</p>
|
|
8
|
+
|
|
9
|
+
<p><%= link_to "Set Your Password", @setup_url %></p>
|
|
10
|
+
|
|
11
|
+
<p>This link will expire in 24 hours.</p>
|
|
12
|
+
|
|
13
|
+
<p>If you did not expect this invitation, you can safely ignore this email.</p>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Welcome to <%= @site_name %> CMS
|
|
2
|
+
|
|
3
|
+
Hello <%= @user.display_name %>,
|
|
4
|
+
|
|
5
|
+
You've been invited to manage content on <%= @site_name %>.
|
|
6
|
+
|
|
7
|
+
Set your password by visiting: <%= @setup_url %>
|
|
8
|
+
|
|
9
|
+
This link will expire in 24 hours.
|
|
10
|
+
|
|
11
|
+
If you did not expect this invitation, you can safely ignore this email.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<h1>Your Account Has Been Reactivated</h1>
|
|
2
|
+
|
|
3
|
+
<p>Hello <%= @user.display_name %>,</p>
|
|
4
|
+
|
|
5
|
+
<p>Your account on <%= @site_name %> CMS has been reactivated.</p>
|
|
6
|
+
|
|
7
|
+
<p>For security, you'll need to set a new password. Click the link below:</p>
|
|
8
|
+
|
|
9
|
+
<p><%= link_to "Set Your Password", @setup_url %></p>
|
|
10
|
+
|
|
11
|
+
<p>This link will expire in 2 hours.</p>
|
|
12
|
+
|
|
13
|
+
<p>If you did not expect this email, please contact your administrator.</p>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Your Account Has Been Reactivated
|
|
2
|
+
|
|
3
|
+
Hello <%= @user.display_name %>,
|
|
4
|
+
|
|
5
|
+
Your account on <%= @site_name %> CMS has been reactivated.
|
|
6
|
+
|
|
7
|
+
For security, you'll need to set a new password. Visit: <%= @setup_url %>
|
|
8
|
+
|
|
9
|
+
This link will expire in 2 hours.
|
|
10
|
+
|
|
11
|
+
If you did not expect this email, please contact your administrator.
|
data/config/importmap.rb
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pin_all_from File.expand_path("../app/javascript/controllers", __dir__), under: "controllers"
|
|
2
|
+
|
|
3
|
+
# Action Text editor (Trix). The field-editor modal renders <trix-editor>
|
|
4
|
+
# for any rich_text field, which needs both `trix` and `@rails/actiontext`
|
|
5
|
+
# loaded. Pin them here so hosts get them automatically — host pins win
|
|
6
|
+
# on conflict if a host wants a different version.
|
|
7
|
+
pin "trix"
|
|
8
|
+
pin "@rails/actiontext", to: "actiontext.esm.js"
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Rails.application.routes.draw do
|
|
2
|
+
namespace :lean_cms, path: 'lean-cms' do
|
|
3
|
+
# Authentication
|
|
4
|
+
get 'login', to: 'sessions#new', as: :new_session
|
|
5
|
+
post 'login', to: 'sessions#create', as: :session
|
|
6
|
+
delete 'login', to: 'sessions#destroy'
|
|
7
|
+
|
|
8
|
+
# Password reset (email-driven, sets a signed token on the user)
|
|
9
|
+
get 'reset-password', to: 'passwords#new', as: :new_password
|
|
10
|
+
post 'reset-password', to: 'passwords#create', as: :passwords
|
|
11
|
+
get 'reset-password/:token/edit', to: 'passwords#edit', as: :edit_password
|
|
12
|
+
patch 'reset-password/:token', to: 'passwords#update', as: :password
|
|
13
|
+
put 'reset-password/:token', to: 'passwords#update'
|
|
14
|
+
|
|
15
|
+
# Magic-link password setup (new user invitations + admin-triggered resets)
|
|
16
|
+
get 'setup-password/:token', to: 'password_setup#show', as: :password_setup
|
|
17
|
+
patch 'setup-password/:token', to: 'password_setup#update'
|
|
18
|
+
|
|
19
|
+
root to: 'dashboard#index'
|
|
20
|
+
|
|
21
|
+
# User Management
|
|
22
|
+
resources :users, except: [:destroy] do
|
|
23
|
+
member do
|
|
24
|
+
patch :deactivate
|
|
25
|
+
patch :activate
|
|
26
|
+
post :send_password_reset
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
resources :posts
|
|
31
|
+
|
|
32
|
+
# Page Contents - section-based editing
|
|
33
|
+
get 'page-contents', to: 'page_contents#index', as: :page_contents
|
|
34
|
+
|
|
35
|
+
# Page Contents - inline field editing (MUST come before section routes)
|
|
36
|
+
get 'page-contents/field/:id/edit', to: 'page_contents#edit_field', as: :edit_page_content_field
|
|
37
|
+
patch 'page-contents/field/:id', to: 'page_contents#update_field', as: :update_page_content_field
|
|
38
|
+
get 'page-contents/field/:id/undo/preview', to: 'page_contents#preview_undo_field', as: :preview_undo_page_content_field
|
|
39
|
+
post 'page-contents/field/:id/undo', to: 'page_contents#undo_field', as: :undo_page_content_field
|
|
40
|
+
|
|
41
|
+
# Page Contents - section routes
|
|
42
|
+
get 'page-contents/:page/:section/edit', to: 'page_contents#edit', as: :edit_page_content
|
|
43
|
+
patch 'page-contents/:page/:section', to: 'page_contents#update', as: :page_content
|
|
44
|
+
|
|
45
|
+
resources :form_submissions, only: [:index, :show, :destroy] do
|
|
46
|
+
member do
|
|
47
|
+
patch :mark_as_read
|
|
48
|
+
patch :mark_as_replied
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Activity Log
|
|
53
|
+
get 'activity', to: 'activity#index', as: :activity
|
|
54
|
+
|
|
55
|
+
# Settings
|
|
56
|
+
get 'settings', to: 'settings#edit', as: :settings
|
|
57
|
+
patch 'settings', to: 'settings#update'
|
|
58
|
+
patch 'settings/update_override', to: 'settings#update_override'
|
|
59
|
+
post 'settings/lock', to: 'settings#lock', as: :lock_content
|
|
60
|
+
post 'settings/unlock', to: 'settings#unlock', as: :unlock_content
|
|
61
|
+
|
|
62
|
+
# Notification Settings
|
|
63
|
+
resource :notification_settings, only: [:edit, :update] do
|
|
64
|
+
post :test_email, on: :collection
|
|
65
|
+
post :test_sms, on: :collection
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# In-app Notifications
|
|
69
|
+
resources :notifications, only: [:index, :show] do
|
|
70
|
+
member do
|
|
71
|
+
patch :mark_as_read
|
|
72
|
+
end
|
|
73
|
+
collection do
|
|
74
|
+
patch :mark_all_as_read
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
class CreateLeanCmsTables < ActiveRecord::Migration[8.1]
|
|
2
|
+
def change
|
|
3
|
+
# Pages (must come before page_contents for FK)
|
|
4
|
+
create_table :lean_cms_pages, if_not_exists: true do |t|
|
|
5
|
+
t.string :slug, null: false
|
|
6
|
+
t.string :parent_slug
|
|
7
|
+
t.string :title, null: false
|
|
8
|
+
t.string :meta_title
|
|
9
|
+
t.text :meta_description
|
|
10
|
+
t.boolean :published, default: false, null: false
|
|
11
|
+
t.integer :position, default: 0
|
|
12
|
+
t.timestamps
|
|
13
|
+
|
|
14
|
+
t.index [:slug, :parent_slug], unique: true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Posts
|
|
18
|
+
create_table :lean_cms_posts, if_not_exists: true do |t|
|
|
19
|
+
# author / last_edited_by reference the host's user table. We intentionally
|
|
20
|
+
# don't add a database-level FK here — the host owns its user table
|
|
21
|
+
# (default `users`, but configurable via LeanCms.user_class) and SQLite's
|
|
22
|
+
# FK validation on later table rebuilds breaks if the host's users table
|
|
23
|
+
# name doesn't match. Belongs-to in the model gives us app-level integrity.
|
|
24
|
+
t.references :author, null: false
|
|
25
|
+
t.references :last_edited_by
|
|
26
|
+
t.string :title, null: false
|
|
27
|
+
t.string :slug, null: false
|
|
28
|
+
t.text :excerpt
|
|
29
|
+
t.integer :content_type, default: 0, null: false
|
|
30
|
+
t.integer :status, default: 0, null: false
|
|
31
|
+
t.datetime :published_at
|
|
32
|
+
t.timestamps
|
|
33
|
+
|
|
34
|
+
t.index :slug, unique: true
|
|
35
|
+
t.index [:status, :published_at]
|
|
36
|
+
t.index :content_type
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Page Contents
|
|
40
|
+
create_table :lean_cms_page_contents, if_not_exists: true do |t|
|
|
41
|
+
t.references :last_edited_by, null: false
|
|
42
|
+
t.integer :page_id
|
|
43
|
+
# FK constraint added at the bottom of this migration after both tables
|
|
44
|
+
# exist, so we can use add_foreign_key with an explicit column.
|
|
45
|
+
t.string :page, null: false
|
|
46
|
+
t.string :section, null: false
|
|
47
|
+
t.string :key, null: false
|
|
48
|
+
t.string :label
|
|
49
|
+
t.text :content
|
|
50
|
+
t.text :value
|
|
51
|
+
t.integer :content_type, default: 0
|
|
52
|
+
t.json :options
|
|
53
|
+
t.integer :position, default: 0
|
|
54
|
+
t.string :display_title
|
|
55
|
+
t.string :page_display_title
|
|
56
|
+
t.integer :page_order, default: 0
|
|
57
|
+
t.integer :section_order, default: 0
|
|
58
|
+
t.timestamps
|
|
59
|
+
|
|
60
|
+
t.index [:page, :section, :key], unique: true, name: "index_page_contents_on_page_section_key"
|
|
61
|
+
t.index [:page, :section], name: "index_page_contents_on_page_and_section"
|
|
62
|
+
t.index [:page_order, :section_order, :position]
|
|
63
|
+
t.index :page_id
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Settings (key-value store)
|
|
67
|
+
create_table :lean_cms_settings, if_not_exists: true do |t|
|
|
68
|
+
t.string :key, null: false
|
|
69
|
+
t.text :value
|
|
70
|
+
t.timestamps
|
|
71
|
+
|
|
72
|
+
t.index :key, unique: true
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Notification Settings
|
|
76
|
+
create_table :lean_cms_notification_settings, if_not_exists: true do |t|
|
|
77
|
+
t.string :email_provider
|
|
78
|
+
t.text :sendgrid_api_key
|
|
79
|
+
t.text :mailgun_api_key
|
|
80
|
+
t.string :mailgun_domain
|
|
81
|
+
t.text :twilio_account_sid
|
|
82
|
+
t.text :twilio_auth_token
|
|
83
|
+
t.string :twilio_from_number
|
|
84
|
+
t.text :notification_emails
|
|
85
|
+
t.text :notification_phones
|
|
86
|
+
t.boolean :email_enabled, default: false
|
|
87
|
+
t.boolean :sms_enabled, default: false
|
|
88
|
+
t.boolean :in_app_enabled, default: true
|
|
89
|
+
t.timestamps
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Meta Tags (polymorphic)
|
|
93
|
+
create_table :lean_cms_meta_tags, if_not_exists: true do |t|
|
|
94
|
+
t.references :taggable, polymorphic: true, null: false
|
|
95
|
+
t.string :title
|
|
96
|
+
t.text :description
|
|
97
|
+
t.string :og_image_url
|
|
98
|
+
t.string :canonical_url
|
|
99
|
+
t.json :structured_data
|
|
100
|
+
t.timestamps
|
|
101
|
+
|
|
102
|
+
t.index [:taggable_type, :taggable_id]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Form Submissions
|
|
106
|
+
create_table :lean_cms_form_submissions, if_not_exists: true do |t|
|
|
107
|
+
t.string :form_type, null: false
|
|
108
|
+
t.string :name
|
|
109
|
+
t.string :email
|
|
110
|
+
t.string :phone
|
|
111
|
+
t.string :company_name
|
|
112
|
+
t.string :city
|
|
113
|
+
t.string :state
|
|
114
|
+
t.string :zip
|
|
115
|
+
t.text :message
|
|
116
|
+
t.json :additional_data
|
|
117
|
+
t.string :ip_address
|
|
118
|
+
t.string :user_agent
|
|
119
|
+
t.integer :status, default: 0, null: false
|
|
120
|
+
t.timestamps
|
|
121
|
+
|
|
122
|
+
t.index :form_type
|
|
123
|
+
t.index :status
|
|
124
|
+
t.index :created_at
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
unless foreign_key_exists?(:lean_cms_page_contents, :lean_cms_pages, column: :page_id)
|
|
128
|
+
add_foreign_key :lean_cms_page_contents, :lean_cms_pages, column: :page_id
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|