commons_yellowme 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +17 -0
  5. data/lib/commons.rb +67 -0
  6. data/lib/commons/authentication/authenticate_by_jwt.rb +27 -0
  7. data/lib/commons/authentication/json_web_token.rb +17 -0
  8. data/lib/commons/concerns/attributes/sex.rb +19 -0
  9. data/lib/commons/concerns/extensions/deleted.rb +25 -0
  10. data/lib/commons/concerns/guard/capitalizable.rb +32 -0
  11. data/lib/commons/concerns/validations/undestroyable.rb +18 -0
  12. data/lib/commons/config.rb +11 -0
  13. data/lib/commons/config/locales/en.yml +1 -0
  14. data/lib/commons/config/locales/es.yml +58 -0
  15. data/lib/commons/controllers/schema_validable.rb +27 -0
  16. data/lib/commons/errors/bad_request.rb +15 -0
  17. data/lib/commons/errors/conflict.rb +15 -0
  18. data/lib/commons/errors/default_handling.rb +44 -0
  19. data/lib/commons/errors/error_base.rb +64 -0
  20. data/lib/commons/errors/error_serializer.rb +10 -0
  21. data/lib/commons/errors/forbidden.rb +15 -0
  22. data/lib/commons/errors/internal_server_error.rb +15 -0
  23. data/lib/commons/errors/invalid_resource.rb +21 -0
  24. data/lib/commons/errors/maintenance_mode.rb +14 -0
  25. data/lib/commons/errors/missing_parameter.rb +13 -0
  26. data/lib/commons/errors/not_unique.rb +14 -0
  27. data/lib/commons/errors/payment_required.rb +15 -0
  28. data/lib/commons/errors/precondition_failed.rb +15 -0
  29. data/lib/commons/errors/resource_not_found.rb +14 -0
  30. data/lib/commons/errors/route_not_found.rb +14 -0
  31. data/lib/commons/errors/unauthorized.rb +15 -0
  32. data/lib/commons/errors/unprocessable_entity.rb +15 -0
  33. data/lib/commons/formatter/e164_phone.rb +47 -0
  34. data/lib/commons/formatter/null_attributes_remover.rb +9 -0
  35. data/lib/commons/formatter/regex_constants.rb +9 -0
  36. data/lib/commons/formatter/string_utils.rb +9 -0
  37. data/lib/commons/middleware/catch_json_parse_errors.rb +30 -0
  38. data/lib/commons/railtie.rb +4 -0
  39. data/lib/commons/repositories/base_repository.rb +227 -0
  40. data/lib/commons/repositories/catalogs/base_catalog.rb +68 -0
  41. data/lib/commons/repositories/catalogs/concerns/model_caching_extention.rb +27 -0
  42. data/lib/commons/services/concerns/callable.rb +15 -0
  43. data/lib/commons/version.rb +3 -0
  44. data/lib/tasks/commons_tasks.rake +4 -0
  45. metadata +254 -0
