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