care 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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +0 -0
  4. data/.rubocop.yml +10 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +0 -0
  7. data/CODE_OF_CONDUCT.md +84 -0
  8. data/Gemfile +27 -0
  9. data/Gemfile.lock +206 -0
  10. data/README.md +40 -0
  11. data/Rakefile +12 -0
  12. data/bin/console +0 -0
  13. data/bin/setup +0 -0
  14. data/care.gemspec +49 -0
  15. data/exe/care +0 -0
  16. data/lib/care.rb +27 -0
  17. data/lib/care/auto_finder/by_ids.rb +24 -0
  18. data/lib/care/auto_finder/findable.rb +68 -0
  19. data/lib/care/auto_finder/finder_methods.rb +28 -0
  20. data/lib/care/auto_finder/paginateble.rb +30 -0
  21. data/lib/care/auto_finder/searchable.rb +56 -0
  22. data/lib/care/auto_finder/searcher.rb +101 -0
  23. data/lib/care/auto_finder/sortable.rb +41 -0
  24. data/lib/care/finder.rb +17 -0
  25. data/lib/care/jwt_service.rb +24 -0
  26. data/lib/care/rspec.rb +15 -0
  27. data/lib/care/seed.rb +21 -0
  28. data/lib/care/support/authorization_helper.rb +20 -0
  29. data/lib/care/support/error_collection.rb +43 -0
  30. data/lib/care/support/factory_bot.rb +5 -0
  31. data/lib/care/support/pagination.rb +9 -0
  32. data/lib/care/support/parameters.rb +39 -0
  33. data/lib/care/support/request_helper.rb +11 -0
  34. data/lib/care/version.rb +5 -0
  35. data/lib/generators/care/install/USAGE +19 -0
  36. data/lib/generators/care/install/install_generator.rb +33 -0
  37. data/lib/generators/care/install/templates/.env.local.example +62 -0
  38. data/lib/generators/care/install/templates/active_model_serializer.rb +1 -0
  39. data/lib/generators/care/install/templates/centrifuge.rb +4 -0
  40. data/lib/generators/care/install/templates/rswag-ui.rb +3 -0
  41. data/lib/generators/care/install/templates/rswag_api.rb +3 -0
  42. data/lib/generators/care/install/templates/swagger.yml +76 -0
  43. data/lib/generators/care/install/templates/swagger_helper.rb +24 -0
  44. data/lib/patch/action_controller/api.rb +17 -0
  45. data/lib/patch/action_controller/concerns/authentication.rb +50 -0
  46. data/lib/patch/action_controller/concerns/authorization.rb +46 -0
  47. data/lib/patch/action_controller/concerns/exception_handler.rb +67 -0
  48. data/lib/patch/action_controller/concerns/filter_sort_pagination.rb +98 -0
  49. metadata +321 -0