@@ -0,0 +1,64 @@
1
+ # Author: carlos@yellowme.mx
2
+ #
3
+ # Description:
4
+ # The APIError class aids us in defining a standard
5
+ # interface for how we handle the errors in our API.
6
+ #
7
+ # It has two main functions:
8
+ # 1. Hold details of the error thrown so it can
9
+ # be translated to one of the standard HTTP codes.
10
+ # 2. Provide a consistent way to "serialize" errors.
11
+ #
12
+ # Attributes:
13
+ # - message: string, Holds a human friendly way to
14
+ # describe the error.
15
+ # - status: integer, Denotes the HTTP status code.
16
+ # - error: string, Similar to message, but this
17
+ # field is intended to show the error message from
18
+ # a raised error.
19
+ # - detail: Hash, Contains a more details about the
20
+ # error.
21
+
22
+ module Commons
23
+ module Errors
24
+ class ErrorBase < StandardError
25
+ include ActiveModel::Serialization
26
+
27
+ attr_reader :message, :backtrace, :title, :detail, :code, :meta
28
+
29
+ def initialize(message = nil,
30
+ backtrace = nil,
31
+ status: :internal_server_error,
32
+ code: I18n.t('status_code.IER5000_internal_server_error.code'),
33
+ title: I18n.t('status_code.IER5000_internal_server_error.title'),
34
+ detail: I18n.t('status_code.IER5000_internal_server_error.detail'),
35
+ meta: {})
36
+
37
+ @message = message
38
+ @backtrace = backtrace
39
+ @title = title
40
+ @detail = detail
41
+ @code = code
42
+ @status = status
43
+ @meta = meta
44
+ @meta.merge!(message: message) unless @meta.nil? || @message.nil?
45
+ @meta = nil if @meta.blank?
46
+ end
47
+
48
+ # returns the error as its hash representation
49
+ def to_hash
50
+ {
51
+ code: code,
52
+ title: title,
53
+ status: status,
54
+ detail: detail,
55
+ meta: meta
56
+ }
57
+ end
58
+
59
+ def status
60
+ Rack::Utils::SYMBOL_TO_STATUS_CODE[@status]
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,10 @@
1
+ module Commons
2
+ module Errors
3
+ class ErrorSerializer < ActiveModel::Serializer
4
+ include Commons::Formatter::NullAttributesRemover
5
+ type 'errors'
6
+
7
+ attributes :status, :code, :title, :detail, :meta
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ module Commons
2
+ module Errors
3
+ class Forbidden < ErrorBase
4
+ def initialize(message = nil, backtrace = nil, title: nil, code: nil, detail: nil, meta: {})
5
+ super message,
6
+ backtrace,
7
+ status: :forbidden,
8
+ title: title || I18n.t('status_code.IER4005_forbidden.title'),
9
+ code: code || I18n.t('status_code.IER4005_forbidden.code'),
10
+ detail: detail || I18n.t('status_code.IER4005_forbidden.detail'),
11
+ meta: meta
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Commons
2
+ module Errors
3
+ class InternalServerError < ErrorBase
4
+ def initialize(message = nil, backtrace = nil, title: nil, code: nil, detail: nil, meta: {})
5
+ super message,
6
+ backtrace,
7
+ status: :internal_server_error,
8
+ title: title || I18n.t('status_code.IER5000_internal_server_error.title'),
9
+ code: code || I18n.t('status_code.IER5000_internal_server_error.code'),
10
+ detail: detail || I18n.t('status_code.IER5000_internal_server_error.detail'),
11
+ meta: meta
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ module Commons
2
+ module Errors
3
+ class InvalidResource < UnprocessableEntity
4
+ def initialize(message = nil,
5
+ backtrace = nil,
6
+ title: nil,
7
+ code: nil,
8
+ detail: nil,
9
+ validation_errors: nil)
10
+ meta = {}
11
+ meta.merge!(validation_errors: validation_errors) unless validation_errors.blank?
12
+ super message,
13
+ backtrace,
14
+ title: title || I18n.t('status_code.IER4006_invalid_resource.title'),
15
+ code: code || I18n.t('status_code.IER4006_invalid_resource.code'),
16
+ detail: detail || I18n.t('status_code.IER4006_invalid_resource.detail'),
17
+ meta: meta
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module Commons
2
+ module Errors
3
+ class MaintenanceMode < ErrorBase
4
+ def initialize(message = nil, title: nil, code: nil, detail: nil, meta: {})
5
+ super message,
6
+ status: :service_unavailable,
7
+ title: title || I18n.t('status_code.IER5030_maintenance_mode.title'),
8
+ code: code || I18n.t('status_code.IER5030_maintenance_mode.code'),
9
+ detail: detail || I18n.t('status_code.IER5030_maintenance_mode.detail'),
10
+ meta: meta
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module Commons
2
+ module Errors
3
+ class MissingParameter < UnprocessableEntity
4
+ def initialize(message = nil, param: nil)
5
+ meta = {}
6
+ meta.merge!(param: param.to_s.camelize(:lower)) unless param.blank?
7
+ super message,
8
+ detail: I18n.t('status_code.IER4007_missing_parameter.detail'),
9
+ meta: meta
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ module Commons
2
+ module Errors
3
+ class NotUnique < Conflict
4
+ def initialize(message = nil, backtrace = nil, title: nil, code: nil, detail: nil, meta: {})
5
+ super message,
6
+ backtrace,
7
+ title: title || I18n.t('status_code.IER4008_not_unique.title'),
8
+ code: code || I18n.t('status_code.IER4008_not_unique.code'),
9
+ detail: detail || I18n.t('status_code.IER4008_not_unique.detail'),
10
+ meta: meta
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module Commons
2
+ module Errors
3
+ class PaymentRequired < ErrorBase
4
+ def initialize(message = nil, backtrace = nil, title: nil, code: nil, detail: nil, meta: {})
5
+ super message,
6
+ backtrace,
7
+ status: :payment_required,
8
+ title: title || I18n.t('status_code.IER4009_payment_required.title'),
9
+ code: code || I18n.t('status_code.IER4009_payment_required.code'),
10
+ detail: detail || I18n.t('status_code.IER4009_payment_required.detail'),
11
+ meta: meta
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Commons
2
+ module Errors
3
+ class PreconditionFailed < ErrorBase
4
+ def initialize(message = nil, backtrace = nil, title: nil, code: nil, detail: nil, meta: {})
5
+ super message,
6
+ backtrace,
7
+ status: :precondition_failed,
8
+ title: title || I18n.t('status_code.IER4010_precondition_failed.title'),
9
+ code: code || I18n.t('status_code.IER4010_precondition_failed.code'),
10
+ detail: detail || I18n.t('status_code.IER4010_precondition_failed.detail'),
11
+ meta: meta
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module Commons
2
+ module Errors
3
+ class RouteNotFound < ErrorBase
4
+ def initialize(message = nil, title: nil, code: nil, detail: nil, meta: {})
5
+ super message,
6
+ status: :not_found,
7
+ title: title || I18n.t('status_code.IER4001_route_not_found.title'),
8
+ code: code || I18n.t('status_code.IER4001_route_not_found.code'),
9
+ detail: detail || I18n.t('status_code.IER4001_route_not_found.detail'),
10
+ meta: meta
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Commons
2
+ module Errors
3
+ class ResourceNotFound < ErrorBase
4
+ def initialize(message = nil, title: nil, code: nil, detail: nil, meta: {})
5
+ super message,
6
+ status: :not_found,
7
+ title: title || I18n.t('status_code.IER4011_resource_not_found.title'),
8
+ code: code || I18n.t('status_code.IER4011_resource_not_found.code'),
9
+ detail: detail || I18n.t('status_code.IER4011_resource_not_found.detail'),
10
+ meta: meta
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module Commons
2
+ module Errors
3
+ class Unauthorized < ErrorBase
4
+ def initialize(message = nil, backtrace = nil, title: nil, code: nil, detail: nil, meta: {})
5
+ super message,
6
+ backtrace,
7
+ status: :unauthorized,
8
+ code: code || I18n.t('status_code.IER4002_unauthorized.code'),
9
+ title: title || I18n.t('status_code.IER4002_unauthorized.title'),
10
+ detail: detail || I18n.t('status_code.IER4002_unauthorized.detail'),
11
+ meta: meta
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Commons
2
+ module Errors
3
+ class UnprocessableEntity < ErrorBase
4
+ def initialize(message = nil, backtrace = nil, title: nil, code: nil, detail: nil, meta: {})
5
+ super message,
6
+ backtrace,
7
+ status: :unprocessable_entity,
8
+ title: title || I18n.t('status_code.IER4222_unprocessable_entity.title'),
9
+ code: code || I18n.t('status_code.IER4222_unprocessable_entity.code'),
10
+ detail: detail || I18n.t('status_code.IER4222_unprocessable_entity.detail'),
11
+ meta: meta
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,47 @@
1
+ module Commons
2
+ module Formatter
3
+ class E164Phone
4
+ attr_accessor :valid
5
+
6
+ # @type [String]
7
+ # Mexico ISO Country Code
8
+ MEXICO_ISO_CODE = 'MX'.freeze
9
+ # @type [String]
10
+ # Mexico Phone Country Code
11
+ MEXICO_CC = '52'.freeze
12
+ # @type [String]
13
+ # Mexico National Destination Code
14
+ MEXICO_NDC = ''.freeze
15
+ MEXICO_REGEX = /^(521?\d{10}|\d{10})$/
16
+ NOT_DIGITS_REGEX = /[^0-9]/
17
+
18
+ def initialize(phone_number)
19
+ @phone = Phonelib.parse(phone_number)
20
+ @valid = @phone.valid?
21
+ end
22
+
23
+ def format
24
+ @valid ? @phone.e164 : nil
25
+ end
26
+
27
+ def format_national
28
+ @valid ? @phone.raw_national : nil
29
+ end
30
+
31
+ def country_code
32
+ @valid ? @phone.country_code : nil
33
+ end
34
+
35
+ def validate
36
+ @valid && @phone.country_code == MEXICO_CC
37
+ end
38
+
39
+ def self.canonical_phone(phone_number)
40
+ phone_digits = phone_number.gsub(NOT_DIGITS_REGEX, '')
41
+ return nil unless MEXICO_REGEX.match(phone_digits)
42
+
43
+ phone_digits.split(//).last(10).join
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,9 @@
1
+ module Commons
2
+ module Formatter
3
+ module NullAttributesRemover
4
+ def attributes(*args)
5
+ super.compact
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Commons
2
+ module Formatter
3
+ module RegexConstants
4
+ PROPER_NOUN = /\A\p{L}[\p{L}'\.\-]*( [\p{L}'\.\-]+)*\z/u
5
+ CURP = /\A([A-Z][AEIOUX][A-Z]{2}\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])[HM](?:AS|B[CS]|C[CLMSH]|D[FG]|G[TR]|HG|JC|M[CNS]|N[ETL]|OC|PL|Q[TR]|S[PLR]|T[CSL]|VZ|YN|ZS)[B-DF-HJ-NP-TV-Z]{3}[A-Z\d])(\d)\z/
6
+ RFC = /\A([A-ZÑ\x26]{3,4}(\d{2})(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[0-1])([A-Z0-9]){2}([A0-9]))?\z/i
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Commons
2
+ module Formatter
3
+ module StringUtils
4
+ def self.capitalize(text)
5
+ text.downcase.gsub(/(?!(las?|del?|los?|y)\b)\b\p{L}/, &:upcase)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,30 @@
1
+ module Commons
2
+ module Middleware
3
+ class CatchJsonParseErrors
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ @app.call(env)
10
+ rescue ActionDispatch::Http::Parameters::ParseError => e
11
+ if env['HTTP_ACCEPT'] =~ %r{application/json} ||
12
+ env['CONTENT_TYPE'] =~ %r{application/json}
13
+ str = Commons::Errors::ErrorSerializer.new(Commons::Errors::BadRequest.new(e)).to_json
14
+ str.tr!("'", '\'')
15
+ return [
16
+ '400',
17
+ { 'Content-Type' => 'application/json' },
18
+ [
19
+ '{ "error":'\
20
+ "#{str}"\
21
+ '}'\
22
+ ]
23
+ ]
24
+ else
25
+ raise e
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,4 @@
1
+ module Commons
2
+ class Railtie < ::Rails::Railtie
3
+ end
4
+ end
@@ -0,0 +1,227 @@
1
+ module Commons
2
+ module Repositories
3
+ class BaseRepository
4
+ include Singleton
5
+
6
+ #
7
+ # Método que devuelve todos los objetos
8
+ #
9
+ # @return [Object,nil]
10
+ #
11
+ def all
12
+ @db_client.all
13
+ end
14
+
15
+ #
16
+ # Método que devuelve todos los objetos que no han sido eliminados
17
+ #
18
+ # @return [Object,nil]
19
+ #
20
+ def kept
21
+ raise ActiveModel::MissingAttributeError unless @db_client.column_names.include? "deleted_at"
22
+
23
+ @db_client.where(deleted_at: nil)
24
+ end
25
+
26
+ #
27
+ # Método que devuelve todos los objetos que han sido eliminados
28
+ #
29
+ # @return [Object,nil]
30
+ #
31
+ def deleted
32
+ raise ActiveModel::MissingAttributeError unless @db_client.column_names.include? "deleted_at"
33
+
34
+ @db_client.where.not(deleted_at: nil)
35
+ end
36
+
37
+ #
38
+ # Método que devuelve el objeto según su ID
39
+ #
40
+ # @return [Object,nil]
41
+ #
42
+ def find(id)
43
+ @db_client.find(id)
44
+ end
45
+
46
+ #
47
+ # Método que devuelve el objeto según su ID
48
+ #
49
+ # @return [Object,nil]
50
+ #
51
+ def find_kept(id)
52
+ raise ActiveModel::MissingAttributeError unless @db_client.column_names.include? "deleted_at"
53
+
54
+ @db_client.find_by!(id: id, deleted_at: nil)
55
+ end
56
+
57
+ #
58
+ # Método que devuelve el objeto según parámetros
59
+ #
60
+ # @return [Object,nil]
61
+ #
62
+ def find_by(params)
63
+ @db_client.find_by(params)
64
+ end
65
+
66
+ #
67
+ # Método que devuelve el objeto según parámetros
68
+ #
69
+ # @return [Object,nil]
70
+ #
71
+ def find_kept_by(params)
72
+ raise ActiveModel::MissingAttributeError unless @db_client.column_names.include? "deleted_at"
73
+
74
+ @db_client.find_by(
75
+ deleted_at: nil,
76
+ **params
77
+ )
78
+ end
79
+
80
+ #
81
+ # Método que devuelve el objeto según parámetros
82
+ #
83
+ # @return [Object]
84
+ #
85
+ # @raise [ActiveRecord::RecordNotFound]
86
+ #
87
+ def find_by!(params)
88
+ @db_client.find_by!(params)
89
+ end
90
+
91
+ #
92
+ # Método que devuelve el objeto según parámetros
93
+ #
94
+ # @return [Object]
95
+ #
96
+ # @raise [ActiveRecord::RecordNotFound]
97
+ #
98
+ def find_kept_by!(params)
99
+ raise ActiveModel::MissingAttributeError unless @db_client.column_names.include? "deleted_at"
100
+
101
+ @db_client.find_by!(
102
+ deleted_at: nil,
103
+ **params
104
+ )
105
+ end
106
+
107
+ #
108
+ # Método que realiza un guardado de un objeto
109
+ #
110
+ # @param [Object]
111
+ #
112
+ # @return [Boolean]
113
+ #
114
+ # @raise [ActiveRecord::RecordInvalid]
115
+ # @raise [ActiveRecord::RecordNotSaved]
116
+ #
117
+ def create!(object)
118
+ raise ArgumentError unless object.is_a? @db_client
119
+
120
+ object.save!
121
+ end
122
+
123
+ #
124
+ # Método que realiza un guardado de un objeto
125
+ #
126
+ # @param [Array<Hash>] params Listado de parámetros del objeto
127
+ #
128
+ # @return [Object] Objeto creado
129
+ #
130
+ # @raises [ActiveRecord::RecordInvalid]
131
+ #
132
+ def create_from_params!(**params)
133
+ @db_client.create!(**params)
134
+ end
135
+
136
+ #
137
+ # Método que realiza una busqueda o guardado de un objeto
138
+ #
139
+ # @param [Array<Hash>] params Listado de parámetros del objeto
140
+ # @param [block] block
141
+ #
142
+ # @return [Object] Objeto creado
143
+ #
144
+ # @raises [ActiveRecord::RecordInvalid]
145
+ #
146
+ def find_or_create_by!(params, &block)
147
+ object = @db_client.find_by(params) || @db_client.create!(params, &block)
148
+ object
149
+ end
150
+
151
+ #
152
+ # Método que realiza una busqueda de la primer entrada,
153
+ # en caso de no encontrarlo crea un objeto con el bloque
154
+ #
155
+ # @param [Array<Hash>] params Listado de parámetros del objeto
156
+ # @param [block] block
157
+ #
158
+ # @return [Object] Objeto creado
159
+ #
160
+ # @raises [ActiveRecord::RecordInvalid]
161
+ #
162
+ def where_first_or_create!(params, attributes = nil, &block)
163
+ @db_client.where(params).first_or_create!(attributes, &block)
164
+ end
165
+
166
+ #
167
+ # Método que guarda los cambios realizados a una instancia del objeto
168
+ #
169
+ # @param [Object]
170
+ #
171
+ # @return [Boolean]
172
+ #
173
+ # @raise [ActiveRecord::RecordInvalid]
174
+ # @raise [ActiveRecord::RecordNotSaved]
175
+ #
176
+ def update!(object)
177
+ create!(object)
178
+ end
179
+
180
+ #
181
+ # Método que realiza un guardado de un objeto
182
+ #
183
+ # @param [Array<Hash>] params Listado de parámetros del objeto
184
+ #
185
+ # @return [Object] Objeto creado
186
+ #
187
+ # @raises [ActiveRecord::RecordInvalid]
188
+ #
189
+ def update_from_params!(id:, **params)
190
+ object = @db_client.find_by!(id: id)
191
+ object.update!(params)
192
+
193
+ object
194
+ end
195
+
196
+ #
197
+ # Método que realiza un borrado lógico de un objeto
198
+ #
199
+ # @param [UUID] id Identificador del objeto
200
+ #
201
+ # @return [Object] Objeto eliminado
202
+ #
203
+ # @raises [ActiveRecord::RecordInvalid]
204
+ # @raises [ActiveModel::MissingAttributeError]
205
+ #
206
+ def soft_delete!(id)
207
+ raise ActiveModel::MissingAttributeError unless @db_client.column_names.include? "deleted_at"
208
+
209
+ object = @db_client.find_by!(id: id, deleted_at: nil)
210
+ object.update!(deleted_at: Time.current)
211
+
212
+ object
213
+ end
214
+
215
+ private
216
+
217
+ def initialize
218
+ @db_client ||= class_object
219
+ end
220
+
221
+ def class_object
222
+ model_name = self.class.to_s.gsub("Repository", "")
223
+ Object.const_get model_name
224
+ end
225
+ end
226
+ end
227
+ end