care 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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