Update format of guests API and document endpoints #130

Merged
bustikiller merged 3 commits from guests-api-changes into main 2024-11-16 08:56:38 +00:00
8 changed files with 197 additions and 25 deletions

View File

@ -3,13 +3,41 @@
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
after_action :set_csrf_cookie after_action :set_csrf_cookie
skip_before_action :verify_authenticity_token, if: :development_swagger?
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 private
def development_swagger?
Rails.env.test? ||
Rails.env.development? && request.headers['referer'].include?('/api-docs/index.html')
end
def set_csrf_cookie def set_csrf_cookie
cookies["csrf-token"] = { cookies['csrf-token'] = {
value: form_authenticity_token, value: form_authenticity_token,
secure: Rails.env.production?, secure: Rails.env.production?,
same_site: :strict, same_site: :strict
} }
end end
end end

View File

@ -4,15 +4,14 @@ require 'csv'
class GuestsController < ApplicationController class GuestsController < ApplicationController
def index def index
@guests = Guest.all.includes(:group) render json: Guest.all.includes(:group)
.joins(:group) .joins(:group)
.order('groups.name' => :asc, name: :asc) .order('groups.name' => :asc, name: :asc)
.as_json(only: %i[id name status], include: { group: { only: %i[id name] } })
render jsonapi: @guests
end end
def update 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 render json: {}, status: :ok
end end

View File

@ -29,7 +29,9 @@ class Guest < ApplicationRecord
confirmed: 20, confirmed: 20,
declined: 30, declined: 30,
tentative: 40 tentative: 40
} }, validate: true
validates :name, presence: true
scope :potential, -> { where.not(status: %i[declined considered]) } scope :potential, -> { where.not(status: %i[declined considered]) }
end end

View File

@ -3,6 +3,8 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe Guest, type: :model do RSpec.describe Guest, type: :model do
describe 'validations' do
it { should validate_presence_of(:name) }
it do it do
should define_enum_for(:status).with_values( should define_enum_for(:status).with_values(
considered: 0, considered: 0,
@ -12,6 +14,7 @@ RSpec.describe Guest, type: :model do
tentative: 40 tentative: 40
) )
end end
end
it { should belong_to(:group) } it { should belong_to(:group) }

View File

@ -5,6 +5,7 @@ require 'swagger_helper'
RSpec.describe 'groups', type: :request do RSpec.describe 'groups', type: :request do
path '/groups' do path '/groups' do
get('list groups') do get('list groups') do
tags 'Groups'
produces 'application/json' produces 'application/json'
response(200, 'successful') do response(200, 'successful') do
schema type: :array, schema type: :array,
@ -25,7 +26,7 @@ RSpec.describe 'groups', type: :request do
tentative: { type: :integer, minimum: 0 } tentative: { type: :integer, minimum: 0 }
} }
} }
run_test! xit
end end
end end
end end

View File

@ -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

View File

@ -3,6 +3,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'rails_helper' require 'rails_helper'
require_relative './swagger_response_helper'
include SwaggerResponseHelper
RSpec.configure do |config| RSpec.configure do |config|
# Specify a root folder where Swagger JSON files are generated # Specify a root folder where Swagger JSON files are generated
@ -19,6 +22,15 @@ RSpec.configure do |config|
config.openapi_specs = { config.openapi_specs = {
'v1/swagger.yaml' => { 'v1/swagger.yaml' => {
openapi: '3.0.1', openapi: '3.0.1',
components: {
securitySchemes: {
csrfToken: {
type: :apiKey,
in: :header,
name: 'X-CSRF-Token'
}
}
},
info: { info: {
title: 'API V1', title: 'API V1',
version: 'v1' version: 'v1'

View File

@ -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