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
File without changes
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ require "care/version"
3
+ require "care/seed"
4
+ require 'care/jwt_service'
5
+
6
+ require 'rswag/specs'
7
+ require 'rswag/api'
8
+ require 'rswag/ui'
9
+
10
+
11
+ require 'rails/generators'
12
+ require 'jwt'
13
+ require 'patch/action_controller/api'
14
+ require 'cancancan'
15
+
16
+ require 'care/finder'
17
+ require 'activerecord-import'
18
+
19
+ require 'active_model_serializers'
20
+ ActiveModel::Serializer.config.adapter = :json
21
+
22
+ require 'kaminari'
23
+
24
+ module Care
25
+ class Error < StandardError; end
26
+ # Your code goes here...
27
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Care::AutoFinder
4
+ #
5
+ # Содержит методы для фильтрации по ids
6
+ #
7
+ # @example
8
+ # class Finder
9
+ # include ByIds
10
+ #
11
+ # attr_accessor :params
12
+ #
13
+ # def call
14
+ # params = { ids: [1, 2, 3] }
15
+ # by_ids(Document)
16
+ # end
17
+ # end
18
+ #
19
+ module ByIds
20
+ def ids(items)
21
+ params[:ids].present? ? items.where(id: params[:ids]) : items
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Care::AutoFinder
4
+ module Findable
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ attr_accessor :chain_filter
9
+ # attr_accessor :relation
10
+
11
+ def set_relation(relation)
12
+ @relation = relation
13
+ end
14
+
15
+ def entity
16
+ self.relation = yield
17
+ end
18
+
19
+ def filter_by(*filters)
20
+ self.chain_filter = filters
21
+ end
22
+
23
+ def relation
24
+ @relation ||= /(.*)Finder/.match(name)[1].singularize.constantize
25
+ end
26
+
27
+ #
28
+ # @param [Hash] options параметры для работы файдера
29
+ # @option options [ActiveRecord] :relation модель, для которой будет вестись поиск
30
+ # @option options [Hash] :params опции для поиска
31
+ #
32
+ # @example
33
+ # FooBarFinder
34
+ # .call(
35
+ # relation: FooBar,
36
+ # params: { page: 10, limit: 2, name: 'Lorem' }
37
+ # )
38
+ #
39
+ def call(options = {})
40
+ new(options).call
41
+ end
42
+ end
43
+
44
+ def initialize(options = {})
45
+ options = options.symbolize_keys
46
+ @relation = options[:relation] || self.class.relation || options[:params][:relation]
47
+ @params = options[:params] || options || {}
48
+ end
49
+
50
+ def call
51
+ collection = relation || self.class.relation
52
+
53
+ chain_filter.each do |filter|
54
+ collection = send(filter, collection)
55
+ end
56
+
57
+ collection.distinct
58
+ end
59
+
60
+ protected
61
+
62
+ attr_reader :relation, :params
63
+
64
+ def chain_filter
65
+ self.class.chain_filter || [:ids, :paginate, :search, :sort]
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Care::AutoFinder
4
+ module FinderMethods
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ include Care::AutoFinder::Paginateble
9
+ include Care::AutoFinder::Searchable
10
+ include Care::AutoFinder::Sortable
11
+ include Care::AutoFinder::ByIds
12
+ end
13
+
14
+ def range_filter(options)
15
+ options = Struct.new(*options.keys).new(*options.values)
16
+
17
+ if options.start.present? && options.end.present?
18
+ options.collection.where(options.column => options.start..options.end)
19
+ elsif options.start.present?
20
+ options.collection.where("#{options.column} >= ?", options.start)
21
+ elsif options.end.present?
22
+ options.collection.where("#{options.column} <= ?", options.end)
23
+ else
24
+ options.collection
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Care::AutoFinder
4
+ #
5
+ # Содержит методы для пагинации коллекции
6
+ #
7
+ # @example
8
+ # class Finder
9
+ # include Paginateble
10
+ #
11
+ # attr_reader :params
12
+ #
13
+ # def call
14
+ # params = { page: 2, limit: 10 }
15
+ # by_page(Document)
16
+ # end
17
+ # end
18
+ #
19
+ module Paginateble
20
+ DEFAULT_LIMIT = 100
21
+
22
+ def by_page(items)
23
+ params[:page].present? ? items.page(params[:page]).per(params[:limit] || DEFAULT_LIMIT) : items
24
+ end
25
+
26
+ def paginate(items)
27
+ by_page(items)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Care::AutoFinder
4
+ #
5
+ # Содержит методы для полнотекстового поиска в коллекции
6
+ #
7
+ # @example
8
+ # class Finder
9
+ # include Care::AutoFinder::Searchable
10
+ #
11
+ # attr_reader :params
12
+ #
13
+ # search_by :name, :version
14
+ # search_by :name, address: %i[locality street house flat]
15
+ # # columns => [:name, {:address=>[:locality, :street, :house, :flat]}]
16
+ #
17
+ # def call
18
+ # params = { search: 'сзи' }
19
+ # search(Document)
20
+ # end
21
+ # end
22
+ #
23
+ module Searchable
24
+ extend ActiveSupport::Concern
25
+
26
+ def search(items)
27
+ if params[:search].present?
28
+ Searcher.call(items: items, columns: columns, search: params[:search])
29
+ else
30
+ items
31
+ end
32
+ end
33
+
34
+ def search_into_array_field(column)
35
+ relation.where("array_to_string(#{column}, '||') LIKE ?", "%#{params[:search]}%")
36
+ end
37
+
38
+ def search_into_joined_field(field_path)
39
+ relation.where("#{field_path} LIKE ?", "%#{params[:search]}%")
40
+ end
41
+
42
+ module ClassMethods
43
+ attr_accessor :columns
44
+
45
+ def search_by(*columns)
46
+ self.columns = columns
47
+ end
48
+ end
49
+
50
+ protected
51
+
52
+ def columns
53
+ self.class.columns
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Care::AutoFinder
4
+ class Searcher
5
+ attr_reader :relation
6
+ attr_reader :query
7
+ attr_reader :columns
8
+ attr_reader :attributes
9
+ attr_reader :arel_table
10
+ attr_reader :table
11
+ attr_reader :pattern
12
+
13
+ def initialize(params)
14
+ @relation = params[:items]
15
+ @columns = params[:columns]
16
+ @attributes = params[:columns]
17
+ @query = params[:search]
18
+ @arel_table = relation.arel_table
19
+ @table = relation.arel_table
20
+ @pattern = "%#{sanitaze(query)}%"
21
+ end
22
+
23
+ def self.call(params)
24
+ new(params).call
25
+ end
26
+
27
+ def call
28
+ predicate = complile_node(relation, attributes).reduce { |memo, expression| memo ? memo.or(expression) : memo }
29
+ relation.where(predicate)
30
+ end
31
+
32
+ protected
33
+
34
+ def complile_node(relation, attributes)
35
+ expression = [*attributes].map { |attribute|
36
+ if attribute.is_a?(Symbol)
37
+ compile(relation, attribute)
38
+ elsif attribute.is_a?(Hash)
39
+ attribute.map do |child_relation, child_attributes|
40
+ complile_node(inner_relation(relation, child_relation), child_attributes)
41
+ end
42
+ end
43
+ }
44
+
45
+ expression.flatten
46
+ end
47
+
48
+ #
49
+ # rubocop:disable Metrics/AbcSize
50
+ #
51
+ def compile(relation, attribute)
52
+ if relation.columns_hash[attribute.to_s] && relation.columns_hash[attribute.to_s].type == :date
53
+ Arel.sql("to_char(#{attribute}, 'DD.MM.YYYY')").matches(pattern)
54
+ elsif relation.columns_hash[attribute.to_s] && relation.columns_hash[attribute.to_s].type == :datetime
55
+ Arel.sql("to_char(#{attribute}, 'DD.MM.YYYY')").matches(pattern)
56
+ elsif relation.columns_hash[attribute.to_s] && relation.columns_hash[attribute.to_s].type == :integer
57
+ Arel.sql("to_char(#{attribute}, '999')").matches(pattern)
58
+ elsif relation.columns_hash[attribute.to_s]&.array?
59
+ Arel.sql("array_to_string(#{attribute}, ', ')").matches(pattern)
60
+ elsif relation.respond_to?(:custom_columns) && relation.custom_columns[attribute]
61
+ Arel.sql(relation.custom_columns[attribute]).matches(pattern)
62
+ else
63
+ relation.arel_table[attribute].matches(pattern)
64
+ end
65
+ end
66
+ #
67
+ # rubocop:enable Metrics/AbcSize
68
+ #
69
+
70
+ private
71
+
72
+ def sanitaze(string, escape_character = '\\')
73
+ pattern = Regexp.union(escape_character, "%", "_")
74
+ string.gsub(pattern) { |x| [escape_character, x].join }
75
+ end
76
+
77
+ def inner_relation(relation, target_relation)
78
+ target = relation.reflections[target_relation.to_s]
79
+ target ? target.klass : ArtificalRelation.new(target_relation)
80
+ end
81
+
82
+ #
83
+ # Исскуственая модель, имеющая функцию arel_table
84
+ #
85
+ class ArtificalRelation
86
+ attr_accessor :table_name
87
+
88
+ def initialize(table_name)
89
+ @table_name = table_name
90
+ end
91
+
92
+ def arel_table
93
+ Arel::Table.new(table_name)
94
+ end
95
+
96
+ def columns_hash
97
+ {}
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Care::AutoFinder
4
+ #
5
+ # Содержит методы для сортировки коллекции
6
+ #
7
+ # @example
8
+ # class Finder
9
+ # include Sortable
10
+ #
11
+ # attr_reader :params
12
+ #
13
+ # def call
14
+ # params = { sort: 'name_asc' }
15
+ # sort(Document)
16
+ # end
17
+ # end
18
+ #
19
+ module Sortable
20
+ # def sort(items)
21
+ # params[:sort].present? ? items.order(order_expiration) : items.order(created_at: :asc)
22
+ # end
23
+
24
+ def sort(items)
25
+ if params[:sort].present?
26
+ items.order(order_expiration)
27
+ elsif items.methods.include?(:default_sort)
28
+ items.default_sort
29
+ else
30
+ items.order(created_at: :asc)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def order_expiration
37
+ order_match_parts = params[:sort].rpartition("_")
38
+ {order_match_parts.first => order_match_parts.last}
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,17 @@
1
+ require_relative 'auto_finder/findable'
2
+ require_relative 'auto_finder/paginateble'
3
+ require_relative 'auto_finder/by_ids'
4
+ require_relative 'auto_finder/sortable'
5
+ require_relative 'auto_finder/searchable'
6
+ require_relative 'auto_finder/finder_methods'
7
+
8
+ module Care
9
+ class Finder
10
+ include Care::AutoFinder::Findable
11
+ include Care::AutoFinder::FinderMethods
12
+
13
+ def self.call(relation, params)
14
+ new(relation: relation, params: params).call
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ module Care
2
+ class JwtService
3
+ def decode(token)
4
+ decoded_token = JWT.decode(token, public_key, true, algorithm: 'RS256')
5
+ HashWithIndifferentAccess.new(decoded_token[0])
6
+ end
7
+
8
+ def encode(payload)
9
+ JWT.encode(payload, secret_key, 'RS256')
10
+ end
11
+
12
+ private
13
+
14
+ def public_key
15
+ key = File.read(ENV['JWT_SECRET_PATH'])
16
+ OpenSSL::PKey::RSA.new(key)
17
+ end
18
+
19
+ def secret_key
20
+ key = File.read(ENV['JWT_SECRET_PATH'])
21
+ OpenSSL::PKey::RSA.new(key)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ require 'factory_bot_rails'
2
+ require 'database_cleaner-active_record'
3
+ require 'faker'
4
+ require 'rspec-rails'
5
+ require 'shoulda-matchers'
6
+ require 'dotenv-rails'
7
+
8
+
9
+ require_relative 'support/authorization_helper'
10
+ require_relative 'support/error_collection'
11
+ require_relative 'support/factory_bot'
12
+ require_relative 'support/pagination'
13
+ require_relative 'support/parameters'
14
+ require_relative 'support/request_helper'
15
+