diff --git a/.circleci/config.yml b/.circleci/config.yml
index 529b645aa27..2efa31e64f5 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -176,9 +176,11 @@ jobs:
<<: *defaults
steps:
- *attach_workspace
+ - *install_system_dependencies
- run: bundle exec i18n-tasks check-normalized
- run: bundle exec i18n-tasks unused -l en
- run: bundle exec i18n-tasks check-consistent-interpolations
+ - run: bundle exec rake repo:check_locales_files
workflows:
version: 2
diff --git a/Dockerfile b/Dockerfile
index b5904ad95c8..3bfe06ad990 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,19 +7,17 @@ SHELL ["bash", "-c"]
ENV NODE_VER="12.9.1"
RUN echo "Etc/UTC" > /etc/localtime && \
apt update && \
- apt -y install wget make gcc g++ python && \
+ apt -y install wget python && \
cd ~ && \
- wget https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER.tar.gz && \
- tar xf node-v$NODE_VER.tar.gz && \
- cd node-v$NODE_VER && \
- ./configure --prefix=/opt/node && \
- make -j$(nproc) > /dev/null && \
- make install
+ wget https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER-linux-x64.tar.gz && \
+ tar xf node-v$NODE_VER-linux-x64.tar.gz && \
+ rm node-v$NODE_VER-linux-x64.tar.gz && \
+ mv node-v$NODE_VER-linux-x64 /opt/node
# Install jemalloc
ENV JE_VER="5.2.1"
RUN apt update && \
- apt -y install autoconf && \
+ apt -y install make autoconf gcc g++ && \
cd ~ && \
wget https://github.com/jemalloc/jemalloc/archive/$JE_VER.tar.gz && \
tar xf $JE_VER.tar.gz && \
diff --git a/Gemfile b/Gemfile
index cfaa6e44470..b9bdd82403b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -50,6 +50,7 @@ gem 'fastimage'
gem 'goldfinger', '~> 2.1'
gem 'hiredis', '~> 0.6'
gem 'redis-namespace', '~> 1.5'
+gem 'health_check', '~> 3.0'
gem 'html2text'
gem 'htmlentities', '~> 4.3'
gem 'http', '~> 3.3'
@@ -59,7 +60,7 @@ gem 'httplog', '~> 1.3'
gem 'idn-ruby', require: 'idn'
gem 'kaminari', '~> 1.1'
gem 'link_header', '~> 0.0'
-gem 'mime-types', '~> 3.2', require: 'mime/types/columnar'
+gem 'mime-types', '~> 3.3', require: 'mime/types/columnar'
gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532'
gem 'nokogiri', '~> 1.10'
gem 'nsa', '~> 0.2'
@@ -77,7 +78,8 @@ gem 'rails-settings-cached', '~> 0.6'
gem 'redis', '~> 4.1', require: ['redis', 'redis/connection/hiredis']
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'rqrcode', '~> 0.10'
-gem 'sanitize', '~> 5.0'
+gem 'ruby-progressbar', '~> 1.10'
+gem 'sanitize', '~> 5.1'
gem 'sidekiq', '~> 5.2'
gem 'sidekiq-scheduler', '~> 3.0'
gem 'sidekiq-unique-jobs', '~> 6.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 68a68c84865..30e5fc1ff88 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -188,7 +188,7 @@ GEM
rack (>= 1)
rake (> 10, < 13)
thor (~> 0.19)
- devise (4.7.0)
+ devise (4.7.1)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
@@ -231,12 +231,12 @@ GEM
tzinfo
excon (0.62.0)
fabrication (2.20.2)
- faker (2.2.1)
- i18n (>= 0.8)
+ faker (2.2.2)
+ i18n (~> 1.6.0)
faraday (0.15.0)
multipart-post (>= 1.2, < 3)
fast_blank (1.0.0)
- fastimage (2.1.5)
+ fastimage (2.1.7)
ffi (1.10.0)
fog-core (2.1.0)
builder
@@ -278,6 +278,8 @@ GEM
concurrent-ruby (~> 1.0)
hashdiff (1.0.0)
hashie (3.6.0)
+ health_check (3.0.0)
+ railties (>= 5.0)
heapy (0.1.4)
highline (2.0.1)
hiredis (0.6.3)
@@ -316,12 +318,12 @@ GEM
jmespath (1.4.0)
json (2.2.0)
json-canonicalization (0.1.0)
- json-ld-preloaded (3.0.3)
+ json-ld-preloaded (3.0.4)
json-ld (~> 3.0)
multi_json (~> 1.12)
rdf (~> 3.0)
jsonapi-renderer (0.2.2)
- jwt (2.1.0)
+ jwt (2.2.1)
kaminari (1.1.1)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.1.1)
@@ -364,9 +366,9 @@ GEM
microformats (4.1.0)
json (~> 2.1)
nokogiri (~> 1.8, >= 1.8.3)
- mime-types (3.2.2)
+ mime-types (3.3)
mime-types-data (~> 3.2015)
- mime-types-data (3.2018.0812)
+ mime-types-data (3.2019.0904)
mimemagic (0.3.3)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
@@ -382,14 +384,14 @@ GEM
nio4r (2.4.0)
nokogiri (1.10.4)
mini_portile2 (~> 2.4.0)
- nokogumbo (2.0.0)
+ nokogumbo (2.0.1)
nokogiri (~> 1.8, >= 1.8.4)
nsa (0.2.7)
activesupport (>= 4.2, < 6)
concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (>= 3.5)
statsd-ruby (~> 1.4, >= 1.4.0)
- oj (3.9.0)
+ oj (3.9.1)
omniauth (1.9.0)
hashie (>= 3.4.6, < 3.7.0)
rack (>= 1.6.2, < 3)
@@ -566,7 +568,7 @@ GEM
rufus-scheduler (3.5.2)
fugit (~> 1.1, >= 1.1.5)
safe_yaml (1.0.5)
- sanitize (5.0.0)
+ sanitize (5.1.0)
crass (~> 1.0.2)
nokogiri (>= 1.8.0)
nokogumbo (~> 2.0)
@@ -655,7 +657,7 @@ GEM
activesupport (>= 4.2)
rack-proxy (>= 0.6.1)
railties (>= 4.2)
- webpush (0.3.8)
+ webpush (1.0.0)
hkdf (~> 0.2)
jwt (~> 2.0)
websocket-driver (0.7.0)
@@ -709,6 +711,7 @@ DEPENDENCIES
fuubar (~> 2.4)
goldfinger (~> 2.1)
hamlit-rails (~> 0.2)
+ health_check (~> 3.0)
hiredis (~> 0.6)
html2text
htmlentities (~> 4.3)
@@ -730,7 +733,7 @@ DEPENDENCIES
mario-redis-lock (~> 1.2)
memory_profiler
microformats (~> 4.1)
- mime-types (~> 3.2)
+ mime-types (~> 3.3)
net-ldap (~> 0.10)
nilsimsa!
nokogiri (~> 1.10)
@@ -771,7 +774,8 @@ DEPENDENCIES
rspec-sidekiq (~> 3.0)
rubocop (~> 0.74)
rubocop-rails (~> 2.3)
- sanitize (~> 5.0)
+ ruby-progressbar (~> 1.10)
+ sanitize (~> 5.1)
sidekiq (~> 5.2)
sidekiq-bulk (~> 0.2.0)
sidekiq-scheduler (~> 3.0)
diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb
index 2fa1dfe5fc6..68b6352f886 100644
--- a/app/controllers/admin/accounts_controller.rb
+++ b/app/controllers/admin/accounts_controller.rb
@@ -41,7 +41,7 @@ module Admin
def reject
authorize @account.user, :reject?
- SuspendAccountService.new.call(@account, including_user: true, destroy: true, skip_distribution: true)
+ SuspendAccountService.new.call(@account, reserve_email: false, reserve_username: false)
redirect_to admin_pending_accounts_path
end
diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb
index f7769916676..2af90f0513c 100644
--- a/app/controllers/admin/custom_emojis_controller.rb
+++ b/app/controllers/admin/custom_emojis_controller.rb
@@ -2,19 +2,20 @@
module Admin
class CustomEmojisController < BaseController
- before_action :set_custom_emoji, except: [:index, :new, :create]
- before_action :set_filter_params
-
include ObfuscateFilename
+
obfuscate_filename [:custom_emoji, :image]
def index
authorize :custom_emoji, :index?
+
@custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page])
+ @form = Form::CustomEmojiBatch.new
end
def new
authorize :custom_emoji, :create?
+
@custom_emoji = CustomEmoji.new
end
@@ -31,69 +32,17 @@ module Admin
end
end
- def update
- authorize @custom_emoji, :update?
-
- if @custom_emoji.update(resource_params)
- log_action :update, @custom_emoji
- flash[:notice] = I18n.t('admin.custom_emojis.updated_msg')
- else
- flash[:alert] = I18n.t('admin.custom_emojis.update_failed_msg')
- end
- redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
- end
-
- def destroy
- authorize @custom_emoji, :destroy?
- @custom_emoji.destroy!
- log_action :destroy, @custom_emoji
- flash[:notice] = I18n.t('admin.custom_emojis.destroyed_msg')
- redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
- end
-
- def copy
- authorize @custom_emoji, :copy?
-
- emoji = CustomEmoji.find_or_initialize_by(domain: nil,
- shortcode: @custom_emoji.shortcode)
- emoji.image = @custom_emoji.image
-
- if emoji.save
- log_action :create, emoji
- flash[:notice] = I18n.t('admin.custom_emojis.copied_msg')
- else
- flash[:alert] = I18n.t('admin.custom_emojis.copy_failed_msg')
- end
-
- redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
- end
-
- def enable
- authorize @custom_emoji, :enable?
- @custom_emoji.update!(disabled: false)
- log_action :enable, @custom_emoji
- flash[:notice] = I18n.t('admin.custom_emojis.enabled_msg')
- redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
- end
-
- def disable
- authorize @custom_emoji, :disable?
- @custom_emoji.update!(disabled: true)
- log_action :disable, @custom_emoji
- flash[:notice] = I18n.t('admin.custom_emojis.disabled_msg')
- redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
+ def batch
+ @form = Form::CustomEmojiBatch.new(form_custom_emoji_batch_params.merge(current_account: current_account, action: action_from_button))
+ @form.save
+ rescue ActionController::ParameterMissing
+ flash[:alert] = I18n.t('admin.accounts.no_account_selected')
+ ensure
+ redirect_to admin_custom_emojis_path(filter_params)
end
private
- def set_custom_emoji
- @custom_emoji = CustomEmoji.find(params[:id])
- end
-
- def set_filter_params
- @filter_params = filter_params.to_hash.symbolize_keys
- end
-
def resource_params
params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker)
end
@@ -103,12 +52,29 @@ module Admin
end
def filter_params
- params.permit(
- :local,
- :remote,
- :by_domain,
- :shortcode
- )
+ params.slice(:local, :remote, :by_domain, :shortcode, :page).permit(:local, :remote, :by_domain, :shortcode, :page)
+ end
+
+ def action_from_button
+ if params[:update]
+ 'update'
+ elsif params[:list]
+ 'list'
+ elsif params[:unlist]
+ 'unlist'
+ elsif params[:enable]
+ 'enable'
+ elsif params[:disable]
+ 'disable'
+ elsif params[:copy]
+ 'copy'
+ elsif params[:delete]
+ 'delete'
+ end
+ end
+
+ def form_custom_emoji_batch_params
+ params.require(:form_custom_emoji_batch).permit(:action, :category_id, :category_name, custom_emoji_ids: [])
end
end
end
diff --git a/app/controllers/admin/report_notes_controller.rb b/app/controllers/admin/report_notes_controller.rb
index bcb3f20260d..b816c5b5d48 100644
--- a/app/controllers/admin/report_notes_controller.rb
+++ b/app/controllers/admin/report_notes_controller.rb
@@ -5,10 +5,10 @@ module Admin
before_action :set_report_note, only: [:destroy]
def create
- authorize ReportNote, :create?
+ authorize :report_note, :create?
@report_note = current_account.report_notes.new(resource_params)
- @report = @report_note.report
+ @report = @report_note.report
if @report_note.save
if params[:create_and_resolve]
@@ -26,9 +26,8 @@ module Admin
redirect_to admin_report_path(@report), notice: I18n.t('admin.report_notes.created_msg')
else
- @report_notes = @report.notes.latest
- @report_history = @report.history
- @form = Form::StatusBatch.new
+ @report_notes = (@report.notes.latest + @report.history + @report.target_account.targeted_account_warnings.latest.custom).sort_by(&:created_at)
+ @form = Form::StatusBatch.new
render template: 'admin/reports/show'
end
diff --git a/app/controllers/admin/tags_controller.rb b/app/controllers/admin/tags_controller.rb
index 8bd4e5f8b7e..376ebe44d3c 100644
--- a/app/controllers/admin/tags_controller.rb
+++ b/app/controllers/admin/tags_controller.rb
@@ -3,12 +3,33 @@
module Admin
class TagsController < BaseController
before_action :set_tags, only: :index
- before_action :set_tag, except: :index
- before_action :set_usage_by_domain, except: :index
- before_action :set_counters, except: :index
+ before_action :set_tag, except: [:index, :batch, :approve_all, :reject_all]
+ before_action :set_usage_by_domain, except: [:index, :batch, :approve_all, :reject_all]
+ before_action :set_counters, except: [:index, :batch, :approve_all, :reject_all]
def index
authorize :tag, :index?
+
+ @form = Form::TagBatch.new
+ end
+
+ def batch
+ @form = Form::TagBatch.new(form_tag_batch_params.merge(current_account: current_account, action: action_from_button))
+ @form.save
+ rescue ActionController::ParameterMissing
+ flash[:alert] = I18n.t('admin.accounts.no_account_selected')
+ ensure
+ redirect_to admin_tags_path(filter_params)
+ end
+
+ def approve_all
+ Form::TagBatch.new(current_account: current_account, tag_ids: Tag.pending_review.pluck(:id), action: 'approve').save
+ redirect_to admin_tags_path(filter_params)
+ end
+
+ def reject_all
+ Form::TagBatch.new(current_account: current_account, tag_ids: Tag.pending_review.pluck(:id), action: 'reject').save
+ redirect_to admin_tags_path(filter_params)
end
def show
@@ -61,7 +82,7 @@ module Admin
end
def filter_params
- params.slice(:context, :review).permit(:context, :review)
+ params.slice(:context, :review, :page).permit(:context, :review, :page)
end
def tag_params
@@ -75,5 +96,17 @@ module Admin
date.to_time(:utc).beginning_of_day.to_i
end
end
+
+ def form_tag_batch_params
+ params.require(:form_tag_batch).permit(:action, tag_ids: [])
+ end
+
+ def action_from_button
+ if params[:approve]
+ 'approve'
+ elsif params[:reject]
+ 'reject'
+ end
+ end
end
end
diff --git a/app/controllers/api/v1/admin/accounts_controller.rb b/app/controllers/api/v1/admin/accounts_controller.rb
index c306180ca25..c35ea5ab254 100644
--- a/app/controllers/api/v1/admin/accounts_controller.rb
+++ b/app/controllers/api/v1/admin/accounts_controller.rb
@@ -58,7 +58,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController
def reject
authorize @account.user, :reject?
- SuspendAccountService.new.call(@account, including_user: true, destroy: true, skip_distribution: true)
+ SuspendAccountService.new.call(@account, reserve_email: false, reserve_username: false)
render json: @account, serializer: REST::Admin::AccountSerializer
end
diff --git a/app/controllers/api/v1/custom_emojis_controller.rb b/app/controllers/api/v1/custom_emojis_controller.rb
index 252f667ddfa..4e6d5d7c613 100644
--- a/app/controllers/api/v1/custom_emojis_controller.rb
+++ b/app/controllers/api/v1/custom_emojis_controller.rb
@@ -7,6 +7,6 @@ class Api::V1::CustomEmojisController < Api::BaseController
def index
expires_in 3.minutes, public: true
- render_with_cache(each_serializer: REST::CustomEmojiSerializer) { CustomEmoji.local.where(disabled: false).includes(:category) }
+ render_with_cache(each_serializer: REST::CustomEmojiSerializer) { CustomEmoji.listed.includes(:category) }
end
end
diff --git a/app/controllers/api/v1/featured_tags/suggestions_controller.rb b/app/controllers/api/v1/featured_tags/suggestions_controller.rb
new file mode 100644
index 00000000000..fb27ef88b93
--- /dev/null
+++ b/app/controllers/api/v1/featured_tags/suggestions_controller.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController
+ before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index
+
+ before_action :require_user!
+ before_action :set_most_used_tags, only: :index
+
+ respond_to :json
+
+ def index
+ render json: @most_used_tags, each_serializer: REST::TagSerializer
+ end
+
+ private
+
+ def set_most_used_tags
+ @most_used_tags = Tag.most_used(current_account).where.not(id: current_account.featured_tags).limit(10)
+ end
+end
diff --git a/app/controllers/api/v1/featured_tags_controller.rb b/app/controllers/api/v1/featured_tags_controller.rb
new file mode 100644
index 00000000000..e4e836c9711
--- /dev/null
+++ b/app/controllers/api/v1/featured_tags_controller.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+class Api::V1::FeaturedTagsController < Api::BaseController
+ before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index
+ before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index
+
+ before_action :require_user!
+ before_action :set_featured_tags, only: :index
+ before_action :set_featured_tag, except: [:index, :create]
+
+ def index
+ render json: @featured_tags, each_serializer: REST::FeaturedTagSerializer
+ end
+
+ def create
+ @featured_tag = current_account.featured_tags.new(featured_tag_params)
+ @featured_tag.reset_data
+ @featured_tag.save!
+ render json: @featured_tag, serializer: REST::FeaturedTagSerializer
+ end
+
+ def destroy
+ @featured_tag.destroy!
+ render_empty
+ end
+
+ private
+
+ def set_featured_tag
+ @featured_tag = current_account.featured_tags.find(params[:id])
+ end
+
+ def set_featured_tags
+ @featured_tags = current_account.featured_tags.order(statuses_count: :desc)
+ end
+
+ def featured_tag_params
+ params.permit(:name)
+ end
+end
diff --git a/app/controllers/api/v1/follow_requests_controller.rb b/app/controllers/api/v1/follow_requests_controller.rb
index e6888154e22..0ee6e531f07 100644
--- a/app/controllers/api/v1/follow_requests_controller.rb
+++ b/app/controllers/api/v1/follow_requests_controller.rb
@@ -14,12 +14,12 @@ class Api::V1::FollowRequestsController < Api::BaseController
def authorize
AuthorizeFollowService.new.call(account, current_account)
NotifyService.new.call(current_account, Follow.find_by(account: account, target_account: current_account))
- render_empty
+ render json: account, serializer: REST::RelationshipSerializer, relationships: relationships
end
def reject
RejectFollowService.new.call(account, current_account)
- render_empty
+ render json: account, serializer: REST::RelationshipSerializer, relationships: relationships
end
private
@@ -28,6 +28,10 @@ class Api::V1::FollowRequestsController < Api::BaseController
Account.find(params[:id])
end
+ def relationships(**options)
+ AccountRelationshipsPresenter.new([params[:id]], current_user.account_id, options)
+ end
+
def load_accounts
default_accounts.merge(paginated_follow_requests).to_a
end
diff --git a/app/controllers/api/v1/markers_controller.rb b/app/controllers/api/v1/markers_controller.rb
new file mode 100644
index 00000000000..28c2ec79168
--- /dev/null
+++ b/app/controllers/api/v1/markers_controller.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+class Api::V1::MarkersController < Api::BaseController
+ before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: [:index]
+ before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, except: [:index]
+
+ before_action :require_user!
+
+ def index
+ @markers = current_user.markers.where(timeline: Array(params[:timeline])).each_with_object({}) { |marker, h| h[marker.timeline] = marker }
+ render json: serialize_map(@markers)
+ end
+
+ def create
+ Marker.transaction do
+ @markers = {}
+
+ resource_params.each_pair do |timeline, timeline_params|
+ @markers[timeline] = current_user.markers.find_or_initialize_by(timeline: timeline)
+ @markers[timeline].update!(timeline_params)
+ end
+ end
+
+ render json: serialize_map(@markers)
+ rescue ActiveRecord::StaleObjectError
+ render json: { error: 'Conflict during update, please try again' }, status: 409
+ end
+
+ private
+
+ def serialize_map(map)
+ serialized = {}
+
+ map.each_pair do |key, value|
+ serialized[key] = ActiveModelSerializers::SerializableResource.new(value, serializer: REST::MarkerSerializer).as_json
+ end
+
+ Oj.dump(serialized)
+ end
+
+ def resource_params
+ params.slice(*Marker::TIMELINES).permit(*Marker::TIMELINES.map { |timeline| { timeline.to_sym => [:last_read_id] } })
+ end
+end
diff --git a/app/controllers/api/v1/search_controller.rb b/app/controllers/api/v1/search_controller.rb
deleted file mode 100644
index 4fb869bb913..00000000000
--- a/app/controllers/api/v1/search_controller.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-class Api::V1::SearchController < Api::BaseController
- include Authorization
-
- RESULTS_LIMIT = (ENV['MAX_SEARCH_RESULTS'] || 20).to_i
-
- before_action -> { doorkeeper_authorize! :read, :'read:search' }
- before_action :require_user!
-
- respond_to :json
-
- def index
- @search = Search.new(search_results)
- render json: @search, serializer: REST::SearchSerializer
- end
-
- private
-
- def search_results
- SearchService.new.call(
- params[:q],
- current_account,
- limit_param(RESULTS_LIMIT),
- search_params.merge(resolve: truthy_param?(:resolve))
- )
- end
-
- def search_params
- params.permit(:type, :offset, :min_id, :max_id, :account_id)
- end
-end
diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb
index aabe2432431..ccc10f966ca 100644
--- a/app/controllers/api/v1/timelines/public_controller.rb
+++ b/app/controllers/api/v1/timelines/public_controller.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
class Api::V1::Timelines::PublicController < Api::BaseController
+ before_action :require_user!, only: [:show], if: :require_auth?
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
respond_to :json
@@ -12,6 +13,10 @@ class Api::V1::Timelines::PublicController < Api::BaseController
private
+ def require_auth?
+ !Setting.timeline_preview
+ end
+
def load_statuses
cached_public_statuses
end
diff --git a/app/controllers/api/v2/search_controller.rb b/app/controllers/api/v2/search_controller.rb
index 9aa6edc6969..7fdc030e5f6 100644
--- a/app/controllers/api/v2/search_controller.rb
+++ b/app/controllers/api/v2/search_controller.rb
@@ -1,8 +1,32 @@
# frozen_string_literal: true
-class Api::V2::SearchController < Api::V1::SearchController
+class Api::V2::SearchController < Api::BaseController
+ include Authorization
+
+ RESULTS_LIMIT = (ENV['MAX_SEARCH_RESULTS'] || 20).to_i
+
+ before_action -> { doorkeeper_authorize! :read, :'read:search' }
+ before_action :require_user!
+
+ respond_to :json
+
def index
@search = Search.new(search_results)
- render json: @search, serializer: REST::V2::SearchSerializer
+ render json: @search, serializer: REST::SearchSerializer
+ end
+
+ private
+
+ def search_results
+ SearchService.new.call(
+ params[:q],
+ current_account,
+ limit_param(RESULTS_LIMIT),
+ search_params.merge(resolve: truthy_param?(:resolve))
+ )
+ end
+
+ def search_params
+ params.permit(:type, :offset, :min_id, :max_id, :account_id)
end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 59624cad536..92339ce2f89 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -42,7 +42,7 @@ class ApplicationController < ActionController::Base
private
def https_enabled?
- Rails.env.production?
+ Rails.env.production? && !request.path.start_with?('/health')
end
def authorized_fetch_mode?
diff --git a/app/controllers/media_proxy_controller.rb b/app/controllers/media_proxy_controller.rb
index 558cd6e301f..47544f21c96 100644
--- a/app/controllers/media_proxy_controller.rb
+++ b/app/controllers/media_proxy_controller.rb
@@ -8,6 +8,8 @@ class MediaProxyController < ApplicationController
before_action :authenticate_user!, if: :whitelist_mode?
rescue_from ActiveRecord::RecordInvalid, with: :not_found
+ rescue_from Mastodon::UnexpectedResponseError, with: :not_found
+ rescue_from HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, with: :internal_server_error
def show
RedisLock.acquire(lock_options) do |lock|
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 6940c853500..40f914f1e9d 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -77,8 +77,12 @@ module ApplicationHelper
content_tag(:i, nil, attributes.merge(class: class_names.join(' ')))
end
- def custom_emoji_tag(custom_emoji)
- image_tag(custom_emoji.image.url, class: 'emojione', alt: ":#{custom_emoji.shortcode}:")
+ def custom_emoji_tag(custom_emoji, animate = true)
+ if animate
+ image_tag(custom_emoji.image.url, class: 'emojione', alt: ":#{custom_emoji.shortcode}:")
+ else
+ image_tag(custom_emoji.image.url(:static), class: 'emojione custom-emoji', alt: ":#{custom_emoji.shortcode}", 'data-original' => full_asset_url(custom_emoji.image.url), 'data-static' => full_asset_url(custom_emoji.image.url(:static)))
+ end
end
def opengraph(property, content)
diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb
index 92bc222ea9d..0cfde7edc69 100644
--- a/app/helpers/settings_helper.rb
+++ b/app/helpers/settings_helper.rb
@@ -42,8 +42,8 @@ module SettingsHelper
no: 'Norsk',
oc: 'Occitan',
pl: 'Polski',
- pt: 'Português',
- 'pt-BR': 'Português do Brasil',
+ pt: 'Português (Portugal)',
+ 'pt-BR': 'Português (Brasil)',
ro: 'Română',
ru: 'Русский',
sk: 'Slovenčina',
diff --git a/app/javascript/mastodon/actions/markers.js b/app/javascript/mastodon/actions/markers.js
new file mode 100644
index 00000000000..c3a5fe86f14
--- /dev/null
+++ b/app/javascript/mastodon/actions/markers.js
@@ -0,0 +1,30 @@
+export const submitMarkers = () => (dispatch, getState) => {
+ const accessToken = getState().getIn(['meta', 'access_token'], '');
+ const params = {};
+
+ const lastHomeId = getState().getIn(['timelines', 'home', 'items', 0]);
+ const lastNotificationId = getState().getIn(['notifications', 'items', 0, 'id']);
+
+ if (lastHomeId) {
+ params.home = {
+ last_read_id: lastHomeId,
+ };
+ }
+
+ if (lastNotificationId) {
+ params.notifications = {
+ last_read_id: lastNotificationId,
+ };
+ }
+
+ if (Object.keys(params).length === 0) {
+ return;
+ }
+
+ const client = new XMLHttpRequest();
+
+ client.open('POST', '/api/v1/markers', false);
+ client.setRequestHeader('Content-Type', 'application/json');
+ client.setRequestHeader('Authorization', `Bearer ${accessToken}`);
+ client.send(JSON.stringify(params));
+};
diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js
index 542b682821e..3ac58cf7c5e 100644
--- a/app/javascript/mastodon/containers/mastodon.js
+++ b/app/javascript/mastodon/containers/mastodon.js
@@ -12,6 +12,8 @@ import { hydrateStore } from '../actions/store';
import { connectUserStream } from '../actions/streaming';
import { IntlProvider, addLocaleData } from 'react-intl';
import { getLocale } from '../locales';
+import { previewState as previewMediaState } from 'mastodon/features/ui/components/media_modal';
+import { previewState as previewVideoState } from 'mastodon/features/ui/components/video_modal';
import initialState from '../initial_state';
import ErrorBoundary from '../components/error_boundary';
@@ -35,6 +37,10 @@ class MastodonMount extends React.PureComponent {
showIntroduction: PropTypes.bool,
};
+ shouldUpdateScroll (_, { location }) {
+ return location.state !== previewMediaState && location.state !== previewVideoState;
+ }
+
render () {
const { showIntroduction } = this.props;
@@ -44,7 +50,7 @@ class MastodonMount extends React.PureComponent {
return (
-
+
diff --git a/app/javascript/mastodon/features/ui/components/focal_point_modal.js b/app/javascript/mastodon/features/ui/components/focal_point_modal.js
index 735e445e88c..d13138a76b0 100644
--- a/app/javascript/mastodon/features/ui/components/focal_point_modal.js
+++ b/app/javascript/mastodon/features/ui/components/focal_point_modal.js
@@ -223,7 +223,7 @@ class FocalPointModal extends ImmutablePureComponent {
-
+