propel_authentication 0.2.1 → 0.3.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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -0
  3. data/lib/generators/propel_authentication/install_generator.rb +190 -228
  4. data/lib/generators/propel_authentication/templates/auth/passwords_controller.rb.tt +10 -13
  5. data/lib/generators/propel_authentication/templates/auth/signup_controller.rb.tt +6 -9
  6. data/lib/generators/propel_authentication/templates/auth/tokens_controller.rb.tt +4 -7
  7. data/lib/generators/propel_authentication/templates/core/configuration_methods.rb +38 -1
  8. data/lib/generators/propel_authentication/templates/db/migrate/create_agencies.rb +4 -2
  9. data/lib/generators/propel_authentication/templates/db/migrate/create_agents.rb +6 -1
  10. data/lib/generators/propel_authentication/templates/db/migrate/create_organizations.rb +4 -2
  11. data/lib/generators/propel_authentication/templates/db/migrate/create_users.rb +4 -2
  12. data/lib/generators/propel_authentication/templates/db/propel_seeds.rb.tt +145 -0
  13. data/lib/generators/propel_authentication/templates/doc/signup_flow.md +9 -4
  14. data/lib/generators/propel_authentication/templates/fixtures/agencies.yml.erb +23 -0
  15. data/lib/generators/propel_authentication/templates/fixtures/agents.yml.erb +71 -0
  16. data/lib/generators/propel_authentication/templates/fixtures/invitations.yml.erb +9 -0
  17. data/lib/generators/propel_authentication/templates/fixtures/organizations.yml.erb +21 -0
  18. data/lib/generators/propel_authentication/templates/fixtures/users.yml.erb +77 -0
  19. data/lib/generators/propel_authentication/templates/models/agency.rb.tt +8 -2
  20. data/lib/generators/propel_authentication/templates/models/agent.rb.tt +13 -2
  21. data/lib/generators/propel_authentication/templates/models/invitation.rb.tt +3 -1
  22. data/lib/generators/propel_authentication/templates/models/organization.rb.tt +15 -2
  23. data/lib/generators/propel_authentication/templates/models/user.rb.tt +18 -2
  24. data/lib/generators/propel_authentication/templates/propel_authentication.rb.tt +33 -6
  25. data/lib/generators/propel_authentication/templates/test/controllers/auth/lockable_integration_test.rb.tt +2 -2
  26. data/lib/generators/propel_authentication/templates/test/controllers/auth/password_reset_integration_test.rb.tt +9 -9
  27. data/lib/generators/propel_authentication/templates/test/controllers/auth/signup_controller_test.rb.tt +6 -6
  28. data/lib/generators/propel_authentication/templates/test/controllers/auth/tokens_controller_test.rb.tt +4 -4
  29. data/lib/generators/propel_authentication/test/generators/authentication/controllers/tokens_controller_test.rb +1 -1
  30. data/lib/generators/propel_authentication/test/generators/authentication/install_generator_test.rb +1 -1
  31. data/lib/generators/propel_authentication/test/integration/multi_version_generator_test.rb +12 -13
  32. data/lib/propel_authentication.rb +1 -1
  33. metadata +30 -5
  34. data/lib/generators/propel_authentication/templates/db/seeds.rb +0 -75
@@ -1,7 +1,4 @@
1
- class <%= auth_controller_class_name('tokens') %> < ApplicationController
2
- <%- unless api_only_app? -%>
3
- include RackSessionDisable
4
- <%- end -%>
1
+ class <%= auth_controller_class_name('tokens') %> < Api::BaseController
5
2
  include PropelAuthenticationConcern
6
3
 
7
4
  before_action :authenticate_user, only: [:show, :refresh]
@@ -42,7 +39,7 @@ class <%= auth_controller_class_name('tokens') %> < ApplicationController
42
39
  render json: { error: 'Invalid credentials' }, status: :unauthorized
43
40
  end
44
41
  rescue ActionController::ParameterMissing
45
- render json: { error: 'Missing required parameters' }, status: :unprocessable_entity
42
+ render json: { error: 'Missing required parameters' }, status: :bad_request
46
43
  end
