ecoportal-api-graphql 2.2.1 → 3.0.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +405 -9
  3. data/README.gem.md +53 -0
  4. data/lib/ecoportal/api/common/graphql/auth_service.rb +1 -1
  5. data/lib/ecoportal/api/common/graphql/client.rb +38 -0
  6. data/lib/ecoportal/api/common/graphql/http_client.rb +39 -6
  7. data/lib/ecoportal/api/graphql/base/page/data_field/image_gallery.rb +159 -11
  8. data/lib/ecoportal/api/graphql/base/temp_image.rb +28 -0
  9. data/lib/ecoportal/api/graphql/base.rb +1 -0
  10. data/lib/ecoportal/api/graphql/builder/template.rb +9 -4
  11. data/lib/ecoportal/api/graphql/compat/filter_translator.rb +1 -1
  12. data/lib/ecoportal/api/graphql/file_upload/client.rb +140 -35
  13. data/lib/ecoportal/api/graphql/fragment/pages/common_page_union.rb +6 -1
  14. data/lib/ecoportal/api/graphql/input/page/update.rb +110 -7
  15. data/lib/ecoportal/api/graphql/input/search_conf.rb +1 -1
  16. data/lib/ecoportal/api/graphql/model/temp_image.rb +10 -0
  17. data/lib/ecoportal/api/graphql/model/template/binding.rb +60 -0
  18. data/lib/ecoportal/api/graphql/model/template/command_grouper.rb +107 -0
  19. data/lib/ecoportal/api/graphql/model/template/command_normalizer.rb +116 -0
  20. data/lib/ecoportal/api/graphql/model/template/command_synthesis.rb +262 -0
  21. data/lib/ecoportal/api/graphql/model/template/field.rb +68 -0
  22. data/lib/ecoportal/api/graphql/model/template/force.rb +65 -0
  23. data/lib/ecoportal/api/graphql/model/template/helper.rb +32 -0
  24. data/lib/ecoportal/api/graphql/model/template/instance.rb +202 -0
  25. data/lib/ecoportal/api/graphql/model/template/node.rb +78 -0
  26. data/lib/ecoportal/api/graphql/model/template/option.rb +49 -0
  27. data/lib/ecoportal/api/graphql/model/template/read.rb +164 -0
  28. data/lib/ecoportal/api/graphql/model/template/section.rb +82 -0
  29. data/lib/ecoportal/api/graphql/model/template/stage.rb +49 -0
  30. data/lib/ecoportal/api/graphql/model/template/staged_executor.rb +236 -0
  31. data/lib/ecoportal/api/graphql/model/template.rb +33 -0
  32. data/lib/ecoportal/api/graphql/model.rb +1 -0
  33. data/lib/ecoportal/api/graphql/mutation/file_container/upload.rb +11 -4
  34. data/lib/ecoportal/api/graphql/mutation/image/upload.rb +88 -0
  35. data/lib/ecoportal/api/graphql/mutation/image.rb +14 -0
  36. data/lib/ecoportal/api/graphql/mutation/template/create.rb +35 -3
  37. data/lib/ecoportal/api/graphql/mutation/template/update.rb +4 -2
  38. data/lib/ecoportal/api/graphql/mutation.rb +1 -0
  39. data/lib/ecoportal/api/graphql/payload/images_upload.rb +14 -0
  40. data/lib/ecoportal/api/graphql/payload.rb +1 -0
  41. data/lib/ecoportal/api/graphql_version.rb +1 -1
  42. metadata +35 -2
  43. data/README.md +0 -24
@@ -5,12 +5,45 @@ module Ecoportal
5
5
  # Concrete counterparts of `Base::Template::*` (see there for the page-vs-Template
6
6
  # type asymmetry). `Information` and `RelatedPages` need no extra wiring — both are
7
7
  # flat, read-only models; `ChangeLogEntry` adds the `owner` embed.
8
+ #
9
+ # This module ALSO carries the mutable, editable template model
10
+ # (the repo's internal docs) — "the three
11
+ # lines":
12
+ #
13
+ # template = Ecoportal::API::GraphQL::Model::Template.load(api, id: page_id)
14
+ # template.fields.each { |f| f.label = "#{f.label} (reviewed)" }
15
+ # template.save!(api)
16
+ #
17
+ # `Instance` is the class `.load` returns; `.load` is the ergonomic module-level entry
18
+ # point so callers never have to spell `Instance` themselves.
8
19
  module Template
9
20
  class Information < Base::Template::Information; end
