diff --git a/Gemfile b/Gemfile index 3a0627ab0..83325dfe2 100644 --- a/Gemfile +++ b/Gemfile @@ -103,8 +103,6 @@ gem 'elasticsearch-persistence', '~> 5' gem 'elasticsearch-rails', '~> 5' gem 'faraday', '~> 0.17' -gem 'notify_with' - gem 'pundit' gem 'oj' diff --git a/Gemfile.lock b/Gemfile.lock index 3997cee17..3f822dc80 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -236,10 +236,6 @@ GEM nokogiri (1.13.10) mini_portile2 (~> 2.8.0) racc (~> 1.4) - notify_with (0.0.2) - jbuilder (~> 2.0) - rails (>= 4.2.0) - responders (~> 2.0) oauth2 (1.4.4) faraday (>= 0.8, < 2.0) jwt (>= 1.0, < 3.0) @@ -531,7 +527,6 @@ DEPENDENCIES message_format mini_magick minitest-reporters - notify_with oj omniauth (~> 1.9.2) omniauth-oauth2 diff --git a/app/controllers/api/notifications_controller.rb b/app/controllers/api/notifications_controller.rb index a77a55e0a..76202452d 100644 --- a/app/controllers/api/notifications_controller.rb +++ b/app/controllers/api/notifications_controller.rb @@ -3,8 +3,8 @@ # API Controller for resources of type Notification # Notifications are scoped by user class API::NotificationsController < API::ApiController - include NotifyWith::NotificationsApi before_action :authenticate_user! + before_action :set_notification, only: :update # notifications can have anything attached, so we won't eager load the whole database around_action :skip_bullet, if: -> { defined?(Bullet) } @@ -14,7 +14,10 @@ class API::NotificationsController < API::ApiController def index loop do - @notifications = current_user.notifications.includes(:attached_object).page(params[:page]).per(NOTIFICATIONS_PER_PAGE).order('created_at DESC') + @notifications = current_user.notifications + .includes(:attached_object) + .page(params[:page]) + .per(NOTIFICATIONS_PER_PAGE).order('created_at DESC') # we delete obsolete notifications on first access break unless delete_obsoletes(@notifications) end @@ -27,7 +30,11 @@ class API::NotificationsController < API::ApiController def last_unread loop do - @notifications = current_user.notifications.includes(:attached_object).where(is_read: false).limit(3).order('created_at DESC') + @notifications = current_user.notifications + .includes(:attached_object) + .where(is_read: false) + .limit(3) + .order('created_at DESC') # we delete obsolete notifications on first access break unless delete_obsoletes(@notifications) end @@ -49,8 +56,22 @@ class API::NotificationsController < API::ApiController render :index end + def update + @notification.mark_as_read + render :show + end + + def update_all + current_user.notifications.where(is_read: false).find_each(&:mark_as_read) + head :no_content + end + private + def set_notification + @notification = current_user.notifications.find(params[:id]) + end + def delete_obsoletes(notifications) cleaned = false notifications.each do |n| diff --git a/app/mailers/notifications_mailer.rb b/app/mailers/notifications_mailer.rb index 370bd3e92..4cd9a3c90 100644 --- a/app/mailers/notifications_mailer.rb +++ b/app/mailers/notifications_mailer.rb @@ -1,11 +1,8 @@ # frozen_string_literal: true # Handle most of the emails sent by the platform. Triggered by notifications -class NotificationsMailer < NotifyWith::NotificationsMailer - default from: ->(*) { Setting.get('email_from') } - layout 'notifications_mailer' - - helper :application +class NotificationsMailer < BaseMailer + after_action :mark_notification_as_send def send_mail_by(notification) @notification = notification @@ -13,14 +10,14 @@ class NotificationsMailer < NotifyWith::NotificationsMailer @attached_object = notification.attached_object unless respond_to?(notification.notification_type) - class_eval %{ - def #{notification.notification_type} - mail to: @recipient.email, - subject: t('notifications_mailer.#{notification.notification_type}.subject'), - template_name: '#{notification.notification_type}', - content_type: 'text/html' - end - }, __FILE__, __LINE__ - 7 + class_eval(<<-RUBY, __FILE__, __LINE__ + 1) + def #{notification.notification_type} # def notify_admin_when_project_published + mail to: @recipient.email, # mail to: @recipient.email, + subject: t('notifications_mailer.#{notification.notification_type}.subject'), # subject: t('notifications_mailer.notify_admin_when_project_published.subject'), + template_name: '#{notification.notification_type}', # template_name: 'notify_admin_when_project_published', + content_type: 'text/html' # content_type: 'text/html' + end # end + RUBY end send(notification.notification_type) @@ -28,10 +25,6 @@ class NotificationsMailer < NotifyWith::NotificationsMailer Rails.logger.error "[NotificationsMailer] notification cannot be sent: #{e}" end - def helpers - ActionController::Base.helpers - end - def notify_user_when_invoice_ready attachments[@attached_object.filename] = File.read(@attached_object.file) mail(to: @recipient.email, @@ -59,4 +52,10 @@ class NotificationsMailer < NotifyWith::NotificationsMailer subject: t('notifications_mailer.notify_member_create_reservation.subject'), template_name: 'notify_member_create_reservation') end + + private + + def mark_notification_as_send + @notification.mark_as_send unless @notification.is_send + end end diff --git a/app/models/abuse.rb b/app/models/abuse.rb index c3b9e0e54..a39040b31 100644 --- a/app/models/abuse.rb +++ b/app/models/abuse.rb @@ -3,7 +3,7 @@ # Abuse is a report made by a visitor (not especially a logged user) who has signaled a content that seems abusive to his eyes. # It is currently used with projects. class Abuse < ApplicationRecord - include NotifyWith::NotificationAttachedObject + include NotificationAttachedObject belongs_to :signaled, polymorphic: true @@ -11,7 +11,6 @@ class Abuse < ApplicationRecord validates :first_name, :last_name, :email, :message, presence: true - private def notify_admins_abuse_reported diff --git a/app/models/concerns/notification_attached_object.rb b/app/models/concerns/notification_attached_object.rb new file mode 100644 index 000000000..886771654 --- /dev/null +++ b/app/models/concerns/notification_attached_object.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# Concern use to delete a notification if its attached object is detroyed +module NotificationAttachedObject + extend ActiveSupport::Concern + + included do + after_destroy :clean_notification + + def clean_notification + Notification.where('attached_object_id = :id and attached_object_type = :type', { id: id, type: self.class.to_s }) + .destroy_all + end + end +end diff --git a/app/models/event.rb b/app/models/event.rb index 330dc8bac..60bb3f70b 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -2,7 +2,7 @@ # Event is an happening organized by the Fablab about a general topic, which does not involve Machines or trainings member's skills. class Event < ApplicationRecord - include NotifyWith::NotificationAttachedObject + include NotificationAttachedObject include ApplicationHelper has_one :event_image, as: :viewable, dependent: :destroy diff --git a/app/models/invoice.rb b/app/models/invoice.rb index a0ef80aec..40a6b1a20 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -2,7 +2,7 @@ # Invoice correspond to a single purchase made by an user. This purchase is linked to one or many invoice_items class Invoice < PaymentDocument - include NotifyWith::NotificationAttachedObject + include NotificationAttachedObject require 'fileutils' scope :only_invoice, -> { where(type: nil) } diff --git a/app/models/notification.rb b/app/models/notification.rb index c2ac344db..4d10409e1 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -2,7 +2,36 @@ # Notification is an in-system alert that is shown to a specific user until it is marked as read. class Notification < ApplicationRecord - include NotifyWith::Notification + belongs_to :notification_type + belongs_to :receiver, polymorphic: true + belongs_to :attached_object, polymorphic: true + + validates :receiver_id, + :receiver_type, + :attached_object_id, + :attached_object_type, + :notification_type_id, + presence: true + + def notification_type + NotificationType.find(notification_type_id).name + end + + def mark_as_read + update(is_read: true) + end + + def mark_as_send + update(is_send: true) + end + + def deliver_now + NotificationsMailer.send_mail_by(self).deliver_now if save + end + + def deliver_later + NotificationsMailer.send_mail_by(self).deliver_later if save + end def get_meta_data(key) meta_data.try(:[], key.to_s) diff --git a/app/models/notification_type.rb b/app/models/notification_type.rb index 6ff2be7f6..0a95a8978 100644 --- a/app/models/notification_type.rb +++ b/app/models/notification_type.rb @@ -1,84 +1,7 @@ # frozen_string_literal: true # NotificationType defines the different types of Notification. -class NotificationType - include NotifyWith::NotificationType - - # DANGER: dont remove a notification type!!! - notification_type_names %w[ - notify_admin_when_project_published - notify_project_collaborator_to_valid - notify_project_author_when_collaborator_valid - notify_user_training_valid - notify_member_subscribed_plan - notify_member_create_reservation - notify_member_subscribed_plan_is_changed - notify_admin_member_create_reservation - notify_member_slot_is_modified - notify_admin_slot_is_modified - notify_admin_when_user_is_created - notify_admin_subscribed_plan - notify_user_when_invoice_ready - notify_member_subscription_will_expire_in_7_days - notify_member_subscription_is_expired - notify_admin_subscription_will_expire_in_7_days - notify_admin_subscription_is_expired - notify_admin_subscription_canceled - notify_member_subscription_canceled - notify_user_when_avoir_ready - notify_member_slot_is_canceled - notify_admin_slot_is_canceled - notify_partner_subscribed_plan - notify_member_subscription_extended - notify_admin_subscription_extended - notify_admin_user_group_changed - notify_user_user_group_changed - notify_admin_when_user_is_imported - notify_user_profile_complete - notify_user_auth_migration - notify_admin_user_merged - notify_admin_profile_complete - notify_admin_abuse_reported - notify_admin_invoicing_changed - notify_user_wallet_is_credited - notify_admin_user_wallet_is_credited - notify_admin_export_complete - notify_member_about_coupon - notify_member_reservation_reminder - notify_admin_free_disk_space - notify_admin_close_period_reminder - notify_admin_archive_complete - notify_privacy_policy_changed - notify_admin_import_complete - notify_admin_refund_created - notify_admins_role_update - notify_user_role_update - notify_admin_objects_stripe_sync - notify_user_when_payment_schedule_ready - notify_admin_payment_schedule_failed - notify_member_payment_schedule_failed - notify_admin_payment_schedule_check_deadline - notify_admin_payment_schedule_transfer_deadline - notify_admin_payment_schedule_error - notify_member_payment_schedule_error - notify_admin_payment_schedule_gateway_canceled - notify_member_payment_schedule_gateway_canceled - notify_admin_user_proof_of_identity_files_created - notify_admin_user_proof_of_identity_files_updated - notify_user_is_validated - notify_user_is_invalidated - notify_user_proof_of_identity_refusal - notify_admin_user_proof_of_identity_refusal - notify_user_order_is_ready - notify_user_order_is_canceled - notify_user_order_is_refunded - notify_admin_low_stock_threshold - notify_admin_training_auto_cancelled - notify_member_training_auto_cancelled - notify_member_training_authorization_expired - notify_member_training_invalidated - ] - # deprecated: - # - notify_member_subscribed_plan_is_changed - # - notify_admin_invoicing_changed +class NotificationType < ApplicationRecord + has_many :notifications, dependent: :destroy + validates :name, uniqueness: true, presence: true end diff --git a/app/models/offer_day.rb b/app/models/offer_day.rb index be998908d..c7ae3c081 100644 --- a/app/models/offer_day.rb +++ b/app/models/offer_day.rb @@ -2,7 +2,7 @@ # OfferDay provides a way for admins to extend the subscription of a member for free. class OfferDay < ApplicationRecord - include NotifyWith::NotificationAttachedObject + include NotificationAttachedObject has_many :invoice_items, as: :object, dependent: :destroy belongs_to :subscription @@ -32,5 +32,4 @@ class OfferDay < ApplicationRecord attached_object: subscription, meta_data: meta_data end - end diff --git a/app/models/project.rb b/app/models/project.rb index 6e0bb3497..860e2f5ad 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -4,7 +4,7 @@ # It can describe the steps taken by the fab-user to build his object, provide photos, description, attached CAO files, etc. class Project < ApplicationRecord include AASM - include NotifyWith::NotificationAttachedObject + include NotificationAttachedObject include OpenlabSync include PgSearch::Model diff --git a/app/models/project_user.rb b/app/models/project_user.rb index 54b13f7de..8c871b22c 100644 --- a/app/models/project_user.rb +++ b/app/models/project_user.rb @@ -3,14 +3,14 @@ # ProjectUser is the relation table between a Project and an User. # Users are collaborators to a Project, with write access if they have confirmed their participation. class ProjectUser < ApplicationRecord - include NotifyWith::NotificationAttachedObject + include NotificationAttachedObject belongs_to :project belongs_to :user before_create :generate_valid_token - after_commit :notify_project_collaborator_to_valid, on: :create after_update :notify_project_author_when_collaborator_valid, if: :saved_change_to_is_valid? + after_commit :notify_project_collaborator_to_valid, on: :create private diff --git a/app/models/reservation.rb b/app/models/reservation.rb index 0bc743eed..d75066ba5 100644 --- a/app/models/reservation.rb +++ b/app/models/reservation.rb @@ -4,7 +4,7 @@ # Slots are for Machine, Space and Training reservations. # Tickets are for Event reservations. class Reservation < ApplicationRecord - include NotifyWith::NotificationAttachedObject + include NotificationAttachedObject include ICalendarConcern belongs_to :statistic_profile diff --git a/app/models/slot.rb b/app/models/slot.rb index c4e75fba0..673b6a306 100644 --- a/app/models/slot.rb +++ b/app/models/slot.rb @@ -4,7 +4,7 @@ # Slots duration are defined globally by Setting.get('slot_duration') but can be # overridden per availability. class Slot < ApplicationRecord - include NotifyWith::NotificationAttachedObject + include NotificationAttachedObject has_many :slots_reservations, dependent: :destroy has_many :reservations, through: :slots_reservations diff --git a/app/models/statistic_profile_training.rb b/app/models/statistic_profile_training.rb index d0364fea8..99164ff0c 100644 --- a/app/models/statistic_profile_training.rb +++ b/app/models/statistic_profile_training.rb @@ -2,7 +2,7 @@ # Stores trainings validated per user (non validated trainings are only recorded in reservations) class StatisticProfileTraining < ApplicationRecord - include NotifyWith::NotificationAttachedObject + include NotificationAttachedObject belongs_to :statistic_profile belongs_to :training diff --git a/app/models/subscription.rb b/app/models/subscription.rb index 3e9ffa370..f9667828e 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -2,7 +2,7 @@ # Subscription is an active or archived subscription of an User to a Plan class Subscription < ApplicationRecord - include NotifyWith::NotificationAttachedObject + include NotificationAttachedObject belongs_to :plan belongs_to :statistic_profile diff --git a/app/models/user.rb b/app/models/user.rb index 570ff2b87..976a1c7e2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,9 +3,7 @@ # User is a physical or moral person with its authentication parameters # It is linked to the Profile model with hold information about this person (like address, name, etc.) class User < ApplicationRecord - include NotifyWith::NotificationReceiver - include NotifyWith::NotificationAttachedObject - + include NotificationAttachedObject include SingleSignOnConcern include UserRoleConcern include UserRessourcesConcern @@ -52,6 +50,8 @@ class User < ApplicationRecord has_many :proof_of_identity_files, dependent: :destroy has_many :proof_of_identity_refusals, dependent: :destroy + has_many :notifications, as: :receiver, dependent: :destroy + # fix for create admin user before_save do email&.downcase! diff --git a/app/models/wallet_transaction.rb b/app/models/wallet_transaction.rb index e95d08b0a..e3ce52d38 100644 --- a/app/models/wallet_transaction.rb +++ b/app/models/wallet_transaction.rb @@ -3,6 +3,7 @@ # track of all transactions payed using the given wallet class WalletTransaction < ApplicationRecord include AmountConcern + include NotificationAttachedObject belongs_to :invoicing_profile belongs_to :wallet diff --git a/app/services/notification_center.rb b/app/services/notification_center.rb index 32a6063c9..8712d763e 100644 --- a/app/services/notification_center.rb +++ b/app/services/notification_center.rb @@ -3,17 +3,14 @@ # send notification to one or several receiver with a type, an attached object and an optional meta data class NotificationCenter def self.call(type: nil, receiver: nil, attached_object: nil, meta_data: {}) - if receiver.respond_to?(:each) - receiver.each do |user| - Notification.new(meta_data: meta_data) - .send_notification(type: type, attached_object: attached_object) - .to(user) - .deliver_later - end - else - Notification.new(meta_data: meta_data) - .send_notification(type: type, attached_object: attached_object) - .to(receiver) + receiver = [receiver] unless receiver.respond_to?(:each) + receiver.each do |user| + Notification.new( + meta_data: meta_data, + attached_object: attached_object, + receiver: user, + notification_type: NotificationType.find_by(name: type) + ) .deliver_later end end diff --git a/config/initializers/notify_with.rb b/config/initializers/notify_with.rb deleted file mode 100644 index 5fc247965..000000000 --- a/config/initializers/notify_with.rb +++ /dev/null @@ -1 +0,0 @@ -NotifyWith.mailer = NotificationsMailer diff --git a/db/migrate/20230126160900_create_notification_types.rb b/db/migrate/20230126160900_create_notification_types.rb new file mode 100644 index 000000000..565b773ee --- /dev/null +++ b/db/migrate/20230126160900_create_notification_types.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +# Creates Notification Types table to record every notification type (previously +# stored in an Array), and records the existing types. + +# This migration is linked to the abandon of the NotifyWith gem. Notification Types +# will now be store in database and we will manage ourself the Notification system. +class CreateNotificationTypes < ActiveRecord::Migration[5.2] + NAMES = %w[ + notify_admin_when_project_published + notify_project_collaborator_to_valid + notify_project_author_when_collaborator_valid + notify_user_training_valid + notify_member_subscribed_plan + notify_member_create_reservation + notify_member_subscribed_plan_is_changed + notify_admin_member_create_reservation + notify_member_slot_is_modified + notify_admin_slot_is_modified + notify_admin_when_user_is_created + notify_admin_subscribed_plan + notify_user_when_invoice_ready + notify_member_subscription_will_expire_in_7_days + notify_member_subscription_is_expired + notify_admin_subscription_will_expire_in_7_days + notify_admin_subscription_is_expired + notify_admin_subscription_canceled + notify_member_subscription_canceled + notify_user_when_avoir_ready + notify_member_slot_is_canceled + notify_admin_slot_is_canceled + notify_partner_subscribed_plan + notify_member_subscription_extended + notify_admin_subscription_extended + notify_admin_user_group_changed + notify_user_user_group_changed + notify_admin_when_user_is_imported + notify_user_profile_complete + notify_user_auth_migration + notify_admin_user_merged + notify_admin_profile_complete + notify_admin_abuse_reported + notify_admin_invoicing_changed + notify_user_wallet_is_credited + notify_admin_user_wallet_is_credited + notify_admin_export_complete + notify_member_about_coupon + notify_member_reservation_reminder + notify_admin_free_disk_space + notify_admin_close_period_reminder + notify_admin_archive_complete + notify_privacy_policy_changed + notify_admin_import_complete + notify_admin_refund_created + notify_admins_role_update + notify_user_role_update + notify_admin_objects_stripe_sync + notify_user_when_payment_schedule_ready + notify_admin_payment_schedule_failed + notify_member_payment_schedule_failed + notify_admin_payment_schedule_check_deadline + notify_admin_payment_schedule_transfer_deadline + notify_admin_payment_schedule_error + notify_member_payment_schedule_error + notify_admin_payment_schedule_gateway_canceled + notify_member_payment_schedule_gateway_canceled + notify_admin_user_proof_of_identity_files_created + notify_admin_user_proof_of_identity_files_updated + notify_user_is_validated + notify_user_is_invalidated + notify_user_proof_of_identity_refusal + notify_admin_user_proof_of_identity_refusal + notify_user_order_is_ready + notify_user_order_is_canceled + notify_user_order_is_refunded + notify_admin_low_stock_threshold + notify_admin_training_auto_cancelled + ].freeze + + def up + create_table :notification_types do |t| + t.string :name, null: false + + t.timestamps + end + + add_index :notification_types, :name, unique: true + + # Records previous notification types + # Index start at 1. This is required due to previous functionning of the NotifyWith gem + NAMES.each.with_index(1) do |type, index| + NotificationType.create!(id: index, name: type) + end + + last_id = NotificationType.order(:id).last.id + execute "SELECT setval('public.notification_types_id_seq', #{last_id})" + + add_foreign_key :notifications, :notification_types + end + + def down + remove_foreign_key :notifications, :notification_types + drop_table :notification_types + end +end diff --git a/db/schema.rb b/db/schema.rb index ea612dafb..241653212 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_01_31_104958) do +ActiveRecord::Schema.define(version: 2023_01_26_160900) do # These are extensions that must be enabled in order to support this database enable_extension "fuzzystrmatch"