Add polls (#10111)
* Add polls Fix #1629 * Add tests * Fixes * Change API for creating polls * Use name instead of content for votes * Remove poll validation for remote polls * Add polls to public pages * When updating the poll, update options just in case they were changed * Fix public pages showing both poll and other media
This commit is contained in:
parent
99dc212ae5
commit
230a012f00
47 changed files with 1038 additions and 19 deletions
51
app/services/activitypub/fetch_remote_poll_service.rb
Normal file
51
app/services/activitypub/fetch_remote_poll_service.rb
Normal file
|
@ -0,0 +1,51 @@
|
|||
# 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'].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!(
|
||||
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'], 'Question')
|
||||
end
|
||||
end
|
|
@ -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?
|
||||
|
@ -93,13 +95,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_id].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
|
||||
|
@ -152,6 +160,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,
|
||||
|
|
40
app/services/vote_service.rb
Normal file
40
app/services/vote_service.rb
Normal file
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue