init invitation

This commit is contained in:
Christophe Robillard 2026-01-12 19:24:51 +01:00
parent 604d517b2c
commit 4567f3ef6e
8 changed files with 96 additions and 2 deletions

View file

@ -0,0 +1,25 @@
class InvitationsController < ApplicationController
before_action :set_user_by_token, only: %i[ edit update ]
allow_unauthenticated_access
def edit
end
def update
password_params = params.expect(user: [ :password, :password_confirmation ])
if @user.update(password_params.merge(invitation_accepted_at: Time.current))
redirect_to root_path, notice: "Account activated!"
else
redirect_to invitation_path(params[:token]), alert: "Passwords did not match."
end
end
private
def set_user_by_token
@user = User.find_by_invitation_token(params[:token])
rescue ActiveSupport::MessageVerifier::InvalidSignature
redirect_to root_path, alert: "Invitation link is invalid or has expired."
end
end

View file

@ -0,0 +1,8 @@
class UserMailer < ApplicationMailer
def invitation_email(user, token)
@user = user
@invite_url = edit_invitation_url(token: token)
mail(to: @user.email_address, subject: "Vous êtes invité à rejoindre la kluk")
end
end

View file

@ -3,4 +3,29 @@ class User < ApplicationRecord
has_many :sessions, dependent: :destroy
normalizes :email_address, with: ->(e) { e.strip.downcase }
generates_token_for :invitation, expires_in: 5.days do
invitation_accepted_at
end
def invite_user!(email)
invited_user = User.create!(
email_address: email,
password: SecureRandom.hex(16) # temporary password
)
# Send invitation email using the generated token
token = invited_user.generate_token_for(:invitation)
UserMailer.invitation_email(invited_user, token).deliver_later
invited_user
end
def self.find_by_invitation_token(token)
find_by_token_for(:invitation, token)
end
def invitation_token
generate_token_for(:invitation)
end
end

View file

@ -0,0 +1,24 @@
<div>
<h1>Set up your password</h1>
<p>Please set your password to activate your account.</p>
<%= form_with model: @user, url: invitation_path(token: params[:token]), method: :patch do |form| %>
<div>
<%= form.password_field :password,
required: true,
autocomplete: "new-password",
placeholder: "Enter new password" %>
</div>
<div>
<%= form.password_field :password_confirmation,
required: true,
autocomplete: "new-password",
placeholder: "Repeat new password" %>
</div>
<div>
<%= form.submit "Activate Account" %>
</div>
<% end %>
</div>

View file

@ -0,0 +1,3 @@
<h1>You're invited!</h1>
<p>Click here to set up your account:</p>
<%= link_to "Set up account", @invite_url %>

View file

@ -1,7 +1,7 @@
Rails.application.routes.draw do
resource :session
resources :passwords, param: :token
post '/rfid_tags' => 'rfid_tags#create'
post "/rfid_tags" => "rfid_tags#create"
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
@ -14,4 +14,7 @@ Rails.application.routes.draw do
# Defines the root path route ("/")
root "moods#index"
get "/invite/:token", to: "invitations#edit", as: :edit_invitation
patch "/invite/:token", to: "invitations#update", as: :invitation
end

View file

@ -0,0 +1,5 @@
class AddInvitationAcceptedAtToUsers < ActiveRecord::Migration[8.0]
def change
add_column :users, :invitation_accepted_at, :datetime
end
end

3
db/schema.rb generated
View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2026_01_12_141055) do
ActiveRecord::Schema[8.0].define(version: 2026_01_12_150457) do
create_table "moods", force: :cascade do |t|
t.string "mode"
t.datetime "recorded_at"
@ -40,6 +40,7 @@ ActiveRecord::Schema[8.0].define(version: 2026_01_12_141055) do
t.string "password_digest", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "invitation_accepted_at"
t.index ["email_address"], name: "index_users_on_email_address", unique: true
end