47
44
 
48
45
  # DELETE <%= auth_route_prefix %>/logout
@@ -69,7 +66,7 @@ class <%= auth_controller_class_name('tokens') %> < ApplicationController
69
66
  token = params[:token]
70
67
 
71
68
  if token.blank?
72
- render json: { error: 'Token is required' }, status: :unprocessable_entity
69
+ render json: { error: 'Token is required' }, status: :unprocessable_content
73
70
  return
74
71
  end
75
72
 
@@ -107,7 +104,7 @@ class <%= auth_controller_class_name('tokens') %> < ApplicationController
107
104
 
108
105
  def validate_login_parameters
109
106
  unless params[:user].present? && params[:user][:email_address].present? && params[:user][:password].present?
110
- render json: { error: 'Email address and password are required' }, status: :unprocessable_entity
107
+ render json: { error: 'Email address and password are required' }, status: :unprocessable_content
111
108
  end
112
109
  end
113
110
  end
@@ -50,11 +50,14 @@ module PropelAuthentication
50
50
 
51
51
 
52
52
  # Single method to determine all authentication configuration
53
- # Handles namespace, version, and auth_scope with consistent priority logic
53
+ # Handles namespace, version, auth_scope, and tenancy options with consistent priority logic
54
54
  def determine_configuration
55
55
  @auth_namespace = determine_config_value(:namespace, nil)
56
56
  @auth_version = determine_config_value(:version, nil)
57
57
  @auth_scope = determine_config_value(:auth_scope, nil)
58
+ @tenancy_enabled = determine_tenancy_enabled
59
+ @organization_required = determine_organization_required
60
+ @agency_required = determine_agency_required
58
61
  end
59
62
 
60
63
  # Generic configuration value determination with consistent priority
@@ -187,5 +190,39 @@ module PropelAuthentication
187
190
 
188
191
  path_parts.join('/')
189
192
  end
193
+
194
+ # Determine tenancy_enabled configuration
195
+ # Priority: 1) Command line option, 2) Default (true)
196
+ def determine_tenancy_enabled
197
+ return options[:tenancy_enabled] unless options[:tenancy_enabled].nil?
198
+ true # Default to enabled
199
+ end
200
+
201
+ # Determine organization_required configuration
202
+ # Priority: 1) Command line option, 2) Default (true)
203
+ def determine_organization_required
204
+ return options[:organization_required] unless options[:organization_required].nil?
205
+ true # Default to required
206
+ end
207
+
208
+ # Determine agency_required configuration
209
+ # Priority: 1) Command line option, 2) Default (true)
210
+ def determine_agency_required
211
+ return options[:agency_required] unless options[:agency_required].nil?
212
+ true # Default to required
213
+ end
214
+
215
+ # Accessor methods for templates
216
+ def tenancy_enabled?
217
+ @tenancy_enabled
218
+ end
219
+
220
+ def organization_required?
221
+ @organization_required
222
+ end
223
+
224
+ def agency_required?
225
+ @agency_required
226
+ end
190
227
  end
191
228
  end
@@ -2,15 +2,17 @@ class CreateAgencies < ActiveRecord::Migration[8.0]
2
2
  def change
3
3
  create_table :agencies do |t|
4
4
  t.string :name, null: false
5
+ <% if PropelAuthentication.configuration.organization_required? %>
5
6
  t.references :organization, null: false, foreign_key: true
7
+ <% end %>
6
8
  t.string :phone_number
7
9
  t.string :address
8
10
  t.string :time_zone
9
11
  if ActiveRecord::Base.connection.adapter_name.downcase.starts_with?('postgresql')
10
- t.jsonb :meta, default: {}
12
+ t.jsonb :metadata, default: {}
11
13
  t.jsonb :settings, default: {}
12
14
  else
13
- t.json :meta, default: {}
15
+ t.json :metadata, default: {}
14
16
  t.json :settings, default: {}
15
17
  end
16
18
 
@@ -1,8 +1,13 @@
1
1
  class CreateAgents < ActiveRecord::Migration[8.0]
