sp-duh 2.0.6

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