From 63c35628f36d1e8c164ca450b937fc9b37b0e88b Mon Sep 17 00:00:00 2001 From: murny <1930474+murny@users.noreply.github.com> Date: Fri, 30 Aug 2024 16:49:17 -0600 Subject: [PATCH] Add admin invitiations to project --- Gemfile | 1 + Gemfile.lock | 4 ++ app/models/admin.rb | 2 +- app/views/admin/admins/index.html.erb | 2 +- config/initializers/devise.rb | 49 +++++++++++++++++++ config/locales/devise_invitable.en.yml | 31 ++++++++++++ ...30185023_devise_invitable_add_to_admins.rb | 24 +++++++++ db/schema.rb | 13 ++++- test/integration/admin_invitation_test.rb | 30 ++++++++++++ 9 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 config/locales/devise_invitable.en.yml create mode 100644 db/migrate/20240830185023_devise_invitable_add_to_admins.rb create mode 100644 test/integration/admin_invitation_test.rb diff --git a/Gemfile b/Gemfile index 7a46838f..3640634d 100644 --- a/Gemfile +++ b/Gemfile @@ -71,3 +71,4 @@ end gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby] gem "devise", "~> 4.9" +gem "devise_invitable", "~> 2.0" diff --git a/Gemfile.lock b/Gemfile.lock index 2dc41061..3184a32a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -130,6 +130,9 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) + devise_invitable (2.0.9) + actionmailer (>= 5.0) + devise (>= 4.6) drb (2.2.1) erubi (1.13.0) execjs (2.9.1) @@ -411,6 +414,7 @@ DEPENDENCIES comfortable_mexican_sofa! cssbundling-rails devise (~> 4.9) + devise_invitable (~> 2.0) htmlentities image_processing (~> 1.13) jbuilder (~> 2.12) diff --git a/app/models/admin.rb b/app/models/admin.rb index 5d130f24..883e5d7e 100644 --- a/app/models/admin.rb +++ b/app/models/admin.rb @@ -3,6 +3,6 @@ class Admin < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :registerable, :timeoutable, and :omniauthable - devise :database_authenticatable, + devise :invitable, :database_authenticatable, :recoverable, :rememberable, :trackable, :validatable end diff --git a/app/views/admin/admins/index.html.erb b/app/views/admin/admins/index.html.erb index 75a88393..700a2d8d 100644 --- a/app/views/admin/admins/index.html.erb +++ b/app/views/admin/admins/index.html.erb @@ -1,5 +1,5 @@ diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 3480f0a0..68ba40da 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -134,6 +134,55 @@ # Send a notification email when the user's password is changed. # config.send_password_change_notification = false + # ==> 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 their account. For instance, if set to 2.days, the user will be diff --git a/config/locales/devise_invitable.en.yml b/config/locales/devise_invitable.en.yml new file mode 100644 index 00000000..f6bfee40 --- /dev/null +++ b/config/locales/devise_invitable.en.yml @@ -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" diff --git a/db/migrate/20240830185023_devise_invitable_add_to_admins.rb b/db/migrate/20240830185023_devise_invitable_add_to_admins.rb new file mode 100644 index 00000000..df0ab5b5 --- /dev/null +++ b/db/migrate/20240830185023_devise_invitable_add_to_admins.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class DeviseInvitableAddToAdmins < ActiveRecord::Migration[7.1] + def up + change_table :admins, bulk: true 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 :invitation_token, unique: true # for invitable + t.index :invited_by_id + end + end + + def down + change_table :admins, bulk: true 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 diff --git a/db/schema.rb b/db/schema.rb index f97b007b..5f80c4e7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_08_14_221626) do +ActiveRecord::Schema[7.1].define(version: 2024_08_30_185023) do create_table "active_storage_attachments", charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name", null: false t.string "record_type", null: false @@ -52,7 +52,18 @@ t.string "last_sign_in_ip" t.datetime "created_at", null: false t.datetime "updated_at", null: 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.bigint "invited_by_id" + t.integer "invitations_count", default: 0 t.index ["email"], name: "index_admins_on_email", unique: true + t.index ["invitation_token"], name: "index_admins_on_invitation_token", unique: true + t.index ["invited_by_id"], name: "index_admins_on_invited_by_id" + t.index ["invited_by_type", "invited_by_id"], name: "index_admins_on_invited_by" t.index ["reset_password_token"], name: "index_admins_on_reset_password_token", unique: true end diff --git a/test/integration/admin_invitation_test.rb b/test/integration/admin_invitation_test.rb new file mode 100644 index 00000000..c193e38d --- /dev/null +++ b/test/integration/admin_invitation_test.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require "test_helper" + +class AdminInvitationTest < ActionDispatch::IntegrationTest + setup do + @admin = admins(:admin) + end + + test "not able to invite other admins when logged out" do + get new_admin_invitation_path + + assert_redirected_to new_admin_session_path + end + + test "able to invite other admins when admin" do + sign_in @admin + + get new_admin_invitation_path + + assert_response :success + end + + # TODO: Add more tests here for the basic functionality: + # test "create an admin invitation" do + # end + + # test "accepting an admin invitation" do + # end +end