10
21
 
11
22
  class RelatedPages < Base::Template::RelatedPages; end
12
23
 
13
24
  require_relative 'template/change_log_entry'
25
+
26
+ require_relative 'template/node'
27
+ require_relative 'template/option'
28
+ require_relative 'template/binding'
29
+ require_relative 'template/helper'
30
+ require_relative 'template/field'
31
+ require_relative 'template/section'
32
+ require_relative 'template/force'
33
+ require_relative 'template/stage'
34
+ require_relative 'template/read'
35
+ require_relative 'template/command_synthesis'
36
+ require_relative 'template/command_normalizer'
37
+ require_relative 'template/command_grouper'
38
+ require_relative 'template/staged_executor'
39
+ require_relative 'template/instance'
40
+
41
+ # @param client [Ecoportal::API::GraphQL] the top-level gem client.
42
+ # @param id [String] the template's page id.
43
+ # @return [Instance]
44
+ def self.load(client, id:)
45
+ Instance.load(client, id: id)
46
+ end
14
47
  end
15
48
  end
16
49
  end
@@ -14,6 +14,7 @@ require_relative 'model/user'
14
14
  require_relative 'model/person_member'
15
15
  require_relative 'model/file_attachment'
16
16
  require_relative 'model/file_container'
17
+ require_relative 'model/temp_image'
17
18
  require_relative 'model/field'
18
19
  require_relative 'model/page'
19
20
  require_relative 'model/page_union'
@@ -31,16 +31,23 @@ module Ecoportal
31
31
  # @param other_tags [Array<String>, nil] tags — the only supported way to tag a
32
32
  # container (there is no separate tag mutation; confirmed 2026-07-30).
33
33
  # @param container_id [String, nil] add a new VERSION to an existing container.
34
- def query(filename:, filetype:, filesize:, url:, location_ids: nil, other_tags: nil, container_id: nil, **kargs, &block)
34
+ # @param client_mutation_id [String, nil] optional correlation/tracing id --
35
+ # `UploadInput`'s own `clientMutationId` (confirmed in this file's own header,
36
+ # `UploadInput { clientMutationId filename filetype filesize locationIds
37
+ # otherTags url containerId }`). Omitted entirely when not given, so every
38
+ # existing caller's input shape is byte-identical to before.
39
+ def query(filename:, filetype:, filesize:, url:, location_ids: nil, other_tags: nil,
40
+ container_id: nil, client_mutation_id: nil, **kargs, &block)
35
41
  input = {
36
42
  filename: filename,
37
43
  filetype: filetype,
38
44
  filesize: filesize.to_s,
39
45
  url: url
40
46
  }
41
- input[:locationIds] = Array(location_ids) if location_ids
42
- input[:otherTags] = Array(other_tags) if other_tags
43
- input[:containerId] = container_id if container_id
47
+ input[:locationIds] = Array(location_ids) if location_ids
48
+ input[:otherTags] = Array(other_tags) if other_tags
49
+ input[:containerId] = container_id if container_id
50
+ input[:clientMutationId] = client_mutation_id unless client_mutation_id.nil?
44
51
 
45
52
  super(input: input, **kargs, &block)
46
53
  end
