diff --git a/Gemfile b/Gemfile index fb9a11ba1..4472f3306 100644 --- a/Gemfile +++ b/Gemfile @@ -50,6 +50,7 @@ gem 'pg_search' gem 'simple-navigation' gem 'statsd-instrument' gem 'ruby-oembed', require: 'oembed' +gem 'fcm' gem 'react-rails' gem 'browserify-rails' diff --git a/Gemfile.lock b/Gemfile.lock index 5361b2a05..bfed29072 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -127,6 +127,9 @@ GEM execjs (2.7.0) fabrication (2.15.2) fast_blank (1.0.0) + fcm (0.0.2) + httparty + json font-awesome-rails (4.6.3.1) railties (>= 3.2, < 5.1) fuubar (2.1.1) @@ -160,6 +163,8 @@ GEM domain_name (~> 0.5) http-form_data (1.0.1) http_parser.rb (0.6.0) + httparty (0.14.0) + multi_xml (>= 0.5.2) httplog (0.3.2) colorize i18n (0.7.0) @@ -207,6 +212,7 @@ GEM mini_portile2 (2.1.0) minitest (5.10.1) multi_json (1.12.1) + multi_xml (0.6.0) nio4r (1.2.1) nokogiri (1.7.0.1) mini_portile2 (~> 2.1.0) @@ -434,6 +440,7 @@ DEPENDENCIES dotenv-rails fabrication fast_blank + fcm font-awesome-rails fuubar goldfinger diff --git a/app/controllers/api/v1/devices_controller.rb b/app/controllers/api/v1/devices_controller.rb new file mode 100644 index 000000000..c565e972b --- /dev/null +++ b/app/controllers/api/v1/devices_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class Api::V1::DevicesController < ApiController + before_action -> { doorkeeper_authorize! :read } + before_action :require_user! + + respond_to :json + + def register + Device.where(account: current_account, registration_id: params[:registration_id]).first_or_create!(account: current_account, registration_id: params[:registration_id]) + render_empty + end + + def unregister + Device.where(account: current_account, registration_id: params[:registration_id]).delete_all + render_empty + end +end diff --git a/app/models/device.rb b/app/models/device.rb new file mode 100644 index 000000000..2782a7f38 --- /dev/null +++ b/app/models/device.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Device < ApplicationRecord + belongs_to :account + + validates :account, :registration_id, presence: true +end diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index 1ec36637c..d1504cadf 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -10,6 +10,7 @@ class NotifyService < BaseService create_notification send_email if email_enabled? + send_push_notification rescue ActiveRecord::RecordInvalid return end @@ -57,6 +58,10 @@ class NotifyService < BaseService NotificationMailer.send(@notification.type, @recipient, @notification).deliver_later end + def send_push_notification + PushNotificationWorker.perform_async(@notification.id) + end + def email_enabled? @recipient.user.settings.notification_emails[@notification.type] end diff --git a/app/services/send_push_notification_service.rb b/app/services/send_push_notification_service.rb new file mode 100644 index 000000000..802614fce --- /dev/null +++ b/app/services/send_push_notification_service.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class SendPushNotificationService < BaseService + def call(notification) + return if ENV['FCM_API_KEY'].blank? + + devices = Device.where(account: notification.account).pluck(:registration_id) + fcm = FCM.new(ENV['FCM_API_KEY']) + + response = fcm.send(devices, data: { notification_id: notification.id }, collapse_key: :notifications, priority: :high) + handle_response(response) + end + + private + + def handle_response(response) + update_canonical_ids(response[:canonical_ids]) if response[:canonical_ids] + remove_bad_ids(response[:not_registered_ids]) if response[:not_registered_ids] + end + + def update_canonical_ids(ids) + ids.each { |pair| Device.find_by(registration_id: pair[:old]).update(registration_id: pair[:new]) } + end + + def remove_bad_ids(bad_ids) + Device.where(registration_id: bad_ids).delete_all + end +end diff --git a/app/workers/push_notification_worker.rb b/app/workers/push_notification_worker.rb new file mode 100644 index 000000000..a61d0e349 --- /dev/null +++ b/app/workers/push_notification_worker.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class PushNotificationWorker + include Sidekiq::Worker + + def perform(notification_id) + SendPushNotificationService.new.call(Notification.find(notification_id)) + rescue ActiveRecord::RecordNotFound + true + end +end diff --git a/config/routes.rb b/config/routes.rb index 87f35770a..bce345f2e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -118,6 +118,9 @@ Rails.application.routes.draw do resources :blocks, only: [:index] resources :favourites, only: [:index] + post '/devices/register', to: 'devices#register', as: :register_device + post '/devices/unregister', to: 'devices#unregister', as: :unregister_device + resources :follow_requests, only: [:index] do member do post :authorize diff --git a/db/migrate/20170129000348_create_devices.rb b/db/migrate/20170129000348_create_devices.rb new file mode 100644 index 000000000..bf8f5fc6e --- /dev/null +++ b/db/migrate/20170129000348_create_devices.rb @@ -0,0 +1,13 @@ +class CreateDevices < ActiveRecord::Migration[5.0] + def change + create_table :devices do |t| + t.integer :account_id, null: false + t.string :registration_id, null: false, default: '' + + t.timestamps + end + + add_index :devices, :registration_id + add_index :devices, :account_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 7a7fea86b..448a7b861 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170127165745) do +ActiveRecord::Schema.define(version: 20170129000348) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -54,6 +54,15 @@ ActiveRecord::Schema.define(version: 20170127165745) do t.index ["account_id", "target_account_id"], name: "index_blocks_on_account_id_and_target_account_id", unique: true, using: :btree end + create_table "devices", force: :cascade do |t| + t.integer "account_id", null: false + t.string "registration_id", default: "", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["account_id"], name: "index_devices_on_account_id", using: :btree + t.index ["registration_id"], name: "index_devices_on_registration_id", using: :btree + end + create_table "domain_blocks", force: :cascade do |t| t.string "domain", default: "", null: false t.datetime "created_at", null: false diff --git a/spec/fabricators/device_fabricator.rb b/spec/fabricators/device_fabricator.rb new file mode 100644 index 000000000..02b24e8b3 --- /dev/null +++ b/spec/fabricators/device_fabricator.rb @@ -0,0 +1,3 @@ +Fabricator(:device) do + registration_id "12345678" +end diff --git a/spec/fabricators/domain_block_fabricator.rb b/spec/fabricators/domain_block_fabricator.rb index 540ddcacd..563a0f65b 100644 --- a/spec/fabricators/domain_block_fabricator.rb +++ b/spec/fabricators/domain_block_fabricator.rb @@ -1,3 +1,3 @@ Fabricator(:domain_block) do - domain "MyString" + domain "example.com" end diff --git a/spec/models/device_spec.rb b/spec/models/device_spec.rb new file mode 100644 index 000000000..f56fbf978 --- /dev/null +++ b/spec/models/device_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Device, type: :model do + +end