power_api 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +9 -0
- data/.hound.yml +4 -0
- data/.rspec +3 -0
- data/.rubocop.yml +479 -0
- data/.ruby-version +1 -0
- data/.travis.yml +15 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +310 -0
- data/Guardfile +15 -0
- data/LICENSE.txt +21 -0
- data/README.md +904 -0
- data/Rakefile +10 -0
- data/app/assets/config/power_api_manifest.js +2 -0
- data/app/assets/images/power_api/.keep +0 -0
- data/app/assets/javascripts/power_api/application.js +13 -0
- data/app/assets/stylesheets/power_api/application.css +15 -0
- data/app/controllers/concerns/api/deprecated.rb +23 -0
- data/app/controllers/concerns/api/error.rb +37 -0
- data/app/controllers/concerns/api/filtered.rb +15 -0
- data/app/controllers/concerns/api/versioned.rb +36 -0
- data/app/controllers/power_api/base_controller.rb +14 -0
- data/app/helpers/power_api/application_helper.rb +4 -0
- data/app/jobs/power_api/application_job.rb +4 -0
- data/app/mailers/power_api/application_mailer.rb +6 -0
- data/app/models/power_api/application_record.rb +5 -0
- data/app/responders/api_responder.rb +13 -0
- data/app/views/layouts/power_api/application.html.erb +14 -0
- data/bin/rails +14 -0
- data/config/routes.rb +2 -0
- data/lib/generators/power_api/controller/USAGE +5 -0
- data/lib/generators/power_api/controller/controller_generator.rb +152 -0
- data/lib/generators/power_api/install/USAGE +5 -0
- data/lib/generators/power_api/install/install_generator.rb +67 -0
- data/lib/generators/power_api/version/USAGE +5 -0
- data/lib/generators/power_api/version/version_generator.rb +54 -0
- data/lib/power_api.rb +35 -0
- data/lib/power_api/engine.rb +28 -0
- data/lib/power_api/errors.rb +4 -0
- data/lib/power_api/generator_helper/active_record_resource.rb +192 -0
- data/lib/power_api/generator_helper/ams_helper.rb +43 -0
- data/lib/power_api/generator_helper/controller_helper.rb +184 -0
- data/lib/power_api/generator_helper/pagination_helper.rb +48 -0
- data/lib/power_api/generator_helper/resource_helper.rb +33 -0
- data/lib/power_api/generator_helper/routes_helper.rb +91 -0
- data/lib/power_api/generator_helper/rubocop_helper.rb +11 -0
- data/lib/power_api/generator_helper/simple_token_auth_helper.rb +124 -0
- data/lib/power_api/generator_helper/swagger_helper.rb +463 -0
- data/lib/power_api/generator_helper/template_builder_helper.rb +23 -0
- data/lib/power_api/generator_helper/version_helper.rb +16 -0
- data/lib/power_api/generator_helpers.rb +25 -0
- data/lib/power_api/version.rb +3 -0
- data/lib/tasks/power_api_tasks.rake +4 -0
- data/power_api.gemspec +44 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/config/manifest.js +5 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/javascripts/cable.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
- data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/concerns/api/deprecated_spec.rb +37 -0
- data/spec/dummy/app/controllers/concerns/api/error_spec.rb +97 -0
- data/spec/dummy/app/controllers/concerns/api/filtered_spec.rb +42 -0
- data/spec/dummy/app/controllers/concerns/api/versioned_spec.rb +64 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/jobs/application_job.rb +2 -0
- data/spec/dummy/app/mailers/application_mailer.rb +4 -0
- data/spec/dummy/app/models/application_record.rb +3 -0
- data/spec/dummy/app/models/blog.rb +5 -0
- data/spec/dummy/app/models/portfolio.rb +5 -0
- data/spec/dummy/app/models/user.rb +3 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +38 -0
- data/spec/dummy/bin/update +29 -0
- data/spec/dummy/bin/yarn +11 -0
- data/spec/dummy/config.ru +5 -0
- data/spec/dummy/config/application.rb +26 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/cable.yml +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +54 -0
- data/spec/dummy/config/environments/production.rb +91 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
- data/spec/dummy/config/initializers/assets.rb +14 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +33 -0
- data/spec/dummy/config/puma.rb +56 -0
- data/spec/dummy/config/routes.rb +12 -0
- data/spec/dummy/config/secrets.yml +32 -0
- data/spec/dummy/config/spring.rb +6 -0
- data/spec/dummy/db/migrate/20190322205209_create_blogs.rb +10 -0
- data/spec/dummy/db/migrate/20200215225917_create_users.rb +9 -0
- data/spec/dummy/db/migrate/20200227150449_create_portfolios.rb +10 -0
- data/spec/dummy/db/migrate/20200227150548_add_portfolio_id_blogs.rb +5 -0
- data/spec/dummy/db/schema.rb +38 -0
- data/spec/dummy/package.json +5 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/dummy/public/apple-touch-icon.png +0 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/spec/assets/image.png +0 -0
- data/spec/dummy/spec/assets/video.mp4 +0 -0
- data/spec/dummy/spec/factories/blogs.rb +7 -0
- data/spec/dummy/spec/factories/portfolios.rb +6 -0
- data/spec/dummy/spec/factories/users.rb +5 -0
- data/spec/dummy/spec/lib/power_api/generator_helper/ams_helper_spec.rb +87 -0
- data/spec/dummy/spec/lib/power_api/generator_helper/controller_helper_spec.rb +361 -0
- data/spec/dummy/spec/lib/power_api/generator_helper/pagination_helper_spec.rb +56 -0
- data/spec/dummy/spec/lib/power_api/generator_helper/resource_helper_spec.rb +31 -0
- data/spec/dummy/spec/lib/power_api/generator_helper/routes_helper_spec.rb +179 -0
- data/spec/dummy/spec/lib/power_api/generator_helper/simple_token_auth_helper_spec.rb +164 -0
- data/spec/dummy/spec/lib/power_api/generator_helper/swagger_helper_spec.rb +451 -0
- data/spec/dummy/spec/lib/power_api/generator_helper/version_helper_spec.rb +55 -0
- data/spec/dummy/spec/support/shared_examples/active_record_resource.rb +101 -0
- data/spec/dummy/spec/support/shared_examples/active_record_resource_atrributes.rb +164 -0
- data/spec/dummy/spec/support/test_generator_helpers.rb +29 -0
- data/spec/dummy/spec/support/test_helpers.rb +11 -0
- data/spec/rails_helper.rb +49 -0
- data/spec/spec_helper.rb +9 -0
- metadata +602 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module PowerApi::GeneratorHelper::PaginationHelper
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
attr_accessor :use_paginator
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def api_pagination_initializer_path
|
|
9
|
+
"config/initializers/api_pagination.rb"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def api_pagination_initializer_tpl
|
|
13
|
+
<<~API_PAGINATION
|
|
14
|
+
ApiPagination.configure do |config|
|
|
15
|
+
# If you have more than one gem included, you can choose a paginator.
|
|
16
|
+
config.paginator = :kaminari
|
|
17
|
+
|
|
18
|
+
# By default, this is set to 'Total'
|
|
19
|
+
config.total_header = 'X-Total'
|
|
20
|
+
|
|
21
|
+
# By default, this is set to 'Per-Page'
|
|
22
|
+
config.per_page_header = 'X-Per-Page'
|
|
23
|
+
|
|
24
|
+
# Optional: set this to add a header with the current page number.
|
|
25
|
+
config.page_header = 'X-Page'
|
|
26
|
+
|
|
27
|
+
# Optional: set this to add other response format. Useful with tools that define :jsonapi format
|
|
28
|
+
# config.response_formats = [:json, :xml, :jsonapi]
|
|
29
|
+
config.response_formats = [:jsonapi]
|
|
30
|
+
|
|
31
|
+
# Optional: what parameter should be used to set the page option
|
|
32
|
+
config.page_param do |params|
|
|
33
|
+
params[:page][:number] if params[:page].is_a?(ActionController::Parameters)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Optional: what parameter should be used to set the per page option
|
|
37
|
+
config.per_page_param do |params|
|
|
38
|
+
params[:page][:size] if params[:page].is_a?(ActionController::Parameters)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Optional: Include the total and last_page link header
|
|
42
|
+
# By default, this is set to true
|
|
43
|
+
# Note: When using kaminari, this prevents the count call to the database
|
|
44
|
+
config.include_total = true
|
|
45
|
+
end
|
|
46
|
+
API_PAGINATION
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module PowerApi::GeneratorHelper::ResourceHelper
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
class Resource
|
|
5
|
+
include PowerApi::GeneratorHelper::ActiveRecordResource
|
|
6
|
+
|
|
7
|
+
def initialize(resource_name)
|
|
8
|
+
self.resource_name = resource_name
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
included do
|
|
13
|
+
attr_reader :resource, :parent_resource
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def resource=(value)
|
|
17
|
+
@resource = Resource.new(value)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def parent_resource=(value)
|
|
21
|
+
return if value.blank?
|
|
22
|
+
|
|
23
|
+
@parent_resource = Resource.new(value)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def resource_attributes=(collection)
|
|
27
|
+
resource.resource_attributes = collection
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def parent_resource?
|
|
31
|
+
!!parent_resource
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# rubocop:disable Layout/AlignArguments
|
|
2
|
+
module PowerApi::GeneratorHelper::RoutesHelper
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
include PowerApi::GeneratorHelper::VersionHelper
|
|
7
|
+
include PowerApi::GeneratorHelper::ResourceHelper
|
|
8
|
+
include PowerApi::GeneratorHelper::TemplateBuilderHelper
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def routes_path
|
|
12
|
+
"config/routes.rb"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def routes_line_to_inject_new_version
|
|
16
|
+
return "routes.draw do\n" if first_version?
|
|
17
|
+
|
|
18
|
+
"'/api' do\n"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def api_version_routes_line_regex
|
|
22
|
+
/Api::V#{version_number}[^\n]*/
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def parent_resource_routes_line_regex
|
|
26
|
+
/#{parent_resource_route_tpl}[^\n]*/
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def version_route_tpl
|
|
30
|
+
return first_version_route_tpl if first_version?
|
|
31
|
+
|
|
32
|
+
new_version_route_tpl
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def resource_route_tpl(actions: [], is_parent: false)
|
|
36
|
+
res = (is_parent ? parent_resource : resource).plural
|
|
37
|
+
line = "resources :#{res}"
|
|
38
|
+
line += ", only: [#{actions.map { |a| ":#{a}" }.join(', ')}]" if actions.any?
|
|
39
|
+
line
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def parent_route_exist?
|
|
43
|
+
routes_match_regex?(/#{parent_resource_route_tpl}/)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def parent_route_already_have_children?
|
|
47
|
+
routes_match_regex?(/#{parent_resource_route_tpl}[\W\w]*do/)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def resource_route_statements(actions: [])
|
|
53
|
+
line = "resources :#{resource.plural}"
|
|
54
|
+
line += ", only: [#{actions.map { |a| ":#{a}" }.join(', ')}]" if actions.any?
|
|
55
|
+
line
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def routes_match_regex?(regex)
|
|
59
|
+
path = File.join(Rails.root, routes_path)
|
|
60
|
+
File.readlines(path).grep(regex).any?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def parent_resource_route_tpl
|
|
64
|
+
raise PowerApi::GeneratorError.new("missing parent_resource") unless parent_resource?
|
|
65
|
+
|
|
66
|
+
"resources :#{parent_resource.plural}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def first_version_route_tpl
|
|
70
|
+
concat_tpl_statements(
|
|
71
|
+
"scope path: '/api' do",
|
|
72
|
+
"api_version(#{api_version_params}) do",
|
|
73
|
+
"end",
|
|
74
|
+
"end\n"
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def new_version_route_tpl
|
|
79
|
+
concat_tpl_statements(
|
|
80
|
+
"api_version(#{api_version_params}) do",
|
|
81
|
+
"end"
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def api_version_params
|
|
86
|
+
"module: 'Api::V#{version_number}', \
|
|
87
|
+
path: { value: 'v#{version_number}' }, \
|
|
88
|
+
defaults: { format: 'json' }"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
# rubocop:enable Layout/AlignArguments
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module PowerApi::GeneratorHelper::RubocopHelper
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
def format_ruby_file(path)
|
|
5
|
+
return unless File.exist?(path)
|
|
6
|
+
|
|
7
|
+
options, paths = RuboCop::Options.new.parse(["-a", "-fa", path])
|
|
8
|
+
runner = RuboCop::Runner.new(options, RuboCop::ConfigStore.new)
|
|
9
|
+
runner.run(paths)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
module PowerApi::GeneratorHelper::SimpleTokenAuthHelper
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
class SimpleTokenAuthResource
|
|
5
|
+
include PowerApi::GeneratorHelper::ActiveRecordResource
|
|
6
|
+
include PowerApi::GeneratorHelper::ResourceHelper
|
|
7
|
+
|
|
8
|
+
def initialize(resource)
|
|
9
|
+
self.resource_name = resource
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def authenticated_resource_migration
|
|
13
|
+
"migration add_authentication_token_to_#{plural} \
|
|
14
|
+
authentication_token:string{30}:uniq"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
included do
|
|
19
|
+
attr_reader :authenticated_resources, :authenticated_resource
|
|
20
|
+
attr_accessor :owned_by_authenticated_resource
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def authenticated_resources=(values)
|
|
24
|
+
@authenticated_resources = values.map { |value| SimpleTokenAuthResource.new(value) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def authenticated_resource=(value)
|
|
28
|
+
return if value.blank?
|
|
29
|
+
|
|
30
|
+
@authenticated_resource = SimpleTokenAuthResource.new(value)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def authenticated_resource?
|
|
34
|
+
!!authenticated_resource
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def owned_by_authenticated_resource?
|
|
38
|
+
owned_by_authenticated_resource && authenticated_resource? && !parent_resource?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def current_authenticated_resource
|
|
42
|
+
"current_#{authenticated_resource.snake_case}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def simple_token_auth_method
|
|
46
|
+
<<-METHOD
|
|
47
|
+
acts_as_token_authenticatable
|
|
48
|
+
|
|
49
|
+
METHOD
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def simple_token_auth_initializer_path
|
|
53
|
+
"config/initializers/simple_token_authentication.rb"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def simple_token_auth_initializer_tpl
|
|
57
|
+
<<~INITIALIZER
|
|
58
|
+
SimpleTokenAuthentication.configure do |config|
|
|
59
|
+
# Configure the session persistence policy after a successful sign in,
|
|
60
|
+
# in other words, if the authentication token acts as a signin token.
|
|
61
|
+
# If true, user is stored in the session and the authentication token and
|
|
62
|
+
# email may be provided only once.
|
|
63
|
+
# If false, users must provide their authentication token and email at every request.
|
|
64
|
+
# config.sign_in_token = false
|
|
65
|
+
|
|
66
|
+
# Configure the name of the HTTP headers watched for authentication.
|
|
67
|
+
#
|
|
68
|
+
# Default header names for a given token authenticatable entity follow the pattern:
|
|
69
|
+
# { entity: { authentication_token: 'X-Entity-Token', email: 'X-Entity-Email'} }
|
|
70
|
+
#
|
|
71
|
+
# When several token authenticatable models are defined, custom header names
|
|
72
|
+
# can be specified for none, any, or all of them.
|
|
73
|
+
#
|
|
74
|
+
# Note: when using the identifiers options, this option behaviour is modified.
|
|
75
|
+
# Please see the example below.
|
|
76
|
+
#
|
|
77
|
+
# Examples
|
|
78
|
+
#
|
|
79
|
+
# Given User and SuperAdmin are token authenticatable,
|
|
80
|
+
# When the following configuration is used:
|
|
81
|
+
# `config.header_names = { super_admin: { authentication_token: 'X-Admin-Auth-Token' } }`
|
|
82
|
+
# Then the token authentification handler for User watches the following headers:
|
|
83
|
+
# `X-User-Token, X-User-Email`
|
|
84
|
+
# And the token authentification handler for SuperAdmin watches the following headers:
|
|
85
|
+
# `X-Admin-Auth-Token, X-SuperAdmin-Email`
|
|
86
|
+
#
|
|
87
|
+
# When the identifiers option is set:
|
|
88
|
+
# `config.identifiers = { super_admin: :phone_number }`
|
|
89
|
+
# Then both the header names identifier key and default value are modified accordingly:
|
|
90
|
+
# `config.header_names = { super_admin: { phone_number: 'X-SuperAdmin-PhoneNumber' } }`
|
|
91
|
+
#
|
|
92
|
+
# config.header_names = { user: { authentication_token: 'X-User-Token', email: 'X-User-Email' } }
|
|
93
|
+
|
|
94
|
+
# Configure the name of the attribute used to identify the user for authentication.
|
|
95
|
+
# That attribute must exist in your model.
|
|
96
|
+
#
|
|
97
|
+
# The default identifiers follow the pattern:
|
|
98
|
+
# { entity: 'email' }
|
|
99
|
+
#
|
|
100
|
+
# Note: the identifer must match your Devise configuration,
|
|
101
|
+
# see https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-sign-in-using-their-username-or-email-address#tell-devise-to-use-username-in-the-authentication_keys
|
|
102
|
+
#
|
|
103
|
+
# Note: setting this option does modify the header_names behaviour,
|
|
104
|
+
# see the header_names section above.
|
|
105
|
+
#
|
|
106
|
+
# Example:
|
|
107
|
+
#
|
|
108
|
+
# `config.identifiers = { super_admin: 'phone_number', user: 'uuid' }`
|
|
109
|
+
#
|
|
110
|
+
# config.identifiers = { user: 'email' }
|
|
111
|
+
|
|
112
|
+
# Configure the Devise trackable strategy integration.
|
|
113
|
+
#
|
|
114
|
+
# If true, tracking is disabled for token authentication: signing in through
|
|
115
|
+
# token authentication won't modify the Devise trackable statistics.
|
|
116
|
+
#
|
|
117
|
+
# If false, given Devise trackable is configured for the relevant model,
|
|
118
|
+
# then signing in through token authentication will be tracked as any other sign in.
|
|
119
|
+
#
|
|
120
|
+
# config.skip_devise_trackable = true
|
|
121
|
+
end
|
|
122
|
+
INITIALIZER
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
# rubocop:disable Metrics/ModuleLength
|
|
2
|
+
# rubocop:disable Metrics/MethodLength
|
|
3
|
+
# rubocop:disable Layout/AlignArguments
|
|
4
|
+
module PowerApi::GeneratorHelper::SwaggerHelper
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
include PowerApi::GeneratorHelper::VersionHelper
|
|
9
|
+
include PowerApi::GeneratorHelper::ResourceHelper
|
|
10
|
+
include PowerApi::GeneratorHelper::SimpleTokenAuthHelper
|
|
11
|
+
include PowerApi::GeneratorHelper::TemplateBuilderHelper
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def swagger_helper_path
|
|
15
|
+
"spec/swagger_helper.rb"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def spec_swagger_path
|
|
19
|
+
"spec/swagger/.gitkeep"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def spec_integration_path
|
|
23
|
+
"spec/integration/.gitkeep"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def rswag_ui_initializer_path
|
|
27
|
+
"config/initializers/rswag-ui.rb"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def swagger_schemas_path
|
|
31
|
+
"spec/swagger/v#{version_number}/schemas/.gitkeep"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def swagger_resource_spec_path
|
|
35
|
+
"spec/integration/api/v#{version_number}/#{resource.plural}_spec.rb"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def swagger_version_definition_path
|
|
39
|
+
"spec/swagger/v#{version_number}/definition.rb"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def swagger_resource_schema_path
|
|
43
|
+
"spec/swagger/v#{version_number}/schemas/#{resource.snake_case}_schema.rb"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def rswag_ui_configure_line
|
|
47
|
+
"Rswag::Ui.configure do |c|\n"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def swagger_helper_api_definition_line
|
|
51
|
+
"config.swagger_docs = {\n"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def swagger_definition_line_to_inject_schema
|
|
55
|
+
/definitions: {/
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def rswag_ui_initializer_tpl
|
|
59
|
+
<<~INITIALIZER
|
|
60
|
+
Rswag::Ui.configure do |c|
|
|
61
|
+
end
|
|
62
|
+
INITIALIZER
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def swagger_helper_tpl
|
|
66
|
+
<<~SWAGGER
|
|
67
|
+
require 'rails_helper'
|
|
68
|
+
|
|
69
|
+
Dir[::Rails.root.join("spec/swagger/**/schemas/*.rb")].each { |f| require f }
|
|
70
|
+
Dir[::Rails.root.join("spec/swagger/**/definition.rb")].each { |f| require f }
|
|
71
|
+
|
|
72
|
+
RSpec.configure do |config|
|
|
73
|
+
# Specify a root folder where Swagger JSON files are generated
|
|
74
|
+
# NOTE: If you're using the rswag-api to serve API descriptions, you'll need
|
|
75
|
+
# to ensure that it's confiugred to serve Swagger from the same folder
|
|
76
|
+
config.swagger_root = Rails.root.to_s + '/swagger'
|
|
77
|
+
|
|
78
|
+
# Define one or more Swagger documents and provide global metadata for each one
|
|
79
|
+
# When you run the 'rswag:specs:to_swagger' rake task, the complete Swagger will
|
|
80
|
+
# be generated at the provided relative path under swagger_root
|
|
81
|
+
# By default, the operations defined in spec files are added to the first
|
|
82
|
+
# document below. You can override this behavior by adding a swagger_doc tag to the
|
|
83
|
+
# the root example_group in your specs, e.g. describe '...', swagger_doc: 'v2/swagger.json'
|
|
84
|
+
config.swagger_docs = {
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
SWAGGER
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def rswag_ui_swagger_endpoint
|
|
91
|
+
" c.swagger_endpoint '/api-docs/v#{version_number}/swagger.json', \
|
|
92
|
+
'API V#{version_number} Docs'\n"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def swagger_helper_api_definition
|
|
96
|
+
content = " 'v#{version_number}/swagger.json' => API_V#{version_number}"
|
|
97
|
+
content = "#{content}," unless first_version?
|
|
98
|
+
"#{content}\n"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def swagger_definition_tpl
|
|
102
|
+
<<~DEFINITION
|
|
103
|
+
API_V#{version_number} = {
|
|
104
|
+
swagger: '2.0',
|
|
105
|
+
info: {
|
|
106
|
+
title: 'API V#{version_number}',
|
|
107
|
+
version: 'v#{version_number}'
|
|
108
|
+
},
|
|
109
|
+
basePath: '/api/v#{version_number}',
|
|
110
|
+
definitions: {
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
DEFINITION
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def swagger_schema_tpl
|
|
117
|
+
<<~SCHEMA
|
|
118
|
+
#{swagger_model_definition_const} = {
|
|
119
|
+
type: :object,
|
|
120
|
+
properties: {
|
|
121
|
+
id: { type: :string, example: '1' },
|
|
122
|
+
type: { type: :string, example: '#{resource.snake_case}' },
|
|
123
|
+
attributes: {
|
|
124
|
+
type: :object,
|
|
125
|
+
properties: {#{get_swagger_schema_attributes_definitions}
|
|
126
|
+
},
|
|
127
|
+
required: [#{get_swagger_schema_attributes_names}
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
required: [
|
|
132
|
+
:id,
|
|
133
|
+
:type,
|
|
134
|
+
:attributes
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#{swagger_collection_definition_const} = {
|
|
139
|
+
type: "object",
|
|
140
|
+
properties: {
|
|
141
|
+
data: {
|
|
142
|
+
type: "array",
|
|
143
|
+
items: { "$ref" => "#/definitions/#{resource.snake_case}" }
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
required: [
|
|
147
|
+
:data
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#{swagger_resource_definition_const} = {
|
|
152
|
+
type: "object",
|
|
153
|
+
properties: {
|
|
154
|
+
data: { "$ref" => "#/definitions/#{resource.snake_case}" }
|
|
155
|
+
},
|
|
156
|
+
required: [
|
|
157
|
+
:data
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
SCHEMA
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def swagger_definition_entry
|
|
164
|
+
[
|
|
165
|
+
"\n #{resource.snake_case}: #{swagger_model_definition_const},",
|
|
166
|
+
"\n #{resource.plural}_collection: #{swagger_collection_definition_const},",
|
|
167
|
+
"\n #{resource.snake_case}_resource: #{swagger_resource_definition_const},"
|
|
168
|
+
].join
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def swagger_resource_spec_tpl
|
|
172
|
+
concat_tpl_statements(
|
|
173
|
+
"require 'swagger_helper'\n",
|
|
174
|
+
concat_tpl_statements(
|
|
175
|
+
spec_tpl_initial_describe_line,
|
|
176
|
+
spec_tpl_authenticated_resource,
|
|
177
|
+
spec_tpl_let_parent_resource,
|
|
178
|
+
spec_tpl_collection_path_statements,
|
|
179
|
+
spec_tpl_resource_path_statements,
|
|
180
|
+
"end\n"
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
private
|
|
186
|
+
|
|
187
|
+
def spec_tpl_initial_describe_line
|
|
188
|
+
"describe 'API V#{version_number} #{resource.plural_titleized}', \
|
|
189
|
+
swagger_doc: 'v#{version_number}/swagger.json' do"
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def spec_tpl_authenticated_resource
|
|
193
|
+
return unless authenticated_resource?
|
|
194
|
+
|
|
195
|
+
res_name = authenticated_resource.snake_case
|
|
196
|
+
concat_tpl_statements(
|
|
197
|
+
"let(:#{res_name}) { create(:#{res_name}) }",
|
|
198
|
+
"let(:#{res_name}_email) { #{res_name}.email }",
|
|
199
|
+
"let(:#{res_name}_token) { #{res_name}.authentication_token }\n"
|
|
200
|
+
)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def spec_tpl_collection_path_statements
|
|
204
|
+
concat_tpl_statements(
|
|
205
|
+
"path '/#{spec_tpl_collection_path}' do",
|
|
206
|
+
spec_tpl_parent_resource_parameter,
|
|
207
|
+
spec_tpl_authenticated_resource_params,
|
|
208
|
+
spec_tpl_index,
|
|
209
|
+
spec_tpl_create,
|
|
210
|
+
"end\n"
|
|
211
|
+
)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def spec_tpl_collection_path
|
|
215
|
+
return resource.plural unless parent_resource?
|
|
216
|
+
|
|
217
|
+
"#{parent_resource.plural}/{#{parent_resource.id}}/#{resource.plural}"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def spec_tpl_resource_path_statements
|
|
221
|
+
concat_tpl_statements(
|
|
222
|
+
"path '/#{resource.plural}/{id}' do",
|
|
223
|
+
spec_tpl_authenticated_resource_params,
|
|
224
|
+
spec_tpl_let_existent_resource,
|
|
225
|
+
spec_tpl_show,
|
|
226
|
+
spec_tpl_update,
|
|
227
|
+
spec_tpl_destroy,
|
|
228
|
+
"end\n"
|
|
229
|
+
)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def spec_tpl_let_existent_resource
|
|
233
|
+
statement = ["let(:existent_#{resource.snake_case}) { create(:#{resource.snake_case}"]
|
|
234
|
+
load_owner_resource_option(statement)
|
|
235
|
+
|
|
236
|
+
concat_tpl_statements(
|
|
237
|
+
"parameter name: :id, in: :path, type: :integer\n",
|
|
238
|
+
"#{statement.join(', ')}) }",
|
|
239
|
+
"let(:id) { existent_#{resource.snake_case}.id }\n"
|
|
240
|
+
)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def spec_tpl_authenticated_resource_params
|
|
244
|
+
return unless authenticated_resource?
|
|
245
|
+
|
|
246
|
+
res_name = authenticated_resource.snake_case
|
|
247
|
+
concat_tpl_statements(
|
|
248
|
+
"parameter name: :#{res_name}_email, in: :query, type: :string",
|
|
249
|
+
"parameter name: :#{res_name}_token, in: :query, type: :string\n"
|
|
250
|
+
)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def spec_tpl_index
|
|
254
|
+
concat_tpl_statements(
|
|
255
|
+
"get 'Retrieves #{resource.plural_titleized}' do",
|
|
256
|
+
"description 'Retrieves all the #{resource.plural}'",
|
|
257
|
+
"produces 'application/json'\n",
|
|
258
|
+
"let(:collection_count) { 5 }",
|
|
259
|
+
"let(:expected_collection_count) { collection_count }\n",
|
|
260
|
+
"before { #{spec_tpl_index_creation_list} }",
|
|
261
|
+
"response '200', '#{resource.plural_titleized} retrieved' do",
|
|
262
|
+
"schema('$ref' => '#/definitions/#{resource.plural}_collection')\n",
|
|
263
|
+
"run_test! do |response|",
|
|
264
|
+
"expect(JSON.parse(response.body)['data'].count).to eq(expected_collection_count)",
|
|
265
|
+
"end",
|
|
266
|
+
"end\n",
|
|
267
|
+
spec_tpl_invalid_credentials,
|
|
268
|
+
"end\n"
|
|
269
|
+
)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def spec_tpl_index_creation_list
|
|
273
|
+
statement = ["create_list(:#{resource.snake_case}, collection_count"]
|
|
274
|
+
load_owner_resource_option(statement)
|
|
275
|
+
statement.join(', ') + ')'
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def load_owner_resource_option(statement)
|
|
279
|
+
if parent_resource?
|
|
280
|
+
statement << "#{parent_resource.snake_case}: #{parent_resource.snake_case}"
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
if owned_by_authenticated_resource?
|
|
284
|
+
statement << "#{authenticated_resource.snake_case}: #{authenticated_resource.snake_case}"
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def spec_tpl_create
|
|
289
|
+
concat_tpl_statements(
|
|
290
|
+
"post 'Creates #{resource.titleized}' do",
|
|
291
|
+
"description 'Creates #{resource.titleized}'",
|
|
292
|
+
"consumes 'application/json'",
|
|
293
|
+
"produces 'application/json'",
|
|
294
|
+
"parameter(name: :#{resource.snake_case}, in: :body)\n",
|
|
295
|
+
"response '201', '#{resource.snake_case} created' do",
|
|
296
|
+
"let(:#{resource.snake_case}) do",
|
|
297
|
+
"{#{resource_params}}",
|
|
298
|
+
"end\n",
|
|
299
|
+
"run_test!",
|
|
300
|
+
"end\n",
|
|
301
|
+
spec_tpl_create_invalid_attrs_test,
|
|
302
|
+
spec_tpl_invalid_credentials(with_body: true),
|
|
303
|
+
"end\n"
|
|
304
|
+
)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def spec_tpl_parent_resource_parameter
|
|
308
|
+
return unless parent_resource?
|
|
309
|
+
|
|
310
|
+
"parameter name: :#{parent_resource.id}, in: :path, type: :integer"
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def spec_tpl_let_parent_resource
|
|
314
|
+
return unless parent_resource?
|
|
315
|
+
|
|
316
|
+
concat_tpl_statements(
|
|
317
|
+
"let(:#{parent_resource.snake_case}) { create(:#{parent_resource.snake_case}) }",
|
|
318
|
+
"let(:#{parent_resource.id}) { #{parent_resource.snake_case}.id }\n"
|
|
319
|
+
)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def spec_tpl_show
|
|
323
|
+
concat_tpl_statements(
|
|
324
|
+
"get 'Retrieves #{resource.titleized}' do",
|
|
325
|
+
"produces 'application/json'\n",
|
|
326
|
+
"response '200', '#{resource.snake_case} retrieved' do",
|
|
327
|
+
"schema('$ref' => '#/definitions/#{resource.snake_case}_resource')\n",
|
|
328
|
+
"run_test!",
|
|
329
|
+
"end\n",
|
|
330
|
+
"response '404', 'invalid #{resource.snake_case} id' do",
|
|
331
|
+
"let(:id) { 'invalid' }",
|
|
332
|
+
"run_test!",
|
|
333
|
+
"end\n",
|
|
334
|
+
spec_tpl_invalid_credentials,
|
|
335
|
+
"end\n"
|
|
336
|
+
)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def spec_tpl_update
|
|
340
|
+
concat_tpl_statements(
|
|
341
|
+
"put 'Updates #{resource.titleized}' do",
|
|
342
|
+
"description 'Updates #{resource.titleized}'",
|
|
343
|
+
"consumes 'application/json'",
|
|
344
|
+
"produces 'application/json'",
|
|
345
|
+
"parameter(name: :#{resource.snake_case}, in: :body)\n",
|
|
346
|
+
"response '200', '#{resource.snake_case} updated' do",
|
|
347
|
+
"let(:#{resource.snake_case}) do",
|
|
348
|
+
"{#{resource_params}}",
|
|
349
|
+
"end\n",
|
|
350
|
+
"run_test!",
|
|
351
|
+
"end\n",
|
|
352
|
+
spec_tpl_update_invalid_attrs_test,
|
|
353
|
+
spec_tpl_invalid_credentials(with_body: true),
|
|
354
|
+
"end\n"
|
|
355
|
+
)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def spec_tpl_destroy
|
|
359
|
+
concat_tpl_statements(
|
|
360
|
+
"delete 'Deletes #{resource.titleized}' do",
|
|
361
|
+
"produces 'application/json'",
|
|
362
|
+
"description 'Deletes specific #{resource.snake_case}'\n",
|
|
363
|
+
"response '204', '#{resource.snake_case} deleted' do",
|
|
364
|
+
"run_test!",
|
|
365
|
+
"end\n",
|
|
366
|
+
"response '404', '#{resource.snake_case} not found' do",
|
|
367
|
+
"let(:id) { 'invalid' }\n",
|
|
368
|
+
"run_test!",
|
|
369
|
+
"end\n",
|
|
370
|
+
spec_tpl_invalid_credentials,
|
|
371
|
+
"end\n"
|
|
372
|
+
)
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def spec_tpl_invalid_credentials(with_body: false)
|
|
376
|
+
return unless authenticated_resource?
|
|
377
|
+
|
|
378
|
+
authenticated_resource_name = authenticated_resource.snake_case
|
|
379
|
+
concat_tpl_statements(
|
|
380
|
+
"response '401', '#{authenticated_resource_name} unauthorized' do",
|
|
381
|
+
with_body ? "let(:#{resource.snake_case}) { {} }" : nil,
|
|
382
|
+
"let(:user_token) { 'invalid' }\n",
|
|
383
|
+
"run_test!",
|
|
384
|
+
"end\n"
|
|
385
|
+
)
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def spec_tpl_update_invalid_attrs_test
|
|
389
|
+
spec_tpl_create_invalid_attrs_test
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def spec_tpl_create_invalid_attrs_test
|
|
393
|
+
return if resource.required_resource_attributes.blank?
|
|
394
|
+
|
|
395
|
+
concat_tpl_statements(
|
|
396
|
+
"response '400', 'invalid attributes' do",
|
|
397
|
+
"let(:#{resource.snake_case}) do",
|
|
398
|
+
"{#{invalid_resource_params}}",
|
|
399
|
+
"end\n",
|
|
400
|
+
"run_test!",
|
|
401
|
+
"end\n"
|
|
402
|
+
)
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def resource_params
|
|
406
|
+
attrs = if resource.required_resource_attributes.any?
|
|
407
|
+
resource.required_resource_attributes
|
|
408
|
+
else
|
|
409
|
+
resource.optional_resource_attributes
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
for_each_schema_attribute(attrs) do |attr|
|
|
413
|
+
"#{attr[:name]}: #{attr[:example]},"
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
def invalid_resource_params
|
|
418
|
+
return unless resource.required_resource_attributes.any?
|
|
419
|
+
|
|
420
|
+
for_each_schema_attribute([resource.required_resource_attributes.first]) do |attr|
|
|
421
|
+
"#{attr[:name]}: nil,"
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def swagger_model_definition_const
|
|
426
|
+
"#{resource.upcase}_SCHEMA"
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def swagger_collection_definition_const
|
|
430
|
+
"#{resource.upcase_plural}_COLLECTION_SCHEMA"
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def swagger_resource_definition_const
|
|
434
|
+
"#{resource.upcase}_RESOURCE_SCHEMA"
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def get_swagger_schema_attributes_definitions
|
|
438
|
+
for_each_schema_attribute(resource.resource_attributes) do |attr|
|
|
439
|
+
opts = ["example: #{attr[:example]}"]
|
|
440
|
+
opts << "'x-nullable': true" unless attr[:required]
|
|
441
|
+
opts
|
|
442
|
+
|
|
443
|
+
"#{attr[:name]}: { type: :#{attr[:swagger_type]}, #{opts.join(', ')} },"
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def get_swagger_schema_attributes_names
|
|
448
|
+
for_each_schema_attribute(resource.required_resource_attributes) do |attr|
|
|
449
|
+
":#{attr[:name]},"
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
def for_each_schema_attribute(attributes)
|
|
454
|
+
attributes.inject("") do |memo, attr|
|
|
455
|
+
memo += "\n"
|
|
456
|
+
memo += yield(attr)
|
|
457
|
+
memo
|
|
458
|
+
end.delete_suffix(",")
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
# rubocop:enable Metrics/ModuleLength
|
|
462
|
+
# rubocop:enable Metrics/MethodLength
|
|
463
|
+
# rubocop:enable Layout/AlignArguments
|