module Hourglass class ApiBaseController < ApplicationController include QueryConcern include SortConcern include BooleanParsing around_action :catch_halt before_action :require_login rescue_from StandardError, with: :internal_server_error rescue_from ActionController::ParameterMissing, with: :missing_parameters rescue_from(ActiveRecord::RecordNotFound) { render_404 no_halt: true } rescue_from Query::StatementInvalid, with: :query_statement_invalid rescue_from Hourglass::TimeLog::AlreadyBookedException, with: :already_booked include ::AuthorizationConcern private # use only these codes: # :ok (200) # :not_modified (304) # :bad_request (400) # :unauthorized (401) # :forbidden (403) # :not_found (404) # :internal_server_error (500) def respond_with_error(status, message, **options) render json: { message: message.is_a?(Array) && options[:array_mode] == :sentence ? message.to_sentence : message, status: Rack::Utils.status_code(status) }, status: status throw :halt unless options[:no_halt] end def respond_with_success(response_obj = nil) if response_obj render json: response_obj else head :no_content end throw :halt end def render_403(options = {}) respond_with_error :forbidden, options[:message] || t('hourglass.api.errors.forbidden'), no_halt: options[:no_halt] end def render_404(options = {}) respond_with_error :not_found, options[:message] || t("hourglass.api.#{controller_name}.errors.not_found", default: t('hourglass.api.errors.not_found')), no_halt: options[:no_halt] end def catch_halt catch :halt do yield end end def do_update(record, params_hash) record = authorize_update record, params_hash if record.errors.empty? respond_with_success else respond_with_error :bad_request, record.errors.full_messages, array_mode: :sentence end end def list_records(klass) authorize klass @query_identifier = klass.name.demodulize.tableize retrieve_query force_new: true init_sort scope = @query.results_scope order: sort_clause offset, limit = api_offset_and_limit respond_with_success( count: scope.count, offset: offset, limit: limit, records: scope.offset(offset).limit(limit).to_a ) end def bulk(params_key = controller_name, &block) @bulk_success = [] @bulk_errors = [] entries = params[params_key] entries = entries.to_unsafe_h if Rails::VERSION::MAJOR >= 5 && entries.instance_of?(ActionController::Parameters) entries.each_with_index do |(id, params), index| if Rails::VERSION::MAJOR <= 4 id, params = "new#{index}", id if id.is_a?(Hash) else id, params = "new#{index}", id if id.instance_of?(ActionController::Parameters) params = ActionController::Parameters.new(params) if params.is_a?(Hash) end error_preface = id.start_with?('new') ? bulk_error_preface(index, mode: :create) : bulk_error_preface(id) evaluate_entry bulk_entry(id, params, &block), error_preface end if @bulk_success.length > 0 flash_array :error, @bulk_errors if @bulk_errors.length > 0 && !api_request? respond_with_success success: @bulk_success, errors: @bulk_errors else respond_with_error :bad_request, @bulk_errors end end def evaluate_entry(entry, error_preface) if entry if entry.is_a? String @bulk_errors.push "#{error_preface} #{entry}" elsif entry.errors.empty? @bulk_success.push entry else @bulk_errors.push "#{error_preface} #{entry.errors.full_messages.to_sentence}" end else @bulk_errors.push "#{error_preface} #{t("hourglass.api.#{controller_name}.errors.not_found")}" end end def bulk_entry(id, params) yield id, params rescue ActiveRecord::RecordNotFound nil rescue Pundit::NotAuthorizedError => e e.policy.message || t('hourglass.api.errors.forbidden') end def bulk_error_preface(id, mode: nil) "[#{t("hourglass.api.#{controller_name}.errors.bulk_#{'create_' if mode == :create}error_preface", id: id)}:]" end def missing_parameters(_e) respond_with_error :bad_request, t('hourglass.api.errors.missing_parameters'), no_halt: true end def internal_server_error(e) messages = [e.message] + e.backtrace Rails.logger.error messages.join("\n") respond_with_error :internal_server_error, Rails.env.production? ? t('hourglass.api.errors.internal_server_error') : messages, no_halt: true end def already_booked(_e) respond_with_error :bad_request, t('hourglass.api.time_logs.errors.already_booked'), no_halt: true end def flash_array(type, messages) flash[type] = render_to_string partial: 'hourglass_ui/flash_array', locals: { messages: messages } end def custom_field_keys(params_hash) return {} unless params_hash[:custom_field_values] params_hash[:custom_field_values].keys end end end