ruby_cms 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.
- checksums.yaml +7 -0
- data/.cursor/dhh.mdc +698 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/README.md +235 -0
- data/Rakefile +30 -0
- data/app/components/ruby_cms/admin/admin_page/admin_table_content.rb +32 -0
- data/app/components/ruby_cms/admin/admin_page.rb +345 -0
- data/app/components/ruby_cms/admin/base_component.rb +78 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table.rb +149 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_actions.rb +127 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_body.rb +15 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_checkbox_cell.rb +41 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_checkbox_head.rb +33 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_delete_modal.rb +174 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_header.rb +59 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_header_bar.rb +159 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_pagination.rb +192 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_row.rb +97 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_actions.rb +137 -0
- data/app/controllers/concerns/ruby_cms/admin_pagination.rb +120 -0
- data/app/controllers/concerns/ruby_cms/admin_turbo_table.rb +68 -0
- data/app/controllers/concerns/ruby_cms/page_tracking.rb +52 -0
- data/app/controllers/concerns/ruby_cms/visitor_error_capture.rb +39 -0
- data/app/controllers/ruby_cms/admin/analytics_controller.rb +191 -0
- data/app/controllers/ruby_cms/admin/base_controller.rb +105 -0
- data/app/controllers/ruby_cms/admin/content_blocks_controller.rb +390 -0
- data/app/controllers/ruby_cms/admin/dashboard_controller.rb +50 -0
- data/app/controllers/ruby_cms/admin/locale_controller.rb +20 -0
- data/app/controllers/ruby_cms/admin/permissions_controller.rb +66 -0
- data/app/controllers/ruby_cms/admin/settings_controller.rb +223 -0
- data/app/controllers/ruby_cms/admin/user_permissions_controller.rb +59 -0
- data/app/controllers/ruby_cms/admin/users_controller.rb +107 -0
- data/app/controllers/ruby_cms/admin/visitor_errors_controller.rb +89 -0
- data/app/controllers/ruby_cms/admin/visual_editor_controller.rb +322 -0
- data/app/controllers/ruby_cms/errors_controller.rb +35 -0
- data/app/helpers/ruby_cms/admin/admin_page_helper.rb +21 -0
- data/app/helpers/ruby_cms/admin/bulk_action_table_helper.rb +159 -0
- data/app/helpers/ruby_cms/application_helper.rb +41 -0
- data/app/helpers/ruby_cms/bulk_action_table_helper.rb +151 -0
- data/app/helpers/ruby_cms/content_blocks_helper.rb +375 -0
- data/app/helpers/ruby_cms/settings_helper.rb +160 -0
- data/app/javascript/controllers/ruby_cms/auto_save_preference_controller.js +73 -0
- data/app/javascript/controllers/ruby_cms/bulk_action_table_controller.js +553 -0
- data/app/javascript/controllers/ruby_cms/clickable_row_controller.js +28 -0
- data/app/javascript/controllers/ruby_cms/flash_messages_controller.js +29 -0
- data/app/javascript/controllers/ruby_cms/index.js +104 -0
- data/app/javascript/controllers/ruby_cms/locale_tabs_controller.js +34 -0
- data/app/javascript/controllers/ruby_cms/mobile_menu_controller.js +55 -0
- data/app/javascript/controllers/ruby_cms/nav_order_sortable_controller.js +192 -0
- data/app/javascript/controllers/ruby_cms/page_preview_controller.js +135 -0
- data/app/javascript/controllers/ruby_cms/toggle_controller.js +39 -0
- data/app/javascript/controllers/ruby_cms/visual_editor_controller.js +321 -0
- data/app/models/concerns/content_block/publishable.rb +54 -0
- data/app/models/concerns/content_block/searchable.rb +22 -0
- data/app/models/content_block.rb +155 -0
- data/app/models/ruby_cms/content_block.rb +8 -0
- data/app/models/ruby_cms/permission.rb +28 -0
- data/app/models/ruby_cms/permittable.rb +39 -0
- data/app/models/ruby_cms/preference.rb +111 -0
- data/app/models/ruby_cms/user_permission.rb +12 -0
- data/app/models/ruby_cms/visitor_error.rb +109 -0
- data/app/services/ruby_cms/analytics/report.rb +362 -0
- data/app/services/ruby_cms/security_tracker.rb +92 -0
- data/app/views/layouts/ruby_cms/_admin_flash_messages.html.erb +37 -0
- data/app/views/layouts/ruby_cms/_admin_sidebar.html.erb +121 -0
- data/app/views/layouts/ruby_cms/admin.html.erb +81 -0
- data/app/views/layouts/ruby_cms/minimal.html.erb +181 -0
- data/app/views/ruby_cms/admin/analytics/index.html.erb +160 -0
- data/app/views/ruby_cms/admin/analytics/page_details.html.erb +84 -0
- data/app/views/ruby_cms/admin/analytics/partials/_back_button.html.erb +3 -0
- data/app/views/ruby_cms/admin/analytics/partials/_browser_device.html.erb +40 -0
- data/app/views/ruby_cms/admin/analytics/partials/_daily_activity_chart.html.erb +58 -0
- data/app/views/ruby_cms/admin/analytics/partials/_hourly_activity_chart.html.erb +51 -0
- data/app/views/ruby_cms/admin/analytics/partials/_recent_activity.html.erb +31 -0
- data/app/views/ruby_cms/admin/analytics/partials/_security_alert.html.erb +4 -0
- data/app/views/ruby_cms/admin/analytics/partials/_top_referrers.html.erb +21 -0
- data/app/views/ruby_cms/admin/analytics/visitor_details.html.erb +125 -0
- data/app/views/ruby_cms/admin/content_blocks/_form.html.erb +161 -0
- data/app/views/ruby_cms/admin/content_blocks/_row.html.erb +25 -0
- data/app/views/ruby_cms/admin/content_blocks/edit.html.erb +17 -0
- data/app/views/ruby_cms/admin/content_blocks/index.html.erb +66 -0
- data/app/views/ruby_cms/admin/content_blocks/new.html.erb +5 -0
- data/app/views/ruby_cms/admin/content_blocks/show.html.erb +110 -0
- data/app/views/ruby_cms/admin/dashboard/index.html.erb +198 -0
- data/app/views/ruby_cms/admin/permissions/_row.html.erb +11 -0
- data/app/views/ruby_cms/admin/permissions/index.html.erb +62 -0
- data/app/views/ruby_cms/admin/settings/index.html.erb +220 -0
- data/app/views/ruby_cms/admin/shared/_bulk_action_table_index.html.erb +56 -0
- data/app/views/ruby_cms/admin/user_permissions/index.html.erb +55 -0
- data/app/views/ruby_cms/admin/users/_row.html.erb +14 -0
- data/app/views/ruby_cms/admin/users/index.html.erb +70 -0
- data/app/views/ruby_cms/admin/visitor_errors/_row.html.erb +35 -0
- data/app/views/ruby_cms/admin/visitor_errors/index.html.erb +57 -0
- data/app/views/ruby_cms/admin/visitor_errors/show.html.erb +147 -0
- data/app/views/ruby_cms/admin/visual_editor/index.html.erb +144 -0
- data/app/views/ruby_cms/errors/not_found.html.erb +92 -0
- data/config/database.yml +6 -0
- data/config/importmap.rb +36 -0
- data/config/locales/en.yml +101 -0
- data/config/routes.rb +65 -0
- data/db/migrate/20260125000001_create_ruby_cms_permissions.rb +14 -0
- data/db/migrate/20260125000002_create_ruby_cms_user_permissions.rb +14 -0
- data/db/migrate/20260125000003_create_ruby_cms_content_blocks.rb +19 -0
- data/db/migrate/20260125000010_add_indexes_to_ruby_cms_tables.rb +9 -0
- data/db/migrate/20260127000001_add_locale_to_ruby_cms_content_blocks.rb +34 -0
- data/db/migrate/20260129000001_create_ruby_cms_visitor_errors.rb +24 -0
- data/db/migrate/20260130000001_add_referer_and_query_to_ruby_cms_visitor_errors.rb +8 -0
- data/db/migrate/20260130000002_create_ruby_cms_preferences.rb +16 -0
- data/db/migrate/20260130000003_add_category_to_ruby_cms_preferences.rb +8 -0
- data/db/migrate/20260211000001_add_ruby_cms_analytics_fields_to_ahoy_events.rb +19 -0
- data/db/migrate/20260212000001_use_unprefixed_cms_tables.rb +146 -0
- data/exe/ruby_cms +25 -0
- data/lib/generators/ruby_cms/install_generator.rb +1062 -0
- data/lib/generators/ruby_cms/templates/admin.html.erb +82 -0
- data/lib/generators/ruby_cms/templates/ruby_cms.rb +86 -0
- data/lib/ruby_cms/app_integration.rb +82 -0
- data/lib/ruby_cms/cli.rb +169 -0
- data/lib/ruby_cms/content_blocks_grouping.rb +41 -0
- data/lib/ruby_cms/content_blocks_sync.rb +329 -0
- data/lib/ruby_cms/css_compiler.rb +35 -0
- data/lib/ruby_cms/engine.rb +498 -0
- data/lib/ruby_cms/settings.rb +145 -0
- data/lib/ruby_cms/settings_registry.rb +289 -0
- data/lib/ruby_cms/version.rb +5 -0
- data/lib/ruby_cms.rb +195 -0
- data/lib/tasks/ruby_cms.rake +27 -0
- data/log/test.log +17875 -0
- data/sig/ruby_cms.rbs +4 -0
- metadata +223 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddLocaleToRubyCmsContentBlocks < ActiveRecord::Migration[7.1]
|
|
4
|
+
def change
|
|
5
|
+
add_column :ruby_cms_content_blocks, :locale, :string, default: "en", null: false
|
|
6
|
+
|
|
7
|
+
# Remove old unique index on key
|
|
8
|
+
remove_index :ruby_cms_content_blocks, :key if index_exists?(:ruby_cms_content_blocks, :key)
|
|
9
|
+
|
|
10
|
+
# Add new composite unique index on key + locale
|
|
11
|
+
add_index :ruby_cms_content_blocks, %i[key locale], unique: true
|
|
12
|
+
|
|
13
|
+
# Add index for locale queries
|
|
14
|
+
add_index :ruby_cms_content_blocks, :locale
|
|
15
|
+
|
|
16
|
+
# Migrate existing records to default locale
|
|
17
|
+
# This ensures existing content blocks get the default locale
|
|
18
|
+
# Use execute to avoid model loading issues during migration
|
|
19
|
+
reversible do |dir|
|
|
20
|
+
dir.up do
|
|
21
|
+
default_locale = begin
|
|
22
|
+
I18n.default_locale.to_s
|
|
23
|
+
rescue StandardError
|
|
24
|
+
"en"
|
|
25
|
+
end
|
|
26
|
+
execute <<~SQL.squish
|
|
27
|
+
UPDATE ruby_cms_content_blocks
|
|
28
|
+
SET locale = '#{default_locale}'
|
|
29
|
+
WHERE locale IS NULL OR locale = ''
|
|
30
|
+
SQL
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateRubyCmsVisitorErrors < ActiveRecord::Migration[7.1]
|
|
4
|
+
def change
|
|
5
|
+
create_table :ruby_cms_visitor_errors do |t|
|
|
6
|
+
t.string :error_class, null: false
|
|
7
|
+
t.text :error_message, null: false
|
|
8
|
+
t.string :request_path, null: false
|
|
9
|
+
t.string :request_method
|
|
10
|
+
t.string :ip_address
|
|
11
|
+
t.text :user_agent
|
|
12
|
+
t.text :backtrace
|
|
13
|
+
t.text :request_params
|
|
14
|
+
t.string :session_id
|
|
15
|
+
t.boolean :resolved, default: false, null: false
|
|
16
|
+
|
|
17
|
+
t.timestamps
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
add_index :ruby_cms_visitor_errors, :created_at
|
|
21
|
+
add_index :ruby_cms_visitor_errors, :request_path
|
|
22
|
+
add_index :ruby_cms_visitor_errors, :resolved
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateRubyCmsPreferences < ActiveRecord::Migration[7.1]
|
|
4
|
+
def change
|
|
5
|
+
create_table :ruby_cms_preferences do |t|
|
|
6
|
+
t.string :key, null: false
|
|
7
|
+
t.text :value
|
|
8
|
+
t.string :value_type, default: "string", null: false
|
|
9
|
+
t.text :description
|
|
10
|
+
|
|
11
|
+
t.timestamps
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
add_index :ruby_cms_preferences, :key, unique: true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddRubyCmsAnalyticsFieldsToAhoyEvents < ActiveRecord::Migration[7.1]
|
|
4
|
+
def change
|
|
5
|
+
return unless table_exists?(:ahoy_events)
|
|
6
|
+
|
|
7
|
+
add_column :ahoy_events, :page_name, :string unless column_exists?(:ahoy_events, :page_name)
|
|
8
|
+
add_column :ahoy_events, :request_path, :string unless column_exists?(:ahoy_events, :request_path)
|
|
9
|
+
add_column :ahoy_events, :ip_address, :string unless column_exists?(:ahoy_events, :ip_address)
|
|
10
|
+
add_column :ahoy_events, :user_agent, :text unless column_exists?(:ahoy_events, :user_agent)
|
|
11
|
+
add_column :ahoy_events, :description, :text unless column_exists?(:ahoy_events, :description)
|
|
12
|
+
|
|
13
|
+
add_index :ahoy_events, :page_name, if_not_exists: true
|
|
14
|
+
add_index :ahoy_events, :request_path, if_not_exists: true
|
|
15
|
+
add_index :ahoy_events, :ip_address, if_not_exists: true
|
|
16
|
+
add_index :ahoy_events, %i[name page_name], if_not_exists: true
|
|
17
|
+
add_index :ahoy_events, %i[name request_path], if_not_exists: true
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Ensures CMS uses unprefixed table names (preferences, content_blocks, permissions,
|
|
4
|
+
# user_permissions, visitor_errors). Creates tables only if they don't exist so host
|
|
5
|
+
# apps with existing tables are not overwritten. Adds missing columns when tables exist.
|
|
6
|
+
class UseUnprefixedCmsTables < ActiveRecord::Migration[7.1]
|
|
7
|
+
def up
|
|
8
|
+
create_preferences_if_missing
|
|
9
|
+
ensure_content_blocks_table
|
|
10
|
+
ensure_content_blocks_columns
|
|
11
|
+
create_permissions_if_missing
|
|
12
|
+
create_user_permissions_if_missing
|
|
13
|
+
ensure_visitor_errors_table
|
|
14
|
+
ensure_visitor_errors_columns
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def down
|
|
18
|
+
# No-op: leave unprefixed tables in place; host app may rely on them.
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def create_preferences_if_missing
|
|
24
|
+
return if table_exists?(:preferences)
|
|
25
|
+
|
|
26
|
+
create_table :preferences do |t|
|
|
27
|
+
t.string :key, null: false
|
|
28
|
+
t.text :value
|
|
29
|
+
t.string :value_type, default: "string", null: false
|
|
30
|
+
t.text :description
|
|
31
|
+
t.string :category, default: "general"
|
|
32
|
+
|
|
33
|
+
t.timestamps
|
|
34
|
+
end
|
|
35
|
+
add_index :preferences, :key, unique: true
|
|
36
|
+
add_index :preferences, :category
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def ensure_content_blocks_table
|
|
40
|
+
return if table_exists?(:content_blocks)
|
|
41
|
+
|
|
42
|
+
create_table :content_blocks do |t|
|
|
43
|
+
t.string :key, null: false
|
|
44
|
+
t.string :title
|
|
45
|
+
t.text :content
|
|
46
|
+
t.string :content_type, default: "text", null: false
|
|
47
|
+
t.boolean :published, default: false, null: false
|
|
48
|
+
t.string :locale, default: "en", null: false
|
|
49
|
+
t.references :updated_by, null: true, foreign_key: false
|
|
50
|
+
|
|
51
|
+
t.timestamps
|
|
52
|
+
end
|
|
53
|
+
add_index :content_blocks, %i[key locale], unique: true
|
|
54
|
+
add_index :content_blocks, :locale
|
|
55
|
+
add_index :content_blocks, %i[published content_type]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def ensure_content_blocks_columns
|
|
59
|
+
return unless table_exists?(:content_blocks)
|
|
60
|
+
|
|
61
|
+
unless column_exists?(:content_blocks, :locale)
|
|
62
|
+
add_column :content_blocks, :locale, :string, default: "en", null: false
|
|
63
|
+
add_index :content_blocks, :locale
|
|
64
|
+
default_locale = begin
|
|
65
|
+
I18n.default_locale
|
|
66
|
+
rescue StandardError
|
|
67
|
+
"en"
|
|
68
|
+
end.to_s
|
|
69
|
+
execute <<~SQL.squish
|
|
70
|
+
UPDATE content_blocks SET locale = '#{default_locale}' WHERE locale IS NULL OR locale = ''
|
|
71
|
+
SQL
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
unless column_exists?(:content_blocks, :updated_by_id)
|
|
75
|
+
add_reference :content_blocks, :updated_by, null: true, foreign_key: false
|
|
76
|
+
add_index :content_blocks, :updated_by_id unless index_exists?(:content_blocks, :updated_by_id)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Replace unique index on key with key+locale if we have old index
|
|
80
|
+
if index_exists?(:content_blocks, :key, unique: true) && !index_exists?(:content_blocks, %i[key locale], unique: true)
|
|
81
|
+
remove_index :content_blocks, :key
|
|
82
|
+
add_index :content_blocks, %i[key locale], unique: true
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
return if index_exists?(:content_blocks, %i[published content_type])
|
|
86
|
+
|
|
87
|
+
add_index :content_blocks, %i[published content_type]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def create_permissions_if_missing
|
|
91
|
+
return if table_exists?(:permissions)
|
|
92
|
+
|
|
93
|
+
create_table :permissions do |t|
|
|
94
|
+
t.string :key, null: false
|
|
95
|
+
t.string :name
|
|
96
|
+
|
|
97
|
+
t.timestamps
|
|
98
|
+
end
|
|
99
|
+
add_index :permissions, :key, unique: true
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def create_user_permissions_if_missing
|
|
103
|
+
return if table_exists?(:user_permissions)
|
|
104
|
+
|
|
105
|
+
create_table :user_permissions do |t|
|
|
106
|
+
t.references :user, null: false, foreign_key: false
|
|
107
|
+
t.references :permission, null: false, foreign_key: false
|
|
108
|
+
|
|
109
|
+
t.timestamps
|
|
110
|
+
end
|
|
111
|
+
add_index :user_permissions, %i[user_id permission_id], unique: true
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def ensure_visitor_errors_table
|
|
115
|
+
return if table_exists?(:visitor_errors)
|
|
116
|
+
|
|
117
|
+
create_table :visitor_errors do |t|
|
|
118
|
+
t.string :error_class, null: false
|
|
119
|
+
t.text :error_message, null: false
|
|
120
|
+
t.string :request_path, null: false
|
|
121
|
+
t.string :request_method
|
|
122
|
+
t.string :ip_address
|
|
123
|
+
t.text :user_agent
|
|
124
|
+
t.text :backtrace
|
|
125
|
+
t.text :request_params
|
|
126
|
+
t.string :session_id
|
|
127
|
+
t.string :referer
|
|
128
|
+
t.string :query_string
|
|
129
|
+
t.boolean :resolved, default: false, null: false
|
|
130
|
+
|
|
131
|
+
t.timestamps
|
|
132
|
+
end
|
|
133
|
+
add_index :visitor_errors, :created_at
|
|
134
|
+
add_index :visitor_errors, :request_path
|
|
135
|
+
add_index :visitor_errors, :resolved
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def ensure_visitor_errors_columns
|
|
139
|
+
return unless table_exists?(:visitor_errors)
|
|
140
|
+
|
|
141
|
+
add_column :visitor_errors, :referer, :string unless column_exists?(:visitor_errors, :referer)
|
|
142
|
+
return if column_exists?(:visitor_errors, :query_string)
|
|
143
|
+
|
|
144
|
+
add_column :visitor_errors, :query_string, :string
|
|
145
|
+
end
|
|
146
|
+
end
|
data/exe/ruby_cms
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Find Rails app root (current dir or nearest parent with config/application.rb)
|
|
5
|
+
app_root = File.expand_path(Dir.pwd)
|
|
6
|
+
loop do
|
|
7
|
+
break if File.file?(File.join(app_root, "config/application.rb"))
|
|
8
|
+
|
|
9
|
+
parent = File.dirname(app_root)
|
|
10
|
+
break if parent == app_root
|
|
11
|
+
|
|
12
|
+
app_root = parent
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
unless File.file?(File.join(app_root, "config/application.rb"))
|
|
16
|
+
warn "Not in a Rails application (config/application.rb not found)."
|
|
17
|
+
warn "Run from your app root, or use: rails ruby_cms:setup_admin"
|
|
18
|
+
exit 1
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Dir.chdir(app_root)
|
|
22
|
+
require File.join(app_root, "config/environment")
|
|
23
|
+
|
|
24
|
+
require "ruby_cms/cli"
|
|
25
|
+
RubyCms::CLI.start(ARGV)
|