diff --git a/backend/Gemfile b/backend/Gemfile index 9f9f2483b..b1b63c216 100644 --- a/backend/Gemfile +++ b/backend/Gemfile @@ -20,6 +20,7 @@ gem 'sentry-raven' gem 'sidekiq' gem 'sidekiq-unique-jobs' gem 'valid_email2' +gem 'whenever' gem 'rails' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' diff --git a/backend/app/controllers/application_controller.rb b/backend/app/controllers/application_controller.rb index 0847c8c6f..eea637044 100644 --- a/backend/app/controllers/application_controller.rb +++ b/backend/app/controllers/application_controller.rb @@ -6,6 +6,7 @@ class ApplicationController < ActionController::API before_action :set_paper_trail_whodunnit before_action :set_locale before_action :set_raven_context + before_action :set_last_login def set_locale I18n.locale = user_locale || guest_locale @@ -25,6 +26,12 @@ def user_locale current_user&.locale end + def set_last_login + if current_user + current_user.update_columns(last_login: Time.current) + end + end + def set_raven_context Raven.user_context(id: current_user.id) if current_user Raven.extra_context(params: params.to_unsafe_h, url: request.url) diff --git a/backend/app/mailers/user_mailer.rb b/backend/app/mailers/user_mailer.rb index f2c861ba3..1c88c1c0b 100644 --- a/backend/app/mailers/user_mailer.rb +++ b/backend/app/mailers/user_mailer.rb @@ -3,4 +3,9 @@ def auth0_migration(user) @user = user mail(to: @user.email, subject: 'Auth0 Migration') end + + def monthly_news(user) + @user = user + mail(to: @user.email, subject: I18n.t('user_mailer.monthly_news.subject')) + end end diff --git a/backend/app/models/user.rb b/backend/app/models/user.rb index 4fe7f575e..7612c6335 100644 --- a/backend/app/models/user.rb +++ b/backend/app/models/user.rb @@ -64,4 +64,15 @@ def update_and_reverse_geocode(params) end save end + + def reasons_for_notifications + # Populate the "reasons" array with symbols depending on which conditions it satisfies + reasons = [] + broadcasts_created_since_last_login = Broadcast.where('created_at >= ?', last_login) + reasons << :recently_created_broadcasts if broadcasts_created_since_last_login.count >= 5 + distributed_sum = impressions.sum(:amount) + reasons << :no_given_amount_for_supported_broadcasts if impressions.positive.where(amount: nil).present? + reasons << :unbalanced_distribution if impressions.where(amount: Impression::BUDGET) + reasons + end end diff --git a/backend/app/views/user_mailer/monthly_news.html.erb b/backend/app/views/user_mailer/monthly_news.html.erb new file mode 100644 index 000000000..09585a931 --- /dev/null +++ b/backend/app/views/user_mailer/monthly_news.html.erb @@ -0,0 +1,31 @@ + + + + + + +

<%= I18n.t('user_mailer.monthly_news.header.hello') %> <%= @user.name %>,

+ +

<%= I18n.t('user_mailer.monthly_news.header.welcome') %>

+ + <% if self.reasons_for_notifications.include? :recently_created_broadcasts %> +

<%= I18n.t('user_mailer.monthly_news.body.recently_created_broadcasts') %> + <% broadcasts_created_since_last_login.each do |b| %> +

+ <% end %> +

+ <% end %> + + <% if self.reasons_for_notifications.include? :no_given_amount_for_supported_broadcasts %> +

<%= I18n.t('user_mailer.monthly_news.body.no_given_amount_for_supported_broadcasts') %>

+ <% end %> + + <% if self.reasons_for_notifications.include? :unbalanced_distribution %> +

<%= I18n.t('user_mailer.monthly_news.body.unbalanced_distribution') %>

+ <% end %> + +

<%= I18n.t('user_mailer.monthly_news.footer.closing') %>

+ +

<%= I18n.t('user_mailer.monthly_news.footer.sign_off_html') %>

