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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +60 -0
- data/lib/generators/propel_authentication/install_generator.rb +190 -228
- data/lib/generators/propel_authentication/templates/auth/passwords_controller.rb.tt +10 -13
- data/lib/generators/propel_authentication/templates/auth/signup_controller.rb.tt +6 -9
- data/lib/generators/propel_authentication/templates/auth/tokens_controller.rb.tt +4 -7
- data/lib/generators/propel_authentication/templates/core/configuration_methods.rb +38 -1
- data/lib/generators/propel_authentication/templates/db/migrate/create_agencies.rb +4 -2
- data/lib/generators/propel_authentication/templates/db/migrate/create_agents.rb +6 -1
- data/lib/generators/propel_authentication/templates/db/migrate/create_organizations.rb +4 -2
- data/lib/generators/propel_authentication/templates/db/migrate/create_users.rb +4 -2
- data/lib/generators/propel_authentication/templates/db/propel_seeds.rb.tt +145 -0
- data/lib/generators/propel_authentication/templates/doc/signup_flow.md +9 -4
- data/lib/generators/propel_authentication/templates/fixtures/agencies.yml.erb +23 -0
- data/lib/generators/propel_authentication/templates/fixtures/agents.yml.erb +71 -0
- data/lib/generators/propel_authentication/templates/fixtures/invitations.yml.erb +9 -0
- data/lib/generators/propel_authentication/templates/fixtures/organizations.yml.erb +21 -0
- data/lib/generators/propel_authentication/templates/fixtures/users.yml.erb +77 -0
- data/lib/generators/propel_authentication/templates/models/agency.rb.tt +8 -2
- data/lib/generators/propel_authentication/templates/models/agent.rb.tt +13 -2
- data/lib/generators/propel_authentication/templates/models/invitation.rb.tt +3 -1
- data/lib/generators/propel_authentication/templates/models/organization.rb.tt +15 -2
- data/lib/generators/propel_authentication/templates/models/user.rb.tt +18 -2
- data/lib/generators/propel_authentication/templates/propel_authentication.rb.tt +33 -6
- data/lib/generators/propel_authentication/templates/test/controllers/auth/lockable_integration_test.rb.tt +2 -2
- data/lib/generators/propel_authentication/templates/test/controllers/auth/password_reset_integration_test.rb.tt +9 -9
- data/lib/generators/propel_authentication/templates/test/controllers/auth/signup_controller_test.rb.tt +6 -6
- data/lib/generators/propel_authentication/templates/test/controllers/auth/tokens_controller_test.rb.tt +4 -4
- data/lib/generators/propel_authentication/test/generators/authentication/controllers/tokens_controller_test.rb +1 -1
- data/lib/generators/propel_authentication/test/generators/authentication/install_generator_test.rb +1 -1
- data/lib/generators/propel_authentication/test/integration/multi_version_generator_test.rb +12 -13
- data/lib/propel_authentication.rb +1 -1
- metadata +30 -5
- data/lib/generators/propel_authentication/templates/db/seeds.rb +0 -75
@@ -1,7 +1,4 @@
|
|
1
|
-
class <%= auth_controller_class_name('tokens') %> <
|
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: :
|
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: :
|
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: :
|
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
|
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 :
|
12
|
+
t.jsonb :metadata, default: {}
|
11
13
|
t.jsonb :settings, default: {}
|
12
14
|
else
|
13
|
-
t.json :
|
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
|
-
|
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 :
|
10
|
+
t.jsonb :metadata, default: {}
|
9
11
|
t.jsonb :settings, default: {}
|
10
12
|
else
|
11
|
-
t.json :
|
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 :
|
31
|
+
t.jsonb :metadata, default: {}
|
30
32
|
t.jsonb :settings, default: {}
|
31
33
|
else
|
32
|
-
t.json :
|
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
|
-
|
15
|
+
Multi-tenancy settings can be configured in `config/initializers/propel_authentication.rb`:
|
16
16
|
|
17
17
|
```ruby
|
18
|
-
|
19
|
-
|
20
|
-
|
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,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,
|
11
|
-
|
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,
|
11
|
-
|
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:
|
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,
|
10
|
-
|
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,
|
25
|
-
|
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
|