Adding unified streamable notifications

This commit is contained in:
Eugen Rochko
2016-11-20 00:33:02 +01:00
parent 3838e6836d
commit da2ef4d676
20 changed files with 205 additions and 44 deletions

View File

@ -10,7 +10,7 @@ module ApplicationCable
return [nil, message] if message['type'] == 'delete'
status = Status.find_by(id: message['id'])
message['message'] = FeedManager.instance.inline_render(current_user.account, status)
message['message'] = FeedManager.instance.inline_render(current_user.account, 'api/v1/statuses/show', status)
[status, message]
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class Api::V1::NotificationsController < ApiController
before_action -> { doorkeeper_authorize! :read }
before_action :require_user!
respond_to :json
def index
@notifications = Notification.where(account: current_account).with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id])
next_path = api_v1_notifications_url(max_id: @notifications.last.id) if @notifications.size == 20
prev_path = api_v1_notifications_url(since_id: @notifications.first.id) unless @notifications.empty?
set_pagination_headers(next_path, prev_path)
end
end

View File

@ -26,7 +26,7 @@ class FeedManager
def push(timeline_type, account, status)
redis.zadd(key(timeline_type, account.id), status.id, status.reblog? ? status.reblog_of_id : status.id)
trim(timeline_type, account.id)
broadcast(account.id, type: 'update', timeline: timeline_type, message: inline_render(account, status))
broadcast(account.id, type: 'update', timeline: timeline_type, message: inline_render(account, 'api/v1/statuses/show', status))
end
def broadcast(timeline_id, options = {})
@ -39,7 +39,7 @@ class FeedManager
redis.zremrangebyscore(key(type, account_id), '-inf', "(#{last.last}")
end
def inline_render(target_account, status)
def inline_render(target_account, template, object)
rabl_scope = Class.new do
include RoutingHelper
@ -56,7 +56,7 @@ class FeedManager
end
end
Rabl::Renderer.new('api/v1/statuses/show', status, view_path: 'app/views', format: :json, scope: rabl_scope.new(target_account)).render
Rabl::Renderer.new(template, object, view_path: 'app/views', format: :json, scope: rabl_scope.new(target_account)).render
end
private

View File

@ -3,46 +3,38 @@
class NotificationMailer < ApplicationMailer
helper StreamEntriesHelper
def mention(mentioned_account, status)
@me = mentioned_account
@status = status
return unless @me.user.settings(:notification_emails).mention
def mention(recipient, notification)
@me = recipient
@status = notification.target_status
I18n.with_locale(@me.user.locale || I18n.default_locale) do
mail to: @me.user.email, subject: I18n.t('notification_mailer.mention.subject', name: @status.account.acct)
end
end
def follow(followed_account, follower)
@me = followed_account
@account = follower
return unless @me.user.settings(:notification_emails).follow
def follow(recipient, notification)
@me = recipient
@account = notification.from_account
I18n.with_locale(@me.user.locale || I18n.default_locale) do
mail to: @me.user.email, subject: I18n.t('notification_mailer.follow.subject', name: @account.acct)
end
end
def favourite(target_status, from_account)
@me = target_status.account
@account = from_account
@status = target_status
return unless @me.user.settings(:notification_emails).favourite
def favourite(recipient, notification)
@me = recipient
@account = notification.from_account
@status = notification.target_status
I18n.with_locale(@me.user.locale || I18n.default_locale) do
mail to: @me.user.email, subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct)
end
end
def reblog(target_status, from_account)
@me = target_status.account
@account = from_account
@status = target_status
return unless @me.user.settings(:notification_emails).reblog
def reblog(recipient, notification)
@me = recipient
@account = notification.from_account
@status = notification.target_status
I18n.with_locale(@me.user.locale || I18n.default_locale) do
mail to: @me.user.email, subject: I18n.t('notification_mailer.reblog.subject', name: @account.acct)

View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
class Notification < ApplicationRecord
include Paginable
belongs_to :account
belongs_to :activity, polymorphic: true
belongs_to :mention, foreign_type: 'Mention', foreign_key: 'activity_id'
belongs_to :status, foreign_type: 'Status', foreign_key: 'activity_id'
belongs_to :follow, foreign_type: 'Follow', foreign_key: 'activity_id'
belongs_to :favourite, foreign_type: 'Favourite', foreign_key: 'activity_id'
STATUS_INCLUDES = [:account, :media_attachments, mentions: :account, reblog: [:account, mentions: :account]].freeze
scope :with_includes, -> { includes(status: STATUS_INCLUDES, mention: [status: STATUS_INCLUDES], favourite: [:account, status: STATUS_INCLUDES], follow: :account) }
def type
case activity_type
when 'Status'
:reblog
else
activity_type.downcase.to_sym
end
end
def from_account
case type
when :mention
activity.status.account
when :follow, :favourite, :reblog
activity.account
end
end
def target_status
case type
when :reblog
activity.reblog
when :favourite, :mention
activity.status
end
end
end

