pg_sql_triggers 1.0.0 → 1.0.1

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.erb_lint.yml +47 -0
  3. data/.rubocop.yml +4 -1
  4. data/CHANGELOG.md +29 -1
  5. data/Goal.md +408 -123
  6. data/README.md +47 -215
  7. data/app/controllers/pg_sql_triggers/application_controller.rb +46 -0
  8. data/app/controllers/pg_sql_triggers/generator_controller.rb +10 -4
  9. data/app/controllers/pg_sql_triggers/migrations_controller.rb +18 -0
  10. data/app/models/pg_sql_triggers/trigger_registry.rb +20 -2
  11. data/app/views/layouts/pg_sql_triggers/application.html.erb +34 -1
  12. data/app/views/pg_sql_triggers/dashboard/index.html.erb +70 -30
  13. data/app/views/pg_sql_triggers/generator/new.html.erb +4 -4
  14. data/app/views/pg_sql_triggers/generator/preview.html.erb +14 -6
  15. data/app/views/pg_sql_triggers/shared/_confirmation_modal.html.erb +189 -0
  16. data/app/views/pg_sql_triggers/shared/_kill_switch_status.html.erb +40 -0
  17. data/app/views/pg_sql_triggers/tables/index.html.erb +0 -2
  18. data/app/views/pg_sql_triggers/tables/show.html.erb +3 -4
  19. data/db/migrate/20251222000001_create_pg_sql_triggers_tables.rb +1 -1
  20. data/docs/README.md +66 -0
  21. data/docs/api-reference.md +663 -0
  22. data/docs/configuration.md +541 -0
  23. data/docs/getting-started.md +135 -0
  24. data/docs/kill-switch.md +586 -0
  25. data/docs/screenshots/.gitkeep +1 -0
  26. data/docs/screenshots/Generate Trigger.png +0 -0
  27. data/docs/screenshots/Triggers Page.png +0 -0
  28. data/docs/screenshots/kill error.png +0 -0
  29. data/docs/screenshots/kill modal for migration down.png +0 -0
  30. data/docs/usage-guide.md +420 -0
  31. data/docs/web-ui.md +339 -0
  32. data/lib/generators/pg_sql_triggers/templates/create_pg_sql_triggers_tables.rb +1 -1
  33. data/lib/generators/pg_sql_triggers/templates/initializer.rb +36 -2
  34. data/lib/pg_sql_triggers/generator/service.rb +1 -1
  35. data/lib/pg_sql_triggers/migration.rb +1 -1
  36. data/lib/pg_sql_triggers/migrator.rb +27 -3
  37. data/lib/pg_sql_triggers/registry/manager.rb +6 -6
  38. data/lib/pg_sql_triggers/sql/kill_switch.rb +300 -0
  39. data/lib/pg_sql_triggers/testing/dry_run.rb +5 -7
  40. data/lib/pg_sql_triggers/testing/safe_executor.rb +23 -11
  41. data/lib/pg_sql_triggers/version.rb +1 -1
  42. data/lib/pg_sql_triggers.rb +12 -0
  43. data/lib/tasks/trigger_migrations.rake +33 -0
  44. metadata +35 -5
