From 1e96ce378e2aa35ed7287a4a88e5165c2ee20101 Mon Sep 17 00:00:00 2001 From: Kurtis Rainbolt-Greene Date: Tue, 4 Apr 2017 20:16:53 -0700 Subject: By pushing this into a worker we can reduce the amount of time the feed manager using workers eat up a connection --- app/lib/feed_manager.rb | 2 +- app/workers/push_update_worker.rb | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 app/workers/push_update_worker.rb (limited to 'app') diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 2cca1cefe..075f86c2d 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -34,7 +34,7 @@ class FeedManager trim(timeline_type, account.id) end - broadcast(account.id, event: 'update', payload: inline_render(account, 'api/v1/statuses/show', status)) + PushUpdateWorker.perform_async(timeline_type, account.id, status.id) end def broadcast(timeline_id, options = {}) diff --git a/app/workers/push_update_worker.rb b/app/workers/push_update_worker.rb new file mode 100644 index 000000000..3d398b5ac --- /dev/null +++ b/app/workers/push_update_worker.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class PushUpdateWorker + include Sidekiq::Worker + + def perform(timeline, account_id, status_id) + account = Account.find(account_id) + status = Status.find(status_id) + message = inline_render(account, 'api/v1/statuses/show', status) + + broadcast(account_id, type: 'update', timeline: timeline, message: message) + end +end -- cgit From 96ef9338208e09cbc52a49a3d7171d877eab3c43 Mon Sep 17 00:00:00 2001 From: Kurtis Rainbolt-Greene Date: Tue, 4 Apr 2017 20:36:03 -0700 Subject: Replacing the broadcast method with the one defined in the feed manager --- app/workers/push_update_worker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/workers/push_update_worker.rb b/app/workers/push_update_worker.rb index 3d398b5ac..5b5e9f68a 100644 --- a/app/workers/push_update_worker.rb +++ b/app/workers/push_update_worker.rb @@ -8,6 +8,6 @@ class PushUpdateWorker status = Status.find(status_id) message = inline_render(account, 'api/v1/statuses/show', status) - broadcast(account_id, type: 'update', timeline: timeline, message: message) + ActionCable.server.broadcast("timeline:#{account_id}", type: 'update', timeline: timeline, message: message) end end -- cgit From dc5704b0b0c8f5a58ff95d3f3c4055929c6ecfba Mon Sep 17 00:00:00 2001 From: Kurtis Rainbolt-Greene Date: Tue, 4 Apr 2017 20:38:07 -0700 Subject: This method isn't used anymore --- app/lib/feed_manager.rb | 5 ----- 1 file changed, 5 deletions(-) (limited to 'app') diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 075f86c2d..6698c78a5 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -37,11 +37,6 @@ class FeedManager PushUpdateWorker.perform_async(timeline_type, account.id, status.id) end - def broadcast(timeline_id, options = {}) - options[:queued_at] = (Time.now.to_f * 1000.0).to_i - ActionCable.server.broadcast("timeline:#{timeline_id}", options) - end - def trim(type, account_id) return unless redis.zcard(key(type, account_id)) > FeedManager::MAX_ITEMS last = redis.zrevrange(key(type, account_id), FeedManager::MAX_ITEMS - 1, FeedManager::MAX_ITEMS - 1) -- cgit From 0069c01285d7fc6b97220fd678f6e5b82f301b1a Mon Sep 17 00:00:00 2001 From: Kurtis Rainbolt-Greene Date: Tue, 4 Apr 2017 20:39:14 -0700 Subject: Moving the queue_at into the worker --- app/workers/push_update_worker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/workers/push_update_worker.rb b/app/workers/push_update_worker.rb index 5b5e9f68a..6512e13ad 100644 --- a/app/workers/push_update_worker.rb +++ b/app/workers/push_update_worker.rb @@ -7,7 +7,7 @@ class PushUpdateWorker account = Account.find(account_id) status = Status.find(status_id) message = inline_render(account, 'api/v1/statuses/show', status) - + queue_at = (Time.now.to_f * 1000.0).to_i ActionCable.server.broadcast("timeline:#{account_id}", type: 'update', timeline: timeline, message: message) end end -- cgit From 220051b8b2d09a741f5edadd34e21115c5938bf0 Mon Sep 17 00:00:00 2001 From: Kurtis Rainbolt-Greene Date: Tue, 4 Apr 2017 20:48:22 -0700 Subject: I don't actually think we need that. --- app/workers/push_update_worker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/workers/push_update_worker.rb b/app/workers/push_update_worker.rb index 6512e13ad..5b5e9f68a 100644 --- a/app/workers/push_update_worker.rb +++ b/app/workers/push_update_worker.rb @@ -7,7 +7,7 @@ class PushUpdateWorker account = Account.find(account_id) status = Status.find(status_id) message = inline_render(account, 'api/v1/statuses/show', status) - queue_at = (Time.now.to_f * 1000.0).to_i + ActionCable.server.broadcast("timeline:#{account_id}", type: 'update', timeline: timeline, message: message) end end -- cgit From 9638894233d31368733574217e4d173e4cd5d13c Mon Sep 17 00:00:00 2001 From: Kurtis Rainbolt-Greene Date: Tue, 4 Apr 2017 20:51:18 -0700 Subject: Moving in the inline render --- app/workers/push_update_worker.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/workers/push_update_worker.rb b/app/workers/push_update_worker.rb index 5b5e9f68a..fef75d909 100644 --- a/app/workers/push_update_worker.rb +++ b/app/workers/push_update_worker.rb @@ -6,8 +6,14 @@ class PushUpdateWorker def perform(timeline, account_id, status_id) account = Account.find(account_id) status = Status.find(status_id) - message = inline_render(account, 'api/v1/statuses/show', status) + message = Rabl::Renderer.new( + 'api/v1/statuses/show', + status, + view_path: 'app/views', + format: :json, + scope: InlineRablScope.new(account) + ) - ActionCable.server.broadcast("timeline:#{account_id}", type: 'update', timeline: timeline, message: message) + ActionCable.server.broadcast("timeline:#{account_id}", type: 'update', timeline: timeline, message: message.render) end end -- cgit From 7bed4e51db18c864c36c6b48eb22c65f11c16b1c Mon Sep 17 00:00:00 2001 From: Kurtis Rainbolt-Greene Date: Tue, 4 Apr 2017 20:51:44 -0700 Subject: Moved to the worker --- app/lib/feed_manager.rb | 4 ---- 1 file changed, 4 deletions(-) (limited to 'app') diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 6698c78a5..87865bfdc 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -76,10 +76,6 @@ class FeedManager end end - def inline_render(target_account, template, object) - Rabl::Renderer.new(template, object, view_path: 'app/views', format: :json, scope: InlineRablScope.new(target_account)).render - end - private def redis -- cgit From 1b8c244dff84ae981d89a1672a9db06f08cf405e Mon Sep 17 00:00:00 2001 From: Eugen Date: Wed, 5 Apr 2017 18:48:41 +0200 Subject: Add proper message to PushUpdateWorker, use redis directly --- app/workers/push_update_worker.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/workers/push_update_worker.rb b/app/workers/push_update_worker.rb index fef75d909..9d16c20bf 100644 --- a/app/workers/push_update_worker.rb +++ b/app/workers/push_update_worker.rb @@ -5,7 +5,8 @@ class PushUpdateWorker def perform(timeline, account_id, status_id) account = Account.find(account_id) - status = Status.find(status_id) + status = Status.find(status_id) + message = Rabl::Renderer.new( 'api/v1/statuses/show', status, @@ -14,6 +15,8 @@ class PushUpdateWorker scope: InlineRablScope.new(account) ) - ActionCable.server.broadcast("timeline:#{account_id}", type: 'update', timeline: timeline, message: message.render) + Redis.current.publish("timeline:#{timeline_id}", Oj.dump({ event: :update, payload: message, queued_at: (Time.now.to_f * 1000.0).to_i })) + rescue ActiveRecord::RecordNotFound + true end end -- cgit From c9ebd5d19fccaabd1192f5e61537251c2c2d782e Mon Sep 17 00:00:00 2001 From: Eugen Date: Wed, 5 Apr 2017 18:58:32 +0200 Subject: Fix wrong variable used in publish channel --- app/workers/push_update_worker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/workers/push_update_worker.rb b/app/workers/push_update_worker.rb index 9d16c20bf..166a9b449 100644 --- a/app/workers/push_update_worker.rb +++ b/app/workers/push_update_worker.rb @@ -15,7 +15,7 @@ class PushUpdateWorker scope: InlineRablScope.new(account) ) - Redis.current.publish("timeline:#{timeline_id}", Oj.dump({ event: :update, payload: message, queued_at: (Time.now.to_f * 1000.0).to_i })) + Redis.current.publish("timeline:#{account.id}", Oj.dump({ event: :update, payload: message, queued_at: (Time.now.to_f * 1000.0).to_i })) rescue ActiveRecord::RecordNotFound true end -- cgit From 5b95be1c42ba69c9a3a79cfa990c80a5f2debfc6 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 5 Apr 2017 19:45:18 +0200 Subject: Replace calls to FeedManager#inline_render and #broadcast --- app/lib/feed_manager.rb | 2 +- app/lib/inline_renderer.rb | 13 +++++++++++++ app/services/fan_out_on_write_service.rb | 10 +++++----- app/services/notify_service.rb | 2 +- app/services/remove_status_service.rb | 6 +++--- app/workers/push_update_worker.rb | 13 +++---------- 6 files changed, 26 insertions(+), 20 deletions(-) create mode 100644 app/lib/inline_renderer.rb (limited to 'app') diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 87865bfdc..58d9fb1fc 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -34,7 +34,7 @@ class FeedManager trim(timeline_type, account.id) end - PushUpdateWorker.perform_async(timeline_type, account.id, status.id) + PushUpdateWorker.perform_async(account.id, status.id) end def trim(type, account_id) diff --git a/app/lib/inline_renderer.rb b/app/lib/inline_renderer.rb new file mode 100644 index 000000000..8e04ad1d5 --- /dev/null +++ b/app/lib/inline_renderer.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class InlineRenderer + def self.render(status, current_account, template) + Rabl::Renderer.new( + template, + status, + view_path: 'app/views', + format: :json, + scope: InlineRablScope.new(current_account) + ).render + end +end diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index 106d257ba..c63fcc1fe 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -50,22 +50,22 @@ class FanOutOnWriteService < BaseService end def render_anonymous_payload(status) - @payload = FeedManager.instance.inline_render(nil, 'api/v1/statuses/show', status) + @payload = InlineRenderer.render(status, nil, 'api/v1/statuses/show') end def deliver_to_hashtags(status) Rails.logger.debug "Delivering status #{status.id} to hashtags" status.tags.pluck(:name).each do |hashtag| - FeedManager.instance.broadcast("hashtag:#{hashtag}", event: 'update', payload: @payload) - FeedManager.instance.broadcast("hashtag:#{hashtag}:local", event: 'update', payload: @payload) if status.account.local? + Redis.current.publish("hashtag:#{hashtag}", Oj.dump(event: :update, payload: @payload)) + Redis.current.publish("hashtag:#{hashtag}:local", Oj.dump(event: :update, payload: @payload)) if status.account.local? end end def deliver_to_public(status) Rails.logger.debug "Delivering status #{status.id} to public timeline" - FeedManager.instance.broadcast(:public, event: 'update', payload: @payload) - FeedManager.instance.broadcast('public:local', event: 'update', payload: @payload) if status.account.local? + Redis.current.publish('public', Oj.dump(event: 'update', payload: @payload)) + Redis.current.publish('public:local', Oj.dump(event: 'update', payload: @payload)) if status.account.local? end end diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index 24486f220..62508a049 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -50,7 +50,7 @@ class NotifyService < BaseService def create_notification @notification.save! return unless @notification.browserable? - FeedManager.instance.broadcast(@recipient.id, event: 'notification', payload: FeedManager.instance.inline_render(@recipient, 'api/v1/notifications/show', @notification)) + Redis.current.publish(@recipient.id, Oj.dump(event: :notification, payload: InlineRenderer.render(@notification, @recipient, 'api/v1/notifications/show'))) end def send_email diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index cf1f432e4..e19fdd030 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -65,17 +65,17 @@ class RemoveStatusService < BaseService redis.zremrangebyscore(FeedManager.instance.key(type, receiver.id), status.id, status.id) end - FeedManager.instance.broadcast(receiver.id, event: 'delete', payload: status.id) + Redis.current.publish(receiver.id, Oj.dump(event: :delete, payload: status.id)) end def remove_from_hashtags(status) status.tags.each do |tag| - FeedManager.instance.broadcast("hashtag:#{tag.name}", event: 'delete', payload: status.id) + Redis.current.publish("hashtag:#{tag.name}", Oj.dump(event: :delete, payload: status.id)) end end def remove_from_public(status) - FeedManager.instance.broadcast(:public, event: 'delete', payload: status.id) + Redis.current.publish('public', Oj.dump(event: :delete, payload: status.id)) end def redis diff --git a/app/workers/push_update_worker.rb b/app/workers/push_update_worker.rb index 166a9b449..fbcdcf634 100644 --- a/app/workers/push_update_worker.rb +++ b/app/workers/push_update_worker.rb @@ -3,19 +3,12 @@ class PushUpdateWorker include Sidekiq::Worker - def perform(timeline, account_id, status_id) + def perform(account_id, status_id) account = Account.find(account_id) status = Status.find(status_id) - - message = Rabl::Renderer.new( - 'api/v1/statuses/show', - status, - view_path: 'app/views', - format: :json, - scope: InlineRablScope.new(account) - ) + message = InlineRenderer.render(status, account, 'api/v1/statuses/show') - Redis.current.publish("timeline:#{account.id}", Oj.dump({ event: :update, payload: message, queued_at: (Time.now.to_f * 1000.0).to_i })) + Redis.current.publish("timeline:#{account.id}", Oj.dump(event: :update, payload: message, queued_at: (Time.now.to_f * 1000.0).to_i)) rescue ActiveRecord::RecordNotFound true end -- cgit From 5442083b3c44c731679fc489568bf7f70a807a39 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 5 Apr 2017 21:41:50 +0200 Subject: Split SalmonWorker into smaller parts, move profile updating into another job --- app/services/follow_remote_account_service.rb | 16 ++++++---------- app/services/process_feed_service.rb | 6 +++--- app/services/process_interaction_service.rb | 10 +++------- app/workers/admin/suspension_worker.rb | 2 ++ app/workers/application_worker.rb | 2 ++ app/workers/distribution_worker.rb | 5 +---- app/workers/remote_profile_update_worker.rb | 20 ++++++++++++++++++++ app/workers/salmon_worker.rb | 2 +- spec/services/process_feed_service_spec.rb | 1 + 9 files changed, 39 insertions(+), 25 deletions(-) create mode 100644 app/workers/remote_profile_update_worker.rb (limited to 'app') diff --git a/app/services/follow_remote_account_service.rb b/app/services/follow_remote_account_service.rb index b39eafc70..936953429 100644 --- a/app/services/follow_remote_account_service.rb +++ b/app/services/follow_remote_account_service.rb @@ -45,13 +45,13 @@ class FollowRemoteAccountService < BaseService account.suspended = true if domain_block && domain_block.suspend? account.silenced = true if domain_block && domain_block.silence? - xml = get_feed(account.remote_url) - hubs = get_hubs(xml) + body, xml = get_feed(account.remote_url) + hubs = get_hubs(xml) account.uri = get_account_uri(xml) account.hub_url = hubs.first.attribute('href').value - get_profile(xml, account) + get_profile(body, account) account.save! account @@ -61,7 +61,7 @@ class FollowRemoteAccountService < BaseService def get_feed(url) response = http_client.get(Addressable::URI.parse(url)) - Nokogiri::XML(response) + [response.to_s, Nokogiri::XML(response)] end def get_hubs(xml) @@ -82,12 +82,8 @@ class FollowRemoteAccountService < BaseService author_uri.content end - def get_profile(xml, account) - update_remote_profile_service.call(xml.at_xpath('/xmlns:feed'), account) - end - - def update_remote_profile_service - @update_remote_profile_service ||= UpdateRemoteProfileService.new + def get_profile(body, account) + RemoteProfileUpdateWorker.perform_async(account.id, body.force_encoding('UTF-8'), false) end def http_client diff --git a/app/services/process_feed_service.rb b/app/services/process_feed_service.rb index 69911abc5..cf2f7a826 100644 --- a/app/services/process_feed_service.rb +++ b/app/services/process_feed_service.rb @@ -5,15 +5,15 @@ class ProcessFeedService < BaseService xml = Nokogiri::XML(body) xml.encoding = 'utf-8' - update_author(xml, account) + update_author(body, xml, account) process_entries(xml, account) end private - def update_author(xml, account) + def update_author(body, xml, account) return if xml.at_xpath('/xmlns:feed', xmlns: TagManager::XMLNS).nil? - UpdateRemoteProfileService.new.call(xml.at_xpath('/xmlns:feed', xmlns: TagManager::XMLNS), account, true) + RemoteProfileUpdateWorker.perform_async(account.id, body.force_encoding('UTF-8'), true) end def process_entries(xml, account) diff --git a/app/services/process_interaction_service.rb b/app/services/process_interaction_service.rb index d5f7b4b3c..805ca5a27 100644 --- a/app/services/process_interaction_service.rb +++ b/app/services/process_interaction_service.rb @@ -24,7 +24,7 @@ class ProcessInteractionService < BaseService return if account.suspended? if salmon.verify(envelope, account.keypair) - update_remote_profile_service.call(xml.at_xpath('/xmlns:entry', xmlns: TagManager::XMLNS), account, true) + RemoteProfileUpdateWorker.perform_async(account.id, body.force_encoding('UTF-8'), true) case verb(xml) when :follow @@ -114,7 +114,7 @@ class ProcessInteractionService < BaseService return if status.nil? - remove_status_service.call(status) if account.id == status.account_id + RemovalWorker.perform_async(status.id) if account.id == status.account_id end def favourite!(xml, from_account) @@ -130,7 +130,7 @@ class ProcessInteractionService < BaseService end def add_post!(body, account) - process_feed_service.call(body, account) + ProcessingWorker.perform_async(account.id, body.force_encoding('UTF-8')) end def status(xml) @@ -153,10 +153,6 @@ class ProcessInteractionService < BaseService @process_feed_service ||= ProcessFeedService.new end - def update_remote_profile_service - @update_remote_profile_service ||= UpdateRemoteProfileService.new - end - def remove_status_service @remove_status_service ||= RemoveStatusService.new end diff --git a/app/workers/admin/suspension_worker.rb b/app/workers/admin/suspension_worker.rb index 38761f3b9..7ef2b35ec 100644 --- a/app/workers/admin/suspension_worker.rb +++ b/app/workers/admin/suspension_worker.rb @@ -3,6 +3,8 @@ class Admin::SuspensionWorker include Sidekiq::Worker + sidekiq_options queue: 'pull' + def perform(account_id) SuspendAccountService.new.call(Account.find(account_id)) end diff --git a/app/workers/application_worker.rb b/app/workers/application_worker.rb index f2d7c1062..436f24763 100644 --- a/app/workers/application_worker.rb +++ b/app/workers/application_worker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ApplicationWorker def info(message) Rails.logger.info("#{self.class.name} - #{message}") diff --git a/app/workers/distribution_worker.rb b/app/workers/distribution_worker.rb index 9a2867ea6..f7953689b 100644 --- a/app/workers/distribution_worker.rb +++ b/app/workers/distribution_worker.rb @@ -4,10 +4,7 @@ class DistributionWorker < ApplicationWorker include Sidekiq::Worker def perform(status_id) - status = Status.find(status_id) - - FanOutOnWriteService.new.call(status) - WarmCacheService.new.call(status) + FanOutOnWriteService.new.call(Status.find(status_id)) rescue ActiveRecord::RecordNotFound info("Couldn't find the status") end diff --git a/app/workers/remote_profile_update_worker.rb b/app/workers/remote_profile_update_worker.rb new file mode 100644 index 000000000..b91dc3466 --- /dev/null +++ b/app/workers/remote_profile_update_worker.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class RemoteProfileUpdateWorker + include Sidekiq::Worker + + sidekiq_options queue: 'pull' + + def perform(account_id, body, resubscribe) + account = Account.find(account_id) + + xml = Nokogiri::XML(body) + xml.encoding = 'utf-8' + + author_container = xml.at_xpath('/xmlns:feed', xmlns: TagManager::XMLNS) || xml.at_xpath('/xmlns:entry', xmlns: TagManager::XMLNS) + + UpdateRemoteProfileService.new.call(author_container, account, resubscribe) + rescue ActiveRecord::RecordNotFound + true + end +end diff --git a/app/workers/salmon_worker.rb b/app/workers/salmon_worker.rb index fc95ce47f..d37d40432 100644 --- a/app/workers/salmon_worker.rb +++ b/app/workers/salmon_worker.rb @@ -7,7 +7,7 @@ class SalmonWorker def perform(account_id, body) ProcessInteractionService.new.call(body, Account.find(account_id)) - rescue ActiveRecord::RecordNotFound + rescue Nokogiri::XML::XPath::SyntaxError, ActiveRecord::RecordNotFound true end end diff --git a/spec/services/process_feed_service_spec.rb b/spec/services/process_feed_service_spec.rb index 5e57d823b..b15284fee 100644 --- a/spec/services/process_feed_service_spec.rb +++ b/spec/services/process_feed_service_spec.rb @@ -16,6 +16,7 @@ RSpec.describe ProcessFeedService do end it 'updates remote user\'s account information' do + account.reload expect(account.display_name).to eq '::1' expect(account).to have_attached_file(:avatar) end -- cgit From 540d6efe88717b0da4a23b5069a3caaff5b3241a Mon Sep 17 00:00:00 2001 From: blackle Date: Wed, 5 Apr 2017 20:04:13 -0400 Subject: Catch more errors in process_follows so it doesn't fail --- app/workers/import_worker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/workers/import_worker.rb b/app/workers/import_worker.rb index 7cf29fb53..d5a33cada 100644 --- a/app/workers/import_worker.rb +++ b/app/workers/import_worker.rb @@ -46,7 +46,7 @@ class ImportWorker begin FollowService.new.call(from_account, row[0]) - rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError + rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound, Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError next end end -- cgit From dbd529109ea95df43aefa514c312d7397e7fc713 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 6 Apr 2017 02:26:59 +0200 Subject: Fix notifications delivered to wrong pubsub channel, optimized RemoveStatusService, slightly optimized FanOutOnWriteService again --- app/services/fan_out_on_write_service.rb | 9 +++++---- app/services/notify_service.rb | 2 +- app/services/remove_status_service.rb | 28 +++++++++++++++------------- 3 files changed, 21 insertions(+), 18 deletions(-) (limited to 'app') diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index c63fcc1fe..a25c20c93 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -51,21 +51,22 @@ class FanOutOnWriteService < BaseService def render_anonymous_payload(status) @payload = InlineRenderer.render(status, nil, 'api/v1/statuses/show') + @payload = Oj.dump(event: :update, payload: @payload) end def deliver_to_hashtags(status) Rails.logger.debug "Delivering status #{status.id} to hashtags" status.tags.pluck(:name).each do |hashtag| - Redis.current.publish("hashtag:#{hashtag}", Oj.dump(event: :update, payload: @payload)) - Redis.current.publish("hashtag:#{hashtag}:local", Oj.dump(event: :update, payload: @payload)) if status.account.local? + Redis.current.publish("hashtag:#{hashtag}", @payload) + Redis.current.publish("hashtag:#{hashtag}:local", @payload) if status.local? end end def deliver_to_public(status) Rails.logger.debug "Delivering status #{status.id} to public timeline" - Redis.current.publish('public', Oj.dump(event: 'update', payload: @payload)) - Redis.current.publish('public:local', Oj.dump(event: 'update', payload: @payload)) if status.account.local? + Redis.current.publish('public', @payload) + Redis.current.publish('public:local', @payload) if status.local? end end diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index 62508a049..ffeee5fcf 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -50,7 +50,7 @@ class NotifyService < BaseService def create_notification @notification.save! return unless @notification.browserable? - Redis.current.publish(@recipient.id, Oj.dump(event: :notification, payload: InlineRenderer.render(@notification, @recipient, 'api/v1/notifications/show'))) + Redis.current.publish("timeline:#{@recipient.id}", Oj.dump(event: :notification, payload: InlineRenderer.render(@notification, @recipient, 'api/v1/notifications/show'))) end def send_email diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index e19fdd030..60ce9987c 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -4,6 +4,8 @@ class RemoveStatusService < BaseService include StreamEntryRenderer def call(status) + @payload = Oj.dump(event: :delete, payload: status.id) + remove_from_self(status) if status.account.local? remove_from_followers(status) remove_from_mentioned(status) @@ -25,25 +27,23 @@ class RemoveStatusService < BaseService end def remove_from_followers(status) - status.account.followers.each do |follower| - next unless follower.local? + status.account.followers.where(domain: nil).each do |follower| unpush(:home, follower, status) end end def remove_from_mentioned(status) + return unless status.local? notified_domains = [] status.mentions.each do |mention| mentioned_account = mention.account - if mentioned_account.local? - unpush(:mentions, mentioned_account, status) - else - next if notified_domains.include?(mentioned_account.domain) - notified_domains << mentioned_account.domain - send_delete_salmon(mentioned_account, status) - end + next if mentioned_account.local? + next if notified_domains.include?(mentioned_account.domain) + + notified_domains << mentioned_account.domain + send_delete_salmon(mentioned_account, status) end end @@ -65,17 +65,19 @@ class RemoveStatusService < BaseService redis.zremrangebyscore(FeedManager.instance.key(type, receiver.id), status.id, status.id) end - Redis.current.publish(receiver.id, Oj.dump(event: :delete, payload: status.id)) + Redis.current.publish("timeline:#{receiver.id}", @payload) end def remove_from_hashtags(status) - status.tags.each do |tag| - Redis.current.publish("hashtag:#{tag.name}", Oj.dump(event: :delete, payload: status.id)) + status.tags.pluck(:name) do |hashtag| + Redis.current.publish("hashtag:#{hashtag}", @payload) + Redis.current.publish("hashtag:#{hashtag}:local", @payload) if status.local? end end def remove_from_public(status) - Redis.current.publish('public', Oj.dump(event: :delete, payload: status.id)) + Redis.current.publish('public', @payload) + Redis.current.publish('public:local', @payload) if status.local? end def redis -- cgit From 51d7caaf1994ee3cb531b64841e12d129dded298 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 6 Apr 2017 04:03:23 +0200 Subject: Fix wrong pubsub channel on public timelines --- app/services/fan_out_on_write_service.rb | 8 ++++---- app/services/remove_status_service.rb | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'app') diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index a25c20c93..19eedc0a7 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -58,15 +58,15 @@ class FanOutOnWriteService < BaseService Rails.logger.debug "Delivering status #{status.id} to hashtags" status.tags.pluck(:name).each do |hashtag| - Redis.current.publish("hashtag:#{hashtag}", @payload) - Redis.current.publish("hashtag:#{hashtag}:local", @payload) if status.local? + Redis.current.publish("timeline:hashtag:#{hashtag}", @payload) + Redis.current.publish("timeline:hashtag:#{hashtag}:local", @payload) if status.local? end end def deliver_to_public(status) Rails.logger.debug "Delivering status #{status.id} to public timeline" - Redis.current.publish('public', @payload) - Redis.current.publish('public:local', @payload) if status.local? + Redis.current.publish('timeline:public', @payload) + Redis.current.publish('timeline:public:local', @payload) if status.local? end end diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index 60ce9987c..50bb7fc97 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -70,14 +70,14 @@ class RemoveStatusService < BaseService def remove_from_hashtags(status) status.tags.pluck(:name) do |hashtag| - Redis.current.publish("hashtag:#{hashtag}", @payload) - Redis.current.publish("hashtag:#{hashtag}:local", @payload) if status.local? + Redis.current.publish("timeline:hashtag:#{hashtag}", @payload) + Redis.current.publish("timeline:hashtag:#{hashtag}:local", @payload) if status.local? end end def remove_from_public(status) - Redis.current.publish('public', @payload) - Redis.current.publish('public:local', @payload) if status.local? + Redis.current.publish('timeline:public', @payload) + Redis.current.publish('timeline:public:local', @payload) if status.local? end def redis -- cgit From 97ae53daa8a2538e7f8b22c1bbf63d0713475438 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 6 Apr 2017 16:24:57 -0400 Subject: Reduce size of background-photo.jpeg Reduced by running through `guetzli` image optimizer. --- app/assets/images/background-photo.jpeg | Bin 894792 -> 214464 bytes 1 file changed, 0 insertions(+), 0 deletions(-) (limited to 'app') diff --git a/app/assets/images/background-photo.jpeg b/app/assets/images/background-photo.jpeg index b0a88ff35..d7937fd4b 100644 Binary files a/app/assets/images/background-photo.jpeg and b/app/assets/images/background-photo.jpeg differ -- cgit From 6d6a429af8fe4bd92ed497f401676353fdc603e0 Mon Sep 17 00:00:00 2001 From: Eugen Date: Fri, 7 Apr 2017 05:56:56 +0200 Subject: Rewrite Atom generation from stream entries to use Ox instead of Nokogiri (#1124) * Rewrite Atom generation from stream entries to use Ox instead of Nokogiri::Builder StreamEntry is now limited to only statuses, which allows some optimization. Removed extra queries on AccountsController#show. AtomSerializer instead of AtomBuilderHelper used in AccountsController#show, StreamEntriesController#show, StreamEntryRenderer and PubSubHubbub::DistributionWorker PubSubHubbub::DistributionWorker moves n+1 DomainBlock query to PubSubHubbub::DeliveryWorker instead. All Salmon slaps that aren't based on StreamEntry still use AtomBuilderHelper and Nokogiri * All Salmon slaps now use Ox instead of Nokogiri. No touch from status on account --- Gemfile | 1 + Gemfile.lock | 2 + app/controllers/accounts_controller.rb | 3 +- app/controllers/stream_entries_controller.rb | 4 +- app/lib/atom_serializer.rb | 348 ++++++++++++++++++++++++ app/lib/tag_manager.rb | 2 + app/models/stream_entry.rb | 28 +- app/services/after_block_service.rb | 18 +- app/services/authorize_follow_service.rb | 27 +- app/services/block_service.rb | 18 +- app/services/concerns/stream_entry_renderer.rb | 3 +- app/services/favourite_service.rb | 22 +- app/services/follow_service.rb | 44 +-- app/services/reject_follow_service.rb | 27 +- app/services/unblock_service.rb | 18 +- app/services/unfavourite_service.rb | 22 +- app/services/unfollow_service.rb | 21 +- app/views/accounts/show.atom.ruby | 27 -- app/views/stream_entries/show.atom.ruby | 9 - app/workers/pubsubhubbub/delivery_worker.rb | 3 + app/workers/pubsubhubbub/distribution_worker.rb | 8 +- 21 files changed, 393 insertions(+), 262 deletions(-) create mode 100644 app/lib/atom_serializer.rb delete mode 100644 app/views/accounts/show.atom.ruby delete mode 100644 app/views/stream_entries/show.atom.ruby (limited to 'app') diff --git a/Gemfile b/Gemfile index b5705e9d1..65bd5eb49 100644 --- a/Gemfile +++ b/Gemfile @@ -34,6 +34,7 @@ gem 'doorkeeper' gem 'rabl' gem 'rqrcode' gem 'twitter-text' +gem 'ox' gem 'oj' gem 'hiredis' gem 'redis', '~>3.2', require: ['redis', 'redis/connection/hiredis'] diff --git a/Gemfile.lock b/Gemfile.lock index 408d85ade..f2a199931 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -240,6 +240,7 @@ GEM addressable (~> 2.4) http (~> 2.0) nokogiri (~> 1.6) + ox (2.4.11) paperclip (5.1.0) activemodel (>= 4.2.0) activesupport (>= 4.2.0) @@ -482,6 +483,7 @@ DEPENDENCIES nokogiri oj ostatus2 + ox paperclip (~> 5.1) paperclip-av-transcoder pg diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index dc1aeb5ea..619c04be2 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -16,7 +16,8 @@ class AccountsController < ApplicationController end format.atom do - @entries = @account.stream_entries.order('id desc').where(activity_type: 'Status').where(hidden: false).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]) + render xml: AtomSerializer.render(AtomSerializer.new.feed(@account, @entries.to_a)) end format.activitystreams2 diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb index de38b3602..469a8c33e 100644 --- a/app/controllers/stream_entries_controller.rb +++ b/app/controllers/stream_entries_controller.rb @@ -19,7 +19,9 @@ class StreamEntriesController < ApplicationController end end - format.atom + format.atom do + render xml: AtomSerializer.render(AtomSerializer.new.entry(@stream_entry, true)) + end end end diff --git a/app/lib/atom_serializer.rb b/app/lib/atom_serializer.rb new file mode 100644 index 000000000..21f485c2d --- /dev/null +++ b/app/lib/atom_serializer.rb @@ -0,0 +1,348 @@ +# frozen_string_literal: true + +class AtomSerializer + include RoutingHelper + + class << self + def render(element) + document = Ox::Document.new(version: '1.0') + document << element + "#{Ox.dump(element)}" + end + end + + def author(account) + author = Ox::Element.new('author') + + uri = TagManager.instance.uri_for(account) + + append_element(author, 'id', uri) + append_element(author, 'activity:object-type', TagManager::TYPES[:person]) + append_element(author, 'uri', uri) + append_element(author, 'name', account.username) + append_element(author, 'email', account.local? ? "#{account.acct}@#{Rails.configuration.x.local_domain}" : account.acct) + append_element(author, 'summary', account.note) + append_element(author, 'link', nil, rel: :alternate, type: 'text/html', href: TagManager.instance.url_for(account)) + append_element(author, 'link', nil, rel: :avatar, type: account.avatar_content_type, 'media:width': 120, 'media:height': 120, href: full_asset_url(account.avatar.url(:original))) + append_element(author, 'link', nil, rel: :header, type: account.header_content_type, 'media:width': 700, 'media:height': 335, href: full_asset_url(account.header.url(:original))) + append_element(author, 'poco:preferredUsername', account.username) + append_element(author, 'poco:displayName', account.display_name) unless account.display_name.blank? + append_element(author, 'poco:note', Formatter.instance.simplified_format(account).to_str) unless account.note.blank? + append_element(author, 'mastodon:scope', account.locked? ? :private : :public) + + author + end + + def feed(account, stream_entries) + feed = Ox::Element.new('feed') + + add_namespaces(feed) + + append_element(feed, 'id', account_url(account, format: 'atom')) + append_element(feed, 'title', account.display_name) + append_element(feed, 'subtitle', account.note) + append_element(feed, 'updated', account.updated_at.iso8601) + append_element(feed, 'logo', full_asset_url(account.avatar.url(:original))) + + feed << author(account) + + append_element(feed, 'link', nil, rel: :alternate, type: 'text/html', href: TagManager.instance.url_for(account)) + append_element(feed, 'link', nil, rel: :self, type: 'application/atom+xml', href: account_url(account, format: 'atom')) + append_element(feed, 'link', nil, rel: :next, type: 'application/atom+xml', href: account_url(account, format: 'atom', max_id: stream_entries.last.id)) if stream_entries.size == 20 + append_element(feed, 'link', nil, rel: :hub, href: api_push_url) + append_element(feed, 'link', nil, rel: :salmon, href: api_salmon_url(account.id)) + + stream_entries.each do |stream_entry| + feed << entry(stream_entry) + end + + feed + end + + def entry(stream_entry, root = false) + entry = Ox::Element.new('entry') + + add_namespaces(entry) if root + + append_element(entry, 'id', TagManager.instance.unique_tag(stream_entry.created_at, stream_entry.activity_id, stream_entry.activity_type)) + append_element(entry, 'published', stream_entry.created_at.iso8601) + append_element(entry, 'updated', stream_entry.updated_at.iso8601) + append_element(entry, 'title', stream_entry&.status&.title) + append_element(entry, 'activity:object-type', TagManager::TYPES[stream_entry.object_type]) + append_element(entry, 'activity:verb', TagManager::VERBS[stream_entry.verb]) + + entry << object(stream_entry.target) if stream_entry.targeted? + + serialize_status_attributes(entry, stream_entry.status) unless stream_entry.status.nil? + + append_element(entry, 'link', nil, rel: :alternate, type: 'text/html', href: account_stream_entry_url(stream_entry.account, stream_entry)) + append_element(entry, 'link', nil, rel: :self, type: 'application/atom+xml', href: account_stream_entry_url(stream_entry.account, stream_entry, format: 'atom')) + append_element(entry, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(stream_entry.thread), href: TagManager.instance.url_for(stream_entry.thread)) if stream_entry.threaded? + + entry + end + + def object(status) + object = Ox::Element.new('activity:object') + + append_element(object, 'id', TagManager.instance.uri_for(status)) + append_element(object, 'published', status.created_at.iso8601) + append_element(object, 'updated', status.updated_at.iso8601) + append_element(object, 'title', status.title) + + object << author(status.account) + + append_element(object, 'activity:object-type', TagManager::TYPES[status.object_type]) + append_element(object, 'activity:verb', TagManager::VERBS[status.verb]) + + serialize_status_attributes(object, status) + + append_element(object, 'link', nil, rel: :alternate, type: 'text/html', href: TagManager.instance.url_for(status)) + append_element(object, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(status.thread), href: TagManager.instance.url_for(status.thread)) if status.reply? + + object + end + + def follow_salmon(follow) + entry = Ox::Element.new('entry') + add_namespaces(entry) + + description = "#{follow.account.acct} started following #{follow.target_account.acct}" + + append_element(entry, 'id', TagManager.instance.unique_tag(follow.created_at, follow.id, 'Follow')) + append_element(entry, 'title', description) + append_element(entry, 'content', description, type: :html) + + entry << author(follow.account) + + append_element(entry, 'activity:object-type', TagManager::TYPES[:activity]) + append_element(entry, 'activity:verb', TagManager::VERBS[:follow]) + + object = author(follow.target_account) + object.value = 'activity:object' + + entry << object + entry + end + + def follow_request_salmon(follow_request) + entry = Ox::Element.new('entry') + add_namespaces(entry) + + append_element(entry, 'id', TagManager.instance.unique_tag(follow_request.created_at, follow_request.id, 'FollowRequest')) + append_element(entry, 'title', "#{follow_request.account.acct} requested to follow #{follow_request.target_account.acct}") + + entry << author(follow_request.account) + + append_element(entry, 'activity:object-type', TagManager::TYPES[:activity]) + append_element(entry, 'activity:verb', TagManager::VERBS[:request_friend]) + + object = author(follow_request.target_account) + object.value = 'activity:object' + + entry << object + entry + end + + def authorize_follow_request_salmon(follow_request) + entry = Ox::Element.new('entry') + add_namespaces(entry) + + append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, follow_request.id, 'FollowRequest')) + append_element(entry, 'title', "#{follow_request.target_account.acct} authorizes follow request by #{follow_request.account.acct}") + + entry << author(follow_request.target_account) + + append_element(entry, 'activity:object-type', TagManager::TYPES[:activity]) + append_element(entry, 'activity:verb', TagManager::VERBS[:authorize]) + + object = Ox::Element.new('activity:object') + object << author(follow_request.account) + + append_element(object, 'activity:object-type', TagManager::TYPES[:activity]) + append_element(object, 'activity:verb', TagManager::VERBS[:request_friend]) + + inner_object = author(follow_request.target_account) + inner_object.value = 'activity:object' + + object << inner_object + entry << object + entry + end + + def reject_follow_request_salmon(follow_request) + entry = Ox::Element.new('entry') + add_namespaces(entry) + + append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, follow_request.id, 'FollowRequest')) + append_element(entry, 'title', "#{follow_request.target_account.acct} rejects follow request by #{follow_request.account.acct}") + + entry << author(follow_request.target_account) + + append_element(entry, 'activity:object-type', TagManager::TYPES[:activity]) + append_element(entry, 'activity:verb', TagManager::VERBS[:reject]) + + object = Ox::Element.new('activity:object') + object << author(follow_request.account) + + append_element(object, 'activity:object-type', TagManager::TYPES[:activity]) + append_element(object, 'activity:verb', TagManager::VERBS[:request_friend]) + + inner_object = author(follow_request.target_account) + inner_object.value = 'activity:object' + + object << inner_object + entry << object + entry + end + + def unfollow_salmon(follow) + entry = Ox::Element.new('entry') + add_namespaces(entry) + + description = "#{follow.account.acct} is no longer following #{follow.target_account.acct}" + + append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, follow.id, 'Follow')) + append_element(entry, 'title', description) + append_element(entry, 'content', description, type: :html) + + entry << author(follow.account) + + append_element(entry, 'activity:object-type', TagManager::TYPES[:activity]) + append_element(entry, 'activity:verb', TagManager::VERBS[:unfollow]) + + object = author(follow.target_account) + object.value = 'activity:object' + + entry << object + entry + end + + def block_salmon(block) + entry = Ox::Element.new('entry') + add_namespaces(entry) + + description = "#{block.account.acct} no longer wishes to interact with #{block.target_account.acct}" + + append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, block.id, 'Block')) + append_element(entry, 'title', description) + + entry << author(block.account) + + append_element(entry, 'activity:object-type', TagManager::TYPES[:activity]) + append_element(entry, 'activity:verb', TagManager::VERBS[:block]) + + object = author(block.target_account) + object.value = 'activity:object' + + entry << object + entry + end + + def unblock_salmon(block) + entry = Ox::Element.new('entry') + add_namespaces(entry) + + description = "#{block.account.acct} no longer blocks #{block.target_account.acct}" + + append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, block.id, 'Block')) + append_element(entry, 'title', description) + + entry << author(block.account) + + append_element(entry, 'activity:object-type', TagManager::TYPES[:activity]) + append_element(entry, 'activity:verb', TagManager::VERBS[:unblock]) + + object = author(block.target_account) + object.value = 'activity:object' + + entry << object + entry + end + + def favourite_salmon(favourite) + entry = Ox::Element.new('entry') + add_namespaces(entry) + + description = "#{favourite.account.acct} favourited a status by #{favourite.status.account.acct}" + + append_element(entry, 'id', TagManager.instance.unique_tag(favourite.created_at, favourite.id, 'Favourite')) + append_element(entry, 'title', description) + append_element(entry, 'content', description, type: :html) + + entry << author(favourite.account) + + append_element(entry, 'activity:object-type', TagManager::TYPES[:activity]) + append_element(entry, 'activity:verb', TagManager::VERBS[:favorite]) + + entry << object(favourite.status) + + append_element(entry, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(favourite.status), href: TagManager.instance.url_for(favourite.status)) + + entry + end + + def unfavourite_salmon(favourite) + entry = Ox::Element.new('entry') + add_namespaces(entry) + + description = "#{favourite.account.acct} no longer favourites a status by #{favourite.status.account.acct}" + + append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, favourite.id, 'Favourite')) + append_element(entry, 'title', description) + append_element(entry, 'content', description, type: :html) + + entry << author(favourite.account) + + append_element(entry, 'activity:object-type', TagManager::TYPES[:activity]) + append_element(entry, 'activity:verb', TagManager::VERBS[:unfavorite]) + + entry << object(favourite.status) + + append_element(entry, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(favourite.status), href: TagManager.instance.url_for(favourite.status)) + + entry + end + + private + + def append_element(parent, name, content = nil, attributes = {}) + element = Ox::Element.new(name) + attributes.each { |k, v| element[k] = v.to_s } + element << content.to_s unless content.nil? + parent << element + end + + def add_namespaces(parent) + parent['xmlns'] = TagManager::XMLNS + parent['xmlns:thr'] = TagManager::THR_XMLNS + parent['xmlns:activity'] = TagManager::AS_XMLNS + parent['xmlns:poco'] = TagManager::POCO_XMLNS + parent['xmlns:media'] = TagManager::MEDIA_XMLNS + parent['xmlns:ostatus'] = TagManager::OS_XMLNS + parent['xmlns:mastodon'] = TagManager::MTDN_XMLNS + end + + def serialize_status_attributes(entry, status) + append_element(entry, 'summary', status.spoiler_text) unless status.spoiler_text.blank? + append_element(entry, 'content', Formatter.instance.format(status.reblog? ? status.reblog : status).to_str, type: 'html') + + status.mentions.each do |mentioned| + append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': TagManager::TYPES[:person], href: TagManager.instance.uri_for(mentioned.account)) + end + + append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': TagManager::TYPES[:collection], href: TagManager::COLLECTIONS[:public]) if status.public_visibility? + + status.tags.each do |tag| + append_element(entry, 'category', nil, term: tag.name) + end + + append_element(entry, 'category', nil, term: 'nsfw') if status.sensitive? + + status.media_attachments.each do |media| + append_element(entry, 'link', nil, rel: :enclosure, type: media.file_content_type, length: media.file_file_size, href: full_asset_url(media.file.url(:original, false))) + end + + append_element(entry, 'mastodon:scope', status.visibility) + end +end diff --git a/app/lib/tag_manager.rb b/app/lib/tag_manager.rb index 2a5e7a409..07b2fb91e 100644 --- a/app/lib/tag_manager.rb +++ b/app/lib/tag_manager.rb @@ -78,6 +78,8 @@ class TagManager case target.object_type when :person account_url(target) + when :note, :comment, :activity + unique_tag(target.created_at, target.id, 'Status') else unique_tag(target.stream_entry.created_at, target.stream_entry.activity_id, target.stream_entry.activity_type) end diff --git a/app/models/stream_entry.rb b/app/models/stream_entry.rb index ae7ae446e..8aff5ae06 100644 --- a/app/models/stream_entry.rb +++ b/app/models/stream_entry.rb @@ -5,25 +5,21 @@ class StreamEntry < ApplicationRecord belongs_to :account, inverse_of: :stream_entries belongs_to :activity, polymorphic: true - belongs_to :status, foreign_type: 'Status', foreign_key: 'activity_id', inverse_of: :stream_entry validates :account, :activity, presence: true - STATUS_INCLUDES = [:account, :stream_entry, :media_attachments, :tags, mentions: :account, reblog: [:stream_entry, :account, mentions: :account], thread: [:stream_entry, :account]].freeze + STATUS_INCLUDES = [:account, :stream_entry, :media_attachments, :tags, mentions: :account, reblog: [:stream_entry, :account, :media_attachments, :tags, mentions: :account], thread: [:stream_entry, :account]].freeze + default_scope { where(activity_type: 'Status') } scope :with_includes, -> { includes(:account, status: STATUS_INCLUDES) } def object_type - if orphaned? - :activity - else - targeted? ? :activity : activity.object_type - end + orphaned? || targeted? ? :activity : status.object_type end def verb - orphaned? ? :delete : activity.verb + orphaned? ? :delete : status.verb end def targeted? @@ -31,15 +27,15 @@ class StreamEntry < ApplicationRecord end def target - orphaned? ? nil : activity.target + orphaned? ? nil : status.target end def title - orphaned? ? nil : activity.title + orphaned? ? nil : status.title end def content - orphaned? ? nil : activity.content + orphaned? ? nil : status.content end def threaded? @@ -47,20 +43,16 @@ class StreamEntry < ApplicationRecord end def thread - orphaned? ? nil : activity.thread + orphaned? ? nil : status.thread end def mentions - activity.respond_to?(:mentions) ? activity.mentions.map(&:account) : [] - end - - def activity - !new_record? ? send(activity_type.underscore) || super : super + orphaned? ? [] : status.mentions.map(&:account) end private def orphaned? - activity.nil? + status.nil? end end diff --git a/app/services/after_block_service.rb b/app/services/after_block_service.rb index 8c6197f2c..0f478bcb7 100644 --- a/app/services/after_block_service.rb +++ b/app/services/after_block_service.rb @@ -9,20 +9,20 @@ class AfterBlockService < BaseService private def clear_timelines(account, target_account) - mentions_key = FeedManager.instance.key(:mentions, account.id) - home_key = FeedManager.instance.key(:home, account.id) + home_key = FeedManager.instance.key(:home, account.id) - target_account.statuses.select('id').find_each do |status| - redis.zrem(mentions_key, status.id) - redis.zrem(home_key, status.id) + redis.pipelined do + target_account.statuses.select('id').find_each do |status| + redis.zrem(home_key, status.id) + end end end def clear_notifications(account, target_account) - Notification.where(account: account).joins(:follow).where(activity_type: 'Follow', follows: { account_id: target_account.id }).destroy_all - Notification.where(account: account).joins(mention: :status).where(activity_type: 'Mention', statuses: { account_id: target_account.id }).destroy_all - Notification.where(account: account).joins(:favourite).where(activity_type: 'Favourite', favourites: { account_id: target_account.id }).destroy_all - Notification.where(account: account).joins(:status).where(activity_type: 'Status', statuses: { account_id: target_account.id }).destroy_all + Notification.where(account: account).joins(:follow).where(activity_type: 'Follow', follows: { account_id: target_account.id }).delete_all + Notification.where(account: account).joins(mention: :status).where(activity_type: 'Mention', statuses: { account_id: target_account.id }).delete_all + Notification.where(account: account).joins(:favourite).where(activity_type: 'Favourite', favourites: { account_id: target_account.id }).delete_all + Notification.where(account: account).joins(:status).where(activity_type: 'Status', statuses: { account_id: target_account.id }).delete_all end def redis diff --git a/app/services/authorize_follow_service.rb b/app/services/authorize_follow_service.rb index ac465bdb2..97c76bee1 100644 --- a/app/services/authorize_follow_service.rb +++ b/app/services/authorize_follow_service.rb @@ -10,31 +10,6 @@ class AuthorizeFollowService < BaseService private def build_xml(follow_request) - Nokogiri::XML::Builder.new do |xml| - entry(xml, true) do - unique_id xml, Time.now.utc, follow_request.id, 'FollowRequest' - title xml, "#{follow_request.target_account.acct} authorizes follow request by #{follow_request.account.acct}" - - author(xml) do - include_author xml, follow_request.target_account - end - - object_type xml, :activity - verb xml, :authorize - - target(xml) do - author(xml) do - include_author xml, follow_request.account - end - - object_type xml, :activity - verb xml, :request_friend - - target(xml) do - include_author xml, follow_request.target_account - end - end - end - end.to_xml + AtomSerializer.render(AtomSerializer.new.authorize_follow_request_salmon(follow_request)) end end diff --git a/app/services/block_service.rb b/app/services/block_service.rb index bd914d8be..d59b47afb 100644 --- a/app/services/block_service.rb +++ b/app/services/block_service.rb @@ -18,22 +18,6 @@ class BlockService < BaseService private def build_xml(block) - Nokogiri::XML::Builder.new do |xml| - entry(xml, true) do - unique_id xml, block.created_at, block.id, 'Block' - title xml, "#{block.account.acct} no longer wishes to interact with #{block.target_account.acct}" - - author(xml) do - include_author xml, block.account - end - - object_type xml, :activity - verb xml, :block - - target(xml) do - include_author xml, block.target_account - end - end - end.to_xml + AtomSerializer.render(AtomSerializer.new.block_salmon(block)) end end diff --git a/app/services/concerns/stream_entry_renderer.rb b/app/services/concerns/stream_entry_renderer.rb index a4255daea..ef176d8a6 100644 --- a/app/services/concerns/stream_entry_renderer.rb +++ b/app/services/concerns/stream_entry_renderer.rb @@ -2,7 +2,6 @@ module StreamEntryRenderer def stream_entry_to_xml(stream_entry) - renderer = StreamEntriesController.renderer.new(method: 'get', http_host: Rails.configuration.x.local_domain, https: Rails.configuration.x.use_https) - renderer.render(:show, assigns: { stream_entry: stream_entry }, formats: [:atom]) + AtomSerializer.render(AtomSerializer.new.entry(stream_entry, true)) end end diff --git a/app/services/favourite_service.rb b/app/services/favourite_service.rb index 5cc96403c..e92aada64 100644 --- a/app/services/favourite_service.rb +++ b/app/services/favourite_service.rb @@ -22,26 +22,6 @@ class FavouriteService < BaseService private def build_xml(favourite) - description = "#{favourite.account.acct} favourited a status by #{favourite.status.account.acct}" - - Nokogiri::XML::Builder.new do |xml| - entry(xml, true) do - unique_id xml, favourite.created_at, favourite.id, 'Favourite' - title xml, description - content xml, description - - author(xml) do - include_author xml, favourite.account - end - - object_type xml, :activity - verb xml, :favorite - in_reply_to xml, TagManager.instance.uri_for(favourite.status), TagManager.instance.url_for(favourite.status) - - target(xml) do - include_target xml, favourite.status - end - end - end.to_xml + AtomSerializer.render(AtomSerializer.new.favourite_salmon(favourite)) end end diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb index 17b3b2542..844f5282d 100644 --- a/app/services/follow_service.rb +++ b/app/services/follow_service.rb @@ -10,7 +10,7 @@ class FollowService < BaseService target_account = FollowRemoteAccountService.new.call(uri) raise ActiveRecord::RecordNotFound if target_account.nil? || target_account.id == source_account.id || target_account.suspended? - raise Mastodon::NotPermittedError if target_account.blocking?(source_account) || source_account.blocking?(target_account) + raise Mastodon::NotPermittedError if target_account.blocking?(source_account) || source_account.blocking?(target_account) if target_account.locked? request_follow(source_account, target_account) @@ -55,48 +55,10 @@ class FollowService < BaseService end def build_follow_request_xml(follow_request) - description = "#{follow_request.account.acct} requested to follow #{follow_request.target_account.acct}" - - Nokogiri::XML::Builder.new do |xml| - entry(xml, true) do - unique_id xml, follow_request.created_at, follow_request.id, 'FollowRequest' - title xml, description - content xml, description - - author(xml) do - include_author xml, follow_request.account - end - - object_type xml, :activity - verb xml, :request_friend - - target(xml) do - include_author xml, follow_request.target_account - end - end - end.to_xml + AtomSerializer.render(AtomSerializer.new.follow_request_salmon(follow_request)) end def build_follow_xml(follow) - description = "#{follow.account.acct} started following #{follow.target_account.acct}" - - Nokogiri::XML::Builder.new do |xml| - entry(xml, true) do - unique_id xml, follow.created_at, follow.id, 'Follow' - title xml, description - content xml, description - - author(xml) do - include_author xml, follow.account - end - - object_type xml, :activity - verb xml, :follow - - target(xml) do - include_author xml, follow.target_account - end - end - end.to_xml + AtomSerializer.render(AtomSerializer.new.follow_salmon(follow)) end end diff --git a/app/services/reject_follow_service.rb b/app/services/reject_follow_service.rb index 1b03d62e6..675007938 100644 --- a/app/services/reject_follow_service.rb +++ b/app/services/reject_follow_service.rb @@ -10,31 +10,6 @@ class RejectFollowService < BaseService private def build_xml(follow_request) - Nokogiri::XML::Builder.new do |xml| - entry(xml, true) do - unique_id xml, Time.now.utc, follow_request.id, 'FollowRequest' - title xml, "#{follow_request.target_account.acct} rejects follow request by #{follow_request.account.acct}" - - author(xml) do - include_author xml, follow_request.target_account - end - - object_type xml, :activity - verb xml, :reject - - target(xml) do - author(xml) do - include_author xml, follow_request.account - end - - object_type xml, :activity - verb xml, :request_friend - - target(xml) do - include_author xml, follow_request.target_account - end - end - end - end.to_xml + AtomSerializer.render(AtomSerializer.new.reject_follow_request_salmon(follow_request)) end end diff --git a/app/services/unblock_service.rb b/app/services/unblock_service.rb index c4f789f74..3a3fd2d8c 100644 --- a/app/services/unblock_service.rb +++ b/app/services/unblock_service.rb @@ -11,22 +11,6 @@ class UnblockService < BaseService private def build_xml(block) - Nokogiri::XML::Builder.new do |xml| - entry(xml, true) do - unique_id xml, Time.now.utc, block.id, 'Block' - title xml, "#{block.account.acct} no longer blocks #{block.target_account.acct}" - - author(xml) do - include_author xml, block.account - end - - object_type xml, :activity - verb xml, :unblock - - target(xml) do - include_author xml, block.target_account - end - end - end.to_xml + AtomSerializer.render(AtomSerializer.new.unblock_salmon(block)) end end diff --git a/app/services/unfavourite_service.rb b/app/services/unfavourite_service.rb index 5f0ba4254..a32e87bff 100644 --- a/app/services/unfavourite_service.rb +++ b/app/services/unfavourite_service.rb @@ -13,26 +13,6 @@ class UnfavouriteService < BaseService private def build_xml(favourite) - description = "#{favourite.account.acct} no longer favourites a status by #{favourite.status.account.acct}" - - Nokogiri::XML::Builder.new do |xml| - entry(xml, true) do - unique_id xml, Time.now.utc, favourite.id, 'Favourite' - title xml, description - content xml, description - - author(xml) do - include_author xml, favourite.account - end - - object_type xml, :activity - verb xml, :unfavorite - in_reply_to xml, TagManager.instance.uri_for(favourite.status), TagManager.instance.url_for(favourite.status) - - target(xml) do - include_target xml, favourite.status - end - end - end.to_xml + AtomSerializer.render(AtomSerializer.new.unfavourite_salmon(favourite)) end end diff --git a/app/services/unfollow_service.rb b/app/services/unfollow_service.rb index 3440da364..244c9b529 100644 --- a/app/services/unfollow_service.rb +++ b/app/services/unfollow_service.rb @@ -13,25 +13,6 @@ class UnfollowService < BaseService private def build_xml(follow) - description = "#{follow.account.acct} is no longer following #{follow.target_account.acct}" - - Nokogiri::XML::Builder.new do |xml| - entry(xml, true) do - unique_id xml, Time.now.utc, follow.id, 'Follow' - title xml, description - content xml, description - - author(xml) do - include_author xml, follow.account - end - - object_type xml, :activity - verb xml, :unfollow - - target(xml) do - include_author xml, follow.target_account - end - end - end.to_xml + AtomSerializer.render(AtomSerializer.new.unfollow_salmon(follow)) end end diff --git a/app/views/accounts/show.atom.ruby b/app/views/accounts/show.atom.ruby deleted file mode 100644 index e15021178..000000000 --- a/app/views/accounts/show.atom.ruby +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -Nokogiri::XML::Builder.new do |xml| - feed(xml) do - simple_id xml, account_url(@account, format: 'atom') - title xml, @account.display_name - subtitle xml, @account.note - updated_at xml, stream_updated_at - logo xml, full_asset_url(@account.avatar.url(:original)) - - author(xml) do - include_author xml, @account - end - - link_alternate xml, TagManager.instance.url_for(@account) - link_self xml, account_url(@account, format: 'atom') - link_next xml, account_url(@account, format: 'atom', max_id: @entries.last.id) if @entries.size == 20 - link_hub xml, api_push_url - link_salmon xml, api_salmon_url(@account.id) - - @entries.each do |stream_entry| - entry(xml, false) do - include_entry xml, stream_entry - end - end - end -end.to_xml diff --git a/app/views/stream_entries/show.atom.ruby b/app/views/stream_entries/show.atom.ruby deleted file mode 100644 index a298f3269..000000000 --- a/app/views/stream_entries/show.atom.ruby +++ /dev/null @@ -1,9 +0,0 @@ -Nokogiri::XML::Builder.new do |xml| - entry(xml, true) do - author(xml) do - include_author xml, @stream_entry.account - end - - include_entry xml, @stream_entry - end -end.to_xml diff --git a/app/workers/pubsubhubbub/delivery_worker.rb b/app/workers/pubsubhubbub/delivery_worker.rb index 466def3a8..8412be4b7 100644 --- a/app/workers/pubsubhubbub/delivery_worker.rb +++ b/app/workers/pubsubhubbub/delivery_worker.rb @@ -13,6 +13,9 @@ class Pubsubhubbub::DeliveryWorker def perform(subscription_id, payload) subscription = Subscription.find(subscription_id) headers = {} + host = Addressable::URI.parse(subscription.callback_url).host + + return if DomainBlock.blocked?(host) headers['User-Agent'] = 'Mastodon/PubSubHubbub' headers['Link'] = LinkHeader.new([[api_push_url, [%w(rel hub)]], [account_url(subscription.account, format: :atom), [%w(rel self)]]]).to_s diff --git a/app/workers/pubsubhubbub/distribution_worker.rb b/app/workers/pubsubhubbub/distribution_worker.rb index 82ff257af..68ca0f870 100644 --- a/app/workers/pubsubhubbub/distribution_worker.rb +++ b/app/workers/pubsubhubbub/distribution_worker.rb @@ -10,14 +10,10 @@ class Pubsubhubbub::DistributionWorker return if stream_entry.hidden? - account = stream_entry.account - renderer = AccountsController.renderer.new(method: 'get', http_host: Rails.configuration.x.local_domain, https: Rails.configuration.x.use_https) - payload = renderer.render(:show, assigns: { account: account, entries: [stream_entry] }, formats: [:atom]) - # domains = account.followers_domains + account = stream_entry.account + payload = AtomSerializer.render(AtomSerializer.new.feed(account, [stream_entry])) Subscription.where(account: account).active.select('id, callback_url').find_each do |subscription| - host = Addressable::URI.parse(subscription.callback_url).host - next if DomainBlock.blocked?(host) # || !domains.include?(host) Pubsubhubbub::DeliveryWorker.perform_async(subscription.id, payload) end rescue ActiveRecord::RecordNotFound -- cgit From 1c351709bc45611c2ed48fb8539c2b27ab51b6fe Mon Sep 17 00:00:00 2001 From: Eugen Date: Fri, 7 Apr 2017 11:09:14 +0200 Subject: Force UTF8 encoding on generated XML (#1140) --- Procfile | 2 +- app/lib/atom_serializer.rb | 2 +- app/views/layouts/application.html.haml | 6 ++++-- config/puma.rb | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) (limited to 'app') diff --git a/Procfile b/Procfile index 6cdd89518..646e26059 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ web: bundle exec puma -C config/puma.rb -worker: bundle exec sidekiq -q default -q mailers -q push +worker: bundle exec sidekiq -q default -q push -q pull -q mailers diff --git a/app/lib/atom_serializer.rb b/app/lib/atom_serializer.rb index 21f485c2d..4e01c3d43 100644 --- a/app/lib/atom_serializer.rb +++ b/app/lib/atom_serializer.rb @@ -7,7 +7,7 @@ class AtomSerializer def render(element) document = Ox::Document.new(version: '1.0') document << element - "#{Ox.dump(element)}" + ('' + Ox.dump(element)).force_encoding('UTF-8') end end diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 7eae6982b..abab14a28 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -11,8 +11,10 @@ %meta{:name => "theme-color", :content => "#282c37"}/ %meta{:name => "apple-mobile-web-app-capable", :content => "yes"}/ - %title - = "#{yield(:page_title)} - " if content_for?(:page_title) + %title< + - if content_for?(:page_title) + = yield(:page_title) + = ' - ' = Setting.site_title = stylesheet_link_tag 'application', media: 'all' diff --git a/config/puma.rb b/config/puma.rb index 550129bdc..191f00cca 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -9,7 +9,7 @@ preload_app! on_worker_boot do if ENV['HEROKU'] # Spawn the workers from Puma, to only use one dyno - @sidekiq_pid ||= spawn('bundle exec sidekiq -q default -q mailers -q push') + @sidekiq_pid ||= spawn('bundle exec sidekiq -q default -q push -q pull -q mailers ') end ActiveRecord::Base.establish_connection if defined?(ActiveRecord) -- cgit From 624a9a7136159d460228a0c2f5df18a9ead3b7f2 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 7 Apr 2017 12:21:00 +0200 Subject: Re-add forgotten element on standalone --- app/lib/atom_serializer.rb | 3 +++ 1 file changed, 3 insertions(+) (limited to 'app') diff --git a/app/lib/atom_serializer.rb b/app/lib/atom_serializer.rb index 4e01c3d43..4ce0f4513 100644 --- a/app/lib/atom_serializer.rb +++ b/app/lib/atom_serializer.rb @@ -68,6 +68,9 @@ class AtomSerializer append_element(entry, 'published', stream_entry.created_at.iso8601) append_element(entry, 'updated', stream_entry.updated_at.iso8601) append_element(entry, 'title', stream_entry&.status&.title) + + entry << author(stream_entry.account) if root + append_element(entry, 'activity:object-type', TagManager::TYPES[stream_entry.object_type]) append_element(entry, 'activity:verb', TagManager::VERBS[stream_entry.verb]) -- cgit From e3a3422a65901c71ec3ff4a2729309c441660d63 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 7 Apr 2017 12:40:26 +0200 Subject: Allow setting of default language through config Setting of locale in controller extracted to Localized concern, the doorkeeper authorized applications controller moved under custom namespace with inclusion of Localized, which resolves the "it sometimes appears in a different random language" bug --- .env.production.sample | 3 +++ app/controllers/application_controller.rb | 9 ++------- app/controllers/concerns/localized.rb | 19 ++++++++++++++++++ app/controllers/oauth/authorizations_controller.rb | 9 ++------- .../oauth/authorized_applications_controller.rb | 16 +++++++++++++++ .../authorized_applications/index.html.haml | 23 ---------------------- .../oauth/authorized_applications/index.html.haml | 23 ++++++++++++++++++++++ config/routes.rb | 2 +- 8 files changed, 66 insertions(+), 38 deletions(-) create mode 100644 app/controllers/concerns/localized.rb create mode 100644 app/controllers/oauth/authorized_applications_controller.rb delete mode 100644 app/views/doorkeeper/authorized_applications/index.html.haml create mode 100644 app/views/oauth/authorized_applications/index.html.haml (limited to 'app') diff --git a/.env.production.sample b/.env.production.sample index a7f9eb4bf..a0e963ca6 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -25,6 +25,9 @@ OTP_SECRET= # Only allow registrations with the following e-mail domains # EMAIL_DOMAIN_WHITELIST=example1.com|example2.de|etc +# Optionally change default language +# DEFAULT_LOCALE=de + # E-mail configuration SMTP_SERVER=smtp.mailgun.org SMTP_PORT=587 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c06142fd4..f00f9c1e3 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class ApplicationController < ActionController::Base + include Localized + # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception @@ -14,7 +16,6 @@ class ApplicationController < ActionController::Base rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity before_action :store_current_location, except: :raise_not_found, unless: :devise_controller? - before_action :set_locale before_action :set_user_activity before_action :check_suspension, if: :user_signed_in? @@ -28,12 +29,6 @@ class ApplicationController < ActionController::Base store_location_for(:user, request.url) end - def set_locale - I18n.locale = current_user.try(:locale) || I18n.default_locale - rescue I18n::InvalidLocale - I18n.locale = I18n.default_locale - end - def require_admin! redirect_to root_path unless current_user&.admin? end diff --git a/app/controllers/concerns/localized.rb b/app/controllers/concerns/localized.rb new file mode 100644 index 000000000..b6f868090 --- /dev/null +++ b/app/controllers/concerns/localized.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Localized + extend ActiveSupport::Concern + + included do + before_action :set_locale + end + + def set_locale + I18n.locale = current_user.try(:locale) || default_locale + rescue I18n::InvalidLocale + I18n.locale = default_locale + end + + def default_locale + ENV.fetch('DEFAULT_LOCALE') { I18n.default_locale } + end +end diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb index 7c25266d8..cdbfde0fb 100644 --- a/app/controllers/oauth/authorizations_controller.rb +++ b/app/controllers/oauth/authorizations_controller.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController + include Localized + skip_before_action :authenticate_resource_owner! - before_action :set_locale before_action :store_current_location before_action :authenticate_resource_owner! @@ -12,10 +13,4 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController def store_current_location store_location_for(:user, request.url) end - - def set_locale - I18n.locale = current_user.try(:locale) || I18n.default_locale - rescue I18n::InvalidLocale - I18n.locale = I18n.default_locale - end end diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb new file mode 100644 index 000000000..09dd5d3c4 --- /dev/null +++ b/app/controllers/oauth/authorized_applications_controller.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicationsController + include Localized + + skip_before_action :authenticate_resource_owner! + + before_action :store_current_location + before_action :authenticate_resource_owner! + + private + + def store_current_location + store_location_for(:user, request.url) + end +end diff --git a/app/views/doorkeeper/authorized_applications/index.html.haml b/app/views/doorkeeper/authorized_applications/index.html.haml deleted file mode 100644 index d4719881c..000000000 --- a/app/views/doorkeeper/authorized_applications/index.html.haml +++ /dev/null @@ -1,23 +0,0 @@ -- content_for :page_title do - = t('doorkeeper.authorized_applications.index.title') - -%table.table - %thead - %tr - %th= t('doorkeeper.authorized_applications.index.application') - %th= t('doorkeeper.authorized_applications.index.scopes') - %th= t('doorkeeper.authorized_applications.index.created_at') - %th - %tbody - - @applications.each do |application| - %tr - %td - - if application.website.blank? - = application.name - - else - = link_to application.name, application.website - %th= application.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.join('
').html_safe - %td= l application.created_at - %td - - unless application.superapp? - = table_link_to 'times', t('doorkeeper.authorized_applications.buttons.revoke'), oauth_authorized_application_path(application), method: :delete, data: { confirm: t('doorkeeper.authorized_applications.confirmations.revoke') } diff --git a/app/views/oauth/authorized_applications/index.html.haml b/app/views/oauth/authorized_applications/index.html.haml new file mode 100644 index 000000000..d4719881c --- /dev/null +++ b/app/views/oauth/authorized_applications/index.html.haml @@ -0,0 +1,23 @@ +- content_for :page_title do + = t('doorkeeper.authorized_applications.index.title') + +%table.table + %thead + %tr + %th= t('doorkeeper.authorized_applications.index.application') + %th= t('doorkeeper.authorized_applications.index.scopes') + %th= t('doorkeeper.authorized_applications.index.created_at') + %th + %tbody + - @applications.each do |application| + %tr + %td + - if application.website.blank? + = application.name + - else + = link_to application.name, application.website + %th= application.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.join('
').html_safe + %td= l application.created_at + %td + - unless application.superapp? + = table_link_to 'times', t('doorkeeper.authorized_applications.buttons.revoke'), oauth_authorized_application_path(application), method: :delete, data: { confirm: t('doorkeeper.authorized_applications.confirmations.revoke') } diff --git a/config/routes.rb b/config/routes.rb index ca77191f7..315ad5da5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,7 +11,7 @@ Rails.application.routes.draw do end use_doorkeeper do - controllers authorizations: 'oauth/authorizations' + controllers authorizations: 'oauth/authorizations', authorized_applications: 'oauth/authorized_applications' end get '.well-known/host-meta', to: 'xrd#host_meta', as: :host_meta -- cgit From 786e6f94b986dbf98eae9d79bc75e147fcefc8ec Mon Sep 17 00:00:00 2001 From: Jantso Porali Date: Fri, 7 Apr 2017 12:58:12 +0200 Subject: Update Finnish translations, add sample Minio config (#954) --- .env.production.sample | 10 ++++++++++ app/views/user_mailer/confirmation_instructions.fi.html.erb | 5 +++++ app/views/user_mailer/confirmation_instructions.fi.text.erb | 5 +++++ app/views/user_mailer/password_change.fi.html.erb | 3 +++ app/views/user_mailer/password_change.fi.text.erb | 3 +++ .../user_mailer/reset_password_instructions.fi.html.erb | 8 ++++++++ .../user_mailer/reset_password_instructions.fi.text.erb | 8 ++++++++ config/locales/fi.yml | 12 ++++++------ docs/Using-Mastodon/FAQ.md | 3 ++- 9 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 app/views/user_mailer/confirmation_instructions.fi.html.erb create mode 100644 app/views/user_mailer/confirmation_instructions.fi.text.erb create mode 100644 app/views/user_mailer/password_change.fi.html.erb create mode 100644 app/views/user_mailer/password_change.fi.text.erb create mode 100644 app/views/user_mailer/reset_password_instructions.fi.html.erb create mode 100644 app/views/user_mailer/reset_password_instructions.fi.text.erb (limited to 'app') diff --git a/.env.production.sample b/.env.production.sample index a7f9eb4bf..a4eabc384 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -44,6 +44,16 @@ SMTP_FROM_ADDRESS=notifications@example.com # S3_PROTOCOL=http # S3_HOSTNAME=192.168.1.123:9000 +# S3 (Minio Config (optional) Please check Minio instance for details) +# S3_ENABLED=true +# S3_BUCKET= +# AWS_ACCESS_KEY_ID= +# AWS_SECRET_ACCESS_KEY= +# S3_REGION= +# S3_PROTOCOL=https +# S3_HOSTNAME= +# S3_ENDPOINT= + # Optional alias for S3 if you want to use Cloudfront or Cloudflare in front # S3_CLOUDFRONT_HOST= diff --git a/app/views/user_mailer/confirmation_instructions.fi.html.erb b/app/views/user_mailer/confirmation_instructions.fi.html.erb new file mode 100644 index 000000000..8b72722da --- /dev/null +++ b/app/views/user_mailer/confirmation_instructions.fi.html.erb @@ -0,0 +1,5 @@ +

Tervetuloa <%= @resource.email %>!

+ +

Voit vahvistaa Mastodon tilisi klikkaamalla alla olevaa linkkiä:

+ +

<%= link_to 'Varmista tilini', confirmation_url(@resource, confirmation_token: @token) %>

diff --git a/app/views/user_mailer/confirmation_instructions.fi.text.erb b/app/views/user_mailer/confirmation_instructions.fi.text.erb new file mode 100644 index 000000000..796913abb --- /dev/null +++ b/app/views/user_mailer/confirmation_instructions.fi.text.erb @@ -0,0 +1,5 @@ +Tervetuloa <%= @resource.email %>! + +Voit vahvistaa Mastodon tilisi klikkaamalla alla olevaa linkkiä: + +<%= confirmation_url(@resource, confirmation_token: @token) %> diff --git a/app/views/user_mailer/password_change.fi.html.erb b/app/views/user_mailer/password_change.fi.html.erb new file mode 100644 index 000000000..c56b96593 --- /dev/null +++ b/app/views/user_mailer/password_change.fi.html.erb @@ -0,0 +1,3 @@ +

Hei <%= @resource.email %>!

+ +

Lähetämme tämän viestin ilmoittaaksemme että salasanasi on vaihdettu.

diff --git a/app/views/user_mailer/password_change.fi.text.erb b/app/views/user_mailer/password_change.fi.text.erb new file mode 100644 index 000000000..d90c3fdeb --- /dev/null +++ b/app/views/user_mailer/password_change.fi.text.erb @@ -0,0 +1,3 @@ +Hei <%= @resource.email %>! + +Lähetämme tämän viestin ilmoittaaksemme että salasanasi on vaihdettu. diff --git a/app/views/user_mailer/reset_password_instructions.fi.html.erb b/app/views/user_mailer/reset_password_instructions.fi.html.erb new file mode 100644 index 000000000..53be0b62b --- /dev/null +++ b/app/views/user_mailer/reset_password_instructions.fi.html.erb @@ -0,0 +1,8 @@ +

