Add invitation link to pending family invitations email

This commit is contained in:
Eugene Burmakin 2025-11-07 12:07:58 +01:00
parent eed9480a9e
commit 313354bf7c
6 changed files with 125 additions and 110 deletions

View file

@ -5,14 +5,14 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby File.read('.ruby-version').strip ruby File.read('.ruby-version').strip
gem 'activerecord-postgis-adapter' gem 'activerecord-postgis-adapter', '~> 11.0'
# https://meta.discourse.org/t/cant-rebuild-due-to-aws-sdk-gem-bump-and-new-aws-data-integrity-protections/354217/40 # https://meta.discourse.org/t/cant-rebuild-due-to-aws-sdk-gem-bump-and-new-aws-data-integrity-protections/354217/40
gem 'aws-sdk-core', '~> 3.215.1', require: false gem 'aws-sdk-core', '~> 3.215.1', require: false
gem 'aws-sdk-kms', '~> 1.96.0', require: false gem 'aws-sdk-kms', '~> 1.96.0', require: false
gem 'aws-sdk-s3', '~> 1.177.0', require: false gem 'aws-sdk-s3', '~> 1.177.0', require: false
gem 'bootsnap', require: false gem 'bootsnap', require: false
gem 'chartkick' gem 'chartkick'
gem 'data_migrate', '>= 11.3.1' gem 'data_migrate'
gem 'devise' gem 'devise'
gem 'geocoder', github: 'Freika/geocoder', branch: 'master' gem 'geocoder', github: 'Freika/geocoder', branch: 'master'
gem 'gpx' gem 'gpx'
@ -34,7 +34,7 @@ gem 'rails_icons'
gem 'redis' gem 'redis'
gem 'rexml' gem 'rexml'
gem 'rgeo' gem 'rgeo'
gem 'rgeo-activerecord', '>= 8.1.0' gem 'rgeo-activerecord', '~> 8.0.0'
gem 'rgeo-geojson' gem 'rgeo-geojson'
gem 'rqrcode', '~> 3.0' gem 'rqrcode', '~> 3.0'
gem 'rswag-api' gem 'rswag-api'

View file

