enviar notificaciones!

This commit is contained in:
fauno 2019-08-03 18:44:28 -03:00
parent 532054de11
commit b1de74aafe
No known key found for this signature in database
GPG key ID: 456032D717A4CD9C
14 changed files with 149 additions and 144 deletions

View file

@ -1,22 +1,6 @@
inherit_from: .rubocop_todo.yml #inherit_from: .rubocop_todo.yml
# No siempre queremos metadatear los modelos
Rails/CreateTableWithTimestamps:
Enabled: false
# Queremos poder hacer comentarios en castellano, esto quiere que # Queremos poder hacer comentarios en castellano, esto quiere que
# saquemos las tildes # saquemos las tildes
Style/AsciiComments: Style/AsciiComments:
Enabled: false Enabled: false
# El estilo de módulo y clases anidados agrega mucha indentación para
# nuestros gusto
Style/ClassAndModuleChildren:
Enabled: false
Metrics/LineLength:
Exclude:
- 'db/schema.rb'
Metrics/BlockLength:
Exclude:
- 'db/schema.rb'
- 'config/routes.rb'
- 'db/seeds.rb'
- 'config/environments/production.rb'

View file

@ -1,80 +0,0 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2019-04-05 17:51:53 -0300 using RuboCop version 0.66.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: empty_lines, no_empty_lines
Layout/EmptyLinesAroundBlockBody:
Exclude:
- 'db/schema.rb'
# Offense count: 1
Metrics/AbcSize:
Max: 18
# Offense count: 1
Metrics/CyclomaticComplexity:
Max: 9
# Offense count: 2
# Configuration parameters: CountComments, ExcludedMethods.
Metrics/MethodLength:
Max: 13
# Offense count: 1
Metrics/PerceivedComplexity:
Max: 9
# Offense count: 1
Style/Documentation:
Exclude:
- 'spec/**/*'
- 'test/**/*'
- 'config/application.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: when_needed, always, never
Style/FrozenStringLiteralComment:
Exclude:
- 'db/schema.rb'
# Offense count: 2
# Cop supports --auto-correct.
Style/IfUnlessModifier:
Exclude:
- 'bin/bundle'
# Offense count: 2
Style/MixinUsage:
Exclude:
- 'bin/setup'
- 'bin/update'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: MinDigits, Strict.
Style/NumericLiterals:
Exclude:
- 'db/schema.rb'
# Offense count: 4
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
# SupportedStyles: single_quotes, double_quotes
Style/StringLiterals:
Exclude:
- 'db/schema.rb'
# Offense count: 12
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Metrics/LineLength:
Max: 190

View file

@ -40,7 +40,7 @@ group :development do
gem 'listen', '>= 3.0.5', '< 3.2' gem 'listen', '>= 3.0.5', '< 3.2'
# Spring speeds up development by keeping your application running in # Spring speeds up development by keeping your application running in
# the background. Read more: https://github.com/rails/spring # the background. Read more: https://github.com/rails/spring
gem 'rubocop' gem 'rubocop-rails'
gem 'spring' gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0' gem 'spring-watcher-listen', '~> 2.0.0'
gem 'yard' gem 'yard'

View file