2
2
  def change
3
3
  create_table :agents do |t|
4
- t.references :user, null: false, foreign_key: true
4
+ <% if PropelAuthentication.configuration.organization_required? %>
5
+ t.references :organization, null: false, foreign_key: true
6
+ <% end %>
7
+ <% if PropelAuthentication.configuration.agency_required? %>
5
8
  t.references :agency, null: false, foreign_key: true
9
+ <% end %>
10
+ t.references :user, null: false, foreign_key: true
6
11
  t.string :role, null: false, default: 'agent'
7
12
  t.string :title
8
13
  t.timestamps
@@ -4,11 +4,13 @@ class CreateOrganizations < ActiveRecord::Migration[8.0]
4
4
  t.string :name, null: false
5
5
  t.string :website
6
6
  t.string :time_zone
7
+ t.string :logo
8
+ t.string :slug
7
9
  if ActiveRecord::Base.connection.adapter_name.downcase.starts_with?('postgresql')
8
- t.jsonb :meta, default: {}
10
+ t.jsonb :metadata, default: {}
9
11
  t.jsonb :settings, default: {}
10
12
  else
11
- t.json :meta, default: {}
13
+ t.json :metadata, default: {}
12
14
  t.json :settings, default: {}
13
15
  end
14
16
 
@@ -23,13 +23,15 @@ class CreateUsers < ActiveRecord::Migration[8.0]
23
23
  t.datetime :locked_at
24
24
 
25
25
  # Multi-tenant association
26
+ <% if PropelAuthentication.configuration.organization_required? %>
26
27
  t.references :organization, null: false, foreign_key: true
28
+ <% end %>
27
29
 
28
30
  if ActiveRecord::Base.connection.adapter_name.downcase.starts_with?('postgresql')
29
- t.jsonb :meta, default: {}
31
+ t.jsonb :metadata, default: {}
30
32
  t.jsonb :settings, default: {}
31
33
  else
32
- t.json :meta, default: {}
34
+ t.json :metadata, default: {}
33
35
  t.json :settings, default: {}
34
36
  end
35
37
 
