Skip to content

Commit a233dac

Browse files
committed
feat: Enhance event reminder and update mailers with new templates and localization support
1 parent de3c632 commit a233dac

File tree

14 files changed

+258
-37
lines changed

14 files changed

+258
-37
lines changed

app/jobs/better_together/event_reminder_job.rb

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ module BetterTogether
55
class EventReminderJob < ApplicationJob
66
queue_as :notifications
77

8-
def perform(event, reminder_type = '24_hours')
8+
retry_on StandardError, wait: :polynomially_longer, attempts: 5
9+
discard_on ActiveRecord::RecordNotFound
10+
11+
def perform(event_or_id, reminder_type = '24_hours')
12+
event = find_event(event_or_id)
913
return unless event_valid?(event)
1014

1115
attendees = going_attendees(event)
@@ -15,13 +19,22 @@ def perform(event, reminder_type = '24_hours')
1519

1620
private
1721

22+
def find_event(event_or_id)
23+
return event_or_id if event_or_id.is_a?(BetterTogether::Event)
24+
25+
BetterTogether::Event.find(event_or_id) if event_or_id.present?
26+
rescue ActiveRecord::RecordNotFound
27+
nil
28+
end
29+
1830
def event_valid?(event)
1931
event.present? && event.starts_at.present?
2032
end
2133

2234
def going_attendees(event)
23-
event.attendees.joins(:event_attendances)
24-
.where(better_together_event_attendances: { status: 'going' })
35+
# Get people who have 'going' status for this event
36+
person_ids = event.event_attendances.where(status: 'going').pluck(:person_id)
37+
BetterTogether::Person.where(id: person_ids)
2538
end
2639

2740
def send_reminders_to_attendees(event, attendees, reminder_type)

app/jobs/better_together/event_reminder_scheduler_job.rb

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@ module BetterTogether
55
class EventReminderSchedulerJob < ApplicationJob
66
queue_as :notifications
77

8-
def perform(event)
8+
retry_on StandardError, wait: :polynomially_longer, attempts: 5
9+
discard_on ActiveRecord::RecordNotFound
10+
11+
def perform(event_or_id)
12+
event = find_event(event_or_id)
913
return unless event_valid?(event)
1014
return if event_in_past?(event)
15+
return unless event_has_attendees?(event)
1116

1217
cancel_existing_reminders(event)
1318
schedule_reminders(event)
@@ -16,6 +21,14 @@ def perform(event)
1621

1722
private
1823

24+
def find_event(event_or_id)
25+
return event_or_id if event_or_id.is_a?(BetterTogether::Event)
26+
27+
BetterTogether::Event.find(event_or_id) if event_or_id.present?
28+
rescue ActiveRecord::RecordNotFound
29+
nil
30+
end
31+
1932
def event_valid?(event)
2033
event.present? && event.starts_at.present?
2134
end
@@ -24,9 +37,14 @@ def event_in_past?(event)
2437
event.starts_at <= Time.current
2538
end
2639

40+
def event_has_attendees?(event)
41+
event.event_attendances.any?
42+
end
43+
2744
def schedule_reminders(event)
2845
schedule_24_hour_reminder(event) if should_schedule_24_hour_reminder?(event)
2946
schedule_1_hour_reminder(event) if should_schedule_1_hour_reminder?(event)
47+
schedule_start_time_reminder(event) if should_schedule_start_time_reminder?(event)
3048
end
3149

3250
def should_schedule_24_hour_reminder?(event)
@@ -37,20 +55,41 @@ def should_schedule_1_hour_reminder?(event)
3755
event.starts_at > 1.hour.from_now
3856
end
3957

58+
def should_schedule_start_time_reminder?(event)
59+
event.starts_at > Time.current
60+
end
61+
4062
def schedule_24_hour_reminder(event)
4163
EventReminderJob.set(wait_until: event.starts_at - 24.hours)
42-
.perform_later(event, '24_hours')
64+
.perform_later(event.id)
4365
end
4466

4567
def schedule_1_hour_reminder(event)
4668
EventReminderJob.set(wait_until: event.starts_at - 1.hour)
47-
.perform_later(event, '1_hour')
69+
.perform_later(event.id)
70+
end
71+
72+
def schedule_start_time_reminder(event)
73+
EventReminderJob.set(wait_until: event.starts_at)
74+
.perform_later(event.id)
4875
end
4976

5077
def log_completion(event)
5178
Rails.logger.info "Scheduled reminders for event #{event.identifier}"
5279
end
5380

