mysociety / alaveteli

Provide a Freedom of Information request system for your jurisdiction
https://alaveteli.org
Other
389 stars 196 forks source link

Extract `IncomingMessage` redelivery code #7825

Open garethrees opened 1 year ago

garethrees commented 1 year ago

We're starting to want to redeliver incoming messages from different places in the codebase. Ideally we'd have an abstraction for redelivering a message such that we don't have to redefine it everywhere.

garethrees commented 1 year ago

Here's a sketch

# Provide a convenience method on IncomingMessage
class IncomingMessage
  def redelivery(editor:)
    Redelivery.new(self, editor: editor)
  end
end

# Extract redelivery to its own abstraction
class IncomingMessage::Redelivery
  class AlreadyReelivered < StandardError ; end

  def initialize(incoming_message, editor:)
    @incoming_message = incoming_message
    @editor = editor

    @redelivered_to = []
    @complete = false
  end

  attr_reader :redelivered_to

  def redeliver(destination_requests)
    raise AlreadyReelivered if complete?

    ActiveRecord::Base.transaction do
      # Redeliver the message to the destination requests
      destination_requests.each do |destination_request|
        destination_request.receive(
          incoming_message.raw_email.mail,
          incoming_message.raw_email.data,
          override_stop_new_responses: true
        )

        incoming_message.info_request.log_event(
          'redeliver_incoming',
          editor: editor,
          destination_request: destination_request.id,
          deleted_incoming_message_id: incoming_message.id
        )

        @redelivered_to << destination_request
      end

      # Expire cached files
      previous_request.expire

      # Destroy the incoming message on the previous request
      incoming_message.destroy!

      # Mark as complete to prevent this redelivery being run again
      @complete = true
    end
  end

  def complete?
    complete
  end

  protected

  attr_reader :incoming_message, :editor, :complete

  private

  def previous_request
    incoming_message.info_request
  end
end

# Clean up the controller method
class AdminIncomingMessageController
  def redeliver
    message_ids = params[:url_title].split(',').map(&:strip)
    destination_requests = requests_for_redelivery(message_ids)
    redelivery = @incoming_message.redelivery(editor: current_user)

    if redelivery.redeliver(destination_requests)
      # TODO: Would be better to render a bulleted list destination requests
      # that received the message
      flash[:notice] =
        "Message has been moved to request(s). Showing the last one:"

      redirect_to admin_request_url(redelivery.redelivered_to.last)
    else
      flash[:error] = "Message could not be redelivered"
      # render appropriate action with appropriate messaging
    end
  end

  private

  def requests_for_redelivery(message_ids)
    destination_requests = []

    # Look up each request and ensure each is valid
    message_ids.each do |m|
      destination_request =
        if m.match(/^[0-9]+$/)
          InfoRequest.find_by_id(m.to_i)
        else
          InfoRequest.find_by_url_title!(m)
        end

      if destination_request
        destination_requests << destination_request
      else
        flash[:error] = "Failed to find destination request '#{m}'"
        return redirect_to admin_request_url(previous_request)
      end
    end

    # Handle the case that none are given
    if destination_requests.empty?
      flash[:error] =
        "You must supply at least one request to redeliver the message to."
      return redirect_to admin_request_url(previous_request)
    end

    destination_requests
  end
end