@@ -0,0 +1,145 @@
1
+ # This file should ensure the existence of records required to run the application in every environment (production,
2
+ # development, test). The code here should be idempotent so that it can be executed at any point in every environment.
3
+ # The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).
4
+ #
5
+ # Example:
6
+ #
7
+ # ["Action", "Comedy", "Drama", "Horror"].each do |genre_name|
8
+ # MovieGenre.find_or_create_by!(name: genre_name)
9
+ # end
10
+
11
+ # Create comprehensive tenancy structure for authentication testing
12
+ puts "🏢 Creating test organization structure..."
13
+
14
+ organization = Organization.find_or_create_by!(name: 'Test Organization') do |org|
15
+ org.website = 'https://test.example.com'
16
+ org.time_zone = 'UTC'
17
+ end
18
+
19
+ # Create agencies for proper tenancy structure
20
+ <% if PropelAuthentication.configuration.agency_required? -%>
21
+ <% if PropelAuthentication.configuration.organization_required? -%>
22
+ # Full tenancy mode: agencies belong to organization
23
+ main_agency = Agency.find_or_create_by!(name: 'Main Department', organization: organization) do |agency|
24
+ agency.phone_number = '+1-555-0101'
25
+ agency.address = '123 Main Street'
26
+ end
27
+
28
+ support_agency = Agency.find_or_create_by!(name: 'Support Department', organization: organization) do |agency|
29
+ agency.phone_number = '+1-555-0102'
30
+ agency.address = '123 Support Street'
31
+ end
32
+ <% else -%>
33
+ # Simple mode: agencies are standalone
34
+ main_agency = Agency.find_or_create_by!(name: 'Main Department') do |agency|
35
+ agency.phone_number = '+1-555-0101'
36
+ agency.address = '123 Main Street'
37
+ end
38
+
39
+ support_agency = Agency.find_or_create_by!(name: 'Support Department') do |agency|
40
+ agency.phone_number = '+1-555-0102'
41
+ agency.address = '123 Support Street'
42
+ end
43
+ <% end -%>
44
+ <% end -%>
45
+
46
+ # Create test users with proper confirmations
47
+ <% if PropelAuthentication.configuration.organization_required? -%>
48
+ # Full tenancy mode: users belong to organization
49
+ user = User.find_or_create_by!(email_address: 'test@example.com') do |u|
50
+ u.username = 'testuser'
51
+ u.email_address = 'test@example.com'
52
+ u.password = 'password123'
53
+ u.password_confirmation = 'password123'
54
+ u.organization = organization
55
+ u.first_name = 'Test'
56
+ u.last_name = 'User'
57
+ u.confirmed_at = 1.week.ago # Confirmed user for easier testing
58
+ end
59
+
60
+ admin_user = User.find_or_create_by!(email_address: 'admin@example.com') do |u|
61
+ u.username = 'adminuser'
62
+ u.email_address = 'admin@example.com'
63
+ u.password = 'password123'
64
+ u.password_confirmation = 'password123'
65
+ u.organization = organization
66
+ u.first_name = 'Admin'
67
+ u.last_name = 'User'
68
+ u.confirmed_at = 1.week.ago # Confirmed admin for easier testing
69
+ end
70
+ <% else -%>
71
+ # Simple mode: users are standalone
72
+ user = User.find_or_create_by!(email_address: 'test@example.com') do |u|
73
+ u.username = 'testuser'
74
+ u.email_address = 'test@example.com'
75
+ u.password = 'password123'
76
+ u.password_confirmation = 'password123'
77
+ u.first_name = 'Test'
78
+ u.last_name = 'User'
79
+ u.confirmed_at = 1.week.ago # Confirmed user for easier testing
80
+ end
81
+
82
+ admin_user = User.find_or_create_by!(email_address: 'admin@example.com') do |u|
83
+ u.username = 'adminuser'
84
+ u.email_address = 'admin@example.com'
85
+ u.password = 'password123'
86
+ u.password_confirmation = 'password123'
87
+ u.first_name = 'Admin'
88
+ u.last_name = 'User'
89
+ u.confirmed_at = 1.week.ago # Confirmed admin for easier testing
90
+ end
91
+ <% end -%>
92
+
93
+ # Create agent associations for agency access (CRITICAL for tenancy validation)
94
+ <% if PropelAuthentication.configuration.agency_required? -%>
95
+ <% if PropelAuthentication.configuration.organization_required? -%>
96
+ # Full tenancy mode: agents belong to both organization and agency
97
+ Agent.find_or_create_by!(user: user, agency: main_agency, organization: organization) do |agent|
98
+ agent.role = 'member'
99
+ end
100
+
101
+ Agent.find_or_create_by!(user: admin_user, agency: main_agency, organization: organization) do |agent|
102
+ agent.role = 'manager'
103
+ end
104
+
105
+ Agent.find_or_create_by!(user: admin_user, agency: support_agency, organization: organization) do |agent|
106
+ agent.role = 'admin'
107
+ end
108
+ <% else -%>
109
+ # Simple mode: agents still need organization even if users don't require it
110
+ Agent.find_or_create_by!(user: user, agency: main_agency, organization: organization) do |agent|
111
+ agent.role = 'member'
112
+ end
113
+
114
+ Agent.find_or_create_by!(user: admin_user, agency: main_agency, organization: organization) do |agent|
115
+ agent.role = 'manager'
116
+ end
117
+
118
+ Agent.find_or_create_by!(user: admin_user, agency: support_agency, organization: organization) do |agent|
119
+ agent.role = 'admin'
120
+ end
121
+ <% end -%>
122
+ <% end -%>
123
+
124
+ <% if PropelAuthentication.configuration.organization_required? -%>
125
+ puts "✅ Created test organization: #{organization.name}"
126
+ <% if PropelAuthentication.configuration.agency_required? -%>
127
+ puts "✅ Created agencies: #{Agency.where(organization: organization).pluck(:name).join(', ')}"
128
+ <% end -%>
129
+ puts "✅ Created test user: #{user.email_address} (password: password123) - Organization: #{user.organization.name}"
130
+ puts "✅ Created admin user: #{admin_user.email_address} (password: password123) - Organization: #{admin_user.organization.name}"
131
+ puts "✅ Created agent associations for proper tenancy access"
132
+ puts ""
133
+ puts "🎯 Test user agency access: #{user.agency_ids}"
134
+ puts "🎯 Admin user agency access: #{admin_user.agency_ids}"
135
+ <% else -%>
136
+ puts "✅ Created agencies: #{Agency.pluck(:name).join(', ')}"
137
+ puts "✅ Created test user: #{user.email_address} (password: password123)"
138
+ puts "✅ Created admin user: #{admin_user.email_address} (password: password123)"
139
+ puts "✅ Created agent associations for agency access"
140
+ puts ""
141
+ puts "🎯 Test user agency access: #{user.agency_ids}"
142
+ puts "🎯 Admin user agency access: #{admin_user.agency_ids}"
143
+ <% end -%>
144
+ puts ""
145
+ puts "🚀 You can now test login with POST /api/v1/login"
@@ -12,12 +12,17 @@ The signup flow creates:
12
12
 