@@ -0,0 +1,88 @@
1
+ module Ecoportal
2
+ module API
3
+ class GraphQL
4
+ module Mutation
5
+ module Image
6
+ # Registers an object already uploaded to S3 as a `TempImage` -- the upload-staging
7
+ # model behind Image Gallery (and other, non-gallery) image uploads.
8
+ #
9
+ # ★ Sibling to `Mutation::FileContainer::Upload`, NOT a variant of it: an Image
10
+ # Gallery image is explicitly NOT a `FileContainer`. Verified against the live
11
+ # ecoPortal server source (read-only, /tmp/work/ecoPortal):
12
+ # * `app/graphql/mutations/images/upload.rb:1-23` -- the mutation builds an
13
+ # `Enzyme::ImageReference::TempImage`, not `uploadFile`/`FileContainer`.
14
+ # * `app/graphql/types/mutation_type.rb:224` -- schema field `uploadImage`
15
+ # (`field :upload_image, mutation: Mutations::Images::Upload`).
16
+ # * `app/graphql/types/enums/files/image_upload_type_enum.rb` -- `type:` is a
17
+ # live enum (`image_gallery`, `policy_group`, `theme`, `user`, `organization`,
18
+ # `brand`); `"image_gallery"` is the one `Base::Page::DataField::ImageGallery`
19
+ # consumes.
20
+ #
21
+ # The SIGNATURE and S3-POST steps upstream of this call are IDENTICAL to the
22
+ # `uploadFile` flow (same `fileUploadSignature`, same multipart POST) -- see
23
+ # `FileUpload::Client#upload_image`, which shares its presign+store step with
24
+ # `#upload` and only diverges here, at registration.
25
+ #
26
+ # Verified against the live schema:
27
+ # uploadImage(input: ImagesUploadInput!) -> ImagesUploadPayload
28
+ # { clientMutationId errors item: TempImage }
29
+ # ImagesUploadInput { clientMutationId key type id import }
30
+ class Upload < Logic::Mutation
31
+ field_name :uploadImage
32
+
33
+ class_resolver :payload_class, 'Ecoportal::API::GraphQL::Payload::ImagesUpload'
34
+
35
+ # @param key [String] the S3 object key produced by the S3 POST step.
36
+ # @param type [String, Symbol] live enum value -- `FileUpload::Client` always
37
+ # passes `'image_gallery'` today; any other value is sent verbatim for a future
38
+ # caller.
39
+ # @param id [String, nil] a target id the image is being uploaded FOR, in
40
+ # contexts that need one (e.g. an existing brand/policy_group/user). Unused by
41
+ # the Image Gallery context -- that attach happens later, via `updatePage`, not
42
+ # here.
43
+ # @param import [Boolean] matches the live schema's own default (`true`).
44
+ # @param client_mutation_id [String, nil] optional correlation/tracing id --
45
+ # `ImagesUploadInput`'s own `clientMutationId` (confirmed in this file's own
46
+ # header, `ImagesUploadInput { clientMutationId key type id import }`). Omitted
47
+ # entirely when not given, so every existing caller's input shape is
48
+ # byte-identical to before.
49
+ def query(key:, type:, id: nil, import: true, client_mutation_id: nil, **kargs, &block)
50
+ super(input: build_input(key: key, type: type, id: id, import: import,
51
+ client_mutation_id: client_mutation_id), **kargs, &block)
52
+ end
53
+
54
+ private
55
+
56
+ def build_input(key:, type:, id:, import:, client_mutation_id: nil)
57
+ input = {key: key, type: type.to_s, import: import}
58
+ input[:id] = id if id
59
+ input[:clientMutationId] = client_mutation_id unless client_mutation_id.nil?
60
+ input
61
+ end
62
+
63
+ def basic_block(&block)
64
+ payload_block = block || default_payload_block
65
+ proc {
66
+ mutation(input: :ImagesUploadInput!) {
67
+ uploadImage(input: :input, &payload_block)
68
+ }
69
+ }
70
+ end
71
+
72
+ def default_payload_block
73
+ proc {
74
+ errors { details fullMessages }
75
+ item {
76
+ id
77
+ s3Key
78
+ complete
79
+ error
80
+ }
81
+ }
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,14 @@
1
+ module Ecoportal
2
+ module API
3
+ class GraphQL
4
+ module Mutation
5
+ # Namespaced `Image` (matches the schema's own `TempImageType`/`ImagesUploadInput`
6
+ # naming) -- sibling to `Mutation::FileContainer`, NOT a variant of it.
7
+ module Image
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ require_relative 'image/upload'
@@ -3,15 +3,47 @@ module Ecoportal
3
3
  class GraphQL
4
4
  module Mutation
5
5
  module Template
6
+ # `createPageTemplate(input: CreatePageTemplateInput!)` — creates a template page from
7
+ # a `WorkflowCommand` batch.
8
+ #
9
+ # ★ `registerId` IS NOT COSMETIC — it is what makes the create AUTHORIZABLE.
10
+ #
11
+ # The backend resolves a page's registers BY TAG
12
+ # (`NewEp::Pages::AssociatedRegisters`: org registers whose `filter_tags` are a subset
13
+ # of the page's `combined_tags`), and `RegisterTemplatePermissionChecker` returns
14
+ # `false` outright when that set is EMPTY. A template created without `registerId`
15
+ # carries no `base_tags`, so it is associated with ZERO registers, so the check fails
16
+ # regardless of what the account is granted — surfacing as the misleading
17
+ # `You are not authorized to perform: edit_template_basic_settings`.
18
+ # `Pages::Templates::CreateForm#bind_register!` is what closes that hole: given
19
+ # `register_id` it sets `page.base_tags = register.filter_tags` BEFORE the commands run.
20
+ #
21
+ # SHAPE (verified against the LIVE eu.live SDL, 2026-09-04): `registerId: ID` is an
22
+ # INPUT FIELD of `CreatePageTemplateInput`, not a sibling argument of the mutation —
23
+ # the backend mutation declares `argument :register_id, ID, required: false` next to
24
+ # `argument :commands`, but `Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation`
25
+ # folds every argument into the generated input object. So it travels inside `input:`,
26
+ # exactly like `commands`.
6
27
  class Create < Logic::Mutation
