Skip to content

Commit

Permalink
Reminder Notification (#91)
Browse files Browse the repository at this point in the history
* ticket Mailers

* added reminder job and config
  • Loading branch information
MdreW authored Dec 10, 2024
1 parent 6aaa330 commit 7aef9c6
Show file tree
Hide file tree
Showing 24 changed files with 281 additions and 21 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,4 @@ gem "hamlit"
gem "pagy"
gem "bulmacomp"
gem "csv"
gem "icalendar"
14 changes: 10 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ GEM
factory_bot_rails (6.4.4)
factory_bot (~> 6.5)
railties (>= 5.0.0)
faraday (2.12.1)
faraday (2.12.2)
faraday-net_http (>= 2.0, < 3.5)
json
logger
Expand All @@ -156,6 +156,10 @@ GEM
hashie (5.0.0)
i18n (1.14.6)
concurrent-ruby (~> 1.0)
icalendar (2.10.3)
ice_cube (~> 0.16)
ostruct
ice_cube (0.17.0)
image_processing (1.13.0)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
Expand Down Expand Up @@ -210,7 +214,7 @@ GEM
net-smtp (0.5.0)
net-protocol
nio4r (2.7.4)
nokogiri (1.16.8-x86_64-linux)
nokogiri (1.17.0-x86_64-linux)
racc (~> 1.4)
omniauth (2.1.2)
hashie (>= 3.4.6)
Expand All @@ -236,7 +240,8 @@ GEM
validate_url
webfinger (~> 2.0)
orm_adapter (0.5.0)
pagy (9.3.2)
ostruct (0.6.1)
pagy (9.3.3)
parallel (1.26.3)
parser (3.3.6.0)
ast (~> 2.4.1)
Expand Down Expand Up @@ -357,7 +362,7 @@ GEM
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
solid_cable (3.0.3)
solid_cable (3.0.4)
actioncable (>= 7.2)
activejob (>= 7.2)
activerecord (>= 7.2)
Expand Down Expand Up @@ -439,6 +444,7 @@ DEPENDENCIES
devise
factory_bot_rails
hamlit
icalendar
image_processing (~> 1.2)
jbuilder
jsbundling-rails
Expand Down
4 changes: 2 additions & 2 deletions app/components/layout/navbar_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def user_submenu
submenu title:, sub:
end

# @retur [Array] admin menu entries
# @return [Array] admin menu entries
def admin_submenu
return unless @user.admin?

Expand All @@ -87,7 +87,7 @@ def admin_submenu
]
end

# @retur [Array] admin menu entries
# @return [Array] admin menu entries
def editor_submenu
return unless @user.editor?

Expand Down
17 changes: 17 additions & 0 deletions app/jobs/reminder_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# This job send a reminder for the next day happening
class ReminderJob < ApplicationJob
queue_as :default

# Send the reminder
# @return [Boolean] `true` if executed, `false` if ENV `RAILS_REMINDER` is not set as "true"
def perform
return false unless ENV.fetch('RAILS_REMINDER', 'false') == 'true'
happenings = Happening.where start_at: (Time.zone.tomorrow.beginning_of_day..Time.zone.tomorrow.end_of_day)
happenings.each do |happening|
happening.users do |user|
TicketMailer.reminder(happening.user).deliver_later
end
end
true
end
end
2 changes: 1 addition & 1 deletion app/mailers/application_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

# This class define the default options for send emails
class ApplicationMailer < ActionMailer::Base
default from: "[email protected]"
default from: ENV.fetch("RAILS_EMAIL_FROM", "[email protected]")
layout "mailer"
end
50 changes: 50 additions & 0 deletions app/mailers/ticket_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# This class send collect the tickets notification
class TicketMailer < ApplicationMailer
# Send confirmation email for a ticket
# @param ticket [Object] istance of created {Ticket}
def confirm(ticket)
@ticket = ticket
@happening = @ticket.happening
mail to: @ticket.user.email, subject: subject(action: 'confirm', date: @happening.start_at)
end

# Send notify email on destroy ticket
# @param ticket [Object] istance of destroyed {Ticket}
def deleted(ticket)
@ticket = ticket
@happening = @ticket.happening
@tickets = Ticket.where(user: ticket.user, happening: ticket.happening)
mail to: @ticket.user.email, subject: subject(action: 'deleted', date: @happening.start_at)
end

# Send reminder for an event happeningf
# @param happening [Object] Istance of {Happening} to remind
# @param user [Object] Istance of {User} send remind. If user have not ticket the remind is not sended
def reminder(happening, user)
@happening = happening
@user = user
@counter = @happening.tickets.with_user(@user).count
mail to: @user.email, subject: subject(action: 'reminder', date: @happening.start_at) if @counter > 0
end

private

