Cleaning up format of broadcast real-time messages, removing
redis-backed "mentions" timeline as redundant (given notifications)
This commit is contained in:
		| @@ -23,7 +23,6 @@ import GettingStarted from '../features/getting_started'; | ||||
| import PublicTimeline from '../features/public_timeline'; | ||||
| import AccountTimeline from '../features/account_timeline'; | ||||
| import HomeTimeline from '../features/home_timeline'; | ||||
| import MentionsTimeline from '../features/mentions_timeline'; | ||||
| import Compose from '../features/compose'; | ||||
| import Followers from '../features/followers'; | ||||
| import Following from '../features/following'; | ||||
| @@ -68,15 +67,15 @@ const Mastodon = React.createClass({ | ||||
|       this.subscription = App.cable.subscriptions.create('TimelineChannel', { | ||||
|  | ||||
|         received (data) { | ||||
|           switch(data.type) { | ||||
|           switch(data.event) { | ||||
|           case 'update': | ||||
|             store.dispatch(updateTimeline(data.timeline, JSON.parse(data.message))); | ||||
|             store.dispatch(updateTimeline('home', JSON.parse(data.payload))); | ||||
|             break; | ||||
|           case 'delete': | ||||
|             store.dispatch(deleteFromTimelines(data.id)); | ||||
|             store.dispatch(deleteFromTimelines(data.payload)); | ||||
|             break; | ||||
|           case 'notification': | ||||
|             store.dispatch(updateNotifications(JSON.parse(data.message), getMessagesForLocale(locale), locale)); | ||||
|             store.dispatch(updateNotifications(JSON.parse(data.payload), getMessagesForLocale(locale), locale)); | ||||
|             break; | ||||
|           } | ||||
|         } | ||||
| @@ -108,7 +107,6 @@ const Mastodon = React.createClass({ | ||||
|  | ||||
|               <Route path='getting-started' component={GettingStarted} /> | ||||
|               <Route path='timelines/home' component={HomeTimeline} /> | ||||
|               <Route path='timelines/mentions' component={MentionsTimeline} /> | ||||
|               <Route path='timelines/public' component={PublicTimeline} /> | ||||
|               <Route path='timelines/tag/:id' component={HashtagTimeline} /> | ||||
|  | ||||
|   | ||||
| @@ -26,11 +26,13 @@ const HashtagTimeline = React.createClass({ | ||||
|       }, { | ||||
|  | ||||
|         received (data) { | ||||
|           switch(data.type) { | ||||
|           switch(data.event) { | ||||
|           case 'update': | ||||
|             return dispatch(updateTimeline('tag', JSON.parse(data.message))); | ||||
|             dispatch(updateTimeline('tag', JSON.parse(data.payload))); | ||||
|             break; | ||||
|           case 'delete': | ||||
|             return dispatch(deleteFromTimelines(data.id)); | ||||
|             dispatch(deleteFromTimelines(data.payload)); | ||||
|             break; | ||||
|           } | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -1,36 +0,0 @@ | ||||
| import { connect }         from 'react-redux'; | ||||
| import PureRenderMixin from 'react-addons-pure-render-mixin'; | ||||
| import StatusListContainer from '../ui/containers/status_list_container'; | ||||
| import Column from '../ui/components/column'; | ||||
| import { refreshTimeline } from '../../actions/timelines'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
|  | ||||
| const messages = defineMessages({ | ||||
|   title: { id: 'column.mentions', defaultMessage: 'Mentions' } | ||||
| }); | ||||
|  | ||||
| const MentionsTimeline = React.createClass({ | ||||
|  | ||||
|   propTypes: { | ||||
|     dispatch: React.PropTypes.func.isRequired | ||||
|   }, | ||||
|  | ||||
|   mixins: [PureRenderMixin], | ||||
|  | ||||
|   componentWillMount () { | ||||
|     this.props.dispatch(refreshTimeline('mentions')); | ||||
|   }, | ||||
|  | ||||
|   render () { | ||||
|     const { intl } = this.props; | ||||
|  | ||||
|     return ( | ||||
|       <Column icon='at' heading={intl.formatMessage(messages.title)}> | ||||
|         <StatusListContainer {...this.props} type='mentions' /> | ||||
|       </Column> | ||||
|     ); | ||||
|   }, | ||||
|  | ||||
| }); | ||||
|  | ||||
| export default connect()(injectIntl(MentionsTimeline)); | ||||
| @@ -32,11 +32,13 @@ const PublicTimeline = React.createClass({ | ||||
|       this.subscription = App.cable.subscriptions.create('PublicChannel', { | ||||
|  | ||||
|         received (data) { | ||||
|           switch(data.type) { | ||||
|           switch(data.event) { | ||||
|           case 'update': | ||||
|             return dispatch(updateTimeline('public', JSON.parse(data.message))); | ||||
|             dispatch(updateTimeline('public', JSON.parse(data.payload))); | ||||
|             break; | ||||
|           case 'delete': | ||||
|             return dispatch(deleteFromTimelines(data.id)); | ||||
|             dispatch(deleteFromTimelines(data.payload)); | ||||
|             break; | ||||
|           } | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -7,10 +7,10 @@ module ApplicationCable | ||||
|     def hydrate_status(encoded_message) | ||||
|       message = ActiveSupport::JSON.decode(encoded_message) | ||||
|  | ||||
|       return [nil, message] if message['type'] == 'delete' | ||||
|       return [nil, message] if message['event'] == 'delete' | ||||
|  | ||||
|       status             = Status.find_by(id: message['id']) | ||||
|       message['message'] = FeedManager.instance.inline_render(current_user.account, 'api/v1/statuses/show', status) | ||||
|       status             = Status.find_by(id: message['payload']) | ||||
|       message['payload'] = FeedManager.instance.inline_render(current_user.account, 'api/v1/statuses/show', status) | ||||
|  | ||||
|       [status, message] | ||||
|     end | ||||
|   | ||||
| @@ -22,22 +22,6 @@ class Api::V1::TimelinesController < ApiController | ||||
|     render action: :index | ||||
|   end | ||||
|  | ||||
|   def mentions | ||||
|     @statuses = Feed.new(:mentions, current_account).get(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id]) | ||||
|     @statuses = cache_collection(@statuses) | ||||
|  | ||||
|     set_maps(@statuses) | ||||
|     set_counters_maps(@statuses) | ||||
|     set_account_counters_maps(@statuses.flat_map { |s| [s.account, s.reblog? ? s.reblog.account : nil] }.compact.uniq) | ||||
|  | ||||
|     next_path = api_v1_mentions_timeline_url(max_id: @statuses.last.id)    if @statuses.size == limit_param(DEFAULT_STATUSES_LIMIT) | ||||
|     prev_path = api_v1_mentions_timeline_url(since_id: @statuses.first.id) unless @statuses.empty? | ||||
|  | ||||
|     set_pagination_headers(next_path, prev_path) | ||||
|  | ||||
|     render action: :index | ||||
|   end | ||||
|  | ||||
|   def public | ||||
|     @statuses = Status.as_public_timeline(current_account).paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id]) | ||||
|     @statuses = cache_collection(@statuses) | ||||
|   | ||||
| @@ -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, 'api/v1/statuses/show', status)) | ||||
|     broadcast(account.id, event: 'update', payload: inline_render(account, 'api/v1/statuses/show', status)) | ||||
|   end | ||||
|  | ||||
|   def broadcast(timeline_id, options = {}) | ||||
|   | ||||
| @@ -102,10 +102,6 @@ class Status < ApplicationRecord | ||||
|       where(account: [account] + account.following) | ||||
|     end | ||||
|  | ||||
|     def as_mentions_timeline(account) | ||||
|       where(id: Mention.where(account: account).select(:status_id)) | ||||
|     end | ||||
|  | ||||
|     def as_public_timeline(account = nil) | ||||
|       query = joins('LEFT OUTER JOIN accounts ON statuses.account_id = accounts.id') | ||||
|               .where(visibility: :public) | ||||
|   | ||||
| @@ -6,7 +6,6 @@ class FanOutOnWriteService < BaseService | ||||
|   def call(status) | ||||
|     deliver_to_self(status) if status.account.local? | ||||
|     deliver_to_followers(status) | ||||
|     deliver_to_mentioned(status) | ||||
|  | ||||
|     return if status.account.silenced? || !status.public_visibility? || status.reblog? | ||||
|  | ||||
| @@ -33,25 +32,15 @@ class FanOutOnWriteService < BaseService | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def deliver_to_mentioned(status) | ||||
|     Rails.logger.debug "Delivering status #{status.id} to mentioned accounts" | ||||
|  | ||||
|     status.mentions.includes(:account).each do |mention| | ||||
|       mentioned_account = mention.account | ||||
|       next if !mentioned_account.local? || FeedManager.instance.filter?(:mentions, status, mentioned_account) | ||||
|       FeedManager.instance.push(:mentions, mentioned_account, status) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def deliver_to_hashtags(status) | ||||
|     Rails.logger.debug "Delivering status #{status.id} to hashtags" | ||||
|     status.tags.find_each do |tag| | ||||
|       FeedManager.instance.broadcast("hashtag:#{tag.name}", type: 'update', id: status.id) | ||||
|       FeedManager.instance.broadcast("hashtag:#{tag.name}", event: 'update', payload: status.id) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def deliver_to_public(status) | ||||
|     Rails.logger.debug "Delivering status #{status.id} to public timeline" | ||||
|     FeedManager.instance.broadcast(:public, type: 'update', id: status.id) | ||||
|     FeedManager.instance.broadcast(:public, event: 'update', payload: status.id) | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -51,7 +51,7 @@ class NotifyService < BaseService | ||||
|   def create_notification | ||||
|     @notification.save! | ||||
|     return unless @notification.browserable? | ||||
|     FeedManager.instance.broadcast(@recipient.id, type: 'notification', message: FeedManager.instance.inline_render(@recipient, 'api/v1/notifications/show', @notification)) | ||||
|     FeedManager.instance.broadcast(@recipient.id, event: 'notification', payload: FeedManager.instance.inline_render(@recipient, 'api/v1/notifications/show', @notification)) | ||||
|   end | ||||
|  | ||||
|   def send_email | ||||
|   | ||||
| @@ -59,17 +59,17 @@ class RemoveStatusService < BaseService | ||||
|       redis.zremrangebyscore(FeedManager.instance.key(type, receiver.id), status.id, status.id) | ||||
|     end | ||||
|  | ||||
|     FeedManager.instance.broadcast(receiver.id, type: 'delete', id: status.id) | ||||
|     FeedManager.instance.broadcast(receiver.id, event: 'delete', payload: status.id) | ||||
|   end | ||||
|  | ||||
|   def remove_from_hashtags(status) | ||||
|     status.tags.each do |tag| | ||||
|       FeedManager.instance.broadcast("hashtag:#{tag.name}", type: 'delete', id: status.id) | ||||
|       FeedManager.instance.broadcast("hashtag:#{tag.name}", event: 'delete', payload: status.id) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def remove_from_public(status) | ||||
|     FeedManager.instance.broadcast(:public, type: 'delete', id: status.id) | ||||
|     FeedManager.instance.broadcast(:public, event: 'delete', payload: status.id) | ||||
|   end | ||||
|  | ||||
|   def redis | ||||
|   | ||||
| @@ -108,7 +108,6 @@ Rails.application.routes.draw do | ||||
|       end | ||||
|  | ||||
|       get '/timelines/home',     to: 'timelines#home', as: :home_timeline | ||||
|       get '/timelines/mentions', to: 'timelines#mentions', as: :mentions_timeline | ||||
|       get '/timelines/public',   to: 'timelines#public', as: :public_timeline | ||||
|       get '/timelines/tag/:id',  to: 'timelines#tag', as: :hashtag_timeline | ||||
|  | ||||
|   | ||||
| @@ -65,7 +65,6 @@ Returns a media object with an ID that can be attached when creating a status (s | ||||
| ### Retrieving a timeline | ||||
|  | ||||
| **GET /api/v1/timelines/home** | ||||
| **GET /api/v1/timelines/mentions** | ||||
| **GET /api/v1/timelines/public** | ||||
| **GET /api/v1/timelines/tag/:hashtag** | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| Push notifications | ||||
| ================== | ||||
|  | ||||
| **Note: This push notification design turned out to not be fully operational on the side of Firebase. A different approach is in consideration** | ||||
|  | ||||
| Mastodon can communicate with the Firebase Cloud Messaging API to send push notifications to apps on users' devices. For this to work, these conditions must be met: | ||||
|  | ||||
| * Responsibility of an instance owner: `FCM_API_KEY` set on the instance. This can be obtained on the Firebase dashboard, in project settings, under Cloud Messaging, as "server key" | ||||
|   | ||||
| @@ -36,7 +36,6 @@ namespace :mastodon do | ||||
|     task clear: :environment do | ||||
|       User.where('current_sign_in_at < ?', 14.days.ago).find_each do |user| | ||||
|         Redis.current.del(FeedManager.instance.key(:home, user.account_id)) | ||||
|         Redis.current.del(FeedManager.instance.key(:mentions, user.account_id)) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|   | ||||
| @@ -19,13 +19,6 @@ RSpec.describe Api::V1::TimelinesController, type: :controller do | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     describe 'GET #mentions' do | ||||
|       it 'returns http success' do | ||||
|         get :mentions | ||||
|         expect(response).to have_http_status(:success) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     describe 'GET #public' do | ||||
|       it 'returns http success' do | ||||
|         get :public | ||||
| @@ -55,13 +48,6 @@ RSpec.describe Api::V1::TimelinesController, type: :controller do | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     describe 'GET #mentions' do | ||||
|       it 'returns http unprocessable entity' do | ||||
|         get :mentions | ||||
|         expect(response).to have_http_status(:unprocessable_entity) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     describe 'GET #public' do | ||||
|       it 'returns http success' do | ||||
|         get :public | ||||
|   | ||||
| @@ -26,10 +26,6 @@ RSpec.describe FanOutOnWriteService do | ||||
|     expect(Feed.new(:home, follower).get(10).map(&:id)).to include status.id | ||||
|   end | ||||
|  | ||||
|   it 'delivers status to mentioned users' do | ||||
|     expect(Feed.new(:mentions, alice).get(10).map(&:id)).to include status.id | ||||
|   end | ||||
|  | ||||
|   it 'delivers status to hashtag' do | ||||
|     expect(Tag.find_by!(name: 'test').statuses.pluck(:id)).to include status.id | ||||
|   end | ||||
|   | ||||
		Reference in New Issue
	
	Block a user