Added email notifications for new dotations

This commit is contained in:
Adrian Hinz 2022-03-15 14:24:58 +01:00
parent f5b9f8bafe
commit 6903ac1418
39 changed files with 448 additions and 41 deletions

View File

@ -0,0 +1,2 @@
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.

View File

@ -37,6 +37,8 @@ class DotationsController < ApplicationController
false false
end end
@dotation.save @dotation.save
NotifyEmailsJob.perform_later(@dotation.id) if @dotation.active.eql?(true)
end end
# POST /dotations or /dotations.json # POST /dotations or /dotations.json

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
# Emails
class EmailFilterController < ApplicationController
layout 'home_layout'
def unsubscribe
@email_filter =
FilterForEmail.by_email(params[:i]).by_unique(params[:k]).first
@email_filter.destroy unless @email_filter.blank?
end
end

View File

@ -5,13 +5,17 @@ class HomeController < ApplicationController
layout 'home_layout' layout 'home_layout'
def index def index
require 'json'
prepare_filters prepare_filters
cookies[:filter] = JSON.generate(build_filter_hash)
@dotations = Dotation.extra_search(params[:search]).public_dot @dotations = Dotation.extra_search(params[:search]).public_dot
.point_desc.end_date_asc.page(params[:page]) .point_desc.end_date_asc.page(params[:page])
end end
def search def search
require 'json'
prepare_filters prepare_filters
cookies[:filter] = JSON.generate(build_filter_hash)
@dotations = Dotation.search_with_filters(build_filter_hash).public_dot @dotations = Dotation.search_with_filters(build_filter_hash).public_dot
.point_desc.end_date_asc.page(params[:page]) .point_desc.end_date_asc.page(params[:page])
end end
@ -23,6 +27,16 @@ class HomeController < ApplicationController
@company_sizes = CompanySize.all @company_sizes = CompanySize.all
end end
def emailfilter
require 'json'
@emailfilter = FilterForEmail.new(
email: params[:email_filter_inp],
filters: JSON.parse(cookies[:filter])
)
@emailfilter.save
SendNotifyEmailJob.perform_later(@emailfilter.id, 1)
end
private private
def prepare_filters def prepare_filters

View File

@ -0,0 +1,2 @@
module EmailFilterHelper
end

View File

@ -0,0 +1,7 @@
class GeneratePdfJob < ApplicationJob
queue_as :default
def perform(*args)
# Do something later
end
end

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
# Job for async email notifications
class NotifyEmailsJob < ApplicationJob
queue_as :default
def perform(dotation_id)
NotifyEmailService.new(dotation_id).call
end
end

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
class SendNotifyEmailJob < ApplicationJob
queue_as :default
def perform(email_id, mail_type)
email_filter = FilterForEmail.where(id: email_id).first
return unless email_filter
if mail_type == 1
NewDotationMailer.with(email_filter: email_filter.id).welcome_email
.deliver
elsif mail_type == 2
NewDotationMailer.with(email_filter: email_filter.id).notification_email
.deliver
end
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
# Mailer for dotations
class NewDotationMailer < ApplicationMailer
default from: 'kalendarzdotacji@gmail.com'
def welcome_email
@email_filter = FilterForEmail.where(id: params[:email_filter]).first
@email_params = {
i: @email_filter.email,
k: @email_filter.unique_name
}
# @emailfilter = FilterForEmail.find(params[:emailfilter])
mail(to: @email_filter.email, subject: 'Dodano subskrypcję dla nowych dotacji')
end
def notification_email
@email_filter = FilterForEmail.where(id: params[:email_filter]).first
@dotation = Dotation.where(id: params[:dotation_id]).first
@email_params = {
i: @email_filter.email,
k: @email_filter.unique_name
}
mail(to: @email_filter.email, subject: 'Pojawiła się nowa dotacja')
end
end

View File

