From a3106b80945100752c7462ce5eae4c41854a7362 Mon Sep 17 00:00:00 2001 From: Jeffrey Phillips Freeman <the@jeffreyfreeman.me> Date: Wed, 28 Oct 2020 15:06:53 -0400 Subject: [PATCH] Initial changes to make group server. --- Gemfile.lock | 6 + .../accounts/following_accounts_controller.rb | 2 +- app/controllers/api/v1/accounts_controller.rb | 9 +- .../api/v1/timelines/home_controller.rb | 2 +- .../api/v1/timelines/list_controller.rb | 2 +- .../api/v1/timelines/public_controller.rb | 2 +- app/javascript/mastodon/actions/compose.js | 6 +- app/javascript/mastodon/components/account.js | 2 - .../features/account/components/header.js | 4 - .../features/community_timeline/index.js | 2 +- .../mastodon/features/compose/index.js | 8 +- .../directory/components/account_card.js | 11 -- .../features/getting_started/index.js | 7 +- .../mastodon/features/home_timeline/index.js | 3 +- .../components/column_settings.js | 30 ---- .../containers/column_settings_container.js | 28 ---- .../features/public_timeline/index.js | 140 ------------------ .../features/ui/components/columns_area.js | 5 - .../ui/components/navigation_panel.js | 4 +- .../features/ui/components/tabs_bar.js | 4 +- app/javascript/mastodon/features/ui/index.js | 8 +- .../features/ui/util/async-components.js | 4 - .../mastodon/locales/defaultMessages.json | 14 +- app/javascript/mastodon/locales/en.json | 5 +- app/javascript/mastodon/locales/ja.json | 5 +- app/javascript/mastodon/reducers/settings.js | 1 - app/lib/activitypub/activity.rb | 1 + app/lib/activitypub/activity/announce.rb | 2 +- app/lib/activitypub/activity/create.rb | 22 ++- app/models/account.rb | 17 +-- app/models/account_stat.rb | 19 +-- app/models/follow.rb | 1 + app/models/follow_request.rb | 2 +- app/models/list.rb | 19 --- app/models/mention.rb | 1 + app/models/mute.rb | 4 +- app/models/status.rb | 12 +- app/models/user.rb | 11 -- app/services/after_block_service.rb | 5 - app/services/batched_remove_status_service.rb | 52 +------ app/services/fan_out_on_write_service.rb | 17 +-- app/services/follow_service.rb | 1 - app/services/mute_service.rb | 1 - app/services/precompute_feed_service.rb | 9 -- app/services/process_mentions_service.rb | 15 +- app/services/remove_status_service.rb | 29 +--- app/services/unfollow_service.rb | 1 - app/services/unmute_service.rb | 2 - app/workers/feed_insert_worker.rb | 43 ------ app/workers/merge_worker.rb | 11 -- app/workers/mute_worker.rb | 12 -- app/workers/regeneration_worker.rb | 14 -- .../scheduler/feed_cleanup_scheduler.rb | 61 -------- app/workers/unmerge_worker.rb | 11 -- config/sidekiq.yml | 3 - db/schema.rb | 106 ++++++++++++- lib/cli.rb | 4 - lib/mastodon/feeds_cli.rb | 63 -------- .../concerns/user_tracking_concern_spec.rb | 41 ----- spec/models/follow_request_spec.rb | 30 ---- spec/models/home_feed_spec.rb | 44 ------ spec/services/after_block_service_spec.rb | 29 ---- spec/services/mute_service_spec.rb | 19 --- spec/services/precompute_feed_service_spec.rb | 36 ----- spec/workers/feed_insert_worker_spec.rb | 52 ------- spec/workers/regeneration_worker_spec.rb | 26 ---- .../scheduler/feed_cleanup_scheduler_spec.rb | 26 ---- 67 files changed, 215 insertions(+), 973 deletions(-) delete mode 100644 app/javascript/mastodon/features/public_timeline/components/column_settings.js delete mode 100644 app/javascript/mastodon/features/public_timeline/containers/column_settings_container.js delete mode 100644 app/javascript/mastodon/features/public_timeline/index.js delete mode 100644 app/services/precompute_feed_service.rb delete mode 100644 app/workers/feed_insert_worker.rb delete mode 100644 app/workers/merge_worker.rb delete mode 100644 app/workers/mute_worker.rb delete mode 100644 app/workers/regeneration_worker.rb delete mode 100644 app/workers/scheduler/feed_cleanup_scheduler.rb delete mode 100644 app/workers/unmerge_worker.rb delete mode 100644 lib/mastodon/feeds_cli.rb delete mode 100644 spec/models/follow_request_spec.rb delete mode 100644 spec/models/home_feed_spec.rb delete mode 100644 spec/services/after_block_service_spec.rb delete mode 100644 spec/services/precompute_feed_service_spec.rb delete mode 100644 spec/workers/feed_insert_worker_spec.rb delete mode 100644 spec/workers/regeneration_worker_spec.rb delete mode 100644 spec/workers/scheduler/feed_cleanup_scheduler_spec.rb diff --git a/Gemfile.lock b/Gemfile.lock index 62c20bf070..a7c5b907f8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -790,3 +790,9 @@ DEPENDENCIES webmock (~> 3.8) webpacker (~> 5.1) webpush + +RUBY VERSION + ruby 2.6.6p146 + +BUNDLED WITH + 2.0.2 diff --git a/app/controllers/api/v1/accounts/following_accounts_controller.rb b/app/controllers/api/v1/accounts/following_accounts_controller.rb index 93d4bd3a4a..4b4c10e0fa 100644 --- a/app/controllers/api/v1/accounts/following_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/following_accounts_controller.rb @@ -6,7 +6,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController after_action :insert_pagination_headers def index - @accounts = load_accounts + @accounts = Account.none render json: @accounts, each_serializer: REST::AccountSerializer end diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index 0080faf330..353b371277 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -31,11 +31,7 @@ class Api::V1::AccountsController < Api::BaseController end def follow - FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs), with_rate_limit: true) - - options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } } - - render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options) + raise Mastodon::NotPermittedError end def block @@ -49,8 +45,7 @@ class Api::V1::AccountsController < Api::BaseController end def unfollow - UnfollowService.new.call(current_user.account, @account) - render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships + raise Mastodon::NotPermittedError end def unblock diff --git a/app/controllers/api/v1/timelines/home_controller.rb b/app/controllers/api/v1/timelines/home_controller.rb index ae6dbcb8b3..433495e12a 100644 --- a/app/controllers/api/v1/timelines/home_controller.rb +++ b/app/controllers/api/v1/timelines/home_controller.rb @@ -6,7 +6,7 @@ class Api::V1::Timelines::HomeController < Api::BaseController after_action :insert_pagination_headers, unless: -> { @statuses.empty? } def show - @statuses = load_statuses + @statuses = Status.none render json: @statuses, each_serializer: REST::StatusSerializer, diff --git a/app/controllers/api/v1/timelines/list_controller.rb b/app/controllers/api/v1/timelines/list_controller.rb index a15eae468d..ab12520c7d 100644 --- a/app/controllers/api/v1/timelines/list_controller.rb +++ b/app/controllers/api/v1/timelines/list_controller.rb @@ -21,7 +21,7 @@ class Api::V1::Timelines::ListController < Api::BaseController end def set_statuses - @statuses = cached_list_statuses + @statuses = Status.none end def cached_list_statuses diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb index c6e7854d93..d5c0ac47c8 100644 --- a/app/controllers/api/v1/timelines/public_controller.rb +++ b/app/controllers/api/v1/timelines/public_controller.rb @@ -39,7 +39,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController end def public_timeline_statuses - Status.as_public_timeline(current_account, truthy_param?(:remote) ? :remote : truthy_param?(:local)) + Status.as_public_timeline(current_account, truthy_param?(:local)) end def insert_pagination_headers diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 0309225202..ce2a4b7add 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -171,11 +171,7 @@ export function submitCompose(routerHistory) { } }; - if (response.data.visibility !== 'direct') { - insertIfOnline('home'); - } - - if (response.data.in_reply_to_id === null && response.data.visibility === 'public') { + if (response.data.visibility === 'public' || response.data.visibility === 'unlisted') { insertIfOnline('community'); insertIfOnline('public'); insertIfOnline(`account:${response.data.account.id}`); diff --git a/app/javascript/mastodon/components/account.js b/app/javascript/mastodon/components/account.js index 2705a60013..5cb887534b 100644 --- a/app/javascript/mastodon/components/account.js +++ b/app/javascript/mastodon/components/account.js @@ -102,8 +102,6 @@ class Account extends ImmutablePureComponent { {hidingNotificationsButton} </Fragment> ); - } else if (!account.get('moved') || following) { - buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />; } } diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index 9613b0b9ed..1884674889 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -150,10 +150,6 @@ class Header extends ImmutablePureComponent { if (me !== account.get('id')) { if (!account.get('relationship')) { // Wait until the relationship is loaded actionBtn = ''; - } else if (account.getIn(['relationship', 'requested'])) { - actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />; - } else if (!account.getIn(['relationship', 'blocking'])) { - actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />; } else if (account.getIn(['relationship', 'blocking'])) { actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />; } diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js index b3cd39685c..52faa22ee0 100644 --- a/app/javascript/mastodon/features/community_timeline/index.js +++ b/app/javascript/mastodon/features/community_timeline/index.js @@ -11,7 +11,7 @@ import ColumnSettingsContainer from './containers/column_settings_container'; import { connectCommunityStream } from '../../actions/streaming'; const messages = defineMessages({ - title: { id: 'column.community', defaultMessage: 'Local timeline' }, + title: { id: 'column.community', defaultMessage: 'Group timeline' }, }); const mapStateToProps = (state, { columnId }) => { diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js index e2de8b0e6a..8b8491ffc2 100644 --- a/app/javascript/mastodon/features/compose/index.js +++ b/app/javascript/mastodon/features/compose/index.js @@ -23,7 +23,7 @@ const messages = defineMessages({ home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' }, notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' }, public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' }, - community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, + community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Group timeline' }, preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' }, @@ -98,18 +98,12 @@ class Compose extends React.PureComponent { header = ( <nav className='drawer__header'> <Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)} aria-label={intl.formatMessage(messages.start)}><Icon id='bars' fixedWidth /></Link> - {!columns.some(column => column.get('id') === 'HOME') && ( - <Link to='/timelines/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><Icon id='home' fixedWidth /></Link> - )} {!columns.some(column => column.get('id') === 'NOTIFICATIONS') && ( <Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}><Icon id='bell' fixedWidth /></Link> )} {!columns.some(column => column.get('id') === 'COMMUNITY') && ( <Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><Icon id='users' fixedWidth /></Link> )} - {!columns.some(column => column.get('id') === 'PUBLIC') && ( - <Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link> - )} <a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><Icon id='cog' fixedWidth /></a> <a href='/auth/sign_out' className='drawer__tab' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)} onClick={this.handleLogoutClick}><Icon id='sign-out' fixedWidth /></a> </nav> diff --git a/app/javascript/mastodon/features/directory/components/account_card.js b/app/javascript/mastodon/features/directory/components/account_card.js index 419ab9e114..c1a938f647 100644 --- a/app/javascript/mastodon/features/directory/components/account_card.js +++ b/app/javascript/mastodon/features/directory/components/account_card.js @@ -199,17 +199,6 @@ class AccountCard extends ImmutablePureComponent { onClick={this.handleMute} /> ); - } else if (!account.get('moved') || following) { - buttons = ( - <IconButton - icon={following ? 'user-times' : 'user-plus'} - title={intl.formatMessage( - following ? messages.unfollow : messages.follow, - )} - onClick={this.handleFollow} - active={following} - /> - ); } } diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index d9838e1c73..959ef19097 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -20,7 +20,7 @@ const messages = defineMessages({ notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' }, public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' }, settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' }, - community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, + community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Group timeline' }, direct: { id: 'navigation_bar.direct', defaultMessage: 'Direct messages' }, bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' }, preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, @@ -81,7 +81,7 @@ class GettingStarted extends ImmutablePureComponent { const { fetchFollowRequests, multiColumn } = this.props; if (!multiColumn && window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) { - this.context.router.history.replace('/timelines/home'); + this.context.router.history.replace('/timelines/public/local'); return; } @@ -99,10 +99,9 @@ class GettingStarted extends ImmutablePureComponent { navItems.push( <ColumnSubheading key={i++} text={intl.formatMessage(messages.discover)} />, <ColumnLink key={i++} icon='users' text={intl.formatMessage(messages.community_timeline)} to='/timelines/public/local' />, - <ColumnLink key={i++} icon='globe' text={intl.formatMessage(messages.public_timeline)} to='/timelines/public' />, ); - height += 34 + 48*2; + height += 34 + 48*1; if (profile_directory) { navItems.push( diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js index 577ff33bb0..25bb29c802 100644 --- a/app/javascript/mastodon/features/home_timeline/index.js +++ b/app/javascript/mastodon/features/home_timeline/index.js @@ -51,7 +51,8 @@ class HomeTimeline extends React.PureComponent { if (columnId) { dispatch(removeColumn(columnId)); } else { - dispatch(addColumn('HOME', {})); + raise("Trying to pin home timeline, should be impossible"); +// dispatch(addColumn('HOME', {})); } } diff --git a/app/javascript/mastodon/features/public_timeline/components/column_settings.js b/app/javascript/mastodon/features/public_timeline/components/column_settings.js deleted file mode 100644 index 756b6fe06d..0000000000 --- a/app/javascript/mastodon/features/public_timeline/components/column_settings.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { injectIntl, FormattedMessage } from 'react-intl'; -import SettingToggle from '../../notifications/components/setting_toggle'; - -export default @injectIntl -class ColumnSettings extends React.PureComponent { - - static propTypes = { - settings: ImmutablePropTypes.map.isRequired, - onChange: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - columnId: PropTypes.string, - }; - - render () { - const { settings, onChange } = this.props; - - return ( - <div> - <div className='column-settings__row'> - <SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} /> - <SettingToggle settings={settings} settingPath={['other', 'onlyRemote']} onChange={onChange} label={<FormattedMessage id='community.column_settings.remote_only' defaultMessage='Remote only' />} /> - </div> - </div> - ); - } - -} diff --git a/app/javascript/mastodon/features/public_timeline/containers/column_settings_container.js b/app/javascript/mastodon/features/public_timeline/containers/column_settings_container.js deleted file mode 100644 index 8c9e8aef41..0000000000 --- a/app/javascript/mastodon/features/public_timeline/containers/column_settings_container.js +++ /dev/null @@ -1,28 +0,0 @@ -import { connect } from 'react-redux'; -import ColumnSettings from '../components/column_settings'; -import { changeSetting } from '../../../actions/settings'; -import { changeColumnParams } from '../../../actions/columns'; - -const mapStateToProps = (state, { columnId }) => { - const uuid = columnId; - const columns = state.getIn(['settings', 'columns']); - const index = columns.findIndex(c => c.get('uuid') === uuid); - - return { - settings: (uuid && index >= 0) ? columns.get(index).get('params') : state.getIn(['settings', 'public']), - }; -}; - -const mapDispatchToProps = (dispatch, { columnId }) => { - return { - onChange (key, checked) { - if (columnId) { - dispatch(changeColumnParams(columnId, key, checked)); - } else { - dispatch(changeSetting(['public', ...key], checked)); - } - }, - }; -}; - -export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings); diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js deleted file mode 100644 index 988b1b070b..0000000000 --- a/app/javascript/mastodon/features/public_timeline/index.js +++ /dev/null @@ -1,140 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import PropTypes from 'prop-types'; -import StatusListContainer from '../ui/containers/status_list_container'; -import Column from '../../components/column'; -import ColumnHeader from '../../components/column_header'; -import { expandPublicTimeline } from '../../actions/timelines'; -import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; -import ColumnSettingsContainer from './containers/column_settings_container'; -import { connectPublicStream } from '../../actions/streaming'; - -const messages = defineMessages({ - title: { id: 'column.public', defaultMessage: 'Federated timeline' }, -}); - -const mapStateToProps = (state, { columnId }) => { - const uuid = columnId; - const columns = state.getIn(['settings', 'columns']); - const index = columns.findIndex(c => c.get('uuid') === uuid); - const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']); - const onlyRemote = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyRemote']) : state.getIn(['settings', 'public', 'other', 'onlyRemote']); - const timelineState = state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`]); - - return { - hasUnread: !!timelineState && timelineState.get('unread') > 0, - onlyMedia, - onlyRemote, - }; -}; - -export default @connect(mapStateToProps) -@injectIntl -class PublicTimeline extends React.PureComponent { - - static contextTypes = { - router: PropTypes.object, - }; - - static defaultProps = { - onlyMedia: false, - }; - - static propTypes = { - dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, - intl: PropTypes.object.isRequired, - columnId: PropTypes.string, - multiColumn: PropTypes.bool, - hasUnread: PropTypes.bool, - onlyMedia: PropTypes.bool, - onlyRemote: PropTypes.bool, - }; - - handlePin = () => { - const { columnId, dispatch, onlyMedia, onlyRemote } = this.props; - - if (columnId) { - dispatch(removeColumn(columnId)); - } else { - dispatch(addColumn(onlyRemote ? 'REMOTE' : 'PUBLIC', { other: { onlyMedia, onlyRemote } })); - } - } - - handleMove = (dir) => { - const { columnId, dispatch } = this.props; - dispatch(moveColumn(columnId, dir)); - } - - handleHeaderClick = () => { - this.column.scrollTop(); - } - - componentDidMount () { - const { dispatch, onlyMedia, onlyRemote } = this.props; - - dispatch(expandPublicTimeline({ onlyMedia, onlyRemote })); - this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote })); - } - - componentDidUpdate (prevProps) { - if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote) { - const { dispatch, onlyMedia, onlyRemote } = this.props; - - this.disconnect(); - dispatch(expandPublicTimeline({ onlyMedia, onlyRemote })); - this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote })); - } - } - - componentWillUnmount () { - if (this.disconnect) { - this.disconnect(); - this.disconnect = null; - } - } - - setRef = c => { - this.column = c; - } - - handleLoadMore = maxId => { - const { dispatch, onlyMedia, onlyRemote } = this.props; - - dispatch(expandPublicTimeline({ maxId, onlyMedia, onlyRemote })); - } - - render () { - const { intl, shouldUpdateScroll, columnId, hasUnread, multiColumn, onlyMedia, onlyRemote } = this.props; - const pinned = !!columnId; - - return ( - <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> - <ColumnHeader - icon='globe' - active={hasUnread} - title={intl.formatMessage(messages.title)} - onPin={this.handlePin} - onMove={this.handleMove} - onClick={this.handleHeaderClick} - pinned={pinned} - multiColumn={multiColumn} - > - <ColumnSettingsContainer columnId={columnId} /> - </ColumnHeader> - - <StatusListContainer - timelineId={`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`} - onLoadMore={this.handleLoadMore} - trackScroll={!pinned} - scrollKey={`public_timeline-${columnId}`} - emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up' />} - shouldUpdateScroll={shouldUpdateScroll} - bindToDocument={!multiColumn} - /> - </Column> - ); - } - -} diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index 9b03cf26d2..15fc92f15c 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -15,9 +15,7 @@ import BundleColumnError from './bundle_column_error'; import { Compose, Notifications, - HomeTimeline, CommunityTimeline, - PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, @@ -34,10 +32,7 @@ import { scrollRight } from '../../../scroll'; const componentMap = { 'COMPOSE': Compose, - 'HOME': HomeTimeline, 'NOTIFICATIONS': Notifications, - 'PUBLIC': PublicTimeline, - 'REMOTE': PublicTimeline, 'COMMUNITY': CommunityTimeline, 'HASHTAG': HashtagTimeline, 'DIRECT': DirectTimeline, diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.js b/app/javascript/mastodon/features/ui/components/navigation_panel.js index 0c12852f5b..2db5ec4b36 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_panel.js +++ b/app/javascript/mastodon/features/ui/components/navigation_panel.js @@ -10,11 +10,9 @@ import TrendsContainer from 'mastodon/features/getting_started/containers/trends const NavigationPanel = () => ( <div className='navigation-panel'> - <NavLink className='column-link column-link--transparent' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon className='column-link__icon' id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink> <NavLink className='column-link column-link--transparent' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon className='column-link__icon' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink> <FollowRequestsNavLink /> - <NavLink className='column-link column-link--transparent' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink> - <NavLink className='column-link column-link--transparent' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon className='column-link__icon' id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink> + <NavLink className='column-link column-link--transparent' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='tabs_bar.group_timeline' defaultMessage='Group' /></NavLink> <NavLink className='column-link column-link--transparent' to='/timelines/direct'><Icon className='column-link__icon' id='envelope' fixedWidth /><FormattedMessage id='navigation_bar.direct' defaultMessage='Direct messages' /></NavLink> <NavLink className='column-link column-link--transparent' to='/favourites'><Icon className='column-link__icon' id='star' fixedWidth /><FormattedMessage id='navigation_bar.favourites' defaultMessage='Favourites' /></NavLink> <NavLink className='column-link column-link--transparent' to='/bookmarks'><Icon className='column-link__icon' id='bookmark' fixedWidth /><FormattedMessage id='navigation_bar.bookmarks' defaultMessage='Bookmarks' /></NavLink> diff --git a/app/javascript/mastodon/features/ui/components/tabs_bar.js b/app/javascript/mastodon/features/ui/components/tabs_bar.js index 1911da8ba3..03b3ff22c9 100644 --- a/app/javascript/mastodon/features/ui/components/tabs_bar.js +++ b/app/javascript/mastodon/features/ui/components/tabs_bar.js @@ -8,10 +8,8 @@ import Icon from 'mastodon/components/icon'; import NotificationsCounterIcon from './notifications_counter_icon'; export const links = [ - <NavLink className='tabs-bar__link' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>, <NavLink className='tabs-bar__link' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>, - <NavLink className='tabs-bar__link' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>, - <NavLink className='tabs-bar__link' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>, + <NavLink className='tabs-bar__link' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon id='users' fixedWidth /><FormattedMessage id='tabs_bar.group_timeline' defaultMessage='Group' /></NavLink>, <NavLink className='tabs-bar__link optional' to='/search' data-preview-title-id='tabs_bar.search' data-preview-icon='bell' ><Icon id='search' fixedWidth /><FormattedMessage id='tabs_bar.search' defaultMessage='Search' /></NavLink>, <NavLink className='tabs-bar__link' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><Icon id='bars' fixedWidth /></NavLink>, ]; diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 553cb33659..1870508d5c 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -26,11 +26,9 @@ import { Status, GettingStarted, KeyboardShortcuts, - PublicTimeline, CommunityTimeline, AccountTimeline, AccountGallery, - HomeTimeline, Followers, Following, Reblogs, @@ -87,10 +85,8 @@ const keyMap = { moveDown: ['down', 'j'], moveUp: ['up', 'k'], back: 'backspace', - goToHome: 'g h', goToNotifications: 'g n', goToLocal: 'g l', - goToFederated: 'g t', goToDirect: 'g d', goToStart: 'g s', goToFavourites: 'g f', @@ -176,7 +172,7 @@ class SwitchingColumnsArea extends React.PureComponent { const { children } = this.props; const { mobile } = this.state; const singleColumn = forceSingleColumn || mobile; - const redirect = singleColumn ? <Redirect from='/' to='/timelines/home' exact /> : <Redirect from='/' to='/getting-started' exact />; + const redirect = singleColumn ? <Redirect from='/' to='/timelines/public/local' exact /> : <Redirect from='/' to='/getting-started' exact />; return ( <ColumnsAreaContainer ref={this.setRef} singleColumn={singleColumn}> @@ -184,8 +180,6 @@ class SwitchingColumnsArea extends React.PureComponent { {redirect} <WrappedRoute path='/getting-started' component={GettingStarted} content={children} /> <WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} /> - <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> - <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> <WrappedRoute path='/timelines/public/local' exact component={CommunityTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> <WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index 986efda1ec..8a45231afd 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -14,10 +14,6 @@ export function HomeTimeline () { return import(/* webpackChunkName: "features/home_timeline" */'../../home_timeline'); } -export function PublicTimeline () { - return import(/* webpackChunkName: "features/public_timeline" */'../../public_timeline'); -} - export function CommunityTimeline () { return import(/* webpackChunkName: "features/community_timeline" */'../../community_timeline'); } diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index bd9ebbc0de..155300271a 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -934,11 +934,11 @@ { "descriptors": [ { - "defaultMessage": "Local timeline", + "defaultMessage": "Group timeline", "id": "column.community" }, { - "defaultMessage": "The local timeline is empty. Write something publicly to get the ball rolling!", + "defaultMessage": "The Group timeline is empty. Write something publicly to get the ball rolling!", "id": "empty_column.community" } ], @@ -2930,6 +2930,10 @@ "defaultMessage": "Notifications", "id": "tabs_bar.notifications" }, + { + "defaultMessage": "Group", + "id": "tabs_bar.group_timeline" + }, { "defaultMessage": "Local", "id": "tabs_bar.local_timeline" @@ -3012,6 +3016,10 @@ "defaultMessage": "Notifications", "id": "tabs_bar.notifications" }, + { + "defaultMessage": "Group", + "id": "tabs_bar.group_timeline" + }, { "defaultMessage": "Local", "id": "tabs_bar.local_timeline" @@ -3103,4 +3111,4 @@ ], "path": "app/javascript/mastodon/features/video/index.json" } -] \ No newline at end of file +] diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 381ff028bb..92d02e2ff8 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -59,7 +59,7 @@ "bundle_modal_error.retry": "Try again", "column.blocks": "Blocked users", "column.bookmarks": "Bookmarks", - "column.community": "Local timeline", + "column.community": "Group timeline", "column.direct": "Direct messages", "column.directory": "Browse profiles", "column.domain_blocks": "Blocked domains", @@ -271,7 +271,7 @@ "navigation_bar.apps": "Mobile apps", "navigation_bar.blocks": "Blocked users", "navigation_bar.bookmarks": "Bookmarks", - "navigation_bar.community_timeline": "Local timeline", + "navigation_bar.community_timeline": "Group timeline", "navigation_bar.compose": "Compose new toot", "navigation_bar.direct": "Direct messages", "navigation_bar.discover": "Discover", @@ -410,6 +410,7 @@ "suggestions.dismiss": "Dismiss suggestion", "suggestions.header": "You might be interested in…", "tabs_bar.federated_timeline": "Federated", + "tabs_bar.group_timeline": "Group", "tabs_bar.home": "Home", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notifications", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index ec3d0ee59a..d7900a2a35 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -59,7 +59,7 @@ "bundle_modal_error.retry": "å†è©¦è¡Œ", "column.blocks": "ブãƒãƒƒã‚¯ã—ãŸãƒ¦ãƒ¼ã‚¶ãƒ¼", "column.bookmarks": "ブックマーク", - "column.community": "ãƒãƒ¼ã‚«ãƒ«ã‚¿ã‚¤ãƒ ライン", + "column.community": "グループタイムライン", "column.direct": "ダイレクトメッセージ", "column.directory": "ディレクトリ", "column.domain_blocks": "ブãƒãƒƒã‚¯ã—ãŸãƒ‰ãƒ¡ã‚¤ãƒ³", @@ -271,7 +271,7 @@ "navigation_bar.apps": "アプリ", "navigation_bar.blocks": "ブãƒãƒƒã‚¯ã—ãŸãƒ¦ãƒ¼ã‚¶ãƒ¼", "navigation_bar.bookmarks": "ブックマーク", - "navigation_bar.community_timeline": "ãƒãƒ¼ã‚«ãƒ«ã‚¿ã‚¤ãƒ ライン", + "navigation_bar.community_timeline": "グループタイムライン", "navigation_bar.compose": "ãƒˆã‚¥ãƒ¼ãƒˆã®æ–°è¦ä½œæˆ", "navigation_bar.direct": "ダイレクトメッセージ", "navigation_bar.discover": "見ã¤ã‘ã‚‹", @@ -410,6 +410,7 @@ "suggestions.dismiss": "éš ã™", "suggestions.header": "興味ã‚ã‚‹ã‹ã‚‚ã—れã¾ã›ã‚“…", "tabs_bar.federated_timeline": "連åˆ", + "tabs_bar.group_timeline": "グループ", "tabs_bar.home": "ホーム", "tabs_bar.local_timeline": "ãƒãƒ¼ã‚«ãƒ«", "tabs_bar.notifications": "通知", diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js index efef2ad9a5..1a97705f1d 100644 --- a/app/javascript/mastodon/reducers/settings.js +++ b/app/javascript/mastodon/reducers/settings.js @@ -83,7 +83,6 @@ const initialState = ImmutableMap({ const defaultColumns = fromJS([ { id: 'COMPOSE', uuid: uuid(), params: {} }, - { id: 'HOME', uuid: uuid(), params: {} }, { id: 'NOTIFICATIONS', uuid: uuid(), params: {} }, ]); diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb index a379a7ef43..b05f073655 100644 --- a/app/lib/activitypub/activity.rb +++ b/app/lib/activitypub/activity.rb @@ -3,6 +3,7 @@ class ActivityPub::Activity include JsonLdHelper include Redisable + include RoutingHelper SUPPORTED_TYPES = %w(Note Question).freeze CONVERTED_TYPES = %w(Image Audio Video Article Page Event).freeze diff --git a/app/lib/activitypub/activity/announce.rb b/app/lib/activitypub/activity/announce.rb index 349e8f77e7..51963e6571 100644 --- a/app/lib/activitypub/activity/announce.rb +++ b/app/lib/activitypub/activity/announce.rb @@ -55,7 +55,7 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity end def announceable?(status) - status.account_id == @account.id || status.distributable? + status.account_id == @account.id || status.distributable? || (@account.group? && status.mentions.where(account_id: @account.id).exists?) end def related_to_local_activity? diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index a60b79d159..172cb873b3 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -91,6 +91,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity check_for_spam distribute(@status) forward_for_reply if @status.distributable? + forward_for_group end def find_existing_status @@ -160,10 +161,6 @@ class ActivityPub::Activity::Create < ActivityPub::Activity @status.mentions.create(account: delivered_to_account, silent: true) @status.update(visibility: :limited) if @status.direct_visibility? - - return unless delivered_to_account.following?(@account) - - FeedInsertWorker.perform_async(@status.id, delivered_to_account.id, :home) end def attach_tags(status) @@ -494,6 +491,23 @@ class ActivityPub::Activity::Create < ActivityPub::Activity ActivityPub::RawDistributionWorker.perform_async(Oj.dump(@json), replied_to_status.account_id, [@account.preferred_inbox_url]) end + def forward_for_group + groups = Account.where(id: @status.mentions.pluck(:account_id)).where(actor_type: 'Group') + + groups.each do |group| + if @json['signature'].present? && audience_includes_followers?(group) + ActivityPub::RawDistributionWorker.perform_async(Oj.dump(@json), group.id) + end + + ReblogService.new.call(group, @status, {visibility: @status.visibility}) + end + end + + def audience_includes_followers?(account) + url = account_followers_url(account) + equals_or_includes?(audience_to, url) || equals_or_includes?(audience_cc, url) + end + def increment_voters_count! poll = replied_to_status.preloadable_poll diff --git a/app/models/account.rb b/app/models/account.rb index 6b7ebda9e6..50a219824c 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -50,6 +50,7 @@ # avatar_storage_schema_version :integer # header_storage_schema_version :integer # devices_url :string +# sensitized_at :datetime # class Account < ApplicationRecord @@ -521,7 +522,6 @@ class Account < ApplicationRecord before_create :generate_keys before_validation :prepare_contents, if: :local? before_validation :prepare_username, on: :create - before_destroy :clean_feed_manager private @@ -551,19 +551,4 @@ class Account < ApplicationRecord def emojifiable_text [note, display_name, fields.map(&:name), fields.map(&:value)].join(' ') end - - def clean_feed_manager - reblog_key = FeedManager.instance.key(:home, id, 'reblogs') - reblogged_id_set = Redis.current.zrange(reblog_key, 0, -1) - - Redis.current.pipelined do - Redis.current.del(FeedManager.instance.key(:home, id)) - Redis.current.del(reblog_key) - - reblogged_id_set.each do |reblogged_id| - reblog_set_key = FeedManager.instance.key(:home, id, "reblogs:#{reblogged_id}") - Redis.current.del(reblog_set_key) - end - end - end end diff --git a/app/models/account_stat.rb b/app/models/account_stat.rb index c84e4217c8..78c61be95c 100644 --- a/app/models/account_stat.rb +++ b/app/models/account_stat.rb @@ -3,15 +3,16 @@ # # Table name: account_stats # -# id :bigint(8) not null, primary key -# account_id :bigint(8) not null -# statuses_count :bigint(8) default(0), not null -# following_count :bigint(8) default(0), not null -# followers_count :bigint(8) default(0), not null -# created_at :datetime not null -# updated_at :datetime not null -# last_status_at :datetime -# lock_version :integer default(0), not null +# id :bigint(8) not null, primary key +# account_id :bigint(8) not null +# statuses_count :bigint(8) default(0), not null +# following_count :bigint(8) default(0), not null +# followers_count :bigint(8) default(0), not null +# created_at :datetime not null +# updated_at :datetime not null +# last_status_at :datetime +# lock_version :integer default(0), not null +# subscribing_count :bigint(8) default(0), not null # class AccountStat < ApplicationRecord diff --git a/app/models/follow.rb b/app/models/follow.rb index f3e48a2ed7..272535ee7e 100644 --- a/app/models/follow.rb +++ b/app/models/follow.rb @@ -10,6 +10,7 @@ # target_account_id :bigint(8) not null # show_reblogs :boolean default(TRUE), not null # uri :string +# delivery :boolean default(TRUE), not null # class Follow < ApplicationRecord diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb index 3325e264cc..59a68e7a02 100644 --- a/app/models/follow_request.rb +++ b/app/models/follow_request.rb @@ -10,6 +10,7 @@ # target_account_id :bigint(8) not null # show_reblogs :boolean default(TRUE), not null # uri :string +# delivery :boolean default(TRUE), not null # class FollowRequest < ApplicationRecord @@ -29,7 +30,6 @@ class FollowRequest < ApplicationRecord def authorize! account.follow!(target_account, reblogs: show_reblogs, uri: uri) - MergeWorker.perform_async(target_account.id, account.id) if account.local? destroy! end diff --git a/app/models/list.rb b/app/models/list.rb index c9c94fca1d..019a9523ff 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -25,23 +25,4 @@ class List < ApplicationRecord validates_each :account_id, on: :create do |record, _attr, value| record.errors.add(:base, I18n.t('lists.errors.limit')) if List.where(account_id: value).count >= PER_ACCOUNT_LIMIT end - - before_destroy :clean_feed_manager - - private - - def clean_feed_manager - reblog_key = FeedManager.instance.key(:list, id, 'reblogs') - reblogged_id_set = Redis.current.zrange(reblog_key, 0, -1) - - Redis.current.pipelined do - Redis.current.del(FeedManager.instance.key(:list, id)) - Redis.current.del(reblog_key) - - reblogged_id_set.each do |reblogged_id| - reblog_set_key = FeedManager.instance.key(:list, id, "reblogs:#{reblogged_id}") - Redis.current.del(reblog_set_key) - end - end - end end diff --git a/app/models/mention.rb b/app/models/mention.rb index d01a88e32e..657a6342b3 100644 --- a/app/models/mention.rb +++ b/app/models/mention.rb @@ -21,6 +21,7 @@ class Mention < ApplicationRecord scope :active, -> { where(silent: false) } scope :silent, -> { where(silent: true) } + scope :groups, -> { joins(:account).where(accounts: { actor_type: :Group }) } delegate( :username, diff --git a/app/models/mute.rb b/app/models/mute.rb index 0e00c2278f..1b034fee33 100644 --- a/app/models/mute.rb +++ b/app/models/mute.rb @@ -6,9 +6,11 @@ # id :bigint(8) not null, primary key # created_at :datetime not null # updated_at :datetime not null +# hide_notifications :boolean default(TRUE), not null # account_id :bigint(8) not null # target_account_id :bigint(8) not null -# hide_notifications :boolean default(TRUE), not null +# expires_at :datetime +# unmute_jid :string # class Mute < ApplicationRecord diff --git a/app/models/status.rb b/app/models/status.rb index 71596ec2f8..27ffcd7987 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -22,7 +22,10 @@ # application_id :bigint(8) # in_reply_to_account_id :bigint(8) # poll_id :bigint(8) +# quote_id :bigint(8) # deleted_at :datetime +# expires_at :datetime +# expires_action :integer default(0), not null # class Status < ApplicationRecord @@ -90,6 +93,7 @@ class Status < ApplicationRecord scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') } scope :without_reblogs, -> { where('statuses.reblog_of_id IS NULL') } scope :with_public_visibility, -> { where(visibility: :public) } + scope :with_distributable_visibility, -> { where(visibility: [:public, :unlisted]) } scope :tagged_with, ->(tag) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag }) } scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced_at: nil }) } scope :including_silenced_accounts, -> { left_outer_joins(:account).where.not(accounts: { silenced_at: nil }) } @@ -282,7 +286,7 @@ class Status < ApplicationRecord end def as_public_timeline(account = nil, local_only = false) - query = timeline_scope(local_only).without_replies + query = timeline_scope([:local, true].include?(local_only) ? :local : :none) apply_timeline_filters(query, account, [:local, true].include?(local_only)) end @@ -382,13 +386,15 @@ class Status < ApplicationRecord Status.local when :remote Status.remote + when :none + Status.none else Status end starting_scope - .with_public_visibility - .without_reblogs + .with_distributable_visibility + end def apply_timeline_filters(query, account, local_only) diff --git a/app/models/user.rb b/app/models/user.rb index 306e2d4355..16cbdac440 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -391,7 +391,6 @@ class User < ApplicationRecord def prepare_returning_user! ActivityTracker.record('activity:logins', id) - regenerate_feed! if needs_feed_update? end def notify_staff_about_pending_account! @@ -401,16 +400,6 @@ class User < ApplicationRecord end end - def regenerate_feed! - return unless Redis.current.setnx("account:#{account_id}:regeneration", true) - Redis.current.expire("account:#{account_id}:regeneration", 1.day.seconds) - RegenerationWorker.perform_async(account_id) - end - - def needs_feed_update? - last_sign_in_at < ACTIVE_DURATION.ago - end - def validate_email_dns? email_changed? && !(Rails.env.test? || Rails.env.development?) end diff --git a/app/services/after_block_service.rb b/app/services/after_block_service.rb index 2a0e10a79a..7e90f7d718 100644 --- a/app/services/after_block_service.rb +++ b/app/services/after_block_service.rb @@ -5,17 +5,12 @@ class AfterBlockService < BaseService @account = account @target_account = target_account - clear_home_feed! clear_notifications! clear_conversations! end private - def clear_home_feed! - FeedManager.instance.clear_from_timeline(@account, @target_account) - end - def clear_conversations! AccountConversation.where(account: @account).where('? = ANY(participant_account_ids)', @target_account.id).in_batches.destroy_all end diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb index 2295a01dc3..b3cfdd84d0 100644 --- a/app/services/batched_remove_status_service.rb +++ b/app/services/batched_remove_status_service.rb @@ -27,16 +27,6 @@ class BatchedRemoveStatusService < BaseService return if options[:skip_side_effects] - # Batch by source account - statuses.group_by(&:account_id).each_value do |account_statuses| - account = account_statuses.first.account - - next unless account - - unpush_from_home_timelines(account, account_statuses) - unpush_from_list_timelines(account, account_statuses) - end - # Cannot be batched statuses.each do |status| unpush_from_public_timelines(status) @@ -45,50 +35,22 @@ class BatchedRemoveStatusService < BaseService private - def unpush_from_home_timelines(account, statuses) - recipients = account.followers_for_local_distribution.to_a - - recipients << account if account.local? - - recipients.each do |follower| - statuses.each do |status| - FeedManager.instance.unpush_from_home(follower, status) - end - end - end - - def unpush_from_list_timelines(account, statuses) - account.lists_for_local_distribution.select(:id, :account_id).each do |list| - statuses.each do |status| - FeedManager.instance.unpush_from_list(list, status) - end - end - end - def unpush_from_public_timelines(status) - return unless status.public_visibility? + return unless status.distributable? payload = @json_payloads[status.id] redis.pipelined do - redis.publish('timeline:public', payload) if status.local? redis.publish('timeline:public:local', payload) - else - redis.publish('timeline:public:remote', payload) - end - if status.media_attachments.any? - redis.publish('timeline:public:media', payload) - if status.local? - redis.publish('timeline:public:local:media', payload) - else - redis.publish('timeline:public:remote:media', payload) - end + redis.publish('timeline:public:local:media', payload) if status.media_attachments.any? end - @tags[status.id].each do |hashtag| - redis.publish("timeline:hashtag:#{hashtag.mb_chars.downcase}", payload) - redis.publish("timeline:hashtag:#{hashtag.mb_chars.downcase}:local", payload) if status.local? + if status.public_visibility? + @tags[status.id].each do |hashtag| + redis.publish("timeline:hashtag:#{hashtag.mb_chars.downcase}", payload) + redis.publish("timeline:hashtag:#{hashtag.mb_chars.downcase}:local", payload) if status.local? + end end end end diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index e05d02cef4..5714238763 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -8,22 +8,13 @@ class FanOutOnWriteService < BaseService if status.direct_visibility? deliver_to_own_conversation(status) - elsif status.limited_visibility? - deliver_to_mentioned_followers(status) - else - deliver_to_self(status) if status.account.local? - deliver_to_followers(status) - deliver_to_lists(status) end - return if status.account.silenced? || !status.public_visibility? || status.reblog? + return if status.account.silenced? || !status.distributable? render_anonymous_payload(status) deliver_to_hashtags(status) - - return if status.reply? && status.in_reply_to_account_id != status.account_id - deliver_to_public(status) deliver_to_media(status) if status.media_attachments.any? end @@ -82,22 +73,16 @@ class FanOutOnWriteService < BaseService def deliver_to_public(status) Rails.logger.debug "Delivering status #{status.id} to public timeline" - Redis.current.publish('timeline:public', @payload) if status.local? Redis.current.publish('timeline:public:local', @payload) - else - Redis.current.publish('timeline:public:remote', @payload) end end def deliver_to_media(status) Rails.logger.debug "Delivering status #{status.id} to media timeline" - Redis.current.publish('timeline:public:media', @payload) if status.local? Redis.current.publish('timeline:public:local:media', @payload) - else - Redis.current.publish('timeline:public:remote:media', @payload) end end diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb index 311ae7fa68..6bb89849d5 100644 --- a/app/services/follow_service.rb +++ b/app/services/follow_service.rb @@ -68,7 +68,6 @@ class FollowService < BaseService follow = @source_account.follow!(@target_account, reblogs: @options[:reblogs], rate_limit: @options[:with_rate_limit]) LocalNotificationWorker.perform_async(@target_account.id, follow.id, follow.class.name) - MergeWorker.perform_async(@target_account.id, @source_account.id) follow end diff --git a/app/services/mute_service.rb b/app/services/mute_service.rb index 676804cb99..f146f1a730 100644 --- a/app/services/mute_service.rb +++ b/app/services/mute_service.rb @@ -9,7 +9,6 @@ class MuteService < BaseService if mute.hide_notifications? BlockWorker.perform_async(account.id, target_account.id) else - MuteWorker.perform_async(account.id, target_account.id) end mute diff --git a/app/services/precompute_feed_service.rb b/app/services/precompute_feed_service.rb deleted file mode 100644 index 076dedacab..0000000000 --- a/app/services/precompute_feed_service.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -class PrecomputeFeedService < BaseService - def call(account) - FeedManager.instance.populate_feed(account) - ensure - Redis.current.del("account:#{account.id}:regeneration") - end -end diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index 8e260811d3..1776f12458 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -13,6 +13,13 @@ class ProcessMentionsService < BaseService @status = status mentions = [] + if status.distributable? + mentioned_account = Account.find_local(ENV.fetch('DEFAULT_GROUP')) + if !mentioned_account.nil? && mentioned_account.group? + mentions << mentioned_account.mentions.where(status: status).first_or_create(status: status) + end + end + status.text = status.text.gsub(Account::MENTION_RE) do |match| username, domain = Regexp.last_match(1).split('@') @@ -58,7 +65,13 @@ class ProcessMentionsService < BaseService mentioned_account = mention.account if mentioned_account.local? - LocalNotificationWorker.perform_async(mentioned_account.id, mention.id, mention.class.name) + if mentioned_account.group? + ActivityPub::DeliveryWorker.push_bulk(mentioned_account.followers.inboxes) do |inbox_url| + [activitypub_json, mentioned_account.id, inbox_url] + end + else + LocalNotificationWorker.perform_async(mentioned_account.id, mention.id, mention.class.name) + end elsif mentioned_account.activitypub? ActivityPub::DeliveryWorker.perform_async(activitypub_json, mention.status.account_id, mentioned_account.inbox_url) end diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index 4f0edc3cfb..54add3d790 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -21,9 +21,6 @@ class RemoveStatusService < BaseService RedisLock.acquire(lock_options) do |lock| if lock.acquired? - remove_from_self if status.account.local? - remove_from_followers - remove_from_lists remove_from_affected remove_reblogs remove_from_hashtags @@ -51,22 +48,6 @@ class RemoveStatusService < BaseService private - def remove_from_self - FeedManager.instance.unpush_from_home(@account, @status) - end - - def remove_from_followers - @account.followers_for_local_distribution.reorder(nil).find_each do |follower| - FeedManager.instance.unpush_from_home(follower, @status) - end - end - - def remove_from_lists - @account.lists_for_local_distribution.select(:id, :account_id).reorder(nil).find_each do |list| - FeedManager.instance.unpush_from_list(list, @status) - end - end - def remove_from_affected @mentions.map(&:account).select(&:local?).each do |account| redis.publish("timeline:#{account.id}", @payload) @@ -137,24 +118,18 @@ class RemoveStatusService < BaseService end def remove_from_public - return unless @status.public_visibility? + return unless @status.distributable? - redis.publish('timeline:public', @payload) if @status.local? redis.publish('timeline:public:local', @payload) - else - redis.publish('timeline:public:remote', @payload) end end def remove_from_media - return unless @status.public_visibility? + return unless @status.distributable? - redis.publish('timeline:public:media', @payload) if @status.local? redis.publish('timeline:public:local:media', @payload) - else - redis.publish('timeline:public:remote:media', @payload) end end diff --git a/app/services/unfollow_service.rb b/app/services/unfollow_service.rb index 151f3674fd..7af3a7a093 100644 --- a/app/services/unfollow_service.rb +++ b/app/services/unfollow_service.rb @@ -27,7 +27,6 @@ class UnfollowService < BaseService create_notification(follow) if !@target_account.local? && @target_account.activitypub? create_reject_notification(follow) if @target_account.local? && !@source_account.local? && @source_account.activitypub? - UnmergeWorker.perform_async(@target_account.id, @source_account.id) unless @options[:skip_unmerge] follow end diff --git a/app/services/unmute_service.rb b/app/services/unmute_service.rb index 6aeea358f7..ed268b7c58 100644 --- a/app/services/unmute_service.rb +++ b/app/services/unmute_service.rb @@ -5,7 +5,5 @@ class UnmuteService < BaseService return unless account.muting?(target_account) account.unmute!(target_account) - - MergeWorker.perform_async(target_account.id, account.id) if account.following?(target_account) end end diff --git a/app/workers/feed_insert_worker.rb b/app/workers/feed_insert_worker.rb deleted file mode 100644 index 1ae3c877b0..0000000000 --- a/app/workers/feed_insert_worker.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -class FeedInsertWorker - include Sidekiq::Worker - - def perform(status_id, id, type = :home) - @type = type.to_sym - @status = Status.find(status_id) - - case @type - when :home - @follower = Account.find(id) - when :list - @list = List.find(id) - @follower = @list.account - end - - check_and_insert - rescue ActiveRecord::RecordNotFound - true - end - - private - - def check_and_insert - perform_push unless feed_filtered? - end - - def feed_filtered? - # Note: Lists are a variation of home, so the filtering rules - # of home apply to both - FeedManager.instance.filter?(:home, @status, @follower.id) - end - - def perform_push - case @type - when :home - FeedManager.instance.push_to_home(@follower, @status) - when :list - FeedManager.instance.push_to_list(@list, @status) - end - end -end diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb deleted file mode 100644 index d745cb99c7..0000000000 --- a/app/workers/merge_worker.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -class MergeWorker - include Sidekiq::Worker - - sidekiq_options queue: 'pull' - - def perform(from_account_id, into_account_id) - FeedManager.instance.merge_into_timeline(Account.find(from_account_id), Account.find(into_account_id)) - end -end diff --git a/app/workers/mute_worker.rb b/app/workers/mute_worker.rb deleted file mode 100644 index 7bf0923a5d..0000000000 --- a/app/workers/mute_worker.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -class MuteWorker - include Sidekiq::Worker - - def perform(account_id, target_account_id) - FeedManager.instance.clear_from_timeline( - Account.find(account_id), - Account.find(target_account_id) - ) - end -end diff --git a/app/workers/regeneration_worker.rb b/app/workers/regeneration_worker.rb deleted file mode 100644 index 5c13c894fe..0000000000 --- a/app/workers/regeneration_worker.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -class RegenerationWorker - include Sidekiq::Worker - - sidekiq_options lock: :until_executed - - def perform(account_id, _ = :home) - account = Account.find(account_id) - PrecomputeFeedService.new.call(account) - rescue ActiveRecord::RecordNotFound - true - end -end diff --git a/app/workers/scheduler/feed_cleanup_scheduler.rb b/app/workers/scheduler/feed_cleanup_scheduler.rb deleted file mode 100644 index 458fe6193e..0000000000 --- a/app/workers/scheduler/feed_cleanup_scheduler.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -class Scheduler::FeedCleanupScheduler - include Sidekiq::Worker - include Redisable - - sidekiq_options lock: :until_executed, retry: 0 - - def perform - clean_home_feeds! - clean_list_feeds! - end - - private - - def clean_home_feeds! - clean_feeds!(inactive_account_ids, :home) - end - - def clean_list_feeds! - clean_feeds!(inactive_list_ids, :list) - end - - def clean_feeds!(ids, type) - reblogged_id_sets = {} - - redis.pipelined do - ids.each do |feed_id| - redis.del(feed_manager.key(type, feed_id)) - reblog_key = feed_manager.key(type, feed_id, 'reblogs') - # We collect a future for this: we don't block while getting - # it, but we can iterate over it later. - reblogged_id_sets[feed_id] = redis.zrange(reblog_key, 0, -1) - redis.del(reblog_key) - end - end - - # Remove all of the reblog tracking keys we just removed the - # references to. - redis.pipelined do - reblogged_id_sets.each do |feed_id, future| - future.value.each do |reblogged_id| - reblog_set_key = feed_manager.key(type, feed_id, "reblogs:#{reblogged_id}") - redis.del(reblog_set_key) - end - end - end - end - - def inactive_account_ids - @inactive_account_ids ||= User.confirmed.inactive.pluck(:account_id) - end - - def inactive_list_ids - List.where(account_id: inactive_account_ids).pluck(:id) - end - - def feed_manager - FeedManager.instance - end -end diff --git a/app/workers/unmerge_worker.rb b/app/workers/unmerge_worker.rb deleted file mode 100644 index ea6aacebf6..0000000000 --- a/app/workers/unmerge_worker.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -class UnmergeWorker - include Sidekiq::Worker - - sidekiq_options queue: 'pull' - - def perform(from_account_id, into_account_id) - FeedManager.instance.unmerge_from_timeline(Account.find(from_account_id), Account.find(into_account_id)) - end -end diff --git a/config/sidekiq.yml b/config/sidekiq.yml index 5de25de234..0f6382cd8d 100644 --- a/config/sidekiq.yml +++ b/config/sidekiq.yml @@ -15,9 +15,6 @@ media_cleanup_scheduler: cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *' class: Scheduler::MediaCleanupScheduler - feed_cleanup_scheduler: - cron: '<%= Random.rand(0..59) %> <%= Random.rand(0..2) %> * * *' - class: Scheduler::FeedCleanupScheduler doorkeeper_cleanup_scheduler: cron: '<%= Random.rand(0..59) %> <%= Random.rand(0..2) %> * * 0' class: Scheduler::DoorkeeperCleanupScheduler diff --git a/db/schema.rb b/db/schema.rb index cf31b6d23d..0dd192ce3d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -105,9 +105,22 @@ ActiveRecord::Schema.define(version: 2020_06_28_133322) do t.datetime "updated_at", null: false t.datetime "last_status_at" t.integer "lock_version", default: 0, null: false + t.bigint "subscribing_count", default: 0, null: false t.index ["account_id"], name: "index_account_stats_on_account_id", unique: true end + create_table "account_subscribes", force: :cascade do |t| + t.bigint "account_id" + t.bigint "target_account_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "list_id" + t.boolean "show_reblogs", default: true, null: false + t.index ["account_id"], name: "index_account_subscribes_on_account_id" + t.index ["list_id"], name: "index_account_subscribes_on_list_id" + t.index ["target_account_id"], name: "index_account_subscribes_on_target_account_id" + end + create_table "account_tag_stats", force: :cascade do |t| t.bigint "tag_id", null: false t.bigint "accounts_count", default: 0, null: false @@ -182,6 +195,7 @@ ActiveRecord::Schema.define(version: 2020_06_28_133322) do t.integer "avatar_storage_schema_version" t.integer "header_storage_schema_version" t.string "devices_url" + t.datetime "sensitized_at" t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin t.index "lower((username)::text), COALESCE(lower((domain)::text), ''::text)", name: "index_accounts_on_username_and_domain_lower", unique: true t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id" @@ -246,11 +260,11 @@ ActiveRecord::Schema.define(version: 2020_06_28_133322) do t.bigint "user_id" t.string "dump_file_name" t.string "dump_content_type" + t.bigint "dump_file_size" t.datetime "dump_updated_at" t.boolean "processed", default: false, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.bigint "dump_file_size" end create_table "blocks", force: :cascade do |t| @@ -354,6 +368,38 @@ ActiveRecord::Schema.define(version: 2020_06_28_133322) do t.index ["domain"], name: "index_domain_blocks_on_domain", unique: true end + create_table "domain_subscribes", force: :cascade do |t| + t.bigint "account_id" + t.bigint "list_id" + t.string "domain", default: "", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "exclude_reblog", default: true + t.index ["account_id"], name: "index_domain_subscribes_on_account_id" + t.index ["list_id"], name: "index_domain_subscribes_on_list_id" + end + + create_table "domains", force: :cascade do |t| + t.string "domain", default: "", null: false + t.string "title", default: "", null: false + t.string "short_description", default: "", null: false + t.string "email", default: "", null: false + t.string "version", default: "", null: false + t.string "thumbnail_remote_url", default: "", null: false + t.string "languages", array: true + t.boolean "registrations" + t.boolean "approval_required" + t.bigint "contact_account_id" + t.string "software", default: "", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "thumbnail_file_name" + t.string "thumbnail_content_type" + t.integer "thumbnail_file_size" + t.datetime "thumbnail_updated_at" + t.index ["contact_account_id"], name: "index_domains_on_contact_account_id" + end + create_table "email_domain_blocks", force: :cascade do |t| t.string "domain", default: "", null: false t.datetime "created_at", null: false @@ -376,6 +422,15 @@ ActiveRecord::Schema.define(version: 2020_06_28_133322) do t.index ["from_account_id"], name: "index_encrypted_messages_on_from_account_id" end + create_table "favourite_tags", force: :cascade do |t| + t.bigint "account_id" + t.bigint "tag_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["account_id"], name: "index_favourite_tags_on_account_id" + t.index ["tag_id"], name: "index_favourite_tags_on_tag_id" + end + create_table "favourites", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false @@ -404,9 +459,21 @@ ActiveRecord::Schema.define(version: 2020_06_28_133322) do t.bigint "target_account_id", null: false t.boolean "show_reblogs", default: true, null: false t.string "uri" + t.boolean "delivery", default: true, null: false t.index ["account_id", "target_account_id"], name: "index_follow_requests_on_account_id_and_target_account_id", unique: true end + create_table "follow_tags", force: :cascade do |t| + t.bigint "account_id" + t.bigint "tag_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "list_id" + t.index ["account_id"], name: "index_follow_tags_on_account_id" + t.index ["list_id"], name: "index_follow_tags_on_list_id" + t.index ["tag_id"], name: "index_follow_tags_on_tag_id" + end + create_table "follows", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false @@ -414,6 +481,7 @@ ActiveRecord::Schema.define(version: 2020_06_28_133322) do t.bigint "target_account_id", null: false t.boolean "show_reblogs", default: true, null: false t.string "uri" + t.boolean "delivery", default: true, null: false t.index ["account_id", "target_account_id"], name: "index_follows_on_account_id_and_target_account_id", unique: true t.index ["target_account_id"], name: "index_follows_on_target_account_id" end @@ -454,6 +522,22 @@ ActiveRecord::Schema.define(version: 2020_06_28_133322) do t.index ["user_id"], name: "index_invites_on_user_id" end + create_table "keyword_subscribes", force: :cascade do |t| + t.bigint "account_id" + t.string "keyword", null: false + t.boolean "ignorecase", default: true + t.boolean "regexp", default: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "name", default: "", null: false + t.boolean "ignore_block", default: false + t.boolean "disabled", default: false + t.string "exclude_keyword", default: "", null: false + t.bigint "list_id" + t.index ["account_id"], name: "index_keyword_subscribes_on_account_id" + t.index ["list_id"], name: "index_keyword_subscribes_on_list_id" + end + create_table "list_accounts", force: :cascade do |t| t.bigint "list_id", null: false t.bigint "account_id", null: false @@ -526,6 +610,8 @@ ActiveRecord::Schema.define(version: 2020_06_28_133322) do t.boolean "hide_notifications", default: true, null: false t.bigint "account_id", null: false t.bigint "target_account_id", null: false + t.datetime "expires_at" + t.string "unmute_jid" t.index ["account_id", "target_account_id"], name: "index_mutes_on_account_id_and_target_account_id", unique: true t.index ["target_account_id"], name: "index_mutes_on_target_account_id" end @@ -780,14 +866,19 @@ ActiveRecord::Schema.define(version: 2020_06_28_133322) do t.bigint "application_id" t.bigint "in_reply_to_account_id" t.bigint "poll_id" + t.bigint "quote_id" t.datetime "deleted_at" + t.datetime "expires_at" + t.integer "expires_action", default: 0, null: false t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20190820", order: { id: :desc }, where: "(deleted_at IS NULL)" t.index ["id", "account_id"], name: "index_statuses_local_20190824", order: { id: :desc }, where: "((local OR (uri IS NULL)) AND (deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))" t.index ["id", "account_id"], name: "index_statuses_public_20200119", order: { id: :desc }, where: "((deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))" t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id" t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id" + t.index ["quote_id"], name: "index_statuses_on_quote_id" t.index ["reblog_of_id", "account_id"], name: "index_statuses_on_reblog_of_id_and_account_id" t.index ["uri"], name: "index_statuses_on_uri", unique: true + t.index ["url"], name: "index_statuses_on_url" end create_table "statuses_tags", id: false, force: :cascade do |t| @@ -923,6 +1014,9 @@ ActiveRecord::Schema.define(version: 2020_06_28_133322) do add_foreign_key "account_pins", "accounts", column: "target_account_id", on_delete: :cascade add_foreign_key "account_pins", "accounts", on_delete: :cascade add_foreign_key "account_stats", "accounts", on_delete: :cascade + add_foreign_key "account_subscribes", "accounts", column: "target_account_id", on_delete: :cascade + add_foreign_key "account_subscribes", "accounts", on_delete: :cascade + add_foreign_key "account_subscribes", "lists", on_delete: :cascade add_foreign_key "account_tag_stats", "tags", on_delete: :cascade add_foreign_key "account_warnings", "accounts", column: "target_account_id", on_delete: :cascade add_foreign_key "account_warnings", "accounts", on_delete: :nullify @@ -943,20 +1037,30 @@ ActiveRecord::Schema.define(version: 2020_06_28_133322) do add_foreign_key "custom_filters", "accounts", on_delete: :cascade add_foreign_key "devices", "accounts", on_delete: :cascade add_foreign_key "devices", "oauth_access_tokens", column: "access_token_id", on_delete: :cascade + add_foreign_key "domain_subscribes", "accounts", on_delete: :cascade + add_foreign_key "domain_subscribes", "lists", on_delete: :cascade + add_foreign_key "domains", "accounts", column: "contact_account_id" add_foreign_key "email_domain_blocks", "email_domain_blocks", column: "parent_id", on_delete: :cascade add_foreign_key "encrypted_messages", "accounts", column: "from_account_id", on_delete: :cascade add_foreign_key "encrypted_messages", "devices", on_delete: :cascade + add_foreign_key "favourite_tags", "accounts", on_delete: :cascade + add_foreign_key "favourite_tags", "tags", on_delete: :cascade add_foreign_key "favourites", "accounts", name: "fk_5eb6c2b873", on_delete: :cascade add_foreign_key "favourites", "statuses", name: "fk_b0e856845e", on_delete: :cascade add_foreign_key "featured_tags", "accounts", on_delete: :cascade add_foreign_key "featured_tags", "tags", on_delete: :cascade add_foreign_key "follow_requests", "accounts", column: "target_account_id", name: "fk_9291ec025d", on_delete: :cascade add_foreign_key "follow_requests", "accounts", name: "fk_76d644b0e7", on_delete: :cascade + add_foreign_key "follow_tags", "accounts", on_delete: :cascade + add_foreign_key "follow_tags", "lists", on_delete: :cascade + add_foreign_key "follow_tags", "tags", on_delete: :cascade add_foreign_key "follows", "accounts", column: "target_account_id", name: "fk_745ca29eac", on_delete: :cascade add_foreign_key "follows", "accounts", name: "fk_32ed1b5560", on_delete: :cascade add_foreign_key "identities", "users", name: "fk_bea040f377", on_delete: :cascade add_foreign_key "imports", "accounts", name: "fk_6db1b6e408", on_delete: :cascade add_foreign_key "invites", "users", on_delete: :cascade + add_foreign_key "keyword_subscribes", "accounts", on_delete: :cascade + add_foreign_key "keyword_subscribes", "lists", on_delete: :cascade add_foreign_key "list_accounts", "accounts", on_delete: :cascade add_foreign_key "list_accounts", "follows", on_delete: :cascade add_foreign_key "list_accounts", "lists", on_delete: :cascade diff --git a/lib/cli.rb b/lib/cli.rb index 9162144cc4..70ed0ad88f 100644 --- a/lib/cli.rb +++ b/lib/cli.rb @@ -4,7 +4,6 @@ require 'thor' require_relative 'mastodon/media_cli' require_relative 'mastodon/emoji_cli' require_relative 'mastodon/accounts_cli' -require_relative 'mastodon/feeds_cli' require_relative 'mastodon/search_cli' require_relative 'mastodon/settings_cli' require_relative 'mastodon/statuses_cli' @@ -30,9 +29,6 @@ module Mastodon desc 'accounts SUBCOMMAND ...ARGS', 'Manage accounts' subcommand 'accounts', Mastodon::AccountsCLI - desc 'feeds SUBCOMMAND ...ARGS', 'Manage feeds' - subcommand 'feeds', Mastodon::FeedsCLI - desc 'search SUBCOMMAND ...ARGS', 'Manage the search engine' subcommand 'search', Mastodon::SearchCLI diff --git a/lib/mastodon/feeds_cli.rb b/lib/mastodon/feeds_cli.rb deleted file mode 100644 index 578ea15c58..0000000000 --- a/lib/mastodon/feeds_cli.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../config/boot' -require_relative '../../config/environment' -require_relative 'cli_helper' - -module Mastodon - class FeedsCLI < Thor - include CLIHelper - - def self.exit_on_failure? - true - end - - option :all, type: :boolean, default: false - option :concurrency, type: :numeric, default: 5, aliases: [:c] - option :verbose, type: :boolean, aliases: [:v] - option :dry_run, type: :boolean, default: false - desc 'build [USERNAME]', 'Build home and list feeds for one or all users' - long_desc <<-LONG_DESC - Build home and list feeds that are stored in Redis from the database. - - With the --all option, all active users will be processed. - Otherwise, a single user specified by USERNAME. - LONG_DESC - def build(username = nil) - dry_run = options[:dry_run] ? '(DRY RUN)' : '' - - if options[:all] || username.nil? - processed, = parallelize_with_progress(Account.joins(:user).merge(User.active)) do |account| - PrecomputeFeedService.new.call(account) unless options[:dry_run] - end - - say("Regenerated feeds for #{processed} accounts #{dry_run}", :green, true) - elsif username.present? - account = Account.find_local(username) - - if account.nil? - say('No such account', :red) - exit(1) - end - - PrecomputeFeedService.new.call(account) unless options[:dry_run] - - say("OK #{dry_run}", :green, true) - else - say('No account(s) given', :red) - exit(1) - end - end - - desc 'clear', 'Remove all home and list feeds from Redis' - def clear - keys = Redis.current.keys('feed:*') - - Redis.current.pipelined do - keys.each { |key| Redis.current.del(key) } - end - - say('OK', :green) - end - end -end diff --git a/spec/controllers/concerns/user_tracking_concern_spec.rb b/spec/controllers/concerns/user_tracking_concern_spec.rb index 1e56202211..dd9ba1c782 100644 --- a/spec/controllers/concerns/user_tracking_concern_spec.rb +++ b/spec/controllers/concerns/user_tracking_concern_spec.rb @@ -43,47 +43,6 @@ describe ApplicationController, type: :controller do expect_updated_sign_in_at(user) end - describe 'feed regeneration' do - before do - alice = Fabricate(:account) - bob = Fabricate(:account) - - user.account.follow!(alice) - user.account.follow!(bob) - - Fabricate(:status, account: alice, text: 'hello world') - Fabricate(:status, account: bob, text: 'yes hello') - Fabricate(:status, account: user.account, text: 'test') - - user.update(last_sign_in_at: 'Tue, 04 Jul 2017 14:45:56 UTC +00:00', current_sign_in_at: 'Wed, 05 Jul 2017 22:10:52 UTC +00:00') - - sign_in user, scope: :user - end - - it 'sets a regeneration marker while regenerating' do - allow(RegenerationWorker).to receive(:perform_async) - get :show - - expect_updated_sign_in_at(user) - expect(Redis.current.get("account:#{user.account_id}:regeneration")).to eq 'true' - expect(RegenerationWorker).to have_received(:perform_async) - end - - it 'sets the regeneration marker to expire' do - allow(RegenerationWorker).to receive(:perform_async) - get :show - expect(Redis.current.ttl("account:#{user.account_id}:regeneration")).to be >= 0 - end - - it 'regenerates feed when sign in is older than two weeks' do - get :show - - expect_updated_sign_in_at(user) - expect(Redis.current.zcard(FeedManager.instance.key(:home, user.account_id))).to eq 3 - expect(Redis.current.get("account:#{user.account_id}:regeneration")).to be_nil - end - end - def expect_updated_sign_in_at(user) expect(user.reload.current_sign_in_at).to be_within(1.0).of(Time.now.utc) end diff --git a/spec/models/follow_request_spec.rb b/spec/models/follow_request_spec.rb deleted file mode 100644 index 2cf28b263c..0000000000 --- a/spec/models/follow_request_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'rails_helper' - -RSpec.describe FollowRequest, type: :model do - describe '#authorize!' do - let(:follow_request) { Fabricate(:follow_request, account: account, target_account: target_account) } - let(:account) { Fabricate(:account) } - let(:target_account) { Fabricate(:account) } - - it 'calls Account#follow!, MergeWorker.perform_async, and #destroy!' do - expect(account).to receive(:follow!).with(target_account, reblogs: true, uri: follow_request.uri) - expect(MergeWorker).to receive(:perform_async).with(target_account.id, account.id) - expect(follow_request).to receive(:destroy!) - follow_request.authorize! - end - - it 'correctly passes show_reblogs when true' do - follow_request = Fabricate.create(:follow_request, show_reblogs: true) - follow_request.authorize! - target = follow_request.target_account - expect(follow_request.account.muting_reblogs?(target)).to be false - end - - it 'correctly passes show_reblogs when false' do - follow_request = Fabricate.create(:follow_request, show_reblogs: false) - follow_request.authorize! - target = follow_request.target_account - expect(follow_request.account.muting_reblogs?(target)).to be true - end - end -end diff --git a/spec/models/home_feed_spec.rb b/spec/models/home_feed_spec.rb deleted file mode 100644 index ee7a83960a..0000000000 --- a/spec/models/home_feed_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -require 'rails_helper' - -RSpec.describe HomeFeed, type: :model do - let(:account) { Fabricate(:account) } - - subject { described_class.new(account) } - - describe '#get' do - before do - Fabricate(:status, account: account, id: 1) - Fabricate(:status, account: account, id: 2) - Fabricate(:status, account: account, id: 3) - Fabricate(:status, account: account, id: 10) - end - - context 'when feed is generated' do - before do - Redis.current.zadd( - FeedManager.instance.key(:home, account.id), - [[4, 4], [3, 3], [2, 2], [1, 1]] - ) - end - - it 'gets statuses with ids in the range from redis' do - results = subject.get(3) - - expect(results.map(&:id)).to eq [3, 2] - expect(results.first.attributes.keys).to eq %w(id updated_at) - end - end - - context 'when feed is being generated' do - before do - Redis.current.set("account:#{account.id}:regeneration", true) - end - - it 'returns nothing' do - results = subject.get(3) - - expect(results.map(&:id)).to eq [] - end - end - end -end diff --git a/spec/services/after_block_service_spec.rb b/spec/services/after_block_service_spec.rb deleted file mode 100644 index f63b2045ad..0000000000 --- a/spec/services/after_block_service_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'rails_helper' - -RSpec.describe AfterBlockService, type: :service do - subject do - -> { described_class.new.call(account, target_account) } - end - - let(:account) { Fabricate(:account) } - let(:target_account) { Fabricate(:account) } - - describe 'home timeline' do - let(:status) { Fabricate(:status, account: target_account) } - let(:other_account_status) { Fabricate(:status) } - let(:home_timeline_key) { FeedManager.instance.key(:home, account.id) } - - before do - Redis.current.del(home_timeline_key) - end - - it "clears account's statuses" do - FeedManager.instance.push_to_home(account, status) - FeedManager.instance.push_to_home(account, other_account_status) - - is_expected.to change { - Redis.current.zrange(home_timeline_key, 0, -1) - }.from([status.id.to_s, other_account_status.id.to_s]).to([other_account_status.id.to_s]) - end - end -end diff --git a/spec/services/mute_service_spec.rb b/spec/services/mute_service_spec.rb index 4bb839b8d4..f1f13bb449 100644 --- a/spec/services/mute_service_spec.rb +++ b/spec/services/mute_service_spec.rb @@ -8,25 +8,6 @@ RSpec.describe MuteService, type: :service do let(:account) { Fabricate(:account) } let(:target_account) { Fabricate(:account) } - describe 'home timeline' do - let(:status) { Fabricate(:status, account: target_account) } - let(:other_account_status) { Fabricate(:status) } - let(:home_timeline_key) { FeedManager.instance.key(:home, account.id) } - - before do - Redis.current.del(home_timeline_key) - end - - it "clears account's statuses" do - FeedManager.instance.push_to_home(account, status) - FeedManager.instance.push_to_home(account, other_account_status) - - is_expected.to change { - Redis.current.zrange(home_timeline_key, 0, -1) - }.from([status.id.to_s, other_account_status.id.to_s]).to([other_account_status.id.to_s]) - end - end - it 'mutes account' do is_expected.to change { account.muting?(target_account) diff --git a/spec/services/precompute_feed_service_spec.rb b/spec/services/precompute_feed_service_spec.rb deleted file mode 100644 index 1f6b6ed883..0000000000 --- a/spec/services/precompute_feed_service_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe PrecomputeFeedService, type: :service do - subject { PrecomputeFeedService.new } - - describe 'call' do - let(:account) { Fabricate(:account) } - it 'fills a user timeline with statuses' do - account = Fabricate(:account) - status = Fabricate(:status, account: account) - - subject.call(account) - - expect(Redis.current.zscore(FeedManager.instance.key(:home, account.id), status.id)).to be_within(0.1).of(status.id.to_f) - end - - it 'does not raise an error even if it could not find any status' do - account = Fabricate(:account) - subject.call(account) - end - - it 'filters statuses' do - account = Fabricate(:account) - muted_account = Fabricate(:account) - Fabricate(:mute, account: account, target_account: muted_account) - reblog = Fabricate(:status, account: muted_account) - status = Fabricate(:status, account: account, reblog: reblog) - - subject.call(account) - - expect(Redis.current.zscore(FeedManager.instance.key(:home, account.id), reblog.id)).to eq nil - end - end -end diff --git a/spec/workers/feed_insert_worker_spec.rb b/spec/workers/feed_insert_worker_spec.rb deleted file mode 100644 index 3509f1f50e..0000000000 --- a/spec/workers/feed_insert_worker_spec.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe FeedInsertWorker do - subject { described_class.new } - - describe 'perform' do - let(:follower) { Fabricate(:account) } - let(:status) { Fabricate(:status) } - - context 'when there are no records' do - it 'skips push with missing status' do - instance = double(push_to_home: nil) - allow(FeedManager).to receive(:instance).and_return(instance) - result = subject.perform(nil, follower.id) - - expect(result).to eq true - expect(instance).not_to have_received(:push_to_home) - end - - it 'skips push with missing account' do - instance = double(push_to_home: nil) - allow(FeedManager).to receive(:instance).and_return(instance) - result = subject.perform(status.id, nil) - - expect(result).to eq true - expect(instance).not_to have_received(:push_to_home) - end - end - - context 'when there are real records' do - it 'skips the push when there is a filter' do - instance = double(push_to_home: nil, filter?: true) - allow(FeedManager).to receive(:instance).and_return(instance) - result = subject.perform(status.id, follower.id) - - expect(result).to be_nil - expect(instance).not_to have_received(:push_to_home) - end - - it 'pushes the status onto the home timeline without filter' do - instance = double(push_to_home: nil, filter?: false) - allow(FeedManager).to receive(:instance).and_return(instance) - result = subject.perform(status.id, follower.id) - - expect(result).to be_nil - expect(instance).to have_received(:push_to_home).with(follower, status) - end - end - end -end diff --git a/spec/workers/regeneration_worker_spec.rb b/spec/workers/regeneration_worker_spec.rb deleted file mode 100644 index c6bdfa0e5e..0000000000 --- a/spec/workers/regeneration_worker_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe RegenerationWorker do - subject { described_class.new } - - describe 'perform' do - let(:account) { Fabricate(:account) } - - it 'calls the precompute feed service for the account' do - service = double(call: nil) - allow(PrecomputeFeedService).to receive(:new).and_return(service) - result = subject.perform(account.id) - - expect(result).to be_nil - expect(service).to have_received(:call).with(account) - end - - it 'fails when account does not exist' do - result = subject.perform('aaa') - - expect(result).to eq(true) - end - end -end diff --git a/spec/workers/scheduler/feed_cleanup_scheduler_spec.rb b/spec/workers/scheduler/feed_cleanup_scheduler_spec.rb deleted file mode 100644 index 7fae680ba6..0000000000 --- a/spec/workers/scheduler/feed_cleanup_scheduler_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -require 'rails_helper' - -describe Scheduler::FeedCleanupScheduler do - subject { described_class.new } - - let!(:active_user) { Fabricate(:user, current_sign_in_at: 2.days.ago) } - let!(:inactive_user) { Fabricate(:user, current_sign_in_at: 22.days.ago) } - - it 'clears feeds of inactives' do - Redis.current.zadd(feed_key_for(inactive_user), 1, 1) - Redis.current.zadd(feed_key_for(active_user), 1, 1) - Redis.current.zadd(feed_key_for(inactive_user, 'reblogs'), 2, 2) - Redis.current.sadd(feed_key_for(inactive_user, 'reblogs:2'), 3) - - subject.perform - - expect(Redis.current.zcard(feed_key_for(inactive_user))).to eq 0 - expect(Redis.current.zcard(feed_key_for(active_user))).to eq 1 - expect(Redis.current.exists(feed_key_for(inactive_user, 'reblogs'))).to be false - expect(Redis.current.exists(feed_key_for(inactive_user, 'reblogs:2'))).to be false - end - - def feed_key_for(user, subtype = nil) - FeedManager.instance.key(:home, user.account_id, subtype) - end -end -- GitLab