robertgauld/osm

View on GitHub
lib/osm/api.rb

Summary

Maintainability
C
1 day
Test Coverage
module Osm

  class Api
    
    # Default options
    @@site = nil      # Used as the defult value for api_site in new instances
    @site = nil       # Used to make requests from an instance
    @@debug = false   # Puts helpful information whilst connected to OSM/OGM
    @@api_details = {:osm=>{}, :ogm=>{}} # API details - [:osm | :ogm] [:id | :token | :name]


    BASE_URLS = {
      :osm => 'https://www.onlinescoutmanager.co.uk',
      :ogm => 'http://www.onlineguidemanager.co.uk',
      :osm_staging => 'http://staging.onlinescoutmanager.co.uk',
      :migration => 'https://migration.onlinescoutmanager.com'
    }


    # Configure the API options used by all instances of the class
    # @param [Hash] options
    # @option options [Symbol] :default_site whether to use OSM (if :osm) or OGM (if :ogm)
    # @option options [Hash] :osm (optional but :osm_api or :ogm_api must be present) the api data for OSM
    # @option options[:osm] [String] :id the apiid given to you for using the OSM id
    # @option options[:osm] [String] :token the token which goes with the above api
    # @option options[:osm] [String] :name the name displayed in the External Access tab of OSM
    # @option options [Hash] :ogm (optional but :osm_api or :ogm_api must be present) the api data for OGM
    # @option options[:ogm] [String] :id the apiid given to you for using the OGM id
    # @option options[:ogm] [String] :token the token which goes with the above api
    # @option options[:ogm] [String] :name the name displayed in the External Access tab of OGM
    # @option options [Boolean] :debug if true debugging info is output (optional, default = false)
    # @return nil
    def self.configure(options)
      unless options[:i_know].eql?(:unsupported)
        raise Osm::Error, 'The OSM gem is now unsupported. ' \
                          'To continue using it append "i_know: :unsupported" to your passed options. ' \
                          "See #{File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'UNSUPPORTED.md'))} for more details."
      end

      raise ArgumentError, ':default_site does not exist in options hash or is invalid, this should be set to either :osm or :ogm' unless Osm::Api::BASE_URLS.keys.include?(options[:default_site])
      raise ArgumentError, ":#{options[:default_site]} does not exist in options hash" if options[options[:default_site]].nil?

      Osm::Api::BASE_URLS.keys.each do |api_key|
        if options[api_key]
          api_data = options[api_key]
          raise ArgumentError, ":#{api_key} must be a Hash" unless api_data.is_a?(Hash)
          [:id, :token, :name].each do |key|
            raise ArgumentError, ":#{api_key} must contain a key :#{key}" unless api_data.key?(key)
          end
        end
      end

      @@site = options[:default_site]
      @@debug = !!options[:debug]
      @@api_details = {
        :osm => (options[:osm] || {}),
        :ogm => (options[:ogm] || {}),
        :osm_staging => (options[:osm_staging] || {}),
        :migration => (options[:migration] || {})
      }
      nil
    end

    # Configure the debug option
    # @param [Boolean] debug whether to display debugging information
    # @return nil
    def self.debug=(debug)
      @@debug = !!debug
    end

    # The debug option
    # @return Boolean whether debugging output is enabled
    def self.debug
      @@debug
    end

    # Initialize a new API connection
    # @param [String] user_id OSM userid of the user to act as (get this by using the authorize method)
    # @param [String] secret OSM secret of the user to act as (get this by using the authorize method)
    # @param [Symbol] site Whether to use OSM (:osm) or OGM (:ogm), defaults to the value set for the class
    # @return nil
    def initialize(user_id, secret, site=@@site)
      raise ArgumentError, 'You must pass a secret (get this by using the authorize method)' if secret.nil?
      raise ArgumentError, 'You must pass a user_id (get this by using the authorize method)' if user_id.nil?
      raise ArgumentError, 'site is invalid, if passed it should be either :osm or :ogm, if not passed then you forgot to run Api.configure' unless Osm::Api::BASE_URLS.keys.include?(site)

      @site = site
      set_user(user_id, secret)
      nil
    end


    # Get the API name
    # @return [String]
    def api_name
      @@api_details[@site][:name]
    end

    # Get the API ID
    # @return [String]
    def api_id
      @@api_details[@site][:id]
    end
    def to_i
      api_id
    end


    # Get the site this Api currently uses
    # @return [Symbol] :osm or :ogm
    def site
      @site
    end


    # Get the current user_id
    # @return [String]
    def user_id
      @user_id
    end


    # Get the userid and secret to be able to act as a certain user on the OSM/OGM system
    # @param [Symbol] site The site to use either :osm or :ogm (defaults to whatever was set in the configure method)
    # @param [String] email The login email address of the user on OSM
    # @param [String] password The login password of the user on OSM
    # @return [Hash] a hash containing the following keys:
    #   * :user_id - the userid to use in future requests
    #   * :secret - the secret to use in future requests
    def self.authorize(site=@@site, email, password)
      api_data = {
        'email' => email,
        'password' => password,
      }
      data = perform_query(site, 'users.php?action=authorise', api_data)
      return {
        :user_id => data['userid'],
        :secret => data['secret'],
      }
    end


    # Set the OSM user to make future requests as
    # @param [String] user_id The OSM userid to use (get this using the authorize method)
    # @param [String] secret The OSM secret to use (get this using the authorize method)
    # @return [Osm::Api] self
    def set_user(user_id, secret)
      @user_id = user_id
      @user_secret = secret
      return self
    end

    # Get the base URL for requests to OSM/OGM
    # @param [Symbol] site For OSM or OGM (:osm or :ogm)
    # @return [String] The base URL for requests
    def self.base_url(site=@@site)
      raise ArgumentError, "Invalid site" unless Osm::Api::BASE_URLS.keys.include?(site)
      BASE_URLS[site]
    end

    # Get the base URL for requests to OSM.OGM
    # @param site For OSM or OGM (:osm or :ogm), defaults to the default for this api object
    # @return [String] The base URL for requests
    def base_url(site=@site)
      self.class.base_url(site)
    end

    # Make a query to the OSM/OGM API
    # @param [String] url The script on the remote server to invoke
    # @param [Hash] api_data A hash containing the values to be sent to the server in the body of the request
    # @return [Hash, Array, String] the parsed JSON returned by OSM
    def perform_query(url, api_data={}, raw=false)
      self.class.perform_query(@site, url, api_data.merge({
        'userid' => @user_id,
        'secret' => @user_secret,
      }), raw)
    end

    # Get API user's roles in OSM
    # @!macro options_get
    # @return [Array<Hash>] data returned by OSM
    def get_user_roles(*args)
      begin
        get_user_roles!(*args)
      rescue Osm::NoActiveRoles
        return []
      end
    end


    # Get API user's roles in OSM
    # @!macro options_get
    # @return [Array<Hash>] data returned by OSM
    # @raises Osm::NoActiveRoles
    def get_user_roles!(options={})
      cache_key = ['user_roles', @user_id]

      if !options[:no_cache] && Osm::Model.cache_exist?(self, cache_key)
        return Osm::Model.cache_read(self, cache_key)
      end

      begin
        data = perform_query('api.php?action=getUserRoles')
        unless data.eql?(false)
          # false equates to no roles
          Osm::Model.cache_write(self, cache_key, data)
          return data
        end
        fail Osm::NoActiveRoles, "You do not have any active roles in OSM."

      rescue Osm::Error => e
        if e.message.eql?('false')
          fail Osm::NoActiveRoles, "You do not have any active roles in OSM."
        else
          raise e
        end
      end

    end

    # Get API user's permissions
    # @!macro options_get
    # @return nil if an error occured or the user does not have access to that section
    # @return [Hash] {section_id => permissions_hash}
    def get_user_permissions(options={})
      cache_key = ['permissions', user_id]

      if !options[:no_cache] && Osm::Model.cache_exist?(self, cache_key)
        return Osm::Model.cache_read(self, cache_key)
      end

      all_permissions = Hash.new
      get_user_roles(options).each do |item|
        unless item['section'].eql?('discount')  # It's not an actual section
          all_permissions.merge!(Osm::to_i_or_nil(item['sectionid']) => Osm.make_permissions_hash(item['permissions']))
        end
      end
      Osm::Model.cache_write(self, cache_key, all_permissions)

      return all_permissions
    end

    # Set access permission for an API user for a given Section
    # @param [Section, Fixnum] section The Section to set permissions for
    # @param [Hash] permissions The permissions Hash
    def set_user_permissions(section, permissions)
      key = ['permissions', user_id]
      permissions = get_user_permissions.merge(section.to_i => permissions)
      Osm::Model.cache_write(self, key, permissions)
    end


    private
    # Make a query to the OSM/OGM API
    # @param [Symbol] site The site to use either :osm or :ogm
    # @param [String] url The script on the remote server to invoke
    # @param [Hash] api_data A hash containing the values to be sent to the server in the body of the request
    # @return [Hash, Array, String] the parsed JSON returned by OSM
    # @raise [Osm::Error] If an error was returned by OSM
    # @raise [Osm::ConnectionError] If an error occured connecting to OSM
    def self.perform_query(site, url, api_data={}, raw=false)
      raise ArgumentError, 'site is invalid, this should be set to either :osm or :ogm' unless Osm::Api::BASE_URLS.keys.include?(site)
 
      data = api_data.merge({
        'apiid' => @@api_details[site][:id],
        'token' => @@api_details[site][:token],
      })

      if @@debug
        puts "Making #{'RAW' if raw} :#{site} API request to #{url}"
        hide_values_for = ['secret', 'token']
        api_data_as_string = api_data.sort.map{ |key, value| "#{key} => #{hide_values_for.include?(key) ? 'PRESENT' : value.inspect}" }.join(', ')
        puts "{#{api_data_as_string}}"
      end

      begin
        result = HTTParty.post("#{BASE_URLS[site]}/#{url}", {:body => data})
      rescue SocketError, TimeoutError, OpenSSL::SSL::SSLError
        raise Osm::ConnectionError, 'A problem occured on the internet.'
      end
      raise Osm::ConnectionError, "HTTP Status code was #{result.response.code}" if !result.response.code.eql?('200')

      if @@debug
        puts "Result from :#{site} request to #{url}"
        puts "#{result.response.content_type}"
        puts result.response.body
      end

      return result.response.body if raw
      return nil if result.response.body.empty?
      case result.response.content_type
        when 'application/json', 'text/html'
          begin
            decoded = ActiveSupport::JSON.decode(result.response.body)
            if osm_error = get_osm_error(decoded)
              fail Osm::Error, osm_error if osm_error
            end
            return decoded
          rescue ActiveModel::VERSION::MAJOR >= 4 ? JSON::ParserError : MultiJson::ParseError
            fail Osm::Error, result.response.body
          end
        when 'image/jpeg'
          return result.response.body
        else
          fail Osm::Error, "Unhandled content-type: #{result.response.content_type}"
      end
    end

    # Get the error returned by OSM
    # @param data what OSM gave us
    # @return false if no error message was found
    # @return [String] the error message
    def self.get_osm_error(data)
      return false unless data.is_a?(Hash)
      return false if data['ok']
      to_return = data['error'] || data['err'] || false
      if to_return.is_a?(Hash)
        to_return = to_return['message'] unless to_return['message'].blank?
      end
      to_return = false if to_return.blank?
      return to_return
    end

  end # Class Api

end # Module