diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index eda95cc..57926f3 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,15 +1,43 @@ # Copyright (C) 2024 Manuel Bustillo class ApplicationController < ActionController::Base - after_action :set_csrf_cookie + after_action :set_csrf_cookie - private + skip_before_action :verify_authenticity_token, if: :development_swagger? - def set_csrf_cookie - cookies["csrf-token"] = { - value: form_authenticity_token, - secure: Rails.env.production?, - same_site: :strict, - } - end + rescue_from ActiveRecord::RecordInvalid do |exception| + render json: { + message: 'Record invalid', + errors: exception.record.errors.full_messages + }, status: :unprocessable_entity + end + + rescue_from ActionController::ParameterMissing do |exception| + render json: { + message: 'Parameter missing', + errors: [exception.message] + }, status: :bad_request + end + + rescue_from ActiveRecord::RecordNotFound do |exception| + render json: { + message: 'Record not found', + errors: [exception.message] + }, status: :not_found + end + + private + + def development_swagger? + Rails.env.test? || + Rails.env.development? && request.headers['referer'].include?('/api-docs/index.html') + end + + def set_csrf_cookie + cookies['csrf-token'] = { + value: form_authenticity_token, + secure: Rails.env.production?, + same_site: :strict + } + end end diff --git a/app/controllers/guests_controller.rb b/app/controllers/guests_controller.rb index 18dc949..efc3e58 100644 --- a/app/controllers/guests_controller.rb +++ b/app/controllers/guests_controller.rb @@ -4,15 +4,14 @@ require 'csv' class GuestsController < ApplicationController def index - @guests = Guest.all.includes(:group) - .joins(:group) - .order('groups.name' => :asc, name: :asc) - - render jsonapi: @guests + render json: Guest.all.includes(:group) + .joins(:group) + .order('groups.name' => :asc, name: :asc) + .as_json(only: %i[id name status], include: { group: { only: %i[id name] } }) end def update - Guests::UpdateUseCase.new(guest_ids: [params[:id]], params: params.require(:guest).permit(:name)).call + Guest.find(params[:id]).update!(params.require(:guest).permit(:name)) render json: {}, status: :ok end diff --git a/app/models/guest.rb b/app/models/guest.rb index 6b8f70e..8e794f2 100644 --- a/app/models/guest.rb +++ b/app/models/guest.rb @@ -29,7 +29,9 @@ class Guest < ApplicationRecord confirmed: 20, declined: 30, tentative: 40 - } + }, validate: true + + validates :name, presence: true scope :potential, -> { where.not(status: %i[declined considered]) } end diff --git a/spec/models/guest_spec.rb b/spec/models/guest_spec.rb index bd560af..b484464 100644 --- a/spec/models/guest_spec.rb +++ b/spec/models/guest_spec.rb @@ -3,14 +3,17 @@ require 'rails_helper' RSpec.describe Guest, type: :model do - it do - should define_enum_for(:status).with_values( - considered: 0, - invited: 10, - confirmed: 20, - declined: 30, - tentative: 40 - ) + describe 'validations' do + it { should validate_presence_of(:name) } + it do + should define_enum_for(:status).with_values( + considered: 0, + invited: 10, + confirmed: 20, + declined: 30, + tentative: 40 + ) + end end it { should belong_to(:group) } diff --git a/spec/requests/groups_spec.rb b/spec/requests/groups_spec.rb index 8bf622b..597848f 100644 --- a/spec/requests/groups_spec.rb +++ b/spec/requests/groups_spec.rb @@ -5,6 +5,7 @@ require 'swagger_helper' RSpec.describe 'groups', type: :request do path '/groups' do get('list groups') do + tags 'Groups' produces 'application/json' response(200, 'successful') do schema type: :array, @@ -25,7 +26,7 @@ RSpec.describe 'groups', type: :request do tentative: { type: :integer, minimum: 0 } } } - run_test! + xit end end end diff --git a/spec/requests/guests_spec.rb b/spec/requests/guests_spec.rb new file mode 100644 index 0000000..fedbce1 --- /dev/null +++ b/spec/requests/guests_spec.rb @@ -0,0 +1,89 @@ +# Copyright (C) 2024 Manuel Bustillo + +require 'swagger_helper' + +RSpec.describe 'guests', type: :request do + path '/guests/bulk_update' do + post('Update multiple guests in a single request') do + tags 'Guests' + consumes 'application/json' + produces 'application/json' + parameter name: :body, in: :body, schema: { + type: :object, + required: %i[guest_ids properties], + properties: { + guest_ids: { type: :array, items: { type: :string, format: :uuid } }, + properties: { + type: :object, + required: %i[status], + properties: { + status: { type: :string, enum: Guest.statuses.keys } + } + } + } + } + let(:body) do + { + guest_ids: [SecureRandom.uuid, SecureRandom.uuid], + properties: { + status: 'confirmed' + } + } + end + response_empty_200 + response_422 + end + end + + path '/guests' do + get('list guests') do + tags 'Guests' + produces 'application/json' + response(200, 'successful') do + schema type: :array, + items: { + type: :object, + required: %i[id name status group], + properties: { + id: { type: :string, format: :uuid }, + name: { type: :string }, + status: { type: :string, enum: Guest.statuses.keys }, + group: { type: :object, + required: %i[id name], + properties: { + id: { type: :string, format: :uuid }, + name: { type: :string } + } } + } + } + xit + end + end + end + + path '/guests/{id}' do + patch('update guest') do + tags 'Guests' + consumes 'application/json' + produces 'application/json' + parameter name: 'id', in: :path, type: :string, format: :uuid + parameter name: :body, in: :body, schema: { + type: :object, + required: %i[guest], + properties: { + guest: { + type: :object, + required: %i[name], + properties: { + name: { type: :string } + } + } + } + } + + response_empty_200 + response_422 + response_404 + end + end +end diff --git a/spec/swagger_helper.rb b/spec/swagger_helper.rb index d07a97e..7ffd0d2 100644 --- a/spec/swagger_helper.rb +++ b/spec/swagger_helper.rb @@ -3,6 +3,9 @@ # frozen_string_literal: true require 'rails_helper' +require_relative './swagger_response_helper' + +include SwaggerResponseHelper RSpec.configure do |config| # Specify a root folder where Swagger JSON files are generated @@ -19,6 +22,15 @@ RSpec.configure do |config| config.openapi_specs = { 'v1/swagger.yaml' => { openapi: '3.0.1', + components: { + securitySchemes: { + csrfToken: { + type: :apiKey, + in: :header, + name: 'X-CSRF-Token' + } + } + }, info: { title: 'API V1', version: 'v1' diff --git a/spec/swagger_response_helper.rb b/spec/swagger_response_helper.rb new file mode 100644 index 0000000..7db38fa --- /dev/null +++ b/spec/swagger_response_helper.rb @@ -0,0 +1,38 @@ +# Copyright (C) 2024 Manuel Bustillo + +module SwaggerResponseHelper + def response_422 + response(422, 'Validation errors in input parameters') do + produces 'application/json' + error_schema + xit + end + end + + def response_empty_200 + response(200, 'Success') do + produces 'application/json' + schema type: :object + xit + end + end + + def response_404 + response(404, 'Record not found') do + produces 'application/json' + error_schema + xit + end + end + + private + + def error_schema + schema type: :object, + required: %i[message errors], + properties: { + message: { type: :string }, + errors: { type: :array, items: { type: :string } } + } + end +end