pg_sql_triggers 1.2.0 → 1.4.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 +4 -4
- data/CHANGELOG.md +397 -1
- data/COVERAGE.md +26 -19
- data/GEM_ANALYSIS.md +368 -0
- data/Goal.md +276 -155
- data/README.md +45 -22
- data/app/assets/javascripts/pg_sql_triggers/trigger_actions.js +50 -0
- data/app/controllers/concerns/pg_sql_triggers/error_handling.rb +56 -0
- data/app/controllers/concerns/pg_sql_triggers/kill_switch_protection.rb +66 -0
- data/app/controllers/concerns/pg_sql_triggers/permission_checking.rb +117 -0
- data/app/controllers/pg_sql_triggers/application_controller.rb +10 -62
- data/app/controllers/pg_sql_triggers/audit_logs_controller.rb +102 -0
- data/app/controllers/pg_sql_triggers/dashboard_controller.rb +4 -9
- data/app/controllers/pg_sql_triggers/tables_controller.rb +30 -4
- data/app/controllers/pg_sql_triggers/triggers_controller.rb +3 -21
- data/app/helpers/pg_sql_triggers/permissions_helper.rb +43 -0
- data/app/models/pg_sql_triggers/audit_log.rb +106 -0
- data/app/models/pg_sql_triggers/trigger_registry.rb +218 -13
- data/app/views/layouts/pg_sql_triggers/application.html.erb +25 -6
- data/app/views/pg_sql_triggers/audit_logs/index.html.erb +177 -0
- data/app/views/pg_sql_triggers/dashboard/index.html.erb +34 -12
- data/app/views/pg_sql_triggers/tables/index.html.erb +75 -5
- data/app/views/pg_sql_triggers/tables/show.html.erb +17 -6
- data/app/views/pg_sql_triggers/triggers/_drop_modal.html.erb +16 -7
- data/app/views/pg_sql_triggers/triggers/_re_execute_modal.html.erb +16 -7
- data/app/views/pg_sql_triggers/triggers/show.html.erb +26 -6
- data/config/routes.rb +2 -14
- data/db/migrate/20260103000001_create_pg_sql_triggers_audit_log.rb +28 -0
- data/db/migrate/20260228000001_add_for_each_to_pg_sql_triggers_registry.rb +8 -0
- data/docs/README.md +15 -5
- data/docs/api-reference.md +233 -151
- data/docs/audit-trail.md +413 -0
- data/docs/configuration.md +28 -7
- data/docs/getting-started.md +17 -16
- data/docs/permissions.md +369 -0
- data/docs/troubleshooting.md +486 -0
- data/docs/ui-guide.md +211 -0
- data/docs/usage-guide.md +38 -67
- data/docs/web-ui.md +251 -128
- data/lib/generators/pg_sql_triggers/templates/trigger_dsl.rb.tt +11 -0
- data/lib/generators/pg_sql_triggers/templates/trigger_migration_full.rb.tt +29 -0
- data/lib/generators/pg_sql_triggers/trigger_generator.rb +83 -0
- data/lib/pg_sql_triggers/drift/db_queries.rb +12 -8
- data/lib/pg_sql_triggers/drift/detector.rb +51 -38
- data/lib/pg_sql_triggers/dsl/trigger_definition.rb +17 -23
- data/lib/pg_sql_triggers/engine.rb +14 -0
- data/lib/pg_sql_triggers/errors.rb +245 -0
- data/lib/pg_sql_triggers/migrator/pre_apply_comparator.rb +8 -9
- data/lib/pg_sql_triggers/migrator/safety_validator.rb +32 -12
- data/lib/pg_sql_triggers/migrator.rb +53 -6
- data/lib/pg_sql_triggers/permissions/checker.rb +9 -2
- data/lib/pg_sql_triggers/registry/manager.rb +36 -11
- data/lib/pg_sql_triggers/registry/validator.rb +62 -5
- data/lib/pg_sql_triggers/registry.rb +141 -8
- data/lib/pg_sql_triggers/sql/kill_switch.rb +153 -247
- data/lib/pg_sql_triggers/sql.rb +0 -6
- data/lib/pg_sql_triggers/testing/function_tester.rb +2 -0
- data/lib/pg_sql_triggers/version.rb +1 -1
- data/lib/pg_sql_triggers.rb +7 -7
- data/pg_sql_triggers.gemspec +53 -0
- metadata +35 -18
- data/app/controllers/pg_sql_triggers/generator_controller.rb +0 -213
- data/app/controllers/pg_sql_triggers/sql_capsules_controller.rb +0 -161
- data/app/views/pg_sql_triggers/generator/new.html.erb +0 -388
- data/app/views/pg_sql_triggers/generator/preview.html.erb +0 -305
- data/app/views/pg_sql_triggers/sql_capsules/new.html.erb +0 -81
- data/app/views/pg_sql_triggers/sql_capsules/show.html.erb +0 -85
- data/docs/screenshots/.gitkeep +0 -1
- data/docs/screenshots/Generate Trigger.png +0 -0
- data/docs/screenshots/Triggers Page.png +0 -0
- data/docs/screenshots/kill error.png +0 -0
- data/docs/screenshots/kill modal for migration down.png +0 -0
- data/lib/generators/trigger/migration_generator.rb +0 -60
- data/lib/pg_sql_triggers/generator/form.rb +0 -80
- data/lib/pg_sql_triggers/generator/service.rb +0 -307
- data/lib/pg_sql_triggers/generator.rb +0 -8
- data/lib/pg_sql_triggers/sql/capsule.rb +0 -79
- data/lib/pg_sql_triggers/sql/executor.rb +0 -200
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pg_sql_triggers
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- samaswin
|
|
@@ -9,6 +9,20 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: csv
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '3.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '3.0'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: pg
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -190,30 +204,33 @@ files:
|
|
|
190
204
|
- ".rubocop.yml"
|
|
191
205
|
- CHANGELOG.md
|
|
192
206
|
- COVERAGE.md
|
|
207
|
+
- GEM_ANALYSIS.md
|
|
193
208
|
- Goal.md
|
|
194
209
|
- LICENSE
|
|
195
210
|
- README.md
|
|
196
211
|
- RELEASE.md
|
|
197
212
|
- Rakefile
|
|
198
213
|
- app/assets/javascripts/pg_sql_triggers/application.js
|
|
214
|
+
- app/assets/javascripts/pg_sql_triggers/trigger_actions.js
|
|
199
215
|
- app/assets/stylesheets/pg_sql_triggers/application.css
|
|
216
|
+
- app/controllers/concerns/pg_sql_triggers/error_handling.rb
|
|
217
|
+
- app/controllers/concerns/pg_sql_triggers/kill_switch_protection.rb
|
|
218
|
+
- app/controllers/concerns/pg_sql_triggers/permission_checking.rb
|
|
200
219
|
- app/controllers/pg_sql_triggers/application_controller.rb
|
|
220
|
+
- app/controllers/pg_sql_triggers/audit_logs_controller.rb
|
|
201
221
|
- app/controllers/pg_sql_triggers/dashboard_controller.rb
|
|
202
|
-
- app/controllers/pg_sql_triggers/generator_controller.rb
|
|
203
222
|
- app/controllers/pg_sql_triggers/migrations_controller.rb
|
|
204
|
-
- app/controllers/pg_sql_triggers/sql_capsules_controller.rb
|
|
205
223
|
- app/controllers/pg_sql_triggers/tables_controller.rb
|
|
206
224
|
- app/controllers/pg_sql_triggers/triggers_controller.rb
|
|
225
|
+
- app/helpers/pg_sql_triggers/permissions_helper.rb
|
|
207
226
|
- app/models/pg_sql_triggers/application_record.rb
|
|
227
|
+
- app/models/pg_sql_triggers/audit_log.rb
|
|
208
228
|
- app/models/pg_sql_triggers/trigger_registry.rb
|
|
209
229
|
- app/views/layouts/pg_sql_triggers/application.html.erb
|
|
230
|
+
- app/views/pg_sql_triggers/audit_logs/index.html.erb
|
|
210
231
|
- app/views/pg_sql_triggers/dashboard/index.html.erb
|
|
211
|
-
- app/views/pg_sql_triggers/generator/new.html.erb
|
|
212
|
-
- app/views/pg_sql_triggers/generator/preview.html.erb
|
|
213
232
|
- app/views/pg_sql_triggers/shared/_confirmation_modal.html.erb
|
|
214
233
|
- app/views/pg_sql_triggers/shared/_kill_switch_status.html.erb
|
|
215
|
-
- app/views/pg_sql_triggers/sql_capsules/new.html.erb
|
|
216
|
-
- app/views/pg_sql_triggers/sql_capsules/show.html.erb
|
|
217
234
|
- app/views/pg_sql_triggers/tables/index.html.erb
|
|
218
235
|
- app/views/pg_sql_triggers/tables/show.html.erb
|
|
219
236
|
- app/views/pg_sql_triggers/triggers/_drop_modal.html.erb
|
|
@@ -223,25 +240,28 @@ files:
|
|
|
223
240
|
- config/routes.rb
|
|
224
241
|
- db/migrate/20251222000001_create_pg_sql_triggers_tables.rb
|
|
225
242
|
- db/migrate/20251229071916_add_timing_to_pg_sql_triggers_registry.rb
|
|
243
|
+
- db/migrate/20260103000001_create_pg_sql_triggers_audit_log.rb
|
|
244
|
+
- db/migrate/20260228000001_add_for_each_to_pg_sql_triggers_registry.rb
|
|
226
245
|
- docs/README.md
|
|
227
246
|
- docs/api-reference.md
|
|
247
|
+
- docs/audit-trail.md
|
|
228
248
|
- docs/configuration.md
|
|
229
249
|
- docs/getting-started.md
|
|
230
250
|
- docs/kill-switch.md
|
|
231
|
-
- docs/
|
|
232
|
-
- docs/
|
|
233
|
-
- docs/
|
|
234
|
-
- docs/screenshots/kill error.png
|
|
235
|
-
- docs/screenshots/kill modal for migration down.png
|
|
251
|
+
- docs/permissions.md
|
|
252
|
+
- docs/troubleshooting.md
|
|
253
|
+
- docs/ui-guide.md
|
|
236
254
|
- docs/usage-guide.md
|
|
237
255
|
- docs/web-ui.md
|
|
238
256
|
- lib/generators/pg_sql_triggers/install_generator.rb
|
|
239
257
|
- lib/generators/pg_sql_triggers/templates/README
|
|
240
258
|
- lib/generators/pg_sql_triggers/templates/create_pg_sql_triggers_tables.rb
|
|
241
259
|
- lib/generators/pg_sql_triggers/templates/initializer.rb
|
|
260
|
+
- lib/generators/pg_sql_triggers/templates/trigger_dsl.rb.tt
|
|
242
261
|
- lib/generators/pg_sql_triggers/templates/trigger_migration.rb.erb
|
|
262
|
+
- lib/generators/pg_sql_triggers/templates/trigger_migration_full.rb.tt
|
|
263
|
+
- lib/generators/pg_sql_triggers/trigger_generator.rb
|
|
243
264
|
- lib/generators/pg_sql_triggers/trigger_migration_generator.rb
|
|
244
|
-
- lib/generators/trigger/migration_generator.rb
|
|
245
265
|
- lib/pg_sql_triggers.rb
|
|
246
266
|
- lib/pg_sql_triggers/database_introspection.rb
|
|
247
267
|
- lib/pg_sql_triggers/drift.rb
|
|
@@ -251,9 +271,7 @@ files:
|
|
|
251
271
|
- lib/pg_sql_triggers/dsl.rb
|
|
252
272
|
- lib/pg_sql_triggers/dsl/trigger_definition.rb
|
|
253
273
|
- lib/pg_sql_triggers/engine.rb
|
|
254
|
-
- lib/pg_sql_triggers/
|
|
255
|
-
- lib/pg_sql_triggers/generator/form.rb
|
|
256
|
-
- lib/pg_sql_triggers/generator/service.rb
|
|
274
|
+
- lib/pg_sql_triggers/errors.rb
|
|
257
275
|
- lib/pg_sql_triggers/migration.rb
|
|
258
276
|
- lib/pg_sql_triggers/migrator.rb
|
|
259
277
|
- lib/pg_sql_triggers/migrator/pre_apply_comparator.rb
|
|
@@ -265,8 +283,6 @@ files:
|
|
|
265
283
|
- lib/pg_sql_triggers/registry/manager.rb
|
|
266
284
|
- lib/pg_sql_triggers/registry/validator.rb
|
|
267
285
|
- lib/pg_sql_triggers/sql.rb
|
|
268
|
-
- lib/pg_sql_triggers/sql/capsule.rb
|
|
269
|
-
- lib/pg_sql_triggers/sql/executor.rb
|
|
270
286
|
- lib/pg_sql_triggers/sql/kill_switch.rb
|
|
271
287
|
- lib/pg_sql_triggers/testing.rb
|
|
272
288
|
- lib/pg_sql_triggers/testing/dry_run.rb
|
|
@@ -275,6 +291,7 @@ files:
|
|
|
275
291
|
- lib/pg_sql_triggers/testing/syntax_validator.rb
|
|
276
292
|
- lib/pg_sql_triggers/version.rb
|
|
277
293
|
- lib/tasks/trigger_migrations.rake
|
|
294
|
+
- pg_sql_triggers.gemspec
|
|
278
295
|
- scripts/generate_coverage_report.rb
|
|
279
296
|
- sig/pg_sql_triggers.rbs
|
|
280
297
|
homepage: https://github.com/samaswin/pg_sql_triggers
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module PgSqlTriggers
|
|
4
|
-
class GeneratorController < ApplicationController
|
|
5
|
-
# Permissions: Require OPERATOR level for generation
|
|
6
|
-
before_action :check_operator_permission
|
|
7
|
-
|
|
8
|
-
# GET /generator/new
|
|
9
|
-
# Display the multi-step form wizard
|
|
10
|
-
def new
|
|
11
|
-
# Restore form data from session if available (when user clicks "Back to Edit")
|
|
12
|
-
session_data = session[:generator_form_data]
|
|
13
|
-
if session_data
|
|
14
|
-
# Convert to hash and symbolize keys for Form initialization
|
|
15
|
-
form_data = session_data.is_a?(Hash) ? session_data.symbolize_keys : session_data.to_h.symbolize_keys
|
|
16
|
-
@form = PgSqlTriggers::Generator::Form.new(form_data)
|
|
17
|
-
session.delete(:generator_form_data)
|
|
18
|
-
else
|
|
19
|
-
@form = PgSqlTriggers::Generator::Form.new
|
|
20
|
-
end
|
|
21
|
-
@available_tables = fetch_available_tables
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# POST /generator/preview
|
|
25
|
-
# Preview generated DSL and function stub (AJAX or regular POST)
|
|
26
|
-
def preview
|
|
27
|
-
@form = PgSqlTriggers::Generator::Form.new(generator_params)
|
|
28
|
-
|
|
29
|
-
# If user clicked "Back to Edit", store form data in session and redirect
|
|
30
|
-
if params[:back_to_edit].present?
|
|
31
|
-
# Store form data as a hash (convert ActionController::Parameters to hash)
|
|
32
|
-
session[:generator_form_data] = generator_params.to_h
|
|
33
|
-
redirect_to new_generator_path
|
|
34
|
-
return
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
if @form.valid?
|
|
38
|
-
# Store form data in session so it can be restored if user clicks "Back to Edit"
|
|
39
|
-
# Convert ActionController::Parameters to hash
|
|
40
|
-
session[:generator_form_data] = generator_params.to_h
|
|
41
|
-
|
|
42
|
-
# Validate SQL function body (required field)
|
|
43
|
-
@sql_validation = validate_function_sql(@form)
|
|
44
|
-
|
|
45
|
-
@dsl_content = PgSqlTriggers::Generator::Service.generate_dsl(@form)
|
|
46
|
-
# Use function_body (required)
|
|
47
|
-
@function_content = @form.function_body
|
|
48
|
-
@file_paths = PgSqlTriggers::Generator::Service.file_paths(@form)
|
|
49
|
-
|
|
50
|
-
render :preview
|
|
51
|
-
else
|
|
52
|
-
@available_tables = fetch_available_tables
|
|
53
|
-
render :new
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# POST /generator/create
|
|
58
|
-
# Actually create the files and register in TriggerRegistry
|
|
59
|
-
def create
|
|
60
|
-
# Check kill switch before generating trigger
|
|
61
|
-
check_kill_switch(operation: :ui_trigger_generate, confirmation: params[:confirmation_text])
|
|
62
|
-
|
|
63
|
-
@form = PgSqlTriggers::Generator::Form.new(generator_params)
|
|
64
|
-
|
|
65
|
-
if @form.valid?
|
|
66
|
-
# Validate SQL function body (required field)
|
|
67
|
-
sql_validation = validate_function_sql(@form)
|
|
68
|
-
unless sql_validation[:valid]
|
|
69
|
-
flash.now[:alert] = "Cannot create trigger: SQL validation failed - #{sql_validation[:error]}"
|
|
70
|
-
@available_tables = fetch_available_tables
|
|
71
|
-
@dsl_content = PgSqlTriggers::Generator::Service.generate_dsl(@form)
|
|
72
|
-
@function_content = @form.function_body
|
|
73
|
-
@file_paths = PgSqlTriggers::Generator::Service.file_paths(@form)
|
|
74
|
-
@sql_validation = sql_validation
|
|
75
|
-
render :preview
|
|
76
|
-
return
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
result = PgSqlTriggers::Generator::Service.create_trigger(@form, actor: current_actor)
|
|
80
|
-
|
|
81
|
-
if result[:success]
|
|
82
|
-
# Clear session data after successful creation
|
|
83
|
-
session.delete(:generator_form_data)
|
|
84
|
-
files_msg = "Migration: #{result[:migration_path]}, DSL: #{result[:dsl_path]}"
|
|
85
|
-
redirect_to root_path,
|
|
86
|
-
notice: "Trigger generated successfully. Files created: #{files_msg}"
|
|
87
|
-
else
|
|
88
|
-
flash[:alert] = "Generation failed: #{result[:error]}"
|
|
89
|
-
@available_tables = fetch_available_tables
|
|
90
|
-
render :new
|
|
91
|
-
end
|
|
92
|
-
else
|
|
93
|
-
@available_tables = fetch_available_tables
|
|
94
|
-
render :new
|
|
95
|
-
end
|
|
96
|
-
rescue PgSqlTriggers::KillSwitchError => e
|
|
97
|
-
flash[:error] = e.message
|
|
98
|
-
redirect_to root_path
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
# POST /generator/validate_table (AJAX)
|
|
102
|
-
# Validate that table exists in database
|
|
103
|
-
def validate_table
|
|
104
|
-
# Extract table_name from JSON request body
|
|
105
|
-
# Rails should parse JSON automatically, but handle both cases
|
|
106
|
-
table_name = extract_table_name_from_request
|
|
107
|
-
|
|
108
|
-
if table_name.blank?
|
|
109
|
-
render json: { valid: false, error: "Table name is required" }, status: :bad_request
|
|
110
|
-
return
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
validator = PgSqlTriggers::DatabaseIntrospection.new
|
|
114
|
-
result = validator.validate_table(table_name)
|
|
115
|
-
render json: result
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
# GET /generator/tables (AJAX)
|
|
119
|
-
# Fetch list of tables for dropdown
|
|
120
|
-
def tables
|
|
121
|
-
tables = fetch_available_tables
|
|
122
|
-
render json: { tables: tables }
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
private
|
|
126
|
-
|
|
127
|
-
def generator_params
|
|
128
|
-
params.require(:pg_sql_triggers_generator_form).permit(
|
|
129
|
-
:trigger_name, :table_name, :function_name, :version,
|
|
130
|
-
:enabled, :condition, :timing, :generate_function_stub, :function_body,
|
|
131
|
-
events: [], environments: []
|
|
132
|
-
)
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def check_operator_permission
|
|
136
|
-
return if PgSqlTriggers::Permissions.can?(current_actor, :apply_trigger)
|
|
137
|
-
|
|
138
|
-
redirect_to root_path, alert: "Insufficient permissions. Operator role required."
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def fetch_available_tables
|
|
142
|
-
PgSqlTriggers::DatabaseIntrospection.new.list_tables
|
|
143
|
-
rescue StandardError => e
|
|
144
|
-
Rails.logger.error("Failed to fetch tables: #{e.message}")
|
|
145
|
-
[]
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def extract_table_name_from_request
|
|
149
|
-
# Rails automatically parses JSON request bodies when Content-Type is application/json
|
|
150
|
-
# The parameters are available directly in params
|
|
151
|
-
table_name = params[:table_name]
|
|
152
|
-
|
|
153
|
-
# If not found, try accessing as string key (some Rails versions use string keys for JSON)
|
|
154
|
-
table_name ||= params["table_name"] if params.key?("table_name")
|
|
155
|
-
|
|
156
|
-
table_name
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def validate_function_sql(form)
|
|
160
|
-
return nil if form.function_body.blank?
|
|
161
|
-
|
|
162
|
-
# Create a temporary trigger registry object for validation
|
|
163
|
-
# Only include condition if the column exists
|
|
164
|
-
registry_attributes = {
|
|
165
|
-
trigger_name: form.trigger_name,
|
|
166
|
-
table_name: form.table_name,
|
|
167
|
-
function_body: form.function_body
|
|
168
|
-
}
|
|
169
|
-
# Only set condition if the column exists in the database
|
|
170
|
-
if PgSqlTriggers::TriggerRegistry.column_names.include?("condition")
|
|
171
|
-
registry_attributes[:condition] = form.condition
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
temp_registry = PgSqlTriggers::TriggerRegistry.new(registry_attributes)
|
|
175
|
-
|
|
176
|
-
# Build definition JSON for condition validation
|
|
177
|
-
definition = {
|
|
178
|
-
name: form.trigger_name,
|
|
179
|
-
table_name: form.table_name,
|
|
180
|
-
function_name: form.function_name,
|
|
181
|
-
events: form.events.compact_blank,
|
|
182
|
-
version: form.version,
|
|
183
|
-
enabled: form.enabled,
|
|
184
|
-
environments: form.environments.compact_blank,
|
|
185
|
-
condition: form.condition,
|
|
186
|
-
timing: form.timing || "before",
|
|
187
|
-
function_body: form.function_body
|
|
188
|
-
}
|
|
189
|
-
temp_registry.definition = definition.to_json
|
|
190
|
-
|
|
191
|
-
validator = PgSqlTriggers::Testing::SyntaxValidator.new(temp_registry)
|
|
192
|
-
|
|
193
|
-
# Validate function syntax
|
|
194
|
-
function_result = validator.validate_function_syntax
|
|
195
|
-
return function_result unless function_result[:valid]
|
|
196
|
-
|
|
197
|
-
# Validate condition if present
|
|
198
|
-
if form.condition.present?
|
|
199
|
-
condition_result = validator.validate_condition
|
|
200
|
-
unless condition_result[:valid]
|
|
201
|
-
return {
|
|
202
|
-
valid: false,
|
|
203
|
-
error: "WHEN condition validation failed: #{condition_result[:error]}"
|
|
204
|
-
}
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
function_result
|
|
209
|
-
rescue StandardError => e
|
|
210
|
-
{ valid: false, error: "Validation error: #{e.message}" }
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
end
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module PgSqlTriggers
|
|
4
|
-
class SqlCapsulesController < ApplicationController
|
|
5
|
-
before_action :check_admin_permission, only: [:execute]
|
|
6
|
-
before_action :check_operator_permission, only: %i[new create show]
|
|
7
|
-
before_action :load_capsule, only: %i[show execute]
|
|
8
|
-
|
|
9
|
-
def show
|
|
10
|
-
unless @capsule
|
|
11
|
-
redirect_to new_sql_capsule_path, alert: "Capsule not found"
|
|
12
|
-
return
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
@checksum = @capsule.checksum
|
|
16
|
-
@can_execute = can_execute_capsule?
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def new
|
|
20
|
-
@capsule_name = params[:name] || ""
|
|
21
|
-
@environment = params[:environment] || current_environment
|
|
22
|
-
@purpose = params[:purpose] || ""
|
|
23
|
-
@sql = params[:sql] || ""
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def create
|
|
27
|
-
# Strip whitespace from parameters
|
|
28
|
-
@capsule_name = params[:name].to_s.strip
|
|
29
|
-
@environment = params[:environment].to_s.strip
|
|
30
|
-
@purpose = params[:purpose].to_s.strip
|
|
31
|
-
@sql = params[:sql].to_s.strip
|
|
32
|
-
|
|
33
|
-
capsule = build_capsule_from_params
|
|
34
|
-
|
|
35
|
-
# Save the capsule to registry
|
|
36
|
-
result = save_capsule_to_registry(capsule)
|
|
37
|
-
|
|
38
|
-
if result[:success]
|
|
39
|
-
redirect_to sql_capsule_path(id: @capsule_name),
|
|
40
|
-
notice: "SQL Capsule '#{capsule.name}' created successfully"
|
|
41
|
-
else
|
|
42
|
-
flash.now[:alert] = result[:message]
|
|
43
|
-
render :new
|
|
44
|
-
end
|
|
45
|
-
rescue ArgumentError => e
|
|
46
|
-
flash.now[:alert] = "Invalid capsule: #{e.message}"
|
|
47
|
-
render :new
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def execute
|
|
51
|
-
unless @capsule
|
|
52
|
-
redirect_to new_sql_capsule_path, alert: "Capsule not found"
|
|
53
|
-
return
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Check kill switch with confirmation
|
|
57
|
-
check_kill_switch(
|
|
58
|
-
operation: :execute_sql_capsule,
|
|
59
|
-
confirmation: params[:confirmation]
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
# Execute the capsule
|
|
63
|
-
result = PgSqlTriggers::SQL::Executor.execute(
|
|
64
|
-
@capsule,
|
|
65
|
-
actor: current_actor,
|
|
66
|
-
confirmation: params[:confirmation],
|
|
67
|
-
dry_run: false
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
if result[:success]
|
|
71
|
-
flash[:notice] = result[:message]
|
|
72
|
-
else
|
|
73
|
-
flash[:alert] = result[:message]
|
|
74
|
-
end
|
|
75
|
-
redirect_to sql_capsule_path(id: params[:id])
|
|
76
|
-
rescue PgSqlTriggers::KillSwitchError => e
|
|
77
|
-
flash[:alert] = "Kill switch blocked execution: #{e.message}"
|
|
78
|
-
redirect_to sql_capsule_path(id: params[:id])
|
|
79
|
-
rescue PgSqlTriggers::PermissionError => e
|
|
80
|
-
flash[:alert] = "Permission denied: #{e.message}"
|
|
81
|
-
redirect_to sql_capsule_path(id: params[:id])
|
|
82
|
-
rescue StandardError => e
|
|
83
|
-
Rails.logger.error("SQL Capsule execution failed: #{e.message}\n#{e.backtrace.join("\n")}")
|
|
84
|
-
flash[:alert] = "Execution failed: #{e.message}"
|
|
85
|
-
redirect_to sql_capsule_path(id: params[:id])
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
private
|
|
89
|
-
|
|
90
|
-
def check_admin_permission
|
|
91
|
-
return if PgSqlTriggers::Permissions.can?(current_actor, :execute_sql)
|
|
92
|
-
|
|
93
|
-
redirect_to dashboard_path, alert: "Insufficient permissions. Admin role required."
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def check_operator_permission
|
|
97
|
-
return if PgSqlTriggers::Permissions.can?(current_actor, :generate_trigger)
|
|
98
|
-
|
|
99
|
-
redirect_to dashboard_path, alert: "Insufficient permissions. Operator role required."
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def build_capsule_from_params
|
|
103
|
-
PgSqlTriggers::SQL::Capsule.new(
|
|
104
|
-
name: params[:name].to_s.strip,
|
|
105
|
-
environment: params[:environment].to_s.strip,
|
|
106
|
-
purpose: params[:purpose].to_s.strip,
|
|
107
|
-
sql: params[:sql].to_s.strip
|
|
108
|
-
)
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def save_capsule_to_registry(capsule)
|
|
112
|
-
# Check if capsule already exists
|
|
113
|
-
existing = PgSqlTriggers::TriggerRegistry.find_by(
|
|
114
|
-
trigger_name: capsule.registry_trigger_name,
|
|
115
|
-
source: "manual_sql"
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
if existing
|
|
119
|
-
return {
|
|
120
|
-
success: false,
|
|
121
|
-
message: "A capsule with this name already exists. Please choose a different name."
|
|
122
|
-
}
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Create new registry entry
|
|
126
|
-
registry_entry = PgSqlTriggers::TriggerRegistry.new(
|
|
127
|
-
trigger_name: capsule.registry_trigger_name,
|
|
128
|
-
table_name: "manual_sql_execution",
|
|
129
|
-
version: Time.current.to_i,
|
|
130
|
-
checksum: capsule.checksum,
|
|
131
|
-
source: "manual_sql",
|
|
132
|
-
function_body: capsule.sql,
|
|
133
|
-
condition: capsule.purpose,
|
|
134
|
-
environment: capsule.environment,
|
|
135
|
-
enabled: false # Not executed yet
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
if registry_entry.save
|
|
139
|
-
{ success: true, message: "Capsule saved successfully" }
|
|
140
|
-
else
|
|
141
|
-
{ success: false, message: "Failed to save capsule: #{registry_entry.errors.full_messages.join(', ')}" }
|
|
142
|
-
end
|
|
143
|
-
rescue StandardError => e
|
|
144
|
-
Rails.logger.error("Failed to save capsule to registry: #{e.message}")
|
|
145
|
-
{ success: false, message: "Failed to save capsule: #{e.message}" }
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def load_capsule
|
|
149
|
-
return if params[:id].blank?
|
|
150
|
-
|
|
151
|
-
@capsule = PgSqlTriggers::SQL::Executor.send(
|
|
152
|
-
:load_capsule_from_registry,
|
|
153
|
-
params[:id]
|
|
154
|
-
)
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def can_execute_capsule?
|
|
158
|
-
PgSqlTriggers::Permissions.can?(current_actor, :execute_sql)
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
end
|