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';
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;
}
}
<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} />
}, {
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;
}
}
+++ /dev/null
-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));
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;
}
}
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
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)
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 = {})
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)
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?
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
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
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
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
### Retrieving a timeline
**GET /api/v1/timelines/home**
-**GET /api/v1/timelines/mentions**
**GET /api/v1/timelines/public**
**GET /api/v1/timelines/tag/:hashtag**
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"
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
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
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
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