simple_teams 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +8 -0
- data/app/assets/config/simple_teams_manifest.js +1 -0
- data/app/assets/stylesheets/simple_teams/application.css +15 -0
- data/app/controllers/simple_teams/accept_invitations_controller.rb +57 -0
- data/app/controllers/simple_teams/admin/invitations_controller.rb +60 -0
- data/app/controllers/simple_teams/admin/memberships_controller.rb +60 -0
- data/app/controllers/simple_teams/admin/teams_controller.rb +60 -0
- data/app/controllers/simple_teams/application_controller.rb +13 -0
- data/app/controllers/simple_teams/invitations_controller.rb +96 -0
- data/app/controllers/simple_teams/leave_teams_controller.rb +25 -0
- data/app/controllers/simple_teams/memberships_controller.rb +61 -0
- data/app/controllers/simple_teams/related_members_controller.rb +33 -0
- data/app/controllers/simple_teams/teams_controller.rb +19 -0
- data/app/forms/simple_teams/invitation_form.rb +77 -0
- data/app/forms/simple_teams/invitation_forms/create.rb +24 -0
- data/app/forms/simple_teams/invitation_forms/create_bulk.rb +125 -0
- data/app/forms/simple_teams/invitation_forms/create_combo.rb +75 -0
- data/app/forms/simple_teams/invitation_forms/update.rb +29 -0
- data/app/forms/simple_teams/membership_form.rb +91 -0
- data/app/helpers/simple_teams/application_helper.rb +4 -0
- data/app/helpers/simple_teams/invitations_helper.rb +4 -0
- data/app/helpers/simple_teams/memberships_helper.rb +4 -0
- data/app/helpers/simple_teams/teams_helper.rb +4 -0
- data/app/jobs/simple_teams/application_job.rb +4 -0
- data/app/mailers/simple_teams/application_mailer.rb +6 -0
- data/app/mailers/simple_teams/mailer.rb +17 -0
- data/app/models/concerns/simple_teams/member_object.rb +22 -0
- data/app/models/concerns/simple_teams/team_object.rb +23 -0
- data/app/models/simple_teams/ability.rb +34 -0
- data/app/models/simple_teams/application_record.rb +5 -0
- data/app/models/simple_teams/invitation.rb +85 -0
- data/app/models/simple_teams/membership.rb +16 -0
- data/app/models/simple_teams/team.rb +23 -0
- data/app/notifications/application_notification.rb +4 -0
- data/app/notifications/simple_teams/bulk_invitations_notification.rb +51 -0
- data/app/notifications/simple_teams/invitation_notification.rb +54 -0
- data/app/notifications/simple_teams/invitations/accepted_notification.rb +13 -0
- data/app/notifications/simple_teams/invitations/created_notification.rb +13 -0
- data/app/notifications/simple_teams/invitations/destroyed_notification.rb +13 -0
- data/app/notifications/simple_teams/invitations/updated_notification.rb +13 -0
- data/app/notifications/simple_teams/membership_notification.rb +58 -0
- data/app/notifications/simple_teams/memberships/destroyed_notification.rb +13 -0
- data/app/notifications/simple_teams/memberships/left_notification.rb +17 -0
- data/app/notifications/simple_teams/memberships/updated_notification.rb +17 -0
- data/app/service_objects/simple_teams/accept_invitation_service.rb +61 -0
- data/app/service_objects/simple_teams/initialize_team_service.rb +25 -0
- data/app/service_objects/simple_teams/leave_team_service.rb +56 -0
- data/app/views/layouts/simple_teams/application.html.erb +20 -0
- data/app/views/simple_teams/_roles_field.html.erb +22 -0
- data/app/views/simple_teams/accept_invitations/invalid_token.html.erb +7 -0
- data/app/views/simple_teams/accept_invitations/new.html.erb +18 -0
- data/app/views/simple_teams/admin/invitations/_form.html.erb +62 -0
- data/app/views/simple_teams/admin/invitations/_invitation.html.erb +47 -0
- data/app/views/simple_teams/admin/invitations/edit.html.erb +10 -0
- data/app/views/simple_teams/admin/invitations/index.html.erb +14 -0
- data/app/views/simple_teams/admin/invitations/new.html.erb +9 -0
- data/app/views/simple_teams/admin/invitations/show.html.erb +10 -0
- data/app/views/simple_teams/admin/memberships/_form.html.erb +32 -0
- data/app/views/simple_teams/admin/memberships/_membership.html.erb +17 -0
- data/app/views/simple_teams/admin/memberships/edit.html.erb +10 -0
- data/app/views/simple_teams/admin/memberships/index.html.erb +14 -0
- data/app/views/simple_teams/admin/memberships/new.html.erb +9 -0
- data/app/views/simple_teams/admin/memberships/show.html.erb +10 -0
- data/app/views/simple_teams/admin/teams/_form.html.erb +22 -0
- data/app/views/simple_teams/admin/teams/_team.html.erb +7 -0
- data/app/views/simple_teams/admin/teams/edit.html.erb +10 -0
- data/app/views/simple_teams/admin/teams/index.html.erb +14 -0
- data/app/views/simple_teams/admin/teams/new.html.erb +9 -0
- data/app/views/simple_teams/admin/teams/show.html.erb +10 -0
- data/app/views/simple_teams/invitations/_bulk_form.html.erb +63 -0
- data/app/views/simple_teams/invitations/_form.html.erb +15 -0
- data/app/views/simple_teams/invitations/_invitation.html.erb +19 -0
- data/app/views/simple_teams/invitations/_invitation.json.jbuilder +2 -0
- data/app/views/simple_teams/invitations/edit.html.erb +12 -0
- data/app/views/simple_teams/invitations/new.html.erb +12 -0
- data/app/views/simple_teams/leave_teams/edit.html.erb +2 -0
- data/app/views/simple_teams/memberships/_form.html.erb +12 -0
- data/app/views/simple_teams/memberships/_membership.html.erb +23 -0
- data/app/views/simple_teams/memberships/edit.html.erb +12 -0
- data/app/views/simple_teams/related_members/index.json.jbuilder +5 -0
- data/app/views/simple_teams/related_members/select2.json.jbuilder +6 -0
- data/app/views/simple_teams/teams/_add_member_link.html.erb +3 -0
- data/app/views/simple_teams/teams/_members_table.html.erb +20 -0
- data/app/views/simple_teams/teams/show.html.erb +38 -0
- data/app/views/simple_teams/teams_mailer/invitation_notification.html.erb +9 -0
- data/config/locales/simple_teams.en.yml +18 -0
- data/config/routes.rb +21 -0
- data/db/migrate/20240311052758_create_simple_teams_teams.rb +9 -0
- data/db/migrate/20240311053006_create_simple_teams_memberships.rb +11 -0
- data/db/migrate/20240311053607_create_simple_teams_invitations.rb +17 -0
- data/lib/simple_teams/engine.rb +22 -0
- data/lib/simple_teams/version.rb +3 -0
- data/lib/simple_teams.rb +6 -0
- data/lib/tasks/simple_teams_tasks.rake +4 -0
- metadata +160 -0
@@ -0,0 +1,125 @@
|
|
1
|
+
module SimpleTeams
|
2
|
+
class InvitationForms::CreateBulk < ApplicationForm
|
3
|
+
|
4
|
+
attr_accessor :team, :current_user, :single_vs_multiple, :select2_emails, :accessible_emails, :emails, :role
|
5
|
+
|
6
|
+
def initialize(team, current_user)
|
7
|
+
@team = team
|
8
|
+
@current_user = current_user
|
9
|
+
@emails = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def perform(params)
|
13
|
+
self.assign_attributes(params)
|
14
|
+
if accessible?
|
15
|
+
self.emails = accessible_emails.split(",").map(&:strip).uniq.reject(&:empty?)
|
16
|
+
else
|
17
|
+
self.emails = select2_emails.map(&:strip).uniq.reject(&:empty?)
|
18
|
+
end
|
19
|
+
|
20
|
+
if valid?
|
21
|
+
create_invitations
|
22
|
+
generate_notification
|
23
|
+
else
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Attributes
|
29
|
+
def available_roles
|
30
|
+
SimpleTeams::Membership.roles
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def accessible?
|
36
|
+
single_vs_multiple == "accessible"
|
37
|
+
end
|
38
|
+
|
39
|
+
def address_attribute
|
40
|
+
accessible? ? :accessible_emails : :select2_emails
|
41
|
+
end
|
42
|
+
|
43
|
+
def valid?
|
44
|
+
self.errors.clear
|
45
|
+
validate_attributes_present
|
46
|
+
validate_invitations
|
47
|
+
validate_role_is_available
|
48
|
+
validate_current_user_has_rights_to_invite_an_owner
|
49
|
+
!self.errors.present?
|
50
|
+
end
|
51
|
+
|
52
|
+
def validate_attributes_present
|
53
|
+
[:team, :current_user, :role].each do |a|
|
54
|
+
unless self.send(a).present?
|
55
|
+
self.errors.add(a, "can't be blank")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
unless self.emails.present?
|
59
|
+
self.errors.add(address_attribute, "can't be blank")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def validate_invitations
|
64
|
+
emails.each do |email|
|
65
|
+
invitation = SimpleTeams::Invitation.new(
|
66
|
+
:team => team,
|
67
|
+
:inviter => current_user,
|
68
|
+
:role => role,
|
69
|
+
:email => email
|
70
|
+
)
|
71
|
+
unless invitation.valid?
|
72
|
+
invitation.errors[:email].each do |error|
|
73
|
+
if error == "is invalid"
|
74
|
+
error_message = "#{email} is not a valid email address"
|
75
|
+
else
|
76
|
+
error_message = error.gsub("this email address", email)
|
77
|
+
end
|
78
|
+
self.errors.add(address_attribute, error_message)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def validate_role_is_available
|
85
|
+
# Note: Users will never see this error message unless they hack the form
|
86
|
+
if role.present?
|
87
|
+
unless available_roles.keys.include? role
|
88
|
+
self.errors.add(:role, "that role is not available for this resource")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def validate_current_user_has_rights_to_invite_an_owner
|
94
|
+
if role == "owner"
|
95
|
+
unless current_user.membership_for_team(team).owner?
|
96
|
+
self.errors.add(:role, "you must be an owner yourself to add add another owner")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def create_invitations
|
102
|
+
ActiveRecord::Base.transaction do
|
103
|
+
emails.each do |email|
|
104
|
+
SimpleTeams::Invitation.create!(
|
105
|
+
:team => team,
|
106
|
+
:inviter => current_user,
|
107
|
+
:role => role,
|
108
|
+
:email => email
|
109
|
+
)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def generate_notification
|
115
|
+
SimpleTeams::BulkInvitationsNotification.with(
|
116
|
+
:team_id => team.id,
|
117
|
+
:user_id => current_user.id,
|
118
|
+
:team_name => team.name,
|
119
|
+
:user_name => current_user.full_name,
|
120
|
+
:invitation_names => self.emails,
|
121
|
+
).deliver_later(team.members)
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module SimpleTeams
|
2
|
+
class InvitationForms::CreateCombo < ApplicationForm
|
3
|
+
|
4
|
+
attr_accessor :team, :current_user, :single_vs_multiple, :email, :emails, :select2_emails, :accessible_emails, :role, :form_instance
|
5
|
+
|
6
|
+
def initialize(team, current_user)
|
7
|
+
@team = team
|
8
|
+
@current_user = current_user
|
9
|
+
end
|
10
|
+
|
11
|
+
def perform(params)
|
12
|
+
# Store input
|
13
|
+
self.assign_attributes(params)
|
14
|
+
|
15
|
+
# Set form instance from input
|
16
|
+
@form_instance = selected_form_klass.new(team, current_user)
|
17
|
+
|
18
|
+
# Perform
|
19
|
+
if form_instance.perform(filter_params(params))
|
20
|
+
return true
|
21
|
+
else
|
22
|
+
reassign_errors
|
23
|
+
unless single?
|
24
|
+
self.emails = form_instance.emails
|
25
|
+
end
|
26
|
+
false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def available_roles
|
31
|
+
SimpleTeams::Membership.roles
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def single?
|
37
|
+
!multiple? and !accessible?
|
38
|
+
end
|
39
|
+
|
40
|
+
def multiple?
|
41
|
+
single_vs_multiple == "multiple"
|
42
|
+
end
|
43
|
+
|
44
|
+
def accessible?
|
45
|
+
single_vs_multiple == "accessible"
|
46
|
+
end
|
47
|
+
|
48
|
+
def selected_form_klass
|
49
|
+
if single?
|
50
|
+
SimpleTeams::InvitationForms::Create
|
51
|
+
else
|
52
|
+
SimpleTeams::InvitationForms::CreateBulk
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def filter_params(params)
|
57
|
+
if single?
|
58
|
+
params.except(:single_vs_multiple, :select2_emails, :accessible_emails)
|
59
|
+
else
|
60
|
+
params.except(:email)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def reassign_errors
|
65
|
+
[:team, :current_user, :email, :select2_emails, :accessible_emails, :role].each do |a|
|
66
|
+
if form_instance.errors[a].present?
|
67
|
+
form_instance.errors[a].each do |error|
|
68
|
+
self.errors.add(a, error)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module SimpleTeams
|
2
|
+
class InvitationForms::Update < SimpleTeams::InvitationForm
|
3
|
+
|
4
|
+
def initialize(invitation, current_user)
|
5
|
+
@invitation = invitation
|
6
|
+
@team = @invitation.team
|
7
|
+
@current_user = current_user
|
8
|
+
|
9
|
+
# Get existing attributes
|
10
|
+
self.class.invitation_attributes.each do |a|
|
11
|
+
self.assign_attributes(a => @invitation.send(a))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def generate_notification
|
18
|
+
SimpleTeams::Invitations::UpdatedNotification.with(
|
19
|
+
:team_id => team.id,
|
20
|
+
:invitation_id => invitation.id,
|
21
|
+
:user_id => current_user.id,
|
22
|
+
:team_name => team.name,
|
23
|
+
:invitation_name => invitation.email,
|
24
|
+
:user_name => current_user.full_name
|
25
|
+
).deliver_later(team.members)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module SimpleTeams
|
2
|
+
class MembershipForm < ApplicationForm
|
3
|
+
|
4
|
+
attr_accessor :membership, :current_user, :role
|
5
|
+
|
6
|
+
def initialize(membership, current_user)
|
7
|
+
@membership = membership
|
8
|
+
@current_user = current_user
|
9
|
+
|
10
|
+
# Get existing attributes
|
11
|
+
@role = @membership.role
|
12
|
+
end
|
13
|
+
|
14
|
+
def valid?
|
15
|
+
self.errors.clear
|
16
|
+
validate_role_is_available
|
17
|
+
validate_current_user_has_rights_to_modify_an_existing_owner
|
18
|
+
validate_current_user_has_rights_to_add_an_owner
|
19
|
+
validate_not_last_owner
|
20
|
+
!self.errors.present?
|
21
|
+
end
|
22
|
+
|
23
|
+
def perform(params)
|
24
|
+
self.assign_attributes(params)
|
25
|
+
|
26
|
+
if valid?
|
27
|
+
membership.update(params)
|
28
|
+
generate_notification
|
29
|
+
else
|
30
|
+
false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def available_roles
|
35
|
+
SimpleTeams::Membership.roles
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def validate_role_is_available
|
41
|
+
# Note: Users will never see this error message unless they hack the form
|
42
|
+
if role.present?
|
43
|
+
unless available_roles.keys.include? role
|
44
|
+
self.errors.add(:role, "that role is not available for this resource")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate_not_last_owner
|
50
|
+
if membership.owner? and only_one_owner? and role != "owner"
|
51
|
+
self.errors.add(:role, "you can't remove the last owner of a team")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def validate_current_user_has_rights_to_modify_an_existing_owner
|
56
|
+
if membership.owner?
|
57
|
+
unless current_user.membership_for_team(team).owner?
|
58
|
+
self.errors.add(:role, "you must be an owner yourself to modify another owner")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def validate_current_user_has_rights_to_add_an_owner
|
64
|
+
if role == "owner" and !membership.owner?
|
65
|
+
unless current_user.membership_for_team(team).owner?
|
66
|
+
self.errors.add(:role, "you must be an owner yourself to add add another owner")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def only_one_owner?
|
72
|
+
team.memberships.where(:role => :owner).count == 1
|
73
|
+
end
|
74
|
+
|
75
|
+
def team
|
76
|
+
membership.team
|
77
|
+
end
|
78
|
+
|
79
|
+
def generate_notification
|
80
|
+
SimpleTeams::Memberships::UpdatedNotification.with(
|
81
|
+
:team_id => team.id,
|
82
|
+
:member_id => membership.member.id,
|
83
|
+
:user_id => current_user.id,
|
84
|
+
:team_name => team.name,
|
85
|
+
:member_name => membership.member.full_name,
|
86
|
+
:user_name => current_user.full_name
|
87
|
+
).deliver_later(team.members)
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module SimpleTeams
|
2
|
+
class Mailer < ApplicationMailer
|
3
|
+
|
4
|
+
def invitation_notification(invitation)
|
5
|
+
@invitation = invitation
|
6
|
+
@name = invitation.team.name
|
7
|
+
@resource_name = invitation.team.teamable.class.model_name.human
|
8
|
+
@invitation_url = accept_team_invitation_url(@invitation.token)
|
9
|
+
|
10
|
+
mail(
|
11
|
+
to: (@invitation.email),
|
12
|
+
subject: "Invitation to #{@name} #{@resource_name}"
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module SimpleTeams::MemberObject
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
has_many :team_memberships, :class_name => "SimpleTeams::Membership", :foreign_key => :member_id, :dependent => :destroy
|
6
|
+
has_many :teams, :through => :team_memberships, :class_name => "SimpleTeams::Team"
|
7
|
+
has_many :team_members, :through => :teams, :source => :members, :class_name => SimpleTeams.member_class.to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
def member_of_team?(team)
|
11
|
+
team_memberships.where(team: team).present?
|
12
|
+
end
|
13
|
+
|
14
|
+
def membership_for_team(team)
|
15
|
+
team_memberships.find_by(:team_id => team.id)
|
16
|
+
end
|
17
|
+
|
18
|
+
def role_for_team(team)
|
19
|
+
membership_for_team(team).role if membership_for_team(team).present?
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SimpleTeams::TeamObject
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
attr_accessor :team_owner
|
6
|
+
|
7
|
+
has_one :team, :as => :teamable, :class_name => "SimpleTeams::Team", :dependent => :destroy
|
8
|
+
|
9
|
+
after_create :generate_team
|
10
|
+
|
11
|
+
def self.team_object?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def generate_team
|
20
|
+
self.create_team!
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module SimpleTeams
|
2
|
+
class Ability
|
3
|
+
include ::CanCan::Ability
|
4
|
+
|
5
|
+
def initialize(user)
|
6
|
+
|
7
|
+
return unless user.present?
|
8
|
+
|
9
|
+
can :create, Team
|
10
|
+
|
11
|
+
# Team
|
12
|
+
can :read, Team do |team|
|
13
|
+
user.member_of_team?(team)
|
14
|
+
end
|
15
|
+
|
16
|
+
can [:update], Team do |team|
|
17
|
+
%w"owner administrator".include? user.role_for_team(team)
|
18
|
+
end
|
19
|
+
|
20
|
+
can [:destroy], Team do |team|
|
21
|
+
%w"owner".include? user.role_for_team(team)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Team Memberships/Invitations
|
25
|
+
can :read, [Membership, Invitation] do |object|
|
26
|
+
user.member_of_team?(object.team)
|
27
|
+
end
|
28
|
+
|
29
|
+
can :manage, [Membership, Invitation] do |object|
|
30
|
+
%w"owner administrator".include? user.role_for_team(object.team)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module SimpleTeams
|
2
|
+
class Invitation < ApplicationRecord
|
3
|
+
belongs_to :team
|
4
|
+
belongs_to :inviter, :class_name => SimpleTeams.member_class.to_s
|
5
|
+
belongs_to :membership, :optional => true
|
6
|
+
|
7
|
+
# validations
|
8
|
+
validates_presence_of :email, :role
|
9
|
+
validates_format_of :email, :with => URI::MailTo::EMAIL_REGEXP
|
10
|
+
validate :email_uninvited, :on => :create
|
11
|
+
|
12
|
+
# callbacks
|
13
|
+
before_validation :format_email
|
14
|
+
before_create :generate_token
|
15
|
+
before_create :set_expires_at
|
16
|
+
after_create :send_invitation_notification
|
17
|
+
after_update :send_invitation_notification, :if => :saved_change_to_email?
|
18
|
+
|
19
|
+
# scopes
|
20
|
+
scope :expired, -> { where("? > expires_at", Time.now) }
|
21
|
+
scope :unexpired, -> { where("? <= expires_at", Time.now) }
|
22
|
+
|
23
|
+
# enums
|
24
|
+
enum :role, [:member, :administrator, :owner]
|
25
|
+
enum :status, [:initial, :accepted, :declined]
|
26
|
+
|
27
|
+
def expired?
|
28
|
+
Time.now > expires_at
|
29
|
+
end
|
30
|
+
|
31
|
+
def unexpired?
|
32
|
+
Time.now <= expires_at
|
33
|
+
end
|
34
|
+
|
35
|
+
def reset_invitation_token
|
36
|
+
generate_token
|
37
|
+
set_expires_at
|
38
|
+
self.save
|
39
|
+
end
|
40
|
+
|
41
|
+
def resend_invitation_notification(options = {})
|
42
|
+
if Time.now > expires_at - self.class.expiration_window + 1.day
|
43
|
+
reset_invitation_token
|
44
|
+
end
|
45
|
+
send_invitation_notification(options)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.expiration_window
|
49
|
+
2.weeks
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def format_email
|
55
|
+
self.email = self.email.downcase.strip if email.present?
|
56
|
+
end
|
57
|
+
|
58
|
+
def generate_token
|
59
|
+
self.token = SecureRandom.hex(32)
|
60
|
+
end
|
61
|
+
|
62
|
+
def set_expires_at
|
63
|
+
self.expires_at = Time.now + self.class.expiration_window
|
64
|
+
end
|
65
|
+
|
66
|
+
def send_invitation_notification(options = {})
|
67
|
+
if options[:now] == true
|
68
|
+
SimpleTeams::Mailer.invitation_notification(self).deliver_now
|
69
|
+
else
|
70
|
+
SimpleTeams::Mailer.invitation_notification(self).deliver_later
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def email_uninvited
|
75
|
+
if team.members.map { |m| m.email }.include?(email)
|
76
|
+
self.errors.add :email, "A person with this email address is already a member of this team."
|
77
|
+
return false
|
78
|
+
elsif team.invitations.initial.where(:email => email).present?
|
79
|
+
self.errors.add :email, "A person with this email address has already been invited to this team."
|
80
|
+
return false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module SimpleTeams
|
2
|
+
class Membership < ApplicationRecord
|
3
|
+
belongs_to :team
|
4
|
+
belongs_to :member, :class_name => SimpleTeams.member_class.to_s
|
5
|
+
has_one :invitation, :dependent => :nullify
|
6
|
+
|
7
|
+
enum :role, [:member, :administrator, :owner]
|
8
|
+
|
9
|
+
validates_presence_of :role
|
10
|
+
|
11
|
+
def role_to_s
|
12
|
+
role.humanize.capitalize
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SimpleTeams
|
2
|
+
class Team < ApplicationRecord
|
3
|
+
|
4
|
+
# Polymorphic relationship
|
5
|
+
belongs_to :teamable, polymorphic: true
|
6
|
+
|
7
|
+
# Team Relationships
|
8
|
+
has_many :invitations, :dependent => :destroy
|
9
|
+
has_many :memberships, :dependent => :destroy
|
10
|
+
has_many :members, :through => :memberships
|
11
|
+
|
12
|
+
delegate :name, :to => :teamable
|
13
|
+
|
14
|
+
def membership_for(member)
|
15
|
+
memberships.find_by(:member_id => member.id)
|
16
|
+
end
|
17
|
+
|
18
|
+
def remove_member(member)
|
19
|
+
memberships.find_by(:member_id => member.id).destroy
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module SimpleTeams
|
2
|
+
class BulkInvitationsNotification < ApplicationNotification
|
3
|
+
|
4
|
+
param :team_id, :user_id, :team_name, :user_name, :invitation_names
|
5
|
+
|
6
|
+
def message
|
7
|
+
"#{user_name} invited #{invitation_names.to_sentence} to #{team_name}."
|
8
|
+
end
|
9
|
+
|
10
|
+
def subject
|
11
|
+
"Users Invited"
|
12
|
+
end
|
13
|
+
|
14
|
+
def url
|
15
|
+
if team.present? and team.members.include? recipient
|
16
|
+
"/teams/#{team.id}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def link_text
|
21
|
+
"View Invitations"
|
22
|
+
end
|
23
|
+
|
24
|
+
# Objects
|
25
|
+
def team
|
26
|
+
Team.find_by(id: params[:team_id])
|
27
|
+
end
|
28
|
+
|
29
|
+
def user
|
30
|
+
SimpleTeams.member_class.find_by(id: params[:user_id])
|
31
|
+
end
|
32
|
+
|
33
|
+
# Names (fallback)
|
34
|
+
def team_name
|
35
|
+
team.present? ? team.name : params[:team_name]
|
36
|
+
end
|
37
|
+
|
38
|
+
def invitation_names
|
39
|
+
params[:invitation_names]
|
40
|
+
end
|
41
|
+
|
42
|
+
def user_name
|
43
|
+
if recipient.id == params[:user_id]
|
44
|
+
"You"
|
45
|
+
else
|
46
|
+
user.present? ? user.full_name : params[:user_name]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|