13
13
  ## Configuration
14
14
 
15
- Agency tenancy can be enabled/disabled in `config/initializers/propel_api.rb`:
15
+ Multi-tenancy settings can be configured in `config/initializers/propel_authentication.rb`:
16
16
 
17
17
  ```ruby
18
- PropelApi.configure do |config|
19
- config.agency_tenancy = true # Enable agency-level tenancy
20
- # config.agency_tenancy = false # Disable for organization-only tenancy
18
+ PropelAuthentication.configure do |config|
19
+ # Multi-tenancy master switch
20
+ config.tenancy_enabled = true # Enable/disable entire tenancy system
21
+
22
+ # Individual tenancy model requirements
23
+ config.organization_required = true # Users must belong to organizations
24
+ config.agency_required = true # Enable agency-level tenancy within orgs
25
+ # config.agency_required = false # Disable for organization-only tenancy
21
26
  end
22
27
  ```
23
28
 
@@ -0,0 +1,23 @@
1
+ marketing_agency:
2
+ name: "Marketing Solutions"
3
+ organization: acme_org
4
+ created_at: <%= 10.days.ago %>
5
+ updated_at: <%= 2.days.ago %>
6
+
7
+ sales_agency:
8
+ name: "Sales Department"
9
+ organization: acme_org
10
+ created_at: <%= 9.days.ago %>
11
+ updated_at: <%= 2.days.ago %>
12
+
13
+ tech_agency:
14
+ name: "Tech Solutions"
15
+ organization: tech_startup
16
+ created_at: <%= 8.days.ago %>
17
+ updated_at: <%= 1.day.ago %>
18
+
19
+ support_agency:
20
+ name: "Support Team"
21
+ organization: tech_startup
22
+ created_at: <%= 7.days.ago %>
23
+ updated_at: <%= 1.day.ago %>
@@ -0,0 +1,71 @@
1
+ john_marketing_agent:
2
+ user: john_user
3
+ <% if PropelAuthentication.configuration.organization_required? -%>
4
+ organization: acme_org
5
+ <% end -%>
6
+ <% if PropelAuthentication.configuration.agency_required? -%>
7
+ agency: marketing_agency
8
+ <% end -%>
9
+ role: "manager"
10
+ created_at: <%= 8.days.ago %>
11
+ updated_at: <%= 1.day.ago %>
12
+
13
+ confirmed_sales_agent:
14
+ user: confirmed_user
15
+ <% if PropelAuthentication.configuration.organization_required? -%>
16
+ organization: acme_org
17
+ <% end -%>
18
+ <% if PropelAuthentication.configuration.agency_required? -%>
19
+ agency: sales_agency
20
+ <% end -%>
21
+ role: "analyst"
22
+ created_at: <%= 7.days.ago %>
23
+ updated_at: <%= 1.day.ago %>
24
+
25
+ locked_marketing_agent:
26
+ user: locked_user
27
+ <% if PropelAuthentication.configuration.organization_required? -%>
28
+ organization: acme_org
29
+ <% end -%>
30
+ <% if PropelAuthentication.configuration.agency_required? -%>
31
+ agency: marketing_agency
32
+ <% end -%>
33
+ role: "coordinator"
34
+ created_at: <%= 6.days.ago %>
35
+ updated_at: <%= 1.day.ago %>
36
+
37
+ jane_tech_agent:
38
+ user: jane_user
39
+ <% if PropelAuthentication.configuration.organization_required? -%>
40
+ organization: tech_startup
41
+ <% end -%>
42
+ <% if PropelAuthentication.configuration.agency_required? -%>
43
+ agency: tech_agency
44
+ <% end -%>
45
+ role: "developer"
46
+ created_at: <%= 5.days.ago %>
47
+ updated_at: <%= 1.day.ago %>
48
+
49
+ expired_support_agent:
50
+ user: expired_confirmation_user
51
+ <% if PropelAuthentication.configuration.organization_required? -%>
52
+ organization: tech_startup
53
+ <% end -%>
54
+ <% if PropelAuthentication.configuration.agency_required? -%>
55
+ agency: support_agency
56
+ <% end -%>
57
+ role: "specialist"
58
+ created_at: <%= 4.days.ago %>
59
+ updated_at: <%= 1.day.ago %>
60
+
61
+ locked_sales_agent:
62
+ user: locked_user
63
+ <% if PropelAuthentication.configuration.organization_required? -%>
64
+ organization: acme_org
65
+ <% end -%>
66
+ <% if PropelAuthentication.configuration.agency_required? -%>
67
+ agency: sales_agency
68
+ <% end -%>
69
+ role: "trainee"
70
+ created_at: <%= 3.days.ago %>
71
+ updated_at: <%= 1.day.ago %>
@@ -0,0 +1,9 @@
1
+ pending_invitation:
2
+ email_address: "newuser@example.com"
3
+ first_name: "New"
4
+ last_name: "User"
5
+ organization: acme_org
6
+ inviter: john_user
7
+ status: "pending"
8
+ created_at: <%= 3.days.ago %>
9
+ updated_at: <%= 3.days.ago %>
@@ -0,0 +1,21 @@
1
+ acme_org:
2
+ name: "Acme Corporation"
3
+ website: "https://acme-corp.com"
4
+ time_zone: "UTC"
5
+ logo: "https://acme-corp.com/logo.png"
6
+ slug: "acme-corporation"
7
+ metadata: { org_type: "enterprise", industry: "technology", size: "large" }
8
+ settings: { theme: "corporate", timezone: "EST", features: "premium" }
9
+ created_at: <%= 30.days.ago %>
10
+ updated_at: <%= 1.day.ago %>
11
+
12
+ tech_startup:
13
+ name: "Tech Startup Inc"
14
+ website: "https://techstartup.io"
15
+ time_zone: "America/New_York"
16
+ logo: "https://techstartup.io/logo.png"
17
+ slug: "tech-startup-inc"
18
+ metadata: { org_type: "startup", industry: "software", size: "small" }
19
+ settings: { theme: "modern", timezone: "PST", features: "basic" }
20
+ created_at: <%= 15.days.ago %>
21
+ updated_at: <%= 2.days.ago %>
@@ -0,0 +1,77 @@
1
+ john_user:
2
+ email_address: "john@example.com"
3
+ username: "john_doe"
4
+ first_name: "John"
5
+ last_name: "Doe"
6
+ password_digest: "<%= BCrypt::Password.create('password123') %>"
7
+ organization: acme_org
8
+ status: "active"
9
+ confirmed_at: null
10
+ confirmation_token: "<%= SecureRandom.hex(32) %>"
11
+ confirmation_sent_at: <%= 1.hour.ago %>
12
+ metadata: { user_type: "admin", department: "engineering", priority: "high" }
13
+ settings: { theme: "dark", language: "en", notifications: true }
14
+ created_at: <%= 7.days.ago %>
15
+ updated_at: <%= 1.day.ago %>
16
+
17
+ jane_user:
18
+ email_address: "jane@example.com"
19
+ username: "jane_smith"
20
+ first_name: "Jane"
21
+ last_name: "Smith"
22
+ password_digest: "<%= BCrypt::Password.create('password123') %>"
23
+ organization: tech_startup
24
+ status: "active"
25
+ confirmed_at: null
26
+ failed_login_attempts: 0
27
+ locked_at: null
28
+ metadata: { user_type: "manager", department: "marketing", priority: "high" }
29
+ settings: { theme: "light", language: "en", notifications: false }
30
+ created_at: <%= 5.days.ago %>
31
+ updated_at: <%= 1.day.ago %>
32
+
33
+ confirmed_user:
34
+ email_address: "confirmed@example.com"
35
+ username: "confirmed_user"
36
+ first_name: "Confirmed"
37
+ last_name: "User"
38
+ password_digest: "<%= BCrypt::Password.create('password123') %>"
39
+ organization: acme_org
40
+ status: "active"
41
+ confirmed_at: <%= 7.days.ago %>
42
+ metadata: { user_type: "employee", department: "sales", priority: "medium" }
43
+ settings: { theme: "auto", language: "es", notifications: true }
44
+ created_at: <%= 14.days.ago %>
45
+ updated_at: <%= 1.day.ago %>
46
+
47
+ locked_user:
48
+ email_address: "locked@example.com"
49
+ username: "locked_user"
50
+ first_name: "Locked"
51
+ last_name: "User"
52
+ password_digest: "<%= BCrypt::Password.create('password123') %>"
53
+ organization: acme_org
54
+ status: "active"
55
+ confirmed_at: <%= 10.days.ago %>
56
+ failed_login_attempts: 5
57
+ locked_at: <%= 1.hour.ago %>
58
+ metadata: { user_type: "employee", department: "support", priority: "low" }
59
+ settings: { theme: "light", language: "en", notifications: false }
60
+ created_at: <%= 20.days.ago %>
61
+ updated_at: <%= 1.hour.ago %>
62
+
63
+ expired_confirmation_user:
64
+ email_address: "expired@example.com"
65
+ username: "expired_user"
66
+ first_name: "Expired"
67
+ last_name: "User"
68
+ password_digest: "<%= BCrypt::Password.create('password123') %>"
69
+ organization: tech_startup
70
+ status: "active"
71
+ confirmed_at: null
72
+ confirmation_token: "<%= SecureRandom.hex(32) %>"
73
+ confirmation_sent_at: <%= 25.hours.ago %>
74
+ metadata: { user_type: "test", department: "qa", priority: "low" }
75
+ settings: { theme: "dark", language: "fr", notifications: false }
76
+ created_at: <%= 30.days.ago %>
77
+ updated_at: <%= 25.hours.ago %>
@@ -1,13 +1,19 @@
1
1
  class Agency < ApplicationRecord