81+
def reminder_intervals
82+
[24.hours, 1.hour, 0.seconds]
83+
end
84+
85+
def schedule_future_reminder?(event_id, reminder_time)
86+
return false if reminder_time <= Time.current
87+
88+
EventReminderJob.set(wait_until: reminder_time)
89+
.perform_later(event_id)
90+
true
91+
end
92+
5493
def cancel_existing_reminders(event)
5594
# Find and cancel existing reminder jobs for this event
5695
# This is a simplified approach - in production you might want to use

app/mailers/better_together/event_mailer.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ def event_reminder(recipient, event, reminder_type = '24_hours')
88
@event = event
99
@reminder_type = reminder_type
1010
@recipient = recipient
11+
@platform = BetterTogether::Platform.find_by(host: true)
1112

1213
mail(
1314
to: recipient.email,
@@ -20,6 +21,7 @@ def event_update(recipient, event, changed_attributes)
2021
@event = event
2122
@changed_attributes = changed_attributes
2223
@recipient = recipient
24+
@platform = BetterTogether::Platform.find_by(host: true)
2325

2426
mail(
2527
to: recipient.email,
@@ -31,15 +33,15 @@ def event_update(recipient, event, changed_attributes)
3133

3234
def reminder_subject(event)
3335
I18n.t(
34-
'better_together.mailers.event_reminder.subject',
36+
'better_together.event_mailer.event_reminder.subject',
3537
event_name: event.name,
3638
default: 'Reminder: %<event_name>s'
3739
)
3840
end
3941

4042
def update_subject(event)
4143
I18n.t(
42-
'better_together.mailers.event_update.subject',
44+
'better_together.event_mailer.event_update.subject',
4345
event_name: event.name,
4446
default: 'Event updated: %<event_name>s'
4547
)

app/models/better_together/person.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,13 @@ def self.primary_community_delegation_attrs
8282

8383
translates :description_html, backend: :action_text
8484

85-
delegate :email, to: :user, allow_nil: true
85+
# Return email from user if available, otherwise from contact details
86+
def email
87+
return user.email if user&.email.present?
88+
89+
# Fallback to primary email address from contact details
90+
email_addresses.find(&:primary_flag)&.email
91+
end
8692

8793
has_one_attached :profile_image
8894
has_one_attached :cover_image
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<!-- app/views/better_together/event_mailer/event_reminder.html.erb -->
2+
3+
<p><%= t('.greeting', recipient_name: @recipient.name) %></p>
4+
5+
<p><%= t('.reminder_message', event_name: @event.name) %></p>
6+
7+
<h3><%= @event.name %></h3>
8+
9+
<% if @event.description.present? %>
10+
<p><strong><%= t('.description') %>:</strong></p>
11+
<p><%= @event.description %></p>
12+
<% end %>
13+
14+
<p><strong><%= t('.when') %>:</strong></p>
15+
<% if @event.starts_at %>
16+
<p><%= l(@event.starts_at, format: :long) %></p>
17+
<% if @event.ends_at %>
18+
<p><%= t('.ends_at') %>: <%= l(@event.ends_at, format: :long) %></p>
19+
<% end %>
20+
<% else %>
21+
<p><%= t('.time_tbd') %></p>
22+
<% end %>
23+
24+
<% if @event.duration_in_hours.present? %>
25+
<p><strong><%= t('.duration') %>:</strong> <%= t('.hours', count: @event.duration_in_hours) %></p>
26+
<% end %>
27+
28+
<% if @event.location? %>
29+
<p><strong><%= t('.location') %>:</strong></p>
30+
<p><%= @event.location_display_name %></p>
31+
<% end %>
32+
33+
<% if @event.registration_url.present? %>
34+
<p><%= link_to t('.register_link'), @event.registration_url, class: 'text-decoration-none' %></p>
35+
<% end %>
36+
37+
<p><%= t('.view_event_link_html', link: link_to(@event.name, event_url(@event))) %></p>
38+
39+
<p><%= t('.signature_html', platform: @platform&.name || 'Better Together') %></p>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<!-- app/views/better_together/event_mailer/event_update.html.erb -->
2+
3+
<p><%= t('.greeting', recipient_name: @recipient.name) %></p>
4+
5+
<% if @changed_attributes.length > 1 %>
6+
<p><%= t('.update_message_plural', event_name: @event.name) %></p>
7+
<% else %>
8+
<p><%= t('.update_message_singular', event_name: @event.name) %></p>
9+
<% end %>
10+
11+
<h3><%= @event.name %></h3>
12+
13+
<p><strong><%= t('.changes_made') %>:</strong></p>
14+
<ul>
15+
<% @changed_attributes.each do |attribute| %>
16+
<li><%= t(".changed_attributes.#{attribute}", default: attribute.humanize) %></li>
17+
<% end %>
18+
</ul>
19+
20+
<p><strong><%= t('.current_details') %>:</strong></p>
21+
22+
<% if @event.description.present? %>
23+
<p><strong><%= t('.description') %>:</strong></p>
24+
<p><%= @event.description %></p>
25+
<% end %>
26+
27+
<p><strong><%= t('.when') %>:</strong></p>
28+
<% if @event.starts_at %>
29+
<p><%= l(@event.starts_at, format: :long) %></p>
30+
<% if @event.ends_at %>
31+
<p><%= t('.ends_at') %>: <%= l(@event.ends_at, format: :long) %></p>
32+
<% end %>
33+
<% else %>
34+
<p><%= t('.time_tbd') %></p>
35+
<% end %>
36+
37+
<% if @event.duration_in_hours.present? %>
38+
<p><strong><%= t('.duration') %>:</strong> <%= t('.hours', count: @event.duration_in_hours) %></p>
39+
<% end %>
40+
41+
<% if @event.location? %>
42+
<p><strong><%= t('.location') %>:</strong></p>
43+
<p><%= @event.location_display_name %></p>
44+
<% end %>
45+
46+
<% if @event.registration_url.present? %>
47+
<p><%= link_to t('.register_link'), @event.registration_url, class: 'text-decoration-none' %></p>
48+
<% end %>
49+
50+
<p><%= t('.view_event_link_html', link: link_to(@event.name, event_url(@event))) %></p>
51+
52+
<p><%= t('.signature_html', platform: @platform&.name || 'Better Together') %></p>

config/locales/en.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,44 @@ en:
673673
signature_html: |-
674674
Best regards,<br />
675675
The %{platform} Team
676+
event_mailer:
677+
event_reminder:
678+
subject: "Reminder: %{event_name}"
679+
greeting: Hello %{recipient_name},
680+
reminder_message: This is a friendly reminder about the upcoming event "%{event_name}".
681+
description: Description
682+
when: When
683+
ends_at: Ends at
684+
duration: Duration
685+
hours:
686+
one: "%{count} hour"
687+
other: "%{count} hours"
688+
location: Location
689+
register_link: Register or RSVP
690+
view_event_link_html: 'View event details: %{link}'
691+
signature_html: |-
692+
Best regards,<br />
693+
The %{platform} Team
694+
event_update:
695+
subject: "Event updated: %{event_name}"
696+
greeting: Hello %{recipient_name},
697+
update_message_singular: The event "%{event_name}" has been updated.
698+
update_message_plural: The event "%{event_name}" has been updated with several changes.
699+
changes_made: Changes made
700+
current_details: Current details
701+
description: Description
702+
when: When
703+
ends_at: Ends at
704+
duration: Duration
705+
hours:
706+
one: "%{count} hour"
707+
other: "%{count} hours"
708+
location: Location
709+
register_link: Register or RSVP
710+
view_event_link_html: 'View event details: %{link}'
711+
signature_html: |-
712+
Best regards,<br />
713+
The %{platform} Team
676714
conversations:
677715
communicator:
678716
active_conversations: Active Conversations
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
FactoryBot.define do
4-
factory :contact_detail do # rubocop:todo Lint/EmptyBlock
4+
factory :better_together_contact_detail, class: BetterTogether::ContactDetail, aliases: [:contact_detail] do
5+
# contactable association should be set by the caller
56
end
67
end
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# frozen_string_literal: true
22

33
FactoryBot.define do
4-
factory :email_address do # rubocop:todo Lint/EmptyBlock
4+
factory :better_together_email_address, class: BetterTogether::EmailAddress, aliases: [:email_address] do
5+
email { Faker::Internet.unique.email }
6+
label { 'primary' }
7+
primary_flag { true }
8+
# contact_detail association should be set by the caller
59
end
610
end

spec/factories/better_together/people.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ module BetterTogether
1111
identifier { Faker::Internet.unique.username(specifier: 10..20) }
1212

1313
community
14+
15+
# Add email address after creation since Person model likely requires it for mailer
16+
after(:create) do |person|
17+
# Ensure person has contact_detail
18+
person.contact_detail ||= create(:better_together_contact_detail, contactable: person)
19+
20+
create(:better_together_email_address,
21+
contact_detail: person.contact_detail,
22+
email: Faker::Internet.unique.email,
23+
primary_flag: true)
24+
end
1425
end
1526
end
1627
end

0 commit comments

Comments
 (0)