loomio/loomio

View on GitHub
app/services/discussion_service.rb

Summary

Maintainability
A
35 mins
Test Coverage
class DiscussionService
  def self.create(discussion:, actor:, params: {})
    actor.ability.authorize!(:create, discussion)

    UserInviter.authorize!(user_ids: params[:recipient_user_ids],
                           emails: params[:recipient_emails],
                           audience: params[:recipient_audience],
                           model: discussion,
                           actor: actor)

    discussion.author = actor

    return false unless discussion.valid?

    discussion.save!

    DiscussionReader.for(user: actor, discussion: discussion)
                    .update(admin: true, guest: !discussion.group.present?, inviter_id: actor.id)

    users = add_users(user_ids: params[:recipient_user_ids],
                      emails: params[:recipient_emails],
                      audience: params[:recipient_audience],
                      discussion: discussion,
                      actor: actor)

    EventBus.broadcast('discussion_create', discussion, actor)
    Events::NewDiscussion.publish!(discussion: discussion,
                                   recipient_user_ids: users.pluck(:id),
                                   recipient_chatbot_ids: params[:recipient_chatbot_ids],
                                   recipient_audience: params[:recipient_audience])

  end

  def self.update(discussion:, actor:, params:)
    actor.ability.authorize! :update, discussion

    UserInviter.authorize!(user_ids: params[:recipient_user_ids],
                           emails: params[:recipient_emails],
                           audience: params[:recipient_audience],
                           model: discussion,
                           actor: actor)


    discussion.assign_attributes_and_files(params.except(:group_id))
    return false unless discussion.valid?
    rearrange = discussion.max_depth_changed?
    discussion.save!

    discussion.update_versions_count
    RepairThreadWorker.perform_async(discussion.id) if rearrange

    users = add_users(discussion: discussion,
                      actor: actor,
                      user_ids: params[:recipient_user_ids],
                      emails: params[:recipient_emails],
                      audience: params[:recipient_audience])

    EventBus.broadcast('discussion_update', discussion, actor, params)

    Events::DiscussionEdited.publish!(discussion: discussion,
                                      actor: actor,
                                      recipient_user_ids: users.pluck(:id),
                                      recipient_chatbot_ids: params[:recipient_chatbot_ids],
                                      recipient_audience: params[:recipient_audience],
                                      recipient_message: params[:recipient_message])
  end

  def self.invite(discussion:, actor:, params:)
    UserInviter.authorize!(user_ids: params[:recipient_user_ids],
                           emails: params[:recipient_emails],
                           audience: params[:recipient_audience],
                           model: discussion,
                           actor: actor)

    users = add_users(discussion: discussion,
                      actor: actor,
                      user_ids: params[:recipient_user_ids],
                      emails: params[:recipient_emails],
                      audience: params[:recipient_audience])

    Events::DiscussionAnnounced.publish!(discussion: discussion,
                                         actor: actor,
                                         recipient_user_ids: users.pluck(:id),
                                         recipient_chatbot_ids: params[:recipient_chatbot_ids],
                                         recipient_audience: params[:recipient_audience],
                                         recipient_message: params[:recipient_message])
  end

  # def self.destroy(discussion:, actor:)
  #   actor.ability.authorize!(:destroy, discussion)
  #   discussion.discard!
  #   DestroyDiscussionWorker.perform_async(discussion.id)
  #   EventBus.broadcast('discussion_destroy', discussion, actor)
  # end

  def self.discard(discussion:, actor:)
    actor.ability.authorize!(:discard, discussion)
    discussion.update(discarded_at: Time.now, discarded_by: actor.id)

    discussion.polls.update_all(discarded_at: Time.now, discarded_by: actor.id)
    GenericWorker.perform_async('SearchService', 'reindex_by_discussion_id', discussion.id)

    EventBus.broadcast('discussion_discard', discussion, actor)
    discussion.created_event
  end

  def self.close(discussion:, actor:)
    actor.ability.authorize! :update, discussion
    discussion.update(closed_at: Time.now, closer_id: actor.id)
    MessageChannelService.publish_models([discussion], group_id: discussion.group_id, user_id: actor.id)
  end

  def self.reopen(discussion:, actor:)
    actor.ability.authorize! :update, discussion
    discussion.update(closed_at: nil, closer_id: nil)
    MessageChannelService.publish_models([discussion], group_id: discussion.group_id, user_id: actor.id)
  end

  def self.move(discussion:, params:, actor:)
    source = discussion.group
    destination = ModelLocator.new(:group, params).locate || NullGroup.new
    destination.present? && actor.ability.authorize!(:move_discussions_to, destination)
    actor.ability.authorize! :move, discussion
    # discussion.add_admin!(actor)

    discussion.update group: destination.presence, private: moved_discussion_privacy_for(discussion, destination)
    discussion.polls.each { |poll| poll.update(group: destination.presence) }
    ActiveStorage::Attachment.where(record: discussion.items.map(&:eventable).concat([discussion])).update_all(group_id: destination.id)

    GenericWorker.perform_async('SearchService', 'reindex_by_discussion_id', discussion.id)
    EventBus.broadcast('discussion_move', discussion, params, actor)
    Events::DiscussionMoved.publish!(discussion, actor, source)
  end

  def self.pin(discussion:, actor:)
    actor.ability.authorize! :pin, discussion

    discussion.update(pinned_at: Time.now)

    EventBus.broadcast('discussion_pin', discussion, actor)
  end

  def self.unpin(discussion:, actor:)
    actor.ability.authorize! :pin, discussion

    discussion.update(pinned_at: nil)

    EventBus.broadcast('discussion_pin', discussion, actor)
  end

  def self.update_reader(discussion:, params:, actor:)
    actor.ability.authorize! :show, discussion
    reader = DiscussionReader.for(discussion: discussion, user: actor)
    reader.update(params.slice(:volume))
    Stance.joins(:poll).
           where('polls.discussion_id': reader.discussion_id).
           where(participant_id: actor.id).
           update(params.slice(:volume))

    EventBus.broadcast('discussion_update_reader', reader, params, actor)
  end

  def self.mark_as_seen(discussion:, actor:)
    actor.ability.authorize! :mark_as_seen, discussion
    reader = DiscussionReader.for_model(discussion, actor)
    reader.viewed!
    MessageChannelService.publish_models([reader.discussion], group_id: reader.discussion.group_id)
    EventBus.broadcast('discussion_mark_as_seen', reader, actor)
  end

  def self.mark_as_read(discussion:, params:, actor:)
    actor.ability.authorize! :mark_as_read, discussion
    RetryOnError.with_limit(2) do
      sequence_ids = RangeSet.ranges_to_list(RangeSet.to_ranges(params[:ranges]))
      NotificationService.viewed_events(actor_id: actor.id, discussion_id: discussion.id, sequence_ids: sequence_ids)
      reader = DiscussionReader.for_model(discussion, actor)
      reader.viewed!(params[:ranges])
      EventBus.broadcast('discussion_mark_as_read', reader, actor)
    end
  end

  def self.dismiss(discussion:, params:, actor:)
    actor.ability.authorize! :dismiss, discussion
    reader = DiscussionReader.for(user: actor, discussion: discussion)
    reader.dismiss!
    EventBus.broadcast('discussion_dismiss', reader, actor)
  end

  def self.recall(discussion:, params:, actor:)
    actor.ability.authorize! :dismiss, discussion
    reader = DiscussionReader.for(user: actor, discussion: discussion)
    reader.recall!
    EventBus.broadcast('discussion_recall', reader, actor)
  end

  def self.moved_discussion_privacy_for(discussion, destination)
    case destination.discussion_privacy_options
    when 'public_only'  then false
    when 'private_only' then true
    else                     discussion.private
    end
  end

  def self.mark_summary_email_as_read(user_id, time_start_i, time_finish_i)
    user = User.find_by!(id: user_id)
    time_start  = Time.at(time_start_i).utc
    time_finish = Time.at(time_finish_i).utc
    time_range = time_start..time_finish

    DiscussionQuery.visible_to(user: user, only_unread: true, or_public: false, or_subgroups: false).last_activity_after(time_start).each do |discussion|
      RetryOnError.with_limit(2) do
        sequence_ids = discussion.items.where("events.created_at": time_range).pluck(:sequence_id)
        DiscussionReader.for(user: user, discussion: discussion).viewed!(sequence_ids)
      end
    end
  end

  def self.add_users(discussion:, actor:, user_ids:, emails:, audience:)
    users = UserInviter.where_or_create!(actor: actor,
                                         user_ids: user_ids,
                                         emails: emails,
                                         model: discussion,
                                         audience: audience)


    volumes = {}
    Membership.where(group_id: discussion.group_id,
                     user_id: users.pluck(:id)).find_each do |m|
      volumes[m.user_id] = m.volume
    end
    
    DiscussionReader.
      where(discussion_id: discussion.id, user_id: users.map(&:id)).
      where("revoked_at is not null").update_all(revoked_at: nil, revoker_id: nil)

    new_discussion_readers = users.map do |user|
      DiscussionReader.new(user: user,
                           discussion: discussion,
                           inviter: actor,
                           guest: !volumes.has_key?(user.id),
                           admin: !discussion.group_id,
                           volume: volumes[user.id] || DiscussionReader.volumes[:normal])
    end

    DiscussionReader.import(new_discussion_readers, on_duplicate_key_ignore: true)

    discussion.update_members_count
    users
  end

  def self.extract_link_preview_urls(discussion)
    urls = discussion.link_previews.map { |lp| lp['url'] }
    discussion.items.each do |event|
      if event.eventable.present? && event.eventable.respond_to?(:link_previews)
        urls.concat(event.eventable.link_previews.map {|lp| lp['url']})
      end
    end
    urls.compact.uniq
  end
end