From b8e1a8bad3fb0551f515226b4fea6e1c865ee070 Mon Sep 17 00:00:00 2001 From: Rasmus Kjellberg <2277443+kjellberg@users.noreply.github.com> Date: Fri, 12 Apr 2024 22:59:18 +0200 Subject: [PATCH] refactor: move invites controller to gem --- .../users/invitations_controller.rb | 43 ------------------- .../kiqr/accounts/invitations/index.html.erb | 2 +- .../{users => kiqr}/invitations/show.html.erb | 8 +--- .../account_mailer/invitation_email.text.erb | 2 +- config/locales/kiqr.en.yml | 9 ---- config/routes/authentication.rb | 4 -- .../kiqr/invitations_controller.rb | 35 +++++++++++++++ gems/kiqr/config/locales/kiqr/en.yml | 18 +++++--- gems/kiqr/lib/kiqr.rb | 7 +++ gems/kiqr/lib/kiqr/rails/routes.rb | 5 +++ .../lib/kiqr/services/invitations/accept.rb | 27 ++++++++++++ .../lib/kiqr/services/invitations/reject.rb | 21 +++++++++ .../users/invitations_controller_test.rb | 14 +++--- 13 files changed, 117 insertions(+), 78 deletions(-) delete mode 100644 app/controllers/users/invitations_controller.rb rename app/views/{users => kiqr}/invitations/show.html.erb (71%) create mode 100644 gems/kiqr/app/controllers/kiqr/invitations_controller.rb create mode 100644 gems/kiqr/lib/kiqr/services/invitations/accept.rb create mode 100644 gems/kiqr/lib/kiqr/services/invitations/reject.rb diff --git a/app/controllers/users/invitations_controller.rb b/app/controllers/users/invitations_controller.rb deleted file mode 100644 index e3e6fe3..0000000 --- a/app/controllers/users/invitations_controller.rb +++ /dev/null @@ -1,43 +0,0 @@ -class Users::InvitationsController < ApplicationController - skip_before_action :authenticate_user!, only: [:show] - - def show - @invitation = AccountInvitation.find_puid!(params[:id]) - - if @invitation.accepted_at? - flash[:alert] = "Invitation has already been accepted by you or someone else." - return redirect_to dashboard_path - elsif current_user&.accounts&.include? @invitation.account - flash[:notice] = "You are already a member of this team." - return redirect_to dashboard_path(account_id: @invitation.account) - end - - @team = @invitation.account - session[:after_sign_in_path] = user_invitation_path(@invitation) - end - - def update - return redirect_back(fallback_location: dashboard_path) unless params[:user_invitation][:status] == "accepted" - return head :forbidden if AccountInvitation.find_puid!(params[:id]).accepted_at? - - @invitation = AccountInvitation.find_puid!(params[:id]) - @account = @invitation.account - @account.account_users.new(user: current_user) - - if @account.save - @invitation.update(accepted_at: Time.now) - flash[:notice] = "You have successfully joined the team" - redirect_to dashboard_path(account_id: @account) - else - redirect_to user_invitation_path(@invitation), alert: "Failed to accept invitation. Please try again later" - end - end - - def destroy - @invitation = AccountInvitation.find_puid!(params[:id]) - @invitation.destroy - - flash[:notice] = "You have declined the invitation" - redirect_to dashboard_path - end -end diff --git a/app/views/kiqr/accounts/invitations/index.html.erb b/app/views/kiqr/accounts/invitations/index.html.erb index 4290530..2e73295 100644 --- a/app/views/kiqr/accounts/invitations/index.html.erb +++ b/app/views/kiqr/accounts/invitations/index.html.erb @@ -22,7 +22,7 @@ <% @invitations.each do |invitation| %>
<%= t(".instructions", team_name: @team.name) %>
<%= t(".guest_instructions", team_name: @team.name) %>
diff --git a/app/views/mailers/account_mailer/invitation_email.text.erb b/app/views/mailers/account_mailer/invitation_email.text.erb index 95c1a0a..de8c632 100644 --- a/app/views/mailers/account_mailer/invitation_email.text.erb +++ b/app/views/mailers/account_mailer/invitation_email.text.erb @@ -3,6 +3,6 @@ <%= t(".instructions", team_name: @account.name, app_name: Kiqr::Config.app_name) %> -<%= user_invitation_url(@invitation) %> +<%= invitation_url(@invitation) %> <%= t(".thanks") %> diff --git a/config/locales/kiqr.en.yml b/config/locales/kiqr.en.yml index 69162df..40d456d 100644 --- a/config/locales/kiqr.en.yml +++ b/config/locales/kiqr.en.yml @@ -128,15 +128,6 @@ en: owned_team_accounts: "Team accounts you are the owner of:" submit: "Delete my account" confirmation_message: "Are you sure? This will DELETE all of your data." - invitations: - show: - title: "Accept invitation" - description: "You've been invited to join team %{team_name}." - guest_instructions: "You've been invited to join team %{team_name}. To accept the invitation, please create a new account or sign in with an existing one." - instructions: "You've been invited to join team %{team_name}. Choose whether you want to accept or decline the invitation." - sign_up: "Create a new account" - sign_in: "Sign in" - or: "or" two_factor: show: title: "Two-factor authentication" diff --git a/config/routes/authentication.rb b/config/routes/authentication.rb index 418e14b..b255fd0 100644 --- a/config/routes/authentication.rb +++ b/config/routes/authentication.rb @@ -15,10 +15,6 @@ resource :preferences, only: %i[edit update], as: :user_preferences end -scope module: :users, path: nil do - resources :invitations, only: %i[show update destroy], as: :user_invitation -end - scope "(/team/:account_id)", account_id: %r{[^/]+} do resources :members, controller: "accounts/members", only: [:index, :edit, :update, :destroy] end diff --git a/gems/kiqr/app/controllers/kiqr/invitations_controller.rb b/gems/kiqr/app/controllers/kiqr/invitations_controller.rb new file mode 100644 index 0000000..49d1b70 --- /dev/null +++ b/gems/kiqr/app/controllers/kiqr/invitations_controller.rb @@ -0,0 +1,35 @@ +class Kiqr::InvitationsController < KiqrController + skip_before_action :authenticate_user!, only: [:show] + + def show + @invitation = AccountInvitation.find_puid!(params[:id]) + + if @invitation.accepted_at? + flash[:alert] = "Invitation has already been accepted by you or someone else." + return redirect_to dashboard_path + elsif @invitation.account.has_member?(current_user) + flash[:notice] = "You are already a member of this team." + return redirect_to dashboard_path(account_id: @invitation.account) + end + + @team = @invitation.account + session[:after_sign_in_path] = invitation_path(@invitation) + end + + def accept + @invite = AccountInvitation.find_puid!(params[:id]) + Kiqr::Services::Invitations::Accept.call!(invitation: @invite, user: current_user) + kiqr_flash_message(:notice, :invitation_accepted, account: @invite.account.name) + redirect_to dashboard_path(account_id: @invite.account) + rescue Kiqr::Errors::InvitationExpired + kiqr_flash_message(:alert, :invitation_expired) + redirect_back(fallback_location: dashboard_path) + end + + def reject + @invitation = AccountInvitation.find_puid!(params[:id]) + Kiqr::Services::Invitations::Reject.call!(invitation: @invitation, user: current_user) + kiqr_flash_message(:alert, :invitation_rejected, account: @invitation.account.name) + redirect_to dashboard_path + end +end diff --git a/gems/kiqr/config/locales/kiqr/en.yml b/gems/kiqr/config/locales/kiqr/en.yml index feaac0a..c5dfd43 100644 --- a/gems/kiqr/config/locales/kiqr/en.yml +++ b/gems/kiqr/config/locales/kiqr/en.yml @@ -5,8 +5,9 @@ en: account_updated: "Your account has been updated successfully." invitation_sent: "An invitation email has been sent to %{email}." invitation_destroyed: "The invitation has been destroyed." - invitation_accepted: "You have accepted the invitation to join %{team}." - invitation_rejected: "You have rejected the invitation to join %{team}." + invitation_accepted: "You have accepted the invitation to join %{account}." + invitation_rejected: "You have rejected the invitation to join %{account}." + invitation_expired: "The invitation has expired." accounts: new: title: "Create a new team account" @@ -51,7 +52,12 @@ en: buttons: invite: "Invite a new user" back: "Back to team members" - create: - invitation_sent: "Invitation has been sent to %{email}." - destroy: - deleted: "Invitation has been successfully deleted." + invitations: + show: + title: "Accept invitation" + description: "You've been invited to join team %{team_name}." + guest_instructions: "You've been invited to join team %{team_name}. To accept the invitation, please create a new account or sign in with an existing one." + instructions: "You've been invited to join team %{team_name}. Choose whether you want to accept or decline the invitation." + sign_up: "Create a new account" + sign_in: "Sign in" + or: "or" diff --git a/gems/kiqr/lib/kiqr.rb b/gems/kiqr/lib/kiqr.rb index de110a1..9f3c664 100644 --- a/gems/kiqr/lib/kiqr.rb +++ b/gems/kiqr/lib/kiqr.rb @@ -16,11 +16,18 @@ module Accounts end module Invitations + autoload :Accept, "kiqr/services/invitations/accept" autoload :Create, "kiqr/services/invitations/create" autoload :Destroy, "kiqr/services/invitations/destroy" + autoload :Reject, "kiqr/services/invitations/reject" end end + module Errors + # Raised when an invitation has expired + class InvitationExpired < StandardError; end + end + def self.config @config ||= Kiqr::Config end diff --git a/gems/kiqr/lib/kiqr/rails/routes.rb b/gems/kiqr/lib/kiqr/rails/routes.rb index 35f0d7b..8160edb 100644 --- a/gems/kiqr/lib/kiqr/rails/routes.rb +++ b/gems/kiqr/lib/kiqr/rails/routes.rb @@ -8,6 +8,11 @@ def kiqr_routes(options = {}) account_routes(options) devise_routes(options) + resources :invitations, controller: "kiqr/invitations", only: %i[show destroy] do + post :accept, on: :member + post :reject, on: :member + end + teamable_scope do resources :account_invitations, controller: "kiqr/accounts/invitations", only: [:index, :new, :create, :destroy] end diff --git a/gems/kiqr/lib/kiqr/services/invitations/accept.rb b/gems/kiqr/lib/kiqr/services/invitations/accept.rb new file mode 100644 index 0000000..1f159a2 --- /dev/null +++ b/gems/kiqr/lib/kiqr/services/invitations/accept.rb @@ -0,0 +1,27 @@ +module Kiqr + module Services + module Invitations + class Accept < Kiqr::ApplicationService + def call(invitation:, user:) + @invitation, @user = invitation, user + @account = @invitation.account + + raise Kiqr::Errors::InvitationExpired, "Invitation has already been used" if invitation.accepted_at? + + account.transaction do + invitation.transaction do + account.account_users.create!(user:) + invitation.update!(accepted_at: Time.now) + end + end + + success invitation + end + + private + + attr_reader :account, :invitation, :user + end + end + end +end diff --git a/gems/kiqr/lib/kiqr/services/invitations/reject.rb b/gems/kiqr/lib/kiqr/services/invitations/reject.rb new file mode 100644 index 0000000..fbd0e0c --- /dev/null +++ b/gems/kiqr/lib/kiqr/services/invitations/reject.rb @@ -0,0 +1,21 @@ +module Kiqr + module Services + module Invitations + class Reject < Kiqr::ApplicationService + def call(invitation:, user:) + @invitation, @user = invitation, user + + invitation.transaction do + invitation.destroy! + end + + success invitation + end + + private + + attr_reader :invitation, :user + end + end + end +end diff --git a/test/controllers/users/invitations_controller_test.rb b/test/controllers/users/invitations_controller_test.rb index 65d12aa..073ce33 100644 --- a/test/controllers/users/invitations_controller_test.rb +++ b/test/controllers/users/invitations_controller_test.rb @@ -3,7 +3,7 @@ class Users::InvitationsControllerTest < ActionDispatch::IntegrationTest test "can view page without beeing signed in" do invitation = create(:account_invitation) - get user_invitation_path(invitation) + get invitation_path(invitation) assert_response :success end @@ -13,7 +13,7 @@ class Users::InvitationsControllerTest < ActionDispatch::IntegrationTest sign_in user assert_difference -> { user.accounts.count } do - patch user_invitation_path(invitation), params: {user_invitation: {status: "accepted"}} + post accept_invitation_path(invitation) end assert_redirected_to dashboard_path(account_id: invitation.account) @@ -25,22 +25,20 @@ class Users::InvitationsControllerTest < ActionDispatch::IntegrationTest invitation = create(:account_invitation, accepted_at: Time.now) sign_in user - get user_invitation_path(invitation) + get invitation_path(invitation) assert_redirected_to dashboard_path assert_no_difference -> { user.accounts.count } do - patch user_invitation_path(invitation), params: {user_invitation: {status: "accepted"}} + post accept_invitation_path(invitation) end - - assert_response :forbidden end - test "can decline invitation" do + test "can reject invitation" do user = create(:user) invitation = create(:account_invitation) sign_in user - delete user_invitation_path(invitation) + post reject_invitation_path(invitation) assert_redirected_to dashboard_path assert_nil AccountInvitation.find_by(id: invitation.id)