mirror of
https://github.com/openSUSE/osem.git
synced 2026-08-13 19:54:02 +00:00
Merge pull request #2535 from rahul2240/booth_invitation
Invitation for booth responsibles
This commit is contained in:
commit
770a63e15c
19 changed files with 273 additions and 8 deletions
1
Gemfile
1
Gemfile
|
|
@ -37,6 +37,7 @@ gem 'rails-i18n'
|
|||
# as authentification framework
|
||||
gem 'devise'
|
||||
gem 'devise_ichain_authenticatable'
|
||||
gem 'devise_invitable'
|
||||
|
||||
# for openID authentication
|
||||
gem 'omniauth'
|
||||
|
|
|
|||
|
|
@ -161,6 +161,9 @@ GEM
|
|||
warden (~> 1.2.3)
|
||||
devise_ichain_authenticatable (0.3.2)
|
||||
devise (>= 2.2)
|
||||
devise_invitable (2.0.1)
|
||||
actionmailer (>= 5.0)
|
||||
devise (>= 4.6)
|
||||
diff-lcs (1.3)
|
||||
docile (1.3.1)
|
||||
domain_name (0.5.20180417)
|
||||
|
|
@ -646,6 +649,7 @@ DEPENDENCIES
|
|||
delayed_job_active_record
|
||||
devise
|
||||
devise_ichain_authenticatable
|
||||
devise_invitable
|
||||
dotenv-rails
|
||||
factory_bot_rails
|
||||
faker
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ module Admin
|
|||
class BoothsController < Admin::BaseController
|
||||
load_and_authorize_resource :conference, find_by: :short_title
|
||||
load_and_authorize_resource through: :conference
|
||||
include Invitation
|
||||
|
||||
def index
|
||||
@file_name = "booths_for_#{@conference.short_title}"
|
||||
|
|
@ -38,6 +39,7 @@ module Admin
|
|||
@booth.submitter = current_user
|
||||
|
||||
if @booth.save
|
||||
booth_responsible_invite
|
||||
redirect_to admin_conference_booths_path,
|
||||
notice: 'Booth successfully created.'
|
||||
else
|
||||
|
|
@ -54,7 +56,7 @@ module Admin
|
|||
@url = admin_conference_booth_path(@conference.short_title, @booth.id)
|
||||
|
||||
@booth.update_attributes(booth_params)
|
||||
|
||||
booth_responsible_invite
|
||||
if @booth.save
|
||||
redirect_to admin_conference_booths_path,
|
||||
notice: "Successfully updated booth for #{@booth.title}."
|
||||
|
|
@ -129,7 +131,8 @@ module Admin
|
|||
|
||||
def booth_params
|
||||
params.require(:booth).permit(:title, :description, :reasoning, :state, :picture, :conference_id,
|
||||
:created_at, :updated_at, :submitter_relationship, :website_url, responsible_ids: [])
|
||||
:created_at, :updated_at, :submitter_relationship, :website_url, :invite_responsible,
|
||||
responsible_ids: [])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ class ApplicationController < ActionController::Base
|
|||
# Ensure every controller authorizes resource or skips authorization (skip_authorization_check)
|
||||
check_authorization unless: :devise_controller?
|
||||
|
||||
before_action :devise_configure_permitted_parameters, if: :devise_controller?
|
||||
|
||||
def store_location
|
||||
# store last url - this is needed for post-login redirect to whatever the user last visited.
|
||||
return unless request.get?
|
||||
|
|
@ -65,4 +67,8 @@ class ApplicationController < ActionController::Base
|
|||
def not_found
|
||||
raise ActionController::RoutingError.new('Not Found')
|
||||
end
|
||||
|
||||
def devise_configure_permitted_parameters
|
||||
devise_parameter_sanitizer.permit(:accept_invitation, keys: [:username])
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ class BoothsController < ApplicationController
|
|||
load_resource :conference, find_by: :short_title
|
||||
load_and_authorize_resource through: :conference
|
||||
skip_authorize_resource only: [:withdraw, :confirm, :restart]
|
||||
include Invitation
|
||||
|
||||
def index
|
||||
@booths = current_user.booths.where(conference_id: @conference.id).uniq
|
||||
|
|
@ -22,6 +23,7 @@ class BoothsController < ApplicationController
|
|||
@booth.submitter = current_user
|
||||
|
||||
if @booth.save
|
||||
booth_responsible_invite
|
||||
redirect_to conference_booths_path,
|
||||
notice: 'Booth successfully created.'
|
||||
else
|
||||
|
|
@ -38,6 +40,7 @@ class BoothsController < ApplicationController
|
|||
@url = conference_booth_path(@conference.short_title, @booth.id)
|
||||
@booth.update_attributes(booth_params)
|
||||
|
||||
booth_responsible_invite
|
||||
if @booth.save
|
||||
redirect_to conference_booths_path,
|
||||
notice: 'Booth successfully updated!'
|
||||
|
|
@ -94,6 +97,7 @@ class BoothsController < ApplicationController
|
|||
|
||||
def booth_params
|
||||
params.require(:booth).permit(:title, :description, :reasoning, :state, :picture, :conference_id,
|
||||
:created_at, :updated_at, :submitter_relationship, :website_url, responsible_ids: [])
|
||||
:created_at, :updated_at, :submitter_relationship, :website_url, :invite_responsible,
|
||||
responsible_ids: [])
|
||||
end
|
||||
end
|
||||
|
|
|
|||
21
app/controllers/concerns/invitation.rb
Normal file
21
app/controllers/concerns/invitation.rb
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
module Invitation
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def booth_responsible_invite
|
||||
if booth_params[:invite_responsible]
|
||||
emails_array = booth_params[:invite_responsible].split(',')
|
||||
|
||||
emails_array.each do |email|
|
||||
new_user = User.find_by(email: email)
|
||||
if new_user.nil?
|
||||
User.invite!({ email: email }, current_user)
|
||||
new_user = User.find_by(email: email)
|
||||
end
|
||||
if new_user && @booth.responsible_ids.exclude?(new_user.id)
|
||||
BoothRequest.create(booth_id: @booth.id, user_id: new_user.id,
|
||||
role: 'responsible')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -4,6 +4,8 @@ class Booth < ApplicationRecord
|
|||
include ActiveRecord::Transitions
|
||||
has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id }
|
||||
|
||||
attr_accessor :invite_responsible
|
||||
|
||||
belongs_to :conference
|
||||
has_many :booth_requests, dependent: :destroy
|
||||
has_many :users, through: :booth_requests
|
||||
|
|
|
|||
|
|
@ -49,8 +49,8 @@ class User < ApplicationRecord
|
|||
[:ichain_authenticatable, :ichain_registerable, :omniauthable, omniauth_providers: []]
|
||||
else
|
||||
[:database_authenticatable, :registerable,
|
||||
:recoverable, :rememberable, :trackable, :validatable, :confirmable,
|
||||
:omniauthable, omniauth_providers: [:suse, :google, :facebook, :github]]
|
||||
:recoverable, :rememberable, :trackable, :validatable, :invitable,
|
||||
:confirmable, :omniauthable, omniauth_providers: [:suse, :google, :facebook, :github]]
|
||||
end
|
||||
|
||||
devise(*devise_modules)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
hint: 'e.g. employee, comunity manager, etc'
|
||||
= f.input :website_url
|
||||
= responsibles_selector_input f
|
||||
= f.input :invite_responsible,
|
||||
hint: 'This field is to invite unregistered users using their email to become booth responsibles.'
|
||||
= image_tag f.object.picture.thumb.url if f.object.picture?
|
||||
= f.input :picture
|
||||
|
||||
|
|
@ -27,4 +29,18 @@
|
|||
plugins: ['remove_button'],
|
||||
minItems: 2
|
||||
} )
|
||||
$('#booth_invite_responsible').selectize({
|
||||
plugins: ['remove_button'],
|
||||
delimiter: ',',
|
||||
persist: false,
|
||||
create: function(input) {
|
||||
if (!/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(input)){
|
||||
return {}
|
||||
}
|
||||
return {
|
||||
value: input,
|
||||
text: input
|
||||
}
|
||||
}
|
||||
} )
|
||||
});
|
||||
|
|
|
|||
20
app/views/devise/invitations/edit.html.haml
Normal file
20
app/views/devise/invitations/edit.html.haml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
.container
|
||||
.row
|
||||
.col-md-6.col-md-offset-3
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
%h3.panel-title
|
||||
%h2= t 'devise.invitations.edit.header'
|
||||
.panel-body
|
||||
= form_for(resource, as: resource_name, url: invitation_path(resource_name), html: { method: :put }) do |f|
|
||||
= render 'devise/shared/error_messages', resource: resource
|
||||
= f.hidden_field :invitation_token, readonly: true
|
||||
- if f.object.class.require_password_on_accepting
|
||||
= f.label :username
|
||||
= f.text_field :username, class: 'form-control'
|
||||
= f.label :password
|
||||
= f.password_field :password, class: 'form-control'
|
||||
= f.label :password_confirmation
|
||||
= f.password_field :password_confirmation, class: 'form-control'
|
||||
%br
|
||||
= f.submit t('devise.invitations.edit.submit_button'), class: 'btn btn-default btn-primary'
|
||||
11
app/views/devise/mailer/invitation_instructions.html.erb
Normal file
11
app/views/devise/mailer/invitation_instructions.html.erb
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<p><%= t("devise.mailer.invitation_instructions.hello", email: @resource.email) %></p>
|
||||
|
||||
<p><%= t("devise.mailer.invitation_instructions.someone_invited_you", url: root_url) %></p>
|
||||
|
||||
<p><%= link_to t("devise.mailer.invitation_instructions.accept"), accept_invitation_url(@resource, invitation_token: @token) %></p>
|
||||
|
||||
<% if @resource.invitation_due_at %>
|
||||
<p><%= t("devise.mailer.invitation_instructions.accept_until", due_date: l(@resource.invitation_due_at, format: :'devise.mailer.invitation_instructions.accept_until_format')) %></p>
|
||||
<% end %>
|
||||
|
||||
<p><%= t("devise.mailer.invitation_instructions.ignore") %></p>
|
||||
11
app/views/devise/mailer/invitation_instructions.text.erb
Normal file
11
app/views/devise/mailer/invitation_instructions.text.erb
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<%= t("devise.mailer.invitation_instructions.hello", email: @resource.email) %>
|
||||
|
||||
<%= t("devise.mailer.invitation_instructions.someone_invited_you", url: root_url) %>
|
||||
|
||||
<%= accept_invitation_url(@resource, invitation_token: @token) %>
|
||||
|
||||
<% if @resource.invitation_due_at %>
|
||||
<%= t("devise.mailer.invitation_instructions.accept_until", due_date: l(@resource.invitation_due_at, format: :'devise.mailer.invitation_instructions.accept_until_format')) %>
|
||||
<% end %>
|
||||
|
||||
<%= t("devise.mailer.invitation_instructions.ignore") %>
|
||||
|
|
@ -96,6 +96,55 @@ Devise.setup do |config|
|
|||
# Setup a pepper to generate the encrypted password.
|
||||
# config.pepper = "9a89de1df41b08b48af904bbbc5b8d713addd9d14e2758f12dd14105acd8627fececa02f33f89c0e9764e74262d8874c4a52534b788f5aa4b1716522ae6888b1"
|
||||
|
||||
# ==> Configuration for :invitable
|
||||
# The period the generated invitation token is valid.
|
||||
# After this period, the invited resource won't be able to accept the invitation.
|
||||
# When invite_for is 0 (the default), the invitation won't expire.
|
||||
# config.invite_for = 2.weeks
|
||||
|
||||
# Number of invitations users can send.
|
||||
# - If invitation_limit is nil, there is no limit for invitations, users can
|
||||
# send unlimited invitations, invitation_limit column is not used.
|
||||
# - If invitation_limit is 0, users can't send invitations by default.
|
||||
# - If invitation_limit n > 0, users can send n invitations.
|
||||
# You can change invitation_limit column for some users so they can send more
|
||||
# or less invitations, even with global invitation_limit = 0
|
||||
# Default: nil
|
||||
# config.invitation_limit = 5
|
||||
|
||||
# The key to be used to check existing users when sending an invitation
|
||||
# and the regexp used to test it when validate_on_invite is not set.
|
||||
# config.invite_key = { email: /\A[^@]+@[^@]+\z/ }
|
||||
# config.invite_key = { email: /\A[^@]+@[^@]+\z/, username: nil }
|
||||
|
||||
# Ensure that invited record is valid.
|
||||
# The invitation won't be sent if this check fails.
|
||||
# Default: false
|
||||
# config.validate_on_invite = true
|
||||
|
||||
# Resend invitation if user with invited status is invited again
|
||||
# Default: true
|
||||
# config.resend_invitation = false
|
||||
|
||||
# The class name of the inviting model. If this is nil,
|
||||
# the #invited_by association is declared to be polymorphic.
|
||||
# Default: nil
|
||||
# config.invited_by_class_name = 'User'
|
||||
|
||||
# The foreign key to the inviting model (if invited_by_class_name is set)
|
||||
# Default: :invited_by_id
|
||||
# config.invited_by_foreign_key = :invited_by_id
|
||||
|
||||
# The column name used for counter_cache column. If this is nil,
|
||||
# the #invited_by association is declared without counter_cache.
|
||||
# Default: nil
|
||||
# config.invited_by_counter_cache = :invitations_count
|
||||
|
||||
# Auto-login after the user accepts the invite. If this is false,
|
||||
# the user will need to manually log in after accepting the invite.
|
||||
# Default: true
|
||||
# config.allow_insecure_sign_in_after_accept = false
|
||||
|
||||
# ==> Configuration for :confirmable
|
||||
# A period that the user is allowed to access the website even without
|
||||
# confirming his account. For instance, if set to 2.days, the user will be
|
||||
|
|
|
|||
31
config/locales/devise_invitable.en.yml
Normal file
31
config/locales/devise_invitable.en.yml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
en:
|
||||
devise:
|
||||
failure:
|
||||
invited: "You have a pending invitation, accept it to finish creating your account."
|
||||
invitations:
|
||||
send_instructions: "An invitation email has been sent to %{email}."
|
||||
invitation_token_invalid: "The invitation token provided is not valid!"
|
||||
updated: "Your password was set successfully. You are now signed in."
|
||||
updated_not_active: "Your password was set successfully."
|
||||
no_invitations_remaining: "No invitations remaining"
|
||||
invitation_removed: "Your invitation was removed."
|
||||
new:
|
||||
header: "Send invitation"
|
||||
submit_button: "Send an invitation"
|
||||
edit:
|
||||
header: "Set your password"
|
||||
submit_button: "Set my password"
|
||||
mailer:
|
||||
invitation_instructions:
|
||||
subject: "Invitation instructions"
|
||||
hello: "Hello %{email}"
|
||||
someone_invited_you: "Someone has invited you to %{url}, you can accept it through the link below."
|
||||
accept: "Accept invitation"
|
||||
accept_until: "This invitation will be due in %{due_date}."
|
||||
ignore: "If you don't want to accept the invitation, please ignore this email. Your account won't be created until you access the link above and set your password."
|
||||
time:
|
||||
formats:
|
||||
devise:
|
||||
mailer:
|
||||
invitation_instructions:
|
||||
accept_until_format: "%B %d, %Y %I:%M %p"
|
||||
23
db/migrate/20190603143107_devise_invitable_add_to_users.rb
Normal file
23
db/migrate/20190603143107_devise_invitable_add_to_users.rb
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
class DeviseInvitableAddToUsers < ActiveRecord::Migration[5.1]
|
||||
def up
|
||||
change_table :users do |t|
|
||||
t.string :invitation_token
|
||||
t.datetime :invitation_created_at
|
||||
t.datetime :invitation_sent_at
|
||||
t.datetime :invitation_accepted_at
|
||||
t.integer :invitation_limit
|
||||
t.references :invited_by, polymorphic: true
|
||||
t.integer :invitations_count, default: 0
|
||||
t.index :invitations_count
|
||||
t.index :invitation_token, unique: true # for invitable
|
||||
t.index :invited_by_id
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
change_table :users do |t|
|
||||
t.remove_references :invited_by, polymorphic: true
|
||||
t.remove :invitations_count, :invitation_limit, :invitation_sent_at, :invitation_accepted_at, :invitation_token, :invitation_created_at
|
||||
end
|
||||
end
|
||||
end
|
||||
14
db/schema.rb
14
db/schema.rb
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20181113195810) do
|
||||
ActiveRecord::Schema.define(version: 2019_06_03_143107) do
|
||||
|
||||
create_table "answers", force: :cascade do |t|
|
||||
t.string "title"
|
||||
|
|
@ -587,8 +587,20 @@ ActiveRecord::Schema.define(version: 20181113195810) do
|
|||
t.boolean "is_admin", default: false
|
||||
t.string "username"
|
||||
t.boolean "is_disabled", default: false
|
||||
t.string "invitation_token"
|
||||
t.datetime "invitation_created_at"
|
||||
t.datetime "invitation_sent_at"
|
||||
t.datetime "invitation_accepted_at"
|
||||
t.integer "invitation_limit"
|
||||
t.string "invited_by_type"
|
||||
t.integer "invited_by_id"
|
||||
t.integer "invitations_count", default: 0
|
||||
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
|
||||
t.index ["email"], name: "index_users_on_email", unique: true
|
||||
t.index ["invitation_token"], name: "index_users_on_invitation_token", unique: true
|
||||
t.index ["invitations_count"], name: "index_users_on_invitations_count"
|
||||
t.index ["invited_by_id"], name: "index_users_on_invited_by_id"
|
||||
t.index ["invited_by_type", "invited_by_id"], name: "index_users_on_invited_by_type_and_invited_by_id"
|
||||
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
||||
t.index ["username"], name: "index_users_on_username", unique: true
|
||||
end
|
||||
|
|
|
|||
|
|
@ -77,6 +77,18 @@ describe Admin::BoothsController do
|
|||
it 'shows success message' do
|
||||
expect(flash[:notice]).to match('Booth successfully created.')
|
||||
end
|
||||
|
||||
it 'creates a new user on inviting booth responsible' do
|
||||
expect(User.where(email: 'user@example.com')).to exist
|
||||
end
|
||||
|
||||
it 'does not create a user with invalid email on inviting booth responsible' do
|
||||
expect(User.where(email: 'example')).not_to exist
|
||||
end
|
||||
|
||||
it 'invited user should be a part of booth responsibles' do
|
||||
expect(Booth.last.responsibles.ids).to include(User.find_by(email: 'user@example.com').id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'create action fails' do
|
||||
|
|
@ -113,7 +125,7 @@ describe Admin::BoothsController do
|
|||
|
||||
describe 'PATCH #update' do
|
||||
context 'updates suchessfully' do
|
||||
before { patch :update, params: { id: booth.id, booth: attributes_for(:booth, title: 'different'), conference_id: conference.short_title } }
|
||||
before { patch :update, params: { id: booth.id, booth: attributes_for(:booth, title: 'different', invited_users: 'user@example.com, example'), conference_id: conference.short_title } }
|
||||
it 'redirects to admin booth index path' do
|
||||
expect(response).to redirect_to admin_conference_booths_path
|
||||
end
|
||||
|
|
@ -126,6 +138,19 @@ describe Admin::BoothsController do
|
|||
booth.reload
|
||||
expect(booth.title).to eq('different')
|
||||
end
|
||||
|
||||
it 'creates a new user on inviting booth responsible' do
|
||||
expect(User.where(email: 'user@example.com')).to exist
|
||||
end
|
||||
|
||||
it 'does not create a user with invalid email on inviting booth responsible' do
|
||||
expect(User.where(email: 'example')).not_to exist
|
||||
end
|
||||
|
||||
it 'invited user should be a part of booth responsibles' do
|
||||
booth.reload
|
||||
expect(booth.responsibles.ids).to include(User.find_by(email: 'user@example.com').id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -57,6 +57,18 @@ describe BoothsController do
|
|||
it 'shows success message' do
|
||||
expect(flash[:notice]).to match('Booth successfully created.')
|
||||
end
|
||||
|
||||
it 'creates a new user on inviting booth responsible' do
|
||||
expect(User.where(email: 'user@example.com')).to exist
|
||||
end
|
||||
|
||||
it 'does not create a user with invalid email on inviting booth responsible' do
|
||||
expect(User.where(email: 'example')).not_to exist
|
||||
end
|
||||
|
||||
it 'invited user should be a part of booth responsibles' do
|
||||
expect(Booth.last.responsibles.ids).to include(User.find_by(email: 'user@example.com').id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'create action fails' do
|
||||
|
|
@ -93,7 +105,7 @@ describe BoothsController do
|
|||
|
||||
describe 'PATCH #update' do
|
||||
context 'updates suchessfully' do
|
||||
before { patch :update, params: { id: booth.id, booth: attributes_for(:booth, title: 'different'), conference_id: conference.short_title } }
|
||||
before { patch :update, params: { id: booth.id, booth: attributes_for(:booth, title: 'different', invited_users: 'user@example.com, example'), conference_id: conference.short_title } }
|
||||
|
||||
it 'redirects to booth index path' do
|
||||
expect(response).to redirect_to conference_booths_path
|
||||
|
|
@ -107,6 +119,19 @@ describe BoothsController do
|
|||
booth.reload
|
||||
expect(booth.title).to eq('different')
|
||||
end
|
||||
|
||||
it 'creates a new user on inviting booth responsible' do
|
||||
expect(User.where(email: 'user@example.com')).to exist
|
||||
end
|
||||
|
||||
it 'does not create a user with invalid email on inviting booth responsible' do
|
||||
expect(User.where(email: 'example')).not_to exist
|
||||
end
|
||||
|
||||
it 'invited user should be a part of booth responsibles' do
|
||||
booth.reload
|
||||
expect(booth.responsibles.ids).to include(User.find_by(email: 'user@example.com').id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,5 +12,6 @@ FactoryBot.define do
|
|||
|
||||
submitter { create(:user) }
|
||||
responsible_ids { [create(:user).id] }
|
||||
invite_responsible { 'user@example.com, example' }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue