From f11b6dac0a1acea552f3d8bc52bb0a4c5797d81d Mon Sep 17 00:00:00 2001 From: michael-duke Date: Fri, 30 Dec 2022 23:58:36 +0300 Subject: [PATCH 01/10] Remove .env from git history and include it in .gitignore --- .env | 6 ------ .gitignore | 5 ++++- 2 files changed, 4 insertions(+), 7 deletions(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index f17858f..0000000 --- a/.env +++ /dev/null @@ -1,6 +0,0 @@ -POSTGRES_USER=postgres -# If you declared a password when creating the database: -POSTGRES_PASSWORD=gres@r00t -POSTGRES_HOST=localhost -POSTGRES_DB=Blog_App_development -POSTGRES_TEST_DB=Blog_App_test \ No newline at end of file diff --git a/.gitignore b/.gitignore index 370921d..726d2b7 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,7 @@ node_modules/ # Ignore blog.http, used for testing api endpoints with REST Client -blog.http \ No newline at end of file +blog.http + +# Ignore .env file for storing environment variables +.env \ No newline at end of file From f6e719112c23334a821ed0be4d953f549c211b21 Mon Sep 17 00:00:00 2001 From: michael-duke Date: Sat, 31 Dec 2022 00:55:36 +0300 Subject: [PATCH 02/10] Add JTI matcher to existing users --- db/migrate/20221230204000_add_jti_to_users.rb | 11 +++++++++++ db/schema.rb | 4 +++- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20221230204000_add_jti_to_users.rb diff --git a/db/migrate/20221230204000_add_jti_to_users.rb b/db/migrate/20221230204000_add_jti_to_users.rb new file mode 100644 index 0000000..2acf7f0 --- /dev/null +++ b/db/migrate/20221230204000_add_jti_to_users.rb @@ -0,0 +1,11 @@ +class AddJtiToUsers < ActiveRecord::Migration[7.0] + def change + # add_column :users, :jti, :string, null: false + # add_index :users, :jti, unique: true + # If you already have user records, you will need to initialize its `jti` column before setting it to not nullable. Your migration will look this way: + add_column :users, :jti, :string + User.all.each { |user| user.update_column(:jti, SecureRandom.uuid) } + change_column_null :users, :jti, false + add_index :users, :jti, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index bdbefaf..ff6196c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2022_12_09_043756) do +ActiveRecord::Schema[7.0].define(version: 2022_12_30_204000) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -67,8 +67,10 @@ t.datetime "confirmation_sent_at" t.string "unconfirmed_email" t.string "role" + t.string "jti", null: false t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true + t.index ["jti"], name: "index_users_on_jti", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end From fe3c6826d2b79e14034df67597bb4b883a7cec06 Mon Sep 17 00:00:00 2001 From: michael-duke Date: Sat, 31 Dec 2022 00:56:32 +0300 Subject: [PATCH 03/10] Add devise-jwt to Gemfile and install using bundle --- Gemfile | 6 ++++++ Gemfile.lock | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/Gemfile b/Gemfile index 229b222..ce870b6 100644 --- a/Gemfile +++ b/Gemfile @@ -16,9 +16,15 @@ gem 'pg', '~> 1.1' gem 'rubocop', '>= 1.0', '< 2.0' # Use for Devise Authentication +# https://github.com/heartcombo/devise gem 'devise' +# JWT for Devise Authentication for API +# https://github.com/waiting-for-dev/devise-jwt +gem 'devise-jwt' + # Use CAnCanCan for Authorization +# https://github.com/CanCanCommunity/cancancan gem 'cancancan' # Use for hiding credentials diff --git a/Gemfile.lock b/Gemfile.lock index f33a6d4..4e40e2a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -105,11 +105,24 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) + devise-jwt (0.10.0) + devise (~> 4.0) + warden-jwt_auth (~> 0.6) diff-lcs (1.5.0) dotenv (2.8.1) dotenv-rails (2.8.1) dotenv (= 2.8.1) railties (>= 3.2) + dry-auto_inject (0.9.0) + dry-container (>= 0.3.4) + dry-configurable (0.16.1) + dry-core (~> 0.6) + zeitwerk (~> 2.6) + dry-container (0.11.0) + concurrent-ruby (~> 1.0) + dry-core (0.9.1) + concurrent-ruby (~> 1.0) + zeitwerk (~> 2.6) erubi (1.11.0) ffi (1.15.5-x64-mingw-ucrt) globalid (1.0.0) @@ -126,6 +139,7 @@ GEM actionview (>= 5.0.0) activesupport (>= 5.0.0) json (2.6.2) + jwt (2.6.0) loofah (2.19.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) @@ -269,6 +283,11 @@ GEM uniform_notifier (1.16.0) warden (1.2.9) rack (>= 2.0.9) + warden-jwt_auth (0.7.0) + dry-auto_inject (~> 0.8) + dry-configurable (~> 0.13) + jwt (~> 2.1) + warden (~> 1.2) web-console (4.2.0) actionview (>= 6.0.0) activemodel (>= 6.0.0) @@ -297,6 +316,7 @@ DEPENDENCIES database_cleaner debug devise + devise-jwt dotenv-rails ffi importmap-rails From 97ba9b5458d3240994e27fbc87a714cc626a6e3d Mon Sep 17 00:00:00 2001 From: michael-duke Date: Sat, 31 Dec 2022 00:57:21 +0300 Subject: [PATCH 04/10] Include JWT configurations for for sing_in and sign_out --- config/initializers/devise.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 3d548ab..6f1669a 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -308,4 +308,14 @@ # When set to false, does not sign a user in automatically after their password is # changed. Defaults to true, so a user is signed in automatically after changing a password. # config.sign_in_after_change_password = true + config.jwt do |jwt| + jwt.secret = ENV['DEVISE_JWT_SECRET_KEY'] + jwt.dispatch_requests = [ + ['POST', %r{^/api/v1/users/sign_in$}] + ] + jwt.revocation_requests = [ + ['DELETE', %r{^/api/v1/users/sign_out$} ] + ] + jwt.expiration_time = 120.minutes.to_i + end end From fb11167071fd787d551855aed995b7208b577750 Mon Sep 17 00:00:00 2001 From: michael-duke Date: Sat, 31 Dec 2022 00:58:20 +0300 Subject: [PATCH 05/10] Add devise fot jwt and make authentication for accessing API --- app/controllers/api/v1/application_controller.rb | 3 ++- app/models/user.rb | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/application_controller.rb b/app/controllers/api/v1/application_controller.rb index 3a1093d..a1a0565 100644 --- a/app/controllers/api/v1/application_controller.rb +++ b/app/controllers/api/v1/application_controller.rb @@ -2,7 +2,8 @@ class Api::V1::ApplicationController < ActionController::API include Response include ExceptionHandler - before_action :restrict_access + # before_action :restrict_access + before_action :authenticate_user! respond_to :json private diff --git a/app/models/user.rb b/app/models/user.rb index d48f0f5..09ca7e7 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,8 +1,11 @@ class User < ApplicationRecord + include Devise::JWT::RevocationStrategies::JTIMatcher # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, - :recoverable, :rememberable, :validatable, :confirmable + :recoverable, :rememberable, :validatable, :confirmable, + :jwt_authenticatable, jwt_revocation_strategy: self + validates :name, presence: true validates :posts_counter, comparison: { greater_than_or_equal_to: 0 }, numericality: { only_integer: true } @@ -10,6 +13,10 @@ class User < ApplicationRecord has_many :comments, foreign_key: :author_id, dependent: :destroy has_many :likes, foreign_key: :author_id, dependent: :destroy + # def jwt_payload + # super + # end + # User::Roles # The available roles ROLES = %i[admin default].freeze From bb1a03e642192d6ccd7894c3a6a3d7295dcbcae1 Mon Sep 17 00:00:00 2001 From: michael-duke Date: Sat, 31 Dec 2022 19:35:21 +0300 Subject: [PATCH 06/10] Add racks-cors gem to allow cross origin requests --- Gemfile | 6 ++++++ Gemfile.lock | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/Gemfile b/Gemfile index ce870b6..2337d32 100644 --- a/Gemfile +++ b/Gemfile @@ -15,6 +15,10 @@ gem 'pg', '~> 1.1' # Use Rubocop for linters gem 'rubocop', '>= 1.0', '< 2.0' +# Use rack-cors for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible +# https:// github.com/cyu/rack-cors +gem 'rack-cors' + # Use for Devise Authentication # https://github.com/heartcombo/devise gem 'devise' @@ -83,6 +87,8 @@ group :development do gem 'web-console' # Use bullet to fix N + 1 problems gem 'bullet' + # Use letter_opener to open emails in the browser + gem 'letter_opener' # Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler] # gem "rack-mini-profiler" diff --git a/Gemfile.lock b/Gemfile.lock index 4e40e2a..b12ab27 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -140,6 +140,10 @@ GEM activesupport (>= 5.0.0) json (2.6.2) jwt (2.6.0) + launchy (2.5.2) + addressable (~> 2.8) + letter_opener (1.8.1) + launchy (>= 2.2, < 3) loofah (2.19.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) @@ -174,6 +178,8 @@ GEM nio4r (~> 2.0) racc (1.6.0) rack (2.2.4) + rack-cors (1.1.1) + rack (>= 2.0.0) rack-test (2.0.2) rack (>= 1.3) rails (7.0.4) @@ -321,9 +327,11 @@ DEPENDENCIES ffi importmap-rails jbuilder + letter_opener pagy (~> 5.10) pg (~> 1.1) puma (~> 5.0) + rack-cors rails (~> 7.0.4) rails-controller-testing rspec-rails From 2b192325e8c5b08bcc04e043d42e2c91bcedcab1 Mon Sep 17 00:00:00 2001 From: michael-duke Date: Sat, 31 Dec 2022 19:39:59 +0300 Subject: [PATCH 07/10] Add configuration for CORS - Include letter_opener gem to send emails in development environment - Add configuration for JWT authorization - Routes for users and sessions controllers are now namespaced --- Gemfile | 2 +- config/application.rb | 3 +++ config/environments/development.rb | 3 +++ config/initializers/cors.rb | 17 +++++++++++++++++ config/routes.rb | 12 +++++++++++- 5 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 config/initializers/cors.rb diff --git a/Gemfile b/Gemfile index 2337d32..c7a2048 100644 --- a/Gemfile +++ b/Gemfile @@ -16,7 +16,7 @@ gem 'pg', '~> 1.1' gem 'rubocop', '>= 1.0', '< 2.0' # Use rack-cors for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible -# https:// github.com/cyu/rack-cors +# https:// github.com/cyu/rack-cors gem 'rack-cors' # Use for Devise Authentication diff --git a/config/application.rb b/config/application.rb index 8bf1de4..4a10da7 100644 --- a/config/application.rb +++ b/config/application.rb @@ -18,5 +18,8 @@ class Application < Rails::Application # # config.time_zone = "Central Time (US & Canada)" # config.eager_load_paths << Rails.root.join("extras") + config.session_store :cookie_store, key: '_interslice_session' + config.middleware.use ActionDispatch::Cookies + config.middleware.use config.session_store, config.session_options end end diff --git a/config/environments/development.rb b/config/environments/development.rb index 1ccc3ec..ab9eba1 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -52,6 +52,9 @@ # Default URL options for the Devise mailer config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } + + # Letter Opener config for development environment + config.action_mailer.delivery_method = :letter_opener # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb new file mode 100644 index 0000000..fe02237 --- /dev/null +++ b/config/initializers/cors.rb @@ -0,0 +1,17 @@ +# Be sure to restart your server when you modify this file. + +# Avoid CORS issues when API is called from the frontend app. +# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. + +# Read more: https://github.com/cyu/rack-cors + +Rails.application.config.middleware.insert_before 0, Rack::Cors do + allow do + origins "example.com" + + resource "*", + headers: :any, + methods: [:get, :post, :put, :patch, :delete, :options, :head], + expose: ["Authorization"] + end +end \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index a4a1f87..7036452 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,5 @@ Rails.application.routes.draw do - devise_for :users + #devise_for :users root to: "users#index" @@ -9,6 +9,16 @@ resources :likes, only: [:create] end end + + scope :api, defaults: { format: :json } do + scope :v1 do + devise_for :users, #as: 'api', # <- this is the important part since we already have a devise_for :users for the web app + controllers: { + registrations: 'api/v1/users/registrations', + sessions: 'api/v1/users/sessions' + } + end + end # API routes namespace :api do From 4add7ce3d7aeb0a02df5e0500128222d9add93d9 Mon Sep 17 00:00:00 2001 From: michael-duke Date: Sun, 1 Jan 2023 01:07:29 +0300 Subject: [PATCH 08/10] Configure jwt authorization for devise Adjust routes for api scope --- config/initializers/devise.rb | 4 ++-- config/routes.rb | 17 +++++++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 6f1669a..a3209c0 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -311,10 +311,10 @@ config.jwt do |jwt| jwt.secret = ENV['DEVISE_JWT_SECRET_KEY'] jwt.dispatch_requests = [ - ['POST', %r{^/api/v1/users/sign_in$}] + ['POST', %r{^/api/v1/login$}] ] jwt.revocation_requests = [ - ['DELETE', %r{^/api/v1/users/sign_out$} ] + ['DELETE', %r{^/api/v1/logout$} ] ] jwt.expiration_time = 120.minutes.to_i end diff --git a/config/routes.rb b/config/routes.rb index 7036452..3b66622 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,5 @@ Rails.application.routes.draw do - #devise_for :users + #devise_for :users # => this is commented out because we are using devise_for :users in the scope :api, defaults: { format: :json } do block below root to: "users#index" @@ -12,10 +12,16 @@ scope :api, defaults: { format: :json } do scope :v1 do - devise_for :users, #as: 'api', # <- this is the important part since we already have a devise_for :users for the web app + devise_for :users, # => this is the devise_for :users that is used for the api controllers: { registrations: 'api/v1/users/registrations', sessions: 'api/v1/users/sessions' + }, + path: '', + path_names: { + sign_in: 'login', + sign_out: 'logout', + registration: 'register' } end end @@ -23,10 +29,9 @@ # API routes namespace :api do namespace :v1 do - resources :users do - resources :posts do - resources :comments - resources :likes + resources :users, only: [:index, :show] do + resources :posts, only: [:index, :show] do + resources :comments, only: [:index, :show, :create] end end end From 22a5d076d9914a01f4e601e2e5aacf58f88e6f35 Mon Sep 17 00:00:00 2001 From: michael-duke Date: Sun, 1 Jan 2023 01:09:35 +0300 Subject: [PATCH 09/10] Generate registrations and sessions controllers to handle authentication for API Point routes to the new controllers for registrations and sessions --- .../api/v1/users/registrations_controller.rb | 85 +++++++++++++++++++ .../api/v1/users/sessions_controller.rb | 85 +++++++++++++++++++ config/routes.rb | 5 +- 3 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 app/controllers/api/v1/users/registrations_controller.rb create mode 100644 app/controllers/api/v1/users/sessions_controller.rb diff --git a/app/controllers/api/v1/users/registrations_controller.rb b/app/controllers/api/v1/users/registrations_controller.rb new file mode 100644 index 0000000..3623947 --- /dev/null +++ b/app/controllers/api/v1/users/registrations_controller.rb @@ -0,0 +1,85 @@ +class Api::V1::Users::RegistrationsController < Devise::RegistrationsController + # CSRF token verification is not required for API requests as they are stateless. + # The token is generated on the client side and is not accessible to the server. + # So, we skip this verification. + skip_before_action :verify_authenticity_token + + respond_to :json + + private + + def respond_with(resource, _opts = {}) + p resource + resource.persisted? ? register_success : register_failed + end + + def register_success + render json: { + status: 200, + message: 'Signed up sucessfully.' + }, status: :ok + end + + def register_failed + render json: { + status: 422, + message: "Signed up failure. #{resource.errors.full_messages.to_sentence}" + }, status: :unprocessable_entity + end + + # GET /resource/sign_up + # def new + # super + # end + + # POST /resoure# + # def create + # super + # end + + # GET /resource/edit + # def edit + # super + # end + + # PUT /resource + # def update + # super + # end + + # DELETE /resource + # def destroy + # super + # end + + # GET /resource/cancel + # Forces the session data which is usually expired after sign + # in to be expired now. This is useful if the user wants to + # cancel oauth signing in/up in the middle of the process, + # removing all OAuth session data. + # def cancel + # super + # end + + # protected + + # If you have extra params to permit, append them to the sanitizer. + # def configure_sign_up_params + # devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute]) + # end + + # If you have extra params to permit, append them to the sanitizer. + # def configure_account_update_params + # devise_parameter_sanitizer.permit(:account_update, keys: [:attribute]) + # end + + # The path used after sign up. + # def after_sign_up_path_for(resource) + # super(resource) + # end + + # The path used after sign up for inactive accounts. + # def after_inactive_sign_up_path_for(resource) + # super(resource) + # end +end diff --git a/app/controllers/api/v1/users/sessions_controller.rb b/app/controllers/api/v1/users/sessions_controller.rb new file mode 100644 index 0000000..8f7803b --- /dev/null +++ b/app/controllers/api/v1/users/sessions_controller.rb @@ -0,0 +1,85 @@ +class Api::V1::Users::SessionsController < Devise::SessionsController + # CSRF token verification is not required for API requests as they are stateless. + # The token is generated on the client side and is not accessible to the server. + # So, we skip this verification. + skip_before_action :verify_authenticity_token + + respond_to :json + + private + + def respond_with(_resource, _opts = {}) + current_user ? log_in_success : log_in_failure + end + + def respond_to_on_destroy + if request.headers['Authorization'].present? + jwt_payload = JWT.decode(request.headers['Authorization'].split.last, + ENV.fetch('DEVISE_JWT_SECRET_KEY')).first + + current_user = User.find(jwt_payload['sub']) + + current_user ? log_out_success : log_out_failure + else + log_out_failure + end + end + + def log_in_success + render json: { + status: { + code: 200, + message: 'Logged in sucessfully.', + data: current_user + } + }, status: :ok + end + + def log_in_failure + render json: { + status: { + code: 401, + message: "Logged in failure. #{resource.errors.full_messages.to_sentence}", + data: current_user + } + }, status: :unauthorized + end + + def log_out_success + render json: { + status: 200, + message: 'Logged out sucessfully.' + }, status: :ok + end + + def log_out_failure + render json: { + status: 401, + message: 'Logged out failure.' + }, status: :unauthorized + end + + # before_action :configure_sign_in_params, only: [:create] + + # GET /resource/sign_in + # def new + # super + # end + + # POST /resource/sign_in + # def create + # super + # end + + # DELETE /resource/sign_out + # def destroy + # super + # end + + # protected + + # If you have extra params to permit, append them to the sanitizer. + # def configure_sign_in_params + # devise_parameter_sanitizer.permit(:sign_in, keys: [:attribute]) + # end +end diff --git a/config/routes.rb b/config/routes.rb index 3b66622..9e217af 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,8 @@ Rails.application.routes.draw do - #devise_for :users # => this is commented out because we are using devise_for :users in the scope :api, defaults: { format: :json } do block below + # => This is commented out because we are using devise_for :users in the scope :api do block below + # => and we cant use the default devise_for :users for the web app and the api at the same time + # => because they both use the same routes + #devise_for :users root to: "users#index" From 554ad577268a0d414e5b6d352916f61e1012c826 Mon Sep 17 00:00:00 2001 From: michael-duke Date: Sun, 1 Jan 2023 01:10:34 +0300 Subject: [PATCH 10/10] Improve the API application_controller.rb Allow users to create comments on other posts --- .../api/v1/application_controller.rb | 18 ++++++++++++++---- app/controllers/api/v1/comments_controller.rb | 2 +- app/controllers/concerns/exception_handler.rb | 4 ++-- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/app/controllers/api/v1/application_controller.rb b/app/controllers/api/v1/application_controller.rb index a1a0565..9ed7d59 100644 --- a/app/controllers/api/v1/application_controller.rb +++ b/app/controllers/api/v1/application_controller.rb @@ -3,13 +3,23 @@ class Api::V1::ApplicationController < ActionController::API include ExceptionHandler # before_action :restrict_access + before_action :authenticate_user! + before_action :configure_permitted_parameters, if: :devise_controller? + respond_to :json - private + protected - def restrict_access - api_key = ApiKey.find_by_access_token(params[:access_token]) - head :unauthorized unless api_key + def configure_permitted_parameters + devise_parameter_sanitizer.permit(:sign_up) { |u| u.permit(:name, :email, :password, :password_confirmation) } + devise_parameter_sanitizer.permit(:sign_in) { |u| u.permit(:email, :password) } end + + # private + + # def restrict_access + # api_key = ApiKey.find_by_access_token(params[:access_token]) + # head :unauthorized unless api_key + # end end diff --git a/app/controllers/api/v1/comments_controller.rb b/app/controllers/api/v1/comments_controller.rb index dcbdb69..03c59de 100644 --- a/app/controllers/api/v1/comments_controller.rb +++ b/app/controllers/api/v1/comments_controller.rb @@ -30,7 +30,7 @@ def set_author end def set_post - @post = set_author.posts.find(params[:post_id]) + @post = Post.find(params[:post_id]) end def set_comment diff --git a/app/controllers/concerns/exception_handler.rb b/app/controllers/concerns/exception_handler.rb index d9429bb..ff7c886 100644 --- a/app/controllers/concerns/exception_handler.rb +++ b/app/controllers/concerns/exception_handler.rb @@ -2,11 +2,11 @@ module ExceptionHandler extend ActiveSupport::Concern included do rescue_from ActiveRecord::RecordNotFound do |e| - json_response({ message: e.message }, :not_found) + json_response({ code: 404, message: e.message }, :not_found) end rescue_from ActiveRecord::RecordInvalid do |e| - json_response({ message: e.message }, :unprocessable_entity) + json_response({ code: 422, message: e.message }, :unprocessable_entity) end end end