7
- SCHEMA_VERSION = '20260605'.freeze
28
+ SCHEMA_VERSION = '20260904'.freeze
8
29
 
9
30
  field_name :createPageTemplate
10
31
 
11
32
  class_resolver :payload_class, 'Ecoportal::API::GraphQL::Payload::Template::Create'
12
33
 
13
- def query(commands:, **kargs, &block)
14
- super(input: { commands: commands }, **kargs, &block)
34
+ # @param commands [Array<Hash>] the `WorkflowCommand` batch.
35
+ # @param register_id [String, nil] the register to bind the new template to. Omitted
36
+ # when nil — never sent as an explicit null, so existing call sites keep producing
37
+ # the byte-identical input they produced before this key existed.
38
+ def query(commands:, register_id: nil, **kargs, &block)
39
+ super(input: self.class.build_input(commands: commands, register_id: register_id), **kargs, &block)
40
+ end
41
+
42
+ # Extracted so the omit-nil rule is testable without a client or a network stub.
43
+ def self.build_input(commands:, register_id: nil)
44
+ {commands: commands}.tap do |input|
45
+ input[:registerId] = register_id unless register_id.nil?
46
+ end
15
47
  end
16
48
 
17
49
  private
@@ -10,9 +10,11 @@ module Ecoportal
10
10
 
11
11
  class_resolver :payload_class, 'Ecoportal::API::GraphQL::Payload::Template::Update'
12
12
 
13
- def query(id:, patch_ver:, commands:, **kargs, &block)
13
+ def query(id:, patch_ver:, commands:, client_mutation_id: nil, **kargs, &block)
14
+ input = { id: id, patchVer: patch_ver, commands: commands }
15
+ input[:clientMutationId] = client_mutation_id unless client_mutation_id.nil?
14
16
  super(
15
- input: { id: id, patchVer: patch_ver, commands: commands },
17
+ input: input,
16
18
  **kargs,
17
19
  &block
18
20
  )
@@ -18,3 +18,4 @@ require_relative 'mutation/ai_summary'
18
18
  require_relative 'mutation/kickstand'
19
19
  require_relative 'mutation/template'
20
20
  require_relative 'mutation/file_container'
21
+ require_relative 'mutation/image'
@@ -0,0 +1,14 @@
1
+ module Ecoportal
2
+ module API
3
+ class GraphQL
4
+ module Payload
5
+ # `uploadImage` -> `ImagesUploadPayload { clientMutationId errors item: TempImage }`.
6
+ # Sibling to `Payload::Upload` (`uploadFile` -> `FileContainer`) -- NOT a variant of
7
+ # it. See `Base::TempImage`'s header for why the item type differs.
8
+ class ImagesUpload < Logic::Payload
9
+ class_resolver :item_class, 'Ecoportal::API::GraphQL::Model::TempImage'
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -22,3 +22,4 @@ require_relative 'payload/execute_workflow_commands'
22
22
  require_relative 'payload/kickstand'
23
23
  require_relative 'payload/template'
24
24
  require_relative 'payload/upload'
25
+ require_relative 'payload/images_upload'
@@ -1,5 +1,5 @@
1
1
  module Ecoportal
2
2
  module API
3
- GRAPQL_VERSION = '2.2.1'.freeze
3
+ GRAPQL_VERSION = '3.0.0'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ecoportal-api-graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oscar Segura
@@ -9,6 +9,20 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: json-canonicalization
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: pry
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -199,7 +213,7 @@ extra_rdoc_files: []
199
213
  files:
200
214
  - CHANGELOG.md
201
215
  - LICENSE
202
- - README.md
216
+ - README.gem.md
203
217
  - lib/ecoportal/api-graphql.rb
204
218
  - lib/ecoportal/api/common.graphql.rb
205
219
  - lib/ecoportal/api/common/graphql.rb
@@ -311,6 +325,7 @@ files:
311
325
  - lib/ecoportal/api/graphql/base/preview_page.rb