@ -64,7 +64,7 @@ GEM
hkdf (0.3.0) hkdf (0.3.0)
i18n (1.6.0) i18n (1.6.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
jaro_winkler (1.5.2) jaro_winkler (1.5.3)
jbuilder (2.8.0) jbuilder (2.8.0)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
multi_json (>= 1.2) multi_json (>= 1.2)
@ -90,13 +90,12 @@ GEM
nio4r (2.3.1) nio4r (2.3.1)
nokogiri (1.10.3) nokogiri (1.10.3)
mini_portile2 (~> 2.4.0) mini_portile2 (~> 2.4.0)
parallel (1.16.2) parallel (1.17.0)
parser (2.6.2.0) parser (2.6.3.0)
ast (~> 2.4.0) ast (~> 2.4.0)
pry (0.12.2) pry (0.12.2)
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.9.0) method_source (~> 0.9.0)
psych (3.1.0)
public_suffix (4.0.0) public_suffix (4.0.0)
puma (3.12.1) puma (3.12.1)
rack (2.0.7) rack (2.0.7)
@ -132,15 +131,17 @@ GEM
rb-fsevent (0.10.3) rb-fsevent (0.10.3)
rb-inotify (0.10.0) rb-inotify (0.10.0)
ffi (~> 1.0) ffi (~> 1.0)
rubocop (0.66.0) rubocop (0.72.0)
jaro_winkler (~> 1.5.1) jaro_winkler (~> 1.5.1)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 2.5, != 2.5.1.1) parser (>= 2.6)
psych (>= 3.1.0)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 1.6) unicode-display_width (>= 1.4.0, < 1.7)
ruby-progressbar (1.10.0) rubocop-rails (2.2.0)
rack (>= 1.1)
rubocop (>= 0.72.0)
ruby-progressbar (1.10.1)
ruby_dep (1.5.0) ruby_dep (1.5.0)
spring (2.0.2) spring (2.0.2)
activesupport (>= 4.2) activesupport (>= 4.2)
@ -159,7 +160,7 @@ GEM
thread_safe (0.3.6) thread_safe (0.3.6)
tzinfo (1.2.5) tzinfo (1.2.5)
thread_safe (~> 0.1) thread_safe (~> 0.1)
unicode-display_width (1.5.0) unicode-display_width (1.6.0)
validate_url (1.0.8) validate_url (1.0.8)
activemodel (>= 3.0.0) activemodel (>= 3.0.0)
public_suffix public_suffix
@ -185,7 +186,7 @@ DEPENDENCIES
puma (~> 3.11) puma (~> 3.11)
rack-cors rack-cors
rails (~> 5.2.3) rails (~> 5.2.3)
rubocop rubocop-rails
spring spring
spring-watcher-listen (~> 2.0.0) spring-watcher-listen (~> 2.0.0)
sqlite3 sqlite3

View file

@ -47,6 +47,9 @@ class BarcasController < ApplicationController
@barca.piratas << current_pirata @barca.piratas << current_pirata
if @barca.save if @barca.save
piratas = Pirata.todas_menos(current_pirata).pluck(:id)
notify(subject: 'barcas.create.subject', piratas: piratas)
render status: :created render status: :created
else else
render json: { errors: @barca.errors.messages }, render json: { errors: @barca.errors.messages },
@ -129,4 +132,17 @@ class BarcasController < ApplicationController
def find_barca def find_barca
@barca = Barca.find(params[:barca_id]) @barca = Barca.find(params[:barca_id])
end end
def notify(piratas:, subject:)
# Notificar a todas las piratas que hay una nueva barca para que
# se puedan sumar
payload = WebpushPayload.new(subject: I18n.t(subject,
barca: @barca.nombre),
message: @barca.descripcion,
endpoint: barca_path(@barca))
WebpushJob.perform_later(piratas: piratas,
ttl: 7.days.to_i,
payload: payload.to_json)
end
end end

View file

@ -66,8 +66,7 @@ class ConsensosController < ApplicationController
begin begin
@consenso = @barca.consensos.find(params[:id]) @consenso = @barca.consensos.find(params[:id])
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render json: {}, status: :not_found render(json: {}, status: :not_found) && return
return
end end
if @consenso.posiciones.empty? && @consenso.destroy if @consenso.posiciones.empty? && @consenso.destroy

View file

@ -15,7 +15,8 @@ class PosicionesController < ApplicationController
# Crea una posición dentro de un consenso # Crea una posición dentro de un consenso
# #
# @param :consenso_id [Integer] El ID del consenso # @param :consenso_id [Integer] El ID del consenso
# @param :posicion [Hash] { posicion: { estado: @string, comentario: @string } } # @param :posicion [Hash] { posicion: { estado: @string,
# comentario: @string } }
# @return [Hash] { id: @int, estado: @string, comentario: @string, # @return [Hash] { id: @int, estado: @string, comentario: @string,
# pirata: @pirata } # pirata: @pirata }
def create def create

37
app/jobs/webpush_job.rb Normal file
View file