@@ -0,0 +1,21 @@
1
+ module Care
2
+ class Seed
3
+ def self.execute
4
+ Dir[File.join(Rails.root, "db", "seeds", "*.json")].sort.each do |path|
5
+ table_name = path.split("/").last.gsub(".json", "").gsub(/^\d*_/, "")
6
+ puts "*** Преднаполнение таблицы #{table_name} ***"
7
+ items = ActiveSupport::JSON.decode(File.read(path))
8
+ begin
9
+ items_class = table_name.classify.constantize
10
+ items_class.import(items, on_duplicate_key_update: :all)
11
+ rescue NameError => _e # Не обнаружен класс с описанием модели
12
+ keys = items[0].keys.join(",")
13
+ values = items.map { |i| "(#{i.values.map { |v| "'#{v}'" }.join(",")})" }.join(",")
14
+ sql = "insert into #{table_name}(#{keys}) values #{values} on conflict (id) do nothing"
15
+ puts sql
16
+ ActiveRecord::Base.connection.execute sql
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ RSpec.shared_context "set_authorization" do
2
+
3
+ let(:organization_id) { SecureRandom.uuid }
4
+
5
+ let(:payload) do
6
+ {
7
+ iss: "DocShell",
8
+ iat: DateTime.now.to_i,
9
+ jti: SecureRandom.uuid,
10
+ exp: (DateTime.now + 1.year).to_i,
11
+ account: {
12
+ id: SecureRandom.uuid,
13
+ email: Faker::Internet.email,
14
+ },
15
+ permissions: { organization_id => permissions }
16
+ }
17
+ end
18
+ let(:Authorization) { "Bearer #{Care::JwtService.new.encode(payload)}" }
19
+ end
20
+
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples "error_collection" do |codes|
4
+ if codes.include?("400")
5
+ response 400, " Недопустимые параметры" do
6
+ let(:params) { {data: "invalid"} }
7
+ schema "$ref": "#/components/schemas/ErrorsOutput"
8
+ run_test!
9
+ end
10
+ end
11
+
12
+ if codes.include?("401")
13
+ response 401, "Учетная запись не авторизована" do
14
+ let(:Authorization) { nil }
15
+ schema "$ref": "#/components/schemas/ErrorsOutput"
16
+ run_test!
17
+ end
18
+ end
19
+
20
+ if codes.include?("403")
21
+ response 403, "Нет прав доступа к ресурсу" do
22
+ let(:permissions) {[]}
23
+ schema "$ref": "#/components/schemas/ErrorsOutput"
24
+ run_test!
25
+ end
26
+ end
27
+
28
+ if codes.include?("404")
29
+ response 404, "Ресурс не найден" do
30
+ let(:organization_id) { SecureRandom.uuid }
31
+ schema "$ref": "#/components/schemas/ErrorsOutput"
32
+ run_test!
33
+ end
34
+ end
35
+
36
+ if codes.include?("422")
37
+ response 422, "Недопустимая модель ресурса" do
38
+ let(:params) { invalid_params }
39
+ schema "$ref": "#/components/schemas/ErrorsOutput"
40
+ run_test!
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.configure do |config|
4
+ config.include FactoryBot::Syntax::Methods
5
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_context "headers" do |header_tags|
4
+ if header_tags.include?(:Authorization)
5
+ header "X-Pagination-Count", type: :integer, description: "Общее количество с учетом search"
6
+ header "X-Pagination-Limit", type: :integer, description: "Количество элементов на странице"
7
+ header "X-Pagination-Page", type: :integer, description: "Номер текущей страницы"
8
+ end
9
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_context "parameters" do |parameter_tags|
4
+ if parameter_tags.include?(:Authorization)
5
+ parameter name: :Authorization,
6
+ description: "Client token",
7
+ in: :header,
8
+ type: :string,
9
+ required: true
10
+ end
11
+ if parameter_tags.include?(:organization_id)
12
+ parameter name: :organization_id,
13
+ description: "Organization ID текущей организации",
14
+ in: :header,
15
+ type: :string,
16
+ required: true
17
+ end
18
+ if parameter_tags.include?(:id)
19
+ parameter name: :id,
20
+ description: "Идентификатор",
21
+ in: :path,
22
+ type: :string,
23
+ required: true
24
+ end
25
+ if parameter_tags.include?(:base_for_index)
26
+ parameter name: :search,
27
+ description: "Строка для поиска ресурса (часть имени, как пример)",
28
+ in: :query, type: :string, required: false
29
+ parameter name: :limit,
30
+ description: "Ограничение кол-ва записей на странице.(20)",
31
+ in: :query, type: :integer, required: false
32
+ parameter name: :sort,
33
+ description: "Поле по которому производится сортировка.",
34
+ in: :query, type: :integer, required: false
35
+ parameter name: :page,
36
+ description: "Номер страницы",
37
+ in: :query, type: :integer, required: false
38
+ end
39
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RequestHelper
4
+ def json
5
+ JSON.parse(response.body, symbolize_names: true)
6
+ end
7
+ end
8
+
9
+ RSpec.configure do |config|
10
+ config.include RequestHelper, type: :request
11
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Care
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,19 @@
1
+ Description:
2
+ Adds care initializer for configuration
3
+
4
+ Example:
5
+ rails generate care:install
6
+
7
+ This will create:
8
+ spec/swagger_helper.rb
9
+ spec/support/#{app_name}.yml
10
+ config/initializers/rswag_api.rb
11
+ config/initializers/rswag-ui.rb
12
+ config/initializers/active_model_serializer.rb
13
+ config/initializers/centrifuge.rb
14
+
15
+ Will be recursively executed rails generate rspec:install
16
+
17
+ Will add route
18
+ mount Rswag::Api::Engine => '/api-docs'
19
+ mount Rswag::Ui::Engine => '/api-docs'
@@ -0,0 +1,33 @@
1
+ require 'rails/generators'
2
+
3
+ module Care
4
+ class InstallGenerator < Rails::Generators::Base
5
+ include Rails::Generators::AppName
6
+
7
+ source_root File.expand_path('../templates', __FILE__)
8
+
9
+ def add_initializer
10
+ gem 'rspec-rails', group: [:development, :test], version: '~> 4.0.1'
11
+ gem "rswag"
12
+ template('swagger_helper.rb', 'spec/swagger_helper.rb')
13
+ template("swagger.yml", "spec/support/#{app_name}.yml")
14
+ template('rswag_api.rb', 'config/initializers/rswag_api.rb')
15
+ template('rswag-ui.rb', 'config/initializers/rswag-ui.rb')
16
+
17
+ gem "active_model_serializers", version: "~> 0.10.10"
18
+ template('active_model_serializer.rb', 'config/initializers/active_model_serializer.rb')
19
+
20
+ gem "centrifuge", version: "~> 1.2"
21
+ template('centrifuge.rb', 'config/initializers/centrifuge.rb')
22
+ end
23
+
24
+ def install_components
25
+ generate 'rspec:install'
26
+ end
27
+
28
+ def add_routes
29
+ route("mount Rswag::Api::Engine => '/api-docs'")
30
+ route("mount Rswag::Ui::Engine => '/api-docs'")
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,62 @@
1
+ # secrets.yml
2
+ SECRET_KEY_BASE=4e8814eb30bf558ce07b3a41fcbc657b257344670003d45e828c493c3febc72d25cf24eea9437f9092644e30e839018328f70704f8dce697c007062717801d76
3
+ JWT_SECRET_PATH=jwtRS256.key.pub
4
+ JWT_EXPIRATION_HOURS=24
5
+
6
+ # database.yml
7
+ DATABASE_HOST=postgres
8
+ DATABASE_PORT=5432
9
+ DATABASE_NAME=docshell
10
+ DATABASE_USERNAME=postgres
11
+ DATABASE_PASSWORD=admin
12
+
13
+ # MAILER
14
+ MAILER_ADDRESS=smtp.yandex.ru
15
+ MAILER_PORT=465
16
+ MAILER_DOMAIN=yandex.ru
17
+ MAILER_USERNAME=info@docshell.ru
18
+ MAILER_PASSWORD=secret
19
+ MAILER_TLS=true
20
+ MAILER_ENABLE_STARTTLS_AUTO=true
21
+
22
+ # SUPPORT SERVICE
23
+ SUPPORT_SERVICE_URL=https://support.docshell.ru
24
+ SUPPORT_API_KEY=s1B2c3D4e5F63421234xcSd
25
+ SUPPORT_EMAIL_TO=dev@dev.dev
26
+ SUPPORT_PHONE=88005553555
27
+ SUPPORT_USE_MAIL=false
28
+
29
+ # REPORT SERVICE
30
+ REPORT_SERVICE_HOST=fastreport
31
+ REPORT_SERVICE_PORT=8081
32
+ REPORT_SERVICE_PATH=Home/ExportTemplate
33
+ REPORT_SERVICE_IMAGEHOST=http://api:80
34
+
35
+ # GOSSOPKA
36
+ GOSSOPKA_URL=
37
+
38
+ # REDIS
39
+ REDIS_HOST=redis
40
+ REDIS_PORT=6379
41
+ REDIS_DB=db
42
+
43
+ # CENTRIFUGO
44
+ CENTRIFUGO_SCHEME=http
45
+ CENTRIFUGO_HOST=centrifugo
46
+ CENTRIFUGO_PORT=8000
47
+ # Секретный ключ должен совпадать с ключом, указанным в файле
48
+ # config/centrifugo/config.json
49
+ CENTRIFUGO_SECRET=c2f0b6a3-ea33-4f20-b708-7943fa7f3bd4
50
+
51
+ # ROLLBAR
52
+ # Если отсутствует переменная конфигурации rollbar_server_token
53
+ # или содержит пустое значение, то Rollbar отключается
54
+ ROLLBAR_SERVER_TOKEN=secret
55
+
56
+ # Максимальное количество паралельных заданий на генерацию
57
+ # документов через Fastreport
58
+ # Значение используется с файле
59
+ # engines/parameter_core/app/workers/parameter_core/fastreport_worker.rb
60
+ FASTREPORT_CONCURRENCY_LIMIT=1
61
+
62
+
@@ -0,0 +1 @@
1
+ ActiveModel::Serializer.config.adapter = :json
@@ -0,0 +1,4 @@
1
+ Centrifuge.scheme = ENV["CENTRIFUGO_SCHEME"]
2
+ Centrifuge.host = ENV["CENTRIFUGO_HOST"]
3
+ Centrifuge.port = ENV["CENTRIFUGO_PORT"]
4
+ Centrifuge.secret = ENV["CENTRIFUGO_SECRET"]
@@ -0,0 +1,3 @@
1
+ Rswag::Ui.configure do |c|
2
+ c.swagger_endpoint '/api-docs/<%= app_name %>.yaml', 'DocShell API. <%= app_name.camelize %>.'
3
+ end
@@ -0,0 +1,3 @@
1
+ Rswag::Api.configure do |c|
2
+ c.swagger_root = Rails.root.to_s + '/swagger'
3
+ end
@@ -0,0 +1,76 @@
1
+ ---
2
+ openapi: 3.0.1
3
+ info:
4
+ title: DocShell API.<%= app_name.camelize %>
5
+ version: v1
6
+ tags:
7
+ - name: DemoModel
8
+ description: Демо
9
+ paths: {}
10
+ components:
11
+ securitySchemes:
12
+ token:
13
+ type: apiKey
14
+ name: Authorization
15
+ in: header
16
+ bearerAuth:
17
+ type: http
18
+ scheme: bearer
19
+ bearerFormat: JWT
20
+ parameters:
21
+ page:
22
+ name: page
23
+ description: Номер страницы
24
+ in: query
25
+ required: false
26
+ schema:
27
+ type: integer
28
+ page_limit:
29
+ name: limit
30
+ description: Ограничение кол-ва записей на странице.(20)
31
+ in: query
32
+ required: false
33
+ schema:
34
+ type: integer
35
+ sort:
36
+ name: sort
37
+ description: Поле по которому производится сортировка.
38
+ in: query
39
+ required: false
40
+ schema:
41
+ type: string
42
+ query_filter:
43
+ name: search
44
+ description: Строка для поиска ресурса (часть имени, как пример)
45
+ in: query
46
+ required: false
47
+ schema:
48
+ type: string
49
+ schemas:
50
+ ErrorsOutput:
51
+ properties:
52
+ errors:
53
+ type: array
54
+ example:
55
+ [ '<Локализованное название атрибута> <Локализованное сообщение об ошибке>' ]
56
+ items:
57
+ type: string
58
+ source:
59
+ type: object
60
+ example: { <Название атрибута>: [ '<Локализованное сообщение об ошибке>' ] }
61
+ security:
62
+ - bearerAuth: []
63
+ servers:
64
+ - url: https://{host}:{port}/
65
+ description: Development server
66
+ variables:
67
+ host:
68
+ default: localhost
69
+ port:
70
+ default: '3000'
71
+ - url: https://new.docshell.ru/api/<%= app_name %>/v1
72
+ description: Production server
73
+ - url: https://demo.docshell.ru/api/<%= app_name %>/v1
74
+ description: Demo server
75
+ - url: https://10.0.100.249:8081/api/<%= app_name %>/v1
76
+ description: Staging server
@@ -0,0 +1,24 @@
1
+ require "rails_helper"
2
+
3
+ RSpec.configure do |config|
4
+ # Specify a root folder where Swagger JSON files are generated
5
+ # NOTE: If you're using the rswag-api to serve API descriptions, you'll need
6
+ # to ensure that it's configured to serve Swagger from the same folder
7
+ config.swagger_root = Rails.root.join("swagger").to_s
8
+
9
+ # Define one or more Swagger documents and provide global metadata for each one
10
+ # When you run the 'rswag:specs:swaggerize' rake task, the complete Swagger will
11
+ # be generated at the provided relative path under swagger_root
12
+ # By default, the operations defined in spec files are added to the first
13
+ # document below. You can override this behavior by adding a swagger_doc tag to the
14
+ # the root example_group in your specs, e.g. describe '...', swagger_doc: 'v2/swagger.json'
15
+ config.swagger_docs = {
16
+ "<%= app_name %>.yml" => YAML.load_file(File.join(__dir__, "support/<%= app_name %>.yml")).with_indifferent_access,
17
+ }
18
+
19
+ # Specify the format of the output Swagger file when running 'rswag:specs:swaggerize'.
20
+ # The swagger_docs configuration option has the filename including format in
21
+ # the key, this may want to be changed to avoid putting yaml in json files.
22
+ # Defaults to json. Accepts ':json' and ':yaml'.
23
+ config.swagger_format = :yaml
24
+ end
@@ -0,0 +1,17 @@
1
+ require "action_view"
2
+ require 'action_controller/api'
3
+ require_relative 'concerns/authentication'
4
+ require_relative 'concerns/exception_handler'
5
+ require_relative 'concerns/authorization'
6
+ require_relative 'concerns/filter_sort_pagination'
7
+
8
+ module ActionController
9
+ class API < Metal
10
+ # include ActionController::MimeResponds
11
+ # include ActiveSupport::Rescuable
12
+ include ExceptionHandler
13
+ include Authentication
14
+ include Authorization
15
+ include FilterSortPagination
16
+ end
17
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # В модуле сгруппированы методы отвечающие за аутентификацию
4
+ #
5
+ module Authentication
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ before_action :authenticate_request
10
+ attr_reader :access_token
11
+
12
+ private
13
+
14
+ def authenticate_request
15
+ raise AuthenticationError unless access_token
16
+ end
17
+
18
+ def access_token
19
+ @access_token ||= decode(http_auth_header)
20
+ end
21
+
22
+ def http_auth_header
23
+ request.headers["Authorization"]&.split(" ")&.last
24
+ end
25
+
26
+ def decode(token)
27
+ decoded_token = JWT.decode(token, secret_key, true, algorithm: "RS256")
28
+ HashWithIndifferentAccess.new(decoded_token[0])
29
+ end
30
+
31
+ def secret_key
32
+ secret_key_file_open { |f| OpenSSL::PKey::RSA.new(f) }
33
+ end
34
+
35
+ def secret_key_file_open(&block)
36
+ File.open(ENV['JWT_SECRET_PATH'], &block)
37
+ end
38
+ end
39
+
40
+ # Используется для сигнализации ошибки во время аутентификации
41
+ #
42
+ class AuthenticationError < StandardError
43
+ attr_reader :param
44
+
45
+ def initialize(param)
46
+ @param = param
47
+ super("Ошибка аутентификации: #{param}")
48
+ end
49
+ end
50
+ end