mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-10-09 13:28:53 +08:00
Add bulk insert for UserSystemNotifications, New service for sending emails
This commit is contained in:
parent
357c6e6b98
commit
2364c2653f
11 changed files with 198 additions and 46 deletions
|
@ -4,8 +4,7 @@ class UserSystemNotification < ApplicationRecord
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :system_notification
|
belongs_to :system_notification
|
||||||
|
|
||||||
after_create :send_email,
|
validates :system_notification, uniqueness: { scope: :user }
|
||||||
if: proc { |sn| sn.user.system_message_email_notification }
|
|
||||||
|
|
||||||
scope :unseen, -> { where(seen_at: nil) }
|
scope :unseen, -> { where(seen_at: nil) }
|
||||||
|
|
||||||
|
@ -15,9 +14,7 @@ class UserSystemNotification < ApplicationRecord
|
||||||
|
|
||||||
def self.mark_as_read(notification_id)
|
def self.mark_as_read(notification_id)
|
||||||
notification = find_by_system_notification_id(notification_id)
|
notification = find_by_system_notification_id(notification_id)
|
||||||
if notification && notification.read_at.nil?
|
notification.update(read_at: Time.now) if notification && notification.read_at.nil?
|
||||||
notification.update(read_at: Time.now)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.show_on_login(update_read_time = false)
|
def self.show_on_login(update_read_time = false)
|
||||||
|
@ -45,10 +42,4 @@ class UserSystemNotification < ApplicationRecord
|
||||||
notification
|
notification
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def send_email
|
|
||||||
AppMailer.delay.system_notification(user, system_notification)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Notifications
|
||||||
|
class HandleSystemNotificationInCommunicationChannelService
|
||||||
|
extend Service
|
||||||
|
|
||||||
|
attr_reader :errors
|
||||||
|
|
||||||
|
def initialize(system_notification)
|
||||||
|
@system_notification = system_notification
|
||||||
|
@errors = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
@system_notification.user_system_notifications.find_each do |usn|
|
||||||
|
user = usn.user
|
||||||
|
AppMailer.delay.system_notification(user, @system_notification) if user.system_message_email_notification
|
||||||
|
end
|
||||||
|
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def succeed?
|
||||||
|
@errors.none?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,40 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Notifications
|
||||||
|
class PushToCommunicationChannelService
|
||||||
|
extend Service
|
||||||
|
|
||||||
|
WHITELISTED_ITEM_TYPES = %w(SystemNotification).freeze
|
||||||
|
|
||||||
|
attr_reader :errors
|
||||||
|
|
||||||
|
def initialize(item_id:, item_type:)
|
||||||
|
@item_type = item_type
|
||||||
|
@item = item_type.constantize.find item_id
|
||||||
|
@errors = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
return self unless valid?
|
||||||
|
|
||||||
|
"Notifications::Handle#{@item_type}InCommunicationChannelService".constantize.call(@item)
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def succeed?
|
||||||
|
@errors.none?
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def valid?
|
||||||
|
raise 'Dont know how to handle this type of items' unless WHITELISTED_ITEM_TYPES.include?(@item_type)
|
||||||
|
|
||||||
|
if @item.nil?
|
||||||
|
@errors[:invalid_arguments] = 'Can\'t find item' if @item.nil?
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -81,12 +81,27 @@ module Notifications
|
||||||
.where(source_id: attrs[:source_id]).first_or_initialize(attrs)
|
.where(source_id: attrs[:source_id]).first_or_initialize(attrs)
|
||||||
|
|
||||||
if n.new_record?
|
if n.new_record?
|
||||||
n.users = User.all
|
save_notification n
|
||||||
n.save!
|
|
||||||
elsif n.last_time_changed_at < attrs[:last_time_changed_at]
|
elsif n.last_time_changed_at < attrs[:last_time_changed_at]
|
||||||
n.update_attributes!(attrs)
|
n.update_attributes!(attrs)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def save_notification(notification)
|
||||||
|
ActiveRecord::Base.transaction do
|
||||||
|
notification.save!
|
||||||
|
|
||||||
|
User.find_in_batches do |user_ids|
|
||||||
|
user_system_notifications = user_ids.pluck(:id).collect do |item|
|
||||||
|
Hash[:user_id, item, :system_notification_id, notification.id]
|
||||||
|
end
|
||||||
|
UserSystemNotification.import user_system_notifications, validate: false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Notifications::PushToCommunicationChannelService.delay.call(item_id: notification.id,
|
||||||
|
item_type: notification.class.name)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddUniqueIndexToSystemNotifications < ActiveRecord::Migration[5.1]
|
||||||
|
def change
|
||||||
|
# remove not unique index and add new with uniq
|
||||||
|
remove_index :system_notifications, :source_id
|
||||||
|
add_index :system_notifications, :source_id, unique: true
|
||||||
|
|
||||||
|
add_index :user_system_notifications, %i(user_id system_notification_id), unique: true,
|
||||||
|
name: 'index_user_system_notifications_on_user_and_notification_id'
|
||||||
|
end
|
||||||
|
end
|
|
@ -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: 20190410110605) do
|
ActiveRecord::Schema.define(version: 20190424113216) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -684,7 +684,7 @@ ActiveRecord::Schema.define(version: 20190410110605) do
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.index ["last_time_changed_at"], name: "index_system_notifications_on_last_time_changed_at"
|
t.index ["last_time_changed_at"], name: "index_system_notifications_on_last_time_changed_at"
|
||||||
t.index ["source_created_at"], name: "index_system_notifications_on_source_created_at"
|
t.index ["source_created_at"], name: "index_system_notifications_on_source_created_at"
|
||||||
t.index ["source_id"], name: "index_system_notifications_on_source_id"
|
t.index ["source_id"], name: "index_system_notifications_on_source_id", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "tables", force: :cascade do |t|
|
create_table "tables", force: :cascade do |t|
|
||||||
|
@ -819,6 +819,7 @@ ActiveRecord::Schema.define(version: 20190410110605) do
|
||||||
t.index ["read_at"], name: "index_user_system_notifications_on_read_at"
|
t.index ["read_at"], name: "index_user_system_notifications_on_read_at"
|
||||||
t.index ["seen_at"], name: "index_user_system_notifications_on_seen_at"
|
t.index ["seen_at"], name: "index_user_system_notifications_on_seen_at"
|
||||||
t.index ["system_notification_id"], name: "index_user_system_notifications_on_system_notification_id"
|
t.index ["system_notification_id"], name: "index_user_system_notifications_on_system_notification_id"
|
||||||
|
t.index ["user_id", "system_notification_id"], name: "index_user_system_notifications_on_user_and_notification_id", unique: true
|
||||||
t.index ["user_id"], name: "index_user_system_notifications_on_user_id"
|
t.index ["user_id"], name: "index_user_system_notifications_on_user_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ FactoryBot.define do
|
||||||
modal_title { Faker::Name.first_name }
|
modal_title { Faker::Name.first_name }
|
||||||
modal_body { Faker::Lorem.paragraphs(4).map { |pr| "<p>#{pr}</p>" }.join }
|
modal_body { Faker::Lorem.paragraphs(4).map { |pr| "<p>#{pr}</p>" }.join }
|
||||||
source_created_at { Faker::Time.between(3.days.ago, Date.today) }
|
source_created_at { Faker::Time.between(3.days.ago, Date.today) }
|
||||||
source_id { Faker::Number.between(1, 1000) }
|
source_id { SystemNotification.order(source_id: :desc).first&.source_id.to_i + 1 }
|
||||||
last_time_changed_at { Time.now }
|
last_time_changed_at { Time.now }
|
||||||
trait :show_on_login do
|
trait :show_on_login do
|
||||||
show_on_login { true }
|
show_on_login { true }
|
||||||
|
|
|
@ -15,36 +15,6 @@ describe UserSystemNotification do
|
||||||
it { is_expected.to belong_to(:system_notification) }
|
it { is_expected.to belong_to(:system_notification) }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.create' do
|
|
||||||
before do
|
|
||||||
Delayed::Worker.delay_jobs = false
|
|
||||||
end
|
|
||||||
|
|
||||||
after do
|
|
||||||
Delayed::Worker.delay_jobs = true
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when user has enabled notifications' do
|
|
||||||
it 'calls send an email on creation' do
|
|
||||||
allow(user_system_notification.user)
|
|
||||||
.to receive(:system_message_email_notification).and_return(true)
|
|
||||||
|
|
||||||
expect(user_system_notification).to receive(:send_email)
|
|
||||||
user_system_notification.save
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when user has disabled notifications' do
|
|
||||||
it 'doesn\'t call send an email on createion' do
|
|
||||||
allow(user_system_notification.user)
|
|
||||||
.to receive(:system_message_email_notification).and_return(false)
|
|
||||||
|
|
||||||
expect(user_system_notification).not_to receive(:send_email)
|
|
||||||
user_system_notification.save
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'Methods' do
|
describe 'Methods' do
|
||||||
let(:notifcation_one) { create :system_notification }
|
let(:notifcation_one) { create :system_notification }
|
||||||
let(:notifcation_two) { create :system_notification }
|
let(:notifcation_two) { create :system_notification }
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe Notifications::HandleSystemNotificationInCommunicationChannelService do
|
||||||
|
let(:system_notification) { create :system_notification }
|
||||||
|
let!(:user_system_notification) do
|
||||||
|
create :user_system_notification, user: user, system_notification: system_notification
|
||||||
|
end
|
||||||
|
let(:user) { create :user }
|
||||||
|
let(:service_call) do
|
||||||
|
Notifications::HandleSystemNotificationInCommunicationChannelService.call(system_notification)
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
Delayed::Worker.delay_jobs = false
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
Delayed::Worker.delay_jobs = true
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user has enabled notifications' do
|
||||||
|
it 'calls AppMailer' do
|
||||||
|
allow_any_instance_of(User).to receive(:system_message_email_notification).and_return(true)
|
||||||
|
|
||||||
|
expect(AppMailer).to receive(:system_notification).and_return(double('Mailer', deliver: true))
|
||||||
|
|
||||||
|
service_call
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user has disabled notifications' do
|
||||||
|
it 'does not call AppMailer' do
|
||||||
|
allow_any_instance_of(User).to receive(:system_message_email_notification).and_return(false)
|
||||||
|
|
||||||
|
expect(AppMailer).not_to receive(:system_notification)
|
||||||
|
|
||||||
|
service_call
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,36 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe Notifications::PushToCommunicationChannelService do
|
||||||
|
let(:system_notification) { create :system_notification }
|
||||||
|
let(:service_call) do
|
||||||
|
Notifications::PushToCommunicationChannelService.call(item_id: system_notification.id,
|
||||||
|
item_type: system_notification.class.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when call with valid items' do
|
||||||
|
it 'call service to to handle sending out' do
|
||||||
|
expect(Notifications::HandleSystemNotificationInCommunicationChannelService)
|
||||||
|
.to receive(:call).with(system_notification)
|
||||||
|
|
||||||
|
service_call
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when call with not valid items' do
|
||||||
|
it 'returns error with key invalid_arguments when system notification not exists' do
|
||||||
|
allow(SystemNotification).to receive(:find).and_return(nil)
|
||||||
|
|
||||||
|
expect(service_call.errors).to have_key(:invalid_arguments)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raise error when have not listed object' do
|
||||||
|
u = create :user
|
||||||
|
|
||||||
|
expect do
|
||||||
|
Notifications::PushToCommunicationChannelService.call(item_id: u.id, item_type: 'User')
|
||||||
|
end.to(raise_error('Dont know how to handle this type of items'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,6 +6,8 @@ describe Notifications::SyncSystemNotificationsService do
|
||||||
url = 'http://system-notifications-service.test/api/system_notifications'
|
url = 'http://system-notifications-service.test/api/system_notifications'
|
||||||
let!(:user) { create :user }
|
let!(:user) { create :user }
|
||||||
let(:service_call) do
|
let(:service_call) do
|
||||||
|
allow_any_instance_of(Notifications::PushToCommunicationChannelService).to receive(:call).and_return(nil)
|
||||||
|
|
||||||
Notifications::SyncSystemNotificationsService.call
|
Notifications::SyncSystemNotificationsService.call
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -80,6 +82,14 @@ describe Notifications::SyncSystemNotificationsService do
|
||||||
|
|
||||||
expect { service_call }.to change { UserSystemNotification.count }.by(20)
|
expect { service_call }.to change { UserSystemNotification.count }.by(20)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'calls service to notify users about notification' do
|
||||||
|
Delayed::Worker.delay_jobs = false
|
||||||
|
expect(Notifications::PushToCommunicationChannelService).to receive(:call).exactly(10)
|
||||||
|
|
||||||
|
service_call
|
||||||
|
Delayed::Worker.delay_jobs = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when request is unsuccessful' do
|
context 'when request is unsuccessful' do
|
||||||
|
@ -101,5 +111,13 @@ describe Notifications::SyncSystemNotificationsService do
|
||||||
|
|
||||||
expect(service_call.errors).to have_key(:socketerror)
|
expect(service_call.errors).to have_key(:socketerror)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'does not call service to notify users about notification' do
|
||||||
|
Delayed::Worker.delay_jobs = false
|
||||||
|
expect(Notifications::PushToCommunicationChannelService).to_not receive(:call)
|
||||||
|
|
||||||
|
service_call
|
||||||
|
Delayed::Worker.delay_jobs = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Reference in a new issue