@ -64,6 +64,7 @@ class Dotation < ApplicationRecord
scope :point_desc, -> { order(points: :desc) } scope :point_desc, -> { order(points: :desc) }
scope :end_date_asc, -> { order(end_date: :asc) } scope :end_date_asc, -> { order(end_date: :asc) }
scope :public_dot, -> { where(active: true) } scope :public_dot, -> { where(active: true) }
scope :by_id, ->(val) { where(id: val) }
# == Callbacks ============================================================ # == Callbacks ============================================================
# == Class Methods ======================================================== # == Class Methods ========================================================
@ -90,4 +91,8 @@ class Dotation < ApplicationRecord
ret ret
end end
# == Instance Methods ===================================================== # == Instance Methods =====================================================
def safe_id
id
end
end end

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
# Emails with filters for customers
class FilterForEmail < ApplicationRecord
# == Constants ============================================================
# == Attributes ===========================================================
serialize :filters, Hash
# == Extensions ===========================================================
# == Relationships ========================================================
has_many :sent_email_filters, dependent: :destroy
# == Validations ==========================================================
# == Scopes ===============================================================
scope :by_email, ->(val) { where(email: val) }
scope :by_unique, ->(val) { where(unique_name: val) }
# == Callbacks ============================================================
after_create :a_create
# == Class Methods ========================================================
# == Instance Methods =====================================================
def a_create
o = [('a'..'z'), ('A'..'Z')].map(&:to_a).flatten
self.unique_name = (0...50).map { o[rand(o.length)] }.join
save
end
end

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
# sent emails
class SentEmailFilter < ApplicationRecord
# == Constants ============================================================
# == Attributes ===========================================================
# == Extensions ===========================================================
# == Relationships ========================================================
belongs_to :dotation
belongs_to :filter_for_email
# == Validations ==========================================================
# == Scopes ===============================================================
scope :by_dotation, ->(val) { where(dotation_id: val) }
scope :by_filter, ->(val) { where(filter_for_email_id: val) }
# == Callbacks ===================== =======================================
# == Class Methods ========================================================
# == Instance Methods =====================================================
end

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
# Popup service
class NotifyEmailService
def initialize(dotation_id)
@dotation_id = dotation_id
end
def call
check_filters
end
private
def check_filters
filter_for_emails = FilterForEmail.all
filter_for_emails.each do |filter_for_email|
hsh = filter_for_email.filters.transform_keys(&:to_sym)
dot = Dotation.search_with_filters(hsh)
.by_id(@dotation_id).public_dot.first
next if dot.blank?
sef =
SentEmailFilter.by_dotation(dot.id).by_filter(filter_for_email.id).first
next unless sef.blank?
SentEmailFilter.create(filter_for_email_id: filter_for_email.id,
dotation_id: dot.id)
NewDotationMailer.with(dotation_id: dot.id,
email_filter: filter_for_email.id)
.notification_email.deliver
end
end
end

View File

@ -0,0 +1,38 @@
<section class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-2">
</div>
<div class="col-sm-8">
<h1>Rezygnacja z subskrypcji</h1>
</div>
<div class="col-sm-2">
</div>
</div>
</div>
</section>
<section class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h3 class="card-title">Subskrybcje</h3>
<div class="card-tools">
</div>
</div>
<div class="card-body">
<div class="alert alert-info">
<h5><i class="icon fas fa-info"></i> Informacja</h5>
Subskrybcja została usunięta
</div>
</div>
</div>
</div>
<div class="col-md-2"></div>
</div>
</div>
</section>

View File

@ -42,7 +42,7 @@
<div class="card-header"> <div class="card-header">
<h4 class="card-title w-100"> <h4 class="card-title w-100">
<a class="d-block w-100" data-toggle="collapse" href="#collapseTwo" aria-expanded="true"> <a class="d-block w-100" data-toggle="collapse" href="#collapseTwo" aria-expanded="true">
Dominująca działalność firmy Działalność firmy
</a> </a>
</h4> </h4>
</div> </div>
@ -133,36 +133,10 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<button type="button" class="btn btn-block btn-outline-success" data-toggle="modal" data-target="#modal-lg"> <button type="button" class="btn btn-block btn-outline-success" data-toggle="modal" data-target="#modal-lg">
<i class="fas fa-bullhorn"></i> Powiadom mnie o wynikach <i class="fas fa-bullhorn"></i> Powiadom mnie o nowych dotacjach
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal fade" id="modal-lg" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Powiadomienie o nowych wynikach</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="exampleInputEmail1">Podaj swój adres e-mail</label>
<input type="email" class="form-control" id="exampleInputEmail1" placeholder="Wprowadź e-mail">
</div>
<div class="callout callout-info">
<h5>Ważne informacje</h5>
<p>Podając adres e-mail wyrażasz zgodę na otrzymywanie wiadomości o wynikach wyszukiwania spełniających wybrane przez Ciebie kryteria</p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Zamknij</button>
<button type="button" class="btn btn-primary">Zapisz powiadomienie</button>
</div>
</div>
</div>
</div>
<% end %> <% end %>

View File

@ -0,0 +1 @@
$("#modal-lg").modal('hide');

View File

@ -13,7 +13,6 @@
<br /> <br />
</div> </div>
<div class="col-md-2"></div> <div class="col-md-2"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-8 offset-md-2"> <div class="col-md-8 offset-md-2">
@ -33,3 +32,32 @@
</div> </div>
</div> </div>
</section> </section>
<div class="modal fade" id="modal-lg" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<%= form_tag(home_emailfilter_path, method: :post, remote: true, id: 'email_filter_form') do %>
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Powiadomienie o nowych dotacjach</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="email_filter_inp">Podaj swój adres e-mail</label>
<input type="email" class="form-control" name="email_filter_inp" id="email_filter_inp" placeholder="Wprowadź e-mail">
</div>
<%= hidden_field_tag 'filter', @filter_hash %>
<div class="callout callout-info">
<h5>Ważne informacje</h5>
<p>Podając adres e-mail wyrażasz zgodę na otrzymywanie wiadomości o wynikach wyszukiwania spełniających wybrane przez Ciebie kryteria</p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Zamknij</button>
<button type="submit" class="btn btn-primary">Zapisz powiadomienie</button>
</div>
</div>
<% end %>
</div>
</div>

View File

@ -2,9 +2,7 @@
<html> <html>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style> <style></style>
/* Email styles need to be inline */
</style>
</head> </head>
<body> <body>

View File