Hei <%= @resource.email %>!

+ +

Joku on pyytänyt salasanvaihto Mastodonissa. Voit tehdä sen allaolevassa linkissä.

+ +

<%= link_to 'Vaihda salasanani', edit_password_url(@resource, reset_password_token: @token) %>

+ +

Jos et pyytänyt vaihtoa, poista tämä viesti.

+

Salasanaasi ei vaihdeta ennen kuin menet ylläolevaan linkkiin ja luot uuden.

diff --git a/app/views/user_mailer/reset_password_instructions.fi.text.erb b/app/views/user_mailer/reset_password_instructions.fi.text.erb new file mode 100644 index 000000000..c826d5fc8 --- /dev/null +++ b/app/views/user_mailer/reset_password_instructions.fi.text.erb @@ -0,0 +1,8 @@ +Hei <%= @resource.email %>! + +Joku on pyytänyt salasanvaihto Mastodonissa. Voit tehdä sen allaolevassa linkissä. + +<%= edit_password_url(@resource, reset_password_token: @token) %> + +Jos et pyytänyt vaihtoa, poista tämä viesti. +Salasanaasi ei vaihdeta ennen kuin menet ylläolevaan linkkiin ja luot uuden. diff --git a/config/locales/fi.yml b/config/locales/fi.yml index cdb2b9886..947d3f646 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -16,18 +16,18 @@ fi: chronology: Aikajana on kronologisessa järjestyksessä ethics: 'Eettinen suunnittelu: ei mainoksia, no seurantaa' gifv: GIFV settejä ja lyhyitä videoita - privacy: Julkaisu kohtainen yksityisyys aseuts + privacy: Julkaisu kohtainen yksityisyys asetus public: Julkiset aikajanat features_headline: Mikä erottaa Mastodonin muista get_started: Aloita käyttö links: Linkit - other_instances: muuhun palvelimeen + other_instances: Muut palvelimet source_code: Lähdekoodi status_count_after: statusta status_count_before: Ovat luoneet terms: Ehdot - user_count_after: käyttäjää - user_count_before: Koti käyttäjälle + user_count_after: käyttäjälle + user_count_before: Koti accounts: follow: Seuraa followers: Seuraajat @@ -130,8 +130,8 @@ fi: authorized_apps: Valtuutetut ohjelmat back: Takaisin Mastodoniin edit_profile: Muokkaa profiilia - export: Datan vienti - import: Datan tuonti + export: Vie dataa + import: Tuo dataa preferences: Ominaisuudet settings: Asetukset two_factor_auth: Kaksivaiheinen tunnistus diff --git a/docs/Using-Mastodon/FAQ.md b/docs/Using-Mastodon/FAQ.md index daedcbdd8..3b03a8ee4 100644 --- a/docs/Using-Mastodon/FAQ.md +++ b/docs/Using-Mastodon/FAQ.md @@ -36,8 +36,9 @@ While Mastodon is compatible with GNU social in terms of server to server commun Because Mastodon has been created from a blank slate, it is much simpler to have the API mirror internal structures as closely as possible, rather than build an emulation layer. Secondly, the GNU social client API is actually a half-way implementation of the legacy Twitter API - that's the reason why it works with some older Twitter client apps. However, many of those apps are not maintained anymore, the GNU social API does not actually keep up with the real Twitter API and never fully implemented all its features; at the same time, the Twitter API was never meant for a federated service and so obscures some of the functionality. + #### How is Mastodon funded? Development of Mastodon and hosting of mastodon.social is funded through my [Patreon (also BTC/PayPal donations)](https://www.patreon.com/user?u=619786). Beyond that, I am not interested in VC funding, monetizing, advertising, or anything of that sort. I could offer setup/maintenance services on demand. -The software is free and open source and communities should host their own servers if they can, that way the costs are more or less distributed. Obviously it'd be hard for me to pay the bills if literally everyone decided to use the mastodon.social instance only. \ No newline at end of file +The software is free and open source and communities should host their own servers if they can, that way the costs are more or less distributed. Obviously it'd be hard for me to pay the bills if literally everyone decided to use the mastodon.social instance only. -- cgit From 8a6d8de60ac573f5b97bfe52f91f5a9e8e09fadf Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 7 Apr 2017 13:05:34 +0200 Subject: Fix nil#object_type error --- app/lib/atom_serializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/lib/atom_serializer.rb b/app/lib/atom_serializer.rb index 4ce0f4513..be1cced8b 100644 --- a/app/lib/atom_serializer.rb +++ b/app/lib/atom_serializer.rb @@ -101,7 +101,7 @@ class AtomSerializer serialize_status_attributes(object, status) append_element(object, 'link', nil, rel: :alternate, type: 'text/html', href: TagManager.instance.url_for(status)) - append_element(object, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(status.thread), href: TagManager.instance.url_for(status.thread)) if status.reply? + append_element(object, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(status.thread), href: TagManager.instance.url_for(status.thread)) if status.reply? && !status.thread.nil? object end -- cgit From d4c94fa004117fdb7226b1b846a12d12dc0542d9 Mon Sep 17 00:00:00 2001 From: Joël Quenneville Date: Fri, 7 Apr 2017 14:18:30 -0400 Subject: DRY up reblog vs original status check Checking reblog vs original status was happening in multiple places across the app. For views, this logic was encapsulated in a helper method named `proper_status` but in the other layers of the app, the logic was duplicated. Because the logic is used at all layers of the app, we extracted it into a `Status#proper` method on the model and changed all uses of the logic to use this method. There is now a single source of truth for this condition. We added test coverage to untested methods that got refactored. --- app/helpers/stream_entries_helper.rb | 4 -- app/lib/atom_serializer.rb | 2 +- app/models/account.rb | 4 +- app/models/status.rb | 6 ++- app/views/stream_entries/_status.html.haml | 2 +- spec/models/account_spec.rb | 68 +++++++++++++++++++++++++++++- spec/models/status_spec.rb | 11 +++++ 7 files changed, 86 insertions(+), 11 deletions(-) (limited to 'app') diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/stream_entries_helper.rb index a26e912a3..38e63ed8d 100644 --- a/app/helpers/stream_entries_helper.rb +++ b/app/helpers/stream_entries_helper.rb @@ -34,10 +34,6 @@ module StreamEntriesHelper user_signed_in? && @favourited.key?(status.id) ? 'favourited' : '' end - def proper_status(status) - status.reblog? ? status.reblog : status - end - def rtl?(text) return false if text.empty? diff --git a/app/lib/atom_serializer.rb b/app/lib/atom_serializer.rb index be1cced8b..b9dcee6b3 100644 --- a/app/lib/atom_serializer.rb +++ b/app/lib/atom_serializer.rb @@ -328,7 +328,7 @@ class AtomSerializer def serialize_status_attributes(entry, status) append_element(entry, 'summary', status.spoiler_text) unless status.spoiler_text.blank? - append_element(entry, 'content', Formatter.instance.format(status.reblog? ? status.reblog : status).to_str, type: 'html') + append_element(entry, 'content', Formatter.instance.format(status.proper).to_str, type: 'html') status.mentions.each do |mentioned| append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': TagManager::TYPES[:person], href: TagManager.instance.uri_for(mentioned.account)) diff --git a/app/models/account.rb b/app/models/account.rb index 6968607a2..cbba8b5b6 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -125,11 +125,11 @@ class Account < ApplicationRecord end def favourited?(status) - (status.reblog? ? status.reblog : status).favourites.where(account: self).count.positive? + status.proper.favourites.where(account: self).count.positive? end def reblogged?(status) - (status.reblog? ? status.reblog : status).reblogs.where(account: self).count.positive? + status.proper.reblogs.where(account: self).count.positive? end def keypair diff --git a/app/models/status.rb b/app/models/status.rb index 6948ad77c..7e3dd3e28 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -62,8 +62,12 @@ class Status < ApplicationRecord reply? ? :comment : :note end + def proper + reblog? ? reblog : self + end + def content - reblog? ? reblog.text : text + proper.text end def target diff --git a/app/views/stream_entries/_status.html.haml b/app/views/stream_entries/_status.html.haml index cdd0dde3b..434c5c8da 100644 --- a/app/views/stream_entries/_status.html.haml +++ b/app/views/stream_entries/_status.html.haml @@ -16,7 +16,7 @@ %strong= display_name(status.account) = t('stream_entries.reblogged') - = render partial: centered ? 'stream_entries/detailed_status' : 'stream_entries/simple_status', locals: { status: proper_status(status) } + = render partial: centered ? 'stream_entries/detailed_status' : 'stream_entries/simple_status', locals: { status: status.proper } - if include_threads = render partial: 'stream_entries/status', collection: @descendants, as: :status, locals: { is_successor: true } diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index d7f59adb8..93a45459d 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -99,11 +99,75 @@ RSpec.describe Account, type: :model do end describe '#favourited?' do - pending + let(:original_status) do + author = Fabricate(:account, username: 'original') + Fabricate(:status, account: author) + end + + context 'when the status is a reblog of another status' do + let(:original_reblog) do + author = Fabricate(:account, username: 'original_reblogger') + Fabricate(:status, reblog: original_status, account: author) + end + + it 'is is true when this account has favourited it' do + Fabricate(:favourite, status: original_reblog, account: subject) + + expect(subject.favourited?(original_status)).to eq true + end + + it 'is false when this account has not favourited it' do + expect(subject.favourited?(original_status)).to eq false + end + end + + context 'when the status is an original status' do + it 'is is true when this account has favourited it' do + Fabricate(:favourite, status: original_status, account: subject) + + expect(subject.favourited?(original_status)).to eq true + end + + it 'is false when this account has not favourited it' do + expect(subject.favourited?(original_status)).to eq false + end + end end describe '#reblogged?' do - pending + let(:original_status) do + author = Fabricate(:account, username: 'original') + Fabricate(:status, account: author) + end + + context 'when the status is a reblog of another status'do + let(:original_reblog) do + author = Fabricate(:account, username: 'original_reblogger') + Fabricate(:status, reblog: original_status, account: author) + end + + it 'is true when this account has reblogged it' do + Fabricate(:status, reblog: original_reblog, account: subject) + + expect(subject.reblogged?(original_reblog)).to eq true + end + + it 'is false when this account has not reblogged it' do + expect(subject.reblogged?(original_reblog)).to eq false + end + end + + context 'when the status is an original status' do + it 'is true when this account has reblogged it' do + Fabricate(:status, reblog: original_status, account: subject) + + expect(subject.reblogged?(original_status)).to eq true + end + + it 'is false when this account has not reblogged it' do + expect(subject.reblogged?(original_status)).to eq false + end + end end describe '.find_local' do diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index b9d079521..db244ebe7 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -97,4 +97,15 @@ RSpec.describe Status, type: :model do describe '#favourites_count' do pending end + + describe '#proper' do + it 'is itself for original statuses' do + expect(subject.proper).to eq subject + end + + it 'is the source status for reblogs' do + subject.reblog = other + expect(subject.proper).to eq other + end + end end -- cgit From ad5ddd5e95062d0d5cd4bc56baff53698c598723 Mon Sep 17 00:00:00 2001 From: Chad Pytel Date: Fri, 7 Apr 2017 14:19:16 -0400 Subject: Use I18n for media attachment validation errors These are currently user facing errors, but are not localized. This adds the ability for these messages to be localized. --- app/services/post_status_service.rb | 4 ++-- config/locales/en.yml | 4 ++++ spec/services/post_status_service_spec.rb | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index b8179f7dc..221aa42a3 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -37,11 +37,11 @@ class PostStatusService < BaseService def validate_media!(media_ids) return if media_ids.nil? || !media_ids.is_a?(Enumerable) - raise Mastodon::ValidationError, 'Cannot attach more than 4 files' if media_ids.size > 4 + raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if media_ids.size > 4 media = MediaAttachment.where(status_id: nil).where(id: media_ids.take(4).map(&:to_i)) - raise Mastodon::ValidationError, 'Cannot attach a video to a toot that already contains images' if media.size > 1 && media.find(&:video?) + raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if media.size > 1 && media.find(&:video?) media end diff --git a/config/locales/en.yml b/config/locales/en.yml index 742219df9..aa3a732f9 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -163,3 +163,7 @@ en: invalid_otp_token: Invalid two-factor code will_paginate: page_gap: "…" + media_attachments: + validations: + too_many: Cannot attach more than 4 files + images_and_video: Cannot attach a video to a status that already contains images diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb index 3fe878f63..0e39cd969 100644 --- a/spec/services/post_status_service_spec.rb +++ b/spec/services/post_status_service_spec.rb @@ -141,7 +141,7 @@ RSpec.describe PostStatusService do ) end.to raise_error( Mastodon::ValidationError, - 'Cannot attach more than 4 files', + I18n.t('media_attachments.validations.too_many'), ) end @@ -160,7 +160,7 @@ RSpec.describe PostStatusService do ) end.to raise_error( Mastodon::ValidationError, - 'Cannot attach a video to a toot that already contains images', + I18n.t('media_attachments.validations.images_and_video'), ) end -- cgit