about summary refs log tree commit diff
path: root/app/services
diff options
context:
space:
mode:
Diffstat (limited to 'app/services')
-rw-r--r--app/services/activitypub/fetch_remote_poll_service.rb52
-rw-r--r--app/services/activitypub/fetch_replies_service.rb4
-rw-r--r--app/services/post_status_service.rb11
-rw-r--r--app/services/resolve_url_service.rb2
-rw-r--r--app/services/suspend_account_service.rb2
-rw-r--r--app/services/vote_service.rb40
6 files changed, 105 insertions, 6 deletions
diff --git a/app/services/activitypub/fetch_remote_poll_service.rb b/app/services/activitypub/fetch_remote_poll_service.rb
new file mode 100644
index 000000000..1dd587d73
--- /dev/null
+++ b/app/services/activitypub/fetch_remote_poll_service.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+class ActivityPub::FetchRemotePollService < BaseService
+  include JsonLdHelper
+
+  def call(poll, on_behalf_of = nil)
+    @json = fetch_resource(poll.status.uri, true, on_behalf_of)
+
+    return unless supported_context? && expected_type?
+
+    expires_at = begin
+      if @json['closed'].is_a?(String)
+        @json['closed']
+      elsif !@json['closed'].nil? && !@json['closed'].is_a?(FalseClass)
+        Time.now.utc
+      else
+        @json['endTime']
+      end
+    end
+
+    items = begin
+      if @json['anyOf'].is_a?(Array)
+        @json['anyOf']
+      else
+        @json['oneOf']
+      end
+    end
+
+    latest_options = items.map { |item| item['name'].presence || item['content'] }
+
+    # If for some reasons the options were changed, it invalidates all previous
+    # votes, so we need to remove them
+    poll.votes.delete_all if latest_options != poll.options
+
+    poll.update!(
+      last_fetched_at: Time.now.utc,
+      expires_at: expires_at,
+      options: latest_options,
+      cached_tallies: items.map { |item| item.dig('replies', 'totalItems') || 0 }
+    )
+  end
+
+  private
+
+  def supported_context?
+    super(@json)
+  end
+
+  def expected_type?
+    equals_or_includes_any?(@json['type'], %w(Question))
+  end
+end
diff --git a/app/services/activitypub/fetch_replies_service.rb b/app/services/activitypub/fetch_replies_service.rb
index 95c486a43..569d0d7c1 100644
--- a/app/services/activitypub/fetch_replies_service.rb
+++ b/app/services/activitypub/fetch_replies_service.rb
@@ -36,9 +36,7 @@ class ActivityPub::FetchRepliesService < BaseService
     return collection_or_uri if collection_or_uri.is_a?(Hash)
     return unless @allow_synchronous_requests
     return if invalid_origin?(collection_or_uri)
-    collection = fetch_resource_without_id_validation(collection_or_uri)
-    raise Mastodon::UnexpectedResponseError if collection.nil?
-    collection
+    fetch_resource_without_id_validation(collection_or_uri, nil, true)
   end
 
   def filtered_replies
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index cfb266fbb..8a9d26c56 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -15,6 +15,7 @@ class PostStatusService < BaseService
   # @option [String] :spoiler_text
   # @option [String] :language
   # @option [String] :scheduled_at
+  # @option [Hash] :poll Optional poll to attach
   # @option [Enumerable] :media_ids Optional array of media IDs to attach
   # @option [Doorkeeper::Application] :application
   # @option [String] :idempotency Optional idempotency key
@@ -28,6 +29,7 @@ class PostStatusService < BaseService
     return idempotency_duplicate if idempotency_given? && idempotency_duplicate?
 
     validate_media!
+    validate_poll!
     preprocess_attributes!
 
     if scheduled?
@@ -98,13 +100,19 @@ class PostStatusService < BaseService
   def validate_media!
     return if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable)
 
-    raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > 4
+    raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > 4 || @options[:poll].present?
 
     @media = @account.media_attachments.where(status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i))
 
     raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:video?)
   end
 
+  def validate_poll!
+    return if @options[:poll].blank?
+
+    @poll = @account.polls.new(@options[:poll])
+  end
+
   def language_from_option(str)
     ISO_639.find(str)&.alpha2
   end
@@ -157,6 +165,7 @@ class PostStatusService < BaseService
       text: @text,
       media_attachments: @media || [],
       thread: @in_reply_to,
+      owned_poll: @poll,
       sensitive: (@options[:sensitive].nil? ? @account.user&.setting_default_sensitive : @options[:sensitive]) || @options[:spoiler_text].present?,
       spoiler_text: @options[:spoiler_text] || '',
       visibility: @visibility,
diff --git a/app/services/resolve_url_service.rb b/app/services/resolve_url_service.rb
index ed0c56923..b98759bf6 100644
--- a/app/services/resolve_url_service.rb
+++ b/app/services/resolve_url_service.rb
@@ -20,7 +20,7 @@ class ResolveURLService < BaseService
   def process_url
     if equals_or_includes_any?(type, %w(Application Group Organization Person Service))
       FetchRemoteAccountService.new.call(atom_url, body, protocol)
-    elsif equals_or_includes_any?(type, %w(Note Article Image Video Page))
+    elsif equals_or_includes_any?(type, %w(Note Article Image Video Page Question))
       FetchRemoteStatusService.new.call(atom_url, body, protocol)
     end
   end
diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb
index fc3bc03a5..b2ae3a47c 100644
--- a/app/services/suspend_account_service.rb
+++ b/app/services/suspend_account_service.rb
@@ -84,7 +84,7 @@ class SuspendAccountService < BaseService
     @account.locked           = false
     @account.display_name     = ''
     @account.note             = ''
-    @account.fields           = {}
+    @account.fields           = []
     @account.statuses_count   = 0
     @account.followers_count  = 0
     @account.following_count  = 0
diff --git a/app/services/vote_service.rb b/app/services/vote_service.rb
new file mode 100644
index 000000000..8bab2810e
--- /dev/null
+++ b/app/services/vote_service.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+class VoteService < BaseService
+  include Authorization
+
+  def call(account, poll, choices)
+    authorize_with account, poll, :vote?
+
+    @account = account
+    @poll    = poll
+    @choices = choices
+    @votes   = []
+
+    ApplicationRecord.transaction do
+      @choices.each do |choice|
+        @votes << @poll.votes.create!(account: @account, choice: choice)
+      end
+    end
+
+    return if @poll.account.local?
+
+    @votes.each do |vote|
+      ActivityPub::DeliveryWorker.perform_async(
+        build_json(vote),
+        @account.id,
+        @poll.account.inbox_url
+      )
+    end
+  end
+
+  private
+
+  def build_json(vote)
+    ActiveModelSerializers::SerializableResource.new(
+      vote,
+      serializer: ActivityPub::VoteSerializer,
+      adapter: ActivityPub::Adapter
+    ).to_json
+  end
+end