2
2
  # Multi-tenant associations
3
+ <% if PropelAuthentication.configuration.organization_required? -%>
3
4
  belongs_to :organization
5
+ <% end -%>
6
+ <% if PropelAuthentication.configuration.agency_required? -%>
4
7
  has_many :agents, dependent: :destroy
8
+ <% end -%>
5
9
 
6
10
  validates :name, presence: true
7
11
  <% if @rendering_engine == 'json_facet' -%>
8
12
 
9
13
  # Facets
10
- json_facet :short, fields: [:id, :name]
11
- json_facet :details, fields: [:id, :name, :organization_id, :created_at, :updated_at]
14
+ json_facet :short,
15
+ fields: [:id, :name]
16
+ json_facet :details,
17
+ fields: [:id, :name, :organization_id, :created_at, :updated_at]
12
18
  <% end -%>
13
19
  end
@@ -1,13 +1,24 @@
1
1
  class Agent < ApplicationRecord
2
2
  # Multi-tenant associations
3
+ <% if PropelAuthentication.configuration.organization_required? -%>
4
+ belongs_to :organization
5
+ <% end -%>
6
+ <% if PropelAuthentication.configuration.agency_required? -%>
3
7
  belongs_to :agency
8
+ <% end -%>
4
9
  belongs_to :user
