ecoportal-api-graphql 1.3.11 → 1.3.13

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/.ai-assistance/code/filter_contract_matrix.md +177 -0
  3. data/.ai-assistance/projects/compat-layer-audit/COMPAT_AUDIT.md +244 -0
  4. data/.ai-assistance/projects/template-automatic-build-maintenance/INTENT.md +10 -0
  5. data/.ai-assistance/projects/template-automatic-build-maintenance/TODO.md +11 -0
  6. data/.ai-assistance/projects/template-model-logic/CATEGORY_CATALOG.md +236 -0
  7. data/.ai-assistance/projects/template-model-logic/CLASSIFICATION_SPIKE.md +243 -0
  8. data/.ai-assistance/projects/template-model-logic/DESIGN_NOTE.md +154 -0
  9. data/.ai-assistance/scripts/bridge-inbox-check.sh +75 -0
  10. data/.ai-assistance/skills/procedural-memory/SKILL.md +319 -0
  11. data/.ai-assistance/skills/project-self-docs/SKILL.md +181 -0
  12. data/.ai-assistance/skills/project-self-docs/scripts/self_docs_scan.py +378 -0
  13. data/.ai-assistance/standards-version.json +22 -21
  14. data/.claude/settings.json +150 -146
  15. data/.gitlab-ci.yml +45 -0
  16. data/CHANGELOG.md +58 -0
  17. data/CLAUDE.md +11 -0
  18. data/docs/self-docs/ARCHITECTURE.md +88 -0
  19. data/docs/self-docs/CHANGES.jsonl +12 -0
  20. data/docs/self-docs/COMPLIANCE.md +79 -0
  21. data/docs/self-docs/CONVENTIONS.md +74 -0
  22. data/docs/self-docs/INTEGRATIONS.md +65 -0
  23. data/docs/self-docs/OPERATIONS.md +76 -0
  24. data/docs/self-docs/OVERVIEW.md +64 -0
  25. data/docs/self-docs/STATUS.md +73 -0
  26. data/docs/self-docs/self-docs-index.json +51 -0
  27. data/docs/worklog.md +15 -20
  28. data/lib/ecoportal/api/common/graphql/client.rb +2 -0
  29. data/lib/ecoportal/api/common/graphql/http_client.rb +189 -177
  30. data/lib/ecoportal/api/common/graphql/model/diffable.rb +54 -54
  31. data/lib/ecoportal/api/graphql/base/action.rb +43 -43
  32. data/lib/ecoportal/api/graphql/base/contractor_entity/member_changes.rb +67 -67
  33. data/lib/ecoportal/api/graphql/base/page/data_field/collection.rb +7 -0
  34. data/lib/ecoportal/api/graphql/base/page/data_field/contractor_entities.rb +28 -28
  35. data/lib/ecoportal/api/graphql/base/page/data_field/file_field.rb +25 -25
  36. data/lib/ecoportal/api/graphql/base/page/data_field/image_gallery.rb +24 -24
  37. data/lib/ecoportal/api/graphql/base/page/section_collection.rb +85 -79
  38. data/lib/ecoportal/api/graphql/base/preset_view.rb +17 -17
  39. data/lib/ecoportal/api/graphql/base/register.rb +18 -18
  40. data/lib/ecoportal/api/graphql/compat/filter_translator.rb +63 -28
  41. data/lib/ecoportal/api/graphql/concerns/page_compat.rb +51 -51
  42. data/lib/ecoportal/api/graphql/concerns.rb +14 -14
  43. data/lib/ecoportal/api/graphql/file_upload/client.rb +181 -181
  44. data/lib/ecoportal/api/graphql/fragment/page.rb +85 -85
  45. data/lib/ecoportal/api/graphql/input/action/update.rb +14 -14
  46. data/lib/ecoportal/api/graphql/input/contractor_entity/update.rb +41 -41
  47. data/lib/ecoportal/api/graphql/input/location_structure/apply_commands.rb +47 -47
  48. data/lib/ecoportal/api/graphql/input/location_structure/draft/add_commands.rb +49 -49
  49. data/lib/ecoportal/api/graphql/input/location_structure/update_command.rb +27 -27
  50. data/lib/ecoportal/api/graphql/input/search_conf.rb +436 -367
  51. data/lib/ecoportal/api/graphql/interface/location_structure/command.rb +30 -30
  52. data/lib/ecoportal/api/graphql/logic/input.rb +26 -26
  53. data/lib/ecoportal/api/graphql_version.rb +1 -1
  54. metadata +20 -1
