From 7d16bb379d1463471faf264bb89e24d5b8a505ca Mon Sep 17 00:00:00 2001 From: nullkal Date: Tue, 19 Sep 2017 23:37:06 +0900 Subject: Use OrderedCollectionPage to return followers/following list (#4949) --- .../activitypub/collection_serializer.rb | 25 +++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) (limited to 'app/serializers') diff --git a/app/serializers/activitypub/collection_serializer.rb b/app/serializers/activitypub/collection_serializer.rb index d01dead28..9832133fc 100644 --- a/app/serializers/activitypub/collection_serializer.rb +++ b/app/serializers/activitypub/collection_serializer.rb @@ -3,23 +3,38 @@ class ActivityPub::CollectionSerializer < ActiveModel::Serializer def self.serializer_for(model, options) return ActivityPub::ActivitySerializer if model.class.name == 'Status' + return ActivityPub::CollectionSerializer if model.class.name == 'ActivityPub::CollectionPresenter' super end attributes :id, :type, :total_items + attribute :next, if: -> { object.next.present? } + attribute :prev, if: -> { object.prev.present? } + attribute :part_of, if: -> { object.part_of.present? } - has_many :items, key: :ordered_items + has_one :first, if: -> { object.first.present? } + has_many :items, key: :items, if: -> { (object.items.present? || page?) && !ordered? } + has_many :items, key: :ordered_items, if: -> { (object.items.present? || page?) && ordered? } def type - case object.type - when :ordered - 'OrderedCollection' + if page? + ordered? ? 'OrderedCollectionPage' : 'CollectionPage' else - 'Collection' + ordered? ? 'OrderedCollection' : 'Collection' end end def total_items object.size end + + private + + def ordered? + object.type == :ordered + end + + def page? + object.part_of.present? + end end -- cgit From bb4d005a8381091911697175416eb9c37379155e Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Wed, 20 Sep 2017 01:08:08 +0900 Subject: Introduce OStatus::TagManager (#5008) --- app/lib/activitypub/activity/create.rb | 2 +- app/lib/activitypub/tag_manager.rb | 4 +- app/lib/ostatus/activity/base.rb | 18 ++-- app/lib/ostatus/activity/creation.rb | 30 +++--- app/lib/ostatus/activity/share.rb | 2 +- app/lib/ostatus/atom_serializer.rb | 106 ++++++++++----------- app/lib/ostatus/tag_manager.rb | 73 ++++++++++++++ app/lib/tag_manager.rb | 67 ------------- app/models/remote_profile.rb | 12 +-- app/serializers/activitypub/delete_serializer.rb | 2 +- app/serializers/activitypub/note_serializer.rb | 6 +- app/serializers/rest/status_serializer.rb | 2 +- app/services/concerns/author_extractor.rb | 6 +- app/services/fetch_remote_account_service.rb | 2 +- app/services/fetch_remote_status_service.rb | 2 +- app/services/process_feed_service.rb | 2 +- app/services/process_interaction_service.rb | 16 ++-- app/services/verify_salmon_service.rb | 2 +- spec/lib/activitypub/tag_manager_spec.rb | 2 +- spec/lib/ostatus/atom_serializer_spec.rb | 90 ++++++++--------- spec/lib/ostatus/tag_manager_spec.rb | 70 ++++++++++++++ spec/lib/tag_manager_spec.rb | 65 ------------- spec/services/authorize_follow_service_spec.rb | 2 +- .../services/batched_remove_status_service_spec.rb | 4 +- spec/services/block_service_spec.rb | 2 +- spec/services/favourite_service_spec.rb | 2 +- spec/services/follow_service_spec.rb | 4 +- spec/services/reject_follow_service_spec.rb | 2 +- spec/services/remove_status_service_spec.rb | 4 +- spec/services/unblock_service_spec.rb | 2 +- spec/services/unfollow_service_spec.rb | 2 +- 31 files changed, 308 insertions(+), 297 deletions(-) create mode 100644 app/lib/ostatus/tag_manager.rb create mode 100644 spec/lib/ostatus/tag_manager_spec.rb (limited to 'app/serializers') diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 41f2b0bad..0964c9f53 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -115,7 +115,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity def conversation_from_uri(uri) return nil if uri.nil? - return Conversation.find_by(id: TagManager.instance.unique_tag_to_local_id(uri, 'Conversation')) if TagManager.instance.local_id?(uri) + return Conversation.find_by(id: OStatus::TagManager.instance.unique_tag_to_local_id(uri, 'Conversation')) if OStatus::TagManager.instance.local_id?(uri) Conversation.find_by(uri: uri) || Conversation.create!(uri: uri) end diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb index 1b4e271db..4ec3b8c56 100644 --- a/app/lib/activitypub/tag_manager.rb +++ b/app/lib/activitypub/tag_manager.rb @@ -98,8 +98,8 @@ class ActivityPub::TagManager else StatusFinder.new(uri).status end - elsif ::TagManager.instance.local_id?(uri) - klass.find_by(id: ::TagManager.instance.unique_tag_to_local_id(uri, klass.to_s)) + elsif OStatus::TagManager.instance.local_id?(uri) + klass.find_by(id: OStatus::TagManager.instance.unique_tag_to_local_id(uri, klass.to_s)) else klass.find_by(uri: uri.split('#').first) end diff --git a/app/lib/ostatus/activity/base.rb b/app/lib/ostatus/activity/base.rb index 1dc7abee3..039381397 100644 --- a/app/lib/ostatus/activity/base.rb +++ b/app/lib/ostatus/activity/base.rb @@ -11,30 +11,30 @@ class OStatus::Activity::Base end def verb - raw = @xml.at_xpath('./activity:verb', activity: TagManager::AS_XMLNS).content - TagManager::VERBS.key(raw) + raw = @xml.at_xpath('./activity:verb', activity: OStatus::TagManager::AS_XMLNS).content + OStatus::TagManager::VERBS.key(raw) rescue :post end def type - raw = @xml.at_xpath('./activity:object-type', activity: TagManager::AS_XMLNS).content - TagManager::TYPES.key(raw) + raw = @xml.at_xpath('./activity:object-type', activity: OStatus::TagManager::AS_XMLNS).content + OStatus::TagManager::TYPES.key(raw) rescue :activity end def id - @xml.at_xpath('./xmlns:id', xmlns: TagManager::XMLNS).content + @xml.at_xpath('./xmlns:id', xmlns: OStatus::TagManager::XMLNS).content end def url - link = @xml.xpath('./xmlns:link[@rel="alternate"]', xmlns: TagManager::XMLNS).find { |link_candidate| link_candidate['type'] == 'text/html' } + link = @xml.xpath('./xmlns:link[@rel="alternate"]', xmlns: OStatus::TagManager::XMLNS).find { |link_candidate| link_candidate['type'] == 'text/html' } link.nil? ? nil : link['href'] end def activitypub_uri - link = @xml.xpath('./xmlns:link[@rel="alternate"]', xmlns: TagManager::XMLNS).find { |link_candidate| ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(link_candidate['type']) } + link = @xml.xpath('./xmlns:link[@rel="alternate"]', xmlns: OStatus::TagManager::XMLNS).find { |link_candidate| ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(link_candidate['type']) } link.nil? ? nil : link['href'] end @@ -45,8 +45,8 @@ class OStatus::Activity::Base private def find_status(uri) - if TagManager.instance.local_id?(uri) - local_id = TagManager.instance.unique_tag_to_local_id(uri, 'Status') + if OStatus::TagManager.instance.local_id?(uri) + local_id = OStatus::TagManager.instance.unique_tag_to_local_id(uri, 'Status') return Status.find_by(id: local_id) elsif ActivityPub::TagManager.instance.local_uri?(uri) local_id = ActivityPub::TagManager.instance.uri_to_local_id(uri) diff --git a/app/lib/ostatus/activity/creation.rb b/app/lib/ostatus/activity/creation.rb index d3f1629c4..4f4ef2971 100644 --- a/app/lib/ostatus/activity/creation.rb +++ b/app/lib/ostatus/activity/creation.rb @@ -63,42 +63,42 @@ class OStatus::Activity::Creation < OStatus::Activity::Base end def content - @xml.at_xpath('./xmlns:content', xmlns: TagManager::XMLNS).content + @xml.at_xpath('./xmlns:content', xmlns: OStatus::TagManager::XMLNS).content end def content_language - @xml.at_xpath('./xmlns:content', xmlns: TagManager::XMLNS)['xml:lang']&.presence || 'en' + @xml.at_xpath('./xmlns:content', xmlns: OStatus::TagManager::XMLNS)['xml:lang']&.presence || 'en' end def content_warning - @xml.at_xpath('./xmlns:summary', xmlns: TagManager::XMLNS)&.content || '' + @xml.at_xpath('./xmlns:summary', xmlns: OStatus::TagManager::XMLNS)&.content || '' end def visibility_scope - @xml.at_xpath('./mastodon:scope', mastodon: TagManager::MTDN_XMLNS)&.content&.to_sym || :public + @xml.at_xpath('./mastodon:scope', mastodon: OStatus::TagManager::MTDN_XMLNS)&.content&.to_sym || :public end def published - @xml.at_xpath('./xmlns:published', xmlns: TagManager::XMLNS).content + @xml.at_xpath('./xmlns:published', xmlns: OStatus::TagManager::XMLNS).content end def thread? - !@xml.at_xpath('./thr:in-reply-to', thr: TagManager::THR_XMLNS).nil? + !@xml.at_xpath('./thr:in-reply-to', thr: OStatus::TagManager::THR_XMLNS).nil? end def thread - thr = @xml.at_xpath('./thr:in-reply-to', thr: TagManager::THR_XMLNS) + thr = @xml.at_xpath('./thr:in-reply-to', thr: OStatus::TagManager::THR_XMLNS) [thr['ref'], thr['href']] end private def find_or_create_conversation - uri = @xml.at_xpath('./ostatus:conversation', ostatus: TagManager::OS_XMLNS)&.attribute('ref')&.content + uri = @xml.at_xpath('./ostatus:conversation', ostatus: OStatus::TagManager::OS_XMLNS)&.attribute('ref')&.content return if uri.nil? - if TagManager.instance.local_id?(uri) - local_id = TagManager.instance.unique_tag_to_local_id(uri, 'Conversation') + if OStatus::TagManager.instance.local_id?(uri) + local_id = OStatus::TagManager.instance.unique_tag_to_local_id(uri, 'Conversation') return Conversation.find_by(id: local_id) end @@ -108,8 +108,8 @@ class OStatus::Activity::Creation < OStatus::Activity::Base def save_mentions(parent) processed_account_ids = [] - @xml.xpath('./xmlns:link[@rel="mentioned"]', xmlns: TagManager::XMLNS).each do |link| - next if [TagManager::TYPES[:group], TagManager::TYPES[:collection]].include? link['ostatus:object-type'] + @xml.xpath('./xmlns:link[@rel="mentioned"]', xmlns: OStatus::TagManager::XMLNS).each do |link| + next if [OStatus::TagManager::TYPES[:group], OStatus::TagManager::TYPES[:collection]].include? link['ostatus:object-type'] mentioned_account = account_from_href(link['href']) @@ -123,14 +123,14 @@ class OStatus::Activity::Creation < OStatus::Activity::Base end def save_hashtags(parent) - tags = @xml.xpath('./xmlns:category', xmlns: TagManager::XMLNS).map { |category| category['term'] }.select(&:present?) + tags = @xml.xpath('./xmlns:category', xmlns: OStatus::TagManager::XMLNS).map { |category| category['term'] }.select(&:present?) ProcessHashtagsService.new.call(parent, tags) end def save_media(parent) do_not_download = DomainBlock.find_by(domain: parent.account.domain)&.reject_media? - @xml.xpath('./xmlns:link[@rel="enclosure"]', xmlns: TagManager::XMLNS).each do |link| + @xml.xpath('./xmlns:link[@rel="enclosure"]', xmlns: OStatus::TagManager::XMLNS).each do |link| next unless link['href'] media = MediaAttachment.where(status: parent, remote_url: link['href']).first_or_initialize(account: parent.account, status: parent, remote_url: link['href']) @@ -156,7 +156,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base return if do_not_download - @xml.xpath('./xmlns:link[@rel="emoji"]', xmlns: TagManager::XMLNS).each do |link| + @xml.xpath('./xmlns:link[@rel="emoji"]', xmlns: OStatus::TagManager::XMLNS).each do |link| next unless link['href'] && link['name'] shortcode = link['name'].delete(':') diff --git a/app/lib/ostatus/activity/share.rb b/app/lib/ostatus/activity/share.rb index 290008021..5ca601415 100644 --- a/app/lib/ostatus/activity/share.rb +++ b/app/lib/ostatus/activity/share.rb @@ -10,7 +10,7 @@ class OStatus::Activity::Share < OStatus::Activity::Creation end def object - @xml.at_xpath('.//activity:object', activity: TagManager::AS_XMLNS) + @xml.at_xpath('.//activity:object', activity: OStatus::TagManager::AS_XMLNS) end private diff --git a/app/lib/ostatus/atom_serializer.rb b/app/lib/ostatus/atom_serializer.rb index a6a5cb0c4..a1ac11a51 100644 --- a/app/lib/ostatus/atom_serializer.rb +++ b/app/lib/ostatus/atom_serializer.rb @@ -15,10 +15,10 @@ class OStatus::AtomSerializer def author(account) author = Ox::Element.new('author') - uri = TagManager.instance.uri_for(account) + uri = OStatus::TagManager.instance.uri_for(account) append_element(author, 'id', uri) - append_element(author, 'activity:object-type', TagManager::TYPES[:person]) + append_element(author, 'activity:object-type', OStatus::TagManager::TYPES[:person]) append_element(author, 'uri', uri) append_element(author, 'name', account.username) append_element(author, 'email', account.local? ? account.local_username_and_domain : account.acct) @@ -65,15 +65,15 @@ class OStatus::AtomSerializer add_namespaces(entry) if root - append_element(entry, 'id', TagManager.instance.uri_for(stream_entry.status)) + append_element(entry, 'id', OStatus::TagManager.instance.uri_for(stream_entry.status)) 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 || "#{stream_entry.account.acct} deleted status") 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]) + append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[stream_entry.object_type]) + append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[stream_entry.verb]) entry << object(stream_entry.target) if stream_entry.targeted? @@ -88,7 +88,7 @@ class OStatus::AtomSerializer append_element(entry, 'link', nil, rel: :alternate, type: 'text/html', href: TagManager.instance.url_for(stream_entry.status)) 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? + append_element(entry, 'thr:in-reply-to', nil, ref: OStatus::TagManager.instance.uri_for(stream_entry.thread), href: TagManager.instance.url_for(stream_entry.thread)) if stream_entry.threaded? append_element(entry, 'ostatus:conversation', nil, ref: conversation_uri(stream_entry.status.conversation)) unless stream_entry&.status&.conversation_id.nil? entry @@ -97,20 +97,20 @@ class OStatus::AtomSerializer def object(status) object = Ox::Element.new('activity:object') - append_element(object, 'id', TagManager.instance.uri_for(status)) + append_element(object, 'id', OStatus::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]) + append_element(object, 'activity:object-type', OStatus::TagManager::TYPES[status.object_type]) + append_element(object, 'activity:verb', OStatus::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)) unless status.thread.nil? + append_element(object, 'thr:in-reply-to', nil, ref: OStatus::TagManager.instance.uri_for(status.thread), href: TagManager.instance.url_for(status.thread)) unless status.thread.nil? append_element(object, 'ostatus:conversation', nil, ref: conversation_uri(status.conversation)) unless status.conversation_id.nil? object @@ -122,14 +122,14 @@ class OStatus::AtomSerializer 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, 'id', OStatus::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]) + append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity]) + append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:follow]) object = author(follow.target_account) object.value = 'activity:object' @@ -142,13 +142,13 @@ class OStatus::AtomSerializer 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, 'id', OStatus::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]) + append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity]) + append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:request_friend]) object = author(follow_request.target_account) object.value = 'activity:object' @@ -161,19 +161,19 @@ class OStatus::AtomSerializer 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, 'id', OStatus::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]) + append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity]) + append_element(entry, 'activity:verb', OStatus::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]) + append_element(object, 'activity:object-type', OStatus::TagManager::TYPES[:activity]) + append_element(object, 'activity:verb', OStatus::TagManager::VERBS[:request_friend]) inner_object = author(follow_request.target_account) inner_object.value = 'activity:object' @@ -187,19 +187,19 @@ class OStatus::AtomSerializer 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, 'id', OStatus::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]) + append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity]) + append_element(entry, 'activity:verb', OStatus::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]) + append_element(object, 'activity:object-type', OStatus::TagManager::TYPES[:activity]) + append_element(object, 'activity:verb', OStatus::TagManager::VERBS[:request_friend]) inner_object = author(follow_request.target_account) inner_object.value = 'activity:object' @@ -215,14 +215,14 @@ class OStatus::AtomSerializer 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, 'id', OStatus::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]) + append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity]) + append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:unfollow]) object = author(follow.target_account) object.value = 'activity:object' @@ -237,13 +237,13 @@ class OStatus::AtomSerializer 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, 'id', OStatus::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]) + append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity]) + append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:block]) object = author(block.target_account) object.value = 'activity:object' @@ -258,13 +258,13 @@ class OStatus::AtomSerializer 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, 'id', OStatus::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]) + append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity]) + append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:unblock]) object = author(block.target_account) object.value = 'activity:object' @@ -279,18 +279,18 @@ class OStatus::AtomSerializer 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, 'id', OStatus::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]) + append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity]) + append_element(entry, 'activity:verb', OStatus::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)) + append_element(entry, 'thr:in-reply-to', nil, ref: OStatus::TagManager.instance.uri_for(favourite.status), href: TagManager.instance.url_for(favourite.status)) entry end @@ -301,18 +301,18 @@ class OStatus::AtomSerializer 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, 'id', OStatus::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]) + append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity]) + append_element(entry, 'activity:verb', OStatus::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)) + append_element(entry, 'thr:in-reply-to', nil, ref: OStatus::TagManager.instance.uri_for(favourite.status), href: TagManager.instance.url_for(favourite.status)) entry end @@ -332,17 +332,17 @@ class OStatus::AtomSerializer def conversation_uri(conversation) return conversation.uri if conversation.uri? - TagManager.instance.unique_tag(conversation.created_at, conversation.id, 'Conversation') + OStatus::TagManager.instance.unique_tag(conversation.created_at, conversation.id, 'Conversation') 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 + parent['xmlns'] = OStatus::TagManager::XMLNS + parent['xmlns:thr'] = OStatus::TagManager::THR_XMLNS + parent['xmlns:activity'] = OStatus::TagManager::AS_XMLNS + parent['xmlns:poco'] = OStatus::TagManager::POCO_XMLNS + parent['xmlns:media'] = OStatus::TagManager::MEDIA_XMLNS + parent['xmlns:ostatus'] = OStatus::TagManager::OS_XMLNS + parent['xmlns:mastodon'] = OStatus::TagManager::MTDN_XMLNS end def serialize_status_attributes(entry, status) @@ -352,10 +352,10 @@ class OStatus::AtomSerializer append_element(entry, 'content', Formatter.instance.format(status).to_str, type: 'html', 'xml:lang': status.language) 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)) + append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': OStatus::TagManager::TYPES[:person], href: OStatus::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? + append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': OStatus::TagManager::TYPES[:collection], href: OStatus::TagManager::COLLECTIONS[:public]) if status.public_visibility? status.tags.each do |tag| append_element(entry, 'category', nil, term: tag.name) diff --git a/app/lib/ostatus/tag_manager.rb b/app/lib/ostatus/tag_manager.rb new file mode 100644 index 000000000..4f4501312 --- /dev/null +++ b/app/lib/ostatus/tag_manager.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +class OStatus::TagManager + include Singleton + include RoutingHelper + + VERBS = { + post: 'http://activitystrea.ms/schema/1.0/post', + share: 'http://activitystrea.ms/schema/1.0/share', + favorite: 'http://activitystrea.ms/schema/1.0/favorite', + unfavorite: 'http://activitystrea.ms/schema/1.0/unfavorite', + delete: 'http://activitystrea.ms/schema/1.0/delete', + follow: 'http://activitystrea.ms/schema/1.0/follow', + request_friend: 'http://activitystrea.ms/schema/1.0/request-friend', + authorize: 'http://activitystrea.ms/schema/1.0/authorize', + reject: 'http://activitystrea.ms/schema/1.0/reject', + unfollow: 'http://ostatus.org/schema/1.0/unfollow', + block: 'http://mastodon.social/schema/1.0/block', + unblock: 'http://mastodon.social/schema/1.0/unblock', + }.freeze + + TYPES = { + activity: 'http://activitystrea.ms/schema/1.0/activity', + note: 'http://activitystrea.ms/schema/1.0/note', + comment: 'http://activitystrea.ms/schema/1.0/comment', + person: 'http://activitystrea.ms/schema/1.0/person', + collection: 'http://activitystrea.ms/schema/1.0/collection', + group: 'http://activitystrea.ms/schema/1.0/group', + }.freeze + + COLLECTIONS = { + public: 'http://activityschema.org/collection/public', + }.freeze + + XMLNS = 'http://www.w3.org/2005/Atom' + MEDIA_XMLNS = 'http://purl.org/syndication/atommedia' + AS_XMLNS = 'http://activitystrea.ms/spec/1.0/' + THR_XMLNS = 'http://purl.org/syndication/thread/1.0' + POCO_XMLNS = 'http://portablecontacts.net/spec/1.0' + DFRN_XMLNS = 'http://purl.org/macgirvin/dfrn/1.0' + OS_XMLNS = 'http://ostatus.org/schema/1.0' + MTDN_XMLNS = 'http://mastodon.social/schema/1.0' + + def unique_tag(date, id, type) + "tag:#{Rails.configuration.x.local_domain},#{date.strftime('%Y-%m-%d')}:objectId=#{id}:objectType=#{type}" + end + + def unique_tag_to_local_id(tag, expected_type) + return nil unless local_id?(tag) + + if ActivityPub::TagManager.instance.local_uri?(tag) + ActivityPub::TagManager.instance.uri_to_local_id(tag) + else + matches = Regexp.new("objectId=([\\d]+):objectType=#{expected_type}").match(tag) + return matches[1] unless matches.nil? + end + end + + def local_id?(id) + id.start_with?("tag:#{Rails.configuration.x.local_domain}") || ActivityPub::TagManager.instance.local_uri?(id) + end + + def uri_for(target) + return target.uri if target.respond_to?(:local?) && !target.local? + + case target.object_type + when :person + account_url(target) + when :note, :comment, :activity + target.uri || unique_tag(target.created_at, target.id, 'Status') + end + end +end diff --git a/app/lib/tag_manager.rb b/app/lib/tag_manager.rb index 1d0a24e42..fb364cb98 100644 --- a/app/lib/tag_manager.rb +++ b/app/lib/tag_manager.rb @@ -6,62 +6,6 @@ class TagManager include Singleton include RoutingHelper - VERBS = { - post: 'http://activitystrea.ms/schema/1.0/post', - share: 'http://activitystrea.ms/schema/1.0/share', - favorite: 'http://activitystrea.ms/schema/1.0/favorite', - unfavorite: 'http://activitystrea.ms/schema/1.0/unfavorite', - delete: 'http://activitystrea.ms/schema/1.0/delete', - follow: 'http://activitystrea.ms/schema/1.0/follow', - request_friend: 'http://activitystrea.ms/schema/1.0/request-friend', - authorize: 'http://activitystrea.ms/schema/1.0/authorize', - reject: 'http://activitystrea.ms/schema/1.0/reject', - unfollow: 'http://ostatus.org/schema/1.0/unfollow', - block: 'http://mastodon.social/schema/1.0/block', - unblock: 'http://mastodon.social/schema/1.0/unblock', - }.freeze - - TYPES = { - activity: 'http://activitystrea.ms/schema/1.0/activity', - note: 'http://activitystrea.ms/schema/1.0/note', - comment: 'http://activitystrea.ms/schema/1.0/comment', - person: 'http://activitystrea.ms/schema/1.0/person', - collection: 'http://activitystrea.ms/schema/1.0/collection', - group: 'http://activitystrea.ms/schema/1.0/group', - }.freeze - - COLLECTIONS = { - public: 'http://activityschema.org/collection/public', - }.freeze - - XMLNS = 'http://www.w3.org/2005/Atom' - MEDIA_XMLNS = 'http://purl.org/syndication/atommedia' - AS_XMLNS = 'http://activitystrea.ms/spec/1.0/' - THR_XMLNS = 'http://purl.org/syndication/thread/1.0' - POCO_XMLNS = 'http://portablecontacts.net/spec/1.0' - DFRN_XMLNS = 'http://purl.org/macgirvin/dfrn/1.0' - OS_XMLNS = 'http://ostatus.org/schema/1.0' - MTDN_XMLNS = 'http://mastodon.social/schema/1.0' - - def unique_tag(date, id, type) - "tag:#{Rails.configuration.x.local_domain},#{date.strftime('%Y-%m-%d')}:objectId=#{id}:objectType=#{type}" - end - - def unique_tag_to_local_id(tag, expected_type) - return nil unless local_id?(tag) - - if ActivityPub::TagManager.instance.local_uri?(tag) - ActivityPub::TagManager.instance.uri_to_local_id(tag) - else - matches = Regexp.new("objectId=([\\d]+):objectType=#{expected_type}").match(tag) - return matches[1] unless matches.nil? - end - end - - def local_id?(id) - id.start_with?("tag:#{Rails.configuration.x.local_domain}") || ActivityPub::TagManager.instance.local_uri?(id) - end - def web_domain?(domain) domain.nil? || domain.gsub(/[\/]/, '').casecmp(Rails.configuration.x.web_domain).zero? end @@ -90,17 +34,6 @@ class TagManager TagManager.instance.web_domain?(domain) end - def uri_for(target) - return target.uri if target.respond_to?(:local?) && !target.local? - - case target.object_type - when :person - account_url(target) - when :note, :comment, :activity - target.uri || unique_tag(target.created_at, target.id, 'Status') - end - end - def url_for(target) return target.url if target.respond_to?(:local?) && !target.local? diff --git a/app/models/remote_profile.rb b/app/models/remote_profile.rb index 93c759930..613911c57 100644 --- a/app/models/remote_profile.rb +++ b/app/models/remote_profile.rb @@ -10,11 +10,11 @@ class RemoteProfile end def root - @root ||= document.at_xpath('/atom:feed|/atom:entry', atom: TagManager::XMLNS) + @root ||= document.at_xpath('/atom:feed|/atom:entry', atom: OStatus::TagManager::XMLNS) end def author - @author ||= root.at_xpath('./atom:author|./dfrn:owner', atom: TagManager::XMLNS, dfrn: TagManager::DFRN_XMLNS) + @author ||= root.at_xpath('./atom:author|./dfrn:owner', atom: OStatus::TagManager::XMLNS, dfrn: OStatus::TagManager::DFRN_XMLNS) end def hub_link @@ -22,15 +22,15 @@ class RemoteProfile end def display_name - @display_name ||= author.at_xpath('./poco:displayName', poco: TagManager::POCO_XMLNS)&.content + @display_name ||= author.at_xpath('./poco:displayName', poco: OStatus::TagManager::POCO_XMLNS)&.content end def note - @note ||= author.at_xpath('./atom:summary|./poco:note', atom: TagManager::XMLNS, poco: TagManager::POCO_XMLNS)&.content + @note ||= author.at_xpath('./atom:summary|./poco:note', atom: OStatus::TagManager::XMLNS, poco: OStatus::TagManager::POCO_XMLNS)&.content end def scope - @scope ||= author.at_xpath('./mastodon:scope', mastodon: TagManager::MTDN_XMLNS)&.content + @scope ||= author.at_xpath('./mastodon:scope', mastodon: OStatus::TagManager::MTDN_XMLNS)&.content end def avatar @@ -48,6 +48,6 @@ class RemoteProfile private def link_href_from_xml(xml, type) - xml.at_xpath(%(./atom:link[@rel="#{type}"]/@href), atom: TagManager::XMLNS)&.content + xml.at_xpath(%(./atom:link[@rel="#{type}"]/@href), atom: OStatus::TagManager::XMLNS)&.content end end diff --git a/app/serializers/activitypub/delete_serializer.rb b/app/serializers/activitypub/delete_serializer.rb index 87a43b95d..2bb65135f 100644 --- a/app/serializers/activitypub/delete_serializer.rb +++ b/app/serializers/activitypub/delete_serializer.rb @@ -13,7 +13,7 @@ class ActivityPub::DeleteSerializer < ActiveModel::Serializer end def atom_uri - ::TagManager.instance.uri_for(object) + OStatus::TagManager.instance.uri_for(object) end end diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb index e5d8e3f03..f94c3b9dc 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -63,13 +63,13 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer def atom_uri return unless object.local? - ::TagManager.instance.uri_for(object) + OStatus::TagManager.instance.uri_for(object) end def in_reply_to_atom_uri return unless object.reply? && !object.thread.nil? - ::TagManager.instance.uri_for(object.thread) + OStatus::TagManager.instance.uri_for(object.thread) end def conversation @@ -78,7 +78,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer if object.conversation.uri? object.conversation.uri else - TagManager.instance.unique_tag(object.conversation.created_at, object.conversation.id, 'Conversation') + OStatus::TagManager.instance.unique_tag(object.conversation.created_at, object.conversation.id, 'Conversation') end end diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index d8efa8e60..066d65d9e 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -24,7 +24,7 @@ class REST::StatusSerializer < ActiveModel::Serializer end def uri - TagManager.instance.uri_for(object) + OStatus::TagManager.instance.uri_for(object) end def content diff --git a/app/services/concerns/author_extractor.rb b/app/services/concerns/author_extractor.rb index 867d6dc25..c2366188a 100644 --- a/app/services/concerns/author_extractor.rb +++ b/app/services/concerns/author_extractor.rb @@ -5,12 +5,12 @@ module AuthorExtractor return nil if xml.nil? # Try for acct - acct = xml.at_xpath('./xmlns:author/xmlns:email', xmlns: TagManager::XMLNS)&.content + acct = xml.at_xpath('./xmlns:author/xmlns:email', xmlns: OStatus::TagManager::XMLNS)&.content # Try + if acct.blank? - username = xml.at_xpath('./xmlns:author/xmlns:name', xmlns: TagManager::XMLNS)&.content - uri = xml.at_xpath('./xmlns:author/xmlns:uri', xmlns: TagManager::XMLNS)&.content + username = xml.at_xpath('./xmlns:author/xmlns:name', xmlns: OStatus::TagManager::XMLNS)&.content + uri = xml.at_xpath('./xmlns:author/xmlns:uri', xmlns: OStatus::TagManager::XMLNS)&.content return nil if username.blank? || uri.blank? diff --git a/app/services/fetch_remote_account_service.rb b/app/services/fetch_remote_account_service.rb index 7c618a0b0..bd98e70d1 100644 --- a/app/services/fetch_remote_account_service.rb +++ b/app/services/fetch_remote_account_service.rb @@ -25,7 +25,7 @@ class FetchRemoteAccountService < BaseService xml = Nokogiri::XML(body) xml.encoding = 'utf-8' - account = author_from_xml(xml.at_xpath('/xmlns:feed', xmlns: TagManager::XMLNS), false) + account = author_from_xml(xml.at_xpath('/xmlns:feed', xmlns: OStatus::TagManager::XMLNS), false) UpdateRemoteProfileService.new.call(xml, account) unless account.nil? diff --git a/app/services/fetch_remote_status_service.rb b/app/services/fetch_remote_status_service.rb index 18af18059..1b90854c4 100644 --- a/app/services/fetch_remote_status_service.rb +++ b/app/services/fetch_remote_status_service.rb @@ -27,7 +27,7 @@ class FetchRemoteStatusService < BaseService xml = Nokogiri::XML(body) xml.encoding = 'utf-8' - account = author_from_xml(xml.at_xpath('/xmlns:entry', xmlns: TagManager::XMLNS)) + account = author_from_xml(xml.at_xpath('/xmlns:entry', xmlns: OStatus::TagManager::XMLNS)) domain = Addressable::URI.parse(url).normalized_host return nil unless !account.nil? && confirmed_domain?(domain, account) diff --git a/app/services/process_feed_service.rb b/app/services/process_feed_service.rb index 31191a818..2a5f1e2bc 100644 --- a/app/services/process_feed_service.rb +++ b/app/services/process_feed_service.rb @@ -16,7 +16,7 @@ class ProcessFeedService < BaseService end def process_entries(xml, account) - xml.xpath('//xmlns:entry', xmlns: TagManager::XMLNS).reverse_each.map { |entry| process_entry(entry, account) }.compact + xml.xpath('//xmlns:entry', xmlns: OStatus::TagManager::XMLNS).reverse_each.map { |entry| process_entry(entry, account) }.compact end def process_entry(xml, account) diff --git a/app/services/process_interaction_service.rb b/app/services/process_interaction_service.rb index d04e926e7..1fca3832b 100644 --- a/app/services/process_interaction_service.rb +++ b/app/services/process_interaction_service.rb @@ -13,7 +13,7 @@ class ProcessInteractionService < BaseService xml = Nokogiri::XML(body) xml.encoding = 'utf-8' - account = author_from_xml(xml.at_xpath('/xmlns:entry', xmlns: TagManager::XMLNS)) + account = author_from_xml(xml.at_xpath('/xmlns:entry', xmlns: OStatus::TagManager::XMLNS)) return if account.nil? || account.suspended? @@ -54,13 +54,13 @@ class ProcessInteractionService < BaseService private def mentions_account?(xml, account) - xml.xpath('/xmlns:entry/xmlns:link[@rel="mentioned"]', xmlns: TagManager::XMLNS).each { |mention_link| return true if [TagManager.instance.uri_for(account), TagManager.instance.url_for(account)].include?(mention_link.attribute('href').value) } + xml.xpath('/xmlns:entry/xmlns:link[@rel="mentioned"]', xmlns: OStatus::TagManager::XMLNS).each { |mention_link| return true if [OStatus::TagManager.instance.uri_for(account), OStatus::TagManager.instance.url_for(account)].include?(mention_link.attribute('href').value) } false end def verb(xml) - raw = xml.at_xpath('//activity:verb', activity: TagManager::AS_XMLNS).content - TagManager::VERBS.key(raw) + raw = xml.at_xpath('//activity:verb', activity: OStatus::TagManager::AS_XMLNS).content + OStatus::TagManager::VERBS.key(raw) rescue :post end @@ -104,7 +104,7 @@ class ProcessInteractionService < BaseService end def delete_post!(xml, account) - status = Status.find(xml.at_xpath('//xmlns:id', xmlns: TagManager::XMLNS).content) + status = Status.find(xml.at_xpath('//xmlns:id', xmlns: OStatus::TagManager::XMLNS).content) return if status.nil? @@ -137,12 +137,12 @@ class ProcessInteractionService < BaseService def status(xml) uri = activity_id(xml) - return nil unless TagManager.instance.local_id?(uri) - Status.find(TagManager.instance.unique_tag_to_local_id(uri, 'Status')) + return nil unless OStatus::TagManager.instance.local_id?(uri) + Status.find(OStatus::TagManager.instance.unique_tag_to_local_id(uri, 'Status')) end def activity_id(xml) - xml.at_xpath('//activity:object', activity: TagManager::AS_XMLNS).at_xpath('./xmlns:id', xmlns: TagManager::XMLNS).content + xml.at_xpath('//activity:object', activity: OStatus::TagManager::AS_XMLNS).at_xpath('./xmlns:id', xmlns: OStatus::TagManager::XMLNS).content end def salmon diff --git a/app/services/verify_salmon_service.rb b/app/services/verify_salmon_service.rb index cd674837d..205b35d8b 100644 --- a/app/services/verify_salmon_service.rb +++ b/app/services/verify_salmon_service.rb @@ -9,7 +9,7 @@ class VerifySalmonService < BaseService xml = Nokogiri::XML(body) xml.encoding = 'utf-8' - account = author_from_xml(xml.at_xpath('/xmlns:entry', xmlns: TagManager::XMLNS)) + account = author_from_xml(xml.at_xpath('/xmlns:entry', xmlns: OStatus::TagManager::XMLNS)) if account.nil? false diff --git a/spec/lib/activitypub/tag_manager_spec.rb b/spec/lib/activitypub/tag_manager_spec.rb index dea8abc65..0d1665216 100644 --- a/spec/lib/activitypub/tag_manager_spec.rb +++ b/spec/lib/activitypub/tag_manager_spec.rb @@ -108,7 +108,7 @@ RSpec.describe ActivityPub::TagManager do it 'returns the local status for OStatus tag: URI' do status = Fabricate(:status) - expect(subject.uri_to_resource(::TagManager.instance.uri_for(status), Status)).to eq status + expect(subject.uri_to_resource(OStatus::TagManager.instance.uri_for(status), Status)).to eq status end it 'returns the local status for OStatus StreamEntry URL' do diff --git a/spec/lib/ostatus/atom_serializer_spec.rb b/spec/lib/ostatus/atom_serializer_spec.rb index b2480a53b..00e6f09dc 100644 --- a/spec/lib/ostatus/atom_serializer_spec.rb +++ b/spec/lib/ostatus/atom_serializer_spec.rb @@ -17,7 +17,7 @@ RSpec.describe OStatus::AtomSerializer do follow_request_salmon = serialize(follow_request) object_type = follow_request_salmon.nodes.find { |node| node.name == 'activity:object-type' } - expect(object_type.text).to eq TagManager::TYPES[:activity] + expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity] end it 'appends activity:verb element with request_friend type' do @@ -26,7 +26,7 @@ RSpec.describe OStatus::AtomSerializer do follow_request_salmon = serialize(follow_request) verb = follow_request_salmon.nodes.find { |node| node.name == 'activity:verb' } - expect(verb.text).to eq TagManager::VERBS[:request_friend] + expect(verb.text).to eq OStatus::TagManager::VERBS[:request_friend] end it 'appends activity:object with target account' do @@ -44,13 +44,13 @@ RSpec.describe OStatus::AtomSerializer do it 'adds namespaces' do element = serialize - expect(element['xmlns']).to eq TagManager::XMLNS - expect(element['xmlns:thr']).to eq TagManager::THR_XMLNS - expect(element['xmlns:activity']).to eq TagManager::AS_XMLNS - expect(element['xmlns:poco']).to eq TagManager::POCO_XMLNS - expect(element['xmlns:media']).to eq TagManager::MEDIA_XMLNS - expect(element['xmlns:ostatus']).to eq TagManager::OS_XMLNS - expect(element['xmlns:mastodon']).to eq TagManager::MTDN_XMLNS + expect(element['xmlns']).to eq OStatus::TagManager::XMLNS + expect(element['xmlns:thr']).to eq OStatus::TagManager::THR_XMLNS + expect(element['xmlns:activity']).to eq OStatus::TagManager::AS_XMLNS + expect(element['xmlns:poco']).to eq OStatus::TagManager::POCO_XMLNS + expect(element['xmlns:media']).to eq OStatus::TagManager::MEDIA_XMLNS + expect(element['xmlns:ostatus']).to eq OStatus::TagManager::OS_XMLNS + expect(element['xmlns:mastodon']).to eq OStatus::TagManager::MTDN_XMLNS end end @@ -98,7 +98,7 @@ RSpec.describe OStatus::AtomSerializer do mentioned = element.nodes.find do |node| node.name == 'link' && node[:rel] == 'mentioned' && - node['ostatus:object-type'] == TagManager::TYPES[:person] + node['ostatus:object-type'] == OStatus::TagManager::TYPES[:person] end expect(mentioned[:href]).to eq 'https://cb6e6126.ngrok.io/users/username' @@ -188,7 +188,7 @@ RSpec.describe OStatus::AtomSerializer do author = OStatus::AtomSerializer.new.author(account) object_type = author.nodes.find { |node| node.name == 'activity:object-type' } - expect(object_type.text).to eq TagManager::TYPES[:person] + expect(object_type.text).to eq OStatus::TagManager::TYPES[:person] end it 'appends email element with username and domain for local account' do @@ -358,9 +358,9 @@ RSpec.describe OStatus::AtomSerializer do mentioned_person = entry.nodes.find do |node| node.name == 'link' && node[:rel] == 'mentioned' && - node['ostatus:object-type'] == TagManager::TYPES[:collection] + node['ostatus:object-type'] == OStatus::TagManager::TYPES[:collection] end - expect(mentioned_person[:href]).to eq TagManager::COLLECTIONS[:public] + expect(mentioned_person[:href]).to eq OStatus::TagManager::COLLECTIONS[:public] end it 'does not append link element for the public collection if status is not publicly visible' do @@ -371,8 +371,8 @@ RSpec.describe OStatus::AtomSerializer do entry.nodes.each do |node| if node.name == 'link' && node[:rel] == 'mentioned' && - node['ostatus:object-type'] == TagManager::TYPES[:collection] - expect(mentioned_collection[:href]).not_to eq TagManager::COLLECTIONS[:public] + node['ostatus:object-type'] == OStatus::TagManager::TYPES[:collection] + expect(mentioned_collection[:href]).not_to eq OStatus::TagManager::COLLECTIONS[:public] end end end @@ -506,7 +506,7 @@ RSpec.describe OStatus::AtomSerializer do status = Fabricate(:status) entry = OStatus::AtomSerializer.new.entry(status.stream_entry) object_type = entry.nodes.find { |node| node.name == 'activity:object-type' } - expect(object_type.text).to eq TagManager::TYPES[:note] + expect(object_type.text).to eq OStatus::TagManager::TYPES[:note] end it 'appends activity:verb element with object type' do @@ -515,7 +515,7 @@ RSpec.describe OStatus::AtomSerializer do entry = OStatus::AtomSerializer.new.entry(status.stream_entry) object_type = entry.nodes.find { |node| node.name == 'activity:verb' } - expect(object_type.text).to eq TagManager::VERBS[:post] + expect(object_type.text).to eq OStatus::TagManager::VERBS[:post] end it 'appends activity:object element with target if present' do @@ -739,8 +739,8 @@ RSpec.describe OStatus::AtomSerializer do time_after = Time.now expect(block_salmon.id.text).to( - eq(TagManager.instance.unique_tag(time_before.utc, block.id, 'Block')) - .or(eq(TagManager.instance.unique_tag(time_after.utc, block.id, 'Block'))) + eq(OStatus::TagManager.instance.unique_tag(time_before.utc, block.id, 'Block')) + .or(eq(OStatus::TagManager.instance.unique_tag(time_after.utc, block.id, 'Block'))) ) end @@ -769,7 +769,7 @@ RSpec.describe OStatus::AtomSerializer do block_salmon = OStatus::AtomSerializer.new.block_salmon(block) object_type = block_salmon.nodes.find { |node| node.name == 'activity:object-type' } - expect(object_type.text).to eq TagManager::TYPES[:activity] + expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity] end it 'appends activity:verb element with block' do @@ -778,7 +778,7 @@ RSpec.describe OStatus::AtomSerializer do block_salmon = OStatus::AtomSerializer.new.block_salmon(block) verb = block_salmon.nodes.find { |node| node.name == 'activity:verb' } - expect(verb.text).to eq TagManager::VERBS[:block] + expect(verb.text).to eq OStatus::TagManager::VERBS[:block] end it 'appends activity:object element with target account' do @@ -826,8 +826,8 @@ RSpec.describe OStatus::AtomSerializer do time_after = Time.now expect(unblock_salmon.id.text).to( - eq(TagManager.instance.unique_tag(time_before.utc, block.id, 'Block')) - .or(eq(TagManager.instance.unique_tag(time_after.utc, block.id, 'Block'))) + eq(OStatus::TagManager.instance.unique_tag(time_before.utc, block.id, 'Block')) + .or(eq(OStatus::TagManager.instance.unique_tag(time_after.utc, block.id, 'Block'))) ) end @@ -856,7 +856,7 @@ RSpec.describe OStatus::AtomSerializer do unblock_salmon = OStatus::AtomSerializer.new.unblock_salmon(block) object_type = unblock_salmon.nodes.find { |node| node.name == 'activity:object-type' } - expect(object_type.text).to eq TagManager::TYPES[:activity] + expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity] end it 'appends activity:verb element with block' do @@ -865,7 +865,7 @@ RSpec.describe OStatus::AtomSerializer do unblock_salmon = OStatus::AtomSerializer.new.unblock_salmon(block) verb = unblock_salmon.nodes.find { |node| node.name == 'activity:verb' } - expect(verb.text).to eq TagManager::VERBS[:unblock] + expect(verb.text).to eq OStatus::TagManager::VERBS[:unblock] end it 'appends activity:object element with target account' do @@ -934,7 +934,7 @@ RSpec.describe OStatus::AtomSerializer do favourite_salmon = OStatus::AtomSerializer.new.favourite_salmon(favourite) verb = favourite_salmon.nodes.find { |node| node.name == 'activity:verb' } - expect(verb.text).to eq TagManager::VERBS[:favorite] + expect(verb.text).to eq OStatus::TagManager::VERBS[:favorite] end it 'appends activity:object element with status' do @@ -1005,8 +1005,8 @@ RSpec.describe OStatus::AtomSerializer do time_after = Time.now expect(unfavourite_salmon.id.text).to( - eq(TagManager.instance.unique_tag(time_before.utc, favourite.id, 'Favourite')) - .or(eq(TagManager.instance.unique_tag(time_after.utc, favourite.id, 'Favourite'))) + eq(OStatus::TagManager.instance.unique_tag(time_before.utc, favourite.id, 'Favourite')) + .or(eq(OStatus::TagManager.instance.unique_tag(time_after.utc, favourite.id, 'Favourite'))) ) end @@ -1034,7 +1034,7 @@ RSpec.describe OStatus::AtomSerializer do unfavourite_salmon = OStatus::AtomSerializer.new.unfavourite_salmon(favourite) verb = unfavourite_salmon.nodes.find { |node| node.name == 'activity:verb' } - expect(verb.text).to eq TagManager::VERBS[:unfavorite] + expect(verb.text).to eq OStatus::TagManager::VERBS[:unfavorite] end it 'appends activity:object element with status' do @@ -1117,7 +1117,7 @@ RSpec.describe OStatus::AtomSerializer do follow_salmon = OStatus::AtomSerializer.new.follow_salmon(follow) object_type = follow_salmon.nodes.find { |node| node.name == 'activity:object-type' } - expect(object_type.text).to eq TagManager::TYPES[:activity] + expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity] end it 'appends activity:verb element with follow' do @@ -1126,7 +1126,7 @@ RSpec.describe OStatus::AtomSerializer do follow_salmon = OStatus::AtomSerializer.new.follow_salmon(follow) verb = follow_salmon.nodes.find { |node| node.name == 'activity:verb' } - expect(verb.text).to eq TagManager::VERBS[:follow] + expect(verb.text).to eq OStatus::TagManager::VERBS[:follow] end it 'appends activity:object element with target account' do @@ -1190,8 +1190,8 @@ RSpec.describe OStatus::AtomSerializer do time_after = Time.now expect(unfollow_salmon.id.text).to( - eq(TagManager.instance.unique_tag(time_before.utc, follow.id, 'Follow')) - .or(eq(TagManager.instance.unique_tag(time_after.utc, follow.id, 'Follow'))) + eq(OStatus::TagManager.instance.unique_tag(time_before.utc, follow.id, 'Follow')) + .or(eq(OStatus::TagManager.instance.unique_tag(time_after.utc, follow.id, 'Follow'))) ) end @@ -1234,7 +1234,7 @@ RSpec.describe OStatus::AtomSerializer do unfollow_salmon = OStatus::AtomSerializer.new.unfollow_salmon(follow) object_type = unfollow_salmon.nodes.find { |node| node.name == 'activity:object-type' } - expect(object_type.text).to eq TagManager::TYPES[:activity] + expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity] end it 'appends activity:verb element with follow' do @@ -1244,7 +1244,7 @@ RSpec.describe OStatus::AtomSerializer do unfollow_salmon = OStatus::AtomSerializer.new.unfollow_salmon(follow) verb = unfollow_salmon.nodes.find { |node| node.name == 'activity:verb' } - expect(verb.text).to eq TagManager::VERBS[:unfollow] + expect(verb.text).to eq OStatus::TagManager::VERBS[:unfollow] end it 'appends activity:object element with target account' do @@ -1338,8 +1338,8 @@ RSpec.describe OStatus::AtomSerializer do time_after = Time.now expect(authorize_follow_request_salmon.id.text).to( - eq(TagManager.instance.unique_tag(time_before.utc, follow_request.id, 'FollowRequest')) - .or(eq(TagManager.instance.unique_tag(time_after.utc, follow_request.id, 'FollowRequest'))) + eq(OStatus::TagManager.instance.unique_tag(time_before.utc, follow_request.id, 'FollowRequest')) + .or(eq(OStatus::TagManager.instance.unique_tag(time_after.utc, follow_request.id, 'FollowRequest'))) ) end @@ -1359,7 +1359,7 @@ RSpec.describe OStatus::AtomSerializer do authorize_follow_request_salmon = OStatus::AtomSerializer.new.authorize_follow_request_salmon(follow_request) object_type = authorize_follow_request_salmon.nodes.find { |node| node.name == 'activity:object-type' } - expect(object_type.text).to eq TagManager::TYPES[:activity] + expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity] end it 'appends activity:verb element with authorize' do @@ -1368,7 +1368,7 @@ RSpec.describe OStatus::AtomSerializer do authorize_follow_request_salmon = OStatus::AtomSerializer.new.authorize_follow_request_salmon(follow_request) verb = authorize_follow_request_salmon.nodes.find { |node| node.name == 'activity:verb' } - expect(verb.text).to eq TagManager::VERBS[:authorize] + expect(verb.text).to eq OStatus::TagManager::VERBS[:authorize] end it 'returns element whose rendered view creates follow from follow request when processed' do @@ -1407,8 +1407,8 @@ RSpec.describe OStatus::AtomSerializer do time_after = Time.now expect(reject_follow_request_salmon.id.text).to( - eq(TagManager.instance.unique_tag(time_before.utc, follow_request.id, 'FollowRequest')) - .or(TagManager.instance.unique_tag(time_after.utc, follow_request.id, 'FollowRequest')) + eq(OStatus::TagManager.instance.unique_tag(time_before.utc, follow_request.id, 'FollowRequest')) + .or(OStatus::TagManager.instance.unique_tag(time_after.utc, follow_request.id, 'FollowRequest')) ) end @@ -1424,14 +1424,14 @@ RSpec.describe OStatus::AtomSerializer do follow_request = Fabricate(:follow_request) reject_follow_request_salmon = OStatus::AtomSerializer.new.reject_follow_request_salmon(follow_request) object_type = reject_follow_request_salmon.nodes.find { |node| node.name == 'activity:object-type' } - expect(object_type.text).to eq TagManager::TYPES[:activity] + expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity] end it 'appends activity:verb element with authorize' do follow_request = Fabricate(:follow_request) reject_follow_request_salmon = OStatus::AtomSerializer.new.reject_follow_request_salmon(follow_request) verb = reject_follow_request_salmon.nodes.find { |node| node.name == 'activity:verb' } - expect(verb.text).to eq TagManager::VERBS[:reject] + expect(verb.text).to eq OStatus::TagManager::VERBS[:reject] end it 'returns element whose rendered view deletes follow request when processed' do @@ -1503,7 +1503,7 @@ RSpec.describe OStatus::AtomSerializer do entry = OStatus::AtomSerializer.new.object(status) object_type = entry.nodes.find { |node| node.name == 'activity:object-type' } - expect(object_type.text).to eq TagManager::TYPES[:note] + expect(object_type.text).to eq OStatus::TagManager::TYPES[:note] end it 'appends activity:verb element with verb' do @@ -1512,7 +1512,7 @@ RSpec.describe OStatus::AtomSerializer do entry = OStatus::AtomSerializer.new.object(status) object_type = entry.nodes.find { |node| node.name == 'activity:verb' } - expect(object_type.text).to eq TagManager::VERBS[:post] + expect(object_type.text).to eq OStatus::TagManager::VERBS[:post] end it 'appends link element for an alternative' do diff --git a/spec/lib/ostatus/tag_manager_spec.rb b/spec/lib/ostatus/tag_manager_spec.rb new file mode 100644 index 000000000..31195bae2 --- /dev/null +++ b/spec/lib/ostatus/tag_manager_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe OStatus::TagManager do + describe '#unique_tag' do + it 'returns a unique tag' do + expect(OStatus::TagManager.instance.unique_tag(Time.utc(2000), 12, 'Status')).to eq 'tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Status' + end + end + + describe '#unique_tag_to_local_id' do + it 'returns the ID part' do + expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Status', 'Status')).to eql '12' + end + + it 'returns nil if it is not local id' do + expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:remote,2000-01-01:objectId=12:objectType=Status', 'Status')).to eq nil + end + + it 'returns nil if it is not expected type' do + expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Block', 'Status')).to eq nil + end + + it 'returns nil if it does not have object ID' do + expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectType=Status', 'Status')).to eq nil + end + end + + describe '#local_id?' do + it 'returns true for a local ID' do + expect(OStatus::TagManager.instance.local_id?('tag:cb6e6126.ngrok.io;objectId=12:objectType=Status')).to be true + end + + it 'returns false for a foreign ID' do + expect(OStatus::TagManager.instance.local_id?('tag:foreign.tld;objectId=12:objectType=Status')).to be false + end + end + + describe '#uri_for' do + subject { OStatus::TagManager.instance.uri_for(target) } + + context 'comment object' do + let(:target) { Fabricate(:status, created_at: '2000-01-01T00:00:00Z', reply: true) } + + it 'returns the unique tag for status' do + expect(target.object_type).to eq :comment + is_expected.to eq target.uri + end + end + + context 'note object' do + let(:target) { Fabricate(:status, created_at: '2000-01-01T00:00:00Z', reply: false, thread: nil) } + + it 'returns the unique tag for status' do + expect(target.object_type).to eq :note + is_expected.to eq target.uri + end + end + + context 'person object' do + let(:target) { Fabricate(:account, username: 'alice') } + + it 'returns the URL for account' do + expect(target.object_type).to eq :person + is_expected.to eq 'https://cb6e6126.ngrok.io/users/alice' + end + end + end +end diff --git a/spec/lib/tag_manager_spec.rb b/spec/lib/tag_manager_spec.rb index 6c7830231..5427a2929 100644 --- a/spec/lib/tag_manager_spec.rb +++ b/spec/lib/tag_manager_spec.rb @@ -120,71 +120,6 @@ RSpec.describe TagManager do end end - describe '#unique_tag' do - it 'returns a unique tag' do - expect(TagManager.instance.unique_tag(Time.utc(2000), 12, 'Status')).to eq 'tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Status' - end - end - - describe '#unique_tag_to_local_id' do - it 'returns the ID part' do - expect(TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Status', 'Status')).to eql '12' - end - - it 'returns nil if it is not local id' do - expect(TagManager.instance.unique_tag_to_local_id('tag:remote,2000-01-01:objectId=12:objectType=Status', 'Status')).to eq nil - end - - it 'returns nil if it is not expected type' do - expect(TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Block', 'Status')).to eq nil - end - - it 'returns nil if it does not have object ID' do - expect(TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectType=Status', 'Status')).to eq nil - end - end - - describe '#local_id?' do - it 'returns true for a local ID' do - expect(TagManager.instance.local_id?('tag:cb6e6126.ngrok.io;objectId=12:objectType=Status')).to be true - end - - it 'returns false for a foreign ID' do - expect(TagManager.instance.local_id?('tag:foreign.tld;objectId=12:objectType=Status')).to be false - end - end - - describe '#uri_for' do - subject { TagManager.instance.uri_for(target) } - - context 'comment object' do - let(:target) { Fabricate(:status, created_at: '2000-01-01T00:00:00Z', reply: true) } - - it 'returns the unique tag for status' do - expect(target.object_type).to eq :comment - is_expected.to eq target.uri - end - end - - context 'note object' do - let(:target) { Fabricate(:status, created_at: '2000-01-01T00:00:00Z', reply: false, thread: nil) } - - it 'returns the unique tag for status' do - expect(target.object_type).to eq :note - is_expected.to eq target.uri - end - end - - context 'person object' do - let(:target) { Fabricate(:account, username: 'alice') } - - it 'returns the URL for account' do - expect(target.object_type).to eq :person - is_expected.to eq 'https://cb6e6126.ngrok.io/users/alice' - end - end - end - describe '#url_for' do let(:alice) { Fabricate(:account, username: 'alice') } diff --git a/spec/services/authorize_follow_service_spec.rb b/spec/services/authorize_follow_service_spec.rb index d74eb41a2..6ea4d83da 100644 --- a/spec/services/authorize_follow_service_spec.rb +++ b/spec/services/authorize_follow_service_spec.rb @@ -42,7 +42,7 @@ RSpec.describe AuthorizeFollowService do it 'sends a follow request authorization salmon slap' do expect(a_request(:post, "http://salmon.example.com/").with { |req| xml = OStatus2::Salmon.new.unpack(req.body) - xml.match(TagManager::VERBS[:authorize]) + xml.match(OStatus::TagManager::VERBS[:authorize]) }).to have_been_made.once end end diff --git a/spec/services/batched_remove_status_service_spec.rb b/spec/services/batched_remove_status_service_spec.rb index b1e9ac567..f5c9adfb5 100644 --- a/spec/services/batched_remove_status_service_spec.rb +++ b/spec/services/batched_remove_status_service_spec.rb @@ -50,14 +50,14 @@ RSpec.describe BatchedRemoveStatusService do it 'sends PuSH update to PuSH subscribers' do expect(a_request(:post, 'http://example.com/push').with { |req| - matches = req.body.match(TagManager::VERBS[:delete]) + matches = req.body.match(OStatus::TagManager::VERBS[:delete]) }).to have_been_made.at_least_once end it 'sends Salmon slap to previously mentioned users' do expect(a_request(:post, "http://example.com/salmon").with { |req| xml = OStatus2::Salmon.new.unpack(req.body) - xml.match(TagManager::VERBS[:delete]) + xml.match(OStatus::TagManager::VERBS[:delete]) }).to have_been_made.once end diff --git a/spec/services/block_service_spec.rb b/spec/services/block_service_spec.rb index bd2ab3d53..c69ff7804 100644 --- a/spec/services/block_service_spec.rb +++ b/spec/services/block_service_spec.rb @@ -32,7 +32,7 @@ RSpec.describe BlockService do it 'sends a block salmon slap' do expect(a_request(:post, "http://salmon.example.com/").with { |req| xml = OStatus2::Salmon.new.unpack(req.body) - xml.match(TagManager::VERBS[:block]) + xml.match(OStatus::TagManager::VERBS[:block]) }).to have_been_made.once end end diff --git a/spec/services/favourite_service_spec.rb b/spec/services/favourite_service_spec.rb index 2ab1f32ca..5bf2c74a9 100644 --- a/spec/services/favourite_service_spec.rb +++ b/spec/services/favourite_service_spec.rb @@ -34,7 +34,7 @@ RSpec.describe FavouriteService do it 'sends a salmon slap' do expect(a_request(:post, "http://salmon.example.com/").with { |req| xml = OStatus2::Salmon.new.unpack(req.body) - xml.match(TagManager::VERBS[:favorite]) + xml.match(OStatus::TagManager::VERBS[:favorite]) }).to have_been_made.once end end diff --git a/spec/services/follow_service_spec.rb b/spec/services/follow_service_spec.rb index 1e2378031..ceb39e5e6 100644 --- a/spec/services/follow_service_spec.rb +++ b/spec/services/follow_service_spec.rb @@ -60,7 +60,7 @@ RSpec.describe FollowService do it 'sends a follow request salmon slap' do expect(a_request(:post, "http://salmon.example.com/").with { |req| xml = OStatus2::Salmon.new.unpack(req.body) - xml.match(TagManager::VERBS[:request_friend]) + xml.match(OStatus::TagManager::VERBS[:request_friend]) }).to have_been_made.once end end @@ -81,7 +81,7 @@ RSpec.describe FollowService do it 'sends a follow salmon slap' do expect(a_request(:post, "http://salmon.example.com/").with { |req| xml = OStatus2::Salmon.new.unpack(req.body) - xml.match(TagManager::VERBS[:follow]) + xml.match(OStatus::TagManager::VERBS[:follow]) }).to have_been_made.once end diff --git a/spec/services/reject_follow_service_spec.rb b/spec/services/reject_follow_service_spec.rb index 2e06345b3..bf49dd2c9 100644 --- a/spec/services/reject_follow_service_spec.rb +++ b/spec/services/reject_follow_service_spec.rb @@ -42,7 +42,7 @@ RSpec.describe RejectFollowService do it 'sends a follow request rejection salmon slap' do expect(a_request(:post, "http://salmon.example.com/").with { |req| xml = OStatus2::Salmon.new.unpack(req.body) - xml.match(TagManager::VERBS[:reject]) + xml.match(OStatus::TagManager::VERBS[:reject]) }).to have_been_made.once end end diff --git a/spec/services/remove_status_service_spec.rb b/spec/services/remove_status_service_spec.rb index 8b34bdb6b..b60015928 100644 --- a/spec/services/remove_status_service_spec.rb +++ b/spec/services/remove_status_service_spec.rb @@ -34,7 +34,7 @@ RSpec.describe RemoveStatusService do it 'sends PuSH update to PuSH subscribers' do expect(a_request(:post, 'http://example.com/push').with { |req| - req.body.match(TagManager::VERBS[:delete]) + req.body.match(OStatus::TagManager::VERBS[:delete]) }).to have_been_made end @@ -45,7 +45,7 @@ RSpec.describe RemoveStatusService do it 'sends Salmon slap to previously mentioned users' do expect(a_request(:post, "http://example.com/salmon").with { |req| xml = OStatus2::Salmon.new.unpack(req.body) - xml.match(TagManager::VERBS[:delete]) + xml.match(OStatus::TagManager::VERBS[:delete]) }).to have_been_made.once end diff --git a/spec/services/unblock_service_spec.rb b/spec/services/unblock_service_spec.rb index def4981e7..ca7a6b77e 100644 --- a/spec/services/unblock_service_spec.rb +++ b/spec/services/unblock_service_spec.rb @@ -34,7 +34,7 @@ RSpec.describe UnblockService do it 'sends an unblock salmon slap' do expect(a_request(:post, "http://salmon.example.com/").with { |req| xml = OStatus2::Salmon.new.unpack(req.body) - xml.match(TagManager::VERBS[:unblock]) + xml.match(OStatus::TagManager::VERBS[:unblock]) }).to have_been_made.once end end diff --git a/spec/services/unfollow_service_spec.rb b/spec/services/unfollow_service_spec.rb index 29040431e..021e76782 100644 --- a/spec/services/unfollow_service_spec.rb +++ b/spec/services/unfollow_service_spec.rb @@ -34,7 +34,7 @@ RSpec.describe UnfollowService do it 'sends an unfollow salmon slap' do expect(a_request(:post, "http://salmon.example.com/").with { |req| xml = OStatus2::Salmon.new.unpack(req.body) - xml.match(TagManager::VERBS[:unfollow]) + xml.match(OStatus::TagManager::VERBS[:unfollow]) }).to have_been_made.once end end -- cgit From 669fe9ee06a82482201377abd303492eb7fa7d94 Mon Sep 17 00:00:00 2001 From: aschmitz Date: Wed, 20 Sep 2017 07:53:48 -0500 Subject: Change IDs to strings rather than numbers in API JSON output (#5019) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix JavaScript interface with long IDs Somewhat predictably, the JS interface handled IDs as numbers, which in JS are IEEE double-precision floats. This loses some precision when working with numbers as large as those generated by the new ID scheme, so we instead handle them here as strings. This is relatively simple, and doesn't appear to have caused any problems, but should definitely be tested more thoroughly than the built-in tests. Several days of use appear to support this working properly. BREAKING CHANGE: The major(!) change here is that IDs are now returned as strings by the REST endpoints, rather than as integers. In practice, relatively few changes were required to make the existing JS UI work with this change, but it will likely hit API clients pretty hard: it's an entirely different type to consume. (The one API client I tested, Tusky, handles this with no problems, however.) Twitter ran into this issue when introducing Snowflake IDs, and decided to instead introduce an `id_str` field in JSON responses. I have opted to *not* do that, and instead force all IDs to 64-bit integers represented by strings in one go. (I believe Twitter exacerbated their problem by rolling out the changes three times: once for statuses, once for DMs, and once for user IDs, as well as by leaving an integer ID value in JSON. As they said, "If you’re using the `id` field with JSON in a Javascript-related language, there is a very high likelihood that the integers will be silently munged by Javascript interpreters. In most cases, this will result in behavior such as being unable to load or delete a specific direct message, because the ID you're sending to the API is different than the actual identifier associated with the message." [1]) However, given that this is a significant change for API users, alternatives or a transition time may be appropriate. 1: https://blog.twitter.com/developer/en_us/a/2011/direct-messages-going-snowflake-on-sep-30-2011.html * Additional fixes for stringified IDs in JSON These should be the last two. These were identified using eslint to try to identify any plain casts to JavaScript numbers. (Some such casts are legitimate, but these were not.) Adding the following to .eslintrc.yml will identify casts to numbers: ~~~ no-restricted-syntax: - warn - selector: UnaryExpression[operator='+'] > :not(Literal) message: Avoid the use of unary + - selector: CallExpression[callee.name='Number'] message: Casting with Number() may coerce string IDs to numbers ~~~ The remaining three casts appear legitimate: two casts to array indices, one in a server to turn an environment variable into a number. * Back out RelationshipsController Change This was made to make a test a bit less flakey, but has nothing to do with this branch. * Change internal streaming payloads to stringified IDs as well Per https://github.com/tootsuite/mastodon/pull/5019#issuecomment-330736452 we need these changes to send deleted status IDs as strings, not integers. --- app/javascript/mastodon/actions/store.js | 3 +-- app/javascript/mastodon/components/account.js | 2 +- .../mastodon/components/autosuggest_textarea.js | 2 +- app/javascript/mastodon/components/status.js | 4 ++-- .../mastodon/components/status_action_bar.js | 2 +- .../mastodon/features/account/components/action_bar.js | 2 +- .../mastodon/features/account/components/header.js | 2 +- .../mastodon/features/account_gallery/index.js | 16 ++++++++-------- .../features/account_timeline/components/header.js | 2 +- .../account_timeline/containers/header_container.js | 2 +- .../mastodon/features/account_timeline/index.js | 18 +++++++++--------- .../features/compose/components/compose_form.js | 2 +- .../features/compose/components/upload_form.js | 2 +- app/javascript/mastodon/features/favourites/index.js | 6 +++--- app/javascript/mastodon/features/followers/index.js | 16 ++++++++-------- app/javascript/mastodon/features/following/index.js | 16 ++++++++-------- app/javascript/mastodon/features/reblogs/index.js | 6 +++--- .../mastodon/features/status/components/action_bar.js | 2 +- app/javascript/mastodon/features/status/index.js | 12 ++++++------ app/serializers/initial_state_serializer.rb | 10 +++++----- app/serializers/rest/account_serializer.rb | 4 ++++ app/serializers/rest/application_serializer.rb | 6 +++++- app/serializers/rest/media_attachment_serializer.rb | 4 ++++ app/serializers/rest/notification_serializer.rb | 4 ++++ app/serializers/rest/relationship_serializer.rb | 4 ++++ app/serializers/rest/report_serializer.rb | 4 ++++ app/serializers/rest/status_serializer.rb | 14 +++++++++++++- app/services/batched_remove_status_service.rb | 2 +- app/services/remove_status_service.rb | 2 +- .../api/v1/accounts/relationships_controller_spec.rb | 4 ++-- spec/controllers/api/v1/media_controller_spec.rb | 6 +++--- .../api/v1/statuses/favourites_controller_spec.rb | 2 +- .../api/v1/statuses/pins_controller_spec.rb | 2 +- .../api/v1/statuses/reblogs_controller_spec.rb | 2 +- 34 files changed, 111 insertions(+), 76 deletions(-) (limited to 'app/serializers') diff --git a/app/javascript/mastodon/actions/store.js b/app/javascript/mastodon/actions/store.js index 0597d265e..a1db0fdd5 100644 --- a/app/javascript/mastodon/actions/store.js +++ b/app/javascript/mastodon/actions/store.js @@ -5,8 +5,7 @@ export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY'; const convertState = rawState => fromJS(rawState, (k, v) => - Iterable.isIndexed(v) ? v.toList() : v.toMap().mapKeys(x => - Number.isNaN(x * 1) ? x : x * 1)); + Iterable.isIndexed(v) ? v.toList() : v.toMap()); export function hydrateStore(rawState) { const state = convertState(rawState); diff --git a/app/javascript/mastodon/components/account.js b/app/javascript/mastodon/components/account.js index 6456c12ba..d614a52c9 100644 --- a/app/javascript/mastodon/components/account.js +++ b/app/javascript/mastodon/components/account.js @@ -21,7 +21,7 @@ export default class Account extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, - me: PropTypes.number.isRequired, + me: PropTypes.string.isRequired, onFollow: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired, onMute: PropTypes.func.isRequired, diff --git a/app/javascript/mastodon/components/autosuggest_textarea.js b/app/javascript/mastodon/components/autosuggest_textarea.js index 35b37600f..30e3049df 100644 --- a/app/javascript/mastodon/components/autosuggest_textarea.js +++ b/app/javascript/mastodon/components/autosuggest_textarea.js @@ -128,7 +128,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { } onSuggestionClick = (e) => { - const suggestion = Number(e.currentTarget.getAttribute('data-index')); + const suggestion = e.currentTarget.getAttribute('data-index'); e.preventDefault(); this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion); this.textarea.focus(); diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 82359156d..3716d522e 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -34,7 +34,7 @@ export default class Status extends ImmutablePureComponent { onBlock: PropTypes.func, onEmbed: PropTypes.func, onHeightChange: PropTypes.func, - me: PropTypes.number, + me: PropTypes.string, boostModal: PropTypes.bool, autoPlayGif: PropTypes.bool, muted: PropTypes.bool, @@ -70,7 +70,7 @@ export default class Status extends ImmutablePureComponent { handleAccountClick = (e) => { if (this.context.router && e.button === 0) { - const id = Number(e.currentTarget.getAttribute('data-id')); + const id = e.currentTarget.getAttribute('data-id'); e.preventDefault(); this.context.router.history.push(`/accounts/${id}`); } diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index 692b1ef2b..803b730d9 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -46,7 +46,7 @@ export default class StatusActionBar extends ImmutablePureComponent { onEmbed: PropTypes.func, onMuteConversation: PropTypes.func, onPin: PropTypes.func, - me: PropTypes.number, + me: PropTypes.string, withDismiss: PropTypes.bool, intl: PropTypes.object.isRequired, }; diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js index c12c0889e..9e8fea69d 100644 --- a/app/javascript/mastodon/features/account/components/action_bar.js +++ b/app/javascript/mastodon/features/account/components/action_bar.js @@ -26,7 +26,7 @@ export default class ActionBar extends React.PureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, - me: PropTypes.number.isRequired, + me: PropTypes.string.isRequired, onFollow: PropTypes.func, onBlock: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index 6eb51a5c7..9ee7a56d9 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -77,7 +77,7 @@ export default class Header extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map, - me: PropTypes.number.isRequired, + me: PropTypes.string.isRequired, onFollow: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, autoPlayGif: PropTypes.bool.isRequired, diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js index 0cfd98f23..2a88addc4 100644 --- a/app/javascript/mastodon/features/account_gallery/index.js +++ b/app/javascript/mastodon/features/account_gallery/index.js @@ -16,9 +16,9 @@ import { ScrollContainer } from 'react-router-scroll'; import LoadMore from '../../components/load_more'; const mapStateToProps = (state, props) => ({ - medias: getAccountGallery(state, Number(props.params.accountId)), - isLoading: state.getIn(['timelines', `account:${Number(props.params.accountId)}:media`, 'isLoading']), - hasMore: !!state.getIn(['timelines', `account:${Number(props.params.accountId)}:media`, 'next']), + medias: getAccountGallery(state, props.params.accountId), + isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']), + hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}:media`, 'next']), autoPlayGif: state.getIn(['meta', 'auto_play_gif']), }); @@ -35,20 +35,20 @@ export default class AccountGallery extends ImmutablePureComponent { }; componentDidMount () { - this.props.dispatch(fetchAccount(Number(this.props.params.accountId))); - this.props.dispatch(refreshAccountMediaTimeline(Number(this.props.params.accountId))); + this.props.dispatch(fetchAccount(this.props.params.accountId)); + this.props.dispatch(refreshAccountMediaTimeline(this.props.params.accountId)); } componentWillReceiveProps (nextProps) { if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { - this.props.dispatch(fetchAccount(Number(nextProps.params.accountId))); - this.props.dispatch(refreshAccountMediaTimeline(Number(this.props.params.accountId))); + this.props.dispatch(fetchAccount(nextProps.params.accountId)); + this.props.dispatch(refreshAccountMediaTimeline(this.props.params.accountId)); } } handleScrollToBottom = () => { if (this.props.hasMore) { - this.props.dispatch(expandAccountMediaTimeline(Number(this.props.params.accountId))); + this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId)); } } diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js index 167a2097e..edfedb864 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.js +++ b/app/javascript/mastodon/features/account_timeline/components/header.js @@ -10,7 +10,7 @@ export default class Header extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map, - me: PropTypes.number.isRequired, + me: PropTypes.string.isRequired, onFollow: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.js b/app/javascript/mastodon/features/account_timeline/containers/header_container.js index dcee78b3e..ab75b40de 100644 --- a/app/javascript/mastodon/features/account_timeline/containers/header_container.js +++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.js @@ -27,7 +27,7 @@ const makeMapStateToProps = () => { const getAccount = makeGetAccount(); const mapStateToProps = (state, { accountId }) => ({ - account: getAccount(state, Number(accountId)), + account: getAccount(state, accountId), me: state.getIn(['meta', 'me']), unfollowModal: state.getIn(['meta', 'unfollow_modal']), }); diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js index 3c8b63114..fe92216d5 100644 --- a/app/javascript/mastodon/features/account_timeline/index.js +++ b/app/javascript/mastodon/features/account_timeline/index.js @@ -13,9 +13,9 @@ import { List as ImmutableList } from 'immutable'; import ImmutablePureComponent from 'react-immutable-pure-component'; const mapStateToProps = (state, props) => ({ - statusIds: state.getIn(['timelines', `account:${Number(props.params.accountId)}`, 'items'], ImmutableList()), - isLoading: state.getIn(['timelines', `account:${Number(props.params.accountId)}`, 'isLoading']), - hasMore: !!state.getIn(['timelines', `account:${Number(props.params.accountId)}`, 'next']), + statusIds: state.getIn(['timelines', `account:${props.params.accountId}`, 'items'], ImmutableList()), + isLoading: state.getIn(['timelines', `account:${props.params.accountId}`, 'isLoading']), + hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}`, 'next']), me: state.getIn(['meta', 'me']), }); @@ -28,24 +28,24 @@ export default class AccountTimeline extends ImmutablePureComponent { statusIds: ImmutablePropTypes.list, isLoading: PropTypes.bool, hasMore: PropTypes.bool, - me: PropTypes.number.isRequired, + me: PropTypes.string.isRequired, }; componentWillMount () { - this.props.dispatch(fetchAccount(Number(this.props.params.accountId))); - this.props.dispatch(refreshAccountTimeline(Number(this.props.params.accountId))); + this.props.dispatch(fetchAccount(this.props.params.accountId)); + this.props.dispatch(refreshAccountTimeline(this.props.params.accountId)); } componentWillReceiveProps (nextProps) { if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { - this.props.dispatch(fetchAccount(Number(nextProps.params.accountId))); - this.props.dispatch(refreshAccountTimeline(Number(nextProps.params.accountId))); + this.props.dispatch(fetchAccount(nextProps.params.accountId)); + this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId)); } } handleScrollToBottom = () => { if (!this.props.isLoading && this.props.hasMore) { - this.props.dispatch(expandAccountTimeline(Number(this.props.params.accountId))); + this.props.dispatch(expandAccountTimeline(this.props.params.accountId)); } } diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index f3320a42b..413142d81 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -42,7 +42,7 @@ export default class ComposeForm extends ImmutablePureComponent { preselectDate: PropTypes.instanceOf(Date), is_submitting: PropTypes.bool, is_uploading: PropTypes.bool, - me: PropTypes.number, + me: PropTypes.string, onChange: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired, onClearSuggestions: PropTypes.func.isRequired, diff --git a/app/javascript/mastodon/features/compose/components/upload_form.js b/app/javascript/mastodon/features/compose/components/upload_form.js index 78473dab4..cf2d2658a 100644 --- a/app/javascript/mastodon/features/compose/components/upload_form.js +++ b/app/javascript/mastodon/features/compose/components/upload_form.js @@ -21,7 +21,7 @@ export default class UploadForm extends React.PureComponent { }; onRemoveFile = (e) => { - const id = Number(e.currentTarget.parentElement.getAttribute('data-id')); + const id = e.currentTarget.parentElement.getAttribute('data-id'); this.props.onRemoveFile(id); } diff --git a/app/javascript/mastodon/features/favourites/index.js b/app/javascript/mastodon/features/favourites/index.js index dc8109d16..4dbfefd87 100644 --- a/app/javascript/mastodon/features/favourites/index.js +++ b/app/javascript/mastodon/features/favourites/index.js @@ -11,7 +11,7 @@ import ColumnBackButton from '../../components/column_back_button'; import ImmutablePureComponent from 'react-immutable-pure-component'; const mapStateToProps = (state, props) => ({ - accountIds: state.getIn(['user_lists', 'favourited_by', Number(props.params.statusId)]), + accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]), }); @connect(mapStateToProps) @@ -24,12 +24,12 @@ export default class Favourites extends ImmutablePureComponent { }; componentWillMount () { - this.props.dispatch(fetchFavourites(Number(this.props.params.statusId))); + this.props.dispatch(fetchFavourites(this.props.params.statusId)); } componentWillReceiveProps (nextProps) { if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { - this.props.dispatch(fetchFavourites(Number(nextProps.params.statusId))); + this.props.dispatch(fetchFavourites(nextProps.params.statusId)); } } diff --git a/app/javascript/mastodon/features/followers/index.js b/app/javascript/mastodon/features/followers/index.js index 2d85b9cc0..89445559f 100644 --- a/app/javascript/mastodon/features/followers/index.js +++ b/app/javascript/mastodon/features/followers/index.js @@ -17,8 +17,8 @@ import ColumnBackButton from '../../components/column_back_button'; import ImmutablePureComponent from 'react-immutable-pure-component'; const mapStateToProps = (state, props) => ({ - accountIds: state.getIn(['user_lists', 'followers', Number(props.params.accountId), 'items']), - hasMore: !!state.getIn(['user_lists', 'followers', Number(props.params.accountId), 'next']), + accountIds: state.getIn(['user_lists', 'followers', props.params.accountId, 'items']), + hasMore: !!state.getIn(['user_lists', 'followers', props.params.accountId, 'next']), }); @connect(mapStateToProps) @@ -32,14 +32,14 @@ export default class Followers extends ImmutablePureComponent { }; componentWillMount () { - this.props.dispatch(fetchAccount(Number(this.props.params.accountId))); - this.props.dispatch(fetchFollowers(Number(this.props.params.accountId))); + this.props.dispatch(fetchAccount(this.props.params.accountId)); + this.props.dispatch(fetchFollowers(this.props.params.accountId)); } componentWillReceiveProps (nextProps) { if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { - this.props.dispatch(fetchAccount(Number(nextProps.params.accountId))); - this.props.dispatch(fetchFollowers(Number(nextProps.params.accountId))); + this.props.dispatch(fetchAccount(nextProps.params.accountId)); + this.props.dispatch(fetchFollowers(nextProps.params.accountId)); } } @@ -47,13 +47,13 @@ export default class Followers extends ImmutablePureComponent { const { scrollTop, scrollHeight, clientHeight } = e.target; if (scrollTop === scrollHeight - clientHeight && this.props.hasMore) { - this.props.dispatch(expandFollowers(Number(this.props.params.accountId))); + this.props.dispatch(expandFollowers(this.props.params.accountId)); } } handleLoadMore = (e) => { e.preventDefault(); - this.props.dispatch(expandFollowers(Number(this.props.params.accountId))); + this.props.dispatch(expandFollowers(this.props.params.accountId)); } render () { diff --git a/app/javascript/mastodon/features/following/index.js b/app/javascript/mastodon/features/following/index.js index e4e2a4811..c34830276 100644 --- a/app/javascript/mastodon/features/following/index.js +++ b/app/javascript/mastodon/features/following/index.js @@ -17,8 +17,8 @@ import ColumnBackButton from '../../components/column_back_button'; import ImmutablePureComponent from 'react-immutable-pure-component'; const mapStateToProps = (state, props) => ({ - accountIds: state.getIn(['user_lists', 'following', Number(props.params.accountId), 'items']), - hasMore: !!state.getIn(['user_lists', 'following', Number(props.params.accountId), 'next']), + accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']), + hasMore: !!state.getIn(['user_lists', 'following', props.params.accountId, 'next']), }); @connect(mapStateToProps) @@ -32,14 +32,14 @@ export default class Following extends ImmutablePureComponent { }; componentWillMount () { - this.props.dispatch(fetchAccount(Number(this.props.params.accountId))); - this.props.dispatch(fetchFollowing(Number(this.props.params.accountId))); + this.props.dispatch(fetchAccount(this.props.params.accountId)); + this.props.dispatch(fetchFollowing(this.props.params.accountId)); } componentWillReceiveProps (nextProps) { if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { - this.props.dispatch(fetchAccount(Number(nextProps.params.accountId))); - this.props.dispatch(fetchFollowing(Number(nextProps.params.accountId))); + this.props.dispatch(fetchAccount(nextProps.params.accountId)); + this.props.dispatch(fetchFollowing(nextProps.params.accountId)); } } @@ -47,13 +47,13 @@ export default class Following extends ImmutablePureComponent { const { scrollTop, scrollHeight, clientHeight } = e.target; if (scrollTop === scrollHeight - clientHeight && this.props.hasMore) { - this.props.dispatch(expandFollowing(Number(this.props.params.accountId))); + this.props.dispatch(expandFollowing(this.props.params.accountId)); } } handleLoadMore = (e) => { e.preventDefault(); - this.props.dispatch(expandFollowing(Number(this.props.params.accountId))); + this.props.dispatch(expandFollowing(this.props.params.accountId)); } render () { diff --git a/app/javascript/mastodon/features/reblogs/index.js b/app/javascript/mastodon/features/reblogs/index.js index dc940ae01..f1904786a 100644 --- a/app/javascript/mastodon/features/reblogs/index.js +++ b/app/javascript/mastodon/features/reblogs/index.js @@ -11,7 +11,7 @@ import ColumnBackButton from '../../components/column_back_button'; import ImmutablePureComponent from 'react-immutable-pure-component'; const mapStateToProps = (state, props) => ({ - accountIds: state.getIn(['user_lists', 'reblogged_by', Number(props.params.statusId)]), + accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId]), }); @connect(mapStateToProps) @@ -24,12 +24,12 @@ export default class Reblogs extends ImmutablePureComponent { }; componentWillMount () { - this.props.dispatch(fetchReblogs(Number(this.props.params.statusId))); + this.props.dispatch(fetchReblogs(this.props.params.statusId)); } componentWillReceiveProps(nextProps) { if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { - this.props.dispatch(fetchReblogs(Number(nextProps.params.statusId))); + this.props.dispatch(fetchReblogs(nextProps.params.statusId)); } } diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index c303caf10..034cc9854 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -36,7 +36,7 @@ export default class ActionBar extends React.PureComponent { onReport: PropTypes.func, onPin: PropTypes.func, onEmbed: PropTypes.func, - me: PropTypes.number.isRequired, + me: PropTypes.string.isRequired, intl: PropTypes.object.isRequired, }; diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index c614f6acb..8da6e743c 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -38,9 +38,9 @@ const makeMapStateToProps = () => { const getStatus = makeGetStatus(); const mapStateToProps = (state, props) => ({ - status: getStatus(state, Number(props.params.statusId)), - ancestorsIds: state.getIn(['contexts', 'ancestors', Number(props.params.statusId)]), - descendantsIds: state.getIn(['contexts', 'descendants', Number(props.params.statusId)]), + status: getStatus(state, props.params.statusId), + ancestorsIds: state.getIn(['contexts', 'ancestors', props.params.statusId]), + descendantsIds: state.getIn(['contexts', 'descendants', props.params.statusId]), me: state.getIn(['meta', 'me']), boostModal: state.getIn(['meta', 'boost_modal']), deleteModal: state.getIn(['meta', 'delete_modal']), @@ -64,7 +64,7 @@ export default class Status extends ImmutablePureComponent { status: ImmutablePropTypes.map, ancestorsIds: ImmutablePropTypes.list, descendantsIds: ImmutablePropTypes.list, - me: PropTypes.number, + me: PropTypes.string, boostModal: PropTypes.bool, deleteModal: PropTypes.bool, autoPlayGif: PropTypes.bool, @@ -72,12 +72,12 @@ export default class Status extends ImmutablePureComponent { }; componentWillMount () { - this.props.dispatch(fetchStatus(Number(this.props.params.statusId))); + this.props.dispatch(fetchStatus(this.props.params.statusId)); } componentWillReceiveProps (nextProps) { if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { - this.props.dispatch(fetchStatus(Number(nextProps.params.statusId))); + this.props.dispatch(fetchStatus(nextProps.params.statusId)); } } diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 32ffcc688..9ee9bd29c 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -10,11 +10,11 @@ class InitialStateSerializer < ActiveModel::Serializer access_token: object.token, locale: I18n.locale, domain: Rails.configuration.x.local_domain, - admin: object.admin&.id, + admin: object.admin&.id&.to_s, } if object.current_account - store[:me] = object.current_account.id + store[:me] = object.current_account.id.to_s store[:unfollow_modal] = object.current_account.user.setting_unfollow_modal store[:boost_modal] = object.current_account.user.setting_boost_modal store[:delete_modal] = object.current_account.user.setting_delete_modal @@ -28,7 +28,7 @@ class InitialStateSerializer < ActiveModel::Serializer store = {} if object.current_account - store[:me] = object.current_account.id + store[:me] = object.current_account.id.to_s store[:default_privacy] = object.current_account.user.setting_default_privacy store[:default_sensitive] = object.current_account.user.setting_default_sensitive end @@ -40,8 +40,8 @@ class InitialStateSerializer < ActiveModel::Serializer def accounts store = {} - store[object.current_account.id] = ActiveModelSerializers::SerializableResource.new(object.current_account, serializer: REST::AccountSerializer) if object.current_account - store[object.admin.id] = ActiveModelSerializers::SerializableResource.new(object.admin, serializer: REST::AccountSerializer) if object.admin + store[object.current_account.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.current_account, serializer: REST::AccountSerializer) if object.current_account + store[object.admin.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.admin, serializer: REST::AccountSerializer) if object.admin store end diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index 012a4fd18..65fdb0308 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -7,6 +7,10 @@ class REST::AccountSerializer < ActiveModel::Serializer :note, :url, :avatar, :avatar_static, :header, :header_static, :followers_count, :following_count, :statuses_count + def id + object.id.to_s + end + def note Formatter.instance.simplified_format(object) end diff --git a/app/serializers/rest/application_serializer.rb b/app/serializers/rest/application_serializer.rb index 868a62f1e..5eb03a513 100644 --- a/app/serializers/rest/application_serializer.rb +++ b/app/serializers/rest/application_serializer.rb @@ -4,8 +4,12 @@ class REST::ApplicationSerializer < ActiveModel::Serializer attributes :id, :name, :website, :redirect_uri, :client_id, :client_secret + def id + object.id.to_s + end + def client_id - object.uid + object.uid.to_s end def client_secret diff --git a/app/serializers/rest/media_attachment_serializer.rb b/app/serializers/rest/media_attachment_serializer.rb index 31189406a..f6e7c79d1 100644 --- a/app/serializers/rest/media_attachment_serializer.rb +++ b/app/serializers/rest/media_attachment_serializer.rb @@ -6,6 +6,10 @@ class REST::MediaAttachmentSerializer < ActiveModel::Serializer attributes :id, :type, :url, :preview_url, :remote_url, :text_url, :meta + def id + object.id.to_s + end + def url if object.needs_redownload? media_proxy_url(object.id, :original) diff --git a/app/serializers/rest/notification_serializer.rb b/app/serializers/rest/notification_serializer.rb index f95d099a3..541a6b8b5 100644 --- a/app/serializers/rest/notification_serializer.rb +++ b/app/serializers/rest/notification_serializer.rb @@ -6,6 +6,10 @@ class REST::NotificationSerializer < ActiveModel::Serializer belongs_to :from_account, key: :account, serializer: REST::AccountSerializer belongs_to :target_status, key: :status, if: :status_type?, serializer: REST::StatusSerializer + def id + object.id.to_s + end + def status_type? [:favourite, :reblog, :mention].include?(object.type) end diff --git a/app/serializers/rest/relationship_serializer.rb b/app/serializers/rest/relationship_serializer.rb index 1d431aa1b..998727e37 100644 --- a/app/serializers/rest/relationship_serializer.rb +++ b/app/serializers/rest/relationship_serializer.rb @@ -4,6 +4,10 @@ class REST::RelationshipSerializer < ActiveModel::Serializer attributes :id, :following, :followed_by, :blocking, :muting, :requested, :domain_blocking + def id + object.id.to_s + end + def following instance_options[:relationships].following[object.id] || false end diff --git a/app/serializers/rest/report_serializer.rb b/app/serializers/rest/report_serializer.rb index 0c6bd6556..ecb88d653 100644 --- a/app/serializers/rest/report_serializer.rb +++ b/app/serializers/rest/report_serializer.rb @@ -2,4 +2,8 @@ class REST::ReportSerializer < ActiveModel::Serializer attributes :id, :action_taken + + def id + object.id.to_s + end end diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index 066d65d9e..e0fd1c77e 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -19,6 +19,18 @@ class REST::StatusSerializer < ActiveModel::Serializer has_many :tags has_many :emojis + def id + object.id.to_s + end + + def in_reply_to_id + object.in_reply_to_id.to_s + end + + def in_reply_to_account_id + object.in_reply_to_account_id.to_s + end + def current_user? !current_user.nil? end @@ -82,7 +94,7 @@ class REST::StatusSerializer < ActiveModel::Serializer attributes :id, :username, :url, :acct def id - object.account_id + object.account_id.to_s end def username diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb index 86eaa5735..e1e845bc0 100644 --- a/app/services/batched_remove_status_service.rb +++ b/app/services/batched_remove_status_service.rb @@ -18,7 +18,7 @@ class BatchedRemoveStatusService < BaseService @stream_entry_batches = [] @salmon_batches = [] @activity_json_batches = [] - @json_payloads = statuses.map { |s| [s.id, Oj.dump(event: :delete, payload: s.id)] }.to_h + @json_payloads = statuses.map { |s| [s.id, Oj.dump(event: :delete, payload: s.id.to_s)] }.to_h @activity_json = {} @activity_xml = {} diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index 83fc77043..5f45fb3ab 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -4,7 +4,7 @@ class RemoveStatusService < BaseService include StreamEntryRenderer def call(status) - @payload = Oj.dump(event: :delete, payload: status.id) + @payload = Oj.dump(event: :delete, payload: status.id.to_s) @status = status @account = status.account @tags = status.tags.pluck(:name).to_a diff --git a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb index a9073b197..431fc2194 100644 --- a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb @@ -50,14 +50,14 @@ describe Api::V1::Accounts::RelationshipsController do json = body_as_json expect(json).to be_a Enumerable - expect(json.first[:id]).to eq simon.id + expect(json.first[:id]).to eq simon.id.to_s expect(json.first[:following]).to be true expect(json.first[:followed_by]).to be false expect(json.first[:muting]).to be false expect(json.first[:requested]).to be false expect(json.first[:domain_blocking]).to be false - expect(json.second[:id]).to eq lewis.id + expect(json.second[:id]).to eq lewis.id.to_s expect(json.second[:following]).to be false expect(json.second[:followed_by]).to be true expect(json.second[:muting]).to be false diff --git a/spec/controllers/api/v1/media_controller_spec.rb b/spec/controllers/api/v1/media_controller_spec.rb index 6bad3f05d..baa22d7e4 100644 --- a/spec/controllers/api/v1/media_controller_spec.rb +++ b/spec/controllers/api/v1/media_controller_spec.rb @@ -53,7 +53,7 @@ RSpec.describe Api::V1::MediaController, type: :controller do end it 'returns media ID in JSON' do - expect(body_as_json[:id]).to eq MediaAttachment.first.id + expect(body_as_json[:id]).to eq MediaAttachment.first.id.to_s end end @@ -75,7 +75,7 @@ RSpec.describe Api::V1::MediaController, type: :controller do end it 'returns media ID in JSON' do - expect(body_as_json[:id]).to eq MediaAttachment.first.id + expect(body_as_json[:id]).to eq MediaAttachment.first.id.to_s end end @@ -97,7 +97,7 @@ RSpec.describe Api::V1::MediaController, type: :controller do end xit 'returns media ID in JSON' do - expect(body_as_json[:id]).to eq MediaAttachment.first.id + expect(body_as_json[:id]).to eq MediaAttachment.first.id.to_s end end end diff --git a/spec/controllers/api/v1/statuses/favourites_controller_spec.rb b/spec/controllers/api/v1/statuses/favourites_controller_spec.rb index 2a029230d..aba7cd458 100644 --- a/spec/controllers/api/v1/statuses/favourites_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/favourites_controller_spec.rb @@ -36,7 +36,7 @@ describe Api::V1::Statuses::FavouritesController do it 'return json with updated attributes' do hash_body = body_as_json - expect(hash_body[:id]).to eq status.id + expect(hash_body[:id]).to eq status.id.to_s expect(hash_body[:favourites_count]).to eq 1 expect(hash_body[:favourited]).to be true end diff --git a/spec/controllers/api/v1/statuses/pins_controller_spec.rb b/spec/controllers/api/v1/statuses/pins_controller_spec.rb index 2e170da24..79005c9de 100644 --- a/spec/controllers/api/v1/statuses/pins_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/pins_controller_spec.rb @@ -32,7 +32,7 @@ describe Api::V1::Statuses::PinsController do it 'return json with updated attributes' do hash_body = body_as_json - expect(hash_body[:id]).to eq status.id + expect(hash_body[:id]).to eq status.id.to_s expect(hash_body[:pinned]).to be true end end diff --git a/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb b/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb index d6d36c1b2..7417ff672 100644 --- a/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb @@ -36,7 +36,7 @@ describe Api::V1::Statuses::ReblogsController do it 'return json with updated attributes' do hash_body = body_as_json - expect(hash_body[:reblog][:id]).to eq status.id + expect(hash_body[:reblog][:id]).to eq status.id.to_s expect(hash_body[:reblog][:reblogs_count]).to eq 1 expect(hash_body[:reblog][:reblogged]).to be true end -- cgit From c8580eb806512fcc8ca76303e7d837f77c2cb413 Mon Sep 17 00:00:00 2001 From: unarist Date: Thu, 21 Sep 2017 02:07:23 +0900 Subject: Use file extensions in addition to MIME types for file picker (#5029) Currently we're using a list of MIME types for `accept` attribute on `input[type="file"]` for filter options of file picker, and actual file extensions will be infered by browsers. However, infered extensions may not include our expected items. For example, "image/jpeg" seems to be infered to only ".jfif" extension in Firefox. To ensure common file extensions are in the list, this PR adds file extensions in addition to MIME types. Also having items in both format is encouraged by HTML5 spec. https://www.w3.org/TR/html5/forms.html#file-upload-state-(type=file) --- app/models/media_attachment.rb | 3 +++ app/serializers/initial_state_serializer.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'app/serializers') diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index d913e7372..e4a974f96 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -25,6 +25,9 @@ class MediaAttachment < ApplicationRecord enum type: [:image, :gifv, :video, :unknown] + IMAGE_FILE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif'].freeze + VIDEO_FILE_EXTENSIONS = ['.webm', '.mp4', '.m4v'].freeze + IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze VIDEO_MIME_TYPES = ['video/webm', 'video/mp4'].freeze diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 9ee9bd29c..88bbc0dff 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -46,6 +46,6 @@ class InitialStateSerializer < ActiveModel::Serializer end def media_attachments - { accept_content_types: MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES } + { accept_content_types: MediaAttachment::IMAGE_FILE_EXTENSIONS + MediaAttachment::VIDEO_FILE_EXTENSIONS + MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES } end end -- cgit From 293972f716476933df2b665ad755cafe4d29d82d Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 23 Sep 2017 01:57:23 +0200 Subject: New API: GET /api/v1/custom_emojis to get a server's custom emojis (#5051) --- app/controllers/admin/custom_emojis_controller.rb | 2 +- app/controllers/api/v1/custom_emojis_controller.rb | 9 +++++++++ app/models/custom_emoji.rb | 2 ++ app/serializers/rest/custom_emoji_serializer.rb | 11 +++++++++++ app/serializers/rest/status_serializer.rb | 12 +----------- config/routes.rb | 1 + .../api/v1/custom_emojis_controller_spec.rb | 18 ++++++++++++++++++ 7 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 app/controllers/api/v1/custom_emojis_controller.rb create mode 100644 app/serializers/rest/custom_emoji_serializer.rb create mode 100644 spec/controllers/api/v1/custom_emojis_controller_spec.rb (limited to 'app/serializers') diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb index 572ad1ac2..d70514d9a 100644 --- a/app/controllers/admin/custom_emojis_controller.rb +++ b/app/controllers/admin/custom_emojis_controller.rb @@ -3,7 +3,7 @@ module Admin class CustomEmojisController < BaseController def index - @custom_emojis = CustomEmoji.where(domain: nil) + @custom_emojis = CustomEmoji.local end def new diff --git a/app/controllers/api/v1/custom_emojis_controller.rb b/app/controllers/api/v1/custom_emojis_controller.rb new file mode 100644 index 000000000..4dd77fb55 --- /dev/null +++ b/app/controllers/api/v1/custom_emojis_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Api::V1::CustomEmojisController < Api::BaseController + respond_to :json + + def index + render json: CustomEmoji.local, each_serializer: REST::CustomEmojiSerializer + end +end diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index f4d3b16a0..aff9f8dfa 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -26,6 +26,8 @@ class CustomEmoji < ApplicationRecord validates_attachment :image, content_type: { content_type: 'image/png' }, presence: true, size: { in: 0..50.kilobytes } validates :shortcode, uniqueness: { scope: :domain }, format: { with: /\A#{SHORTCODE_RE_FRAGMENT}\z/ }, length: { minimum: 2 } + scope :local, -> { where(domain: nil) } + include Remotable class << self diff --git a/app/serializers/rest/custom_emoji_serializer.rb b/app/serializers/rest/custom_emoji_serializer.rb new file mode 100644 index 000000000..b744dd4ec --- /dev/null +++ b/app/serializers/rest/custom_emoji_serializer.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class REST::CustomEmojiSerializer < ActiveModel::Serializer + include RoutingHelper + + attributes :shortcode, :url + + def url + full_asset_url(object.image.url) + end +end diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index e0fd1c77e..ef3c325ba 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -17,7 +17,7 @@ class REST::StatusSerializer < ActiveModel::Serializer has_many :media_attachments, serializer: REST::MediaAttachmentSerializer has_many :mentions has_many :tags - has_many :emojis + has_many :emojis, serializer: REST::CustomEmojiSerializer def id object.id.to_s @@ -119,14 +119,4 @@ class REST::StatusSerializer < ActiveModel::Serializer tag_url(object) end end - - class CustomEmojiSerializer < ActiveModel::Serializer - include RoutingHelper - - attributes :shortcode, :url - - def url - full_asset_url(object.image.url) - end - end end diff --git a/config/routes.rb b/config/routes.rb index d38f5308a..cb7e84d7b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -188,6 +188,7 @@ Rails.application.routes.draw do end resources :streaming, only: [:index] + resources :custom_emojis, only: [:index] get '/search', to: 'search#index', as: :search diff --git a/spec/controllers/api/v1/custom_emojis_controller_spec.rb b/spec/controllers/api/v1/custom_emojis_controller_spec.rb new file mode 100644 index 000000000..9f3522812 --- /dev/null +++ b/spec/controllers/api/v1/custom_emojis_controller_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Api::V1::CustomEmojisController, type: :controller do + render_views + + describe 'GET #index' do + before do + Fabricate(:custom_emoji) + get :index + end + + it 'returns http success' do + expect(response).to have_http_status(:success) + end + end +end -- cgit From 66126f302167d21e4bf247e660f595ff0beaaf20 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 23 Sep 2017 05:40:28 +0200 Subject: Add custom emojis to the emoji picker (#5052) --- app/javascript/mastodon/emoji.js | 40 ++++++++++++++++++++++ .../features/compose/components/compose_form.js | 2 +- .../compose/components/emoji_picker_dropdown.js | 16 ++++++++- .../containers/emoji_picker_dropdown_container.js | 8 +++++ app/javascript/mastodon/reducers/custom_emojis.js | 13 +++++++ app/javascript/mastodon/reducers/index.js | 2 ++ app/javascript/styles/components.scss | 6 ++++ app/serializers/initial_state_serializer.rb | 6 ++++ 8 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js create mode 100644 app/javascript/mastodon/reducers/custom_emojis.js (limited to 'app/serializers') diff --git a/app/javascript/mastodon/emoji.js b/app/javascript/mastodon/emoji.js index 407b21b4b..39123768a 100644 --- a/app/javascript/mastodon/emoji.js +++ b/app/javascript/mastodon/emoji.js @@ -47,3 +47,43 @@ const emojify = (str, customEmojis = {}) => { }; export default emojify; + +export const toCodePoint = (unicodeSurrogates, sep = '-') => { + let r = [], c = 0, p = 0, i = 0; + + while (i < unicodeSurrogates.length) { + c = unicodeSurrogates.charCodeAt(i++); + + if (p) { + r.push((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).toString(16)); + p = 0; + } else if (0xD800 <= c && c <= 0xDBFF) { + p = c; + } else { + r.push(c.toString(16)); + } + } + + return r.join(sep); +}; + +export const buildCustomEmojis = customEmojis => { + const emojis = []; + + customEmojis.forEach(emoji => { + const shortcode = emoji.get('shortcode'); + const url = emoji.get('url'); + const name = shortcode.replace(':', ''); + + emojis.push({ + name, + short_names: [name], + text: '', + emoticons: [], + keywords: [name], + imageUrl: url, + }); + }); + + return emojis; +}; diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index bb747b611..d31041061 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -12,7 +12,7 @@ import Collapsable from '../../../components/collapsable'; import SpoilerButtonContainer from '../containers/spoiler_button_container'; import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; import SensitiveButtonContainer from '../containers/sensitive_button_container'; -import EmojiPickerDropdown from './emoji_picker_dropdown'; +import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container'; import UploadFormContainer from '../containers/upload_form_container'; import WarningContainer from '../containers/warning_container'; import { isMobile } from '../../../is_mobile'; diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js index 43e175be5..f55d59e03 100644 --- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js @@ -4,6 +4,8 @@ import { defineMessages, injectIntl } from 'react-intl'; import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components'; import { Overlay } from 'react-overlays'; import classNames from 'classnames'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { buildCustomEmojis } from '../../../emoji'; const messages = defineMessages({ emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, @@ -130,6 +132,7 @@ class ModifierPicker extends React.PureComponent { class EmojiPickerMenu extends React.PureComponent { static propTypes = { + custom_emojis: ImmutablePropTypes.list, loading: PropTypes.bool, onClose: PropTypes.func.isRequired, onPick: PropTypes.func.isRequired, @@ -194,6 +197,10 @@ class EmojiPickerMenu extends React.PureComponent { } handleClick = emoji => { + if (!emoji.native) { + emoji.native = emoji.colons; + } + this.props.onClose(); this.props.onPick(emoji); } @@ -225,6 +232,7 @@ class EmojiPickerMenu extends React.PureComponent { return (
- +
); diff --git a/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js b/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js new file mode 100644 index 000000000..7a8026bbc --- /dev/null +++ b/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js @@ -0,0 +1,8 @@ +import { connect } from 'react-redux'; +import EmojiPickerDropdown from '../components/emoji_picker_dropdown'; + +const mapStateToProps = state => ({ + custom_emojis: state.get('custom_emojis'), +}); + +export default connect(mapStateToProps)(EmojiPickerDropdown); diff --git a/app/javascript/mastodon/reducers/custom_emojis.js b/app/javascript/mastodon/reducers/custom_emojis.js new file mode 100644 index 000000000..15bba7bcc --- /dev/null +++ b/app/javascript/mastodon/reducers/custom_emojis.js @@ -0,0 +1,13 @@ +import { List as ImmutableList } from 'immutable'; +import { STORE_HYDRATE } from '../actions/store'; + +const initialState = ImmutableList(); + +export default function statuses(state = initialState, action) { + switch(action.type) { + case STORE_HYDRATE: + return action.state.get('custom_emojis'); + default: + return state; + } +}; diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js index 0a8c3ce6c..e65144871 100644 --- a/app/javascript/mastodon/reducers/index.js +++ b/app/javascript/mastodon/reducers/index.js @@ -20,6 +20,7 @@ import search from './search'; import media_attachments from './media_attachments'; import notifications from './notifications'; import height_cache from './height_cache'; +import custom_emojis from './custom_emojis'; const reducers = { timelines, @@ -43,6 +44,7 @@ const reducers = { media_attachments, notifications, height_cache, + custom_emojis, }; export default combineReducers(reducers); diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index abf5cfd5b..595ab3658 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -2629,6 +2629,12 @@ button.icon-button.active i.fa-retweet { } } +.emoji-mart-emoji { + span { + background-repeat: no-repeat; + } +} + .upload-area { align-items: center; background: rgba($base-overlay-background, 0.8); diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 88bbc0dff..e2f15a601 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -4,6 +4,12 @@ class InitialStateSerializer < ActiveModel::Serializer attributes :meta, :compose, :accounts, :media_attachments, :settings, :push_subscription + has_many :custom_emojis, serializer: REST::CustomEmojiSerializer + + def custom_emojis + CustomEmoji.local + end + def meta store = { streaming_api_base_url: Rails.configuration.x.streaming_api_base_url, -- cgit From adc38078dde102d4b6e98d364ada47fa3dd3f22c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 24 Sep 2017 04:09:32 +0200 Subject: Make sure nil IDs to not get cast to empty string in REST API (#5068) --- app/serializers/rest/application_serializer.rb | 2 +- app/serializers/rest/status_serializer.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'app/serializers') diff --git a/app/serializers/rest/application_serializer.rb b/app/serializers/rest/application_serializer.rb index 5eb03a513..a8945f66e 100644 --- a/app/serializers/rest/application_serializer.rb +++ b/app/serializers/rest/application_serializer.rb @@ -9,7 +9,7 @@ class REST::ApplicationSerializer < ActiveModel::Serializer end def client_id - object.uid.to_s + object.uid end def client_secret diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index ef3c325ba..e6270f902 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -24,11 +24,11 @@ class REST::StatusSerializer < ActiveModel::Serializer end def in_reply_to_id - object.in_reply_to_id.to_s + object.in_reply_to_id&.to_s end def in_reply_to_account_id - object.in_reply_to_account_id.to_s + object.in_reply_to_account_id&.to_s end def current_user? -- cgit