# make email subject with site title, action text, date of referenced happening
# @param action [String] optional action name to add a locale path on subject: `reminder` -> `mailer.ticket.reminder.action`.
# @param date [Date,DateTime] If present append the localized date / datetime on subject
# @return [String] subject text
# @example
# subject
# -> Partecipo
# subject action: 'prova'
# -> Partecipo - prova - 25/12/24 00:00
# subject action: 'prova', date: @happening.start_at
# -> Partecipo - prova
def subject(action: nil, date: nil, other: nil)
title_text = ENV.fetch "RAILS_TITLE", "Partecipo"
action_text = t("mailer.ticket.#{action}.action", locale: I18n.locale) if action.present?
data_text = l date, format: :detailed if date.present?
other_text = other if other.present?
[title_text, action_text, data_text, other_text].compact.join(' - ')
end
end
1 change: 1 addition & 0 deletions app/models/happening.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class Happening < ApplicationRecord
has_rich_text :body
belongs_to :event, counter_cache: true
has_many :tickets, dependent: :destroy
has_many :users, through: :tickets
has_many :questions, dependent: :destroy
has_one_attached :image do |attachable|
attachable.variant :card, resize_to_limit: [ 417, 278 ]
Expand Down
6 changes: 3 additions & 3 deletions app/models/ticket.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ class Ticket < ApplicationRecord
belongs_to :happening, counter_cache: true
belongs_to :user
has_many :answers, dependent: :destroy
delegate :event, :event_id, :max_tickets, :max_tickets_for_user, :saleable?, :start_at, to: :happening,
allow_nil: true
delegate :event, :event_id, :max_tickets, :max_tickets_for_user, :saleable?, :start_at, to: :happening, allow_nil: true
accepts_nested_attributes_for :answers, reject_if: :all_blank

attr_accessor :by_editor
after_create -> { TicketMailer.confirm(self).deliver_later }
after_destroy -> { TicketMailer.deleted(self).deliver_later }

validates :happening, presence: true
validates :user, presence: true
Expand Down
20 changes: 19 additions & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,33 @@ def self.from_omniauth(auth)
user
end

# @return [String] gravatar url for user
# Make gravatar url from email
# @return [String] gravatar user url
def avatar_url
hash = Digest::MD5.hexdigest(email)
"https://www.gravatar.com/avatar/#{hash}?s=64i&d=identicon"
end

# @return [String] name and/or surname if presents, otherwise return username or email
# @example User without name, surname, and username
# user = User.new email: '[email protected]'
# user.title -> '[email protected]'
# @example user with username
# user = User.new email: '[email protected]', username: 'test'
# user.title -> 'test'
# @example user with all data
# user = User.new name: 'Mario', surname: 'Rossi', username....
# user.title: 'Mario Rossi'
def title
name.present? || surname.present? ? [name, surname].join(" ") : username || email
end

private

# if username is empty, set username value as email
# @return [String] username value
def add_username
self.username = email unless username?
username
end
end
2 changes: 2 additions & 0 deletions app/views/layouts/mailer.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>

<style>
/* Email styles need to be inline */
</style>
Expand Down
21 changes: 19 additions & 2 deletions app/views/ticket_mailer/confirm.html.haml
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
%p Buongiorno #{@ticket.user.username}
%h4.title.is-4= @ticket.happening.event.title
- if @ticket.happening.title.present?
%h5.subtitle.is-5= @ticket.happening.title

%p Le confermiamo la prenotazione di n. <b>#{@ticket.seats}</b> per l'evento "<b>#{@ticket.happening.event.title}</b >" del #{l @ticket.happening.start_at, format: :detailed}
.content
%p
= t "mailer.generic.hi"
%b= @ticket.user.title

%p
= t "mailer.ticket.confirm.message"
= l @ticket.happening.start_at, format: :detailed
- if @ticket.answers.present?
%p= t "mailer.ticket.confirm.data"
%ul
- @ticket.answers.each do |answer|
%li= [answer.question.title, answer.value].join(': ')
%p
= t 'mailer.ticket.confirm.manage'
= link_to happening_url(@ticket.happening, locale: I18n.locale), happening_url(@ticket.happening, locale: I18n.locale)
20 changes: 18 additions & 2 deletions app/views/ticket_mailer/deleted.html.haml
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
%p Buongiorno #{@ticket.user.username}
%h4.title.is-4= @ticket.happening.event.title
- if @ticket.happening.title.present?
%h5.subtitle.is-5= @ticket.happening.title

%p Le confermiamo l'eliminazione della prenotazione di n. <b>#{@ticket.seats}</b> per l'evento "<b>#{@ticket.happening.event.title}</b >" del #{l @ticket.happening.start_at, format: :detailed}
.content
%p
= t "mailer.generic.hi"
%b= @ticket.user.title

%p
= t "mailer.ticket.deleted.message"
= l @ticket.happening.start_at, format: :detailed

