From 8a3469447ba4f08695c5beb80d57df9236cafea6 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Fri, 15 Nov 2024 08:45:56 +0100 Subject: [PATCH 1/6] Install rswag gem with default configuration --- Gemfile | 1 + Gemfile.lock | 21 ++++++++++++++++ config/initializers/rswag_api.rb | 14 +++++++++++ config/initializers/rswag_ui.rb | 16 ++++++++++++ config/routes.rb | 2 ++ spec/swagger_helper.rb | 43 ++++++++++++++++++++++++++++++++ 6 files changed, 97 insertions(+) create mode 100644 config/initializers/rswag_api.rb create mode 100644 config/initializers/rswag_ui.rb create mode 100644 spec/swagger_helper.rb diff --git a/Gemfile b/Gemfile index e3f3306..81b24ff 100644 --- a/Gemfile +++ b/Gemfile @@ -28,6 +28,7 @@ group :development, :test do gem 'license_finder' gem 'pry' gem 'rspec-rails', '~> 7.1.0' + gem 'rswag' gem 'shoulda-matchers', '~> 6.0' end diff --git a/Gemfile.lock b/Gemfile.lock index 8b7b155..9667dfe 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -72,6 +72,8 @@ GEM securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) uri (>= 0.13.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) annotaterb (4.13.0) ast (2.4.2) babel-source (5.8.35) @@ -127,6 +129,8 @@ GEM actionview (>= 5.0.0) activesupport (>= 5.0.0) json (2.7.5) + json-schema (5.0.1) + addressable (~> 2.8) jsonapi-deserializable (0.2.0) jsonapi-parser (0.1.1) jsonapi-rails (0.4.1) @@ -195,6 +199,7 @@ GEM method_source (~> 1.0) psych (5.2.0) stringio + public_suffix (6.0.1) puma (6.4.3) nio4r (~> 2.0) raabro (1.4.0) @@ -272,6 +277,21 @@ GEM rspec-mocks (~> 3.13) rspec-support (~> 3.13) rspec-support (3.13.1) + rswag (2.16.0) + rswag-api (= 2.16.0) + rswag-specs (= 2.16.0) + rswag-ui (= 2.16.0) + rswag-api (2.16.0) + activesupport (>= 5.2, < 8.1) + railties (>= 5.2, < 8.1) + rswag-specs (2.16.0) + activesupport (>= 5.2, < 8.1) + json-schema (>= 2.2, < 6.0) + railties (>= 5.2, < 8.1) + rspec-core (>= 2.14) + rswag-ui (2.16.0) + actionpack (>= 5.2, < 8.1) + railties (>= 5.2, < 8.1) rubocop (1.68.0) json (~> 2.3) language_server-protocol (>= 3.17.0) @@ -362,6 +382,7 @@ DEPENDENCIES react-rails redis (>= 4.0.1) rspec-rails (~> 7.1.0) + rswag rubocop rubytree shoulda-matchers (~> 6.0) diff --git a/config/initializers/rswag_api.rb b/config/initializers/rswag_api.rb new file mode 100644 index 0000000..c4462b2 --- /dev/null +++ b/config/initializers/rswag_api.rb @@ -0,0 +1,14 @@ +Rswag::Api.configure do |c| + + # Specify a root folder where Swagger JSON files are located + # This is used by the Swagger middleware to serve requests for API descriptions + # NOTE: If you're using rswag-specs to generate Swagger, you'll need to ensure + # that it's configured to generate files in the same folder + c.openapi_root = Rails.root.to_s + '/swagger' + + # Inject a lambda function to alter the returned Swagger prior to serialization + # The function will have access to the rack env for the current request + # For example, you could leverage this to dynamically assign the "host" property + # + #c.swagger_filter = lambda { |swagger, env| swagger['host'] = env['HTTP_HOST'] } +end diff --git a/config/initializers/rswag_ui.rb b/config/initializers/rswag_ui.rb new file mode 100644 index 0000000..1d6151b --- /dev/null +++ b/config/initializers/rswag_ui.rb @@ -0,0 +1,16 @@ +Rswag::Ui.configure do |c| + + # List the Swagger endpoints that you want to be documented through the + # swagger-ui. The first parameter is the path (absolute or relative to the UI + # host) to the corresponding endpoint and the second is a title that will be + # displayed in the document selector. + # NOTE: If you're using rspec-api to expose Swagger files + # (under openapi_root) as JSON or YAML endpoints, then the list below should + # correspond to the relative paths for those endpoints. + + c.swagger_endpoint '/api-docs/v1/swagger.yaml', 'API V1 Docs' + + # Add Basic Auth in case your API is private + # c.basic_auth_enabled = true + # c.basic_auth_credentials 'username', 'password' +end diff --git a/config/routes.rb b/config/routes.rb index 7e40865..80aa2e0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,8 @@ # Copyright (C) 2024 Manuel Bustillo Rails.application.routes.draw do + mount Rswag::Ui::Engine => '/api-docs' + mount Rswag::Api::Engine => '/api-docs' resources :groups, only: :index resources :guests, only: %i[index update] do post :bulk_update, on: :collection diff --git a/spec/swagger_helper.rb b/spec/swagger_helper.rb new file mode 100644 index 0000000..14c1214 --- /dev/null +++ b/spec/swagger_helper.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.configure do |config| + # Specify a root folder where Swagger JSON files are generated + # NOTE: If you're using the rswag-api to serve API descriptions, you'll need + # to ensure that it's configured to serve Swagger from the same folder + config.openapi_root = Rails.root.join('swagger').to_s + + # Define one or more Swagger documents and provide global metadata for each one + # When you run the 'rswag:specs:swaggerize' rake task, the complete Swagger will + # be generated at the provided relative path under openapi_root + # By default, the operations defined in spec files are added to the first + # document below. You can override this behavior by adding a openapi_spec tag to the + # the root example_group in your specs, e.g. describe '...', openapi_spec: 'v2/swagger.json' + config.openapi_specs = { + 'v1/swagger.yaml' => { + openapi: '3.0.1', + info: { + title: 'API V1', + version: 'v1' + }, + paths: {}, + servers: [ + { + url: 'https://{defaultHost}', + variables: { + defaultHost: { + default: 'www.example.com' + } + } + } + ] + } + } + + # Specify the format of the output Swagger file when running 'rswag:specs:swaggerize'. + # The openapi_specs configuration option has the filename including format in + # the key, this may want to be changed to avoid putting yaml in json files. + # Defaults to json. Accepts ':json' and ':yaml'. + config.openapi_format = :yaml +end From bcbcf9b469f4992928077d4e352d6caa89e39f67 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Fri, 15 Nov 2024 18:28:45 +0100 Subject: [PATCH 2/6] MVP of swagger documentation --- .gitignore | 3 +++ Dockerfile.dev | 2 +- config/initializers/rswag_ui.rb | 2 +- spec/requests/groups_spec.rb | 19 +++++++++++++++++++ spec/swagger_helper.rb | 4 ++-- 5 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 spec/requests/groups_spec.rb diff --git a/.gitignore b/.gitignore index 5fb66c9..13e3051 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ # Ignore master key for decrypting credentials and more. /config/master.key + +# Ignore swagger generated documentation +swagger/v1/swagger.yaml diff --git a/Dockerfile.dev b/Dockerfile.dev index 792a1ce..f911e52 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,7 +1,7 @@ # syntax = docker/dockerfile:1 # Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile -ARG RUBY_VERSION=3.3.5 +ARG RUBY_VERSION=3.3.6 FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base # Rails app lives here diff --git a/config/initializers/rswag_ui.rb b/config/initializers/rswag_ui.rb index 1d6151b..30172aa 100644 --- a/config/initializers/rswag_ui.rb +++ b/config/initializers/rswag_ui.rb @@ -8,7 +8,7 @@ Rswag::Ui.configure do |c| # (under openapi_root) as JSON or YAML endpoints, then the list below should # correspond to the relative paths for those endpoints. - c.swagger_endpoint '/api-docs/v1/swagger.yaml', 'API V1 Docs' + c.swagger_endpoint '/api/api-docs/v1/swagger.yaml', 'API V1 Docs' # Add Basic Auth in case your API is private # c.basic_auth_enabled = true diff --git a/spec/requests/groups_spec.rb b/spec/requests/groups_spec.rb new file mode 100644 index 0000000..4dcfd71 --- /dev/null +++ b/spec/requests/groups_spec.rb @@ -0,0 +1,19 @@ +require 'swagger_helper' + +RSpec.describe 'groups', type: :request do + path '/groups' do + get('list groups') do + response(200, 'successful') do + + after do |example| + example.metadata[:response][:content] = { + 'application/json' => { + example: JSON.parse(response.body, symbolize_names: true) + } + } + end + run_test! + end + end + end +end diff --git a/spec/swagger_helper.rb b/spec/swagger_helper.rb index 14c1214..2cae0bb 100644 --- a/spec/swagger_helper.rb +++ b/spec/swagger_helper.rb @@ -24,10 +24,10 @@ RSpec.configure do |config| paths: {}, servers: [ { - url: 'https://{defaultHost}', + url: 'http://{defaultHost}/api', variables: { defaultHost: { - default: 'www.example.com' + default: 'libre-wedding-planner.app.localhost' } } } From 41cb719bf49b2e9630347a1bdc0296f97f3f9eb5 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Fri, 15 Nov 2024 17:29:56 +0000 Subject: [PATCH 3/6] Add copyright notice --- config/initializers/rswag_api.rb | 2 ++ config/initializers/rswag_ui.rb | 2 ++ spec/requests/groups_spec.rb | 2 ++ spec/swagger_helper.rb | 2 ++ 4 files changed, 8 insertions(+) diff --git a/config/initializers/rswag_api.rb b/config/initializers/rswag_api.rb index c4462b2..5c46141 100644 --- a/config/initializers/rswag_api.rb +++ b/config/initializers/rswag_api.rb @@ -1,3 +1,5 @@ +# Copyright (C) 2024 Manuel Bustillo + Rswag::Api.configure do |c| # Specify a root folder where Swagger JSON files are located diff --git a/config/initializers/rswag_ui.rb b/config/initializers/rswag_ui.rb index 30172aa..9540ca4 100644 --- a/config/initializers/rswag_ui.rb +++ b/config/initializers/rswag_ui.rb @@ -1,3 +1,5 @@ +# Copyright (C) 2024 Manuel Bustillo + Rswag::Ui.configure do |c| # List the Swagger endpoints that you want to be documented through the diff --git a/spec/requests/groups_spec.rb b/spec/requests/groups_spec.rb index 4dcfd71..eb96268 100644 --- a/spec/requests/groups_spec.rb +++ b/spec/requests/groups_spec.rb @@ -1,3 +1,5 @@ +# Copyright (C) 2024 Manuel Bustillo + require 'swagger_helper' RSpec.describe 'groups', type: :request do diff --git a/spec/swagger_helper.rb b/spec/swagger_helper.rb index 2cae0bb..c9479c3 100644 --- a/spec/swagger_helper.rb +++ b/spec/swagger_helper.rb @@ -1,3 +1,5 @@ +# Copyright (C) 2024 Manuel Bustillo + # frozen_string_literal: true require 'rails_helper' From ca0b1b18d3b83a8614a6e78c1976e1cb80b074e6 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Fri, 15 Nov 2024 19:04:22 +0100 Subject: [PATCH 4/6] Use different server URLs for development and testing --- spec/swagger_helper.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/swagger_helper.rb b/spec/swagger_helper.rb index c9479c3..d07a97e 100644 --- a/spec/swagger_helper.rb +++ b/spec/swagger_helper.rb @@ -26,12 +26,12 @@ RSpec.configure do |config| paths: {}, servers: [ { - url: 'http://{defaultHost}/api', - variables: { - defaultHost: { - default: 'libre-wedding-planner.app.localhost' - } - } + url: '/', + description: 'suitable for testing' + }, + { + url: 'http://libre-wedding-planner.app.localhost/api', + description: 'Suitable for development' } ] } From 94b1066c17dc87b70b076ce899bc2e7a18e91e15 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Fri, 15 Nov 2024 19:09:55 +0100 Subject: [PATCH 5/6] Include OpenAPI information in the README --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 4d4f615..49752d5 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,16 @@ Unit tests can be executed with bundle exec rspec ``` +## API documentation + +Generate the OpenAPI documentation with the command: + +``` +rake rswag:specs:swaggerize +``` + +The documentation is available in Swagger UI in http://libre-wedding-planner.app.localhost/api/api-docs/index.html. If testing the API through the UI, you will need to select the second server (which includes the `/api` path), intended for development. + ## Contributing Contributions of all kinds (code, UX/UI, testing, translations, etc.) are welcome. The procedures to contribute are still being defined, but don't hesitate to reach out in case you want to participate. From cc3c8fdd6345c7f390c907049459ed81fee69523 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Fri, 15 Nov 2024 19:42:59 +0100 Subject: [PATCH 6/6] Improve documentation of groups endpoint --- app/queries/groups/summary_query.rb | 2 +- spec/requests/groups_spec.rb | 27 +++++++++++++++++++-------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/app/queries/groups/summary_query.rb b/app/queries/groups/summary_query.rb index 637a508..70fc234 100644 --- a/app/queries/groups/summary_query.rb +++ b/app/queries/groups/summary_query.rb @@ -10,7 +10,7 @@ module Groups def query <<~SQL.squish - SELECT#{' '} + SELECT groups.id, groups.name, groups.icon, diff --git a/spec/requests/groups_spec.rb b/spec/requests/groups_spec.rb index eb96268..8bf622b 100644 --- a/spec/requests/groups_spec.rb +++ b/spec/requests/groups_spec.rb @@ -5,15 +5,26 @@ require 'swagger_helper' RSpec.describe 'groups', type: :request do path '/groups' do get('list groups') do + produces 'application/json' response(200, 'successful') do - - after do |example| - example.metadata[:response][:content] = { - 'application/json' => { - example: JSON.parse(response.body, symbolize_names: true) - } - } - end + schema type: :array, + items: { + type: :object, + required: %i[id name icon parent_id color total considered invited confirmed declined tentative], + properties: { + id: { type: :string, format: :uuid, required: true }, + name: { type: :string }, + icon: { type: :string, example: 'pi pi-crown', description: 'The CSS classes used by the icon' }, + parent_id: { type: :string, format: :uuid }, + color: { type: :string, pattern: '^#(?:[0-9a-fA-F]{3}){1,2}$' }, + total: { type: :integer, minimum: 0, description: 'Total number of guests in any status' }, + considered: { type: :integer, minimum: 0 }, + invited: { type: :integer, minimum: 0 }, + confirmed: { type: :integer, minimum: 0 }, + declined: { type: :integer, minimum: 0 }, + tentative: { type: :integer, minimum: 0 } + } + } run_test! end end