@ -10,29 +10,29 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (8.0.4) actioncable (8.0.3)
actionpack (= 8.0.4) actionpack (= 8.0.3)
activesupport (= 8.0.4) activesupport (= 8.0.3)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
actionmailbox (8.0.4) actionmailbox (8.0.3)
actionpack (= 8.0.4) actionpack (= 8.0.3)
activejob (= 8.0.4) activejob (= 8.0.3)
activerecord (= 8.0.4) activerecord (= 8.0.3)
activestorage (= 8.0.4) activestorage (= 8.0.3)
activesupport (= 8.0.4) activesupport (= 8.0.3)
mail (>= 2.8.0) mail (>= 2.8.0)
actionmailer (8.0.4) actionmailer (8.0.3)
actionpack (= 8.0.4) actionpack (= 8.0.3)
actionview (= 8.0.4) actionview (= 8.0.3)
activejob (= 8.0.4) activejob (= 8.0.3)
activesupport (= 8.0.4) activesupport (= 8.0.3)
mail (>= 2.8.0) mail (>= 2.8.0)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
actionpack (8.0.4) actionpack (8.0.3)
actionview (= 8.0.4) actionview (= 8.0.3)
activesupport (= 8.0.4) activesupport (= 8.0.3)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
rack (>= 2.2.4) rack (>= 2.2.4)
rack-session (>= 1.0.1) rack-session (>= 1.0.1)
@ -40,38 +40,38 @@ GEM
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
useragent (~> 0.16) useragent (~> 0.16)
actiontext (8.0.4) actiontext (8.0.3)
actionpack (= 8.0.4) actionpack (= 8.0.3)
activerecord (= 8.0.4) activerecord (= 8.0.3)
activestorage (= 8.0.4) activestorage (= 8.0.3)
activesupport (= 8.0.4) activesupport (= 8.0.3)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (8.0.4) actionview (8.0.3)
activesupport (= 8.0.4) activesupport (= 8.0.3)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.11) erubi (~> 1.11)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
activejob (8.0.4) activejob (8.0.3)
activesupport (= 8.0.4) activesupport (= 8.0.3)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (8.0.4) activemodel (8.0.3)
activesupport (= 8.0.4) activesupport (= 8.0.3)
activerecord (8.0.4) activerecord (8.0.3)
activemodel (= 8.0.4) activemodel (= 8.0.3)
activesupport (= 8.0.4) activesupport (= 8.0.3)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activerecord-postgis-adapter (11.0.0) activerecord-postgis-adapter (11.0.0)
activerecord (~> 8.0.0) activerecord (~> 8.0.0)
rgeo-activerecord (~> 8.0.0) rgeo-activerecord (~> 8.0.0)
activestorage (8.0.4) activestorage (8.0.3)
actionpack (= 8.0.4) actionpack (= 8.0.3)
activejob (= 8.0.4) activejob (= 8.0.3)
activerecord (= 8.0.4) activerecord (= 8.0.3)
activesupport (= 8.0.4) activesupport (= 8.0.3)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (8.0.4) activesupport (8.0.3)
base64 base64
benchmark (>= 0.3) benchmark (>= 0.3)
bigdecimal bigdecimal
@ -106,7 +106,7 @@ GEM
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
base64 (0.3.0) base64 (0.3.0)
bcrypt (3.1.20) bcrypt (3.1.20)
benchmark (0.5.0) benchmark (0.4.1)
bigdecimal (3.3.1) bigdecimal (3.3.1)
bootsnap (1.18.6) bootsnap (1.18.6)
msgpack (~> 1.2) msgpack (~> 1.2)
@ -139,7 +139,7 @@ GEM
tzinfo tzinfo
unicode (>= 0.4.4.5) unicode (>= 0.4.4.5)
csv (3.3.4) csv (3.3.4)
data_migrate (11.3.0) data_migrate (11.3.1)
activerecord (>= 6.1) activerecord (>= 6.1)
railties (>= 6.1) railties (>= 6.1)
database_consistency (2.0.6) database_consistency (2.0.6)
@ -328,20 +328,20 @@ GEM
rack (>= 1.3) rack (>= 1.3)
rackup (2.2.1) rackup (2.2.1)
rack (>= 3) rack (>= 3)
rails (8.0.4) rails (8.0.3)
actioncable (= 8.0.4) actioncable (= 8.0.3)
actionmailbox (= 8.0.4) actionmailbox (= 8.0.3)
actionmailer (= 8.0.4) actionmailer (= 8.0.3)
actionpack (= 8.0.4) actionpack (= 8.0.3)
actiontext (= 8.0.4) actiontext (= 8.0.3)
actionview (= 8.0.4) actionview (= 8.0.3)
activejob (= 8.0.4) activejob (= 8.0.3)
activemodel (= 8.0.4) activemodel (= 8.0.3)
activerecord (= 8.0.4) activerecord (= 8.0.3)
activestorage (= 8.0.4) activestorage (= 8.0.3)
activesupport (= 8.0.4) activesupport (= 8.0.3)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 8.0.4) railties (= 8.0.3)
rails-dom-testing (2.3.0) rails-dom-testing (2.3.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
minitest minitest
@ -352,9 +352,9 @@ GEM
rails_icons (1.4.0) rails_icons (1.4.0)
nokogiri (~> 1.16, >= 1.16.4) nokogiri (~> 1.16, >= 1.16.4)
rails (> 6.1) rails (> 6.1)
railties (8.0.4) railties (8.0.3)
actionpack (= 8.0.4) actionpack (= 8.0.3)
activesupport (= 8.0.4) activesupport (= 8.0.3)
irb (~> 1.13) irb (~> 1.13)
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
@ -408,17 +408,17 @@ GEM
rspec-mocks (~> 3.13) rspec-mocks (~> 3.13)
rspec-support (~> 3.13) rspec-support (~> 3.13)
rspec-support (3.13.3) rspec-support (3.13.3)
rswag-api (2.16.0) rswag-api (2.17.0)
activesupport (>= 5.2, < 8.1) activesupport (>= 5.2, < 8.2)
railties (>= 5.2, < 8.1) railties (>= 5.2, < 8.2)
rswag-specs (2.16.0) rswag-specs (2.17.0)
activesupport (>= 5.2, < 8.1) activesupport (>= 5.2, < 8.2)
json-schema (>= 2.2, < 6.0) json-schema (>= 2.2, < 7.0)
railties (>= 5.2, < 8.1) railties (>= 5.2, < 8.2)
rspec-core (>= 2.14) rspec-core (>= 2.14)
rswag-ui (2.16.0) rswag-ui (2.17.0)
actionpack (>= 5.2, < 8.1) actionpack (>= 5.2, < 8.2)
railties (>= 5.2, < 8.1) railties (>= 5.2, < 8.2)
rubocop (1.81.1) rubocop (1.81.1)
json (~> 2.3) json (~> 2.3)
language_server-protocol (~> 3.17.0.2) language_server-protocol (~> 3.17.0.2)
@ -540,7 +540,7 @@ PLATFORMS
x86_64-linux x86_64-linux
DEPENDENCIES DEPENDENCIES
activerecord-postgis-adapter activerecord-postgis-adapter (~> 11.0)
aws-sdk-core (~> 3.215.1) aws-sdk-core (~> 3.215.1)
aws-sdk-kms (~> 1.96.0) aws-sdk-kms (~> 1.96.0)
aws-sdk-s3 (~> 1.177.0) aws-sdk-s3 (~> 1.177.0)
@ -580,7 +580,7 @@ DEPENDENCIES
redis redis
rexml rexml
rgeo rgeo
rgeo-activerecord rgeo-activerecord (~> 8.0.0)
rgeo-geojson rgeo-geojson
rqrcode (~> 3.0) rqrcode (~> 3.0)
rspec-rails (>= 8.0.1) rspec-rails (>= 8.0.1)

File diff suppressed because one or more lines are too long

View file

@ -81,7 +81,7 @@ module Families
end end
def send_invitation_email(invitation) def send_invitation_email(invitation)
Families::Invitations::SendingJob.perform_later(invitation.id) Family::Invitations::SendingJob.perform_later(invitation.id)
end end
def send_notification def send_notification

View file

@ -175,7 +175,8 @@
<% if @pending_invitations.any? %> <% if @pending_invitations.any? %>
<div class="space-y-3 mb-4"> <div class="space-y-3 mb-4">
<% @pending_invitations.each do |invitation| %> <% @pending_invitations.each do |invitation| %>
<div class="flex items-center justify-between p-3 bg-base-100 rounded-lg"> <div class="p-3 bg-base-100 rounded-lg">
<div class="flex items-center justify-between">
<div class="flex-grow"> <div class="flex-grow">
<div class="font-medium text-base-content"><%= invitation.email %></div> <div class="font-medium text-base-content"><%= invitation.email %></div>
<div class="text-sm text-base-content opacity-60"> <div class="text-sm text-base-content opacity-60">
@ -186,16 +187,6 @@
<%= t('families.show.expires_on', default: 'Expires') %> <%= t('families.show.expires_on', default: 'Expires') %>
<%= invitation.expires_at.strftime('%b %d, %Y at %I:%M %p') %> <%= invitation.expires_at.strftime('%b %d, %Y at %I:%M %p') %>
</div> </div>
<div class="mt-2">
<button data-controller="clipboard"
data-clipboard-text-value="<%= public_invitation_url(invitation.token) %>"
data-action="click->clipboard#copy"
class="btn btn-outline btn-info btn-xs"
title="Copy invitation link">
<%= icon 'copy', class: "inline-block w-3" %>
Copy Invitation Link
</button>
</div>
</div> </div>
<% if policy(@family).manage_invitations? %> <% if policy(@family).manage_invitations? %>
<div class="ml-3"> <div class="ml-3">
@ -208,6 +199,23 @@
</div> </div>
<% end %> <% end %>
</div> </div>
<div class="flex items-center gap-2 mt-3">
<input type="text"
readonly
value="<%= public_invitation_url(invitation.token) %>"
class="input input-bordered input-sm flex-grow"
onclick="this.select();"
/>
<button data-controller="clipboard"
data-clipboard-text-value="<%= public_invitation_url(invitation.token) %>"
data-action="click->clipboard#copy"
class="btn btn-outline btn-info btn-sm ml-auto"
title="Copy invitation link">
<%= icon 'copy', class: "inline-block w-3" %>
Copy Invitation Link
</button>
</div>
</div>
<% end %> <% end %>
</div> </div>
<% else %> <% else %>

View file

@ -57,14 +57,21 @@ RSpec.describe Family::Invitations::SendingJob, type: :job do
end end
context 'integration test' do context 'integration test' do
it 'actually sends the email' do before do
expect do ActionMailer::Base.deliveries.clear
described_class.perform_now(invitation.id) # Set a from address for the mailer to avoid SMTP errors
end.to change { ActionMailer::Base.deliveries.count }.by(1) allow(ActionMailer::Base).to receive(:default).and_return(from: 'noreply@dawarich.app')
end
email = ActionMailer::Base.deliveries.last it 'actually calls the mailer' do
expect(email.to).to include(invitation.email) mailer = instance_double(ActionMailer::MessageDelivery)
expect(email.subject).to include("You've been invited to join #{family.name}") allow(FamilyMailer).to receive(:invitation).and_return(mailer)
allow(mailer).to receive(:deliver_now)
described_class.perform_now(invitation.id)
expect(FamilyMailer).to have_received(:invitation).with(invitation)
expect(mailer).to have_received(:deliver_now)
end end
end end
end end