@@ -1,177 +1,189 @@
1
- module Ecoportal
2
- module API
3
- module Common
4
- module GraphQL
5
- class ResponseError < StandardError; end
6
-
7
- class HttpClient < Common::Client
8
- class << self
9
- def base_url(host)
10
- "#{protocol(host)}://#{host}"
11
- end
12
-
13
- def protocol(host)
14
- host.match(/^localhost|^127\.0\.0\.1/)? 'http' : 'https'
15
- end
16
- end
17
-
18
- ENDPOINT_PATH = 'external/graphql'.freeze
19
- READ_TIMEOUT = 90
20
- WRITE_TIMEOUT = 90
21
-
22
- attr_reader :host, :version
23
-
24
- include Ecoportal::API::Common::GraphQL::AuthService
25
-
26
- def initialize(
27
- email: nil,
28
- pass: nil,
29
- org_id: nil,
30
- api_key: nil,
31
- version: 'graphql',
32
- host: 'live.ecoportal.com',
33
- logger: ::Logger.new(IO::NULL),
34
- deep_logging: false,
35
- no_schema: true
36
- )
37
- @org_id = org_id
38
- @user_email = email
39
- @user_pass = pass
40
- @no_schema = no_schema
41
- @version = version
42
-
43
- super(
44
- api_key: api_key,
45
- host: host,
46
- logger: logger,
47
- deep_logging: deep_logging
48
- )
49
- end
50
-
51
- # Creates a HTTP object adding the `X-ApiKey` or `X-ECOPORTAL-API-KEY` param to the header, depending on the API version.
52
- # @note It configures HTTP so it only allows body data in json format.
53
- # @return [HTTP] HTTP object.
54
- def base_request
55
- @base_request ||=
56
- case @version
57
- when NilClass, 'http'
58
- HTTP
59
- when 'v1', 'v0'
60
- HTTP.headers('X-ApiKey' => key_token)
61
- when 'v2', 'v3'
62
- HTTP.headers('X-ECOPORTAL-API-KEY' => key_token)
63
- when 'graphql'
64
- HTTP.headers('Authorization' => "Bearer #{session_token(host: host)}")
65
- end.then do |request|
66
- request ||= HTTP
67
- request.accept(:json).timeout(
68
- read: READ_TIMEOUT,
69
- write: WRITE_TIMEOUT
70
- )
71
- end
72
- end
73
-
74
- def org_id
75
- @org_id || fetch_env_required('ORGANIZATION_ID')
76
- end
77
-
78
- # Executes a GraphQL query or mutation over HTTP.
79
- # @param query_string [String] full GraphQL query document.
80
- # @param variables [Hash] variable values to pass to the operation.
81
- # @param operation_name [String, nil] when the document has multiple operations.
82
- # @return [Hash] parsed response body (string-keyed, includes 'data' and optionally 'errors').
83
- # @raise [Ecoportal::API::Common::Client::Error::UnexpectedServerError] on non-2xx HTTP status.
84
- # @raise [Ecoportal::API::Common::GraphQL::ResponseError] when the response body contains GraphQL errors.
85
- def execute(query_string, variables: {}, operation_name: nil)
86
- body = { query: query_string, variables: variables }
87
- body[:operationName] = operation_name if operation_name
88
- response = base_request.post(url_for(''), json: body)
89
- handle_http_response(response)
90
- end
91
-
92
- # Full URl builder of the request
93
- # @param path [String] the tail that completes the url of the request.
94
- # @return [String] the final url.
95
- def url_for(path)
96
- "#{api_url}#{path}"
97
- end
98
-
99
- private
100
-
101
- def key_token
102
- return if http?
103
- return session_token(host: host) if graphql?
104
- return @api_key if api_key?
105
- return ENV['ORG_INT_KEY'] if v0?
106
-
107
- puts 'Api-key missing!'
108
- end
109
-
110
- def api_url
111
- base_url.then do |url|
112
- next "#{url}/api/#{version}" if v1_0? || v2_3?
113
- next "#{url}/api/#{org_id}/#{ENDPOINT_PATH}" if graphql?
114
-
115
- url
116
- end
117
- end
118
-
119
- def base_url
120
- @base_url ||= self.class.base_url(host)
121
- end
122
-
123
- def protocol
124
- @protocol ||= self.class.protocol(host)
125
- end
126
-
127
- def http?
128
- return true if @version.nil?
129
-
130
- @version == 'http'
131
- end
132
-
133
- def api_key?
134
- @api_key && !@api_key.match(/\A\W*\z/)
135
- end
136
-
137
- def v1_0?
138
- v1? || v0?
139
- end
140
-
141
- def v1?
142
- @version == 'v1'
143
- end
144
-
145
- def v0?
146
- @version == 'v0'
147
- end
148
-
149
- def v2_3?
150
- return true if @version == 'v2'
151
- return true if @version == 'v3'
152
-
153
- false
154
- end
155
-
156
- def graphql?
157
- @version == 'graphql'
158
- end
159
-
160
- def handle_http_response(response)
161
- unless response.status.success?
162
- raise Ecoportal::API::Common::Client::Error::UnexpectedServerError.new(
163
- response.body.to_s, code: response.status.code
164
- )
165
- end
166
-
167
- parsed = response.parse(:json)
168
- errors = parsed['errors']
169
- raise ResponseError, errors.map { |e| e['message'] }.join('; ') if errors&.any?
170
-
171
- parsed
172
- end
173
- end
174
- end
175
- end
176
- end
177
- end
1
+ module Ecoportal
2
+ module API
3
+ module Common
4
+ module GraphQL
5
+ class ResponseError < StandardError; end
6
+
7
+ class HttpClient < Common::Client
8
+ class << self
9
+ def base_url(host)
10
+ "#{protocol(host)}://#{host}"
11
+ end
12
+
13
+ def protocol(host)
14
+ host.match(/^localhost|^127\.0\.0\.1/)? 'http' : 'https'
15
+ end
16
+ end
17
+
18
+ ENDPOINT_PATH = 'external/graphql'.freeze
19
+ READ_TIMEOUT = 90
20
+ WRITE_TIMEOUT = 90
21
+
22
+ attr_reader :host, :version
23
+
24
+ include Ecoportal::API::Common::GraphQL::AuthService
25
+
26
+ def initialize(
27
+ email: nil,
28
+ pass: nil,
29
+ org_id: nil,
30
+ api_key: nil,
31
+ version: 'graphql',
32
+ host: 'live.ecoportal.com',
33
+ logger: ::Logger.new(IO::NULL),
34
+ deep_logging: false,
35
+ no_schema: true
36
+ )
37
+ @org_id = org_id
38
+ @user_email = email
39
+ @user_pass = pass
40
+ @no_schema = no_schema
41
+ @version = version
42
+
43
+ super(
44
+ api_key: api_key,
45
+ host: host,
46
+ logger: logger,
47
+ deep_logging: deep_logging
48
+ )
49
+ end
50
+
51
+ # Creates a HTTP object adding the `X-ApiKey` or `X-ECOPORTAL-API-KEY` param to the header, depending on the API version.
52
+ # @note It configures HTTP so it only allows body data in json format.
53
+ # @return [HTTP] HTTP object.
54
+ def base_request
55
+ @base_request ||=
56
+ case @version
57
+ when NilClass, 'http'
58
+ HTTP
59
+ when 'v1', 'v0'
60
+ HTTP.headers('X-ApiKey' => key_token)
61
+ when 'v2', 'v3'
62
+ HTTP.headers('X-ECOPORTAL-API-KEY' => key_token)
63
+ when 'graphql'
64
+ HTTP.headers('Authorization' => "Bearer #{session_token(host: host)}")
65
+ end.then do |request|
66
+ request ||= HTTP
67
+ request.accept(:json).timeout(
68
+ read: READ_TIMEOUT,
69
+ write: WRITE_TIMEOUT
70
+ )
71
+ end
72
+ end
73
+
74
+ def org_id
75
+ @org_id || fetch_env_required('ORGANIZATION_ID')
76
+ end
77
+
78
+ # Executes a GraphQL query or mutation over HTTP.
79
+ # @param query_string [String] full GraphQL query document.
80
+ # @param variables [Hash] variable values to pass to the operation.
81
+ # @param operation_name [String, nil] when the document has multiple operations.
82
+ # @return [Hash] parsed response body (string-keyed, includes 'data' and optionally 'errors').
83
+ # @raise [Ecoportal::API::Common::Client::Error::UnexpectedServerError] on non-2xx HTTP status.
84
+ # @raise [Ecoportal::API::Common::GraphQL::ResponseError] when the response body contains GraphQL errors.
85
+ def execute(query_string, variables: {}, operation_name: nil)
86
+ body = { query: query_string, variables: variables }
87
+ body[:operationName] = operation_name if operation_name
88
+
89
+ # Route through the inherited `Common::Client` pipeline
90
+ # (`instrument` -> `with_retry` { `rate_throttling` }) so GraphQL requests
91
+ # inherit the SAME battle-tested contingencies as the REST clients:
92
+ # rate-limit backoff (429 / Cloudflare 1015), 5xx + connection-error retries,
93
+ # timeouts, and instrumentation. Do NOT re-issue the request raw here — that
94
+ # bypasses all of the above and is what let live runs die on the first 429.
95
+ response = instrument('POST', ENDPOINT_PATH, body) do
96
+ request { |http| http.post(url_for(''), json: body) }
97
+ end
98
+
99
+ handle_http_response(response)
100
+ end
101
+
102
+ # Full URl builder of the request
103
+ # @param path [String] the tail that completes the url of the request.
104
+ # @return [String] the final url.
105
+ def url_for(path)
106
+ "#{api_url}#{path}"
107
+ end
108
+
109
+ private
110
+
111
+ def key_token
112
+ return if http?
113
+ return session_token(host: host) if graphql?
114
+ return @api_key if api_key?
115
+ return ENV['ORG_INT_KEY'] if v0?
116
+
117
+ puts 'Api-key missing!'
118
+ end
119
+
120
+ def api_url
121
+ base_url.then do |url|
122
+ next "#{url}/api/#{version}" if v1_0? || v2_3?
123
+ next "#{url}/api/#{org_id}/#{ENDPOINT_PATH}" if graphql?
124
+
125
+ url
126
+ end
127
+ end
128
+
129
+ def base_url
130
+ @base_url ||= self.class.base_url(host)
131
+ end
132
+
133
+ def protocol
134
+ @protocol ||= self.class.protocol(host)
135
+ end
136
+
137
+ def http?
138
+ return true if @version.nil?
139
+
140
+ @version == 'http'
141
+ end
142
+
143
+ def api_key?
144
+ @api_key && !@api_key.match(/\A\W*\z/)
145
+ end
146
+
147
+ def v1_0?
148
+ v1? || v0?
149
+ end
150
+
151
+ def v1?
152
+ @version == 'v1'
153
+ end
154
+
155
+ def v0?
156
+ @version == 'v0'
157
+ end
158
+
159
+ def v2_3?
160
+ return true if @version == 'v2'
161
+ return true if @version == 'v3'
162
+
163
+ false
164
+ end
165
+
166
+ def graphql?
167
+ @version == 'graphql'
168
+ end
169
+
170
+ # @param response [Ecoportal::API::Common::Response] the wrapped response
171
+ # produced by the inherited pipeline (body already JSON-parsed).
172
+ def handle_http_response(response)
173
+ unless response.success?
174
+ raise Ecoportal::API::Common::Client::Error::UnexpectedServerError.new(
175
+ response.src_body, code: response.status.code
176
+ )
177
+ end
178
+
179
+ parsed = response.body
180
+ errors = parsed && parsed['errors']
181
+ raise ResponseError, errors.map { |e| e['message'] }.join('; ') if errors&.any?
182
+
183
+ parsed
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
@@ -1,54 +1,54 @@
1
- module Ecoportal
2
- module API
3
- module Common
4
- module GraphQL
5
- class Model
6
- module Diffable
7
- require_relative 'diffable/hash_diff_nesting'
8
- require_relative 'diffable/classic_diff_service'
9
- require_relative 'diffable/diff_service'
10
-
11
- DIFF_CLASS = DiffService
12
-
13
- # INSTANCE METHODS
14
-
15
- # @note **cascaded callbacks**
16
- # 1. Can **skip** rooted objects (where `root?` is `true`).
17
- # This is because nested rooted objects are usually look-ups
18
- # (not really part of the target model).
19
- # 2. Allow to redefine `as_update` on each model.
20
- # @param flat [Boolean] whether it should NOT perform a cascaded callback
21
- # througout all the instance objects of the model hierarchy (nested).
22
- # @param ignored [Array<String>] the keys that should be ignored, not part
23
- # of the result of `as_update`
24
- # @return [nil, Hash] the patch `Hash` model including only
25
- # the changes between `original_doc` and `doc`.
26
- def as_update(**kargs)
27
- diff_class.new(
28
- self,
29
- **kargs
30
- ).diff
31
- end
32
-
33
- # @note `cascaded` will be more accurate in a `GraphQL` scenario.
34
- # @return [Boolean] stating if there are changes.
35
- def dirty?(...)
36
- au = as_update(...)
37
-
38
- return false if au.nil?
39
- return false if au == {}
40
-
41
- true
42
- end
43
-
44
- private
45
-
46
- def diff_class
47
- self.class::DIFF_CLASS
48
- end
49
- end
50
- end
51
- end
52
- end
53
- end
54
- end
1
+ module Ecoportal
2
+ module API
3
+ module Common
4
+ module GraphQL
5
+ class Model
6
+ module Diffable
7
+ require_relative 'diffable/hash_diff_nesting'
8
+ require_relative 'diffable/classic_diff_service'
9
+ require_relative 'diffable/diff_service'
10
+
11
+ DIFF_CLASS = DiffService
12
+
13
+ # INSTANCE METHODS
14
+
15
+ # @note **cascaded callbacks**
16
+ # 1. Can **skip** rooted objects (where `root?` is `true`).
17
+ # This is because nested rooted objects are usually look-ups
18
+ # (not really part of the target model).
19
+ # 2. Allow to redefine `as_update` on each model.
20
+ # @param flat [Boolean] whether it should NOT perform a cascaded callback
21
+ # througout all the instance objects of the model hierarchy (nested).
22
+ # @param ignored [Array<String>] the keys that should be ignored, not part
23
+ # of the result of `as_update`
24
+ # @return [nil, Hash] the patch `Hash` model including only
25
+ # the changes between `original_doc` and `doc`.
26
+ def as_update(**kargs)
27
+ diff_class.new(
28
+ self,
29
+ **kargs
30
+ ).diff
31
+ end
32
+
33
+ # @note `cascaded` will be more accurate in a `GraphQL` scenario.
34
+ # @return [Boolean] stating if there are changes.
35
+ def dirty?(...)
36
+ au = as_update(...)
37
+
38
+ return false if au.nil?
39
+ return false if au == {}
40
+
41
+ true
42
+ end
43
+
44
+ private
45
+
46
+ def diff_class
47
+ self.class::DIFF_CLASS
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -1,43 +1,43 @@
1
- module Ecoportal
2
- module API
3
- class GraphQL
4
- module Base
5
- class Action < Logic::BaseModel
6
- root!
7
-
8
- passkey :id
9
- passthrough :altId
10
- passthrough :name, :description
11
- passthrough :age
12
-
13
- passarray :locationIds
14
-
15
- passthrough :status, :relativeStatus
16
- passboolean :archived, :attached, :standaloneAction
17
- passthrough :actionCategoryId
18
-
19
- passarray :fileContainerIds
20
-
21
- passarray :assignedPersonMemberIds
22
- passthrough :creatorId, :creatorUserId
23
- passthrough :updaterId, :updaterUserId
24
- passthrough :completerId
25
-
26
- passthrough :timeZone
27
- embeds_one :dueDate, klass: Base::DateTime
28
- embeds_one :reminderDate, klass: Base::DateTime
29
- embeds_one :createdAt, klass: Base::DateTime
30
- embeds_one :updatedAt, klass: Base::DateTime
31
- embeds_one :completedAt, klass: Base::DateTime
32
- embeds_one :openededAt, klass: Base::DateTime
33
- embeds_one :closedAt, klass: Base::DateTime
34
- embeds_one :dueOrClosedDate, klass: Base::DateTime
35
-
36
- passboolean :completed
37
- passthrough :closeDetail
38
- #passthrough :dueIn # only for templates
39
- end
40
- end
41
- end
42
- end
43
- end
1
+ module Ecoportal
2
+ module API
3
+ class GraphQL
4
+ module Base
5
+ class Action < Logic::BaseModel
6
+ root!
7
+
8
+ passkey :id
9
+ passthrough :altId
10
+ passthrough :name, :description
11
+ passthrough :age
12
+
13
+ passarray :locationIds
14
+
15
+ passthrough :status, :relativeStatus
16
+ passboolean :archived, :attached, :standaloneAction
17
+ passthrough :actionCategoryId
18
+
19
+ passarray :fileContainerIds
20
+
21
+ passarray :assignedPersonMemberIds
22
+ passthrough :creatorId, :creatorUserId
23
+ passthrough :updaterId, :updaterUserId
24
+ passthrough :completerId
25
+
26
+ passthrough :timeZone
27
+ embeds_one :dueDate, klass: Base::DateTime
28
+ embeds_one :reminderDate, klass: Base::DateTime
29
+ embeds_one :createdAt, klass: Base::DateTime
30
+ embeds_one :updatedAt, klass: Base::DateTime
31
+ embeds_one :completedAt, klass: Base::DateTime
32
+ embeds_one :openededAt, klass: Base::DateTime
33
+ embeds_one :closedAt, klass: Base::DateTime
34
+ embeds_one :dueOrClosedDate, klass: Base::DateTime
35
+
36
+ passboolean :completed
37
+ passthrough :closeDetail
38
+ #passthrough :dueIn # only for templates
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end