Add antenna timeline
This commit is contained in:
parent
f393aa2a85
commit
5c758b344c
15 changed files with 375 additions and 14 deletions
67
app/controllers/api/v1/timelines/antenna_controller.rb
Normal file
67
app/controllers/api/v1/timelines/antenna_controller.rb
Normal file
|
@ -0,0 +1,67 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Timelines::AntennaController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:lists' }
|
||||
before_action :require_user!
|
||||
before_action :set_antenna
|
||||
before_action :set_statuses
|
||||
|
||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||
|
||||
def show
|
||||
render json: @statuses,
|
||||
each_serializer: REST::StatusSerializer,
|
||||
relationships: StatusRelationshipsPresenter.new(@statuses, current_user.account_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_antenna
|
||||
@antenna = Antenna.where(account: current_account).find(params[:id])
|
||||
end
|
||||
|
||||
def set_statuses
|
||||
@statuses = cached_list_statuses
|
||||
end
|
||||
|
||||
def cached_list_statuses
|
||||
cache_collection list_statuses, Status
|
||||
end
|
||||
|
||||
def list_statuses
|
||||
list_feed.get(
|
||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id],
|
||||
params[:min_id]
|
||||
)
|
||||
end
|
||||
|
||||
def list_feed
|
||||
AntennaFeed.new(@antenna)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_timelines_antenna_url params[:id], pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_timelines_antenna_url params[:id], pagination_params(min_id: pagination_since_id)
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@statuses.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@statuses.first.id
|
||||
end
|
||||
end
|
|
@ -22,6 +22,7 @@ import {
|
|||
fillPublicTimelineGaps,
|
||||
fillCommunityTimelineGaps,
|
||||
fillListTimelineGaps,
|
||||
fillAntennaTimelineGaps,
|
||||
} from './timelines';
|
||||
|
||||
/**
|
||||
|
@ -185,3 +186,10 @@ export const connectDirectStream = () =>
|
|||
*/
|
||||
export const connectListStream = listId =>
|
||||
connectTimelineStream(`list:${listId}`, 'list', { list: listId }, { fillGaps: () => fillListTimelineGaps(listId) });
|
||||
|
||||
/**
|
||||
* @param {string} antennaId
|
||||
* @returns {function(): void}
|
||||
*/
|
||||
export const connectAntennaStream = antennaId =>
|
||||
connectTimelineStream(`antenna:${antennaId}`, 'antenna', { antenna: antennaId }, { fillGaps: () => fillAntennaTimelineGaps(antennaId) });
|
||||
|
|
|
@ -149,6 +149,7 @@ export const expandAccountTimeline = (accountId, { maxId, withReplies, t
|
|||
export const expandAccountFeaturedTimeline = (accountId, { tagged } = {}) => expandTimeline(`account:${accountId}:pinned${tagged ? `:${tagged}` : ''}`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true, tagged });
|
||||
export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40 });
|
||||
export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
|
||||
export const expandAntennaTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`antenna:${id}`, `/api/v1/timelines/antenna/${id}`, { max_id: maxId }, done);
|
||||
export const expandHashtagTimeline = (hashtag, { maxId, tags, local } = {}, done = noOp) => {
|
||||
return expandTimeline(`hashtag:${hashtag}${local ? ':local' : ''}`, `/api/v1/timelines/tag/${hashtag}`, {
|
||||
max_id: maxId,
|
||||
|
@ -163,6 +164,7 @@ export const fillHomeTimelineGaps = (done = noOp) => fillTimelineGaps('home
|
|||
export const fillPublicTimelineGaps = ({ onlyMedia, onlyRemote } = {}, done = noOp) => fillTimelineGaps(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { remote: !!onlyRemote, only_media: !!onlyMedia }, done);
|
||||
export const fillCommunityTimelineGaps = ({ onlyMedia } = {}, done = noOp) => fillTimelineGaps(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, only_media: !!onlyMedia }, done);
|
||||
export const fillListTimelineGaps = (id, done = noOp) => fillTimelineGaps(`list:${id}`, `/api/v1/timelines/list/${id}`, {}, done);
|
||||
export const fillAntennaTimelineGaps = (id, done = noOp) => fillTimelineGaps(`antenna:${id}`, `/api/v1/timelines/antenna/${id}`, {}, done);
|
||||
|
||||
export function expandTimelineRequest(timeline, isLoadingMore) {
|
||||
return {
|
||||
|
|
203
app/javascript/mastodon/features/antenna_timeline/index.jsx
Normal file
203
app/javascript/mastodon/features/antenna_timeline/index.jsx
Normal file
|
@ -0,0 +1,203 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { fetchAntenna, deleteAntenna } from 'mastodon/actions/antennas';
|
||||
import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { connectAntennaStream } from 'mastodon/actions/streaming';
|
||||
import { expandAntennaTimeline } from 'mastodon/actions/timelines';
|
||||
import Column from 'mastodon/components/column';
|
||||
import ColumnHeader from 'mastodon/components/column_header';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
||||
import StatusListContainer from 'mastodon/features/ui/containers/status_list_container';
|
||||
|
||||
const messages = defineMessages({
|
||||
deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' },
|
||||
deleteConfirm: { id: 'confirmations.delete_list.confirm', defaultMessage: 'Delete' },
|
||||
followed: { id: 'lists.replies_policy.followed', defaultMessage: 'Any followed user' },
|
||||
none: { id: 'lists.replies_policy.none', defaultMessage: 'No one' },
|
||||
antenna: { id: 'lists.replies_policy.list', defaultMessage: 'Members of the list' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
antenna: state.getIn(['antennas', props.params.id]),
|
||||
hasUnread: state.getIn(['timelines', `antenna:${props.params.id}`, 'unread']) > 0,
|
||||
});
|
||||
|
||||
class AntennaTimeline extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
columnId: PropTypes.string,
|
||||
hasUnread: PropTypes.bool,
|
||||
multiColumn: PropTypes.bool,
|
||||
antenna: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handlePin = () => {
|
||||
const { columnId, dispatch } = this.props;
|
||||
|
||||
if (columnId) {
|
||||
dispatch(removeColumn(columnId));
|
||||
} else {
|
||||
dispatch(addColumn('ANTENNA_TIMELINE', { id: this.props.params.id }));
|
||||
this.context.router.history.push('/');
|
||||
}
|
||||
};
|
||||
|
||||
handleMove = (dir) => {
|
||||
const { columnId, dispatch } = this.props;
|
||||
dispatch(moveColumn(columnId, dir));
|
||||
};
|
||||
|
||||
handleHeaderClick = () => {
|
||||
this.column.scrollTop();
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
const { dispatch } = this.props;
|
||||
const { id } = this.props.params;
|
||||
|
||||
dispatch(fetchAntenna(id));
|
||||
dispatch(expandAntennaTimeline(id));
|
||||
|
||||
this.disconnect = dispatch(connectAntennaStream(id));
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||
const { dispatch } = this.props;
|
||||
const { id } = nextProps.params;
|
||||
|
||||
if (id !== this.props.params.id) {
|
||||
if (this.disconnect) {
|
||||
this.disconnect();
|
||||
this.disconnect = null;
|
||||
}
|
||||
|
||||
dispatch(fetchAntenna(id));
|
||||
dispatch(expandAntennaTimeline(id));
|
||||
|
||||
this.disconnect = dispatch(connectAntennaStream(id));
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.disconnect) {
|
||||
this.disconnect();
|
||||
this.disconnect = null;
|
||||
}
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.column = c;
|
||||
};
|
||||
|
||||
handleLoadMore = maxId => {
|
||||
const { id } = this.props.params;
|
||||
this.props.dispatch(expandAntennaTimeline(id, { maxId }));
|
||||
};
|
||||
|
||||
handleEditClick = () => {
|
||||
this.context.router.history.push(`/antennasw/${this.props.params.id}`);
|
||||
};
|
||||
|
||||
handleDeleteClick = () => {
|
||||
const { dispatch, columnId, intl } = this.props;
|
||||
const { id } = this.props.params;
|
||||
|
||||
dispatch(openModal({
|
||||
modalType: 'CONFIRM',
|
||||
modalProps: {
|
||||
message: intl.formatMessage(messages.deleteMessage),
|
||||
confirm: intl.formatMessage(messages.deleteConfirm),
|
||||
onConfirm: () => {
|
||||
dispatch(deleteAntenna(id));
|
||||
|
||||
if (columnId) {
|
||||
dispatch(removeColumn(columnId));
|
||||
} else {
|
||||
this.context.router.history.push('/antennasw');
|
||||
}
|
||||
},
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
render () {
|
||||
const { hasUnread, columnId, multiColumn, antenna } = this.props;
|
||||
const { id } = this.props.params;
|
||||
const pinned = !!columnId;
|
||||
const title = antenna ? antenna.get('title') : id;
|
||||
|
||||
if (typeof antenna === 'undefined') {
|
||||
return (
|
||||
<Column>
|
||||
<div className='scrollable'>
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
</Column>
|
||||
);
|
||||
} else if (antenna === false) {
|
||||
return (
|
||||
<BundleColumnError multiColumn={multiColumn} errorType='routing' />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={title}>
|
||||
<ColumnHeader
|
||||
icon='wifi'
|
||||
active={hasUnread}
|
||||
title={title}
|
||||
onPin={this.handlePin}
|
||||
onMove={this.handleMove}
|
||||
onClick={this.handleHeaderClick}
|
||||
pinned={pinned}
|
||||
multiColumn={multiColumn}
|
||||
>
|
||||
<div className='column-settings__row column-header__links'>
|
||||
<button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleEditClick}>
|
||||
<Icon id='pencil' /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' />
|
||||
</button>
|
||||
|
||||
<button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleDeleteClick}>
|
||||
<Icon id='trash' /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' />
|
||||
</button>
|
||||
</div>
|
||||
</ColumnHeader>
|
||||
|
||||
<StatusListContainer
|
||||
trackScroll={!pinned}
|
||||
scrollKey={`antenna_timeline-${columnId}`}
|
||||
timelineId={`antenna:${id}`}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.antenna' defaultMessage='There is nothing in this antenna yet. When members of this list post new statuses, they will appear here.' />}
|
||||
bindToDocument={!multiColumn}
|
||||
/>
|
||||
|
||||
<Helmet>
|
||||
<title>{title}</title>
|
||||
<meta name='robots' content='noindex' />
|
||||
</Helmet>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(AntennaTimeline));
|
|
@ -77,7 +77,7 @@ class Antennas extends ImmutablePureComponent {
|
|||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{antennas.map(antenna => (
|
||||
<ColumnLink key={antenna.get('id')} to={`/antennasw/${antenna.get('id')}`} icon='wifi' text={antenna.get('title')}>
|
||||
<ColumnLink key={antenna.get('id')} to={`/antennast/${antenna.get('id')}`} icon='wifi' text={antenna.get('title')}>
|
||||
<p className='antenna-list-detail'>
|
||||
<span className='group'><Icon id='users' />{antenna.get('accounts_count')}</span>
|
||||
<span className='group'><Icon id='sitemap' />{antenna.get('domains_count')}</span>
|
||||
|
|
|
@ -146,7 +146,7 @@ class ListTimeline extends PureComponent {
|
|||
|
||||
handleEditAntennaClick = (e) => {
|
||||
const id = e.currentTarget.getAttribute('data-id');
|
||||
window.open(`/antennas/${id}/edit`, '_blank');
|
||||
this.context.router.history.push(`/antennasw/${id}/edit`);
|
||||
}
|
||||
|
||||
handleRepliesPolicyChange = ({ target }) => {
|
||||
|
|
|
@ -48,6 +48,7 @@ import {
|
|||
StatusReferences,
|
||||
DirectTimeline,
|
||||
HashtagTimeline,
|
||||
AntennaTimeline,
|
||||
Notifications,
|
||||
FollowRequests,
|
||||
FavouritedStatuses,
|
||||
|
@ -210,6 +211,7 @@ class SwitchingColumnsArea extends PureComponent {
|
|||
<WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} />
|
||||
<WrappedRoute path='/lists/:id' component={ListTimeline} content={children} />
|
||||
<WrappedRoute path='/antennasw/:id' component={AntennaSetting} content={children} />
|
||||
<WrappedRoute path='/antennast/:id' component={AntennaTimeline} content={children} />
|
||||
<WrappedRoute path='/notifications' component={Notifications} content={children} />
|
||||
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
|
||||
<WrappedRoute path='/emoji_reactions' component={EmojiReactedStatuses} content={children} />
|
||||
|
|
|
@ -34,6 +34,10 @@ export function DirectTimeline() {
|
|||
return import(/* webpackChunkName: "features/direct_timeline" */'../../direct_timeline');
|
||||
}
|
||||
|
||||
export function AntennaTimeline () {
|
||||
return import(/* webpackChunkName: "features/antenna_timeline" */'../../antenna_timeline');
|
||||
}
|
||||
|
||||
export function ListTimeline () {
|
||||
return import(/* webpackChunkName: "features/list_timeline" */'../../list_timeline');
|
||||
}
|
||||
|
|
|
@ -90,6 +90,14 @@ class FeedManager
|
|||
true
|
||||
end
|
||||
|
||||
def push_to_antenna(antenna, status, update: false)
|
||||
return false unless add_to_feed(:antenna, antenna.id, status, aggregate_reblogs: antenna.account.user&.aggregates_reblogs?)
|
||||
|
||||
trim(:antenna, antenna.id)
|
||||
PushUpdateWorker.perform_async(antenna.account_id, status.id, "timeline:antenna:#{antenna.id}", { 'update' => update }) if push_update_required?("timeline:antenna:#{antenna.id}")
|
||||
true
|
||||
end
|
||||
|
||||
# Remove a status from a list feed and send a streaming API update
|
||||
# @param [List] list
|
||||
# @param [Status] status
|
||||
|
@ -102,6 +110,13 @@ class FeedManager
|
|||
true
|
||||
end
|
||||
|
||||
def unpush_from_antenna(antenna, status, update: false)
|
||||
return false unless remove_from_feed(:antenna, antenna.id, status, aggregate_reblogs: antenna.account.user&.aggregates_reblogs?)
|
||||
|
||||
redis.publish("timeline:antenna:#{antenna.id}", Oj.dump(event: :delete, payload: status.id.to_s)) unless update
|
||||
true
|
||||
end
|
||||
|
||||
# Fill a home feed with an account's statuses
|
||||
# @param [Account] from_account
|
||||
# @param [Account] into_account
|
||||
|
|
7
app/models/antenna_feed.rb
Normal file
7
app/models/antenna_feed.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AntennaFeed < Feed
|
||||
def initialize(antenna)
|
||||
super(:antenna, antenna.id)
|
||||
end
|
||||
end
|
|
@ -51,7 +51,7 @@ class FanOutOnWriteService < BaseService
|
|||
when :public, :unlisted, :public_unlisted, :login, :private
|
||||
deliver_to_all_followers!
|
||||
deliver_to_lists!
|
||||
deliver_to_antennas! if [:public, :public_unlisted, :login].include?(@status.visibility.to_sym) && !@account.dissubscribable
|
||||
deliver_to_antennas! unless @account.dissubscribable
|
||||
deliver_to_stl_antennas!
|
||||
when :limited
|
||||
deliver_to_lists_mentioned_accounts_only!
|
||||
|
@ -159,9 +159,6 @@ class FanOutOnWriteService < BaseService
|
|||
|
||||
antennas = Antenna.availables
|
||||
antennas = antennas.left_joins(:antenna_domains).where(any_domains: true).or(Antenna.left_joins(:antenna_domains).where(antenna_domains: { name: domain }))
|
||||
antennas = antennas.where(with_media_only: false) unless @status.with_media?
|
||||
antennas = antennas.where(ignore_reblog: false) unless @status.reblog?
|
||||
antennas = antennas.where(stl: false)
|
||||
|
||||
antennas = Antenna.where(id: antennas.select(:id))
|
||||
antennas = antennas.left_joins(:antenna_accounts).where(any_accounts: true).or(Antenna.left_joins(:antenna_accounts).where(antenna_accounts: { account: @account }))
|
||||
|
@ -171,13 +168,17 @@ class FanOutOnWriteService < BaseService
|
|||
antennas = antennas.left_joins(:antenna_tags).where(any_tags: true).or(Antenna.left_joins(:antenna_tags).where(antenna_tags: { tag_id: tag_ids }))
|
||||
|
||||
antennas = antennas.where(account_id: Account.without_suspended.joins(:user).select('accounts.id').where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago))
|
||||
antennas = antennas.where(account: @status.account.followers) if [:public, :public_unlisted, :login].exclude?(@status.visibility.to_sym)
|
||||
antennas = antennas.where(with_media_only: false) unless @status.with_media?
|
||||
antennas = antennas.where(ignore_reblog: false) unless @status.reblog?
|
||||
antennas = antennas.where(stl: false)
|
||||
|
||||
collection = AntennaCollection.new(@status, @options[:update], false)
|
||||
|
||||
antennas.in_batches do |ans|
|
||||
ans.each do |antenna|
|
||||
next unless antenna.enabled?
|
||||
next if antenna.keywords.any? && antenna.keywords.none? { |keyword| @status.text.include?(keyword) }
|
||||
next if antenna.keywords&.any? && antenna.keywords&.none? { |keyword| @status.text.include?(keyword) }
|
||||
next if antenna.exclude_keywords&.any? { |keyword| @status.text.include?(keyword) }
|
||||
next if antenna.exclude_accounts&.include?(@status.account_id)
|
||||
next if antenna.exclude_domains&.include?(domain)
|
||||
|
@ -273,25 +274,25 @@ class FanOutOnWriteService < BaseService
|
|||
|
||||
def push(antenna)
|
||||
if antenna.list_id.zero?
|
||||
@home_account_ids << antenna.account_id
|
||||
else
|
||||
@list_ids << antenna.list_id
|
||||
@home_account_ids << { id: antenna.account_id, antenna_id: antenna.id } if @home_account_ids.none? { |id| id.id == antenna.account_id }
|
||||
elsif @list_ids.none? { |id| id.id == antenna.list_id }
|
||||
@list_ids << { id: antenna.list_id, antenna_id: antenna.id }
|
||||
end
|
||||
end
|
||||
|
||||
def deliver!
|
||||
lists = @list_ids.uniq
|
||||
homes = @home_account_ids.uniq
|
||||
lists = @list_ids
|
||||
homes = @home_account_ids
|
||||
|
||||
if lists.any?
|
||||
FeedInsertWorker.push_bulk(lists) do |list|
|
||||
[@status.id, list, 'list', { 'update' => @update, 'stl_home' => @stl_home || false }]
|
||||
[@status.id, list[:id], 'list', { 'update' => @update, 'stl_home' => @stl_home || false, 'antenna_id' => list[:antenna_id] }]
|
||||
end
|
||||
end
|
||||
|
||||
if homes.any?
|
||||
FeedInsertWorker.push_bulk(homes) do |home|
|
||||
[@status.id, home, 'home', { 'update' => @update }]
|
||||
[@status.id, home[:id], 'home', { 'update' => @update, 'antenna_id' => home[:antenna_id] }]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -61,6 +61,11 @@ class FeedInsertWorker
|
|||
when :list
|
||||
FeedManager.instance.push_to_list(@list, @status, update: update?)
|
||||
end
|
||||
|
||||
return if @options[:antenna_id].blank?
|
||||
|
||||
antenna = Antenna.find(@options[:antenna_id])
|
||||
FeedManager.instance.push_to_antenna(antenna, @status, update: update?) if antenna.present?
|
||||
end
|
||||
|
||||
def perform_unpush
|
||||
|
@ -70,6 +75,11 @@ class FeedInsertWorker
|
|||
when :list
|
||||
FeedManager.instance.unpush_from_list(@list, @status, update: true)
|
||||
end
|
||||
|
||||
return if @options[:antenna_id].blank?
|
||||
|
||||
antenna = Antenna.find(@options[:antenna_id])
|
||||
FeedManager.instance.unpush_from_antenna(antenna, @status, update: true) if antenna.present?
|
||||
end
|
||||
|
||||
def perform_notify
|
||||
|
|
|
@ -17,6 +17,7 @@ Rails.application.routes.draw do
|
|||
/conversations
|
||||
/lists/(*any)
|
||||
/antennasw/(*any)
|
||||
/antennast/(*any)
|
||||
/notifications
|
||||
/favourites
|
||||
/emoji_reactions
|
||||
|
|
|
@ -49,6 +49,7 @@ namespace :api, format: false do
|
|||
resource :public, only: :show, controller: :public
|
||||
resources :tag, only: :show
|
||||
resources :list, only: :show
|
||||
resources :antenna, only: :show
|
||||
end
|
||||
|
||||
get '/streaming', to: 'streaming#index'
|
||||
|
|
|
@ -701,6 +701,33 @@ const startServer = async () => {
|
|||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {string} antennaId
|
||||
* @param {any} req
|
||||
* @returns {Promise.<void>}
|
||||
*/
|
||||
const authorizeAntennaAccess = (antennaId, req) => new Promise((resolve, reject) => {
|
||||
const { accountId } = req;
|
||||
|
||||
pgPool.connect((err, client, done) => {
|
||||
if (err) {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
client.query('SELECT id, account_id FROM antennas WHERE id = $1 LIMIT 1', [antennaId], (err, result) => {
|
||||
done();
|
||||
|
||||
if (err || result.rows.length === 0 || result.rows[0].account_id !== accountId) {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {string[]} ids
|
||||
* @param {any} req
|
||||
|
@ -1214,6 +1241,17 @@ const startServer = async () => {
|
|||
reject('Not authorized to stream this list');
|
||||
});
|
||||
|
||||
break;
|
||||
case 'antenna':
|
||||
authorizeAntennaAccess(params.antenna, req).then(() => {
|
||||
resolve({
|
||||
channelIds: [`timeline:antenna:${params.antenna}`],
|
||||
options: { needsFiltering: false },
|
||||
});
|
||||
}).catch(() => {
|
||||
reject('Not authorized to stream this antenna');
|
||||
});
|
||||
|
||||
break;
|
||||
default:
|
||||
reject('Unknown stream type');
|
||||
|
@ -1228,6 +1266,8 @@ const startServer = async () => {
|
|||
const streamNameFromChannelName = (channelName, params) => {
|
||||
if (channelName === 'list') {
|
||||
return [channelName, params.list];
|
||||
} else if (channelName === 'antenna') {
|
||||
return [channelName, params.antenna];
|
||||
} else if (['hashtag', 'hashtag:local'].includes(channelName)) {
|
||||
return [channelName, params.tag];
|
||||
} else {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue