# frozen_string_literal: true module Apicasso # Controller to extract common API features, # such as authentication and authorization class ApplicationController < ActionController::API include ActionController::HttpAuthentication::Token::ControllerMethods prepend_before_action :restrict_access after_action :register_api_request # Sets the authorization scope for the current API key def current_ability @current_ability ||= Apicasso::Ability.new(@api_key) end private # Identifies API key used in the request, avoiding unauthenticated access def restrict_access authenticate_or_request_with_http_token do |token, _options| @api_key = Apicasso::Key.find_by!(token: token) end end # Creates a request object in databse, registering the API key and # a hash of the request and the response def register_api_request Apicasso::Request.delay.create(api_key_id: @api_key.id, object: { request: request_hash, response: response_hash }) end # Request data built as a hash. # Returns UUID, URL, HTTP Headers and origin IP def request_hash { uuid: request.uuid, url: request.original_url, headers: request.env.select { |key, _v| key =~ /^HTTP_/ }, ip: request.remote_ip } end # Resonse data built as a hash. # Returns HTTP Status and request body def response_hash { status: response.status, body: JSON.parse(response.body) } end # Used to avoid errors parsing the search query, # which can be passed as a JSON or as a key-value param def parsed_query JSON.parse(params[:q]) rescue JSON::ParserError, TypeError params[:q] end # Used to avoid errors in included associations parsing def parsed_include params[:include].split(',') rescue NoMethodError [] end # Receives a `.paginate`d collection and returns the pagination # metadata to be merged into response def pagination_metadata_for(records) { total: records.total_entries, total_pages: records.total_pages, last_page: records.next_page.blank?, previous_page: previous_link_for(records), next_page: next_link_for(records), out_of_bounds: records.out_of_bounds?, offset: records.offset } end # Generates a contextualized URL of the next page for this request def next_link_for(records) uri = URI.parse(request.original_url) query = Rack::Utils.parse_query(uri.query) query['page'] = records.next_page uri.query = Rack::Utils.build_query(query) uri.to_s end # Generates a contextualized URL of the previous page for this request def previous_link_for(records) uri = URI.parse(request.original_url) query = Rack::Utils.parse_query(uri.query) query['page'] = records.previous_page uri.query = Rack::Utils.build_query(query) uri.to_s end # Receives a `:action, :resource, :object` hash to validate authorization def authorize_for(opts = {}) authorize! opts[:action], opts[:resource] if opts[:resource].present? authorize! opts[:action], opts[:object] if opts[:object].present? end end end