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/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/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. 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/config/initializers/rswag_api.rb b/config/initializers/rswag_api.rb new file mode 100644 index 0000000..5c46141 --- /dev/null +++ b/config/initializers/rswag_api.rb @@ -0,0 +1,16 @@ +# Copyright (C) 2024 Manuel Bustillo + +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..9540ca4 --- /dev/null +++ b/config/initializers/rswag_ui.rb @@ -0,0 +1,18 @@ +# Copyright (C) 2024 Manuel Bustillo + +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/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/requests/groups_spec.rb b/spec/requests/groups_spec.rb new file mode 100644 index 0000000..8bf622b --- /dev/null +++ b/spec/requests/groups_spec.rb @@ -0,0 +1,32 @@ +# Copyright (C) 2024 Manuel Bustillo + +require 'swagger_helper' + +RSpec.describe 'groups', type: :request do + path '/groups' do + get('list groups') do + produces 'application/json' + response(200, 'successful') do + 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 + end +end diff --git a/spec/swagger_helper.rb b/spec/swagger_helper.rb new file mode 100644 index 0000000..d07a97e --- /dev/null +++ b/spec/swagger_helper.rb @@ -0,0 +1,45 @@ +# Copyright (C) 2024 Manuel Bustillo + +# 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: '/', + description: 'suitable for testing' + }, + { + url: 'http://libre-wedding-planner.app.localhost/api', + description: 'Suitable for development' + } + ] + } + } + + # 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