data/docs/web-ui.md ADDED
@@ -0,0 +1,339 @@
1
+ # Web UI Documentation
2
+
3
+ The PgSqlTriggers web interface provides a visual dashboard for managing triggers, migrations, and monitoring drift status.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Accessing the Web UI](#accessing-the-web-ui)
8
+ - [Dashboard Overview](#dashboard-overview)
9
+ - [Managing Triggers](#managing-triggers)
10
+ - [Migration Management](#migration-management)
11
+ - [SQL Capsules](#sql-capsules)
12
+ - [Permissions and Safety](#permissions-and-safety)
13
+
14
+ ## Accessing the Web UI
15
+
16
+ By default, the web UI is mounted at:
17
+
18
+ ```
19
+ http://localhost:3000/pg_sql_triggers
20
+ ```
21
+
22
+ You can customize the mount path in your routes:
23
+
24
+ ```ruby
25
+ # config/routes.rb
26
+ Rails.application.routes.draw do
27
+ mount PgSqlTriggers::Engine, at: "/admin/triggers" # Custom path
28
+ end
29
+ ```
30
+
31
+ ## Dashboard Overview
32
+
33
+ The dashboard provides a comprehensive view of your trigger ecosystem.
34
+
35
+ ### Main Features
36
+
37
+ 1. **Trigger List**: View all triggers with their current status
38
+ 2. **Drift Detection**: Visual indicators for drift states
39
+ 3. **Migration Status**: See pending and applied migrations
40
+ 4. **Quick Actions**: Enable/disable triggers, run migrations
41
+ 5. **Kill Switch Status**: Production environment indicator
42
+
43
+ ![Dashboard Screenshot](screenshots/dashboard.png)
44
+
45
+ ### Status Indicators
46
+
47
+ - **✓ Green**: Managed & In Sync
48
+ - **⚠ Yellow**: Drifted or Manual Override
49
+ - **✗ Red**: Dropped or Error
50
+ - **○ Gray**: Disabled
51
+ - **? Purple**: Unknown
52
+
53
+ ## Managing Triggers
54
+
55
+ ### Viewing Trigger Details
56
+
57
+ Click on any trigger to view:
58
+ - Current status and drift state
59
+ - Table and function information
60
+ - Version history
61
+ - Enabled/disabled state
62
+ - Environment configuration
63
+
64
+ ### Enabling/Disabling Triggers
65
+
66
+ #### Enable a Trigger
67
+
68
+ 1. Navigate to the trigger in the dashboard
69
+ 2. Click the "Enable" button
70
+ 3. In production environments, enter the confirmation text when prompted
71
+ 4. Confirm the action
72
+
73
+ #### Disable a Trigger
74
+
75
+ 1. Navigate to the trigger in the dashboard
76
+ 2. Click the "Disable" button
77
+ 3. In production environments, enter the confirmation text when prompted
78
+ 4. Confirm the action
79
+
80
+ ### Viewing Drift Status
81
+
82
+ The dashboard automatically shows drift status for each trigger:
83
+
84
+ - **In Sync**: Green checkmark, no action needed
85
+ - **Drifted**: Yellow warning, shows differences between DSL and database
86
+ - **Manual Override**: Yellow warning, indicates changes made outside PgSqlTriggers
87
+ - **Dropped**: Red X, trigger removed from database but still in registry
88
+
89
+ ### Trigger Actions
90
+
91
+ Available actions depend on trigger state and your permissions:
92
+
93
+ - **Enable/Disable**: Toggle trigger activation
94
+ - **Apply**: Apply generated trigger definition
95
+ - **Drop**: Remove trigger from database (Admin only)
96
+ - **View SQL**: See the trigger's SQL definition
97
+ - **View Diff**: Compare DSL vs database state
98
+
99
+ ## Migration Management
100
+
101
+ The Web UI provides full migration management capabilities.
102
+
103
+ ### Migration Status View
104
+
105
+ Navigate to the "Migrations" tab to see:
106
+
107
+ - **Pending Migrations**: Not yet applied (down state)
108
+ - **Applied Migrations**: Successfully run (up state)
109
+ - **Migration Details**: Timestamp, name, and status
110
+
111
+ ### Applying Migrations
112
+
113
+ #### Apply All Pending Migrations
114
+
115
+ 1. Click "Apply All Pending Migrations" button
116
+ 2. Review the list of migrations to be applied
117
+ 3. In production, enter confirmation text: `EXECUTE UI_MIGRATION_UP`
118
+ 4. Confirm the action
119
+ 5. Wait for completion and review results
120
+
121
+ #### Apply Individual Migration
122
+
123
+ 1. Find the migration in the status table
124
+ 2. Click the "Up" button next to the migration
125
+ 3. In production, enter confirmation text
126
+ 4. Confirm the action
127
+
128
+ ### Rolling Back Migrations
129
+
130
+ #### Rollback Last Migration
131
+
132
+ 1. Click "Rollback Last Migration" button
133
+ 2. Review which migration will be rolled back
134
+ 3. In production, enter confirmation text: `EXECUTE UI_MIGRATION_DOWN`
135
+ 4. Confirm the action
136
+
137
+ #### Rollback Individual Migration
138
+
139
+ 1. Find the migration in the status table
140
+ 2. Click the "Down" button next to the migration
141
+ 3. In production, enter confirmation text
142
+ 4. Confirm the action
143
+
144
+ ### Redo Migrations
145
+
146
+ Redo (rollback and re-apply) a migration:
147
+
148
+ 1. Click "Redo Last Migration" for the most recent migration
149
+ 2. Or click "Redo" button next to a specific migration
150
+ 3. In production, enter confirmation text: `EXECUTE UI_MIGRATION_REDO`
151
+ 4. Confirm the action
152
+
153
+ ### Migration Feedback
154
+
155
+ After each migration action:
156
+ - **Success**: Green flash message with details
157
+ - **Error**: Red flash message with error details
158
+ - **Warnings**: Yellow flash message if issues occurred
159
+
160
+ ## SQL Capsules
161
+
162
+ SQL Capsules provide emergency escape hatches for executing SQL directly.
163
+
164
+ ### When to Use SQL Capsules
165
+
166
+ Use SQL Capsules for:
167
+ - Emergency fixes in production
168
+ - Quick database queries
169
+ - Testing SQL functions
170
+ - Debugging trigger behavior
171
+
172
+ ### Executing SQL
173
+
174
+ 1. Navigate to "SQL Capsules" tab
175
+ 2. Enter your SQL query:
176
+ ```sql
177
+ SELECT * FROM pg_sql_triggers_registry;
178
+ ```
179
+ 3. Click "Execute"
180
+ 4. In production, enter confirmation text: `EXECUTE SQL`
181
+ 5. Review results in the output panel
182
+
183
+ ### Safety Features
184
+
185
+ - **Production Protection**: Requires confirmation in protected environments
186
+ - **Read-Only Mode**: Optional configuration for limiting to SELECT queries
187
+ - **Query Logging**: All SQL execution is logged
188
+ - **Permission Checks**: Requires Admin permission level
189
+
190
+ ### Example SQL Capsules
191
+
192
+ #### View All Triggers
193
+ ```sql
194
+ SELECT
195
+ trigger_name,
196
+ event_object_table,
197
+ action_timing,
198
+ event_manipulation
199
+ FROM information_schema.triggers
200
+ WHERE trigger_schema = 'public';
201
+ ```
202
+
203
+ #### Check Function Definitions
204
+ ```sql
205
+ SELECT
206
+ routine_name,
207
+ routine_type
208
+ FROM information_schema.routines
209
+ WHERE routine_schema = 'public'
210
+ AND routine_name LIKE '%trigger%';
211
+ ```
212
+
213
+ #### Verify Trigger State
214
+ ```sql
215
+ SELECT * FROM pg_sql_triggers_registry
216
+ WHERE trigger_name = 'users_email_validation';
217
+ ```
218
+
219
+ ## Permissions and Safety
220
+
221
+ ### Permission Levels
222
+
223
+ The Web UI enforces three permission levels:
224
+
225
+ #### Viewer (Read-Only)
226
+ - View triggers and their status
227
+ - View drift information
228
+ - Check migration status
229
+ - View SQL definitions
230
+
231
+ Cannot:
232
+ - Enable/disable triggers
233
+ - Run migrations
234
+ - Execute SQL
235
+ - Drop triggers
236
+
237
+ #### Operator
238
+ - All Viewer permissions
239
+ - Enable/disable triggers
240
+ - Apply generated triggers
241
+ - Run migrations (up/down/redo)
242
+
243
+ Cannot:
244
+ - Drop triggers
245
+ - Execute arbitrary SQL
246
+
247
+ #### Admin (Full Access)
248
+ - All Operator permissions
249
+ - Drop triggers
250
+ - Execute SQL via capsules
251
+ - Modify registry directly
252
+
253
+ ### Kill Switch Protection
254
+
255
+ In protected environments (production, staging), the Web UI enforces additional safety:
256
+
257
+ 1. **Status Indicator**: Kill switch badge shows protection status
258
+ 2. **Confirmation Required**: Dangerous operations require typed confirmation
259
+ 3. **Warning Banners**: Visual alerts for production environment
260
+ 4. **Audit Logging**: All protected operations are logged
261
+
262
+ ### Configuring Permissions
263
+
264
+ Set up custom permission checking in the initializer:
265
+
266
+ ```ruby
267
+ # config/initializers/pg_sql_triggers.rb
268
+ PgSqlTriggers.configure do |config|
269
+ config.permission_checker = ->(actor, action, environment) {
270
+ user = User.find(actor[:id])
271
+
272
+ case action
273
+ when :view
274
+ user.present?
275
+ when :operate
276
+ user.admin? || user.operator?
277
+ when :admin
278
+ user.admin?
279
+ else
280
+ false
281
+ end
282
+ }
283
+ end
284
+ ```
285
+
286
+ ## Screenshots
287
+
288
+ ### Main Dashboard
289
+ ![Main Dashboard](screenshots/dashboard.png)
290
+
291
+ ### Trigger Generator
292
+ ![Trigger Generator](screenshots/generator.png)
293
+
294
+ ### Migration Management
295
+ ![Migration Management](screenshots/migrations.png)
296
+
297
+ ### Kill Switch Protection
298
+ ![Kill Switch](screenshots/kill-switch.png)
299
+
300
+ ### SQL Capsules
301
+ ![SQL Capsules](screenshots/sql-capsules.png)
302
+
303
+ ## Tips and Best Practices
304
+
305
+ 1. **Check Status Regularly**: Monitor drift detection to catch unexpected changes
306
+ 2. **Use Confirmations**: Don't bypass production confirmations without understanding the impact
307
+ 3. **Test in Development**: Always test UI actions in development before production
308
+ 4. **Review Logs**: Check application logs after important operations
309
+ 5. **Document Changes**: Add comments when making manual changes via SQL Capsules
310
+
311
+ ## Troubleshooting
312
+
313
+ ### UI Not Accessible
314
+
315
+ Check that:
316
+ 1. The engine is mounted in routes
317
+ 2. Your database migrations are up to date
318
+ 3. The registry table exists
319
+
320
+ ### Permission Denied
321
+
322
+ Verify:
323
+ 1. Your permission checker is configured correctly
324
+ 2. Your user has the required permission level
325
+ 3. The kill switch isn't blocking your operation
326
+
327
+ ### Migration Failures
328
+
329
+ If migrations fail:
330
+ 1. Check the error message in the flash notification
331
+ 2. Review the migration SQL in `db/triggers/`
332
+ 3. Test the SQL in a console first
333
+ 4. Check database logs for detailed error information
334
+
335
+ ## Next Steps
336
+
337
+ - [Kill Switch Documentation](kill-switch.md) - Understand production safety
338
+ - [Configuration](configuration.md) - Customize UI behavior
339
+ - [API Reference](api-reference.md) - Programmatic access
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class CreatePgSqlTriggersTables < ActiveRecord::Migration[6.0]
3
+ class CreatePgSqlTriggersTables < ActiveRecord::Migration[6.1]
4
4
  def change
5
5
  # Registry table - source of truth for all triggers
6
6
  create_table :pg_sql_triggers_registry do |t|
@@ -1,10 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  PgSqlTriggers.configure do |config|
4
- # Enable or disable the production kill switch
5
- # When enabled, all destructive operations in production require explicit confirmation
4
+ # ========== Kill Switch Configuration ==========
5
+ # The Kill Switch is a safety mechanism that prevents accidental destructive operations
6
+ # in protected environments (production, staging, etc.)
7
+
8
+ # Enable or disable the kill switch globally
9
+ # Default: true (recommended for safety)
6
10
  config.kill_switch_enabled = true
7
11
 
12
+ # Specify which environments should be protected by the kill switch
13
+ # Default: %i[production staging]
14
+ config.kill_switch_environments = %i[production staging]
15
+
16
+ # Require confirmation text for kill switch overrides
17
+ # When true, users must type a specific confirmation text to proceed
18
+ # Default: true (recommended for maximum safety)
19
+ config.kill_switch_confirmation_required = true
20
+
21
+ # Custom confirmation pattern generator
22
+ # Takes an operation symbol and returns the required confirmation text
23
+ # Default: "EXECUTE <OPERATION_NAME>"
24
+ config.kill_switch_confirmation_pattern = ->(operation) { "EXECUTE #{operation.to_s.upcase}" }
25
+
26
+ # Logger for kill switch events
27
+ # Default: Rails.logger
28
+ config.kill_switch_logger = Rails.logger
29
+
30
+ # Enable audit trail for kill switch events (optional enhancement)
31
+ # When enabled, all kill switch events are logged to a database table
32
+ # Default: false (can be enabled later)
33
+ # config.kill_switch_audit_trail_enabled = false
34
+
35
+ # Time-window auto-lock configuration (optional enhancement)
36
+ # Automatically enable kill switch during specific time windows
37
+ # Default: false
38
+ # config.kill_switch_auto_lock_enabled = false
39
+ # config.kill_switch_auto_lock_window = 30.minutes
40
+ # config.kill_switch_auto_lock_after = -> { Time.current.hour.between?(22, 6) } # Night hours
41
+
8
42
  # Set the default environment detection
9
43
  # By default, uses Rails.env
10
44
  config.default_environment = -> { Rails.env }
@@ -140,7 +140,7 @@ module PgSqlTriggers
140
140
  }
141
141
  end
142
142
 
143
- def create_trigger(form, _actor: nil)
143
+ def create_trigger(form, actor: nil) # rubocop:disable Lint/UnusedMethodArgument
144
144
  paths = file_paths(form)
145
145
 
146
146
  # Determine if we're in a Rails app context or standalone gem
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgSqlTriggers
4
- class Migration < ActiveRecord::Migration[6.0]
4
+ class Migration < ActiveRecord::Migration[6.1]
5
5
  # Base class for trigger migrations
6
6
  # Similar to ActiveRecord::Migration but for trigger-specific migrations
7
7
 
@@ -38,7 +38,7 @@ module PgSqlTriggers
38
38
  def migrations
39
39
  return [] unless Dir.exist?(migrations_path)
40
40
 
41
- files = Dir.glob(migrations_path.join("*.rb")).sort
41
+ files = Dir.glob(migrations_path.join("*.rb"))
42
42
  files.map do |file|
43
43
  basename = File.basename(file, ".rb")
44
44
  # Handle Rails migration format: YYYYMMDDHHMMSS_name
@@ -78,7 +78,19 @@ module PgSqlTriggers
78
78
  end
79
79
  end
80
80
 
81
- def run_up(target_version = nil)
81
+ def run_up(target_version = nil, confirmation: nil)
82
+ # Check kill switch before running migrations
83
+ # This provides protection when called directly from console
84
+ # When called from rake tasks, the ENV override will already be in place
85
+ # Use ENV["CONFIRMATION_TEXT"] if confirmation is not provided (for rake task compatibility)
86
+ confirmation ||= ENV.fetch("CONFIRMATION_TEXT", nil)
87
+ PgSqlTriggers::SQL::KillSwitch.check!(
88
+ operation: :migrator_run_up,
89
+ environment: Rails.env,
90
+ confirmation: confirmation,
91
+ actor: { type: "Console", id: "Migrator.run_up" }
92
+ )
93
+
82
94
  if target_version
83
95
  # Apply a specific migration version
84
96
  migration_to_apply = migrations.find { |m| m.version == target_version }
@@ -102,7 +114,19 @@ module PgSqlTriggers
102
114
  end
103
115
  end
104
116
 
105
- def run_down(target_version = nil)
117
+ def run_down(target_version = nil, confirmation: nil)
118
+ # Check kill switch before running migrations
119
+ # This provides protection when called directly from console
120
+ # When called from rake tasks, the ENV override will already be in place
121
+ # Use ENV["CONFIRMATION_TEXT"] if confirmation is not provided (for rake task compatibility)
122
+ confirmation ||= ENV.fetch("CONFIRMATION_TEXT", nil)
123
+ PgSqlTriggers::SQL::KillSwitch.check!(
124
+ operation: :migrator_run_down,
125
+ environment: Rails.env,
126
+ confirmation: confirmation,
127
+ actor: { type: "Console", id: "Migrator.run_down" }
128
+ )
129
+
106
130
  current_ver = current_version
107
131
  return if current_ver.zero?
108
132
 
@@ -6,7 +6,7 @@ module PgSqlTriggers
6
6
  class << self
7
7
  def register(definition)
8
8
  trigger_name = definition.name
9
- existing = TriggerRegistry.find_by(trigger_name: trigger_name)
9
+ existing = PgSqlTriggers::TriggerRegistry.find_by(trigger_name: trigger_name)
10
10
 
11
11
  attributes = {
12
12
  trigger_name: definition.name,
@@ -22,19 +22,19 @@ module PgSqlTriggers
22
22
  existing.update!(attributes)
23
23
  existing
24
24
  else
25
- TriggerRegistry.create!(attributes.merge(checksum: "placeholder"))
25
+ PgSqlTriggers::TriggerRegistry.create!(attributes.merge(checksum: "placeholder"))
26
26
  end
27
27
  end
28
28
 
29
29
  def list
30
- TriggerRegistry.all
30
+ PgSqlTriggers::TriggerRegistry.all
31
31
  end
32
32
 
33
- delegate :enabled, to: :TriggerRegistry
33
+ delegate :enabled, to: PgSqlTriggers::TriggerRegistry
34
34
 
35
- delegate :disabled, to: :TriggerRegistry
35
+ delegate :disabled, to: PgSqlTriggers::TriggerRegistry
36
36
 
37
- delegate :for_table, to: :TriggerRegistry
37
+ delegate :for_table, to: PgSqlTriggers::TriggerRegistry
38
38
 
39
39
  def diff
40
40
  # Compare DSL definitions with actual database state