propel_api 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -6
- data/lib/generators/propel_api/core/named_base.rb +88 -27
- data/lib/generators/propel_api/core/relationship_inferrer.rb +42 -8
- data/lib/generators/propel_api/templates/config/propel_api.rb.tt +3 -2
- data/lib/generators/propel_api/templates/controllers/api_controller_graphiti.rb +49 -6
- data/lib/generators/propel_api/templates/controllers/api_controller_propel_facets.rb +215 -4
- data/lib/generators/propel_api/templates/scaffold/facet_controller_template.rb.tt +27 -1
- data/lib/generators/propel_api/templates/scaffold/facet_model_template.rb.tt +39 -6
- data/lib/generators/propel_api/templates/seeds/seeds_template.rb.tt +11 -2
- data/lib/generators/propel_api/templates/tests/controller_test_template.rb.tt +279 -36
- data/lib/generators/propel_api/templates/tests/fixtures_template.yml.tt +30 -0
- data/lib/generators/propel_api/templates/tests/integration_test_template.rb.tt +459 -56
- data/lib/generators/propel_api/templates/tests/model_test_template.rb.tt +25 -7
- data/lib/generators/propel_api/unpack/unpack_generator.rb +52 -30
- data/lib/propel_api.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fda016a4637d6921f4cbd7c1739aa12b652453b68f2bf00e627adda7a0384086
|
4
|
+
data.tar.gz: bee75517d7fee3d5f04536be44f5dba602bc5525076814a87e7b6f743a467913
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a19fc6c6f6c4d12773081adc7a856d7f4fcb1977c56cb3686334d8e316bb29ef540829f0f25adcd61ae6285f10a010691f063b0003bad463bc8b8624afd05974
|
7
|
+
data.tar.gz: 65349f86faa2e171c64b20d6699f90dc56649ae1b8c888f64ac8c3b76f147bbd301689ac79a0032c71cc134e3d1c3e380f0f5a1fbb7a35c46ea0adaebd63fb44
|
data/CHANGELOG.md
CHANGED
@@ -10,14 +10,90 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
10
10
|
### Planned Features
|
11
11
|
- GraphQL adapter support
|
12
12
|
|
13
|
-
## [0.
|
13
|
+
## [0.2.0] - 2025-09-02
|
14
|
+
|
15
|
+
### BREAKING CHANGES
|
16
|
+
- **Security-first API architecture**: Complete redesign of tenancy validation flow
|
17
|
+
- Invalid tenancy context (organization_id, agency_id) now returns 403 Forbidden instead of 422 Unprocessable Entity
|
18
|
+
- Security validation occurs before business validation (prevents information disclosure)
|
19
|
+
- Error response structure changed from `{"errors": {...}}` to `{"error": "...", "message": "...", "code": "..."}`
|
20
|
+
- **Controller generation format**: Generated controllers now use foreign key format in permitted_params
|
21
|
+
- `permitted_params :organization` → `permitted_params :organization_id`
|
22
|
+
- Ensures proper strong parameter filtering for security validation
|
23
|
+
- **Configuration dependency**: `agency_tenancy` configuration moved to PropelAuthentication
|
24
|
+
- Remove `PropelApi.configuration.agency_tenancy` from config files
|
25
|
+
- Agency tenancy now controlled entirely by PropelAuthentication
|
26
|
+
|
27
|
+
### Added
|
28
|
+
- **Organization-level multi-tenancy security** - Complete data isolation between organizations
|
29
|
+
- All API queries automatically scoped to user's organization
|
30
|
+
- JWT tokens include `organization_id` for secure context extraction
|
31
|
+
- `for_organization(org_id)` scope added to ApplicationRecord base class
|
32
|
+
- Cross-organization data access completely blocked (show, update, delete return 404)
|
33
|
+
- New records automatically assigned to authenticated user's organization
|
34
|
+
- Comprehensive security test suite covering all attack vectors
|
35
|
+
- Zero impact on existing single-tenant applications
|
36
|
+
- **Configurable auto-assignment**: Integration with PropelAuthentication tenancy configuration
|
37
|
+
- Respects `require_organization_id` and `require_user_id` settings from PropelAuthentication
|
38
|
+
- Helper methods: `require_organization_id?`, `require_user_id?` for configuration access
|
39
|
+
- **Enhanced security validation**: Comprehensive unauthorized access protection
|
40
|
+
- Organization access validation with detailed error codes
|
41
|
+
- User assignment validation for admin delegation scenarios
|
42
|
+
- Agency access validation with proper user permission checking
|
43
|
+
- **Conditional test generation**: Tests now adapt behavior based on PropelAuthentication configuration
|
44
|
+
- Auto-assignment mode: Tests expect 201 Created with proper context assignment
|
45
|
+
- Strict mode: Tests expect 422 Unprocessable Entity when required fields missing
|
46
|
+
- Security tests: Tests expect 403 Forbidden for unauthorized access attempts
|
14
47
|
|
15
48
|
### Fixed
|
16
|
-
- **
|
17
|
-
-
|
18
|
-
-
|
49
|
+
- **Authentication namespace conflict resolved** - Renamed authentication concern to prevent module name collision
|
50
|
+
- `PropelAuthentication` concern renamed to `PropelAuthenticationConcern`
|
51
|
+
- Eliminates conflict between authentication controller concern and PropelAuthentication configuration module
|
52
|
+
- Updated all controller templates to use `include PropelAuthenticationConcern`
|
53
|
+
- Updated PropelFacets and Graphiti API controller templates
|
54
|
+
- Improved method visibility: `authenticate_user`, `current_user`, and `extract_jwt_token` are now public methods
|
55
|
+
- Enhanced flexibility for custom authentication scenarios (email notifications, audit logging, token refresh)
|
56
|
+
- **Attribute introspection**: Fixed foreign key detection for User and other models with associations
|
57
|
+
- Database column introspection now properly generates foreign key format for permitted_params
|
58
|
+
- Association detection preserved for model relationship generation
|
59
|
+
- JSON field handling improved with proper `field: {}` syntax for nested objects
|
60
|
+
- **Test data generation**: Enhanced User model test data generation
|
61
|
+
- Unique email and username generation to prevent fixture conflicts
|
62
|
+
- Proper field names for User model tests (email_address, username, password vs generic title)
|
63
|
+
- Model-specific test data patterns for comprehensive validation coverage
|
64
|
+
|
65
|
+
### Security
|
66
|
+
- **Multi-tenant data isolation** - Zero-trust organization scoping prevents data leaks
|
67
|
+
- Index queries: Users only see their organization's records
|
68
|
+
- Individual access: 404 responses for other organizations' records
|
69
|
+
- CRUD operations: All create/update/delete operations respect organization boundaries
|
70
|
+
- JWT security: Organization context properly extracted and validated
|
71
|
+
- Database-level enforcement via ActiveRecord scopes
|
72
|
+
|
73
|
+
### Improved
|
74
|
+
- **Multi-step security validation**: Three-phase validation for robust security
|
75
|
+
1. Security validation (403 for unauthorized access)
|
76
|
+
2. Auto-assignment (based on configuration)
|
77
|
+
3. Final validation (422 for missing required fields)
|
78
|
+
- **Authentication concern API design** - Better method organization and access patterns
|
79
|
+
- `authenticate_user` - Public method for `before_action` callbacks
|
80
|
+
- `current_user` - Public method for accessing authenticated user
|
81
|
+
- `current_organization_id` - Public method for accessing organization context
|
82
|
+
- `extract_jwt_token` - Public method for custom authentication scenarios
|
83
|
+
- Clean separation between public API and internal implementation
|
84
|
+
- **Template reliability**: Attribute detection using generator attributes instead of database queries during generation
|
85
|
+
|
86
|
+
## [0.1.4] - 2025-08-15
|
87
|
+
|
88
|
+
### Improved
|
89
|
+
- Minor bug fixes and stability improvements
|
90
|
+
|
91
|
+
## [0.1.3] - 2025-07-22
|
92
|
+
|
93
|
+
### Improved
|
94
|
+
- Performance optimizations and code refinements
|
19
95
|
|
20
|
-
## [0.1.2] - 2025-
|
96
|
+
## [0.1.2] - 2025-07-15
|
21
97
|
|
22
98
|
### Added
|
23
99
|
- **Automatic inverse relationship generation** - When creating resources with references, parent models are automatically updated
|
@@ -49,7 +125,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
49
125
|
- **Better error handling** - Graceful recovery when parent model modifications fail
|
50
126
|
- **Console output** - Color-coded feedback for relationship additions and warnings
|
51
127
|
|
52
|
-
## [0.1.1] - 2025-
|
128
|
+
## [0.1.1] - 2025-07-11
|
53
129
|
|
54
130
|
### Added
|
55
131
|
- **Optional usage comments in controllers** - `--with-comments` flag for generators
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative 'configuration_methods'
|
4
4
|
require_relative 'path_generation_methods'
|
5
|
+
require_relative 'relationship_inferrer'
|
5
6
|
|
6
7
|
##
|
7
8
|
# Named base class for PropelApi generators that work with named resources
|
@@ -44,6 +45,10 @@ module PropelApi
|
|
44
45
|
file_name.pluralize
|
45
46
|
end
|
46
47
|
|
48
|
+
def singular_route_name
|
49
|
+
file_name
|
50
|
+
end
|
51
|
+
|
47
52
|
def api_route_path
|
48
53
|
path_parts = []
|
49
54
|
path_parts << @api_namespace if @api_namespace.present?
|
@@ -60,6 +65,14 @@ module PropelApi
|
|
60
65
|
path_parts.join("_")
|
61
66
|
end
|
62
67
|
|
68
|
+
def api_singular_route_helper
|
69
|
+
path_parts = []
|
70
|
+
path_parts << @api_namespace if @api_namespace.present?
|
71
|
+
path_parts << @api_version if @api_version.present?
|
72
|
+
path_parts << singular_route_name
|
73
|
+
path_parts.join("_")
|
74
|
+
end
|
75
|
+
|
63
76
|
# Model introspection methods for existing models
|
64
77
|
def introspect_model_attributes
|
65
78
|
return [] unless model_exists?
|
@@ -70,30 +83,46 @@ module PropelApi
|
|
70
83
|
# Extract basic attribute information from model file
|
71
84
|
attributes = []
|
72
85
|
|
73
|
-
#
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
reference: association
|
82
|
-
}
|
86
|
+
# Use RelationshipInferrer to introspect existing associations
|
87
|
+
association_data = RelationshipInferrer.introspect_existing_associations(model_content)
|
88
|
+
association_names = association_data[:association_names]
|
89
|
+
|
90
|
+
# Add association attributes (with filtering)
|
91
|
+
association_data[:attributes].each do |attr_data|
|
92
|
+
unless should_exclude_from_permitted_params?(attr_data[:name], attr_data[:type])
|
93
|
+
attributes << attr_data
|
83
94
|
end
|
84
95
|
end
|
85
96
|
|
86
97
|
# Look for basic validations that might indicate attributes
|
98
|
+
# Exclude association names (they're already handled above as foreign keys)
|
99
|
+
# NOTE: Only add validated attributes if they weren't already detected by schema introspection
|
100
|
+
# This prevents overriding correct types (e.g., :decimal) with default :string
|
87
101
|
model_content.scan(/validates\s+:(\w+)/) do |attr|
|
88
102
|
attr_name = attr.first
|
89
|
-
unless
|
103
|
+
unless association_names.include?(attr_name) ||
|
104
|
+
attributes.any? { |a| a[:name] == attr_name } || # Proper deduplication - don't override existing attributes
|
105
|
+
should_exclude_from_permitted_params?(attr_name, :string)
|
90
106
|
attributes << {
|
91
107
|
name: attr_name,
|
92
|
-
type: :string #
|
108
|
+
type: :string # Only use :string as fallback for truly unknown attributes
|
93
109
|
}
|
94
110
|
end
|
95
111
|
end
|
96
112
|
|
113
|
+
# Detect common virtual attributes patterns
|
114
|
+
# Handle has_secure_password virtual attributes (password, password_confirmation)
|
115
|
+
if model_content.match?(/has_secure_password|include\s+Authenticatable/)
|
116
|
+
unless should_exclude_from_permitted_params?('password', :string) ||
|
117
|
+
attributes.any? { |a| a[:name] == 'password' }
|
118
|
+
attributes << { name: 'password', type: :string, required: true }
|
119
|
+
end
|
120
|
+
unless should_exclude_from_permitted_params?('password_confirmation', :string) ||
|
121
|
+
attributes.any? { |a| a[:name] == 'password_confirmation' }
|
122
|
+
attributes << { name: 'password_confirmation', type: :string, required: false }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
97
126
|
# Try to introspect from schema if available
|
98
127
|
if defined?(ActiveRecord::Base) && model_class_defined?
|
99
128
|
begin
|
@@ -103,13 +132,25 @@ module PropelApi
|
|
103
132
|
attr_name = column.name
|
104
133
|
attr_type = schema_type_to_generator_type(column.type)
|
105
134
|
|
106
|
-
# Skip if
|
107
|
-
|
135
|
+
# Skip if it should be excluded or if foreign key is covered by association
|
136
|
+
# Also skip foreign key columns if we already have the corresponding association
|
137
|
+
foreign_key_association = attr_name.end_with?('_id') ? attr_name.gsub(/_id$/, '') : nil
|
138
|
+
already_has_association = foreign_key_association && attributes.any? { |a| a[:name] == foreign_key_association && a[:type] == :references }
|
108
139
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
140
|
+
next if already_has_association || should_exclude_from_permitted_params?(attr_name, attr_type)
|
141
|
+
|
142
|
+
# Schema introspection is authoritative - override existing attributes with correct database types
|
143
|
+
# EXCEPT for associations, which should remain as :references
|
144
|
+
existing_index = attributes.find_index { |a| a[:name] == attr_name }
|
145
|
+
if existing_index
|
146
|
+
existing_attr = attributes[existing_index]
|
147
|
+
# Don't override associations detected by RelationshipInferrer
|
148
|
+
unless existing_attr[:type] == :references
|
149
|
+
attributes[existing_index] = { name: attr_name, type: attr_type } # Override with correct schema type
|
150
|
+
end
|
151
|
+
else
|
152
|
+
attributes << { name: attr_name, type: attr_type } # Add new attribute
|
153
|
+
end
|
113
154
|
end
|
114
155
|
end
|
115
156
|
rescue => e
|
@@ -126,19 +167,37 @@ module PropelApi
|
|
126
167
|
|
127
168
|
attributes = introspect_model_attributes
|
128
169
|
|
129
|
-
# Convert attributes to permitted param names
|
170
|
+
# Convert attributes to permitted param names with proper Rails strong parameter syntax
|
130
171
|
params = attributes.map do |attr|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
172
|
+
# For foreign key columns (organization_id, agency_id, user_id), use column name directly
|
173
|
+
if attr[:name].end_with?('_id') && model_has_association_for_foreign_key?(attr[:name])
|
174
|
+
attr[:name] # Keep foreign key format: organization_id, agency_id, user_id
|
175
|
+
elsif attr[:type] == :references
|
176
|
+
# Convert association references to foreign key format for strong parameters
|
177
|
+
"#{attr[:name]}_id" # Convert :organization to :organization_id for permitted_params
|
178
|
+
elsif attr[:type] == :json
|
179
|
+
# JSON/JSONB fields need hash syntax for Rails strong parameters to allow nested objects
|
180
|
+
"#{attr[:name]}: {}"
|
181
|
+
else
|
182
|
+
attr[:name] # Regular attributes: title, description, price, etc.
|
183
|
+
end
|
137
184
|
end
|
138
185
|
|
139
186
|
params
|
140
187
|
end
|
141
188
|
|
189
|
+
def model_has_association_for_foreign_key?(foreign_key)
|
190
|
+
return false unless model_class_defined?
|
191
|
+
|
192
|
+
begin
|
193
|
+
association_name = foreign_key.gsub('_id', '')
|
194
|
+
model_class = class_name.constantize
|
195
|
+
model_class.reflect_on_association(association_name.to_sym).present?
|
196
|
+
rescue
|
197
|
+
false
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
142
201
|
def validate_attributes_exist
|
143
202
|
return if options[:all_attributes] || !model_exists?
|
144
203
|
|
@@ -265,8 +324,8 @@ module PropelApi
|
|
265
324
|
# Fallback: Use the same patterns as templates for consistency
|
266
325
|
attr_str = attribute_name.to_s
|
267
326
|
|
268
|
-
#
|
269
|
-
excluded_patterns = /\A(created_at|updated_at|deleted_at|password_digest|reset_password_token|confirmation_token|unlock_token)\z/i
|
327
|
+
# Fallback: Basic exclusion patterns (PropelApi.attribute_filter should handle most cases)
|
328
|
+
excluded_patterns = /\A(id|created_at|updated_at|deleted_at|password_digest|reset_password_token|confirmation_token|unlock_token)\z/i
|
270
329
|
|
271
330
|
# Always exclude security-sensitive fields (same as templates)
|
272
331
|
security_patterns = /(password|digest|token|secret|key|salt|encrypted|confirmation|unlock|reset|api_key|access_token|refresh_token)/i
|
@@ -305,6 +364,8 @@ module PropelApi
|
|
305
364
|
:date
|
306
365
|
when :time
|
307
366
|
:time
|
367
|
+
when :json, :jsonb
|
368
|
+
:json
|
308
369
|
else
|
309
370
|
:string
|
310
371
|
end
|
@@ -19,6 +19,42 @@ class RelationshipInferrer
|
|
19
19
|
@inverse_relationships = {}
|
20
20
|
end
|
21
21
|
|
22
|
+
# Class method to introspect existing associations from model content
|
23
|
+
# Returns: { association_names: [], attributes: [] }
|
24
|
+
def self.introspect_existing_associations(model_content)
|
25
|
+
association_names = []
|
26
|
+
attributes = []
|
27
|
+
|
28
|
+
# Look for belongs_to associations
|
29
|
+
model_content.scan(/belongs_to\s+:(\w+)(?:,\s*(.*))?/) do |association, options|
|
30
|
+
association_names << association
|
31
|
+
|
32
|
+
# Convert belongs_to to reference attribute for controller generation
|
33
|
+
attributes << {
|
34
|
+
name: association, # Use association name for template (organization, not organization_id)
|
35
|
+
type: :references,
|
36
|
+
reference: association
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
# Look for has_many associations (for completeness)
|
41
|
+
model_content.scan(/has_many\s+:(\w+)(?:,\s*(.*))?/) do |association, options|
|
42
|
+
# We don't create controller attributes for has_many, but track for validation exclusion
|
43
|
+
association_names << association
|
44
|
+
end
|
45
|
+
|
46
|
+
# Look for has_one associations (for completeness)
|
47
|
+
model_content.scan(/has_one\s+:(\w+)(?:,\s*(.*))?/) do |association, options|
|
48
|
+
# We don't create controller attributes for has_one, but track for validation exclusion
|
49
|
+
association_names << association
|
50
|
+
end
|
51
|
+
|
52
|
+
{
|
53
|
+
association_names: association_names.uniq,
|
54
|
+
attributes: attributes
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
22
58
|
# Generate relationships for this model
|
23
59
|
def belongs_to_relationships
|
24
60
|
relationships = []
|
@@ -64,12 +100,9 @@ class RelationshipInferrer
|
|
64
100
|
# Add inverse relationship for non-polymorphic references only
|
65
101
|
add_inverse_relationship(class_name, standard_has_many)
|
66
102
|
|
67
|
-
# All references
|
68
|
-
if
|
69
|
-
|
70
|
-
else
|
71
|
-
"belongs_to :#{reference_name}, optional: true"
|
72
|
-
end
|
103
|
+
# All references are required by default (Rails convention)
|
104
|
+
# Add 'optional: true' manually if optional behavior is needed
|
105
|
+
"belongs_to :#{reference_name}"
|
73
106
|
end
|
74
107
|
|
75
108
|
def polymorphic_belongs_to(attribute)
|
@@ -84,8 +117,9 @@ class RelationshipInferrer
|
|
84
117
|
base_name = attribute.name.to_s
|
85
118
|
end
|
86
119
|
|
87
|
-
# Polymorphic associations
|
88
|
-
|
120
|
+
# Polymorphic associations default to required (Rails convention)
|
121
|
+
# Add 'optional: true' manually if optional behavior is needed
|
122
|
+
"belongs_to :#{base_name}, polymorphic: true"
|
89
123
|
end
|
90
124
|
|
91
125
|
|
@@ -26,9 +26,10 @@ module PropelApi
|
|
26
26
|
attr_accessor :sensitive_patterns, :excluded_patterns, :large_content_patterns
|
27
27
|
|
28
28
|
def initialize
|
29
|
-
# Security-sensitive field patterns
|
29
|
+
# Security-sensitive field patterns (exclude from permitted params)
|
30
|
+
# Note: Excludes stored/hashed fields but allows input fields like password, password_confirmation
|
30
31
|
@sensitive_patterns = [
|
31
|
-
/(
|
32
|
+
/(password_digest|reset_password_token|confirmation_token|unlock_token|remember_token|digest|secret|key|salt|encrypted|api_key|access_token|refresh_token)/i,
|
32
33
|
/\A(ssn|social_security|credit_card|cvv|pin|tax_id)\z/i
|
33
34
|
]
|
34
35
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class <%= api_controller_class_name %> < ApplicationController
|
2
2
|
include Graphiti::Rails
|
3
3
|
include Graphiti::Responders
|
4
|
-
include
|
4
|
+
include PropelAuthenticationConcern
|
5
5
|
|
6
6
|
before_action :authenticate_user
|
7
7
|
|
@@ -17,17 +17,26 @@ class <%= api_controller_class_name %> < ApplicationController
|
|
17
17
|
|
18
18
|
<% end -%>
|
19
19
|
def index
|
20
|
-
|
20
|
+
scoped_params = params_with_organization_scope
|
21
|
+
return unless scoped_params # Early return if organization context missing
|
22
|
+
|
23
|
+
resources = resource.all(scoped_params)
|
21
24
|
respond_with(resources)
|
22
25
|
end
|
23
26
|
|
24
27
|
def show
|
25
|
-
|
28
|
+
scoped_params = params_with_organization_scope
|
29
|
+
return unless scoped_params # Early return if organization context missing
|
30
|
+
|
31
|
+
resource_instance = resource.find(scoped_params)
|
26
32
|
respond_with(resource_instance)
|
27
33
|
end
|
28
34
|
|
29
35
|
def create
|
30
|
-
|
36
|
+
scoped_params = params_with_organization_context
|
37
|
+
return unless scoped_params # Early return if organization context missing
|
38
|
+
|
39
|
+
resource_instance = resource.build(scoped_params)
|
31
40
|
|
32
41
|
if resource_instance.save
|
33
42
|
respond_with(resource_instance, status: :created)
|
@@ -37,7 +46,10 @@ class <%= api_controller_class_name %> < ApplicationController
|
|
37
46
|
end
|
38
47
|
|
39
48
|
def update
|
40
|
-
|
49
|
+
scoped_params = params_with_organization_scope
|
50
|
+
return unless scoped_params # Early return if organization context missing
|
51
|
+
|
52
|
+
resource_instance = resource.find(scoped_params)
|
41
53
|
|
42
54
|
if resource_instance.update_attributes
|
43
55
|
respond_with(resource_instance)
|
@@ -47,7 +59,10 @@ class <%= api_controller_class_name %> < ApplicationController
|
|
47
59
|
end
|
48
60
|
|
49
61
|
def destroy
|
50
|
-
|
62
|
+
scoped_params = params_with_organization_scope
|
63
|
+
return unless scoped_params # Early return if organization context missing
|
64
|
+
|
65
|
+
resource_instance = resource.find(scoped_params)
|
51
66
|
resource_instance.destroy
|
52
67
|
|
53
68
|
respond_with(resource_instance)
|
@@ -77,6 +92,34 @@ class <%= api_controller_class_name %> < ApplicationController
|
|
77
92
|
raise "#{resource_class_name} not found. Please create it using: rails generate graphiti:resource #{controller_name.classify}"
|
78
93
|
end
|
79
94
|
|
95
|
+
# Add organization scope to params for query operations
|
96
|
+
def params_with_organization_scope
|
97
|
+
unless current_organization_id
|
98
|
+
render json: {
|
99
|
+
error: 'Organization context required',
|
100
|
+
message: 'Valid organization_id must be provided in JWT token',
|
101
|
+
code: 'MISSING_ORGANIZATION_CONTEXT'
|
102
|
+
}, status: :forbidden
|
103
|
+
return nil
|
104
|
+
end
|
105
|
+
|
106
|
+
params.merge(organization_scope: current_organization_id)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Add organization context to params for create operations
|
110
|
+
def params_with_organization_context
|
111
|
+
unless current_organization_id
|
112
|
+
render json: {
|
113
|
+
error: 'Organization context required',
|
114
|
+
message: 'Valid organization_id must be provided in JWT token',
|
115
|
+
code: 'MISSING_ORGANIZATION_CONTEXT'
|
116
|
+
}, status: :forbidden
|
117
|
+
return nil
|
118
|
+
end
|
119
|
+
|
120
|
+
params.merge(organization_id: current_organization_id)
|
121
|
+
end
|
122
|
+
|
80
123
|
<% if options[:with_comments] -%>
|
81
124
|
# Graphiti handles parameter filtering through resources automatically
|
82
125
|
# No need for manual strong parameters - resources define allowed attributes
|