+ + diff --git a/backend/app/views/user_mailer/monthly_news.text.erb b/backend/app/views/user_mailer/monthly_news.text.erb new file mode 100644 index 000000000..524dc9c5e --- /dev/null +++ b/backend/app/views/user_mailer/monthly_news.text.erb @@ -0,0 +1,23 @@ +<%= I18n.t('user_mailer.monthly_news.header.hello') %> <%= @user.name %>, + +<%= I18n.t('user_mailer.monthly_news.header.welcome') %> + +<% if self.reasons_for_notifications.include? :recently_created_broadcasts %> + <%= I18n.t('user_mailer.monthly_news.body.recently_created_broadcasts') %> + <% broadcasts_created_since_last_login.each do |b| %> + <%= b.title %> + <% end %> +<% end %> + + +<% if self.reasons_for_notifications.include? :no_given_amount_for_supported_broadcasts %> + <%= I18n.t('user_mailer.monthly_news.body.no_given_amount_for_supported_broadcasts') %> +<% end %> + +<% if self.reasons_for_notifications.include? :unbalanced_distribution %> + <%= I18n.t('user_mailer.monthly_news.body.unbalanced_distribution') %> +<% end %> + +<%= I18n.t('user_mailer.monthly_news.footer.closing') %> + +<%= I18n.t('user_mailer.monthly_news.footer.sign_off_html') %> diff --git a/backend/config/application.rb b/backend/config/application.rb index 2e27a9331..8fd324f85 100644 --- a/backend/config/application.rb +++ b/backend/config/application.rb @@ -33,5 +33,6 @@ class Application < Rails::Application config.filter_parameters << :password config.active_record.schema_format = :sql + config.i18n.default_locale = :de end end diff --git a/backend/config/locales/de.yml b/backend/config/locales/de.yml index 76f9afc75..5ad593af1 100644 --- a/backend/config/locales/de.yml +++ b/backend/config/locales/de.yml @@ -24,6 +24,24 @@ de: has_one: Datensatz kann nicht gelöscht werden, da ein abhängiger %{record}-Datensatz existiert. has_many: Datensatz kann nicht gelöscht werden, da abhängige %{record} existieren. + user_mailer: + monthly_news: + subject: Neue Updates von Rundfunk Mitbestimmen + header: + hello: Hallo + welcome: 'Willkommen zu den Updates dieses Monats:' + body: + recently_created_broadcasts: 'Hier sind die neuen Sendungen, die seit Ihrer letzten Anmeldung den Rundfunk Mitbeständen hinzugefügt wurden:' + no_given_amount_for_supported_broadcasts: Offenbar haben Sie Übertragungen unterstützt, ohne Ihre Mittel für die ausgewählten Sendungen tatsächlich zu verteilen. + Bitte loggen Sie sich ein und verteilen Sie Gelder für Ihre Lieblingsprogramme! + unbalanced_distribution: Es sieht so aus, als ob Sie alle Ihre Gelder an eine bestimmte Sendung verteilt haben, sind Sie sicher, + dass das Ihre Absicht war? Wenn nicht, loggen Sie sich bitte ein und erkunden Sie weitere Sendungen, um Ihre Gelder zu verteilen. + Wenn das Ihre Absicht war, dann ignorieren Sie bitte diese Nachricht! + footer: + closing: Danke, dass Sie ein geschätztes Mitglied von Rundfunk Mitbestimmen sind! Wir hoffen, dass Sie bald zurückschauen, um diese neuen Updates zu sehen! + sign_off_html: | + Freundliche Grüße,
+ Das Rundfunk Mitbestimmen Team date: abbr_day_names: - So @@ -233,4 +251,3 @@ de: long: "%A, %d. %B %Y, %H:%M Uhr" short: "%d. %B, %H:%M Uhr" pm: nachmittags - diff --git a/backend/config/locales/en.yml b/backend/config/locales/en.yml index 4ebf07c84..ca2aad0fc 100644 --- a/backend/config/locales/en.yml +++ b/backend/config/locales/en.yml @@ -13,3 +13,22 @@ en: attributes: amount: total: Total amount of %{sum} exceeds the available budget of %{budget} + user_mailer: + monthly_news: + subject: New happenings in Rundfunk Mitbestimmen + header: + hello: Hello + welcome: "Welcome to this month's updates:" + body: + recently_created_broadcasts: 'Here are the new broadcasts added to Rundfunk Mitbestimmen since you last logged in:' + no_given_amount_for_supported_broadcasts: It looks like you have supported broadcasts without actually distributing your funds for your + selected broadcasts. Please log in and distribute funds for your favorite programs! + unbalanced_distribution: It looks like you have distributed all of your funds to one particular broadcast, are you sure + that was your intention? If not, then please log in and explore more broadcasts to distribute your funds. + If that was your intention, then please ignore this message! + footer: + closing: Thank you for being a valued member of Rundfunk Mitbestimmen! We hope you will check back soon to see + these new updates! + sign_off_html: | + Best Regards,
+ The Rundfunk Mitbestimmen team diff --git a/backend/config/schedule.rb b/backend/config/schedule.rb new file mode 100644 index 000000000..db2e3c072 --- /dev/null +++ b/backend/config/schedule.rb @@ -0,0 +1,23 @@ +# Use this file to easily define all of your cron jobs. +# +# It's helpful, but not entirely necessary to understand cron before proceeding. +# http://en.wikipedia.org/wiki/Cron + +# Example: +# +# set :output, "/path/to/my/cron_log.log" +# +# every 2.hours do +# command "/usr/bin/some_great_command" +# runner "MyModel.some_method" +# rake "some:great:rake:task" +# end +# +# every 4.days do +# runner "AnotherModel.prune_old_records" +# end + +# Learn more: http://github.com/javan/whenever +every 1.month do + rake 'monthly_news' +end diff --git a/backend/db/migrate/20180719134105_add_last_login_to_user.rb b/backend/db/migrate/20180719134105_add_last_login_to_user.rb new file mode 100644 index 000000000..32034f3fe --- /dev/null +++ b/backend/db/migrate/20180719134105_add_last_login_to_user.rb @@ -0,0 +1,5 @@ +class AddLastLoginToUser < ActiveRecord::Migration[5.1] + def change + add_column :users, :last_login, :datetime + end +end diff --git a/backend/lib/tasks/monthly_mailer.rake b/backend/lib/tasks/monthly_mailer.rake new file mode 100644 index 000000000..23cad08a5 --- /dev/null +++ b/backend/lib/tasks/monthly_mailer.rake @@ -0,0 +1,9 @@ +namespace :monthly_mailer do + desc "Send mail alerting non-active users new happenings on Rundfunk Mitbestimmen" + + task monthly_news: :environment do + User.find_each do |user| + UserMailer.monthly_news(user).deliver_now + end + end +end diff --git a/backend/spec/models/user_spec.rb b/backend/spec/models/user_spec.rb index b2791b6df..9601b63c3 100644 --- a/backend/spec/models/user_spec.rb +++ b/backend/spec/models/user_spec.rb @@ -8,6 +8,44 @@ let(:liked_broadcast) { create(:impression, response: :positive, user: user).broadcast } let(:unsupported_broadcast) { create(:impression, response: :neutral, user: user).broadcast } + describe '#reasons_for_notifications' do + subject { user.reasons_for_notifications } + + context 'by default' do + it { is_expected.to be_empty } + end + + context 'given a last_login timestamp' do + before do + user.update_columns(last_login: Time.current) + end + it { is_expected.to be_empty } + end + + context 'many new broadcasts since last login' do + before do + user.update_columns(last_login: 2.months.ago) + create_list(:broadcast, 20) + end + it { is_expected.to include(:recently_created_broadcasts) } + end + + context 'forgot to distribute money for supported broadcasts' do + before { create_list(:impression, 3, user: user, response: :positive, amount: nil) } + it { is_expected.to include(:no_given_amount_for_supported_broadcasts) } + end + + context 'quite an unbalanced distribution, was it on purpose?' do + before { create(:impression, user: user, response: :positive, amount: Impression::BUDGET) } + it { is_expected.to include(:unbalanced_distribution) } + end + + context 'error occurs when user has not logged in before' do + before { create(user: user, last_login: nil) } + it { is_expected.to raise_error 'NilPointerException' } + end + end + describe 'update_and_reverse_geocode' do subject { user.update_and_reverse_geocode(params) } let(:user) { create(:user) }