View File

@ -10,7 +10,7 @@ class FavouriteService < BaseService
HubPingWorker.perform_async(account.id)
if status.local?
NotificationMailer.favourite(status, account).deliver_later unless status.account.blocking?(account)
NotifyService.new.call(status.account, favourite)
else
NotificationWorker.perform_async(favourite.stream_entry.id, status.account_id)
end

View File

@ -12,7 +12,7 @@ class FollowService < BaseService
follow = source_account.follow!(target_account)
if target_account.local?
NotificationMailer.follow(target_account, source_account).deliver_later unless target_account.blocking?(source_account)
NotifyService.new.call(target_account, follow)
else
subscribe_service.call(target_account)
NotificationWorker.perform_async(follow.stream_entry.id, target_account.id)

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
class NotifyService < BaseService
def call(recipient, activity)
@recipient = recipient
@activity = activity
@notification = Notification.new(account: @recipient, activity: @activity)
return if blocked?
create_notification
send_email if email_enabled?
end
private
def blocked?
blocked = false
blocked ||= @recipient.id == @notification.from_account.id
blocked ||= @recipient.blocking?(@notification.from_account)
blocked
end
def create_notification
@notification.save!
FeedManager.instance.broadcast(@recipient.id, type: 'notification', message: FeedManager.instance.inline_render(@recipient, 'api/v1/notifications/show', @notification))
end
def send_email
NotificationMailer.send(@notification.type, @recipient, @notification).deliver_later
end
def email_enabled?
@recipient.user.settings(:notification_emails).send(@notification.type)
end
end

View File

@ -150,12 +150,10 @@ class ProcessFeedService < BaseService
next if mentioned_account.nil? || processed_account_ids.include?(mentioned_account.id)
if mentioned_account.local?
# Send notifications
NotificationMailer.mention(mentioned_account, parent).deliver_later unless mentioned_account.blocking?(parent.account)
end
mention = mentioned_account.mentions.where(status: parent).first_or_create(status: parent)
mentioned_account.mentions.where(status: parent).first_or_create(status: parent)
# Notify local user
NotifyService.new.call(mentioned_account, mention) if mentioned_account.local?
# So we can skip duplicate mentions
processed_account_ids << mentioned_account.id

View File

@ -65,8 +65,8 @@ class ProcessInteractionService < BaseService
end
def follow!(account, target_account)
account.follow!(target_account)
NotificationMailer.follow(target_account, account).deliver_later unless target_account.blocking?(account)
follow = account.follow!(target_account)
NotifyService.new.call(target_account, follow)
end
def unfollow!(account, target_account)
@ -83,8 +83,8 @@ class ProcessInteractionService < BaseService
def favourite!(xml, from_account)
current_status = status(xml)
current_status.favourites.where(account: from_account).first_or_create!(account: from_account)
NotificationMailer.favourite(current_status, from_account).deliver_later unless current_status.account.blocking?(from_account)
favourite = current_status.favourites.where(account: from_account).first_or_create!(account: from_account)
NotifyService.new.call(current_status.account, favourite)
end
def add_post!(body, account)

View File

@ -29,7 +29,7 @@ class ProcessMentionsService < BaseService
mentioned_account = mention.account
if mentioned_account.local?
NotificationMailer.mention(mentioned_account, status).deliver_later unless mentioned_account.blocking?(status.account)
NotifyService.new.call(mentioned_account, mention)
else
NotificationWorker.perform_async(status.stream_entry.id, mentioned_account.id)
end

View File

@ -11,7 +11,7 @@ class ReblogService < BaseService
HubPingWorker.perform_async(account.id)
if reblogged_status.local?
NotificationMailer.reblog(reblogged_status, account).deliver_later unless reblogged_status.account.blocking?(account)
NotifyService.new.call(reblogged_status.account, reblog)
else
NotificationWorker.perform_async(reblog.stream_entry.id, reblogged_status.account_id)
end

View File

@ -0,0 +1,2 @@
collection @notifications
extends 'api/v1/notifications/show'

View File

@ -0,0 +1,11 @@
object @notification
attributes :id, :type
child from_account: :account do
extends 'api/v1/accounts/show'
end
node(:status, if: lambda { |n| [:favourite, :reblog, :mention].include?(n.type) }) do |n|
partial 'api/v1/statuses/show', object: n.target_status
end