@ -0,0 +1,34 @@
<table style="font-family:Verdana,sans-serif; font-size:18px; color:#374953;">
<tbody>
<tr>
<td>&nbsp;</td>
</tr>
<tr>
<td align="left">
Witaj, <strong style="color:#0cd4d2;"><%= @email_filter.email %></strong><br />
Pojawiła się nowa dotacja, która spełnia warunki Twojego wyszukiwania.<br />
Aby zobaczyć tą dotację kliknij <%= link_to 'pokaż dotację', "#{APP_CONFIG['mail_link']}/dotacja/#{@dotation.safe_id}" %>
</td>
</tr>
<tr>
<td>&nbsp;</td>
</tr>
<tr>
<td>Jeśli to nie Ty poprosiłeś o dodanie Ciebie do listy, możesz usunąć tą prośbę klikając w link <%= link_to 'Zrezygnuj', "#{APP_CONFIG['mail_link']}/zrezygnuj?#{@email_params.to_query}" %>
</td>
</tr>
<tr>
<td>&nbsp;</td>
</tr>
</tbody>
</table>
<table style="font-family:Verdana,sans-serif; font-size:14px; color:#374953;">
<tbody>
<tr>
<td>&nbsp;</td>
</tr>
<tr>
<td>UWAGA! Prosimy nie odpowiadać na tą wiadomość, została ona automatycznie wygenerowana przez system</td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1,30 @@
<table style="font-family:Verdana,sans-serif; font-size:18px; color:#374953;">
<tbody>
<tr>
<td>&nbsp;</td>
</tr>
<tr>
<td align="left">Witaj, <strong style="color:#0cd4d2;"><%= @email_filter.email %></strong><br />przyjęliśmy Twoją prośbę o informowanie Ciebie gdy pojawią się nowe dotacje.</td>
</tr>
<tr>
<td>&nbsp;</td>
</tr>
<tr>
<td>Jeśli to nie Ty poprosiłeś o dodanie Ciebie do listy, możesz usunąć tą prośbę klikając w link <%= link_to 'Zrezygnuj', "#{APP_CONFIG['mail_link']}/zrezygnuj?#{@email_params.to_query}" %>
</td>
</tr>
<tr>
<td>&nbsp;</td>
</tr>
</tbody>
</table>
<table style="font-family:Verdana,sans-serif; font-size:14px; color:#374953;">
<tbody>
<tr>
<td>&nbsp;</td>
</tr>
<tr>
<td>UWAGA! Prosimy nie odpowiadać na tą wiadomość, została ona automatycznie wygenerowana przez system</td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1 @@
Witaj

View File

@ -1,7 +1,6 @@
require_relative 'boot' require_relative 'boot'
require 'rails/all' require 'rails/all'
# Require the gems listed in Gemfile, including any gems # Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production. # you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups) Bundler.require(*Rails.groups)

1
config/config.yml Normal file
View File

@ -0,0 +1 @@
mail_link: 'http://localhost:3434'

View File

@ -3,3 +3,5 @@ require_relative 'application'
# Initialize the Rails application. # Initialize the Rails application.
Rails.application.initialize! Rails.application.initialize!
APP_CONFIG = YAML.load_file("#{Rails.root}/config/config.yml").freeze

View File

@ -34,8 +34,17 @@ Rails.application.configure do
config.action_mailer.raise_delivery_errors = false config.action_mailer.raise_delivery_errors = false
config.action_mailer.perform_caching = false config.action_mailer.perform_caching = false
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } config.action_mailer.default_url_options = { host: 'localhost', port: 3434 }
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: 'smtp.gmail.com',
port: 587,
domain: 'kalendarzdotacji.pl',
user_name: 'kalendarzdotacji@gmail.com',
password: '498FAvFd4YBapu5',
authentication: 'plain',
enable_starttls_auto: true
}
# Print deprecation notices to the Rails logger. # Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log config.active_support.deprecation = :log

View File

@ -1,9 +1,11 @@
Rails.application.routes.draw do Rails.application.routes.draw do
get 'zrezygnuj' => 'email_filter#unsubscribe'
get 'email_filter/unsubscribe'
get 'kontakt' => 'home#contact' get 'kontakt' => 'home#contact'
get 'dotacja/:id' => 'home#show' get 'dotacja/:id' => 'home#show'
get 'home/search' get 'home/search'
post 'home/search' post 'home/search'
post 'home/emailfilter'
resources :home, only: %i[index show] resources :home, only: %i[index show]
devise_for :users devise_for :users
resources :company_sizes resources :company_sizes

View File

@ -0,0 +1,11 @@
class CreateFilterForEmails < ActiveRecord::Migration[5.2]
def change
create_table :filter_for_emails do |t|
t.string :email
t.string :unique_name
t.text :filters
t.timestamps
end
end
end

View File

@ -0,0 +1,10 @@
class CreateSentEmailFilters < ActiveRecord::Migration[5.2]
def change
create_table :sent_email_filters do |t|
t.bigint :filter_for_email_id
t.bigint :dotation_id
t.timestamps
end
end
end

