openfoodfoundation/openfoodnetwork

View on GitHub
app/controllers/admin/product_import_controller.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

require 'roo'

module Admin
  class ProductImportController < Spree::Admin::BaseController
    before_action :validate_upload_presence, except: %i[index guide validate_data]

    def index
      @product_categories = Spree::Taxon.order('name ASC').pluck(:name).uniq
      @tax_categories = Spree::TaxCategory.order('name ASC').pluck(:name)
      @shipping_categories = Spree::ShippingCategory.order('name ASC').pluck(:name)
      @producers = OpenFoodNetwork::Permissions.new(spree_current_user).
        managed_product_enterprises.is_primary_producer.by_name.to_a
    end

    def import
      @filepath = save_uploaded_file(params[:file])
      @importer = ProductImport::ProductImporter.new(File.new(@filepath), spree_current_user,
                                                     params[:settings])
      @original_filename = params[:file].try(:original_filename)
      @non_updatable_fields = ProductImport::EntryValidator.non_updatable_fields

      return if contains_errors? @importer

      @ams_data = ams_data
    end

    def validate_data
      return unless process_data('validate')

      render json: @importer.import_results, response: 200
    end

    def save_data
      return unless process_data('save')

      render json: @importer.save_results, response: 200
    end

    def reset_absent_products
      @importer = ProductImport::ProductImporter.new(
        File.new(file_path),
        spree_current_user,
        import_into: params[:import_into],
        enterprises_to_reset: params[:enterprises_to_reset],
        updated_ids: params[:updated_ids],
        settings: params[:settings]
      )

      if params.key?(:enterprises_to_reset) && params.key?(:updated_ids)
        @importer.reset_absent(params[:updated_ids])
      end

      render json: @importer.products_reset_count
    end

    private

    def validate_upload_presence
      return if params[:file] || (params[:filepath] && File.exist?(params[:filepath]))

      redirect_to '/admin/product_import', notice: I18n.t(:product_import_file_not_found_notice)
    end

    def process_data(method)
      @importer = ProductImport::ProductImporter.new(
        File.new(file_path),
        spree_current_user,
        start: params[:start],
        end: params[:end],
        settings: params[:settings]
      )

      begin
        @importer.public_send("#{method}_entries")
      rescue StandardError => e
        render json: e.message, response: 500
        return false
      end

      true
    end

    def contains_errors?(importer)
      if importer.errors.present?
        redirect_to '/admin/product_import', notice: @importer.errors.full_messages.to_sentence
        return true
      end

      check_spreadsheet_has_data importer
    end

    def check_spreadsheet_has_data(importer)
      return if importer.item_count

      redirect_to '/admin/product_import',
                  notice: I18n.t(:product_import_no_data_in_spreadsheet_notice)
      true
    end

    def save_uploaded_file(upload)
      extension = File.extname(upload.original_filename)
      directory = Dir.mktmpdir 'product_import'
      File.open(File.join(directory, "import#{extension}"), 'wb') do |f|
        data = UploadSanitizer.new(upload.read).call
        f.write(data)
        f.path
      end
    end

    def ams_data
      {
        filepath: @filepath,
        item_count: @importer.item_count,
        enterprise_product_counts: @importer.enterprise_products,
        import_url: main_app.admin_product_import_process_async_path,
        save_url: main_app.admin_product_import_save_async_path,
        reset_url: main_app.admin_product_import_reset_async_path,
        importSettings: @importer.import_settings,
      }
    end

    # Define custom model class for Cancan permissions
    def model_class
      ProductImport::ProductImporter
    end

    def file_path
      @file_path ||= validate_file_path(sanitize_file_path(params[:filepath]))
    end

    def sanitize_file_path(file_path)
      FilePathSanitizer.new.sanitize(file_path, on_error: method(:raise_invalid_file_path))
    end

    def validate_file_path(file_path)
      return file_path if file_path.to_s.match?(TEMP_FILE_PATH_REGEX)

      raise_invalid_file_path
    end

    def raise_invalid_file_path
      redirect_to '/admin/product_import',
                  notice: I18n.t(:product_import_no_data_in_spreadsheet_notice)
      raise 'Invalid File Path'
    end
    TEMP_FILE_PATH_REGEX = %r{^/tmp/product_import[A-Za-z0-9-]*/import\.csv$}
  end
end