theforeman/foreman

View on GitHub
app/controllers/api/v2/base_controller.rb

Summary

Maintainability
A
45 mins
Test Coverage
module Api
  module V2
    class BaseController < Api::BaseController
      include Api::Version2
      include Foreman::Controller::Authorize

      resource_description do
        api_version "v2"
        app_info N_("Foreman API v2 is currently the default API version.")
        param :location_id, Integer, :required => false, :desc => N_("Set the current location context for the request")
        param :organization_id, Integer, :required => false, :desc => N_("Set the current organization context for the request")
      end

      def_param_group :pagination do
        param :page, :number, :desc => N_("Page number, starting at 1")
        param :per_page, /\A([1-9]\d*|all)\Z$/, :desc => N_("Number of results per page to return, 'all' to return all results")
      end

      def_param_group :search_and_pagination do
        param :search, String, :desc => N_("filter results")
        param :order, String, :desc => N_("Sort and order by a searchable field, e.g. '<field> DESC'")
        param_group :pagination, ::Api::V2::BaseController
      end

      def_param_group :taxonomies do
        param :location_ids, Array, :required => false, :desc => N_("REPLACE locations with given ids")
        param :organization_ids, Array, :required => false, :desc => N_("REPLACE organizations with given ids.")
      end

      def_param_group :taxonomy_scope do
        param :location_id, Integer, :required => false, :desc => N_("Scope by locations")
        param :organization_id, Integer, :required => false, :desc => N_("Scope by organizations")
      end

      def_param_group :template_import_options do
        param :options, Hash, :required => false do
          param :force, :bool, :allow_nil => true, :desc => N_('use if you want update locked templates')
          param :associate, ['new', 'always', 'never'], :allow_nil => true, :desc => N_('determines when the template should associate objects based on metadata, new means only when new template is being created, always means both for new and existing template which is only being updated, never ignores metadata')
          param :lock, :bool, :allow_nil => true, :desc => N_('lock imported templates (false by default)')
          param :default, :bool, :allow_nil => true, :desc => N_('makes the template default meaning it will be automatically associated with newly created organizations and locations (false by default)')
        end
      end

      before_action :setup_has_many_params, :only => [:create, :update]
      before_action :check_media_type

      layout 'api/v2/layouts/index_layout', :only => :index

      helper_method :root_node_name, :metadata_total, :metadata_subtotal, :metadata_search,
        :metadata_order, :metadata_by, :metadata_page, :metadata_per_page, :index_node_permissions_snippet
      def root_node_name
        @root_node_name ||= if Rabl.configuration.use_controller_name_as_json_root
                              controller_name.split('/').last
                            elsif params['root_name'].present?
                              params['root_name']
                            else
                              Rabl.configuration.json_root_default_name
                            end
      end

      # may be extended by plugins to add additional permissions
      def index_node_permissions
        {
          :can_create => can_create?,
          :can_edit => authorized_for(:controller => controller_name, :action => 'edit'),
        }
      end

      def index_node_permissions_snippet
        perms = index_node_permissions.map do |key, value|
          "#{key.to_json}: #{value.to_json}"
        end
        perms.join(",")
      end

      def metadata_total
        @total ||= resource_scope.try(:size).to_i
      end

      def metadata_subtotal
        if params[:search].present?
          @subtotal ||= instance_variable_get("@#{controller_name}").try(:count).to_i
        else
          @subtotal ||= metadata_total
        end
      end

      def metadata_search
        @search ||= params[:search]
      end

      def metadata_order
        @order ||=  (params[:order].present? && (order_array = params[:order].split(' ')).any?) ? (order_array[1] || 'ASC') : nil
      end

      def metadata_by
        @by ||= (params[:order].present? && (order_array = params[:order].split(' ')).any?) ? order_array[0] : nil
      end

      def metadata_page
        @page ||= params[:page].present? ? params[:page].to_i : 1
      end

      def metadata_per_page
        @per_page ||= Setting[:entries_per_page] if params[:per_page].blank?
        @per_page ||= params[:per_page] == 'all' ? metadata_total : params[:per_page].to_i
      end

      # For the purpose of ADDING/REMOVING associations in CHILD node on POST/PUT payload
      # This method adds a Rails magic method ({association}_ids) based on the CHILD node ARRAY of OBJECTS
      # Example: PUT api/operatingsystems/24
      # {
      #   "operatingsystem": {
      #     "id": 24,
      #     "name": "CentOs",
      #     "architectures": [
      #         {
      #             "name": "i386",
      #             "id": 2
      #         },
      #         {
      #             "name": "x86_64",
      #             "id": 1
      #         }
      #      ]
      #     }
      #  }
      #
      #  Rails magic method (ex. architecture_ids) is added to params hash based on CHILD node (architectures)
      #
      #   "operatingsystem": {
      #     "id": 24,
      #     "name": "CentOs",
      #     "architecture_ids": [1,2]
      #    }
      #
      def append_array_of_ids(hash_params)
        model_name = controller_name.singularize
        hash_params&.dup&.each do |k, v|
          if v.is_a?(Array)
            association_name_ids = "#{k.singularize}_ids"
            association_name_names = "#{k.singularize}_names"
            if resource_class.instance_methods.map(&:to_s).include?(association_name_ids) && v.any? && v.all? { |a| a.key?("id") }
              params[model_name][association_name_ids] = v.map { |a| a["id"] }
              params[model_name].delete(k)
            elsif resource_class.instance_methods.map(&:to_s).include?(association_name_names) && v.any? && v.all? { |a| a.key?("name") }
              params[model_name][association_name_names] = v.map { |a| a["name"] }
              params[model_name].delete(k)
            end
          end
        end
      end

      def setup_has_many_params
        model_name = controller_name.singularize
        append_array_of_ids(params[model_name]) # wrapped params
        append_array_of_ids(params)             # unwrapped params
      end

      def check_media_type
        if (request.post? || request.put?) && request.media_type != "application/json"
          render_error(:unsupported_media_type, :status => :unsupported_media_type)
        end
      end

      def render_error(error, options = { })
        options = set_error_details(error, options)
        render options.merge(:template => "api/v2/errors/#{error}",
                             :layout   => 'api/v2/layouts/error_layout')
      end
    end
  end
end