diff --git a/app/assets/javascripts/components/actions/compose.jsx b/app/assets/javascripts/components/actions/compose.jsx index a9fbe6b91..fdb0abdcd 100644 --- a/app/assets/javascripts/components/actions/compose.jsx +++ b/app/assets/javascripts/components/actions/compose.jsx @@ -67,7 +67,7 @@ export function submitCompose() { in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')), sensitive: getState().getIn(['compose', 'sensitive']), - unlisted: getState().getIn(['compose', 'unlisted']) + visibility: getState().getIn(['compose', 'unlisted']) ? 'unlisted' : 'public' }).then(function (response) { dispatch(submitComposeSuccess({ ...response.data })); diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 57f25a273..411a41ccc 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -11,12 +11,12 @@ class AccountsController < ApplicationController def show respond_to do |format| format.html do - @statuses = @account.statuses.order('id desc').paginate_by_max_id(20, params[:max_id], params[:since_id]) + @statuses = @account.statuses.permitted_for(@account, current_account).order('id desc').paginate_by_max_id(20, params[:max_id], params[:since_id]) @statuses = cache_collection(@statuses, Status) end format.atom do - @entries = @account.stream_entries.order('id desc').with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id]) + @entries = @account.stream_entries.order('id desc').where(hidden: false).with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id]) end end end diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index 0abdfd9fa..de53a9602 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -8,8 +8,7 @@ class Api::V1::AccountsController < ApiController respond_to :json - def show - end + def show; end def verify_credentials @account = current_user.account @@ -47,7 +46,7 @@ class Api::V1::AccountsController < ApiController end def statuses - @statuses = @account.statuses.paginate_by_max_id(DEFAULT_STATUSES_LIMIT, params[:max_id], params[:since_id]) + @statuses = @account.statuses.permitted_for(@account, current_account).paginate_by_max_id(DEFAULT_STATUSES_LIMIT, params[:max_id], params[:since_id]) @statuses = cache_collection(@statuses, Status) set_maps(@statuses) diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 453d003da..f7b4ed610 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -52,7 +52,7 @@ class Api::V1::StatusesController < ApiController end def create - @status = PostStatusService.new.call(current_user.account, params[:status], params[:in_reply_to_id].blank? ? nil : Status.find(params[:in_reply_to_id]), media_ids: params[:media_ids], sensitive: params[:sensitive], unlisted: params[:unlisted]) + @status = PostStatusService.new.call(current_user.account, params[:status], params[:in_reply_to_id].blank? ? nil : Status.find(params[:in_reply_to_id]), media_ids: params[:media_ids], sensitive: params[:sensitive], visibility: params[:visibility]) render action: :show end @@ -95,5 +95,6 @@ class Api::V1::StatusesController < ApiController def set_status @status = Status.find(params[:id]) + raise ActiveRecord::RecordNotFound unless @status.permitted?(current_account) end end diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb index 58dd423f7..438d51a84 100644 --- a/app/controllers/stream_entries_controller.rb +++ b/app/controllers/stream_entries_controller.rb @@ -14,8 +14,8 @@ class StreamEntriesController < ApplicationController return gone if @stream_entry.activity.nil? if @stream_entry.activity_type == 'Status' - @ancestors = @stream_entry.activity.ancestors - @descendants = @stream_entry.activity.descendants + @ancestors = @stream_entry.activity.ancestors(current_account) + @descendants = @stream_entry.activity.descendants(current_account) end end @@ -43,7 +43,7 @@ class StreamEntriesController < ApplicationController end def set_stream_entry - @stream_entry = @account.stream_entries.find(params[:id]) + @stream_entry = @account.stream_entries.where(hidden: false).find(params[:id]) @type = @stream_entry.activity_type.downcase end diff --git a/app/models/block.rb b/app/models/block.rb index dc05bce87..ad225d180 100644 --- a/app/models/block.rb +++ b/app/models/block.rb @@ -1,9 +1,31 @@ # frozen_string_literal: true class Block < ApplicationRecord + include Streamable + belongs_to :account belongs_to :target_account, class_name: 'Account' validates :account, :target_account, presence: true validates :account_id, uniqueness: { scope: :target_account_id } + + def verb + destroyed? ? :unblock : :block + end + + def target + target_account + end + + def object_type + :person + end + + def hidden? + true + end + + def title + destroyed? ? "#{account.acct} is no longer blocking #{target_account.acct}" : "#{account.acct} blocked #{target_account.acct}" + end end diff --git a/app/models/concerns/streamable.rb b/app/models/concerns/streamable.rb index d9f5dc4d8..58c15cfbc 100644 --- a/app/models/concerns/streamable.rb +++ b/app/models/concerns/streamable.rb @@ -26,8 +26,12 @@ module Streamable super end + def hidden? + false + end + after_create do - account.stream_entries.create!(activity: self) if account.local? + account.stream_entries.create!(activity: self, hidden: hidden?) if account.local? end end end diff --git a/app/models/status.rb b/app/models/status.rb index e87828e32..603f3b7a2 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -5,7 +5,7 @@ class Status < ApplicationRecord include Streamable include Cacheable - enum visibility: [:public, :unlisted], _suffix: :visibility + enum visibility: [:public, :unlisted, :private], _suffix: :visibility belongs_to :account, inverse_of: :statuses @@ -66,19 +66,19 @@ class Status < ApplicationRecord content end - def reblogs_count - attributes['reblogs_count'] || reblogs.count + def hidden? + private_visibility? end - def favourites_count - attributes['favourites_count'] || favourites.count + def permitted?(other_account = nil) + private_visibility? ? (account.id == other_account&.id || other_account&.following?(account)) : true end def ancestors(account = nil) ids = (Status.find_by_sql(['WITH RECURSIVE search_tree(id, in_reply_to_id, path) AS (SELECT id, in_reply_to_id, ARRAY[id] FROM statuses WHERE id = ? UNION ALL SELECT statuses.id, statuses.in_reply_to_id, path || statuses.id FROM search_tree JOIN statuses ON statuses.id = search_tree.in_reply_to_id WHERE NOT statuses.id = ANY(path)) SELECT id FROM search_tree ORDER BY path DESC', id]) - [self]).pluck(:id) statuses = Status.where(id: ids).with_includes.group_by(&:id) results = ids.map { |id| statuses[id].first } - results = results.reject { |status| account.blocking?(status.account) } unless account.nil? + results = results.reject { |status| filter_from_context?(status, account) } results end @@ -87,7 +87,7 @@ class Status < ApplicationRecord ids = (Status.find_by_sql(['WITH RECURSIVE search_tree(id, path) AS (SELECT id, ARRAY[id] FROM statuses WHERE id = ? UNION ALL SELECT statuses.id, path || statuses.id FROM search_tree JOIN statuses ON statuses.in_reply_to_id = search_tree.id WHERE NOT statuses.id = ANY(path)) SELECT id FROM search_tree ORDER BY path', id]) - [self]).pluck(:id) statuses = Status.where(id: ids).with_includes.group_by(&:id) results = ids.map { |id| statuses[id].first } - results = results.reject { |status| account.blocking?(status.account) } unless account.nil? + results = results.reject { |status| filter_from_context?(status, account) } results end @@ -128,6 +128,14 @@ class Status < ApplicationRecord select('reblog_of_id').where(reblog_of_id: status_ids).where(account_id: account_id).map { |s| [s.reblog_of_id, true] }.to_h end + def permitted_for(target_account, account) + if account&.id == target_account.id || account&.following?(target_account) + self + else + where.not(visibility: :private) + end + end + def reload_stale_associations!(cached_items) account_ids = [] @@ -161,5 +169,12 @@ class Status < ApplicationRecord before_validation do text.strip! self.in_reply_to_account_id = thread.account_id if reply? + self.visibility = :public if visibility.nil? + end + + private + + def filter_from_context?(status, account) + account&.blocking?(status.account) || !status.permitted?(account) end end diff --git a/app/models/stream_entry.rb b/app/models/stream_entry.rb index f6c8f461b..fcc691bef 100644 --- a/app/models/stream_entry.rb +++ b/app/models/stream_entry.rb @@ -9,6 +9,7 @@ class StreamEntry < ApplicationRecord belongs_to :status, foreign_type: 'Status', foreign_key: 'activity_id' belongs_to :follow, foreign_type: 'Follow', foreign_key: 'activity_id' belongs_to :favourite, foreign_type: 'Favourite', foreign_key: 'activity_id' + belongs_to :block, foreign_type: 'Block', foreign_key: 'activity_id' validates :account, :activity, presence: true @@ -29,7 +30,7 @@ class StreamEntry < ApplicationRecord end def targeted? - [:follow, :share, :favorite].include? verb + [:follow, :unfollow, :block, :unblock, :share, :favorite].include? verb end def target @@ -57,7 +58,7 @@ class StreamEntry < ApplicationRecord end def activity - send(activity_type.downcase.to_sym) + !new_record? ? send(activity_type.downcase) : super end private diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index d5204151b..55405c0db 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -10,7 +10,7 @@ class PostStatusService < BaseService # @option [Enumerable] :media_ids Optional array of media IDs to attach # @return [Status] def call(account, text, in_reply_to = nil, options = {}) - status = account.statuses.create!(text: text, thread: in_reply_to, sensitive: options[:sensitive], visibility: options[:unlisted] ? :unlisted : :public) + status = account.statuses.create!(text: text, thread: in_reply_to, sensitive: options[:sensitive], visibility: options[:visibility]) attach_media(status, options[:media_ids]) process_mentions_service.call(status) process_hashtags_service.call(status) diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb index 7d0c90d2f..1a78b8f69 100644 --- a/app/services/reblog_service.rb +++ b/app/services/reblog_service.rb @@ -6,6 +6,8 @@ class ReblogService < BaseService # @param [Status] reblogged_status Status to be reblogged # @return [Status] def call(account, reblogged_status) + raise ActiveRecord::RecordInvalid if reblogged_status.private_visibility? + reblog = account.statuses.create!(reblog: reblogged_status, text: '') DistributionWorker.perform_async(reblog.id) diff --git a/app/views/api/v1/statuses/_show.rabl b/app/views/api/v1/statuses/_show.rabl index 579c47b26..a3391a67e 100644 --- a/app/views/api/v1/statuses/_show.rabl +++ b/app/views/api/v1/statuses/_show.rabl @@ -1,10 +1,10 @@ -attributes :id, :created_at, :in_reply_to_id, :sensitive +attributes :id, :created_at, :in_reply_to_id, :sensitive, :visibility node(:uri) { |status| TagManager.instance.uri_for(status) } node(:content) { |status| Formatter.instance.format(status) } node(:url) { |status| TagManager.instance.url_for(status) } -node(:reblogs_count) { |status| defined?(@reblogs_counts_map) ? (@reblogs_counts_map[status.id] || 0) : status.reblogs_count } -node(:favourites_count) { |status| defined?(@favourites_counts_map) ? (@favourites_counts_map[status.id] || 0) : status.favourites_count } +node(:reblogs_count) { |status| defined?(@reblogs_counts_map) ? (@reblogs_counts_map[status.id] || 0) : status.reblogs.count } +node(:favourites_count) { |status| defined?(@favourites_counts_map) ? (@favourites_counts_map[status.id] || 0) : status.favourites.count } child :account do extends 'api/v1/accounts/show' diff --git a/db/migrate/20161221152630_add_hidden_to_stream_entries.rb b/db/migrate/20161221152630_add_hidden_to_stream_entries.rb new file mode 100644 index 000000000..0d2def7f8 --- /dev/null +++ b/db/migrate/20161221152630_add_hidden_to_stream_entries.rb @@ -0,0 +1,5 @@ +class AddHiddenToStreamEntries < ActiveRecord::Migration[5.0] + def change + add_column :stream_entries, :hidden, :boolean, null: false, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 4f23cf144..706099897 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20161205214545) do +ActiveRecord::Schema.define(version: 20161221152630) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -196,8 +196,9 @@ ActiveRecord::Schema.define(version: 20161205214545) do t.integer "account_id" t.integer "activity_id" t.string "activity_type" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "hidden", default: false, null: false t.index ["account_id"], name: "index_stream_entries_on_account_id", using: :btree t.index ["activity_id", "activity_type"], name: "index_stream_entries_on_activity_id_and_activity_type", using: :btree end diff --git a/public/404.html b/public/404.html index 514a935e2..eecfd6743 100644 --- a/public/404.html +++ b/public/404.html @@ -2,67 +2,42 @@ - The page you were looking for doesn't exist (404) + The page you were looking for doesn't exist + -
+ Mastodon +
-

The page you were looking for doesn't exist.

-

You may have mistyped the address or the page may have moved.

+

The page you were looking for doesn't exist

-

If you are the application owner check the logs for more information.

diff --git a/public/422.html b/public/422.html deleted file mode 100644 index eb3601e71..000000000 --- a/public/422.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - The change you wanted was rejected (422) - - - - - - -
-
-

The change you wanted was rejected.

-

Maybe you tried to change something you didn't have access to.

-
-

If you are the application owner check the logs for more information.

-
- - diff --git a/spec/controllers/api/v1/statuses_controller_spec.rb b/spec/controllers/api/v1/statuses_controller_spec.rb index 9b027daf8..ab918fe50 100644 --- a/spec/controllers/api/v1/statuses_controller_spec.rb +++ b/spec/controllers/api/v1/statuses_controller_spec.rb @@ -97,7 +97,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do end it 'updates the reblogs count' do - expect(status.reblogs_count).to eq 1 + expect(status.reblogs.count).to eq 1 end it 'updates the reblogged attribute' do @@ -126,7 +126,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do end it 'updates the reblogs count' do - expect(status.reblogs_count).to eq 0 + expect(status.reblogs.count).to eq 0 end it 'updates the reblogged attribute' do @@ -146,7 +146,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do end it 'updates the favourites count' do - expect(status.favourites_count).to eq 1 + expect(status.favourites.count).to eq 1 end it 'updates the favourited attribute' do @@ -175,7 +175,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do end it 'updates the favourites count' do - expect(status.favourites_count).to eq 0 + expect(status.favourites.count).to eq 0 end it 'updates the favourited attribute' do