mirror of https://github.com/mastodon/mastodon
Implement data storage + UI behaviours to allow users to block domains
This commit is contained in:
parent
295fc0e612
commit
829b774588
|
@ -0,0 +1,77 @@
|
|||
import api, { getLinks } from '../api'
|
||||
import Immutable from 'immutable';
|
||||
|
||||
export const DOMAIN_BLOCK_REQUEST = 'DOMAIN_BLOCK_REQUEST';
|
||||
export const DOMAIN_BLOCK_SUCCESS = 'DOMAIN_BLOCK_SUCCESS';
|
||||
export const DOMAIN_BLOCK_FAIL = 'DOMAIN_BLOCK_FAIL';
|
||||
|
||||
export const DOMAIN_UNBLOCK_REQUEST = 'DOMAIN_UNBLOCK_REQUEST';
|
||||
export const DOMAIN_UNBLOCK_SUCCESS = 'DOMAIN_UNBLOCK_SUCCESS';
|
||||
export const DOMAIN_UNBLOCK_FAIL = 'DOMAIN_UNBLOCK_FAIL';
|
||||
|
||||
export function blockDomain(domain) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(blockDomainRequest(domain));
|
||||
|
||||
api(getState).post(`/api/v1/domains/block?domain=${domain}`).then(response => {
|
||||
// Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
|
||||
dispatch(blockDomainSuccess(response.data));
|
||||
}).catch(error => {
|
||||
dispatch(blockDomainFail(domain, error));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function unblockDomain(domain) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(unblockDomainRequest(domain));
|
||||
|
||||
api(getState).post(`/api/v1/domains/unblock?domain=${domain}`).then(response => {
|
||||
dispatch(unblockDomainSuccess(response.data));
|
||||
}).catch(error => {
|
||||
dispatch(unblockDomainFail(domain, error));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function blockDomainRequest(id) {
|
||||
return {
|
||||
type: DOMAIN_BLOCK_REQUEST,
|
||||
id
|
||||
};
|
||||
};
|
||||
|
||||
export function blockDomainSuccess(blocked_domains) {
|
||||
return {
|
||||
type: DOMAIN_BLOCK_SUCCESS,
|
||||
blocked_domains: blocked_domains
|
||||
};
|
||||
};
|
||||
|
||||
export function blockDomainFail(error) {
|
||||
return {
|
||||
type: DOMAIN_BLOCK_FAIL,
|
||||
error
|
||||
};
|
||||
};
|
||||
|
||||
export function unblockDomainRequest(id) {
|
||||
return {
|
||||
type: DOMAIN_UNBLOCK_REQUEST,
|
||||
id
|
||||
};
|
||||
};
|
||||
|
||||
export function unblockDomainSuccess(blocked_domains) {
|
||||
return {
|
||||
type: DOMAIN_UNBLOCK_SUCCESS,
|
||||
blocked_domains: blocked_domains
|
||||
};
|
||||
};
|
||||
|
||||
export function unblockDomainFail(error) {
|
||||
return {
|
||||
type: DOMAIN_UNBLOCK_FAIL,
|
||||
error
|
||||
};
|
||||
};
|
|
@ -8,10 +8,11 @@ const messages = defineMessages({
|
|||
mention: { id: 'account.mention', defaultMessage: 'Mention' },
|
||||
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
|
||||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock' },
|
||||
unblock_domain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain' },
|
||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||
block: { id: 'account.block', defaultMessage: 'Block' },
|
||||
block: { id: 'account.block', defaultMessage: 'Block user' },
|
||||
block_domain: { id: 'account.block_domain', defaultMessage: 'Block domain' },
|
||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||
block: { id: 'account.block', defaultMessage: 'Block' }
|
||||
});
|
||||
|
||||
const outerStyle = {
|
||||
|
@ -41,6 +42,7 @@ const ActionBar = React.createClass({
|
|||
me: React.PropTypes.number.isRequired,
|
||||
onFollow: React.PropTypes.func.isRequired,
|
||||
onBlock: React.PropTypes.func.isRequired,
|
||||
onBlockDomain: React.PropTypes.func.isRequired,
|
||||
onMention: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
|
@ -55,6 +57,8 @@ const ActionBar = React.createClass({
|
|||
|
||||
if (account.get('id') === me) {
|
||||
menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
|
||||
} else if (account.getIn(['relationship', 'blocking_domain'])) {
|
||||
// Do not show per account block / unblock if the whole domain is filtered.
|
||||
} else if (account.getIn(['relationship', 'blocking'])) {
|
||||
menu.push({ text: intl.formatMessage(messages.unblock), action: this.props.onBlock });
|
||||
} else if (account.getIn(['relationship', 'following'])) {
|
||||
|
@ -63,6 +67,12 @@ const ActionBar = React.createClass({
|
|||
menu.push({ text: intl.formatMessage(messages.block), action: this.props.onBlock });
|
||||
}
|
||||
|
||||
if (account.getIn(['relationship', 'blocking_domain'])) {
|
||||
menu.push({ text: intl.formatMessage(messages.unblock_domain), action: this.props.onBlockDomain });
|
||||
} else if (account.get('acct').indexOf('@') != -1 /* Is this account from an external instance? */) {
|
||||
menu.push({ text: intl.formatMessage(messages.block_domain), action: this.props.onBlockDomain });
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={outerStyle}>
|
||||
<div style={outerDropdownStyle}>
|
||||
|
|
|
@ -10,6 +10,10 @@ import {
|
|||
fetchAccountTimeline,
|
||||
expandAccountTimeline
|
||||
} from '../../actions/accounts';
|
||||
import {
|
||||
blockDomain,
|
||||
unblockDomain
|
||||
} from '../../actions/domains';
|
||||
import { mentionCompose } from '../../actions/compose';
|
||||
import Header from './components/header';
|
||||
import {
|
||||
|
@ -69,6 +73,24 @@ const Account = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
handleBlockDomain () {
|
||||
let domain = null;
|
||||
const acct = this.props.account.get('acct');
|
||||
if (acct.indexOf('@') == -1) {
|
||||
// same domain as current user... ?
|
||||
// we should not hit here because the UI should not show the option.
|
||||
return;
|
||||
}
|
||||
|
||||
domain = acct.substring(acct.indexOf('@') + 1);
|
||||
|
||||
if (this.props.account.getIn(['relationship', 'blocking_domain'])) {
|
||||
this.props.dispatch(unblockDomain(domain));
|
||||
} else {
|
||||
this.props.dispatch(blockDomain(domain));
|
||||
}
|
||||
},
|
||||
|
||||
handleMention () {
|
||||
this.props.dispatch(mentionCompose(this.props.account));
|
||||
},
|
||||
|
@ -88,7 +110,7 @@ const Account = React.createClass({
|
|||
<Column>
|
||||
<ColumnBackButton />
|
||||
<Header account={account} me={me} onFollow={this.handleFollow} />
|
||||
<ActionBar account={account} me={me} onBlock={this.handleBlock} onMention={this.handleMention} />
|
||||
<ActionBar account={account} me={me} onBlock={this.handleBlock} onBlockDomain={this.handleBlockDomain} onMention={this.handleMention} />
|
||||
|
||||
{this.props.children}
|
||||
</Column>
|
||||
|
|
|
@ -14,10 +14,11 @@ const en = {
|
|||
"account.mention": "Mention",
|
||||
"account.edit_profile": "Edit profile",
|
||||
"account.unblock": "Unblock",
|
||||
"account.unblock_domain": "Unblock domain",
|
||||
"account.unfollow": "Unfollow",
|
||||
"account.block": "Block",
|
||||
"account.block": "Block user",
|
||||
"account.block_domain": "Block domain",
|
||||
"account.follow": "Follow",
|
||||
"account.block": "Block",
|
||||
"account.posts": "Posts",
|
||||
"account.follows": "Follows",
|
||||
"account.followers": "Followers",
|
||||
|
|
|
@ -85,10 +85,11 @@ class Api::V1::AccountsController < ApiController
|
|||
|
||||
def relationships
|
||||
ids = params[:id].is_a?(Enumerable) ? params[:id].map(&:to_i) : [params[:id].to_i]
|
||||
@accounts = Account.where(id: ids).select('id')
|
||||
@accounts = Account.where(id: ids).select('id, domain')
|
||||
@following = Account.following_map(ids, current_user.account_id)
|
||||
@followed_by = Account.followed_by_map(ids, current_user.account_id)
|
||||
@blocking = Account.blocking_map(ids, current_user.account_id)
|
||||
@blocking_domain = Account.blocking_domains_map(Account.where(id: ids).select('domain'), current_user.account_id)
|
||||
end
|
||||
|
||||
def search
|
||||
|
@ -110,6 +111,7 @@ class Api::V1::AccountsController < ApiController
|
|||
@following = Account.following_map([@account.id], current_user.account_id)
|
||||
@followed_by = Account.followed_by_map([@account.id], current_user.account_id)
|
||||
@blocking = Account.blocking_map([@account.id], current_user.account_id)
|
||||
@blocking_domain = Account.blocking_domains_map([@account.domain], current_user.account_id)
|
||||
end
|
||||
|
||||
def cache(raw)
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::DomainsController < ApiController
|
||||
before_action -> { doorkeeper_authorize! :read }
|
||||
before_action -> { doorkeeper_authorize! :write }
|
||||
before_action :require_user!
|
||||
|
||||
respond_to :json
|
||||
|
||||
def blocks
|
||||
@domains = AccountDomainBlock.where(account: current_account)
|
||||
end
|
||||
|
||||
def block
|
||||
AccountDomainBlockService.new.call(current_user.account, params[:domain])
|
||||
|
||||
@domains = AccountDomainBlock.where(account: current_account)
|
||||
render action: :index
|
||||
end
|
||||
|
||||
def unblock
|
||||
AccountDomainUnblockService.new.call(current_user.account, params[:domain])
|
||||
|
||||
@domains = AccountDomainBlock.where(account: current_account)
|
||||
render action: :index
|
||||
end
|
||||
end
|
|
@ -43,6 +43,8 @@ class Account < ApplicationRecord
|
|||
# Block relationships
|
||||
has_many :block_relationships, class_name: 'Block', foreign_key: 'account_id', dependent: :destroy
|
||||
has_many :blocking, -> { order('blocks.id desc') }, through: :block_relationships, source: :target_account
|
||||
has_many :block_domain_relationships, class_name: 'AccountDomainBlock', foreign_key: 'account_id', dependent: :destroy
|
||||
has_many :blocked_domains, -> { order('account_domain_blocks.id desc') }, through: :block_domain_relationships, source: :domain
|
||||
|
||||
has_many :media_attachments, dependent: :destroy
|
||||
|
||||
|
@ -64,6 +66,10 @@ class Account < ApplicationRecord
|
|||
block_relationships.where(target_account: other_account).first_or_create!(target_account: other_account)
|
||||
end
|
||||
|
||||
def block_domain!(domain)
|
||||
block_domain_relationships.where(domain: domain).first_or_create!(domain: domain)
|
||||
end
|
||||
|
||||
def unfollow!(other_account)
|
||||
follow = active_relationships.find_by(target_account: other_account)
|
||||
follow&.destroy
|
||||
|
@ -74,6 +80,11 @@ class Account < ApplicationRecord
|
|||
block&.destroy
|
||||
end
|
||||
|
||||
def unblock_domain!(domain)
|
||||
block = block_domain_relationships.find_by(domain: domain)
|
||||
block&.destroy
|
||||
end
|
||||
|
||||
def following?(other_account)
|
||||
following.include?(other_account)
|
||||
end
|
||||
|
@ -166,6 +177,10 @@ class Account < ApplicationRecord
|
|||
def blocking_map(target_account_ids, account_id)
|
||||
Block.where(target_account_id: target_account_ids).where(account_id: account_id).map { |b| [b.target_account_id, true] }.to_h
|
||||
end
|
||||
|
||||
def blocking_domains_map(domains, account_id)
|
||||
AccountDomainBlock.where(domain: domains).where(account_id: account_id).map { |d| [d.domain, true] }.to_h
|
||||
end
|
||||
end
|
||||
|
||||
before_create do
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccountDomainBlock < ApplicationRecord
|
||||
belongs_to :account
|
||||
validates :domain, presence: true, uniqueness: true
|
||||
|
||||
validates :account_id, uniqueness: { scope: :domain }
|
||||
|
||||
def self.blocked?(account, domain)
|
||||
where(domain: domain, account: account).exists?
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccountDomainBlockService < BaseService
|
||||
def call(account, target_domain)
|
||||
account.block_domain!(target_domain)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccountDomainUnblockService < BaseService
|
||||
def call(account, target_domain)
|
||||
account.unblock_domain!(target_domain)
|
||||
end
|
||||
end
|
|
@ -4,3 +4,4 @@ attribute :id
|
|||
node(:following) { |account| @following[account.id] || false }
|
||||
node(:followed_by) { |account| @followed_by[account.id] || false }
|
||||
node(:blocking) { |account| @blocking[account.id] || false }
|
||||
node(:blocking_domain) { |account| @blocking_domain[account.domain] || false }
|
|
@ -0,0 +1,2 @@
|
|||
collection @domains
|
||||
extends('api/v1/domains/show')
|
|
@ -0,0 +1,3 @@
|
|||
object @domain
|
||||
|
||||
attributes :id, :domain, :account_id
|
|
@ -76,6 +76,14 @@ Rails.application.routes.draw do
|
|||
|
||||
resources :notifications, only: [:index]
|
||||
|
||||
resources :domains, only: [] do
|
||||
collection do
|
||||
get :blocks
|
||||
post :block
|
||||
post :unblock
|
||||
end
|
||||
end
|
||||
|
||||
resources :accounts, only: [:show] do
|
||||
collection do
|
||||
get :relationships
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
class CreateAccountDomainBlocks < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
create_table :account_domain_blocks do |t|
|
||||
t.integer :account_id, null: false
|
||||
t.string :domain, null: false, default: ''
|
||||
|
||||
t.timestamps null: false
|
||||
end
|
||||
|
||||
add_index :account_domain_blocks, [:account_id, :domain], unique: true
|
||||
end
|
||||
end
|
10
db/schema.rb
10
db/schema.rb
|
@ -10,11 +10,19 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20161123093447) do
|
||||
ActiveRecord::Schema.define(version: 20161127152000) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
create_table "account_domain_blocks", force: :cascade do |t|
|
||||
t.integer "account_id", null: false
|
||||
t.string "domain", default: "", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["account_id", "domain"], name: "index_account_domain_blocks_on_account_id_and_domain", unique: true, using: :btree
|
||||
end
|
||||
|
||||
create_table "accounts", force: :cascade do |t|
|
||||
t.string "username", default: "", null: false
|
||||
t.string "domain"
|
||||
|
|
Loading…
Reference in New Issue