# frozen_string_literal: true module Api module V1 class BaseController < ActionController::API include ActionController::HttpAuthentication::Token::ControllerMethods # Both first, so that nothing below meets a query that is not shaped as it expects. include RequestShape include DashedKeys before_action :authenticate_member_from_token! before_action :enforce_member_for_write_operations!, only: %i(create update destroy) before_action :check_request_body, only: %i(create update) rescue_from CanCan::AccessDenied do head :forbidden end # What Graphiti raises for requests it cannot make sense of, which are the client's to fix. BAD_REQUEST_ERRORS = [ Graphiti::Errors::UnsupportedPageSize, Graphiti::Errors::UnsupportedPagination, Graphiti::Errors::UnsupportedSort, Graphiti::Errors::UnsupportedOperator, Graphiti::Errors::InvalidFilterValue, Graphiti::Errors::InvalidInclude, Graphiti::Errors::UnknownAttribute, Graphiti::Errors::InvalidAttributeAccess, Graphiti::Errors::InvalidRequest, Graphiti::Errors::StatNotFound, Graphiti::Errors::TypecastFailed ].freeze rescue_from(*BAD_REQUEST_ERRORS) { |error| render_error(:bad_request, error.message) } rescue_from(Graphiti::Errors::RecordNotFound, ActiveRecord::RecordNotFound) { |error| render_error(:not_found, error.message) } def index render jsonapi: resource_class.all(params) end def show render jsonapi: resource_class.find(params) end def create resource = resource_class.build(params) render_write resource, resource.save, status: :created end def update resource = resource_class.find(params) # The record as it is; the resource then checks what the changes would make of it. authorize! :update, resource.data render_write resource, resource.update_attributes end def destroy resource = resource_class.find(params) resource.destroy ? head(:no_content) : render(jsonapi_errors: resource) end # Public, as Graphiti gives resources this controller as their context, so that they can see who is writing what. attr_reader :current_user def resource_class @resource_class ||= "#{controller_name.singularize.camelize}Resource".constantize end private def render_write(resource, saved, status: :ok) saved ? render(jsonapi: resource, status: status) : render(jsonapi_errors: resource) end def render_error(status, detail, title: nil) code = Rack::Utils.status_code(status) title ||= Rack::Utils::HTTP_STATUS_CODES[code] render json: { errors: [{ status: code.to_s, title: title, detail: detail }] }, status: status, content_type: Mime[:jsonapi] end def enforce_member_for_write_operations! head :unauthorized unless current_user end def authenticate_member_from_token! authenticate_with_http_token do |token, _options| @current_user = Authentication.find_by(token: token, provider: 'api')&.member end end end end end