ecoportal-api-graphql 1.3.12 → 1.3.14
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.
- checksums.yaml +4 -4
- data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.inbox.md +29 -29
- data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.inbox.md +42 -42
- data/.ai-assistance/projects/compat-layer-audit/COMPAT_AUDIT.md +244 -0
- data/.ai-assistance/projects/template-model-logic/CATEGORY_CATALOG.md +236 -0
- data/.ai-assistance/projects/template-model-logic/CLASSIFICATION_SPIKE.md +243 -0
- data/.ai-assistance/projects/template-model-logic/DESIGN_NOTE.md +154 -0
- data/.ai-assistance/scripts/bridge-inbox-check.sh +75 -0
- data/.ai-assistance/skills/project-self-docs/SKILL.md +181 -0
- data/.ai-assistance/skills/project-self-docs/scripts/self_docs_scan.py +378 -0
- data/.ai-assistance/standards-version.json +9 -9
- data/.claude/settings.json +150 -146
- data/.gitignore +37 -37
- data/.gitlab-ci.yml +45 -0
- data/CHANGELOG.md +43 -0
- data/CLAUDE.md +1 -0
- data/docs/self-docs/ARCHITECTURE.md +3 -3
- data/docs/self-docs/CHANGES.jsonl +5 -0
- data/docs/self-docs/COMPLIANCE.md +79 -0
- data/docs/self-docs/CONVENTIONS.md +3 -3
- data/docs/self-docs/INTEGRATIONS.md +17 -15
- data/docs/self-docs/OPERATIONS.md +76 -0
- data/docs/self-docs/OVERVIEW.md +16 -3
- data/docs/self-docs/STATUS.md +9 -5
- data/docs/self-docs/self-docs-index.json +20 -8
- data/lib/ecoportal/api/common/graphql/http_client.rb +189 -177
- data/lib/ecoportal/api/graphql/base/location_classification_type.rb +15 -15
- data/lib/ecoportal/api/graphql/base/location_structure.rb +27 -27
- data/lib/ecoportal/api/graphql/base/organization.rb +15 -15
- data/lib/ecoportal/api/graphql/base.rb +35 -35
- data/lib/ecoportal/api/graphql/builder/contractor_entity.rb +41 -41
- data/lib/ecoportal/api/graphql/connection/action.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/action_category.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/contractor_entity.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/page.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/person_member.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/preview_page.rb +11 -11
- data/lib/ecoportal/api/graphql/connection.rb +17 -17
- data/lib/ecoportal/api/graphql/fragment/location_node.rb +26 -26
- data/lib/ecoportal/api/graphql/input/contractor_entity.rb +14 -14
- data/lib/ecoportal/api/graphql/input/workflow_command/add_stage.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_field_configuration.rb +21 -21
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_page.rb +28 -28
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_page_creator_permissions.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/cross_reference.rb +23 -23
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/date.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/image_gallery.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/plain_text.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/rich_text.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/select.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/move_field.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_forces.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_section.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command.rb +251 -251
- data/lib/ecoportal/api/graphql/input.rb +24 -24
- data/lib/ecoportal/api/graphql/logic/connection.rb +34 -34
- data/lib/ecoportal/api/graphql/model/person_member.rb +15 -15
- data/lib/ecoportal/api/graphql/model.rb +30 -30
- data/lib/ecoportal/api/graphql/mutation/contractor_entity.rb +14 -14
- data/lib/ecoportal/api/graphql/payload/contractor_entity.rb +14 -14
- data/lib/ecoportal/api/graphql/query/location_structures.rb +57 -57
- data/lib/ecoportal/api/graphql/query.rb +27 -27
- data/lib/ecoportal/api/graphql_version.rb +1 -1
- metadata +11 -1
|
@@ -1,33 +1,35 @@
|
|
|
1
1
|
---
|
|
2
|
-
schema_version: "1.
|
|
2
|
+
schema_version: "1.1"
|
|
3
3
|
repo: "ecoportal-api-graphql"
|
|
4
4
|
doc: integrations
|
|
5
|
-
last_generated: "2026-07-
|
|
6
|
-
source_head: "
|
|
5
|
+
last_generated: "2026-07-09"
|
|
6
|
+
source_head: "3f7b877"
|
|
7
7
|
review_status: draft
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# Integrations -- ecoportal-api-graphql
|
|
11
11
|
|
|
12
12
|
**Purpose:** the ties this project has to other systems and repos. This is the section the central
|
|
13
|
-
hub uses to trace inter-project dependencies and surface reuse (ADR-015 Section 5).
|
|
13
|
+
hub uses to trace inter-project dependencies and surface reuse (ADR-015 Section 5). It records, per
|
|
14
|
+
tie, the DATA that flows and the PERMISSIONS required -- feeding the compliance (vendor/PII) and the
|
|
15
|
+
cross-project-permissions questions. Detailed data-handling lives in `COMPLIANCE.md`.
|
|
14
16
|
|
|
15
17
|
## External services
|
|
16
18
|
|
|
17
|
-
| Service | Purpose |
|
|
18
|
-
|
|
19
|
-
| ecoPortal GraphQL API | The API this gem is a client for -- reads/writes pages, templates, location structures, contractor entities, actions, organizations. |
|
|
20
|
-
| ecoPortal REST API (v2) | Underlying REST infrastructure the GraphQL layer builds on and stays compatible with via the `Compat` layer. |
|
|
19
|
+
| Service | Purpose | Data sent (class + PII?) | Auth / permissions (scopes) |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| ecoPortal GraphQL API | The API this gem is a client for -- reads/writes pages, templates, location structures, contractor entities, actions, organizations. | Customer data + PII: page records can contain personal data. Also template/org-config data (non-PII). The gem is a full-access client by capability; the active template-automation work restricts itself to templates/org-config only by SERVICE-ACCOUNT grant. | OAuth via `Common::GraphQL::AuthService`, scoped by `org_id`; per-environment host (e.g. live.ecoportal.com). `no_schema: true` (no introspection). Credential scope is the load-bearing control -- see COMPLIANCE.md. |
|
|
22
|
+
| ecoPortal REST API (v2) | Underlying REST infrastructure the GraphQL layer builds on and stays compatible with via the `Compat` layer. | Same classes as above (indirect, via `ecoportal-api-v2`). | Same OAuth / org-scoped runtime credentials; provided through the upstream `ecoportal-api-v2` gem. |
|
|
21
23
|
|
|
22
24
|
## Upstream dependencies (repos we depend on)
|
|
23
25
|
|
|
24
|
-
| Repo / package | What we use it for |
|
|
25
|
-
|
|
26
|
-
| `ecoportal-api` | REST API base: auth, org context, base models, and the `Ecoportal::API::Common` namespace this gem extends. Team-managed. |
|
|
27
|
-
| `ecoportal-api-v2` | REST API v2 layer (Page, Register, etc.); required by the entry point. Team-managed. Being retired over time. |
|
|
28
|
-
| `graphlient` | Friendly GraphQL DSL/client wrapper; `Common::GraphQL::Client` inherits from `Graphlient::Client`. Active fork owned by the team (upstream: `ashkan18/graphlient`). |
|
|
29
|
-
| `graphql-client` | Low-level GraphQL HTTP + parsing, pulled in transitively via graphlient. Fork for upstream contribution only (upstream: `github-community-projects/graphql-client`); not team-maintained. |
|
|
30
|
-
| `graphql` (graphql-ruby) | Schema definition/execution engine (`rmosolgo/graphql-ruby`); external, read-only reference. Used server-side by ecoPortal; client-side usage here is indirect. |
|
|
26
|
+
| Repo / package | What we use it for | Permission/scope required (if any) |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| `ecoportal-api` | REST API base: auth, org context, base models, and the `Ecoportal::API::Common` namespace this gem extends. Team-managed. | None beyond the runtime ecoPortal API credentials (email/pass/org_id/host) passed at instantiation. |
|
|
29
|
+
| `ecoportal-api-v2` | REST API v2 layer (Page, Register, etc.); required by the entry point. Team-managed. Being retired over time. | Same runtime ecoPortal API credentials. |
|
|
30
|
+
| `graphlient` | Friendly GraphQL DSL/client wrapper; `Common::GraphQL::Client` inherits from `Graphlient::Client`. Active fork owned by the team (upstream: `ashkan18/graphlient`). | None (library dependency). |
|
|
31
|
+
| `graphql-client` | Low-level GraphQL HTTP + parsing, pulled in transitively via graphlient. Fork for upstream contribution only (upstream: `github-community-projects/graphql-client`); not team-maintained. | None (library dependency). |
|
|
32
|
+
| `graphql` (graphql-ruby) | Schema definition/execution engine (`rmosolgo/graphql-ruby`); external, read-only reference. Used server-side by ecoPortal; client-side usage here is indirect. | None (library dependency). |
|
|
31
33
|
|
|
32
34
|
## Downstream consumers (who depends on us)
|
|
33
35
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: "1.1"
|
|
3
|
+
repo: "ecoportal-api-graphql"
|
|
4
|
+
doc: operations
|
|
5
|
+
last_generated: "2026-07-09"
|
|
6
|
+
source_head: "3f7b877"
|
|
7
|
+
review_status: draft
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Operations -- ecoportal-api-graphql
|
|
11
|
+
|
|
12
|
+
**Purpose:** the infrastructure / exposure / value surface for DevOps and Engineering. It lets ROVO answer
|
|
13
|
+
"what infrastructure is involved, is it behind Cloudflare, what governs its flows, and is it worth it?"
|
|
14
|
+
Same trust rule as COMPLIANCE.md: separate EVIDENCE from INTENT; audit answers use `reviewed` docs only.
|
|
15
|
+
|
|
16
|
+
> This document is DRAFT and composed from the repo's own context. It is a client LIBRARY (a gem): it has
|
|
17
|
+
> no infrastructure or public exposure of its own. Value/KPI figures below are unmeasured placeholders for
|
|
18
|
+
> the repo owner to fill; do not cite them as measured until reviewed.
|
|
19
|
+
|
|
20
|
+
## Infrastructure inventory
|
|
21
|
+
|
|
22
|
+
This project is a Ruby gem distributed as source/package -- it runs INSIDE whatever process requires it
|
|
23
|
+
(e.g. an `eco-helpers` integration script). It owns no compute, storage, or queue of its own.
|
|
24
|
+
|
|
25
|
+
| Component | Type (compute/store/queue/...) | Where hosted | Notes |
|
|
26
|
+
|---|---|---|---|
|
|
27
|
+
| The gem itself | library (no runtime infra) | Consumer's process / developer machine / CI | No servers, no data stores; loaded via Bundler into a host process. |
|
|
28
|
+
| ecoPortal GraphQL API | external service (dependency) | ecoPortal platform, per-environment host (e.g. live.ecoportal.com) | The gem is a CLIENT of this; the API's infrastructure is owned by the platform, not this repo. |
|
|
29
|
+
| RubyGems / Bundler | package distribution | RubyGems (or team gem source) | Distribution channel for the gem. |
|
|
30
|
+
|
|
31
|
+
## Public exposure
|
|
32
|
+
|
|
33
|
+
**Nothing in this repo is publicly exposed.** It is a client library with no listening endpoint, no
|
|
34
|
+
domain, and no inbound surface. It makes OUTBOUND authenticated calls to the ecoPortal GraphQL API; the
|
|
35
|
+
public-exposure and Cloudflare/WAF posture of that API belongs to the ecoPortal platform's own OPERATIONS
|
|
36
|
+
doc, not to this gem.
|
|
37
|
+
|
|
38
|
+
| Public domain / endpoint | Behind Cloudflare? | Security rules governing the flow |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| (none -- library, no inbound surface) | N/A | Outbound only: OAuth-authenticated, `org_id`-scoped calls to the ecoPortal GraphQL API over HTTPS. |
|
|
41
|
+
|
|
42
|
+
## DevOps audit runbook
|
|
43
|
+
|
|
44
|
+
How to check/verify this project autonomously:
|
|
45
|
+
|
|
46
|
+
- **Build / install:** `bundle install`; gem spec is `ecoportal-api-graphql.gemspec`; version constant
|
|
47
|
+
`Ecoportal::API::GRAPQL_VERSION`.
|
|
48
|
+
- **Tests:** `bundle exec rspec` for the unit suite; `bundle exec rspec --tag live` for live-API/AI tests
|
|
49
|
+
(requires `.env` with `ANTHROPIC_API_KEY` and ecoPortal credentials, loaded via dotenv).
|
|
50
|
+
- **Lint:** `bundle exec rubocop <file>` -- rubocop compliance is required for all generated Ruby
|
|
51
|
+
(config in `.rubocop.yml`).
|
|
52
|
+
- **CI:** no CI config file was detected at the repo root by the scanner (`ci_files: []`). Human to
|
|
53
|
+
confirm whether CI runs elsewhere (e.g. a GitLab CI defined outside the root) or is absent.
|
|
54
|
+
- **Version / release check:** `CHANGELOG.md` is authoritative; releases follow
|
|
55
|
+
`chore(release): vX.Y.Z`. Recent releases raised the `ecoportal-api-v2` floor to fix version-drift
|
|
56
|
+
crashes -- verify the dependency floors in the gemspec match the CHANGELOG.
|
|
57
|
+
- **Deeper docs:** `.ai-assistance/code/` (code-specs, dependency map, diff/build engine, schema
|
|
58
|
+
analysis) is the detailed runbook for engineers; `.ai-assistance/code/ecoPortal_architecture/` is the
|
|
59
|
+
verified platform knowledge base.
|
|
60
|
+
|
|
61
|
+
## Value & KPIs
|
|
62
|
+
|
|
63
|
+
No usage KPIs are instrumented in this repo today. The value narrative below is qualitative /
|
|
64
|
+
estimated; the repo owner should replace estimates with measured figures (or a dashboard link) before
|
|
65
|
+
these are treated as evidence.
|
|
66
|
+
|
|
67
|
+
| Metric | Value (measured/estimated) | Source / dashboard |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| Templates targeted for programmatic build/maintenance | ~300+ (estimated, project scope) | `.ai-assistance/projects/template-automatic-build-maintenance/INTENT.md` |
|
|
70
|
+
| Hours saved / delivery-speed gain | Not measured | -- (owner to instrument) |
|
|
71
|
+
| Active monitoring | None in-repo; staleness of these self-docs is detectable via `source_head` vs git head | `self-docs-index.json` |
|
|
72
|
+
| Cost-vs-return | Not measured | -- (owner to instrument) |
|
|
73
|
+
|
|
74
|
+
**Coverage-on-neglect:** these self-docs carry a `source_head` and a per-doc `content_hash` in
|
|
75
|
+
`self-docs-index.json`, so the hub and `project-review` can flag them stale rather than silently trusting
|
|
76
|
+
them.
|
data/docs/self-docs/OVERVIEW.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
schema_version: "1.
|
|
2
|
+
schema_version: "1.1"
|
|
3
3
|
repo: "ecoportal-api-graphql"
|
|
4
4
|
doc: overview
|
|
5
|
-
last_generated: "2026-07-
|
|
6
|
-
source_head: "
|
|
5
|
+
last_generated: "2026-07-09"
|
|
6
|
+
source_head: "3f7b877"
|
|
7
7
|
review_status: draft # draft | reviewed (only a human sets 'reviewed')
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -43,9 +43,22 @@ threading).
|
|
|
43
43
|
- Entry points: `require 'ecoportal/api-graphql'` -> `lib/ecoportal/api-graphql.rb` ->
|
|
44
44
|
`Ecoportal::API::GraphQL` (`lib/ecoportal/api/graphql.rb`)
|
|
45
45
|
|
|
46
|
+
## Discovery & access
|
|
47
|
+
|
|
48
|
+
Access model: **on-request, developer-scoped** -- this is a Ruby gem, not a self-serve product. It
|
|
49
|
+
reaches its users (the ecoPortal Integrations Team and internal script authors) by being declared as
|
|
50
|
+
a dependency in a consuming project's Gemfile/gemspec (e.g. `eco-helpers ~> 1.3, >= 1.3.4`), not by
|
|
51
|
+
a public listing or an end-user-facing install. Developers learn it exists through the team's
|
|
52
|
+
integration-gem stack documentation (`.ai-assistance/code/ecoPortal_architecture/11_integration_gems.md`),
|
|
53
|
+
the repo README, and this self-docs set once mirrored to the EP Projects Register. There is no
|
|
54
|
+
customer-facing discovery surface: end customers never interact with the gem directly; they see only
|
|
55
|
+
the ecoPortal platform it talks to.
|
|
56
|
+
|
|
46
57
|
## Related self-docs
|
|
47
58
|
|
|
48
59
|
- Architecture: `ARCHITECTURE.md`
|
|
49
60
|
- Conventions: `CONVENTIONS.md`
|
|
50
61
|
- Integrations: `INTEGRATIONS.md`
|
|
51
62
|
- Status: `STATUS.md`
|
|
63
|
+
- Compliance: `COMPLIANCE.md`
|
|
64
|
+
- Operations: `OPERATIONS.md`
|
data/docs/self-docs/STATUS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
schema_version: "1.
|
|
2
|
+
schema_version: "1.1"
|
|
3
3
|
repo: "ecoportal-api-graphql"
|
|
4
4
|
doc: status
|
|
5
|
-
last_generated: "2026-07-
|
|
6
|
-
source_head: "
|
|
5
|
+
last_generated: "2026-07-09"
|
|
6
|
+
source_head: "3f7b877"
|
|
7
7
|
review_status: draft
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -65,5 +65,9 @@ intent. The #1-priority project is Template Automatic Build and Maintenance (see
|
|
|
65
65
|
|
|
66
66
|
## Recent changes
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
Upgraded the self-docs set to schema 1.1 (audit/compliance surface): added `COMPLIANCE.md` and
|
|
69
|
+
`OPERATIONS.md`, enriched `INTEGRATIONS.md` (data class + PII + permissions per tie) and `OVERVIEW.md`
|
|
70
|
+
(Discovery & access), and bumped all docs to `source_head` `3f7b877`. Everything remains
|
|
71
|
+
`review_status: draft` -- a human must set `reviewed` before any audit use. Prior state: first
|
|
72
|
+
generation at git head `2abd6fc` (EP Projects Register pilot). The machine-readable delta is in
|
|
73
|
+
`CHANGES.jsonl`.
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schema_version": "1.
|
|
3
|
-
"generated_at": "2026-07-
|
|
2
|
+
"schema_version": "1.1",
|
|
3
|
+
"generated_at": "2026-07-09T07:08:00Z",
|
|
4
4
|
"repo_name": "ecoportal-api-graphql",
|
|
5
5
|
"repo_type": "ruby-gem",
|
|
6
|
-
"head": "
|
|
6
|
+
"head": "3f7b877",
|
|
7
7
|
"docs": [
|
|
8
8
|
{
|
|
9
9
|
"file": "docs/self-docs/OVERVIEW.md",
|
|
10
|
-
"purpose": "What the project is, who it serves, current priority",
|
|
10
|
+
"purpose": "What the project is, who it serves, current priority, discovery & access",
|
|
11
11
|
"exists": true,
|
|
12
|
-
"content_hash": "
|
|
12
|
+
"content_hash": "c403012498ab9247"
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"file": "docs/self-docs/ARCHITECTURE.md",
|
|
@@ -25,15 +25,27 @@
|
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"file": "docs/self-docs/INTEGRATIONS.md",
|
|
28
|
-
"purpose": "External services, upstream/downstream repos
|
|
28
|
+
"purpose": "External services + data + permissions, upstream/downstream repos",
|
|
29
29
|
"exists": true,
|
|
30
|
-
"content_hash": "
|
|
30
|
+
"content_hash": "d271ac76361e4638"
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"file": "docs/self-docs/STATUS.md",
|
|
34
34
|
"purpose": "Active work, migration state, known risks, open questions",
|
|
35
35
|
"exists": true,
|
|
36
|
-
"content_hash": "
|
|
36
|
+
"content_hash": "1acf51a3e7822dfb"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"file": "docs/self-docs/COMPLIANCE.md",
|
|
40
|
+
"purpose": "ISO-27001/audit, data classes + PII, vendors, AI content, leak controls",
|
|
41
|
+
"exists": true,
|
|
42
|
+
"content_hash": "1e72c2356b4e3579"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"file": "docs/self-docs/OPERATIONS.md",
|
|
46
|
+
"purpose": "Infrastructure, public exposure + Cloudflare, DevOps audit, usage KPIs",
|
|
47
|
+
"exists": true,
|
|
48
|
+
"content_hash": "878b50d02339acb7"
|
|
37
49
|
}
|
|
38
50
|
]
|
|
39
51
|
}
|
|
@@ -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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
@
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def
|
|
134
|
-
@
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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,15 +1,15 @@
|
|
|
1
|
-
module Ecoportal
|
|
2
|
-
module API
|
|
3
|
-
class GraphQL
|
|
4
|
-
module Base
|
|
5
|
-
class LocationClassificationType < Logic::BaseModel
|
|
6
|
-
root!
|
|
7
|
-
|
|
8
|
-
passkey :id
|
|
9
|
-
passthrough :name
|
|
10
|
-
passboolean :active
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class GraphQL
|
|
4
|
+
module Base
|
|
5
|
+
class LocationClassificationType < Logic::BaseModel
|
|
6
|
+
root!
|
|
7
|
+
|
|
8
|
+
passkey :id
|
|
9
|
+
passthrough :name
|
|
10
|
+
passboolean :active
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|