about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/components/actions/compose.jsx2
-rw-r--r--app/controllers/accounts_controller.rb4
-rw-r--r--app/controllers/api/v1/accounts_controller.rb5
-rw-r--r--app/controllers/api/v1/statuses_controller.rb3
-rw-r--r--app/controllers/stream_entries_controller.rb6
-rw-r--r--app/models/block.rb22
-rw-r--r--app/models/concerns/streamable.rb6
-rw-r--r--app/models/status.rb29
-rw-r--r--app/models/stream_entry.rb5
-rw-r--r--app/services/post_status_service.rb2
-rw-r--r--app/services/reblog_service.rb2
-rw-r--r--app/views/api/v1/statuses/_show.rabl6
-rw-r--r--db/migrate/20161221152630_add_hidden_to_stream_entries.rb5
-rw-r--r--db/schema.rb7
-rw-r--r--public/404.html75
-rw-r--r--public/422.html68
-rw-r--r--spec/controllers/api/v1/statuses_controller_spec.rb8
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
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 @@
 <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
deleted file mode 100644
index eb3601e71..000000000
--- a/public/422.html
+++ /dev/null
@@ -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
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