From 33d42908165a58d0c38185ff3362d258102e000c Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Thu, 12 Oct 2023 11:21:42 -0500 Subject: [PATCH 01/39] Always use json column type to simplify model generator. Postgres users can edit migration to jsonb if needed. --- lib/generators/noticed/model_generator.rb | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/lib/generators/noticed/model_generator.rb b/lib/generators/noticed/model_generator.rb index 1f4bcd5f..76ccff92 100644 --- a/lib/generators/noticed/model_generator.rb +++ b/lib/generators/noticed/model_generator.rb @@ -15,7 +15,7 @@ class ModelGenerator < Rails::Generators::NamedBase argument :attributes, type: :array, default: [], banner: "field:type field:type" def generate_notification - generate :model, name, "recipient:references{polymorphic}", "type", params_column, "read_at:datetime:index", *attributes + generate :model, name, "recipient:references{polymorphic}", "type", "params:json", "read_at:datetime:index", *attributes end def add_noticed_model @@ -40,24 +40,6 @@ def done def model_path @model_path ||= File.join("app", "models", "#{file_path}.rb") end - - def params_column - case current_adapter - when "postgresql", "postgis" - "params:jsonb" - else - # MySQL and SQLite both support json - "params:json" - end - end - - def current_adapter - if ActiveRecord::Base.respond_to?(:connection_db_config) - ActiveRecord::Base.connection_db_config.adapter - else - ActiveRecord::Base.connection_config[:adapter] - end - end end end end From f93fc2dacd86f3e3f45f0d4cc348803c2abee711 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Thu, 12 Oct 2023 11:22:24 -0500 Subject: [PATCH 02/39] Add record polymorphic association to model for simpler associations --- lib/generators/noticed/model_generator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/generators/noticed/model_generator.rb b/lib/generators/noticed/model_generator.rb index 76ccff92..5ccdbc82 100644 --- a/lib/generators/noticed/model_generator.rb +++ b/lib/generators/noticed/model_generator.rb @@ -15,7 +15,7 @@ class ModelGenerator < Rails::Generators::NamedBase argument :attributes, type: :array, default: [], banner: "field:type field:type" def generate_notification - generate :model, name, "recipient:references{polymorphic}", "type", "params:json", "read_at:datetime:index", *attributes + generate :model, name, "recipient:references{polymorphic}", "type", "record:references{polymorphic}", "params:json", "read_at:datetime:index", *attributes end def add_noticed_model From 55aa7bf2dc22c5b60a3a64110f5ee69b9011f7d8 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Thu, 12 Oct 2023 11:22:46 -0500 Subject: [PATCH 03/39] Add record association --- lib/noticed/model.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/noticed/model.rb b/lib/noticed/model.rb index ba70dd35..44c57c0a 100644 --- a/lib/noticed/model.rb +++ b/lib/noticed/model.rb @@ -20,6 +20,7 @@ module Model end belongs_to :recipient, polymorphic: true + belongs_to :record, polymorphic: true scope :newest_first, -> { order(created_at: :desc) } scope :unread, -> { where(read_at: nil) } From b7e9207e64c8fd8e23db3da57535cb925f58fcef Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 20 Oct 2023 20:15:39 -0500 Subject: [PATCH 04/39] Add support for delivery_by config blocks (#315) --- lib/noticed/base.rb | 4 +++- lib/noticed/delivery_methods/database.rb | 6 ++++- .../app/notifications/comment_notification.rb | 23 +++++++++++-------- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/lib/noticed/base.rb b/lib/noticed/base.rb index afe01fb5..6450e53f 100644 --- a/lib/noticed/base.rb +++ b/lib/noticed/base.rb @@ -16,7 +16,9 @@ class Base class << self def deliver_by(name, options = {}) - delivery_methods.push(name: name, options: options) + config = ActiveSupport::OrderedOptions.new.merge(options) + yield config if block_given? + delivery_methods.push(name: name, options: config) define_model_callbacks(name) end diff --git a/lib/noticed/delivery_methods/database.rb b/lib/noticed/delivery_methods/database.rb index d074e594..85e2d429 100644 --- a/lib/noticed/delivery_methods/database.rb +++ b/lib/noticed/delivery_methods/database.rb @@ -21,7 +21,11 @@ def association_name def attributes if (method = options[:format]) - notification.send(method) + if method.respond_to? :call + notification.instance_eval(&method) + else + notification.send(method) + end else { type: notification.class.name, diff --git a/test/dummy/app/notifications/comment_notification.rb b/test/dummy/app/notifications/comment_notification.rb index 66971165..1ac7b3af 100644 --- a/test/dummy/app/notifications/comment_notification.rb +++ b/test/dummy/app/notifications/comment_notification.rb @@ -1,17 +1,22 @@ class CommentNotification < Noticed::Base - deliver_by :database, format: :attributes_for_database + deliver_by :database do |config| + config.format = proc do + { + account_id: 1, + type: self.class.name, + params: params + } + end + end + deliver_by :action_cable - deliver_by :email, mailer: "UserMailer" - deliver_by :discord, class: "DiscordNotification" - def attributes_for_database - { - account_id: 1, - type: self.class.name, - params: params - } + deliver_by :email do |config| + config.mailer = "UserMailer" end + deliver_by :discord, class: "DiscordNotification" + def url root_url end From 22e8fd5b129e28d795bb796263d885eddd0f7aa3 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 20 Oct 2023 20:16:18 -0500 Subject: [PATCH 05/39] Update appraisals for Rails 6.1+ and Ruby 3+ --- .github/workflows/ci.yml | 39 +--- Appraisals | 8 - Gemfile.lock | 308 +++++++++++++++++-------------- gemfiles/rails_5_2.gemfile | 17 -- gemfiles/rails_5_2.gemfile.lock | 256 ------------------------- gemfiles/rails_6.gemfile | 17 -- gemfiles/rails_6.gemfile.lock | 272 --------------------------- gemfiles/rails_6_1.gemfile.lock | 273 +++++++++++++-------------- gemfiles/rails_7.gemfile.lock | 269 +++++++++++++-------------- gemfiles/rails_main.gemfile.lock | 275 ++++++++++++++------------- 10 files changed, 569 insertions(+), 1165 deletions(-) delete mode 100644 gemfiles/rails_5_2.gemfile delete mode 100644 gemfiles/rails_5_2.gemfile.lock delete mode 100644 gemfiles/rails_6.gemfile delete mode 100644 gemfiles/rails_6.gemfile.lock diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85e174cc..c25e9315 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,22 +12,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - ruby: ['2.7', '3.0', '3.1', '3.2'] + ruby: ['3.0', '3.1', '3.2'] gemfile: - - rails_5_2 - - rails_6 - rails_6_1 - rails_7 - rails_main - exclude: - - ruby: '3.0' - gemfile: 'rails_5_2' - - ruby: '3.1' - gemfile: 'rails_5_2' - - ruby: '3.2' - gemfile: 'rails_5_2' - - ruby: '3.2' - gemfile: 'rails_6' env: BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile @@ -59,22 +48,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - ruby: ['2.7', '3.0', '3.1', '3.2'] + ruby: ['3.0', '3.1', '3.2'] gemfile: - - rails_5_2 - - rails_6 - rails_6_1 - rails_7 - rails_main - exclude: - - ruby: '3.0' - gemfile: 'rails_5_2' - - ruby: '3.1' - gemfile: 'rails_5_2' - - ruby: '3.2' - gemfile: 'rails_5_2' - - ruby: '3.2' - gemfile: 'rails_6' env: BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile @@ -115,22 +93,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - ruby: ['2.7', '3.0', '3.1', '3.2'] + ruby: ['3.0', '3.1', '3.2'] gemfile: - - rails_5_2 - - rails_6 - rails_6_1 - rails_7 - rails_main - exclude: - - ruby: '3.0' - gemfile: 'rails_5_2' - - ruby: '3.1' - gemfile: 'rails_5_2' - - ruby: '3.2' - gemfile: 'rails_5_2' - - ruby: '3.2' - gemfile: 'rails_6' env: BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile diff --git a/Appraisals b/Appraisals index e238b042..275931af 100644 --- a/Appraisals +++ b/Appraisals @@ -1,11 +1,3 @@ -appraise "rails-5-2" do - gem "rails", "~> 5.2.0" -end - -appraise "rails-6" do - gem "rails", "~> 6.0.0" -end - appraise "rails-6-1" do gem "rails", "~> 6.1.0" end diff --git a/Gemfile.lock b/Gemfile.lock index 894edb34..0f82a82c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,248 +2,270 @@ PATH remote: . specs: noticed (1.6.3) - http (>= 4.0.0) - rails (>= 5.2.0) + rails (>= 6.1.0) GEM remote: https://rubygems.org/ specs: - actioncable (7.0.4.2) - actionpack (= 7.0.4.2) - activesupport (= 7.0.4.2) + actioncable (7.1.1) + actionpack (= 7.1.1) + activesupport (= 7.1.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.4.2) - actionpack (= 7.0.4.2) - activejob (= 7.0.4.2) - activerecord (= 7.0.4.2) - activestorage (= 7.0.4.2) - activesupport (= 7.0.4.2) + zeitwerk (~> 2.6) + actionmailbox (7.1.1) + actionpack (= 7.1.1) + activejob (= 7.1.1) + activerecord (= 7.1.1) + activestorage (= 7.1.1) + activesupport (= 7.1.1) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.4.2) - actionpack (= 7.0.4.2) - actionview (= 7.0.4.2) - activejob (= 7.0.4.2) - activesupport (= 7.0.4.2) + actionmailer (7.1.1) + actionpack (= 7.1.1) + actionview (= 7.1.1) + activejob (= 7.1.1) + activesupport (= 7.1.1) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp - rails-dom-testing (~> 2.0) - actionpack (7.0.4.2) - actionview (= 7.0.4.2) - activesupport (= 7.0.4.2) - rack (~> 2.0, >= 2.2.0) + rails-dom-testing (~> 2.2) + actionpack (7.1.1) + actionview (= 7.1.1) + activesupport (= 7.1.1) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.4.2) - actionpack (= 7.0.4.2) - activerecord (= 7.0.4.2) - activestorage (= 7.0.4.2) - activesupport (= 7.0.4.2) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + actiontext (7.1.1) + actionpack (= 7.1.1) + activerecord (= 7.1.1) + activestorage (= 7.1.1) + activesupport (= 7.1.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.4.2) - activesupport (= 7.0.4.2) + actionview (7.1.1) + activesupport (= 7.1.1) builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (7.0.4.2) - activesupport (= 7.0.4.2) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (7.1.1) + activesupport (= 7.1.1) globalid (>= 0.3.6) - activemodel (7.0.4.2) - activesupport (= 7.0.4.2) - activerecord (7.0.4.2) - activemodel (= 7.0.4.2) - activesupport (= 7.0.4.2) - activestorage (7.0.4.2) - actionpack (= 7.0.4.2) - activejob (= 7.0.4.2) - activerecord (= 7.0.4.2) - activesupport (= 7.0.4.2) + activemodel (7.1.1) + activesupport (= 7.1.1) + activerecord (7.1.1) + activemodel (= 7.1.1) + activesupport (= 7.1.1) + timeout (>= 0.4.0) + activestorage (7.1.1) + actionpack (= 7.1.1) + activejob (= 7.1.1) + activerecord (= 7.1.1) + activesupport (= 7.1.1) marcel (~> 1.0) - mini_mime (>= 1.1.0) - activesupport (7.0.4.2) + activesupport (7.1.1) + base64 + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) minitest (>= 5.1) + mutex_m tzinfo (~> 2.0) - addressable (2.8.1) + addressable (2.8.5) public_suffix (>= 2.0.2, < 6.0) - apnotic (1.7.0) + apnotic (1.7.1) connection_pool (~> 2) net-http2 (>= 0.18.3, < 2) - appraisal (2.4.1) + appraisal (2.5.0) bundler rake thor (>= 0.14.0) ast (2.4.2) + base64 (0.1.1) + bigdecimal (3.1.4) builder (3.2.4) byebug (11.1.3) - concurrent-ruby (1.2.0) - connection_pool (2.3.0) + concurrent-ruby (1.2.2) + connection_pool (2.4.1) crack (0.4.5) rexml crass (1.0.6) date (3.3.3) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) + drb (2.1.1) + ruby2_keywords erubi (1.12.0) - faraday (2.7.4) + faraday (2.7.11) + base64 faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) faraday-net_http (3.0.2) - ffi (1.15.5) - ffi-compiler (1.0.1) - ffi (>= 1.0.0) - rake - globalid (1.1.0) - activesupport (>= 5.0) - googleauth (1.3.0) + globalid (1.2.1) + activesupport (>= 6.1) + googleauth (1.8.1) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) - memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) hashdiff (1.0.1) http-2 (0.11.0) - http (5.1.1) - addressable (~> 2.8) - http-cookie (~> 1.0) - http-form_data (~> 2.2) - llhttp-ffi (~> 0.4.0) - http-cookie (1.0.5) - domain_name (~> 0.5) - http-form_data (2.3.0) - i18n (1.12.0) + i18n (1.14.1) concurrent-ruby (~> 1.0) + io-console (0.6.0) + irb (1.8.3) + rdoc + reline (>= 0.3.8) json (2.6.3) - jwt (2.7.0) + jwt (2.7.1) language_server-protocol (3.17.0.3) - llhttp-ffi (0.4.0) - ffi-compiler (~> 1.0) - rake (~> 13.0) - loofah (2.19.1) + lint_roller (1.1.0) + loofah (2.21.4) crass (~> 1.0.2) - nokogiri (>= 1.5.9) + nokogiri (>= 1.12.0) mail (2.8.1) mini_mime (>= 0.1.1) net-imap net-pop net-smtp marcel (1.0.2) - memoist (0.16.2) - method_source (1.0.0) - mini_mime (1.1.2) - minitest (5.17.0) + mini_mime (1.1.5) + minitest (5.20.0) multi_json (1.15.0) + mutex_m (0.1.2) mysql2 (0.5.5) - net-http2 (0.18.4) + net-http2 (0.18.5) http-2 (~> 0.11) - net-imap (0.3.4) + net-imap (0.4.2) date net-protocol net-pop (0.1.2) net-protocol net-protocol (0.2.1) timeout - net-smtp (0.3.3) + net-smtp (0.4.0) net-protocol - nio4r (2.5.8) - nokogiri (1.14.1-x86_64-darwin) + nio4r (2.5.9) + nokogiri (1.15.4-x86_64-darwin) racc (~> 1.4) - nokogiri (1.14.1-x86_64-linux) + nokogiri (1.15.4-x86_64-linux) racc (~> 1.4) os (1.1.4) - parallel (1.22.1) - parser (3.2.1.0) + parallel (1.23.0) + parser (3.2.2.4) ast (~> 2.4.1) - pg (1.4.5) - public_suffix (5.0.1) - racc (1.6.2) - rack (2.2.6.2) - rack-test (2.0.2) + racc + pg (1.5.4) + psych (5.1.1.1) + stringio + public_suffix (5.0.3) + racc (1.7.1) + rack (3.0.8) + rack-session (2.0.0) + rack (>= 3.0.0) + rack-test (2.1.0) rack (>= 1.3) - rails (7.0.4.2) - actioncable (= 7.0.4.2) - actionmailbox (= 7.0.4.2) - actionmailer (= 7.0.4.2) - actionpack (= 7.0.4.2) - actiontext (= 7.0.4.2) - actionview (= 7.0.4.2) - activejob (= 7.0.4.2) - activemodel (= 7.0.4.2) - activerecord (= 7.0.4.2) - activestorage (= 7.0.4.2) - activesupport (= 7.0.4.2) + rackup (2.1.0) + rack (>= 3) + webrick (~> 1.8) + rails (7.1.1) + actioncable (= 7.1.1) + actionmailbox (= 7.1.1) + actionmailer (= 7.1.1) + actionpack (= 7.1.1) + actiontext (= 7.1.1) + actionview (= 7.1.1) + activejob (= 7.1.1) + activemodel (= 7.1.1) + activerecord (= 7.1.1) + activestorage (= 7.1.1) + activesupport (= 7.1.1) bundler (>= 1.15.0) - railties (= 7.0.4.2) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + railties (= 7.1.1) + rails-dom-testing (2.2.0) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.5.0) - loofah (~> 2.19, >= 2.19.1) - railties (7.0.4.2) - actionpack (= 7.0.4.2) - activesupport (= 7.0.4.2) - method_source + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) + railties (7.1.1) + actionpack (= 7.1.1) + activesupport (= 7.1.1) + irb + rackup (>= 1.0.0) rake (>= 12.2) - thor (~> 1.0) - zeitwerk (~> 2.5) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.0.6) - regexp_parser (2.7.0) - rexml (3.2.5) - rubocop (1.44.1) + rdoc (6.5.0) + psych (>= 4.0.0) + regexp_parser (2.8.2) + reline (0.3.9) + io-console (~> 0.5) + rexml (3.2.6) + rubocop (1.56.4) + base64 (~> 0.1.1) json (~> 2.3) + language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.2.0.0) + parser (>= 3.2.2.3) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.24.1, < 2.0) + rubocop-ast (>= 1.28.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.24.1) - parser (>= 3.1.1.0) - rubocop-performance (1.15.2) + rubocop-ast (1.29.0) + parser (>= 3.2.1.0) + rubocop-performance (1.19.1) rubocop (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) - ruby-progressbar (1.11.0) + ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) - signet (0.17.0) + signet (0.18.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - sqlite3 (1.6.0-x86_64-darwin) - sqlite3 (1.6.0-x86_64-linux) - standard (1.24.2) + sqlite3 (1.6.7-x86_64-darwin) + sqlite3 (1.6.7-x86_64-linux) + standard (1.31.2) language_server-protocol (~> 3.17.0.2) - rubocop (= 1.44.1) - rubocop-performance (= 1.15.2) - thor (1.2.1) - timeout (0.3.1) + lint_roller (~> 1.0) + rubocop (~> 1.56.4) + standard-custom (~> 1.0.0) + standard-performance (~> 1.2) + standard-custom (1.0.2) + lint_roller (~> 1.0) + rubocop (~> 1.50) + standard-performance (1.2.1) + lint_roller (~> 1.1) + rubocop-performance (~> 1.19.1) + stringio (3.0.8) + thor (1.3.0) + timeout (0.4.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) - unicode-display_width (2.4.2) - webmock (3.18.1) + unicode-display_width (2.5.0) + webmock (3.19.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - websocket-driver (0.7.5) + webrick (1.8.1) + websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - zeitwerk (2.6.7) + zeitwerk (2.6.12) PLATFORMS x86_64-darwin-22 @@ -263,4 +285,4 @@ DEPENDENCIES webmock BUNDLED WITH - 2.4.12 + 2.4.21 diff --git a/gemfiles/rails_5_2.gemfile b/gemfiles/rails_5_2.gemfile deleted file mode 100644 index 459eb9a1..00000000 --- a/gemfiles/rails_5_2.gemfile +++ /dev/null @@ -1,17 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "standard" -gem "webmock" -gem "pg" -gem "mysql2" -gem "sqlite3", "~> 1.6.0.rc2" -gem "byebug", group: [:development, :test] -gem "appraisal" -gem "net-smtp" -gem "apnotic", "~> 1.7" -gem "googleauth", "~> 1.1" -gem "rails", "~> 5.2.0" - -gemspec path: "../" diff --git a/gemfiles/rails_5_2.gemfile.lock b/gemfiles/rails_5_2.gemfile.lock deleted file mode 100644 index 4d609e90..00000000 --- a/gemfiles/rails_5_2.gemfile.lock +++ /dev/null @@ -1,256 +0,0 @@ -PATH - remote: .. - specs: - noticed (1.6.3) - http (>= 4.0.0) - rails (>= 5.2.0) - -GEM - remote: https://rubygems.org/ - specs: - actioncable (5.2.8.1) - actionpack (= 5.2.8.1) - nio4r (~> 2.0) - websocket-driver (>= 0.6.1) - actionmailer (5.2.8.1) - actionpack (= 5.2.8.1) - actionview (= 5.2.8.1) - activejob (= 5.2.8.1) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 2.0) - actionpack (5.2.8.1) - actionview (= 5.2.8.1) - activesupport (= 5.2.8.1) - rack (~> 2.0, >= 2.0.8) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.8.1) - activesupport (= 5.2.8.1) - builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.2.8.1) - activesupport (= 5.2.8.1) - globalid (>= 0.3.6) - activemodel (5.2.8.1) - activesupport (= 5.2.8.1) - activerecord (5.2.8.1) - activemodel (= 5.2.8.1) - activesupport (= 5.2.8.1) - arel (>= 9.0) - activestorage (5.2.8.1) - actionpack (= 5.2.8.1) - activerecord (= 5.2.8.1) - marcel (~> 1.0.0) - activesupport (5.2.8.1) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - addressable (2.8.1) - public_suffix (>= 2.0.2, < 6.0) - apnotic (1.7.0) - connection_pool (~> 2) - net-http2 (>= 0.18.3, < 2) - appraisal (2.4.1) - bundler - rake - thor (>= 0.14.0) - arel (9.0.0) - ast (2.4.2) - builder (3.2.4) - byebug (11.1.3) - concurrent-ruby (1.1.10) - connection_pool (2.3.0) - crack (0.4.5) - rexml - crass (1.0.6) - date (3.3.3) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) - erubi (1.12.0) - faraday (2.7.3) - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) - faraday-net_http (3.0.2) - ffi (1.15.5) - ffi-compiler (1.0.1) - ffi (>= 1.0.0) - rake - globalid (1.0.1) - activesupport (>= 5.0) - googleauth (1.3.0) - faraday (>= 0.17.3, < 3.a) - jwt (>= 1.4, < 3.0) - memoist (~> 0.16) - multi_json (~> 1.11) - os (>= 0.9, < 2.0) - signet (>= 0.16, < 2.a) - hashdiff (1.0.1) - http-2 (0.11.0) - http (5.1.1) - addressable (~> 2.8) - http-cookie (~> 1.0) - http-form_data (~> 2.2) - llhttp-ffi (~> 0.4.0) - http-cookie (1.0.5) - domain_name (~> 0.5) - http-form_data (2.3.0) - i18n (1.12.0) - concurrent-ruby (~> 1.0) - json (2.6.3) - jwt (2.6.0) - language_server-protocol (3.17.0.2) - llhttp-ffi (0.4.0) - ffi-compiler (~> 1.0) - rake (~> 13.0) - loofah (2.19.1) - crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.8.0.1) - mini_mime (>= 0.1.1) - net-imap - net-pop - net-smtp - marcel (1.0.2) - memoist (0.16.2) - method_source (1.0.0) - mini_mime (1.1.2) - mini_portile2 (2.8.1) - minitest (5.17.0) - multi_json (1.15.0) - mysql2 (0.5.4) - net-http2 (0.18.4) - http-2 (~> 0.11) - net-imap (0.3.4) - date - net-protocol - net-pop (0.1.2) - net-protocol - net-protocol (0.2.1) - timeout - net-smtp (0.3.3) - net-protocol - nio4r (2.5.8) - nokogiri (1.14.0) - mini_portile2 (~> 2.8.0) - racc (~> 1.4) - nokogiri (1.14.0-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.14.0-x86_64-linux) - racc (~> 1.4) - os (1.1.4) - parallel (1.22.1) - parser (3.2.0.0) - ast (~> 2.4.1) - pg (1.4.5) - public_suffix (5.0.1) - racc (1.6.2) - rack (2.2.6.2) - rack-test (2.0.2) - rack (>= 1.3) - rails (5.2.8.1) - actioncable (= 5.2.8.1) - actionmailer (= 5.2.8.1) - actionpack (= 5.2.8.1) - actionview (= 5.2.8.1) - activejob (= 5.2.8.1) - activemodel (= 5.2.8.1) - activerecord (= 5.2.8.1) - activestorage (= 5.2.8.1) - activesupport (= 5.2.8.1) - bundler (>= 1.3.0) - railties (= 5.2.8.1) - sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) - nokogiri (>= 1.6) - rails-html-sanitizer (1.4.4) - loofah (~> 2.19, >= 2.19.1) - railties (5.2.8.1) - actionpack (= 5.2.8.1) - activesupport (= 5.2.8.1) - method_source - rake (>= 0.8.7) - thor (>= 0.19.0, < 2.0) - rainbow (3.1.1) - rake (13.0.6) - regexp_parser (2.6.1) - rexml (3.2.5) - rubocop (1.42.0) - json (~> 2.3) - parallel (~> 1.10) - parser (>= 3.1.2.1) - rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.24.1, < 2.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.24.1) - parser (>= 3.1.1.0) - rubocop-performance (1.15.2) - rubocop (>= 1.7.0, < 2.0) - rubocop-ast (>= 0.4.0) - ruby-progressbar (1.11.0) - ruby2_keywords (0.0.5) - signet (0.17.0) - addressable (~> 2.8) - faraday (>= 0.17.5, < 3.a) - jwt (>= 1.5, < 3.0) - multi_json (~> 1.10) - sprockets (4.2.0) - concurrent-ruby (~> 1.0) - rack (>= 2.2.4, < 4) - sprockets-rails (3.4.2) - actionpack (>= 5.2) - activesupport (>= 5.2) - sprockets (>= 3.0.0) - sqlite3 (1.6.0) - mini_portile2 (~> 2.8.0) - sqlite3 (1.6.0-x86_64-darwin) - sqlite3 (1.6.0-x86_64-linux) - standard (1.22.0) - language_server-protocol (~> 3.17.0.2) - rubocop (= 1.42.0) - rubocop-performance (= 1.15.2) - thor (1.2.1) - thread_safe (0.3.6) - timeout (0.3.1) - tzinfo (1.2.10) - thread_safe (~> 0.1) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) - unicode-display_width (2.4.2) - webmock (3.18.1) - addressable (>= 2.8.0) - crack (>= 0.3.2) - hashdiff (>= 0.4.0, < 2.0.0) - websocket-driver (0.7.5) - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.5) - -PLATFORMS - ruby - x86_64-darwin-22 - x86_64-linux - -DEPENDENCIES - apnotic (~> 1.7) - appraisal - byebug - googleauth (~> 1.1) - mysql2 - net-smtp - noticed! - pg - rails (~> 5.2.0) - sqlite3 (~> 1.6.0.rc2) - standard - webmock - -BUNDLED WITH - 2.4.1 diff --git a/gemfiles/rails_6.gemfile b/gemfiles/rails_6.gemfile deleted file mode 100644 index af45cbfc..00000000 --- a/gemfiles/rails_6.gemfile +++ /dev/null @@ -1,17 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "standard" -gem "webmock" -gem "pg" -gem "mysql2" -gem "sqlite3", "~> 1.6.0.rc2" -gem "byebug", group: [:development, :test] -gem "appraisal" -gem "net-smtp" -gem "apnotic", "~> 1.7" -gem "googleauth", "~> 1.1" -gem "rails", "~> 6.0.0" - -gemspec path: "../" diff --git a/gemfiles/rails_6.gemfile.lock b/gemfiles/rails_6.gemfile.lock deleted file mode 100644 index 1fc00a57..00000000 --- a/gemfiles/rails_6.gemfile.lock +++ /dev/null @@ -1,272 +0,0 @@ -PATH - remote: .. - specs: - noticed (1.6.3) - http (>= 4.0.0) - rails (>= 5.2.0) - -GEM - remote: https://rubygems.org/ - specs: - actioncable (6.0.6.1) - actionpack (= 6.0.6.1) - nio4r (~> 2.0) - websocket-driver (>= 0.6.1) - actionmailbox (6.0.6.1) - actionpack (= 6.0.6.1) - activejob (= 6.0.6.1) - activerecord (= 6.0.6.1) - activestorage (= 6.0.6.1) - activesupport (= 6.0.6.1) - mail (>= 2.7.1) - actionmailer (6.0.6.1) - actionpack (= 6.0.6.1) - actionview (= 6.0.6.1) - activejob (= 6.0.6.1) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 2.0) - actionpack (6.0.6.1) - actionview (= 6.0.6.1) - activesupport (= 6.0.6.1) - rack (~> 2.0, >= 2.0.8) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.0.6.1) - actionpack (= 6.0.6.1) - activerecord (= 6.0.6.1) - activestorage (= 6.0.6.1) - activesupport (= 6.0.6.1) - nokogiri (>= 1.8.5) - actionview (6.0.6.1) - activesupport (= 6.0.6.1) - builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.0.6.1) - activesupport (= 6.0.6.1) - globalid (>= 0.3.6) - activemodel (6.0.6.1) - activesupport (= 6.0.6.1) - activerecord (6.0.6.1) - activemodel (= 6.0.6.1) - activesupport (= 6.0.6.1) - activestorage (6.0.6.1) - actionpack (= 6.0.6.1) - activejob (= 6.0.6.1) - activerecord (= 6.0.6.1) - marcel (~> 1.0) - activesupport (6.0.6.1) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - addressable (2.8.1) - public_suffix (>= 2.0.2, < 6.0) - apnotic (1.7.0) - connection_pool (~> 2) - net-http2 (>= 0.18.3, < 2) - appraisal (2.4.1) - bundler - rake - thor (>= 0.14.0) - ast (2.4.2) - builder (3.2.4) - byebug (11.1.3) - concurrent-ruby (1.1.10) - connection_pool (2.3.0) - crack (0.4.5) - rexml - crass (1.0.6) - date (3.3.3) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) - erubi (1.12.0) - faraday (2.7.3) - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) - faraday-net_http (3.0.2) - ffi (1.15.5) - ffi-compiler (1.0.1) - ffi (>= 1.0.0) - rake - globalid (1.0.1) - activesupport (>= 5.0) - googleauth (1.3.0) - faraday (>= 0.17.3, < 3.a) - jwt (>= 1.4, < 3.0) - memoist (~> 0.16) - multi_json (~> 1.11) - os (>= 0.9, < 2.0) - signet (>= 0.16, < 2.a) - hashdiff (1.0.1) - http-2 (0.11.0) - http (5.1.1) - addressable (~> 2.8) - http-cookie (~> 1.0) - http-form_data (~> 2.2) - llhttp-ffi (~> 0.4.0) - http-cookie (1.0.5) - domain_name (~> 0.5) - http-form_data (2.3.0) - i18n (1.12.0) - concurrent-ruby (~> 1.0) - json (2.6.3) - jwt (2.6.0) - language_server-protocol (3.17.0.2) - llhttp-ffi (0.4.0) - ffi-compiler (~> 1.0) - rake (~> 13.0) - loofah (2.19.1) - crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.8.0.1) - mini_mime (>= 0.1.1) - net-imap - net-pop - net-smtp - marcel (1.0.2) - memoist (0.16.2) - method_source (1.0.0) - mini_mime (1.1.2) - mini_portile2 (2.8.1) - minitest (5.17.0) - multi_json (1.15.0) - mysql2 (0.5.4) - net-http2 (0.18.4) - http-2 (~> 0.11) - net-imap (0.3.4) - date - net-protocol - net-pop (0.1.2) - net-protocol - net-protocol (0.2.1) - timeout - net-smtp (0.3.3) - net-protocol - nio4r (2.5.8) - nokogiri (1.14.0) - mini_portile2 (~> 2.8.0) - racc (~> 1.4) - nokogiri (1.14.0-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.14.0-x86_64-linux) - racc (~> 1.4) - os (1.1.4) - parallel (1.22.1) - parser (3.2.0.0) - ast (~> 2.4.1) - pg (1.4.5) - public_suffix (5.0.1) - racc (1.6.2) - rack (2.2.6.2) - rack-test (2.0.2) - rack (>= 1.3) - rails (6.0.6.1) - actioncable (= 6.0.6.1) - actionmailbox (= 6.0.6.1) - actionmailer (= 6.0.6.1) - actionpack (= 6.0.6.1) - actiontext (= 6.0.6.1) - actionview (= 6.0.6.1) - activejob (= 6.0.6.1) - activemodel (= 6.0.6.1) - activerecord (= 6.0.6.1) - activestorage (= 6.0.6.1) - activesupport (= 6.0.6.1) - bundler (>= 1.3.0) - railties (= 6.0.6.1) - sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) - nokogiri (>= 1.6) - rails-html-sanitizer (1.4.4) - loofah (~> 2.19, >= 2.19.1) - railties (6.0.6.1) - actionpack (= 6.0.6.1) - activesupport (= 6.0.6.1) - method_source - rake (>= 0.8.7) - thor (>= 0.20.3, < 2.0) - rainbow (3.1.1) - rake (13.0.6) - regexp_parser (2.6.1) - rexml (3.2.5) - rubocop (1.42.0) - json (~> 2.3) - parallel (~> 1.10) - parser (>= 3.1.2.1) - rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.24.1, < 2.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.24.1) - parser (>= 3.1.1.0) - rubocop-performance (1.15.2) - rubocop (>= 1.7.0, < 2.0) - rubocop-ast (>= 0.4.0) - ruby-progressbar (1.11.0) - ruby2_keywords (0.0.5) - signet (0.17.0) - addressable (~> 2.8) - faraday (>= 0.17.5, < 3.a) - jwt (>= 1.5, < 3.0) - multi_json (~> 1.10) - sprockets (4.2.0) - concurrent-ruby (~> 1.0) - rack (>= 2.2.4, < 4) - sprockets-rails (3.4.2) - actionpack (>= 5.2) - activesupport (>= 5.2) - sprockets (>= 3.0.0) - sqlite3 (1.6.0) - mini_portile2 (~> 2.8.0) - sqlite3 (1.6.0-x86_64-darwin) - sqlite3 (1.6.0-x86_64-linux) - standard (1.22.0) - language_server-protocol (~> 3.17.0.2) - rubocop (= 1.42.0) - rubocop-performance (= 1.15.2) - thor (1.2.1) - thread_safe (0.3.6) - timeout (0.3.1) - tzinfo (1.2.10) - thread_safe (~> 0.1) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) - unicode-display_width (2.4.2) - webmock (3.18.1) - addressable (>= 2.8.0) - crack (>= 0.3.2) - hashdiff (>= 0.4.0, < 2.0.0) - websocket-driver (0.7.5) - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.5) - zeitwerk (2.6.6) - -PLATFORMS - ruby - x86_64-darwin-22 - x86_64-linux - -DEPENDENCIES - apnotic (~> 1.7) - appraisal - byebug - googleauth (~> 1.1) - mysql2 - net-smtp - noticed! - pg - rails (~> 6.0.0) - sqlite3 (~> 1.6.0.rc2) - standard - webmock - -BUNDLED WITH - 2.4.1 diff --git a/gemfiles/rails_6_1.gemfile.lock b/gemfiles/rails_6_1.gemfile.lock index 841666fe..0a2c1d19 100644 --- a/gemfiles/rails_6_1.gemfile.lock +++ b/gemfiles/rails_6_1.gemfile.lock @@ -2,255 +2,248 @@ PATH remote: .. specs: noticed (1.6.3) - http (>= 4.0.0) - rails (>= 5.2.0) + rails (>= 6.1.0) GEM remote: https://rubygems.org/ specs: - actioncable (6.1.7.1) - actionpack (= 6.1.7.1) - activesupport (= 6.1.7.1) + actioncable (6.1.7.6) + actionpack (= 6.1.7.6) + activesupport (= 6.1.7.6) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.7.1) - actionpack (= 6.1.7.1) - activejob (= 6.1.7.1) - activerecord (= 6.1.7.1) - activestorage (= 6.1.7.1) - activesupport (= 6.1.7.1) + actionmailbox (6.1.7.6) + actionpack (= 6.1.7.6) + activejob (= 6.1.7.6) + activerecord (= 6.1.7.6) + activestorage (= 6.1.7.6) + activesupport (= 6.1.7.6) mail (>= 2.7.1) - actionmailer (6.1.7.1) - actionpack (= 6.1.7.1) - actionview (= 6.1.7.1) - activejob (= 6.1.7.1) - activesupport (= 6.1.7.1) + actionmailer (6.1.7.6) + actionpack (= 6.1.7.6) + actionview (= 6.1.7.6) + activejob (= 6.1.7.6) + activesupport (= 6.1.7.6) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.7.1) - actionview (= 6.1.7.1) - activesupport (= 6.1.7.1) + actionpack (6.1.7.6) + actionview (= 6.1.7.6) + activesupport (= 6.1.7.6) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.7.1) - actionpack (= 6.1.7.1) - activerecord (= 6.1.7.1) - activestorage (= 6.1.7.1) - activesupport (= 6.1.7.1) + actiontext (6.1.7.6) + actionpack (= 6.1.7.6) + activerecord (= 6.1.7.6) + activestorage (= 6.1.7.6) + activesupport (= 6.1.7.6) nokogiri (>= 1.8.5) - actionview (6.1.7.1) - activesupport (= 6.1.7.1) + actionview (6.1.7.6) + activesupport (= 6.1.7.6) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.1.7.1) - activesupport (= 6.1.7.1) + activejob (6.1.7.6) + activesupport (= 6.1.7.6) globalid (>= 0.3.6) - activemodel (6.1.7.1) - activesupport (= 6.1.7.1) - activerecord (6.1.7.1) - activemodel (= 6.1.7.1) - activesupport (= 6.1.7.1) - activestorage (6.1.7.1) - actionpack (= 6.1.7.1) - activejob (= 6.1.7.1) - activerecord (= 6.1.7.1) - activesupport (= 6.1.7.1) + activemodel (6.1.7.6) + activesupport (= 6.1.7.6) + activerecord (6.1.7.6) + activemodel (= 6.1.7.6) + activesupport (= 6.1.7.6) + activestorage (6.1.7.6) + actionpack (= 6.1.7.6) + activejob (= 6.1.7.6) + activerecord (= 6.1.7.6) + activesupport (= 6.1.7.6) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.7.1) + activesupport (6.1.7.6) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) - addressable (2.8.1) + addressable (2.8.5) public_suffix (>= 2.0.2, < 6.0) - apnotic (1.7.0) + apnotic (1.7.1) connection_pool (~> 2) net-http2 (>= 0.18.3, < 2) - appraisal (2.4.1) + appraisal (2.5.0) bundler rake thor (>= 0.14.0) ast (2.4.2) + base64 (0.1.1) builder (3.2.4) byebug (11.1.3) - concurrent-ruby (1.1.10) - connection_pool (2.3.0) + concurrent-ruby (1.2.2) + connection_pool (2.4.1) crack (0.4.5) rexml crass (1.0.6) date (3.3.3) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) erubi (1.12.0) - faraday (2.7.3) + faraday (2.7.11) + base64 faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) faraday-net_http (3.0.2) - ffi (1.15.5) - ffi-compiler (1.0.1) - ffi (>= 1.0.0) - rake - globalid (1.0.1) - activesupport (>= 5.0) - googleauth (1.3.0) + globalid (1.2.1) + activesupport (>= 6.1) + googleauth (1.8.1) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) - memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) hashdiff (1.0.1) http-2 (0.11.0) - http (5.1.1) - addressable (~> 2.8) - http-cookie (~> 1.0) - http-form_data (~> 2.2) - llhttp-ffi (~> 0.4.0) - http-cookie (1.0.5) - domain_name (~> 0.5) - http-form_data (2.3.0) - i18n (1.12.0) + i18n (1.14.1) concurrent-ruby (~> 1.0) json (2.6.3) - jwt (2.6.0) - language_server-protocol (3.17.0.2) - llhttp-ffi (0.4.0) - ffi-compiler (~> 1.0) - rake (~> 13.0) - loofah (2.19.1) + jwt (2.7.1) + language_server-protocol (3.17.0.3) + lint_roller (1.1.0) + loofah (2.21.4) crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.8.0.1) + nokogiri (>= 1.12.0) + mail (2.8.1) mini_mime (>= 0.1.1) net-imap net-pop net-smtp marcel (1.0.2) - memoist (0.16.2) method_source (1.0.0) - mini_mime (1.1.2) - mini_portile2 (2.8.1) - minitest (5.17.0) + mini_mime (1.1.5) + mini_portile2 (2.8.4) + minitest (5.20.0) multi_json (1.15.0) - mysql2 (0.5.4) - net-http2 (0.18.4) + mysql2 (0.5.5) + net-http2 (0.18.5) http-2 (~> 0.11) - net-imap (0.3.4) + net-imap (0.4.2) date net-protocol net-pop (0.1.2) net-protocol net-protocol (0.2.1) timeout - net-smtp (0.3.3) + net-smtp (0.4.0) net-protocol - nio4r (2.5.8) - nokogiri (1.14.0) - mini_portile2 (~> 2.8.0) + nio4r (2.5.9) + nokogiri (1.15.4) + mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.14.0-x86_64-darwin) + nokogiri (1.15.4-x86_64-darwin) racc (~> 1.4) - nokogiri (1.14.0-x86_64-linux) + nokogiri (1.15.4-x86_64-linux) racc (~> 1.4) os (1.1.4) - parallel (1.22.1) - parser (3.2.0.0) + parallel (1.23.0) + parser (3.2.2.4) ast (~> 2.4.1) - pg (1.4.5) - public_suffix (5.0.1) - racc (1.6.2) - rack (2.2.6.2) - rack-test (2.0.2) + racc + pg (1.5.4) + public_suffix (5.0.3) + racc (1.7.1) + rack (2.2.8) + rack-test (2.1.0) rack (>= 1.3) - rails (6.1.7.1) - actioncable (= 6.1.7.1) - actionmailbox (= 6.1.7.1) - actionmailer (= 6.1.7.1) - actionpack (= 6.1.7.1) - actiontext (= 6.1.7.1) - actionview (= 6.1.7.1) - activejob (= 6.1.7.1) - activemodel (= 6.1.7.1) - activerecord (= 6.1.7.1) - activestorage (= 6.1.7.1) - activesupport (= 6.1.7.1) + rails (6.1.7.6) + actioncable (= 6.1.7.6) + actionmailbox (= 6.1.7.6) + actionmailer (= 6.1.7.6) + actionpack (= 6.1.7.6) + actiontext (= 6.1.7.6) + actionview (= 6.1.7.6) + activejob (= 6.1.7.6) + activemodel (= 6.1.7.6) + activerecord (= 6.1.7.6) + activestorage (= 6.1.7.6) + activesupport (= 6.1.7.6) bundler (>= 1.15.0) - railties (= 6.1.7.1) + railties (= 6.1.7.6) sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + rails-dom-testing (2.2.0) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.4.4) - loofah (~> 2.19, >= 2.19.1) - railties (6.1.7.1) - actionpack (= 6.1.7.1) - activesupport (= 6.1.7.1) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) + railties (6.1.7.6) + actionpack (= 6.1.7.6) + activesupport (= 6.1.7.6) method_source rake (>= 12.2) thor (~> 1.0) rainbow (3.1.1) rake (13.0.6) - regexp_parser (2.6.1) - rexml (3.2.5) - rubocop (1.42.0) + regexp_parser (2.8.2) + rexml (3.2.6) + rubocop (1.56.4) + base64 (~> 0.1.1) json (~> 2.3) + language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.1.2.1) + parser (>= 3.2.2.3) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.24.1, < 2.0) + rubocop-ast (>= 1.28.1, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.24.1) - parser (>= 3.1.1.0) - rubocop-performance (1.15.2) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.29.0) + parser (>= 3.2.1.0) + rubocop-performance (1.19.1) rubocop (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) - ruby-progressbar (1.11.0) + ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) - signet (0.17.0) + signet (0.18.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - sprockets (4.2.0) + sprockets (4.2.1) concurrent-ruby (~> 1.0) rack (>= 2.2.4, < 4) sprockets-rails (3.4.2) actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - sqlite3 (1.6.0) + sqlite3 (1.6.7) mini_portile2 (~> 2.8.0) - sqlite3 (1.6.0-x86_64-darwin) - sqlite3 (1.6.0-x86_64-linux) - standard (1.22.0) + sqlite3 (1.6.7-x86_64-darwin) + sqlite3 (1.6.7-x86_64-linux) + standard (1.31.2) language_server-protocol (~> 3.17.0.2) - rubocop (= 1.42.0) - rubocop-performance (= 1.15.2) - thor (1.2.1) - timeout (0.3.1) - tzinfo (2.0.5) + lint_roller (~> 1.0) + rubocop (~> 1.56.4) + standard-custom (~> 1.0.0) + standard-performance (~> 1.2) + standard-custom (1.0.2) + lint_roller (~> 1.0) + rubocop (~> 1.50) + standard-performance (1.2.1) + lint_roller (~> 1.1) + rubocop-performance (~> 1.19.1) + thor (1.3.0) + timeout (0.4.0) + tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) - unicode-display_width (2.4.2) - webmock (3.18.1) + unicode-display_width (2.5.0) + webmock (3.19.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - websocket-driver (0.7.5) + websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - zeitwerk (2.6.6) + zeitwerk (2.6.12) PLATFORMS ruby diff --git a/gemfiles/rails_7.gemfile.lock b/gemfiles/rails_7.gemfile.lock index 64eef686..9e96ce8f 100644 --- a/gemfiles/rails_7.gemfile.lock +++ b/gemfiles/rails_7.gemfile.lock @@ -2,251 +2,244 @@ PATH remote: .. specs: noticed (1.6.3) - http (>= 4.0.0) - rails (>= 5.2.0) + rails (>= 6.1.0) GEM remote: https://rubygems.org/ specs: - actioncable (7.0.4.1) - actionpack (= 7.0.4.1) - activesupport (= 7.0.4.1) + actioncable (7.0.8) + actionpack (= 7.0.8) + activesupport (= 7.0.8) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.4.1) - actionpack (= 7.0.4.1) - activejob (= 7.0.4.1) - activerecord (= 7.0.4.1) - activestorage (= 7.0.4.1) - activesupport (= 7.0.4.1) + actionmailbox (7.0.8) + actionpack (= 7.0.8) + activejob (= 7.0.8) + activerecord (= 7.0.8) + activestorage (= 7.0.8) + activesupport (= 7.0.8) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.4.1) - actionpack (= 7.0.4.1) - actionview (= 7.0.4.1) - activejob (= 7.0.4.1) - activesupport (= 7.0.4.1) + actionmailer (7.0.8) + actionpack (= 7.0.8) + actionview (= 7.0.8) + activejob (= 7.0.8) + activesupport (= 7.0.8) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.0) - actionpack (7.0.4.1) - actionview (= 7.0.4.1) - activesupport (= 7.0.4.1) - rack (~> 2.0, >= 2.2.0) + actionpack (7.0.8) + actionview (= 7.0.8) + activesupport (= 7.0.8) + rack (~> 2.0, >= 2.2.4) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.4.1) - actionpack (= 7.0.4.1) - activerecord (= 7.0.4.1) - activestorage (= 7.0.4.1) - activesupport (= 7.0.4.1) + actiontext (7.0.8) + actionpack (= 7.0.8) + activerecord (= 7.0.8) + activestorage (= 7.0.8) + activesupport (= 7.0.8) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.4.1) - activesupport (= 7.0.4.1) + actionview (7.0.8) + activesupport (= 7.0.8) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (7.0.4.1) - activesupport (= 7.0.4.1) + activejob (7.0.8) + activesupport (= 7.0.8) globalid (>= 0.3.6) - activemodel (7.0.4.1) - activesupport (= 7.0.4.1) - activerecord (7.0.4.1) - activemodel (= 7.0.4.1) - activesupport (= 7.0.4.1) - activestorage (7.0.4.1) - actionpack (= 7.0.4.1) - activejob (= 7.0.4.1) - activerecord (= 7.0.4.1) - activesupport (= 7.0.4.1) + activemodel (7.0.8) + activesupport (= 7.0.8) + activerecord (7.0.8) + activemodel (= 7.0.8) + activesupport (= 7.0.8) + activestorage (7.0.8) + actionpack (= 7.0.8) + activejob (= 7.0.8) + activerecord (= 7.0.8) + activesupport (= 7.0.8) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (7.0.4.1) + activesupport (7.0.8) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.1) + addressable (2.8.5) public_suffix (>= 2.0.2, < 6.0) - apnotic (1.7.0) + apnotic (1.7.1) connection_pool (~> 2) net-http2 (>= 0.18.3, < 2) - appraisal (2.4.1) + appraisal (2.5.0) bundler rake thor (>= 0.14.0) ast (2.4.2) + base64 (0.1.1) builder (3.2.4) byebug (11.1.3) - concurrent-ruby (1.1.10) - connection_pool (2.3.0) + concurrent-ruby (1.2.2) + connection_pool (2.4.1) crack (0.4.5) rexml crass (1.0.6) date (3.3.3) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) erubi (1.12.0) - faraday (2.7.3) + faraday (2.7.11) + base64 faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) faraday-net_http (3.0.2) - ffi (1.15.5) - ffi-compiler (1.0.1) - ffi (>= 1.0.0) - rake - globalid (1.0.1) - activesupport (>= 5.0) - googleauth (1.3.0) + globalid (1.2.1) + activesupport (>= 6.1) + googleauth (1.8.1) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) - memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) hashdiff (1.0.1) http-2 (0.11.0) - http (5.1.1) - addressable (~> 2.8) - http-cookie (~> 1.0) - http-form_data (~> 2.2) - llhttp-ffi (~> 0.4.0) - http-cookie (1.0.5) - domain_name (~> 0.5) - http-form_data (2.3.0) - i18n (1.12.0) + i18n (1.14.1) concurrent-ruby (~> 1.0) json (2.6.3) - jwt (2.6.0) - language_server-protocol (3.17.0.2) - llhttp-ffi (0.4.0) - ffi-compiler (~> 1.0) - rake (~> 13.0) - loofah (2.19.1) + jwt (2.7.1) + language_server-protocol (3.17.0.3) + lint_roller (1.1.0) + loofah (2.21.4) crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.8.0.1) + nokogiri (>= 1.12.0) + mail (2.8.1) mini_mime (>= 0.1.1) net-imap net-pop net-smtp marcel (1.0.2) - memoist (0.16.2) method_source (1.0.0) - mini_mime (1.1.2) - minitest (5.17.0) + mini_mime (1.1.5) + minitest (5.20.0) multi_json (1.15.0) - mysql2 (0.5.4) - net-http2 (0.18.4) + mysql2 (0.5.5) + net-http2 (0.18.5) http-2 (~> 0.11) - net-imap (0.3.4) + net-imap (0.4.2) date net-protocol net-pop (0.1.2) net-protocol net-protocol (0.2.1) timeout - net-smtp (0.3.3) + net-smtp (0.4.0) net-protocol - nio4r (2.5.8) - nokogiri (1.14.0-arm64-darwin) + nio4r (2.5.9) + nokogiri (1.15.4-arm64-darwin) racc (~> 1.4) - nokogiri (1.14.0-x86_64-darwin) + nokogiri (1.15.4-x86_64-darwin) racc (~> 1.4) - nokogiri (1.14.0-x86_64-linux) + nokogiri (1.15.4-x86_64-linux) racc (~> 1.4) os (1.1.4) - parallel (1.22.1) - parser (3.2.0.0) + parallel (1.23.0) + parser (3.2.2.4) ast (~> 2.4.1) - pg (1.4.5) - public_suffix (5.0.1) - racc (1.6.2) - rack (2.2.6.2) - rack-test (2.0.2) + racc + pg (1.5.4) + public_suffix (5.0.3) + racc (1.7.1) + rack (2.2.8) + rack-test (2.1.0) rack (>= 1.3) - rails (7.0.4.1) - actioncable (= 7.0.4.1) - actionmailbox (= 7.0.4.1) - actionmailer (= 7.0.4.1) - actionpack (= 7.0.4.1) - actiontext (= 7.0.4.1) - actionview (= 7.0.4.1) - activejob (= 7.0.4.1) - activemodel (= 7.0.4.1) - activerecord (= 7.0.4.1) - activestorage (= 7.0.4.1) - activesupport (= 7.0.4.1) + rails (7.0.8) + actioncable (= 7.0.8) + actionmailbox (= 7.0.8) + actionmailer (= 7.0.8) + actionpack (= 7.0.8) + actiontext (= 7.0.8) + actionview (= 7.0.8) + activejob (= 7.0.8) + activemodel (= 7.0.8) + activerecord (= 7.0.8) + activestorage (= 7.0.8) + activesupport (= 7.0.8) bundler (>= 1.15.0) - railties (= 7.0.4.1) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + railties (= 7.0.8) + rails-dom-testing (2.2.0) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.4.4) - loofah (~> 2.19, >= 2.19.1) - railties (7.0.4.1) - actionpack (= 7.0.4.1) - activesupport (= 7.0.4.1) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) + railties (7.0.8) + actionpack (= 7.0.8) + activesupport (= 7.0.8) method_source rake (>= 12.2) thor (~> 1.0) zeitwerk (~> 2.5) rainbow (3.1.1) rake (13.0.6) - regexp_parser (2.6.1) - rexml (3.2.5) - rubocop (1.42.0) + regexp_parser (2.8.2) + rexml (3.2.6) + rubocop (1.56.4) + base64 (~> 0.1.1) json (~> 2.3) + language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.1.2.1) + parser (>= 3.2.2.3) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.24.1, < 2.0) + rubocop-ast (>= 1.28.1, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.24.1) - parser (>= 3.1.1.0) - rubocop-performance (1.15.2) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.29.0) + parser (>= 3.2.1.0) + rubocop-performance (1.19.1) rubocop (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) - ruby-progressbar (1.11.0) + ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) - signet (0.17.0) + signet (0.18.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - sqlite3 (1.6.0-arm64-darwin) - sqlite3 (1.6.0-x86_64-darwin) - sqlite3 (1.6.0-x86_64-linux) - standard (1.22.0) + sqlite3 (1.6.7-arm64-darwin) + sqlite3 (1.6.7-x86_64-darwin) + sqlite3 (1.6.7-x86_64-linux) + standard (1.31.2) language_server-protocol (~> 3.17.0.2) - rubocop (= 1.42.0) - rubocop-performance (= 1.15.2) - thor (1.2.1) - timeout (0.3.1) - tzinfo (2.0.5) + lint_roller (~> 1.0) + rubocop (~> 1.56.4) + standard-custom (~> 1.0.0) + standard-performance (~> 1.2) + standard-custom (1.0.2) + lint_roller (~> 1.0) + rubocop (~> 1.50) + standard-performance (1.2.1) + lint_roller (~> 1.1) + rubocop-performance (~> 1.19.1) + thor (1.3.0) + timeout (0.4.0) + tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) - unicode-display_width (2.4.2) - webmock (3.18.1) + unicode-display_width (2.5.0) + webmock (3.19.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - websocket-driver (0.7.5) + websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - zeitwerk (2.6.6) + zeitwerk (2.6.12) PLATFORMS arm64-darwin-21 diff --git a/gemfiles/rails_main.gemfile.lock b/gemfiles/rails_main.gemfile.lock index 17594dbb..25845b87 100644 --- a/gemfiles/rails_main.gemfile.lock +++ b/gemfiles/rails_main.gemfile.lock @@ -1,207 +1,201 @@ GIT remote: https://github.com/rails/rails.git - revision: cc359c077fadc619dd4d098a3639b6eab22d6a06 + revision: 4f42b313a119e8d71e4297e6868d10c90ae9d8fd branch: main specs: - actioncable (7.1.0.alpha) - actionpack (= 7.1.0.alpha) - activesupport (= 7.1.0.alpha) + actioncable (7.2.0.alpha) + actionpack (= 7.2.0.alpha) + activesupport (= 7.2.0.alpha) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.1.0.alpha) - actionpack (= 7.1.0.alpha) - activejob (= 7.1.0.alpha) - activerecord (= 7.1.0.alpha) - activestorage (= 7.1.0.alpha) - activesupport (= 7.1.0.alpha) + actionmailbox (7.2.0.alpha) + actionpack (= 7.2.0.alpha) + activejob (= 7.2.0.alpha) + activerecord (= 7.2.0.alpha) + activestorage (= 7.2.0.alpha) + activesupport (= 7.2.0.alpha) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.1.0.alpha) - actionpack (= 7.1.0.alpha) - actionview (= 7.1.0.alpha) - activejob (= 7.1.0.alpha) - activesupport (= 7.1.0.alpha) + actionmailer (7.2.0.alpha) + actionpack (= 7.2.0.alpha) + actionview (= 7.2.0.alpha) + activejob (= 7.2.0.alpha) + activesupport (= 7.2.0.alpha) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp - rails-dom-testing (~> 2.0) - actionpack (7.1.0.alpha) - actionview (= 7.1.0.alpha) - activesupport (= 7.1.0.alpha) + rails-dom-testing (~> 2.2) + actionpack (7.2.0.alpha) + actionview (= 7.2.0.alpha) + activesupport (= 7.2.0.alpha) nokogiri (>= 1.8.5) + racc rack (>= 2.2.4) rack-session (>= 1.0.1) rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.1.0.alpha) - actionpack (= 7.1.0.alpha) - activerecord (= 7.1.0.alpha) - activestorage (= 7.1.0.alpha) - activesupport (= 7.1.0.alpha) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + actiontext (7.2.0.alpha) + actionpack (= 7.2.0.alpha) + activerecord (= 7.2.0.alpha) + activestorage (= 7.2.0.alpha) + activesupport (= 7.2.0.alpha) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.1.0.alpha) - activesupport (= 7.1.0.alpha) + actionview (7.2.0.alpha) + activesupport (= 7.2.0.alpha) builder (~> 3.1) erubi (~> 1.11) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (7.1.0.alpha) - activesupport (= 7.1.0.alpha) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (7.2.0.alpha) + activesupport (= 7.2.0.alpha) globalid (>= 0.3.6) - activemodel (7.1.0.alpha) - activesupport (= 7.1.0.alpha) - activerecord (7.1.0.alpha) - activemodel (= 7.1.0.alpha) - activesupport (= 7.1.0.alpha) - activestorage (7.1.0.alpha) - actionpack (= 7.1.0.alpha) - activejob (= 7.1.0.alpha) - activerecord (= 7.1.0.alpha) - activesupport (= 7.1.0.alpha) + activemodel (7.2.0.alpha) + activesupport (= 7.2.0.alpha) + activerecord (7.2.0.alpha) + activemodel (= 7.2.0.alpha) + activesupport (= 7.2.0.alpha) + timeout (>= 0.4.0) + activestorage (7.2.0.alpha) + actionpack (= 7.2.0.alpha) + activejob (= 7.2.0.alpha) + activerecord (= 7.2.0.alpha) + activesupport (= 7.2.0.alpha) marcel (~> 1.0) - activesupport (7.1.0.alpha) + activesupport (7.2.0.alpha) + base64 + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - rails (7.1.0.alpha) - actioncable (= 7.1.0.alpha) - actionmailbox (= 7.1.0.alpha) - actionmailer (= 7.1.0.alpha) - actionpack (= 7.1.0.alpha) - actiontext (= 7.1.0.alpha) - actionview (= 7.1.0.alpha) - activejob (= 7.1.0.alpha) - activemodel (= 7.1.0.alpha) - activerecord (= 7.1.0.alpha) - activestorage (= 7.1.0.alpha) - activesupport (= 7.1.0.alpha) + rails (7.2.0.alpha) + actioncable (= 7.2.0.alpha) + actionmailbox (= 7.2.0.alpha) + actionmailer (= 7.2.0.alpha) + actionpack (= 7.2.0.alpha) + actiontext (= 7.2.0.alpha) + actionview (= 7.2.0.alpha) + activejob (= 7.2.0.alpha) + activemodel (= 7.2.0.alpha) + activerecord (= 7.2.0.alpha) + activestorage (= 7.2.0.alpha) + activesupport (= 7.2.0.alpha) bundler (>= 1.15.0) - railties (= 7.1.0.alpha) - railties (7.1.0.alpha) - actionpack (= 7.1.0.alpha) - activesupport (= 7.1.0.alpha) + railties (= 7.2.0.alpha) + railties (7.2.0.alpha) + actionpack (= 7.2.0.alpha) + activesupport (= 7.2.0.alpha) irb rackup (>= 1.0.0) rake (>= 12.2) - thor (~> 1.0) + thor (~> 1.0, >= 1.2.2) zeitwerk (~> 2.6) PATH remote: .. specs: noticed (1.6.3) - http (>= 4.0.0) - rails (>= 5.2.0) + rails (>= 6.1.0) GEM remote: https://rubygems.org/ specs: - addressable (2.8.4) + addressable (2.8.5) public_suffix (>= 2.0.2, < 6.0) - apnotic (1.7.0) + apnotic (1.7.1) connection_pool (~> 2) net-http2 (>= 0.18.3, < 2) - appraisal (2.4.1) + appraisal (2.5.0) bundler rake thor (>= 0.14.0) ast (2.4.2) + base64 (0.1.1) + bigdecimal (3.1.4) builder (3.2.4) byebug (11.1.3) concurrent-ruby (1.2.2) - connection_pool (2.4.0) + connection_pool (2.4.1) crack (0.4.5) rexml crass (1.0.6) date (3.3.3) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) + drb (2.1.1) + ruby2_keywords erubi (1.12.0) - faraday (2.7.4) + faraday (2.7.11) + base64 faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) faraday-net_http (3.0.2) - ffi (1.15.5) - ffi-compiler (1.0.1) - ffi (>= 1.0.0) - rake - globalid (1.1.0) - activesupport (>= 5.0) - googleauth (1.5.2) + globalid (1.2.1) + activesupport (>= 6.1) + googleauth (1.8.1) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) - memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) hashdiff (1.0.1) http-2 (0.11.0) - http (5.1.1) - addressable (~> 2.8) - http-cookie (~> 1.0) - http-form_data (~> 2.2) - llhttp-ffi (~> 0.4.0) - http-cookie (1.0.5) - domain_name (~> 0.5) - http-form_data (2.3.0) - i18n (1.13.0) + i18n (1.14.1) concurrent-ruby (~> 1.0) io-console (0.6.0) - irb (1.6.4) - reline (>= 0.3.0) + irb (1.8.3) + rdoc + reline (>= 0.3.8) json (2.6.3) - jwt (2.7.0) + jwt (2.7.1) language_server-protocol (3.17.0.3) - lint_roller (1.0.0) - llhttp-ffi (0.4.0) - ffi-compiler (~> 1.0) - rake (~> 13.0) - loofah (2.20.0) + lint_roller (1.1.0) + loofah (2.21.4) crass (~> 1.0.2) - nokogiri (>= 1.5.9) + nokogiri (>= 1.12.0) mail (2.8.1) mini_mime (>= 0.1.1) net-imap net-pop net-smtp marcel (1.0.2) - memoist (0.16.2) - mini_mime (1.1.2) - minitest (5.18.0) + mini_mime (1.1.5) + minitest (5.20.0) multi_json (1.15.0) mysql2 (0.5.5) net-http2 (0.18.5) http-2 (~> 0.11) - net-imap (0.3.4) + net-imap (0.4.2) date net-protocol net-pop (0.1.2) net-protocol net-protocol (0.2.1) timeout - net-smtp (0.3.3) + net-smtp (0.4.0) net-protocol nio4r (2.5.9) - nokogiri (1.14.3-x86_64-darwin) + nokogiri (1.15.4-x86_64-darwin) racc (~> 1.4) - nokogiri (1.14.3-x86_64-linux) + nokogiri (1.15.4-x86_64-linux) racc (~> 1.4) os (1.1.4) parallel (1.23.0) - parser (3.2.2.1) + parser (3.2.2.4) ast (~> 2.4.1) - pg (1.5.3) - public_suffix (5.0.1) - racc (1.6.2) - rack (3.0.7) + racc + pg (1.5.4) + psych (5.1.1.1) + stringio + public_suffix (5.0.3) + racc (1.7.1) + rack (3.0.8) rack-session (2.0.0) rack (>= 3.0.0) rack-test (2.1.0) @@ -209,69 +203,74 @@ GEM rackup (2.1.0) rack (>= 3) webrick (~> 1.8) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + rails-dom-testing (2.2.0) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.5.0) - loofah (~> 2.19, >= 2.19.1) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) rainbow (3.1.1) rake (13.0.6) - regexp_parser (2.8.0) - reline (0.3.3) + rdoc (6.5.0) + psych (>= 4.0.0) + regexp_parser (2.8.2) + reline (0.3.9) io-console (~> 0.5) - rexml (3.2.5) - rubocop (1.50.2) + rexml (3.2.6) + rubocop (1.56.4) + base64 (~> 0.1.1) json (~> 2.3) + language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.2.0.0) + parser (>= 3.2.2.3) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.28.0, < 2.0) + rubocop-ast (>= 1.28.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.28.0) + rubocop-ast (1.29.0) parser (>= 3.2.1.0) - rubocop-performance (1.16.0) + rubocop-performance (1.19.1) rubocop (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) - signet (0.17.0) + signet (0.18.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - sqlite3 (1.6.2-x86_64-darwin) - sqlite3 (1.6.2-x86_64-linux) - standard (1.28.0) + sqlite3 (1.6.7-x86_64-darwin) + sqlite3 (1.6.7-x86_64-linux) + standard (1.31.2) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) - rubocop (~> 1.50.2) + rubocop (~> 1.56.4) standard-custom (~> 1.0.0) - standard-performance (~> 1.0.1) - standard-custom (1.0.0) - lint_roller (~> 1.0) - standard-performance (1.0.1) + standard-performance (~> 1.2) + standard-custom (1.0.2) lint_roller (~> 1.0) - rubocop-performance (~> 1.16.0) - thor (1.2.1) - timeout (0.3.2) + rubocop (~> 1.50) + standard-performance (1.2.1) + lint_roller (~> 1.1) + rubocop-performance (~> 1.19.1) + stringio (3.0.8) + thor (1.3.0) + timeout (0.4.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) - unicode-display_width (2.4.2) - webmock (3.18.1) + unicode-display_width (2.5.0) + webmock (3.19.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) webrick (1.8.1) - websocket-driver (0.7.5) + websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - zeitwerk (2.6.8) + zeitwerk (2.6.12) PLATFORMS x86_64-darwin-22 From 507794f79a80b21f57410133d6016fc85447fcef Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 20 Oct 2023 20:17:54 -0500 Subject: [PATCH 06/39] Replace http gem dependency with net/http --- lib/noticed/delivery_methods/base.rb | 31 +++++++++++++------ lib/noticed/delivery_methods/vonage.rb | 3 +- noticed.gemspec | 3 +- test/delivery_methods/microsoft_teams_test.rb | 4 +-- test/delivery_methods/slack_test.rb | 4 +-- test/delivery_methods/twilio_test.rb | 4 +-- test/delivery_methods/vonage_test.rb | 4 +-- test/test_helper.rb | 12 ++++--- 8 files changed, 40 insertions(+), 25 deletions(-) diff --git a/lib/noticed/delivery_methods/base.rb b/lib/noticed/delivery_methods/base.rb index 3b6793c0..4cec4dad 100644 --- a/lib/noticed/delivery_methods/base.rb +++ b/lib/noticed/delivery_methods/base.rb @@ -1,3 +1,5 @@ +require "net/http" + module Noticed module DeliveryMethods class Base < Noticed.parent_class.constantize @@ -68,23 +70,32 @@ def deliver # post("http://example.com", basic_auth: {user:, pass:}, headers: {}, json: {}, form: {}) # def post(url, args = {}) - basic_auth = args.delete(:basic_auth) - headers = args.delete(:headers) + uri = URI(url) + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true if uri.instance_of? URI::HTTPS + + request = Net::HTTP::Post.new(uri.request_uri, args.delete(:headers)) - request = HTTP - request = request.basic_auth(user: basic_auth[:user], pass: basic_auth[:pass]) if basic_auth - request = request.headers(headers) if headers + if (basic_auth = args.delete(:basic_auth)) + request.basic_auth basic_auth.fetch(:user), basic_auth.fetch(:pass) + end + + if (json = args.delete(:json)) + request.body = json.to_json + elsif (form = args.delete(:form)) + request.set_form(form, "multipart/form-data") + end - response = request.post(url, args) + response = http.request(request) if options[:debug] logger.debug("POST #{url}") - logger.debug("Response: #{response.code}: #{response}") + logger.debug("Response: #{response.code}: #{response.body}") end - if !options[:ignore_failure] && !response.status.success? - puts response.status - puts response.body + if !options[:ignore_failure] && !response.code.start_with?("20") + logger.debug response.code + logger.debug response.body raise ResponseUnsuccessful.new(response) end diff --git a/lib/noticed/delivery_methods/vonage.rb b/lib/noticed/delivery_methods/vonage.rb index 92bfecd7..dc7e3860 100644 --- a/lib/noticed/delivery_methods/vonage.rb +++ b/lib/noticed/delivery_methods/vonage.rb @@ -3,7 +3,8 @@ module DeliveryMethods class Vonage < Base def deliver response = post("https://rest.nexmo.com/sms/json", json: format) - status = response.parse.dig("messages", 0, "status") + json = JSON.parse(response.body) + status = json.dig("messages", 0, "status") if !options[:ignore_failure] && status != "0" raise ResponseUnsuccessful.new(response) end diff --git a/noticed.gemspec b/noticed.gemspec index 83d97d20..df8837aa 100644 --- a/noticed.gemspec +++ b/noticed.gemspec @@ -16,6 +16,5 @@ Gem::Specification.new do |spec| spec.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] - spec.add_dependency "rails", ">= 5.2.0" - spec.add_dependency "http", ">= 4.0.0" + spec.add_dependency "rails", ">= 6.1.0" end diff --git a/test/delivery_methods/microsoft_teams_test.rb b/test/delivery_methods/microsoft_teams_test.rb index decf0f86..a1953f4d 100644 --- a/test/delivery_methods/microsoft_teams_test.rb +++ b/test/delivery_methods/microsoft_teams_test.rb @@ -42,7 +42,7 @@ def actions MicrosoftTeamsExample.new.deliver(user) } - assert_equal HTTP::Response, e.response.class + assert_equal Net::HTTPForbidden, e.response.class end test "deliver returns an http response" do @@ -55,6 +55,6 @@ def actions } response = Noticed::DeliveryMethods::MicrosoftTeams.new.perform(args) - assert_kind_of HTTP::Response, response + assert_kind_of Net::HTTPResponse, response end end diff --git a/test/delivery_methods/slack_test.rb b/test/delivery_methods/slack_test.rb index 3ceb0ab1..6c3e37aa 100644 --- a/test/delivery_methods/slack_test.rb +++ b/test/delivery_methods/slack_test.rb @@ -28,7 +28,7 @@ def slack_url e = assert_raises(::Noticed::ResponseUnsuccessful) { SlackExample.new.deliver(user) } - assert_equal HTTP::Response, e.response.class + assert_equal Net::HTTPForbidden, e.response.class end test "deliver returns an http response" do @@ -41,7 +41,7 @@ def slack_url } response = Noticed::DeliveryMethods::Slack.new.perform(args) - assert_kind_of HTTP::Response, response + assert_kind_of Net::HTTPResponse, response end test "logs verbosely in debug mode" do diff --git a/test/delivery_methods/twilio_test.rb b/test/delivery_methods/twilio_test.rb index 49047293..454d6327 100644 --- a/test/delivery_methods/twilio_test.rb +++ b/test/delivery_methods/twilio_test.rb @@ -23,7 +23,7 @@ def twilio_creds e = assert_raises(::Noticed::ResponseUnsuccessful) { TwilioExample.new.deliver(user) } - assert_equal HTTP::Response, e.response.class + assert_equal Net::HTTPForbidden, e.response.class end test "deliver returns an http response" do @@ -36,6 +36,6 @@ def twilio_creds } response = Noticed::DeliveryMethods::Twilio.new.perform(args) - assert_kind_of HTTP::Response, response + assert_kind_of Net::HTTPResponse, response end end diff --git a/test/delivery_methods/vonage_test.rb b/test/delivery_methods/vonage_test.rb index a7819a2e..42caad15 100644 --- a/test/delivery_methods/vonage_test.rb +++ b/test/delivery_methods/vonage_test.rb @@ -26,7 +26,7 @@ def to_vonage e = assert_raises(::Noticed::ResponseUnsuccessful) { VonageExample.new.deliver(user) } - assert_equal HTTP::Response, e.response.class + assert_equal Net::HTTPForbidden, e.response.class end test "deliver returns an http response" do @@ -39,6 +39,6 @@ def to_vonage } response = Noticed::DeliveryMethods::Vonage.new.perform(args) - assert_kind_of HTTP::Response, response + assert_kind_of Net::HTTPResponse, response end end diff --git a/test/test_helper.rb b/test/test_helper.rb index d200d50e..87c96119 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -13,13 +13,17 @@ Rails::TestUnitReporter.executable = "bin/test" # Load fixtures from the engine -if ActiveSupport::TestCase.respond_to?(:fixture_path=) - ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__) +if ActiveSupport::TestCase.respond_to?(:fixture_paths=) + ActiveSupport::TestCase.fixture_paths << File.expand_path("../fixtures", __FILE__) + ActionDispatch::IntegrationTest.fixture_paths << File.expand_path("../fixtures", __FILE__) +elsif ActiveSupport::TestCase.respond_to?(:fixture_path=) + ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path - ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files" - ActiveSupport::TestCase.fixtures :all end +ActiveSupport::TestCase.file_fixture_path = File.expand_path("../fixtures/files", __FILE__) +ActiveSupport::TestCase.fixtures :all + require "minitest/unit" require "webmock/minitest" From 49675d60daf7c52dd4e310ea3258a9b297cdd366 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 20 Oct 2023 20:18:15 -0500 Subject: [PATCH 07/39] Remove Rails 6 polyfills now that we support 6.1+ --- .../actioncable/test_adapter.rb | 70 ----- .../actioncable/test_helper.rb | 143 ----------- .../activejob/serializers.rb | 240 ------------------ lib/rails_6_polyfills/base.rb | 18 -- 4 files changed, 471 deletions(-) delete mode 100644 lib/rails_6_polyfills/actioncable/test_adapter.rb delete mode 100644 lib/rails_6_polyfills/actioncable/test_helper.rb delete mode 100644 lib/rails_6_polyfills/activejob/serializers.rb delete mode 100644 lib/rails_6_polyfills/base.rb diff --git a/lib/rails_6_polyfills/actioncable/test_adapter.rb b/lib/rails_6_polyfills/actioncable/test_adapter.rb deleted file mode 100644 index c95641e8..00000000 --- a/lib/rails_6_polyfills/actioncable/test_adapter.rb +++ /dev/null @@ -1,70 +0,0 @@ -# frozen_string_literal: true - -require "action_cable/subscription_adapter/base" -require "action_cable/subscription_adapter/subscriber_map" -require "action_cable/subscription_adapter/async" - -module ActionCable - module SubscriptionAdapter - # == Test adapter for Action Cable - # - # The test adapter should be used only in testing. Along with - # ActionCable::TestHelper it makes a great tool to test your Rails application. - # - # To use the test adapter set +adapter+ value to +test+ in your +config/cable.yml+ file. - # - # NOTE: Test adapter extends the ActionCable::SubscriptionsAdapter::Async adapter, - # so it could be used in system tests too. - class Test < Async - def broadcast(channel, payload) - broadcasts(channel) << payload - super - end - - def broadcasts(channel) - channels_data[channel] ||= [] - end - - def clear_messages(channel) - channels_data[channel] = [] - end - - def clear - @channels_data = nil - end - - private - - def channels_data - @channels_data ||= {} - end - end - end - - # Update how broadcast_for determines the channel name so it's consistent with the Rails 6 way - module Channel - module Broadcasting - delegate :broadcast_to, to: :class - module ClassMethods - def broadcast_to(model, message) - ActionCable.server.broadcast(broadcasting_for(model), message) - end - - def broadcasting_for(model) - serialize_broadcasting([channel_name, model]) - end - - def serialize_broadcasting(object) # :nodoc: - case # standard:disable Style/EmptyCaseCondition - when object.is_a?(Array) - object.map { |m| serialize_broadcasting(m) }.join(":") - when object.respond_to?(:to_gid_param) - object.to_gid_param - else - object.to_param - end - end - end - end - end -end diff --git a/lib/rails_6_polyfills/actioncable/test_helper.rb b/lib/rails_6_polyfills/actioncable/test_helper.rb deleted file mode 100644 index 55fd62b9..00000000 --- a/lib/rails_6_polyfills/actioncable/test_helper.rb +++ /dev/null @@ -1,143 +0,0 @@ -# frozen_string_literal: true - -module ActionCable - # Have ActionCable pick its Test SubscriptionAdapter when it's called for in cable.yml - module Server - class Configuration - def pubsub_adapter - (cable["adapter"] == "test") ? ActionCable::SubscriptionAdapter::Test : super - end - end - end - - # Provides helper methods for testing Action Cable broadcasting - module TestHelper - def before_setup # :nodoc: - server = ActionCable.server - test_adapter = ActionCable::SubscriptionAdapter::Test.new(server) - - @old_pubsub_adapter = server.pubsub - - server.instance_variable_set(:@pubsub, test_adapter) - super - end - - def after_teardown # :nodoc: - super - ActionCable.server.instance_variable_set(:@pubsub, @old_pubsub_adapter) - end - - # Asserts that the number of broadcasted messages to the stream matches the given number. - # - # def test_broadcasts - # assert_broadcasts 'messages', 0 - # ActionCable.server.broadcast 'messages', { text: 'hello' } - # assert_broadcasts 'messages', 1 - # ActionCable.server.broadcast 'messages', { text: 'world' } - # assert_broadcasts 'messages', 2 - # end - # - # If a block is passed, that block should cause the specified number of - # messages to be broadcasted. - # - # def test_broadcasts_again - # assert_broadcasts('messages', 1) do - # ActionCable.server.broadcast 'messages', { text: 'hello' } - # end - # - # assert_broadcasts('messages', 2) do - # ActionCable.server.broadcast 'messages', { text: 'hi' } - # ActionCable.server.broadcast 'messages', { text: 'how are you?' } - # end - # end - # - def assert_broadcasts(stream, number) - if block_given? - original_count = broadcasts_size(stream) - yield - new_count = broadcasts_size(stream) - actual_count = new_count - original_count - else - actual_count = broadcasts_size(stream) - end - - assert_equal number, actual_count, "#{number} broadcasts to #{stream} expected, but #{actual_count} were sent" - end - - # Asserts that no messages have been sent to the stream. - # - # def test_no_broadcasts - # assert_no_broadcasts 'messages' - # ActionCable.server.broadcast 'messages', { text: 'hi' } - # assert_broadcasts 'messages', 1 - # end - # - # If a block is passed, that block should not cause any message to be sent. - # - # def test_broadcasts_again - # assert_no_broadcasts 'messages' do - # # No job messages should be sent from this block - # end - # end - # - # Note: This assertion is simply a shortcut for: - # - # assert_broadcasts 'messages', 0, &block - # - def assert_no_broadcasts(stream, &block) - assert_broadcasts stream, 0, &block - end - - # Asserts that the specified message has been sent to the stream. - # - # def test_assert_transmitted_message - # ActionCable.server.broadcast 'messages', text: 'hello' - # assert_broadcast_on('messages', text: 'hello') - # end - # - # If a block is passed, that block should cause a message with the specified data to be sent. - # - # def test_assert_broadcast_on_again - # assert_broadcast_on('messages', text: 'hello') do - # ActionCable.server.broadcast 'messages', text: 'hello' - # end - # end - # - def assert_broadcast_on(stream, data) - # Encode to JSON and back–we want to use this value to compare - # with decoded JSON. - # Comparing JSON strings doesn't work due to the order of the keys. - serialized_msg = - ActiveSupport::JSON.decode(ActiveSupport::JSON.encode(data)) - - new_messages = broadcasts(stream) - if block_given? - old_messages = new_messages - clear_messages(stream) - - yield - new_messages = broadcasts(stream) - clear_messages(stream) - - # Restore all sent messages - (old_messages + new_messages).each { |m| pubsub_adapter.broadcast(stream, m) } - end - - message = new_messages.find { |msg| ActiveSupport::JSON.decode(msg) == serialized_msg } - - assert message, "No messages sent with #{data} to #{stream}" - end - - def pubsub_adapter # :nodoc: - ActionCable.server.pubsub - end - - delegate :broadcasts, :clear_messages, to: :pubsub_adapter - - private - - def broadcasts_size(channel) - broadcasts(channel).size - end - end -end diff --git a/lib/rails_6_polyfills/activejob/serializers.rb b/lib/rails_6_polyfills/activejob/serializers.rb deleted file mode 100644 index 73c533d6..00000000 --- a/lib/rails_6_polyfills/activejob/serializers.rb +++ /dev/null @@ -1,240 +0,0 @@ -# frozen_string_literal: true - -# First add Rails 6.0 ActiveJob Serializers support, and then the -# DurationSerializer and SymbolSerializer. -module ActiveJob - module Arguments - # :nodoc: - OBJECT_SERIALIZER_KEY = "_aj_serialized" - - def serialize_argument(argument) - case argument - when *TYPE_WHITELIST - argument - when GlobalID::Identification - convert_to_global_id_hash(argument) - when Array - argument.map { |arg| serialize_argument(arg) } - when ActiveSupport::HashWithIndifferentAccess - serialize_indifferent_hash(argument) - when Hash - symbol_keys = argument.each_key.grep(Symbol).map(&:to_s) - result = serialize_hash(argument) - result[SYMBOL_KEYS_KEY] = symbol_keys - result - when ->(arg) { arg.respond_to?(:permitted?) } - serialize_indifferent_hash(argument.to_h) - else # Add Rails 6 support for Serializers - Serializers.serialize(argument) - end - end - - def deserialize_argument(argument) - case argument - when String - argument - when *TYPE_WHITELIST - argument - when Array - argument.map { |arg| deserialize_argument(arg) } - when Hash - if serialized_global_id?(argument) - deserialize_global_id argument - elsif custom_serialized?(argument) - Serializers.deserialize(argument) - else - deserialize_hash(argument) - end - else - raise ArgumentError, "Can only deserialize primitive arguments: #{argument.inspect}" - end - end - - def custom_serialized?(hash) - hash.key?(OBJECT_SERIALIZER_KEY) - end - end - - # The ActiveJob::Serializers module is used to store a list of known serializers - # and to add new ones. It also has helpers to serialize/deserialize objects. - module Serializers # :nodoc: - # Base class for serializing and deserializing custom objects. - # - # Example: - # - # class MoneySerializer < ActiveJob::Serializers::ObjectSerializer - # def serialize(money) - # super("amount" => money.amount, "currency" => money.currency) - # end - # - # def deserialize(hash) - # Money.new(hash["amount"], hash["currency"]) - # end - # - # private - # - # def klass - # Money - # end - # end - class ObjectSerializer - include Singleton - - class << self - delegate :serialize?, :serialize, :deserialize, to: :instance - end - - # Determines if an argument should be serialized by a serializer. - def serialize?(argument) - argument.is_a?(klass) - end - - # Serializes an argument to a JSON primitive type. - def serialize(hash) - {Arguments::OBJECT_SERIALIZER_KEY => self.class.name}.merge!(hash) - end - - # Deserializes an argument from a JSON primitive type. - def deserialize(_argument) - raise NotImplementedError - end - - private - - # The class of the object that will be serialized. - def klass - raise NotImplementedError - end - end - - class DurationSerializer < ObjectSerializer # :nodoc: - def serialize(duration) - super("value" => duration.value, "parts" => Arguments.serialize(duration.parts.each_with_object({}) { |v, s| s[v.first.to_s] = v.last })) - end - - def deserialize(hash) - value = hash["value"] - parts = Arguments.deserialize(hash["parts"]) - - klass.new(value, parts) - end - - private - - def klass - ActiveSupport::Duration - end - end - - class SymbolSerializer < ObjectSerializer # :nodoc: - def serialize(argument) - super("value" => argument.to_s) - end - - def deserialize(argument) - argument["value"].to_sym - end - - private - - def klass - Symbol - end - end - - # ----------------------------- - - mattr_accessor :_additional_serializers - self._additional_serializers = Set.new - - class << self - # Returns serialized representative of the passed object. - # Will look up through all known serializers. - # Raises ActiveJob::SerializationError if it can't find a proper serializer. - def serialize(argument) - serializer = serializers.detect { |s| s.serialize?(argument) } - raise SerializationError.new("Unsupported argument type: #{argument.class.name}") unless serializer - serializer.serialize(argument) - end - - # Returns deserialized object. - # Will look up through all known serializers. - # If no serializer found will raise ArgumentError. - def deserialize(argument) - serializer_name = argument[Arguments::OBJECT_SERIALIZER_KEY] - raise ArgumentError, "Serializer name is not present in the argument: #{argument.inspect}" unless serializer_name - - serializer = serializer_name.safe_constantize - raise ArgumentError, "Serializer #{serializer_name} is not known" unless serializer - - serializer.deserialize(argument) - end - - # Returns list of known serializers. - def serializers - self._additional_serializers # standard:disable Style/RedundantSelf - end - - # Adds new serializers to a list of known serializers. - def add_serializers(*new_serializers) - self._additional_serializers += new_serializers.flatten - end - end - - add_serializers DurationSerializer, - SymbolSerializer - # The full set of 6 serializers that Rails 6.0 normally adds here -- feel free to include any others if you wish: - # SymbolSerializer, - # DurationSerializer, # (The one that we've added above in order to support testing) - # DateTimeSerializer, - # DateSerializer, - # TimeWithZoneSerializer, - # TimeSerializer - end - - # Is the updated version of perform_enqueued_jobs from Rails 6.0 missing from ActionJob's TestHelper? - unless TestHelper.private_instance_methods.include?(:flush_enqueued_jobs) - module TestHelper - def perform_enqueued_jobs(only: nil, except: nil, queue: nil) - return flush_enqueued_jobs(only: only, except: except, queue: queue) unless block_given? - - super - end - - private - - def jobs_with(jobs, only: nil, except: nil, queue: nil) - validate_option(only: only, except: except) - - jobs.count do |job| - job_class = job.fetch(:job) - - if only - next false unless filter_as_proc(only).call(job) - elsif except - next false if filter_as_proc(except).call(job) - end - - if queue - next false unless queue.to_s == job.fetch(:queue, job_class.queue_name) - end - - yield job if block_given? - - true - end - end - - def enqueued_jobs_with(only: nil, except: nil, queue: nil, &block) - jobs_with(enqueued_jobs, only: only, except: except, queue: queue, &block) - end - - def flush_enqueued_jobs(only: nil, except: nil, queue: nil) - enqueued_jobs_with(only: only, except: except, queue: queue) do |payload| - instantiate_job(payload).perform_now - queue_adapter.performed_jobs << payload - end - end - end - end -end diff --git a/lib/rails_6_polyfills/base.rb b/lib/rails_6_polyfills/base.rb deleted file mode 100644 index efdef208..00000000 --- a/lib/rails_6_polyfills/base.rb +++ /dev/null @@ -1,18 +0,0 @@ -# The following implements polyfills for Rails < 6.0 -module ActionCable - # If the Rails 6.0 ActionCable::TestHelper is missing then allow it to autoload - unless ActionCable.const_defined? :TestHelper - autoload :TestHelper, "rails_6_polyfills/actioncable/test_helper.rb" - end - # If the Rails 6.0 test SubscriptionAdapter is missing then allow it to autoload - unless ActionCable::SubscriptionAdapter.const_defined? :Test - module SubscriptionAdapter - autoload :Test, "rails_6_polyfills/actioncable/test_adapter.rb" - end - end -end - -# If the Rails 6.0 ActionJob Serializers are missing then load support for them -unless ActiveJob.const_defined?(:Serializers) - require "rails_6_polyfills/activejob/serializers" -end From 3c5713a752e7d0ff2511a4f727088ed443b80e9a Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 20 Oct 2023 20:19:26 -0500 Subject: [PATCH 08/39] Rename to Notifiers --- README.md | 56 +++++++++---------- .../noticed/delivery_method_generator.rb | 2 +- .../noticed/notification_generator.rb | 4 +- lib/generators/noticed/templates/README | 3 +- .../{notification.rb.tt => notifier.rb.tt} | 0 lib/noticed/model.rb | 4 +- test/dummy/app/notifiers/comment_notifier.rb | 18 ++++++ test/noticed_test.rb | 2 +- 8 files changed, 54 insertions(+), 35 deletions(-) rename lib/generators/noticed/templates/{notification.rb.tt => notifier.rb.tt} (100%) create mode 100644 test/dummy/app/notifiers/comment_notifier.rb diff --git a/README.md b/README.md index b87f062f..e8783d8f 100644 --- a/README.md +++ b/README.md @@ -47,38 +47,38 @@ This will generate a Notification model and instructions for associating User mo To generate a notification object, simply run: -`rails generate noticed:notification CommentNotification` +`rails generate noticed:notifier CommentNotifier` #### Sending Notifications To send a notification to a user: ```ruby -# Instantiate a new notification -notification = CommentNotification.with(comment: @comment) +# Instantiate a new notifier +notifier = CommentNotifier.with(record: @comment) # Deliver notification in background job -notification.deliver_later(@comment.post.author) +notifier.deliver_later(@comment.post.author) # Deliver notification immediately -notification.deliver(@comment.post.author) +notifier.deliver(@comment.post.author) # Deliver notification to multiple recipients -notification.deliver_later(User.all) +notifier.deliver_later(User.all) ``` -This will instantiate a new notification with the `comment` stored in the notification's params. +This will instantiate a new notifier with the `comment` stored in the notification's params. Each delivery method is able to transform this metadata that's best for the format. For example, the database may simply store the comment so it can be linked when rendering in the navbar. The websocket mechanism may transform this into a browser notification or insert it into the navbar. -#### Notification Objects +#### Notifier Objects -Notifications inherit from `Noticed::Base`. This provides all their functionality and allows them to be delivered. +Notifiers inherit from `Noticed::Base`. This provides all their functionality and allows them to be delivered. To add delivery methods, simply `include` the module for the delivery methods you would like to use. ```ruby -class CommentNotification < Noticed::Base +class CommentNotifier < Noticed::Base deliver_by :database deliver_by :action_cable deliver_by :email, mailer: 'CommentMailer', if: :email_notifications? @@ -113,7 +113,7 @@ end ##### Helper Methods -You can define helper methods inside your Notification object to make it easier to render. +You can define helper methods inside your Notifier object to make it easier to render. ##### URL Helpers @@ -130,7 +130,7 @@ Rails.application.routes.default_url_options[:host] = 'localhost:3000' Like ActiveRecord, notifications have several different types of callbacks. ```ruby -class CommentNotification < Noticed::Base +class CommentNotifier < Noticed::Base deliver_by :database deliver_by :email, mailer: 'CommentMailer' @@ -173,7 +173,7 @@ You can use the `if:` and `unless: ` options on your delivery methods to check t For example: ```ruby -class CommentNotification < Noticed::Base +class CommentNotifier < Noticed::Base deliver_by :email, mailer: 'CommentMailer', if: :email_notifications? def email_notifications? @@ -207,7 +207,7 @@ class Message < ApplicationRecord private def notify_recipient - NewMessageNotification.with(message: self).deliver_later(recipient) + NewMessageNotifier.with(message: self).deliver_later(recipient) end ``` @@ -223,24 +223,24 @@ A common symptom of this problem is undelivered notifications and the following If you rename the class of a notification object your existing queries can break. This is because Noticed serializes the class name and sets it to the `type` column on the `Notification` record. -You can catch these errors at runtime by using `YourNotificationClassName.name` instead of hardcoding the string when performing a query. +You can catch these errors at runtime by using `YourNotifierClassName.name` instead of hardcoding the string when performing a query. ```ruby -Notification.where(type: YourNotificationClassName.name) # good -Notification.where(type: "YourNotificationClassName") # bad +Notification.where(type: YourNotifierClassName.name) # good +Notification.where(type: "YourNotifierClassName") # bad ``` When renaming a notification class you will need to backfill existing notifications to reference the new name. ```ruby -Notification.where(type: "OldNotificationClassName").update_all(type: NewNotificationClassName.name) +Notification.where(type: "OldNotifierClassName").update_all(type: NewNotifierClassName.name) ``` ## 🚛 Delivery Methods The delivery methods are designed to be modular so you can customize the way each type gets delivered. -For example, emails will require a subject, body, and email address while an SMS requires a phone number and simple message. You can define the formats for each of these in your Notification and the delivery method will handle the processing of it. +For example, emails will require a subject, body, and email address while an SMS requires a phone number and simple message. You can define the formats for each of these in your Notifier and the delivery method will handle the processing of it. * [Database](docs/delivery_methods/database.md) * [Email](docs/delivery_methods/email.md) @@ -258,7 +258,7 @@ For example, emails will require a subject, body, and email address while an SMS A common pattern is to deliver a notification via the database and then, after some time has passed, email the user if they have not yet read the notification. You can implement this functionality by combining multiple delivery methods, the `delay` option, and the conditional `if` / `unless` option. ```ruby -class CommentNotification < Noticed::Base +class CommentNotifier< Noticed::Base deliver_by :database deliver_by :email, mailer: 'CommentMailer', delay: 15.minutes, unless: :read? end @@ -269,7 +269,7 @@ Here a notification will be created immediately in the database (for display dir You can also configure multiple fallback options: ```ruby -class CriticalSystemNotification < Noticed::Base +class CriticalSystemNotifier < Noticed::Base deliver_by :database deliver_by :slack deliver_by :email, mailer: 'CriticalSystemMailer', delay: 10.minutes, if: :unread? @@ -307,7 +307,7 @@ end You can use the custom delivery method thus created by adding a `deliver_by` line with a unique name and `class` option in your notification class. ```ruby -class MyNotification < Noticed::Base +class MyNotifier < Noticed::Base deliver_by :discord, class: "DeliveryMethods::Discord" end ``` @@ -343,7 +343,7 @@ class DeliveryMethods::Discord < Noticed::DeliveryMethods::Base end end -class CommentNotification < Noticed::Base +class CommentNotifier < Noticed::Base deliver_by :discord, class: 'DeliveryMethods::Discord' end ``` @@ -353,7 +353,7 @@ Now it will raise an error because a required argument is missing. To fix the error, the argument has to be passed correctly. For example: ```ruby -class CommentNotification < Noticed::Base +class CommentNotifier < Noticed::Base deliver_by :discord, class: 'DeliveryMethods::Discord', username: User.admin.username end ``` @@ -399,10 +399,10 @@ user.notifications.mark_as_unread! #### Instance methods -Convert back into a Noticed notification object: +Convert back into a Noticed notifier object: ```ruby -@notification.to_notification +@notification.to_notifier ``` Mark notification as read / unread: @@ -445,11 +445,11 @@ class Post < ApplicationRecord end # Create a CommentNotification with a post param -CommentNotification.with(post: @post).deliver(user) +CommentNotifier.with(post: @post).deliver(user) # Lookup Notifications where params: {post: @post} @post.notifications_as_post -CommentNotification.with(parent: @post).deliver(user) +CommentNotifier.with(parent: @post).deliver(user) @post.notifications_as_parent ``` diff --git a/lib/generators/noticed/delivery_method_generator.rb b/lib/generators/noticed/delivery_method_generator.rb index 5edf33ae..2465a86a 100644 --- a/lib/generators/noticed/delivery_method_generator.rb +++ b/lib/generators/noticed/delivery_method_generator.rb @@ -12,7 +12,7 @@ class DeliveryMethodGenerator < Rails::Generators::NamedBase desc "Generates a class for a custom delivery method with the given NAME." def generate_notification - template "delivery_method.rb", "app/notifications/delivery_methods/#{singular_name}.rb" + template "delivery_method.rb", "app/notifiers/delivery_methods/#{singular_name}.rb" end end end diff --git a/lib/generators/noticed/notification_generator.rb b/lib/generators/noticed/notification_generator.rb index d0aad925..eaeace12 100644 --- a/lib/generators/noticed/notification_generator.rb +++ b/lib/generators/noticed/notification_generator.rb @@ -4,7 +4,7 @@ module Noticed module Generators - class NotificationGenerator < Rails::Generators::NamedBase + class NotifierGenerator < Rails::Generators::NamedBase include Rails::Generators::ResourceHelpers source_root File.expand_path("../templates", __FILE__) @@ -12,7 +12,7 @@ class NotificationGenerator < Rails::Generators::NamedBase desc "Generates a notification with the given NAME." def generate_notification - template "notification.rb", "app/notifications/#{file_path}.rb" + template "notifier.rb", "app/notifiers/#{file_path}.rb" end end end diff --git a/lib/generators/noticed/templates/README b/lib/generators/noticed/templates/README index f8f4c8f5..e7e618a0 100644 --- a/lib/generators/noticed/templates/README +++ b/lib/generators/noticed/templates/README @@ -4,4 +4,5 @@ Next steps: 1. Run "rails db:migrate" 2. Add "has_many :notifications, as: :recipient, dependent: :destroy" to your User model(s). -3. Generate notifications with "rails g noticed:notification" +2. Add "has_many :notifications, as: :record, dependent: :destroy" to your model(s) that notifications are about. +3. Generate notifiers with "rails g noticed:notifier" diff --git a/lib/generators/noticed/templates/notification.rb.tt b/lib/generators/noticed/templates/notifier.rb.tt similarity index 100% rename from lib/generators/noticed/templates/notification.rb.tt rename to lib/generators/noticed/templates/notifier.rb.tt diff --git a/lib/noticed/model.rb b/lib/noticed/model.rb index 44c57c0a..d4632e3f 100644 --- a/lib/noticed/model.rb +++ b/lib/noticed/model.rb @@ -53,8 +53,8 @@ def noticed_coder end # Rehydrate the database notification into the Notification object for rendering - def to_notification - @_notification ||= begin + def to_notifier + @_notifier||= begin instance = type.constantize.with(params) instance.record = self instance.recipient = recipient diff --git a/test/dummy/app/notifiers/comment_notifier.rb b/test/dummy/app/notifiers/comment_notifier.rb new file mode 100644 index 00000000..2c4fbbee --- /dev/null +++ b/test/dummy/app/notifiers/comment_notifier.rb @@ -0,0 +1,18 @@ +class CommentNotifier < Noticed::Base + deliver_by :database, format: :attributes_for_database + deliver_by :action_cable + deliver_by :email, mailer: "UserMailer" + deliver_by :discord, class: "DiscordNotification" + + def attributes_for_database + { + account_id: 1, + type: self.class.name, + params: params + } + end + + def url + root_url + end +end diff --git a/test/noticed_test.rb b/test/noticed_test.rb index 0c5c27c5..7b7ec08d 100644 --- a/test/noticed_test.rb +++ b/test/noticed_test.rb @@ -148,7 +148,7 @@ class Noticed::Test < ActiveSupport::TestCase test "has access to recipient in notification instance" do RecipientExample.deliver(user) - assert_equal user.id, Notification.last.to_notification.message + assert_equal user.id, Notification.last.to_notifier.message end test "validates attributes for params" do From 255ed390691acea828c8b5584d1bc86a840759df Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 20 Oct 2023 20:19:39 -0500 Subject: [PATCH 09/39] Clean up initializers for polyfills and http gem --- lib/noticed.rb | 1 - lib/noticed/engine.rb | 4 ---- 2 files changed, 5 deletions(-) diff --git a/lib/noticed.rb b/lib/noticed.rb index 921598eb..c83f6e81 100644 --- a/lib/noticed.rb +++ b/lib/noticed.rb @@ -1,5 +1,4 @@ require "active_job/arguments" -require "http" require "noticed/engine" module Noticed diff --git a/lib/noticed/engine.rb b/lib/noticed/engine.rb index d2d1c4e0..d5edc70a 100644 --- a/lib/noticed/engine.rb +++ b/lib/noticed/engine.rb @@ -5,9 +5,5 @@ class Engine < ::Rails::Engine include Noticed::HasNotifications end end - - initializer "noticed.rails_5_2_support" do - require "rails_6_polyfills/base" if Rails::VERSION::MAJOR < 6 - end end end From 9f5d22f5f0d276133c16a879a5d165d130da8865 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 20 Oct 2023 20:22:36 -0500 Subject: [PATCH 10/39] Standardize to Ruby 3.0 --- .standard.yml | 1 + lib/noticed/model.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.standard.yml b/.standard.yml index f1b942c7..3c6e1900 100644 --- a/.standard.yml +++ b/.standard.yml @@ -1,3 +1,4 @@ +ruby_version: 3.0 ignore: - '**/*': - Style/HashSyntax diff --git a/lib/noticed/model.rb b/lib/noticed/model.rb index d4632e3f..d14ad6e5 100644 --- a/lib/noticed/model.rb +++ b/lib/noticed/model.rb @@ -54,7 +54,7 @@ def noticed_coder # Rehydrate the database notification into the Notification object for rendering def to_notifier - @_notifier||= begin + @_notifier ||= begin instance = type.constantize.with(params) instance.record = self instance.recipient = recipient From a870e98a4c6a3ca0179ee285bb931bb775f63f0d Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 20 Oct 2023 20:28:07 -0500 Subject: [PATCH 11/39] Use main branch on CI --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c25e9315..c44e5f7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,8 @@ on: - '*' push: branches: - - master + - main + jobs: sqlite: runs-on: ubuntu-latest From 9992d1843575b51397e7172d23cc46b85d37690e Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Wed, 25 Oct 2023 12:03:41 -0500 Subject: [PATCH 12/39] Extract record key from params and save it to the record database column --- docs/delivery_methods/database.md | 20 +++++++++++++++-- lib/noticed/delivery_methods/database.rb | 28 +++++++++++------------- test/delivery_methods/database_test.rb | 22 +++++++++++++++++++ test/dummy/db/schema.rb | 2 ++ 4 files changed, 55 insertions(+), 17 deletions(-) diff --git a/docs/delivery_methods/database.md b/docs/delivery_methods/database.md index f20d6df5..6be85150 100644 --- a/docs/delivery_methods/database.md +++ b/docs/delivery_methods/database.md @@ -12,8 +12,24 @@ Writes notification to the database. The name of the database association to use. Defaults to `:notifications` -* `format: :format_for_database` - *Optional* +* `attributes:` - *Optional* - Use a custom method to define the attributes saved to the database + Pass a symbol or callable object to define custom attributes to save to the database record. +##### Examples +```ruby +class CommentNotification + deliver_by :database do |config| + config.association = :notifications + + config.attributes = ->{ + { column: value } + } + end +end +``` + +```ruby +CommentNotification.with(record: @post).deliver(user) +``` diff --git a/lib/noticed/delivery_methods/database.rb b/lib/noticed/delivery_methods/database.rb index 85e2d429..5a9cf2fb 100644 --- a/lib/noticed/delivery_methods/database.rb +++ b/lib/noticed/delivery_methods/database.rb @@ -3,6 +3,7 @@ module DeliveryMethods class Database < Base # Must return the database record def deliver + association_name = options.fetch(:association, :notifications) recipient.send(association_name).create!(attributes) end @@ -15,23 +16,20 @@ def self.validate!(options) private - def association_name - options[:association] || :notifications + def attributes + record = notification.params.delete(:record) + { + params: notification.params, + record: record, + type: notification.class.name + }.merge(custom_attributes) end - def attributes - if (method = options[:format]) - if method.respond_to? :call - notification.instance_eval(&method) - else - notification.send(method) - end - else - { - type: notification.class.name, - params: notification.params - } - end + def custom_attributes + method = (options[:attributes] || options[:format]) + return {} unless method.present? + + method.respond_to?(:call) ? notification.instance_eval(&method) : notification.send(method) end end end diff --git a/test/delivery_methods/database_test.rb b/test/delivery_methods/database_test.rb index b0518973..44f6ea0a 100644 --- a/test/delivery_methods/database_test.rb +++ b/test/delivery_methods/database_test.rb @@ -9,6 +9,14 @@ class WithDelayedDatabaseDelivery < Noticed::Base deliver_by :database, delay: 5.minutes end + class WithCustomDatabaseAttributes < Noticed::Base + deliver_by :database do |config| + config.attributes = ->(notification) { + {created_at: 1.year.from_now} + } + end + end + test "writes to database" do notification = CommentNotification.with(foo: :bar) @@ -63,4 +71,18 @@ class WithDelayedDatabaseDelivery < Noticed::Base WithDelayedDatabaseDelivery.new.deliver(user) end end + + test "with custom database attributes" do + freeze_time + WithCustomDatabaseAttributes.deliver(user) + assert_equal 1.year.from_now, user.notifications.last.created_at + end + + test "record param is saved to the record polymorphic association and removed from params" do + account = accounts(:primary) + CommentNotification.with(record: account).deliver(user) + notification = Notification.last + assert_equal account, notification.record + assert_not_includes notification.params.keys, :record + end end diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb index 8636f94f..d4079de5 100644 --- a/test/dummy/db/schema.rb +++ b/test/dummy/db/schema.rb @@ -18,6 +18,8 @@ t.integer "account_id" t.string "recipient_type", null: false t.bigint "recipient_id", null: false + t.string "record_type" + t.bigint "record_id" t.string "type" if t.respond_to? :jsonb t.jsonb "params" From d1c53767cbfd7198f5ebe1239699b1ba93d3d74c Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Thu, 7 Dec 2023 09:50:49 -0600 Subject: [PATCH 13/39] Rename notification to notifier generator --- .../noticed/{notification_generator.rb => notifier_generator.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/generators/noticed/{notification_generator.rb => notifier_generator.rb} (100%) diff --git a/lib/generators/noticed/notification_generator.rb b/lib/generators/noticed/notifier_generator.rb similarity index 100% rename from lib/generators/noticed/notification_generator.rb rename to lib/generators/noticed/notifier_generator.rb From 47b032d8f2a7d7e677d4bea592bba0855fe66280 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 22 Dec 2023 12:15:47 -0600 Subject: [PATCH 14/39] WIP version 2 --- .github/workflows/ci.yml | 3 - Gemfile.lock | 191 +++++++------- app/jobs/.keep | 0 app/jobs/noticed/application_job.rb | 9 + app/jobs/noticed/event_job.rb | 19 ++ app/models/.keep | 0 app/models/concerns/.keep | 0 app/models/concerns/noticed/deliverable.rb | 120 +++++++++ app/models/noticed/application_record.rb | 6 + app/models/noticed/event.rb | 12 + app/models/noticed/notification.rb | 13 + app/views/.keep | 0 bin/rails | 13 +- .../20231215190233_create_noticed_tables.rb | 24 ++ gemfiles/rails_6_1.gemfile.lock | 68 +++-- gemfiles/rails_7.gemfile.lock | 2 +- gemfiles/rails_7_1.gemfile.lock | 2 +- gemfiles/rails_main.gemfile.lock | 2 +- lib/generators/noticed/install_generator.rb | 19 ++ lib/generators/noticed/model_generator.rb | 45 ---- lib/generators/noticed/templates/README | 8 +- .../noticed/templates/notifier.rb.tt | 31 ++- lib/noticed.rb | 41 +-- lib/noticed/api_client.rb | 44 ++++ lib/noticed/base.rb | 162 ------------ lib/noticed/bulk_delivery_method.rb | 43 ++++ lib/noticed/bulk_delivery_methods/discord.rb | 17 ++ lib/noticed/bulk_delivery_methods/slack.rb | 17 ++ lib/noticed/bulk_delivery_methods/webhook.rb | 18 ++ lib/noticed/coder.rb | 15 -- lib/noticed/delivery_method.rb | 45 ++++ lib/noticed/delivery_methods/action_cable.rb | 46 +--- lib/noticed/delivery_methods/base.rb | 106 -------- lib/noticed/delivery_methods/database.rb | 36 --- lib/noticed/delivery_methods/email.rb | 54 +--- lib/noticed/delivery_methods/fcm.rb | 87 ++----- lib/noticed/delivery_methods/ios.rb | 124 ++------- .../delivery_methods/microsoft_teams.rb | 27 +- lib/noticed/delivery_methods/slack.rb | 22 +- lib/noticed/delivery_methods/test.rb | 14 +- lib/noticed/delivery_methods/twilio.rb | 51 ---- .../delivery_methods/twilio_messaging.rb | 37 +++ lib/noticed/delivery_methods/vonage.rb | 41 --- lib/noticed/delivery_methods/vonage_sms.rb | 18 ++ lib/noticed/delivery_methods/webhook.rb | 17 ++ lib/noticed/engine.rb | 5 - lib/noticed/has_notifications.rb | 49 ---- lib/noticed/model.rb | 86 ------- lib/noticed/notification_channel.rb | 15 -- lib/noticed/required_options.rb | 21 ++ lib/noticed/text_coder.rb | 16 -- lib/noticed/translation.rb | 4 +- lib/noticed/version.rb | 2 +- lib/tasks/noticed_tasks.rake | 4 - test/bulk_delivery_methods/webhook_test.rb | 58 +++++ test/delivery_method_test.rb | 27 ++ test/delivery_methods/action_cable_test.rb | 55 ++-- test/delivery_methods/base_test.rb | 44 ---- test/delivery_methods/database_test.rb | 88 ------- test/delivery_methods/email_test.rb | 54 ++-- test/delivery_methods/fcm_test.rb | 102 ++++---- test/delivery_methods/ios_test.rb | 117 ++++----- test/delivery_methods/microsoft_teams_test.rb | 65 ++--- test/delivery_methods/slack_test.rb | 60 ++--- .../delivery_methods/twilio_messaging_test.rb | 41 +++ test/delivery_methods/twilio_test.rb | 41 --- test/delivery_methods/vonage_sms_test.rb | 27 ++ test/delivery_methods/vonage_test.rb | 44 ---- test/delivery_methods/webhook_test.rb | 58 +++++ test/dummy/.ruby-version | 1 - .../channels/application_cable/connection.rb | 14 ++ .../app/channels/notification_channel.rb | 9 + .../dummy/app/javascript/packs/application.js | 15 -- test/dummy/app/mailers/user_mailer.rb | 8 +- test/dummy/app/models/account.rb | 2 + test/dummy/app/models/application_record.rb | 6 +- test/dummy/app/models/discord_notification.rb | 2 - test/dummy/app/models/json_notification.rb | 4 - test/dummy/app/models/jsonb_notification.rb | 4 - test/dummy/app/models/notification.rb | 3 - test/dummy/app/models/text_notification.rb | 3 - test/dummy/app/models/user.rb | 9 +- .../app/notifications/comment_notification.rb | 23 -- test/dummy/app/notifiers/comment_notifier.rb | 66 ++++- test/dummy/app/notifiers/receipt_notifier.rb | 9 + .../app/views/layouts/application.html.erb | 3 +- test/dummy/app/views/layouts/mailer.html.erb | 2 +- test/dummy/bin/setup | 8 +- test/dummy/config.ru | 1 + test/dummy/config/application.rb | 24 +- test/dummy/config/credentials/fcm.json | 3 - test/dummy/config/database.yml | 21 +- test/dummy/config/environments/development.rb | 38 ++- test/dummy/config/environments/production.rb | 87 +++---- test/dummy/config/environments/test.rb | 33 ++- .../application_controller_renderer.rb | 8 - test/dummy/config/initializers/assets.rb | 4 +- .../initializers/backtrace_silencers.rb | 7 - .../initializers/content_security_policy.rb | 45 ++-- .../config/initializers/cookies_serializer.rb | 5 - .../initializers/filter_parameter_logging.rb | 8 +- test/dummy/config/initializers/inflections.rb | 8 +- test/dummy/config/initializers/mime_types.rb | 4 - .../config/initializers/permissions_policy.rb | 13 + .../config/initializers/wrap_parameters.rb | 14 -- test/dummy/config/locales/en.yml | 30 ++- test/dummy/config/puma.rb | 45 ++-- test/dummy/config/routes.rb | 10 +- test/dummy/config/spring.rb | 6 - test/dummy/config/storage.yml | 10 +- .../db/migrate/20231215202921_create_users.rb | 9 + .../migrate/20231215202924_create_accounts.rb | 9 + test/dummy/db/schema.rb | 91 ++----- test/dummy/noticed_test | Bin 69632 -> 0 bytes test/dummy/test/fixtures/accounts.yml | 7 + test/dummy/test/fixtures/users.yml | 7 + test/dummy/test/models/account_test.rb | 7 + test/dummy/test/models/user_test.rb | 7 + test/fixtures/accounts.yml | 7 +- test/fixtures/files/.keep | 0 .../files/microsoft_teams/failure.txt | 19 -- .../files/microsoft_teams/success.txt | 21 -- test/fixtures/files/slack/failure.txt | 11 - test/fixtures/files/slack/success.txt | 13 - test/fixtures/files/twilio/failure.txt | 18 -- test/fixtures/files/twilio/success.txt | 20 -- test/fixtures/files/vonage/failure.txt | 10 - test/fixtures/files/vonage/success.txt | 22 -- test/fixtures/noticed/events.yml | 22 ++ test/fixtures/noticed/notifications.yml | 25 ++ test/fixtures/notifications.yml | 21 -- test/fixtures/users.yml | 8 +- test/generators/model_generator_test.rb | 27 -- test/jobs/event_job_test.rb | 7 + test/models/.keep | 0 test/models/noticed/event_test.rb | 40 +++ test/models/noticed/notification_test.rb | 17 ++ test/noticed/coder_test.rb | 30 --- test/noticed/has_notifications_test.rb | 47 ---- test/noticed/model_test.rb | 42 ---- test/noticed_test.rb | 235 +----------------- test/notifier_test.rb | 76 ++++++ test/test_helper.rb | 59 +---- test/translation_test.rb | 17 +- 144 files changed, 1765 insertions(+), 2553 deletions(-) create mode 100644 app/jobs/.keep create mode 100644 app/jobs/noticed/application_job.rb create mode 100644 app/jobs/noticed/event_job.rb create mode 100644 app/models/.keep create mode 100644 app/models/concerns/.keep create mode 100644 app/models/concerns/noticed/deliverable.rb create mode 100644 app/models/noticed/application_record.rb create mode 100644 app/models/noticed/event.rb create mode 100644 app/models/noticed/notification.rb create mode 100644 app/views/.keep create mode 100644 db/migrate/20231215190233_create_noticed_tables.rb create mode 100644 lib/generators/noticed/install_generator.rb delete mode 100644 lib/generators/noticed/model_generator.rb create mode 100644 lib/noticed/api_client.rb delete mode 100644 lib/noticed/base.rb create mode 100644 lib/noticed/bulk_delivery_method.rb create mode 100644 lib/noticed/bulk_delivery_methods/discord.rb create mode 100644 lib/noticed/bulk_delivery_methods/slack.rb create mode 100644 lib/noticed/bulk_delivery_methods/webhook.rb delete mode 100644 lib/noticed/coder.rb create mode 100644 lib/noticed/delivery_method.rb delete mode 100644 lib/noticed/delivery_methods/base.rb delete mode 100644 lib/noticed/delivery_methods/database.rb delete mode 100644 lib/noticed/delivery_methods/twilio.rb create mode 100644 lib/noticed/delivery_methods/twilio_messaging.rb delete mode 100644 lib/noticed/delivery_methods/vonage.rb create mode 100644 lib/noticed/delivery_methods/vonage_sms.rb create mode 100644 lib/noticed/delivery_methods/webhook.rb delete mode 100644 lib/noticed/has_notifications.rb delete mode 100644 lib/noticed/model.rb delete mode 100644 lib/noticed/notification_channel.rb create mode 100644 lib/noticed/required_options.rb delete mode 100644 lib/noticed/text_coder.rb delete mode 100644 lib/tasks/noticed_tasks.rake create mode 100644 test/bulk_delivery_methods/webhook_test.rb create mode 100644 test/delivery_method_test.rb delete mode 100644 test/delivery_methods/base_test.rb delete mode 100644 test/delivery_methods/database_test.rb create mode 100644 test/delivery_methods/twilio_messaging_test.rb delete mode 100644 test/delivery_methods/twilio_test.rb create mode 100644 test/delivery_methods/vonage_sms_test.rb delete mode 100644 test/delivery_methods/vonage_test.rb create mode 100644 test/delivery_methods/webhook_test.rb delete mode 100644 test/dummy/.ruby-version create mode 100644 test/dummy/app/channels/notification_channel.rb delete mode 100644 test/dummy/app/javascript/packs/application.js delete mode 100644 test/dummy/app/models/discord_notification.rb delete mode 100644 test/dummy/app/models/json_notification.rb delete mode 100644 test/dummy/app/models/jsonb_notification.rb delete mode 100644 test/dummy/app/models/notification.rb delete mode 100644 test/dummy/app/models/text_notification.rb delete mode 100644 test/dummy/app/notifications/comment_notification.rb create mode 100644 test/dummy/app/notifiers/receipt_notifier.rb delete mode 100644 test/dummy/config/credentials/fcm.json delete mode 100644 test/dummy/config/initializers/application_controller_renderer.rb delete mode 100644 test/dummy/config/initializers/backtrace_silencers.rb delete mode 100644 test/dummy/config/initializers/cookies_serializer.rb delete mode 100644 test/dummy/config/initializers/mime_types.rb create mode 100644 test/dummy/config/initializers/permissions_policy.rb delete mode 100644 test/dummy/config/initializers/wrap_parameters.rb delete mode 100644 test/dummy/config/spring.rb create mode 100644 test/dummy/db/migrate/20231215202921_create_users.rb create mode 100644 test/dummy/db/migrate/20231215202924_create_accounts.rb delete mode 100644 test/dummy/noticed_test create mode 100644 test/dummy/test/fixtures/accounts.yml create mode 100644 test/dummy/test/fixtures/users.yml create mode 100644 test/dummy/test/models/account_test.rb create mode 100644 test/dummy/test/models/user_test.rb create mode 100644 test/fixtures/files/.keep delete mode 100644 test/fixtures/files/microsoft_teams/failure.txt delete mode 100644 test/fixtures/files/microsoft_teams/success.txt delete mode 100644 test/fixtures/files/slack/failure.txt delete mode 100644 test/fixtures/files/slack/success.txt delete mode 100644 test/fixtures/files/twilio/failure.txt delete mode 100644 test/fixtures/files/twilio/success.txt delete mode 100644 test/fixtures/files/vonage/failure.txt delete mode 100644 test/fixtures/files/vonage/success.txt create mode 100644 test/fixtures/noticed/events.yml create mode 100644 test/fixtures/noticed/notifications.yml delete mode 100644 test/fixtures/notifications.yml delete mode 100644 test/generators/model_generator_test.rb create mode 100644 test/jobs/event_job_test.rb create mode 100644 test/models/.keep create mode 100644 test/models/noticed/event_test.rb create mode 100644 test/models/noticed/notification_test.rb delete mode 100644 test/noticed/coder_test.rb delete mode 100644 test/noticed/has_notifications_test.rb delete mode 100644 test/noticed/model_test.rb create mode 100644 test/notifier_test.rb diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd792733..d9c28ab1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,6 @@ jobs: matrix: ruby: ['3.0', '3.1', '3.2'] gemfile: - - rails_6_1 - rails_7 - rails_7_1 - rails_main @@ -52,7 +51,6 @@ jobs: matrix: ruby: ['3.0', '3.1', '3.2'] gemfile: - - rails_6_1 - rails_7 - rails_7_1 - rails_main @@ -98,7 +96,6 @@ jobs: matrix: ruby: ['3.0', '3.1', '3.2'] gemfile: - - rails_6_1 - rails_7 - rails_7_1 - rails_main diff --git a/Gemfile.lock b/Gemfile.lock index 0b09d2d3..02c29c87 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,76 +1,77 @@ PATH remote: . specs: - noticed (1.6.3) + noticed (2.0.0) rails (>= 6.1.0) GEM remote: https://rubygems.org/ specs: - actioncable (7.1.1) - actionpack (= 7.1.1) - activesupport (= 7.1.1) + actioncable (7.1.2) + actionpack (= 7.1.2) + activesupport (= 7.1.2) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.1.1) - actionpack (= 7.1.1) - activejob (= 7.1.1) - activerecord (= 7.1.1) - activestorage (= 7.1.1) - activesupport (= 7.1.1) + actionmailbox (7.1.2) + actionpack (= 7.1.2) + activejob (= 7.1.2) + activerecord (= 7.1.2) + activestorage (= 7.1.2) + activesupport (= 7.1.2) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.1.1) - actionpack (= 7.1.1) - actionview (= 7.1.1) - activejob (= 7.1.1) - activesupport (= 7.1.1) + actionmailer (7.1.2) + actionpack (= 7.1.2) + actionview (= 7.1.2) + activejob (= 7.1.2) + activesupport (= 7.1.2) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.2) - actionpack (7.1.1) - actionview (= 7.1.1) - activesupport (= 7.1.1) + actionpack (7.1.2) + actionview (= 7.1.2) + activesupport (= 7.1.2) nokogiri (>= 1.8.5) + racc rack (>= 2.2.4) rack-session (>= 1.0.1) rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - actiontext (7.1.1) - actionpack (= 7.1.1) - activerecord (= 7.1.1) - activestorage (= 7.1.1) - activesupport (= 7.1.1) + actiontext (7.1.2) + actionpack (= 7.1.2) + activerecord (= 7.1.2) + activestorage (= 7.1.2) + activesupport (= 7.1.2) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.1.1) - activesupport (= 7.1.1) + actionview (7.1.2) + activesupport (= 7.1.2) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (7.1.1) - activesupport (= 7.1.1) + activejob (7.1.2) + activesupport (= 7.1.2) globalid (>= 0.3.6) - activemodel (7.1.1) - activesupport (= 7.1.1) - activerecord (7.1.1) - activemodel (= 7.1.1) - activesupport (= 7.1.1) + activemodel (7.1.2) + activesupport (= 7.1.2) + activerecord (7.1.2) + activemodel (= 7.1.2) + activesupport (= 7.1.2) timeout (>= 0.4.0) - activestorage (7.1.1) - actionpack (= 7.1.1) - activejob (= 7.1.1) - activerecord (= 7.1.1) - activesupport (= 7.1.1) + activestorage (7.1.2) + actionpack (= 7.1.2) + activejob (= 7.1.2) + activerecord (= 7.1.2) + activesupport (= 7.1.2) marcel (~> 1.0) - activesupport (7.1.1) + activesupport (7.1.2) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) @@ -80,7 +81,7 @@ GEM minitest (>= 5.1) mutex_m tzinfo (~> 2.0) - addressable (2.8.5) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) apnotic (1.7.1) connection_pool (~> 2) @@ -90,8 +91,8 @@ GEM rake thor (>= 0.14.0) ast (2.4.2) - base64 (0.1.1) - bigdecimal (3.1.4) + base64 (0.2.0) + bigdecimal (3.1.5) builder (3.2.4) byebug (11.1.3) concurrent-ruby (1.2.2) @@ -99,36 +100,39 @@ GEM crack (0.4.5) rexml crass (1.0.6) - date (3.3.3) - drb (2.1.1) + date (3.3.4) + drb (2.2.0) ruby2_keywords erubi (1.12.0) - faraday (2.7.11) + faraday (2.8.1) base64 faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) faraday-net_http (3.0.2) globalid (1.2.1) activesupport (>= 6.1) - googleauth (1.8.1) - faraday (>= 0.17.3, < 3.a) + google-cloud-env (2.1.0) + faraday (>= 1.0, < 3.a) + googleauth (1.9.1) + faraday (>= 1.0, < 3.a) + google-cloud-env (~> 2.1) jwt (>= 1.4, < 3.0) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) - hashdiff (1.0.1) + hashdiff (1.1.0) http-2 (0.11.0) i18n (1.14.1) concurrent-ruby (~> 1.0) - io-console (0.6.0) - irb (1.8.3) + io-console (0.7.1) + irb (1.11.0) rdoc reline (>= 0.3.8) - json (2.6.3) + json (2.7.1) jwt (2.7.1) language_server-protocol (3.17.0.3) lint_roller (1.1.0) - loofah (2.21.4) + loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -140,36 +144,36 @@ GEM mini_mime (1.1.5) minitest (5.20.0) multi_json (1.15.0) - mutex_m (0.1.2) + mutex_m (0.2.0) mysql2 (0.5.5) net-http2 (0.18.5) http-2 (~> 0.11) - net-imap (0.4.2) + net-imap (0.4.8) date net-protocol net-pop (0.1.2) net-protocol - net-protocol (0.2.1) + net-protocol (0.2.2) timeout net-smtp (0.4.0) net-protocol - nio4r (2.5.9) - nokogiri (1.15.4-arm64-darwin) + nio4r (2.7.0) + nokogiri (1.15.5-arm64-darwin) racc (~> 1.4) - nokogiri (1.15.4-x86_64-darwin) + nokogiri (1.15.5-x86_64-darwin) racc (~> 1.4) - nokogiri (1.15.4-x86_64-linux) + nokogiri (1.15.5-x86_64-linux) racc (~> 1.4) os (1.1.4) - parallel (1.23.0) + parallel (1.24.0) parser (3.2.2.4) ast (~> 2.4.1) racc pg (1.5.4) - psych (5.1.1.1) + psych (5.1.2) stringio - public_suffix (5.0.3) - racc (1.7.1) + public_suffix (5.0.4) + racc (1.7.3) rack (3.0.8) rack-session (2.0.0) rack (>= 3.0.0) @@ -178,20 +182,20 @@ GEM rackup (2.1.0) rack (>= 3) webrick (~> 1.8) - rails (7.1.1) - actioncable (= 7.1.1) - actionmailbox (= 7.1.1) - actionmailer (= 7.1.1) - actionpack (= 7.1.1) - actiontext (= 7.1.1) - actionview (= 7.1.1) - activejob (= 7.1.1) - activemodel (= 7.1.1) - activerecord (= 7.1.1) - activestorage (= 7.1.1) - activesupport (= 7.1.1) + rails (7.1.2) + actioncable (= 7.1.2) + actionmailbox (= 7.1.2) + actionmailer (= 7.1.2) + actionpack (= 7.1.2) + actiontext (= 7.1.2) + actionview (= 7.1.2) + activejob (= 7.1.2) + activemodel (= 7.1.2) + activerecord (= 7.1.2) + activestorage (= 7.1.2) + activesupport (= 7.1.2) bundler (>= 1.15.0) - railties (= 7.1.1) + railties (= 7.1.2) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -199,35 +203,34 @@ GEM rails-html-sanitizer (1.6.0) loofah (~> 2.21) nokogiri (~> 1.14) - railties (7.1.1) - actionpack (= 7.1.1) - activesupport (= 7.1.1) + railties (7.1.2) + actionpack (= 7.1.2) + activesupport (= 7.1.2) irb rackup (>= 1.0.0) rake (>= 12.2) thor (~> 1.0, >= 1.2.2) zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.0.6) - rdoc (6.5.0) + rake (13.1.0) + rdoc (6.6.2) psych (>= 4.0.0) - regexp_parser (2.8.2) - reline (0.3.9) + regexp_parser (2.8.3) + reline (0.4.1) io-console (~> 0.5) rexml (3.2.6) - rubocop (1.56.4) - base64 (~> 0.1.1) + rubocop (1.57.2) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.2.2.3) + parser (>= 3.2.2.4) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) rubocop-ast (>= 1.28.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.29.0) + rubocop-ast (1.30.0) parser (>= 3.2.1.0) rubocop-performance (1.19.1) rubocop (>= 1.7.0, < 2.0) @@ -239,13 +242,13 @@ GEM faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - sqlite3 (1.6.7-arm64-darwin) - sqlite3 (1.6.7-x86_64-darwin) - sqlite3 (1.6.7-x86_64-linux) - standard (1.31.2) + sqlite3 (1.6.9-arm64-darwin) + sqlite3 (1.6.9-x86_64-darwin) + sqlite3 (1.6.9-x86_64-linux) + standard (1.32.1) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) - rubocop (~> 1.56.4) + rubocop (~> 1.57.2) standard-custom (~> 1.0.0) standard-performance (~> 1.2) standard-custom (1.0.2) @@ -254,9 +257,9 @@ GEM standard-performance (1.2.1) lint_roller (~> 1.1) rubocop-performance (~> 1.19.1) - stringio (3.0.8) + stringio (3.1.0) thor (1.3.0) - timeout (0.4.0) + timeout (0.4.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) @@ -289,4 +292,4 @@ DEPENDENCIES webmock BUNDLED WITH - 2.4.21 + 2.5.3 diff --git a/app/jobs/.keep b/app/jobs/.keep new file mode 100644 index 00000000..e69de29b diff --git a/app/jobs/noticed/application_job.rb b/app/jobs/noticed/application_job.rb new file mode 100644 index 00000000..4d94d406 --- /dev/null +++ b/app/jobs/noticed/application_job.rb @@ -0,0 +1,9 @@ +module Noticed + class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + discard_on ActiveJob::DeserializationError + end +end diff --git a/app/jobs/noticed/event_job.rb b/app/jobs/noticed/event_job.rb new file mode 100644 index 00000000..02ee7483 --- /dev/null +++ b/app/jobs/noticed/event_job.rb @@ -0,0 +1,19 @@ +module Noticed + class EventJob < ApplicationJob + queue_as :default + + def perform(event) + # Enqueue bulk deliveries + event.bulk_delivery_methods.each do |_, deliver_by| + deliver_by.perform_later(event) + end + + # Enqueue individual deliveries + event.notifications.each do |notification| + event.delivery_methods.each do |_, deliver_by| + deliver_by.perform_later(notification) + end + end + end + end +end diff --git a/app/models/.keep b/app/models/.keep new file mode 100644 index 00000000..e69de29b diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/app/models/concerns/noticed/deliverable.rb b/app/models/concerns/noticed/deliverable.rb new file mode 100644 index 00000000..b8e50d03 --- /dev/null +++ b/app/models/concerns/noticed/deliverable.rb @@ -0,0 +1,120 @@ +module Noticed + module Deliverable + extend ActiveSupport::Concern + + class DeliverBy + attr_reader :name, :config, :bulk + + def initialize(name, config, bulk: false) + @name, @config, @bulk, = name, config, bulk + end + + def constant + namespace = bulk ? "Noticed::BulkDeliveryMethods" : "Noticed::DeliveryMethods" + config.fetch(:class, [namespace, name.to_s.camelize].join("::")).constantize + end + + def validate! + constant.required_option_names.each do |option| + raise ValidationError, "option `#{option}` must be set for `deliver_by :#{name}`" unless config.has_key?(option) + end + end + + def perform_later(event) + constant.perform_later(name, event) + end + end + + included do + class_attribute :bulk_delivery_methods, instance_writer: false, default: {} + class_attribute :delivery_methods, instance_writer: false, default: {} + class_attribute :required_param_names, instance_writer: false, default: [] + end + + class_methods do + def inherited(base) + base.bulk_delivery_methods = bulk_delivery_methods.dup + base.delivery_methods = delivery_methods.dup + base.required_param_names = required_param_names.dup + super + end + + def bulk_deliver_by(name, options = {}) + raise NameError, "#{name} has already been used for this Notifier." if bulk_delivery_methods.has_key?(name) + + config = ActiveSupport::OrderedOptions.new.merge(options) + yield config if block_given? + bulk_delivery_methods[name] = DeliverBy.new(name, config, bulk: true) + end + + def deliver_by(name, options = {}) + raise NameError, "#{name} has already been used for this Notifier." if delivery_methods.has_key?(name) + + config = ActiveSupport::OrderedOptions.new.merge(options) + yield config if block_given? + delivery_methods[name] = DeliverBy.new(name, config) + end + + def required_params(*names) + required_param_names.concat names + end + alias_method :required_param, :required_params + + def with(params) + record = params.delete(:record) + new(params: params, record: record) + end + + def deliver(recipients = nil) + new.deliver(recipients) + end + end + + def deliver(recipients = nil) + validate! + + recipients_attributes = Array.wrap(recipients).map do |recipient| + { + recipient_type: recipient.class.name, + recipient_id: recipient.id + } + end + + transaction do + save + + if Rails.gem_version >= Gem::Version.new("7.0.0.alpha1") + notifications.insert_all(recipients_attributes, record_timestamps: true) if recipients_attributes.any? + else + time = Time.current + recipients_attributes.each do |attributes| + attributes[:created_at] = time + attributes[:updated_at] = time + end + notifications.insert_all(recipients_attributes) if recipients_attributes.any? + end + end + + # Enqueue delivery job + EventJob.perform_later(self) + + self + end + + def validate! + validate_params! + validate_delivery_methods! + end + + def validate_params! + required_param_names.each do |param_name| + raise ValidationError, "Param `#{param_name}` is required for #{self.class.name}." unless params.has_key?(param_name.to_s) + end + end + + def validate_delivery_methods! + bulk_delivery_methods.values.each(&:validate!) + delivery_methods.values.each(&:validate!) + end + end +end diff --git a/app/models/noticed/application_record.rb b/app/models/noticed/application_record.rb new file mode 100644 index 00000000..9cd3e459 --- /dev/null +++ b/app/models/noticed/application_record.rb @@ -0,0 +1,6 @@ +module Noticed + class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true + self.table_name_prefix = "noticed_" + end +end diff --git a/app/models/noticed/event.rb b/app/models/noticed/event.rb new file mode 100644 index 00000000..027956ec --- /dev/null +++ b/app/models/noticed/event.rb @@ -0,0 +1,12 @@ +module Noticed + class Event < ApplicationRecord + include Deliverable + include Translation + include Rails.application.routes.url_helpers + + belongs_to :record, polymorphic: true, optional: true + has_many :notifications, dependent: :delete_all + + attribute :params, default: {} + end +end diff --git a/app/models/noticed/notification.rb b/app/models/noticed/notification.rb new file mode 100644 index 00000000..b8190cc9 --- /dev/null +++ b/app/models/noticed/notification.rb @@ -0,0 +1,13 @@ +module Noticed + class Notification < ApplicationRecord + include Translation + include Rails.application.routes.url_helpers + + belongs_to :event + belongs_to :recipient, polymorphic: true + + delegate :params, :record, to: :event + + attribute :params, default: {} + end +end diff --git a/app/views/.keep b/app/views/.keep new file mode 100644 index 00000000..e69de29b diff --git a/bin/rails b/bin/rails index de173dd1..c263e360 100755 --- a/bin/rails +++ b/bin/rails @@ -2,12 +2,13 @@ # This command will automatically be run when you run "rails" with Rails gems # installed from the root of your application. -ENGINE_ROOT = File.expand_path('..', __dir__) -APP_PATH = File.expand_path('../test/dummy/config/application', __dir__) +ENGINE_ROOT = File.expand_path("..", __dir__) +ENGINE_PATH = File.expand_path("../lib/noticed/engine", __dir__) +APP_PATH = File.expand_path("../test/dummy/config/application", __dir__) # Set up gems listed in the Gemfile. -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) -require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) +require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) -require 'rails/all' -require 'rails/engine/commands' +require "rails/all" +require "rails/engine/commands" diff --git a/db/migrate/20231215190233_create_noticed_tables.rb b/db/migrate/20231215190233_create_noticed_tables.rb new file mode 100644 index 00000000..0cb4ac61 --- /dev/null +++ b/db/migrate/20231215190233_create_noticed_tables.rb @@ -0,0 +1,24 @@ +class CreateNoticedTables < ActiveRecord::Migration[7.1] + def change + create_table :noticed_events do |t| + t.string :type + t.belongs_to :record, polymorphic: true + if t.respond_to?(:jsonb) + t.jsonb :params + else + t.json :params + end + + t.timestamps + end + + create_table :noticed_notifications do |t| + t.belongs_to :event, null: false + t.belongs_to :recipient, polymorphic: true, null: false + t.datetime :read_at + t.datetime :seen_at + + t.timestamps + end + end +end diff --git a/gemfiles/rails_6_1.gemfile.lock b/gemfiles/rails_6_1.gemfile.lock index 6076a5c3..39f3c1c9 100644 --- a/gemfiles/rails_6_1.gemfile.lock +++ b/gemfiles/rails_6_1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - noticed (1.6.3) + noticed (2.0.0) rails (>= 6.1.0) GEM @@ -66,7 +66,7 @@ GEM minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) - addressable (2.8.5) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) apnotic (1.7.1) connection_pool (~> 2) @@ -76,7 +76,7 @@ GEM rake thor (>= 0.14.0) ast (2.4.2) - base64 (0.1.1) + base64 (0.2.0) builder (3.2.4) byebug (11.1.3) concurrent-ruby (1.2.2) @@ -84,30 +84,33 @@ GEM crack (0.4.5) rexml crass (1.0.6) - date (3.3.3) + date (3.3.4) erubi (1.12.0) - faraday (2.7.11) + faraday (2.8.1) base64 faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) faraday-net_http (3.0.2) globalid (1.2.1) activesupport (>= 6.1) - googleauth (1.8.1) - faraday (>= 0.17.3, < 3.a) + google-cloud-env (2.1.0) + faraday (>= 1.0, < 3.a) + googleauth (1.9.1) + faraday (>= 1.0, < 3.a) + google-cloud-env (~> 2.1) jwt (>= 1.4, < 3.0) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) - hashdiff (1.0.1) + hashdiff (1.1.0) http-2 (0.11.0) i18n (1.14.1) concurrent-ruby (~> 1.0) - json (2.6.3) + json (2.7.1) jwt (2.7.1) language_server-protocol (3.17.0.3) lint_roller (1.1.0) - loofah (2.21.4) + loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -123,30 +126,26 @@ GEM mysql2 (0.5.5) net-http2 (0.18.5) http-2 (~> 0.11) - net-imap (0.4.2) + net-imap (0.4.8) date net-protocol net-pop (0.1.2) net-protocol - net-protocol (0.2.1) + net-protocol (0.2.2) timeout net-smtp (0.4.0) net-protocol - nio4r (2.5.9) - nokogiri (1.15.4-arm64-darwin) - racc (~> 1.4) - nokogiri (1.15.4-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.15.4-x86_64-linux) + nio4r (2.7.0) + nokogiri (1.15.5-arm64-darwin) racc (~> 1.4) os (1.1.4) - parallel (1.23.0) + parallel (1.24.0) parser (3.2.2.4) ast (~> 2.4.1) racc pg (1.5.4) - public_suffix (5.0.3) - racc (1.7.1) + public_suffix (5.0.4) + racc (1.7.3) rack (2.2.8) rack-test (2.1.0) rack (>= 1.3) @@ -179,22 +178,21 @@ GEM rake (>= 12.2) thor (~> 1.0) rainbow (3.1.1) - rake (13.0.6) - regexp_parser (2.8.2) + rake (13.1.0) + regexp_parser (2.8.3) rexml (3.2.6) - rubocop (1.56.4) - base64 (~> 0.1.1) + rubocop (1.57.2) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.2.2.3) + parser (>= 3.2.2.4) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) rubocop-ast (>= 1.28.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.29.0) + rubocop-ast (1.30.0) parser (>= 3.2.1.0) rubocop-performance (1.19.1) rubocop (>= 1.7.0, < 2.0) @@ -213,13 +211,11 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - sqlite3 (1.6.7-arm64-darwin) - sqlite3 (1.6.7-x86_64-darwin) - sqlite3 (1.6.7-x86_64-linux) - standard (1.31.2) + sqlite3 (1.6.9-arm64-darwin) + standard (1.32.1) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) - rubocop (~> 1.56.4) + rubocop (~> 1.57.2) standard-custom (~> 1.0.0) standard-performance (~> 1.2) standard-custom (1.0.2) @@ -229,7 +225,7 @@ GEM lint_roller (~> 1.1) rubocop-performance (~> 1.19.1) thor (1.3.0) - timeout (0.4.0) + timeout (0.4.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) @@ -243,9 +239,7 @@ GEM zeitwerk (2.6.12) PLATFORMS - arm64-darwin-22 - x86_64-darwin-22 - x86_64-linux + arm64-darwin DEPENDENCIES apnotic (~> 1.7) @@ -262,4 +256,4 @@ DEPENDENCIES webmock BUNDLED WITH - 2.4.21 + 2.5.3 diff --git a/gemfiles/rails_7.gemfile.lock b/gemfiles/rails_7.gemfile.lock index 828994fe..57a114d3 100644 --- a/gemfiles/rails_7.gemfile.lock +++ b/gemfiles/rails_7.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - noticed (1.6.3) + noticed (2.0.0) rails (>= 6.1.0) GEM diff --git a/gemfiles/rails_7_1.gemfile.lock b/gemfiles/rails_7_1.gemfile.lock index 1621e11d..75662764 100644 --- a/gemfiles/rails_7_1.gemfile.lock +++ b/gemfiles/rails_7_1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - noticed (1.6.3) + noticed (2.0.0) rails (>= 6.1.0) GEM diff --git a/gemfiles/rails_main.gemfile.lock b/gemfiles/rails_main.gemfile.lock index b0df4c1e..789fd195 100644 --- a/gemfiles/rails_main.gemfile.lock +++ b/gemfiles/rails_main.gemfile.lock @@ -102,7 +102,7 @@ GIT PATH remote: .. specs: - noticed (1.6.3) + noticed (2.0.0) rails (>= 6.1.0) GEM diff --git a/lib/generators/noticed/install_generator.rb b/lib/generators/noticed/install_generator.rb new file mode 100644 index 00000000..77b56abc --- /dev/null +++ b/lib/generators/noticed/install_generator.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Noticed + module Generators + class ModelGenerator < Rails::Generators::Base + include Rails::Generators::ResourceHelpers + + source_root File.expand_path("../templates", __FILE__) + + def create_migrations + rails_command "railties:install:migrations FROM=noticed", inline: true + end + + def done + readme "README" if behavior == :invoke + end + end + end +end diff --git a/lib/generators/noticed/model_generator.rb b/lib/generators/noticed/model_generator.rb deleted file mode 100644 index 5ccdbc82..00000000 --- a/lib/generators/noticed/model_generator.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -require "rails/generators/named_base" - -module Noticed - module Generators - class ModelGenerator < Rails::Generators::NamedBase - include Rails::Generators::ResourceHelpers - - source_root File.expand_path("../templates", __FILE__) - - desc "Generates a Notification model for storing notifications." - - argument :name, type: :string, default: "Notification", banner: "Notification" - argument :attributes, type: :array, default: [], banner: "field:type field:type" - - def generate_notification - generate :model, name, "recipient:references{polymorphic}", "type", "record:references{polymorphic}", "params:json", "read_at:datetime:index", *attributes - end - - def add_noticed_model - inject_into_class model_path, class_name, " include Noticed::Model\n" - end - - def add_not_nullable - migration_path = Dir.glob(Rails.root.join("db/migrate/*")).max_by { |f| File.mtime(f) } - - # Force is required because null: false already exists in the file and Thor isn't smart enough to tell the difference - insert_into_file migration_path, after: "t.string :type", force: true do - ", null: false" - end - end - - def done - readme "README" if behavior == :invoke - end - - private - - def model_path - @model_path ||= File.join("app", "models", "#{file_path}.rb") - end - end - end -end diff --git a/lib/generators/noticed/templates/README b/lib/generators/noticed/templates/README index e7e618a0..bb6d1931 100644 --- a/lib/generators/noticed/templates/README +++ b/lib/generators/noticed/templates/README @@ -1,8 +1,8 @@ -🚚 Your notifications database model has been generated! +🚚 You're ready to start sending notifications! Next steps: -1. Run "rails db:migrate" -2. Add "has_many :notifications, as: :recipient, dependent: :destroy" to your User model(s). -2. Add "has_many :notifications, as: :record, dependent: :destroy" to your model(s) that notifications are about. +1. Run `rails db:migrate` +2. Add `has_many :notifications, as: :recipient, dependent: :destroy, class_name: "Noticed::Notification"` to your User model(s). +2. Add `has_many :notifications, as: :record, dependent: :destroy, class_name: "Noticed::Event"` to your model(s) that notifications reference. 3. Generate notifiers with "rails g noticed:notifier" diff --git a/lib/generators/noticed/templates/notifier.rb.tt b/lib/generators/noticed/templates/notifier.rb.tt index 77131f10..37e0b847 100644 --- a/lib/generators/noticed/templates/notifier.rb.tt +++ b/lib/generators/noticed/templates/notifier.rb.tt @@ -1,27 +1,24 @@ # To deliver this notification: # -# <%= class_name %>.with(post: @post).deliver_later(current_user) -# <%= class_name %>.with(post: @post).deliver(current_user) +# <%= class_name %>.with(record: @post, message: "New post").deliver(User.all) -class <%= class_name %> < Noticed::Base +class <%= class_name %> < Noticed::Event # Add your delivery methods # - # deliver_by :database - # deliver_by :email, mailer: "UserMailer" - # deliver_by :slack - # deliver_by :custom, class: "MyDeliveryMethod" - - # Add required params - # - # param :post - - # Define helper methods to make rendering easier. + # deliver_by :email do |config| + # config.mailer = "UserMailer" + # config.method = "new_post" + # end # - # def message - # t(".message") + # bulk_deliver_by :slack do |config| + # config.url = -> { Rails.application.credentials.slack_webhook_url } # end # - # def url - # post_path(params[:post]) + # deliver_by :custom do |config| + # config.class = "MyDeliveryMethod" # end + + # Add required params + # + # required_param :message end diff --git a/lib/noticed.rb b/lib/noticed.rb index c83f6e81..e439ffe7 100644 --- a/lib/noticed.rb +++ b/lib/noticed.rb @@ -1,32 +1,43 @@ -require "active_job/arguments" +require "noticed/version" require "noticed/engine" module Noticed - autoload :Base, "noticed/base" - autoload :Coder, "noticed/coder" - autoload :HasNotifications, "noticed/has_notifications" - autoload :Model, "noticed/model" - autoload :TextCoder, "noticed/text_coder" + include ActiveSupport::Deprecation::DeprecatedConstantAccessor + + def self.deprecator # :nodoc: + @deprecator ||= ActiveSupport::Deprecation.new + end + + deprecate_constant :Base, "Noticed::Event", deprecator: deprecator + + autoload :ApiClient, "noticed/api_client" + autoload :BulkDeliveryMethod, "noticed/bulk_delivery_method" + autoload :DeliveryMethod, "noticed/delivery_method" + autoload :RequiredOptions, "noticed/required_options" autoload :Translation, "noticed/translation" - autoload :NotificationChannel, "noticed/notification_channel" + + module BulkDeliveryMethods + autoload :Discord, "noticed/bulk_delivery_methods/discord" + autoload :Slack, "noticed/bulk_delivery_methods/slack" + autoload :Webhook, "noticed/bulk_delivery_methods/webhook" + end module DeliveryMethods + include ActiveSupport::Deprecation::DeprecatedConstantAccessor + deprecate_constant :Base, "Noticed::DeliveryMethod", deprecator: Noticed.deprecator + autoload :ActionCable, "noticed/delivery_methods/action_cable" - autoload :Base, "noticed/delivery_methods/base" - autoload :Database, "noticed/delivery_methods/database" autoload :Email, "noticed/delivery_methods/email" autoload :Fcm, "noticed/delivery_methods/fcm" autoload :Ios, "noticed/delivery_methods/ios" autoload :MicrosoftTeams, "noticed/delivery_methods/microsoft_teams" autoload :Slack, "noticed/delivery_methods/slack" autoload :Test, "noticed/delivery_methods/test" - autoload :Twilio, "noticed/delivery_methods/twilio" - autoload :Vonage, "noticed/delivery_methods/vonage" + autoload :TwilioMessaging, "noticed/delivery_methods/twilio_messaging" + autoload :VonageSms, "noticed/delivery_methods/vonage_sms" + autoload :Webhook, "noticed/delivery_methods/webhook" end - mattr_accessor :parent_class - @@parent_class = "ApplicationJob" - class ValidationError < StandardError end @@ -35,6 +46,8 @@ class ResponseUnsuccessful < StandardError def initialize(response) @response = response + + super "Request to returned #{response.code} response" end end end diff --git a/lib/noticed/api_client.rb b/lib/noticed/api_client.rb new file mode 100644 index 00000000..60dbcf52 --- /dev/null +++ b/lib/noticed/api_client.rb @@ -0,0 +1,44 @@ +require "net/http" + +module Noticed + module ApiClient + extend ActiveSupport::Concern + + # Helper method for making POST requests from delivery methods + # + # Usage: + # post_request("http://example.com", basic_auth: {user:, pass:}, headers: {}, json: {}, form: {}) + # + def post_request(url, args = {}) + args.compact! + + uri = URI(url) + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true if uri.instance_of? URI::HTTPS + + headers = args.delete(:headers) || {} + headers["Content-Type"] = "application/json" if args.has_key?(:json) + + request = Net::HTTP::Post.new(uri.request_uri, headers) + + if (basic_auth = args.delete(:basic_auth)) + request.basic_auth basic_auth.fetch(:user), basic_auth.fetch(:pass) + end + + if (json = args.delete(:json)) + request.body = json.to_json + elsif (form = args.delete(:form)) + request.set_form(form, "multipart/form-data") + end + + logger.debug("POST #{url}") + logger.debug(request.body) + response = http.request(request) + logger.debug("Response: #{response.code}: #{response.body.inspect}") + + raise ResponseUnsuccessful.new(response) unless response.code.start_with?("20") + + response + end + end +end diff --git a/lib/noticed/base.rb b/lib/noticed/base.rb deleted file mode 100644 index 6450e53f..00000000 --- a/lib/noticed/base.rb +++ /dev/null @@ -1,162 +0,0 @@ -module Noticed - class Base - include Translation - include Rails.application.routes.url_helpers - - extend ActiveModel::Callbacks - define_model_callbacks :deliver - - class_attribute :delivery_methods, instance_writer: false, default: [] - class_attribute :param_names, instance_writer: false, default: [] - - # Gives notifications access to the record and recipient during delivery - attr_accessor :record, :recipient - - delegate :read?, :unread?, to: :record - - class << self - def deliver_by(name, options = {}) - config = ActiveSupport::OrderedOptions.new.merge(options) - yield config if block_given? - delivery_methods.push(name: name, options: config) - define_model_callbacks(name) - end - - # Copy delivery methods from parent - def inherited(base) # :nodoc: - base.delivery_methods = delivery_methods.dup - base.param_names = param_names.dup - super - end - - def with(params) - new(params) - end - - # Shortcut for delivering without params - def deliver(recipients) - new.deliver(recipients) - end - - # Shortcut for delivering later without params - def deliver_later(recipients) - new.deliver_later(recipients) - end - - def params(*names) - param_names.concat Array.wrap(names) - end - alias_method :param, :params - end - - def initialize(params = {}) - @params = params - end - - def deliver(recipients) - validate! - - run_callbacks :deliver do - Array.wrap(recipients).uniq.each do |recipient| - run_delivery(recipient, enqueue: false) - end - end - end - - def deliver_later(recipients) - validate! - - run_callbacks :deliver do - Array.wrap(recipients).uniq.each do |recipient| - run_delivery(recipient, enqueue: true) - end - end - end - - def params - @params || {} - end - - def clear_recipient - self.recipient = nil - end - - private - - # Runs all delivery methods for a notification - def run_delivery(recipient, enqueue: true) - delivery_methods = self.class.delivery_methods.dup - - self.recipient = recipient - - # Run database delivery inline first if it exists so other methods have access to the record - if (index = delivery_methods.find_index { |m| m[:name] == :database }) - delivery_method = delivery_methods.delete_at(index) - self.record = run_delivery_method(delivery_method, recipient: recipient, enqueue: false, record: nil) - end - - delivery_methods.each do |delivery_method| - run_delivery_method(delivery_method, recipient: recipient, enqueue: enqueue, record: record) - end - end - - # Actually runs an individual delivery - def run_delivery_method(delivery_method, recipient:, enqueue:, record:) - args = { - notification_class: self.class.name, - options: delivery_method[:options], - params: params, - recipient: recipient, - record: record - } - - run_callbacks delivery_method[:name] do - method = delivery_method_for(delivery_method[:name], delivery_method[:options]) - - # If the queue is `nil`, ActiveJob will use a default queue name. - queue = delivery_method.dig(:options, :queue) - - # Always perfrom later if a delay is present - if (delay = delivery_method.dig(:options, :delay)) - # Dynamic delays with metho calls or - delay = send(delay) if delay.is_a? Symbol - - method.set(wait: delay, queue: queue).perform_later(args) - elsif enqueue - method.set(queue: queue).perform_later(args) - else - method.perform_now(args) - end - end - end - - def delivery_method_for(name, options) - if options[:class] - options[:class].constantize - else - "Noticed::DeliveryMethods::#{name.to_s.camelize}".constantize - end - end - - def validate! - validate_params_present! - validate_options_of_delivery_methods! - end - - # Validates that all params are present - def validate_params_present! - self.class.param_names.each do |param_name| - if params[param_name].nil? - raise ValidationError, "#{param_name} is missing." - end - end - end - - def validate_options_of_delivery_methods! - delivery_methods.each do |delivery_method| - method = delivery_method_for(delivery_method[:name], delivery_method[:options]) - method.validate!(delivery_method[:options]) - end - end - end -end diff --git a/lib/noticed/bulk_delivery_method.rb b/lib/noticed/bulk_delivery_method.rb new file mode 100644 index 00000000..390f96c4 --- /dev/null +++ b/lib/noticed/bulk_delivery_method.rb @@ -0,0 +1,43 @@ +module Noticed + class BulkDeliveryMethod < ApplicationJob + include ApiClient + include RequiredOptions + + class_attribute :logger, default: Rails.logger + + attr_reader :config, :event + + def perform(delivery_method_name, event) + @event = event + @config = event.bulk_delivery_methods.fetch(delivery_method_name).config + + deliver + end + + def deliver + raise NotImplementedError, "Bulk delivery methods must implement the `deliver` method" + end + + def fetch_constant(name) + option = config[name] + option.is_a?(String) ? option.constantize : option + end + + def evaluate_option(name) + option = config[name] + + # Evaluate Proc within the context of the notifier + if option&.respond_to?(:call) + event.instance_exec(&option) + + # Call method if symbol and matching method + elsif option.is_a?(Symbol) && event.respond_to?(option) + event.send(option) + + # Return the value + else + option + end + end + end +end diff --git a/lib/noticed/bulk_delivery_methods/discord.rb b/lib/noticed/bulk_delivery_methods/discord.rb new file mode 100644 index 00000000..525b7426 --- /dev/null +++ b/lib/noticed/bulk_delivery_methods/discord.rb @@ -0,0 +1,17 @@ +module Noticed + module BulkDeliveryMethods + class Slack < BulkDeliveryMethod + DEFAULT_URL = "https://slack.com/api/chat.postMessage" + + required_options :json + + def deliver + post_request url, headers: evaluate_option(:headers), json: evaluate_option(:json) + end + + def url + evaluate_option(:url) || DEFAULT_URL + end + end + end +end diff --git a/lib/noticed/bulk_delivery_methods/slack.rb b/lib/noticed/bulk_delivery_methods/slack.rb new file mode 100644 index 00000000..525b7426 --- /dev/null +++ b/lib/noticed/bulk_delivery_methods/slack.rb @@ -0,0 +1,17 @@ +module Noticed + module BulkDeliveryMethods + class Slack < BulkDeliveryMethod + DEFAULT_URL = "https://slack.com/api/chat.postMessage" + + required_options :json + + def deliver + post_request url, headers: evaluate_option(:headers), json: evaluate_option(:json) + end + + def url + evaluate_option(:url) || DEFAULT_URL + end + end + end +end diff --git a/lib/noticed/bulk_delivery_methods/webhook.rb b/lib/noticed/bulk_delivery_methods/webhook.rb new file mode 100644 index 00000000..c5d006fc --- /dev/null +++ b/lib/noticed/bulk_delivery_methods/webhook.rb @@ -0,0 +1,18 @@ +module Noticed + module BulkDeliveryMethods + class Webhook < BulkDeliveryMethod + required_options :url + + def deliver + Rails.logger.debug(evaluate_option(:json)) + post_request( + evaluate_option(:url), + basic_auth: evaluate_option(:basic_auth), + headers: evaluate_option(:headers), + json: evaluate_option(:json), + form: evaluate_option(:form) + ) + end + end + end +end diff --git a/lib/noticed/coder.rb b/lib/noticed/coder.rb deleted file mode 100644 index b4ad0926..00000000 --- a/lib/noticed/coder.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Noticed - class Coder - def self.load(data) - return if data.nil? - ActiveJob::Arguments.send(:deserialize_argument, data) - rescue ActiveRecord::RecordNotFound => error - {noticed_error: error.message, original_params: data} - end - - def self.dump(data) - return if data.nil? - ActiveJob::Arguments.send(:serialize_argument, data) - end - end -end diff --git a/lib/noticed/delivery_method.rb b/lib/noticed/delivery_method.rb new file mode 100644 index 00000000..9ca12e5c --- /dev/null +++ b/lib/noticed/delivery_method.rb @@ -0,0 +1,45 @@ +module Noticed + class DeliveryMethod < ApplicationJob + include ApiClient + include RequiredOptions + + class_attribute :logger, default: Rails.logger + + attr_reader :config, :event, :notification + delegate :recipient, to: :notification + + def perform(delivery_method_name, notification) + @notification = notification + @event = notification.event + @config = event.delivery_methods.fetch(delivery_method_name).config + + deliver + end + + def deliver + raise NotImplementedError, "Delivery methods must implement the `deliver` method" + end + + def fetch_constant(name) + option = config[name] + option.is_a?(String) ? option.constantize : option + end + + def evaluate_option(name) + option = config[name] + + # Evaluate Proc within the context of the notification + if option&.respond_to?(:call) + notification.instance_exec(&option) + + # Call method if symbol and matching method + elsif option.is_a?(Symbol) && event.respond_to?(option) + event.send(option) + + # Return the value + else + option + end + end + end +end diff --git a/lib/noticed/delivery_methods/action_cable.rb b/lib/noticed/delivery_methods/action_cable.rb index 7fe40060..d094f240 100644 --- a/lib/noticed/delivery_methods/action_cable.rb +++ b/lib/noticed/delivery_methods/action_cable.rb @@ -1,46 +1,14 @@ module Noticed module DeliveryMethods - class ActionCable < Base - def deliver - channel.broadcast_to stream, format - end + class ActionCable < DeliveryMethod + required_options :channel, :stream, :message - private - - def format - if (method = options[:format]) - notification.send(method) - else - notification.params - end - end - - def channel - @channel ||= begin - value = options[:channel] - case value - when String - value.constantize - when Symbol - notification.send(value) - when Class - value - else - Noticed::NotificationChannel - end - end - end + def deliver + channel = fetch_constant(:channel) + stream = evaluate_option(:stream) + message = evaluate_option(:message) - def stream - value = options[:stream] - case value - when String - value - when Symbol - notification.send(value) - else - recipient - end + channel.broadcast_to stream, message end end end diff --git a/lib/noticed/delivery_methods/base.rb b/lib/noticed/delivery_methods/base.rb deleted file mode 100644 index 4cec4dad..00000000 --- a/lib/noticed/delivery_methods/base.rb +++ /dev/null @@ -1,106 +0,0 @@ -require "net/http" - -module Noticed - module DeliveryMethods - class Base < Noticed.parent_class.constantize - extend ActiveModel::Callbacks - define_model_callbacks :deliver - - class_attribute :option_names, instance_writer: false, default: [] - - attr_reader :notification, :options, :params, :recipient, :record, :logger - - class << self - # Copy option names from parent - def inherited(base) # :nodoc: - base.option_names = option_names.dup - super - end - - def options(*names) - option_names.concat Array.wrap(names) - end - alias_method :option, :options - - def validate!(delivery_method_options) - option_names.each do |option_name| - unless delivery_method_options.key? option_name - raise ValidationError, "option `#{option_name}` must be set for #{name}" - end - end - end - end - - def assign_args(args) - @notification = args.fetch(:notification_class).constantize.new(args[:params]) - @options = args[:options] || {} - @params = args[:params] - @recipient = args[:recipient] - @record = args[:record] - - # Set the default logger - @logger = @options.fetch(:logger, Rails.logger) - - # Make notification aware of database record and recipient during delivery - @notification.record = args[:record] - @notification.recipient = args[:recipient] - self - end - - def perform(args) - assign_args(args) - - return if (condition = @options[:if]) && !@notification.send(condition) - return if (condition = @options[:unless]) && @notification.send(condition) - - run_callbacks :deliver do - deliver - end - end - - def deliver - raise NotImplementedError, "Delivery methods must implement a deliver method" - end - - private - - # Helper method for making POST requests from delivery methods - # - # Usage: - # post("http://example.com", basic_auth: {user:, pass:}, headers: {}, json: {}, form: {}) - # - def post(url, args = {}) - uri = URI(url) - http = Net::HTTP.new(uri.host, uri.port) - http.use_ssl = true if uri.instance_of? URI::HTTPS - - request = Net::HTTP::Post.new(uri.request_uri, args.delete(:headers)) - - if (basic_auth = args.delete(:basic_auth)) - request.basic_auth basic_auth.fetch(:user), basic_auth.fetch(:pass) - end - - if (json = args.delete(:json)) - request.body = json.to_json - elsif (form = args.delete(:form)) - request.set_form(form, "multipart/form-data") - end - - response = http.request(request) - - if options[:debug] - logger.debug("POST #{url}") - logger.debug("Response: #{response.code}: #{response.body}") - end - - if !options[:ignore_failure] && !response.code.start_with?("20") - logger.debug response.code - logger.debug response.body - raise ResponseUnsuccessful.new(response) - end - - response - end - end - end -end diff --git a/lib/noticed/delivery_methods/database.rb b/lib/noticed/delivery_methods/database.rb deleted file mode 100644 index 5a9cf2fb..00000000 --- a/lib/noticed/delivery_methods/database.rb +++ /dev/null @@ -1,36 +0,0 @@ -module Noticed - module DeliveryMethods - class Database < Base - # Must return the database record - def deliver - association_name = options.fetch(:association, :notifications) - recipient.send(association_name).create!(attributes) - end - - def self.validate!(options) - super - - # Must be executed right away so the other deliveries can access the db record - raise ArgumentError, "database delivery cannot be delayed" if options.key?(:delay) - end - - private - - def attributes - record = notification.params.delete(:record) - { - params: notification.params, - record: record, - type: notification.class.name - }.merge(custom_attributes) - end - - def custom_attributes - method = (options[:attributes] || options[:format]) - return {} unless method.present? - - method.respond_to?(:call) ? notification.instance_eval(&method) : notification.send(method) - end - end - end -end diff --git a/lib/noticed/delivery_methods/email.rb b/lib/noticed/delivery_methods/email.rb index a62d3e3e..a0083096 100644 --- a/lib/noticed/delivery_methods/email.rb +++ b/lib/noticed/delivery_methods/email.rb @@ -1,54 +1,18 @@ module Noticed module DeliveryMethods - class Email < Base - option :mailer + class Email < DeliveryMethod + required_options :mailer, :method def deliver - if options[:enqueue] - mailer.with(format).send(mailer_method.to_sym).deliver_later - else - mailer.with(format).send(mailer_method.to_sym).deliver_now - end - end - - private + mailer = fetch_constant(:mailer) + email = evaluate_option(:method) + params = evaluate_option(:params) || notification&.params&.merge(record: notification.record) + args = evaluate_option(:args) - # mailer: "UserMailer" - # mailer: UserMailer - # mailer: :my_method - `my_method` should return Class - def mailer - option = options.fetch(:mailer) - case option - when String - option.constantize - when Symbol - notification.send(option) - else - option - end - end - - # Method should be a symbol - # - # If notification responds to symbol, call that method and use return value - # If notification does not respond to symbol, use the symbol for the mailer method - # Otherwise, use the underscored notification class name as the mailer method - def mailer_method - method_name = options[:method]&.to_sym - if method_name.present? - notification.respond_to?(method_name) ? notification.send(method_name) : method_name - else - notification.class.name.underscore - end - end + mail = mailer.with(params) + mail = args.present? ? mail.send(email, *args) : mail.send(email) - def format - params = if (method = options[:format]) - notification.send(method) - else - notification.params - end - params.merge(recipient: recipient, record: record) + (!!evaluate_option(:enqueue)) ? mail.deliver_later : mail.deliver_now end end end diff --git a/lib/noticed/delivery_methods/fcm.rb b/lib/noticed/delivery_methods/fcm.rb index c18cde28..ad166637 100644 --- a/lib/noticed/delivery_methods/fcm.rb +++ b/lib/noticed/delivery_methods/fcm.rb @@ -1,95 +1,54 @@ require "googleauth" -# class CommentNotifier -# deliver_by :fcm, credentials: Rails.root.join("config/certs/fcm.json"), format: :format_notification -# -# deliver_by :fcm, credentials: :fcm_credentials -# def fcm_credentials -# { project_id: "api-12345" } -# end -# end - module Noticed module DeliveryMethods - class Fcm < Base - BASE_URI = "https://fcm.googleapis.com/v1/projects/" - - option :format + class Fcm < DeliveryMethod + required_option :credentials, :device_tokens, :json def deliver - device_tokens.each do |device_token| - post("#{BASE_URI}#{project_id}/messages:send", headers: {authorization: "Bearer #{access_token}"}, json: {message: format(device_token)}) - rescue ResponseUnsuccessful => exception - if exception.response.code == 404 - cleanup_invalid_token(device_token) - else - raise - end + evaluate_option(:device_tokens).each do |device_token| + send_notification device_token end end - def cleanup_invalid_token(device_token) - return unless notification.respond_to?(:cleanup_device_token) - notification.send(:cleanup_device_token, token: device_token, platform: "fcm") + def send_notification(device_token) + post_request("https://fcm.googleapis.com/v1/projects/#{credentials[:project_id]}/messages:send", + headers: {authorization: "Bearer #{access_token}"}, + json: notification.instance_exec(device_token, &config[:json])) + rescue Noticed::ResponseUnsuccessful => exception + if exception.response.code == "404" && config[:invalid_token] + notification.instance_exec(device_token, &config[:invalid_token]) + else + raise + end end def credentials @credentials ||= begin - option = options[:credentials] - credentials_hash = case option + value = evaluate_option(:credentials) + case value when Hash - option + value when Pathname - load_json(option) + load_json(value) when String - load_json(Rails.root.join(option)) - when Symbol - notification.send(option) + load_json(Rails.root.join(value)) else - Rails.application.credentials.fcm + raise ArgumentError, "FCM credentials must be a Hash, String, Pathname, or Symbol" end - - credentials_hash.symbolize_keys end end def load_json(path) - JSON.parse(File.read(path)) - end - - def project_id - credentials[:project_id] + JSON.parse(File.read(path), symbolize_names: true) end def access_token - token = authorizer.fetch_access_token! - token["access_token"] - end - - def authorizer - @authorizer ||= options.fetch(:authorizer, Google::Auth::ServiceAccountCredentials).make_creds( + @authorizer ||= (evaluate_option(:authorizer) || Google::Auth::ServiceAccountCredentials).make_creds( json_key_io: StringIO.new(credentials.to_json), scope: "https://www.googleapis.com/auth/firebase.messaging" ) - end - - def format(device_token) - notification.send(options[:format], device_token) - end - - def device_tokens - if notification.respond_to?(:fcm_device_tokens) - Array.wrap(notification.fcm_device_tokens(recipient)) - else - raise NoMethodError, <<~MESSAGE - You must implement `fcm_device_tokens` to send Firebase Cloud Messaging notifications - - # This must return an Array of FCM device tokens - def fcm_device_tokens(recipient) - recipient.fcm_device_tokens.pluck(:token) - end - MESSAGE - end + @authorizer.fetch_access_token!["access_token"] end end end diff --git a/lib/noticed/delivery_methods/ios.rb b/lib/noticed/delivery_methods/ios.rb index 42150fe1..70d3e47d 100644 --- a/lib/noticed/delivery_methods/ios.rb +++ b/lib/noticed/delivery_methods/ios.rb @@ -2,26 +2,23 @@ module Noticed module DeliveryMethods - class Ios < Base + class Ios < DeliveryMethod cattr_accessor :connection_pool + required_options :bundle_identifier, :key_id, :team_id, :apns_key, :device_tokens + def deliver - raise ArgumentError, "bundle_identifier is missing" if bundle_identifier.blank? - raise ArgumentError, "key_id is missing" if key_id.blank? - raise ArgumentError, "team_id is missing" if team_id.blank? - raise ArgumentError, "Could not find APN cert at '#{cert_path}'" unless valid_cert_path? + evaluate_option(:device_tokens).each do |device_token| + apn = Apnotic::Notification.new(device_token) + format_notification(apn) - device_tokens.each do |device_token| connection_pool.with do |connection| - apn = Apnotic::Notification.new(device_token) - format_notification(apn) - response = connection.push(apn) raise "Timeout sending iOS push notification" unless response - if bad_token?(response) + if bad_token?(response) && config[:invalid_token] # Allow notification to cleanup invalid iOS device tokens - cleanup_invalid_token(device_token) + notification.instance_exec(device_token, &config[:invalid_token]) elsif !response.ok? raise "Request failed #{response.body}" end @@ -32,41 +29,21 @@ def deliver private def format_notification(apn) - apn.topic = bundle_identifier + apn.topic = evaluate_option(:bundle_identifier) - if (method = options[:format]) - notification.send(method, apn) - elsif params[:message].present? - apn.alert = params[:message] + if (method = config[:format]) + notification.instance_exec(apn, &method) + elsif notification.params.try(:has_key?, :message) + apn.alert = notification.params[:message] else raise ArgumentError, "No message for iOS delivery. Either include message in params or add the 'format' option in 'deliver_by :ios'." end end - def device_tokens - if notification.respond_to?(:ios_device_tokens) - Array.wrap(notification.ios_device_tokens(recipient)) - else - raise NoMethodError, <<~MESSAGE - You must implement `ios_device_tokens` to send iOS notifications - - # This must return an Array of iOS device tokens - def ios_device_tokens(recipient) - recipient.ios_device_tokens.pluck(:token) - end - MESSAGE - end - end - def bad_token?(response) response.status == "410" || (response.status == "400" && response.body["reason"] == "BadDeviceToken") end - def cleanup_invalid_token(token) - return unless notification.respond_to?(:cleanup_device_token) - notification.send(:cleanup_device_token, token: token, platform: "iOS") - end - def connection_pool self.class.connection_pool ||= new_connection_pool end @@ -88,83 +65,18 @@ def new_connection_pool def connection_pool_options { auth_method: :token, - cert_path: cert_path, - key_id: key_id, - team_id: team_id + cert_path: StringIO.new(config.fetch(:apns_key)), + key_id: config.fetch(:key_id), + team_id: config.fetch(:team_id) } end - def bundle_identifier - option = options[:bundle_identifier] - case option - when String - option - when Symbol - notification.send(option) - else - Rails.application.credentials.dig(:ios, :bundle_identifier) - end - end - - def cert_path - option = options[:cert_path] - case option - when String - option - when Symbol - notification.send(option) - else - Rails.root.join("config/certs/ios/apns.p8") - end - end - - def key_id - option = options[:key_id] - case option - when String - option - when Symbol - notification.send(option) - else - Rails.application.credentials.dig(:ios, :key_id) - end - end - - def team_id - option = options[:team_id] - case option - when String - option - when Symbol - notification.send(option) - else - Rails.application.credentials.dig(:ios, :team_id) - end - end - def development? - option = options[:development] - case option - when Symbol - !!notification.send(option) - else - !!option - end - end - - def valid_cert_path? - case cert_path - when File, StringIO - cert_path.size > 0 - else - File.exist?(cert_path) - end + !!evaluate_option(:development) end def pool_options - { - size: options.fetch(:pool_size, 5) - } + {size: evaluate_option(:pool_size) || 5} end end end diff --git a/lib/noticed/delivery_methods/microsoft_teams.rb b/lib/noticed/delivery_methods/microsoft_teams.rb index 02f0c15f..81e64738 100644 --- a/lib/noticed/delivery_methods/microsoft_teams.rb +++ b/lib/noticed/delivery_methods/microsoft_teams.rb @@ -1,31 +1,14 @@ module Noticed module DeliveryMethods - class MicrosoftTeams < Base - def deliver - post(url, json: format) - end + class MicrosoftTeams < DeliveryMethod + required_options :json - private - - def format - if (method = options[:format]) - notification.send(method) - else - { - title: notification.params[:title], - text: notification.params[:text], - sections: notification.params[:sections], - potentialAction: notification.params[:notification_action] - } - end + def deliver + post_request url, headers: evaluate_option(:headers), json: evaluate_option(:json) end def url - if (method = options[:url]) - notification.send(method) - else - Rails.application.credentials.microsoft_teams[:notification_url] - end + evaluate_option(:url) || Rails.application.credentials.dig(:microsoft_teams, :notification_url) end end end diff --git a/lib/noticed/delivery_methods/slack.rb b/lib/noticed/delivery_methods/slack.rb index d8b4d930..2b5045e9 100644 --- a/lib/noticed/delivery_methods/slack.rb +++ b/lib/noticed/delivery_methods/slack.rb @@ -1,26 +1,16 @@ module Noticed module DeliveryMethods - class Slack < Base - def deliver - post(url, json: format) - end + class Slack < DeliveryMethod + DEFAULT_URL = "https://slack.com/api/chat.postMessage" - private + required_options :json - def format - if (method = options[:format]) - notification.send(method) - else - notification.params - end + def deliver + post_request url, headers: evaluate_option(:headers), json: evaluate_option(:json) end def url - if (method = options[:url]) - notification.send(method) - else - Rails.application.credentials.slack[:notification_url] - end + evaluate_option(:url) || DEFAULT_URL end end end diff --git a/lib/noticed/delivery_methods/test.rb b/lib/noticed/delivery_methods/test.rb index bdc9b96e..c3044587 100644 --- a/lib/noticed/delivery_methods/test.rb +++ b/lib/noticed/delivery_methods/test.rb @@ -1,20 +1,10 @@ module Noticed module DeliveryMethods - class Test < Base + class Test < DeliveryMethod class_attribute :delivered, default: [] - class_attribute :callbacks, default: [] - - after_deliver do - self.class.callbacks << :after - end - - def self.clear! - delivered.clear - callbacks.clear - end def deliver - self.class.delivered << notification + delivered << notification end end end diff --git a/lib/noticed/delivery_methods/twilio.rb b/lib/noticed/delivery_methods/twilio.rb deleted file mode 100644 index 61213bee..00000000 --- a/lib/noticed/delivery_methods/twilio.rb +++ /dev/null @@ -1,51 +0,0 @@ -module Noticed - module DeliveryMethods - class Twilio < Base - def deliver - post(url, basic_auth: {user: account_sid, pass: auth_token}, form: format) - end - - private - - def format - if (method = options[:format]) - notification.send(method) - else - { - From: phone_number, - To: recipient.phone_number, - Body: notification.params[:message] - } - end - end - - def url - if (method = options[:url]) - notification.send(method) - else - "https://api.twilio.com/2010-04-01/Accounts/#{account_sid}/Messages.json" - end - end - - def account_sid - credentials.fetch(:account_sid) - end - - def auth_token - credentials.fetch(:auth_token) - end - - def phone_number - credentials.fetch(:phone_number) - end - - def credentials - if (method = options[:credentials]) - notification.send(method) - else - Rails.application.credentials.twilio - end - end - end - end -end diff --git a/lib/noticed/delivery_methods/twilio_messaging.rb b/lib/noticed/delivery_methods/twilio_messaging.rb new file mode 100644 index 00000000..8f802c21 --- /dev/null +++ b/lib/noticed/delivery_methods/twilio_messaging.rb @@ -0,0 +1,37 @@ +module Noticed + module DeliveryMethods + class TwilioMessaging < DeliveryMethod + def deliver + post_request url, basic_auth: {user: account_sid, pass: auth_token}, form: json + end + + def json + evaluate_option(:json) || { + From: phone_number, + To: recipient.phone_number, + Body: params.fetch(:message) + } + end + + def url + evaluate_option(:url) || "https://api.twilio.com/2010-04-01/Accounts/#{account_sid}/Messages.json" + end + + def account_sid + evaluate_option(:account_sid) || credentials.fetch(:account_sid) + end + + def auth_token + evaluate_option(:auth_token) || credentials.fetch(:auth_token) + end + + def phone_number + evaluate_option(:phone_number) || credentials.fetch(:phone_number) + end + + def credentials + evaluate_option(:credentials) || Rails.application.credentials.twilio + end + end + end +end diff --git a/lib/noticed/delivery_methods/vonage.rb b/lib/noticed/delivery_methods/vonage.rb deleted file mode 100644 index dc7e3860..00000000 --- a/lib/noticed/delivery_methods/vonage.rb +++ /dev/null @@ -1,41 +0,0 @@ -module Noticed - module DeliveryMethods - class Vonage < Base - def deliver - response = post("https://rest.nexmo.com/sms/json", json: format) - json = JSON.parse(response.body) - status = json.dig("messages", 0, "status") - if !options[:ignore_failure] && status != "0" - raise ResponseUnsuccessful.new(response) - end - - response - end - - private - - def format - if (method = options[:format]) - notification.send(method) - else - { - api_key: credentials[:api_key], - api_secret: credentials[:api_secret], - from: notification.params[:from], - text: notification.params[:body], - to: notification.params[:to], - type: "unicode" - } - end - end - - def credentials - if (method = options[:credentials]) - notification.send(method) - else - Rails.application.credentials.vonage - end - end - end - end -end diff --git a/lib/noticed/delivery_methods/vonage_sms.rb b/lib/noticed/delivery_methods/vonage_sms.rb new file mode 100644 index 00000000..a0cabb8d --- /dev/null +++ b/lib/noticed/delivery_methods/vonage_sms.rb @@ -0,0 +1,18 @@ +module Noticed + module DeliveryMethods + class VonageSms < DeliveryMethod + DEFAULT_URL = "https://rest.nexmo.com/sms/json" + + required_options :json + + def deliver + response = post_request url, headers: evaluate_option(:headers), json: evaluate_option(:json) + raise ResponseUnsuccessful.new(response) if JSON.parse(response.body).dig("messages", 0, "status") != "0" + end + + def url + evaluate_option(:url) || DEFAULT_URL + end + end + end +end diff --git a/lib/noticed/delivery_methods/webhook.rb b/lib/noticed/delivery_methods/webhook.rb new file mode 100644 index 00000000..68a52dbe --- /dev/null +++ b/lib/noticed/delivery_methods/webhook.rb @@ -0,0 +1,17 @@ +module Noticed + module DeliveryMethods + class Webhook < DeliveryMethod + required_options :url + + def deliver + post_request( + evaluate_option(:url), + basic_auth: evaluate_option(:basic_auth), + headers: evaluate_option(:headers), + json: evaluate_option(:json), + form: evaluate_option(:form) + ) + end + end + end +end diff --git a/lib/noticed/engine.rb b/lib/noticed/engine.rb index d5edc70a..b75897d1 100644 --- a/lib/noticed/engine.rb +++ b/lib/noticed/engine.rb @@ -1,9 +1,4 @@ module Noticed class Engine < ::Rails::Engine - initializer "noticed.has_notifications" do - ActiveSupport.on_load(:active_record) do - include Noticed::HasNotifications - end - end end end diff --git a/lib/noticed/has_notifications.rb b/lib/noticed/has_notifications.rb deleted file mode 100644 index fd8d0d23..00000000 --- a/lib/noticed/has_notifications.rb +++ /dev/null @@ -1,49 +0,0 @@ -module Noticed - module HasNotifications - # Defines a method for the association and a before_destroy callback to remove notifications - # where this record is a param - # - # class User < ApplicationRecord - # has_noticed_notifications - # has_noticed_notifications param_name: :owner, destroy: false, model: "Notification" - # end - # - # @user.notifications_as_user - # @user.notifications_as_owner - - extend ActiveSupport::Concern - - class_methods do - def has_noticed_notifications(param_name: model_name.singular, **options) - define_method "notifications_as_#{param_name}" do - model = options.fetch(:model_name, "Notification").constantize - case current_adapter - when "postgresql", "postgis" - model.where("params @> ?", Noticed::Coder.dump(param_name.to_sym => self).to_json) - when "mysql2" - model.where("JSON_CONTAINS(params, ?)", Noticed::Coder.dump(param_name.to_sym => self).to_json) - when "sqlite3" - model.where("json_extract(params, ?) = ?", "$.#{param_name}", Noticed::Coder.dump(self).to_json) - else - # This will perform an exact match which isn't ideal - model.where(params: {param_name.to_sym => self}) - end - end - - if options.fetch(:destroy, true) - before_destroy do - send("notifications_as_#{param_name}").destroy_all - end - end - end - end - - def current_adapter - if ActiveRecord::Base.respond_to?(:connection_db_config) - ActiveRecord::Base.connection_db_config.adapter - else - ActiveRecord::Base.connection_config[:adapter] - end - end - end -end diff --git a/lib/noticed/model.rb b/lib/noticed/model.rb deleted file mode 100644 index d14ad6e5..00000000 --- a/lib/noticed/model.rb +++ /dev/null @@ -1,86 +0,0 @@ -module Noticed - module Model - DATABASE_ERROR_CLASS_NAMES = lambda { - classes = [ActiveRecord::NoDatabaseError] - classes << ActiveRecord::ConnectionNotEstablished - classes << Mysql2::Error if defined?(::Mysql2) - classes << PG::ConnectionBad if defined?(::PG) - classes - }.call.freeze - - extend ActiveSupport::Concern - - included do - self.inheritance_column = nil - - if Rails.gem_version >= Gem::Version.new("7.1.0.alpha") - serialize :params, coder: noticed_coder - else - serialize :params, noticed_coder - end - - belongs_to :recipient, polymorphic: true - belongs_to :record, polymorphic: true - - scope :newest_first, -> { order(created_at: :desc) } - scope :unread, -> { where(read_at: nil) } - scope :read, -> { where.not(read_at: nil) } - end - - class_methods do - def mark_as_read! - update_all(read_at: Time.current, updated_at: Time.current) - end - - def mark_as_unread! - update_all(read_at: nil, updated_at: Time.current) - end - - def noticed_coder - return Noticed::TextCoder unless table_exists? - - case attribute_types["params"].type - when :json, :jsonb - Noticed::Coder - else - Noticed::TextCoder - end - rescue *DATABASE_ERROR_CLASS_NAMES => _error - warn("Noticed was unable to bootstrap correctly as the database is unavailable.") - - Noticed::TextCoder - end - end - - # Rehydrate the database notification into the Notification object for rendering - def to_notifier - @_notifier ||= begin - instance = type.constantize.with(params) - instance.record = self - instance.recipient = recipient - instance - end - end - - def mark_as_read! - update(read_at: Time.current) - end - - def mark_as_unread! - update(read_at: nil) - end - - def unread? - !read? - end - - def read? - read_at? - end - - # If a GlobalID record in params is no longer found, the params will default with a noticed_error key - def deserialize_error? - !!params[:noticed_error] - end - end -end diff --git a/lib/noticed/notification_channel.rb b/lib/noticed/notification_channel.rb deleted file mode 100644 index d3fecb67..00000000 --- a/lib/noticed/notification_channel.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Noticed - class NotificationChannel < ApplicationCable::Channel - def subscribed - stream_for current_user - end - - def unsubscribed - stop_all_streams - end - - def mark_as_read(data) - current_user.notifications.where(id: data["ids"]).mark_as_read! - end - end -end diff --git a/lib/noticed/required_options.rb b/lib/noticed/required_options.rb new file mode 100644 index 00000000..756496b3 --- /dev/null +++ b/lib/noticed/required_options.rb @@ -0,0 +1,21 @@ +module Noticed + module RequiredOptions + extend ActiveSupport::Concern + + included do + class_attribute :required_option_names, instance_writer: false, default: [] + end + + class_methods do + def inherited(base) + base.required_option_names = required_option_names.dup + super + end + + def required_options(*names) + required_option_names.concat names + end + alias_method :required_option, :required_options + end + end +end diff --git a/lib/noticed/text_coder.rb b/lib/noticed/text_coder.rb deleted file mode 100644 index cef44a97..00000000 --- a/lib/noticed/text_coder.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Noticed - class TextCoder - def self.load(data) - return if data.nil? - - # Text columns need JSON parsing - data = JSON.parse(data) - ActiveJob::Arguments.send(:deserialize_argument, data) - end - - def self.dump(data) - return if data.nil? - ActiveJob::Arguments.send(:serialize_argument, data).to_json - end - end -end diff --git a/lib/noticed/translation.rb b/lib/noticed/translation.rb index 0810157b..cad69d5f 100644 --- a/lib/noticed/translation.rb +++ b/lib/noticed/translation.rb @@ -4,7 +4,7 @@ module Translation # Returns the +i18n_scope+ for the class. Overwrite if you want custom lookup. def i18n_scope - :notifications + :notifiers end def class_scope @@ -22,7 +22,7 @@ def translate(key, **options) def scope_translation_key(key) if key.to_s.start_with?(".") - "#{i18n_scope}.#{class_scope}#{key}" + [i18n_scope, class_scope].compact.join(".") + key else key end diff --git a/lib/noticed/version.rb b/lib/noticed/version.rb index f7e52b85..9ad721bf 100644 --- a/lib/noticed/version.rb +++ b/lib/noticed/version.rb @@ -1,3 +1,3 @@ module Noticed - VERSION = "1.6.3" + VERSION = "2.0.0" end diff --git a/lib/tasks/noticed_tasks.rake b/lib/tasks/noticed_tasks.rake deleted file mode 100644 index a01ede42..00000000 --- a/lib/tasks/noticed_tasks.rake +++ /dev/null @@ -1,4 +0,0 @@ -# desc "Explaining what the task does" -# task :noticed do -# # Task goes here -# end diff --git a/test/bulk_delivery_methods/webhook_test.rb b/test/bulk_delivery_methods/webhook_test.rb new file mode 100644 index 00000000..e1ec9369 --- /dev/null +++ b/test/bulk_delivery_methods/webhook_test.rb @@ -0,0 +1,58 @@ +require "test_helper" + +class WebhookBulkDeliveryMethodTest < ActiveSupport::TestCase + setup do + @delivery_method = Noticed::BulkDeliveryMethods::Webhook.new + end + + test "webhook with json payload" do + set_config( + url: "https://example.org/webhook", + json: {foo: :bar} + ) + stub_request(:post, "https://example.org/webhook").with(body: "{\"foo\":\"bar\"}") + + @delivery_method.deliver + end + + test "webhook with form payload" do + set_config( + url: "https://example.org/webhook", + form: {foo: :bar} + ) + stub_request(:post, "https://example.org/webhook").with(headers: {"Content-Type" => /multipart\/form-data/}) + @delivery_method.deliver + end + + test "webhook with basic auth" do + set_config( + url: "https://example.org/webhook", + basic_auth: {user: "username", pass: "password"} + ) + stub_request(:post, "https://example.org/webhook").with(headers: {"Authorization" => "Basic dXNlcm5hbWU6cGFzc3dvcmQ="}) + @delivery_method.deliver + end + + test "webhook with headers" do + set_config( + url: "https://example.org/webhook", + headers: {"Content-Type" => "application/json"} + ) + stub_request(:post, "https://example.org/webhook").with(headers: {"Content-Type" => "application/json"}) + @delivery_method.deliver + end + + test "webhook raises error with unsuccessful status codes" do + set_config(url: "https://example.org/webhook") + stub_request(:post, "https://example.org/webhook").to_return(status: 422) + assert_raises Noticed::ResponseUnsuccessful do + @delivery_method.deliver + end + end + + private + + def set_config(config) + @delivery_method.instance_variable_set :@config, ActiveSupport::HashWithIndifferentAccess.new(config) + end +end diff --git a/test/delivery_method_test.rb b/test/delivery_method_test.rb new file mode 100644 index 00000000..6151521b --- /dev/null +++ b/test/delivery_method_test.rb @@ -0,0 +1,27 @@ +require "test_helper" + +class DeliveryMethodTest < ActiveSupport::TestCase + class InheritedDeliveryMethod < Noticed::DeliveryMethods::ActionCable + end + + setup do + @delivery_method = Noticed::DeliveryMethod.new + end + + test "fetch_constant looks up constants" do + set_config( + mailer: "UserMailer" + ) + assert_equal UserMailer, @delivery_method.fetch_constant(:mailer) + end + + test "delivery methods inhiert required options" do + assert_equal [:channel, :stream, :message], InheritedDeliveryMethod.required_option_names + end + + private + + def set_config(config) + @delivery_method.instance_variable_set :@config, ActiveSupport::HashWithIndifferentAccess.new(config) + end +end diff --git a/test/delivery_methods/action_cable_test.rb b/test/delivery_methods/action_cable_test.rb index b09905c8..38c8b163 100644 --- a/test/delivery_methods/action_cable_test.rb +++ b/test/delivery_methods/action_cable_test.rb @@ -1,51 +1,30 @@ require "test_helper" -class FakeChannel < ApplicationCable::Channel -end - -class FakeChannelNotification < Noticed::Base - deliver_by :action_cable, channel: :get_channel +class ActionCableDeliveryMethodTest < ActiveSupport::TestCase + include ActionCable::TestHelper - def get_channel - FakeChannel + setup do + @delivery_method = Noticed::DeliveryMethods::ActionCable.new end -end -class ActionCableTest < ActiveSupport::TestCase test "sends websocket message" do - channel = Noticed::NotificationChannel.broadcasting_for(user) - assert_broadcasts(channel, 1) do - CommentNotification.new.deliver(user) - end - end + user = users(:one) + channel = NotificationChannel.broadcasting_for(user) - test "accepts channel as string" do - delivery_method = Noticed::DeliveryMethods::ActionCable.new - delivery_method.instance_variable_set(:@options, {channel: "FakeChannel"}) - assert_equal FakeChannel, delivery_method.send(:channel) - end - - test "accepts channel as object" do - delivery_method = Noticed::DeliveryMethods::ActionCable.new - delivery_method.instance_variable_set(:@options, {channel: FakeChannel}) - assert_equal FakeChannel, delivery_method.send(:channel) - end + set_config( + channel: "NotificationChannel", + stream: user, + message: {foo: :bar} + ) - test "accepts channel as symbol" do - delivery_method = Noticed::DeliveryMethods::ActionCable.new - delivery_method.instance_variable_set(:@notification, FakeChannelNotification.new) - delivery_method.instance_variable_set(:@options, {channel: :get_channel}) - assert_equal FakeChannel, delivery_method.send(:channel) + assert_broadcasts(channel, 1) do + @delivery_method.deliver + end end - test "deliver returns nothing" do - args = { - notification_class: "Noticed::Base", - recipient: user, - options: {} - } - nothing = Noticed::DeliveryMethods::ActionCable.new.perform(args) + private - assert_nil nothing + def set_config(config) + @delivery_method.instance_variable_set :@config, ActiveSupport::HashWithIndifferentAccess.new(config) end end diff --git a/test/delivery_methods/base_test.rb b/test/delivery_methods/base_test.rb deleted file mode 100644 index 4a111484..00000000 --- a/test/delivery_methods/base_test.rb +++ /dev/null @@ -1,44 +0,0 @@ -require "test_helper" - -class CustomDeliveryMethod < Noticed::DeliveryMethods::Base - class_attribute :deliveries, default: [] - - def deliver - self.class.deliveries << params - end -end - -class CustomDeliveryMethodExample < Noticed::Base - deliver_by :example, class: "CustomDeliveryMethod" -end - -class DeliveryMethodWithOptions < Noticed::DeliveryMethods::Test - option :foo -end - -class DeliveryMethodWithOptionsExample < Noticed::Base - deliver_by :example, class: "DeliveryMethodWithOptions" -end - -class DeliveryMethodWithNilOptionsExample < Noticed::Base - deliver_by :example, class: "DeliveryMethodWithOptions", foo: nil -end - -class Noticed::DeliveryMethods::BaseTest < ActiveSupport::TestCase - test "can use custom delivery method with params" do - CustomDeliveryMethodExample.new.deliver(user) - assert_equal 1, CustomDeliveryMethod.deliveries.count - end - - test "validates delivery method options" do - assert_raises Noticed::ValidationError do - DeliveryMethodWithOptionsExample.new.deliver(user) - end - end - - test "nil options are valid" do - assert_difference "Noticed::DeliveryMethods::Test.delivered.count" do - DeliveryMethodWithNilOptionsExample.new.deliver(user) - end - end -end diff --git a/test/delivery_methods/database_test.rb b/test/delivery_methods/database_test.rb deleted file mode 100644 index 44f6ea0a..00000000 --- a/test/delivery_methods/database_test.rb +++ /dev/null @@ -1,88 +0,0 @@ -require "test_helper" - -class DatabaseTest < ActiveSupport::TestCase - class JustDatabaseDelivery < Noticed::Base - deliver_by :database - end - - class WithDelayedDatabaseDelivery < Noticed::Base - deliver_by :database, delay: 5.minutes - end - - class WithCustomDatabaseAttributes < Noticed::Base - deliver_by :database do |config| - config.attributes = ->(notification) { - {created_at: 1.year.from_now} - } - end - end - - test "writes to database" do - notification = CommentNotification.with(foo: :bar) - - assert_difference "user.notifications.count" do - assert_difference "Notification.count" do - notification.deliver(user) - end - end - - assert_equal :bar, user.notifications.last.params[:foo] - end - - test "delivery is executed but not enqueued" do - assert_difference "Notification.count" do - JustDatabaseDelivery.new.deliver_later(user) - assert_enqueued_jobs 0 - end - end - - test "writes to custom params database" do - CommentNotification.with(foo: :bar).deliver(user) - assert_equal 1, user.notifications.last.account_id - end - - test "writes to the database before other delivery methods" do - CommentNotification.with(foo: :bar).deliver_later(user) - perform_enqueued_jobs - assert_not_nil Notification.last - assert_equal Notification.last, Noticed::DeliveryMethods::Test.delivered.first.record - end - - test "serializes database attributes like ActiveJob does" do - assert_difference "Notification.count" do - CommentNotification.with(user: user).deliver(user) - end - assert_equal @user, Notification.last.params[:user] - end - - test "deliver returns the created record" do - args = { - notification_class: "Noticed::Base", - recipient: user, - options: {} - } - record = Noticed::DeliveryMethods::Database.new.perform(args) - - assert_kind_of ActiveRecord::Base, record - end - - test "delay option is not provided" do - assert_raises ArgumentError do - WithDelayedDatabaseDelivery.new.deliver(user) - end - end - - test "with custom database attributes" do - freeze_time - WithCustomDatabaseAttributes.deliver(user) - assert_equal 1.year.from_now, user.notifications.last.created_at - end - - test "record param is saved to the record polymorphic association and removed from params" do - account = accounts(:primary) - CommentNotification.with(record: account).deliver(user) - notification = Notification.last - assert_equal account, notification.record - assert_not_includes notification.params.keys, :record - end -end diff --git a/test/delivery_methods/email_test.rb b/test/delivery_methods/email_test.rb index e4b73a34..e1e8b18a 100644 --- a/test/delivery_methods/email_test.rb +++ b/test/delivery_methods/email_test.rb @@ -1,46 +1,40 @@ require "test_helper" -class EmailDeliveryWithoutMailer < Noticed::Base - deliver_by :email -end - -class EmailDeliveryWithActiveJob < Noticed::Base - deliver_by :email, mailer: "UserMailer", enqueue: true, method: "comment_notification" -end - class EmailTest < ActiveSupport::TestCase + include ActionMailer::TestHelper + setup do - @user = users(:one) + @delivery_method = Noticed::DeliveryMethods::Email.new end test "sends email" do - assert_emails 1 do - CommentNotification.new.deliver(user) + set_config( + mailer: "UserMailer", + method: "new_comment", + params: -> { {foo: :bar} }, + args: -> { ["hey"] } + ) + + assert_emails(1) do + @delivery_method.deliver end end - test "validates `mailer` is specified for email delivery method" do - assert_raises Noticed::ValidationError do - EmailDeliveryWithoutMailer.new.deliver(user) + test "enqueues email" do + set_config( + mailer: "UserMailer", + method: "receipt", + enqueue: true + ) + + assert_enqueued_emails(1) do + @delivery_method.deliver end end - test "deliver returns the email object" do - args = { - notification_class: "Noticed::Base", - recipient: user, - options: { - mailer: "UserMailer", - method: "comment_notification" - } - } - email = Noticed::DeliveryMethods::Email.new.perform(args) - - assert_kind_of Mail::Message, email - end + private - test "delivery spawns an ActiveJob for email" do - EmailDeliveryWithActiveJob.new.deliver(user) - assert_enqueued_emails 1 + def set_config(config) + @delivery_method.instance_variable_set :@config, ActiveSupport::HashWithIndifferentAccess.new(config) end end diff --git a/test/delivery_methods/fcm_test.rb b/test/delivery_methods/fcm_test.rb index fd7e4a8e..3eb58275 100644 --- a/test/delivery_methods/fcm_test.rb +++ b/test/delivery_methods/fcm_test.rb @@ -1,64 +1,76 @@ require "test_helper" -class FcmExample < Noticed::Base - deliver_by :fcm, credentials: :fcm_credentials, format: :format_notification +class FcmTest < ActiveSupport::TestCase + class FakeAuthorizer + def self.make_creds(options = {}) + new + end - def fcm_credentials - {project_id: "api-12345"} + def fetch_access_token! + {"access_token" => "access-token-12341234"} + end end - def format_notification(device_token) - { - token: device_token, - notification: { - title: "Hey Chris", - body: "Am I worky?" - } - } + setup do + @delivery_method = Noticed::DeliveryMethods::Fcm.new end -end -class FakeAuthorizer - def self.make_creds(options = {}) - new - end + test "notifies each device token" do + set_config( + authorizer: FakeAuthorizer, + credentials: { + "type" => "service_account", + "project_id" => "p_1234", + "private_key_id" => "private_key" + }, + device_tokens: [:a, :b], + json: ->(device_token) { + { + message: { + token: device_token, + notification: {title: "Title", body: "Body"} + } + } + } + ) - def fetch_access_token! - {"access_token" => "access-token-12341234"} - end -end + stub_request(:post, "https://fcm.googleapis.com/v1/projects/p_1234/messages:send").with(body: "{\"message\":{\"token\":\"a\",\"notification\":{\"title\":\"Title\",\"body\":\"Body\"}}}") + stub_request(:post, "https://fcm.googleapis.com/v1/projects/p_1234/messages:send").with(body: "{\"message\":{\"token\":\"b\",\"notification\":{\"title\":\"Title\",\"body\":\"Body\"}}}") -class FcmTest < ActiveSupport::TestCase - test "when credentials option is a hash, it returns the hash" do - credentials_hash = {foo: "bar"} - assert_equal credentials_hash, Noticed::DeliveryMethods::Fcm.new.assign_args(notification_class: "FcmExample", options: {credentials: credentials_hash}).credentials + @delivery_method.deliver end - test "when credentials option is a Pathname object, it returns the file contents" do - credentials_hash = {project_id: "api-12345"} - assert_equal credentials_hash, Noticed::DeliveryMethods::Fcm.new.assign_args(notification_class: "FcmExample", options: {credentials: Rails.root.join("config/credentials/fcm.json")}).credentials - end + test "notifies of invalid tokens for clean up" do + cleanups = 0 - test "when credentials option is a string, it returns the file contents" do - credentials_hash = {project_id: "api-12345"} - assert_equal credentials_hash, Noticed::DeliveryMethods::Fcm.new.assign_args(notification_class: "FcmExample", options: {credentials: "config/credentials/fcm.json"}).credentials - end + set_config( + authorizer: FakeAuthorizer, + credentials: { + "type" => "service_account", + "project_id" => "p_1234", + "private_key_id" => "private_key" + }, + device_tokens: [:a, :b], + json: ->(device_token) { + { + message: { + token: device_token, + notification: {title: "Title", body: "Body"} + } + } + }, + invalid_token: ->(device_token) { cleanups += 1 } + ) - test "when credentials option is a symbol, it returns the return value of the method" do - credentials_hash = {project_id: "api-12345"} - assert_equal credentials_hash, Noticed::DeliveryMethods::Fcm.new.assign_args(notification_class: "FcmExample", options: {credentials: :fcm_credentials}).credentials - end + stub_request(:post, "https://fcm.googleapis.com/v1/projects/p_1234/messages:send").to_return(status: 404, body: "", headers: {}) - test "project_id returns the project id value from the credentials" do - assert_equal "api-12345", Noticed::DeliveryMethods::Fcm.new.assign_args(notification_class: "FcmExample", options: {credentials: :fcm_credentials}).project_id + @delivery_method.deliver + assert_equal 2, cleanups end - test "access token returns a string" do - assert_equal "access-token-12341234", Noticed::DeliveryMethods::Fcm.new.assign_args(notification_class: "FcmExample", options: {credentials: :fcm_credentials, authorizer: FakeAuthorizer}).access_token - end + private - test "format" do - fcm_message = Noticed::DeliveryMethods::Fcm.new.assign_args(notification_class: "FcmExample", options: {credentials: :fcm_credentials, format: :format_notification}).format("12345") - assert_equal "12345", fcm_message.fetch(:token) + def set_config(config) + @delivery_method.instance_variable_set :@config, ActiveSupport::HashWithIndifferentAccess.new(config) end end diff --git a/test/delivery_methods/ios_test.rb b/test/delivery_methods/ios_test.rb index 8b90f924..506e6b0f 100644 --- a/test/delivery_methods/ios_test.rb +++ b/test/delivery_methods/ios_test.rb @@ -1,80 +1,83 @@ require "test_helper" -class IosExample < Noticed::Base - deliver_by :ios +class IosTest < ActiveSupport::TestCase + class FakeConnectionPool + class_attribute :invalid_tokens, default: [] + attr_reader :deliveries + + def initialize(response) + @response = response + @deliveries = [] + end - def ios_device_tokens(recipient) - [] + def with + yield self + end + + def push(apn) + @deliveries.push(apn) + @response + end end -end -class IosExampleWithoutDeviceTokens < Noticed::Base - deliver_by :ios -end + class FakeResponse + attr_reader :status -class IosTest < ActiveSupport::TestCase - test "raises error when bundle_identifier missing" do - exception = assert_raises ArgumentError do - Noticed::DeliveryMethods::Ios.new.perform(notification_class: "IosExample") + def initialize(status, body = {}) + @status = status end - assert_equal "bundle_identifier is missing", exception.message + def ok? + status.start_with?("20") + end end - test "raises error when key_id missing" do - exception = assert_raises ArgumentError do - Noticed::DeliveryMethods::Ios.new.perform( - notification_class: "IosExample", - options: { - bundle_identifier: "test" - } - ) - end + setup do + FakeConnectionPool.invalid_tokens = [] - assert_equal "key_id is missing", exception.message + @delivery_method = Noticed::DeliveryMethods::Ios.new + @delivery_method.instance_variable_set :@notification, noticed_notifications(:one) + set_config( + bundle_identifier: "bundle_id", + key_id: "key_id", + team_id: "team_id", + apns_key: "apns_key", + device_tokens: [:a, :b], + format: ->(apn) { + apn.alert = "Hello world" + apn.custom_payload = {url: root_url(host: "example.org")} + }, + invalid_token: ->(device_token) { + FakeConnectionPool.invalid_tokens << device_token + } + ) end - test "raises error when team_id missing" do - exception = assert_raises ArgumentError do - Noticed::DeliveryMethods::Ios.new.perform( - notification_class: "IosExample", - options: { - bundle_identifier: "test", - key_id: "test" - } - ) + test "notifies each device token" do + connection_pool = FakeConnectionPool.new(FakeResponse.new("200")) + @delivery_method.stub(:connection_pool, connection_pool) do + @delivery_method.deliver end - assert_equal "team_id is missing", exception.message + assert_equal 2, connection_pool.deliveries.count + assert_equal 0, FakeConnectionPool.invalid_tokens.count end - test "raises error when cert missing" do - exception = assert_raises ArgumentError do - Noticed::DeliveryMethods::Ios.new.perform( - notification_class: "IosExample", - options: { - bundle_identifier: "test", - key_id: "test", - team_id: "test" - } - ) + test "notifies of invalid tokens for cleanup" do + connection_pool = FakeConnectionPool.new(FakeResponse.new("410")) + @delivery_method.stub(:connection_pool, connection_pool) do + @delivery_method.deliver end - assert_match "Could not find APN cert at", exception.message + # Our fake connection pool doesn't understand these wouldn't be delivered in the real world + assert_equal 2, connection_pool.deliveries.count + + assert_equal 2, FakeConnectionPool.invalid_tokens.count end - test "raises error when ios_device_tokens method is missing" do - assert_raises NoMethodError do - File.stub :exist?, true do - Noticed::DeliveryMethods::Ios.new.perform( - notification_class: "IosExampleWithoutDeviceTokens", - options: { - bundle_identifier: "test", - key_id: "test", - team_id: "test" - } - ) - end - end + private + + def set_config(config) + @delivery_method.instance_variable_set :@config, ActiveSupport::HashWithIndifferentAccess.new(config) end end diff --git a/test/delivery_methods/microsoft_teams_test.rb b/test/delivery_methods/microsoft_teams_test.rb index a1953f4d..e8eed267 100644 --- a/test/delivery_methods/microsoft_teams_test.rb +++ b/test/delivery_methods/microsoft_teams_test.rb @@ -1,60 +1,29 @@ require "test_helper" class MicrosoftTeamsTest < ActiveSupport::TestCase - class MicrosoftTeamsExample < Noticed::Base - deliver_by :microsoft_teams, debug: true, url: :teams_url, format: :to_teams - - def teams_url - "https://outlook.office.com/webhooks/00000-00000/IncomingWebhook/00000-00000" - end - - def to_teams - { - title: "This is the title", - text: "this is the text", - section: sections, - potentialAction: actions - } - end - - def sections - [{activityTitle: "Section Title", activityText: "Section Text"}] - end - - def actions - [{ - "@type": "OpenUri", - name: "View on Foo.Com", - targets: [{os: "default", uri: "https://foo.example.com"}] - }] - end + setup do + @delivery_method = Noticed::DeliveryMethods::MicrosoftTeams.new + set_config( + json: {foo: :bar}, + url: "https://teams.microsoft.com" + ) end - test "sends a POST to Teams" do - stub_delivery_method_request(delivery_method: :microsoft_teams, matcher: /outlook.office.com/) - MicrosoftTeamsExample.new.deliver(user) + test "sends a message" do + stub_request(:post, "https://teams.microsoft.com").with(body: "{\"foo\":\"bar\"}") + @delivery_method.deliver end - test "raises an error when http request fails" do - stub_delivery_method_request(delivery_method: :microsoft_teams, matcher: /outlook.office.com/, type: :failure) - - e = assert_raises(::Noticed::ResponseUnsuccessful) { - MicrosoftTeamsExample.new.deliver(user) - } - - assert_equal Net::HTTPForbidden, e.response.class + test "raises error on failure" do + stub_request(:post, "https://teams.microsoft.com").to_return(status: 422) + assert_raises Noticed::ResponseUnsuccessful do + @delivery_method.deliver + end end - test "deliver returns an http response" do - stub_delivery_method_request(delivery_method: :microsoft_teams, matcher: /outlook.office.com/) - - args = { - notification_class: "::MicrosoftTeamsTest::MicrosoftTeamsExample", - recipient: user, - options: {url: :teams_url, format: :to_teams} - } - response = Noticed::DeliveryMethods::MicrosoftTeams.new.perform(args) + private - assert_kind_of Net::HTTPResponse, response + def set_config(config) + @delivery_method.instance_variable_set :@config, ActiveSupport::HashWithIndifferentAccess.new(config) end end diff --git a/test/delivery_methods/slack_test.rb b/test/delivery_methods/slack_test.rb index 6c3e37aa..fdd671e2 100644 --- a/test/delivery_methods/slack_test.rb +++ b/test/delivery_methods/slack_test.rb @@ -1,58 +1,26 @@ require "test_helper" class SlackTest < ActiveSupport::TestCase - class TestLogger - attr_reader :logs - - def debug(msg) - @logs ||= [] - @logs << msg - end - end - - class SlackExample < Noticed::Base - deliver_by :slack, debug: true, url: :slack_url, logger: TestLogger.new - - def slack_url - "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX" - end + setup do + @delivery_method = Noticed::DeliveryMethods::Slack.new + set_config(json: {foo: :bar}) end - test "sends a POST to Slack" do - stub_delivery_method_request(delivery_method: :slack, matcher: /hooks.slack.com/) - SlackExample.new.deliver(user) + test "sends a slack message" do + stub_request(:post, Noticed::DeliveryMethods::Slack::DEFAULT_URL).with(body: "{\"foo\":\"bar\"}") + @delivery_method.deliver end - test "raises an error when http request fails" do - stub_delivery_method_request(delivery_method: :slack, matcher: /hooks.slack.com/, type: :failure) - e = assert_raises(::Noticed::ResponseUnsuccessful) { - SlackExample.new.deliver(user) - } - assert_equal Net::HTTPForbidden, e.response.class - end - - test "deliver returns an http response" do - stub_delivery_method_request(delivery_method: :slack, matcher: /hooks.slack.com/) - - args = { - notification_class: "::SlackTest::SlackExample", - recipient: user, - options: {url: :slack_url} - } - response = Noticed::DeliveryMethods::Slack.new.perform(args) - - assert_kind_of Net::HTTPResponse, response + test "raises error on failure" do + stub_request(:post, Noticed::DeliveryMethods::Slack::DEFAULT_URL).to_return(status: 422) + assert_raises Noticed::ResponseUnsuccessful do + @delivery_method.deliver + end end - test "logs verbosely in debug mode" do - stub_delivery_method_request(delivery_method: :slack, matcher: /hooks.slack.com/) - - SlackExample.new.deliver(user) + private - logger = SlackExample.delivery_methods.find { |m| m[:name] == :slack }.dig(:options, :logger) - assert_equal logger.logs[-2..], [ - "POST https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX", - "Response: 200: ok\r\n" - ] + def set_config(config) + @delivery_method.instance_variable_set :@config, ActiveSupport::HashWithIndifferentAccess.new(config) end end diff --git a/test/delivery_methods/twilio_messaging_test.rb b/test/delivery_methods/twilio_messaging_test.rb new file mode 100644 index 00000000..7f6a7bda --- /dev/null +++ b/test/delivery_methods/twilio_messaging_test.rb @@ -0,0 +1,41 @@ +require "test_helper" + +class TwilioMessagingTest < ActiveSupport::TestCase + setup do + @delivery_method = Noticed::DeliveryMethods::TwilioMessaging.new + set_config( + account_sid: "acct_1234", + auth_token: "token", + json: -> { + { + From: "+1234567890", + To: "+1234567890", + Body: "Hello world" + } + } + ) + end + + test "sends sms" do + stub_request(:post, "https://api.twilio.com/2010-04-01/Accounts/acct_1234/Messages.json").with( + headers: { + "Authorization" => "Basic YWNjdF8xMjM0OnRva2Vu", + "Content-Type" => "multipart/form-data" + } + ).to_return(status: 200) + @delivery_method.deliver + end + + test "raises error on failure" do + stub_request(:post, "https://api.twilio.com/2010-04-01/Accounts/acct_1234/Messages.json").to_return(status: 422) + assert_raises Noticed::ResponseUnsuccessful do + @delivery_method.deliver + end + end + + private + + def set_config(config) + @delivery_method.instance_variable_set :@config, ActiveSupport::HashWithIndifferentAccess.new(config) + end +end diff --git a/test/delivery_methods/twilio_test.rb b/test/delivery_methods/twilio_test.rb deleted file mode 100644 index 454d6327..00000000 --- a/test/delivery_methods/twilio_test.rb +++ /dev/null @@ -1,41 +0,0 @@ -require "test_helper" - -class TwilioTest < ActiveSupport::TestCase - class TwilioExample < Noticed::Base - deliver_by :twilio, credentials: :twilio_creds, debug: true # , ignore_failure: true - - def twilio_creds - { - account_sid: "a", - auth_token: "b", - phone_number: "c" - } - end - end - - test "sends a POST to Twilio" do - stub_delivery_method_request(delivery_method: :twilio, matcher: /api.twilio.com/) - TwilioExample.new.deliver(user) - end - - test "raises an error when http request fails" do - stub_delivery_method_request(delivery_method: :twilio, matcher: /api.twilio.com/, type: :failure) - e = assert_raises(::Noticed::ResponseUnsuccessful) { - TwilioExample.new.deliver(user) - } - assert_equal Net::HTTPForbidden, e.response.class - end - - test "deliver returns an http response" do - stub_delivery_method_request(delivery_method: :twilio, matcher: /api.twilio.com/) - - args = { - notification_class: "::TwilioTest::TwilioExample", - recipient: user, - options: {credentials: :twilio_creds} - } - response = Noticed::DeliveryMethods::Twilio.new.perform(args) - - assert_kind_of Net::HTTPResponse, response - end -end diff --git a/test/delivery_methods/vonage_sms_test.rb b/test/delivery_methods/vonage_sms_test.rb new file mode 100644 index 00000000..dd49c48e --- /dev/null +++ b/test/delivery_methods/vonage_sms_test.rb @@ -0,0 +1,27 @@ +require "test_helper" + +class VonageSmsTest < ActiveSupport::TestCase + setup do + @delivery_method = Noticed::DeliveryMethods::VonageSms.new + end + + test "sends sms" do + set_config(json: {foo: :bar}) + stub_request(:post, Noticed::DeliveryMethods::VonageSms::DEFAULT_URL).with(body: "{\"foo\":\"bar\"}").to_return(status: 200, body: "{\"messages\":[{\"status\":\"0\"}]}") + @delivery_method.deliver + end + + test "raises error on failure" do + set_config(json: {foo: :bar}) + stub_request(:post, Noticed::DeliveryMethods::VonageSms::DEFAULT_URL).to_return(status: 200, body: "{\"messages\":[{\"status\":\"1\"}]}") + assert_raises Noticed::ResponseUnsuccessful do + @delivery_method.deliver + end + end + + private + + def set_config(config) + @delivery_method.instance_variable_set :@config, ActiveSupport::HashWithIndifferentAccess.new(config) + end +end diff --git a/test/delivery_methods/vonage_test.rb b/test/delivery_methods/vonage_test.rb deleted file mode 100644 index 42caad15..00000000 --- a/test/delivery_methods/vonage_test.rb +++ /dev/null @@ -1,44 +0,0 @@ -require "test_helper" - -class VonageTest < ActiveSupport::TestCase - class VonageExample < Noticed::Base - deliver_by :vonage, format: :to_vonage, debug: true - - def to_vonage - { - api_key: "a", - api_secret: "b", - from: "c", - text: "d", - to: "e", - type: "unicode" - } - end - end - - test "sends a POST to Vonage" do - stub_delivery_method_request(delivery_method: :vonage, matcher: /rest.nexmo.com/) - VonageExample.new.deliver(user) - end - - test "raises an error when http request fails" do - stub_delivery_method_request(delivery_method: :vonage, matcher: /rest.nexmo.com/, type: :failure) - e = assert_raises(::Noticed::ResponseUnsuccessful) { - VonageExample.new.deliver(user) - } - assert_equal Net::HTTPForbidden, e.response.class - end - - test "deliver returns an http response" do - stub_delivery_method_request(delivery_method: :vonage, matcher: /rest.nexmo.com/) - - args = { - notification_class: "::VonageTest::VonageExample", - recipient: user, - options: {format: :to_vonage} - } - response = Noticed::DeliveryMethods::Vonage.new.perform(args) - - assert_kind_of Net::HTTPResponse, response - end -end diff --git a/test/delivery_methods/webhook_test.rb b/test/delivery_methods/webhook_test.rb new file mode 100644 index 00000000..3e6240e7 --- /dev/null +++ b/test/delivery_methods/webhook_test.rb @@ -0,0 +1,58 @@ +require "test_helper" + +class WebhookDeliveryMethodTest < ActiveSupport::TestCase + setup do + @delivery_method = Noticed::DeliveryMethods::Webhook.new + end + + test "webhook with json payload" do + set_config( + url: "https://example.org/webhook", + json: {foo: :bar} + ) + stub_request(:post, "https://example.org/webhook").with(body: "{\"foo\":\"bar\"}") + + @delivery_method.deliver + end + + test "webhook with form payload" do + set_config( + url: "https://example.org/webhook", + form: {foo: :bar} + ) + stub_request(:post, "https://example.org/webhook").with(headers: {"Content-Type" => /multipart\/form-data/}) + @delivery_method.deliver + end + + test "webhook with basic auth" do + set_config( + url: "https://example.org/webhook", + basic_auth: {user: "username", pass: "password"} + ) + stub_request(:post, "https://example.org/webhook").with(headers: {"Authorization" => "Basic dXNlcm5hbWU6cGFzc3dvcmQ="}) + @delivery_method.deliver + end + + test "webhook with headers" do + set_config( + url: "https://example.org/webhook", + headers: {"Content-Type" => "application/json"} + ) + stub_request(:post, "https://example.org/webhook").with(headers: {"Content-Type" => "application/json"}) + @delivery_method.deliver + end + + test "webhook raises error with unsuccessful status codes" do + set_config(url: "https://example.org/webhook") + stub_request(:post, "https://example.org/webhook").to_return(status: 422) + assert_raises Noticed::ResponseUnsuccessful do + @delivery_method.deliver + end + end + + private + + def set_config(config) + @delivery_method.instance_variable_set :@config, ActiveSupport::HashWithIndifferentAccess.new(config) + end +end diff --git a/test/dummy/.ruby-version b/test/dummy/.ruby-version deleted file mode 100644 index 2eb2fe97..00000000 --- a/test/dummy/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -ruby-2.7.2 diff --git a/test/dummy/app/channels/application_cable/connection.rb b/test/dummy/app/channels/application_cable/connection.rb index 0ff5442f..6bf0c201 100644 --- a/test/dummy/app/channels/application_cable/connection.rb +++ b/test/dummy/app/channels/application_cable/connection.rb @@ -1,4 +1,18 @@ module ApplicationCable class Connection < ActionCable::Connection::Base + def connect + self.current_user = find_verified_user + logger.add_tags "ActionCable", "User #{current_user.id}" + end + + protected + + def find_verified_user + if (current_user = env["warden"].user(:user)) + current_user + else + reject_unauthorized_connection + end + end end end diff --git a/test/dummy/app/channels/notification_channel.rb b/test/dummy/app/channels/notification_channel.rb new file mode 100644 index 00000000..66cdb454 --- /dev/null +++ b/test/dummy/app/channels/notification_channel.rb @@ -0,0 +1,9 @@ +class NotificationChannel < ApplicationCable::Channel + def subscribed + stream_for current_user + end + + def unsubscribed + stop_all_streams + end +end diff --git a/test/dummy/app/javascript/packs/application.js b/test/dummy/app/javascript/packs/application.js deleted file mode 100644 index 67ce4675..00000000 --- a/test/dummy/app/javascript/packs/application.js +++ /dev/null @@ -1,15 +0,0 @@ -// This is a manifest file that'll be compiled into application.js, which will include all the files -// listed below. -// -// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, -// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. -// -// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the -// compiled file. JavaScript code in this file should be added after the last require_* statement. -// -// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details -// about supported directives. -// -//= require rails-ujs -//= require activestorage -//= require_tree . diff --git a/test/dummy/app/mailers/user_mailer.rb b/test/dummy/app/mailers/user_mailer.rb index cba5fb63..dee30036 100644 --- a/test/dummy/app/mailers/user_mailer.rb +++ b/test/dummy/app/mailers/user_mailer.rb @@ -1,5 +1,9 @@ class UserMailer < ApplicationMailer - def comment_notification - mail(body: "") + def new_comment(args) + mail(body: "new comment") + end + + def receipt + mail(body: "receipt") end end diff --git a/test/dummy/app/models/account.rb b/test/dummy/app/models/account.rb index c17a8747..9a5b0294 100644 --- a/test/dummy/app/models/account.rb +++ b/test/dummy/app/models/account.rb @@ -1,2 +1,4 @@ class Account < ApplicationRecord + has_many :notifications, as: :record, dependent: :destroy, class_name: "Noticed::Notification" + has_many :notifiers, as: :record, dependent: :destroy, class_name: "Noticed::Event" end diff --git a/test/dummy/app/models/application_record.rb b/test/dummy/app/models/application_record.rb index 10a4cba8..022a777d 100644 --- a/test/dummy/app/models/application_record.rb +++ b/test/dummy/app/models/application_record.rb @@ -1,3 +1,7 @@ class ApplicationRecord < ActiveRecord::Base - self.abstract_class = true + if respond_to?(:primary_abstract_class) + primary_abstract_class + else + self.abstract_class = true + end end diff --git a/test/dummy/app/models/discord_notification.rb b/test/dummy/app/models/discord_notification.rb deleted file mode 100644 index 9f24f8fd..00000000 --- a/test/dummy/app/models/discord_notification.rb +++ /dev/null @@ -1,2 +0,0 @@ -class DiscordNotification < Noticed::DeliveryMethods::Test -end diff --git a/test/dummy/app/models/json_notification.rb b/test/dummy/app/models/json_notification.rb deleted file mode 100644 index 4fe80f19..00000000 --- a/test/dummy/app/models/json_notification.rb +++ /dev/null @@ -1,4 +0,0 @@ -class JsonNotification < ApplicationRecord - include Noticed::Model - self.table_name = "json_notifications" -end diff --git a/test/dummy/app/models/jsonb_notification.rb b/test/dummy/app/models/jsonb_notification.rb deleted file mode 100644 index 3c174ff8..00000000 --- a/test/dummy/app/models/jsonb_notification.rb +++ /dev/null @@ -1,4 +0,0 @@ -class JsonbNotification < ApplicationRecord - include Noticed::Model - self.table_name = "jsonb_notifications" -end diff --git a/test/dummy/app/models/notification.rb b/test/dummy/app/models/notification.rb deleted file mode 100644 index 15dcdf6d..00000000 --- a/test/dummy/app/models/notification.rb +++ /dev/null @@ -1,3 +0,0 @@ -class Notification < ApplicationRecord - include Noticed::Model -end diff --git a/test/dummy/app/models/text_notification.rb b/test/dummy/app/models/text_notification.rb deleted file mode 100644 index 64605aaf..00000000 --- a/test/dummy/app/models/text_notification.rb +++ /dev/null @@ -1,3 +0,0 @@ -class TextNotification < ApplicationRecord - include Noticed::Model -end diff --git a/test/dummy/app/models/user.rb b/test/dummy/app/models/user.rb index 76370be5..68f955ee 100644 --- a/test/dummy/app/models/user.rb +++ b/test/dummy/app/models/user.rb @@ -1,10 +1,3 @@ class User < ApplicationRecord - has_many :notifications, as: :recipient - - has_noticed_notifications - has_noticed_notifications param_name: :owner, destroy: false - - def phone_number - "8675309" - end + has_many :notifications, as: :recipient, dependent: :destroy, class_name: "Noticed::Notification" end diff --git a/test/dummy/app/notifications/comment_notification.rb b/test/dummy/app/notifications/comment_notification.rb deleted file mode 100644 index 1ac7b3af..00000000 --- a/test/dummy/app/notifications/comment_notification.rb +++ /dev/null @@ -1,23 +0,0 @@ -class CommentNotification < Noticed::Base - deliver_by :database do |config| - config.format = proc do - { - account_id: 1, - type: self.class.name, - params: params - } - end - end - - deliver_by :action_cable - - deliver_by :email do |config| - config.mailer = "UserMailer" - end - - deliver_by :discord, class: "DiscordNotification" - - def url - root_url - end -end diff --git a/test/dummy/app/notifiers/comment_notifier.rb b/test/dummy/app/notifiers/comment_notifier.rb index 2c4fbbee..7e80c635 100644 --- a/test/dummy/app/notifiers/comment_notifier.rb +++ b/test/dummy/app/notifiers/comment_notifier.rb @@ -1,18 +1,58 @@ -class CommentNotifier < Noticed::Base - deliver_by :database, format: :attributes_for_database - deliver_by :action_cable - deliver_by :email, mailer: "UserMailer" - deliver_by :discord, class: "DiscordNotification" - - def attributes_for_database - { - account_id: 1, - type: self.class.name, - params: params +class CommentNotifier < Noticed::Event + required_params :message + + deliver_by :test + + # delivery_by :email, mailer: "UserMailer", method: "new_comment" + deliver_by :email do |config| + config.mailer = "UserMailer" + config.method = :new_comment + # config.params = -> { params } + # config.args = -> { recipient } + end + + deliver_by :action_cable do |config| + config.channel = "NotificationChannel" + config.stream = -> { recipient } + config.message = -> { params } + end + + deliver_by :twilio_messaging do |config| + config.phone_number = "+1234567890" + config.account_sid = "abcd1234" + config.auth_token = "secret" + end + + deliver_by :microsoft_teams do |config| + config.url = "https://example.org" + config.json = -> { params } + end + + deliver_by :slack do |config| + config.headers = {"Authorization" => "Bearer xoxb-xxxxxxxxx-xxxxxxxxxx"} + config.json = -> { params } + end + + deliver_by :fcm do |config| + config.credentials = Rails.root.join("config/certs/fcm.json") + # Or store them in the Rails credentials + # config.credentials = Rails.application.credentials.fcm + config.device_tokens = -> { recipient.device_tokens } + + config.json = ->(device_token) { + { + token: device_token, + notification: { + title: "Test Title", + body: "Test body" + } + } } + + # Clean up invalid tokens that are no longer usable + config.invalid_token = ->(token:, platform:) { recipient.device_tokens.where(id: token.id).destroy_all } end - def url - root_url + deliver_by :ios do |config| end end diff --git a/test/dummy/app/notifiers/receipt_notifier.rb b/test/dummy/app/notifiers/receipt_notifier.rb new file mode 100644 index 00000000..8dfd7eb7 --- /dev/null +++ b/test/dummy/app/notifiers/receipt_notifier.rb @@ -0,0 +1,9 @@ +class ReceiptNotifier < Noticed::Event + deliver_by :test + + deliver_by :email do |config| + config.mailer = "UserMailer" + config.method = :receipt + config.params = -> { params } + end +end diff --git a/test/dummy/app/views/layouts/application.html.erb b/test/dummy/app/views/layouts/application.html.erb index 24307d38..f72b4ef0 100644 --- a/test/dummy/app/views/layouts/application.html.erb +++ b/test/dummy/app/views/layouts/application.html.erb @@ -2,10 +2,11 @@ Dummy + <%= csrf_meta_tags %> <%= csp_meta_tag %> - <%= stylesheet_link_tag 'application', media: 'all' %> + <%= stylesheet_link_tag "application" %> diff --git a/test/dummy/app/views/layouts/mailer.html.erb b/test/dummy/app/views/layouts/mailer.html.erb index cbd34d2e..3aac9002 100644 --- a/test/dummy/app/views/layouts/mailer.html.erb +++ b/test/dummy/app/views/layouts/mailer.html.erb @@ -1,7 +1,7 @@ - + diff --git a/test/dummy/bin/setup b/test/dummy/bin/setup index 5635f8ec..ec47b79b 100755 --- a/test/dummy/bin/setup +++ b/test/dummy/bin/setup @@ -9,8 +9,8 @@ def system!(*args) end FileUtils.chdir APP_ROOT do - # This script is a way to setup or update your development environment automatically. - # This script is idempotent, so that you can run it at anytime and get an expectable outcome. + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. # Add necessary setup steps to this file. puts "== Installing dependencies ==" @@ -18,8 +18,8 @@ FileUtils.chdir APP_ROOT do system("bundle check") || system!("bundle install") # puts "\n== Copying sample files ==" - # unless File.exist?('config/database.yml') - # FileUtils.cp 'config/database.yml.sample', 'config/database.yml' + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" # end puts "\n== Preparing database ==" diff --git a/test/dummy/config.ru b/test/dummy/config.ru index 441e6ff0..4a3c09a6 100644 --- a/test/dummy/config.ru +++ b/test/dummy/config.ru @@ -3,3 +3,4 @@ require_relative "config/environment" run Rails.application +Rails.application.load_server diff --git a/test/dummy/config/application.rb b/test/dummy/config/application.rb index 0eabbd3d..bf33dd8c 100644 --- a/test/dummy/config/application.rb +++ b/test/dummy/config/application.rb @@ -2,14 +2,28 @@ require "rails/all" +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) -require "noticed" module Dummy class Application < Rails::Application - # Settings in config/environments/* take precedence over those specified here. - # Application configuration can go into files in config/initializers - # -- all .rb files in that directory are automatically loaded after loading - # the framework and any gems in your application. + config.load_defaults Rails::VERSION::STRING.to_f + + # For compatibility with applications that use this config + config.action_controller.include_all_helpers = false + + # Please, add to the `ignore` list any other `lib` subdirectories that do + # not contain `.rb` files, or that should not be reloaded or eager loaded. + # Common ones are `templates`, `generators`, or `middleware`, for example. + # config.autoload_lib(ignore: %w[assets tasks]) + + # Configuration for the application, engines, and railties goes here. + # + # 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.eager_load_paths << Rails.root.join("extras") end end diff --git a/test/dummy/config/credentials/fcm.json b/test/dummy/config/credentials/fcm.json deleted file mode 100644 index 5632c1d4..00000000 --- a/test/dummy/config/credentials/fcm.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "project_id": "api-12345" -} diff --git a/test/dummy/config/database.yml b/test/dummy/config/database.yml index 5b7a33f6..796466ba 100644 --- a/test/dummy/config/database.yml +++ b/test/dummy/config/database.yml @@ -2,19 +2,24 @@ # gem install sqlite3 # # Ensure the SQLite 3 gem is defined in your Gemfile -# gem 'sqlite3' +# gem "sqlite3" # -development: - adapter: postgresql +default: &default + adapter: sqlite3 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> timeout: 5000 - database: noticed_dev + +development: + <<: *default + database: storage/development.sqlite3 # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: - adapter: postgresql - pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> - timeout: 5000 - database: noticed_test + <<: *default + database: storage/test.sqlite3 + +production: + <<: *default + database: storage/production.sqlite3 diff --git a/test/dummy/config/environments/development.rb b/test/dummy/config/environments/development.rb index 150fcfe2..2e7fb486 100644 --- a/test/dummy/config/environments/development.rb +++ b/test/dummy/config/environments/development.rb @@ -1,10 +1,12 @@ +require "active_support/core_ext/integer/time" + Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. - # In the development environment your application's code is reloaded on - # every request. This slows down response time but is perfect for development + # In the development environment your application's code is reloaded any time + # it changes. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. - config.cache_classes = false + config.enable_reloading = true # Do not eager load code on boot. config.eager_load = false @@ -12,9 +14,12 @@ # Show full error reports. config.consider_all_requests_local = true + # Enable server timing + config.server_timing = true + # Enable/disable caching. By default caching is disabled. # Run rails dev:cache to toggle caching. - if Rails.root.join("tmp", "caching-dev.txt").exist? + if Rails.root.join("tmp/caching-dev.txt").exist? config.action_controller.perform_caching = true config.action_controller.enable_fragment_cache_logging = true @@ -39,16 +44,33 @@ # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log + # Raise exceptions for disallowed deprecations. + config.active_support.disallowed_deprecation = :raise + + # Tell Active Support which deprecation messages to disallow. + config.active_support.disallowed_deprecation_warnings = [] + # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load # Highlight code that triggered database queries in logs. config.active_record.verbose_query_logs = true + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Suppress logger output for asset requests. + config.assets.quiet = true + # Raises error for missing translations. - # config.action_view.raise_on_missing_translations = true + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true - # Use an evented file watcher to asynchronously detect changes in source code, - # routes, locales, etc. This feature depends on the listen gem. - # config.file_watcher = ActiveSupport::EventedFileUpdateChecker + # Raise error when a before_action's only/except options reference missing actions + config.action_controller.raise_on_missing_callback_actions = true end diff --git a/test/dummy/config/environments/production.rb b/test/dummy/config/environments/production.rb index 8d6cdab4..a0b10508 100644 --- a/test/dummy/config/environments/production.rb +++ b/test/dummy/config/environments/production.rb @@ -1,8 +1,10 @@ +require "active_support/core_ext/integer/time" + Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # Code is not reloaded between requests. - config.cache_classes = true + config.enable_reloading = false # Eager load code on boot. This eager loads most of Rails and # your application in memory, allowing both threaded web servers @@ -14,13 +16,12 @@ config.consider_all_requests_local = false config.action_controller.perform_caching = true - # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] - # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). + # Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment + # key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files). # config.require_master_key = true - # Disable serving static files from the `/public` folder by default since - # Apache or NGINX already handles this. - config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? + # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead. + # config.public_file_server.enabled = false # Compress CSS using a preprocessor. # config.assets.css_compressor = :sass @@ -29,35 +30,45 @@ config.assets.compile = false # Enable serving of images, stylesheets, and JavaScripts from an asset server. - # config.action_controller.asset_host = 'http://assets.example.com' + # config.asset_host = "http://assets.example.com" # Specifies the header that your server uses for sending files. - # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache - # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache + # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX # Store uploaded files on the local file system (see config/storage.yml for options). config.active_storage.service = :local # Mount Action Cable outside main process or domain. # config.action_cable.mount_path = nil - # config.action_cable.url = 'wss://example.com/cable' - # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + # config.action_cable.url = "wss://example.com/cable" + # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies. + # config.assume_ssl = true # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - # config.force_ssl = true + config.force_ssl = true - # Use the lowest log level to ensure availability of diagnostic information - # when problems arise. - config.log_level = :debug + # Log to STDOUT by default + config.logger = ActiveSupport::Logger.new($stdout) + .tap { |logger| logger.formatter = ::Logger::Formatter.new } + .then { |logger| ActiveSupport::TaggedLogging.new(logger) } # Prepend all log lines with the following tags. config.log_tags = [:request_id] + # Info include generic and useful information about system operation, but avoids logging too much + # information to avoid inadvertent exposure of personally identifiable information (PII). If you + # want to log everything, set the level to "debug". + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + # Use a different cache store in production. # config.cache_store = :mem_cache_store # Use a real queuing backend for Active Job (and separate queues per environment). - # config.active_job.queue_adapter = :resque + # config.active_job.queue_adapter = :resque # config.active_job.queue_name_prefix = "dummy_production" config.action_mailer.perform_caching = false @@ -70,43 +81,17 @@ # the I18n.default_locale when a translation cannot be found). config.i18n.fallbacks = true - # Send deprecation notices to registered listeners. - config.active_support.deprecation = :notify - - # Use default logging formatter so that PID and timestamp are not suppressed. - config.log_formatter = ::Logger::Formatter.new - - # Use a different logger for distributed setups. - # require 'syslog/logger' - # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') - - if ENV["RAILS_LOG_TO_STDOUT"].present? - logger = ActiveSupport::Logger.new($stdout) - logger.formatter = config.log_formatter - config.logger = ActiveSupport::TaggedLogging.new(logger) - end + # Don't log any deprecations. + config.active_support.report_deprecations = false # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false - # Inserts middleware to perform automatic connection switching. - # The `database_selector` hash is used to pass options to the DatabaseSelector - # middleware. The `delay` is used to determine how long to wait after a write - # to send a subsequent read to the primary. - # - # The `database_resolver` class is used by the middleware to determine which - # database is appropriate to use based on the time delay. - # - # The `database_resolver_context` class is used by the middleware to set - # timestamps for the last write to the primary. The resolver uses the context - # class timestamps to determine how long to wait before reading from the - # replica. - # - # By default Rails will store a last write timestamp in the session. The - # DatabaseSelector middleware is designed as such you can define your own - # strategy for connection switching and pass that into the middleware through - # these configuration options. - # config.active_record.database_selector = { delay: 2.seconds } - # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver - # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session + # Enable DNS rebinding protection and other `Host` header attacks. + # config.hosts = [ + # "example.com", # Allow requests from example.com + # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` + # ] + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } end diff --git a/test/dummy/config/environments/test.rb b/test/dummy/config/environments/test.rb index 09ece9fd..3595bc82 100644 --- a/test/dummy/config/environments/test.rb +++ b/test/dummy/config/environments/test.rb @@ -1,3 +1,5 @@ +require "active_support/core_ext/integer/time" + # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped @@ -6,13 +8,14 @@ Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. - config.cache_classes = false - config.action_view.cache_template_loading = true + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false - # Do not eager load code on boot. This avoids loading your whole application - # just for the purpose of running a single test. If you are using a tool that - # preloads Rails for running tests, you may have to set it to true. - config.eager_load = false + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true @@ -25,8 +28,8 @@ config.action_controller.perform_caching = false config.cache_store = :null_store - # Raise exceptions instead of rendering exception templates. - config.action_dispatch.show_exceptions = false + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :rescuable # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false @@ -44,6 +47,18 @@ # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr + # Raise exceptions for disallowed deprecations. + config.active_support.disallowed_deprecation = :raise + + # Tell Active Support which deprecation messages to disallow. + config.active_support.disallowed_deprecation_warnings = [] + # Raises error for missing translations. - # config.action_view.raise_on_missing_translations = true + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions + # config.action_controller.raise_on_missing_callback_actions = true end diff --git a/test/dummy/config/initializers/application_controller_renderer.rb b/test/dummy/config/initializers/application_controller_renderer.rb deleted file mode 100644 index 89d2efab..00000000 --- a/test/dummy/config/initializers/application_controller_renderer.rb +++ /dev/null @@ -1,8 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# ActiveSupport::Reloader.to_prepare do -# ApplicationController.renderer.defaults.merge!( -# http_host: 'example.org', -# https: false -# ) -# end diff --git a/test/dummy/config/initializers/assets.rb b/test/dummy/config/initializers/assets.rb index 7e807838..d6156df0 100644 --- a/test/dummy/config/initializers/assets.rb +++ b/test/dummy/config/initializers/assets.rb @@ -1,9 +1,7 @@ # Be sure to restart your server when you modify this file. # Version of your assets, change this if you want to expire all your assets. -if Rails.application.config.respond_to?(:assets) - Rails.application.config.assets.version = "1.0" -end +# Rails.application.config.assets.version = "1.0" # Add additional assets to the asset load path. # Rails.application.config.assets.paths << Emoji.images_path diff --git a/test/dummy/config/initializers/backtrace_silencers.rb b/test/dummy/config/initializers/backtrace_silencers.rb deleted file mode 100644 index 59385cdf..00000000 --- a/test/dummy/config/initializers/backtrace_silencers.rb +++ /dev/null @@ -1,7 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. -# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } - -# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. -# Rails.backtrace_cleaner.remove_silencers! diff --git a/test/dummy/config/initializers/content_security_policy.rb b/test/dummy/config/initializers/content_security_policy.rb index 41c43016..b3076b38 100644 --- a/test/dummy/config/initializers/content_security_policy.rb +++ b/test/dummy/config/initializers/content_security_policy.rb @@ -1,28 +1,25 @@ # Be sure to restart your server when you modify this file. -# Define an application-wide content security policy -# For further information see the following documentation -# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header -# Rails.application.config.content_security_policy do |policy| -# policy.default_src :self, :https -# policy.font_src :self, :https, :data -# policy.img_src :self, :https, :data -# policy.object_src :none -# policy.script_src :self, :https -# policy.style_src :self, :https - -# # Specify URI for violation reports -# # policy.report_uri "/csp-violation-report-endpoint" +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap, inline scripts, and inline styles. +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src style-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true # end - -# If you are using UJS then enable automatic nonce generation -# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } - -# Set the nonce only to specific directives -# Rails.application.config.content_security_policy_nonce_directives = %w(script-src) - -# Report CSP violations to a specified URI -# For further information see the following documentation: -# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only -# Rails.application.config.content_security_policy_report_only = true diff --git a/test/dummy/config/initializers/cookies_serializer.rb b/test/dummy/config/initializers/cookies_serializer.rb deleted file mode 100644 index 5a6a32d3..00000000 --- a/test/dummy/config/initializers/cookies_serializer.rb +++ /dev/null @@ -1,5 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Specify a serializer for the signed and encrypted cookie jars. -# Valid options are :json, :marshal, and :hybrid. -Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/test/dummy/config/initializers/filter_parameter_logging.rb b/test/dummy/config/initializers/filter_parameter_logging.rb index 4a994e1e..c2d89e28 100644 --- a/test/dummy/config/initializers/filter_parameter_logging.rb +++ b/test/dummy/config/initializers/filter_parameter_logging.rb @@ -1,4 +1,8 @@ # Be sure to restart your server when you modify this file. -# Configure sensitive parameters which will be filtered from the log file. -Rails.application.config.filter_parameters += [:password] +# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. +# Use this to limit dissemination of sensitive information. +# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn +] diff --git a/test/dummy/config/initializers/inflections.rb b/test/dummy/config/initializers/inflections.rb index ac033bf9..3860f659 100644 --- a/test/dummy/config/initializers/inflections.rb +++ b/test/dummy/config/initializers/inflections.rb @@ -4,13 +4,13 @@ # are locale specific, and you may define rules for as many different # locales as you wish. All of these examples are active by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| -# inflect.plural /^(ox)$/i, '\1en' -# inflect.singular /^(ox)en/i, '\1' -# inflect.irregular 'person', 'people' +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" # inflect.uncountable %w( fish sheep ) # end # These inflection rules are supported but not enabled by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| -# inflect.acronym 'RESTful' +# inflect.acronym "RESTful" # end diff --git a/test/dummy/config/initializers/mime_types.rb b/test/dummy/config/initializers/mime_types.rb deleted file mode 100644 index dc189968..00000000 --- a/test/dummy/config/initializers/mime_types.rb +++ /dev/null @@ -1,4 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Add new mime types for use in respond_to blocks: -# Mime::Type.register "text/richtext", :rtf diff --git a/test/dummy/config/initializers/permissions_policy.rb b/test/dummy/config/initializers/permissions_policy.rb new file mode 100644 index 00000000..7db3b957 --- /dev/null +++ b/test/dummy/config/initializers/permissions_policy.rb @@ -0,0 +1,13 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide HTTP permissions policy. For further +# information see: https://developers.google.com/web/updates/2018/06/feature-policy + +# Rails.application.config.permissions_policy do |policy| +# policy.camera :none +# policy.gyroscope :none +# policy.microphone :none +# policy.usb :none +# policy.fullscreen :self +# policy.payment :self, "https://secure.example.com" +# end diff --git a/test/dummy/config/initializers/wrap_parameters.rb b/test/dummy/config/initializers/wrap_parameters.rb deleted file mode 100644 index bbfc3961..00000000 --- a/test/dummy/config/initializers/wrap_parameters.rb +++ /dev/null @@ -1,14 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# This file contains settings for ActionController::ParamsWrapper which -# is enabled by default. - -# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. -ActiveSupport.on_load(:action_controller) do - wrap_parameters format: [:json] -end - -# To enable root element in JSON for ActiveRecord objects. -# ActiveSupport.on_load(:active_record) do -# self.include_root_in_json = true -# end diff --git a/test/dummy/config/locales/en.yml b/test/dummy/config/locales/en.yml index 676340a8..56770f48 100644 --- a/test/dummy/config/locales/en.yml +++ b/test/dummy/config/locales/en.yml @@ -1,14 +1,14 @@ -# Files in the config/locales directory are used for internationalization -# and are automatically loaded by Rails. If you want to use locales other -# than English, add the necessary files in this directory. +# Files in the config/locales directory are used for internationalization and +# are automatically loaded by Rails. If you want to use locales other than +# English, add the necessary files in this directory. # # To use the locales, use `I18n.t`: # -# I18n.t 'hello' +# I18n.t "hello" # # In views, this is aliased to just `t`: # -# <%= t('hello') %> +# <%= t("hello") %> # # To use a different locale, set it with `I18n.locale`: # @@ -16,23 +16,21 @@ # # This would use the information in config/locales/es.yml. # -# The following keys must be escaped otherwise they will not be retrieved by -# the default I18n backend: +# To learn more about the API, please read the Rails Internationalization guide +# at https://guides.rubyonrails.org/i18n.html. # -# true, false, on, off, yes, no +# Be aware that YAML interprets the following case-insensitive strings as +# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings +# must be quoted to be interpreted as strings. For example: # -# Instead, surround them with single quotes. -# -# en: -# 'true': 'foo' -# -# To learn more, please read the Rails Internationalization guide -# available at https://guides.rubyonrails.org/i18n.html. +# en: +# "yes": yup +# enabled: "ON" en: hello: "Hello world" message_html: "

Hello world

" - notifications: + notifiers: noticed: i18n_example: message: "This is a notification" diff --git a/test/dummy/config/puma.rb b/test/dummy/config/puma.rb index 0064140a..afa809b4 100644 --- a/test/dummy/config/puma.rb +++ b/test/dummy/config/puma.rb @@ -1,38 +1,35 @@ +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. + # Puma can serve each request in a thread from an internal thread pool. # The `threads` method setting takes two numbers: a minimum and maximum. # Any libraries that use thread pools should be configured to match # the maximum value specified for Puma. Default is set to 5 threads for minimum # and maximum; this matches the default thread size of Active Record. -# -max_threads_count = ENV.fetch("RAILS_MAX_THREADS", 5) -min_threads_count = ENV.fetch("RAILS_MIN_THREADS", max_threads_count) +max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } threads min_threads_count, max_threads_count +# Specifies that the worker count should equal the number of processors in production. +if ENV["RAILS_ENV"] == "production" + require "concurrent-ruby" + worker_count = Integer(ENV.fetch("WEB_CONCURRENCY") { Concurrent.physical_processor_count }) + workers worker_count if worker_count > 1 +end + +# Specifies the `worker_timeout` threshold that Puma will use to wait before +# terminating a worker in development environments. +worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" + # Specifies the `port` that Puma will listen on to receive requests; default is 3000. -# -port ENV.fetch("PORT", 3000) +port ENV.fetch("PORT") { 3000 } # Specifies the `environment` that Puma will run in. -# -environment ENV.fetch("RAILS_ENV", "development") +environment ENV.fetch("RAILS_ENV") { "development" } # Specifies the `pidfile` that Puma will use. -pidfile ENV.fetch("PIDFILE", "tmp/pids/server.pid") - -# Specifies the number of `workers` to boot in clustered mode. -# Workers are forked web server processes. If using threads and workers together -# the concurrency of the application would be max `threads` * `workers`. -# Workers do not work on JRuby or Windows (both of which do not support -# processes). -# -# workers ENV.fetch("WEB_CONCURRENCY") { 2 } - -# Use the `preload_app!` method when specifying a `workers` number. -# This directive tells Puma to first boot the application and load code -# before forking the application. This takes advantage of Copy On Write -# process behavior so workers use less memory. -# -# preload_app! +pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } -# Allow puma to be restarted by `rails restart` command. +# Allow puma to be restarted by `bin/rails restart` command. plugin :tmp_restart diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb index 130fd76f..ad060d79 100644 --- a/test/dummy/config/routes.rb +++ b/test/dummy/config/routes.rb @@ -1,4 +1,10 @@ Rails.application.routes.draw do - # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html - root to: "main#index" + # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html + + # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. + # Can be used by load balancers and uptime monitors to verify that the app is live. + get "up" => "rails/health#show", :as => :rails_health_check + + # Defines the root path route ("/") + root "posts#index" end diff --git a/test/dummy/config/spring.rb b/test/dummy/config/spring.rb deleted file mode 100644 index db5bf130..00000000 --- a/test/dummy/config/spring.rb +++ /dev/null @@ -1,6 +0,0 @@ -Spring.watch( - ".ruby-version", - ".rbenv-vars", - "tmp/restart.txt", - "tmp/caching-dev.txt" -) diff --git a/test/dummy/config/storage.yml b/test/dummy/config/storage.yml index d32f76e8..4942ab66 100644 --- a/test/dummy/config/storage.yml +++ b/test/dummy/config/storage.yml @@ -6,27 +6,27 @@ local: service: Disk root: <%= Rails.root.join("storage") %> -# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) # amazon: # service: S3 # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> # region: us-east-1 -# bucket: your_own_bucket +# bucket: your_own_bucket-<%= Rails.env %> # Remember not to checkin your GCS keyfile to a repository # google: # service: GCS # project: your_project # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> -# bucket: your_own_bucket +# bucket: your_own_bucket-<%= Rails.env %> -# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) # microsoft: # service: AzureStorage # storage_account_name: your_account_name # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> -# container: your_container_name +# container: your_container_name-<%= Rails.env %> # mirror: # service: Mirror diff --git a/test/dummy/db/migrate/20231215202921_create_users.rb b/test/dummy/db/migrate/20231215202921_create_users.rb new file mode 100644 index 00000000..1098860c --- /dev/null +++ b/test/dummy/db/migrate/20231215202921_create_users.rb @@ -0,0 +1,9 @@ +class CreateUsers < ActiveRecord::Migration[7.1] + def change + create_table :users do |t| + t.string :email + + t.timestamps + end + end +end diff --git a/test/dummy/db/migrate/20231215202924_create_accounts.rb b/test/dummy/db/migrate/20231215202924_create_accounts.rb new file mode 100644 index 00000000..528f2b98 --- /dev/null +++ b/test/dummy/db/migrate/20231215202924_create_accounts.rb @@ -0,0 +1,9 @@ +class CreateAccounts < ActiveRecord::Migration[7.1] + def change + create_table :accounts do |t| + t.string :name + + t.timestamps + end + end +end diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb index d4079de5..fda6271e 100644 --- a/test/dummy/db/schema.rb +++ b/test/dummy/db/schema.rb @@ -2,91 +2,46 @@ # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # -# This file is the source Rails uses to define your schema when running `rails -# db:schema:load`. When creating a new database, `rails db:schema:load` tends to +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to # be faster and is potentially less error prone than running all of your # migrations from scratch. Old migrations may fail to apply correctly if those # migrations use external dependencies or application code. # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_08_03_191250) do - # These are extensions that must be enabled in order to support this database - enable_extension "plpgsql" - - create_table "notifications", force: :cascade do |t| - t.integer "account_id" - t.string "recipient_type", null: false - t.bigint "recipient_id", null: false - t.string "record_type" - t.bigint "record_id" - t.string "type" - if t.respond_to? :jsonb - t.jsonb "params" - else - t.json "params" - end - t.datetime "read_at" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.index ["account_id"], name: "index_notifications_on_account_id" - t.index ["recipient_type", "recipient_id"], name: "index_notifications_on_recipient_type_and_recipient_id" +ActiveRecord::Schema[7.1].define(version: 2023_12_15_202924) do + create_table "accounts", force: :cascade do |t| + t.string "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - create_table "json_notifications", force: :cascade do |t| - t.integer "account_id" - t.string "recipient_type", null: false - t.bigint "recipient_id", null: false + create_table "noticed_events", force: :cascade do |t| t.string "type" + t.string "record_type" + t.integer "record_id" t.json "params" - t.datetime "read_at" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.index ["account_id"], name: "index_with_json_on_account_id" - t.index ["recipient_type", "recipient_id"], name: "index_with_json_on_recipient_type_and_recipient_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["record_type", "record_id"], name: "index_noticed_events_on_record" end - create_table "jsonb_notifications", force: :cascade do |t| - t.integer "account_id" + create_table "noticed_notifications", force: :cascade do |t| + t.integer "event_id", null: false t.string "recipient_type", null: false - t.bigint "recipient_id", null: false - t.string "type" - if t.respond_to? :jsonb - t.jsonb "params" - else - t.json "params" - end - t.datetime "read_at" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.index ["account_id"], name: "index_with_jsonb_on_account_id" - t.index ["recipient_type", "recipient_id"], name: "index_with_jsonb_on_recipient_type_and_recipient_id" - end - - create_table "text_notifications", force: :cascade do |t| - t.integer "account_id" - t.string "recipient_type", null: false - t.bigint "recipient_id", null: false - t.string "type" - t.text "params" + t.integer "recipient_id", null: false t.datetime "read_at" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.index ["account_id"], name: "index_text_notifications_on_account_id" - t.index ["recipient_type", "recipient_id"], name: "index_text_on_recipient_type_and_recipient_id" - end - - create_table "accounts", force: :cascade do |t| - t.string "name" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false + t.datetime "seen_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["event_id"], name: "index_noticed_notifications_on_event_id" + t.index ["recipient_type", "recipient_id"], name: "index_noticed_notifications_on_recipient" end create_table "users", force: :cascade do |t| - t.string "first_name" - t.string "last_name" t.string "email" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end end diff --git a/test/dummy/noticed_test b/test/dummy/noticed_test deleted file mode 100644 index 22868a11c1a68edf72d2136305f0c287cd5ff46f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69632 zcmeI($!^<57{GB-vP3VjWupj)A}~V82S>1kSY$eJe zY1cl=Npow_LxG|X&`W_{ddMLU&==@(>Lox=JtSyfASGo-7Guc~+ytmCWLdhRX_6$3?EUqh>f50I*t7RKc#@l5w|hAvU6}iN zF!qNu9DObgJsK(w{yG>R_;KJ=|6K30-c{v`_|LIFW2a-^MW6Tl-mVkM5kTOL2-KDn ziki#GJ9D16Q#OTTShi<6wkR7F(-TGEiB@I9@7XX<1ubnV;9L-<(~@-(1z^W>(K>$xTt-FdMb}f`VfT&nz0kOKSe{P0y;B zXD+51?fohn>%orpRZ>@nQ6|?a$L`_MI|(oWznM02eitKvA?GIuDvs$sH3BeG%ggX8@A_)z2_6*alR-% z(okO>W{ zck9_z&nj7k-P|158}#hr`!gSD$s=@{)Rq>tWQ(o?xNty8eVr(sn`oihcqQ;RjsVS}(XIn@y znCQld(tNgsptU>JUH@Ed-XCh{ceyScQE`)65P1CvBK*Na(xf^am>dw!nYI8k`x;-xMM4P5Ml!+bWTBv@k)%1sIbkO5&UW8h8 zl(>UYMO_+~Yx-fM*-xy{0eU(Le7|v<7@I1ju1DhP*|YNQNABML#$5B>hG`efm)=H0 z>H5;nGr_;T=lqtbovX5_X{*#cSqdj}~X1MCiUl03(QE~mhJLi_D5kLR|1Q0*~0R#|0009IL=um+5 ze}_<1LjVB;5I_I{1Q0*~0R#|0po<0g{eKrv9-$+E00IagfB*srAb@0?cI~Slz9}iv+b8vW{+3y&+Qk*ublm!@E9RD{te4FT zg=(esQaqcUo@&2m<^C@}ed#EYdU7J4|G;uwFX+1zy!fwO?_U3ZDkYvCGf=`o009IL zKmY**5I_I{1Q0*~fg=hG%aN4-HA6Z*ozCi)^h_=t{Qh6|7yqpPk4Q>01Q0*~0R#|0 z009ILKmY**j-9|rG}RYg|2OXcf8_rNfDHiz5I_I{1Q0*~0R#|0009IJ6(~nyW1r;n zZeh)=2*X_yx}GhRa#J~dx|AsiktvxOQOHeBi=vpE&ZVcOrbIeBt(QuzZu`$%)F*S3 z?e|=hV`C@tdDGssoT^!33M8Uqa?`gGk)h~VU(@#n{{q|e$E5%O diff --git a/test/dummy/test/fixtures/accounts.yml b/test/dummy/test/fixtures/accounts.yml new file mode 100644 index 00000000..7d412240 --- /dev/null +++ b/test/dummy/test/fixtures/accounts.yml @@ -0,0 +1,7 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: MyString + +two: + name: MyString diff --git a/test/dummy/test/fixtures/users.yml b/test/dummy/test/fixtures/users.yml new file mode 100644 index 00000000..6c868efa --- /dev/null +++ b/test/dummy/test/fixtures/users.yml @@ -0,0 +1,7 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + email: MyString + +two: + email: MyString diff --git a/test/dummy/test/models/account_test.rb b/test/dummy/test/models/account_test.rb new file mode 100644 index 00000000..b6de6a15 --- /dev/null +++ b/test/dummy/test/models/account_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class AccountTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/dummy/test/models/user_test.rb b/test/dummy/test/models/user_test.rb new file mode 100644 index 00000000..5c07f490 --- /dev/null +++ b/test/dummy/test/models/user_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class UserTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/fixtures/accounts.yml b/test/fixtures/accounts.yml index c742788d..a44e54ad 100644 --- a/test/fixtures/accounts.yml +++ b/test/fixtures/accounts.yml @@ -1,2 +1,5 @@ -primary: - name: "Primary" +one: + name: Account One + +two: + name: Account Two diff --git a/test/fixtures/files/.keep b/test/fixtures/files/.keep new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/files/microsoft_teams/failure.txt b/test/fixtures/files/microsoft_teams/failure.txt deleted file mode 100644 index 01ea11d0..00000000 --- a/test/fixtures/files/microsoft_teams/failure.txt +++ /dev/null @@ -1,19 +0,0 @@ -HTTP/1.1 403 -cache-control: no-cache -pragma: no-cache -content-length: 1 -content-type: text/plain; charset=utf-8 -expires: -1 -request-id: ea4f4a2e-4077-4e46-908b-c4dcf1c5cf78 -strict-transport-security: max-age=31536000; includeSubDomains; preload -x-calculatedbetarget: AM9PR07MB7138.eurprd07.prod.outlook.com -x-backendhttpstatus: 200 -x-aspnet-version: 4.0.30319 -x-cafeserver: AM8P191CA0028.EURP191.PROD.OUTLOOK.COM -x-beserver: AM9PR07MB7138 -x-proxy-routingcorrectness: 1 -x-proxy-backendserverstatus: 200 -x-powered-by: ASP.NET -x-feserver: AM8P191CA0028 -x-msedge-ref: Ref A: 75193BF48B8E459C89D7DE54777A06AA Ref B: MAD30EDGE0716 Ref C: 2020-11-09T19:00:35Z -date: Mon, 09 Nov 2020 19:00:35 GMT diff --git a/test/fixtures/files/microsoft_teams/success.txt b/test/fixtures/files/microsoft_teams/success.txt deleted file mode 100644 index 3ecd94f2..00000000 --- a/test/fixtures/files/microsoft_teams/success.txt +++ /dev/null @@ -1,21 +0,0 @@ -HTTP/1.1 200 -cache-control: no-cache -pragma: no-cache -content-length: 1 -content-type: text/plain; charset=utf-8 -expires: -1 -request-id: ea4f4a2e-4077-4e46-908b-c4dcf1c5cf78 -strict-transport-security: max-age=31536000; includeSubDomains; preload -x-calculatedbetarget: AM9PR07MB7138.eurprd07.prod.outlook.com -x-backendhttpstatus: 200 -x-aspnet-version: 4.0.30319 -x-cafeserver: AM8P191CA0028.EURP191.PROD.OUTLOOK.COM -x-beserver: AM9PR07MB7138 -x-proxy-routingcorrectness: 1 -x-proxy-backendserverstatus: 200 -x-powered-by: ASP.NET -x-feserver: AM8P191CA0028 -x-msedge-ref: Ref A: 75193BF48B8E459C89D7DE54777A06AA Ref B: MAD30EDGE0716 Ref C: 2020-11-09T19:00:35Z -date: Mon, 09 Nov 2020 19:00:35 GMT - -1 \ No newline at end of file diff --git a/test/fixtures/files/slack/failure.txt b/test/fixtures/files/slack/failure.txt deleted file mode 100644 index 06eb0306..00000000 --- a/test/fixtures/files/slack/failure.txt +++ /dev/null @@ -1,11 +0,0 @@ -HTTP/1.1 403 -date: Mon, 09 Nov 2020 12:14:30 GMT -server: Apache -strict-transport-security: max-age=31536000; includeSubDomains; preload -x-slack-backend: r -access-control-allow-origin: * -x-frame-options: SAMEORIGIN -referrer-policy: no-referrer -vary: Accept-Encoding -content-type: text/html -x-via: haproxy-www-2n6w,haproxy-edge-fra-9k3b diff --git a/test/fixtures/files/slack/success.txt b/test/fixtures/files/slack/success.txt deleted file mode 100644 index edb11430..00000000 --- a/test/fixtures/files/slack/success.txt +++ /dev/null @@ -1,13 +0,0 @@ -HTTP/1.1 200 -date: Mon, 09 Nov 2020 12:14:30 GMT -server: Apache -strict-transport-security: max-age=31536000; includeSubDomains; preload -x-slack-backend: r -access-control-allow-origin: * -x-frame-options: SAMEORIGIN -referrer-policy: no-referrer -vary: Accept-Encoding -content-type: text/html -x-via: haproxy-www-2n6w,haproxy-edge-fra-9k3b - -ok diff --git a/test/fixtures/files/twilio/failure.txt b/test/fixtures/files/twilio/failure.txt deleted file mode 100644 index 678c8dff..00000000 --- a/test/fixtures/files/twilio/failure.txt +++ /dev/null @@ -1,18 +0,0 @@ -HTTP/1.1 403 -Date: Mon, 09 Nov 2020 17:49:49 GMT -Content-Type: application/json -Content-Length: 821 -Connection: keep-alive -Twilio-Concurrent-Requests: 1 -Twilio-Request-Id: rcfgo3MAij1kMIb50j8mQBQ9kIp0vcsp -Twilio-Request-Duration: 0.113 -Access-Control-Allow-Origin: * -Access-Control-Allow-Headers: Accept, Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since -Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS -Access-Control-Expose-Headers: ETag -Access-Control-Allow-Credentials: true -X-Powered-By: AT-5000 -X-Shenanigans: none -X-Home-Region: us1 -X-API-Domain: api.twilio.com -Strict-Transport-Security: max-age=31536000 diff --git a/test/fixtures/files/twilio/success.txt b/test/fixtures/files/twilio/success.txt deleted file mode 100644 index b1de4d91..00000000 --- a/test/fixtures/files/twilio/success.txt +++ /dev/null @@ -1,20 +0,0 @@ -HTTP/1.1 201 CREATED -Date: Mon, 09 Nov 2020 17:49:49 GMT -Content-Type: application/json -Content-Length: 821 -Connection: keep-alive -Twilio-Concurrent-Requests: 1 -Twilio-Request-Id: rcfgo3MAij1kMIb50j8mQBQ9kIp0vcsp -Twilio-Request-Duration: 0.113 -Access-Control-Allow-Origin: * -Access-Control-Allow-Headers: Accept, Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since -Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS -Access-Control-Expose-Headers: ETag -Access-Control-Allow-Credentials: true -X-Powered-By: AT-5000 -X-Shenanigans: none -X-Home-Region: us1 -X-API-Domain: api.twilio.com -Strict-Transport-Security: max-age=31536000 - -{"sid": "um2w7iplBZAe6Ogi7o8YVCNIE2YeOfAP", "date_created": "Mon, 09 Nov 2020 17:49:49 +0000", "date_updated": "Mon, 09 Nov 2020 17:49:49 +0000", "date_sent": null, "account_sid": "a", "to": "8675309", "from": "c", "messaging_service_sid": null, "body": "Noticed", "status": "queued", "num_segments": "1", "num_media": "0", "direction": "outbound-api", "api_version": "2010-04-01", "price": null, "price_unit": "USD", "error_code": null, "error_message": null, "uri": "/2010-04-01/Accounts/a/Messages/um2w7iplBZAe6Ogi7o8YVCNIE2YeOfAP.json", "subresource_uris": {"media": "/2010-04-01/Accounts/a/Messages/um2w7iplBZAe6Ogi7o8YVCNIE2YeOfAP/Media.json"}} \ No newline at end of file diff --git a/test/fixtures/files/vonage/failure.txt b/test/fixtures/files/vonage/failure.txt deleted file mode 100644 index 42cd6331..00000000 --- a/test/fixtures/files/vonage/failure.txt +++ /dev/null @@ -1,10 +0,0 @@ -HTTP/1.1 403 -server: nginx -date: Mon, 09 Nov 2020 18:24:47 GMT -content-type: application/json -cache-control: max-age=1 -x-frame-options: deny -x-xss-protection: 1; mode=block; -strict-transport-security: max-age=31536000; includeSubdomains -content-disposition: attachment; filename="api.txt" -x-nexmo-trace-id: cNvzs9rIF6DymshFf63xsi85UiiOQRYl diff --git a/test/fixtures/files/vonage/success.txt b/test/fixtures/files/vonage/success.txt deleted file mode 100644 index 949ebe8b..00000000 --- a/test/fixtures/files/vonage/success.txt +++ /dev/null @@ -1,22 +0,0 @@ -HTTP/1.1 200 -server: nginx -date: Mon, 09 Nov 2020 18:24:47 GMT -content-type: application/json -cache-control: max-age=1 -x-frame-options: deny -x-xss-protection: 1; mode=block; -strict-transport-security: max-age=31536000; includeSubdomains -content-disposition: attachment; filename="api.txt" -x-nexmo-trace-id: cNvzs9rIF6DymshFf63xsi85UiiOQRYl - -{ - "message-count": "1", - "messages": [{ - "to": "e", - "message-id": "FFQ4KtfvqqpSpye8", - "status": "0", - "remaining-balance": "1.86220000", - "message-price": "0.06890000", - "network": "21407" - }] -} \ No newline at end of file diff --git a/test/fixtures/noticed/events.yml b/test/fixtures/noticed/events.yml new file mode 100644 index 00000000..8313e405 --- /dev/null +++ b/test/fixtures/noticed/events.yml @@ -0,0 +1,22 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + type: CommentNotifier + record: one + record_type: User + params: + foo: bar + +two: + type: CommentNotifier + record: two + record_type: User + params: + foo: bar + +three: + type: ReceiptNotifier + record: two + record_type: User + params: + foo: bar diff --git a/test/fixtures/noticed/notifications.yml b/test/fixtures/noticed/notifications.yml new file mode 100644 index 00000000..456e1275 --- /dev/null +++ b/test/fixtures/noticed/notifications.yml @@ -0,0 +1,25 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + event: one + recipient: one (User) + read_at: 2023-12-15 13:03:19 + seen_at: 2023-12-15 13:03:19 + +two: + event: two + recipient: two (User) + read_at: 2023-12-15 13:03:19 + seen_at: 2023-12-15 13:03:19 + +three: + event: three + recipient: one (Account) + read_at: 2023-12-15 13:03:19 + seen_at: 2023-12-15 13:03:19 + +two: + event: three + recipient: two (Account) + read_at: 2023-12-15 13:03:19 + seen_at: 2023-12-15 13:03:19 diff --git a/test/fixtures/notifications.yml b/test/fixtures/notifications.yml deleted file mode 100644 index 37427f71..00000000 --- a/test/fixtures/notifications.yml +++ /dev/null @@ -1,21 +0,0 @@ -one: - type: CommentNotification - recipient: one (User) - params: - foo: bar - account: - _aj_globalid: gid://dummy/Account/<%= ActiveRecord::FixtureSet.identify(:primary) %> - _aj_symbol_keys: - - account - read_at: <%= Time.current %> - -missing_account: - type: CommentNotification - recipient: one (User) - params: - foo: bar - account: - _aj_globalid: gid://dummy/Account/100000 - _aj_symbol_keys: - - account - read_at: <%= Time.current %> diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index 38000f54..584e4641 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -1,9 +1,5 @@ one: - first_name: "First" - last_name: "User" - email: "first@example.com" + email: one@example.org two: - first_name: "Second" - last_name: "User" - email: "second@example.com" + email: two@example.org diff --git a/test/generators/model_generator_test.rb b/test/generators/model_generator_test.rb deleted file mode 100644 index bd80d62e..00000000 --- a/test/generators/model_generator_test.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" -require "generators/noticed/model_generator" - -class Noticed::ModelGeneratorTest < ::Rails::Generators::TestCase - tests ::Noticed::Generators::ModelGenerator - - destination Rails.root - - teardown do - remove_if_exists("app/models/test_notification.rb") - remove_if_exists("db/migrate") - remove_if_exists("test") - end - - test "Active Record model and migration are built" do - run_generator ["TestNotification"] - assert_file "app/models/test_notification.rb" - assert_migration "db/migrate/create_test_notifications.rb" - end - - def remove_if_exists(path) - full_path = Rails.root.join(path) - FileUtils.rm_rf(full_path) - end -end diff --git a/test/jobs/event_job_test.rb b/test/jobs/event_job_test.rb new file mode 100644 index 00000000..72c16f24 --- /dev/null +++ b/test/jobs/event_job_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class EventJobTest < ActiveJob::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/.keep b/test/models/.keep new file mode 100644 index 00000000..e69de29b diff --git a/test/models/noticed/event_test.rb b/test/models/noticed/event_test.rb new file mode 100644 index 00000000..aeb18824 --- /dev/null +++ b/test/models/noticed/event_test.rb @@ -0,0 +1,40 @@ +require "test_helper" + +class Noticed::EventTest < ActiveSupport::TestCase + class ExampleNotifier < Noticed::Event + deliver_by :test + required_params :message + end + + test "validates required params" do + assert_raises Noticed::ValidationError do + ExampleNotifier.deliver + end + end + + test "deliver saves event" do + assert_difference "Noticed::Event.count" do + ExampleNotifier.with(message: "test").deliver + end + end + + test "deliver saves notifications" do + assert_no_difference "Noticed::Notification.count" do + ExampleNotifier.with(message: "test").deliver + end + + assert_difference "Noticed::Notification.count" do + ExampleNotifier.with(message: "test").deliver(users(:one)) + end + + assert_difference "Noticed::Notification.count", User.count do + ExampleNotifier.with(message: "test").deliver(User.all) + end + end + + test "deliver extracts record from params" do + account = accounts(:one) + event = ExampleNotifier.with(message: "test", record: account).deliver + assert_equal account, event.record + end +end diff --git a/test/models/noticed/notification_test.rb b/test/models/noticed/notification_test.rb new file mode 100644 index 00000000..733c2eff --- /dev/null +++ b/test/models/noticed/notification_test.rb @@ -0,0 +1,17 @@ +require "test_helper" + +class Noticed::NotificationTest < ActiveSupport::TestCase + test "delegates params to event" do + notification = noticed_notifications(:one) + assert_equal notification.event.params, notification.params + end + + test "delegates record to event" do + notification = noticed_notifications(:one) + assert_equal notification.event.record, notification.record + end + + test "notification associations" do + assert_equal 1, users(:one).notifications.count + end +end diff --git a/test/noticed/coder_test.rb b/test/noticed/coder_test.rb deleted file mode 100644 index a827d254..00000000 --- a/test/noticed/coder_test.rb +++ /dev/null @@ -1,30 +0,0 @@ -require "test_helper" - -class CoderTest < ActiveSupport::TestCase - test "uses TextCoder for text columns" do - assert_equal Noticed::TextCoder, TextNotification.noticed_coder - end - - test "uses Coder for json columns" do - assert_equal Noticed::Coder, JsonNotification.noticed_coder - end - - test "uses Coder for jsonb columns" do - assert_equal Noticed::Coder, JsonbNotification.noticed_coder - end - - test "serializes globalid objects with text column" do - notification = Notification.create!(recipient: user, type: "Example", params: {user: user}) - assert_equal({user: user}, notification.params) - end - - test "serializes globalid objects with json column" do - notification = JsonNotification.create!(recipient: user, type: "Example", params: {user: user}) - assert_equal({user: user}, notification.params) - end - - test "serializes globalid objects with jsonb column" do - notification = JsonbNotification.create!(recipient: user, type: "Example", params: {user: user}) - assert_equal({user: user}, notification.params) - end -end diff --git a/test/noticed/has_notifications_test.rb b/test/noticed/has_notifications_test.rb deleted file mode 100644 index 3a27db19..00000000 --- a/test/noticed/has_notifications_test.rb +++ /dev/null @@ -1,47 +0,0 @@ -require "test_helper" - -class HasNotificationsTest < ActiveSupport::TestCase - class DatabaseDelivery < Noticed::Base - deliver_by :database - end - - test "has_noticed_notifications" do - assert User.respond_to?(:has_noticed_notifications) - end - - test "noticed notifications association" do - assert user.respond_to?(:notifications_as_user) - end - - test "noticed notifications with custom name" do - assert user.respond_to?(:notifications_as_owner) - end - - test "association returns notifications" do - assert_difference "user.notifications_as_user.count" do - DatabaseDelivery.with(user: user, foo: :bar).deliver(user) - end - end - - test "association with custom name returns notifications" do - assert_difference "user.notifications_as_owner.count" do - DatabaseDelivery.with(owner: user, foo: :bar).deliver(user) - end - end - - test "deletes notifications with matching param" do - DatabaseDelivery.with(user: user, foo: :bar).deliver(users(:two)) - - assert_difference "Notification.count", -1 do - user.destroy - end - end - - test "doesn't delete notifications when disabled" do - DatabaseDelivery.with(owner: user, foo: :bar).deliver(users(:two)) - - assert_no_difference "Notification.count" do - user.destroy - end - end -end diff --git a/test/noticed/model_test.rb b/test/noticed/model_test.rb deleted file mode 100644 index 0e44ef22..00000000 --- a/test/noticed/model_test.rb +++ /dev/null @@ -1,42 +0,0 @@ -require "test_helper" - -class ModelTest < ActiveSupport::TestCase - test "can mark notifications as read" do - notification = make_notification - Notification.mark_as_read! - assert_not_nil notification.reload.read_at - end - - test "can mark notifications as unread" do - notification = make_notification(read: true) - Notification.mark_as_unread! - assert_nil notification.reload.read_at - end - - test "unread scope" do - assert_difference "Notification.unread.count" do - make_notification - end - end - - test "read scope" do - assert_difference "Notification.read.count" do - make_notification(read: true) - end - end - - test "safely handles missing GlobalID records in params" do - notification = notifications(:missing_account) - assert_nothing_raised do - notification.params - assert notification.deserialize_error? - end - end - - def make_notification(read: false) - CommentNotification.with(foo: :bar).deliver(users(:one)) - notification = Notification.last - notification.mark_as_read! if read - notification - end -end diff --git a/test/noticed_test.rb b/test/noticed_test.rb index 7b7ec08d..1ec72b5e 100644 --- a/test/noticed_test.rb +++ b/test/noticed_test.rb @@ -1,236 +1,7 @@ require "test_helper" -class IfExample < Noticed::Base - deliver_by :test, if: :falsey - def falsey - false - end -end - -class UnlessExample < Noticed::Base - deliver_by :test, unless: :truthy - def truthy - true - end -end - -class RecipientExample < Noticed::Base - deliver_by :database - - def message - recipient.id - end -end - -class IfRecipientExample < Noticed::Base - deliver_by :test, if: :falsey - def falsey - raise ArgumentError unless recipient - end -end - -class UnlessRecipientExample < Noticed::Base - deliver_by :test, unless: :truthy - def truthy - raise ArgumentError unless recipient - end -end - -class AttributeExample < Noticed::Base - param :user_id -end - -class MultipleParamsExample < Noticed::Base - params :foo, :bar -end - -class CallbackExample < Noticed::Base - class_attribute :callbacks, default: [] - - deliver_by :database - - before_database do - raise ArgumentError unless recipient - - self.class.callbacks << :before_database - end - - around_database do - raise ArgumentError unless recipient - - self.class.callbacks << :around_database - end - - after_database do - raise ArgumentError unless recipient - - self.class.callbacks << :after_database - end - - after_deliver do - self.class.callbacks << :after_everything - end -end - -class RequiredOption < Noticed::DeliveryMethods::Base - def deliver - end - - def self.validate!(options) - unless options.key?(:a_required_option) - raise Noticed::ValidationError, "the `a_required_option` attribute is missing" - end - end -end - -class NotificationWithValidOptions < Noticed::Base - deliver_by :custom, class: "RequiredOption", a_required_option: true -end - -class NotificationWithoutValidOptions < Noticed::Base - deliver_by :custom, class: "RequiredOption" -end - -class With5MinutesDelay < Noticed::Base - deliver_by :test, delay: 5.minutes -end - -class WithDynamicDelay < Noticed::Base - deliver_by :test, delay: :dynamic_delay - - def dynamic_delay - (recipient.email == "first@example.com") ? 1.minute : 2.minutes - end -end - -class WithCustomQueue < Noticed::Base - deliver_by :test, queue: "custom" -end - -class Noticed::Test < ActiveSupport::TestCase - test "stores data in params" do - notification = make_notification(foo: :bar, user: user) - assert_equal :bar, notification.params[:foo] - assert_equal user, notification.params[:user] - end - - test "can deliver a notification" do - assert make_notification(foo: :bar).deliver(user) - end - - test "enqueues notification jobs (skipping database)" do - assert_enqueued_jobs CommentNotification.delivery_methods.length - 1 do - CommentNotification.deliver_later(user) - end - end - - test "cancels delivery when if clause is falsey" do - IfExample.deliver(user) - assert_empty Noticed::DeliveryMethods::Test.delivered - end - - test "cancels delivery when unless clause is truthy" do - UnlessExample.deliver(user) - assert_empty Noticed::DeliveryMethods::Test.delivered - end - - test "has access to recipient in if clause" do - assert_nothing_raised do - IfRecipientExample.deliver(user) - end - end - - test "has access to recipient in unless clause" do - assert_nothing_raised do - UnlessRecipientExample.deliver(user) - end - end - - test "has access to recipient in notification instance" do - RecipientExample.deliver(user) - assert_equal user.id, Notification.last.to_notifier.message - end - - test "validates attributes for params" do - assert_raises Noticed::ValidationError do - AttributeExample.deliver(users(:one)) - end - end - - test "allows to pass multiple params" do - assert_equal [:foo, :bar], MultipleParamsExample.with(foo: true, bar: false).param_names - end - - test "runs callbacks on notifications" do - CallbackExample.deliver(user) - assert_equal [:before_database, :around_database, :after_database, :after_everything], CallbackExample.callbacks - end - - test "runs callbacks on delivery methods" do - assert_difference "Noticed::DeliveryMethods::Test.callbacks.count" do - make_notification(foo: :bar).deliver(user) - end - end - - test "can send notifications to multiple recipients" do - assert User.count >= 2 - assert_difference "Notification.count", User.count do - make_notification(foo: :bar).deliver(User.all) - end - end - - test "assigns record to notification when delivering" do - notification = make_notification(foo: :bar) - notification.deliver(user) - assert_equal Notification.last, Noticed::DeliveryMethods::Test.delivered.last.record - assert_equal notification.record, Noticed::DeliveryMethods::Test.delivered.last.record - end - - test "assigns recipient to notification when delivering" do - make_notification(foo: :bar).deliver(user) - assert_equal user, Noticed::DeliveryMethods::Test.delivered.last.recipient - end - - test "validates options of delivery methods when options are valid" do - assert_nothing_raised do - NotificationWithValidOptions.deliver(user) - end - end - - test "validates options of delivery methods when options are invalid" do - assert_raises Noticed::ValidationError do - NotificationWithoutValidOptions.deliver(user) - end - end - - test "asserts delivery is delayed" do - freeze_time do - assert_enqueued_with(at: 5.minutes.from_now) do - With5MinutesDelay.deliver(user) - end - end - end - - test "asserts dynamic delay" do - freeze_time do - assert_enqueued_with(at: 1.minutes.from_now) do - WithDynamicDelay.deliver(users(:one)) - end - - assert_enqueued_with(at: 2.minutes.from_now) do - WithDynamicDelay.deliver(users(:two)) - end - end - end - - test "asserts delivery is queued with different queue" do - assert_enqueued_with(queue: "custom") do - WithCustomQueue.deliver_later(user) - end - end - - test "loading notification from fixture" do - notification = notifications(:one) - assert_equal accounts(:primary), notification.params[:account] +class NoticedTest < ActiveSupport::TestCase + test "it has a version number" do + assert Noticed::VERSION end end diff --git a/test/notifier_test.rb b/test/notifier_test.rb new file mode 100644 index 00000000..7fc1f361 --- /dev/null +++ b/test/notifier_test.rb @@ -0,0 +1,76 @@ +require "test_helper" + +class NotifierTest < ActiveSupport::TestCase + include ActiveJob::TestHelper + + class SimpleNotifier < Noticed::Event + deliver_by :test + required_params :message + + def url + root_url(host: "example.org") + end + end + + class InheritedNotifier < SimpleNotifier + end + + class BulkNotifier < Noticed::Event + bulk_deliver_by :webhook, url: "https://example.org/bulk" + end + + test "includes Rails urls" do + assert_equal "http://example.org/", SimpleNotifier.new.url + end + + test "notifiers inherit required params" do + assert_equal [:message], InheritedNotifier.required_params + end + + test "deliver creates an event" do + assert_difference "Noticed::Event.count" do + ReceiptNotifier.deliver(User.first) + end + end + + test "deliver creates notifications for each recipient" do + assert_no_difference "Noticed::Notification.count" do + ReceiptNotifier.deliver + end + + assert_difference "Noticed::Notification.count" do + ReceiptNotifier.deliver(User.first) + end + + assert_difference "Noticed::Notification.count", User.count do + ReceiptNotifier.deliver(User.all) + end + end + + test "creates jobs for deliveries" do + # Delivering a notification creates records + assert_enqueued_jobs 1, only: Noticed::EventJob do + ReceiptNotifier.deliver(User.first) + end + + # Run the Event Job + assert_enqueued_jobs 1, only: Noticed::DeliveryMethods::Test do + perform_enqueued_jobs + end + + # Run the individual deliveries + perform_enqueued_jobs + + assert_equal Noticed::Notification.last, Noticed::DeliveryMethods::Test.delivered.last + end + + test "creates jobs for bulk deliveries" do + assert_enqueued_jobs 1, only: Noticed::EventJob do + BulkNotifier.deliver + end + + assert_enqueued_jobs 1, only: Noticed::BulkDeliveryMethods::Webhook do + perform_enqueued_jobs + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 87c96119..9d1ac1cc 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -4,59 +4,18 @@ require_relative "../test/dummy/config/environment" ActiveRecord::Migrator.migrations_paths = [File.expand_path("../test/dummy/db/migrate", __dir__)] require "rails/test_help" -require "byebug" - -# Filter out the backtrace from minitest while preserving the one from other libraries. -Minitest.backtrace_filter = Minitest::BacktraceFilter.new - -require "rails/test_unit/reporter" -Rails::TestUnitReporter.executable = "bin/test" +require "minitest/mock" +require "webmock/minitest" # Load fixtures from the engine if ActiveSupport::TestCase.respond_to?(:fixture_paths=) - ActiveSupport::TestCase.fixture_paths << File.expand_path("../fixtures", __FILE__) - ActionDispatch::IntegrationTest.fixture_paths << File.expand_path("../fixtures", __FILE__) + ActiveSupport::TestCase.fixture_paths = [File.expand_path("fixtures", __dir__)] + ActionDispatch::IntegrationTest.fixture_paths = ActiveSupport::TestCase.fixture_paths + ActiveSupport::TestCase.file_fixture_path = File.expand_path("fixtures", __dir__) + "/files" + ActiveSupport::TestCase.fixtures :all elsif ActiveSupport::TestCase.respond_to?(:fixture_path=) - ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) + ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__) ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path -end - -ActiveSupport::TestCase.file_fixture_path = File.expand_path("../fixtures/files", __FILE__) -ActiveSupport::TestCase.fixtures :all - -require "minitest/unit" -require "webmock/minitest" - -class ExampleNotification < Noticed::Base - class_attribute :callback_responses, default: [] - - deliver_by :test, foo: :bar - deliver_by :database - - # after_deliver do - # self.class.callback_reponses << "delivered" - # end -end - -class ActiveSupport::TestCase - include ActionCable::TestHelper - include ActionMailer::TestHelper - - teardown do - Noticed::DeliveryMethods::Test.clear! - end - - private - - def user - @user ||= users(:one) - end - - def make_notification(params) - ExampleNotification.with(params) - end - - def stub_delivery_method_request(delivery_method:, matcher:, method: :post, type: :success) - stub_request(method, matcher).to_return(File.new(file_fixture("#{delivery_method}/#{type}.txt"))) - end + ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files" + ActiveSupport::TestCase.fixtures :all end diff --git a/test/translation_test.rb b/test/translation_test.rb index 8be0dfdb..391e88b5 100644 --- a/test/translation_test.rb +++ b/test/translation_test.rb @@ -1,7 +1,11 @@ require "test_helper" class TranslationTest < ActiveSupport::TestCase - class I18nExample < Noticed::Base + class I18nExample < Noticed::Event + deliver_by :test do |config| + config.message = -> { t("hello") } + end + def message t("hello") end @@ -11,13 +15,13 @@ def html_message end end - class Noticed::I18nExample < Noticed::Base + class Noticed::I18nExample < Noticed::Event def message t(".message") end end - class ::ScopedI18nExample < Noticed::Base + class ::ScopedI18nExample < Noticed::Event def i18n_scope :noticed end @@ -33,7 +37,7 @@ def message end test "I18n supports namespaces" do - assert_equal "notifications.noticed.i18n_example.message", Noticed::I18nExample.new.send(:scope_translation_key, ".message") + assert_equal "notifiers.noticed.i18n_example.message", Noticed::I18nExample.new.send(:scope_translation_key, ".message") assert_equal "This is a notification", Noticed::I18nExample.new.message end @@ -49,4 +53,9 @@ def message assert message.html_safe? end end + + test "delivery method blocks can use translations" do + block = I18nExample.delivery_methods[:test].config[:message] + assert_equal "Hello world", noticed_notifications(:one).instance_exec(&block) + end end From a0a9c4ee2521018e8d9a49eb3284ae23fefe136b Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 22 Dec 2023 13:53:19 -0600 Subject: [PATCH 15/39] Use Rails 6.1 for migrations --- db/migrate/20231215190233_create_noticed_tables.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20231215190233_create_noticed_tables.rb b/db/migrate/20231215190233_create_noticed_tables.rb index 0cb4ac61..9b966f48 100644 --- a/db/migrate/20231215190233_create_noticed_tables.rb +++ b/db/migrate/20231215190233_create_noticed_tables.rb @@ -1,4 +1,4 @@ -class CreateNoticedTables < ActiveRecord::Migration[7.1] +class CreateNoticedTables < ActiveRecord::Migration[6.1] def change create_table :noticed_events do |t| t.string :type From a94c79a68d9285614132bf033c9ff02060807a62 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 22 Dec 2023 13:56:58 -0600 Subject: [PATCH 16/39] Update test schema for Rails 6.1 compat --- test/dummy/db/schema.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb index fda6271e..3314139f 100644 --- a/test/dummy/db/schema.rb +++ b/test/dummy/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2023_12_15_202924) do +ActiveRecord::Schema[6.1].define(version: 2023_12_15_202924) do create_table "accounts", force: :cascade do |t| t.string "name" t.datetime "created_at", null: false From 09de357319abfa7eb8bf8ad4daad68ccc1a55129 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 22 Dec 2023 14:15:16 -0600 Subject: [PATCH 17/39] Add Rails 6.1 back to CI --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9c28ab1..fdf46fc6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,7 @@ jobs: matrix: ruby: ['3.0', '3.1', '3.2'] gemfile: + - rails_6_1 - rails_7 - rails_7_1 - rails_main @@ -51,6 +52,7 @@ jobs: matrix: ruby: ['3.0', '3.1', '3.2'] gemfile: + - rails_6_1 - rails_7 - rails_7_1 - rails_main @@ -96,6 +98,7 @@ jobs: matrix: ruby: ['3.0', '3.1', '3.2'] gemfile: + - rails_6_1 - rails_7 - rails_7_1 - rails_main @@ -106,7 +109,7 @@ jobs: services: postgres: - image: postgres:12 + image: postgres:16 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: password From fe42e81a10e52959d7bf82425df81e6d417a8833 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 22 Dec 2023 14:21:18 -0600 Subject: [PATCH 18/39] Add linux to lock --- gemfiles/rails_6_1.gemfile.lock | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gemfiles/rails_6_1.gemfile.lock b/gemfiles/rails_6_1.gemfile.lock index 39f3c1c9..31d76de8 100644 --- a/gemfiles/rails_6_1.gemfile.lock +++ b/gemfiles/rails_6_1.gemfile.lock @@ -239,7 +239,9 @@ GEM zeitwerk (2.6.12) PLATFORMS - arm64-darwin + arm64-darwin-22 + x86_64-darwin-22 + x86_64-linux DEPENDENCIES apnotic (~> 1.7) From 10ef5bb456dea2282ee0919ac60f300929fa76c7 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 22 Dec 2023 14:37:18 -0600 Subject: [PATCH 19/39] Update lockfiles --- gemfiles/rails_6_1.gemfile.lock | 4 +++- gemfiles/rails_7.gemfile.lock | 2 +- gemfiles/rails_7_1.gemfile.lock | 2 +- gemfiles/rails_main.gemfile.lock | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/gemfiles/rails_6_1.gemfile.lock b/gemfiles/rails_6_1.gemfile.lock index 31d76de8..4f29815e 100644 --- a/gemfiles/rails_6_1.gemfile.lock +++ b/gemfiles/rails_6_1.gemfile.lock @@ -138,6 +138,8 @@ GEM nio4r (2.7.0) nokogiri (1.15.5-arm64-darwin) racc (~> 1.4) + nokogiri (1.15.5-x86_64-darwin) + racc (~> 1.4) os (1.1.4) parallel (1.24.0) parser (3.2.2.4) @@ -212,6 +214,7 @@ GEM activesupport (>= 5.2) sprockets (>= 3.0.0) sqlite3 (1.6.9-arm64-darwin) + sqlite3 (1.6.9-x86_64-darwin) standard (1.32.1) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) @@ -241,7 +244,6 @@ GEM PLATFORMS arm64-darwin-22 x86_64-darwin-22 - x86_64-linux DEPENDENCIES apnotic (~> 1.7) diff --git a/gemfiles/rails_7.gemfile.lock b/gemfiles/rails_7.gemfile.lock index 57a114d3..65dc11ba 100644 --- a/gemfiles/rails_7.gemfile.lock +++ b/gemfiles/rails_7.gemfile.lock @@ -261,4 +261,4 @@ DEPENDENCIES webmock BUNDLED WITH - 2.4.21 + 2.5.3 diff --git a/gemfiles/rails_7_1.gemfile.lock b/gemfiles/rails_7_1.gemfile.lock index 75662764..fda05ce4 100644 --- a/gemfiles/rails_7_1.gemfile.lock +++ b/gemfiles/rails_7_1.gemfile.lock @@ -290,4 +290,4 @@ DEPENDENCIES webmock BUNDLED WITH - 2.4.21 + 2.5.3 diff --git a/gemfiles/rails_main.gemfile.lock b/gemfiles/rails_main.gemfile.lock index 789fd195..76913eea 100644 --- a/gemfiles/rails_main.gemfile.lock +++ b/gemfiles/rails_main.gemfile.lock @@ -295,4 +295,4 @@ DEPENDENCIES webmock BUNDLED WITH - 2.4.21 + 2.5.3 From 1b2b6b1da792904a513fbf00ba396abc5d3ea373 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 22 Dec 2023 14:40:28 -0600 Subject: [PATCH 20/39] Update gemfile --- gemfiles/rails_6_1.gemfile.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/gemfiles/rails_6_1.gemfile.lock b/gemfiles/rails_6_1.gemfile.lock index 4f29815e..9aebcdb7 100644 --- a/gemfiles/rails_6_1.gemfile.lock +++ b/gemfiles/rails_6_1.gemfile.lock @@ -244,6 +244,7 @@ GEM PLATFORMS arm64-darwin-22 x86_64-darwin-22 + x86_64-linux DEPENDENCIES apnotic (~> 1.7) From e08428cda6234aeb183ac1a059764f7c6d61e94d Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 22 Dec 2023 14:45:21 -0600 Subject: [PATCH 21/39] Add linux to rails 6.1 lockfile --- gemfiles/rails_6_1.gemfile.lock | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gemfiles/rails_6_1.gemfile.lock b/gemfiles/rails_6_1.gemfile.lock index 9aebcdb7..5aa717b4 100644 --- a/gemfiles/rails_6_1.gemfile.lock +++ b/gemfiles/rails_6_1.gemfile.lock @@ -140,6 +140,8 @@ GEM racc (~> 1.4) nokogiri (1.15.5-x86_64-darwin) racc (~> 1.4) + nokogiri (1.15.5-x86_64-linux) + racc (~> 1.4) os (1.1.4) parallel (1.24.0) parser (3.2.2.4) @@ -215,6 +217,7 @@ GEM sprockets (>= 3.0.0) sqlite3 (1.6.9-arm64-darwin) sqlite3 (1.6.9-x86_64-darwin) + sqlite3 (1.6.9-x86_64-linux) standard (1.32.1) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) From 922b037bc9822fbfd77189443fc33218c5d93f4a Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 22 Dec 2023 14:47:43 -0600 Subject: [PATCH 22/39] Remove version from schema --- test/dummy/db/schema.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb index 3314139f..b921920a 100644 --- a/test/dummy/db/schema.rb +++ b/test/dummy/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[6.1].define(version: 2023_12_15_202924) do +ActiveRecord::Schema.define(version: 2023_12_15_202924) do create_table "accounts", force: :cascade do |t| t.string "name" t.datetime "created_at", null: false From 7d76d00bf6f5910e33b7cf9e87e8481954c4bd4b Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 22 Dec 2023 15:10:39 -0600 Subject: [PATCH 23/39] Remove deliver transaction --- app/models/concerns/noticed/deliverable.rb | 21 ++++++++----------- test/dummy/config/environments/development.rb | 2 +- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/app/models/concerns/noticed/deliverable.rb b/app/models/concerns/noticed/deliverable.rb index b8e50d03..d3ed83aa 100644 --- a/app/models/concerns/noticed/deliverable.rb +++ b/app/models/concerns/noticed/deliverable.rb @@ -72,6 +72,7 @@ def deliver(recipients = nil) def deliver(recipients = nil) validate! + save recipients_attributes = Array.wrap(recipients).map do |recipient| { @@ -80,19 +81,15 @@ def deliver(recipients = nil) } end - transaction do - save - - if Rails.gem_version >= Gem::Version.new("7.0.0.alpha1") - notifications.insert_all(recipients_attributes, record_timestamps: true) if recipients_attributes.any? - else - time = Time.current - recipients_attributes.each do |attributes| - attributes[:created_at] = time - attributes[:updated_at] = time - end - notifications.insert_all(recipients_attributes) if recipients_attributes.any? + if Rails.gem_version >= Gem::Version.new("7.0.0.alpha1") + notifications.insert_all(recipients_attributes, record_timestamps: true) if recipients_attributes.any? + else + time = Time.current + recipients_attributes.each do |attributes| + attributes[:created_at] = time + attributes[:updated_at] = time end + notifications.insert_all(recipients_attributes) if recipients_attributes.any? end # Enqueue delivery job diff --git a/test/dummy/config/environments/development.rb b/test/dummy/config/environments/development.rb index 2e7fb486..444a15ca 100644 --- a/test/dummy/config/environments/development.rb +++ b/test/dummy/config/environments/development.rb @@ -60,7 +60,7 @@ config.active_job.verbose_enqueue_logs = true # Suppress logger output for asset requests. - config.assets.quiet = true + # config.assets.quiet = true # Raises error for missing translations. # config.i18n.raise_on_missing_translations = true From 3e33fc0ee78833d2e792abe2c266f7167a6d3752 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 22 Dec 2023 15:39:37 -0600 Subject: [PATCH 24/39] Use test queue adapter because async adapter has a bug in Rails https://github.com/rails/rails/issues/46797 --- app/models/concerns/noticed/deliverable.rb | 33 ++++++++++++---------- test/dummy/config/application.rb | 2 ++ 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/app/models/concerns/noticed/deliverable.rb b/app/models/concerns/noticed/deliverable.rb index d3ed83aa..9e3fb981 100644 --- a/app/models/concerns/noticed/deliverable.rb +++ b/app/models/concerns/noticed/deliverable.rb @@ -72,24 +72,27 @@ def deliver(recipients = nil) def deliver(recipients = nil) validate! - save - recipients_attributes = Array.wrap(recipients).map do |recipient| - { - recipient_type: recipient.class.name, - recipient_id: recipient.id - } - end + transaction do + save! + + recipients_attributes = Array.wrap(recipients).map do |recipient| + { + recipient_type: recipient.class.name, + recipient_id: recipient.id + } + end - if Rails.gem_version >= Gem::Version.new("7.0.0.alpha1") - notifications.insert_all(recipients_attributes, record_timestamps: true) if recipients_attributes.any? - else - time = Time.current - recipients_attributes.each do |attributes| - attributes[:created_at] = time - attributes[:updated_at] = time + if Rails.gem_version >= Gem::Version.new("7.0.0.alpha1") + notifications.insert_all!(recipients_attributes, record_timestamps: true) if recipients_attributes.any? + else + time = Time.current + recipients_attributes.each do |attributes| + attributes[:created_at] = time + attributes[:updated_at] = time + end + notifications.insert_all!(recipients_attributes) if recipients_attributes.any? end - notifications.insert_all(recipients_attributes) if recipients_attributes.any? end # Enqueue delivery job diff --git a/test/dummy/config/application.rb b/test/dummy/config/application.rb index bf33dd8c..c357d7ae 100644 --- a/test/dummy/config/application.rb +++ b/test/dummy/config/application.rb @@ -25,5 +25,7 @@ class Application < Rails::Application # # config.time_zone = "Central Time (US & Canada)" # config.eager_load_paths << Rails.root.join("extras") + + config.active_job.queue_adapter = :test end end From c222cc00d087b079a50935b819527f6cb606ae80 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 22 Dec 2023 16:47:25 -0600 Subject: [PATCH 25/39] Add read & seen to notifications --- app/models/concerns/noticed/deliverable.rb | 4 +- app/models/concerns/noticed/readable.rb | 46 +++++++++++++++++ app/models/noticed/notification.rb | 3 +- test/fixtures/noticed/notifications.yml | 2 +- test/models/noticed/notification_test.rb | 60 ++++++++++++++++++++++ 5 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 app/models/concerns/noticed/readable.rb diff --git a/app/models/concerns/noticed/deliverable.rb b/app/models/concerns/noticed/deliverable.rb index 9e3fb981..9128779e 100644 --- a/app/models/concerns/noticed/deliverable.rb +++ b/app/models/concerns/noticed/deliverable.rb @@ -16,7 +16,7 @@ def constant def validate! constant.required_option_names.each do |option| - raise ValidationError, "option `#{option}` must be set for `deliver_by :#{name}`" unless config.has_key?(option) + raise ValidationError, "option `#{option}` must be set for `deliver_by :#{name}`" unless config[option].present? end end @@ -108,7 +108,7 @@ def validate! def validate_params! required_param_names.each do |param_name| - raise ValidationError, "Param `#{param_name}` is required for #{self.class.name}." unless params.has_key?(param_name.to_s) + raise ValidationError, "Param `#{param_name}` is required for #{self.class.name}." unless params[param_name.to_s].present? end end diff --git a/app/models/concerns/noticed/readable.rb b/app/models/concerns/noticed/readable.rb new file mode 100644 index 00000000..bb952927 --- /dev/null +++ b/app/models/concerns/noticed/readable.rb @@ -0,0 +1,46 @@ +module Noticed + module Readable + extend ActiveSupport::Concern + + included do + scope :read, ->{ where.not(read_at: nil) } + scope :unread, ->{ where(read_at: nil) } + scope :seen, ->{ where.not(seen_at: nil) } + scope :unseen, ->{ where(seen_at: nil) } + end + + class_methods do + def mark_as_read + update_all(read_at: Time.current) + end + + def mark_as_unread + update_all(read_at: nil) + end + + def mark_as_seen + update_all(seen_at: Time.current) + end + + def mark_as_unseen + update_all(seen_at: nil) + end + end + + def read? + read_at? + end + + def unread? + !read_at? + end + + def seen? + seen_at? + end + + def unseen? + !seen_at? + end + end +end diff --git a/app/models/noticed/notification.rb b/app/models/noticed/notification.rb index b8190cc9..713398e4 100644 --- a/app/models/noticed/notification.rb +++ b/app/models/noticed/notification.rb @@ -1,7 +1,8 @@ module Noticed class Notification < ApplicationRecord - include Translation include Rails.application.routes.url_helpers + include Readable + include Translation belongs_to :event belongs_to :recipient, polymorphic: true diff --git a/test/fixtures/noticed/notifications.yml b/test/fixtures/noticed/notifications.yml index 456e1275..46e354f8 100644 --- a/test/fixtures/noticed/notifications.yml +++ b/test/fixtures/noticed/notifications.yml @@ -18,7 +18,7 @@ three: read_at: 2023-12-15 13:03:19 seen_at: 2023-12-15 13:03:19 -two: +four: event: three recipient: two (Account) read_at: 2023-12-15 13:03:19 diff --git a/test/models/noticed/notification_test.rb b/test/models/noticed/notification_test.rb index 733c2eff..b2690dee 100644 --- a/test/models/noticed/notification_test.rb +++ b/test/models/noticed/notification_test.rb @@ -14,4 +14,64 @@ class Noticed::NotificationTest < ActiveSupport::TestCase test "notification associations" do assert_equal 1, users(:one).notifications.count end + + test "read scope" do + assert_equal 4, Noticed::Notification.read.count + end + + test "unread scope" do + assert_equal 0, Noticed::Notification.unread.count + end + + test "seen scope" do + assert_equal 4, Noticed::Notification.seen.count + end + + test "unseen scope" do + assert_equal 0, Noticed::Notification.unseen.count + end + + test "mark_as_read" do + Noticed::Notification.update_all(read_at: nil) + assert_equal 0, Noticed::Notification.read.count + Noticed::Notification.mark_as_read + assert_equal 4, Noticed::Notification.read.count + end + + test "mark_as_unread" do + Noticed::Notification.update_all(read_at: Time.current) + assert_equal 4, Noticed::Notification.read.count + Noticed::Notification.mark_as_unread + assert_equal 0, Noticed::Notification.read.count + end + + test "mark_as_seen" do + Noticed::Notification.update_all(seen_at: nil) + assert_equal 0, Noticed::Notification.seen.count + Noticed::Notification.mark_as_seen + assert_equal 4, Noticed::Notification.seen.count + end + + test "mark_as_unseen" do + Noticed::Notification.update_all(seen_at: Time.current) + assert_equal 4, Noticed::Notification.seen.count + Noticed::Notification.mark_as_unseen + assert_equal 0, Noticed::Notification.seen.count + end + + test "read?" do + assert noticed_notifications(:one).read? + end + + test "unread?" do + assert_not noticed_notifications(:one).unread? + end + + test "seen?" do + assert noticed_notifications(:one).seen? + end + + test "unseen?" do + assert_not noticed_notifications(:one).unseen? + end end From 3d28b808f49029826b941fd6a237a4acc707bf87 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 22 Dec 2023 16:50:14 -0600 Subject: [PATCH 26/39] Standardize --- app/models/concerns/noticed/readable.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/concerns/noticed/readable.rb b/app/models/concerns/noticed/readable.rb index bb952927..81b5de80 100644 --- a/app/models/concerns/noticed/readable.rb +++ b/app/models/concerns/noticed/readable.rb @@ -3,10 +3,10 @@ module Readable extend ActiveSupport::Concern included do - scope :read, ->{ where.not(read_at: nil) } - scope :unread, ->{ where(read_at: nil) } - scope :seen, ->{ where.not(seen_at: nil) } - scope :unseen, ->{ where(seen_at: nil) } + scope :read, -> { where.not(read_at: nil) } + scope :unread, -> { where(read_at: nil) } + scope :seen, -> { where.not(seen_at: nil) } + scope :unseen, -> { where(seen_at: nil) } end class_methods do From ee9396951408d3174ab323b1ec6d42291046bbe1 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Sat, 23 Dec 2023 19:43:21 -0600 Subject: [PATCH 27/39] Add wait, wait_until, queue, and priority for delivery methods to match ActiveJob --- app/models/concerns/noticed/deliverable.rb | 21 ++++++++- lib/noticed/delivery_method.rb | 15 ++++-- test/delivery_method_test.rb | 29 +++++++++--- test/dummy/app/notifiers/bulk_notifier.rb | 3 ++ .../dummy/app/notifiers/inherited_notifier.rb | 2 + test/dummy/app/notifiers/priority_notifier.rb | 3 ++ test/dummy/app/notifiers/queue_notifier.rb | 3 ++ test/dummy/app/notifiers/simple_notifier.rb | 8 ++++ test/dummy/app/notifiers/test_notifier.rb | 3 ++ test/dummy/app/notifiers/wait_notifier.rb | 3 ++ .../app/notifiers/wait_until_notifier.rb | 3 ++ test/notifier_test.rb | 46 ++++++++++++------- 12 files changed, 109 insertions(+), 30 deletions(-) create mode 100644 test/dummy/app/notifiers/bulk_notifier.rb create mode 100644 test/dummy/app/notifiers/inherited_notifier.rb create mode 100644 test/dummy/app/notifiers/priority_notifier.rb create mode 100644 test/dummy/app/notifiers/queue_notifier.rb create mode 100644 test/dummy/app/notifiers/simple_notifier.rb create mode 100644 test/dummy/app/notifiers/test_notifier.rb create mode 100644 test/dummy/app/notifiers/wait_notifier.rb create mode 100644 test/dummy/app/notifiers/wait_until_notifier.rb diff --git a/app/models/concerns/noticed/deliverable.rb b/app/models/concerns/noticed/deliverable.rb index 9128779e..3045979f 100644 --- a/app/models/concerns/noticed/deliverable.rb +++ b/app/models/concerns/noticed/deliverable.rb @@ -20,8 +20,25 @@ def validate! end end - def perform_later(event) - constant.perform_later(name, event) + def perform_later(event_or_notification, options = {}) + options[:wait] = evaluate_option(:wait, event_or_notification) if config.has_key?(:wait) + options[:wait_until] = evaluate_option(:wait_until, event_or_notification) if config.has_key?(:wait_until) + options[:queue] = evaluate_option(:queue, event_or_notification) if config.has_key?(:queue) + options[:priority] = evaluate_option(:priority, event_or_notification) if config.has_key?(:priority) + + constant.set(options).perform_later(name, event_or_notification) + end + + def evaluate_option(name, context) + option = config[name] + + if option&.respond_to?(:call) + context.instance_exec(&option) + elsif option.is_a?(Symbol) && context.respond_to?(option) + context.send(option) + else + option + end end end diff --git a/lib/noticed/delivery_method.rb b/lib/noticed/delivery_method.rb index 9ca12e5c..6fc704ae 100644 --- a/lib/noticed/delivery_method.rb +++ b/lib/noticed/delivery_method.rb @@ -8,10 +8,15 @@ class DeliveryMethod < ApplicationJob attr_reader :config, :event, :notification delegate :recipient, to: :notification - def perform(delivery_method_name, notification) + def perform(delivery_method_name, notification, overrides: {}) @notification = notification @event = notification.event - @config = event.delivery_methods.fetch(delivery_method_name).config + + # Look up config from Notifier and merge overrides + @config = event.delivery_methods.fetch(delivery_method_name).config.merge(overrides) + + return false if config.has_key?(:if) && !evaluate_option(:if) + return false if config.has_key?(:unless) && evaluate_option(:unless) deliver end @@ -28,13 +33,13 @@ def fetch_constant(name) def evaluate_option(name) option = config[name] - # Evaluate Proc within the context of the notification + # Evaluate Proc within the context of the Notification if option&.respond_to?(:call) notification.instance_exec(&option) - # Call method if symbol and matching method + # Call method if symbol and matching method on Notifier elsif option.is_a?(Symbol) && event.respond_to?(option) - event.send(option) + event.send(option, self) # Return the value else diff --git a/test/delivery_method_test.rb b/test/delivery_method_test.rb index 6151521b..f174025c 100644 --- a/test/delivery_method_test.rb +++ b/test/delivery_method_test.rb @@ -4,14 +4,9 @@ class DeliveryMethodTest < ActiveSupport::TestCase class InheritedDeliveryMethod < Noticed::DeliveryMethods::ActionCable end - setup do - @delivery_method = Noticed::DeliveryMethod.new - end - test "fetch_constant looks up constants" do - set_config( - mailer: "UserMailer" - ) + @delivery_method = Noticed::DeliveryMethod.new + set_config(mailer: "UserMailer") assert_equal UserMailer, @delivery_method.fetch_constant(:mailer) end @@ -19,6 +14,26 @@ class InheritedDeliveryMethod < Noticed::DeliveryMethods::ActionCable assert_equal [:channel, :stream, :message], InheritedDeliveryMethod.required_option_names end + test "if config" do + event = TestNotifier.deliver(User.first) + notification = event.notifications.first + @delivery_method = Noticed::DeliveryMethods::Test.new + + assert @delivery_method.perform(:test, notification, overrides: {if: true}) + assert @delivery_method.perform(:test, notification, overrides: {if: -> { unread? }}) + refute @delivery_method.perform(:test, notification, overrides: {if: false}) + end + + test "unless overrides" do + event = TestNotifier.deliver(User.first) + notification = event.notifications.first + @delivery_method = Noticed::DeliveryMethods::Test.new + + refute @delivery_method.perform(:test, notification, overrides: {unless: true}) + assert @delivery_method.perform(:test, notification, overrides: {unless: false}) + assert @delivery_method.perform(:test, notification, overrides: {unless: -> { read? }}) + end + private def set_config(config) diff --git a/test/dummy/app/notifiers/bulk_notifier.rb b/test/dummy/app/notifiers/bulk_notifier.rb new file mode 100644 index 00000000..42cc3c05 --- /dev/null +++ b/test/dummy/app/notifiers/bulk_notifier.rb @@ -0,0 +1,3 @@ +class BulkNotifier < Noticed::Event + bulk_deliver_by :webhook, url: "https://example.org/bulk" +end diff --git a/test/dummy/app/notifiers/inherited_notifier.rb b/test/dummy/app/notifiers/inherited_notifier.rb new file mode 100644 index 00000000..6c4acd8b --- /dev/null +++ b/test/dummy/app/notifiers/inherited_notifier.rb @@ -0,0 +1,2 @@ +class InheritedNotifier < SimpleNotifier +end diff --git a/test/dummy/app/notifiers/priority_notifier.rb b/test/dummy/app/notifiers/priority_notifier.rb new file mode 100644 index 00000000..1bd89234 --- /dev/null +++ b/test/dummy/app/notifiers/priority_notifier.rb @@ -0,0 +1,3 @@ +class PriorityNotifier < Noticed::Event + deliver_by :test, priority: 2 +end diff --git a/test/dummy/app/notifiers/queue_notifier.rb b/test/dummy/app/notifiers/queue_notifier.rb new file mode 100644 index 00000000..6bd99b03 --- /dev/null +++ b/test/dummy/app/notifiers/queue_notifier.rb @@ -0,0 +1,3 @@ +class QueueNotifier < Noticed::Event + deliver_by :test, queue: :example_queue +end diff --git a/test/dummy/app/notifiers/simple_notifier.rb b/test/dummy/app/notifiers/simple_notifier.rb new file mode 100644 index 00000000..baf3026a --- /dev/null +++ b/test/dummy/app/notifiers/simple_notifier.rb @@ -0,0 +1,8 @@ +class SimpleNotifier < Noticed::Event + deliver_by :test + required_params :message + + def url + root_url(host: "example.org") + end +end diff --git a/test/dummy/app/notifiers/test_notifier.rb b/test/dummy/app/notifiers/test_notifier.rb new file mode 100644 index 00000000..4103c5a0 --- /dev/null +++ b/test/dummy/app/notifiers/test_notifier.rb @@ -0,0 +1,3 @@ +class TestNotifier < Noticed::Event + deliver_by :test +end diff --git a/test/dummy/app/notifiers/wait_notifier.rb b/test/dummy/app/notifiers/wait_notifier.rb new file mode 100644 index 00000000..38f82150 --- /dev/null +++ b/test/dummy/app/notifiers/wait_notifier.rb @@ -0,0 +1,3 @@ +class WaitNotifier < Noticed::Event + deliver_by :test, wait: 5.minutes +end diff --git a/test/dummy/app/notifiers/wait_until_notifier.rb b/test/dummy/app/notifiers/wait_until_notifier.rb new file mode 100644 index 00000000..eab4bd42 --- /dev/null +++ b/test/dummy/app/notifiers/wait_until_notifier.rb @@ -0,0 +1,3 @@ +class WaitUntilNotifier < Noticed::Event + deliver_by :test, wait_until: -> { 1.hour.from_now } +end diff --git a/test/notifier_test.rb b/test/notifier_test.rb index 7fc1f361..35625b81 100644 --- a/test/notifier_test.rb +++ b/test/notifier_test.rb @@ -3,22 +3,6 @@ class NotifierTest < ActiveSupport::TestCase include ActiveJob::TestHelper - class SimpleNotifier < Noticed::Event - deliver_by :test - required_params :message - - def url - root_url(host: "example.org") - end - end - - class InheritedNotifier < SimpleNotifier - end - - class BulkNotifier < Noticed::Event - bulk_deliver_by :webhook, url: "https://example.org/bulk" - end - test "includes Rails urls" do assert_equal "http://example.org/", SimpleNotifier.new.url end @@ -73,4 +57,34 @@ class BulkNotifier < Noticed::Event perform_enqueued_jobs end end + + test "wait delivery method option" do + freeze_time + event = WaitNotifier.deliver(User.first) + assert_enqueued_with(job: Noticed::DeliveryMethods::Test, args: [:test, event.notifications.last], at: 5.minutes.from_now) do + perform_enqueued_jobs + end + end + + test "wait_until delivery method option" do + freeze_time + event = WaitUntilNotifier.deliver(User.first) + assert_enqueued_with(job: Noticed::DeliveryMethods::Test, args: [:test, event.notifications.last], at: 1.hour.from_now) do + perform_enqueued_jobs + end + end + + test "queue delivery method option" do + event = QueueNotifier.deliver(User.first) + assert_enqueued_with(job: Noticed::DeliveryMethods::Test, args: [:test, event.notifications.last], queue: :example_queue) do + perform_enqueued_jobs + end + end + + test "priority delivery method option" do + event = PriorityNotifier.deliver(User.first) + assert_enqueued_with(job: Noticed::DeliveryMethods::Test, args: [:test, event.notifications.last], priority: 2) do + perform_enqueued_jobs + end + end end From d23a3247ea397e33e9df0b7981855fb682d9198c Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Sat, 23 Dec 2023 19:50:22 -0600 Subject: [PATCH 28/39] Use string for queue name assertion --- test/notifier_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/notifier_test.rb b/test/notifier_test.rb index 35625b81..06785480 100644 --- a/test/notifier_test.rb +++ b/test/notifier_test.rb @@ -76,7 +76,7 @@ class NotifierTest < ActiveSupport::TestCase test "queue delivery method option" do event = QueueNotifier.deliver(User.first) - assert_enqueued_with(job: Noticed::DeliveryMethods::Test, args: [:test, event.notifications.last], queue: :example_queue) do + assert_enqueued_with(job: Noticed::DeliveryMethods::Test, args: [:test, event.notifications.last], queue: "example_queue") do perform_enqueued_jobs end end From 850bff36d67f73540137b08c1e1644b6fe11a3a5 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Sat, 23 Dec 2023 20:09:33 -0600 Subject: [PATCH 29/39] Skip priority test on Rails 6.1 --- test/notifier_test.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/notifier_test.rb b/test/notifier_test.rb index 06785480..bc8d1276 100644 --- a/test/notifier_test.rb +++ b/test/notifier_test.rb @@ -81,10 +81,13 @@ class NotifierTest < ActiveSupport::TestCase end end - test "priority delivery method option" do - event = PriorityNotifier.deliver(User.first) - assert_enqueued_with(job: Noticed::DeliveryMethods::Test, args: [:test, event.notifications.last], priority: 2) do - perform_enqueued_jobs + # assert_enqeued_with doesn't support priority before Rails 7 + if Rails.gem_version >= Gem::Version.new("7.0.0.alpha1") + test "priority delivery method option" do + event = PriorityNotifier.deliver(User.first) + assert_enqueued_with(job: Noticed::DeliveryMethods::Test, args: [:test, event.notifications.last], priority: 2) do + perform_enqueued_jobs + end end end end From 8f2e9c87d6e9707dbde53cbb3d05c6b4fc790b84 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Tue, 26 Dec 2023 09:33:30 -0600 Subject: [PATCH 30/39] Add Ruby 3.3 to CI --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fdf46fc6..05e81302 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - ruby: ['3.0', '3.1', '3.2'] + ruby: ['3.0', '3.1', '3.2', '3.3'] gemfile: - rails_6_1 - rails_7 @@ -50,7 +50,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - ruby: ['3.0', '3.1', '3.2'] + ruby: ['3.0', '3.1', '3.2', '3.3'] gemfile: - rails_6_1 - rails_7 @@ -96,7 +96,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - ruby: ['3.0', '3.1', '3.2'] + ruby: ['3.0', '3.1', '3.2', '3.3'] gemfile: - rails_6_1 - rails_7 From 9493af679ae1769fdb699e49da9efa35f655e43d Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Tue, 2 Jan 2024 11:37:15 -0600 Subject: [PATCH 31/39] Add upgrade guide --- UPGRADE.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 UPGRADE.md diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 00000000..0e7047d5 --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,49 @@ +# Noticed Upgrade Guide + +Follow this guide to upgrade your Noticed implementation to the next version + +## Noticed 2.0 + +We've made some major changes to Noticed to simplify and support more delivery methods. + +### Models + +Instead of having models live in your application, Noticed v2 adds models managed by the gem. + +```bash +rails noticed:install:migrations +rails db:migrate +``` + +To migrate your data to the new tables, loop through your existing notifications and create new records for each one + +```ruby +Notification.find_each do |notification| + attributes = notification.attributes.slice(:id, :type, :params) + attributes[:recipients_attributes] = {recipient_type: notification.recipient_type, recipient_id: notification.recipient_id, read_at: notification.read_at) + Noticed::Notification.create(attributes) +end +``` + +After migrating, you can drop the old notifications table and model. + +### Database Delivery Method + +The database delivery is now baked into notifications. + +You will need to remove `deliver_by :database` from your notifiers. + +### Notifiers + +For clarity, we've renamed `app/notifications` to `app/notifiers`. + +Notifiers - the class that delivers notifications +Notification - the database record of the notification + +We recommend renaming your existing classes to match. You'll also need to update the `type` column on existing notifications when renaming. + +```ruby +Noticed::Notification.find_each do |notification| + notification.update(type: notification.type.sub("Notification", "Notifier")) +end +``` \ No newline at end of file From 9073d5c4d2a2fbaf352f76a9ebfafa815a0376d4 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Tue, 2 Jan 2024 15:16:00 -0600 Subject: [PATCH 32/39] Add some more things --- Gemfile.lock | 35 +++++----- UPGRADE.md | 75 +++++++++++++++++++++- app/models/concerns/noticed/deliverable.rb | 20 ++++-- app/models/concerns/noticed/readable.rb | 16 +++++ app/models/noticed/event.rb | 4 ++ app/models/noticed/notification.rb | 2 + gemfiles/rails_6_1.gemfile.lock | 14 ++-- gemfiles/rails_7.gemfile.lock | 14 ++-- gemfiles/rails_7_1.gemfile.lock | 14 ++-- gemfiles/rails_main.gemfile.lock | 14 ++-- lib/noticed.rb | 1 + lib/noticed/coder.rb | 15 +++++ lib/noticed/engine.rb | 1 + test/coder_test.rb | 30 +++++++++ 14 files changed, 197 insertions(+), 58 deletions(-) create mode 100644 lib/noticed/coder.rb create mode 100644 test/coder_test.rb diff --git a/Gemfile.lock b/Gemfile.lock index 02c29c87..36a35ddb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -142,13 +142,14 @@ GEM net-smtp marcel (1.0.2) mini_mime (1.1.5) + mini_portile2 (2.8.5) minitest (5.20.0) multi_json (1.15.0) mutex_m (0.2.0) mysql2 (0.5.5) net-http2 (0.18.5) http-2 (~> 0.11) - net-imap (0.4.8) + net-imap (0.4.9) date net-protocol net-pop (0.1.2) @@ -158,11 +159,11 @@ GEM net-smtp (0.4.0) net-protocol nio4r (2.7.0) - nokogiri (1.15.5-arm64-darwin) + nokogiri (1.16.0-arm64-darwin) racc (~> 1.4) - nokogiri (1.15.5-x86_64-darwin) + nokogiri (1.16.0-x86_64-darwin) racc (~> 1.4) - nokogiri (1.15.5-x86_64-linux) + nokogiri (1.16.0-x86_64-linux) racc (~> 1.4) os (1.1.4) parallel (1.24.0) @@ -219,7 +220,7 @@ GEM reline (0.4.1) io-console (~> 0.5) rexml (3.2.6) - rubocop (1.57.2) + rubocop (1.59.0) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -227,14 +228,14 @@ GEM rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.28.1, < 2.0) + rubocop-ast (>= 1.30.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) rubocop-ast (1.30.0) parser (>= 3.2.1.0) - rubocop-performance (1.19.1) - rubocop (>= 1.7.0, < 2.0) - rubocop-ast (>= 0.4.0) + rubocop-performance (1.20.1) + rubocop (>= 1.48.1, < 2.0) + rubocop-ast (>= 1.30.0, < 2.0) ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) signet (0.18.0) @@ -242,21 +243,20 @@ GEM faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - sqlite3 (1.6.9-arm64-darwin) - sqlite3 (1.6.9-x86_64-darwin) - sqlite3 (1.6.9-x86_64-linux) - standard (1.32.1) + sqlite3 (1.6.9) + mini_portile2 (~> 2.8.0) + standard (1.33.0) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) - rubocop (~> 1.57.2) + rubocop (~> 1.59.0) standard-custom (~> 1.0.0) - standard-performance (~> 1.2) + standard-performance (~> 1.3) standard-custom (1.0.2) lint_roller (~> 1.0) rubocop (~> 1.50) - standard-performance (1.2.1) + standard-performance (1.3.0) lint_roller (~> 1.1) - rubocop-performance (~> 1.19.1) + rubocop-performance (~> 1.20.1) stringio (3.1.0) thor (1.3.0) timeout (0.4.1) @@ -276,6 +276,7 @@ GEM PLATFORMS arm64-darwin-22 x86_64-darwin-22 + x86_64-darwin-23 x86_64-linux DEPENDENCIES diff --git a/UPGRADE.md b/UPGRADE.md index 0e7047d5..abfe48b1 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -19,14 +19,33 @@ To migrate your data to the new tables, loop through your existing notifications ```ruby Notification.find_each do |notification| - attributes = notification.attributes.slice(:id, :type, :params) - attributes[:recipients_attributes] = {recipient_type: notification.recipient_type, recipient_id: notification.recipient_id, read_at: notification.read_at) - Noticed::Notification.create(attributes) + attributes = notification.attributes.slice("id", "type", "params") + attributes[:notifications_attributes] = [{recipient_type: notification.recipient_type, recipient_id: notification.recipient_id, seen_at: notification.read_at, read_at: notification.interacted_at}] + Noticed::Event.create!(attributes) end ``` +class Notification < ActiveRecord::Base + self.inheritance_column = nil +end + +Notification.find_each do |notification| + attributes = notification.attributes.slice("id", "type", "params") + attributes[:notifications_attributes] = [{recipient_type: notification.recipient_type, recipient_id: notification.recipient_id, seen_at: notification.read_at, read_at: notification.interacted_at}] + Noticed::Event.create!(attributes) +end + After migrating, you can drop the old notifications table and model. +### Parent Class + +`Noticed::Base` has been deprecated in favor of `Noticed::Event`. This is an STI model that tracks all Notifier deliveries and recipients. + +```ruby +class CommentNotifier < Noticed::Event +end +``` + ### Database Delivery Method The database delivery is now baked into notifications. @@ -46,4 +65,54 @@ We recommend renaming your existing classes to match. You'll also need to update Noticed::Notification.find_each do |notification| notification.update(type: notification.type.sub("Notification", "Notifier")) end +``` + +### Delivery Method Configuration + +Configuration for each delivery method can be contained within a block now. This improves organization but + +```ruby +class CommentNotifier < Noticed::Event + deliver_by :action_cable do |config| + config.channel = "NotificationChannel" + config.stream = ->{ recipient } + config.message = :to_websocket + end + + def to_websocket + { foo: :bar } + end +``` + +### Required Params + +`param` and `params` have been renamed to `required_param(s)` to be more clear. + +```ruby +class CommentNotifier < Noticed::Event + required_param :comment + required_params :account, :comment +end +``` + +### Has Noticed Notifications + +`has_noticed_notifications` has been removed in favor of the `record` polymorphic relationship that can be directly queried with ActiveRecord. You can add the necessary json query to your model(s) to restore the json query if needed. + +We recommend backfilling the `record` association if your notification params has a primary related record and switching to a has_many association instead. + +```ruby +class Comment < ApplicationRecord + has_many :noticed_events, as: :record, dependent: :destroy, class_name: "Noticed::Event" +end +``` + +### Receipient Notifications Association + +Recipients can be associated with notifications using the following. This is useful for displaying notifications in your UI. + +```ruby +class User < ApplicationRecord + has_many :notifications, as: :recipient, dependent: :destroy, class_name: "Noticed::Notification" +end ``` \ No newline at end of file diff --git a/app/models/concerns/noticed/deliverable.rb b/app/models/concerns/noticed/deliverable.rb index 3045979f..edbfe627 100644 --- a/app/models/concerns/noticed/deliverable.rb +++ b/app/models/concerns/noticed/deliverable.rb @@ -46,6 +46,12 @@ def evaluate_option(name, context) class_attribute :bulk_delivery_methods, instance_writer: false, default: {} class_attribute :delivery_methods, instance_writer: false, default: {} class_attribute :required_param_names, instance_writer: false, default: [] + + if Rails.gem_version >= Gem::Version.new("7.1.0.alpha") + serialize :params, coder: Coder + else + serialize :params, Coder + end end class_methods do @@ -93,12 +99,7 @@ def deliver(recipients = nil) transaction do save! - recipients_attributes = Array.wrap(recipients).map do |recipient| - { - recipient_type: recipient.class.name, - recipient_id: recipient.id - } - end + recipients_attributes = Array.wrap(recipients).map(&:recipient_attributes_for) if Rails.gem_version >= Gem::Version.new("7.0.0.alpha1") notifications.insert_all!(recipients_attributes, record_timestamps: true) if recipients_attributes.any? @@ -118,6 +119,13 @@ def deliver(recipients = nil) self end + def recipient_attributes_for(recipient) + { + recipient_type: recipient.class.name, + recipient_id: recipient.id + } + end + def validate! validate_params! validate_delivery_methods! diff --git a/app/models/concerns/noticed/readable.rb b/app/models/concerns/noticed/readable.rb index 81b5de80..ef2144a3 100644 --- a/app/models/concerns/noticed/readable.rb +++ b/app/models/concerns/noticed/readable.rb @@ -27,6 +27,22 @@ def mark_as_unseen end end + def mark_as_read + update(read_at: Time.current) + end + + def mark_as_unread + update(read_at: nil) + end + + def mark_as_seen + update(seen_at: Time.current) + end + + def mark_as_unseen + update(seen_at: nil) + end + def read? read_at? end diff --git a/app/models/noticed/event.rb b/app/models/noticed/event.rb index 027956ec..f3fb3258 100644 --- a/app/models/noticed/event.rb +++ b/app/models/noticed/event.rb @@ -7,6 +7,10 @@ class Event < ApplicationRecord belongs_to :record, polymorphic: true, optional: true has_many :notifications, dependent: :delete_all + scope :newest_first, -> { order(created_at: :desc) } + + accepts_nested_attributes_for :notifications + attribute :params, default: {} end end diff --git a/app/models/noticed/notification.rb b/app/models/noticed/notification.rb index 713398e4..36d343b8 100644 --- a/app/models/noticed/notification.rb +++ b/app/models/noticed/notification.rb @@ -7,6 +7,8 @@ class Notification < ApplicationRecord belongs_to :event belongs_to :recipient, polymorphic: true + scope :newest_first, -> { order(created_at: :desc) } + delegate :params, :record, to: :event attribute :params, default: {} diff --git a/gemfiles/rails_6_1.gemfile.lock b/gemfiles/rails_6_1.gemfile.lock index 5aa717b4..672f1194 100644 --- a/gemfiles/rails_6_1.gemfile.lock +++ b/gemfiles/rails_6_1.gemfile.lock @@ -121,6 +121,7 @@ GEM marcel (1.0.2) method_source (1.0.0) mini_mime (1.1.5) + mini_portile2 (2.8.5) minitest (5.20.0) multi_json (1.15.0) mysql2 (0.5.5) @@ -136,11 +137,8 @@ GEM net-smtp (0.4.0) net-protocol nio4r (2.7.0) - nokogiri (1.15.5-arm64-darwin) - racc (~> 1.4) - nokogiri (1.15.5-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.15.5-x86_64-linux) + nokogiri (1.15.5) + mini_portile2 (~> 2.8.2) racc (~> 1.4) os (1.1.4) parallel (1.24.0) @@ -215,9 +213,8 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - sqlite3 (1.6.9-arm64-darwin) - sqlite3 (1.6.9-x86_64-darwin) - sqlite3 (1.6.9-x86_64-linux) + sqlite3 (1.6.9) + mini_portile2 (~> 2.8.0) standard (1.32.1) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) @@ -247,6 +244,7 @@ GEM PLATFORMS arm64-darwin-22 x86_64-darwin-22 + x86_64-darwin-23 x86_64-linux DEPENDENCIES diff --git a/gemfiles/rails_7.gemfile.lock b/gemfiles/rails_7.gemfile.lock index 65dc11ba..af56e601 100644 --- a/gemfiles/rails_7.gemfile.lock +++ b/gemfiles/rails_7.gemfile.lock @@ -124,6 +124,7 @@ GEM marcel (1.0.2) method_source (1.0.0) mini_mime (1.1.5) + mini_portile2 (2.8.5) minitest (5.20.0) multi_json (1.15.0) mysql2 (0.5.5) @@ -139,11 +140,8 @@ GEM net-smtp (0.4.0) net-protocol nio4r (2.5.9) - nokogiri (1.15.4-arm64-darwin) - racc (~> 1.4) - nokogiri (1.15.4-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.15.4-x86_64-linux) + nokogiri (1.15.4) + mini_portile2 (~> 2.8.2) racc (~> 1.4) os (1.1.4) parallel (1.23.0) @@ -212,9 +210,8 @@ GEM faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - sqlite3 (1.6.7-arm64-darwin) - sqlite3 (1.6.7-x86_64-darwin) - sqlite3 (1.6.7-x86_64-linux) + sqlite3 (1.6.7) + mini_portile2 (~> 2.8.0) standard (1.31.2) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) @@ -244,6 +241,7 @@ GEM PLATFORMS arm64-darwin-22 x86_64-darwin-22 + x86_64-darwin-23 x86_64-linux DEPENDENCIES diff --git a/gemfiles/rails_7_1.gemfile.lock b/gemfiles/rails_7_1.gemfile.lock index fda05ce4..39de1963 100644 --- a/gemfiles/rails_7_1.gemfile.lock +++ b/gemfiles/rails_7_1.gemfile.lock @@ -138,6 +138,7 @@ GEM net-smtp marcel (1.0.2) mini_mime (1.1.5) + mini_portile2 (2.8.5) minitest (5.20.0) multi_json (1.15.0) mutex_m (0.1.2) @@ -154,11 +155,8 @@ GEM net-smtp (0.4.0) net-protocol nio4r (2.5.9) - nokogiri (1.15.4-arm64-darwin) - racc (~> 1.4) - nokogiri (1.15.4-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.15.4-x86_64-linux) + nokogiri (1.15.4) + mini_portile2 (~> 2.8.2) racc (~> 1.4) os (1.1.4) parallel (1.23.0) @@ -239,9 +237,8 @@ GEM faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - sqlite3 (1.6.7-arm64-darwin) - sqlite3 (1.6.7-x86_64-darwin) - sqlite3 (1.6.7-x86_64-linux) + sqlite3 (1.6.7) + mini_portile2 (~> 2.8.0) standard (1.31.2) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) @@ -273,6 +270,7 @@ GEM PLATFORMS arm64-darwin-22 x86_64-darwin-22 + x86_64-darwin-23 x86_64-linux DEPENDENCIES diff --git a/gemfiles/rails_main.gemfile.lock b/gemfiles/rails_main.gemfile.lock index 76913eea..56541458 100644 --- a/gemfiles/rails_main.gemfile.lock +++ b/gemfiles/rails_main.gemfile.lock @@ -166,6 +166,7 @@ GEM net-smtp marcel (1.0.2) mini_mime (1.1.5) + mini_portile2 (2.8.5) minitest (5.20.0) multi_json (1.15.0) mysql2 (0.5.5) @@ -181,11 +182,8 @@ GEM net-smtp (0.4.0) net-protocol nio4r (2.5.9) - nokogiri (1.15.4-arm64-darwin) - racc (~> 1.4) - nokogiri (1.15.4-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.15.4-x86_64-linux) + nokogiri (1.15.4) + mini_portile2 (~> 2.8.2) racc (~> 1.4) os (1.1.4) parallel (1.23.0) @@ -244,9 +242,8 @@ GEM faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - sqlite3 (1.6.7-arm64-darwin) - sqlite3 (1.6.7-x86_64-darwin) - sqlite3 (1.6.7-x86_64-linux) + sqlite3 (1.6.7) + mini_portile2 (~> 2.8.0) standard (1.31.2) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) @@ -278,6 +275,7 @@ GEM PLATFORMS arm64-darwin-22 x86_64-darwin-22 + x86_64-darwin-23 x86_64-linux DEPENDENCIES diff --git a/lib/noticed.rb b/lib/noticed.rb index e439ffe7..ee3ec9de 100644 --- a/lib/noticed.rb +++ b/lib/noticed.rb @@ -12,6 +12,7 @@ def self.deprecator # :nodoc: autoload :ApiClient, "noticed/api_client" autoload :BulkDeliveryMethod, "noticed/bulk_delivery_method" + autoload :Coder, "noticed/coder" autoload :DeliveryMethod, "noticed/delivery_method" autoload :RequiredOptions, "noticed/required_options" autoload :Translation, "noticed/translation" diff --git a/lib/noticed/coder.rb b/lib/noticed/coder.rb new file mode 100644 index 00000000..b4ad0926 --- /dev/null +++ b/lib/noticed/coder.rb @@ -0,0 +1,15 @@ +module Noticed + class Coder + def self.load(data) + return if data.nil? + ActiveJob::Arguments.send(:deserialize_argument, data) + rescue ActiveRecord::RecordNotFound => error + {noticed_error: error.message, original_params: data} + end + + def self.dump(data) + return if data.nil? + ActiveJob::Arguments.send(:serialize_argument, data) + end + end +end diff --git a/lib/noticed/engine.rb b/lib/noticed/engine.rb index b75897d1..474a2405 100644 --- a/lib/noticed/engine.rb +++ b/lib/noticed/engine.rb @@ -1,4 +1,5 @@ module Noticed class Engine < ::Rails::Engine + isolate_namespace Noticed end end diff --git a/test/coder_test.rb b/test/coder_test.rb new file mode 100644 index 00000000..a827d254 --- /dev/null +++ b/test/coder_test.rb @@ -0,0 +1,30 @@ +require "test_helper" + +class CoderTest < ActiveSupport::TestCase + test "uses TextCoder for text columns" do + assert_equal Noticed::TextCoder, TextNotification.noticed_coder + end + + test "uses Coder for json columns" do + assert_equal Noticed::Coder, JsonNotification.noticed_coder + end + + test "uses Coder for jsonb columns" do + assert_equal Noticed::Coder, JsonbNotification.noticed_coder + end + + test "serializes globalid objects with text column" do + notification = Notification.create!(recipient: user, type: "Example", params: {user: user}) + assert_equal({user: user}, notification.params) + end + + test "serializes globalid objects with json column" do + notification = JsonNotification.create!(recipient: user, type: "Example", params: {user: user}) + assert_equal({user: user}, notification.params) + end + + test "serializes globalid objects with jsonb column" do + notification = JsonbNotification.create!(recipient: user, type: "Example", params: {user: user}) + assert_equal({user: user}, notification.params) + end +end From e5553d3c11c62b71d881146cdecddfd4d16796e3 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Tue, 2 Jan 2024 16:34:47 -0600 Subject: [PATCH 33/39] Refactor --- app/models/concerns/noticed/deliverable.rb | 6 +++-- lib/noticed.rb | 2 +- test/coder_test.rb | 30 ---------------------- test/notifier_test.rb | 6 +++++ 4 files changed, 11 insertions(+), 33 deletions(-) delete mode 100644 test/coder_test.rb diff --git a/app/models/concerns/noticed/deliverable.rb b/app/models/concerns/noticed/deliverable.rb index edbfe627..4716cfc0 100644 --- a/app/models/concerns/noticed/deliverable.rb +++ b/app/models/concerns/noticed/deliverable.rb @@ -99,7 +99,9 @@ def deliver(recipients = nil) transaction do save! - recipients_attributes = Array.wrap(recipients).map(&:recipient_attributes_for) + recipients_attributes = Array.wrap(recipients).map do |recipient| + recipient_attributes_for(recipient) + end if Rails.gem_version >= Gem::Version.new("7.0.0.alpha1") notifications.insert_all!(recipients_attributes, record_timestamps: true) if recipients_attributes.any? @@ -133,7 +135,7 @@ def validate! def validate_params! required_param_names.each do |param_name| - raise ValidationError, "Param `#{param_name}` is required for #{self.class.name}." unless params[param_name.to_s].present? + raise ValidationError, "Param `#{param_name}` is required for #{self.class.name}." unless params[param_name].present? end end diff --git a/lib/noticed.rb b/lib/noticed.rb index ee3ec9de..94b5baa4 100644 --- a/lib/noticed.rb +++ b/lib/noticed.rb @@ -48,7 +48,7 @@ class ResponseUnsuccessful < StandardError def initialize(response) @response = response - super "Request to returned #{response.code} response" + super("Request to returned #{response.code} response") end end end diff --git a/test/coder_test.rb b/test/coder_test.rb deleted file mode 100644 index a827d254..00000000 --- a/test/coder_test.rb +++ /dev/null @@ -1,30 +0,0 @@ -require "test_helper" - -class CoderTest < ActiveSupport::TestCase - test "uses TextCoder for text columns" do - assert_equal Noticed::TextCoder, TextNotification.noticed_coder - end - - test "uses Coder for json columns" do - assert_equal Noticed::Coder, JsonNotification.noticed_coder - end - - test "uses Coder for jsonb columns" do - assert_equal Noticed::Coder, JsonbNotification.noticed_coder - end - - test "serializes globalid objects with text column" do - notification = Notification.create!(recipient: user, type: "Example", params: {user: user}) - assert_equal({user: user}, notification.params) - end - - test "serializes globalid objects with json column" do - notification = JsonNotification.create!(recipient: user, type: "Example", params: {user: user}) - assert_equal({user: user}, notification.params) - end - - test "serializes globalid objects with jsonb column" do - notification = JsonbNotification.create!(recipient: user, type: "Example", params: {user: user}) - assert_equal({user: user}, notification.params) - end -end diff --git a/test/notifier_test.rb b/test/notifier_test.rb index bc8d1276..17cd1cbe 100644 --- a/test/notifier_test.rb +++ b/test/notifier_test.rb @@ -11,6 +11,12 @@ class NotifierTest < ActiveSupport::TestCase assert_equal [:message], InheritedNotifier.required_params end + test "serializes globalid objects with text column" do + user = users(:one) + notification = Noticed::Event.create!(type: "SimpleNotifier", params: {user: user}) + assert_equal({user: user}, notification.params) + end + test "deliver creates an event" do assert_difference "Noticed::Event.count" do ReceiptNotifier.deliver(User.first) From 04a3ee809cc364b838ca146f0b9c3a0f36cc4b30 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Wed, 3 Jan 2024 10:45:47 -0600 Subject: [PATCH 34/39] Start updating docs --- CHANGELOG.md | 24 +++ README.md | 152 +++++++----------- docs/actioncable.md | 60 ------- docs/bulk_delivery_methods/discord.md | 20 +++ docs/bulk_delivery_methods/slack.md | 18 +++ docs/bulk_delivery_methods/webhook.md | 18 +++ docs/delivery_methods/action_cable.md | 53 +++++- docs/delivery_methods/database.md | 35 ---- docs/delivery_methods/discord.md | 20 +++ docs/delivery_methods/email.md | 19 ++- docs/delivery_methods/fcm.md | 30 ++-- .../{twilio.md => twilio_messaging.md} | 6 +- docs/delivery_methods/vonage.md | 2 +- docs/delivery_methods/vonage_sms.md | 28 ++++ lib/noticed/bulk_delivery_methods/discord.rb | 12 +- lib/noticed/delivery_methods/discord.rb | 11 ++ lib/noticed/delivery_methods/email.rb | 2 +- 17 files changed, 282 insertions(+), 228 deletions(-) delete mode 100644 docs/actioncable.md create mode 100644 docs/bulk_delivery_methods/discord.md create mode 100644 docs/bulk_delivery_methods/slack.md create mode 100644 docs/bulk_delivery_methods/webhook.md delete mode 100644 docs/delivery_methods/database.md create mode 100644 docs/delivery_methods/discord.md rename docs/delivery_methods/{twilio.md => twilio_messaging.md} (89%) create mode 100644 docs/delivery_methods/vonage_sms.md create mode 100644 lib/noticed/delivery_methods/discord.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d04e8bf..c939a0fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ ### Unreleased +### 2.0.0 + +* [Breaking] Noticed now provides its own models for managing notifications. Migrate existing model(s) to use the new Noticed tables. + +TODO - add migration example + +* [Breaking] Noticed::NotificationChannel has been removed. Use an ActionCable channel in your application instead. +* [Breaking] Twilio has been renamed to `twilio_messaging` to provide support for other Twilio services in the future. +* [Breaking] Vonage / Nexmo has been renamed to `vonage_sms` to provide support for other Vonage services in the future. + +```ruby +class NotificationChannel < ApplicationCable::Channel + def subscribed + stream_for current_user + end + + def unsubscribed + stop_all_streams + end +end +``` + +* `Notifications` have now been renamed to `Notifiers` and now inherit from the +* Email delivery method now supports args * Support html safe translations for Rails 7+ ### 1.6.3 diff --git a/README.md b/README.md index e8783d8f..78dfe716 100644 --- a/README.md +++ b/README.md @@ -6,25 +6,33 @@ [![Build Status](https://github.com/excid3/noticed/workflows/Tests/badge.svg)](https://github.com/excid3/noticed/actions) [![Gem Version](https://badge.fury.io/rb/noticed.svg)](https://badge.fury.io/rb/noticed) -Currently, we support these notification delivery methods out of the box: +Noticed allows you to send notifications to any number of recipients. You might want a Slack notification with 0 recipients to let your team know when something happens. A notification can also be sent to 1+ recipients -* Database -* Email -* ActionCable channels -* Slack -* Microsoft Teams -* Twilio (SMS) -* Vonage / Nexmo (SMS) -* iOS Apple Push Notifications -* Firebase Cloud Messaging (Android and more) +There are two types of delivery methods: +1. Individual Deliveries - one notification to each recipient. +2. Bulk Deliveries - one notification for all recipients. This is useful for sending a notification to your Slack team, for example. -And you can easily add new notification types for any other delivery methods. +Delivery methods we officially support: + +* [ActionCable](docs/delivery_methods/action_cable.md) +* [Apple Push Notification Service](docs/delivery_methods/ios.md) +* [Email](docs/delivery_methods/email.md) +* [Firebase Cloud Messaging](docs/delivery_methods/fcm.md) (iOS, Android, and web clients) +* [Microsoft Teams](docs/delivery_methods/microsoft_teams.md) +* [Slack](docs/delivery_methods/slack.md) +* [Twilio Messaging](docs/delivery_methods/twilio_messaging.md) - SMS, Whatsapp +* [Vonage SMS](docs/delivery_methods/vonage_sms.md) +* [Test](docs/delivery_methods/test.md) + +Bulk delivery methods we support: + +* [Discord](docs/bulk_delivery_methods/discord.md) +* [Slack](docs/bulk_delivery_methods/slack.md) +* [Webhook](docs/bulk_delivery_methods/webhook.md) ## 🎬 Screencast -
- -
+ [Watch Screencast](https://www.youtube.com/watch?v=Scffi4otlFc) @@ -35,53 +43,64 @@ Run the following command to add Noticed to your Gemfile bundle add "noticed" ``` -To save notifications to your database, use the following command to generate a Notification model. +Add the migraitons -```ruby -rails generate noticed:model +```bash +rails noticed:install:migrations +rails db:migrate ``` -This will generate a Notification model and instructions for associating User models with the notifications table. - ## 📝 Usage To generate a notification object, simply run: `rails generate noticed:notifier CommentNotifier` +#### Add Delivery Methods +Then add your delivery methods to the Notifier. + +```ruby +# app/notifiers/comment_notifier.rb +class CommentNotifier < Noticed::Event + bulk_deliver_by :webhook do |config| + config.url = "https://example.org..." + config.json = ->{ text: "New comment: #{record.body}" } + end + + deliver_by :email do |config| + config.mailer = "UserMailer" + config.method = :new_comment + end +end +``` + #### Sending Notifications -To send a notification to a user: +To send a notification to user(s): ```ruby # Instantiate a new notifier -notifier = CommentNotifier.with(record: @comment) +CommentNotifier.with(record: @comment, foo: "bar").deliver_later(User.all) +``` -# Deliver notification in background job -notifier.deliver_later(@comment.post.author) +This instantiates a new `CommentNotifier` with params. Similar to ActiveJob, you can pass any params can be serialized. -# Deliver notification immediately -notifier.deliver(@comment.post.author) +The `record:` param is a special param that gets assigned to the `record` polymorphic association in the database. -# Deliver notification to multiple recipients -notifier.deliver_later(User.all) -``` +This notification will be delivered to `User.all`. Delivering will create a Noticed::Event record and associated Noticed::Notification records for each recipient. -This will instantiate a new notifier with the `comment` stored in the notification's params. +After saving, a job will be enqueued for processing this notification and delivering it to all recipients. -Each delivery method is able to transform this metadata that's best for the format. For example, the database may simply store the comment so it can be linked when rendering in the navbar. The websocket mechanism may transform this into a browser notification or insert it into the navbar. +Each delivery method also spawns its own job. This allows you to skip email notifications if the user had already opened a push notification, for example. #### Notifier Objects -Notifiers inherit from `Noticed::Base`. This provides all their functionality and allows them to be delivered. - -To add delivery methods, simply `include` the module for the delivery methods you would like to use. +Notifiers inherit from `Noticed::Event`. This provides all their functionality and allows them to be delivered. ```ruby -class CommentNotifier < Noticed::Base - deliver_by :database +class CommentNotifier < Noticed::Event deliver_by :action_cable - deliver_by :email, mailer: 'CommentMailer', if: :email_notifications? + deliver_by :email, mailer: 'CommentMailer', if: ->(recipient) { !!recipient.preferences[:email] }:email_notifications? # I18n helpers def message @@ -93,20 +112,12 @@ class CommentNotifier < Noticed::Base def url post_path(params[:post]) end - - def email_notifications? - !!recipient.preferences[:email] - end - - after_deliver do - # Anything you want - end end ``` **Shared Options** -* `if: :method_name` - Calls `method_name` and cancels delivery method if `false` is returned +* `if: :method_name` - Calls `method_name` and cancels delivery method if `false` is returned. This can also be specified as a lambda. * `unless: :method_name` - Calls `method_name` and cancels delivery method if `true` is returned * `delay: ActiveSupport::Duration` - Delays the delivery for the given duration of time * `delay: :method_name` - Calls `method_name` which should return an `ActiveSupport::Duration` and delays the delivery for the given duration of time @@ -125,35 +136,6 @@ Don't forget, you'll need to configure `default_url_options` in order for Rails Rails.application.routes.default_url_options[:host] = 'localhost:3000' ``` -**Callbacks** - -Like ActiveRecord, notifications have several different types of callbacks. - -```ruby -class CommentNotifier < Noticed::Base - deliver_by :database - deliver_by :email, mailer: 'CommentMailer' - - # Callbacks for the entire delivery - before_deliver :whatever - around_deliver :whatever - after_deliver :whatever - - # Callbacks for each delivery method - before_database :whatever - around_database :whatever - after_database :whatever - - before_email :whatever - around_email :whatever - after_email :whatever -end -``` - -When using `deliver_later` callbacks will be run around queuing the delivery method jobs (not inside the jobs as they actually execute). - -Defining custom delivery methods allows you to add callbacks that run inside the background job as each individual delivery is executed. See the Custom Delivery Methods section for more information. - ##### Translations We've added `translate` and `t` helpers like Rails has to provide an easy way of scoping translations. If the key starts with a period, it will automatically scope the key under `notifications` and the underscored name of the notification class it is used in. @@ -174,29 +156,19 @@ For example: ```ruby class CommentNotifier < Noticed::Base - deliver_by :email, mailer: 'CommentMailer', if: :email_notifications? - - def email_notifications? - recipient.email_notifications? + deliver_by :email do |config| + config.mailer = 'CommentMailer' + config.method = :new_comment + config.if = ->{ recipient.email_notifications? } end end ``` -## 🐞 Debugging - -In order to figure out what's up when you run in to errors, you can set the `debug` parameter to `true` in your notification, which will give you a more detailed error message about what went wrong. - -Example: - -```ruby -deliver_by :slack, debug: true -``` - ## ✅ Best Practices ### Creating a notification from an Active Record callback -A common use case is to trigger a notification when a record is created. For example, +Always use `after_commit` hooks to send notifications from ActiveRecord callbacks. For example, to send a notification automatically after a message is created: ```ruby class Message < ApplicationRecord @@ -211,8 +183,6 @@ class Message < ApplicationRecord end ``` -If you are creating the notification on a background job (i.e. via `#deliver_later`), make sure you use a `commit` hook such as `after_create_commit` or `after_commit`. - Using `after_create` might cause the notification delivery methods to fail. This is because the job was enqueued while inside a database transaction, and the `Message` record might not yet be saved to the database. A common symptom of this problem is undelivered notifications and the following error in your logs. diff --git a/docs/actioncable.md b/docs/actioncable.md deleted file mode 100644 index b7b7616d..00000000 --- a/docs/actioncable.md +++ /dev/null @@ -1,60 +0,0 @@ -# ActionCable Notifications - -ActionCable notifications in noticed are broadcast to the Noticed::NotificationChannel. - -By default, we simply send over the `params` as JSON and subscribe to the `current_user` stream. - -This requires `identified_by :current_user` in your ApplicationCable::Connection. For example, using Devise for authentication: - -```ruby -module ApplicationCable - class Connection < ActionCable::Connection::Base - identified_by :current_user - - def connect - self.current_user = find_verified_user - logger.add_tags "ActionCable", "User #{current_user.id}" - end - - protected - - def find_verified_user - if current_user = env['warden'].user - current_user - else - reject_unauthorized_connection - end - end - end -end -``` - -## Subscribing to the Noticed::NotificationChannel with Javascript - -To receive Noticed notifications client-side, you'll need to subscribe to the Noticed::NotificationChannel. - -```javascript -// app/javascript/channels/notification_channel.js - -import consumer from "./consumer" - -consumer.subscriptions.create("Noticed::NotificationChannel", { - connected() { - // Called when the subscription is ready for use on the server - }, - - disconnected() { - // Called when the subscription has been terminated by the server - }, - - received(data) { - // Called when there's incoming data on the websocket for this channel - console.log(data) - } -}); -``` - -## References - -ActionCable Delivery Method: https://github.com/excid3/noticed/blob/master/lib/noticed/delivery_methods/action_cable.rb -NotificationsChannel: https://github.com/excid3/noticed/blob/master/lib/noticed/notification_channel.rb diff --git a/docs/bulk_delivery_methods/discord.md b/docs/bulk_delivery_methods/discord.md new file mode 100644 index 00000000..7a9207e8 --- /dev/null +++ b/docs/bulk_delivery_methods/discord.md @@ -0,0 +1,20 @@ +# Discord Bulk Delivery Method + +Send a Discord message to builk notify users in a channel. + +We recommend using [Discohook](https://discohook.org) to design your messages. + +## Usage + +```ruby +class CommentNotification + bulk_deliver_by :discord do |config| + config.url = "https://discord.com..." + config.json = -> { + { + # ... + } + } + end +end +``` diff --git a/docs/bulk_delivery_methods/slack.md b/docs/bulk_delivery_methods/slack.md new file mode 100644 index 00000000..4fb1fdbe --- /dev/null +++ b/docs/bulk_delivery_methods/slack.md @@ -0,0 +1,18 @@ +# Slack Bulk Delivery Method + +Send a Slack message to builk notify users in a channel. + +## Usage + +```ruby +class CommentNotification + deliver_by :slackdo |config| + config.url = "https://slack.com..." + config.json = -> { + { + # ... + } + } + end +end +``` diff --git a/docs/bulk_delivery_methods/webhook.md b/docs/bulk_delivery_methods/webhook.md new file mode 100644 index 00000000..258054b1 --- /dev/null +++ b/docs/bulk_delivery_methods/webhook.md @@ -0,0 +1,18 @@ +# Webhook Bulk Delivery Method + +Send a webhook request to builk notify users in a channel. + +## Usage + +```ruby +class CommentNotification + deliver_by :webhook do |config| + config.url = "https://example.org..." + config.json = -> { + { + # ... + } + } + end +end +``` diff --git a/docs/delivery_methods/action_cable.md b/docs/delivery_methods/action_cable.md index 732658dd..a401f1c5 100644 --- a/docs/delivery_methods/action_cable.md +++ b/docs/delivery_methods/action_cable.md @@ -1,10 +1,16 @@ -### ActionCable Delivery Method +# ActionCable Delivery Method Sends a notification to the browser via websockets (ActionCable channel by default). -`deliver_by :action_cable` +```ruby +deliver_by :action_cable do |config| + config.channel = "NotificationsChannel" + config.stream = :custom_stream + config.message = ->{ params.merge( user_id: recipient.id) } +end +``` -##### Options +## Options * `format: :format_for_action_cable` - *Optional* @@ -22,12 +28,43 @@ Sends a notification to the browser via websockets (ActionCable channel by defau Defaults to `recipient` +## Authentication + +To send notifications to individual users, you'll want to use `stream_for current_user` + ```ruby -deliver_by :action_cable, channel: MyChannel, stream: :custom_stream, format: :action_cable_data -def custom_stream - "user_#{recipient.id}" +class NotificationChannel < ApplicationCable::Channel + def subscribed + stream_for current_user + end + + def unsubscribed + stop_all_streams + end end -def action_cable_data - { user_id: recipient.id } +``` + +This requires `identified_by :current_user` in your ApplicationCable::Connection. For example, using Devise for authentication: + +```ruby +module ApplicationCable + class Connection < ActionCable::Connection::Base + identified_by :current_user + + def connect + self.current_user = find_verified_user + logger.add_tags "ActionCable", "User #{current_user.id}" + end + + protected + + def find_verified_user + if current_user = env['warden'].user + current_user + else + reject_unauthorized_connection + end + end + end end ``` diff --git a/docs/delivery_methods/database.md b/docs/delivery_methods/database.md deleted file mode 100644 index 6be85150..00000000 --- a/docs/delivery_methods/database.md +++ /dev/null @@ -1,35 +0,0 @@ -### Database Delivery Method - -Writes notification to the database. - -`deliver_by :database` - -**Note:** Database notifications are special in that they will run before the other delivery methods. We do this so you can reference the database record ID in other delivery methods. For that same reason, the delivery can't be delayed (via the `delay` option) or an error will be raised. - -##### Options - -* `association` - *Optional* - - The name of the database association to use. Defaults to `:notifications` - -* `attributes:` - *Optional* - - Pass a symbol or callable object to define custom attributes to save to the database record. - -##### Examples - -```ruby -class CommentNotification - deliver_by :database do |config| - config.association = :notifications - - config.attributes = ->{ - { column: value } - } - end -end -``` - -```ruby -CommentNotification.with(record: @post).deliver(user) -``` diff --git a/docs/delivery_methods/discord.md b/docs/delivery_methods/discord.md new file mode 100644 index 00000000..69cdd053 --- /dev/null +++ b/docs/delivery_methods/discord.md @@ -0,0 +1,20 @@ +# Discord Bulk Delivery Method + +Send Discord messages to builk notify users in a channel. + +We recommend using [Discohook](https://discohook.org) to design your messages. + +## Usage + +```ruby +class CommentNotification + deliver_by :discord do |config| + config.url = "https://discord.com..." + config.json = -> { + { + # ... + } + } + end +end +``` diff --git a/docs/delivery_methods/email.md b/docs/delivery_methods/email.md index 7bbce97e..90001647 100644 --- a/docs/delivery_methods/email.md +++ b/docs/delivery_methods/email.md @@ -1,8 +1,19 @@ ### Email Delivery Method -Sends an email notification. Emails will always be sent with `deliver_later` +Sends an email to each recipient. -`deliver_by :email, mailer: "UserMailer"` +```ruby +deliver_by :email do |config| + config.mailer = "UserMailer" + config.method = :receipt + config.params = ->{ params } + config.args = ->{ [1, 2, 3] } + + # Enqueues a separate job for sending the email using deliver_later. + # Deliveries already happen in jobs so this is typically unnecessary. + # config.enqueue = false +end +``` ##### Options @@ -14,10 +25,12 @@ Sends an email notification. Emails will always be sent with `deliver_later` Used to customize the method on the mailer that is called -- `format: :format_for_email` - _Optional_ +- `params` - _Optional_ Use a custom method to define the params sent to the mailer. `recipient` will be merged into the params. +- `args` - _Optional_ + - `enqueue: false` - _Optional_ Use `deliver_later` to queue email delivery with ActiveJob. This is `false` by default as each delivery method is already a separate job. diff --git a/docs/delivery_methods/fcm.md b/docs/delivery_methods/fcm.md index f17fd97c..759a1bdd 100644 --- a/docs/delivery_methods/fcm.md +++ b/docs/delivery_methods/fcm.md @@ -1,6 +1,6 @@ # Firebase Cloud Messaging Delivery Method -Send Android Device Notifications using the Google Firebase Cloud Messaging service and the `googleauth` gem. +Send Device Notifications using the Google Firebase Cloud Messaging service and the `googleauth` gem. FCM supports Android, iOS, and web clients. ```bash bundle add "googleauth" @@ -24,19 +24,15 @@ See the below instructions on where to store this information within your applic ```ruby class CommentNotification - deliver_by :fcm, credentials: :fcm_credentials, format: :format_notification - - # This needs to return the path to your FCM credentials - def fcm_credentials - Rails.root.join("config/certs/fcm.json") - end - - def format_notification(device_token) - { - token: device_token, - notification: { - title: "Test Title", - body: "Test body" + deliver_by :fcm do |config| + config.credentials = Rails.root.join("config/certs/fcm.json") + config.json = ->(device_token) { + { + token: device_token, + notification: { + title: "Test Title", + body: "Test body" + } } } end @@ -45,10 +41,10 @@ end ## Options -* `format: :format_notification` - Customize the Firebase Cloud Messaging notification object +* `json` + Customize the Firebase Cloud Messaging notification object. This can be a Lambda or Symbol of a method name on the notifier. The callable object will be given the device token as an argument. -* `credentials: :fcm_credentials` +* `credentials` The location of your Firebase Cloud Messaging credentials. - When a String object: `deliver_by :fcm, credentials: "config/credentials/fcm.json"` * Interally, this string is passed to `Rails.root.join()` as an argument so there is no need to do this beforehand. diff --git a/docs/delivery_methods/twilio.md b/docs/delivery_methods/twilio_messaging.md similarity index 89% rename from docs/delivery_methods/twilio.md rename to docs/delivery_methods/twilio_messaging.md index 30292c8c..e1eb045e 100644 --- a/docs/delivery_methods/twilio.md +++ b/docs/delivery_methods/twilio_messaging.md @@ -1,8 +1,8 @@ -### Twilio SMS Delivery Method +### Twilio Messaaging Delivery Method -Sends an SMS notification via Twilio. +Sends an SMS or Whatsapp message via Twilio Messaging. -`deliver_by :twilio` +`deliver_by :twilio_messaging` ##### Options diff --git a/docs/delivery_methods/vonage.md b/docs/delivery_methods/vonage.md index 0cbf4d14..d56f5943 100644 --- a/docs/delivery_methods/vonage.md +++ b/docs/delivery_methods/vonage.md @@ -2,7 +2,7 @@ Sends an SMS notification via Vonage / Nexmo. -`deliver_by :vonage` +`deliver_by :vonage_sms` ##### Options diff --git a/docs/delivery_methods/vonage_sms.md b/docs/delivery_methods/vonage_sms.md new file mode 100644 index 00000000..0cbf4d14 --- /dev/null +++ b/docs/delivery_methods/vonage_sms.md @@ -0,0 +1,28 @@ +### Vonage SMS + +Sends an SMS notification via Vonage / Nexmo. + +`deliver_by :vonage` + +##### Options + +* `credentials: :get_credentials` - *Optional* + + Use a custom method for retrieving credentials. Method should return a Hash with `:api_key` and `:api_secret` keys. + + Defaults to `Rails.application.credentials.vonage[:api_key]` and `Rails.application.credentials.vonage[:api_secret]` + +* `deliver_by :vonage, format: :format_for_vonage` - *Optional* + + Use a custom method to generate the params sent to Vonage. Method should return a Hash. Defaults to: + + ```ruby + { + api_key: vonage_credentials[:api_key], + api_secret: vonage_credentials[:api_secret], + from: notification.params[:from], + text: notification.params[:body], + to: notification.params[:to], + type: "unicode" + } + ``` diff --git a/lib/noticed/bulk_delivery_methods/discord.rb b/lib/noticed/bulk_delivery_methods/discord.rb index 525b7426..c6f9e1c4 100644 --- a/lib/noticed/bulk_delivery_methods/discord.rb +++ b/lib/noticed/bulk_delivery_methods/discord.rb @@ -1,16 +1,10 @@ module Noticed module BulkDeliveryMethods - class Slack < BulkDeliveryMethod - DEFAULT_URL = "https://slack.com/api/chat.postMessage" - - required_options :json + class Discord < BulkDeliveryMethod + required_options :json, :url def deliver - post_request url, headers: evaluate_option(:headers), json: evaluate_option(:json) - end - - def url - evaluate_option(:url) || DEFAULT_URL + post_request evaluate_option(:url), headers: evaluate_option(:headers), json: evaluate_option(:json) end end end diff --git a/lib/noticed/delivery_methods/discord.rb b/lib/noticed/delivery_methods/discord.rb new file mode 100644 index 00000000..1356ae8c --- /dev/null +++ b/lib/noticed/delivery_methods/discord.rb @@ -0,0 +1,11 @@ +module Noticed + module DeliveryMethods + class Discord < BulkDeliveryMethod + required_options :json, :url + + def deliver + post_request evaluate_option(:url), headers: evaluate_option(:headers), json: evaluate_option(:json) + end + end + end +end diff --git a/lib/noticed/delivery_methods/email.rb b/lib/noticed/delivery_methods/email.rb index a0083096..f1bef99d 100644 --- a/lib/noticed/delivery_methods/email.rb +++ b/lib/noticed/delivery_methods/email.rb @@ -6,7 +6,7 @@ class Email < DeliveryMethod def deliver mailer = fetch_constant(:mailer) email = evaluate_option(:method) - params = evaluate_option(:params) || notification&.params&.merge(record: notification.record) + params = (evaluate_option(:params) || notification&.params || {}).merge(record: notification&.record) args = evaluate_option(:args) mail = mailer.with(params) From dbe565ed64a91a29323328b5d7ad1d583f55beef Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Wed, 3 Jan 2024 11:07:48 -0600 Subject: [PATCH 35/39] Order default params value before serialize --- app/models/concerns/noticed/deliverable.rb | 2 ++ app/models/noticed/event.rb | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/concerns/noticed/deliverable.rb b/app/models/concerns/noticed/deliverable.rb index 4716cfc0..99c73e0c 100644 --- a/app/models/concerns/noticed/deliverable.rb +++ b/app/models/concerns/noticed/deliverable.rb @@ -47,6 +47,8 @@ def evaluate_option(name, context) class_attribute :delivery_methods, instance_writer: false, default: {} class_attribute :required_param_names, instance_writer: false, default: [] + attribute :params, default: {} + if Rails.gem_version >= Gem::Version.new("7.1.0.alpha") serialize :params, coder: Coder else diff --git a/app/models/noticed/event.rb b/app/models/noticed/event.rb index f3fb3258..51d2bc08 100644 --- a/app/models/noticed/event.rb +++ b/app/models/noticed/event.rb @@ -7,10 +7,8 @@ class Event < ApplicationRecord belongs_to :record, polymorphic: true, optional: true has_many :notifications, dependent: :delete_all - scope :newest_first, -> { order(created_at: :desc) } - accepts_nested_attributes_for :notifications - attribute :params, default: {} + scope :newest_first, -> { order(created_at: :desc) } end end From d754cf028c8413c65e2e271741b95f48d463a89b Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Mon, 15 Jan 2024 08:22:54 -0600 Subject: [PATCH 36/39] More upgrade notes --- UPGRADE.md | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index abfe48b1..3bfe6c0f 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -19,22 +19,14 @@ To migrate your data to the new tables, loop through your existing notifications ```ruby Notification.find_each do |notification| - attributes = notification.attributes.slice("id", "type", "params") + attributes = notification.attributes.slice("id", "type") + attributes[:type] = attributes[:type].sub("Notification", "Notifier")) + attributes[:params] = Noticed::Coder.load(notification.params) attributes[:notifications_attributes] = [{recipient_type: notification.recipient_type, recipient_id: notification.recipient_id, seen_at: notification.read_at, read_at: notification.interacted_at}] Noticed::Event.create!(attributes) end ``` -class Notification < ActiveRecord::Base - self.inheritance_column = nil -end - -Notification.find_each do |notification| - attributes = notification.attributes.slice("id", "type", "params") - attributes[:notifications_attributes] = [{recipient_type: notification.recipient_type, recipient_id: notification.recipient_id, seen_at: notification.read_at, read_at: notification.interacted_at}] - Noticed::Event.create!(attributes) -end - After migrating, you can drop the old notifications table and model. ### Parent Class @@ -69,7 +61,8 @@ end ### Delivery Method Configuration -Configuration for each delivery method can be contained within a block now. This improves organization but +Configuration for each delivery method can be contained within a block now. This improves organization for delivery method options by defining them in the block. +Procs/Lambdas will be evaluated when needed and symbols can be used to call a method. ```ruby class CommentNotifier < Noticed::Event @@ -107,6 +100,25 @@ class Comment < ApplicationRecord end ``` +If you would like to keep the JSON querying, you can implement a method for querying your model depending on the database you use: + +```ruby +# Define the +param_name = "user" + +# PostgreSQL +model.where("params @> ?", Noticed::Coder.dump(param_name.to_sym => self).to_json) + +# MySQL +model.where("JSON_CONTAINS(params, ?)", Noticed::Coder.dump(param_name.to_sym => self).to_json) + +# SQLite +model.where("json_extract(params, ?) = ?", "$.#{param_name}", Noticed::Coder.dump(self).to_json) + +# Other +model.where(params: {param_name.to_sym => self}) +``` + ### Receipient Notifications Association Recipients can be associated with notifications using the following. This is useful for displaying notifications in your UI. @@ -115,4 +127,4 @@ Recipients can be associated with notifications using the following. This is use class User < ApplicationRecord has_many :notifications, as: :recipient, dependent: :destroy, class_name: "Noticed::Notification" end -``` \ No newline at end of file +``` From 963d1e3a99dfed73a5c19b8fe7fd170a73cabd3d Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Mon, 15 Jan 2024 08:23:15 -0600 Subject: [PATCH 37/39] Support if/unless for bulk deliveries --- lib/noticed/bulk_delivery_method.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/noticed/bulk_delivery_method.rb b/lib/noticed/bulk_delivery_method.rb index 390f96c4..1e3de5ad 100644 --- a/lib/noticed/bulk_delivery_method.rb +++ b/lib/noticed/bulk_delivery_method.rb @@ -11,6 +11,9 @@ def perform(delivery_method_name, event) @event = event @config = event.bulk_delivery_methods.fetch(delivery_method_name).config + return false if config.has_key?(:if) && !evaluate_option(:if) + return false if config.has_key?(:unless) && evaluate_option(:unless) + deliver end From 8daeec52d9c613513ce16b1f87e581eb6b4a4184 Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Mon, 15 Jan 2024 08:31:52 -0600 Subject: [PATCH 38/39] Update test dependencies --- Gemfile.lock | 34 ++--- gemfiles/rails_6_1.gemfile.lock | 51 ++++---- gemfiles/rails_7.gemfile.lock | 89 +++++++------ gemfiles/rails_7_1.gemfile.lock | 216 ++++++++++++++++--------------- gemfiles/rails_main.gemfile.lock | 123 +++++++++--------- 5 files changed, 267 insertions(+), 246 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 36a35ddb..0995d582 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -104,11 +104,10 @@ GEM drb (2.2.0) ruby2_keywords erubi (1.12.0) - faraday (2.8.1) - base64 - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) - faraday-net_http (3.0.2) + faraday (2.9.0) + faraday-net_http (>= 2.0, < 3.2) + faraday-net_http (3.1.0) + net-http globalid (1.2.1) activesupport (>= 6.1) google-cloud-env (2.1.0) @@ -125,9 +124,9 @@ GEM i18n (1.14.1) concurrent-ruby (~> 1.0) io-console (0.7.1) - irb (1.11.0) + irb (1.11.1) rdoc - reline (>= 0.3.8) + reline (>= 0.4.2) json (2.7.1) jwt (2.7.1) language_server-protocol (3.17.0.3) @@ -143,20 +142,22 @@ GEM marcel (1.0.2) mini_mime (1.1.5) mini_portile2 (2.8.5) - minitest (5.20.0) + minitest (5.21.1) multi_json (1.15.0) mutex_m (0.2.0) mysql2 (0.5.5) + net-http (0.4.1) + uri net-http2 (0.18.5) http-2 (~> 0.11) - net-imap (0.4.9) + net-imap (0.4.9.1) date net-protocol net-pop (0.1.2) net-protocol net-protocol (0.2.2) timeout - net-smtp (0.4.0) + net-smtp (0.4.0.1) net-protocol nio4r (2.7.0) nokogiri (1.16.0-arm64-darwin) @@ -167,7 +168,7 @@ GEM racc (~> 1.4) os (1.1.4) parallel (1.24.0) - parser (3.2.2.4) + parser (3.3.0.3) ast (~> 2.4.1) racc pg (1.5.4) @@ -216,8 +217,8 @@ GEM rake (13.1.0) rdoc (6.6.2) psych (>= 4.0.0) - regexp_parser (2.8.3) - reline (0.4.1) + regexp_parser (2.9.0) + reline (0.4.2) io-console (~> 0.5) rexml (3.2.6) rubocop (1.59.0) @@ -233,7 +234,7 @@ GEM unicode-display_width (>= 2.4.0, < 3.0) rubocop-ast (1.30.0) parser (>= 3.2.1.0) - rubocop-performance (1.20.1) + rubocop-performance (1.20.2) rubocop (>= 1.48.1, < 2.0) rubocop-ast (>= 1.30.0, < 2.0) ruby-progressbar (1.13.0) @@ -254,15 +255,16 @@ GEM standard-custom (1.0.2) lint_roller (~> 1.0) rubocop (~> 1.50) - standard-performance (1.3.0) + standard-performance (1.3.1) lint_roller (~> 1.1) - rubocop-performance (~> 1.20.1) + rubocop-performance (~> 1.20.2) stringio (3.1.0) thor (1.3.0) timeout (0.4.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) + uri (0.13.0) webmock (3.19.1) addressable (>= 2.8.0) crack (>= 0.3.2) diff --git a/gemfiles/rails_6_1.gemfile.lock b/gemfiles/rails_6_1.gemfile.lock index 672f1194..8d4ea682 100644 --- a/gemfiles/rails_6_1.gemfile.lock +++ b/gemfiles/rails_6_1.gemfile.lock @@ -76,7 +76,6 @@ GEM rake thor (>= 0.14.0) ast (2.4.2) - base64 (0.2.0) builder (3.2.4) byebug (11.1.3) concurrent-ruby (1.2.2) @@ -86,11 +85,10 @@ GEM crass (1.0.6) date (3.3.4) erubi (1.12.0) - faraday (2.8.1) - base64 - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) - faraday-net_http (3.0.2) + faraday (2.9.0) + faraday-net_http (>= 2.0, < 3.2) + faraday-net_http (3.1.0) + net-http globalid (1.2.1) activesupport (>= 6.1) google-cloud-env (2.1.0) @@ -122,27 +120,32 @@ GEM method_source (1.0.0) mini_mime (1.1.5) mini_portile2 (2.8.5) - minitest (5.20.0) + minitest (5.21.1) multi_json (1.15.0) mysql2 (0.5.5) + net-http (0.4.1) + uri net-http2 (0.18.5) http-2 (~> 0.11) - net-imap (0.4.8) + net-imap (0.4.9.1) date net-protocol net-pop (0.1.2) net-protocol net-protocol (0.2.2) timeout - net-smtp (0.4.0) + net-smtp (0.4.0.1) net-protocol nio4r (2.7.0) - nokogiri (1.15.5) - mini_portile2 (~> 2.8.2) + nokogiri (1.16.0-arm64-darwin) + racc (~> 1.4) + nokogiri (1.16.0-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.16.0-x86_64-linux) racc (~> 1.4) os (1.1.4) parallel (1.24.0) - parser (3.2.2.4) + parser (3.3.0.3) ast (~> 2.4.1) racc pg (1.5.4) @@ -181,9 +184,9 @@ GEM thor (~> 1.0) rainbow (3.1.1) rake (13.1.0) - regexp_parser (2.8.3) + regexp_parser (2.9.0) rexml (3.2.6) - rubocop (1.57.2) + rubocop (1.59.0) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -191,16 +194,15 @@ GEM rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.28.1, < 2.0) + rubocop-ast (>= 1.30.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) rubocop-ast (1.30.0) parser (>= 3.2.1.0) - rubocop-performance (1.19.1) - rubocop (>= 1.7.0, < 2.0) - rubocop-ast (>= 0.4.0) + rubocop-performance (1.20.2) + rubocop (>= 1.48.1, < 2.0) + rubocop-ast (>= 1.30.0, < 2.0) ruby-progressbar (1.13.0) - ruby2_keywords (0.0.5) signet (0.18.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) @@ -215,23 +217,24 @@ GEM sprockets (>= 3.0.0) sqlite3 (1.6.9) mini_portile2 (~> 2.8.0) - standard (1.32.1) + standard (1.33.0) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) - rubocop (~> 1.57.2) + rubocop (~> 1.59.0) standard-custom (~> 1.0.0) - standard-performance (~> 1.2) + standard-performance (~> 1.3) standard-custom (1.0.2) lint_roller (~> 1.0) rubocop (~> 1.50) - standard-performance (1.2.1) + standard-performance (1.3.1) lint_roller (~> 1.1) - rubocop-performance (~> 1.19.1) + rubocop-performance (~> 1.20.2) thor (1.3.0) timeout (0.4.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) + uri (0.13.0) webmock (3.19.1) addressable (>= 2.8.0) crack (>= 0.3.2) diff --git a/gemfiles/rails_7.gemfile.lock b/gemfiles/rails_7.gemfile.lock index af56e601..896acbdb 100644 --- a/gemfiles/rails_7.gemfile.lock +++ b/gemfiles/rails_7.gemfile.lock @@ -72,7 +72,7 @@ GEM i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.5) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) apnotic (1.7.1) connection_pool (~> 2) @@ -82,7 +82,6 @@ GEM rake thor (>= 0.14.0) ast (2.4.2) - base64 (0.1.1) builder (3.2.4) byebug (11.1.3) concurrent-ruby (1.2.2) @@ -90,30 +89,32 @@ GEM crack (0.4.5) rexml crass (1.0.6) - date (3.3.3) + date (3.3.4) erubi (1.12.0) - faraday (2.7.11) - base64 - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) - faraday-net_http (3.0.2) + faraday (2.9.0) + faraday-net_http (>= 2.0, < 3.2) + faraday-net_http (3.1.0) + net-http globalid (1.2.1) activesupport (>= 6.1) - googleauth (1.8.1) - faraday (>= 0.17.3, < 3.a) + google-cloud-env (2.1.0) + faraday (>= 1.0, < 3.a) + googleauth (1.9.1) + faraday (>= 1.0, < 3.a) + google-cloud-env (~> 2.1) jwt (>= 1.4, < 3.0) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) - hashdiff (1.0.1) + hashdiff (1.1.0) http-2 (0.11.0) i18n (1.14.1) concurrent-ruby (~> 1.0) - json (2.6.3) + json (2.7.1) jwt (2.7.1) language_server-protocol (3.17.0.3) lint_roller (1.1.0) - loofah (2.21.4) + loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -125,32 +126,37 @@ GEM method_source (1.0.0) mini_mime (1.1.5) mini_portile2 (2.8.5) - minitest (5.20.0) + minitest (5.21.1) multi_json (1.15.0) mysql2 (0.5.5) + net-http (0.4.1) + uri net-http2 (0.18.5) http-2 (~> 0.11) - net-imap (0.4.2) + net-imap (0.4.9.1) date net-protocol net-pop (0.1.2) net-protocol - net-protocol (0.2.1) + net-protocol (0.2.2) timeout - net-smtp (0.4.0) + net-smtp (0.4.0.1) net-protocol - nio4r (2.5.9) - nokogiri (1.15.4) - mini_portile2 (~> 2.8.2) + nio4r (2.7.0) + nokogiri (1.16.0-arm64-darwin) + racc (~> 1.4) + nokogiri (1.16.0-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.16.0-x86_64-linux) racc (~> 1.4) os (1.1.4) - parallel (1.23.0) - parser (3.2.2.4) + parallel (1.24.0) + parser (3.3.0.3) ast (~> 2.4.1) racc pg (1.5.4) - public_suffix (5.0.3) - racc (1.7.1) + public_suffix (5.0.4) + racc (1.7.3) rack (2.2.8) rack-test (2.1.0) rack (>= 1.3) @@ -183,52 +189,51 @@ GEM thor (~> 1.0) zeitwerk (~> 2.5) rainbow (3.1.1) - rake (13.0.6) - regexp_parser (2.8.2) + rake (13.1.0) + regexp_parser (2.9.0) rexml (3.2.6) - rubocop (1.56.4) - base64 (~> 0.1.1) + rubocop (1.59.0) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.2.2.3) + parser (>= 3.2.2.4) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.28.1, < 2.0) + rubocop-ast (>= 1.30.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.29.0) + rubocop-ast (1.30.0) parser (>= 3.2.1.0) - rubocop-performance (1.19.1) - rubocop (>= 1.7.0, < 2.0) - rubocop-ast (>= 0.4.0) + rubocop-performance (1.20.2) + rubocop (>= 1.48.1, < 2.0) + rubocop-ast (>= 1.30.0, < 2.0) ruby-progressbar (1.13.0) - ruby2_keywords (0.0.5) signet (0.18.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - sqlite3 (1.6.7) + sqlite3 (1.6.9) mini_portile2 (~> 2.8.0) - standard (1.31.2) + standard (1.33.0) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) - rubocop (~> 1.56.4) + rubocop (~> 1.59.0) standard-custom (~> 1.0.0) - standard-performance (~> 1.2) + standard-performance (~> 1.3) standard-custom (1.0.2) lint_roller (~> 1.0) rubocop (~> 1.50) - standard-performance (1.2.1) + standard-performance (1.3.1) lint_roller (~> 1.1) - rubocop-performance (~> 1.19.1) + rubocop-performance (~> 1.20.2) thor (1.3.0) - timeout (0.4.0) + timeout (0.4.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) + uri (0.13.0) webmock (3.19.1) addressable (>= 2.8.0) crack (>= 0.3.2) diff --git a/gemfiles/rails_7_1.gemfile.lock b/gemfiles/rails_7_1.gemfile.lock index 39de1963..9c112f6f 100644 --- a/gemfiles/rails_7_1.gemfile.lock +++ b/gemfiles/rails_7_1.gemfile.lock @@ -7,70 +7,71 @@ PATH GEM remote: https://rubygems.org/ specs: - actioncable (7.1.1) - actionpack (= 7.1.1) - activesupport (= 7.1.1) + actioncable (7.1.2) + actionpack (= 7.1.2) + activesupport (= 7.1.2) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.1.1) - actionpack (= 7.1.1) - activejob (= 7.1.1) - activerecord (= 7.1.1) - activestorage (= 7.1.1) - activesupport (= 7.1.1) + actionmailbox (7.1.2) + actionpack (= 7.1.2) + activejob (= 7.1.2) + activerecord (= 7.1.2) + activestorage (= 7.1.2) + activesupport (= 7.1.2) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.1.1) - actionpack (= 7.1.1) - actionview (= 7.1.1) - activejob (= 7.1.1) - activesupport (= 7.1.1) + actionmailer (7.1.2) + actionpack (= 7.1.2) + actionview (= 7.1.2) + activejob (= 7.1.2) + activesupport (= 7.1.2) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.2) - actionpack (7.1.1) - actionview (= 7.1.1) - activesupport (= 7.1.1) + actionpack (7.1.2) + actionview (= 7.1.2) + activesupport (= 7.1.2) nokogiri (>= 1.8.5) + racc rack (>= 2.2.4) rack-session (>= 1.0.1) rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - actiontext (7.1.1) - actionpack (= 7.1.1) - activerecord (= 7.1.1) - activestorage (= 7.1.1) - activesupport (= 7.1.1) + actiontext (7.1.2) + actionpack (= 7.1.2) + activerecord (= 7.1.2) + activestorage (= 7.1.2) + activesupport (= 7.1.2) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.1.1) - activesupport (= 7.1.1) + actionview (7.1.2) + activesupport (= 7.1.2) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (7.1.1) - activesupport (= 7.1.1) + activejob (7.1.2) + activesupport (= 7.1.2) globalid (>= 0.3.6) - activemodel (7.1.1) - activesupport (= 7.1.1) - activerecord (7.1.1) - activemodel (= 7.1.1) - activesupport (= 7.1.1) + activemodel (7.1.2) + activesupport (= 7.1.2) + activerecord (7.1.2) + activemodel (= 7.1.2) + activesupport (= 7.1.2) timeout (>= 0.4.0) - activestorage (7.1.1) - actionpack (= 7.1.1) - activejob (= 7.1.1) - activerecord (= 7.1.1) - activesupport (= 7.1.1) + activestorage (7.1.2) + actionpack (= 7.1.2) + activejob (= 7.1.2) + activerecord (= 7.1.2) + activesupport (= 7.1.2) marcel (~> 1.0) - activesupport (7.1.1) + activesupport (7.1.2) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) @@ -80,7 +81,7 @@ GEM minitest (>= 5.1) mutex_m tzinfo (~> 2.0) - addressable (2.8.5) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) apnotic (1.7.1) connection_pool (~> 2) @@ -90,8 +91,8 @@ GEM rake thor (>= 0.14.0) ast (2.4.2) - base64 (0.1.1) - bigdecimal (3.1.4) + base64 (0.2.0) + bigdecimal (3.1.5) builder (3.2.4) byebug (11.1.3) concurrent-ruby (1.2.2) @@ -99,36 +100,38 @@ GEM crack (0.4.5) rexml crass (1.0.6) - date (3.3.3) - drb (2.1.1) + date (3.3.4) + drb (2.2.0) ruby2_keywords erubi (1.12.0) - faraday (2.7.11) - base64 - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) - faraday-net_http (3.0.2) + faraday (2.9.0) + faraday-net_http (>= 2.0, < 3.2) + faraday-net_http (3.1.0) + net-http globalid (1.2.1) activesupport (>= 6.1) - googleauth (1.8.1) - faraday (>= 0.17.3, < 3.a) + google-cloud-env (2.1.0) + faraday (>= 1.0, < 3.a) + googleauth (1.9.1) + faraday (>= 1.0, < 3.a) + google-cloud-env (~> 2.1) jwt (>= 1.4, < 3.0) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) - hashdiff (1.0.1) + hashdiff (1.1.0) http-2 (0.11.0) i18n (1.14.1) concurrent-ruby (~> 1.0) - io-console (0.6.0) - irb (1.8.3) + io-console (0.7.1) + irb (1.11.1) rdoc - reline (>= 0.3.8) - json (2.6.3) + reline (>= 0.4.2) + json (2.7.1) jwt (2.7.1) language_server-protocol (3.17.0.3) lint_roller (1.1.0) - loofah (2.21.4) + loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -139,35 +142,40 @@ GEM marcel (1.0.2) mini_mime (1.1.5) mini_portile2 (2.8.5) - minitest (5.20.0) + minitest (5.21.1) multi_json (1.15.0) - mutex_m (0.1.2) + mutex_m (0.2.0) mysql2 (0.5.5) + net-http (0.4.1) + uri net-http2 (0.18.5) http-2 (~> 0.11) - net-imap (0.4.2) + net-imap (0.4.9.1) date net-protocol net-pop (0.1.2) net-protocol - net-protocol (0.2.1) + net-protocol (0.2.2) timeout - net-smtp (0.4.0) + net-smtp (0.4.0.1) net-protocol - nio4r (2.5.9) - nokogiri (1.15.4) - mini_portile2 (~> 2.8.2) + nio4r (2.7.0) + nokogiri (1.16.0-arm64-darwin) + racc (~> 1.4) + nokogiri (1.16.0-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.16.0-x86_64-linux) racc (~> 1.4) os (1.1.4) - parallel (1.23.0) - parser (3.2.2.4) + parallel (1.24.0) + parser (3.3.0.3) ast (~> 2.4.1) racc pg (1.5.4) - psych (5.1.1.1) + psych (5.1.2) stringio - public_suffix (5.0.3) - racc (1.7.1) + public_suffix (5.0.4) + racc (1.7.3) rack (3.0.8) rack-session (2.0.0) rack (>= 3.0.0) @@ -176,20 +184,20 @@ GEM rackup (2.1.0) rack (>= 3) webrick (~> 1.8) - rails (7.1.1) - actioncable (= 7.1.1) - actionmailbox (= 7.1.1) - actionmailer (= 7.1.1) - actionpack (= 7.1.1) - actiontext (= 7.1.1) - actionview (= 7.1.1) - activejob (= 7.1.1) - activemodel (= 7.1.1) - activerecord (= 7.1.1) - activestorage (= 7.1.1) - activesupport (= 7.1.1) + rails (7.1.2) + actioncable (= 7.1.2) + actionmailbox (= 7.1.2) + actionmailer (= 7.1.2) + actionpack (= 7.1.2) + actiontext (= 7.1.2) + actionview (= 7.1.2) + activejob (= 7.1.2) + activemodel (= 7.1.2) + activerecord (= 7.1.2) + activestorage (= 7.1.2) + activesupport (= 7.1.2) bundler (>= 1.15.0) - railties (= 7.1.1) + railties (= 7.1.2) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -197,39 +205,38 @@ GEM rails-html-sanitizer (1.6.0) loofah (~> 2.21) nokogiri (~> 1.14) - railties (7.1.1) - actionpack (= 7.1.1) - activesupport (= 7.1.1) + railties (7.1.2) + actionpack (= 7.1.2) + activesupport (= 7.1.2) irb rackup (>= 1.0.0) rake (>= 12.2) thor (~> 1.0, >= 1.2.2) zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.0.6) - rdoc (6.5.0) + rake (13.1.0) + rdoc (6.6.2) psych (>= 4.0.0) - regexp_parser (2.8.2) - reline (0.3.9) + regexp_parser (2.9.0) + reline (0.4.2) io-console (~> 0.5) rexml (3.2.6) - rubocop (1.56.4) - base64 (~> 0.1.1) + rubocop (1.59.0) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.2.2.3) + parser (>= 3.2.2.4) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.28.1, < 2.0) + rubocop-ast (>= 1.30.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.29.0) + rubocop-ast (1.30.0) parser (>= 3.2.1.0) - rubocop-performance (1.19.1) - rubocop (>= 1.7.0, < 2.0) - rubocop-ast (>= 0.4.0) + rubocop-performance (1.20.2) + rubocop (>= 1.48.1, < 2.0) + rubocop-ast (>= 1.30.0, < 2.0) ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) signet (0.18.0) @@ -237,26 +244,27 @@ GEM faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - sqlite3 (1.6.7) + sqlite3 (1.6.9) mini_portile2 (~> 2.8.0) - standard (1.31.2) + standard (1.33.0) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) - rubocop (~> 1.56.4) + rubocop (~> 1.59.0) standard-custom (~> 1.0.0) - standard-performance (~> 1.2) + standard-performance (~> 1.3) standard-custom (1.0.2) lint_roller (~> 1.0) rubocop (~> 1.50) - standard-performance (1.2.1) + standard-performance (1.3.1) lint_roller (~> 1.1) - rubocop-performance (~> 1.19.1) - stringio (3.0.8) + rubocop-performance (~> 1.20.2) + stringio (3.1.0) thor (1.3.0) - timeout (0.4.0) + timeout (0.4.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) + uri (0.13.0) webmock (3.19.1) addressable (>= 2.8.0) crack (>= 0.3.2) diff --git a/gemfiles/rails_main.gemfile.lock b/gemfiles/rails_main.gemfile.lock index 56541458..30355228 100644 --- a/gemfiles/rails_main.gemfile.lock +++ b/gemfiles/rails_main.gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/rails/rails.git - revision: 4f42b313a119e8d71e4297e6868d10c90ae9d8fd + revision: fc7befc87af05796612d03531b21c691699aeb74 branch: main specs: actioncable (7.2.0.alpha) @@ -15,19 +15,13 @@ GIT activerecord (= 7.2.0.alpha) activestorage (= 7.2.0.alpha) activesupport (= 7.2.0.alpha) - mail (>= 2.7.1) - net-imap - net-pop - net-smtp + mail (>= 2.8.0) actionmailer (7.2.0.alpha) actionpack (= 7.2.0.alpha) actionview (= 7.2.0.alpha) activejob (= 7.2.0.alpha) activesupport (= 7.2.0.alpha) - mail (~> 2.5, >= 2.5.4) - net-imap - net-pop - net-smtp + mail (>= 2.8.0) rails-dom-testing (~> 2.2) actionpack (7.2.0.alpha) actionview (= 7.2.0.alpha) @@ -39,6 +33,7 @@ GIT rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) actiontext (7.2.0.alpha) actionpack (= 7.2.0.alpha) activerecord (= 7.2.0.alpha) @@ -75,7 +70,7 @@ GIT drb i18n (>= 1.6, < 2) minitest (>= 5.1) - tzinfo (~> 2.0) + tzinfo (~> 2.0, >= 2.0.5) rails (7.2.0.alpha) actioncable (= 7.2.0.alpha) actionmailbox (= 7.2.0.alpha) @@ -108,7 +103,7 @@ PATH GEM remote: https://rubygems.org/ specs: - addressable (2.8.5) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) apnotic (1.7.1) connection_pool (~> 2) @@ -118,8 +113,8 @@ GEM rake thor (>= 0.14.0) ast (2.4.2) - base64 (0.1.1) - bigdecimal (3.1.4) + base64 (0.2.0) + bigdecimal (3.1.5) builder (3.2.4) byebug (11.1.3) concurrent-ruby (1.2.2) @@ -127,36 +122,38 @@ GEM crack (0.4.5) rexml crass (1.0.6) - date (3.3.3) - drb (2.1.1) + date (3.3.4) + drb (2.2.0) ruby2_keywords erubi (1.12.0) - faraday (2.7.11) - base64 - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) - faraday-net_http (3.0.2) + faraday (2.9.0) + faraday-net_http (>= 2.0, < 3.2) + faraday-net_http (3.1.0) + net-http globalid (1.2.1) activesupport (>= 6.1) - googleauth (1.8.1) - faraday (>= 0.17.3, < 3.a) + google-cloud-env (2.1.0) + faraday (>= 1.0, < 3.a) + googleauth (1.9.1) + faraday (>= 1.0, < 3.a) + google-cloud-env (~> 2.1) jwt (>= 1.4, < 3.0) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) - hashdiff (1.0.1) + hashdiff (1.1.0) http-2 (0.11.0) i18n (1.14.1) concurrent-ruby (~> 1.0) - io-console (0.6.0) - irb (1.8.3) + io-console (0.7.1) + irb (1.11.1) rdoc - reline (>= 0.3.8) - json (2.6.3) + reline (>= 0.4.2) + json (2.7.1) jwt (2.7.1) language_server-protocol (3.17.0.3) lint_roller (1.1.0) - loofah (2.21.4) + loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -167,34 +164,39 @@ GEM marcel (1.0.2) mini_mime (1.1.5) mini_portile2 (2.8.5) - minitest (5.20.0) + minitest (5.21.1) multi_json (1.15.0) mysql2 (0.5.5) + net-http (0.4.1) + uri net-http2 (0.18.5) http-2 (~> 0.11) - net-imap (0.4.2) + net-imap (0.4.9.1) date net-protocol net-pop (0.1.2) net-protocol - net-protocol (0.2.1) + net-protocol (0.2.2) timeout - net-smtp (0.4.0) + net-smtp (0.4.0.1) net-protocol - nio4r (2.5.9) - nokogiri (1.15.4) - mini_portile2 (~> 2.8.2) + nio4r (2.7.0) + nokogiri (1.16.0-arm64-darwin) + racc (~> 1.4) + nokogiri (1.16.0-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.16.0-x86_64-linux) racc (~> 1.4) os (1.1.4) - parallel (1.23.0) - parser (3.2.2.4) + parallel (1.24.0) + parser (3.3.0.3) ast (~> 2.4.1) racc pg (1.5.4) - psych (5.1.1.1) + psych (5.1.2) stringio - public_suffix (5.0.3) - racc (1.7.1) + public_suffix (5.0.4) + racc (1.7.3) rack (3.0.8) rack-session (2.0.0) rack (>= 3.0.0) @@ -211,30 +213,29 @@ GEM loofah (~> 2.21) nokogiri (~> 1.14) rainbow (3.1.1) - rake (13.0.6) - rdoc (6.5.0) + rake (13.1.0) + rdoc (6.6.2) psych (>= 4.0.0) - regexp_parser (2.8.2) - reline (0.3.9) + regexp_parser (2.9.0) + reline (0.4.2) io-console (~> 0.5) rexml (3.2.6) - rubocop (1.56.4) - base64 (~> 0.1.1) + rubocop (1.59.0) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.2.2.3) + parser (>= 3.2.2.4) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.28.1, < 2.0) + rubocop-ast (>= 1.30.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.29.0) + rubocop-ast (1.30.0) parser (>= 3.2.1.0) - rubocop-performance (1.19.1) - rubocop (>= 1.7.0, < 2.0) - rubocop-ast (>= 0.4.0) + rubocop-performance (1.20.2) + rubocop (>= 1.48.1, < 2.0) + rubocop-ast (>= 1.30.0, < 2.0) ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) signet (0.18.0) @@ -242,26 +243,28 @@ GEM faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - sqlite3 (1.6.7) + sqlite3 (1.6.9) mini_portile2 (~> 2.8.0) - standard (1.31.2) + standard (1.33.0) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) - rubocop (~> 1.56.4) + rubocop (~> 1.59.0) standard-custom (~> 1.0.0) - standard-performance (~> 1.2) + standard-performance (~> 1.3) standard-custom (1.0.2) lint_roller (~> 1.0) rubocop (~> 1.50) - standard-performance (1.2.1) + standard-performance (1.3.1) lint_roller (~> 1.1) - rubocop-performance (~> 1.19.1) - stringio (3.0.8) + rubocop-performance (~> 1.20.2) + stringio (3.1.0) thor (1.3.0) - timeout (0.4.0) + timeout (0.4.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) + uri (0.13.0) + useragent (0.16.10) webmock (3.19.1) addressable (>= 2.8.0) crack (>= 0.3.2) From 5eaade5120d6c10966e28cbe6b0ce5fdd5cc4abd Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Mon, 15 Jan 2024 08:40:33 -0600 Subject: [PATCH 39/39] Update github configs --- .github/ISSUE_TEMPLATE/config.yml | 11 +++++++++++ .github/pull_request_template.md | 28 ++++++++++++++++++++++++++++ .github/workflows/ci.yml | 9 +++++++++ 3 files changed, 48 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/pull_request_template.md diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..239d6622 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Get Help + url: https://github.com/excid3/noticed/discussions/new?category=help + about: If you can't get something to work the way you expect, open a question in our discussion forums. + - name: Feature Request + url: https://github.com/excid3/noticed/discussions/new?category=ideas + about: 'Suggest any ideas you have using our discussion forums.' + - name: Bug Report + url: https://github.com/excid3/noticed/issues/new?body=%3C%21--%20Please%20provide%20all%20of%20the%20information%20requested%20below.%20We%27re%20a%20small%20team%20and%20without%20all%20of%20this%20information%20it%27s%20not%20possible%20for%20us%20to%20help%20and%20your%20bug%20report%20will%20be%20closed.%20--%3E%0A%0A%2A%2AWhat%20version%20of%20Noticed%20are%20you%20using%3F%2A%2A%0A%0AFor%20example%3A%20v2.0.4%0A%0A%2A%2AWhat%20version%20of%20Rails%20are%20you%20using%3F%2A%2A%0A%0AFor%20example%3A%20v7.1.1%0A%0A%2A%2ADescribe%20your%20issue%2A%2A%0A%0ADescribe%20the%20problem%20you%27re%20seeing%2C%20any%20important%20steps%20to%20reproduce%20and%20what%20behavior%20you%20expect%20instead. + about: If you've already asked for help with a problem and confirmed something is broken with Noticed itself, create a bug report. \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..293a0f4f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,28 @@ +## Pull Request + +**Summary:** + + +**Related Issue:** + + +**Description:** + + +**Testing:** + + +**Screenshots (if applicable):** + + +**Checklist:** + + +- [ ] Code follows the project's coding standards +- [ ] Tests have been added or updated to cover the changes +- [ ] Documentation has been updated (if applicable) +- [ ] All existing tests pass +- [ ] Conforms to the contributing guidelines + +**Additional Notes:** + \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05e81302..148ab106 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,9 @@ jobs: - rails_7 - rails_7_1 - rails_main + exclude: + - ruby: '3.0' + gemfile: 'rails_main' env: BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile @@ -56,6 +59,9 @@ jobs: - rails_7 - rails_7_1 - rails_main + exclude: + - ruby: '3.0' + gemfile: 'rails_main' env: BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile @@ -102,6 +108,9 @@ jobs: - rails_7 - rails_7_1 - rails_main + exclude: + - ruby: '3.0' + gemfile: 'rails_main' env: BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile