sp-duh 2.0.6

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 (56) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +661 -0
  3. data/README.md +2 -0
  4. data/Rakefile +32 -0
  5. data/config/i18n/i18n.xlsx +0 -0
  6. data/config/initializers/active_record/connection_adapters_postgre_sql_adapter.rb +165 -0
  7. data/config/initializers/active_record/migration_without_transaction.rb +4 -0
  8. data/config/initializers/active_record/migrator.rb +34 -0
  9. data/config/initializers/rails/generators.rb +13 -0
  10. data/config/jsonapi/settings.yml +14 -0
  11. data/config/locales/pt.yml +15 -0
  12. data/lib/generators/accounting_migration/accounting_migration_generator.rb +10 -0
  13. data/lib/generators/accounting_migration/templates/migration.rb +42 -0
  14. data/lib/generators/accounting_payroll_migration/accounting_payroll_migration_generator.rb +10 -0
  15. data/lib/generators/accounting_payroll_migration/templates/migration.rb +73 -0
  16. data/lib/generators/sharded_migration/sharded_migration_generator.rb +10 -0
  17. data/lib/generators/sharded_migration/templates/migration.rb +45 -0
  18. data/lib/sp-duh.rb +32 -0
  19. data/lib/sp/duh.rb +180 -0
  20. data/lib/sp/duh/adapters/pg/text_decoder/json.rb +15 -0
  21. data/lib/sp/duh/adapters/pg/text_encoder/json.rb +15 -0
  22. data/lib/sp/duh/db/transfer/backup.rb +71 -0
  23. data/lib/sp/duh/db/transfer/restore.rb +89 -0
  24. data/lib/sp/duh/engine.rb +35 -0
  25. data/lib/sp/duh/exceptions.rb +70 -0
  26. data/lib/sp/duh/i18n/excel_loader.rb +26 -0
  27. data/lib/sp/duh/jsonapi/adapters/base.rb +168 -0
  28. data/lib/sp/duh/jsonapi/adapters/db.rb +36 -0
  29. data/lib/sp/duh/jsonapi/adapters/raw_db.rb +77 -0
  30. data/lib/sp/duh/jsonapi/configuration.rb +167 -0
  31. data/lib/sp/duh/jsonapi/doc/apidoc_documentation_format_generator.rb +286 -0
  32. data/lib/sp/duh/jsonapi/doc/generator.rb +32 -0
  33. data/lib/sp/duh/jsonapi/doc/schema_catalog_helper.rb +97 -0
  34. data/lib/sp/duh/jsonapi/doc/victor_pinus_metadata_format_parser.rb +374 -0
  35. data/lib/sp/duh/jsonapi/exceptions.rb +56 -0
  36. data/lib/sp/duh/jsonapi/model/base.rb +25 -0
  37. data/lib/sp/duh/jsonapi/model/concerns/attributes.rb +94 -0
  38. data/lib/sp/duh/jsonapi/model/concerns/model.rb +42 -0
  39. data/lib/sp/duh/jsonapi/model/concerns/persistence.rb +221 -0
  40. data/lib/sp/duh/jsonapi/model/concerns/serialization.rb +59 -0
  41. data/lib/sp/duh/jsonapi/parameters.rb +44 -0
  42. data/lib/sp/duh/jsonapi/resource_publisher.rb +28 -0
  43. data/lib/sp/duh/jsonapi/service.rb +110 -0
  44. data/lib/sp/duh/migrations.rb +47 -0
  45. data/lib/sp/duh/migrations/migrator.rb +41 -0
  46. data/lib/sp/duh/repl.rb +193 -0
  47. data/lib/sp/duh/version.rb +25 -0
  48. data/lib/tasks/db_utils.rake +98 -0
  49. data/lib/tasks/doc.rake +27 -0
  50. data/lib/tasks/i18n.rake +23 -0
  51. data/lib/tasks/oauth.rake +29 -0
  52. data/lib/tasks/transfer.rake +48 -0
  53. data/lib/tasks/xls2jrxml.rake +15 -0
  54. data/test/jsonapi/server.rb +67 -0
  55. data/test/tasks/test.rake +10 -0
  56. metadata +170 -0
@@ -0,0 +1,26 @@
1
+ require 'sp-excel-loader'
2
+
3
+ module SP
4
+ module Duh
5
+ module I18n
6
+
7
+ class ExcelLoader < ::Sp::Excel::Loader::WorkbookLoader
8
+
9
+ def initialize(filename, pg_connection)
10
+ super(filename)
11
+ @connection = pg_connection
12
+ end
13
+
14
+ def clear
15
+ @connection.exec "DELETE FROM public.i18n"
16
+ end
17
+
18
+ def reload
19
+ export_table_to_pg(@connection, 'public', '', 'i18n')
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,168 @@
1
+ module SP
2
+ module Duh
3
+ module JSONAPI
4
+ module Adapters
5
+
6
+ class Base
7
+
8
+ def service ; @service ; end
9
+
10
+ def initialize(service)
11
+ @service = service
12
+ end
13
+
14
+ def get(path, params = {})
15
+ request('GET', path, params)
16
+ end
17
+ def post(path, params = {})
18
+ request('POST', path, params)
19
+ end
20
+ def patch(path, params = {})
21
+ request('PATCH', path, params)
22
+ end
23
+ def delete(path)
24
+ request('DELETE', path, nil)
25
+ end
26
+
27
+ def get!(path, params = {})
28
+ request!('GET', path, params)
29
+ end
30
+ def post!(path, params = {})
31
+ request!('POST', path, params)
32
+ end
33
+ def patch!(path, params = {})
34
+ request!('PATCH', path, params)
35
+ end
36
+ def delete!(path)
37
+ request!('DELETE', path, nil)
38
+ end
39
+
40
+ def get_explicit!(exp_accounting_schema, exp_accounting_prefix, path, params = {})
41
+ explicit_request!(exp_accounting_schema, exp_accounting_prefix, 'GET', path, params)
42
+ end
43
+ def post_explicit!(exp_accounting_schema, exp_accounting_prefix, path, params = {})
44
+ explicit_request!(exp_accounting_schema, exp_accounting_prefix, 'POST', path, params)
45
+ end
46
+ def patch_explicit!(exp_accounting_schema, exp_accounting_prefix, path, params = {})
47
+ explicit_request!(exp_accounting_schema, exp_accounting_prefix, 'PATCH', path, params)
48
+ end
49
+ def delete_explicit!(exp_accounting_schema, exp_accounting_prefix, path)
50
+ explicit_request!(exp_accounting_schema, exp_accounting_prefix, 'DELETE', path, nil)
51
+ end
52
+
53
+ alias_method :put, :patch
54
+ alias_method :put!, :patch!
55
+ alias_method :put_explicit!, :patch_explicit!
56
+
57
+ def unwrap_request
58
+ unwrap_response(yield)
59
+ end
60
+
61
+ # do_request MUST be implemented by each specialized adapter, and returns a tuple: the request status and a JSONAPI string or hash with the result
62
+ def do_request(method, path, params) ; ; end
63
+ def explicit_do_request(exp_accounting_schema, exp_accounting_prefix, method, path, params) ; ; end
64
+
65
+ def request(method, path, params)
66
+ # As it is now, this method is EXACTLY the same as request!()
67
+ # And it cannot be reverted without affecting lots of changes already made in the app's controllers.
68
+ # TODO: end it, or end the !() version
69
+ # begin
70
+ unwrap_request do
71
+ do_request(method, path, params)
72
+ end
73
+ # THIS CAN'T BE DONE, because the same method cannot return both a single result (in case there is NOT an error) and a pair (in case there IS an error)
74
+ # rescue SP::Duh::JSONAPI::Exceptions::GenericModelError => e
75
+ # [
76
+ # e.status,
77
+ # e.result
78
+ # ]
79
+ # rescue Exception => e
80
+ # [
81
+ # SP::Duh::JSONAPI::Status::ERROR,
82
+ # get_error_response(path, e)
83
+ # ]
84
+ # end
85
+ end
86
+
87
+ def request!(method, path, params)
88
+ unwrap_request do
89
+ do_request(method, path, params)
90
+ end
91
+ end
92
+
93
+ def explicit_request!(exp_accounting_schema, exp_accounting_prefix, method, path, params)
94
+ unwrap_request do
95
+ explicit_do_request(exp_accounting_schema, exp_accounting_prefix, method, path, params)
96
+ end
97
+ end
98
+
99
+ protected
100
+
101
+ def url(path) ; File.join(service.url, path) ; end
102
+
103
+ def url_with_params_for_query(path, params)
104
+ query = params_for_query(params)
105
+ query.blank? ? url(path) : url(path) + "?" + query
106
+ end
107
+
108
+ def params_for_query(params)
109
+ query = ""
110
+ if !params.blank?
111
+ case
112
+ when params.is_a?(Array)
113
+ # query = params.join('&')
114
+ query = params.map{ |v| URI.encode(URI.encode(v).gsub("'","''"), "&") }.join('&')
115
+ when params.is_a?(Hash)
116
+ query = params.map do |k,v|
117
+ if v.is_a?(String)
118
+ "#{k}=\"#{URI.encode(URI.encode(v).gsub("'","''"), "&")}\""
119
+ else
120
+ "#{k}=#{v}"
121
+ end
122
+ end.join('&')
123
+ else
124
+ query = params.to_s
125
+ end
126
+ end
127
+ query
128
+ end
129
+
130
+ def params_for_body(params)
131
+ params.blank? ? '' : params.to_json.gsub("'","''")
132
+ end
133
+
134
+ # unwrap_response SHOULD be implemented by each specialized adapter, and returns the request result as a JSONAPI string or hash and raises an exception if there was an error
135
+ def unwrap_response(response)
136
+ # As the method request() is EXACTLY the same as request!(), and it cannot be reverted without affecting lots of changes already made in the app's controllers...
137
+ # Allow for response being both a [ status, result ] pair (as of old) OR a single result (as of now)
138
+ if response.is_a?(Array)
139
+ status = response[0].to_i
140
+ result = response[1]
141
+ result
142
+ else
143
+ response
144
+ end
145
+ end
146
+
147
+ def error_response(path, error)
148
+ {
149
+ errors: [
150
+ {
151
+ status: "#{SP::Duh::JSONAPI::Status::ERROR}",
152
+ code: error.message
153
+ }
154
+ ],
155
+ links: { self: url(path) },
156
+ jsonapi: { version: SP::Duh::JSONAPI::VERSION }
157
+ }
158
+ end
159
+
160
+ # get_error_response MUST be implemented by each specialized adapter, and returns a JSONAPI error result as a string or hash
161
+ def get_error_response(path, error) ; ; end
162
+
163
+ end
164
+
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,36 @@
1
+ module SP
2
+ module Duh
3
+ module JSONAPI
4
+ module Adapters
5
+
6
+ class Db < RawDb
7
+
8
+ protected
9
+
10
+ def get_error_response(path, error) ; HashWithIndifferentAccess.new(error_response(path, error)) ; end
11
+
12
+ def do_request(method, path, params)
13
+ process_result(do_request_on_the_db(method, path, params))
14
+ end
15
+
16
+ def explicit_do_request(exp_accounting_schema, exp_accounting_prefix, method, path, params)
17
+ process_result(explicit_do_request_on_the_db(exp_accounting_schema, exp_accounting_prefix, method, path, params))
18
+ end
19
+
20
+ private
21
+
22
+ def is_error?(result) ; !result[:errors].blank? ; end
23
+
24
+ def process_result(result)
25
+ result = HashWithIndifferentAccess.new(result)
26
+ result[:response] = JSON.parse(result[:response])
27
+ raise SP::Duh::JSONAPI::Exceptions::GenericModelError.new(result[:response]) if is_error?(result[:response])
28
+ [ result[:http_status], result[:response] ]
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,77 @@
1
+ module SP
2
+ module Duh
3
+ module JSONAPI
4
+ module Adapters
5
+
6
+ class RawDb < Base
7
+
8
+ protected
9
+
10
+ def unwrap_response(response)
11
+ # As the method request() is EXACTLY the same as request!(), and it cannot be reverted without affecting lots of changes already made in the app's controllers...
12
+ # Allow for response being both a [ status, result ] pair (as of old) OR a single result (as of now)
13
+ if response.is_a?(Array)
14
+ status = response[0].to_i
15
+ result = response[1]
16
+ raise SP::Duh::JSONAPI::Exceptions::GenericModelError.new(result) if status != SP::Duh::JSONAPI::Status::OK
17
+ result
18
+ else
19
+ # No raise here, we do not know the status...
20
+ response
21
+ end
22
+ end
23
+
24
+ def get_error_response(path, error) ; error_response(path, error).to_json ; end
25
+
26
+ def do_request(method, path, param)
27
+ process_result(do_request_on_the_db(method, path, params))
28
+ end
29
+
30
+ def explicit_do_request(accounting_schema, method, path, param)
31
+ process_result(explicit_do_request_on_the_db(accounting_schema, method, path, params))
32
+ end
33
+
34
+ private
35
+ def user_id ; "'#{service.parameters.user_id}'" ; end
36
+ def company_id ; "'#{service.parameters.company_id}'" ; end
37
+ def company_schema ; service.parameters.company_schema.nil? ? 'NULL' : "'#{service.parameters.company_schema}'" ; end
38
+ def sharded_schema ; service.parameters.sharded_schema.nil? ? 'NULL' : "'#{service.parameters.sharded_schema}'" ; end
39
+ def accounting_schema ; service.parameters.accounting_schema.nil? ? 'NULL' : "'#{service.parameters.accounting_schema}'" ; end
40
+ def accounting_prefix ; service.parameters.accounting_prefix.nil? ? 'NULL' : "'#{service.parameters.accounting_prefix}'" ; end
41
+
42
+ def process_result(result)
43
+ raise SP::Duh::JSONAPI::Exceptions::GenericModelError.new(result) if is_error?(result)
44
+ [ SP::Duh::JSONAPI::Status::OK, result ]
45
+ end
46
+
47
+ # Implement the JSONAPI request by direct querying of the JSONAPI function in the database
48
+ def do_request_on_the_db(method, path, params)
49
+ jsonapi_query = if method == 'GET'
50
+ %Q[ SELECT * FROM public.jsonapi('#{method}', '#{url_with_params_for_query(path, params)}', '', #{user_id}, #{company_id}, #{company_schema}, #{sharded_schema}, #{accounting_schema}, #{accounting_prefix}) ]
51
+ else
52
+ %Q[ SELECT * FROM public.jsonapi('#{method}', '#{url(path)}', '#{params_for_body(params)}', #{user_id}, #{company_id}, #{company_schema}, #{sharded_schema}, #{accounting_schema}, #{accounting_prefix}) ]
53
+ end
54
+ response = service.connection.exec jsonapi_query
55
+ response.first if response.first
56
+ end
57
+
58
+ def explicit_do_request_on_the_db(exp_accounting_schema, exp_accounting_prefix, method, path, params)
59
+ _accounting_schema = "'#{exp_accounting_schema}'"
60
+ _accounting_prefix = "'#{exp_accounting_prefix}'"
61
+
62
+ jsonapi_query = if method == 'GET'
63
+ %Q[ SELECT * FROM public.jsonapi('#{method}', '#{url_with_params_for_query(path, params)}', '', #{user_id}, #{company_id}, #{company_schema}, #{sharded_schema}, #{_accounting_schema}, #{_accounting_prefix}) ]
64
+ else
65
+ %Q[ SELECT * FROM public.jsonapi('#{method}', '#{url(path)}', '#{params_for_body(params)}', #{user_id}, #{company_id}, #{company_schema}, #{sharded_schema}, #{_accounting_schema}, #{_accounting_prefix}) ]
66
+ end
67
+ response = service.connection.exec jsonapi_query
68
+ response.first if response.first
69
+ end
70
+
71
+ def is_error?(result) ; result =~ /^\s*{\s*"errors"\s*:/ ; end
72
+ end
73
+
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,167 @@
1
+ module SP
2
+ module Duh
3
+ module JSONAPI
4
+
5
+ class Configuration
6
+
7
+ CONFIGURATION_TABLE_NAME = 'public.jsonapi_config'
8
+ DEFAULT_SETTINGS_FILE = 'config/jsonapi/settings.yml'
9
+
10
+ @@publishers = []
11
+
12
+ def settings
13
+ @settings ||= {}
14
+ if @settings.blank?
15
+ load_settings_from_file(File.join(SP::Duh.root, DEFAULT_SETTINGS_FILE))
16
+ end
17
+ @settings
18
+ end
19
+
20
+ def settings=(hash)
21
+ @settings = hash
22
+ end
23
+
24
+ def resources ; @resources || [] ; end
25
+ def resource_names ; resources.map { |r| r.keys.first } ; end
26
+ def connection ; @pg_connection ; end
27
+ def url ; @url ; end
28
+
29
+ def publishers ; @@publishers || [] ; end
30
+
31
+ def initialize(pg_connection, url)
32
+ @pg_connection = pg_connection
33
+ @url = url
34
+ end
35
+
36
+ def self.add_publisher(publisher)
37
+ begin
38
+ publisher = publisher.constantize if publisher.is_a?(String)
39
+ raise Exceptions::InvalidResourcePublisherError.new(publisher: publisher.name) if !publisher.include?(ResourcePublisher)
40
+ @@publishers << publisher
41
+ rescue StandardError => e
42
+ raise Exceptions::InvalidResourcePublisherError.new(publisher: publisher.is_a?(String) ? publisher : publisher.name)
43
+ end
44
+ end
45
+
46
+ def setup
47
+ begin
48
+ create_jsonapi_configuration_store()
49
+ rescue StandardError => e
50
+ raise Exceptions::GenericServiceError.new(e)
51
+ end
52
+ end
53
+
54
+ def exists?
55
+ check = connection.exec %Q[ SELECT COUNT(*) FROM #{Configuration::CONFIGURATION_TABLE_NAME} WHERE prefix = '#{url}' ]
56
+ return check.first.values.first.to_i > 0
57
+ end
58
+
59
+ def load_from_database
60
+ @resources = []
61
+ @settings = {}
62
+ configuration = connection.exec %Q[ SELECT config FROM #{Configuration::CONFIGURATION_TABLE_NAME} WHERE prefix = '#{url}' ]
63
+ if configuration.first
64
+ configuration = JSON.parse(configuration.first['config'])
65
+ @resources = configuration['resources']
66
+ @settings = configuration.reject { |k,v| k == 'resources' }
67
+ end
68
+ @resources
69
+ end
70
+
71
+ def load_from_publishers(replace = false)
72
+ @resources = []
73
+ @settings = {}
74
+ @@publishers.each do |publisher|
75
+ add_resources_from_folder(publisher.jsonapi_resources_root, replace)
76
+ end
77
+ @resources
78
+ end
79
+
80
+ def save
81
+ begin
82
+ if exists?
83
+ connection.exec %Q[
84
+ UPDATE #{Configuration::CONFIGURATION_TABLE_NAME} SET config='#{definition.to_json}' WHERE prefix='#{url}';
85
+ ]
86
+ else
87
+ connection.exec %Q[
88
+ INSERT INTO #{Configuration::CONFIGURATION_TABLE_NAME} (prefix, config) VALUES ('#{url}','#{definition.to_json}');
89
+ ]
90
+ end
91
+ rescue StandardError => e
92
+ raise Exceptions::SaveConfigurationError.new(nil, e)
93
+ end
94
+ end
95
+
96
+ def reload!
97
+ load_from_publishers(true)
98
+ save
99
+ @resources
100
+ end
101
+
102
+ def load_settings_from_file(file_name)
103
+ @settings = YAML.load_file(file_name)
104
+ end
105
+
106
+ private
107
+
108
+ def create_jsonapi_configuration_store
109
+ connection.exec %Q[
110
+ CREATE TABLE IF NOT EXISTS #{Configuration::CONFIGURATION_TABLE_NAME} (
111
+ prefix varchar(64) PRIMARY KEY,
112
+ config text NOT NULL
113
+ );
114
+ ]
115
+ end
116
+
117
+ def definition
118
+ settings.merge(resources: resources)
119
+ end
120
+
121
+ def add_resources_from_folder(folder_name, replace)
122
+ @resources ||= []
123
+ # First load resources at the root folder
124
+ Dir.glob(File.join(folder_name, '*.yml')) do |configuration_file|
125
+ add_resources_from_file(configuration_file, replace)
126
+ end
127
+ # Then load resources at the inner folders
128
+ Dir.glob(File.join(folder_name, '*', '*.yml')) do |configuration_file|
129
+ add_resources_from_file(configuration_file, replace)
130
+ end
131
+ @resources
132
+ end
133
+
134
+ def add_resources_from_file(configuration_file, replace)
135
+ _log "Processing resources from file #{configuration_file}", "JSONAPI::Configuration"
136
+ configuration = YAML.load_file(configuration_file)
137
+ if configuration.is_a? Hash
138
+ add_resource(configuration, configuration_file, replace)
139
+ else
140
+ if configuration.is_a? Array
141
+ configuration.each { |resource| add_resource(resource, configuration_file, replace) }
142
+ else
143
+ raise Exceptions::InvalidResourceConfigurationError.new(file: configuration_file)
144
+ end
145
+ end
146
+ end
147
+
148
+ def add_resource(resource, configuration_file, replace)
149
+ raise Exceptions::InvalidResourceConfigurationError.new(file: configuration_file) if (resource.keys.count != 1)
150
+ resource_name = resource.keys[0]
151
+ _log "Processing resource #{resource_name}", "JSONAPI::Configuration"
152
+ processed = false
153
+ @resources.each_with_index do |r, i|
154
+ if r.keys.include?(resource_name)
155
+ raise Exceptions::DuplicateResourceError.new(name: resource_name) if !replace
156
+ @resources[i] = resource
157
+ processed = true
158
+ break
159
+ end
160
+ end
161
+ @resources << resource if !processed
162
+ end
163
+ end
164
+
165
+ end
166
+ end
167
+ end