@ -0,0 +1,37 @@
# frozen_string_literal: true
# Envía las notificaciones
class WebpushJob < ApplicationJob
queue_as :default
# @param :piratas [String,Array] 'all' para traer todas, array de IDs
# para filtrar
# @param :payload [WebpushPayload] La notificación a enviar
# @param :urgency [Symbol] La urgencia
# @param :ttl [Any] Duración convertible a segundos
def perform(piratas:, payload:, urgency: :normal, ttl: 1.day.to_i)
# Encontrar todas las piratas
piratas = find_piratas(piratas)
payload = WebpushPayload.from_json payload
# No cargar todas las piratas en memoria!
piratas.find_each do |pirata|
# Enviarles a todas sus suscripciones
pirata.webpush_subscriptions.find_each do |subscription|
subscription.payload_send(payload: payload,
urgency: urgency,
ttl: ttl)
end
end
end
private
def find_piratas(ids)
if ids == 'all'
Pirata.all
else
Pirata.where(id: ids)
end
end
end

View file

@ -18,4 +18,6 @@ class Pirata < ApplicationRecord
# Una por correo # Una por correo
validates :email, presence: true, uniqueness: true validates :email, presence: true, uniqueness: true
validates :nick, presence: true, uniqueness: true validates :nick, presence: true, uniqueness: true
scope :todas_menos, ->(pirata) { where.not(id: pirata) }
end end

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
# El payload que se envía en la notificación
#
# @param :subject [String] El título de la notificación
# @param :message [String] El cuerpo de la notificación
# @param :endpoint [String] La URL a visitar al abrir la notificación
WebpushPayload = Struct.new(:subject, :message, :endpoint,
keyword_init: true) do
# Convertir el payload a JSON para poder enviarlo
#
# @return String
def to_json(opts = nil)
to_h.to_json(opts)
end
def self.from_json(string)
new(JSON.parse(string))
end
end

View file

@ -6,4 +6,30 @@ class WebpushSubscription < ApplicationRecord
validates :endpoint, url: true validates :endpoint, url: true
validates_uniqueness_of :auth, scope: %i[p256dh endpoint], validates_uniqueness_of :auth, scope: %i[p256dh endpoint],
case_sensitive: false case_sensitive: false
URGENCY = %w[very-low low normal high].freeze
# Envía la notificación
def payload_send(payload:, urgency: :normal, ttl: 1.day)
Webpush.payload_send(
vapid: {
subject: 'mailto:lumi@partidopirata.com.ar',
public_key: Rails.application.credentials.vapid.public_key,
private_key: Rails.application.credentials.vapid.private_key
},
endpoint: endpoint, auth: auth, p256dh: p256dh,
ttl: ttl.to_i, urgency: normalize_urgency(urgency),
message: payload.to_json
)
end
private
def normalize_urgency(urgency)
if URGENCY.includes? urgency.to_s
urgency.to_s
else
'normal'
end
end
end end

View file

@ -1,33 +1,4 @@
# 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'
#
# In views, this is aliased to just `t`:
#
# <%= t('hello') %>
#
# To use a different locale, set it with `I18n.locale`:
#
# I18n.locale = :es
#
# 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:
#
# true, false, on, off, yes, no
#
# Instead, surround them with single quotes.
#
# en:
# 'true': 'foo'
#
# To learn more, please read the Rails Internationalization guide
# available at http://guides.rubyonrails.org/i18n.html.
en: en:
hello: "Hello world" barcas:
create:
subject: '%{barca} has been created!'

4
config/locales/es.yml Normal file
View file

@ -0,0 +1,4 @@
es:
barcas:
create:
subject: '%{barca} creada, ¡al abordaje!'

View file

@ -0,0 +1,24 @@
# frozen_string_literal: true
require 'test_helper'
class WebpushJobTest < ActiveJob::TestCase
test 'se pueden enviar a todas las piratas' do
payload = WebpushPayload.new(subject: 'test', message: 'test', endpoint: 'https://partidopirata.com.ar')
assert_nothing_raised do
WebpushJob.perform_now(piratas: 'all', payload: payload.to_json,
urgency: :low, ttl: 1.day.to_i)
end
end
test 'se pueden enviar a algunas piratas' do
pirata = create :pirata
payload = WebpushPayload.new(subject: 'test', message: 'test', endpoint: 'https://partidopirata.com.ar')
assert_nothing_raised do
WebpushJob.perform_now(piratas: [pirata.id], payload: payload.to_json,
urgency: :low, ttl: 1.day.to_i)
end
end
end