- if @tickets.present?
%p= t 'mailer.ticket.deleted.counter', number: @tickets.count

%p
= t 'mailer.ticket.deleted.manage'
= link_to happening_url(@ticket.happening, locale: I18n.locale), happening_url(@ticket.happening, locale: I18n.locale)
15 changes: 15 additions & 0 deletions app/views/ticket_mailer/reminder.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
%h4.title.is-4= @happening.event.title
- if @happening.title.present?
%h5.subtitle.is-5= @happening.title

.content
%p
= t "mailer.generic.hi"
%b= @user.title

%p
= t "mailer.ticket.reminder.message"
= l @happening.start_at, format: :detailed
%p
= t 'mailer.ticket.confirm.manage'
= link_to happening_url(@happening, locale: I18n.locale), happening_url(@happening, locale: I18n.locale)
2 changes: 1 addition & 1 deletion config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Application < Rails::Application
# These settings can be overridden in specific environments using the files
# in config/environments, which are processed later.
#
# config.time_zone = "Central Time (US & Canada)"
config.time_zone = "Europe/Rome"
# config.eager_load_paths << Rails.root.join("extras")
end
end
19 changes: 19 additions & 0 deletions config/locales/mailer.en.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
en:
mailer:
generic:
hi: Hi
ticket:
confirm:
action: Booking confirm
message: Your reservation is confirmed on
data: Data entered at booking
manage: You can check the event details and manage bookings on
deleted:
action: Booking deleted
message: Your reservation is deleted on
manage: You can check the event details and manage bookings on
counter: You have other %{number} booking for this Event
reminder:
action: Event Reminder
message: This is a reminder for the event on
manage: You can check the event details and manage bookings on
20 changes: 20 additions & 0 deletions config/locales/mailer.it.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
it:
mailer:
generic:
hi: Buongiorno
ticket:
confirm:
action: Conferma prenotazione
message: Confermiamo la sua prenotazioni del
data: Dati inseriti al momento della prenotazione
manage: Può visualizzare i dettagli dell'evento e gestire le prenotazioni all'indirizzo
deleted:
action: Eliminazione prenotazione
message: È stata eliminata la sua prenotazione del
manage: Può visualizzare i dettagli dell'evento e gestire le prenotazioni all'indirizzo
counter: Hai altre %{number} prenotazioni attive per lo stesso evento.
reminder:
action: Promemoria evento
message: Questo è un promemoria l'evento del
manage: Può visualizzare i dettagli dell'evento e gestire le prenotazioni all'indirizzo

1 change: 1 addition & 0 deletions config/locales/site.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ en:
back: Back
from: from
groups: Groups
hi: Hi
login: Sign in
login_button: Sign in to continue
logout: Sign out
Expand Down
1 change: 1 addition & 0 deletions config/locales/site.it.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ it:
back: Indietro
from: da
groups: Gruppi
hi: Buongiorno
login: Accedi
login_button: Accedi per continuare
logout: Disconnettiti
Expand Down
12 changes: 7 additions & 5 deletions config/recurring.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
production:
periodic_cleanup:
class: CleanSoftDeletedRecordsJob
class: CleanJob
queue: background
args:
days: ENV.fetch("RAILS_CLEAN_AFTER_DAYS", nil).to_i
schedule: "0 2 * * *"
# periodic_command:
# command: "SoftDeletedRecord.due.delete_all"
# priority: 2
# schedule: at 5am every day
periodic_reminder:
class: CleanSoftDeletedRecordsJob
queue: background
args:
days: ENV.fetch("RAILS_CLEAN_AFTER_DAYS", nil).to_i
schedule: "0 6 * * *"
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ x-rails_conf: &rails_conf
RAILS_FOOTER_URL: /footer.html
### RAILS_CLEAN_AFTER_DAYS, destroy old happening and ticket after this number of days. If nil isn't removed anything
RAILS_CLEAN_AFTER_DAYS: 60
### RAILS_REMINDER, if set as 'true', at 06:00AM is sent a mail reminder for next day booking
RAILS_REMINDER: true
### RAILS_PORT, port used for generate oidc url, use 80 for localhost demo, 443 on production
RAILS_PORT: 80
### RAILS_SCHEME, Scheme user for generate oidc url, use "http" for localhost demo, "https" on production
Expand Down
2 changes: 2 additions & 0 deletions test/factories/users.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
FactoryBot.define do
factory :user do
email
name { "Mario" }
surname { "Rossi" }
password { "asdf1234!" }
confirmed_at { Time.zone.now }
end
Expand Down
7 changes: 7 additions & 0 deletions test/jobs/reminder_job_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require "test_helper"

class ReminderJobTest < ActiveJob::TestCase
# test "the truth" do
# assert true
# end
end
Loading

0 comments on commit 7aef9c6

Please sign in to comment.