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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +0 -0
- data/.rubocop.yml +10 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +0 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +27 -0
- data/Gemfile.lock +206 -0
- data/README.md +40 -0
- data/Rakefile +12 -0
- data/bin/console +0 -0
- data/bin/setup +0 -0
- data/care.gemspec +49 -0
- data/exe/care +0 -0
- data/lib/care.rb +27 -0
- data/lib/care/auto_finder/by_ids.rb +24 -0
- data/lib/care/auto_finder/findable.rb +68 -0
- data/lib/care/auto_finder/finder_methods.rb +28 -0
- data/lib/care/auto_finder/paginateble.rb +30 -0
- data/lib/care/auto_finder/searchable.rb +56 -0
- data/lib/care/auto_finder/searcher.rb +101 -0
- data/lib/care/auto_finder/sortable.rb +41 -0
- data/lib/care/finder.rb +17 -0
- data/lib/care/jwt_service.rb +24 -0
- data/lib/care/rspec.rb +15 -0
- data/lib/care/seed.rb +21 -0
- data/lib/care/support/authorization_helper.rb +20 -0
- data/lib/care/support/error_collection.rb +43 -0
- data/lib/care/support/factory_bot.rb +5 -0
- data/lib/care/support/pagination.rb +9 -0
- data/lib/care/support/parameters.rb +39 -0
- data/lib/care/support/request_helper.rb +11 -0
- data/lib/care/version.rb +5 -0
- data/lib/generators/care/install/USAGE +19 -0
- data/lib/generators/care/install/install_generator.rb +33 -0
- data/lib/generators/care/install/templates/.env.local.example +62 -0
- data/lib/generators/care/install/templates/active_model_serializer.rb +1 -0
- data/lib/generators/care/install/templates/centrifuge.rb +4 -0
- data/lib/generators/care/install/templates/rswag-ui.rb +3 -0
- data/lib/generators/care/install/templates/rswag_api.rb +3 -0
- data/lib/generators/care/install/templates/swagger.yml +76 -0
- data/lib/generators/care/install/templates/swagger_helper.rb +24 -0
- data/lib/patch/action_controller/api.rb +17 -0
- data/lib/patch/action_controller/concerns/authentication.rb +50 -0
- data/lib/patch/action_controller/concerns/authorization.rb +46 -0
- data/lib/patch/action_controller/concerns/exception_handler.rb +67 -0
- data/lib/patch/action_controller/concerns/filter_sort_pagination.rb +98 -0
- metadata +321 -0
data/lib/care/seed.rb
ADDED
@@ -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,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
|
data/lib/care/version.rb
ADDED
@@ -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,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
|