commit: 80e02b90e4210b0f4982be6a8e817900143374a5
parent: 6d71044c854c453f499f97af420fe0ed762238d3
Author: Eugen Rochko <eugen@zeonfederated.com>
Date: Wed, 21 Dec 2016 20:00:18 +0100
Private visibility on statuses prevents non-followers from seeing those
Filters out hidden stream entries from Atom feed
Blocks now generate hidden stream entries, can be used to federate blocks
Private statuses cannot be reblogged (generates generic 422 error for now)
POST /api/v1/statuses now takes visibility=(public|unlisted|private) param instead of unlisted boolean
Statuses JSON now contains visibility=(public|unlisted|private) field
Diffstat:
17 files changed, 106 insertions(+), 149 deletions(-)
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -2,67 +2,42 @@
<html lang="en">
<head>
<meta charset="utf-8">
- <title>The page you were looking for doesn't exist (404)</title>
+ <title>The page you were looking for doesn't exist</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
+ <link href="https://fonts.googleapis.com/css?family=Roboto:400" rel="stylesheet">
<style>
- body {
- background-color: #EFEFEF;
- color: #2E2F30;
- text-align: center;
- font-family: arial, sans-serif;
- margin: 0;
- }
+ body {
+ font-family: 'Roboto', sans-serif;
+ background: #282c37;
+ color: #9baec8;
+ text-align: center;
+ margin: 0;
+ padding: 20px;
+ }
- div.dialog {
- width: 95%;
- max-width: 33em;
- margin: 4em auto 0;
- }
+ .dialog img {
+ display: block;
+ margin: 20px auto;
+ margin-top: 50px;
+ max-width: 600px;
+ width: 100%;
+ height: auto;
+ }
- div.dialog > div {
- border: 1px solid #CCC;
- border-right-color: #999;
- border-left-color: #999;
- border-bottom-color: #BBB;
- border-top: #B00100 solid 4px;
- border-top-left-radius: 9px;
- border-top-right-radius: 9px;
- background-color: white;
- padding: 7px 12% 0;
- box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
- }
-
- h1 {
- font-size: 100%;
- color: #730E15;
- line-height: 1.5em;
- }
-
- div.dialog > p {
- margin: 0 0 1em;
- padding: 1em;
- background-color: #F7F7F7;
- border: 1px solid #CCC;
- border-right-color: #999;
- border-left-color: #999;
- border-bottom-color: #999;
- border-bottom-left-radius: 4px;
- border-bottom-right-radius: 4px;
- border-top-color: #DADADA;
- color: #666;
- box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
- }
+ .dialog h1 {
+ font: 20px/28px 'Roboto', sans-serif;
+ font-weight: 400;
+ }
</style>
</head>
<body>
- <!-- This file lives in public/404.html -->
<div class="dialog">
+ <img src="/oops.png" alt="Mastodon" />
+
<div>
- <h1>The page you were looking for doesn't exist.</h1>
- <p>You may have mistyped the address or the page may have moved.</p>
+ <h1>The page you were looking for doesn't exist</h1>
</div>
- <p>If you are the application owner check the logs for more information.</p>
</div>
</body>
</html>
diff --git a/public/422.html b/public/422.html
@@ -1,68 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="utf-8">
- <title>The change you wanted was rejected (422)</title>
- <meta name="viewport" content="width=device-width,initial-scale=1">
- <style>
- body {
- background-color: #EFEFEF;
- color: #2E2F30;
- text-align: center;
- font-family: arial, sans-serif;
- margin: 0;
- }
-
- div.dialog {
- width: 95%;
- max-width: 33em;
- margin: 4em auto 0;
- }
-
- div.dialog > div {
- border: 1px solid #CCC;
- border-right-color: #999;
- border-left-color: #999;
- border-bottom-color: #BBB;
- border-top: #B00100 solid 4px;
- border-top-left-radius: 9px;
- border-top-right-radius: 9px;
- background-color: white;
- padding: 7px 12% 0;
- box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
- }
-
- h1 {
- font-size: 100%;
- color: #730E15;
- line-height: 1.5em;
- }
-
- div.dialog > p {
- margin: 0 0 1em;
- padding: 1em;
- background-color: #F7F7F7;
- border: 1px solid #CCC;
- border-right-color: #999;
- border-left-color: #999;
- border-bottom-color: #999;
- border-bottom-left-radius: 4px;
- border-bottom-right-radius: 4px;
- border-top-color: #DADADA;
- color: #666;
- box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
- }
- </style>
-</head>
-
-<body>
- <!-- This file lives in public/422.html -->
- <div class="dialog">
- <div>
- <h1>The change you wanted was rejected.</h1>
- <p>Maybe you tried to change something you didn't have access to.</p>
- </div>
- <p>If you are the application owner check the logs for more information.</p>
- </div>
-</body>
-</html>
diff --git 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