practical 0.1.0 → 3.0.0.pre.alpha1
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/README.md +4 -4
- data/app/components/practical/views/flash_messages_component.rb +0 -1
- data/app/components/practical/views/form/fallback_errors_section_component.rb +5 -3
- data/app/components/practical/views/form/option_label_component.rb +0 -1
- data/app/components/practical/views/navigation/breadcrumb_item_component.rb +0 -1
- data/app/components/practical/views/navigation/breadcrumbs_component.rb +2 -1
- data/app/{controllers/concerns/practical/auth/passkeys → concerns/practical/auth/passkeys/controllers}/emergency_registrations.rb +2 -2
- data/app/{controllers/concerns/practical/auth/passkeys → concerns/practical/auth/passkeys/controllers}/web_authn_debug_context.rb +1 -1
- data/app/concerns/practical/memberships/controllers/membership_invitations/register_with_passkey.rb +92 -0
- data/app/lib/practical/forms/datatables/base.rb +80 -0
- data/app/lib/practical/loaders/base.rb +44 -0
- data/app/lib/practical/relation_builders/base.rb +35 -0
- data/app/lib/practical/test/shared/attachment/models/attachment/base.rb +123 -0
- data/app/lib/practical/test/shared/attachment/models/attachment/for_organization.rb +39 -0
- data/app/lib/practical/test/shared/attachment/models/organization/has_attachments.rb +12 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/emergency_registration/base.rb +9 -6
- data/app/lib/practical/test/shared/auth/passkeys/controllers/emergency_registration/cross_pollination.rb +49 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/passkey_management/base.rb +508 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/reauthentication/base.rb +27 -9
- data/app/lib/practical/test/shared/auth/passkeys/controllers/reauthentication/cross_pollination.rb +19 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/self_destroy.rb +26 -8
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/self_signup.rb +3 -2
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/update.rb +55 -19
- data/app/lib/practical/test/shared/auth/passkeys/controllers/sessions/cross_pollination.rb +29 -0
- data/app/lib/practical/test/shared/auth/passkeys/forms/emergency_registration.rb +0 -1
- data/app/lib/practical/test/shared/auth/passkeys/models/{passkey.rb → passkey/base.rb} +1 -1
- data/app/lib/practical/test/shared/auth/passkeys/models/passkey/emergency_registration.rb +23 -0
- data/app/lib/practical/test/shared/auth/passkeys/models/{resource_with_passkeys.rb → resource_with_passkeys/base.rb} +1 -1
- data/app/lib/practical/test/shared/auth/passkeys/models/resource_with_passkeys/emergency_registration.rb +41 -0
- data/app/lib/practical/test/shared/memberships/controllers/membership_invitations/base.rb +165 -0
- data/app/lib/practical/test/shared/memberships/controllers/membership_invitations/register_with_passkey.rb +417 -0
- data/app/lib/practical/test/shared/memberships/controllers/organization/membership.rb +400 -0
- data/app/lib/practical/test/shared/memberships/controllers/organization/membership_invitation.rb +148 -0
- data/app/lib/practical/test/shared/memberships/controllers/user/membership.rb +119 -0
- data/app/lib/practical/test/shared/memberships/controllers/user/membership_invitation.rb +57 -0
- data/app/lib/practical/test/shared/memberships/forms/create_new_user_with_membership_invitation.rb +197 -0
- data/app/lib/practical/test/shared/memberships/forms/organization/membership.rb +162 -0
- data/app/lib/practical/test/shared/memberships/forms/organization/new_membership_invitation.rb +195 -0
- data/app/lib/practical/test/shared/memberships/forms/user/membership.rb +87 -0
- data/app/lib/practical/test/shared/memberships/models/membership/base.rb +45 -0
- data/app/lib/practical/test/shared/memberships/models/membership_invitation/base.rb +85 -0
- data/app/lib/practical/test/shared/memberships/models/membership_invitation/sending.rb +76 -0
- data/app/lib/practical/test/shared/memberships/models/membership_invitation/use_for_and_notify.rb +55 -0
- data/app/lib/practical/test/shared/memberships/models/organization/base.rb +25 -0
- data/app/lib/practical/test/shared/memberships/models/user/base.rb +23 -0
- data/app/lib/practical/test/shared/memberships/policies/organization/base_resource.rb +29 -0
- data/app/lib/practical/test/shared/memberships/policies/organization/membership.rb +103 -0
- data/app/lib/practical/test/shared/memberships/policies/organization/membership_invitation.rb +94 -0
- data/app/lib/practical/test/shared/memberships/policies/organization/resource/inherits.rb +10 -0
- data/app/lib/practical/test/shared/memberships/policies/organization.rb +70 -0
- data/app/lib/practical/test/shared/memberships/policies/user/membership.rb +78 -0
- data/app/lib/practical/test/shared/memberships/policies/user/membership_invitation.rb +31 -0
- data/app/lib/practical/test/shared/models/normalized_email.rb +0 -1
- data/app/lib/practical/test/shared/policies/user/base.rb +14 -0
- data/app/lib/practical/views/error_handling.rb +2 -0
- data/app/lib/practical/views/error_response.rb +27 -0
- data/app/lib/practical/views/form_builders/base.rb +5 -4
- data/app/lib/practical/views/form_builders/collection_option.rb +5 -0
- data/app/lib/practical/views/icon_set.rb +12 -6
- data/config/locales/auth.en.yml +18 -0
- data/config/locales/memberships.en.yml +129 -0
- data/db/seeds/memberships/default.rb +68 -0
- data/db/seeds/moderators/default.rb +36 -0
- data/db/seeds/setup.rb +16 -0
- data/db/seeds/test/cases/membership_invitations.rb +31 -0
- data/db/seeds/users/default.rb +17 -15
- data/lib/generators/practical/test/shared_test/shared_test_generator.rb +2 -0
- data/lib/practical/framework/engine.rb +8 -0
- data/lib/practical/helpers/honeybadger_helper.rb +11 -0
- data/lib/practical/helpers/selector_helper.rb +8 -0
- data/lib/practical/version.rb +1 -1
- data/lib/practical/views/element_helper.rb +2 -0
- data/lib/practical/views/theme_helper.rb +13 -0
- data/lib/practical.rb +4 -1
- data/lib/tasks/practical/utility.rake +20 -0
- metadata +54 -11
- data/lib/tasks/practical/framework_tasks.rake +0 -6
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Policies::User::Membership
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
def assert_can_manage(membership:, user:)
|
8
|
+
assert_equal true, policy_for(membership: membership, user: user).apply(:manage?)
|
9
|
+
end
|
10
|
+
|
11
|
+
def assert_cannot_manage(membership:, user:)
|
12
|
+
assert_equal false, policy_for(membership: membership, user: user).apply(:manage?)
|
13
|
+
end
|
14
|
+
|
15
|
+
def assert_can_leave(membership:, user:)
|
16
|
+
assert_equal true, policy_for(membership: membership, user: user).apply(:leave?)
|
17
|
+
end
|
18
|
+
|
19
|
+
def assert_cannot_leave(membership:, user:)
|
20
|
+
assert_equal false, policy_for(membership: membership, user: user).apply(:leave?)
|
21
|
+
end
|
22
|
+
|
23
|
+
test "manage?: only true for the user's memberships" do
|
24
|
+
staff_membership = users.works_at_org_1_and_2.memberships.first
|
25
|
+
|
26
|
+
assert_can_manage(membership: staff_membership, user: users.works_at_org_1_and_2)
|
27
|
+
assert_cannot_manage(membership: staff_membership, user: users.organization_1_owner)
|
28
|
+
assert_cannot_manage(membership: staff_membership, user: users.organization_1_manager)
|
29
|
+
assert_cannot_manage(membership: staff_membership, user: users.organization_2_owner)
|
30
|
+
assert_cannot_manage(membership: staff_membership, user: users.organization_1_staff)
|
31
|
+
assert_cannot_manage(membership: staff_membership, user: users.retired_staff)
|
32
|
+
assert_cannot_manage(membership: staff_membership, user: users.archived_organization_1_manager)
|
33
|
+
|
34
|
+
organization_manager_membership = users.organization_1_manager.memberships.first
|
35
|
+
|
36
|
+
assert_can_manage(membership: organization_manager_membership, user: users.organization_1_manager)
|
37
|
+
assert_cannot_manage(membership: organization_manager_membership, user: users.organization_1_owner)
|
38
|
+
assert_cannot_manage(membership: organization_manager_membership, user: users.organization_2_owner)
|
39
|
+
assert_cannot_manage(membership: organization_manager_membership, user: users.works_at_org_1_and_2)
|
40
|
+
assert_cannot_manage(membership: organization_manager_membership, user: users.retired_staff)
|
41
|
+
assert_cannot_manage(membership: organization_manager_membership, user: users.archived_organization_1_manager)
|
42
|
+
end
|
43
|
+
|
44
|
+
test "leave?: only true if the membership_type is not organization_manager && there are multiple organization_managers for the organization" do
|
45
|
+
staff_membership = users.works_at_org_1_and_2.memberships.first
|
46
|
+
|
47
|
+
assert_can_leave(membership: staff_membership, user: users.works_at_org_1_and_2)
|
48
|
+
assert_cannot_leave(membership: staff_membership, user: users.organization_1_owner)
|
49
|
+
assert_cannot_leave(membership: staff_membership, user: users.organization_1_manager)
|
50
|
+
assert_cannot_leave(membership: staff_membership, user: users.organization_2_owner)
|
51
|
+
assert_cannot_leave(membership: staff_membership, user: users.organization_1_staff)
|
52
|
+
assert_cannot_leave(membership: staff_membership, user: users.retired_staff)
|
53
|
+
assert_cannot_leave(membership: staff_membership, user: users.archived_organization_1_manager)
|
54
|
+
|
55
|
+
organization_manager_membership = users.organization_1_manager.memberships.first
|
56
|
+
|
57
|
+
assert_can_leave(membership: organization_manager_membership, user: users.organization_1_manager)
|
58
|
+
assert_cannot_leave(membership: organization_manager_membership, user: users.organization_1_owner)
|
59
|
+
assert_cannot_leave(membership: organization_manager_membership, user: users.organization_2_owner)
|
60
|
+
assert_cannot_leave(membership: organization_manager_membership, user: users.works_at_org_1_and_2)
|
61
|
+
assert_cannot_leave(membership: organization_manager_membership, user: users.retired_staff)
|
62
|
+
assert_cannot_leave(membership: organization_manager_membership, user: users.archived_organization_1_manager)
|
63
|
+
|
64
|
+
|
65
|
+
only_manager_membership = users.organization_2_owner.memberships.first
|
66
|
+
|
67
|
+
assert_cannot_leave(membership: only_manager_membership, user: users.organization_1_manager)
|
68
|
+
assert_cannot_leave(membership: only_manager_membership, user: users.organization_1_owner)
|
69
|
+
assert_cannot_leave(membership: only_manager_membership, user: users.organization_2_owner)
|
70
|
+
assert_cannot_leave(membership: only_manager_membership, user: users.works_at_org_1_and_2)
|
71
|
+
assert_cannot_leave(membership: only_manager_membership, user: users.retired_staff)
|
72
|
+
assert_cannot_leave(membership: only_manager_membership, user: users.archived_organization_1_manager)
|
73
|
+
|
74
|
+
organization_1_staff_membership = users.organization_1_staff.memberships.first
|
75
|
+
assert_can_leave(membership: organization_1_staff_membership, user: users.organization_1_staff)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Policies::User::MembershipInvitation
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
def assert_can_manage(invitation:, user:)
|
8
|
+
assert_equal true, policy_for(invitation: invitation, user: user).apply(:manage?)
|
9
|
+
end
|
10
|
+
|
11
|
+
def assert_cannot_manage(invitation:, user:)
|
12
|
+
assert_equal false, policy_for(invitation: invitation, user: user).apply(:manage?)
|
13
|
+
end
|
14
|
+
|
15
|
+
test "failing manage?: only true for the user's unused invitations" do
|
16
|
+
seed "cases/membership_invitations"
|
17
|
+
assert_can_manage(invitation: membership_invitations.one_invite_only, user: users.one_invite_only)
|
18
|
+
assert_cannot_manage(invitation: membership_invitations.one_invite_only, user: users.invite_organization_1_manager)
|
19
|
+
end
|
20
|
+
|
21
|
+
test "manage?: only true for the user's unused invitations" do
|
22
|
+
user = users.invited_user_1
|
23
|
+
membership_invitation = find_membership_invitation!(email: user.email)
|
24
|
+
assert_can_manage(invitation: membership_invitation, user: user)
|
25
|
+
|
26
|
+
user = users.invited_user_2
|
27
|
+
membership_invitation = find_membership_invitation!(email: user.email)
|
28
|
+
assert_cannot_manage(invitation: membership_invitation, user: user)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -14,7 +14,6 @@ module Practical::Test::Shared::Models::NormalizedEmail
|
|
14
14
|
assert_equal instance, model_class.find_by(email: original_email.upcase)
|
15
15
|
assert_equal normalized_email, model_class.find_by(email: original_email.upcase).email
|
16
16
|
|
17
|
-
|
18
17
|
instance.update!(email: "\n\n\t#{original_email.upcase}\n\n\t")
|
19
18
|
assert_equal instance, model_class.find_by(email: "\n\n\t#{original_email.upcase}\n\n\t")
|
20
19
|
assert_equal normalized_email, model_class.find_by(email: "\n\n\t#{original_email.upcase}\n\n\t").email
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Policies::User::Base
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
included do
|
6
|
+
test "manage?: only true for the user" do
|
7
|
+
user = users.user_1
|
8
|
+
other_user = users.user_2
|
9
|
+
|
10
|
+
assert_equal true, policy_class.new(user, user: user).apply(:manage?)
|
11
|
+
assert_equal false, policy_class.new(user, user: other_user).apply(:manage?)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Views::ErrorResponse
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def render_json_error(format:, model:)
|
7
|
+
format.json do
|
8
|
+
errors = Practical::Views::ErrorHandling.build_error_json(model: model, helpers: helpers)
|
9
|
+
yield(errors) if block_given?
|
10
|
+
render json: errors, status: :bad_request
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def render_html_error(action:, format:)
|
15
|
+
format.html do
|
16
|
+
yield if block_given?
|
17
|
+
render action, status: :bad_request
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_respond_to_model_validation_error(html_action:, model:)
|
22
|
+
respond_to do |format|
|
23
|
+
render_json_error(format: format, model: model)
|
24
|
+
render_html_error(action: html_action, format: format)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -94,10 +94,10 @@ class Practical::Views::FormBuilders::Base < ActionView::Helpers::FormBuilder
|
|
94
94
|
|
95
95
|
def radio_collection(field_method:, options:, collection_check_boxes_options: {})
|
96
96
|
collection_radio_buttons(field_method,
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
97
|
+
options,
|
98
|
+
:value,
|
99
|
+
:title,
|
100
|
+
collection_check_boxes_options
|
101
101
|
) do |collection_builder|
|
102
102
|
collection_builder.label(class: "wa-flank") do
|
103
103
|
template.safe_join([
|
@@ -136,6 +136,7 @@ class Practical::Views::FormBuilders::Base < ActionView::Helpers::FormBuilder
|
|
136
136
|
def fallback_error_section(blurb_key: :"practical_framework.forms.generic_error_blurb", id:, options: {})
|
137
137
|
template.render Practical::Views::Form::FallbackErrorsSectionComponent.new(
|
138
138
|
f: self,
|
139
|
+
id: id,
|
139
140
|
blurb: template.translate(blurb_key, raise: true),
|
140
141
|
options: options
|
141
142
|
)
|
@@ -129,9 +129,12 @@ class Practical::Views::IconSet
|
|
129
129
|
SpritesheetIconDefinition.new(method_name: :add_note_icon, icon_name: :"solid-note-circle-plus", library: :kit),
|
130
130
|
SpritesheetIconDefinition.new(method_name: :edit_note_icon, icon_name: :"solid-note-pen", library: :kit),
|
131
131
|
SpritesheetIconDefinition.new(method_name: :delete_note_icon, icon_name: :"solid-note-slash", library: :kit),
|
132
|
-
SpritesheetIconDefinition.new(method_name: :archive_membership_icon, icon_name: :"solid-id-badge-slash",
|
133
|
-
|
134
|
-
SpritesheetIconDefinition.new(method_name: :
|
132
|
+
SpritesheetIconDefinition.new(method_name: :archive_membership_icon, icon_name: :"solid-id-badge-slash",
|
133
|
+
library: :kit),
|
134
|
+
SpritesheetIconDefinition.new(method_name: :accepted_membership_invitation_icon,
|
135
|
+
icon_name: :"solid-warehouse-circle-check", library: :kit),
|
136
|
+
SpritesheetIconDefinition.new(method_name: :destroy_membership_invitation_icon,
|
137
|
+
icon_name: :"solid-warehouse-slash", library: :kit),
|
135
138
|
SpritesheetIconDefinition.new(method_name: :delete_note_icon, icon_name: :"solid-note-slash", library: :kit),
|
136
139
|
|
137
140
|
])
|
@@ -143,14 +146,17 @@ class Practical::Views::IconSet
|
|
143
146
|
end
|
144
147
|
|
145
148
|
def self.emergency_passkey_registration_icon
|
146
|
-
duotone_icon(name: :"light-emergency-on",
|
149
|
+
duotone_icon(name: :"light-emergency-on",
|
150
|
+
options: {style: "--secondary-color: var(--wa-color-danger-fill-loud); --secondary-opacity: 0.7;"})
|
147
151
|
end
|
148
152
|
|
149
153
|
def self.signin_icon
|
150
|
-
duotone_icon(name: :"person-to-portal",
|
154
|
+
duotone_icon(name: :"person-to-portal",
|
155
|
+
options: {style: "--secondary-color: var(--wa-color-brand-fill-normal); --secondary-opacity: 0.9;"})
|
151
156
|
end
|
152
157
|
|
153
158
|
def self.signout_icon
|
154
|
-
duotone_icon(name: :"person-from-portal",
|
159
|
+
duotone_icon(name: :"person-from-portal",
|
160
|
+
options: {style: "--secondary-color: var(--wa-color-brand-fill-normal); --secondary-opacity: 0.9;"})
|
155
161
|
end
|
156
162
|
end
|
data/config/locales/auth.en.yml
CHANGED
@@ -31,6 +31,24 @@ en:
|
|
31
31
|
title: Create an account
|
32
32
|
submit_button_title: "Sign up"
|
33
33
|
|
34
|
+
destroy:
|
35
|
+
form:
|
36
|
+
title: "Delete my account"
|
37
|
+
submit_button_title: "Verify & destroy my account"
|
38
|
+
contact_support_message:
|
39
|
+
html: "<p>Want to delete your account? Please contact customer support</p>"
|
40
|
+
|
41
|
+
passkeys:
|
42
|
+
section_title: "Passkeys"
|
43
|
+
current_passkeys_title: "My passkeys"
|
44
|
+
create:
|
45
|
+
form:
|
46
|
+
title: "Add passkey"
|
47
|
+
submit_button_title: "Add passkey"
|
48
|
+
destroy:
|
49
|
+
form:
|
50
|
+
submit_button_title: "Remove"
|
51
|
+
|
34
52
|
app_navigation:
|
35
53
|
user_settings:
|
36
54
|
home_title: "Home"
|
@@ -0,0 +1,129 @@
|
|
1
|
+
en:
|
2
|
+
app_navigation:
|
3
|
+
user_settings:
|
4
|
+
organizations_title: "Change Organization"
|
5
|
+
memberships_title: "Memberships"
|
6
|
+
organization_settings:
|
7
|
+
memberships_title: "Memberships"
|
8
|
+
organizations:
|
9
|
+
title: "Available Organizations"
|
10
|
+
|
11
|
+
organization_memberships:
|
12
|
+
title: "Organization Membership"
|
13
|
+
invitation_form:
|
14
|
+
title: "Invite someone to your organization"
|
15
|
+
submit_button_title: "Send invitation"
|
16
|
+
active:
|
17
|
+
title: "Active members"
|
18
|
+
pending_reacceptance:
|
19
|
+
title: "Waiting for response"
|
20
|
+
pending_invitations:
|
21
|
+
title: "Sent invitations"
|
22
|
+
archived:
|
23
|
+
title: "Archived members"
|
24
|
+
|
25
|
+
edit:
|
26
|
+
page_title: "Membership for %{name}"
|
27
|
+
link_title: "Edit"
|
28
|
+
form:
|
29
|
+
submit_button_title: "Update membership"
|
30
|
+
|
31
|
+
archive:
|
32
|
+
button_title: "Archive"
|
33
|
+
|
34
|
+
activate:
|
35
|
+
button_title: "Reactivate"
|
36
|
+
|
37
|
+
invitation:
|
38
|
+
resend:
|
39
|
+
button_title: "Resend"
|
40
|
+
revoke:
|
41
|
+
button_title: "Revoke"
|
42
|
+
|
43
|
+
invitation_sent_message: "Invitation sent to ‟%{email}”!"
|
44
|
+
invitation_revoked_message: "Invitation to ‟%{email}” has been revoked"
|
45
|
+
cannot_be_resent_message: "We were not able to resend this invitation; please contact support."
|
46
|
+
already_member_message: "Good news, everyone! They’re already a member!"
|
47
|
+
awaiting_reacceptance_message: "We’ve sent them an invitation to rejoin, please ask them to check their ‟Memberships” tab."
|
48
|
+
updated_message: "Updated membership!"
|
49
|
+
|
50
|
+
user_memberships:
|
51
|
+
title: "My Memberships"
|
52
|
+
active:
|
53
|
+
title: "Active memberships"
|
54
|
+
pending_reacceptance:
|
55
|
+
title: "Memberships awaiting my response"
|
56
|
+
pending_invitations:
|
57
|
+
title: "Membership invitations"
|
58
|
+
archived:
|
59
|
+
title: "Archived memberships"
|
60
|
+
|
61
|
+
archive:
|
62
|
+
button_title: "Leave"
|
63
|
+
|
64
|
+
invitation:
|
65
|
+
accept:
|
66
|
+
button_title: "Accept"
|
67
|
+
hide:
|
68
|
+
button_title: "Hide"
|
69
|
+
updated_message: "Updated membership!"
|
70
|
+
archived_message: "This membership has been archived."
|
71
|
+
|
72
|
+
invitation_hidden_message: "Hidden the invitation for %{organization_name}."
|
73
|
+
|
74
|
+
membership_invitations:
|
75
|
+
accept_form:
|
76
|
+
title: "Accept this invitation?"
|
77
|
+
description:
|
78
|
+
html: "<p>You have been invited to join a new organization.</p>"
|
79
|
+
details:
|
80
|
+
caption:
|
81
|
+
html: "Invitation details"
|
82
|
+
accept_as_current_user:
|
83
|
+
title: "Accept this invitation as %{name}"
|
84
|
+
description:
|
85
|
+
html: "<p>You’ll share the following information with %{organization_name}:</p>"
|
86
|
+
accept_button_title: "Accept invitation"
|
87
|
+
sign_out_button_title: "Actually, sign out"
|
88
|
+
new_user_from_invitation_form:
|
89
|
+
signin_button_title: "Wait! I have an account; let me sign in."
|
90
|
+
submit_button_title: "Sign up & Accept invitation"
|
91
|
+
accepted_message: "You’re now a member of %{organization_name}"
|
92
|
+
registered_message: "Your account has been created; you can login now!"
|
93
|
+
|
94
|
+
errors:
|
95
|
+
attributes:
|
96
|
+
sender:
|
97
|
+
cannot_manage_organization: "We were not able to send this; please contact support."
|
98
|
+
membership_type:
|
99
|
+
blank: "Please select a role."
|
100
|
+
resource:
|
101
|
+
cannot_access_organization: "We were not able to save this; please contact support."
|
102
|
+
activemodel:
|
103
|
+
errors:
|
104
|
+
models:
|
105
|
+
organization/new_membership_invitation_form:
|
106
|
+
attributes:
|
107
|
+
base:
|
108
|
+
cannot_be_resent: "We were not able to resend this invitation; please contact support."
|
109
|
+
|
110
|
+
organization/membership_form:
|
111
|
+
attributes:
|
112
|
+
state:
|
113
|
+
cannot_be_archived: "This member cannot be archived."
|
114
|
+
membership_type:
|
115
|
+
cannot_be_downgraded: "There must be at least one organization manager."
|
116
|
+
|
117
|
+
user/membership_form:
|
118
|
+
attributes:
|
119
|
+
state:
|
120
|
+
cannot_be_archived: "This membership cannot be archived."
|
121
|
+
cannot_be_changed: "This membership cannot be changed."
|
122
|
+
inclusion: "This membership can only be archived."
|
123
|
+
activerecord:
|
124
|
+
errors:
|
125
|
+
models:
|
126
|
+
membership:
|
127
|
+
attributes:
|
128
|
+
user:
|
129
|
+
taken: "This user is already part of this organization."
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
organization_1 = organizations.create(:organization_1,
|
4
|
+
name: Faker::Company.name
|
5
|
+
)
|
6
|
+
|
7
|
+
organization_2 = organizations.create(:organization_2,
|
8
|
+
name: Faker::Company.name
|
9
|
+
)
|
10
|
+
|
11
|
+
organization_3 = organizations.create(:organization_3,
|
12
|
+
name: Faker::Company.name
|
13
|
+
)
|
14
|
+
|
15
|
+
organization_1_owner = users.create_labeled(:organization_1_owner)
|
16
|
+
organization_2_owner = users.create_labeled(:organization_2_owner)
|
17
|
+
organization_3_owner = users.create_labeled(:organization_3_owner)
|
18
|
+
|
19
|
+
organization_1.memberships.create!(user: organization_1_owner, state: :active, membership_type: :organization_manager)
|
20
|
+
organization_2.memberships.create!(user: organization_2_owner, state: :active, membership_type: :organization_manager)
|
21
|
+
organization_3.memberships.create!(user: organization_3_owner, state: :active, membership_type: :organization_manager)
|
22
|
+
|
23
|
+
organization_1_manager = users.create_labeled(:organization_1_manager)
|
24
|
+
|
25
|
+
organization_1.memberships.create!(user: organization_1_manager,
|
26
|
+
state: :active,
|
27
|
+
membership_type: :organization_manager
|
28
|
+
)
|
29
|
+
|
30
|
+
works_at_org_1_and_2 = users.create_labeled(:works_at_org_1_and_2)
|
31
|
+
|
32
|
+
organization_1.memberships.create!(user: works_at_org_1_and_2, state: :active, membership_type: :staff)
|
33
|
+
organization_2.memberships.create!(user: works_at_org_1_and_2, state: :active, membership_type: :staff)
|
34
|
+
|
35
|
+
organization_1_staff = users.create_labeled(:organization_1_staff)
|
36
|
+
organization_2.memberships.create!(user: organization_1_staff, state: :pending_reacceptance, membership_type: :staff)
|
37
|
+
|
38
|
+
retired_staff = users.create_labeled(:retired_staff)
|
39
|
+
organization_1.memberships.create!(user: retired_staff, state: :archived_by_organization, membership_type: :staff)
|
40
|
+
|
41
|
+
archived_organization_1_manager = users.create_labeled(:archived_organization_1_manager)
|
42
|
+
organization_1.memberships.create!(user: archived_organization_1_manager,
|
43
|
+
state: :archived_by_organization,
|
44
|
+
membership_type: :organization_manager
|
45
|
+
)
|
46
|
+
|
47
|
+
invited_user_1 = users.create_labeled(:invited_user_1)
|
48
|
+
invited_user_2 = users.create_labeled(:invited_user_2)
|
49
|
+
|
50
|
+
organization_3.membership_invitations.create!(
|
51
|
+
email: Faker::Internet.email,
|
52
|
+
sender: organization_3_owner,
|
53
|
+
membership_type: :staff
|
54
|
+
)
|
55
|
+
|
56
|
+
organization_3.membership_invitations.create!(
|
57
|
+
email: invited_user_1.email,
|
58
|
+
sender: organization_3_owner,
|
59
|
+
membership_type: :staff
|
60
|
+
)
|
61
|
+
|
62
|
+
organization_3.membership_invitations.create!(
|
63
|
+
user: invited_user_2,
|
64
|
+
email: invited_user_2.email,
|
65
|
+
sender: organization_3_owner,
|
66
|
+
membership: organization_3.memberships.create!(user: invited_user_2, state: :active, membership_type: :staff),
|
67
|
+
membership_type: :staff
|
68
|
+
)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
moderator_1 = moderators.create_labeled(:moderator_1)
|
4
|
+
|
5
|
+
moderator_passkeys.create(:moderator_1_passkey,
|
6
|
+
unique_by: :moderator,
|
7
|
+
moderator: moderator_1,
|
8
|
+
label: "Passkey 1",
|
9
|
+
external_id: SecureRandom.hex,
|
10
|
+
public_key: SecureRandom.hex
|
11
|
+
)
|
12
|
+
|
13
|
+
moderator_1.emergency_passkey_registrations.create!
|
14
|
+
|
15
|
+
moderator_2 = moderators.create_labeled(:moderator_2)
|
16
|
+
|
17
|
+
moderator_passkeys.create(:moderator_2_passkey,
|
18
|
+
unique_by: :moderator,
|
19
|
+
moderator: moderator_2,
|
20
|
+
label: "Passkey 2",
|
21
|
+
external_id: SecureRandom.hex,
|
22
|
+
public_key: SecureRandom.hex
|
23
|
+
)
|
24
|
+
|
25
|
+
moderator_passkeys.create(:moderator_1_passkey_2,
|
26
|
+
unique_by: :moderator,
|
27
|
+
moderator: moderator_1,
|
28
|
+
label: "Passkey 3",
|
29
|
+
external_id: SecureRandom.hex,
|
30
|
+
public_key: SecureRandom.hex
|
31
|
+
)
|
32
|
+
|
33
|
+
moderator_1.emergency_passkey_registrations.create!(
|
34
|
+
used_at: 1.week.ago,
|
35
|
+
passkey: moderator_passkeys.moderator_1_passkey_2
|
36
|
+
)
|
data/db/seeds/setup.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
users.defaults(name: -> { Faker::Name.name },
|
2
4
|
email: -> { Faker::Internet.email },
|
3
5
|
webauthn_id: -> { SecureRandom.uuid }
|
@@ -10,4 +12,18 @@ end
|
|
10
12
|
|
11
13
|
def users.labeled_email(label)
|
12
14
|
"#{label}@example.com"
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
moderators.defaults(email: -> { Faker::Internet.email },
|
19
|
+
webauthn_id: -> { SecureRandom.uuid }
|
20
|
+
)
|
21
|
+
|
22
|
+
|
23
|
+
def moderators.create_labeled(label, email: labeled_email(label), **)
|
24
|
+
create(label, unique_by: :email, email: email, **)
|
25
|
+
end
|
26
|
+
|
27
|
+
def moderators.labeled_email(label)
|
28
|
+
"#{label}@example.com"
|
13
29
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
users.create_labeled(:one_invite_only) => one_invite_only_user
|
4
|
+
users.create_labeled(:only_used_invites)
|
5
|
+
users.create_labeled(:only_hidden_invites)
|
6
|
+
users.create_labeled(:multiple_open_invites)
|
7
|
+
users.create_labeled(:one_invite_of_each_type)
|
8
|
+
|
9
|
+
def create_dummy_organization(label:)
|
10
|
+
organizations.create(label, name: Faker::Company.name).tap do |organization|
|
11
|
+
organization.memberships.organization_manager.active.create!(user: users.create(:"#{label}_manager"))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
create_dummy_organization(label: :invite_organization_1)
|
16
|
+
create_dummy_organization(label: :invite_organization_2) => invite_organization_2
|
17
|
+
create_dummy_organization(label: :invite_organization_3)
|
18
|
+
|
19
|
+
def membership_invitations.create_labeled(label, email:, organization:, sender:)
|
20
|
+
create(label, email: email,
|
21
|
+
membership_type: :staff,
|
22
|
+
organization: organization,
|
23
|
+
sender: sender
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
membership_invitations.create_labeled(:one_invite_only, email: one_invite_only_user.email,
|
29
|
+
organization: invite_organization_2,
|
30
|
+
sender: invite_organization_2.users.first
|
31
|
+
)
|
data/db/seeds/users/default.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
user_1 = users.create_labeled(:user_1)
|
2
4
|
|
3
5
|
passkeys.create(:user_1_passkey,
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
unique_by: :user,
|
7
|
+
user: user_1,
|
8
|
+
label: "Passkey 1",
|
9
|
+
external_id: SecureRandom.hex,
|
10
|
+
public_key: SecureRandom.hex
|
9
11
|
)
|
10
12
|
|
11
13
|
user_1.emergency_passkey_registrations.create!
|
@@ -13,19 +15,19 @@ user_1.emergency_passkey_registrations.create!
|
|
13
15
|
user_2 = users.create_labeled(:user_2)
|
14
16
|
|
15
17
|
passkeys.create(:user_2_passkey,
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
unique_by: :user,
|
19
|
+
user: user_2,
|
20
|
+
label: "Passkey 2",
|
21
|
+
external_id: SecureRandom.hex,
|
22
|
+
public_key: SecureRandom.hex
|
21
23
|
)
|
22
24
|
|
23
25
|
passkeys.create(:user_1_passkey_2,
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
unique_by: :user,
|
27
|
+
user: user_1,
|
28
|
+
label: "Passkey 3",
|
29
|
+
external_id: SecureRandom.hex,
|
30
|
+
public_key: SecureRandom.hex
|
29
31
|
)
|
30
32
|
|
31
33
|
user_1.emergency_passkey_registrations.create!(
|
@@ -3,6 +3,11 @@
|
|
3
3
|
module Practical
|
4
4
|
module Framework
|
5
5
|
class Engine < ::Rails::Engine
|
6
|
+
rake_tasks do
|
7
|
+
load 'tasks/practical/coverage.rake'
|
8
|
+
load 'tasks/practical/utility.rake'
|
9
|
+
end
|
10
|
+
|
6
11
|
config.app_generators do |g|
|
7
12
|
g.apply_rubocop_autocorrect_after_generate!
|
8
13
|
g.test_framework :test_unit, fixture: false, fixture_replacement: :oaken
|
@@ -24,10 +29,13 @@ module Practical
|
|
24
29
|
initializer 'practical-framework.view_helpers' do
|
25
30
|
ActiveSupport.on_load(:action_view) do
|
26
31
|
include Practical::Framework::Engine::PracticalFrameworkFormBuilderActiveSupportExtension
|
32
|
+
include Practical::Views::ThemeHelper
|
27
33
|
include Practical::Helpers::FormWithHelper
|
28
34
|
include Practical::Helpers::IconHelper
|
29
35
|
include Practical::Helpers::TextHelper
|
30
36
|
include Practical::Helpers::TranslationHelper
|
37
|
+
include Practical::Helpers::SelectorHelper
|
38
|
+
include Practical::Helpers::HoneybadgerHelpers
|
31
39
|
end
|
32
40
|
end
|
33
41
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Helpers::HoneybadgerHelpers
|
4
|
+
def honeybadger_js_configuration
|
5
|
+
{
|
6
|
+
"apiKey" => AppSettings.honeybadger_js_api_key,
|
7
|
+
"environment" => AppSettings.app_env,
|
8
|
+
"revision" => AppSettings.app_revision
|
9
|
+
}
|
10
|
+
end
|
11
|
+
end
|
data/lib/practical/version.rb
CHANGED