View File

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2022_02_24_074408) do ActiveRecord::Schema.define(version: 2022_03_11_073315) do
create_table "active_storage_attachments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| create_table "active_storage_attachments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
@ -75,8 +75,10 @@ ActiveRecord::Schema.define(version: 2022_02_24_074408) do
t.string "formal_name" t.string "formal_name"
t.date "date_from" t.date "date_from"
t.date "date_to" t.date "date_to"
t.integer "min_amount" t.bigint "min_amount"
t.integer "max_amount" t.integer "min_amount_curr_id", default: 1, null: false
t.bigint "max_amount"
t.integer "max_amount_curr_id", default: 1, null: false
t.integer "max_percent" t.integer "max_percent"
t.text "localization" t.text "localization"
t.integer "company_size_id" t.integer "company_size_id"
@ -88,7 +90,7 @@ ActiveRecord::Schema.define(version: 2022_02_24_074408) do
t.datetime "announcement_date" t.datetime "announcement_date"
t.text "full_descr", limit: 4294967295 t.text "full_descr", limit: 4294967295
t.text "positioning_text", limit: 4294967295 t.text "positioning_text", limit: 4294967295
t.integer "points" t.integer "points", default: 1
t.datetime "date_of_recruitment" t.datetime "date_of_recruitment"
t.boolean "arch", default: false t.boolean "arch", default: false
t.boolean "active", default: false t.boolean "active", default: false
@ -144,9 +146,16 @@ ActiveRecord::Schema.define(version: 2022_02_24_074408) do
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
end end
create_table "filter_for_emails", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.string "email"
t.text "filters"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "partners", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| create_table "partners", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.string "name" t.string "name"
t.string "description" t.text "description"
t.string "link_url" t.string "link_url"
t.integer "created_by" t.integer "created_by"
t.integer "updated_by" t.integer "updated_by"
@ -176,6 +185,13 @@ ActiveRecord::Schema.define(version: 2022_02_24_074408) do
t.index ["user_id", "role_id"], name: "index_roles_users_on_user_id_and_role_id" t.index ["user_id", "role_id"], name: "index_roles_users_on_user_id_and_role_id"
end end
create_table "sent_email_filters", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.bigint "filter_for_email_id"
t.bigint "dotation_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "tags", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| create_table "tags", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.string "name" t.string "name"
t.string "description" t.string "description"

View File

@ -0,0 +1,9 @@
require 'test_helper'
class EmailFilterControllerTest < ActionDispatch::IntegrationTest
test "should get unsubscribe" do
get email_filter_unsubscribe_url
assert_response :success
end
end

11
test/fixtures/filter_for_emails.yml vendored Normal file
View File

@ -0,0 +1,11 @@
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
email: MyString
filters: MyText
sended_emails: 1
two:
email: MyString
filters: MyText
sended_emails: 1

9
test/fixtures/sent_email_filters.yml vendored Normal file
View File

@ -0,0 +1,9 @@
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
filter_for_email_id:
dotation_id:
two:
filter_for_email_id:
dotation_id:

View File

@ -0,0 +1,7 @@
require 'test_helper'
class GeneratePdfJobTest < ActiveJob::TestCase
# test "the truth" do
# assert true
# end
end

View File

@ -0,0 +1,7 @@
require 'test_helper'
class SendNotifyEmailJobTest < ActiveJob::TestCase
# test "the truth" do
# assert true
# end
end

View File

@ -0,0 +1,7 @@
require 'test_helper'
class NewDotationMailerTest < ActionMailer::TestCase
# test "the truth" do
# assert true
# end
end

View File

@ -0,0 +1,4 @@
# Preview all emails at http://localhost:3000/rails/mailers/new_dotation_mailer
class NewDotationMailerPreview < ActionMailer::Preview
end

View File

@ -0,0 +1,7 @@
require 'test_helper'
class FilterForEmailTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

View File

@ -0,0 +1,7 @@
require 'test_helper'
class SentEmailFilterTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end