Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rails & firebaseで作る通知機能とお知らせ設計手法

chikarav2
October 09, 2019

Rails & firebaseで作る通知機能とお知らせ設計手法

Twitterアカウント:https://twitter.com/matsudachikara2
LTテーマ: Rails & firebaseで作る通知機能とお知らせ設計手法
LT概要:firebaseでの通知実装は、情報が徐々に多くなってきましたが、今回は実際にサービスでのお知らせと通知の設計から、具体的なrailsでの実装方法までを含めてお話ししたいと思います。

chikarav2

October 09, 2019
Tweet

Other Decks in Technology

Transcript

  1. Issue 59% ར༻ऀͷงғؾ 56% ଞͷϝϯόʔͱͷަྲྀ 55% ڧ͍ίϛϡχςΟ 51% Ոͱͷڑ཭ 38%

    ΦϑΟεΠϯϑϥ ίϫʔΩϯάεϖʔεʹର͠ɺϢʔβʔ͕࠷΋ظ଴͢Δͷ͸ɺ ʮίϛϡχςΟʯʹؔ͢Δ͜ͱͰ͢ɻ ίϫʔΩϯάεϖʔεʹظ଴͢Δ͜ͱ ࢀߟɿTJNQMFUFYUJOH IUUQTTJNQMFUFYUJOHDPNDPXPSLJOHUSFOET
  2. ઃܭ ؔ࿈Ϟσϧ Users Notifications # id :bigint(8) not null, primary

    key # activity_type :string(50) not null # body :text(65535) not null # deleted_at :datetime not null # dst_type :string(50) not null # read :boolean default(FALSE), not null # title :string(100) default(""), not null # created_at :datetime not null # updated_at :datetime not null # activity_id :bigint(8) not null # notifier_id :bigint(8) not null # receiver_id :bigint(8) not null NotificationMappings # id :bigint(8) not null, primary key # activity_type :string(50) not null # body :text(65535) not null # deleted_at :datetime # dst_type :string(50) not null # receiver_type :string(50) not null # title :string(100) default(""), not null # created_at :datetime not null # updated_at :datetime not null Activities # id :bigint(8) not null, primary key # activitiable_id :bigint(8) not null # activitiable_type :string(45) not null # activity_type :string(50) not null # deleted_at :datetime not null # created_at :datetime not null # updated_at :datetime not null # user_id :bigint(8) not null RegistrationTokens # id :bigint(8) not null, primary key # token :string(255) not null # created_at :datetime not null # updated_at :datetime not null # user_id :bigint(8) not null
  3. FCM API POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send Header: Authorization: Bearer <access_token> BODY: {

    "message": { "topic": "news", "notification": { "title": "Breaking News", "body": "New news story available." }, "data": { "story_id": "story_12345" }, "android": { "notification": { "click_action": "TOP_STORY_ACTIVITY", "body": "Check out the Top Story" } }, "apns": { "payload": { "aps": { "category" : "NEW_MESSAGE_CATEGORY" } } } } }
  4. ͍͍ͶAPI Controller module Api module V1 module Tickets class LikesController

    < Api::V1::ApplicationController # POST /api/v1/stations/:station_id/tickets/:ticket_id/like def create station = Station.find(params[:station_id]) ticket = station.tickets.find(params[:ticket_id]) LikeContentService.execute(user: current_user, content: ticket, activity_type: 'tickets/likes.create', dst_type: 'tickets.show') head :created end end end end end
  5. LikeContentService class LikeContentService def self.execute(user:, content:, activity_type:, dst_type:) new(user: user,

    content: content, activity_type: activity_type, dst_type: dst_type).run end def initialize(user:, content:, activity_type:, dst_type:) ~লུ~ end def run like = @user.likes.find_or_initialize_by(likable: @content) return if like.persisted? like.save! activity = Activities::CreateService.execute( user: @user, activitiable: like, activity_type: @activity_type ) return unless activity Notification.create( notifier: @user, receiver: @content.user, activity: activity, activity_type: activity.activity_type, title: @content.title, body: "#{@user.screen_name}͞Μ ͕ #{@content.title} ʹ͍͍Ͷ͠·ͨ͠", dst_type: @dst_type, read: false ) end end
  6. PushNotifications::SendService module PushNotifications class SendService < ::PushNotifications::BaseService def execute #

    ௨஌ઌͷొ࿥τʔΫϯऔಘ registration_tokens = @notification.receiver.registration_tokens return if registration_tokens.blank? registration_tokens.each do |registration_token| Notifications::PushNotificationWorker .perform(registration_token, message(registration_token.token).to_json) end rescue StandardError => e Rails.logger.error(e) false end
  7. PushNotifications::SendService def message(registration_token) hash = {} hash[:android] = { priority:

    10, data: { image_url: image_url, body: @notification.body } } hash[:apns] = { headers: { 'apns-topic': bundle_id, 'apns-priority': '10' }, payload: { aps: { 'badge': 1, 'mutable-content': 1, 'alert': { 'body': @notification.body } }, 'image_url': image_url } } hash[:data] = data hash[:token] = registration_token { message: hash } end
  8. PushNotifications::SendService def data # rubocop:disable Metrics/CyclomaticComplexity # ϦϯΫઌ΍ɺͦΕʹඞཁͳidΛؚΉhashΛ࡞੒ hash =

    {} station_id = @activity&.activitiable&.station&.id if @activity&.activity_type&.split('.')&.first ! = ‘messages' case @activity&.activitiable&.class&.name when 'StationUser' hash['user_id'] = @activity&.activitiable&.user_id&.to_s when 'Message' hash['chat_id'] = @activity&.activitiable&.chat&.id&.to_s end activitiable_id_key = @activity&.activitiable&.class&.name&.underscore &.+ '_id' activitiable_id = @activity&.activitiable&.id hash[:dst_type] = @notification&.dst_type if @notification&.dst_type.present? hash[activitiable_id_key] = activitiable_id&.to_s if activitiable_id_key.present? hash[:station_id] = station_id&.to_s if station_id.present? hash end
  9. Notifications::PushNotificationWorker module Notifications class PushNotificationWorker include Sidekiq::Worker sidekiq_options queue: :push_notification

    def self.perform(registration_token, json) scope = 'https://www.googleapis.com/auth/firebase.messaging' authorizer = Google::Auth::ServiceAccountCredentials.make_creds( json_key_io: File.open(Rails.root.join('firebase.json')), scope: scope ) authorization = "Bearer #{authorizer.fetch_access_token!['access_token']}" conn = Faraday.new(url: 'https://fcm.googleapis.com/v1/projects/station-9792c/ messages:send') do |faraday| faraday.request :url_encoded faraday.response :logger faraday.adapter Faraday.default_adapter end response = conn.post do |req| req.headers['Content-Type'] = 'application/json' req.headers['Authorization'] = authorization req.body = json end registration_token.destroy unless response.success? end end end