5
10
 
11
+ <% if PropelAuthentication.configuration.agency_required? -%>
6
12
  validates :user_id, uniqueness: { scope: :agency_id }
13
+ <% else -%>
14
+ validates :user_id, uniqueness: { scope: :organization_id }
15
+ <% end -%>
7
16
  <% if @rendering_engine == 'json_facet' -%>
8
17
 
9
18
  # Facets
10
- json_facet :short, fields: [:id, :title]
11
- json_facet :details, fields: [:id, :title, :role, :organization_id, :created_at, :updated_at]
19
+ json_facet :short,
20
+ fields: [:id, :title]
21
+ json_facet :details,
22
+ fields: [:id, :title, :role, :organization_id, :created_at, :updated_at]
12
23
  <% end -%>
13
24
  end
@@ -99,9 +99,11 @@ class Invitation < ApplicationRecord
99
99
  update!(status: :accepted)
100
100
 
101
101
  # Create agent relationship if needed
102
+ agency = self.organization.agencies.first || self.organization.agencies.create!(name: 'Default Agency')
102
103
  Agent.create!(
103
104
  user: user,
104
- agency: self.organization.agencies.first || self.organization.agencies.create!(name: 'Default Agency')
105
+ agency: agency,
106
+ organization: self.organization
105
107
  )
106
108
 
107
109
  user
@@ -2,11 +2,24 @@ class Organization < ApplicationRecord
2
2
  # Multi-tenant associations
3
3
  has_many :users, dependent: :destroy
4
4
  has_many :agencies, dependent: :destroy
5
+ <% if PropelAuthentication.configuration.agency_required? -%>
6
+ has_many :agents, dependent: :destroy
7
+ <% end -%>
5
8
 
6
9
  validates :name, presence: true
7
10
  <% if @rendering_engine == 'json_facet' -%>
8
11
  # Facets
9
- json_facet :short, fields: [:id, :name, :website, :time_zone]
10
- json_facet :details, fields: [:id, :name, :website, :time_zone, :meta, :settings, :created_at, :updated_at]
12
+ json_facet :short,
13
+ fields:
14
+ [
15
+ :id, :name, :website, :time_zone, :logo, :slug
16
+ ]
17
+ json_facet :details,
18
+ fields:
19
+ [
20
+ :id, :name, :website, :time_zone, :logo, :slug,
21
+ :created_at, :updated_at,
22
+ :metadata, :settings
23
+ ]
11
24
  <% end -%>
12
25
  end
@@ -21,6 +21,22 @@ class User < ApplicationRecord
21
21
  # include Statusable
22
22
 
23
23
  # Facets
24
- json_facet :short, fields: [:id, :email_address, :username, :phone_number, :first_name, :middle_name, :last_name, :time_zone, :status]
25
- json_facet :details, fields: [:id, :email_address, :username, :phone_number, :first_name, :middle_name, :last_name, :time_zone, :confirmation_sent_at, :unconfirmed_email_address, :confirmed_at, :status, :last_login_at, :failed_login_attempts, :locked_at, :organization_id, :meta, :settings, :created_at, :updated_at], include: [:organization]
24
+ json_facet :short,
25
+ fields:
26
+ [
27
+ :id, :email_address, :username, :phone_number,
28
+ :first_name, :middle_name, :last_name,
29
+ :time_zone, :status
30
+ ]
31
+ json_facet :details,
32
+ fields:
33
+ [
34
+ :id, :email_address, :username, :phone_number,
35
+ :first_name, :middle_name, :last_name,
36
+ :time_zone, :status,
37
+ :confirmation_sent_at, :unconfirmed_email_address, :confirmed_at,
38
+ :last_login_at, :failed_login_attempts, :locked_at,
39
+ :created_at, :updated_at,
40
+ :organization_id, :metadata, :settings
41
+ ], include: [:organization]
26
42
  end