bullet_train 1.0.0 → 1.0.4
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/app/models/concerns/invitations/core.rb +46 -0
- data/app/models/concerns/memberships/core.rb +137 -0
- data/app/models/concerns/teams/core.rb +51 -0
- data/app/models/concerns/users/core.rb +164 -0
- data/app/models/invitation.rb +1 -52
- data/app/models/invitations.rb +5 -0
- data/app/models/membership.rb +1 -143
- data/app/models/memberships.rb +5 -0
- data/app/models/team.rb +1 -59
- data/app/models/teams.rb +5 -0
- data/app/models/user.rb +1 -170
- data/app/models/users.rb +5 -0
- data/config/routes.rb +40 -0
- data/lib/bullet_train/version.rb +1 -1
- metadata +9 -12
- data/app/controllers/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments_controller.rb +0 -83
- data/app/models/memberships/reassignments/assignment.rb +0 -12
- data/app/models/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignment.rb +0 -38
- data/app/models/memberships/reassignments.rb +0 -5
- data/app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/_breadcrumbs.html.erb +0 -10
- data/app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/_form.html.erb +0 -24
- data/app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/_scaffolding_completely_concrete_tangible_things_reassignment.json.jbuilder +0 -8
- data/app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/index.json.jbuilder +0 -1
- data/app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/new.html.erb +0 -11
- data/app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/show.json.jbuilder +0 -1
- data/config/locales/en/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments.en.yml +0 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 533e95a2c22629bce2db6569181981dd661621d7a2b17347d210f782554ac6f9
|
4
|
+
data.tar.gz: 7d179440547451b7ff69480478a8f2cb481e79b4b4ee3e80e1f950b219768150
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '059cb145c72d2adf059902f5d0934d36362ad06ea6dc728a3951057b60758187e4f192e7bd24d62f0030fbd1c3d36e7797ed94ea3cd390b4c955a49fc4653301'
|
7
|
+
data.tar.gz: 108c4abc412f29b2321b1f6acc5c6c6e1e35d358fda6ba14f322dcfb49591c7ccc3c29ca10989e30ffcb915c5c2df727b9f4d4e044776f6e3a6f92d391f91389
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Invitations::Core
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
belongs_to :team
|
6
|
+
belongs_to :from_membership, class_name: "Membership"
|
7
|
+
has_one :membership, dependent: :nullify
|
8
|
+
has_many :roles, through: :membership
|
9
|
+
|
10
|
+
accepts_nested_attributes_for :membership
|
11
|
+
|
12
|
+
validates :email, presence: true
|
13
|
+
|
14
|
+
before_create :generate_uuid
|
15
|
+
after_create :set_added_by_membership
|
16
|
+
after_create :send_invitation_email
|
17
|
+
end
|
18
|
+
|
19
|
+
def set_added_by_membership
|
20
|
+
membership.update(added_by: from_membership)
|
21
|
+
end
|
22
|
+
|
23
|
+
def send_invitation_email
|
24
|
+
UserMailer.invited(uuid).deliver_later
|
25
|
+
end
|
26
|
+
|
27
|
+
def generate_uuid
|
28
|
+
self.uuid = SecureRandom.hex
|
29
|
+
end
|
30
|
+
|
31
|
+
def accept_for(user)
|
32
|
+
User.transaction do
|
33
|
+
user.memberships << membership
|
34
|
+
user.update(current_team: team, former_user: false)
|
35
|
+
destroy
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def name
|
40
|
+
I18n.t("invitations.values.name", team_name: team.name)
|
41
|
+
end
|
42
|
+
|
43
|
+
def is_for?(user)
|
44
|
+
user.email.downcase.strip == email.downcase.strip
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module Memberships::Core
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
# See `docs/permissions.md` for details.
|
6
|
+
include Roles::Support
|
7
|
+
|
8
|
+
belongs_to :user, optional: true
|
9
|
+
belongs_to :team
|
10
|
+
belongs_to :invitation, optional: true, dependent: :destroy
|
11
|
+
belongs_to :added_by, class_name: "Membership", optional: true
|
12
|
+
belongs_to :platform_agent_of, class_name: "Platform::Application", optional: true
|
13
|
+
|
14
|
+
has_many :scaffolding_completely_concrete_tangible_things_assignments, class_name: "Scaffolding::CompletelyConcrete::TangibleThings::Assignment", dependent: :destroy
|
15
|
+
has_many :scaffolding_completely_concrete_tangible_things, through: :scaffolding_completely_concrete_tangible_things_assignments, source: :tangible_thing
|
16
|
+
has_many :reassignments_scaffolding_completely_concrete_tangible_things_reassignments, class_name: "Memberships::Reassignments::ScaffoldingCompletelyConcreteTangibleThingsReassignment", dependent: :destroy, foreign_key: :membership_id
|
17
|
+
|
18
|
+
has_many :scaffolding_absolutely_abstract_creative_concepts_collaborators, class_name: "Scaffolding::AbsolutelyAbstract::CreativeConcepts::Collaborator", dependent: :destroy
|
19
|
+
|
20
|
+
after_destroy do
|
21
|
+
# if we're destroying a user's membership to the team they have set as
|
22
|
+
# current, then we need to remove that so they don't get an error.
|
23
|
+
if user&.current_team == team
|
24
|
+
user.current_team = nil
|
25
|
+
user.save
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
scope :current_and_invited, -> { includes(:invitation).where("user_id IS NOT NULL OR invitations.id IS NOT NULL").references(:invitation) }
|
30
|
+
scope :current, -> { where("user_id IS NOT NULL") }
|
31
|
+
scope :tombstones, -> { includes(:invitation).where("user_id IS NULL AND invitations.id IS NULL").references(:invitation) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def name
|
35
|
+
full_name
|
36
|
+
end
|
37
|
+
|
38
|
+
def label_string
|
39
|
+
full_name
|
40
|
+
end
|
41
|
+
|
42
|
+
# we overload this method so that when setting the list of role ids
|
43
|
+
# associated with a membership, admins can never remove the last admin
|
44
|
+
# of a team.
|
45
|
+
def role_ids=(ids)
|
46
|
+
# if this membership was an admin, and the new list of role ids don't include admin.
|
47
|
+
if admin? && !ids.include?(Role.admin.id)
|
48
|
+
unless team.admins.count > 1
|
49
|
+
raise RemovingLastTeamAdminException.new("You can't remove the last team admin.")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
super(ids)
|
54
|
+
end
|
55
|
+
|
56
|
+
def unclaimed?
|
57
|
+
user.nil? && !invitation.nil?
|
58
|
+
end
|
59
|
+
|
60
|
+
def tombstone?
|
61
|
+
user.nil? && invitation.nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
def last_admin?
|
65
|
+
return false unless admin?
|
66
|
+
return false unless user.present?
|
67
|
+
team.memberships.current.select(&:admin?) == [self]
|
68
|
+
end
|
69
|
+
|
70
|
+
def nullify_user
|
71
|
+
if last_admin?
|
72
|
+
raise RemovingLastTeamAdminException.new("You can't remove the last team admin.")
|
73
|
+
end
|
74
|
+
|
75
|
+
if (user_was = user)
|
76
|
+
unless user_first_name.present?
|
77
|
+
self.user_first_name = user.first_name
|
78
|
+
end
|
79
|
+
|
80
|
+
unless user_last_name.present?
|
81
|
+
self.user_last_name = user.last_name
|
82
|
+
end
|
83
|
+
|
84
|
+
unless user_profile_photo_id.present?
|
85
|
+
self.user_profile_photo_id = user.profile_photo_id
|
86
|
+
end
|
87
|
+
|
88
|
+
unless user_email.present?
|
89
|
+
self.user_email = user.email
|
90
|
+
end
|
91
|
+
|
92
|
+
self.user = nil
|
93
|
+
save
|
94
|
+
|
95
|
+
user_was.invalidate_ability_cache
|
96
|
+
|
97
|
+
user_was.update(
|
98
|
+
current_team: user_was.teams.first,
|
99
|
+
former_user: user_was.teams.empty?
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
# we do this here just in case by some weird chance an active membership had an invitation attached.
|
104
|
+
invitation&.destroy
|
105
|
+
end
|
106
|
+
|
107
|
+
def email
|
108
|
+
user&.email || user_email.presence || invitation&.email
|
109
|
+
end
|
110
|
+
|
111
|
+
def full_name
|
112
|
+
user&.full_name || [first_name.presence, last_name.presence].join(" ").presence || email
|
113
|
+
end
|
114
|
+
|
115
|
+
def first_name
|
116
|
+
user&.first_name || user_first_name
|
117
|
+
end
|
118
|
+
|
119
|
+
def last_name
|
120
|
+
user&.last_name || user_last_name
|
121
|
+
end
|
122
|
+
|
123
|
+
def last_initial
|
124
|
+
return nil unless last_name.present?
|
125
|
+
"#{last_name}."
|
126
|
+
end
|
127
|
+
|
128
|
+
def first_name_last_initial
|
129
|
+
[first_name, last_initial].map(&:present?).join(" ")
|
130
|
+
end
|
131
|
+
|
132
|
+
# TODO utilize this.
|
133
|
+
# members shouldn't receive notifications unless they are either an active user or an outstanding invitation.
|
134
|
+
def should_receive_notifications?
|
135
|
+
invitation.present? || user.present?
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Teams::Core
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
# super scaffolding
|
6
|
+
unless scaffolding_things_disabled?
|
7
|
+
has_many :scaffolding_absolutely_abstract_creative_concepts, class_name: "Scaffolding::AbsolutelyAbstract::CreativeConcept", dependent: :destroy, enable_updates: true
|
8
|
+
end
|
9
|
+
|
10
|
+
# memberships and invitations
|
11
|
+
has_many :memberships, dependent: :destroy
|
12
|
+
has_many :users, through: :memberships
|
13
|
+
has_many :invitations
|
14
|
+
|
15
|
+
# oauth for grape api
|
16
|
+
has_many :platform_applications, class_name: "Platform::Application", dependent: :destroy, foreign_key: :team_id
|
17
|
+
|
18
|
+
# integrations
|
19
|
+
has_many :integrations_stripe_installations, class_name: "Integrations::StripeInstallation", dependent: :destroy if stripe_enabled?
|
20
|
+
|
21
|
+
# validations
|
22
|
+
validates :name, presence: true
|
23
|
+
validates :time_zone, inclusion: {in: ActiveSupport::TimeZone.all.map(&:name)}, allow_nil: true
|
24
|
+
end
|
25
|
+
|
26
|
+
def admins
|
27
|
+
memberships.current_and_invited.admins
|
28
|
+
end
|
29
|
+
|
30
|
+
def admin_users
|
31
|
+
admins.map(&:user).compact
|
32
|
+
end
|
33
|
+
|
34
|
+
def primary_contact
|
35
|
+
admin_users.min { |user| user.created_at }
|
36
|
+
end
|
37
|
+
|
38
|
+
def formatted_email_address
|
39
|
+
primary_contact.email
|
40
|
+
end
|
41
|
+
|
42
|
+
def invalidate_caches
|
43
|
+
users.map(&:invalidate_ability_cache)
|
44
|
+
end
|
45
|
+
|
46
|
+
def team
|
47
|
+
# some generic features appeal to the `team` method for security or scoping purposes, but sometimes those same
|
48
|
+
# generic functions need to function for a team model as well, so we do this.
|
49
|
+
self
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module Users::Core
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
if two_factor_authentication_enabled?
|
6
|
+
devise :two_factor_authenticatable, :two_factor_backupable, :omniauthable,
|
7
|
+
:registerable, :recoverable, :rememberable, :trackable, :validatable,
|
8
|
+
otp_secret_encryption_key: ENV["TWO_FACTOR_ENCRYPTION_KEY"]
|
9
|
+
else
|
10
|
+
devise :omniauthable, :database_authenticatable, :registerable,
|
11
|
+
:recoverable, :rememberable, :trackable, :validatable
|
12
|
+
end
|
13
|
+
|
14
|
+
# teams
|
15
|
+
has_many :memberships, dependent: :destroy
|
16
|
+
has_many :scaffolding_absolutely_abstract_creative_concepts_collaborators, through: :memberships
|
17
|
+
has_many :teams, through: :memberships
|
18
|
+
belongs_to :current_team, class_name: "Team", optional: true
|
19
|
+
accepts_nested_attributes_for :current_team
|
20
|
+
|
21
|
+
# oauth providers
|
22
|
+
has_many :oauth_stripe_accounts, class_name: "Oauth::StripeAccount" if stripe_enabled?
|
23
|
+
|
24
|
+
# platform functionality.
|
25
|
+
belongs_to :platform_agent_of, class_name: "Platform::Application", optional: true
|
26
|
+
|
27
|
+
# validations
|
28
|
+
validate :real_emails_only
|
29
|
+
validates :time_zone, inclusion: {in: ActiveSupport::TimeZone.all.map(&:name)}, allow_nil: true
|
30
|
+
|
31
|
+
# callbacks
|
32
|
+
after_update :set_teams_time_zone
|
33
|
+
end
|
34
|
+
|
35
|
+
# TODO we need to update this to some sort of invalid email address or something
|
36
|
+
# people know to ignore. it would be a security problem to have this pointing
|
37
|
+
# at anybody's real email address.
|
38
|
+
def email_is_oauth_placeholder?
|
39
|
+
!!email.match(/noreply\+.*@bullettrain.co/)
|
40
|
+
end
|
41
|
+
|
42
|
+
def label_string
|
43
|
+
name
|
44
|
+
end
|
45
|
+
|
46
|
+
def name
|
47
|
+
full_name.present? ? full_name : email
|
48
|
+
end
|
49
|
+
|
50
|
+
def full_name
|
51
|
+
[first_name_was, last_name_was].select(&:present?).join(" ")
|
52
|
+
end
|
53
|
+
|
54
|
+
def details_provided?
|
55
|
+
first_name.present? && last_name.present? && current_team.name.present?
|
56
|
+
end
|
57
|
+
|
58
|
+
def send_welcome_email
|
59
|
+
UserMailer.welcome(self).deliver_later
|
60
|
+
end
|
61
|
+
|
62
|
+
def create_default_team
|
63
|
+
# This creates a `Membership`, because `User` `has_many :teams, through: :memberships`
|
64
|
+
# TODO The team name should take into account the user's current locale.
|
65
|
+
default_team = teams.create(name: "Your Team", time_zone: time_zone)
|
66
|
+
memberships.find_by(team: default_team).update role_ids: [Role.admin.id]
|
67
|
+
update(current_team: default_team)
|
68
|
+
end
|
69
|
+
|
70
|
+
def real_emails_only
|
71
|
+
if ENV["REALEMAIL_API_KEY"] && !Rails.env.test?
|
72
|
+
uri = URI("https://realemail.expeditedaddons.com")
|
73
|
+
|
74
|
+
# Change the input parameters here
|
75
|
+
uri.query = URI.encode_www_form({
|
76
|
+
api_key: ENV["REAL_EMAIL_KEY"],
|
77
|
+
email: email,
|
78
|
+
fix_typos: false
|
79
|
+
})
|
80
|
+
|
81
|
+
# Results are returned as a JSON object
|
82
|
+
result = JSON.parse(Net::HTTP.get_response(uri).body)
|
83
|
+
|
84
|
+
if result["syntax_error"]
|
85
|
+
errors.add(:email, "is not a valid email address")
|
86
|
+
elsif result["domain_error"] || (result.key?("mx_records_found") && !result["mx_records_found"])
|
87
|
+
errors.add(:email, "can't actually receive emails")
|
88
|
+
elsif result["is_disposable"]
|
89
|
+
errors.add(:email, "is a disposable email address")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def multiple_teams?
|
95
|
+
teams.count > 1
|
96
|
+
end
|
97
|
+
|
98
|
+
def one_team?
|
99
|
+
!multiple_teams?
|
100
|
+
end
|
101
|
+
|
102
|
+
def formatted_email_address
|
103
|
+
if details_provided?
|
104
|
+
"\"#{first_name} #{last_name}\" <#{email}>"
|
105
|
+
else
|
106
|
+
email
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def administrating_team_ids
|
111
|
+
parent_ids_for(Role.admin, :memberships, :team)
|
112
|
+
end
|
113
|
+
|
114
|
+
def parent_ids_for(role, through, parent)
|
115
|
+
parent_id_column = "#{parent}_id"
|
116
|
+
key = "#{role.key}_#{through}_#{parent_id_column}s"
|
117
|
+
return ability_cache[key] if ability_cache && ability_cache[key]
|
118
|
+
role = nil if role.default?
|
119
|
+
value = send(through).with_role(role).distinct.pluck(parent_id_column)
|
120
|
+
current_cache = ability_cache || {}
|
121
|
+
current_cache[key] = value
|
122
|
+
update_column :ability_cache, current_cache
|
123
|
+
value
|
124
|
+
end
|
125
|
+
|
126
|
+
def invalidate_ability_cache
|
127
|
+
update_column(:ability_cache, {})
|
128
|
+
end
|
129
|
+
|
130
|
+
def otp_qr_code
|
131
|
+
issuer = I18n.t("application.name")
|
132
|
+
label = "#{issuer}:#{email}"
|
133
|
+
RQRCode::QRCode.new(otp_provisioning_uri(label, issuer: issuer))
|
134
|
+
end
|
135
|
+
|
136
|
+
def scaffolding_absolutely_abstract_creative_concepts_collaborators
|
137
|
+
Scaffolding::AbsolutelyAbstract::CreativeConcepts::Collaborator.joins(:membership).where(membership: {user_id: id})
|
138
|
+
end
|
139
|
+
|
140
|
+
def admin_scaffolding_absolutely_abstract_creative_concepts_ids
|
141
|
+
scaffolding_absolutely_abstract_creative_concepts_collaborators.admins.pluck(:creative_concept_id)
|
142
|
+
end
|
143
|
+
|
144
|
+
def editor_scaffolding_absolutely_abstract_creative_concepts_ids
|
145
|
+
scaffolding_absolutely_abstract_creative_concepts_collaborators.editors.pluck(:creative_concept_id)
|
146
|
+
end
|
147
|
+
|
148
|
+
def viewer_scaffolding_absolutely_abstract_creative_concepts_ids
|
149
|
+
scaffolding_absolutely_abstract_creative_concepts_collaborators.viewers.pluck(:creative_concept_id)
|
150
|
+
end
|
151
|
+
|
152
|
+
def developer?
|
153
|
+
return false unless ENV["DEVELOPER_EMAILS"]
|
154
|
+
# we use email_was so they can't try setting their email to the email of an admin.
|
155
|
+
return false unless email_was
|
156
|
+
ENV["DEVELOPER_EMAILS"].split(",").include?(email_was)
|
157
|
+
end
|
158
|
+
|
159
|
+
def set_teams_time_zone
|
160
|
+
teams.where(time_zone: nil).each do |team|
|
161
|
+
team.update(time_zone: time_zone) if team.users.count == 1
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
data/app/models/invitation.rb
CHANGED
@@ -1,25 +1,5 @@
|
|
1
1
|
class Invitation < ApplicationRecord
|
2
|
-
|
3
|
-
# Typically you should avoid adding your own functionality in this section to avoid merge conflicts in the future.
|
4
|
-
# (If you specifically want to change Bullet Train's default behavior, that's OK and you can do that here.)
|
5
|
-
|
6
|
-
belongs_to :team
|
7
|
-
belongs_to :from_membership, class_name: "Membership"
|
8
|
-
has_one :membership, dependent: :nullify
|
9
|
-
has_many :roles, through: :membership
|
10
|
-
|
11
|
-
accepts_nested_attributes_for :membership
|
12
|
-
|
13
|
-
validates :email, presence: true
|
14
|
-
|
15
|
-
before_create :generate_uuid
|
16
|
-
after_create :set_added_by_membership
|
17
|
-
after_create :send_invitation_email
|
18
|
-
|
19
|
-
# ✅ YOUR APPLICATION'S INVITATION FUNCTIONALITY
|
20
|
-
# This is the place where you should implement your own features on top of Bullet Train's functionality. There
|
21
|
-
# are a bunch of Super Scaffolding hooks here by default to try and help keep generated code logically organized.
|
22
|
-
|
2
|
+
include Invitations::Core
|
23
3
|
# 🚅 add concerns above.
|
24
4
|
|
25
5
|
# 🚅 add belongs_to associations above.
|
@@ -39,35 +19,4 @@ class Invitation < ApplicationRecord
|
|
39
19
|
# 🚅 add delegations above.
|
40
20
|
|
41
21
|
# 🚅 add methods above.
|
42
|
-
|
43
|
-
# 🚫 DEFAULT BULLET TRAIN INVITATION FUNCTIONALITY
|
44
|
-
# We put these at the bottom of this file to keep them out of the way. You should define your own methods above here.
|
45
|
-
|
46
|
-
def set_added_by_membership
|
47
|
-
membership.update(added_by: from_membership)
|
48
|
-
end
|
49
|
-
|
50
|
-
def send_invitation_email
|
51
|
-
UserMailer.invited(uuid).deliver_later
|
52
|
-
end
|
53
|
-
|
54
|
-
def generate_uuid
|
55
|
-
self.uuid = SecureRandom.hex
|
56
|
-
end
|
57
|
-
|
58
|
-
def accept_for(user)
|
59
|
-
User.transaction do
|
60
|
-
user.memberships << membership
|
61
|
-
user.update(current_team: team, former_user: false)
|
62
|
-
destroy
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def name
|
67
|
-
I18n.t("invitations.values.name", team_name: team.name)
|
68
|
-
end
|
69
|
-
|
70
|
-
def is_for?(user)
|
71
|
-
user.email.downcase.strip == email.downcase.strip
|
72
|
-
end
|
73
22
|
end
|
data/app/models/membership.rb
CHANGED
@@ -1,40 +1,5 @@
|
|
1
1
|
class Membership < ApplicationRecord
|
2
|
-
|
3
|
-
# Typically you should avoid adding your own functionality in this section to avoid merge conflicts in the future.
|
4
|
-
# (If you specifically want to change Bullet Train's default behavior, that's OK and you can do that here.)
|
5
|
-
|
6
|
-
# See `docs/permissions.md` for details.
|
7
|
-
include Roles::Support
|
8
|
-
|
9
|
-
belongs_to :user, optional: true
|
10
|
-
belongs_to :team
|
11
|
-
belongs_to :invitation, optional: true, dependent: :destroy
|
12
|
-
belongs_to :added_by, class_name: "Membership", optional: true
|
13
|
-
belongs_to :platform_agent_of, class_name: "Platform::Application", optional: true
|
14
|
-
|
15
|
-
has_many :scaffolding_completely_concrete_tangible_things_assignments, class_name: "Scaffolding::CompletelyConcrete::TangibleThings::Assignment", dependent: :destroy
|
16
|
-
has_many :scaffolding_completely_concrete_tangible_things, through: :scaffolding_completely_concrete_tangible_things_assignments, source: :tangible_thing
|
17
|
-
has_many :reassignments_scaffolding_completely_concrete_tangible_things_reassignments, class_name: "Memberships::Reassignments::ScaffoldingCompletelyConcreteTangibleThingsReassignment", dependent: :destroy, foreign_key: :membership_id
|
18
|
-
|
19
|
-
has_many :scaffolding_absolutely_abstract_creative_concepts_collaborators, class_name: "Scaffolding::AbsolutelyAbstract::CreativeConcepts::Collaborator", dependent: :destroy
|
20
|
-
|
21
|
-
after_destroy do
|
22
|
-
# if we're destroying a user's membership to the team they have set as
|
23
|
-
# current, then we need to remove that so they don't get an error.
|
24
|
-
if user&.current_team == team
|
25
|
-
user.current_team = nil
|
26
|
-
user.save
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
scope :current_and_invited, -> { includes(:invitation).where("user_id IS NOT NULL OR invitations.id IS NOT NULL").references(:invitation) }
|
31
|
-
scope :current, -> { where("user_id IS NOT NULL") }
|
32
|
-
scope :tombstones, -> { includes(:invitation).where("user_id IS NULL AND invitations.id IS NULL").references(:invitation) }
|
33
|
-
|
34
|
-
# ✅ YOUR APPLICATION'S MEMBERSHIP FUNCTIONALITY
|
35
|
-
# This is the place where you should implement your own features on top of Bullet Train's functionality. There
|
36
|
-
# are a bunch of Super Scaffolding hooks here by default to try and help keep generated code logically organized.
|
37
|
-
|
2
|
+
include Memberships::Core
|
38
3
|
# 🚅 add concerns above.
|
39
4
|
|
40
5
|
# 🚅 add belongs_to associations above.
|
@@ -54,111 +19,4 @@ class Membership < ApplicationRecord
|
|
54
19
|
# 🚅 add delegations above.
|
55
20
|
|
56
21
|
# 🚅 add methods above.
|
57
|
-
|
58
|
-
# 🚫 DEFAULT BULLET TRAIN TEAM FUNCTIONALITY
|
59
|
-
# We put these at the bottom of this file to keep them out of the way. You should define your own methods above here.
|
60
|
-
|
61
|
-
def name
|
62
|
-
full_name
|
63
|
-
end
|
64
|
-
|
65
|
-
def label_string
|
66
|
-
full_name
|
67
|
-
end
|
68
|
-
|
69
|
-
# we overload this method so that when setting the list of role ids
|
70
|
-
# associated with a membership, admins can never remove the last admin
|
71
|
-
# of a team.
|
72
|
-
def role_ids=(ids)
|
73
|
-
# if this membership was an admin, and the new list of role ids don't include admin.
|
74
|
-
if admin? && !ids.include?(Role.admin.id)
|
75
|
-
unless team.admins.count > 1
|
76
|
-
raise RemovingLastTeamAdminException.new("You can't remove the last team admin.")
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
super(ids)
|
81
|
-
end
|
82
|
-
|
83
|
-
def unclaimed?
|
84
|
-
user.nil? && !invitation.nil?
|
85
|
-
end
|
86
|
-
|
87
|
-
def tombstone?
|
88
|
-
user.nil? && invitation.nil?
|
89
|
-
end
|
90
|
-
|
91
|
-
def last_admin?
|
92
|
-
return false unless admin?
|
93
|
-
return false unless user.present?
|
94
|
-
team.memberships.current.select(&:admin?) == [self]
|
95
|
-
end
|
96
|
-
|
97
|
-
def nullify_user
|
98
|
-
if last_admin?
|
99
|
-
raise RemovingLastTeamAdminException.new("You can't remove the last team admin.")
|
100
|
-
end
|
101
|
-
|
102
|
-
if (user_was = user)
|
103
|
-
unless user_first_name.present?
|
104
|
-
self.user_first_name = user.first_name
|
105
|
-
end
|
106
|
-
|
107
|
-
unless user_last_name.present?
|
108
|
-
self.user_last_name = user.last_name
|
109
|
-
end
|
110
|
-
|
111
|
-
unless user_profile_photo_id.present?
|
112
|
-
self.user_profile_photo_id = user.profile_photo_id
|
113
|
-
end
|
114
|
-
|
115
|
-
unless user_email.present?
|
116
|
-
self.user_email = user.email
|
117
|
-
end
|
118
|
-
|
119
|
-
self.user = nil
|
120
|
-
save
|
121
|
-
|
122
|
-
user_was.invalidate_ability_cache
|
123
|
-
|
124
|
-
user_was.update(
|
125
|
-
current_team: user_was.teams.first,
|
126
|
-
former_user: user_was.teams.empty?
|
127
|
-
)
|
128
|
-
end
|
129
|
-
|
130
|
-
# we do this here just in case by some weird chance an active membership had an invitation attached.
|
131
|
-
invitation&.destroy
|
132
|
-
end
|
133
|
-
|
134
|
-
def email
|
135
|
-
user&.email || user_email.presence || invitation&.email
|
136
|
-
end
|
137
|
-
|
138
|
-
def full_name
|
139
|
-
user&.full_name || [first_name.presence, last_name.presence].join(" ").presence || email
|
140
|
-
end
|
141
|
-
|
142
|
-
def first_name
|
143
|
-
user&.first_name || user_first_name
|
144
|
-
end
|
145
|
-
|
146
|
-
def last_name
|
147
|
-
user&.last_name || user_last_name
|
148
|
-
end
|
149
|
-
|
150
|
-
def last_initial
|
151
|
-
return nil unless last_name.present?
|
152
|
-
"#{last_name}."
|
153
|
-
end
|
154
|
-
|
155
|
-
def first_name_last_initial
|
156
|
-
[first_name, last_initial].map(&:present?).join(" ")
|
157
|
-
end
|
158
|
-
|
159
|
-
# TODO utilize this.
|
160
|
-
# members shouldn't receive notifications unless they are either an active user or an outstanding invitation.
|
161
|
-
def should_receive_notifications?
|
162
|
-
invitation.present? || user.present?
|
163
|
-
end
|
164
22
|
end
|
data/app/models/team.rb
CHANGED
@@ -1,35 +1,6 @@
|
|
1
1
|
class Team < ApplicationRecord
|
2
|
-
|
3
|
-
# Typically you should avoid adding your own functionality in this section to avoid merge conflicts in the future.
|
4
|
-
# (If you specifically want to change Bullet Train's default behavior, that's OK and you can do that here.)
|
5
|
-
|
6
|
-
# Outgoing webhooks.
|
2
|
+
include Teams::Core
|
7
3
|
include Webhooks::Outgoing::TeamSupport
|
8
|
-
|
9
|
-
# super scaffolding
|
10
|
-
unless scaffolding_things_disabled?
|
11
|
-
has_many :scaffolding_absolutely_abstract_creative_concepts, class_name: "Scaffolding::AbsolutelyAbstract::CreativeConcept", dependent: :destroy, enable_updates: true
|
12
|
-
end
|
13
|
-
|
14
|
-
# memberships and invitations
|
15
|
-
has_many :memberships, dependent: :destroy
|
16
|
-
has_many :users, through: :memberships
|
17
|
-
has_many :invitations
|
18
|
-
|
19
|
-
# oauth for grape api
|
20
|
-
has_many :platform_applications, class_name: "Platform::Application", dependent: :destroy, foreign_key: :team_id
|
21
|
-
|
22
|
-
# integrations
|
23
|
-
has_many :integrations_stripe_installations, class_name: "Integrations::StripeInstallation", dependent: :destroy if stripe_enabled?
|
24
|
-
|
25
|
-
# validations
|
26
|
-
validates :name, presence: true
|
27
|
-
validates :time_zone, inclusion: {in: ActiveSupport::TimeZone.all.map(&:name)}, allow_nil: true
|
28
|
-
|
29
|
-
# ✅ YOUR APPLICATION'S TEAM FUNCTIONALITY
|
30
|
-
# This is the place where you should implement your own features on top of Bullet Train's functionality. There
|
31
|
-
# are a bunch of Super Scaffolding hooks here by default to try and help keep generated code logically organized.
|
32
|
-
|
33
4
|
# 🚅 add concerns above.
|
34
5
|
|
35
6
|
# 🚅 add belongs_to associations above.
|
@@ -49,33 +20,4 @@ class Team < ApplicationRecord
|
|
49
20
|
# 🚅 add delegations above.
|
50
21
|
|
51
22
|
# 🚅 add methods above.
|
52
|
-
|
53
|
-
# 🚫 DEFAULT BULLET TRAIN TEAM FUNCTIONALITY
|
54
|
-
# We put these at the bottom of this file to keep them out of the way. You should define your own methods above here.
|
55
|
-
|
56
|
-
def admins
|
57
|
-
memberships.current_and_invited.admins
|
58
|
-
end
|
59
|
-
|
60
|
-
def admin_users
|
61
|
-
admins.map(&:user).compact
|
62
|
-
end
|
63
|
-
|
64
|
-
def primary_contact
|
65
|
-
admin_users.min { |user| user.created_at }
|
66
|
-
end
|
67
|
-
|
68
|
-
def formatted_email_address
|
69
|
-
primary_contact.email
|
70
|
-
end
|
71
|
-
|
72
|
-
def invalidate_caches
|
73
|
-
users.map(&:invalidate_ability_cache)
|
74
|
-
end
|
75
|
-
|
76
|
-
def team
|
77
|
-
# some generic features appeal to the `team` method for security or scoping purposes, but sometimes those same
|
78
|
-
# generic functions need to function for a team model as well, so we do this.
|
79
|
-
self
|
80
|
-
end
|
81
23
|
end
|
data/app/models/teams.rb
ADDED
data/app/models/user.rb
CHANGED
@@ -1,41 +1,5 @@
|
|
1
1
|
class User < ApplicationRecord
|
2
|
-
|
3
|
-
# Typically you should avoid adding your own functionality in this section to avoid merge conflicts in the future.
|
4
|
-
# (If you specifically want to change Bullet Train's default behavior, that's OK and you can do that here.)
|
5
|
-
|
6
|
-
if two_factor_authentication_enabled?
|
7
|
-
devise :two_factor_authenticatable, :two_factor_backupable, :omniauthable,
|
8
|
-
:registerable, :recoverable, :rememberable, :trackable, :validatable,
|
9
|
-
otp_secret_encryption_key: ENV["TWO_FACTOR_ENCRYPTION_KEY"]
|
10
|
-
else
|
11
|
-
devise :omniauthable, :database_authenticatable, :registerable,
|
12
|
-
:recoverable, :rememberable, :trackable, :validatable
|
13
|
-
end
|
14
|
-
|
15
|
-
# teams
|
16
|
-
has_many :memberships, dependent: :destroy
|
17
|
-
has_many :scaffolding_absolutely_abstract_creative_concepts_collaborators, through: :memberships
|
18
|
-
has_many :teams, through: :memberships
|
19
|
-
belongs_to :current_team, class_name: "Team", optional: true
|
20
|
-
accepts_nested_attributes_for :current_team
|
21
|
-
|
22
|
-
# oauth providers
|
23
|
-
has_many :oauth_stripe_accounts, class_name: "Oauth::StripeAccount" if stripe_enabled?
|
24
|
-
|
25
|
-
# platform functionality.
|
26
|
-
belongs_to :platform_agent_of, class_name: "Platform::Application", optional: true
|
27
|
-
|
28
|
-
# validations
|
29
|
-
validate :real_emails_only
|
30
|
-
validates :time_zone, inclusion: {in: ActiveSupport::TimeZone.all.map(&:name)}, allow_nil: true
|
31
|
-
|
32
|
-
# callbacks
|
33
|
-
after_update :set_teams_time_zone
|
34
|
-
|
35
|
-
# ✅ YOUR APPLICATION'S USER FUNCTIONALITY
|
36
|
-
# This is the place where you should implement your own features on top of Bullet Train's user functionality. There
|
37
|
-
# are a bunch of Super Scaffolding hooks here by default to try and help keep generated code logically organized.
|
38
|
-
|
2
|
+
include Users::Core
|
39
3
|
# 🚅 add concerns above.
|
40
4
|
|
41
5
|
# 🚅 add belongs_to associations above.
|
@@ -55,137 +19,4 @@ class User < ApplicationRecord
|
|
55
19
|
# 🚅 add delegations above.
|
56
20
|
|
57
21
|
# 🚅 add methods above.
|
58
|
-
|
59
|
-
# 🚫 DEFAULT BULLET TRAIN USER FUNCTIONALITY
|
60
|
-
# We put these at the bottom of this file to keep them out of the way. You should define your own methods above here.
|
61
|
-
|
62
|
-
# TODO we need to update this to some sort of invalid email address or something
|
63
|
-
# people know to ignore. it would be a security problem to have this pointing
|
64
|
-
# at anybody's real email address.
|
65
|
-
def email_is_oauth_placeholder?
|
66
|
-
!!email.match(/noreply\+.*@bullettrain.co/)
|
67
|
-
end
|
68
|
-
|
69
|
-
def label_string
|
70
|
-
name
|
71
|
-
end
|
72
|
-
|
73
|
-
def name
|
74
|
-
full_name.present? ? full_name : email
|
75
|
-
end
|
76
|
-
|
77
|
-
def full_name
|
78
|
-
[first_name_was, last_name_was].select(&:present?).join(" ")
|
79
|
-
end
|
80
|
-
|
81
|
-
def details_provided?
|
82
|
-
first_name.present? && last_name.present? && current_team.name.present?
|
83
|
-
end
|
84
|
-
|
85
|
-
def send_welcome_email
|
86
|
-
UserMailer.welcome(self).deliver_later
|
87
|
-
end
|
88
|
-
|
89
|
-
def create_default_team
|
90
|
-
# This creates a `Membership`, because `User` `has_many :teams, through: :memberships`
|
91
|
-
# TODO The team name should take into account the user's current locale.
|
92
|
-
default_team = teams.create(name: "Your Team", time_zone: time_zone)
|
93
|
-
memberships.find_by(team: default_team).update role_ids: [Role.admin.id]
|
94
|
-
update(current_team: default_team)
|
95
|
-
end
|
96
|
-
|
97
|
-
def real_emails_only
|
98
|
-
if ENV["REALEMAIL_API_KEY"] && !Rails.env.test?
|
99
|
-
uri = URI("https://realemail.expeditedaddons.com")
|
100
|
-
|
101
|
-
# Change the input parameters here
|
102
|
-
uri.query = URI.encode_www_form({
|
103
|
-
api_key: ENV["REAL_EMAIL_KEY"],
|
104
|
-
email: email,
|
105
|
-
fix_typos: false
|
106
|
-
})
|
107
|
-
|
108
|
-
# Results are returned as a JSON object
|
109
|
-
result = JSON.parse(Net::HTTP.get_response(uri).body)
|
110
|
-
|
111
|
-
if result["syntax_error"]
|
112
|
-
errors.add(:email, "is not a valid email address")
|
113
|
-
elsif result["domain_error"] || (result.key?("mx_records_found") && !result["mx_records_found"])
|
114
|
-
errors.add(:email, "can't actually receive emails")
|
115
|
-
elsif result["is_disposable"]
|
116
|
-
errors.add(:email, "is a disposable email address")
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
def multiple_teams?
|
122
|
-
teams.count > 1
|
123
|
-
end
|
124
|
-
|
125
|
-
def one_team?
|
126
|
-
!multiple_teams?
|
127
|
-
end
|
128
|
-
|
129
|
-
def formatted_email_address
|
130
|
-
if details_provided?
|
131
|
-
"\"#{first_name} #{last_name}\" <#{email}>"
|
132
|
-
else
|
133
|
-
email
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
def administrating_team_ids
|
138
|
-
parent_ids_for(Role.admin, :memberships, :team)
|
139
|
-
end
|
140
|
-
|
141
|
-
def parent_ids_for(role, through, parent)
|
142
|
-
parent_id_column = "#{parent}_id"
|
143
|
-
key = "#{role.key}_#{through}_#{parent_id_column}s"
|
144
|
-
return ability_cache[key] if ability_cache && ability_cache[key]
|
145
|
-
role = nil if role.default?
|
146
|
-
value = send(through).with_role(role).distinct.pluck(parent_id_column)
|
147
|
-
current_cache = ability_cache || {}
|
148
|
-
current_cache[key] = value
|
149
|
-
update_column :ability_cache, current_cache
|
150
|
-
value
|
151
|
-
end
|
152
|
-
|
153
|
-
def invalidate_ability_cache
|
154
|
-
update_column(:ability_cache, {})
|
155
|
-
end
|
156
|
-
|
157
|
-
def otp_qr_code
|
158
|
-
issuer = I18n.t("application.name")
|
159
|
-
label = "#{issuer}:#{email}"
|
160
|
-
RQRCode::QRCode.new(otp_provisioning_uri(label, issuer: issuer))
|
161
|
-
end
|
162
|
-
|
163
|
-
def scaffolding_absolutely_abstract_creative_concepts_collaborators
|
164
|
-
Scaffolding::AbsolutelyAbstract::CreativeConcepts::Collaborator.joins(:membership).where(membership: {user_id: id})
|
165
|
-
end
|
166
|
-
|
167
|
-
def admin_scaffolding_absolutely_abstract_creative_concepts_ids
|
168
|
-
scaffolding_absolutely_abstract_creative_concepts_collaborators.admins.pluck(:creative_concept_id)
|
169
|
-
end
|
170
|
-
|
171
|
-
def editor_scaffolding_absolutely_abstract_creative_concepts_ids
|
172
|
-
scaffolding_absolutely_abstract_creative_concepts_collaborators.editors.pluck(:creative_concept_id)
|
173
|
-
end
|
174
|
-
|
175
|
-
def viewer_scaffolding_absolutely_abstract_creative_concepts_ids
|
176
|
-
scaffolding_absolutely_abstract_creative_concepts_collaborators.viewers.pluck(:creative_concept_id)
|
177
|
-
end
|
178
|
-
|
179
|
-
def developer?
|
180
|
-
return false unless ENV["DEVELOPER_EMAILS"]
|
181
|
-
# we use email_was so they can't try setting their email to the email of an admin.
|
182
|
-
return false unless email_was
|
183
|
-
ENV["DEVELOPER_EMAILS"].split(",").include?(email_was)
|
184
|
-
end
|
185
|
-
|
186
|
-
def set_teams_time_zone
|
187
|
-
teams.where(time_zone: nil).each do |team|
|
188
|
-
team.update(time_zone: time_zone) if team.users.count == 1
|
189
|
-
end
|
190
|
-
end
|
191
22
|
end
|
data/app/models/users.rb
ADDED
data/config/routes.rb
CHANGED
@@ -1,2 +1,42 @@
|
|
1
1
|
Rails.application.routes.draw do
|
2
|
+
namespace :account do
|
3
|
+
shallow do
|
4
|
+
resource :two_factor, only: [:create, :destroy]
|
5
|
+
|
6
|
+
# user-level onboarding tasks.
|
7
|
+
namespace :onboarding do
|
8
|
+
resources :user_details
|
9
|
+
resources :user_email
|
10
|
+
end
|
11
|
+
|
12
|
+
# user specific resources.
|
13
|
+
resources :users
|
14
|
+
|
15
|
+
# team-level resources.
|
16
|
+
resources :teams do
|
17
|
+
resources :invitations do
|
18
|
+
member do
|
19
|
+
get :accept
|
20
|
+
post :accept
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
resources :memberships do
|
25
|
+
member do
|
26
|
+
post :demote
|
27
|
+
post :promote
|
28
|
+
post :reinvite
|
29
|
+
end
|
30
|
+
|
31
|
+
collection do
|
32
|
+
get :search
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
member do
|
37
|
+
post :switch_to
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
2
42
|
end
|
data/lib/bullet_train/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bullet_train
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Culver
|
@@ -36,7 +36,6 @@ files:
|
|
36
36
|
- Rakefile
|
37
37
|
- app/assets/config/bullet_train_manifest.js
|
38
38
|
- app/controllers/account/invitations_controller.rb
|
39
|
-
- app/controllers/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments_controller.rb
|
40
39
|
- app/controllers/account/memberships_controller.rb
|
41
40
|
- app/controllers/account/onboarding/user_details_controller.rb
|
42
41
|
- app/controllers/account/onboarding/user_email_controller.rb
|
@@ -48,13 +47,18 @@ files:
|
|
48
47
|
- app/helpers/account/users_helper.rb
|
49
48
|
- app/helpers/invitation_only_helper.rb
|
50
49
|
- app/helpers/invitations_helper.rb
|
50
|
+
- app/models/concerns/invitations/core.rb
|
51
|
+
- app/models/concerns/memberships/core.rb
|
52
|
+
- app/models/concerns/teams/core.rb
|
53
|
+
- app/models/concerns/users/core.rb
|
51
54
|
- app/models/invitation.rb
|
55
|
+
- app/models/invitations.rb
|
52
56
|
- app/models/membership.rb
|
53
|
-
- app/models/memberships
|
54
|
-
- app/models/memberships/reassignments/assignment.rb
|
55
|
-
- app/models/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignment.rb
|
57
|
+
- app/models/memberships.rb
|
56
58
|
- app/models/team.rb
|
59
|
+
- app/models/teams.rb
|
57
60
|
- app/models/user.rb
|
61
|
+
- app/models/users.rb
|
58
62
|
- app/views/account/invitations/_breadcrumbs.html.erb
|
59
63
|
- app/views/account/invitations/_form.html.erb
|
60
64
|
- app/views/account/invitations/_invitation.json.jbuilder
|
@@ -69,12 +73,6 @@ files:
|
|
69
73
|
- app/views/account/memberships/_tombstones.html.erb
|
70
74
|
- app/views/account/memberships/edit.html.erb
|
71
75
|
- app/views/account/memberships/index.html.erb
|
72
|
-
- app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/_breadcrumbs.html.erb
|
73
|
-
- app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/_form.html.erb
|
74
|
-
- app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/_scaffolding_completely_concrete_tangible_things_reassignment.json.jbuilder
|
75
|
-
- app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/index.json.jbuilder
|
76
|
-
- app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/new.html.erb
|
77
|
-
- app/views/account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/show.json.jbuilder
|
78
76
|
- app/views/account/memberships/show.html.erb
|
79
77
|
- app/views/account/onboarding/user_details/edit.html.erb
|
80
78
|
- app/views/account/onboarding/user_email/edit.html.erb
|
@@ -95,7 +93,6 @@ files:
|
|
95
93
|
- app/views/account/users/show.html.erb
|
96
94
|
- config/locales/en/invitations.en.yml
|
97
95
|
- config/locales/en/memberships.en.yml
|
98
|
-
- config/locales/en/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments.en.yml
|
99
96
|
- config/locales/en/onboarding/user_details.en.yml
|
100
97
|
- config/locales/en/onboarding/user_email.en.yml
|
101
98
|
- config/locales/en/teams.en.yml
|
@@ -1,83 +0,0 @@
|
|
1
|
-
class Account::Memberships::Reassignments::ScaffoldingCompletelyConcreteTangibleThingsReassignmentsController < Account::ApplicationController
|
2
|
-
account_load_and_authorize_resource :scaffolding_completely_concrete_tangible_things_reassignment, through: :membership, through_association: :reassignments_scaffolding_completely_concrete_tangible_things_reassignments
|
3
|
-
|
4
|
-
# GET /account/memberships/:membership_id/reassignments/scaffolding_completely_concrete_tangible_things_reassignments
|
5
|
-
# GET /account/memberships/:membership_id/reassignments/scaffolding_completely_concrete_tangible_things_reassignments.json
|
6
|
-
def index
|
7
|
-
# if you only want these objects shown on their parent's show page, uncomment this:
|
8
|
-
redirect_to [:account, @membership]
|
9
|
-
end
|
10
|
-
|
11
|
-
# GET /account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/:id
|
12
|
-
# GET /account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/:id.json
|
13
|
-
def show
|
14
|
-
end
|
15
|
-
|
16
|
-
# GET /account/memberships/:membership_id/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/new
|
17
|
-
def new
|
18
|
-
end
|
19
|
-
|
20
|
-
# GET /account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/:id/edit
|
21
|
-
def edit
|
22
|
-
end
|
23
|
-
|
24
|
-
# POST /account/memberships/:membership_id/reassignments/scaffolding_completely_concrete_tangible_things_reassignments
|
25
|
-
# POST /account/memberships/:membership_id/reassignments/scaffolding_completely_concrete_tangible_things_reassignments.json
|
26
|
-
def create
|
27
|
-
respond_to do |format|
|
28
|
-
if @scaffolding_completely_concrete_tangible_things_reassignment.save
|
29
|
-
format.html { redirect_to [:account, @membership, :reassignments_scaffolding_completely_concrete_tangible_things_reassignments], notice: I18n.t("memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments.notifications.created") }
|
30
|
-
format.json { render :show, status: :created, location: [:account, @scaffolding_completely_concrete_tangible_things_reassignment] }
|
31
|
-
else
|
32
|
-
format.html { render :new, status: :unprocessable_entity }
|
33
|
-
format.json { render json: @scaffolding_completely_concrete_tangible_things_reassignment.errors, status: :unprocessable_entity }
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
# PATCH/PUT /account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/:id
|
39
|
-
# PATCH/PUT /account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/:id.json
|
40
|
-
def update
|
41
|
-
respond_to do |format|
|
42
|
-
if @scaffolding_completely_concrete_tangible_things_reassignment.update(scaffolding_completely_concrete_tangible_things_reassignment_params)
|
43
|
-
format.html { redirect_to [:account, @scaffolding_completely_concrete_tangible_things_reassignment], notice: I18n.t("memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments.notifications.updated") }
|
44
|
-
format.json { render :show, status: :ok, location: [:account, @scaffolding_completely_concrete_tangible_things_reassignment] }
|
45
|
-
else
|
46
|
-
format.html { render :edit, status: :unprocessable_entity }
|
47
|
-
format.json { render json: @scaffolding_completely_concrete_tangible_things_reassignment.errors, status: :unprocessable_entity }
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
# DELETE /account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/:id
|
53
|
-
# DELETE /account/memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/:id.json
|
54
|
-
def destroy
|
55
|
-
@scaffolding_completely_concrete_tangible_things_reassignment.destroy
|
56
|
-
respond_to do |format|
|
57
|
-
format.html { redirect_to [:account, @membership, :reassignments_scaffolding_completely_concrete_tangible_things_reassignments], notice: I18n.t("memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments.notifications.destroyed") }
|
58
|
-
format.json { head :no_content }
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
private
|
63
|
-
|
64
|
-
# Never trust parameters from the scary internet, only allow the white list through.
|
65
|
-
def scaffolding_completely_concrete_tangible_things_reassignment_params
|
66
|
-
strong_params = params.require(:memberships_reassignments_scaffolding_completely_concrete_tangible_things_reassignment).permit(
|
67
|
-
# 🚅 super scaffolding will insert new fields above this line.
|
68
|
-
# 🚅 super scaffolding will insert new arrays above this line.
|
69
|
-
membership_ids: [],
|
70
|
-
)
|
71
|
-
|
72
|
-
strong_params[:membership_ids] = create_models_if_new(strong_params[:membership_ids]) do |email|
|
73
|
-
# stub out a new membership and invitation with no special permissions.
|
74
|
-
membership = current_team.memberships.create(user_email: email, added_by: current_membership)
|
75
|
-
current_team.invitations.create(email: email, from_membership: current_membership, membership: membership)
|
76
|
-
membership
|
77
|
-
end
|
78
|
-
|
79
|
-
# 🚅 super scaffolding will insert processing for new fields above this line.
|
80
|
-
|
81
|
-
strong_params
|
82
|
-
end
|
83
|
-
end
|
@@ -1,12 +0,0 @@
|
|
1
|
-
class Memberships::Reassignments::Assignment < ApplicationRecord
|
2
|
-
belongs_to :scaffolding_completely_concrete_tangible_things_reassignment, class_name: "Memberships::Reassignments::ScaffoldingCompletelyConcreteTangibleThingsReassignment"
|
3
|
-
belongs_to :membership
|
4
|
-
|
5
|
-
validate :validate_membership
|
6
|
-
|
7
|
-
def validate_membership
|
8
|
-
unless scaffolding_completely_concrete_tangible_things_reassignment.valid_memberships.include?(membership)
|
9
|
-
errors.add(:membership, :invalid)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
class Memberships::Reassignments::ScaffoldingCompletelyConcreteTangibleThingsReassignment < ApplicationRecord
|
2
|
-
# 🚅 add concerns above.
|
3
|
-
|
4
|
-
belongs_to :membership # this is the member being reassigned from.
|
5
|
-
has_one :team, through: :membership
|
6
|
-
# 🚅 add belongs_to associations above.
|
7
|
-
|
8
|
-
has_many :assignments
|
9
|
-
has_many :memberships, through: :assignments # these are the members being reassigned to.
|
10
|
-
# 🚅 add has_many associations above.
|
11
|
-
|
12
|
-
# 🚅 add has_one associations above.
|
13
|
-
|
14
|
-
# 🚅 add scopes above.
|
15
|
-
|
16
|
-
# 🚅 add validations above.
|
17
|
-
|
18
|
-
after_save :reassign
|
19
|
-
# 🚅 add callbacks above.
|
20
|
-
|
21
|
-
# 🚅 add delegations above.
|
22
|
-
|
23
|
-
def valid_memberships
|
24
|
-
team.memberships.current_and_invited
|
25
|
-
end
|
26
|
-
|
27
|
-
def reassign
|
28
|
-
membership.scaffolding_completely_concrete_tangible_things_assignments.each do |existing_assignment|
|
29
|
-
memberships.each do |target_membership|
|
30
|
-
unless existing_assignment.tangible_thing.memberships.include?(target_membership)
|
31
|
-
existing_assignment.tangible_thing.memberships << target_membership
|
32
|
-
end
|
33
|
-
end
|
34
|
-
existing_assignment.destroy
|
35
|
-
end
|
36
|
-
end
|
37
|
-
# 🚅 add methods above.
|
38
|
-
end
|
@@ -1,10 +0,0 @@
|
|
1
|
-
<% scaffolding_completely_concrete_tangible_things_reassignment ||= @scaffolding_completely_concrete_tangible_things_reassignment %>
|
2
|
-
<% membership ||= @membership || scaffolding_completely_concrete_tangible_things_reassignment&.membership %>
|
3
|
-
<%= render 'account/memberships/breadcrumbs', membership: membership %>
|
4
|
-
<li class="breadcrumb-item">
|
5
|
-
<%= link_to t('.label'), [:account, membership, :reassignments_scaffolding_completely_concrete_tangible_things_reassignments] %>
|
6
|
-
</li>
|
7
|
-
<% if scaffolding_completely_concrete_tangible_things_reassignment&.persisted? %>
|
8
|
-
<li class="breadcrumb-item"><%= scaffolding_completely_concrete_tangible_things_reassignment.label_string %></li>
|
9
|
-
<% end %>
|
10
|
-
<%= render 'account/shared/breadcrumbs/actions', only_for: 'memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments' %>
|
@@ -1,24 +0,0 @@
|
|
1
|
-
<%= form_with(model: scaffolding_completely_concrete_tangible_things_reassignment, url: (scaffolding_completely_concrete_tangible_things_reassignment.persisted? ? [:account, scaffolding_completely_concrete_tangible_things_reassignment] : [:account, @membership, :reassignments_scaffolding_completely_concrete_tangible_things_reassignments]), local: true) do |form| %>
|
2
|
-
<%= render 'account/shared/forms/errors', form: form %>
|
3
|
-
|
4
|
-
<%= render 'shared/fields/super_select', form: form, method: :membership_ids, options: {autofocus: true},
|
5
|
-
html_options: {multiple: true, class: can_invite? ? 'accepts-new' : ''},
|
6
|
-
choices: memberships_as_select_options(scaffolding_completely_concrete_tangible_things_reassignment.valid_memberships, form.object.membership_ids) do %>
|
7
|
-
<% if can_invite? %>
|
8
|
-
<% content_for :after_help do %>
|
9
|
-
<%= t('memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments.fields.membership_ids.invite_help').html_safe %>
|
10
|
-
<% end %>
|
11
|
-
<% end %>
|
12
|
-
<% end %>
|
13
|
-
<%# 🚅 super scaffolding will insert new fields above this line. %>
|
14
|
-
|
15
|
-
<div class="form-buttons-w">
|
16
|
-
<%= form.submit (form.object.persisted? ? t('.buttons.update') : t('.buttons.create')), class: "btn btn-primary" %>
|
17
|
-
<% if form.object.persisted? %>
|
18
|
-
<%= link_to t('global.buttons.cancel'), [:account, scaffolding_completely_concrete_tangible_things_reassignment], class: "btn btn-link" %>
|
19
|
-
<% else %>
|
20
|
-
<%= link_to t('global.buttons.cancel'), [:account, @membership, :reassignments_scaffolding_completely_concrete_tangible_things_reassignments], class: "btn btn-link" %>
|
21
|
-
<% end %>
|
22
|
-
</div>
|
23
|
-
|
24
|
-
<% end %>
|
@@ -1,8 +0,0 @@
|
|
1
|
-
json.extract! scaffolding_completely_concrete_tangible_things_reassignment,
|
2
|
-
:id,
|
3
|
-
:membership_id,
|
4
|
-
:membership_ids,
|
5
|
-
# 🚅 super scaffolding will insert new fields above this line.
|
6
|
-
:created_at,
|
7
|
-
:updated_at
|
8
|
-
json.url account_memberships_reassignments_scaffolding_completely_concrete_tangible_things_reassignment_url(scaffolding_completely_concrete_tangible_things_reassignment, format: :json)
|
@@ -1 +0,0 @@
|
|
1
|
-
json.array! @scaffolding_completely_concrete_tangible_things_reassignments, partial: "memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/scaffolding_completely_concrete_tangible_things_reassignment", as: :scaffolding_completely_concrete_tangible_things_reassignment
|
@@ -1,11 +0,0 @@
|
|
1
|
-
<div class="element-wrapper">
|
2
|
-
<h6 class="element-header"><%= t('.section') %></h6>
|
3
|
-
<%= render 'account/shared/notices' %>
|
4
|
-
<div class="padded-lg">
|
5
|
-
<div class="element-box">
|
6
|
-
<h5 class="form-header"><%= t('.header') %></h5>
|
7
|
-
<div class="form-desc"><%= t('.description') %></div>
|
8
|
-
<%= render 'form', scaffolding_completely_concrete_tangible_things_reassignment: @scaffolding_completely_concrete_tangible_things_reassignment %>
|
9
|
-
</div>
|
10
|
-
</div>
|
11
|
-
</div>
|
@@ -1 +0,0 @@
|
|
1
|
-
json.partial! "memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments/scaffolding_completely_concrete_tangible_things_reassignment", scaffolding_completely_concrete_tangible_things_reassignment: @scaffolding_completely_concrete_tangible_things_reassignment
|
@@ -1,42 +0,0 @@
|
|
1
|
-
en:
|
2
|
-
memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignments: &scaffolding_completely_concrete_tangible_things_reassignments
|
3
|
-
label: &label Tangible Things Reassignments
|
4
|
-
breadcrumbs:
|
5
|
-
label: *label
|
6
|
-
buttons: &buttons
|
7
|
-
new: Reassign to Another Team Member
|
8
|
-
create: Reassign Tangible Things
|
9
|
-
fields: &fields
|
10
|
-
membership_ids:
|
11
|
-
name: &membership_ids Target Members
|
12
|
-
label: Who should %{memberships_possessive} Tangible Things be reassigned to?
|
13
|
-
heading: *membership_ids
|
14
|
-
help: You can reassign to multiple team members. You can leave this blank to simply unassign Tangible Things from %{membership_name}.
|
15
|
-
invite_help: You can invite new team members by entering their email address and hitting <kbd>enter</kbd>.
|
16
|
-
# 🚅 super scaffolding will insert new fields above this line.
|
17
|
-
index:
|
18
|
-
section: "%{memberships_possessive} Tangible Things Reassignments"
|
19
|
-
contexts:
|
20
|
-
membership:
|
21
|
-
header: Tangible Things Reassignments
|
22
|
-
description: Below is a list of Tangible Things Reassignments that have been added to %{membership_name}. You can manage them with the options below.
|
23
|
-
fields: *fields
|
24
|
-
buttons: *buttons
|
25
|
-
form: &form
|
26
|
-
buttons: *buttons
|
27
|
-
new:
|
28
|
-
section: "Reassign %{memberships_possessive} Tangible Things"
|
29
|
-
header: Reassign Tangible Things
|
30
|
-
description: This will unassign all Tangible Things currently assigned to %{membership_name} and assign them to whichever team members are specified below.
|
31
|
-
form: *form
|
32
|
-
notifications:
|
33
|
-
created: Tangible Things were successfully reassigned.
|
34
|
-
account:
|
35
|
-
memberships:
|
36
|
-
reassignments:
|
37
|
-
scaffolding_completely_concrete_tangible_things_reassignments: *scaffolding_completely_concrete_tangible_things_reassignments
|
38
|
-
activerecord:
|
39
|
-
attributes:
|
40
|
-
memberships/reassignments/scaffolding_completely_concrete_tangible_things_reassignment:
|
41
|
-
membership_ids: *membership_ids
|
42
|
-
# 🚅 super scaffolding will insert new activerecord attributes above this line.
|