312
326
  - lib/ecoportal/api/graphql/base/register.rb
313
327
  - lib/ecoportal/api/graphql/base/resource.rb
328
+ - lib/ecoportal/api/graphql/base/temp_image.rb
314
329
  - lib/ecoportal/api/graphql/base/template.rb
315
330
  - lib/ecoportal/api/graphql/base/template/change_log_entry.rb
316
331
  - lib/ecoportal/api/graphql/builder.rb
@@ -625,8 +640,23 @@ files:
625
640
  - lib/ecoportal/api/graphql/model/preview_page.rb
626
641
  - lib/ecoportal/api/graphql/model/register.rb
627
642
  - lib/ecoportal/api/graphql/model/resource.rb
643
+ - lib/ecoportal/api/graphql/model/temp_image.rb
628
644
  - lib/ecoportal/api/graphql/model/template.rb
645
+ - lib/ecoportal/api/graphql/model/template/binding.rb
629
646
  - lib/ecoportal/api/graphql/model/template/change_log_entry.rb
647
+ - lib/ecoportal/api/graphql/model/template/command_grouper.rb
648
+ - lib/ecoportal/api/graphql/model/template/command_normalizer.rb
649
+ - lib/ecoportal/api/graphql/model/template/command_synthesis.rb
650
+ - lib/ecoportal/api/graphql/model/template/field.rb
651
+ - lib/ecoportal/api/graphql/model/template/force.rb
652
+ - lib/ecoportal/api/graphql/model/template/helper.rb
653
+ - lib/ecoportal/api/graphql/model/template/instance.rb
654
+ - lib/ecoportal/api/graphql/model/template/node.rb
655
+ - lib/ecoportal/api/graphql/model/template/option.rb
656
+ - lib/ecoportal/api/graphql/model/template/read.rb
657
+ - lib/ecoportal/api/graphql/model/template/section.rb
658
+ - lib/ecoportal/api/graphql/model/template/stage.rb
659
+ - lib/ecoportal/api/graphql/model/template/staged_executor.rb
630
660
  - lib/ecoportal/api/graphql/model/user.rb
631
661
  - lib/ecoportal/api/graphql/mutation.rb
632
662
  - lib/ecoportal/api/graphql/mutation/action.rb
@@ -643,6 +673,8 @@ files:
643
673
  - lib/ecoportal/api/graphql/mutation/contractor_entity/update_manager_settings.rb
644
674
  - lib/ecoportal/api/graphql/mutation/file_container.rb
645
675
  - lib/ecoportal/api/graphql/mutation/file_container/upload.rb
676
+ - lib/ecoportal/api/graphql/mutation/image.rb
677
+ - lib/ecoportal/api/graphql/mutation/image/upload.rb
646
678
  - lib/ecoportal/api/graphql/mutation/kickstand.rb
647
679
  - lib/ecoportal/api/graphql/mutation/kickstand/bulk_update_jobs.rb
648
680
  - lib/ecoportal/api/graphql/mutation/kickstand/bulk_update_workflows.rb
@@ -707,6 +739,7 @@ files:
707
739
  - lib/ecoportal/api/graphql/payload/contractor_entity/update.rb
708
740
  - lib/ecoportal/api/graphql/payload/execute_workflow_commands.rb
709
741
  - lib/ecoportal/api/graphql/payload/force_commands.rb
742
+ - lib/ecoportal/api/graphql/payload/images_upload.rb
710
743
  - lib/ecoportal/api/graphql/payload/kickstand.rb
711
744
  - lib/ecoportal/api/graphql/payload/kickstand/bulk_update_jobs.rb
712
745
  - lib/ecoportal/api/graphql/payload/kickstand/bulk_update_workflows.rb
data/README.md DELETED
@@ -1,24 +0,0 @@
1
- # Ecoportal::API::GraphQL
2
-
3
- This is a basic collection of helpers that can be used to interact with the
4
- [ecoPortal](https://www.ecoportal.com) `GraphQL` API
5
-
6
- ## Installation
7
-
8
- Add this line to your application's Gemfile:
9
-
10
- ```ruby
11
- gem 'ecoportal-api-graphql', require: %w[ecoportal/api-graphql]
12
- ```
13
-
14
- And then execute:
15
-
16
- $ bundle
17
-
18
- Or install it yourself as:
19
-
20
- $ gem install ecoportal-api-graphql
21
-
22
- ## Changelog
23
-
24
- See {file:CHANGELOG.md} for a list of changes.