diff --git a/.rubocop.yml b/.rubocop.yml index aaa6befd..2ecdf4ed 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,3 +2,12 @@ require: rubocop-rails Style/Documentation: Enabled: false + +Style/ClassAndModuleChildren: + Enabled: false + +Layout/HashAlignment: + Enabled: false + +Metrics/BlockLength: + Enabled: false diff --git a/Gemfile b/Gemfile index 6f45e370..45a9dee8 100644 --- a/Gemfile +++ b/Gemfile @@ -13,6 +13,8 @@ gem 'pg' gem 'puma' gem 'pundit' gem 'rails' +gem 'rswag-api' +gem 'rswag-ui' gem 'shrine', '~> 3.6' gem 'sidekiq' gem 'sidekiq-cron' @@ -30,6 +32,7 @@ group :development, :test do gem 'pry-byebug' gem 'pry-rails' gem 'rspec-rails' + gem 'rswag-specs' end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index ed9576f1..c6fdc36f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -145,6 +145,8 @@ GEM rdoc (>= 4.0.0) reline (>= 0.4.2) json (2.7.2) + json-schema (4.3.0) + addressable (>= 2.8) language_server-protocol (3.17.0.3) loofah (2.22.0) crass (~> 1.0.2) @@ -277,6 +279,17 @@ GEM rspec-mocks (~> 3.13) rspec-support (~> 3.13) rspec-support (3.13.1) + rswag-api (2.13.0) + activesupport (>= 3.1, < 7.2) + railties (>= 3.1, < 7.2) + rswag-specs (2.13.0) + activesupport (>= 3.1, < 7.2) + json-schema (>= 2.2, < 5.0) + railties (>= 3.1, < 7.2) + rspec-core (>= 2.14) + rswag-ui (2.13.0) + actionpack (>= 3.1, < 7.2) + railties (>= 3.1, < 7.2) rubocop (1.62.1) json (~> 2.3) language_server-protocol (>= 3.17.0) @@ -390,6 +403,9 @@ DEPENDENCIES rails redis rspec-rails + rswag-api + rswag-specs + rswag-ui rubocop-rails shoulda-matchers shrine (~> 3.6) diff --git a/app/controllers/api/v1/points_controller.rb b/app/controllers/api/v1/points_controller.rb index 6ec03339..675cca1a 100644 --- a/app/controllers/api/v1/points_controller.rb +++ b/app/controllers/api/v1/points_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Api::V1::PointsController < ApplicationController skip_forgery_protection diff --git a/app/jobs/owntracks/point_creating_job.rb b/app/jobs/owntracks/point_creating_job.rb index a1324ccf..928d75a0 100644 --- a/app/jobs/owntracks/point_creating_job.rb +++ b/app/jobs/owntracks/point_creating_job.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + class Owntracks::PointCreatingJob < ApplicationJob queue_as :default def perform(point_params) parsed_params = OwnTracks::Params.new(point_params).call - point = Point.create(parsed_params) + Point.create(parsed_params) end end diff --git a/config/initializers/rswag_api.rb b/config/initializers/rswag_api.rb new file mode 100644 index 00000000..c4462b27 --- /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 00000000..1d6151b6 --- /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 b8276184..f516cda4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true Rails.application.routes.draw do + mount Rswag::Api::Engine => '/api-docs' + mount Rswag::Ui::Engine => '/api-docs' get 'settings/theme', to: 'settings#theme' get 'export', to: 'export#index' get 'export/download', to: 'export#download' diff --git a/lib/tasks/rswag.rake b/lib/tasks/rswag.rake new file mode 100644 index 00000000..58d45b23 --- /dev/null +++ b/lib/tasks/rswag.rake @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +namespace :rswag do + desc 'Generate Swagger docs' + task generate: [:environment] do + system 'bundle exec rake rswag:specs:swaggerize PATTERN="spec/swagger/**/*_spec.rb"' + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 71d1f67e..fb73717f 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -6,6 +6,11 @@ require_relative '../config/environment' abort('The Rails environment is running in production mode!') if Rails.env.production? require 'rspec/rails' +require 'rswag/specs' + +require 'rake' + +Rails.application.load_tasks # Add additional requires below this line. Rails is not loaded until this point! Dir[Rails.root.join('spec/support/**/*.rb')].sort.each { |f| require f } @@ -27,9 +32,15 @@ RSpec.configure do |config| config.include FactoryBot::Syntax::Methods config.include Devise::Test::IntegrationHelpers, type: :request + config.rswag_dry_run = false + config.before do ActiveJob::Base.queue_adapter = :test end + + config.after(:suite) do + Rake::Task['rswag:generate'].invoke + end end Shoulda::Matchers.configure do |config| diff --git a/spec/requests/api/v1/points_spec.rb b/spec/requests/api/v1/points_spec.rb index 91a24dea..cd4c6d72 100644 --- a/spec/requests/api/v1/points_spec.rb +++ b/spec/requests/api/v1/points_spec.rb @@ -1,22 +1,24 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe "Api::V1::Points", type: :request do - describe "POST /api/v1/points" do +RSpec.describe 'Api::V1::Points', type: :request do + describe 'POST /api/v1/points' do context 'with valid params' do let(:params) do - { lat: 1.0, lon: 1.0, tid: 'test', tst: Time.now.to_i, topic: 'iPhone 12 pro' } + { lat: 1.0, lon: 1.0, tid: 'test', tst: Time.current.to_i, topic: 'iPhone 12 pro' } end - it "returns http success" do + it 'returns http success' do post api_v1_points_path, params: params expect(response).to have_http_status(:success) end it 'enqueues a job' do - expect { + expect do post api_v1_points_path, params: params - }.to have_enqueued_job(Owntracks::PointCreatingJob) + end.to have_enqueued_job(Owntracks::PointCreatingJob) end end end diff --git a/spec/swagger/api/v1/points_controller_spec.rb b/spec/swagger/api/v1/points_controller_spec.rb new file mode 100644 index 00000000..74f3d504 --- /dev/null +++ b/spec/swagger/api/v1/points_controller_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'swagger_helper' + +describe 'Points API', type: :request do + path '/api/v1/points' do + post 'Creates a point' do + request_body_example value: { + lat: 52.502397, + lon: 13.356718, + tid: 'Swagger', + tst: Time.current.to_i + } + tags 'Points' + consumes 'application/json' + parameter name: :point, in: :body, schema: { + type: :object, + properties: { + acc: { type: :number }, + alt: { type: :number }, + batt: { type: :number }, + bs: { type: :number }, + cog: { type: :number }, + lat: { type: :string, format: :decimal }, + lon: { type: :string, format: :decimal }, + rad: { type: :number }, + t: { type: :string }, + tid: { type: :string }, + tst: { type: :number }, + vac: { type: :number }, + vel: { type: :number }, + p: { type: :string, format: :decimal }, + poi: { type: :string }, + conn: { type: :string }, + tag: { type: :string }, + topic: { type: :string }, + inregions: { type: :array }, + SSID: { type: :string }, + BSSID: { type: :string }, + created_at: { type: :string }, + inrids: { type: :array }, + m: { type: :number } + }, + required: %w[lat lon tid tst] + } + + response '200', 'point created' do + let(:point) { { lat: 1.0, lon: 2.0, tid: 3, tst: 4 } } + + run_test! + end + + response '200', 'invalid request' do + let(:point) { { lat: 1.0 } } + + run_test! + end + end + end +end diff --git a/spec/swagger_helper.rb b/spec/swagger_helper.rb new file mode 100644 index 00000000..aa67e751 --- /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: 'http://{defaultHost}', + variables: { + defaultHost: { + default: 'localhost:3000' + } + } + } + ] + } + } + + # 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 diff --git a/swagger/v1/swagger.yaml b/swagger/v1/swagger.yaml new file mode 100644 index 00000000..8a5f9804 --- /dev/null +++ b/swagger/v1/swagger.yaml @@ -0,0 +1,90 @@ +--- +openapi: 3.0.1 +info: + title: API V1 + version: v1 +paths: + "/api/v1/points": + post: + summary: Creates a point + tags: + - Points + parameters: [] + responses: + '200': + description: invalid request + requestBody: + content: + application/json: + schema: + type: object + properties: + acc: + type: number + alt: + type: number + batt: + type: number + bs: + type: number + cog: + type: number + lat: + type: string + format: decimal + lon: + type: string + format: decimal + rad: + type: number + t: + type: string + tid: + type: string + tst: + type: number + vac: + type: number + vel: + type: number + p: + type: string + format: decimal + poi: + type: string + conn: + type: string + tag: + type: string + topic: + type: string + inregions: + type: array + SSID: + type: string + BSSID: + type: string + created_at: + type: string + inrids: + type: array + m: + type: number + required: + - lat + - lon + - tid + - tst + examples: + '0': + summary: Creates a point + value: + lat: 52.502397 + lon: 13.356718 + tid: Swagger + tst: 1716032145 +servers: +- url: http://{defaultHost} + variables: + defaultHost: + default: localhost:3000