Compare commits

...

422 Commits

Author SHA1 Message Date
releaser 6e46a2cbe7 chore(release): prepare for v0.9.6 (#789)
Release / release (push) Successful in 50s
schemas / vulnerabilities (push) Successful in 2m4s
schemas / check (push) Successful in 2m32s
schemas / check-release (push) Successful in 2m54s
Goreleaser / release (push) Successful in 2m52s
pre-commit / pre-commit (push) Successful in 6m33s
schemas / build (push) Successful in 6m24s
schemas / deploy-prod (push) Has been skipped
## [0.9.6] - 2026-03-29

### 🐛 Bug Fixes

- *(graph)* Stabilize debouncer tests with synctest fake clock
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.257 (#743)
- *(deps)* Update golang.org/x/net to v0.51.0
- *(deps)* Update module go.opentelemetry.io/contrib/bridges/otelslog to v0.16.0 (#745)
- *(deps)* Update opentelemetry-go monorepo (#744)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.260 (#750)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.261 (#751)
- *(deps)* Update opentelemetry-go monorepo (#755)
- *(deps)* Update module go.opentelemetry.io/contrib/bridges/otelslog to v0.17.0 (#756)
- *(deps)* Update module golang.org/x/sync to v0.20.0 (#760)
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.88 (#762)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.262 (#765)
- *(deps)* Update module golang.org/x/crypto to v0.49.0 (#767)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.263 (#771)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.264 (#773)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.265 (#776)
- *(deps)* Update module gitlab.com/unboundsoftware/eventsourced/pg to v1.18.5 (#778)
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.89 (#782)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.266 (#784)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.267 (#786)
- Cap Node.js heap to prevent OOM during wgc composition (#788)

### ⚙️ Miscellaneous Tasks

- *(deps)* Update golang docker tag to v1.26.1 (#752)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.1
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.2 (#758)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.3 (#763)
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.30.1 (#769)
- *(deps)* Update golang:1.26.1 docker digest to 984bf90 (#774)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.4 (#780)
- *(deps)* Update node.js to v24.14.1 (#783)

<!-- generated by git-cliff -->

---

**Note:** Please use **Squash Merge** when merging this PR.

Reviewed-on: #789
Co-authored-by: Unbound Releaser <releaser@unbound.se>
Co-committed-by: Unbound Releaser <releaser@unbound.se>
2026-03-29 09:54:53 +00:00
argoyle 9a32cdb1b3 fix: cap Node.js heap to prevent OOM during wgc composition (#788)
schemas / vulnerabilities (push) Successful in 1m56s
schemas / check (push) Successful in 2m46s
schemas / check-release (push) Successful in 2m53s
schemas / build (push) Successful in 6m4s
pre-commit / pre-commit (push) Successful in 6m51s
schemas / deploy-prod (push) Successful in 1m14s
Release / release (push) Successful in 1m4s
## Summary
- Sets `NODE_OPTIONS=--max-old-space-size=64` in Dockerfile to cap Node.js heap when running `wgc router compose`
- Prevents container OOMKills in memory-constrained environments (e.g. acctest with 128Mi limit)
- The wgc Node.js process was using unbounded heap, which combined with the Go service easily exceeded container memory limits during rapid schema publishing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #788
2026-03-29 09:31:53 +00:00
renovate 1150e82c22 chore(deps): update node.js to v24.14.1 (#783)
Release / release (push) Successful in 1m8s
schemas / vulnerabilities (push) Successful in 2m7s
schemas / check (push) Successful in 2m48s
schemas / check-release (push) Successful in 3m14s
pre-commit / pre-commit (push) Successful in 6m51s
schemas / build (push) Successful in 7m5s
schemas / deploy-prod (push) Successful in 1m25s
2026-03-26 14:08:20 +00:00
renovate eaae92ec0b fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.267 (#786)
Release / release (push) Failing after 44s
schemas / vulnerabilities (push) Successful in 2m4s
schemas / check (push) Successful in 2m48s
schemas / check-release (push) Successful in 3m5s
pre-commit / pre-commit (push) Successful in 7m22s
schemas / build (push) Successful in 5m34s
schemas / deploy-prod (push) Successful in 1m20s
2026-03-26 12:24:33 +00:00
renovate e4fd2b2b63 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.266 (#784)
Release / release (push) Successful in 1m9s
schemas / check (push) Successful in 2m40s
schemas / vulnerabilities (push) Successful in 2m46s
schemas / check-release (push) Successful in 3m17s
pre-commit / pre-commit (push) Successful in 6m56s
schemas / build (push) Successful in 6m32s
schemas / deploy-prod (push) Successful in 1m17s
2026-03-26 11:23:35 +00:00
renovate 81af7a2416 fix(deps): update module github.com/99designs/gqlgen to v0.17.89 (#782)
Release / release (push) Failing after 57s
schemas / vulnerabilities (push) Successful in 2m21s
schemas / check (push) Successful in 2m38s
schemas / check-release (push) Successful in 2m48s
pre-commit / pre-commit (push) Successful in 6m50s
schemas / build (push) Successful in 10m16s
schemas / deploy-prod (push) Successful in 1m7s
2026-03-24 18:44:05 +00:00
renovate 62e9cef341 chore(deps): update pre-commit hook golangci/golangci-lint to v2.11.4 (#780)
schemas / vulnerabilities (push) Successful in 2m10s
schemas / check (push) Successful in 2m34s
schemas / check-release (push) Successful in 2m54s
Release / release (push) Successful in 58s
pre-commit / pre-commit (push) Successful in 7m10s
schemas / build (push) Successful in 12m21s
schemas / deploy-prod (push) Successful in 1m17s
2026-03-22 19:43:56 +00:00
renovate 972968f865 fix(deps): update module gitlab.com/unboundsoftware/eventsourced/pg to v1.18.5 (#778)
schemas / vulnerabilities (push) Successful in 1m54s
schemas / check (push) Successful in 2m51s
Release / release (push) Successful in 1m11s
schemas / check-release (push) Successful in 3m0s
pre-commit / pre-commit (push) Successful in 6m49s
schemas / build (push) Successful in 13m23s
schemas / deploy-prod (push) Successful in 1m35s
2026-03-19 10:00:34 +00:00
renovate 1ede47df82 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.265 (#776)
Release / release (push) Successful in 1m10s
schemas / vulnerabilities (push) Successful in 2m5s
schemas / check (push) Successful in 2m40s
schemas / check-release (push) Successful in 3m16s
pre-commit / pre-commit (push) Successful in 6m50s
schemas / build (push) Successful in 8m47s
schemas / deploy-prod (push) Successful in 1m14s
2026-03-17 14:22:34 +00:00
renovate 882b87e882 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.264 (#773)
schemas / vulnerabilities (push) Successful in 1m42s
schemas / check (push) Successful in 2m41s
Release / release (push) Successful in 1m12s
schemas / check-release (push) Successful in 3m11s
pre-commit / pre-commit (push) Successful in 7m12s
schemas / build (push) Successful in 8m28s
schemas / deploy-prod (push) Successful in 1m12s
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [github.com/wundergraph/graphql-go-tools/v2](https://github.com/wundergraph/graphql-go-tools) | `v2.0.0-rc.263` → `v2.0.0-rc.264` | ![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fwundergraph%2fgraphql-go-tools%2fv2/v2.0.0-rc.264?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fwundergraph%2fgraphql-go-tools%2fv2/v2.0.0-rc.263/v2.0.0-rc.264?slim=true) |

---

### Release Notes

<details>
<summary>wundergraph/graphql-go-tools (github.com/wundergraph/graphql-go-tools/v2)</summary>

### [`v2.0.0-rc.264`](https://github.com/wundergraph/graphql-go-tools/releases/tag/v2.0.0-rc.264)

[Compare Source](https://github.com/wundergraph/graphql-go-tools/compare/v2.0.0-rc.263...v2.0.0-rc.264)

##### Features

- support sizedFields in cost computations ([#&#8203;1410](https://github.com/wundergraph/graphql-go-tools/issues/1410)) ([48f7582](https://github.com/wundergraph/graphql-go-tools/commit/48f75821b0d7e0ffb5f7e66cffba3d1ad0cbf9dc))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My41OS40IiwidXBkYXRlZEluVmVyIjoiNDMuNTkuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: https://gitea.unbound.se/unboundsoftware/schemas/pulls/773
Co-authored-by: Renovate Bot <renovate@unbound.se>
Co-committed-by: Renovate Bot <renovate@unbound.se>
2026-03-17 05:48:52 +00:00
renovate 85dc71fe24 chore(deps): update golang:1.26.1 docker digest to 984bf90 (#774)
schemas / check (push) Successful in 2m23s
schemas / vulnerabilities (push) Successful in 2m3s
Release / release (push) Failing after 53s
schemas / check-release (push) Successful in 3m55s
pre-commit / pre-commit (push) Successful in 7m43s
schemas / build (push) Successful in 8m22s
schemas / deploy-prod (push) Successful in 1m13s
2026-03-17 04:11:31 +00:00
renovate a7804ac970 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.263 (#771)
Release / release (push) Successful in 1m52s
schemas / vulnerabilities (push) Successful in 2m33s
schemas / check (push) Successful in 3m4s
schemas / check-release (push) Successful in 3m42s
schemas / build (push) Successful in 6m44s
pre-commit / pre-commit (push) Successful in 11m4s
schemas / deploy-prod (push) Successful in 1m45s
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [github.com/wundergraph/graphql-go-tools/v2](https://github.com/wundergraph/graphql-go-tools) | `v2.0.0-rc.262` → `v2.0.0-rc.263` | ![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fwundergraph%2fgraphql-go-tools%2fv2/v2.0.0-rc.263?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fwundergraph%2fgraphql-go-tools%2fv2/v2.0.0-rc.262/v2.0.0-rc.263?slim=true) |

---

### Release Notes

<details>
<summary>wundergraph/graphql-go-tools (github.com/wundergraph/graphql-go-tools/v2)</summary>

### [`v2.0.0-rc.263`](https://github.com/wundergraph/graphql-go-tools/releases/tag/v2.0.0-rc.263)

[Compare Source](https://github.com/wundergraph/graphql-go-tools/compare/v2.0.0-rc.262...v2.0.0-rc.263)

##### Features

- support field resolvers with empty arguments ([#&#8203;1440](https://github.com/wundergraph/graphql-go-tools/issues/1440)) ([58305bc](https://github.com/wundergraph/graphql-go-tools/commit/58305bc61a774e0ced6def84f63f75f5c4556dc8))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My41OS40IiwidXBkYXRlZEluVmVyIjoiNDMuNTkuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: https://gitea.unbound.se/unboundsoftware/schemas/pulls/771
Co-authored-by: Renovate Bot <renovate@unbound.se>
Co-committed-by: Renovate Bot <renovate@unbound.se>
2026-03-13 12:37:04 +00:00
renovate a94f9cb0c0 chore(deps): update pre-commit hook gitleaks/gitleaks to v8.30.1 (#769)
Release / release (push) Successful in 1m15s
schemas / vulnerabilities (push) Successful in 2m18s
schemas / check (push) Successful in 2m52s
schemas / check-release (push) Successful in 4m12s
pre-commit / pre-commit (push) Successful in 8m1s
schemas / build (push) Successful in 5m49s
schemas / deploy-prod (push) Successful in 1m9s
2026-03-12 17:19:56 +00:00
renovate 68870bb37e fix(deps): update module golang.org/x/crypto to v0.49.0 (#767)
Release / release (push) Successful in 59s
schemas / vulnerabilities (push) Successful in 1m30s
schemas / check (push) Successful in 1m52s
schemas / check-release (push) Successful in 2m30s
pre-commit / pre-commit (push) Successful in 4m53s
schemas / build (push) Successful in 4m55s
schemas / deploy-prod (push) Successful in 59s
2026-03-11 23:17:04 +00:00
renovate d8e34ccae5 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.262 (#765)
Release / release (push) Successful in 1m17s
schemas / vulnerabilities (push) Successful in 2m43s
schemas / check (push) Successful in 3m2s
schemas / check-release (push) Successful in 3m51s
pre-commit / pre-commit (push) Successful in 6m55s
schemas / build (push) Successful in 7m24s
schemas / deploy-prod (push) Successful in 1m11s
2026-03-10 14:20:15 +00:00
renovate fdfdce46c0 chore(deps): update pre-commit hook golangci/golangci-lint to v2.11.3 (#763)
Release / release (push) Successful in 1m12s
schemas / check (push) Successful in 2m34s
schemas / vulnerabilities (push) Successful in 2m35s
schemas / check-release (push) Successful in 3m25s
schemas / build (push) Successful in 10m27s
pre-commit / pre-commit (push) Successful in 14m31s
schemas / deploy-prod (push) Successful in 1m11s
2026-03-10 11:39:58 +00:00
renovate 357be92d1e fix(deps): update module github.com/99designs/gqlgen to v0.17.88 (#762)
schemas / vulnerabilities (push) Successful in 1m57s
schemas / check (push) Successful in 2m42s
Release / release (push) Failing after 1m6s
schemas / check-release (push) Successful in 3m42s
pre-commit / pre-commit (push) Successful in 6m7s
schemas / build (push) Successful in 5m25s
schemas / deploy-prod (push) Successful in 1m25s
2026-03-09 11:50:57 +00:00
renovate 4edddee47b fix(deps): update module golang.org/x/sync to v0.20.0 (#760)
Release / release (push) Successful in 1m2s
schemas / check (push) Successful in 1m55s
schemas / vulnerabilities (push) Successful in 1m58s
schemas / check-release (push) Successful in 2m33s
pre-commit / pre-commit (push) Successful in 6m0s
schemas / build (push) Successful in 5m10s
schemas / deploy-prod (push) Successful in 1m16s
2026-03-08 13:17:26 +00:00
renovate f6df1a51da chore(deps): update pre-commit hook golangci/golangci-lint to v2.11.2 (#758)
schemas / vulnerabilities (push) Successful in 1m47s
Release / release (push) Successful in 1m21s
schemas / check (push) Successful in 2m27s
schemas / check-release (push) Successful in 2m48s
pre-commit / pre-commit (push) Successful in 5m42s
schemas / build (push) Successful in 5m42s
schemas / deploy-prod (push) Successful in 59s
2026-03-08 08:39:33 +00:00
renovate 7c109f3c69 fix(deps): update module go.opentelemetry.io/contrib/bridges/otelslog to v0.17.0 (#756)
Release / release (push) Successful in 1m8s
schemas / vulnerabilities (push) Successful in 2m5s
schemas / check (push) Successful in 2m40s
schemas / check-release (push) Successful in 3m0s
pre-commit / pre-commit (push) Successful in 7m44s
schemas / build (push) Successful in 8m45s
schemas / deploy-prod (push) Successful in 1m19s
2026-03-06 21:42:28 +00:00
renovate f9d0b470c9 fix(deps): update opentelemetry-go monorepo (#755)
schemas / vulnerabilities (push) Successful in 1m53s
Release / release (push) Failing after 2m30s
schemas / check (push) Successful in 2m59s
schemas / check-release (push) Successful in 3m11s
pre-commit / pre-commit (push) Successful in 6m2s
schemas / build (push) Successful in 21m13s
schemas / deploy-prod (push) Successful in 1m15s
2026-03-06 20:35:36 +00:00
argoyle 924588bef9 Merge pull request 'chore(deps): update pre-commit hook golangci/golangci-lint to v2.11.1' (#754) from renovate/golangci-golangci-lint-2.x into main
Release / release (push) Failing after 1m2s
schemas / vulnerabilities (push) Successful in 2m20s
schemas / check (push) Successful in 2m44s
schemas / check-release (push) Successful in 3m15s
pre-commit / pre-commit (push) Successful in 8m30s
schemas / build (push) Successful in 22m36s
schemas / deploy-prod (push) Successful in 1m30s
Reviewed-on: #754
2026-03-06 19:58:31 +00:00
renovate 086c2175d9 chore(deps): update pre-commit hook golangci/golangci-lint to v2.11.1
schemas / vulnerabilities (pull_request) Successful in 1m49s
schemas / check (pull_request) Successful in 2m22s
schemas / check-release (pull_request) Successful in 3m49s
pre-commit / pre-commit (pull_request) Successful in 6m56s
schemas / build (pull_request) Successful in 6m12s
schemas / deploy-prod (pull_request) Has been skipped
2026-03-06 19:35:23 +00:00
renovate d657883648 chore(deps): update golang docker tag to v1.26.1 (#752)
Release / release (push) Successful in 1m6s
schemas / vulnerabilities (push) Successful in 2m15s
schemas / check (push) Successful in 2m51s
schemas / check-release (push) Successful in 3m20s
pre-commit / pre-commit (push) Successful in 9m41s
schemas / build (push) Successful in 6m38s
schemas / deploy-prod (push) Successful in 1m18s
2026-03-06 03:26:08 +00:00
renovate bd7497639d fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.261 (#751)
Release / release (push) Failing after 48s
schemas / vulnerabilities (push) Successful in 1m48s
schemas / check (push) Successful in 2m25s
schemas / check-release (push) Successful in 3m46s
pre-commit / pre-commit (push) Successful in 6m41s
schemas / build (push) Successful in 5m42s
schemas / deploy-prod (push) Successful in 1m50s
2026-03-06 02:32:53 +00:00
renovate 9db7b89d9f fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.260 (#750)
Release / release (push) Failing after 1m18s
schemas / vulnerabilities (push) Successful in 2m26s
schemas / check (push) Successful in 2m46s
schemas / check-release (push) Successful in 3m16s
pre-commit / pre-commit (push) Successful in 7m26s
schemas / build (push) Successful in 5m53s
schemas / deploy-prod (push) Successful in 1m17s
2026-03-05 15:06:04 +00:00
renovate a01211c089 fix(deps): update opentelemetry-go monorepo (#744)
Release / release (push) Successful in 1m28s
schemas / vulnerabilities (push) Successful in 2m16s
schemas / check-release (push) Successful in 4m58s
schemas / check (push) Successful in 5m27s
pre-commit / pre-commit (push) Successful in 8m31s
schemas / build (push) Successful in 8m24s
schemas / deploy-prod (push) Successful in 1m10s
2026-03-03 10:19:26 +00:00
renovate 20ca68ad4c fix(deps): update module go.opentelemetry.io/contrib/bridges/otelslog to v0.16.0 (#745)
Release / release (push) Successful in 1m32s
schemas / vulnerabilities (push) Successful in 3m27s
schemas / check-release (push) Successful in 4m25s
schemas / check (push) Successful in 5m21s
schemas / build (push) Successful in 5m44s
schemas / deploy-prod (push) Successful in 2m17s
pre-commit / pre-commit (push) Successful in 12m58s
2026-03-03 09:22:41 +00:00
argoyle 87d1e631f9 Merge pull request 'fix(deps): update golang.org/x/net to v0.51.0' (#746) from fix-vulncheck-xnet into main
Release / release (push) Successful in 1m17s
schemas / vulnerabilities (push) Successful in 3m20s
schemas / check-release (push) Successful in 4m5s
schemas / check (push) Successful in 5m59s
pre-commit / pre-commit (push) Successful in 9m7s
schemas / build (push) Successful in 6m50s
schemas / deploy-prod (push) Successful in 1m13s
Reviewed-on: #746
2026-03-03 08:22:09 +00:00
argoyle c98df1b4e7 fix(deps): update golang.org/x/net to v0.51.0
schemas / vulnerabilities (pull_request) Successful in 2m4s
schemas / check-release (pull_request) Successful in 4m15s
schemas / check (pull_request) Successful in 5m19s
pre-commit / pre-commit (pull_request) Successful in 9m45s
schemas / build (pull_request) Successful in 9m1s
schemas / deploy-prod (pull_request) Has been skipped
2026-03-03 09:03:39 +01:00
renovate 7bd605d90f fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.257 (#743)
Release / release (push) Failing after 1m16s
schemas / vulnerabilities (push) Successful in 2m20s
schemas / check-release (push) Successful in 3m46s
schemas / check (push) Successful in 4m47s
pre-commit / pre-commit (push) Successful in 9m12s
schemas / build (push) Successful in 6m18s
schemas / deploy-prod (push) Successful in 1m2s
2026-02-25 15:22:49 +00:00
argoyle 60bec67023 Merge pull request 'fix(graph): stabilize debouncer tests with synctest fake clock' (#741) from fix-flaky-debouncer-test into main
Release / release (push) Successful in 1m15s
schemas / vulnerabilities (push) Successful in 2m43s
schemas / check-release (push) Successful in 4m22s
schemas / check (push) Successful in 6m22s
pre-commit / pre-commit (push) Successful in 12m20s
schemas / build (push) Successful in 9m21s
schemas / deploy-prod (push) Successful in 1m13s
Reviewed-on: #741
2026-02-25 12:49:10 +00:00
argoyle cc33c651cd fix(graph): stabilize debouncer tests with synctest fake clock
schemas / vulnerabilities (pull_request) Successful in 2m17s
schemas / check-release (pull_request) Successful in 4m9s
schemas / check (pull_request) Successful in 4m52s
pre-commit / pre-commit (pull_request) Successful in 9m43s
schemas / build (pull_request) Successful in 6m58s
schemas / deploy-prod (pull_request) Has been skipped
Replace real time.Sleep waits with testing/synctest fake clock to
eliminate CI flakiness caused by timer races on loaded machines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 13:25:18 +01:00
argoyle 38b4ab8ee2 Merge pull request 'chore(release): prepare for v0.9.5' (#740) from next-release into main
Release / release (push) Successful in 1m0s
schemas / vulnerabilities (push) Successful in 4m2s
Goreleaser / release (push) Successful in 5m8s
schemas / check-release (push) Successful in 6m38s
schemas / check (push) Successful in 8m59s
schemas / build (push) Successful in 6m0s
schemas / deploy-prod (push) Has been skipped
pre-commit / pre-commit (push) Successful in 8m14s
Reviewed-on: #740
2026-02-25 11:32:35 +00:00
releaser 1fa91ad573 chore(release): prepare for v0.9.5
schemas / vulnerabilities (pull_request) Successful in 2m48s
schemas / check-release (pull_request) Successful in 5m48s
schemas / check (pull_request) Successful in 5m48s
schemas / build (pull_request) Successful in 12m9s
schemas / deploy-prod (pull_request) Has been skipped
pre-commit / pre-commit (pull_request) Successful in 12m36s
2026-02-25 06:27:43 +00:00
releaser 7182ae2a11 chore(release): prepare for v0.9.5 2026-02-25 06:27:39 +00:00
renovate 19b7f0edb6 chore(deps): update node.js to 7fddd9d (#739)
Release / release (push) Successful in 1m36s
schemas / vulnerabilities (push) Successful in 3m25s
schemas / check-release (push) Successful in 5m10s
schemas / check (push) Successful in 6m21s
pre-commit / pre-commit (push) Successful in 11m40s
schemas / build (push) Successful in 6m36s
schemas / deploy-prod (push) Successful in 1m13s
2026-02-25 06:26:07 +00:00
renovate 0b68cadd24 chore(deps): update node.js to v24.14.0 (#735)
Release / release (push) Successful in 1m5s
schemas / vulnerabilities (push) Successful in 2m51s
schemas / check-release (push) Successful in 3m42s
schemas / check (push) Successful in 6m23s
pre-commit / pre-commit (push) Successful in 10m4s
schemas / build (push) Successful in 7m19s
schemas / deploy-prod (push) Successful in 1m11s
2026-02-25 00:28:09 +00:00
renovate 50087adeac chore(deps): update golang:1.26.0 docker digest to 9835fb4 (#736)
schemas / vulnerabilities (push) Successful in 2m38s
schemas / check (push) Successful in 4m25s
schemas / check-release (push) Successful in 4m34s
Release / release (push) Successful in 1m23s
pre-commit / pre-commit (push) Successful in 17m31s
schemas / build (push) Successful in 26m14s
schemas / deploy-prod (push) Successful in 1m16s
2026-02-24 22:44:21 +00:00
argoyle 03c903cf0e Merge pull request 'fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.256' (#733) from renovate/github.com-wundergraph-graphql-go-tools-v2-2.x into main
Release / release (push) Successful in 2m0s
schemas / vulnerabilities (push) Successful in 4m40s
schemas / check-release (push) Successful in 10m7s
schemas / check (push) Successful in 11m58s
schemas / build (push) Successful in 9m21s
schemas / deploy-prod (push) Successful in 2m12s
pre-commit / pre-commit (push) Successful in 24m26s
Reviewed-on: #733
2026-02-23 16:32:56 +00:00
renovate 59d0d40d4d fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.256
schemas / vulnerabilities (pull_request) Successful in 4m44s
schemas / check-release (pull_request) Successful in 10m55s
schemas / check (pull_request) Successful in 12m29s
pre-commit / pre-commit (pull_request) Successful in 20m0s
schemas / build (pull_request) Successful in 10m4s
schemas / deploy-prod (pull_request) Has been skipped
2026-02-23 16:07:55 +00:00
argoyle 3bb0511277 Merge pull request 'chore(release): prepare for v0.9.4' (#732) from next-release into main
Release / release (push) Successful in 1m36s
schemas / vulnerabilities (push) Successful in 2m31s
schemas / check-release (push) Successful in 5m33s
schemas / check (push) Successful in 6m17s
pre-commit / pre-commit (push) Successful in 10m5s
Goreleaser / release (push) Successful in 3m39s
schemas / build (push) Failing after 1m1s
schemas / deploy-prod (push) Has been skipped
Reviewed-on: #732
2026-02-23 13:30:22 +00:00
releaser f0fadb4ab7 chore(release): prepare for v0.9.4
schemas / vulnerabilities (pull_request) Successful in 11m30s
schemas / check (pull_request) Successful in 17m40s
pre-commit / pre-commit (pull_request) Successful in 22m31s
schemas / check-release (pull_request) Successful in 25m51s
schemas / build (pull_request) Successful in 23m52s
schemas / deploy-prod (pull_request) Has been skipped
2026-02-23 12:39:51 +00:00
releaser 59c128fe0c chore(release): prepare for v0.9.4 2026-02-23 12:39:47 +00:00
argoyle 9518403394 Merge pull request 'fix(ci): pin goreleaser to v2.13.3 for Gitea SDK compatibility' (#731) from fix-goreleaser-gitea-sdk-compat into main
schemas / vulnerabilities (push) Successful in 3m20s
schemas / check-release (push) Successful in 4m57s
Release / release (push) Successful in 2m12s
schemas / check (push) Successful in 18m55s
pre-commit / pre-commit (push) Successful in 16m38s
schemas / build (push) Failing after 27m35s
schemas / deploy-prod (push) Has been skipped
Reviewed-on: #731
2026-02-23 12:28:21 +00:00
argoyle 3b4e513653 fix(ci): pin goreleaser to v2.13.3 for Gitea SDK compatibility
schemas / vulnerabilities (pull_request) Successful in 2m19s
schemas / check-release (pull_request) Successful in 6m5s
schemas / check (pull_request) Successful in 6m24s
pre-commit / pre-commit (pull_request) Successful in 12m13s
schemas / build (pull_request) Successful in 7m21s
schemas / deploy-prod (pull_request) Has been skipped
GoReleaser v2.14.0 upgraded the Gitea SDK from v0.22.1 to v0.23.2,
which fails to parse the Gitea server version during homebrew cask
publishing (error: unknown version: daf0483). Pin to v2.13.3 until
the Gitea server is updated to a compatible version.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 13:08:38 +01:00
argoyle fe1de50ded Merge pull request 'chore(release): prepare for v0.9.3' (#730) from next-release into main
Release / release (push) Successful in 56s
schemas / vulnerabilities (push) Successful in 8m44s
schemas / check-release (push) Successful in 16m53s
schemas / check (push) Successful in 21m15s
pre-commit / pre-commit (push) Successful in 29m33s
schemas / build (push) Successful in 11m14s
schemas / deploy-prod (push) Successful in 1m34s
Goreleaser / release (push) Failing after 5m37s
Reviewed-on: #730
2026-02-23 08:14:51 +00:00
releaser 2d243469ed chore(release): prepare for v0.9.3
schemas / vulnerabilities (pull_request) Successful in 9m25s
schemas / check-release (pull_request) Successful in 16m59s
schemas / check (pull_request) Successful in 27m0s
pre-commit / pre-commit (pull_request) Successful in 32m15s
schemas / build (pull_request) Successful in 7m55s
schemas / deploy-prod (pull_request) Has been skipped
2026-02-23 07:39:42 +00:00
releaser a23aa5e5e9 chore(release): prepare for v0.9.3 2026-02-23 07:39:39 +00:00
argoyle 2286e092d9 Merge pull request 'fix: prevent OOM on rapid schema publishing' (#729) from fix-oom-rapid-schema-publishing into main
schemas / vulnerabilities (push) Successful in 5m37s
Release / release (push) Successful in 1m42s
schemas / check-release (push) Successful in 14m32s
schemas / check (push) Successful in 22m34s
schemas / build (push) Successful in 11m37s
schemas / deploy-prod (push) Successful in 2m35s
pre-commit / pre-commit (push) Successful in 37m18s
Reviewed-on: #729
2026-02-23 07:31:08 +00:00
argoyle 28aa32ad8c fix: prevent OOM on rapid schema publishing
schemas / vulnerabilities (pull_request) Successful in 6m43s
schemas / check-release (pull_request) Successful in 11m27s
schemas / check (pull_request) Successful in 14m51s
pre-commit / pre-commit (pull_request) Successful in 19m39s
schemas / build (pull_request) Successful in 8m26s
schemas / deploy-prod (pull_request) Has been skipped
Add concurrency-limited CosmoGenerator (semaphore limit=1, 60s timeout)
to prevent unbounded concurrent wgc process spawning. Add debouncer
(500ms) to coalesce rapid schema updates per org+ref. Fix double
subgraph fetch in Supergraph resolver and goroutine leak in
SchemaUpdates subscription.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 08:05:47 +01:00
renovate a9885f8b65 fix(deps): update module gitlab.com/unboundsoftware/eventsourced/pg to v1.18.4 (#727)
Release / release (push) Successful in 1m48s
schemas / vulnerabilities (push) Successful in 3m31s
schemas / check-release (push) Successful in 19m53s
schemas / check (push) Successful in 28m16s
pre-commit / pre-commit (push) Successful in 41m21s
schemas / build (push) Successful in 15m44s
schemas / deploy-prod (push) Successful in 1m33s
2026-02-22 23:07:02 +00:00
argoyle 2fbe44fea1 Merge pull request 'fix(deps): update module github.com/pressly/goose/v3 to v3.27.0' (#725) from renovate/github.com-pressly-goose-v3-3.x into main
Release / release (push) Successful in 1m41s
schemas / vulnerabilities (push) Successful in 7m40s
schemas / check-release (push) Successful in 12m45s
schemas / check (push) Successful in 25m22s
pre-commit / pre-commit (push) Successful in 43m42s
schemas / build (push) Successful in 21m37s
schemas / deploy-prod (push) Successful in 1m47s
Reviewed-on: #725
2026-02-22 18:55:45 +00:00
renovate 80d08c1ba7 fix(deps): update module github.com/pressly/goose/v3 to v3.27.0
schemas / vulnerabilities (pull_request) Successful in 13m33s
schemas / check-release (pull_request) Successful in 31m22s
schemas / check (pull_request) Successful in 38m33s
pre-commit / pre-commit (pull_request) Successful in 57m7s
schemas / build (pull_request) Successful in 28m17s
schemas / deploy-prod (pull_request) Has been skipped
2026-02-22 17:09:40 +00:00
argoyle d6866b9f72 Merge pull request 'chore(deps): update actions/setup-node action to v6' (#724) from renovate/actions-setup-node-6.x into main
Release / release (push) Failing after 1m50s
schemas / vulnerabilities (push) Successful in 4m33s
schemas / check-release (push) Successful in 7m19s
schemas / check (push) Successful in 8m18s
pre-commit / pre-commit (push) Successful in 16m9s
schemas / build (push) Successful in 10m21s
schemas / deploy-prod (push) Successful in 1m1s
Reviewed-on: #724
2026-02-22 13:22:13 +00:00
renovate 3c1fc68016 chore(deps): update actions/setup-node action to v6
schemas / vulnerabilities (pull_request) Successful in 1m46s
schemas / check-release (pull_request) Successful in 4m33s
schemas / check (pull_request) Successful in 11m3s
pre-commit / pre-commit (pull_request) Successful in 14m11s
schemas / build (pull_request) Successful in 4m31s
schemas / deploy-prod (pull_request) Has been skipped
2026-02-22 11:35:29 +00:00
argoyle 817449afbc Merge pull request 'fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.255' (#721) from renovate/github.com-wundergraph-graphql-go-tools-v2-2.x into main
Release / release (push) Successful in 1m18s
schemas / vulnerabilities (push) Successful in 6m17s
schemas / check-release (push) Successful in 7m2s
schemas / check (push) Successful in 8m8s
schemas / build (push) Successful in 15m11s
schemas / deploy-prod (push) Successful in 1m57s
pre-commit / pre-commit (push) Successful in 25m52s
Reviewed-on: #721
2026-02-22 11:14:35 +00:00
argoyle 856423a221 Merge pull request 'chore(deps): update goreleaser/goreleaser-action action to v7' (#720) from renovate/goreleaser-goreleaser-action-7.x into main
Release / release (push) Successful in 2m17s
schemas / vulnerabilities (push) Successful in 4m32s
schemas / check (push) Successful in 7m22s
schemas / check-release (push) Successful in 8m32s
schemas / build (push) Successful in 7m4s
schemas / deploy-prod (push) Successful in 1m11s
pre-commit / pre-commit (push) Successful in 18m44s
Reviewed-on: #720
2026-02-22 10:48:57 +00:00
renovate 3c43f1bd80 chore(deps): update goreleaser/goreleaser-action action to v7
schemas / vulnerabilities (pull_request) Successful in 2m19s
pre-commit / pre-commit (pull_request) Successful in 11m12s
schemas / check-release (pull_request) Successful in 14m14s
schemas / check (pull_request) Successful in 19m52s
schemas / build (pull_request) Successful in 7m24s
schemas / deploy-prod (pull_request) Has been skipped
2026-02-22 10:03:48 +01:00
renovate 4fd36c1330 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.255
schemas / vulnerabilities (pull_request) Successful in 4m39s
schemas / check-release (pull_request) Successful in 6m29s
schemas / check (pull_request) Successful in 9m54s
pre-commit / pre-commit (pull_request) Successful in 12m35s
schemas / build (pull_request) Successful in 7m25s
schemas / deploy-prod (pull_request) Has been skipped
2026-02-21 22:06:11 +00:00
renovate 9411a2dd7d fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.254 (#718)
schemas / vulnerabilities (push) Successful in 3m34s
Release / release (push) Successful in 5m28s
schemas / check-release (push) Successful in 5m53s
schemas / check (push) Successful in 10m11s
pre-commit / pre-commit (push) Successful in 19m21s
schemas / build (push) Successful in 9m58s
schemas / deploy-prod (push) Successful in 2m14s
2026-02-20 20:07:54 +00:00
argoyle f1dc71163b Merge pull request 'fix(deps): update module github.com/99designs/gqlgen to v0.17.87' (#713) from renovate/github.com-99designs-gqlgen-0.x into main
schemas / vulnerabilities (push) Successful in 4m50s
Release / release (push) Successful in 5m18s
schemas / check-release (push) Successful in 6m1s
schemas / check (push) Successful in 10m14s
pre-commit / pre-commit (push) Successful in 12m39s
schemas / build (push) Successful in 6m49s
schemas / deploy-prod (push) Successful in 1m44s
Reviewed-on: #713
2026-02-20 14:04:17 +00:00
renovate 08e84ac1dc fix(deps): update module github.com/99designs/gqlgen to v0.17.87
schemas / check-release (pull_request) Successful in 7m11s
schemas / check (pull_request) Successful in 10m16s
pre-commit / pre-commit (pull_request) Successful in 13m23s
schemas / vulnerabilities (pull_request) Successful in 21m6s
schemas / build (pull_request) Successful in 8m44s
schemas / deploy-prod (pull_request) Has been skipped
2026-02-20 14:26:46 +01:00
renovate 3ea1ef520c fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.253 (#716)
Release / release (push) Failing after 1m32s
schemas / vulnerabilities (push) Successful in 2m38s
schemas / check-release (push) Successful in 3m21s
schemas / check (push) Successful in 6m6s
pre-commit / pre-commit (push) Successful in 9m19s
schemas / build (push) Successful in 6m3s
schemas / deploy-prod (push) Failing after 2m59s
2026-02-20 12:24:11 +00:00
renovate e30ab93a47 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.252 (#714)
schemas / vulnerabilities (push) Successful in 2m4s
Release / release (push) Successful in 2m23s
schemas / check-release (push) Successful in 3m36s
schemas / check (push) Successful in 5m25s
pre-commit / pre-commit (push) Successful in 9m0s
schemas / build (push) Successful in 11m55s
schemas / deploy-prod (push) Successful in 2m19s
2026-02-19 16:30:35 +00:00
argoyle 0eb59235c1 Merge pull request 'fix(deps): update module github.com/vektah/gqlparser/v2 to v2.5.32' (#711) from renovate/github.com-vektah-gqlparser-v2-2.x into main
schemas / vulnerabilities (push) Successful in 4m16s
schemas / check-release (push) Successful in 5m2s
Release / release (push) Successful in 3m50s
schemas / check (push) Successful in 9m44s
pre-commit / pre-commit (push) Successful in 16m21s
schemas / build (push) Successful in 12m51s
schemas / deploy-prod (push) Failing after 4m59s
Reviewed-on: #711
2026-02-19 15:27:00 +00:00
renovate 31022037ff fix(deps): update module github.com/vektah/gqlparser/v2 to v2.5.32
schemas / vulnerabilities (pull_request) Successful in 4m17s
schemas / check-release (pull_request) Successful in 6m2s
schemas / check (pull_request) Successful in 9m14s
pre-commit / pre-commit (pull_request) Successful in 12m3s
schemas / build (pull_request) Successful in 9m27s
schemas / deploy-prod (pull_request) Has been skipped
2026-02-19 13:44:52 +00:00
argoyle 085bf35d92 Merge pull request 'fix(deps): update module gitlab.com/unboundsoftware/eventsourced/pg to v1.18.3' (#708) from renovate/eventsourced into main
Release / release (push) Successful in 1m53s
schemas / vulnerabilities (push) Successful in 4m17s
schemas / check-release (push) Successful in 8m14s
schemas / check (push) Successful in 12m36s
pre-commit / pre-commit (push) Successful in 17m14s
schemas / build (push) Successful in 11m43s
schemas / deploy-prod (push) Successful in 1m32s
Reviewed-on: #708
2026-02-18 12:47:52 +00:00
argoyle 9d655e1a23 Merge pull request 'fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.251' (#707) from renovate/github.com-wundergraph-graphql-go-tools-v2-2.x into main
Release / release (push) Successful in 1m11s
schemas / vulnerabilities (push) Successful in 3m35s
schemas / check-release (push) Successful in 5m23s
schemas / check (push) Successful in 10m11s
schemas / build (push) Successful in 7m50s
schemas / deploy-prod (push) Successful in 2m15s
pre-commit / pre-commit (push) Successful in 20m42s
Reviewed-on: #707
2026-02-18 12:46:17 +00:00
renovate eee1d45d8c fix(deps): update module gitlab.com/unboundsoftware/eventsourced/pg to v1.18.3
schemas / vulnerabilities (pull_request) Successful in 10m29s
schemas / check-release (pull_request) Successful in 25m55s
schemas / check (pull_request) Successful in 43m26s
pre-commit / pre-commit (pull_request) Successful in 55m0s
schemas / build (pull_request) Successful in 29m31s
schemas / deploy-prod (pull_request) Has been skipped
2026-02-18 11:08:55 +00:00
renovate 782185981f fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.251
schemas / vulnerabilities (pull_request) Successful in 10m52s
schemas / check-release (pull_request) Successful in 12m47s
schemas / check (pull_request) Successful in 30m49s
pre-commit / pre-commit (pull_request) Successful in 57m57s
schemas / build (pull_request) Successful in 22m15s
schemas / deploy-prod (pull_request) Has been skipped
2026-02-18 11:08:42 +00:00
renovate e3f156fe9e chore(deps): update pre-commit hook golangci/golangci-lint to v2.10.1 (#706)
Release / release (push) Failing after 1m29s
schemas / check (push) Successful in 3m12s
schemas / vulnerabilities (push) Successful in 5m8s
schemas / check-release (push) Successful in 6m8s
pre-commit / pre-commit (push) Successful in 14m2s
schemas / build (push) Successful in 9m32s
schemas / deploy-prod (push) Successful in 2m4s
2026-02-17 17:55:12 +00:00
renovate 230876b523 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.250 (#704)
Release / release (push) Failing after 2m21s
schemas / vulnerabilities (push) Successful in 4m47s
schemas / check-release (push) Successful in 10m4s
schemas / check (push) Successful in 12m57s
pre-commit / pre-commit (push) Successful in 15m14s
schemas / build (push) Successful in 9m15s
schemas / deploy-prod (push) Successful in 51s
2026-02-16 17:05:42 +00:00
renovate e64adc96ae fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.249 (#702)
Release / release (push) Successful in 2m53s
schemas / vulnerabilities (push) Successful in 4m10s
schemas / check (push) Successful in 11m2s
schemas / check-release (push) Successful in 11m33s
pre-commit / pre-commit (push) Successful in 16m21s
schemas / build (push) Successful in 6m34s
schemas / deploy-prod (push) Successful in 57s
2026-02-13 21:35:11 +00:00
argoyle f534752e2c Merge pull request 'chore(release): prepare for v0.9.2' (#701) from next-release into main
Release / release (push) Successful in 1m20s
schemas / vulnerabilities (push) Successful in 5m8s
schemas / check (push) Successful in 8m3s
schemas / check-release (push) Successful in 7m12s
Goreleaser / release (push) Successful in 7m12s
pre-commit / pre-commit (push) Successful in 12m36s
schemas / build (push) Successful in 5m40s
schemas / deploy-prod (push) Successful in 54s
Reviewed-on: #701
2026-02-13 09:28:17 +00:00
releaser 42f4c8014d chore(release): prepare for v0.9.2
schemas / vulnerabilities (pull_request) Successful in 3m25s
schemas / check-release (pull_request) Successful in 4m6s
schemas / check (pull_request) Successful in 7m0s
schemas / build (pull_request) Successful in 4m17s
schemas / deploy-prod (pull_request) Has been skipped
pre-commit / pre-commit (pull_request) Successful in 10m25s
2026-02-13 09:14:54 +00:00
releaser e098562e0c chore(release): prepare for v0.9.2 2026-02-13 09:14:50 +00:00
renovate 948ab167c1 chore(deps): update node.js to v24.13.1 (#699)
schemas / vulnerabilities (push) Successful in 3m20s
schemas / check (push) Successful in 4m36s
schemas / check-release (push) Successful in 5m3s
schemas / build (push) Successful in 5m40s
pre-commit / pre-commit (push) Successful in 11m19s
schemas / deploy-prod (push) Successful in 1m19s
Release / release (push) Successful in 1m3s
2026-02-12 18:19:39 +00:00
renovate 404c4106bc fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.248 (#698)
schemas / vulnerabilities (push) Successful in 5m13s
schemas / check-release (push) Successful in 5m36s
Release / release (push) Successful in 4m12s
schemas / check (push) Successful in 12m42s
pre-commit / pre-commit (push) Successful in 16m33s
schemas / build (push) Successful in 15m24s
schemas / deploy-prod (push) Failing after 2m47s
2026-02-11 20:17:37 +00:00
renovate 593874ba52 chore(deps): update golang docker tag to v1.26.0 (#696)
Release / release (push) Successful in 4m12s
schemas / vulnerabilities (push) Successful in 6m10s
schemas / check (push) Successful in 6m44s
pre-commit / pre-commit (push) Successful in 9m35s
schemas / check-release (push) Successful in 16m33s
schemas / build (push) Successful in 1h10m47s
schemas / deploy-prod (push) Successful in 2m41s
2026-02-11 15:30:17 +00:00
renovate bcbdc4458e chore(deps): update pre-commit hook golangci/golangci-lint to v2.9.0 (#697)
Release / release (push) Successful in 1m37s
schemas / vulnerabilities (push) Successful in 3m42s
schemas / check-release (push) Successful in 6m28s
schemas / check (push) Successful in 14m42s
pre-commit / pre-commit (push) Successful in 22m33s
schemas / build (push) Successful in 25m34s
schemas / deploy-prod (push) Successful in 1m9s
2026-02-11 13:51:39 +00:00
renovate 4b6637b027 chore(deps): update golang:1.25.7 docker digest to d2819ff (#695)
schemas / check (push) Failing after 2s
schemas / check-release (push) Failing after 2s
pre-commit / pre-commit (push) Failing after 2s
schemas / vulnerabilities (push) Failing after 3s
schemas / build (push) Has been skipped
Release / release (push) Failing after 2s
schemas / deploy-prod (push) Has been skipped
2026-02-11 00:22:36 +00:00
renovate bc7729b702 fix(deps): update module golang.org/x/crypto to v0.48.0 (#694)
Release / release (push) Failing after 2m0s
schemas / vulnerabilities (push) Successful in 5m6s
schemas / check-release (push) Successful in 6m0s
schemas / check (push) Successful in 13m25s
schemas / build (push) Successful in 4m41s
pre-commit / pre-commit (push) Successful in 17m25s
schemas / deploy-prod (push) Successful in 1m21s
2026-02-09 17:30:20 +00:00
renovate 942508e467 fix(deps): update eventsourced (#693)
Release / release (push) Successful in 1m8s
schemas / vulnerabilities (push) Successful in 1m50s
schemas / check-release (push) Successful in 6m4s
schemas / check (push) Successful in 6m7s
pre-commit / pre-commit (push) Successful in 15m11s
schemas / build (push) Successful in 11m52s
schemas / deploy-prod (push) Successful in 1m28s
2026-02-07 20:32:28 +00:00
renovate 59e8a3c43d fix(deps): update module github.com/alecthomas/kong to v1.14.0 (#692)
schemas / vulnerabilities (push) Successful in 2m1s
Release / release (push) Successful in 2m5s
schemas / check-release (push) Successful in 5m56s
pre-commit / pre-commit (push) Successful in 8m13s
schemas / check (push) Successful in 8m45s
schemas / build (push) Successful in 8m24s
schemas / deploy-prod (push) Successful in 55s
2026-02-07 00:41:14 +00:00
renovate d7358d8053 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.247 (#691)
Release / release (push) Failing after 1m33s
schemas / vulnerabilities (push) Successful in 3m28s
schemas / check-release (push) Successful in 3m31s
schemas / check (push) Successful in 8m56s
pre-commit / pre-commit (push) Successful in 12m25s
schemas / build (push) Successful in 6m11s
schemas / deploy-prod (push) Successful in 1m1s
2026-02-06 13:17:32 +00:00
argoyle 2350099e7f Merge pull request 'chore(deps): update golang docker tag to v1.25.7' (#690) from renovate/golang-1.x into main
Release / release (push) Successful in 1m16s
schemas / vulnerabilities (push) Successful in 3m35s
schemas / check-release (push) Successful in 4m11s
pre-commit / pre-commit (push) Successful in 8m11s
schemas / check (push) Successful in 9m18s
schemas / build (push) Successful in 5m0s
schemas / deploy-prod (push) Successful in 1m24s
Reviewed-on: #690
2026-02-05 12:00:52 +00:00
renovate ab5a8a9f6a chore(deps): update golang docker tag to v1.25.7
schemas / vulnerabilities (pull_request) Successful in 2m14s
schemas / check (pull_request) Successful in 4m45s
schemas / check-release (pull_request) Successful in 10m57s
schemas / build (pull_request) Successful in 4m32s
schemas / deploy-prod (pull_request) Has been skipped
2026-02-04 18:15:45 +00:00
renovate 369a0b050a chore(deps): update golang:1.25.6 docker digest to ceda080 (#689)
schemas / check (push) Successful in 3m22s
pre-commit / pre-commit (push) Successful in 7m59s
schemas / check-release (push) Failing after 12m25s
schemas / vulnerabilities (push) Failing after 12m25s
schemas / build (push) Has been skipped
Release / release (push) Failing after 22m10s
schemas / deploy-prod (push) Has been skipped
2026-02-03 09:01:58 +00:00
renovate 3bf43210a1 fix(deps): update module go.opentelemetry.io/contrib/bridges/otelslog to v0.15.0 (#688)
Release / release (push) Successful in 55s
schemas / vulnerabilities (push) Successful in 1m31s
schemas / check-release (push) Successful in 4m58s
schemas / check (push) Successful in 5m51s
pre-commit / pre-commit (push) Successful in 7m51s
schemas / build (push) Successful in 8m38s
schemas / deploy-prod (push) Successful in 55s
2026-02-02 20:48:58 +00:00
renovate 148f8befe7 fix(deps): update opentelemetry-go monorepo (#687)
Release / release (push) Failing after 47s
schemas / vulnerabilities (push) Successful in 2m21s
schemas / check (push) Successful in 3m27s
schemas / check-release (push) Successful in 4m42s
schemas / build (push) Successful in 5m9s
schemas / deploy-prod (push) Successful in 1m25s
pre-commit / pre-commit (push) Successful in 15m42s
2026-02-02 19:34:16 +00:00
renovate 90b9e91d20 fix(deps): update module gitlab.com/unboundsoftware/eventsourced/pg to v1.18.1 (#686)
schemas / vulnerabilities (push) Successful in 1m41s
Release / release (push) Successful in 1m24s
schemas / check-release (push) Successful in 3m33s
schemas / check (push) Successful in 7m19s
pre-commit / pre-commit (push) Successful in 13m29s
schemas / build (push) Successful in 9m25s
schemas / deploy-prod (push) Successful in 57s
2026-01-30 07:24:43 +00:00
renovate 985bca2f58 fix(deps): update module gitlab.com/unboundsoftware/eventsourced/pg to v1.18.0 (#685)
schemas / vulnerabilities (push) Successful in 1m31s
Release / release (push) Successful in 1m23s
schemas / check-release (push) Successful in 6m10s
schemas / check (push) Successful in 12m6s
schemas / build (push) Successful in 3m24s
pre-commit / pre-commit (push) Successful in 16m9s
schemas / deploy-prod (push) Successful in 53s
2026-01-29 10:42:16 +00:00
renovate 3fce241596 chore(deps): update node.js to cd6fb7e (#684)
Release / release (push) Successful in 1m49s
schemas / check-release (push) Successful in 4m33s
schemas / vulnerabilities (push) Successful in 4m34s
schemas / check (push) Successful in 5m5s
schemas / build (push) Successful in 5m1s
schemas / deploy-prod (push) Successful in 1m30s
pre-commit / pre-commit (push) Successful in 13m38s
2026-01-28 08:30:07 +00:00
renovate a0baa44320 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.246 (#683)
Release / release (push) Successful in 1m12s
schemas / vulnerabilities (push) Successful in 3m44s
schemas / check-release (push) Successful in 5m47s
schemas / check (push) Successful in 8m53s
pre-commit / pre-commit (push) Successful in 9m12s
schemas / build (push) Successful in 6m25s
schemas / deploy-prod (push) Successful in 58s
2026-01-27 14:40:50 +00:00
renovate 60939c3159 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.245 (#682)
Release / release (push) Successful in 2m50s
schemas / vulnerabilities (push) Successful in 4m56s
schemas / check-release (push) Successful in 7m4s
schemas / check (push) Successful in 15m32s
schemas / build (push) Successful in 5m3s
pre-commit / pre-commit (push) Successful in 20m34s
schemas / deploy-prod (push) Successful in 1m47s
2026-01-26 14:25:49 +00:00
renovate 6530408f15 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.244 (#681)
Release / release (push) Failing after 3m11s
schemas / vulnerabilities (push) Successful in 4m53s
schemas / check-release (push) Successful in 5m38s
schemas / check (push) Successful in 14m0s
pre-commit / pre-commit (push) Successful in 16m56s
schemas / build (push) Successful in 7m22s
schemas / deploy-prod (push) Successful in 1m4s
2026-01-26 10:08:30 +00:00
renovate fd6694614e fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.243 (#680)
Release / release (push) Successful in 2m44s
schemas / vulnerabilities (push) Successful in 5m49s
schemas / check-release (push) Successful in 12m43s
schemas / check (push) Successful in 14m54s
schemas / build (push) Successful in 15m55s
schemas / deploy-prod (push) Failing after 4m4s
pre-commit / pre-commit (push) Successful in 57m25s
2026-01-20 15:13:13 +00:00
argoyle 6143a96c75 Merge pull request 'fix(deps): update module github.com/auth0/go-jwt-middleware/v2 to v3' (#678) from renovate/github.com-auth0-go-jwt-middleware-v2-3.x into main
Release / release (push) Successful in 1m39s
schemas / vulnerabilities (push) Successful in 2m53s
schemas / check (push) Successful in 3m11s
schemas / check-release (push) Successful in 3m51s
pre-commit / pre-commit (push) Successful in 8m5s
schemas / build (push) Successful in 5m33s
schemas / deploy-prod (push) Successful in 1m0s
Reviewed-on: #678
2026-01-19 20:07:08 +00:00
argoyle 817927cb7d fix: migrate to go-jwt-middleware v3 API
schemas / check-release (pull_request) Successful in 1m57s
schemas / vulnerabilities (pull_request) Successful in 2m48s
schemas / check (pull_request) Successful in 8m17s
pre-commit / pre-commit (pull_request) Successful in 11m38s
schemas / build (pull_request) Successful in 5m31s
schemas / deploy-prod (pull_request) Has been skipped
- Use validator and jwks packages for JWT validation
- Replace manual JWKS caching with jwks.NewCachingProvider
- Add CustomClaims struct for https://unbound.se/roles claim
- Rename TokenFromContext to ClaimsFromContext
- Update middleware/auth.go to use new claims structure
- Update tests to use core.SetClaims and validator.ValidatedClaims

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 20:31:45 +01:00
renovate e2c1803683 fix(deps): update module github.com/auth0/go-jwt-middleware/v2 to v3 2026-01-19 20:19:58 +01:00
argoyle d79a10e910 Merge pull request 'chore(release): prepare for v0.9.1' (#677) from next-release into main
schemas / vulnerabilities (push) Successful in 1m58s
schemas / check-release (push) Successful in 2m40s
pre-commit / pre-commit (push) Successful in 7m12s
Goreleaser / release (push) Successful in 2m30s
schemas / check (push) Successful in 5m21s
schemas / build (push) Successful in 4m0s
schemas / deploy-prod (push) Has been skipped
Release / release (push) Successful in 1m18s
Reviewed-on: #677
2026-01-18 09:46:25 +00:00
releaser 48f0ec0bbe chore(release): prepare for v0.9.1 2026-01-18 09:43:20 +00:00
releaser b89b124a0e chore(release): prepare for v0.9.1 2026-01-18 09:43:11 +00:00
argoyle d008303068 Merge pull request 'fix(ci): run build job on tags for Docker images' (#676) from fix-build-on-tags into main
schemas / vulnerabilities (push) Successful in 1m21s
Release / release (push) Successful in 1m30s
schemas / check-release (push) Successful in 2m57s
schemas / check (push) Successful in 4m1s
pre-commit / pre-commit (push) Successful in 7m0s
schemas / build (push) Successful in 5m52s
schemas / deploy-prod (push) Successful in 1m6s
Reviewed-on: #676
2026-01-18 09:41:09 +00:00
argoyle e328ef5a64 fix(ci): run build job on tags for Docker images
schemas / vulnerabilities (pull_request) Successful in 1m33s
schemas / check (pull_request) Successful in 2m55s
schemas / check-release (pull_request) Successful in 2m9s
pre-commit / pre-commit (pull_request) Successful in 5m40s
schemas / build (pull_request) Successful in 4m20s
schemas / deploy-prod (pull_request) Has been skipped
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 10:30:24 +01:00
argoyle b2506188f3 Merge pull request 'chore(release): prepare for v0.9.0' (#639) from next-release into main
schemas / check (push) Successful in 11m47s
schemas / deploy-prod (push) Successful in 1m38s
Goreleaser / release (push) Successful in 16m29s
schemas / build (push) Successful in 12m52s
pre-commit / pre-commit (push) Successful in 31m14s
Release / release (push) Successful in 1m23s
schemas / vulnerabilities (push) Successful in 6m23s
schemas / check-release (push) Successful in 12m39s
Reviewed-on: #639
2026-01-18 07:46:37 +00:00
releaser 16ce04ea86 chore(release): prepare for v0.9.0 2026-01-17 22:21:23 +00:00
releaser 1d0f82a851 chore(release): prepare for v0.9.0 2026-01-17 22:20:56 +00:00
argoyle 7be533dc6c Merge pull request 'feat: migrate from GitLab CI to Gitea Actions' (#675) from migrate-to-gitea into main
Release / release (push) Successful in 2m34s
schemas / vulnerabilities (push) Successful in 4m48s
schemas / check-release (push) Successful in 5m40s
schemas / check (push) Successful in 6m46s
pre-commit / pre-commit (push) Successful in 6m29s
schemas / build (push) Successful in 5m3s
schemas / deploy-prod (push) Successful in 59s
Reviewed-on: #675
2026-01-17 22:18:02 +00:00
argoyle 73eae98929 feat: migrate from GitLab CI to Gitea Actions
schemas / vulnerabilities (pull_request) Successful in 2m15s
schemas / check-release (pull_request) Successful in 2m17s
schemas / check (pull_request) Successful in 4m48s
pre-commit / pre-commit (pull_request) Successful in 5m58s
schemas / build (pull_request) Successful in 3m36s
schemas / deploy-prod (pull_request) Has been skipped
- Update git remote to git.unbound.se
- Add Gitea workflows: ci.yaml, pre-commit.yaml, release.yaml, goreleaser.yaml
- Delete .gitlab-ci.yml
- Update Go module path to gitea.unbound.se/unboundsoftware/schemas
- Update all imports to new module path
- Update Docker registry to oci.unbound.se
- Update .goreleaser.yml for Gitea releases with internal cluster URL
- Remove GitLab CI linter from pre-commit config
- Use shared release workflow with tag_only for versioning

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 22:53:46 +01:00
argoyle aa41f48b0e Merge branch 'renovate/golang-1.x' into 'main'
chore(deps): update golang docker tag to v1.25.6

See merge request unboundsoftware/schemas!670
2026-01-17 20:12:07 +01:00
Renovate bb0c020812 chore(deps): update golang docker tag to v1.25.6 2026-01-15 23:56:35 +00:00
Unbound Release f0c7415d88 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release fbe180020c chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 3eba214a72 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 134b571baa chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release a1a23d69cf chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 6f1dda7be5 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release d50827018a chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 8a6163a921 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 054cfa1e52 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 7d25af472a chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release ec0d5dff74 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release e3d19384a0 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 42adcb1df5 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release e072ad685b chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 4222eb1268 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release ab3eafa331 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 86612d9e25 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release abc5668d33 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 892fc29331 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release bdb6e37b22 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 8d512e0290 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 3ab0a8e701 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 3bd3daccaf chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 3d6016d7e2 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 31cc2e10b6 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 136623c04b chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 12ff2fa1ba chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 1aef6a7a31 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release c02034d6b0 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release dc4555a168 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 0571f4d986 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 3c194dc127 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release f7ba0e3dc7 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 33667d4de1 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release bbf82399e1 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release f927d1c750 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release ef273520b5 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release c8e6287761 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 7052f0bfb1 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release c6f35f324e chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 08f0ee3374 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release d3c7941de8 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release b1a9021fee chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release ab32fddd2b chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release ded73a3065 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 4c571197b8 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release 9f276d7420 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release f56dfeafa7 chore(release): prepare for v0.9.0 2026-01-15 07:51:24 +00:00
Unbound Release f6f3810ffb chore(release): prepare for v0.8.1 2026-01-15 07:51:24 +00:00
Unbound Release 9d32174ae4 chore(release): prepare for v0.8.1 2026-01-15 07:51:24 +00:00
Unbound Release 26073e744d chore(release): prepare for v0.8.1 2026-01-15 07:51:24 +00:00
Unbound Release 6e38f3d5b5 chore(release): prepare for v0.8.1 2026-01-15 07:51:24 +00:00
argoyle 9199be9da5 Merge branch 'renovate/node-24.x' into 'main'
chore(deps): update node.js to v24.13.0

See merge request unboundsoftware/schemas!669
2026-01-15 08:43:39 +01:00
Renovate e3dcb013b6 chore(deps): update node.js to v24.13.0 2026-01-15 05:55:55 +00:00
argoyle 5982f3ac41 Merge branch 'renovate/alessandrojcm-commitlint-pre-commit-hook-9.x' into 'main'
chore(deps): update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.24.0

See merge request unboundsoftware/schemas!668
2026-01-14 07:10:27 +01:00
Renovate eb23f2f62b chore(deps): update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.24.0 2026-01-13 23:56:25 +00:00
argoyle 1effc3937a Merge branch 'renovate/github.com-99designs-gqlgen-0.x' into 'main'
fix(deps): update module github.com/99designs/gqlgen to v0.17.86

See merge request unboundsoftware/schemas!665
2026-01-13 09:28:17 +01:00
Renovate b69de655e4 fix(deps): update module github.com/99designs/gqlgen to v0.17.86 2026-01-13 08:40:58 +01:00
argoyle 73fdf6c896 Merge branch 'renovate/golang-1.25.5' into 'main'
chore(deps): update golang:1.25.5 docker digest to 3a01526

See merge request unboundsoftware/schemas!667
2026-01-13 08:39:13 +01:00
argoyle bf0953b520 Merge branch 'renovate/golang.org-x-crypto-0.x' into 'main'
fix(deps): update module golang.org/x/crypto to v0.47.0

See merge request unboundsoftware/schemas!666
2026-01-13 08:38:49 +01:00
argoyle 95aaea7ecf Merge branch 'renovate/goreleaser-goreleaser-2.x' into 'main'
chore(deps): update goreleaser/goreleaser docker tag to v2.13.3

See merge request unboundsoftware/schemas!664
2026-01-13 08:38:24 +01:00
Renovate 81c3a7ff13 chore(deps): update golang:1.25.5 docker digest to 3a01526 2026-01-13 05:57:40 +00:00
Renovate e322f7f7ad fix(deps): update module golang.org/x/crypto to v0.47.0 2026-01-12 17:58:30 +00:00
Renovate 4997c53968 chore(deps): update goreleaser/goreleaser docker tag to v2.13.3 2026-01-10 05:56:48 +00:00
argoyle cfcd023a83 Merge branch 'renovate/golangci-golangci-lint-2.x' into 'main'
chore(deps): update pre-commit hook golangci/golangci-lint to v2.8.0

See merge request unboundsoftware/schemas!663
2026-01-09 11:39:29 +01:00
Renovate 34e7a0b718 chore(deps): update pre-commit hook golangci/golangci-lint to v2.8.0 2026-01-07 21:59:31 +00:00
argoyle 7d38c31370 Merge branch 'renovate/golang-1.25.5' into 'main'
chore(deps): update golang:1.25.5 docker digest to ad03ba9

See merge request unboundsoftware/schemas!662
2025-12-30 19:39:57 +01:00
Renovate 968553b532 chore(deps): update golang:1.25.5 docker digest to ad03ba9 2025-12-30 03:58:45 +00:00
argoyle 0067b42f92 Merge branch 'renovate/goreleaser-goreleaser-2.x' into 'main'
chore(deps): update goreleaser/goreleaser docker tag to v2.13.2

See merge request unboundsoftware/schemas!661
2025-12-25 13:34:02 +01:00
Renovate 48776b6afc chore(deps): update goreleaser/goreleaser docker tag to v2.13.2 2025-12-24 19:09:17 +00:00
argoyle 03cf6be52c Merge branch 'renovate/github.com-wundergraph-graphql-go-tools-v2-2.x' into 'main'
fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.242

See merge request unboundsoftware/schemas!660
2025-12-19 13:15:38 +01:00
Renovate 6b5e66b1be fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.242 2025-12-19 12:00:46 +00:00
argoyle 25c3bf2356 Merge branch 'renovate/node-24.12.0-alpine' into 'main'
chore(deps): update node.js to c921b97

See merge request unboundsoftware/schemas!659
2025-12-18 07:12:30 +01:00
Renovate 7e5bbf3baa chore(deps): update node.js to c921b97 2025-12-18 04:57:56 +00:00
argoyle 8adae23068 Merge branch 'renovate/github.com-99designs-gqlgen-0.x' into 'main'
fix(deps): update module github.com/99designs/gqlgen to v0.17.85

See merge request unboundsoftware/schemas!658
2025-12-17 08:36:17 +01:00
Renovate 0e8d0965b7 fix(deps): update module github.com/99designs/gqlgen to v0.17.85 2025-12-17 07:58:03 +01:00
argoyle cfc5de1bb9 Merge branch 'renovate/node-24.x' into 'main'
chore(deps): update node.js to v24.12.0

See merge request unboundsoftware/schemas!657
2025-12-12 08:42:09 +01:00
Renovate 63567eaa8b chore(deps): update node.js to v24.12.0 2025-12-11 22:58:46 +00:00
argoyle e38e7a2936 Merge branch 'renovate/github.com-wundergraph-graphql-go-tools-v2-2.x' into 'main'
fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.241

See merge request unboundsoftware/schemas!656
2025-12-10 13:22:05 +01:00
Renovate 718585ebe8 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.241 2025-12-10 11:58:49 +00:00
argoyle 6b3515ed14 Merge branch 'renovate/golang-1.25.5' into 'main'
chore(deps): update golang:1.25.5 docker digest to 0c27bcf

See merge request unboundsoftware/schemas!655
2025-12-09 09:40:20 +01:00
argoyle fee47b271a Merge branch 'renovate/opentelemetry-go-contrib-monorepo' into 'main'
fix(deps): update module go.opentelemetry.io/contrib/bridges/otelslog to v0.14.0

See merge request unboundsoftware/schemas!654
2025-12-09 09:40:00 +01:00
argoyle 1da8439372 Merge branch 'renovate/golang.org-x-crypto-0.x' into 'main'
fix(deps): update module golang.org/x/crypto to v0.46.0

See merge request unboundsoftware/schemas!653
2025-12-09 09:39:37 +01:00
Renovate 2de1324458 chore(deps): update golang:1.25.5 docker digest to 0c27bcf 2025-12-09 02:12:10 +00:00
Renovate 4673ecdd85 fix(deps): update module golang.org/x/crypto to v0.46.0 2025-12-08 22:11:32 +00:00
Renovate d92f24b0a1 fix(deps): update module go.opentelemetry.io/contrib/bridges/otelslog to v0.14.0 2025-12-08 22:11:26 +00:00
argoyle f80ee9d391 Merge branch 'renovate/opentelemetry-go-monorepo' into 'main'
fix(deps): update opentelemetry-go monorepo

See merge request unboundsoftware/schemas!651
2025-12-08 22:31:13 +01:00
Renovate 3da293252d fix(deps): update opentelemetry-go monorepo 2025-12-08 21:31:33 +01:00
argoyle 6ae6a4d6cf Merge branch 'test-cache-legacy-hash-optimizations' into 'main'
test(cache): update tests to use legacy hash for speed

See merge request unboundsoftware/schemas!652
2025-12-08 21:31:02 +01:00
argoyle 129cd8aad1 test(cache): update tests to use legacy hash for speed
Update test setup to leverage legacy hash for API keys in order  
to avoid the performance impact of bcrypt. Replace the API key  
based test with a user subscription-based test to enhance  
concurrency and reliability. Replace `OrganizationByAPIKey`  
with `OrganizationsByUser` to optimize the retrieval process.
2025-12-08 21:06:02 +01:00
argoyle 8dd2e57c70 Merge branch 'renovate/github.com-wundergraph-graphql-go-tools-v2-2.x' into 'main'
fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.240

See merge request unboundsoftware/schemas!650
2025-12-08 20:14:52 +01:00
Renovate 1914211b85 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.240 2025-12-08 15:59:27 +00:00
argoyle ea8eb0a68c Merge branch 'renovate/goreleaser-goreleaser-2.x' into 'main'
chore(deps): update goreleaser/goreleaser docker tag to v2.13.1

See merge request unboundsoftware/schemas!649
2025-12-08 09:51:01 +01:00
argoyle e9d0e855af Merge branch 'renovate/golangci-golangci-lint-2.x' into 'main'
chore(deps): update pre-commit hook golangci/golangci-lint to v2.7.2

See merge request unboundsoftware/schemas!648
2025-12-08 09:42:48 +01:00
Renovate 8790fd0d82 chore(deps): update goreleaser/goreleaser docker tag to v2.13.1 2025-12-07 21:57:46 +00:00
Renovate a77a7f3a32 chore(deps): update pre-commit hook golangci/golangci-lint to v2.7.2 2025-12-07 16:59:19 +00:00
argoyle fbe962a7b7 Merge branch 'refactor-cache-optimize-test-setup' into 'main'
refactor(cache): optimize test setup and reduce iterations

See merge request unboundsoftware/schemas!647
2025-12-05 09:08:44 +01:00
argoyle b5bdcc9dbc Merge branch 'fix/docker-update-nodejs-version' into 'main'
fix(docker): update Node.js version to 24.11.1-alpine

See merge request unboundsoftware/schemas!646
2025-12-05 08:51:58 +01:00
argoyle fd1685867e refactor(cache): optimize test setup and reduce iterations
Remove bcrypt hashing for API keys to speed up concurrent tests and 
replace it with a legacy hashing function. Reduce the number of 
concurrent readers and writers in the test to improve performance 
while retaining essential functionality checks. Use a more efficient 
method to fetch organizations within the concurrency test block.
2025-12-05 08:47:11 +01:00
argoyle 114cbf89c5 fix(docker): update Node.js version to 24.11.1-alpine
Updates the Node.js base image in the Dockerfile to the latest
24.11.1-alpine version for improved performance and security. This
change ensures the application runs on a stable and supported
version of Node.js.
2025-12-05 08:32:22 +01:00
argoyle 000ad8b4ad Merge branch 'renovate/node-24-alpine' into 'main'
chore(deps): update node.js to 682368d

See merge request unboundsoftware/schemas!645
2025-12-05 08:20:40 +01:00
Renovate 0820fb542f chore(deps): update node.js to 682368d 2025-12-05 01:57:47 +00:00
argoyle f0d4285bee Merge branch 'renovate/golangci-golangci-lint-2.x' into 'main'
chore(deps): update pre-commit hook golangci/golangci-lint to v2.7.1

See merge request unboundsoftware/schemas!644
2025-12-04 20:42:45 +01:00
Renovate e1c10f0537 chore(deps): update pre-commit hook golangci/golangci-lint to v2.7.1 2025-12-04 15:00:22 +00:00
argoyle f8c593de3e Merge branch 'renovate/golangci-golangci-lint-2.x' into 'main'
chore(deps): update pre-commit hook golangci/golangci-lint to v2.7.0

See merge request unboundsoftware/schemas!643
2025-12-04 08:28:46 +01:00
Renovate 870f29e59f chore(deps): update pre-commit hook golangci/golangci-lint to v2.7.0 2025-12-03 19:58:58 +00:00
argoyle a246c236db Merge branch 'renovate/golang-1.x' into 'main'
chore(deps): update golang docker tag to v1.25.5

See merge request unboundsoftware/schemas!642
2025-12-02 19:43:34 +01:00
Renovate cebeba4461 chore(deps): update golang docker tag to v1.25.5 2025-12-02 18:12:06 +00:00
argoyle b77165f6c8 Merge branch 'renovate/goreleaser-goreleaser-2.x' into 'main'
chore(deps): update goreleaser/goreleaser docker tag to v2.13.0

See merge request unboundsoftware/schemas!641
2025-12-01 07:40:22 +01:00
Renovate 9e85ee1473 chore(deps): update goreleaser/goreleaser docker tag to v2.13.0 2025-11-30 19:57:39 +00:00
argoyle c113eb920b Merge branch 'renovate/gitleaks-gitleaks-8.x' into 'main'
chore(deps): update pre-commit hook gitleaks/gitleaks to v8.30.0

See merge request unboundsoftware/schemas!640
2025-11-27 00:04:46 +01:00
Renovate 90cc64ece9 chore(deps): update pre-commit hook gitleaks/gitleaks to v8.30.0 2025-11-26 18:59:33 +00:00
argoyle ea4df08beb Merge branch 'renovate/github.com-99designs-gqlgen-0.x' into 'main'
fix(deps): update module github.com/99designs/gqlgen to v0.17.84

See merge request unboundsoftware/schemas!639
2025-11-24 20:42:05 +01:00
Renovate ca7e063888 fix(deps): update module github.com/99designs/gqlgen to v0.17.84 2025-11-24 19:34:22 +01:00
argoyle 7b9dc1456b Merge branch 'fix/k8s-update-ingress-class' into 'main'
fix(k8s): update ingress class configuration for schema

See merge request unboundsoftware/schemas!638
2025-11-23 16:00:39 +01:00
argoyle 49af5f0cb1 fix(k8s): update ingress class configuration for schema
Remove the deprecated annotation for ingress class and add 
ingressClassName to align with current Kubernetes standards. 
This ensures better compatibility and adherence to best practices.
2025-11-23 14:28:08 +01:00
argoyle e347d74a39 Merge branch 'feat/manage-organizations-users' into 'main'
feat: add commands for managing organizations and users

See merge request unboundsoftware/schemas!637
2025-11-22 18:57:00 +01:00
argoyle ffcf41b85a feat: add commands for managing organizations and users
Introduce `AddUserToOrganization`, `RemoveAPIKey`, and 
`RemoveOrganization` commands to enhance organization 
management. Implement validation for user addition and 
API key removal. Update GraphQL schema to support new 
mutations and add caching for the new events, ensuring 
that organizations and their relationships are accurately 
represented in the cache.
2025-11-22 18:37:07 +01:00
argoyle 335a9f3b54 Merge branch 'renovate/github.com-wundergraph-graphql-go-tools-v2-2.x' into 'main'
fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.239

See merge request unboundsoftware/schemas!636
2025-11-22 10:00:25 +01:00
Renovate c0e790b684 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.239 2025-11-21 22:08:12 +00:00
argoyle 3b47365f10 Merge branch 'test/add-validation-event-tests' into 'main'
test: add validation and event tests for organization and API key

See merge request unboundsoftware/schemas!634
2025-11-21 13:12:02 +01:00
argoyle 862060875b test: add validation and event tests for organization and API key
Adds unit tests for the `AddOrganization` and `AddAPIKey` commands. 
These tests validate various scenarios, including success cases, 
handling of already existing organizations or keys, and ensuring 
required fields are checked. The
changes enhance test coverage and ensure robustness of the command 
logic.
2025-11-21 12:49:23 +01:00
argoyle 06aeedc3b0 Merge branch 'next-release' into 'main'
chore(release): prepare for v0.8.0

See merge request unboundsoftware/schemas!624
2025-11-21 11:32:33 +01:00
Unbound Release fce85782f0 chore(release): prepare for v0.8.0 2025-11-21 11:32:33 +01:00
argoyle 9cd8218eb4 Merge branch 'test/cache-reduce-goroutines-stability' into 'main'
test(cache): reduce goroutines for race detector stability

See merge request unboundsoftware/schemas!633
2025-11-21 11:19:28 +01:00
argoyle 98ef62b144 Merge branch 'renovate/github.com-auth0-go-jwt-middleware-v2-2.x' into 'main'
fix(deps): update module github.com/auth0/go-jwt-middleware/v2 to v2.3.1

See merge request unboundsoftware/schemas!630
2025-11-21 11:10:23 +01:00
argoyle e0cdd2aa58 Merge branch 'renovate/golang.org-x-crypto-0.x' into 'main'
fix(deps): update module golang.org/x/crypto to v0.45.0

See merge request unboundsoftware/schemas!629
2025-11-21 11:09:17 +01:00
argoyle e22e8b339c Merge branch 'renovate/node-24.x' into 'main'
chore(deps): update node.js to v24

See merge request unboundsoftware/schemas!627
2025-11-21 11:09:03 +01:00
argoyle 6404f7a497 test(cache): reduce goroutines for race detector stability
Decrease the number of goroutines in concurrent read and write tests to 
minimize race conditions during testing. This ensures more reliable 
test results and makes it easier to identify concurrency issues.
2025-11-21 11:06:36 +01:00
argoyle 5dc5043d46 Merge branch 'feat/cache-concurrency-logging' into 'main'
feat(cache): add concurrency safety and logging improvements

See merge request unboundsoftware/schemas!631
2025-11-21 10:45:48 +01:00
argoyle bcca005256 Merge branch 'feat/add-health-check-endpoints' into 'main'
feat(health): add health checking endpoints and logic

See merge request unboundsoftware/schemas!632
2025-11-21 10:38:01 +01:00
argoyle a9dea19531 feat(health): add health checking endpoints and logic
Introduce health checking functionality with liveness and readiness 
endpoints to monitor the application's status. Implement a health 
checker that verifies database connectivity and provides a simple 
liveness check. Update service routing to use the new health 
checker functionality. Add corresponding unit tests for validation.
2025-11-21 10:24:34 +01:00
argoyle 130e92dc5f feat(cache): add concurrency safety and logging improvements
Implement read-write mutex locks for cache functions to ensure
concurrency safety. Add debug logging for cache updates to enhance
traceability of operations. Optimize user addition logic to prevent
duplicates. Introduce a new test file for comprehensive cache
functionality testing, ensuring reliable behavior.
2025-11-21 10:21:08 +01:00
Renovate c4112a005f fix(deps): update module github.com/auth0/go-jwt-middleware/v2 to v2.3.1 2025-11-21 08:08:20 +00:00
Renovate 549f6617df fix(deps): update module golang.org/x/crypto to v0.45.0 2025-11-20 22:08:30 +00:00
argoyle a1b0f49aab Merge branch 'feat/cache-hashed-api-key-storage' into 'main'
feat(cache): implement hashed API key storage and retrieval

See merge request unboundsoftware/schemas!628
2025-11-20 22:30:49 +01:00
argoyle 4468903535 feat(cache): implement hashed API key storage and retrieval
Adds a new hashed key storage mechanism for API keys in the cache. 
Replaces direct mapping to API keys with composite keys based on 
organizationId and name. Implements searching of API keys using 
hash comparisons for improved security. Updates related tests to 
ensure correct functionality and validate the hashing. Also, 
adds support for a new dependency `golang.org/x/crypto`.
2025-11-20 22:11:24 +01:00
Renovate df054ca451 chore(deps): update node.js to v24 2025-11-20 21:09:40 +00:00
argoyle 1e2236dc9e Merge branch 'add-claude-md-documentation' into 'main'
feat: add CLAUDE.md for project documentation and guidelines

See merge request unboundsoftware/schemas!625
2025-11-20 21:50:16 +01:00
argoyle 6ccd7f4f25 feat: add CLAUDE.md for project documentation and guidelines
Adds CLAUDE.md to provide comprehensive documentation for the 
GraphQL schema registry service, covering architecture, event 
sourcing, GraphQL layer, schema merging, authentication, 
Cosmo Router integration, and development workflow. Updates 
.gitignore to include the claude directory.
2025-11-20 21:36:58 +01:00
argoyle b1a46f9d4e Merge branch 'enhance-api-key-handling-logging' into 'main'
fix: enhance API key handling and logging in middleware

See merge request unboundsoftware/schemas!623
2025-11-20 21:26:21 +01:00
argoyle 47dbf827f2 fix: add command executor interface for better testing
Introduce the CommandExecutor interface to abstract command execution, 
allowing for easier mocking in tests. Implement DefaultCommandExecutor 
to use the os/exec package for executing commands. Update the 
GenerateCosmoRouterConfig function to utilize the new 
GenerateCosmoRouterConfigWithExecutor function that accepts a command 
executor parameter. Add a MockCommandExecutor for simulating command 
execution in unit tests with realistic behavior based on input YAML 
files. This enhances test coverage and simplifies error handling.
2025-11-20 21:09:00 +01:00
argoyle df44ddbb8e test: enhance assertions for version and subscription config
Update version check to validate it is a non-empty string. Improve 
assertions for the subscription configuration by ensuring the presence 
of required fields and correct types. Adapt checks for routing URLs 
and decentralize subscription validation for more robust testing. 
These changes ensure better verification of configuration 
integrity and correctness in test scenarios.
2025-11-20 18:24:49 +01:00
argoyle 9368d77bc8 feat: add latestSchema query for retrieving schema updates
Implements the `latestSchema` query to fetch the latest schema 
updates for an organization. This change enhances the GraphQL API by
allowing clients to retrieve the most recent schema version and its 
associated subgraphs. The resolver performs necessary access checks, 
logs relevant information, and generates the Cosmo router configuration 
from fetched subgraph SDLs, returning structured schema update details.
2025-11-20 18:24:36 +01:00
argoyle 4d18cf4175 feat(tests): add unit tests for WebSocket initialization logic
Adds unit tests for the WebSocket initialization function to validate
behavior with valid, invalid, and absent API keys. Introduces a mock
cache implementation to simulate organization retrieval based on
hashed API keys. Ensures proper context value setting upon
initialization, enhancing test coverage and reliability for API key
handling in WebSocket connections.
2025-11-20 14:25:02 +01:00
argoyle bb0c08be06 fix: enhance API key handling and logging in middleware
Refactor API key processing to improve clarity and reduce code 
duplication. Introduce detailed logging for schema updates and 
initializations, capturing relevant context information. Use 
background context for async operations to avoid blocking. 
Implement organization lookup logic in the WebSocket init 
function for consistent API key handling across connections.
2025-11-20 12:58:15 +01:00
argoyle a9a47c1690 Merge branch 'renovate/gitleaks-gitleaks-8.x' into 'main'
chore(deps): update pre-commit hook gitleaks/gitleaks to v8.29.1

See merge request unboundsoftware/schemas!622
2025-11-20 09:21:24 +01:00
Renovate de073ce2da chore(deps): update pre-commit hook gitleaks/gitleaks to v8.29.1 2025-11-19 21:58:54 +00:00
argoyle af045687ae Merge branch 'next-release' into 'main'
chore(release): prepare for v0.7.0

See merge request unboundsoftware/schemas!598
2025-11-19 12:01:31 +01:00
Unbound Release c90ee3c9b1 chore(release): prepare for v0.7.0 2025-11-19 12:01:31 +01:00
argoyle 83e99e7d0a Merge branch 'feat/add-cosmo-router-config-pubsub' into 'main'
feat: add Cosmo Router config generation and PubSub support

See merge request unboundsoftware/schemas!621
2025-11-19 11:42:36 +01:00
argoyle 80daed081d feat: add Cosmo Router config generation and PubSub support
Creates a new `GenerateCosmoRouterConfig` function to build and 
serialize a Cosmo Router configuration from subgraphs. Implements 
PubSub mechanism for managing schema updates, allowing 
subscription to updates. Adds Subscription resolver and updates 
existing structures to accommodate new functionalities. This 
enhances the system's capabilities for dynamic updates and 
configuration management.
2025-11-19 11:29:30 +01:00
argoyle f6e4458efa Merge branch 'renovate/golang-1.25.4' into 'main'
chore(deps): update golang:1.25.4 docker digest to efe81fa

See merge request unboundsoftware/schemas!620
2025-11-18 21:29:05 +01:00
Renovate 11b9a46802 chore(deps): update golang:1.25.4 docker digest to efe81fa 2025-11-18 11:58:47 +00:00
argoyle d2324d27df Merge branch 'renovate/github.com-wundergraph-graphql-go-tools-v2-2.x' into 'main'
fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.238

See merge request unboundsoftware/schemas!619
2025-11-17 10:25:53 +01:00
Renovate c496ed025e fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.238 2025-11-17 08:58:02 +00:00
argoyle 4773ada816 Merge branch 'renovate/golangci-golangci-lint-2.x' into 'main'
chore(deps): update pre-commit hook golangci/golangci-lint to v2.6.2

See merge request unboundsoftware/schemas!618
2025-11-14 16:22:16 +01:00
Renovate 6447a299b3 chore(deps): update pre-commit hook golangci/golangci-lint to v2.6.2 2025-11-14 13:58:47 +00:00
argoyle b8e9e0d632 Merge branch 'renovate/github.com-alecthomas-kong-1.x' into 'main'
fix(deps): update module github.com/alecthomas/kong to v1.13.0

See merge request unboundsoftware/schemas!617
2025-11-14 12:57:15 +01:00
Renovate 3179bb7ae3 fix(deps): update module github.com/alecthomas/kong to v1.13.0 2025-11-13 22:59:09 +00:00
argoyle 73f6fe31d9 Merge branch 'renovate/github.com-99designs-gqlgen-0.x' into 'main'
fix(deps): update module github.com/99designs/gqlgen to v0.17.83

See merge request unboundsoftware/schemas!616
2025-11-11 10:50:15 +01:00
Renovate 87eab3a04e fix(deps): update module github.com/99designs/gqlgen to v0.17.83 2025-11-10 23:10:38 +00:00
argoyle e751d35e38 Merge branch 'renovate/github.com-wundergraph-graphql-go-tools-v2-2.x' into 'main'
fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.237

See merge request unboundsoftware/schemas!615
2025-11-10 17:54:01 +01:00
Renovate 84e30c0771 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.237 2025-11-10 13:58:40 +00:00
argoyle bdb3b80f4a Merge branch 'renovate/github.com-99designs-gqlgen-0.x' into 'main'
fix(deps): update module github.com/99designs/gqlgen to v0.17.82

See merge request unboundsoftware/schemas!614
2025-11-07 08:36:04 +01:00
Renovate d08c9663fe fix(deps): update module github.com/99designs/gqlgen to v0.17.82 2025-11-07 01:59:53 +00:00
argoyle a17b8dd122 Merge branch 'renovate/golang-1.x' into 'main'
chore(deps): update golang docker tag to v1.25.4

See merge request unboundsoftware/schemas!613
2025-11-06 07:03:06 +01:00
Renovate ae8cf15b0a chore(deps): update golang docker tag to v1.25.4 2025-11-05 22:11:22 +00:00
argoyle 8fd2c1790b Merge branch 'renovate/gitleaks-gitleaks-8.x' into 'main'
chore(deps): update pre-commit hook gitleaks/gitleaks to v8.29.0

See merge request unboundsoftware/schemas!612
2025-11-05 06:52:56 +01:00
Renovate fe4efa7b97 chore(deps): update pre-commit hook gitleaks/gitleaks to v8.29.0 2025-11-05 01:59:15 +00:00
argoyle 78599baa5b Merge branch 'renovate/golangci-golangci-lint-2.x' into 'main'
chore(deps): update pre-commit hook golangci/golangci-lint to v2.6.1

See merge request unboundsoftware/schemas!611
2025-11-04 13:43:40 +01:00
argoyle 8ad47c2e54 Merge branch 'renovate/golang-1.25.3' into 'main'
chore(deps): update golang:1.25.3 docker digest to 9ac0edc

See merge request unboundsoftware/schemas!610
2025-11-04 13:43:05 +01:00
Renovate ecce66b579 chore(deps): update pre-commit hook golangci/golangci-lint to v2.6.1 2025-11-04 11:59:20 +00:00
Renovate a26e66649a chore(deps): update golang:1.25.3 docker digest to 9ac0edc 2025-11-04 11:59:17 +00:00
argoyle b8b5951883 Merge branch 'renovate/github.com-wundergraph-graphql-go-tools-v2-2.x' into 'main'
fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.236

See merge request unboundsoftware/schemas!609
2025-10-31 02:14:34 +01:00
Renovate f860d80a81 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.236 2025-10-31 00:57:50 +00:00
argoyle d27331cef0 Merge branch 'renovate/golangci-golangci-lint-2.x' into 'main'
chore(deps): update pre-commit hook golangci/golangci-lint to v2.6.0

See merge request unboundsoftware/schemas!607
2025-10-30 11:43:43 +01:00
Renovate e343ff7538 chore(deps): update pre-commit hook golangci/golangci-lint to v2.6.0 2025-10-30 11:19:43 +01:00
argoyle 4fe45bf125 Merge branch 'renovate/github.com-vektah-gqlparser-v2-2.x' into 'main'
fix(deps): update module github.com/vektah/gqlparser/v2 to v2.5.31

See merge request unboundsoftware/schemas!608
2025-10-30 07:56:05 +01:00
Renovate 89e35c4ee7 fix(deps): update module github.com/vektah/gqlparser/v2 to v2.5.31 2025-10-30 01:59:20 +00:00
argoyle 0f077a53bb Merge branch 'renovate/github.com-wundergraph-graphql-go-tools-v2-2.x' into 'main'
fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.235

See merge request unboundsoftware/schemas!606
2025-10-29 15:03:09 +01:00
Renovate 51fd889f6a fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.235 2025-10-29 12:58:02 +00:00
argoyle e13c9b1a28 Merge branch 'renovate/goreleaser-goreleaser-2.x' into 'main'
chore(deps): update goreleaser/goreleaser docker tag to v2.12.7

See merge request unboundsoftware/schemas!605
2025-10-25 09:55:26 +02:00
argoyle 576a530886 Merge branch 'renovate/github.com-wundergraph-graphql-go-tools-v2-2.x' into 'main'
fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.234

See merge request unboundsoftware/schemas!604
2025-10-25 09:54:52 +02:00
Renovate aa9fea580f chore(deps): update goreleaser/goreleaser docker tag to v2.12.7 2025-10-24 18:57:11 +00:00
Renovate ccb8a10f92 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.234 2025-10-24 17:57:43 +00:00
argoyle d86beb8308 Merge branch 'renovate/github.com-wundergraph-graphql-go-tools-v2-2.x' into 'main'
fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.233

See merge request unboundsoftware/schemas!603
2025-10-23 20:50:39 +02:00
Renovate 429cf6e66d fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.233 2025-10-23 15:58:14 +00:00
argoyle 2dd0ac9392 Merge branch 'renovate/goreleaser-goreleaser-2.x' into 'main'
chore(deps): update goreleaser/goreleaser docker tag to v2.12.6

See merge request unboundsoftware/schemas!601
2025-10-22 11:47:28 +02:00
argoyle 780244b165 Merge branch 'renovate/github.com-wundergraph-graphql-go-tools-v2-2.x' into 'main'
fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.232

See merge request unboundsoftware/schemas!602
2025-10-22 11:21:04 +02:00
Renovate 540e90a577 chore(deps): update goreleaser/goreleaser docker tag to v2.12.6 2025-10-22 11:19:22 +02:00
Renovate 1b527bab74 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.232 2025-10-22 08:57:48 +00:00
argoyle 5b64cd0165 Merge branch 'renovate/golang-1.25.3' into 'main'
chore(deps): update golang:1.25.3 docker digest to 69d1009

See merge request unboundsoftware/schemas!600
2025-10-21 14:19:45 +02:00
Renovate e2fb56f505 chore(deps): update golang:1.25.3 docker digest to 69d1009 2025-10-21 09:58:16 +00:00
argoyle 6bdbe36c7f Merge branch 'renovate/github.com-wundergraph-graphql-go-tools-v2-2.x' into 'main'
fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.231

See merge request unboundsoftware/schemas!599
2025-10-20 17:17:03 +02:00
Renovate 2a1415bc35 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.231 2025-10-20 14:58:41 +00:00
argoyle 8e53b1fbf4 Merge branch 'ci-add-git-cliff-changelog-config' into 'main'
ci: add git-cliff configuration for changelog generation

See merge request unboundsoftware/schemas!597
2025-10-14 11:37:20 +02:00
argoyle 2cf992c948 ci: add git-cliff configuration for changelog generation
This commit introduces a new configuration file for git-cliff to 
automate changelog generation. It defines templates for the header, 
body, and footer of the changelog, ensuring that all notable changes 
are documented effectively. The configuration enables parsing of 
conventional commit messages, allowing for better organization of 
changes by type. This enhancement streamlines the release process and 
improves project maintainability.
2025-10-14 11:28:16 +02:00
argoyle ca9e7c3aa0 Merge branch 'next-release' into 'main'
chore(release): prepare for v0.6.6

See merge request unboundsoftware/schemas!584
2025-10-14 11:17:51 +02:00
Unbound Release db762cb496 chore(release): prepare for v0.6.6 2025-10-14 11:17:50 +02:00
argoyle c33885a0ab Merge branch 'renovate/golang-1.x' into 'main'
chore(deps): update golang docker tag to v1.25.3

See merge request unboundsoftware/schemas!596
2025-10-14 08:59:55 +02:00
Renovate 1135d77c35 chore(deps): update golang docker tag to v1.25.3 2025-10-13 23:58:57 +00:00
argoyle d1d14c1097 Merge branch 'renovate/github.com-wundergraph-graphql-go-tools-v2-2.x' into 'main'
fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.230

See merge request unboundsoftware/schemas!595
2025-10-10 10:32:46 +02:00
Renovate 41075e06a3 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.230 2025-10-09 15:02:02 +00:00
argoyle a7d4c01089 Merge branch 'renovate/golang-1.x' into 'main'
chore(deps): update golang docker tag to v1.25.2

See merge request unboundsoftware/schemas!594
2025-10-08 09:19:12 +02:00
Renovate 7c30a66144 chore(deps): update golang docker tag to v1.25.2 2025-10-07 22:08:51 +00:00
argoyle 98c17196c8 Merge branch 'renovate/github.com-pressly-goose-v3-3.x' into 'main'
fix(deps): update module github.com/pressly/goose/v3 to v3.26.0

See merge request unboundsoftware/schemas!593
2025-10-05 11:04:54 +02:00
Renovate 6635019035 fix(deps): update module github.com/pressly/goose/v3 to v3.26.0 2025-10-03 13:59:47 +00:00
argoyle 1ae73e7203 Merge branch 'renovate/alessandrojcm-commitlint-pre-commit-hook-9.x' into 'main'
chore(deps): update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.23.0

See merge request unboundsoftware/schemas!592
2025-10-02 17:06:07 +02:00
Renovate 91c02eb499 chore(deps): update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.23.0 2025-10-02 10:00:25 +00:00
argoyle 32fc6b2641 Merge branch 'renovate/golang-1.25.1' into 'main'
chore(deps): update golang:1.25.1 docker digest to 12640a4

See merge request unboundsoftware/schemas!591
2025-10-01 19:19:11 +02:00
Renovate ce86af4486 chore(deps): update golang:1.25.1 docker digest to 12640a4 2025-10-01 13:58:52 +00:00
argoyle 5258a68682 Merge branch 'renovate/goreleaser-goreleaser-2.x' into 'main'
chore(deps): update goreleaser/goreleaser docker tag to v2.12.5

See merge request unboundsoftware/schemas!590
2025-10-01 15:16:26 +02:00
Renovate 1d69be641c chore(deps): update goreleaser/goreleaser docker tag to v2.12.5 2025-10-01 12:57:39 +00:00
argoyle 3c571a1dc3 Merge branch 'renovate/goreleaser-goreleaser-2.x' into 'main'
chore(deps): update goreleaser/goreleaser docker tag to v2.12.4

See merge request unboundsoftware/schemas!589
2025-10-01 07:35:32 +02:00
Renovate 4c16e293c0 chore(deps): update goreleaser/goreleaser docker tag to v2.12.4 2025-10-01 02:56:55 +00:00
argoyle 18fb8da472 Merge branch 'renovate/github.com-99designs-gqlgen-0.x' into 'main'
fix(deps): update module github.com/99designs/gqlgen to v0.17.81

See merge request unboundsoftware/schemas!588
2025-09-26 11:24:45 +02:00
Renovate c9fa0ecc2c fix(deps): update module github.com/99designs/gqlgen to v0.17.81 2025-09-25 22:58:35 +00:00
argoyle 35db2fc74e Merge branch 'renovate/github.com-wundergraph-graphql-go-tools-v2-2.x' into 'main'
fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.229

See merge request unboundsoftware/schemas!587
2025-09-25 16:16:31 +02:00
Renovate e10ab9d75f fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.229 2025-09-25 13:57:58 +00:00
argoyle d6100bcb76 Merge branch 'renovate/goreleaser-goreleaser-2.x' into 'main'
chore(deps): update goreleaser/goreleaser docker tag to v2.12.3

See merge request unboundsoftware/schemas!586
2025-09-25 07:59:03 +02:00
argoyle 47d122aa8d Merge branch 'renovate/github.com-wundergraph-graphql-go-tools-v2-2.x' into 'main'
fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.228

See merge request unboundsoftware/schemas!585
2025-09-25 07:58:22 +02:00
Renovate b089801216 chore(deps): update goreleaser/goreleaser docker tag to v2.12.3 2025-09-25 03:56:44 +00:00
Renovate 3c071cb300 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.228 2025-09-24 15:57:59 +00:00
argoyle 8fe56f6ce0 Merge branch 'renovate/github.com-wundergraph-graphql-go-tools-v2-2.x' into 'main'
fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.227

See merge request unboundsoftware/schemas!583
2025-09-23 16:11:22 +02:00
Renovate c9aa447a9f fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.227 2025-09-23 13:59:32 +00:00
argoyle 575f97e8d1 Merge branch 'renovate/golangci-golangci-lint-2.x' into 'main'
chore(deps): update pre-commit hook golangci/golangci-lint to v2.5.0

See merge request unboundsoftware/schemas!582
2025-09-22 11:22:53 +02:00
Renovate dde2a965ec chore(deps): update pre-commit hook golangci/golangci-lint to v2.5.0 2025-09-21 19:57:48 +00:00
argoyle b6ae9826b2 Merge branch 'next-release' into 'main'
chore(release): prepare for v0.6.5

See merge request unboundsoftware/schemas!575
2025-09-18 13:57:02 +02:00
Unbound Release 787f388168 chore(release): prepare for v0.6.5 2025-09-18 13:57:02 +02:00
argoyle c4b01dbe07 Merge branch 'renovate/eventsourced' into 'main'
fix(deps): update module gitlab.com/unboundsoftware/eventsourced/eventsourced to v1.19.3

See merge request unboundsoftware/schemas!581
2025-09-18 12:14:01 +02:00
Renovate 992a95ff8d fix(deps): update module gitlab.com/unboundsoftware/eventsourced/eventsourced to v1.19.3 2025-09-18 09:58:42 +00:00
argoyle 0c31b8c2b9 Merge branch 'renovate/goreleaser-goreleaser-2.x' into 'main'
chore(deps): update goreleaser/goreleaser docker tag to v2.12.2

See merge request unboundsoftware/schemas!580
2025-09-18 09:36:58 +02:00
Renovate b75b61f724 chore(deps): update goreleaser/goreleaser docker tag to v2.12.2 2025-09-18 02:57:23 +00:00
argoyle 0e76377865 Merge branch 'renovate/github.com-99designs-gqlgen-0.x' into 'main'
fix(deps): update module github.com/99designs/gqlgen to v0.17.80

See merge request unboundsoftware/schemas!579
2025-09-17 22:08:20 +02:00
Renovate 52e58f8df8 fix(deps): update module github.com/99designs/gqlgen to v0.17.80 2025-09-17 21:19:09 +02:00
argoyle 35ba7679c9 Merge branch 'renovate/goreleaser-goreleaser-2.x' into 'main'
chore(deps): update goreleaser/goreleaser docker tag to v2.12.1

See merge request unboundsoftware/schemas!578
2025-09-16 16:44:34 +02:00
Renovate 9d5dc75c2b chore(deps): update goreleaser/goreleaser docker tag to v2.12.1 2025-09-16 02:56:58 +00:00
argoyle 83cb25a2bb Merge branch 'renovate/github.com-99designs-gqlgen-0.x' into 'main'
fix(deps): update module github.com/99designs/gqlgen to v0.17.79

See merge request unboundsoftware/schemas!577
2025-09-15 07:53:07 +02:00
Renovate e7a09e8322 fix(deps): update module github.com/99designs/gqlgen to v0.17.79 2025-09-15 07:19:10 +02:00
argoyle 2eae446669 Merge branch 'renovate/lietu-go-pre-commit-1.x' into 'main'
chore(deps): update pre-commit hook lietu/go-pre-commit to v1

See merge request unboundsoftware/schemas!576
2025-09-13 17:23:59 +02:00
Renovate 2e86f80727 chore(deps): update pre-commit hook lietu/go-pre-commit to v1 2025-09-13 09:12:24 +00:00
argoyle 1311a07dcb Merge branch 'renovate/github.com-wundergraph-graphql-go-tools-v2-2.x' into 'main'
fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.226

See merge request unboundsoftware/schemas!574
2025-09-12 14:17:49 +02:00
Renovate cd080189a9 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.226 2025-09-12 11:57:31 +00:00
argoyle 2d86c77c39 Merge branch 'next-release' into 'main'
chore(release): prepare for v0.6.4

See merge request unboundsoftware/schemas!573
2025-09-11 13:23:32 +02:00
Unbound Release eea7f86bbe chore(release): prepare for v0.6.4 2025-09-11 13:23:32 +02:00
argoyle baccd12e63 Merge branch 'renovate/eventsourced' into 'main'
fix(deps): update module gitlab.com/unboundsoftware/eventsourced/amqp to v1.9.0

See merge request unboundsoftware/schemas!572
2025-09-11 12:09:50 +02:00
Renovate 7fe90ee9af fix(deps): update module gitlab.com/unboundsoftware/eventsourced/amqp to v1.9.0 2025-09-11 09:58:15 +00:00
argoyle a3b9cae8eb Merge branch 'next-release' into 'main'
chore(release): prepare for v0.6.3

See merge request unboundsoftware/schemas!561
2025-09-11 10:42:36 +02:00
Unbound Release 769cbd3f14 chore(release): prepare for v0.6.3 2025-09-11 10:42:35 +02:00
Renovate ecc4da28ff fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.225 2025-09-09 14:59:00 +00:00
Renovate 4a14f41324 chore(deps): update golang:1.25.1 docker digest to 53f7808 2025-09-08 23:23:22 +00:00
Renovate 3f8fdce292 chore(deps): update golang docker tag to v1.25.1 2025-09-03 19:58:22 +00:00
Renovate 335c419a44 chore(deps): update goreleaser/goreleaser docker tag to v2.12.0 2025-09-03 03:57:02 +00:00
Renovate 82d2aa1812 fix(deps): update module go.opentelemetry.io/contrib/bridges/otelslog to v0.13.0 2025-09-01 11:25:33 +02:00
Renovate 24bbb39c3d fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.224 2025-09-01 08:57:51 +00:00
Renovate d98a20afff fix(deps): update opentelemetry-go monorepo 2025-08-29 20:07:41 +00:00
Renovate 737b133b8b fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.223 2025-08-28 09:57:27 +00:00
Renovate 177f923fc2 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.222 2025-08-28 08:52:22 +02:00
Renovate fd0c89dce9 fix(deps): update module github.com/stretchr/testify to v1.11.1 2025-08-27 12:00:45 +00:00
Renovate 1e58f8e0d5 fix(deps): update module github.com/pressly/goose/v3 to v3.25.0 2025-08-25 08:34:02 +02:00
Renovate 53349071aa fix(deps): update module github.com/stretchr/testify to v1.11.0 2025-08-24 16:59:02 +00:00
Renovate efe9e0a5a0 chore(deps): update golang:1.25.0 docker digest to f6b9e1a 2025-08-22 18:58:38 +00:00
Unbound Release 406cb8e4d0 chore(release): prepare for v0.6.2 2025-08-22 13:47:28 +00:00
argoyle 6e7ee0110b chore: remove conflicts entry from homebrew-taps config
Remove the conflicts entry for the unbound-schemas formula in the 
homebrew-taps configuration to simplify the integration process. This 
change enhances compatibility and streamlines the build setup.
2025-08-22 15:32:55 +02:00
Unbound Release 6eac3a7796 chore(release): prepare for v0.6.1 2025-08-22 14:33:00 +02:00
Renovate 890a6fd50e fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.221 2025-08-22 09:58:39 +00:00
Renovate e1cf0d8cb3 chore(deps): update golang docker tag to v1.25.0 2025-08-14 18:57:49 +00:00
Renovate 63d70d7e35 chore(deps): update golang:1.24.6 docker digest to cd8f653 2025-08-14 13:57:53 +00:00
Renovate 35a454f8b1 fix(deps): update module github.com/sparetimecoders/goamqp to v0.3.3 2025-08-14 13:01:31 +00:00
Renovate 6651553246 chore(deps): update pre-commit hook golangci/golangci-lint to v2.4.0 2025-08-13 20:58:38 +00:00
Renovate 311ef3f530 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.220 2025-08-13 09:58:44 +00:00
Renovate d8cce2fb05 chore(deps): update golang:1.24.6 docker digest to 958bfd1 2025-08-12 22:58:04 +00:00
Renovate 309b9423a4 chore(deps): update pre-commit hook pre-commit/pre-commit-hooks to v6 2025-08-09 19:58:04 +00:00
Renovate 323145a076 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.219 2025-08-08 10:57:11 +00:00
Renovate 28b4dc5572 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.218 2025-08-07 15:57:39 +00:00
Renovate c1173c20ae chore(deps): update golang docker tag to v1.24.6 2025-08-06 20:58:16 +00:00
Renovate 2d12077016 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.217 2025-08-06 12:57:31 +00:00
Renovate c38db83cd1 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.216 2025-08-06 09:58:51 +00:00
Renovate 25bd438d05 chore(deps): update pre-commit hook golangci/golangci-lint to v2.3.1 2025-08-02 21:58:23 +00:00
Renovate f4355d620f fix(deps): update module github.com/golang-jwt/jwt/v5 to v5.3.0 2025-07-31 22:14:05 +02:00
Renovate 887ba69c5e fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.215 2025-07-31 22:12:36 +02:00
Renovate 8e6c7aade7 chore(deps): update goreleaser/goreleaser docker tag to v2.11.2 2025-07-31 14:11:53 +00:00
Renovate 0f0f50111f fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.213 2025-07-29 00:05:28 +00:00
Renovate 81a895e5a6 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.212 2025-07-28 15:57:36 +00:00
Renovate 0fab1d5098 fix(deps): update module github.com/99designs/gqlgen to v0.17.78 2025-07-28 12:21:43 +02:00
Renovate 4fbfc0f42e chore(deps): update goreleaser/goreleaser docker tag to v2.11.1 2025-07-25 02:57:47 +00:00
Renovate 0ba3706c12 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.210 2025-07-22 19:56:58 +00:00
Renovate c44ac87b5e chore(deps): update golang:1.24.5 docker digest to 0a156a4 2025-07-22 04:57:28 +00:00
Renovate deefcd7045 chore(deps): update pre-commit hook golangci/golangci-lint to v2.3.0 2025-07-21 20:04:12 +02:00
Renovate 4933857351 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.208 2025-07-21 14:57:27 +00:00
Renovate 18616e8346 fix(deps): update module github.com/alecthomas/kong to v1.12.1 2025-07-21 07:37:41 +02:00
Renovate dd075afb8d chore(deps): update pre-commit hook gitleaks/gitleaks to v8.28.0 2025-07-20 16:56:45 +00:00
Renovate 003bd3cd50 fix(deps): update module github.com/golang-jwt/jwt/v5 to v5.2.3 2025-07-15 21:09:48 +02:00
Renovate a68fb437dc fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.207 2025-07-15 17:54:08 +00:00
Renovate 8aa57e8f68 chore(deps): update pre-commit hook golangci/golangci-lint to v2.2.2 2025-07-11 12:54:20 +00:00
Renovate 84e8764054 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.204 2025-07-11 06:54:06 +00:00
Renovate c40bbad892 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.203 2025-07-10 09:54:19 +00:00
Renovate c159c6d20e chore(deps): update goreleaser/goreleaser docker tag to v2.11.0 2025-07-09 21:49:06 +02:00
Renovate ae0d796e93 chore(deps): update golang docker tag to v1.24.5 2025-07-09 18:55:13 +00:00
Renovate 1ec0f9a3a7 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.202 2025-07-09 12:54:39 +00:00
Renovate 0dad651959 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.200 2025-07-08 15:54:10 +00:00
Renovate 24fcc4e6a2 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.199 2025-07-07 15:54:28 +00:00
Renovate 4c7406d97b fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.198 2025-07-04 12:54:27 +00:00
Renovate 51affc5a55 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.197 2025-07-03 11:54:02 +00:00
Renovate 10871a9b32 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.196 2025-07-02 17:54:39 +00:00
Renovate 411b51f895 fix(deps): update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.195 2025-07-02 02:22:25 +00:00
Renovate 6a478209ea chore(deps): update golang:1.24.4 docker digest to 9f820b6 2025-07-01 05:54:30 +00:00
Renovate 043ca65698 chore(deps): update pre-commit hook golangci/golangci-lint to v2.2.1 2025-06-29 21:53:56 +00:00
52 changed files with 8524 additions and 3335 deletions
+1 -2
View File
@@ -1,6 +1,5 @@
.gitignore
/.gitlab
.gitlab-ci.yml
/.gitea
.graphqlconfig
/exported
/k8s
+94
View File
@@ -0,0 +1,94 @@
name: schemas
on:
push:
branches: [main]
tags:
- 'v*'
pull_request:
branches: [main]
workflow_dispatch:
inputs:
deploy_prod:
description: 'Deploy to production'
required: false
default: 'false'
type: boolean
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: 'stable'
- name: Generate and format check
run: |
go install mvdan.cc/gofumpt@latest
go install golang.org/x/tools/cmd/goimports@latest
go generate ./...
git diff --stat --exit-code
- name: Run tests
run: go test -race -coverprofile=coverage.txt ./...
vulnerabilities:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: 'stable'
- name: Check vulnerabilities
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
check-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-node@v6
with:
node-version: '24'
- uses: actions/setup-go@v6
with:
go-version: 'stable'
- name: Check goreleaser config
uses: goreleaser/goreleaser-action@v7
with:
version: '~> v2'
args: check
- name: Test release build
uses: goreleaser/goreleaser-action@v7
with:
version: '~> v2'
args: release --snapshot --clean
build:
needs: [check, vulnerabilities, check-release]
runs-on: ubuntu-latest
env:
BUILDTOOLS_CONTENT: ${{ secrets.BUILDTOOLS_CONTENT }}
GITEA_REPOSITORY: ${{ gitea.repository }}
steps:
- uses: actions/checkout@v6
- uses: buildtool/setup-buildtools-action@v1
- name: Build and push
run: unset GITEA_TOKEN && build && push
deploy-prod:
needs: build
if: gitea.ref == 'refs/heads/main'
runs-on: ubuntu-latest
env:
BUILDTOOLS_CONTENT: ${{ secrets.BUILDTOOLS_CONTENT }}
GITEA_REPOSITORY: ${{ gitea.repository }}
environment: prod
steps:
- uses: actions/checkout@v6
- uses: buildtool/setup-buildtools-action@v1
- name: Deploy to production
run: deploy prod
+33
View File
@@ -0,0 +1,33 @@
name: Goreleaser
on:
push:
tags:
- 'v*'
env:
RELEASE_TOKEN_FILE: /runner-secrets/release-token
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-node@v6
with:
node-version: '24'
- uses: actions/setup-go@v6
with:
go-version: 'stable'
- name: Install goreleaser
uses: goreleaser/goreleaser-action@v7
with:
version: 'v2.13.3'
install-only: true
- name: Release
run: |
GITEA_TOKEN=$(cat "${RELEASE_TOKEN_FILE}")
export GITEA_TOKEN
goreleaser release --clean
+25
View File
@@ -0,0 +1,25 @@
name: pre-commit
permissions: read-all
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
pre-commit:
runs-on: ubuntu-latest
env:
SKIP: no-commit-to-branch
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: 'stable'
- uses: actions/setup-python@v6
with:
python-version: '3.14'
- name: Install goimports
run: go install golang.org/x/tools/cmd/goimports@latest
- uses: pre-commit/action@v3.0.1
+11
View File
@@ -0,0 +1,11 @@
name: Release
on:
push:
branches: [main]
jobs:
release:
uses: unboundsoftware/shared-workflows/.gitea/workflows/Release.yml@main
with:
tag_only: true
+3
View File
@@ -1,9 +1,12 @@
.idea
.claude
.testCoverage.txt
.testCoverage.txt.tmp
coverage.html
coverage.out
/exported
/release
/schemactl
/service
CHANGES.md
VERSION
-89
View File
@@ -1,89 +0,0 @@
include:
- template: 'Workflows/MergeRequest-Pipelines.gitlab-ci.yml'
- project: unboundsoftware/ci-templates
file: Defaults.gitlab-ci.yml
- project: unboundsoftware/ci-templates
file: Release.gitlab-ci.yml
- project: unboundsoftware/ci-templates
file: Pre-Commit-Go.gitlab-ci.yml
stages:
- build
- test
- deploy-prod
- release
variables:
UNBOUND_RELEASE_TAG_ONLY: true
.buildtools:
image: buildtool/build-tools:${BUILDTOOLS_VERSION}
check:
stage: .pre
image: amd64/golang:1.24.4@sha256:3494bbe140127d12656113203ec91b8e3ff34e8a2b06a0a22bb0d8a41cc69e53
script:
- go install mvdan.cc/gofumpt@latest
- go install golang.org/x/tools/cmd/goimports@latest
- go generate ./...
- git diff --stat --exit-code
build:
extends: .buildtools
stage: build
script:
- build
- curl -Os https://uploader.codecov.io/latest/linux/codecov
- chmod +x codecov
- ./codecov -t ${CODECOV_TOKEN} -R $CI_PROJECT_DIR -C $CI_COMMIT_SHA -r $CI_PROJECT_PATH
- push
vulnerabilities:
stage: build
image: amd64/golang:1.24.4@sha256:3494bbe140127d12656113203ec91b8e3ff34e8a2b06a0a22bb0d8a41cc69e53
script:
- go install golang.org/x/vuln/cmd/govulncheck@latest
- govulncheck ./...
deploy-prod:
extends: .buildtools
stage: deploy-prod
before_script:
- echo Deploy to prod
script:
- deploy prod
rules:
- if: $CI_COMMIT_BRANCH == "main"
environment:
name: prod
resource_group: prod
check_release:
stage: test
image:
name: goreleaser/goreleaser:v2.10.2@sha256:ebc884882268c835edd70c628dbdf830481ae23a569b343cd7d7b178c91573ac
entrypoint: [ '' ]
variables:
GOTOOLCHAIN: auto
script: |
goreleaser check
goreleaser release --snapshot --clean
release:
stage: release
needs:
- unbound_release_prepare_release
image:
name: goreleaser/goreleaser:v2.10.2@sha256:ebc884882268c835edd70c628dbdf830481ae23a569b343cd7d7b178c91573ac
entrypoint: [ '' ]
variables:
# Disable shallow cloning so that goreleaser can diff between tags to
# generate a changelog.
GIT_DEPTH: 0
GITLAB_TOKEN: $GITLAB_CI_TOKEN
GOTOOLCHAIN: auto
# Only run this release job for tags, not every commit (for example).
rules:
- if: $CI_COMMIT_TAG
script: |
goreleaser release --clean --release-notes=CHANGES.md
+5 -8
View File
@@ -1,6 +1,10 @@
project_name: unbound-schemas
version: 2
gitea_urls:
api: http://gitea-http.gitea.svc.cluster.local:3000/api/v1
download: https://gitea.unbound.se
env:
- CGO_ENABLED=0
@@ -21,19 +25,12 @@ homebrew_casks:
repository:
owner: unboundsoftware
name: homebrew-taps
binary: schemactl
binaries: [schemactl]
directory: Casks
conflicts:
- formula: unbound-schemas
commit_author:
name: "Joakim Olsson"
email: joakim@unbound.se
homepage: "https://schemas.unbound.se/"
hooks:
post:
install: |
# replace foo with the actual binary name
system_command "/usr/bin/xattr", args: ["-dr", "com.apple.quarantine", "#{staged_path}/schemactl"]
archives:
- id: unbound-schemas
+6 -13
View File
@@ -2,7 +2,7 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
@@ -10,15 +10,8 @@ repos:
args:
- --allow-multiple-documents
- id: check-added-large-files
- repo: https://gitlab.com/devopshq/gitlab-ci-linter
rev: v1.0.6
hooks:
- id: gitlab-ci-linter
args:
- --project
- unboundsoftware/schemas
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
rev: v9.22.0
rev: v9.24.0
hooks:
- id: commitlint
stages: [ commit-msg ]
@@ -30,18 +23,18 @@ repos:
- id: go-imports
args:
- -local
- gitlab.com/unboundsoftware/schemas
- git.unbound.se/unboundsoftware/schemas
- repo: https://github.com/lietu/go-pre-commit
rev: v0.1.0
rev: v1.0.0
hooks:
- id: go-test
- id: gofumpt
- repo: https://github.com/golangci/golangci-lint
rev: v2.2.0
rev: v2.11.4
hooks:
- id: golangci-lint-full
- repo: https://github.com/gitleaks/gitleaks
rev: v8.27.2
rev: v8.30.1
hooks:
- id: gitleaks
exclude: '^ctl/generated.go|graph/generated/.*$|^graph/model/models_gen.go|^tools/.*$$'
+3
View File
@@ -0,0 +1,3 @@
{
"version": "v0.9.6"
}
+658
View File
@@ -2,6 +2,359 @@
All notable changes to this project will be documented in this file.
## [0.9.6] - 2026-03-29
### 🐛 Bug Fixes
- *(graph)* Stabilize debouncer tests with synctest fake clock
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.257 (#743)
- *(deps)* Update golang.org/x/net to v0.51.0
- *(deps)* Update module go.opentelemetry.io/contrib/bridges/otelslog to v0.16.0 (#745)
- *(deps)* Update opentelemetry-go monorepo (#744)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.260 (#750)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.261 (#751)
- *(deps)* Update opentelemetry-go monorepo (#755)
- *(deps)* Update module go.opentelemetry.io/contrib/bridges/otelslog to v0.17.0 (#756)
- *(deps)* Update module golang.org/x/sync to v0.20.0 (#760)
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.88 (#762)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.262 (#765)
- *(deps)* Update module golang.org/x/crypto to v0.49.0 (#767)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.263 (#771)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.264 (#773)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.265 (#776)
- *(deps)* Update module gitlab.com/unboundsoftware/eventsourced/pg to v1.18.5 (#778)
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.89 (#782)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.266 (#784)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.267 (#786)
- Cap Node.js heap to prevent OOM during wgc composition (#788)
### ⚙️ Miscellaneous Tasks
- *(deps)* Update golang docker tag to v1.26.1 (#752)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.1
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.2 (#758)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.3 (#763)
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.30.1 (#769)
- *(deps)* Update golang:1.26.1 docker digest to 984bf90 (#774)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.4 (#780)
- *(deps)* Update node.js to v24.14.1 (#783)
## [0.9.5] - 2026-02-25
### 🐛 Bug Fixes
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.256
### ⚙️ Miscellaneous Tasks
- *(deps)* Update golang:1.26.0 docker digest to 9835fb4 (#736)
- *(deps)* Update node.js to v24.14.0 (#735)
- *(deps)* Update node.js to 7fddd9d (#739)
## [0.9.4] - 2026-02-23
### 🐛 Bug Fixes
- *(ci)* Pin goreleaser to v2.13.3 for Gitea SDK compatibility
## [0.9.3] - 2026-02-23
### 🐛 Bug Fixes
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.249 (#702)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.250 (#704)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.251
- *(deps)* Update module gitlab.com/unboundsoftware/eventsourced/pg to v1.18.3
- *(deps)* Update module github.com/vektah/gqlparser/v2 to v2.5.32
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.252 (#714)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.253 (#716)
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.87
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.254 (#718)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.255
- *(deps)* Update module github.com/pressly/goose/v3 to v3.27.0
- *(deps)* Update module gitlab.com/unboundsoftware/eventsourced/pg to v1.18.4 (#727)
- Prevent OOM on rapid schema publishing
### ⚙️ Miscellaneous Tasks
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.10.1 (#706)
- *(deps)* Update goreleaser/goreleaser-action action to v7
- *(deps)* Update actions/setup-node action to v6
## [0.9.2] - 2026-02-13
### 🐛 Bug Fixes
- *(deps)* Update module github.com/auth0/go-jwt-middleware/v2 to v3
- Migrate to go-jwt-middleware v3 API
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.243 (#680)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.244 (#681)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.245 (#682)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.246 (#683)
- *(deps)* Update module gitlab.com/unboundsoftware/eventsourced/pg to v1.18.0 (#685)
- *(deps)* Update module gitlab.com/unboundsoftware/eventsourced/pg to v1.18.1 (#686)
- *(deps)* Update opentelemetry-go monorepo (#687)
- *(deps)* Update module go.opentelemetry.io/contrib/bridges/otelslog to v0.15.0 (#688)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.247 (#691)
- *(deps)* Update module github.com/alecthomas/kong to v1.14.0 (#692)
- *(deps)* Update eventsourced (#693)
- *(deps)* Update module golang.org/x/crypto to v0.48.0 (#694)
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.248 (#698)
### ⚙️ Miscellaneous Tasks
- *(deps)* Update node.js to cd6fb7e (#684)
- *(deps)* Update golang:1.25.6 docker digest to ceda080 (#689)
- *(deps)* Update golang docker tag to v1.25.7
- *(deps)* Update golang:1.25.7 docker digest to d2819ff (#695)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.9.0 (#697)
- *(deps)* Update golang docker tag to v1.26.0 (#696)
- *(deps)* Update node.js to v24.13.1 (#699)
## [0.9.1] - 2026-01-18
### 🐛 Bug Fixes
- *(ci)* Run build job on tags for Docker images
## [0.9.0] - 2026-01-18
### 🚀 Features
- Add commands for managing organizations and users
- Migrate from GitLab CI to Gitea Actions
### 🐛 Bug Fixes
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.239
- *(k8s)* Update ingress class configuration for schema
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.84
- *(docker)* Update Node.js version to 24.11.1-alpine
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.240
- *(deps)* Update opentelemetry-go monorepo
- *(deps)* Update module golang.org/x/crypto to v0.46.0
- *(deps)* Update module go.opentelemetry.io/contrib/bridges/otelslog to v0.14.0
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.241
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.85
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.242
- *(deps)* Update module golang.org/x/crypto to v0.47.0
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.86
### 🚜 Refactor
- *(cache)* Optimize test setup and reduce iterations
### 🧪 Testing
- Add validation and event tests for organization and API key
- *(cache)* Update tests to use legacy hash for speed
### ⚙️ Miscellaneous Tasks
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.30.0
- *(deps)* Update goreleaser/goreleaser docker tag to v2.13.0
- *(deps)* Update golang docker tag to v1.25.5
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.7.0
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.7.1
- *(deps)* Update node.js to 682368d
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.7.2
- *(deps)* Update goreleaser/goreleaser docker tag to v2.13.1
- *(deps)* Update golang:1.25.5 docker digest to 0c27bcf
- *(deps)* Update node.js to v24.12.0
- *(deps)* Update node.js to c921b97
- *(deps)* Update goreleaser/goreleaser docker tag to v2.13.2
- *(deps)* Update golang:1.25.5 docker digest to ad03ba9
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.8.0
- *(deps)* Update goreleaser/goreleaser docker tag to v2.13.3
- *(deps)* Update golang:1.25.5 docker digest to 3a01526
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.24.0
- *(deps)* Update node.js to v24.13.0
- *(deps)* Update golang docker tag to v1.25.6
## [0.8.0] - 2025-11-21
### 🚀 Features
- *(tests)* Add unit tests for WebSocket initialization logic
- Add latestSchema query for retrieving schema updates
- Add CLAUDE.md for project documentation and guidelines
- *(cache)* Implement hashed API key storage and retrieval
- *(health)* Add health checking endpoints and logic
- *(cache)* Add concurrency safety and logging improvements
### 🐛 Bug Fixes
- Enhance API key handling and logging in middleware
- Add command executor interface for better testing
- *(deps)* Update module golang.org/x/crypto to v0.45.0
- *(deps)* Update module github.com/auth0/go-jwt-middleware/v2 to v2.3.1
### 🧪 Testing
- Enhance assertions for version and subscription config
- *(cache)* Reduce goroutines for race detector stability
### ⚙️ Miscellaneous Tasks
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.29.1
- *(deps)* Update node.js to v24
## [0.7.0] - 2025-11-19
### 🚀 Features
- Add Cosmo Router config generation and PubSub support
### 🐛 Bug Fixes
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.231
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.232
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.233
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.234
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.235
- *(deps)* Update module github.com/vektah/gqlparser/v2 to v2.5.31
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.236
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.82
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.237
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.83
- *(deps)* Update module github.com/alecthomas/kong to v1.13.0
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.238
### ⚙️ Miscellaneous Tasks
- Add git-cliff configuration for changelog generation
- *(deps)* Update golang:1.25.3 docker digest to 69d1009
- *(deps)* Update goreleaser/goreleaser docker tag to v2.12.6
- *(deps)* Update goreleaser/goreleaser docker tag to v2.12.7
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.6.0
- *(deps)* Update golang:1.25.3 docker digest to 9ac0edc
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.6.1
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.29.0
- *(deps)* Update golang docker tag to v1.25.4
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.6.2
- *(deps)* Update golang:1.25.4 docker digest to efe81fa
## [0.6.6] - 2025-10-14
### 🐛 Bug Fixes
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.227
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.228
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.229
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.81
- *(deps)* Update module github.com/pressly/goose/v3 to v3.26.0
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.230
### ⚙️ Miscellaneous Tasks
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.5.0
- *(deps)* Update goreleaser/goreleaser docker tag to v2.12.3
- *(deps)* Update goreleaser/goreleaser docker tag to v2.12.4
- *(deps)* Update goreleaser/goreleaser docker tag to v2.12.5
- *(deps)* Update golang:1.25.1 docker digest to 12640a4
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.23.0
- *(deps)* Update golang docker tag to v1.25.2
- *(deps)* Update golang docker tag to v1.25.3
## [0.6.5] - 2025-09-18
### 🐛 Bug Fixes
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.226
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.79
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.80
- *(deps)* Update module gitlab.com/unboundsoftware/eventsourced/eventsourced to v1.19.3
### ⚙️ Miscellaneous Tasks
- *(deps)* Update pre-commit hook lietu/go-pre-commit to v1
- *(deps)* Update goreleaser/goreleaser docker tag to v2.12.1
- *(deps)* Update goreleaser/goreleaser docker tag to v2.12.2
## [0.6.4] - 2025-09-11
### 🐛 Bug Fixes
- *(deps)* Update module gitlab.com/unboundsoftware/eventsourced/amqp to v1.9.0
## [0.6.3] - 2025-09-09
### 🐛 Bug Fixes
- *(deps)* Update module github.com/stretchr/testify to v1.11.0
- *(deps)* Update module github.com/pressly/goose/v3 to v3.25.0
- *(deps)* Update module github.com/stretchr/testify to v1.11.1
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.222
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.223
- *(deps)* Update opentelemetry-go monorepo
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.224
- *(deps)* Update module go.opentelemetry.io/contrib/bridges/otelslog to v0.13.0
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.225
### ⚙️ Miscellaneous Tasks
- *(deps)* Update golang:1.25.0 docker digest to f6b9e1a
- *(deps)* Update goreleaser/goreleaser docker tag to v2.12.0
- *(deps)* Update golang docker tag to v1.25.1
- *(deps)* Update golang:1.25.1 docker digest to 53f7808
## [0.6.2] - 2025-08-22
### ⚙️ Miscellaneous Tasks
- Remove conflicts entry from homebrew-taps config
## [0.6.1] - 2025-08-22
### 🐛 Bug Fixes
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.195
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.196
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.197
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.198
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.199
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.200
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.202
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.203
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.204
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.207
- *(deps)* Update module github.com/golang-jwt/jwt/v5 to v5.2.3
- *(deps)* Update module github.com/alecthomas/kong to v1.12.1
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.208
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.210
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.78
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.212
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.213
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.215
- *(deps)* Update module github.com/golang-jwt/jwt/v5 to v5.3.0
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.216
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.217
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.218
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.219
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.220
- *(deps)* Update module github.com/sparetimecoders/goamqp to v0.3.3
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.221
### ⚙️ Miscellaneous Tasks
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.2.1
- *(deps)* Update golang:1.24.4 docker digest to 9f820b6
- *(deps)* Update golang docker tag to v1.24.5
- *(deps)* Update goreleaser/goreleaser docker tag to v2.11.0
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.2.2
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.28.0
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.3.0
- *(deps)* Update golang:1.24.5 docker digest to 0a156a4
- *(deps)* Update goreleaser/goreleaser docker tag to v2.11.1
- *(deps)* Update goreleaser/goreleaser docker tag to v2.11.2
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.3.1
- *(deps)* Update golang docker tag to v1.24.6
- *(deps)* Update pre-commit hook pre-commit/pre-commit-hooks to v6
- *(deps)* Update golang:1.24.6 docker digest to 958bfd1
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.4.0
- *(deps)* Update golang:1.24.6 docker digest to cd8f653
- *(deps)* Update golang docker tag to v1.25.0
## [0.6.0] - 2025-06-29
### 🚀 Features
@@ -23,6 +376,10 @@ All notable changes to this project will be documented in this file.
- *(deps)* Update module github.com/vektah/gqlparser/v2 to v2.5.30
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.76
### ⚙️ Miscellaneous Tasks
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.2.0
## [0.5.3] - 2025-06-13
### 🐛 Bug Fixes
@@ -35,6 +392,10 @@ All notable changes to this project will be documented in this file.
- Remove Sentry integration and replace with OpenTelemetry
### ⚙️ Miscellaneous Tasks
- *(deps)* Update golang:1.24.4 docker digest to 3494bbe
## [0.5.2] - 2025-06-09
### 🐛 Bug Fixes
@@ -58,6 +419,15 @@ All notable changes to this project will be documented in this file.
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.185
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.186
### ⚙️ Miscellaneous Tasks
- *(deps)* Update golang:1.24.3 docker digest to f255a7d
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.27.0
- *(deps)* Update golang docker tag to v1.24.4
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.27.1
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.27.2
- *(deps)* Update goreleaser/goreleaser docker tag to v2.10.2
## [0.5.0] - 2025-05-15
### 🚀 Features
@@ -80,7 +450,16 @@ All notable changes to this project will be documented in this file.
### ⚙️ Miscellaneous Tasks
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.1.4
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.1.5
- *(deps)* Update golang:1.24.2 docker digest to bf7899c
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.25.0
- *(deps)* Update goreleaser/goreleaser docker tag to v2.9.0
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.25.1
- *(ci)* Update GitLab CI configuration for templates
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.1.6
- *(deps)* Update golang docker tag to v1.24.3
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.26.0
## [0.4.1] - 2025-04-24
@@ -93,6 +472,11 @@ All notable changes to this project will be documented in this file.
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.171
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.172
### ⚙️ Miscellaneous Tasks
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.1.1
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.1.2
## [0.4.0] - 2025-04-12
### 🚀 Features
@@ -112,6 +496,10 @@ All notable changes to this project will be documented in this file.
- *(deploy)* Remove cpu and memory limits for schemas
### ⚙️ Miscellaneous Tasks
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.24.3
## [0.3.0] - 2025-04-08
### 🚀 Features
@@ -143,6 +531,24 @@ All notable changes to this project will be documented in this file.
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.168
- *(deps)* Update module github.com/wundergraph/graphql-go-tools/v2 to v2.0.0-rc.169
### ⚙️ Miscellaneous Tasks
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.64.6
- *(deps)* Update golang docker tag to v1.24.1
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.22.0
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.64.7
- *(deps)* Update goreleaser/goreleaser docker tag to v2.8.0
- *(deps)* Update goreleaser/goreleaser docker tag to v2.8.1
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.64.8
- *(deps)* Update golang:1.24.1 docker digest to 5ecf333
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.24.2
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.0.1
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.0.2
- *(deps)* Update goreleaser/goreleaser docker tag to v2.8.2
- *(deps)* Update golang docker tag to v1.24.2
- *(deps)* Update golang:1.24.2 docker digest to aebb7df
## [0.2.0] - 2025-02-28
### 🚀 Features
@@ -167,7 +573,24 @@ All notable changes to this project will be documented in this file.
### ⚙️ Miscellaneous Tasks
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.23.3
- *(deps)* Update golang:1.23.5 docker digest to e213430
- *(deps)* Update dependency go to v1.23.6
- *(deps)* Update golang docker tag to v1.23.6
- *(deps)* Update golang:1.23.6 docker digest to 958bd2e
- *(deps)* Update golang:1.23.6 docker digest to 9271129
- *(deps)* Update goreleaser/goreleaser docker tag to v2.7.0
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.64.2
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.64.3
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.64.4
- *(go)* Update go version to 1.23.6 and remove toolchain
- *(deps)* Update golang docker tag to v1.24.0
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.64.5
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.21.0
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.24.0
- *(deps)* Update golang:1.24.0 docker digest
- *(deps)* Update golang:1.24.0 docker digest to a14c5a6
- *(deps)* Update golang:1.24.0 docker digest to 58cf31c
- *(docker)* Update base image architecture to amd64
## [0.1.1] - 2025-01-24
@@ -181,6 +604,27 @@ All notable changes to this project will be documented in this file.
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.63
- *(k8s)* Standardize app label to app.kubernetes.io/name
### ⚙️ Miscellaneous Tasks
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.63.1
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.63.2
- *(deps)* Update goreleaser/goreleaser docker tag to v2.5.1
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.63.3
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.63.4
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.23.0
- *(deps)* Update golang:1.23.4 docker digest to 3b1a7de
- *(deps)* Update golang:1.23.4 docker digest to 08e1417
- *(deps)* Update golang:1.23.4 docker digest to 585103a
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.23.1
- *(deps)* Update golang:1.23.4 docker digest to 5305905
- *(deps)* Update golang:1.23.4 docker digest to 9820aca
- *(deps)* Update dependency go to v1.23.5
- *(deps)* Update golang docker tag to v1.23.5
- *(deps)* Update goreleaser/goreleaser docker tag to v2.6.0
- *(deps)* Update golang:1.23.5 docker digest to 8c10f21
- *(deps)* Update goreleaser/goreleaser docker tag to v2.6.1
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.23.2
## [0.1.0] - 2025-01-01
### 🚀 Features
@@ -221,8 +665,47 @@ All notable changes to this project will be documented in this file.
### ⚙️ Miscellaneous Tasks
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.21.2
- *(deps)* Update goreleaser/goreleaser docker tag to v2.4.1
- *(deps)* Pin dependencies
- *(deps)* Pin dependencies
- *(deps)* Update goreleaser/goreleaser docker tag to v2.4.2
- *(deps)* Update goreleaser/goreleaser docker tag to v2.4.4
- *(deps)* Update dependency go to v1.23.3
- *(deps)* Update golang docker tag to v1.23.3
- *(deps)* Update unbound/pre-commit docker digest to 596abf5
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.62.0
- *(deps)* Update golang:1.23.3 docker digest to 8956c08
- *(deps)* Update unbound/pre-commit docker digest to e78425c
- *(deps)* Update golang:1.23.3 docker digest to 3694e36
- *(deps)* Update golang:1.23.3 docker digest to b2ca381
- *(deps)* Update golang:1.23.3 docker digest to 2660218
- *(deps)* Update golang:1.23.3 docker digest to c2d828f
- *(deps)* Update goreleaser/goreleaser docker tag to v2.4.5
- *(deps)* Update golang:1.23.3 docker digest to 73f06be
- *(deps)* Update goreleaser/goreleaser docker tag to v2.4.6
- *(deps)* Update goreleaser/goreleaser docker tag to v2.4.7
- *(deps)* Update goreleaser/goreleaser docker tag to v2.4.8
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.62.2
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.19.0
- *(deps)* Update golang:1.23.3 docker digest to ee5f0ad
- *(deps)* Update golang:1.23.3 docker digest to b4aabba
- *(deps)* Update golang:1.23.3 docker digest to 2b01164
- *(deps)* Update golang:1.23.3 docker digest to 017ec6b
- *(deps)* Update dependency go to v1.23.4
- *(deps)* Update golang docker tag to v1.23.4
- *(deps)* Update golang:1.23.4 docker digest to 574185e
- Remove unnecessary Docker variables from configuration
- *(ci)* Remove unused docker service from buildtools
- *(deps)* Update golang:1.23.4 docker digest to 7003184
- *(deps)* Update goreleaser/goreleaser docker tag to v2.5.0
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.20.0
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.21.3
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.21.4
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.22.0
- *(deps)* Update golang:1.23.4 docker digest to 7ea4c9d
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.22.1
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.63.0
## [0.0.7] - 2024-10-22
@@ -267,9 +750,49 @@ All notable changes to this project will be documented in this file.
### ⚙️ Miscellaneous Tasks
- *(deps)* Update pre-commit hook pre-commit/pre-commit-hooks to v4.6.0
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.15.0
- *(deps)* Update dependency go to v1.22.2
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.16.0
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.58.0
- *(deps)* Update dependency go to v1.22.3
- *(deps)* Update golang docker tag to v1.22.3
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.58.1
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.58.2
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.59.0
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.18.3
- *(deps)* Update dependency go to v1.22.4
- *(deps)* Update golang docker tag to v1.22.4
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.59.1
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.18.4
- *(deps)* Update dependency go to v1.22.5
- *(deps)* Update golang docker tag to v1.22.5
- *(deps)* Update dependency go to v1.22.6
- *(deps)* Update golang docker tag to v1.22.6
- *(deps)* Update dependency go to v1.23.0
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.60.1
- *(deps)* Update golang docker tag to v1.23.0
- Update golangci-lint hook identifier in pre-commit config
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.60.2
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.60.3
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.17.0
- *(deps)* Update dependency go to v1.23.1
- *(deps)* Update golang docker tag to v1.23.1
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.61.0
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.18.0
- Update goreleaser image to v2.3.1
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.19.1
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.19.2
- Add generate check
- *(deps)* Update goreleaser/goreleaser docker tag to v2.3.2
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.19.3
- *(deps)* Update dependency go to v1.23.2
- *(deps)* Update golang docker tag to v1.23.2
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.20.0
- *(deps)* Update pre-commit hook pre-commit/pre-commit-hooks to v5
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.20.1
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.21.0
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.21.1
- Add release notes for goreleaser command in .gitlab-ci.yml
## [0.0.6] - 2024-04-04
@@ -281,6 +804,7 @@ All notable changes to this project will be documented in this file.
### ⚙️ Miscellaneous Tasks
- Add step for checking release
- *(deps)* Update golang docker tag to v1.22.2
## [0.0.5] - 2024-04-03
@@ -305,15 +829,106 @@ All notable changes to this project will be documented in this file.
### ⚙️ Miscellaneous Tasks
- *(deps)* Bump github.com/stretchr/testify from 1.8.3 to 1.8.4
- Update golangci-lint
- *(deps)* Bump golang from 1.20.4 to 1.20.5
- Update Go version for vulnerabilities
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.31 to 0.17.32
- *(deps)* Bump github.com/wundergraph/graphql-go-tools
- *(deps)* Bump github.com/vektah/gqlparser/v2
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.32 to 0.17.33
- *(deps)* Bump github.com/getsentry/sentry-go from 0.21.0 to 0.22.0
- *(deps)* Bump github.com/alecthomas/kong from 0.7.1 to 0.8.0
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.33 to 0.17.34
- *(deps)* Bump github.com/vektah/gqlparser/v2 from 2.5.3 to 2.5.5
- *(deps)* Bump github.com/vektah/gqlparser/v2 from 2.5.5 to 2.5.6
- *(deps)* Bump github.com/pressly/goose/v3 from 3.11.2 to 3.13.0
- *(deps)* Bump github.com/sparetimecoders/goamqp from 0.1.4 to 0.1.5
- *(deps)* Bump github.com/pressly/goose/v3 from 3.13.0 to 3.13.1
- *(deps)* Bump github.com/pressly/goose/v3 from 3.13.1 to 3.13.4
- *(deps)* Bump gitlab.com/unboundsoftware/eventsourced/pg
- *(deps)* Bump github.com/wundergraph/graphql-go-tools
- *(deps)* Bump golang from 1.20.5 to 1.20.6
- *(deps)* Bump github.com/vektah/gqlparser/v2 from 2.5.6 to 2.5.7
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.34 to 0.17.35
- *(deps)* Bump github.com/vektah/gqlparser/v2 from 2.5.7 to 2.5.8
- *(deps)* Bump github.com/wundergraph/graphql-go-tools
- *(deps)* Bump github.com/pressly/goose/v3 from 3.13.4 to 3.14.0
- *(deps)* Bump github.com/wundergraph/graphql-go-tools
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.35 to 0.17.36
- *(deps)* Bump github.com/wundergraph/graphql-go-tools
- *(deps)* Bump github.com/getsentry/sentry-go from 0.22.0 to 0.23.0
- *(deps)* Bump golang from 1.20.6 to 1.20.7
- Update to Go 1.20.7
- *(deps)* Bump github.com/wundergraph/graphql-go-tools
- *(deps)* Bump github.com/wundergraph/graphql-go-tools
- *(deps)* Bump golang from 1.20.7 to 1.21.0
- *(deps)* Bump github.com/pressly/goose/v3 from 3.14.0 to 3.15.0
- *(deps)* Bump github.com/wundergraph/graphql-go-tools
- Update to Golang 1.21.0 for vulnerabilities
- Update pre-commit versions
- *(deps)* Bump github.com/getsentry/sentry-go from 0.23.0 to 0.24.0
- *(deps)* Bump github.com/rs/cors from 1.9.0 to 1.10.0
- *(deps)* Bump golang from 1.21.0 to 1.21.1
- Update to Go 1.21.1 for vulnerabilities
- *(deps)* Bump github.com/vektah/gqlparser/v2 from 2.5.8 to 2.5.9
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.36 to 0.17.37
- *(deps)* Bump github.com/getsentry/sentry-go from 0.24.0 to 0.24.1
- *(deps)* Bump github.com/vektah/gqlparser/v2 from 2.5.9 to 2.5.10
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.37 to 0.17.38
- *(deps)* Bump github.com/wundergraph/graphql-go-tools
- *(deps)* Bump github.com/rs/cors from 1.10.0 to 1.10.1
- *(deps)* Bump github.com/getsentry/sentry-go from 0.24.1 to 0.25.0
- *(deps)* Bump github.com/sparetimecoders/goamqp from 0.1.5 to 0.2.0
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.38 to 0.17.39
- *(deps)* Bump golang from 1.21.1 to 1.21.2
- *(deps)* Bump github.com/pressly/goose/v3 from 3.15.0 to 3.15.1
- *(deps)* Bump golang from 1.21.2 to 1.21.3
- *(deps)* Bump github.com/alecthomas/kong from 0.8.0 to 0.8.1
- *(deps)* Bump github.com/wundergraph/graphql-go-tools
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.39 to 0.17.40
- *(deps)* Bump gitlab.com/unboundsoftware/eventsourced/eventsourced
- *(deps)* Bump gitlab.com/unboundsoftware/eventsourced/pg
- *(deps)* Bump golang from 1.21.3 to 1.21.4
- *(deps)* Bump gitlab.com/unboundsoftware/eventsourced/pg
- *(deps)* Bump github.com/pressly/goose/v3 from 3.15.1 to 3.16.0
- *(deps)* Bump gitlab.com/unboundsoftware/eventsourced/pg
- *(deps)* Bump gitlab.com/unboundsoftware/eventsourced/pg
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.40 to 0.17.41
- *(deps)* Bump github.com/auth0/go-jwt-middleware/v2
- *(deps)* Bump golang from 1.21.4 to 1.21.5
- *(deps)* Bump gitlab.com/unboundsoftware/eventsourced/pg
- *(deps)* Bump github.com/pressly/goose/v3 from 3.16.0 to 3.17.0
- *(deps)* Bump gitlab.com/unboundsoftware/eventsourced/pg
- *(deps)* Bump github.com/sparetimecoders/goamqp from 0.2.0 to 0.2.1
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.41 to 0.17.42
- *(deps)* Bump golang from 1.21.5 to 1.21.6
- *(deps)* Bump github.com/getsentry/sentry-go from 0.25.0 to 0.26.0
- *(deps)* Bump github.com/sparetimecoders/goamqp from 0.2.1 to 0.3.0
- *(deps)* Bump github.com/vektah/gqlparser/v2 from 2.5.10 to 2.5.11
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.42 to 0.17.43
- *(deps)* Bump github.com/auth0/go-jwt-middleware/v2
- *(deps)* Bump github.com/wundergraph/graphql-go-tools
- *(deps)* Bump github.com/pressly/goose/v3 from 3.17.0 to 3.18.0
- *(deps)* Bump gitlab.com/unboundsoftware/eventsourced/eventsourced
- *(deps)* Bump golang from 1.21.6 to 1.22.0
- *(deps)* Bump github.com/getsentry/sentry-go from 0.26.0 to 0.27.0
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.43 to 0.17.44
- *(deps)* Update pre-commit hook devopshq/gitlab-ci-linter to v1.0.6
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.11.0
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.56.2
- *(deps)* Update pre-commit hook lietu/go-pre-commit to v0.1.0
- *(deps)* Update pre-commit hook pre-commit/pre-commit-hooks to v4.5.0
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.12.0
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.13.0
- Use OrbStack for local dev
- *(deps)* Update golang docker tag to v1.22.1
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.57.0
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.57.1
- Add gitleaks to pre-commit setup
- Update resources
- *(deps)* Update pre-commit hook golangci/golangci-lint to v1.57.2
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.14.0
- Run release on medium instance
- Back to small and upgrade goreleaser
- Remove deprecated replacements
@@ -326,8 +941,18 @@ All notable changes to this project will be documented in this file.
### ⚙️ Miscellaneous Tasks
- *(deps)* Bump golang from 1.20.3 to 1.20.4
- *(deps)* Bump github.com/pressly/goose/v3 from 3.10.0 to 3.11.0
- Update Go version for vulnerabilities
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.30 to 0.17.31
- *(deps)* Bump github.com/Khan/genqlient from 0.5.0 to 0.6.0
- *(deps)* Bump github.com/getsentry/sentry-go from 0.20.0 to 0.21.0
- *(deps)* Bump github.com/pressly/goose/v3 from 3.11.0 to 3.11.2
- *(deps)* Bump github.com/wundergraph/graphql-go-tools
- *(deps)* Bump github.com/sparetimecoders/goamqp from 0.1.3 to 0.1.4
- *(deps)* Bump github.com/wundergraph/graphql-go-tools
- Update pre-commit and fix golangci-lint
- *(deps)* Bump github.com/stretchr/testify from 1.8.2 to 1.8.3
- Actually validate API key privileges and refs
## [0.0.3] - 2023-04-27
@@ -346,19 +971,52 @@ All notable changes to this project will be documented in this file.
- Update schema if URLs have changed
- Add pre-commit and remove those checks from Dockerfile
- *(deps)* Bump github.com/alecthomas/kong from 0.6.1 to 0.7.1
- *(deps)* Bump github.com/stretchr/testify from 1.8.0 to 1.8.1
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.20 to 0.17.22
- *(deps)* Bump github.com/getsentry/sentry-go from 0.14.0 to 0.16.0
- *(deps)* Bump gitlab.com/unboundsoftware/eventsourced/eventsourced
- *(deps)* Bump gitlab.com/unboundsoftware/eventsourced/amqp
- *(deps)* Bump gitlab.com/unboundsoftware/eventsourced/pg
- Add context and error handling
- *(deps)* Bump github.com/rs/cors from 1.8.2 to 1.8.3
- Move to default ingress group
- Decrease trace sample rate
- Improve docker caching
- *(deps)* Bump golang from 1.19.4 to 1.19.5
- *(deps)* Bump github.com/getsentry/sentry-go from 0.16.0 to 0.17.0
- Add local module to pre-commit config
- Only ignore generated files with do not edit
- Default ingress group
- *(deps)* Bump gitlab.com/unboundsoftware/eventsourced/eventsourced
- *(deps)* Bump gitlab.com/unboundsoftware/eventsourced/eventsourced
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.22 to 0.17.24
- *(deps)* Bump gitlab.com/unboundsoftware/eventsourced/eventsourced
- *(deps)* Bump gitlab.com/unboundsoftware/eventsourced/pg
- *(deps)* Bump gitlab.com/unboundsoftware/eventsourced/pg
- *(deps)* Bump gitlab.com/unboundsoftware/eventsourced/eventsourced
- *(deps)* Bump golang from 1.19.5 to 1.20.0
- Use Docker DinD version from variable
- *(deps)* Bump github.com/getsentry/sentry-go from 0.17.0 to 0.18.0
- Switch to manual rebases for Dependabot
- *(deps)* Bump golang from 1.20.0 to 1.20.1
- Update to golang 1.20.1
- *(deps)* Bump github.com/stretchr/testify from 1.8.1 to 1.8.2
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.24 to 0.17.25
- *(deps)* Bump github.com/getsentry/sentry-go from 0.18.0 to 0.19.0
- *(deps)* Bump golang from 1.20.1 to 1.20.2
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.25 to 0.17.26
- Update Go verion for vulnerabilities scan
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.26 to 0.17.27
- Reduce sample rate
- *(deps)* Bump github.com/getsentry/sentry-go from 0.19.0 to 0.20.0
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.27 to 0.17.28
- *(deps)* Bump golang from 1.20.2 to 1.20.3
- Update to Go 1.20.3
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.28 to 0.17.29
- *(deps)* Bump github.com/rs/cors from 1.8.3 to 1.9.0
- *(deps)* Bump gitlab.com/unboundsoftware/eventsourced/pg
- *(deps)* Bump github.com/99designs/gqlgen from 0.17.29 to 0.17.30
- Fix Gitlab CI lint
## [0.0.2] - 2022-10-14
+136
View File
@@ -0,0 +1,136 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is a GraphQL schema registry service that manages federated GraphQL schemas for microservices. It allows services to publish their subgraph schemas and provides merged supergraphs with Cosmo Router configuration for federated GraphQL gateways.
## Architecture
### Event Sourcing
The system uses event sourcing via `gitlab.com/unboundsoftware/eventsourced`. Key domain aggregates are:
- **Organization** (domain/aggregates.go): Manages organizations, users, and API keys
- **SubGraph** (domain/aggregates.go): Tracks subgraph schemas with versioning
All state changes flow through events (domain/events.go) and commands (domain/commands.go). The EventStore persists events to PostgreSQL, and events are published to RabbitMQ for downstream consumers.
### GraphQL Layer
- **Schema**: graph/schema.graphqls defines the API
- **Resolvers**: graph/schema.resolvers.go implements mutations/queries
- **Generated Code**: graph/generated/ and graph/model/ (auto-generated by gqlgen)
The resolver (graph/resolver.go) coordinates between the EventStore, Publisher (RabbitMQ), Cache, and PubSub for subscriptions.
### Schema Merging
The sdlmerge/ package handles GraphQL schema federation:
- Merges multiple subgraph SDL schemas into a unified supergraph
- Uses wundergraph/graphql-go-tools for AST manipulation
- Removes duplicates, extends types, and applies federation directives
### Authentication & Authorization
- **Auth0 JWT** (middleware/auth0.go): Validates user tokens from Auth0
- **API Keys** (middleware/apikey.go): Validates service API keys
- **Auth Middleware** (middleware/auth.go): Routes auth based on context
The @auth directive controls field-level access (user vs organization API key).
### Cosmo Router Integration
The service generates Cosmo Router configuration (graph/cosmo.go) using the wgc CLI tool installed in the Docker container. This config enables federated query execution across subgraphs.
### PubSub for Real-time Updates
graph/pubsub.go implements subscription support for schemaUpdates, allowing clients to receive real-time notifications when schemas change.
## Commands
### Code Generation
```bash
# Generate GraphQL server code (gqlgen), format, and organize imports
go generate ./...
```
Always run this after modifying graph/schema.graphqls. The go:generate directives are in:
- graph/resolver.go: runs gqlgen, gofumpt, and goimports
- ctl/ctl.go: generates genqlient client code
### Testing
```bash
# Run all tests
go test ./... -v
# Run tests with race detection and coverage (as used in CI)
CGO_ENABLED=1 go test -race -coverprofile=coverage.txt -covermode=atomic ./...
# Run specific package tests
go test ./middleware -v
go test ./graph -v -run TestGenerateCosmoRouterConfig
# Run single test
go test ./cmd/service -v -run TestWebSocket
```
### Building
```bash
# Build the service binary
go build -o service ./cmd/service/service.go
# Build the CLI tool
go build -o schemactl ./cmd/schemactl/schemactl.go
# Docker build (multi-stage)
docker build -t schemas .
```
The Dockerfile runs tests with coverage before building the production binary.
### Running the Service
```bash
# Start the service (requires PostgreSQL and RabbitMQ)
go run ./cmd/service/service.go \
--postgres-url="postgres://user:pass@localhost:5432/schemas?sslmode=disable" \
--amqp-url="amqp://user:pass@localhost:5672/" \
--issuer="your-auth0-domain.auth0.com"
# The service listens on port 8080 by default
# GraphQL Playground available at http://localhost:8080/
```
### Using the schemactl CLI
```bash
# Publish a subgraph schema
schemactl publish \
--api-key="your-api-key" \
--schema-ref="production" \
--service="users" \
--url="http://users-service:8080/query" \
--sdl=schema.graphql
# List subgraphs for a ref
schemactl list \
--api-key="your-api-key" \
--schema-ref="production"
```
## Development Workflow
1. **Schema Changes**: Edit graph/schema.graphqls → run `go generate ./...`
2. **Resolver Implementation**: Implement in graph/schema.resolvers.go
3. **Testing**: Write tests, run `go test ./...`
4. **Pre-commit**: Hooks run go-mod-tidy, goimports, gofumpt, golangci-lint, and tests
## Key Dependencies
- **gqlgen**: GraphQL server generation
- **genqlient**: GraphQL client generation (for ctl package)
- **eventsourced**: Event sourcing framework
- **wundergraph/graphql-go-tools**: Schema federation and composition
- **wgc CLI**: Cosmo Router config generation (Node.js tool)
- **Auth0**: JWT authentication
- **OpenTelemetry**: Observability (traces, metrics, logs)
## Important Files
- gqlgen.yml: gqlgen configuration
- graph/tools.go: Declares build-time tool dependencies
- .pre-commit-config.yaml: Pre-commit hooks configuration
- cliff.toml: Changelog generation config
+13 -2
View File
@@ -1,4 +1,4 @@
FROM amd64/golang:1.24.4@sha256:3494bbe140127d12656113203ec91b8e3ff34e8a2b06a0a22bb0d8a41cc69e53 as modules
FROM amd64/golang:1.26.1@sha256:984bf90a420602a708a0e5b08d5660e2902daf7d26f0be3c5db5d07ced6dcce3 as modules
WORKDIR /build
ADD go.* /build
RUN go mod download
@@ -24,9 +24,20 @@ RUN GOOS=linux GOARCH=amd64 go build \
FROM scratch as export
COPY --from=build /build/coverage.txt /
FROM scratch
FROM node:24.14.1-alpine@sha256:01743339035a5c3c11a373cd7c83aeab6ed1457b55da6a69e014a95ac4e4700b
ENV TZ Europe/Stockholm
# Install wgc CLI globally for Cosmo Router composition
RUN npm install -g wgc@latest
# Cap Node.js heap for runtime wgc invocations to prevent OOM
ENV NODE_OPTIONS="--max-old-space-size=64"
# Copy timezone data and certificates
COPY --from=build /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Copy the service binary
COPY --from=build /release/service /
CMD ["/service"]
+148 -23
View File
@@ -3,18 +3,21 @@ package cache
import (
"fmt"
"log/slog"
"sync"
"time"
"github.com/sparetimecoders/goamqp"
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitlab.com/unboundsoftware/schemas/domain"
"gitlab.com/unboundsoftware/schemas/hash"
"gitea.unbound.se/unboundsoftware/schemas/domain"
"gitea.unbound.se/unboundsoftware/schemas/hash"
)
type Cache struct {
mu sync.RWMutex
organizations map[string]domain.Organization
users map[string][]string
apiKeys map[string]domain.APIKey
apiKeys map[string]domain.APIKey // keyed by organizationId-name
services map[string]map[string]map[string]struct{}
subGraphs map[string]string
lastUpdate map[string]string
@@ -22,18 +25,26 @@ type Cache struct {
}
func (c *Cache) OrganizationByAPIKey(apiKey string) *domain.Organization {
key, exists := c.apiKeys[apiKey]
if !exists {
return nil
c.mu.RLock()
defer c.mu.RUnlock()
// Find the API key by comparing hashes
for _, key := range c.apiKeys {
if hash.CompareAPIKey(key.Key, apiKey) {
org, exists := c.organizations[key.OrganizationId]
if !exists {
return nil
}
return &org
}
}
org, exists := c.organizations[key.OrganizationId]
if !exists {
return nil
}
return &org
return nil
}
func (c *Cache) OrganizationsByUser(sub string) []domain.Organization {
c.mu.RLock()
defer c.mu.RUnlock()
orgIds := c.users[sub]
orgs := make([]domain.Organization, len(orgIds))
for i, id := range orgIds {
@@ -42,15 +53,34 @@ func (c *Cache) OrganizationsByUser(sub string) []domain.Organization {
return orgs
}
func (c *Cache) ApiKeyByKey(key string) *domain.APIKey {
k, exists := c.apiKeys[hash.String(key)]
if !exists {
return nil
func (c *Cache) AllOrganizations() []domain.Organization {
c.mu.RLock()
defer c.mu.RUnlock()
orgs := make([]domain.Organization, 0, len(c.organizations))
for _, org := range c.organizations {
orgs = append(orgs, org)
}
return &k
return orgs
}
func (c *Cache) ApiKeyByKey(key string) *domain.APIKey {
c.mu.RLock()
defer c.mu.RUnlock()
// Find the API key by comparing hashes
for _, apiKey := range c.apiKeys {
if hash.CompareAPIKey(apiKey.Key, key) {
return &apiKey
}
}
return nil
}
func (c *Cache) Services(orgId, ref, lastUpdate string) ([]string, string) {
c.mu.RLock()
defer c.mu.RUnlock()
key := refKey(orgId, ref)
var services []string
if lastUpdate == "" || c.lastUpdate[key] > lastUpdate {
@@ -62,41 +92,123 @@ func (c *Cache) Services(orgId, ref, lastUpdate string) ([]string, string) {
}
func (c *Cache) SubGraphId(orgId, ref, service string) string {
c.mu.RLock()
defer c.mu.RUnlock()
return c.subGraphs[subGraphKey(orgId, ref, service)]
}
func (c *Cache) Update(msg any, _ goamqp.Headers) (any, error) {
c.mu.Lock()
defer c.mu.Unlock()
switch m := msg.(type) {
case *domain.OrganizationAdded:
o := domain.Organization{}
o := domain.Organization{
BaseAggregate: eventsourced.BaseAggregateFromString(m.ID.String()),
}
m.UpdateOrganization(&o)
c.organizations[m.ID.String()] = o
c.addUser(m.Initiator, o)
c.logger.With("org_id", m.ID.String(), "event", "OrganizationAdded").Debug("cache updated")
case *domain.UserAddedToOrganization:
org, exists := c.organizations[m.ID.String()]
if exists {
m.UpdateOrganization(&org)
c.organizations[m.ID.String()] = org
c.addUser(m.UserId, org)
c.logger.With("org_id", m.ID.String(), "user_id", m.UserId, "event", "UserAddedToOrganization").Debug("cache updated")
} else {
c.logger.With("org_id", m.ID.String(), "event", "UserAddedToOrganization").Warn("organization not found in cache")
}
case *domain.APIKeyAdded:
key := domain.APIKey{
Name: m.Name,
OrganizationId: m.OrganizationId,
Key: m.Key,
Key: m.Key, // This is now the hashed key
Refs: m.Refs,
Read: m.Read,
Publish: m.Publish,
CreatedBy: m.Initiator,
CreatedAt: m.When(),
}
c.apiKeys[m.Key] = key
// Use composite key: organizationId-name
c.apiKeys[apiKeyId(m.OrganizationId, m.Name)] = key
org := c.organizations[m.OrganizationId]
org.APIKeys = append(org.APIKeys, key)
c.organizations[m.OrganizationId] = org
c.logger.With("org_id", m.OrganizationId, "key_name", m.Name, "event", "APIKeyAdded").Debug("cache updated")
case *domain.APIKeyRemoved:
orgId := m.ID.String()
org, exists := c.organizations[orgId]
if exists {
// Remove from organization's API keys list
for i, key := range org.APIKeys {
if key.Name == m.KeyName {
org.APIKeys = append(org.APIKeys[:i], org.APIKeys[i+1:]...)
break
}
}
c.organizations[orgId] = org
// Remove from apiKeys map
delete(c.apiKeys, apiKeyId(orgId, m.KeyName))
c.logger.With("org_id", orgId, "key_name", m.KeyName, "event", "APIKeyRemoved").Debug("cache updated")
} else {
c.logger.With("org_id", orgId, "event", "APIKeyRemoved").Warn("organization not found in cache")
}
case *domain.OrganizationRemoved:
orgId := m.ID.String()
org, exists := c.organizations[orgId]
if exists {
// Remove all API keys for this organization
for _, key := range org.APIKeys {
delete(c.apiKeys, apiKeyId(orgId, key.Name))
}
// Remove organization from all users
for userId, userOrgs := range c.users {
for i, userOrgId := range userOrgs {
if userOrgId == orgId {
c.users[userId] = append(userOrgs[:i], userOrgs[i+1:]...)
break
}
}
// If user has no more organizations, remove from map
if len(c.users[userId]) == 0 {
delete(c.users, userId)
}
}
// Remove services for this organization
if refs, exists := c.services[orgId]; exists {
for ref := range refs {
// Remove all subgraphs for this org/ref combination
for service := range refs[ref] {
delete(c.subGraphs, subGraphKey(orgId, ref, service))
}
// Remove lastUpdate for this org/ref
delete(c.lastUpdate, refKey(orgId, ref))
}
delete(c.services, orgId)
}
// Remove organization
delete(c.organizations, orgId)
c.logger.With("org_id", orgId, "event", "OrganizationRemoved").Debug("cache updated")
} else {
c.logger.With("org_id", orgId, "event", "OrganizationRemoved").Warn("organization not found in cache")
}
case *domain.SubGraphUpdated:
c.updateSubGraph(m.OrganizationId, m.Ref, m.ID.String(), m.Service, m.Time)
c.logger.With("org_id", m.OrganizationId, "ref", m.Ref, "service", m.Service, "event", "SubGraphUpdated").Debug("cache updated")
case *domain.Organization:
c.organizations[m.ID.String()] = *m
c.addUser(m.CreatedBy, *m)
for _, k := range m.APIKeys {
c.apiKeys[k.Key] = k
// Use composite key: organizationId-name
c.apiKeys[apiKeyId(k.OrganizationId, k.Name)] = k
}
c.logger.With("org_id", m.ID.String(), "event", "Organization aggregate loaded").Debug("cache updated")
case *domain.SubGraph:
c.updateSubGraph(m.OrganizationId, m.Ref, m.ID.String(), m.Service, m.ChangedAt)
c.logger.With("org_id", m.OrganizationId, "ref", m.Ref, "service", m.Service, "event", "SubGraph aggregate loaded").Debug("cache updated")
default:
c.logger.With("msg", msg).Warn("unexpected message received")
}
@@ -117,11 +229,20 @@ func (c *Cache) updateSubGraph(orgId string, ref string, subGraphId string, serv
func (c *Cache) addUser(sub string, organization domain.Organization) {
user, exists := c.users[sub]
orgId := organization.ID.String()
if !exists {
c.users[sub] = []string{organization.ID.String()}
} else {
c.users[sub] = append(user, organization.ID.String())
c.users[sub] = []string{orgId}
return
}
// Check if organization already exists for this user
for _, id := range user {
if id == orgId {
return // Already exists, no need to add
}
}
c.users[sub] = append(user, orgId)
}
func New(logger *slog.Logger) *Cache {
@@ -143,3 +264,7 @@ func refKey(orgId string, ref string) string {
func subGraphKey(orgId string, ref string, service string) string {
return fmt.Sprintf("%s<->%s<->%s", orgId, ref, service)
}
func apiKeyId(orgId string, name string) string {
return fmt.Sprintf("%s<->%s", orgId, name)
}
+645
View File
@@ -0,0 +1,645 @@
package cache
import (
"log/slog"
"os"
"sync"
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitea.unbound.se/unboundsoftware/schemas/domain"
"gitea.unbound.se/unboundsoftware/schemas/hash"
)
func TestCache_OrganizationByAPIKey(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
c := New(logger)
orgID := uuid.New().String()
apiKey := "test-api-key-123" // gitleaks:allow
hashedKey, err := hash.APIKey(apiKey)
require.NoError(t, err)
// Add organization to cache
org := domain.Organization{
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
Name: "Test Org",
}
c.organizations[orgID] = org
// Add API key to cache
c.apiKeys[apiKeyId(orgID, "test-key")] = domain.APIKey{
Name: "test-key",
OrganizationId: orgID,
Key: hashedKey,
Refs: []string{"main"},
Read: true,
Publish: true,
}
// Test finding organization by plaintext API key
foundOrg := c.OrganizationByAPIKey(apiKey)
require.NotNil(t, foundOrg)
assert.Equal(t, org.Name, foundOrg.Name)
assert.Equal(t, orgID, foundOrg.ID.String())
// Test with wrong API key
notFoundOrg := c.OrganizationByAPIKey("wrong-key")
assert.Nil(t, notFoundOrg)
}
func TestCache_OrganizationByAPIKey_Legacy(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
c := New(logger)
orgID := uuid.New().String()
apiKey := "legacy-api-key-456" // gitleaks:allow
legacyHash := hash.String(apiKey)
// Add organization to cache
org := domain.Organization{
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
Name: "Legacy Org",
}
c.organizations[orgID] = org
// Add API key with legacy SHA256 hash
c.apiKeys[apiKeyId(orgID, "legacy-key")] = domain.APIKey{
Name: "legacy-key",
OrganizationId: orgID,
Key: legacyHash,
Refs: []string{"main"},
Read: true,
Publish: false,
}
// Test finding organization with legacy hash
foundOrg := c.OrganizationByAPIKey(apiKey)
require.NotNil(t, foundOrg)
assert.Equal(t, org.Name, foundOrg.Name)
}
func TestCache_OrganizationsByUser(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
c := New(logger)
userSub := "user-123"
org1ID := uuid.New().String()
org2ID := uuid.New().String()
org1 := domain.Organization{
BaseAggregate: eventsourced.BaseAggregateFromString(org1ID),
Name: "Org 1",
}
org2 := domain.Organization{
BaseAggregate: eventsourced.BaseAggregateFromString(org2ID),
Name: "Org 2",
}
c.organizations[org1ID] = org1
c.organizations[org2ID] = org2
c.users[userSub] = []string{org1ID, org2ID}
orgs := c.OrganizationsByUser(userSub)
assert.Len(t, orgs, 2)
assert.Contains(t, []string{orgs[0].Name, orgs[1].Name}, "Org 1")
assert.Contains(t, []string{orgs[0].Name, orgs[1].Name}, "Org 2")
}
func TestCache_ApiKeyByKey(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
c := New(logger)
orgID := uuid.New().String()
apiKey := "test-api-key-789" // gitleaks:allow
hashedKey, err := hash.APIKey(apiKey)
require.NoError(t, err)
expectedKey := domain.APIKey{
Name: "test-key",
OrganizationId: orgID,
Key: hashedKey,
Refs: []string{"main", "dev"},
Read: true,
Publish: true,
}
c.apiKeys[apiKeyId(orgID, "test-key")] = expectedKey
foundKey := c.ApiKeyByKey(apiKey)
require.NotNil(t, foundKey)
assert.Equal(t, expectedKey.Name, foundKey.Name)
assert.Equal(t, expectedKey.OrganizationId, foundKey.OrganizationId)
assert.Equal(t, expectedKey.Refs, foundKey.Refs)
// Test with wrong key
notFoundKey := c.ApiKeyByKey("wrong-key")
assert.Nil(t, notFoundKey)
}
func TestCache_Services(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
c := New(logger)
orgID := uuid.New().String()
ref := "main"
service1 := "service-1"
service2 := "service-2"
lastUpdate := "2024-01-01T12:00:00Z"
c.services[orgID] = map[string]map[string]struct{}{
ref: {
service1: {},
service2: {},
},
}
c.lastUpdate[refKey(orgID, ref)] = lastUpdate
// Test getting services with empty lastUpdate
services, returnedLastUpdate := c.Services(orgID, ref, "")
assert.Len(t, services, 2)
assert.Contains(t, services, service1)
assert.Contains(t, services, service2)
assert.Equal(t, lastUpdate, returnedLastUpdate)
// Test with older lastUpdate (should return services)
services, returnedLastUpdate = c.Services(orgID, ref, "2023-12-31T12:00:00Z")
assert.Len(t, services, 2)
assert.Equal(t, lastUpdate, returnedLastUpdate)
// Test with newer lastUpdate (should return empty)
services, returnedLastUpdate = c.Services(orgID, ref, "2024-01-02T12:00:00Z")
assert.Len(t, services, 0)
assert.Equal(t, lastUpdate, returnedLastUpdate)
}
func TestCache_SubGraphId(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
c := New(logger)
orgID := uuid.New().String()
ref := "main"
service := "test-service"
subGraphID := uuid.New().String()
c.subGraphs[subGraphKey(orgID, ref, service)] = subGraphID
foundID := c.SubGraphId(orgID, ref, service)
assert.Equal(t, subGraphID, foundID)
// Test with non-existent key
notFoundID := c.SubGraphId("wrong-org", ref, service)
assert.Empty(t, notFoundID)
}
func TestCache_Update_OrganizationAdded(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
c := New(logger)
orgID := uuid.New().String()
event := &domain.OrganizationAdded{
Name: "New Org",
Initiator: "user-123",
}
event.ID = *eventsourced.IdFromString(orgID)
_, err := c.Update(event, nil)
require.NoError(t, err)
// Verify organization was added
org, exists := c.organizations[orgID]
assert.True(t, exists)
assert.Equal(t, "New Org", org.Name)
// Verify user was added
assert.Contains(t, c.users["user-123"], orgID)
}
func TestCache_Update_APIKeyAdded(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
c := New(logger)
orgID := uuid.New().String()
keyName := "test-key"
hashedKey := "hashed-key-value"
// Add organization first
org := domain.Organization{
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
Name: "Test Org",
APIKeys: []domain.APIKey{},
}
c.organizations[orgID] = org
event := &domain.APIKeyAdded{
OrganizationId: orgID,
Name: keyName,
Key: hashedKey,
Refs: []string{"main"},
Read: true,
Publish: false,
Initiator: "user-123",
}
event.ID = *eventsourced.IdFromString(uuid.New().String())
_, err := c.Update(event, nil)
require.NoError(t, err)
// Verify API key was added to cache
key, exists := c.apiKeys[apiKeyId(orgID, keyName)]
assert.True(t, exists)
assert.Equal(t, keyName, key.Name)
assert.Equal(t, hashedKey, key.Key)
assert.Equal(t, []string{"main"}, key.Refs)
// Verify API key was added to organization
updatedOrg := c.organizations[orgID]
assert.Len(t, updatedOrg.APIKeys, 1)
assert.Equal(t, keyName, updatedOrg.APIKeys[0].Name)
}
func TestCache_Update_SubGraphUpdated(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
c := New(logger)
orgID := uuid.New().String()
ref := "main"
service := "test-service"
subGraphID := uuid.New().String()
event := &domain.SubGraphUpdated{
OrganizationId: orgID,
Ref: ref,
Service: service,
Initiator: "user-123",
}
event.ID = *eventsourced.IdFromString(subGraphID)
event.SetWhen(time.Now())
_, err := c.Update(event, nil)
require.NoError(t, err)
// Verify subgraph was added to services
assert.Contains(t, c.services[orgID][ref], subGraphID)
// Verify subgraph ID was stored
assert.Equal(t, subGraphID, c.subGraphs[subGraphKey(orgID, ref, service)])
// Verify lastUpdate was set
assert.NotEmpty(t, c.lastUpdate[refKey(orgID, ref)])
}
func TestCache_AddUser_NoDuplicates(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
c := New(logger)
userSub := "user-123"
orgID := uuid.New().String()
org := domain.Organization{
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
Name: "Test Org",
}
// Add user first time
c.addUser(userSub, org)
assert.Len(t, c.users[userSub], 1)
assert.Equal(t, orgID, c.users[userSub][0])
// Add same user/org again - should not create duplicate
c.addUser(userSub, org)
assert.Len(t, c.users[userSub], 1, "Should not add duplicate organization")
assert.Equal(t, orgID, c.users[userSub][0])
}
func TestCache_ConcurrentReads(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
c := New(logger)
// Setup test data - use legacy hash to avoid slow bcrypt
orgID := uuid.New().String()
userSub := "test-user"
org := domain.Organization{
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
Name: "Concurrent Test Org",
}
c.organizations[orgID] = org
c.users[userSub] = []string{orgID}
// Run concurrent reads using fast OrganizationsByUser
var wg sync.WaitGroup
numGoroutines := 20
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
orgs := c.OrganizationsByUser(userSub)
assert.NotEmpty(t, orgs)
assert.Equal(t, "Concurrent Test Org", orgs[0].Name)
}()
}
wg.Wait()
}
func TestCache_ConcurrentWrites(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
c := New(logger)
var wg sync.WaitGroup
numGoroutines := 10 // Reduced for race detector
// Concurrent organization additions
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(index int) {
defer wg.Done()
orgID := uuid.New().String()
event := &domain.OrganizationAdded{
Name: "Org " + string(rune(index)),
Initiator: "user-" + string(rune(index)),
}
event.ID = *eventsourced.IdFromString(orgID)
_, err := c.Update(event, nil)
assert.NoError(t, err)
}(i)
}
wg.Wait()
// Verify all organizations were added
assert.Equal(t, numGoroutines, len(c.organizations))
}
func TestCache_ConcurrentReadsAndWrites(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
c := New(logger)
// Setup initial data - use legacy hash to avoid slow bcrypt in concurrent test
orgID := uuid.New().String()
legacyKey := "test-rw-key" // gitleaks:allow
legacyHash := hash.String(legacyKey)
org := domain.Organization{
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
Name: "RW Test Org",
}
c.organizations[orgID] = org
c.apiKeys[apiKeyId(orgID, "test-key")] = domain.APIKey{
Name: "test-key",
OrganizationId: orgID,
Key: legacyHash,
}
c.users["user-initial"] = []string{orgID}
var wg sync.WaitGroup
numReaders := 5
numWriters := 3
// Concurrent readers - use OrganizationsByUser which is fast
for i := 0; i < numReaders; i++ {
wg.Add(1)
go func() {
defer wg.Done()
orgs := c.OrganizationsByUser("user-initial")
assert.NotEmpty(t, orgs)
}()
}
// Concurrent writers
for i := 0; i < numWriters; i++ {
wg.Add(1)
go func(index int) {
defer wg.Done()
newOrgID := uuid.New().String()
event := &domain.OrganizationAdded{
Name: "New Org " + string(rune(index)),
Initiator: "user-new-" + string(rune(index)),
}
event.ID = *eventsourced.IdFromString(newOrgID)
_, err := c.Update(event, nil)
assert.NoError(t, err)
}(i)
}
wg.Wait()
// Verify cache is in consistent state
assert.GreaterOrEqual(t, len(c.organizations), numWriters)
}
func TestCache_Update_APIKeyRemoved(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
c := New(logger)
orgID := uuid.New().String()
keyName := "test-key"
hashedKey := "hashed-key-value"
// Add organization with API key
org := domain.Organization{
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
Name: "Test Org",
APIKeys: []domain.APIKey{
{
Name: keyName,
OrganizationId: orgID,
Key: hashedKey,
Refs: []string{"main"},
Read: true,
Publish: false,
},
},
}
c.organizations[orgID] = org
c.apiKeys[apiKeyId(orgID, keyName)] = org.APIKeys[0]
// Verify key exists before removal
_, exists := c.apiKeys[apiKeyId(orgID, keyName)]
assert.True(t, exists)
// Remove the API key
event := &domain.APIKeyRemoved{
KeyName: keyName,
Initiator: "user-123",
}
event.ID = *eventsourced.IdFromString(orgID)
_, err := c.Update(event, nil)
require.NoError(t, err)
// Verify API key was removed from cache
_, exists = c.apiKeys[apiKeyId(orgID, keyName)]
assert.False(t, exists, "API key should be removed from cache")
// Verify API key was removed from organization
updatedOrg := c.organizations[orgID]
assert.Len(t, updatedOrg.APIKeys, 0, "API key should be removed from organization")
}
func TestCache_Update_APIKeyRemoved_MultipleKeys(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
c := New(logger)
orgID := uuid.New().String()
// Add organization with multiple API keys
org := domain.Organization{
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
Name: "Test Org",
APIKeys: []domain.APIKey{
{
Name: "key1",
OrganizationId: orgID,
Key: "hash1",
},
{
Name: "key2",
OrganizationId: orgID,
Key: "hash2",
},
{
Name: "key3",
OrganizationId: orgID,
Key: "hash3",
},
},
}
c.organizations[orgID] = org
c.apiKeys[apiKeyId(orgID, "key1")] = org.APIKeys[0]
c.apiKeys[apiKeyId(orgID, "key2")] = org.APIKeys[1]
c.apiKeys[apiKeyId(orgID, "key3")] = org.APIKeys[2]
// Remove the middle key
event := &domain.APIKeyRemoved{
KeyName: "key2",
Initiator: "user-123",
}
event.ID = *eventsourced.IdFromString(orgID)
_, err := c.Update(event, nil)
require.NoError(t, err)
// Verify only key2 was removed
_, exists := c.apiKeys[apiKeyId(orgID, "key1")]
assert.True(t, exists, "key1 should still exist")
_, exists = c.apiKeys[apiKeyId(orgID, "key2")]
assert.False(t, exists, "key2 should be removed")
_, exists = c.apiKeys[apiKeyId(orgID, "key3")]
assert.True(t, exists, "key3 should still exist")
// Verify organization has 2 keys remaining
updatedOrg := c.organizations[orgID]
assert.Len(t, updatedOrg.APIKeys, 2)
assert.Equal(t, "key1", updatedOrg.APIKeys[0].Name)
assert.Equal(t, "key3", updatedOrg.APIKeys[1].Name)
}
func TestCache_Update_OrganizationRemoved(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
c := New(logger)
orgID := uuid.New().String()
userSub := "user-123"
// Add organization with API keys, users, and subgraphs
org := domain.Organization{
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
Name: "Test Org",
APIKeys: []domain.APIKey{
{
Name: "key1",
OrganizationId: orgID,
Key: "hash1",
},
},
}
c.organizations[orgID] = org
c.apiKeys[apiKeyId(orgID, "key1")] = org.APIKeys[0]
c.users[userSub] = []string{orgID}
c.services[orgID] = map[string]map[string]struct{}{
"main": {
"service1": {},
},
}
c.subGraphs[subGraphKey(orgID, "main", "service1")] = "subgraph-id"
c.lastUpdate[refKey(orgID, "main")] = "2024-01-01T12:00:00Z"
// Remove the organization
event := &domain.OrganizationRemoved{
Initiator: userSub,
}
event.ID = *eventsourced.IdFromString(orgID)
_, err := c.Update(event, nil)
require.NoError(t, err)
// Verify organization was removed
_, exists := c.organizations[orgID]
assert.False(t, exists, "Organization should be removed from cache")
// Verify API keys were removed
_, exists = c.apiKeys[apiKeyId(orgID, "key1")]
assert.False(t, exists, "API keys should be removed from cache")
// Verify user association was removed
userOrgs := c.users[userSub]
assert.NotContains(t, userOrgs, orgID, "User should not be associated with removed organization")
// Verify services were removed
_, exists = c.services[orgID]
assert.False(t, exists, "Services should be removed from cache")
// Verify subgraphs were removed
_, exists = c.subGraphs[subGraphKey(orgID, "main", "service1")]
assert.False(t, exists, "Subgraphs should be removed from cache")
// Verify lastUpdate was removed
_, exists = c.lastUpdate[refKey(orgID, "main")]
assert.False(t, exists, "LastUpdate should be removed from cache")
}
func TestCache_Update_OrganizationRemoved_MultipleUsers(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
c := New(logger)
orgID := uuid.New().String()
user1 := "user-1"
user2 := "user-2"
otherOrgID := uuid.New().String()
// Add organization
org := domain.Organization{
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
Name: "Test Org",
}
c.organizations[orgID] = org
// Add users with multiple org associations
c.users[user1] = []string{orgID, otherOrgID}
c.users[user2] = []string{orgID}
// Remove the organization
event := &domain.OrganizationRemoved{
Initiator: user1,
}
event.ID = *eventsourced.IdFromString(orgID)
_, err := c.Update(event, nil)
require.NoError(t, err)
// Verify user1 still has otherOrgID but not removed orgID
assert.Len(t, c.users[user1], 1)
assert.Equal(t, otherOrgID, c.users[user1][0])
// Verify user2 has no organizations
assert.Len(t, c.users[user2], 0)
}
+80
View File
@@ -0,0 +1,80 @@
# git-cliff ~ default configuration file
# https://git-cliff.org/docs/configuration
#
# Lines starting with "#" are comments.
# Configuration options are organized into tables and keys.
# See documentation for more information on available options.
[changelog]
# template for the changelog header
header = """
# Changelog\n
All notable changes to this project will be documented in this file.\n
"""
# template for the changelog body
# https://keats.github.io/tera/docs/#introduction
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
{% if commit.breaking %}[**breaking**] {% endif %}\
{{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}\n
"""
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing s
trim = true
# postprocessors
postprocessors = [
# { pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL
]
# render body even when there are no releases to process
# render_always = true
# output file path
# output = "test.md"
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# process each line of a commit as an individual commit
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = [
# Replace issue numbers
#{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"},
# Check spelling of the commit with https://github.com/crate-ci/typos
# If the spelling is incorrect, it will be automatically fixed.
#{ pattern = '.*', replace_command = 'typos --write-changes -' },
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "<!-- 0 -->🚀 Features" },
{ message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
{ message = "^doc", group = "<!-- 3 -->📚 Documentation" },
{ message = "^perf", group = "<!-- 4 -->⚡ Performance" },
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
{ message = "^chore\\(release\\): prepare for", skip = true },
{ message = "^chore|^ci", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
{ body = ".*security", group = "<!-- 8 -->🛡️ Security" },
{ message = "^revert", group = "<!-- 9 -->◀️ Revert" },
]
# filter out the commits that are not matched by commit parsers
filter_commits = false
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "oldest"
+1 -1
View File
@@ -10,7 +10,7 @@ import (
"github.com/alecthomas/kong"
"github.com/apex/log"
"gitlab.com/unboundsoftware/schemas/ctl"
"gitea.unbound.se/unboundsoftware/schemas/ctl"
)
type Context struct {
+48 -17
View File
@@ -26,14 +26,15 @@ import (
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitlab.com/unboundsoftware/eventsourced/pg"
"gitlab.com/unboundsoftware/schemas/cache"
"gitlab.com/unboundsoftware/schemas/domain"
"gitlab.com/unboundsoftware/schemas/graph"
"gitlab.com/unboundsoftware/schemas/graph/generated"
"gitlab.com/unboundsoftware/schemas/logging"
"gitlab.com/unboundsoftware/schemas/middleware"
"gitlab.com/unboundsoftware/schemas/monitoring"
"gitlab.com/unboundsoftware/schemas/store"
"gitea.unbound.se/unboundsoftware/schemas/cache"
"gitea.unbound.se/unboundsoftware/schemas/domain"
"gitea.unbound.se/unboundsoftware/schemas/graph"
"gitea.unbound.se/unboundsoftware/schemas/graph/generated"
"gitea.unbound.se/unboundsoftware/schemas/health"
"gitea.unbound.se/unboundsoftware/schemas/logging"
"gitea.unbound.se/unboundsoftware/schemas/middleware"
"gitea.unbound.se/unboundsoftware/schemas/monitoring"
"gitea.unbound.se/unboundsoftware/schemas/store"
)
type CLI struct {
@@ -91,7 +92,10 @@ func start(closeEvents chan error, logger *slog.Logger, connectToAmqpFunc func(u
pg.WithEventTypes(
&domain.SubGraphUpdated{},
&domain.OrganizationAdded{},
&domain.UserAddedToOrganization{},
&domain.APIKeyAdded{},
&domain.APIKeyRemoved{},
&domain.OrganizationRemoved{},
),
)
if err != nil {
@@ -126,10 +130,16 @@ func start(closeEvents chan error, logger *slog.Logger, connectToAmqpFunc func(u
goamqp.EventStreamPublisher(publisher),
goamqp.TransientEventStreamConsumer("SubGraph.Updated", serviceCache.Update, domain.SubGraphUpdated{}),
goamqp.TransientEventStreamConsumer("Organization.Added", serviceCache.Update, domain.OrganizationAdded{}),
goamqp.TransientEventStreamConsumer("Organization.UserAdded", serviceCache.Update, domain.UserAddedToOrganization{}),
goamqp.TransientEventStreamConsumer("Organization.APIKeyAdded", serviceCache.Update, domain.APIKeyAdded{}),
goamqp.TransientEventStreamConsumer("Organization.APIKeyRemoved", serviceCache.Update, domain.APIKeyRemoved{}),
goamqp.TransientEventStreamConsumer("Organization.Removed", serviceCache.Update, domain.OrganizationRemoved{}),
goamqp.WithTypeMapping("SubGraph.Updated", domain.SubGraphUpdated{}),
goamqp.WithTypeMapping("Organization.Added", domain.OrganizationAdded{}),
goamqp.WithTypeMapping("Organization.UserAdded", domain.UserAddedToOrganization{}),
goamqp.WithTypeMapping("Organization.APIKeyAdded", domain.APIKeyAdded{}),
goamqp.WithTypeMapping("Organization.APIKeyRemoved", domain.APIKeyRemoved{}),
goamqp.WithTypeMapping("Organization.Removed", domain.OrganizationRemoved{}),
}
if err := conn.Start(rootCtx, setups...); err != nil {
return fmt.Errorf("failed to setup AMQP: %v", err)
@@ -191,10 +201,13 @@ func start(closeEvents chan error, logger *slog.Logger, connectToAmqpFunc func(u
defer rootCancel()
resolver := &graph.Resolver{
EventStore: eventStore,
Publisher: eventPublisher,
Logger: logger,
Cache: serviceCache,
EventStore: eventStore,
Publisher: eventPublisher,
Logger: logger,
Cache: serviceCache,
PubSub: graph.NewPubSub(),
CosmoGenerator: graph.NewCosmoGenerator(&graph.DefaultCommandExecutor{}, 60*time.Second),
Debouncer: graph.NewDebouncer(500 * time.Millisecond),
}
config := generated.Config{
@@ -209,6 +222,24 @@ func start(closeEvents chan error, logger *slog.Logger, connectToAmqpFunc func(u
srv.AddTransport(transport.Websocket{
KeepAlivePingInterval: 10 * time.Second,
InitFunc: func(ctx context.Context, initPayload transport.InitPayload) (context.Context, *transport.InitPayload, error) {
// Extract API key from WebSocket connection_init payload
if apiKey, ok := initPayload["X-Api-Key"].(string); ok && apiKey != "" {
logger.Info("WebSocket connection with API key", "has_key", true)
ctx = context.WithValue(ctx, middleware.ApiKey, apiKey)
// Look up organization by API key (cache handles hash comparison)
if organization := serviceCache.OrganizationByAPIKey(apiKey); organization != nil {
logger.Info("WebSocket: Organization found for API key", "org_id", organization.ID.String())
ctx = context.WithValue(ctx, middleware.OrganizationKey, *organization)
} else {
logger.Warn("WebSocket: No organization found for API key")
}
} else {
logger.Info("WebSocket connection without API key")
}
return ctx, &initPayload, nil
},
})
srv.AddTransport(transport.Options{})
srv.AddTransport(transport.GET{})
@@ -222,8 +253,12 @@ func start(closeEvents chan error, logger *slog.Logger, connectToAmqpFunc func(u
Cache: lru.New[string](100),
})
healthChecker := health.New(db.DB, logger)
mux.Handle("/", monitoring.Handler(playground.Handler("GraphQL playground", "/query")))
mux.Handle("/health", http.HandlerFunc(healthFunc))
mux.Handle("/health", http.HandlerFunc(healthChecker.LivenessHandler))
mux.Handle("/health/live", http.HandlerFunc(healthChecker.LivenessHandler))
mux.Handle("/health/ready", http.HandlerFunc(healthChecker.ReadinessHandler))
mux.Handle("/query", cors.AllowAll().Handler(
monitoring.Handler(
mw.Middleware().CheckJWT(
@@ -282,10 +317,6 @@ func loadSubGraphs(ctx context.Context, eventStore eventsourced.EventStore, serv
return nil
}
func healthFunc(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("OK"))
}
func ConnectAMQP(url string) (Connection, error) {
return goamqp.NewFromURL(serviceName, url)
}
+362
View File
@@ -0,0 +1,362 @@
package main
import (
"context"
"testing"
"github.com/99designs/gqlgen/graphql/handler/transport"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitea.unbound.se/unboundsoftware/schemas/domain"
"gitea.unbound.se/unboundsoftware/schemas/hash"
"gitea.unbound.se/unboundsoftware/schemas/middleware"
)
// MockCache is a mock implementation for testing
type MockCache struct {
organizations map[string]*domain.Organization // keyed by orgId-name composite
apiKeys map[string]string // maps orgId-name to hashed key
}
func (m *MockCache) OrganizationByAPIKey(plainKey string) *domain.Organization {
// Find organization by comparing plaintext key with stored hash
for compositeKey, hashedKey := range m.apiKeys {
if hash.CompareAPIKey(hashedKey, plainKey) {
return m.organizations[compositeKey]
}
}
return nil
}
func TestWebSocketInitFunc_WithValidAPIKey(t *testing.T) {
// Setup
orgID := uuid.New()
org := &domain.Organization{
BaseAggregate: eventsourced.BaseAggregate{
ID: eventsourced.IdFromString(orgID.String()),
},
Name: "Test Organization",
}
apiKey := "test-api-key-123"
hashedKey, err := hash.APIKey(apiKey)
require.NoError(t, err)
compositeKey := orgID.String() + "-test-key"
mockCache := &MockCache{
organizations: map[string]*domain.Organization{
compositeKey: org,
},
apiKeys: map[string]string{
compositeKey: hashedKey,
},
}
// Create InitFunc (simulating the WebSocket InitFunc logic)
initFunc := func(ctx context.Context, initPayload transport.InitPayload) (context.Context, *transport.InitPayload, error) {
// Extract API key from WebSocket connection_init payload
if apiKey, ok := initPayload["X-Api-Key"].(string); ok && apiKey != "" {
ctx = context.WithValue(ctx, middleware.ApiKey, apiKey)
// Look up organization by API key (cache handles hash comparison)
if organization := mockCache.OrganizationByAPIKey(apiKey); organization != nil {
ctx = context.WithValue(ctx, middleware.OrganizationKey, *organization)
}
}
return ctx, &initPayload, nil
}
// Test
ctx := context.Background()
initPayload := transport.InitPayload{
"X-Api-Key": apiKey,
}
resultCtx, resultPayload, err := initFunc(ctx, initPayload)
// Assert
require.NoError(t, err)
require.NotNil(t, resultPayload)
// Check API key is in context
if value := resultCtx.Value(middleware.ApiKey); value != nil {
assert.Equal(t, apiKey, value.(string))
} else {
t.Fatal("API key not found in context")
}
// Check organization is in context
if value := resultCtx.Value(middleware.OrganizationKey); value != nil {
capturedOrg, ok := value.(domain.Organization)
require.True(t, ok, "Organization should be of correct type")
assert.Equal(t, org.Name, capturedOrg.Name)
assert.Equal(t, org.ID.String(), capturedOrg.ID.String())
} else {
t.Fatal("Organization not found in context")
}
}
func TestWebSocketInitFunc_WithInvalidAPIKey(t *testing.T) {
// Setup
mockCache := &MockCache{
organizations: map[string]*domain.Organization{},
apiKeys: map[string]string{},
}
apiKey := "invalid-api-key"
// Create InitFunc
initFunc := func(ctx context.Context, initPayload transport.InitPayload) (context.Context, *transport.InitPayload, error) {
// Extract API key from WebSocket connection_init payload
if apiKey, ok := initPayload["X-Api-Key"].(string); ok && apiKey != "" {
ctx = context.WithValue(ctx, middleware.ApiKey, apiKey)
// Look up organization by API key (cache handles hash comparison)
if organization := mockCache.OrganizationByAPIKey(apiKey); organization != nil {
ctx = context.WithValue(ctx, middleware.OrganizationKey, *organization)
}
}
return ctx, &initPayload, nil
}
// Test
ctx := context.Background()
initPayload := transport.InitPayload{
"X-Api-Key": apiKey,
}
resultCtx, resultPayload, err := initFunc(ctx, initPayload)
// Assert
require.NoError(t, err)
require.NotNil(t, resultPayload)
// Check API key is in context
if value := resultCtx.Value(middleware.ApiKey); value != nil {
assert.Equal(t, apiKey, value.(string))
} else {
t.Fatal("API key not found in context")
}
// Check organization is NOT in context (since API key is invalid)
value := resultCtx.Value(middleware.OrganizationKey)
assert.Nil(t, value, "Organization should not be set for invalid API key")
}
func TestWebSocketInitFunc_WithoutAPIKey(t *testing.T) {
// Setup
mockCache := &MockCache{
organizations: map[string]*domain.Organization{},
apiKeys: map[string]string{},
}
// Create InitFunc
initFunc := func(ctx context.Context, initPayload transport.InitPayload) (context.Context, *transport.InitPayload, error) {
// Extract API key from WebSocket connection_init payload
if apiKey, ok := initPayload["X-Api-Key"].(string); ok && apiKey != "" {
ctx = context.WithValue(ctx, middleware.ApiKey, apiKey)
// Look up organization by API key (cache handles hash comparison)
if organization := mockCache.OrganizationByAPIKey(apiKey); organization != nil {
ctx = context.WithValue(ctx, middleware.OrganizationKey, *organization)
}
}
return ctx, &initPayload, nil
}
// Test
ctx := context.Background()
initPayload := transport.InitPayload{}
resultCtx, resultPayload, err := initFunc(ctx, initPayload)
// Assert
require.NoError(t, err)
require.NotNil(t, resultPayload)
// Check API key is NOT in context
value := resultCtx.Value(middleware.ApiKey)
assert.Nil(t, value, "API key should not be set when not provided")
// Check organization is NOT in context
value = resultCtx.Value(middleware.OrganizationKey)
assert.Nil(t, value, "Organization should not be set when API key is not provided")
}
func TestWebSocketInitFunc_WithEmptyAPIKey(t *testing.T) {
// Setup
mockCache := &MockCache{
organizations: map[string]*domain.Organization{},
apiKeys: map[string]string{},
}
// Create InitFunc
initFunc := func(ctx context.Context, initPayload transport.InitPayload) (context.Context, *transport.InitPayload, error) {
// Extract API key from WebSocket connection_init payload
if apiKey, ok := initPayload["X-Api-Key"].(string); ok && apiKey != "" {
ctx = context.WithValue(ctx, middleware.ApiKey, apiKey)
// Look up organization by API key (cache handles hash comparison)
if organization := mockCache.OrganizationByAPIKey(apiKey); organization != nil {
ctx = context.WithValue(ctx, middleware.OrganizationKey, *organization)
}
}
return ctx, &initPayload, nil
}
// Test
ctx := context.Background()
initPayload := transport.InitPayload{
"X-Api-Key": "", // Empty string
}
resultCtx, resultPayload, err := initFunc(ctx, initPayload)
// Assert
require.NoError(t, err)
require.NotNil(t, resultPayload)
// Check API key is NOT in context (because empty string fails the condition)
value := resultCtx.Value(middleware.ApiKey)
assert.Nil(t, value, "API key should not be set when empty")
// Check organization is NOT in context
value = resultCtx.Value(middleware.OrganizationKey)
assert.Nil(t, value, "Organization should not be set when API key is empty")
}
func TestWebSocketInitFunc_WithWrongTypeAPIKey(t *testing.T) {
// Setup
mockCache := &MockCache{
organizations: map[string]*domain.Organization{},
apiKeys: map[string]string{},
}
// Create InitFunc
initFunc := func(ctx context.Context, initPayload transport.InitPayload) (context.Context, *transport.InitPayload, error) {
// Extract API key from WebSocket connection_init payload
if apiKey, ok := initPayload["X-Api-Key"].(string); ok && apiKey != "" {
ctx = context.WithValue(ctx, middleware.ApiKey, apiKey)
// Look up organization by API key (cache handles hash comparison)
if organization := mockCache.OrganizationByAPIKey(apiKey); organization != nil {
ctx = context.WithValue(ctx, middleware.OrganizationKey, *organization)
}
}
return ctx, &initPayload, nil
}
// Test
ctx := context.Background()
initPayload := transport.InitPayload{
"X-Api-Key": 12345, // Wrong type (int instead of string)
}
resultCtx, resultPayload, err := initFunc(ctx, initPayload)
// Assert
require.NoError(t, err)
require.NotNil(t, resultPayload)
// Check API key is NOT in context (type assertion fails)
value := resultCtx.Value(middleware.ApiKey)
assert.Nil(t, value, "API key should not be set when wrong type")
// Check organization is NOT in context
value = resultCtx.Value(middleware.OrganizationKey)
assert.Nil(t, value, "Organization should not be set when API key has wrong type")
}
func TestWebSocketInitFunc_WithMultipleOrganizations(t *testing.T) {
// Setup - create multiple organizations
org1ID := uuid.New()
org1 := &domain.Organization{
BaseAggregate: eventsourced.BaseAggregate{
ID: eventsourced.IdFromString(org1ID.String()),
},
Name: "Organization 1",
}
org2ID := uuid.New()
org2 := &domain.Organization{
BaseAggregate: eventsourced.BaseAggregate{
ID: eventsourced.IdFromString(org2ID.String()),
},
Name: "Organization 2",
}
apiKey1 := "api-key-org-1"
apiKey2 := "api-key-org-2"
hashedKey1, err := hash.APIKey(apiKey1)
require.NoError(t, err)
hashedKey2, err := hash.APIKey(apiKey2)
require.NoError(t, err)
compositeKey1 := org1ID.String() + "-key1"
compositeKey2 := org2ID.String() + "-key2"
mockCache := &MockCache{
organizations: map[string]*domain.Organization{
compositeKey1: org1,
compositeKey2: org2,
},
apiKeys: map[string]string{
compositeKey1: hashedKey1,
compositeKey2: hashedKey2,
},
}
// Create InitFunc
initFunc := func(ctx context.Context, initPayload transport.InitPayload) (context.Context, *transport.InitPayload, error) {
// Extract API key from WebSocket connection_init payload
if apiKey, ok := initPayload["X-Api-Key"].(string); ok && apiKey != "" {
ctx = context.WithValue(ctx, middleware.ApiKey, apiKey)
// Look up organization by API key (cache handles hash comparison)
if organization := mockCache.OrganizationByAPIKey(apiKey); organization != nil {
ctx = context.WithValue(ctx, middleware.OrganizationKey, *organization)
}
}
return ctx, &initPayload, nil
}
// Test with first API key
ctx1 := context.Background()
initPayload1 := transport.InitPayload{
"X-Api-Key": apiKey1,
}
resultCtx1, _, err := initFunc(ctx1, initPayload1)
require.NoError(t, err)
if value := resultCtx1.Value(middleware.OrganizationKey); value != nil {
capturedOrg, ok := value.(domain.Organization)
require.True(t, ok)
assert.Equal(t, org1.Name, capturedOrg.Name)
assert.Equal(t, org1.ID.String(), capturedOrg.ID.String())
} else {
t.Fatal("Organization 1 not found in context")
}
// Test with second API key
ctx2 := context.Background()
initPayload2 := transport.InitPayload{
"X-Api-Key": apiKey2,
}
resultCtx2, _, err := initFunc(ctx2, initPayload2)
require.NoError(t, err)
if value := resultCtx2.Value(middleware.OrganizationKey); value != nil {
capturedOrg, ok := value.(domain.Organization)
require.True(t, ok)
assert.Equal(t, org2.Name, capturedOrg.Name)
assert.Equal(t, org2.ID.String(), capturedOrg.ID.String())
} else {
t.Fatal("Organization 2 not found in context")
}
}
+6
View File
@@ -23,6 +23,8 @@ func (o *Organization) Apply(event eventsourced.Event) error {
switch e := event.(type) {
case *OrganizationAdded:
e.UpdateOrganization(o)
case *UserAddedToOrganization:
e.UpdateOrganization(o)
case *APIKeyAdded:
o.APIKeys = append(o.APIKeys, APIKey{
Name: e.Name,
@@ -36,6 +38,10 @@ func (o *Organization) Apply(event eventsourced.Event) error {
})
o.ChangedBy = e.Initiator
o.ChangedAt = e.When()
case *APIKeyRemoved:
e.UpdateOrganization(o)
case *OrganizationRemoved:
e.UpdateOrganization(o)
default:
return fmt.Errorf("unexpected event type: %+v", event)
}
+95 -2
View File
@@ -7,7 +7,7 @@ import (
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitlab.com/unboundsoftware/schemas/hash"
"gitea.unbound.se/unboundsoftware/schemas/hash"
)
type AddOrganization struct {
@@ -34,6 +34,37 @@ func (a AddOrganization) Event(context.Context) eventsourced.Event {
var _ eventsourced.Command = AddOrganization{}
type AddUserToOrganization struct {
UserId string
Initiator string
}
func (a AddUserToOrganization) Validate(_ context.Context, aggregate eventsourced.Aggregate) error {
if aggregate.Identity() == nil {
return fmt.Errorf("organization does not exist")
}
if len(a.UserId) == 0 {
return fmt.Errorf("userId is required")
}
// Check if user is already in the organization
org := aggregate.(*Organization)
for _, user := range org.Users {
if user == a.UserId {
return fmt.Errorf("user is already a member of this organization")
}
}
return nil
}
func (a AddUserToOrganization) Event(context.Context) eventsourced.Event {
return &UserAddedToOrganization{
UserId: a.UserId,
Initiator: a.Initiator,
}
}
var _ eventsourced.Command = AddUserToOrganization{}
type AddAPIKey struct {
Name string
Key string
@@ -56,9 +87,20 @@ func (a AddAPIKey) Validate(_ context.Context, aggregate eventsourced.Aggregate)
}
func (a AddAPIKey) Event(context.Context) eventsourced.Event {
// Hash the API key using bcrypt for secure storage
// Note: We can't return an error here, but bcrypt errors are extremely rare
// (only if system runs out of memory or bcrypt cost is invalid)
// We use a fixed cost of 12 which is always valid
hashedKey, err := hash.APIKey(a.Key)
if err != nil {
// This should never happen with bcrypt cost 12, but if it does,
// we'll store an empty hash which will fail validation later
hashedKey = ""
}
return &APIKeyAdded{
Name: a.Name,
Key: hash.String(a.Key),
Key: hashedKey,
Refs: a.Refs,
Read: a.Read,
Publish: a.Publish,
@@ -68,6 +110,57 @@ func (a AddAPIKey) Event(context.Context) eventsourced.Event {
var _ eventsourced.Command = AddAPIKey{}
type RemoveAPIKey struct {
KeyName string
Initiator string
}
func (r RemoveAPIKey) Validate(_ context.Context, aggregate eventsourced.Aggregate) error {
if aggregate.Identity() == nil {
return fmt.Errorf("organization does not exist")
}
org := aggregate.(*Organization)
found := false
for _, k := range org.APIKeys {
if k.Name == r.KeyName {
found = true
break
}
}
if !found {
return fmt.Errorf("API key '%s' not found", r.KeyName)
}
return nil
}
func (r RemoveAPIKey) Event(context.Context) eventsourced.Event {
return &APIKeyRemoved{
KeyName: r.KeyName,
Initiator: r.Initiator,
}
}
var _ eventsourced.Command = RemoveAPIKey{}
type RemoveOrganization struct {
Initiator string
}
func (r RemoveOrganization) Validate(_ context.Context, aggregate eventsourced.Aggregate) error {
if aggregate.Identity() == nil {
return fmt.Errorf("organization does not exist")
}
return nil
}
func (r RemoveOrganization) Event(context.Context) eventsourced.Event {
return &OrganizationRemoved{
Initiator: r.Initiator,
}
}
var _ eventsourced.Command = RemoveOrganization{}
type UpdateSubGraph struct {
OrganizationId string
Ref string
+524 -10
View File
@@ -2,12 +2,73 @@ package domain
import (
"context"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitea.unbound.se/unboundsoftware/schemas/hash"
)
// AddOrganization tests
func TestAddOrganization_Validate_Success(t *testing.T) {
cmd := AddOrganization{
Name: "Test Org",
Initiator: "user@example.com",
}
org := &Organization{} // New organization with no identity
err := cmd.Validate(context.Background(), org)
assert.NoError(t, err)
}
func TestAddOrganization_Validate_AlreadyExists(t *testing.T) {
cmd := AddOrganization{
Name: "Test Org",
Initiator: "user@example.com",
}
org := &Organization{
BaseAggregate: eventsourced.BaseAggregateFromString("existing-org-id"),
}
err := cmd.Validate(context.Background(), org)
require.Error(t, err)
assert.Contains(t, err.Error(), "already exists")
}
func TestAddOrganization_Validate_EmptyName(t *testing.T) {
cmd := AddOrganization{
Name: "",
Initiator: "user@example.com",
}
org := &Organization{}
err := cmd.Validate(context.Background(), org)
require.Error(t, err)
assert.Contains(t, err.Error(), "name is required")
}
func TestAddOrganization_Event(t *testing.T) {
cmd := AddOrganization{
Name: "Test Org",
Initiator: "user@example.com",
}
event := cmd.Event(context.Background())
require.NotNil(t, event)
orgEvent, ok := event.(*OrganizationAdded)
require.True(t, ok)
assert.Equal(t, "Test Org", orgEvent.Name)
assert.Equal(t, "user@example.com", orgEvent.Initiator)
}
// AddAPIKey tests
func TestAddAPIKey_Event(t *testing.T) {
type fields struct {
Name string
@@ -24,7 +85,6 @@ func TestAddAPIKey_Event(t *testing.T) {
name string
fields fields
args args
want eventsourced.Event
}{
{
name: "event",
@@ -37,14 +97,6 @@ func TestAddAPIKey_Event(t *testing.T) {
Initiator: "jim@example.org",
},
args: args{},
want: &APIKeyAdded{
Name: "test",
Key: "dXNfYWtfMTIzNDU2Nzg5MDEyMzQ1NuOwxEKY/BwUmvv0yJlvuSQnrkHkZJuTTKSVmRt4UrhV",
Refs: []string{"Example@dev"},
Read: true,
Publish: true,
Initiator: "jim@example.org",
},
},
}
for _, tt := range tests {
@@ -57,7 +109,469 @@ func TestAddAPIKey_Event(t *testing.T) {
Publish: tt.fields.Publish,
Initiator: tt.fields.Initiator,
}
assert.Equalf(t, tt.want, a.Event(tt.args.in0), "Event(%v)", tt.args.in0)
event := a.Event(tt.args.in0)
require.NotNil(t, event)
// Cast to APIKeyAdded to verify fields
apiKeyEvent, ok := event.(*APIKeyAdded)
require.True(t, ok, "Event should be *APIKeyAdded")
// Verify non-key fields match exactly
assert.Equal(t, tt.fields.Name, apiKeyEvent.Name)
assert.Equal(t, tt.fields.Refs, apiKeyEvent.Refs)
assert.Equal(t, tt.fields.Read, apiKeyEvent.Read)
assert.Equal(t, tt.fields.Publish, apiKeyEvent.Publish)
assert.Equal(t, tt.fields.Initiator, apiKeyEvent.Initiator)
// Verify the key is hashed correctly (bcrypt format)
assert.True(t, strings.HasPrefix(apiKeyEvent.Key, "$2"), "Key should be bcrypt hashed")
assert.NotEqual(t, tt.fields.Key, apiKeyEvent.Key, "Key should be hashed, not plaintext")
// Verify the hash matches the original key
assert.True(t, hash.CompareAPIKey(apiKeyEvent.Key, tt.fields.Key), "Hashed key should match original")
})
}
}
func TestAddAPIKey_Validate_Success(t *testing.T) {
cmd := AddAPIKey{
Name: "production-key",
Key: "us_ak_1234567890123456",
Refs: []string{"main"},
Read: true,
Publish: false,
Initiator: "user@example.com",
}
org := &Organization{
BaseAggregate: eventsourced.BaseAggregateFromString("org-123"),
APIKeys: []APIKey{},
}
err := cmd.Validate(context.Background(), org)
assert.NoError(t, err)
}
func TestAddAPIKey_Validate_OrganizationNotExists(t *testing.T) {
cmd := AddAPIKey{
Name: "production-key",
Key: "us_ak_1234567890123456",
Refs: []string{"main"},
Read: true,
Publish: false,
Initiator: "user@example.com",
}
org := &Organization{} // No identity means it doesn't exist
err := cmd.Validate(context.Background(), org)
require.Error(t, err)
assert.Contains(t, err.Error(), "does not exist")
}
func TestAddAPIKey_Validate_DuplicateKeyName(t *testing.T) {
cmd := AddAPIKey{
Name: "existing-key",
Key: "us_ak_1234567890123456",
Refs: []string{"main"},
Read: true,
Publish: false,
Initiator: "user@example.com",
}
org := &Organization{
BaseAggregate: eventsourced.BaseAggregateFromString("org-123"),
APIKeys: []APIKey{
{
Name: "existing-key",
Key: "hashed-key",
},
},
}
err := cmd.Validate(context.Background(), org)
require.Error(t, err)
assert.Contains(t, err.Error(), "already exist")
assert.Contains(t, err.Error(), "existing-key")
}
// UpdateSubGraph tests
func TestUpdateSubGraph_Validate_Success(t *testing.T) {
url := "http://example.com/graphql"
cmd := UpdateSubGraph{
OrganizationId: "org-123",
Ref: "main",
Service: "users",
Url: &url,
Sdl: "type Query { hello: String }",
Initiator: "user@example.com",
}
subGraph := &SubGraph{
BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"),
}
err := cmd.Validate(context.Background(), subGraph)
assert.NoError(t, err)
}
func TestUpdateSubGraph_Validate_MissingRef(t *testing.T) {
url := "http://example.com/graphql"
cmd := UpdateSubGraph{
OrganizationId: "org-123",
Ref: "",
Service: "users",
Url: &url,
Sdl: "type Query { hello: String }",
Initiator: "user@example.com",
}
subGraph := &SubGraph{
BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"),
}
err := cmd.Validate(context.Background(), subGraph)
require.Error(t, err)
assert.Contains(t, err.Error(), "ref is missing")
}
func TestUpdateSubGraph_Validate_RefWhitespaceOnly(t *testing.T) {
url := "http://example.com/graphql"
cmd := UpdateSubGraph{
OrganizationId: "org-123",
Ref: " ",
Service: "users",
Url: &url,
Sdl: "type Query { hello: String }",
Initiator: "user@example.com",
}
subGraph := &SubGraph{
BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"),
}
err := cmd.Validate(context.Background(), subGraph)
require.Error(t, err)
assert.Contains(t, err.Error(), "ref is missing")
}
func TestUpdateSubGraph_Validate_MissingService(t *testing.T) {
url := "http://example.com/graphql"
cmd := UpdateSubGraph{
OrganizationId: "org-123",
Ref: "main",
Service: "",
Url: &url,
Sdl: "type Query { hello: String }",
Initiator: "user@example.com",
}
subGraph := &SubGraph{
BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"),
}
err := cmd.Validate(context.Background(), subGraph)
require.Error(t, err)
assert.Contains(t, err.Error(), "service is missing")
}
func TestUpdateSubGraph_Validate_ServiceWhitespaceOnly(t *testing.T) {
url := "http://example.com/graphql"
cmd := UpdateSubGraph{
OrganizationId: "org-123",
Ref: "main",
Service: " ",
Url: &url,
Sdl: "type Query { hello: String }",
Initiator: "user@example.com",
}
subGraph := &SubGraph{
BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"),
}
err := cmd.Validate(context.Background(), subGraph)
require.Error(t, err)
assert.Contains(t, err.Error(), "service is missing")
}
func TestUpdateSubGraph_Validate_MissingSDL(t *testing.T) {
url := "http://example.com/graphql"
cmd := UpdateSubGraph{
OrganizationId: "org-123",
Ref: "main",
Service: "users",
Url: &url,
Sdl: "",
Initiator: "user@example.com",
}
subGraph := &SubGraph{
BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"),
}
err := cmd.Validate(context.Background(), subGraph)
require.Error(t, err)
assert.Contains(t, err.Error(), "SDL is missing")
}
func TestUpdateSubGraph_Validate_SDLWhitespaceOnly(t *testing.T) {
url := "http://example.com/graphql"
cmd := UpdateSubGraph{
OrganizationId: "org-123",
Ref: "main",
Service: "users",
Url: &url,
Sdl: " \n\t ",
Initiator: "user@example.com",
}
subGraph := &SubGraph{
BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"),
}
err := cmd.Validate(context.Background(), subGraph)
require.Error(t, err)
assert.Contains(t, err.Error(), "SDL is missing")
}
func TestUpdateSubGraph_Validate_MissingURL_NoExistingURL(t *testing.T) {
cmd := UpdateSubGraph{
OrganizationId: "org-123",
Ref: "main",
Service: "users",
Url: nil,
Sdl: "type Query { hello: String }",
Initiator: "user@example.com",
}
subGraph := &SubGraph{
BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"),
Url: nil, // No existing URL
}
err := cmd.Validate(context.Background(), subGraph)
require.Error(t, err)
assert.Contains(t, err.Error(), "url is missing")
}
func TestUpdateSubGraph_Validate_MissingURL_HasExistingURL(t *testing.T) {
existingURL := "http://example.com/graphql"
cmd := UpdateSubGraph{
OrganizationId: "org-123",
Ref: "main",
Service: "users",
Url: nil,
Sdl: "type Query { hello: String }",
Initiator: "user@example.com",
}
subGraph := &SubGraph{
BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"),
Url: &existingURL, // Has existing URL, so nil is OK
}
err := cmd.Validate(context.Background(), subGraph)
assert.NoError(t, err)
}
func TestUpdateSubGraph_Validate_EmptyURL_NoExistingURL(t *testing.T) {
emptyURL := ""
cmd := UpdateSubGraph{
OrganizationId: "org-123",
Ref: "main",
Service: "users",
Url: &emptyURL,
Sdl: "type Query { hello: String }",
Initiator: "user@example.com",
}
subGraph := &SubGraph{
BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"),
Url: nil,
}
err := cmd.Validate(context.Background(), subGraph)
require.Error(t, err)
assert.Contains(t, err.Error(), "url is missing")
}
func TestUpdateSubGraph_Validate_URLWhitespaceOnly_NoExistingURL(t *testing.T) {
whitespaceURL := " "
cmd := UpdateSubGraph{
OrganizationId: "org-123",
Ref: "main",
Service: "users",
Url: &whitespaceURL,
Sdl: "type Query { hello: String }",
Initiator: "user@example.com",
}
subGraph := &SubGraph{
BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"),
Url: nil,
}
err := cmd.Validate(context.Background(), subGraph)
require.Error(t, err)
assert.Contains(t, err.Error(), "url is missing")
}
func TestUpdateSubGraph_Validate_WrongAggregateType(t *testing.T) {
url := "http://example.com/graphql"
cmd := UpdateSubGraph{
OrganizationId: "org-123",
Ref: "main",
Service: "users",
Url: &url,
Sdl: "type Query { hello: String }",
Initiator: "user@example.com",
}
// Pass wrong aggregate type
org := &Organization{
BaseAggregate: eventsourced.BaseAggregateFromString("org-123"),
}
err := cmd.Validate(context.Background(), org)
require.Error(t, err)
assert.Contains(t, err.Error(), "not a SubGraph")
}
func TestUpdateSubGraph_Event(t *testing.T) {
url := "http://example.com/graphql"
wsURL := "ws://example.com/graphql"
cmd := UpdateSubGraph{
OrganizationId: "org-123",
Ref: "main",
Service: "users",
Url: &url,
WSUrl: &wsURL,
Sdl: "type Query { hello: String }",
Initiator: "user@example.com",
}
event := cmd.Event(context.Background())
require.NotNil(t, event)
subGraphEvent, ok := event.(*SubGraphUpdated)
require.True(t, ok)
assert.Equal(t, "org-123", subGraphEvent.OrganizationId)
assert.Equal(t, "main", subGraphEvent.Ref)
assert.Equal(t, "users", subGraphEvent.Service)
assert.Equal(t, url, *subGraphEvent.Url)
assert.Equal(t, wsURL, *subGraphEvent.WSUrl)
assert.Equal(t, "type Query { hello: String }", subGraphEvent.Sdl)
assert.Equal(t, "user@example.com", subGraphEvent.Initiator)
}
// RemoveAPIKey tests
func TestRemoveAPIKey_Validate_Success(t *testing.T) {
cmd := RemoveAPIKey{
KeyName: "production-key",
Initiator: "user@example.com",
}
org := &Organization{
BaseAggregate: eventsourced.BaseAggregateFromString("org-123"),
APIKeys: []APIKey{
{
Name: "production-key",
Key: "hashed-key",
},
},
}
err := cmd.Validate(context.Background(), org)
assert.NoError(t, err)
}
func TestRemoveAPIKey_Validate_OrganizationNotExists(t *testing.T) {
cmd := RemoveAPIKey{
KeyName: "production-key",
Initiator: "user@example.com",
}
org := &Organization{} // No identity means it doesn't exist
err := cmd.Validate(context.Background(), org)
require.Error(t, err)
assert.Contains(t, err.Error(), "does not exist")
}
func TestRemoveAPIKey_Validate_KeyNotFound(t *testing.T) {
cmd := RemoveAPIKey{
KeyName: "non-existent-key",
Initiator: "user@example.com",
}
org := &Organization{
BaseAggregate: eventsourced.BaseAggregateFromString("org-123"),
APIKeys: []APIKey{
{
Name: "production-key",
Key: "hashed-key",
},
},
}
err := cmd.Validate(context.Background(), org)
require.Error(t, err)
assert.Contains(t, err.Error(), "not found")
assert.Contains(t, err.Error(), "non-existent-key")
}
func TestRemoveAPIKey_Event(t *testing.T) {
cmd := RemoveAPIKey{
KeyName: "production-key",
Initiator: "user@example.com",
}
event := cmd.Event(context.Background())
require.NotNil(t, event)
keyEvent, ok := event.(*APIKeyRemoved)
require.True(t, ok)
assert.Equal(t, "production-key", keyEvent.KeyName)
assert.Equal(t, "user@example.com", keyEvent.Initiator)
}
// RemoveOrganization tests
func TestRemoveOrganization_Validate_Success(t *testing.T) {
cmd := RemoveOrganization{
Initiator: "user@example.com",
}
org := &Organization{
BaseAggregate: eventsourced.BaseAggregateFromString("org-123"),
Name: "Test Org",
}
err := cmd.Validate(context.Background(), org)
assert.NoError(t, err)
}
func TestRemoveOrganization_Validate_OrganizationNotExists(t *testing.T) {
cmd := RemoveOrganization{
Initiator: "user@example.com",
}
org := &Organization{} // No identity means it doesn't exist
err := cmd.Validate(context.Background(), org)
require.Error(t, err)
assert.Contains(t, err.Error(), "does not exist")
}
func TestRemoveOrganization_Event(t *testing.T) {
cmd := RemoveOrganization{
Initiator: "user@example.com",
}
event := cmd.Event(context.Background())
require.NotNil(t, event)
orgEvent, ok := event.(*OrganizationRemoved)
require.True(t, ok)
assert.Equal(t, "user@example.com", orgEvent.Initiator)
}
+48
View File
@@ -17,6 +17,24 @@ func (a *OrganizationAdded) UpdateOrganization(o *Organization) {
o.ChangedAt = a.When()
}
type UserAddedToOrganization struct {
eventsourced.BaseEvent
UserId string `json:"userId"`
Initiator string `json:"initiator"`
}
func (a *UserAddedToOrganization) UpdateOrganization(o *Organization) {
// Check if user is already in the organization
for _, user := range o.Users {
if user == a.UserId {
return // User already exists, no need to add
}
}
o.Users = append(o.Users, a.UserId)
o.ChangedBy = a.Initiator
o.ChangedAt = a.When()
}
type APIKeyAdded struct {
eventsourced.BaseEvent
OrganizationId string `json:"organizationId"`
@@ -34,6 +52,36 @@ func (a *APIKeyAdded) EnrichFromAggregate(aggregate eventsourced.Aggregate) {
var _ eventsourced.EnrichableEvent = &APIKeyAdded{}
type APIKeyRemoved struct {
eventsourced.BaseEvent
KeyName string `json:"keyName"`
Initiator string `json:"initiator"`
}
func (a *APIKeyRemoved) UpdateOrganization(o *Organization) {
// Remove the API key from the organization
for i, key := range o.APIKeys {
if key.Name == a.KeyName {
o.APIKeys = append(o.APIKeys[:i], o.APIKeys[i+1:]...)
break
}
}
o.ChangedBy = a.Initiator
o.ChangedAt = a.When()
}
type OrganizationRemoved struct {
eventsourced.BaseEvent
Initiator string `json:"initiator"`
}
func (a *OrganizationRemoved) UpdateOrganization(o *Organization) {
// Mark organization as removed by clearing critical fields
// The aggregate will still exist in the event store, but it's logically deleted
o.ChangedBy = a.Initiator
o.ChangedAt = a.When()
}
type SubGraphUpdated struct {
eventsourced.BaseEvent
OrganizationId string `json:"organizationId"`
+254
View File
@@ -0,0 +1,254 @@
package domain
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
)
func TestOrganizationAdded_UpdateOrganization(t *testing.T) {
event := &OrganizationAdded{
BaseEvent: eventsourced.BaseEvent{
EventTime: eventsourced.EventTime{
Time: time.Now(),
},
},
Name: "Test Organization",
Initiator: "user@example.com",
}
org := &Organization{
BaseAggregate: eventsourced.BaseAggregateFromString("org-123"),
}
event.UpdateOrganization(org)
assert.Equal(t, "Test Organization", org.Name)
assert.Equal(t, []string{"user@example.com"}, org.Users)
assert.Equal(t, "user@example.com", org.CreatedBy)
assert.Equal(t, "user@example.com", org.ChangedBy)
assert.Equal(t, event.When(), org.CreatedAt)
assert.Equal(t, event.When(), org.ChangedAt)
}
func TestUserAddedToOrganization_UpdateOrganization(t *testing.T) {
event := &UserAddedToOrganization{
BaseEvent: eventsourced.BaseEvent{
EventTime: eventsourced.EventTime{
Time: time.Now(),
},
},
UserId: "new-user@example.com",
Initiator: "admin@example.com",
}
org := &Organization{
BaseAggregate: eventsourced.BaseAggregateFromString("org-123"),
Users: []string{"existing-user@example.com"},
}
event.UpdateOrganization(org)
assert.Len(t, org.Users, 2)
assert.Contains(t, org.Users, "existing-user@example.com")
assert.Contains(t, org.Users, "new-user@example.com")
assert.Equal(t, "admin@example.com", org.ChangedBy)
assert.Equal(t, event.When(), org.ChangedAt)
}
func TestUserAddedToOrganization_UpdateOrganization_DuplicateUser(t *testing.T) {
event := &UserAddedToOrganization{
BaseEvent: eventsourced.BaseEvent{
EventTime: eventsourced.EventTime{
Time: time.Now(),
},
},
UserId: "existing-user@example.com",
Initiator: "admin@example.com",
}
org := &Organization{
BaseAggregate: eventsourced.BaseAggregateFromString("org-123"),
Users: []string{"existing-user@example.com"},
ChangedBy: "previous-admin@example.com",
}
originalChangedBy := org.ChangedBy
originalChangedAt := org.ChangedAt
event.UpdateOrganization(org)
// User should not be added twice
assert.Len(t, org.Users, 1)
assert.Equal(t, "existing-user@example.com", org.Users[0])
// ChangedBy and ChangedAt should NOT be updated when user already exists (idempotent)
assert.Equal(t, originalChangedBy, org.ChangedBy)
assert.Equal(t, originalChangedAt, org.ChangedAt)
}
func TestAPIKeyRemoved_UpdateOrganization(t *testing.T) {
event := &APIKeyRemoved{
BaseEvent: eventsourced.BaseEvent{
EventTime: eventsourced.EventTime{
Time: time.Now(),
},
},
KeyName: "production-key",
Initiator: "admin@example.com",
}
org := &Organization{
BaseAggregate: eventsourced.BaseAggregateFromString("org-123"),
APIKeys: []APIKey{
{Name: "dev-key", Key: "hashed-key-1"},
{Name: "production-key", Key: "hashed-key-2"},
{Name: "staging-key", Key: "hashed-key-3"},
},
}
event.UpdateOrganization(org)
assert.Len(t, org.APIKeys, 2)
assert.Equal(t, "dev-key", org.APIKeys[0].Name)
assert.Equal(t, "staging-key", org.APIKeys[1].Name)
assert.Equal(t, "admin@example.com", org.ChangedBy)
assert.Equal(t, event.When(), org.ChangedAt)
}
func TestAPIKeyRemoved_UpdateOrganization_KeyNotFound(t *testing.T) {
event := &APIKeyRemoved{
BaseEvent: eventsourced.BaseEvent{
EventTime: eventsourced.EventTime{
Time: time.Now(),
},
},
KeyName: "non-existent-key",
Initiator: "admin@example.com",
}
org := &Organization{
BaseAggregate: eventsourced.BaseAggregateFromString("org-123"),
APIKeys: []APIKey{
{Name: "dev-key", Key: "hashed-key-1"},
{Name: "production-key", Key: "hashed-key-2"},
},
}
event.UpdateOrganization(org)
// No keys should be removed
assert.Len(t, org.APIKeys, 2)
assert.Equal(t, "dev-key", org.APIKeys[0].Name)
assert.Equal(t, "production-key", org.APIKeys[1].Name)
// But metadata should still be updated
assert.Equal(t, "admin@example.com", org.ChangedBy)
assert.Equal(t, event.When(), org.ChangedAt)
}
func TestAPIKeyRemoved_UpdateOrganization_OnlyKey(t *testing.T) {
event := &APIKeyRemoved{
BaseEvent: eventsourced.BaseEvent{
EventTime: eventsourced.EventTime{
Time: time.Now(),
},
},
KeyName: "only-key",
Initiator: "admin@example.com",
}
org := &Organization{
BaseAggregate: eventsourced.BaseAggregateFromString("org-123"),
APIKeys: []APIKey{
{Name: "only-key", Key: "hashed-key"},
},
}
event.UpdateOrganization(org)
// All keys should be removed
assert.Len(t, org.APIKeys, 0)
assert.Equal(t, "admin@example.com", org.ChangedBy)
assert.Equal(t, event.When(), org.ChangedAt)
}
func TestOrganizationRemoved_UpdateOrganization(t *testing.T) {
event := &OrganizationRemoved{
BaseEvent: eventsourced.BaseEvent{
EventTime: eventsourced.EventTime{
Time: time.Now(),
},
},
Initiator: "admin@example.com",
}
org := &Organization{
BaseAggregate: eventsourced.BaseAggregateFromString("org-123"),
Name: "Test Organization",
Users: []string{"user1@example.com", "user2@example.com"},
APIKeys: []APIKey{
{Name: "key1", Key: "hashed-key-1"},
},
CreatedBy: "creator@example.com",
CreatedAt: time.Now().Add(-24 * time.Hour),
}
event.UpdateOrganization(org)
// Organization data remains (soft delete), but metadata is updated
assert.Equal(t, "Test Organization", org.Name)
assert.Len(t, org.Users, 2)
assert.Len(t, org.APIKeys, 1)
// Metadata should be updated to reflect removal
assert.Equal(t, "admin@example.com", org.ChangedBy)
assert.Equal(t, event.When(), org.ChangedAt)
}
func TestAPIKeyAdded_EnrichFromAggregate(t *testing.T) {
orgId := "org-123"
aggregate := &Organization{
BaseAggregate: eventsourced.BaseAggregateFromString(orgId),
}
event := &APIKeyAdded{
Name: "test-key",
Key: "hashed-key",
Refs: []string{"main"},
Read: true,
Publish: false,
Initiator: "user@example.com",
}
event.EnrichFromAggregate(aggregate)
assert.Equal(t, orgId, event.OrganizationId)
}
func TestSubGraphUpdated_Event(t *testing.T) {
// Verify SubGraphUpdated event structure
url := "http://service.example.com"
wsUrl := "ws://service.example.com"
event := &SubGraphUpdated{
OrganizationId: "org-123",
Ref: "main",
Service: "users-service",
Url: &url,
WSUrl: &wsUrl,
Sdl: "type Query { user: User }",
Initiator: "system",
}
require.NotNil(t, event)
assert.Equal(t, "org-123", event.OrganizationId)
assert.Equal(t, "main", event.Ref)
assert.Equal(t, "users-service", event.Service)
assert.Equal(t, url, *event.Url)
assert.Equal(t, wsUrl, *event.WSUrl)
assert.Equal(t, "type Query { user: User }", event.Sdl)
assert.Equal(t, "system", event.Initiator)
}
+63 -51
View File
@@ -1,79 +1,91 @@
module gitlab.com/unboundsoftware/schemas
module gitea.unbound.se/unboundsoftware/schemas
go 1.24.4
go 1.25.0
require (
github.com/99designs/gqlgen v0.17.76
github.com/99designs/gqlgen v0.17.89
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/Khan/genqlient v0.8.1
github.com/alecthomas/kong v1.12.0
github.com/alecthomas/kong v1.14.0
github.com/apex/log v1.9.0
github.com/auth0/go-jwt-middleware/v2 v2.3.0
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/auth0/go-jwt-middleware/v3 v3.0.0
github.com/google/uuid v1.6.0
github.com/jmoiron/sqlx v1.4.0
github.com/pkg/errors v0.9.1
github.com/pressly/goose/v3 v3.24.3
github.com/pressly/goose/v3 v3.27.0
github.com/rs/cors v1.11.1
github.com/sparetimecoders/goamqp v0.3.2
github.com/stretchr/testify v1.10.0
github.com/vektah/gqlparser/v2 v2.5.30
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.194
gitlab.com/unboundsoftware/eventsourced/amqp v1.8.1
gitlab.com/unboundsoftware/eventsourced/eventsourced v1.19.2
gitlab.com/unboundsoftware/eventsourced/pg v1.17.0
go.opentelemetry.io/contrib/bridges/otelslog v0.12.0
go.opentelemetry.io/otel v1.37.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.13.0
go.opentelemetry.io/otel/log v0.13.0
go.opentelemetry.io/otel/sdk v1.37.0
go.opentelemetry.io/otel/sdk/log v0.13.0
go.opentelemetry.io/otel/sdk/metric v1.37.0
go.opentelemetry.io/otel/trace v1.37.0
github.com/sparetimecoders/goamqp v0.3.3
github.com/stretchr/testify v1.11.1
github.com/vektah/gqlparser/v2 v2.5.32
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.267
gitlab.com/unboundsoftware/eventsourced/amqp v1.9.1
gitlab.com/unboundsoftware/eventsourced/eventsourced v1.19.4
gitlab.com/unboundsoftware/eventsourced/pg v1.18.5
go.opentelemetry.io/contrib/bridges/otelslog v0.17.0
go.opentelemetry.io/otel v1.42.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0
go.opentelemetry.io/otel/log v0.18.0
go.opentelemetry.io/otel/sdk v1.42.0
go.opentelemetry.io/otel/sdk/log v0.18.0
go.opentelemetry.io/otel/sdk/metric v1.42.0
go.opentelemetry.io/otel/trace v1.42.0
golang.org/x/crypto v0.49.0
golang.org/x/sync v0.20.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/agnivade/levenshtein v1.2.1 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
github.com/lestrrat-go/dsig v1.0.0 // indirect
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc/v3 v3.0.3 // indirect
github.com/lestrrat-go/jwx/v3 v3.0.12 // indirect
github.com/lestrrat-go/option/v2 v2.0.0 // indirect
github.com/lib/pq v1.12.0 // indirect
github.com/mfridman/interpolate v0.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rabbitmq/amqp091-go v1.10.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/segmentio/asm v1.2.1 // indirect
github.com/sethvargo/go-retry v0.3.0 // indirect
github.com/sosodev/duration v1.3.1 // indirect
github.com/sosodev/duration v1.4.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/tidwall/gjson v1.17.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/urfave/cli/v2 v2.27.7 // indirect
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
github.com/urfave/cli/v3 v3.7.0 // indirect
github.com/valyala/fastjson v1.6.7 // indirect
github.com/wundergraph/astjson v1.1.0 // indirect
github.com/wundergraph/go-arena v1.1.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/tools v0.34.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect
golang.org/x/tools v0.42.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
google.golang.org/grpc v1.79.2 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)
+140 -116
View File
@@ -1,21 +1,22 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/99designs/gqlgen v0.17.76 h1:YsJBcfACWmXWU2t1yCjoGdOmqcTfOFpjbLAE443fmYI=
github.com/99designs/gqlgen v0.17.76/go.mod h1:miiU+PkAnTIDKMQ1BseUOIVeQHoiwYDZGCswoxl7xec=
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
github.com/99designs/gqlgen v0.17.89 h1:KzEcxPiMgQoMw3m/E85atUEHyZyt0PbAflMia5Kw8z8=
github.com/99designs/gqlgen v0.17.89/go.mod h1:GFqruTVGB7ZTdrf1uzOagpXbY7DrEt1pIxnTdhIbWvQ=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs=
github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU=
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=
github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/kong v1.12.0 h1:oKd/0fHSdajj5PfGDd3ScvEvpVJf9mT2mb5r9xYadYM=
github.com/alecthomas/kong v1.12.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alecthomas/kong v1.14.0 h1:gFgEUZWu2ZmZ+UhyZ1bDhuutbKN1nTtJTwh19Wsn21s=
github.com/alecthomas/kong v1.14.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
@@ -27,21 +28,21 @@ github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy
github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/auth0/go-jwt-middleware/v2 v2.3.0 h1:4QREj6cS3d8dS05bEm443jhnqQF97FX9sMBeWqnNRzE=
github.com/auth0/go-jwt-middleware/v2 v2.3.0/go.mod h1:dL4ObBs1/dj4/W4cYxd8rqAdDGXYyd5rqbpMIxcbVrU=
github.com/auth0/go-jwt-middleware/v3 v3.0.0 h1:+rvUPCT+VbAuK4tpS13fWfZrMyqTwLopt3VoY0Y7kvA=
github.com/auth0/go-jwt-middleware/v3 v3.0.0/go.mod h1:iU42jqjRyeKbf9YYSnRnolr836gk6Ty/jnUNuVq2b0o=
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo=
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
@@ -55,12 +56,14 @@ github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
@@ -72,8 +75,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
@@ -85,6 +88,7 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -93,8 +97,23 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38=
github.com/lestrrat-go/dsig v1.0.0/go.mod h1:dEgoOYYEJvW6XGbLasr8TFcAxoWrKlbQvmJgCR0qkDo=
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY=
github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/httprc/v3 v3.0.3 h1:WjLHWkDkgWXeIUrKi/7lS/sGq2DjkSAwdTbH5RHXAKs=
github.com/lestrrat-go/httprc/v3 v3.0.3/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0=
github.com/lestrrat-go/jwx/v3 v3.0.12 h1:p25r68Y4KrbBdYjIsQweYxq794CtGCzcrc5dGzJIRjg=
github.com/lestrrat-go/jwx/v3 v3.0.12/go.mod h1:HiUSaNmMLXgZ08OmGBaPVvoZQgJVOQphSrGr5zMamS8=
github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=
github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo=
github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
@@ -106,8 +125,8 @@ github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxU
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -115,8 +134,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pressly/goose/v3 v3.24.3 h1:DSWWNwwggVUsYZ0X2VitiAa9sKuqtBfe+Jr9zFGwWlM=
github.com/pressly/goose/v3 v3.24.3/go.mod h1:v9zYL4xdViLHCUUJh/mhjnm6JrK7Eul8AS93IxiZM4E=
github.com/pressly/goose/v3 v3.27.0 h1:/D30gVTuQhu0WsNZYbJi4DMOsx1lNq+6SkLe+Wp59BM=
github.com/pressly/goose/v3 v3.27.0/go.mod h1:3ZBeCXqzkgIRvrEMDkYh1guvtoJTU5oMMuDdkutoM78=
github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
@@ -126,12 +145,12 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rYg=
github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y=
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/sebdah/goldie/v2 v2.7.1 h1:PkBHymaYdtvEkZV7TmyqKxdmn5/Vcj+8TpATWZjnG5E=
github.com/sebdah/goldie/v2 v2.7.1/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
@@ -140,15 +159,18 @@ github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLy
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
github.com/sparetimecoders/goamqp v0.3.2 h1:XdlyUBAJS5RcURw+SnnPjPJJuofddZwQsjAf05VPXvI=
github.com/sparetimecoders/goamqp v0.3.2/go.mod h1:W9NRCpWLE+Vruv2dcRSbszNil2O826d2Nv6kAkETW5o=
github.com/sosodev/duration v1.4.0 h1:35ed0KiVFriGHHzZZJaZLgmTEEICIyt8Sx0RQfj9IjE=
github.com/sosodev/duration v1.4.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
github.com/sparetimecoders/goamqp v0.3.3 h1:z/nfTPmrjeU/rIVuNOgsVLCimp3WFoNFvS3ZzXRJ6HE=
github.com/sparetimecoders/goamqp v0.3.3/go.mod h1:W9NRCpWLE+Vruv2dcRSbszNil2O826d2Nv6kAkETW5o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
@@ -166,100 +188,102 @@ github.com/tj/go-buffer v1.1.0/go.mod h1:iyiJpfFcR2B9sXu7KvjbT9fpM4mOelRSDTbntVj
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
github.com/vektah/gqlparser/v2 v2.5.30 h1:EqLwGAFLIzt1wpx1IPpY67DwUujF1OfzgEyDsLrN6kE=
github.com/vektah/gqlparser/v2 v2.5.30/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083 h1:8/D7f8gKxTBjW+SZK4mhxTTBVpxcqeBgWF1Rfmltbfk=
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083/go.mod h1:eOTL6acwctsN4F3b7YE+eE2t8zcJ/doLm9sZzsxxxrE=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.194 h1:qBGT+5oyo86EdTtBIIExE/TPbqa2iBkNb0HTl+kGKJQ=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.194/go.mod h1:DaBrBCMgKGd3t7zg7z11jKm+0mVJiesr/IQCRG9qgP0=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
gitlab.com/unboundsoftware/eventsourced/amqp v1.8.1 h1:MGHH2Uxp68J9i4V3/3vApB6gjBUjn6RjiPHhbc8Wsno=
gitlab.com/unboundsoftware/eventsourced/amqp v1.8.1/go.mod h1:clGBkdpFWb5/27aLOhJ6+DB15enJf+T4J5lR6X0lqAs=
gitlab.com/unboundsoftware/eventsourced/eventsourced v1.19.2 h1:8sCnThNHEPB3BQomcJ7u6fmc2t043fAZSMmVPDDbQOs=
gitlab.com/unboundsoftware/eventsourced/eventsourced v1.19.2/go.mod h1:KeLn3U67hxbdFLfeXd0c0LI/r1C5rijbWrfNdARWe98=
gitlab.com/unboundsoftware/eventsourced/pg v1.17.0 h1:pUJzMpNPX0GVsffRZXlpKR1d7Ws96KTxJwbLFPpASSc=
gitlab.com/unboundsoftware/eventsourced/pg v1.17.0/go.mod h1:WgPrZhyCbsZ3TG2tPUbh2MUjOEaANJjsWi/0hlIwRVU=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/bridges/otelslog v0.12.0 h1:lFM7SZo8Ce01RzRfnUFQZEYeWRf/MtOA3A5MobOqk2g=
go.opentelemetry.io/contrib/bridges/otelslog v0.12.0/go.mod h1:Dw05mhFtrKAYu72Tkb3YBYeQpRUJ4quDgo2DQw3No5A=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0 h1:9PgnL3QNlj10uGxExowIDIZu66aVBwWhXmbOp1pa6RA=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0/go.mod h1:0ineDcLELf6JmKfuo0wvvhAVMuxWFYvkTin2iV4ydPQ=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.13.0 h1:yEX3aC9KDgvYPhuKECHbOlr5GLwH6KTjLJ1sBSkkxkc=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.13.0/go.mod h1:/GXR0tBmmkxDaCUGahvksvp66mx4yh5+cFXgSlhg0vQ=
go.opentelemetry.io/otel/log v0.13.0 h1:yoxRoIZcohB6Xf0lNv9QIyCzQvrtGZklVbdCoyb7dls=
go.opentelemetry.io/otel/log v0.13.0/go.mod h1:INKfG4k1O9CL25BaM1qLe0zIedOpvlS5Z7XgSbmN83E=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/log v0.13.0 h1:I3CGUszjM926OphK8ZdzF+kLqFvfRY/IIoFq/TjwfaQ=
go.opentelemetry.io/otel/sdk/log v0.13.0/go.mod h1:lOrQyCCXmpZdN7NchXb6DOZZa1N5G1R2tm5GMMTpDBw=
go.opentelemetry.io/otel/sdk/log/logtest v0.13.0 h1:9yio6AFZ3QD9j9oqshV1Ibm9gPLlHNxurno5BreMtIA=
go.opentelemetry.io/otel/sdk/log/logtest v0.13.0/go.mod h1:QOGiAJHl+fob8Nu85ifXfuQYmJTFAvcrxL6w5/tu168=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
github.com/urfave/cli/v3 v3.7.0 h1:AGSnbUyjtLiM+WJUb4dzXKldl/gL+F8OwmRDtVr6g2U=
github.com/urfave/cli/v3 v3.7.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/valyala/fastjson v1.6.7 h1:ZE4tRy0CIkh+qDc5McjatheGX2czdn8slQjomexVpBM=
github.com/valyala/fastjson v1.6.7/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/vektah/gqlparser/v2 v2.5.32 h1:k9QPJd4sEDTL+qB4ncPLflqTJ3MmjB9SrVzJrawpFSc=
github.com/vektah/gqlparser/v2 v2.5.32/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts=
github.com/wundergraph/astjson v1.1.0 h1:xORDosrZ87zQFJwNGe/HIHXqzpdHOFmqWgykCLVL040=
github.com/wundergraph/astjson v1.1.0/go.mod h1:h12D/dxxnedtLzsKyBLK7/Oe4TAoGpRVC9nDpDrZSWw=
github.com/wundergraph/go-arena v1.1.0 h1:9+wSRkJAkA2vbYHp6s8tEGhPViRGQNGXqPHT0QzhdIc=
github.com/wundergraph/go-arena v1.1.0/go.mod h1:ROOysEHWJjLQ8FSfNxZCziagb7Qw2nXY3/vgKRh7eWw=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.267 h1:qMkYR0oq0Cw61aDZs9VsCCVwNVSxRxT13ytz6WqCwJg=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.267/go.mod h1:HjTAO/cuICpu31IfHY9qmSPygx6Gza7Wt9hTSReTI+A=
gitlab.com/unboundsoftware/eventsourced/amqp v1.9.1 h1:X6269JoAzHIKCVmtgMHZH3m7xOpACSp37ca3eODe9iU=
gitlab.com/unboundsoftware/eventsourced/amqp v1.9.1/go.mod h1:EAs0d6Eh0aDiQkUJlSWErHqgHFQdxx0e8I7aG/2FarY=
gitlab.com/unboundsoftware/eventsourced/eventsourced v1.19.4 h1:+yZkhi9/sTyBEN5vJTfvycyXgGrm07QKGSh3jiWiQdM=
gitlab.com/unboundsoftware/eventsourced/eventsourced v1.19.4/go.mod h1:LrA7I7etRmhIC1PjO8c26BHm+gWsy2rC3eSMe5+XUWE=
gitlab.com/unboundsoftware/eventsourced/pg v1.18.5 h1:sJCGXDpw5mZCEwFE6Bhdg6M3O9PNsKbgpNUilz0c/Xk=
gitlab.com/unboundsoftware/eventsourced/pg v1.18.5/go.mod h1:Lmmtj/YZSN3cFYS6NEg04ymTAtpzRJIMETfujRKXPR8=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/bridges/otelslog v0.17.0 h1:NFIS6x7wyObQ7cR84x7bt1sr8nYBx89s3x3GwRjw40k=
go.opentelemetry.io/contrib/bridges/otelslog v0.17.0/go.mod h1:39SaByOyDMRMe872AE7uelMuQZidIw7LLFAnQi0FWTE=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 h1:H7O6RlGOMTizyl3R08Kn5pdM06bnH8oscSj7o11tmLA=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0/go.mod h1:mBFWu/WOVDkWWsR7Tx7h6EpQB8wsv7P0Yrh0Pb7othc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 h1:KJVjPD3rcPb98rIs3HznyJlrfx9ge5oJvxxlGR+P/7s=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0/go.mod h1:K3kRa2ckmHWQaTWQdPRHc7qGXASuVuoEQXzrvlA98Ws=
go.opentelemetry.io/otel/log v0.18.0 h1:XgeQIIBjZZrliksMEbcwMZefoOSMI1hdjiLEiiB0bAg=
go.opentelemetry.io/otel/log v0.18.0/go.mod h1:KEV1kad0NofR3ycsiDH4Yjcoj0+8206I6Ox2QYFSNgI=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/log v0.18.0 h1:n8OyZr7t7otkeTnPTbDNom6rW16TBYGtvyy2Gk6buQw=
go.opentelemetry.io/otel/sdk/log v0.18.0/go.mod h1:C0+wxkTwKpOCZLrlJ3pewPiiQwpzycPI/u6W0Z9fuYk=
go.opentelemetry.io/otel/sdk/log/logtest v0.18.0 h1:l3mYuPsuBx6UKE47BVcPrZoZ0q/KER57vbj2qkgDLXA=
go.opentelemetry.io/otel/sdk/log/logtest v0.18.0/go.mod h1:7cHtiVJpZebB3wybTa4NG+FUo5NPe3PROz1FqB0+qdw=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs=
gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -267,11 +291,11 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/libc v1.65.0 h1:e183gLDnAp9VJh6gWKdTy0CThL9Pt7MfcR/0bgb7Y1Y=
modernc.org/libc v1.65.0/go.mod h1:7m9VzGq7APssBTydds2zBcxGREwvIGpuUBaKTXdm2Qs=
modernc.org/libc v1.68.0 h1:PJ5ikFOV5pwpW+VqCK1hKJuEWsonkIJhhIXyuF/91pQ=
modernc.org/libc v1.68.0/go.mod h1:NnKCYeoYgsEqnY3PgvNgAeaJnso968ygU8Z0DxjoEc0=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.10.0 h1:fzumd51yQ1DxcOxSO+S6X7+QTuVU+n8/Aj7swYjFfC4=
modernc.org/memory v1.10.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI=
modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
+3 -3
View File
@@ -1,8 +1,8 @@
package graph
import (
"gitlab.com/unboundsoftware/schemas/domain"
"gitlab.com/unboundsoftware/schemas/graph/model"
"gitea.unbound.se/unboundsoftware/schemas/domain"
"gitea.unbound.se/unboundsoftware/schemas/graph/model"
)
func ToGqlOrganizations(orgs []domain.Organization) []*model.Organization {
@@ -38,7 +38,7 @@ func ToGqlAPIKeys(keys []domain.APIKey) []*model.APIKey {
result[i] = &model.APIKey{
ID: apiKeyId(k.OrganizationId, k.Name),
Name: k.Name,
Key: &k.Key,
Key: nil, // Never return the hashed key - only return plaintext on creation
Organization: nil,
Refs: k.Refs,
Read: k.Read,
+161
View File
@@ -0,0 +1,161 @@
package graph
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"time"
"golang.org/x/sync/semaphore"
"gopkg.in/yaml.v3"
"gitea.unbound.se/unboundsoftware/schemas/graph/model"
)
// CommandExecutor is an interface for executing external commands
// This allows for mocking in tests
type CommandExecutor interface {
Execute(name string, args ...string) ([]byte, error)
}
// DefaultCommandExecutor implements CommandExecutor using os/exec
type DefaultCommandExecutor struct{}
// Execute runs a command and returns its combined output
func (e *DefaultCommandExecutor) Execute(name string, args ...string) ([]byte, error) {
cmd := exec.Command(name, args...)
return cmd.CombinedOutput()
}
// GenerateCosmoRouterConfig generates a Cosmo Router execution config from subgraphs
// using the official wgc CLI tool via npx
func GenerateCosmoRouterConfig(subGraphs []*model.SubGraph) (string, error) {
return GenerateCosmoRouterConfigWithExecutor(subGraphs, &DefaultCommandExecutor{})
}
// GenerateCosmoRouterConfigWithExecutor generates a Cosmo Router execution config from subgraphs
// using the provided command executor (useful for testing)
func GenerateCosmoRouterConfigWithExecutor(subGraphs []*model.SubGraph, executor CommandExecutor) (string, error) {
if len(subGraphs) == 0 {
return "", fmt.Errorf("no subgraphs provided")
}
// Create a temporary directory for composition
tmpDir, err := os.MkdirTemp("", "cosmo-compose-*")
if err != nil {
return "", fmt.Errorf("create temp dir: %w", err)
}
defer os.RemoveAll(tmpDir)
// Write each subgraph SDL to a file
type SubgraphConfig struct {
Name string `yaml:"name"`
RoutingURL string `yaml:"routing_url,omitempty"`
Schema map[string]string `yaml:"schema"`
Subscription map[string]interface{} `yaml:"subscription,omitempty"`
}
type InputConfig struct {
Version int `yaml:"version"`
Subgraphs []SubgraphConfig `yaml:"subgraphs"`
}
inputConfig := InputConfig{
Version: 1,
Subgraphs: make([]SubgraphConfig, 0, len(subGraphs)),
}
for _, sg := range subGraphs {
// Write SDL to a temp file
schemaFile := filepath.Join(tmpDir, fmt.Sprintf("%s.graphql", sg.Service))
if err := os.WriteFile(schemaFile, []byte(sg.Sdl), 0o644); err != nil {
return "", fmt.Errorf("write schema file for %s: %w", sg.Service, err)
}
subgraphCfg := SubgraphConfig{
Name: sg.Service,
Schema: map[string]string{
"file": schemaFile,
},
}
if sg.URL != nil {
subgraphCfg.RoutingURL = *sg.URL
}
if sg.WsURL != nil {
subgraphCfg.Subscription = map[string]interface{}{
"url": *sg.WsURL,
"protocol": "ws",
"websocket_subprotocol": "graphql-ws",
}
}
inputConfig.Subgraphs = append(inputConfig.Subgraphs, subgraphCfg)
}
// Write input config YAML
inputFile := filepath.Join(tmpDir, "input.yaml")
inputYAML, err := yaml.Marshal(inputConfig)
if err != nil {
return "", fmt.Errorf("marshal input config: %w", err)
}
if err := os.WriteFile(inputFile, inputYAML, 0o644); err != nil {
return "", fmt.Errorf("write input config: %w", err)
}
// Execute wgc router compose
// wgc is installed globally in the Docker image
outputFile := filepath.Join(tmpDir, "config.json")
output, err := executor.Execute("wgc", "router", "compose",
"--input", inputFile,
"--out", outputFile,
"--suppress-warnings",
)
if err != nil {
return "", fmt.Errorf("wgc router compose failed: %w\nOutput: %s", err, string(output))
}
// Read the generated config
configJSON, err := os.ReadFile(outputFile)
if err != nil {
return "", fmt.Errorf("read output config: %w", err)
}
return string(configJSON), nil
}
// CosmoGenerator wraps config generation with a concurrency limit and timeout
// to prevent unbounded wgc process spawning under rapid schema updates.
type CosmoGenerator struct {
sem *semaphore.Weighted
executor CommandExecutor
timeout time.Duration
}
// NewCosmoGenerator creates a CosmoGenerator that allows at most one concurrent
// wgc process and applies the given timeout to each generation attempt.
func NewCosmoGenerator(executor CommandExecutor, timeout time.Duration) *CosmoGenerator {
return &CosmoGenerator{
sem: semaphore.NewWeighted(1),
executor: executor,
timeout: timeout,
}
}
// Generate produces a Cosmo Router config, blocking if another generation is
// already in progress. The provided context (plus the configured timeout)
// controls cancellation.
func (g *CosmoGenerator) Generate(ctx context.Context, subGraphs []*model.SubGraph) (string, error) {
ctx, cancel := context.WithTimeout(ctx, g.timeout)
defer cancel()
if err := g.sem.Acquire(ctx, 1); err != nil {
return "", fmt.Errorf("acquire cosmo generator: %w", err)
}
defer g.sem.Release(1)
return GenerateCosmoRouterConfigWithExecutor(subGraphs, g.executor)
}
+577
View File
@@ -0,0 +1,577 @@
package graph
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
"gitea.unbound.se/unboundsoftware/schemas/graph/model"
)
// MockCommandExecutor implements CommandExecutor for testing
type MockCommandExecutor struct {
// CallCount tracks how many times Execute was called
CallCount int
// LastArgs stores the arguments from the last call
LastArgs []string
// Error can be set to simulate command failures
Error error
}
// Execute mocks the wgc command by generating a realistic config.json file
func (m *MockCommandExecutor) Execute(name string, args ...string) ([]byte, error) {
m.CallCount++
m.LastArgs = append([]string{name}, args...)
if m.Error != nil {
return nil, m.Error
}
// Parse the input file to understand what subgraphs we're composing
var inputFile, outputFile string
for i, arg := range args {
if arg == "--input" && i+1 < len(args) {
inputFile = args[i+1]
}
if arg == "--out" && i+1 < len(args) {
outputFile = args[i+1]
}
}
if inputFile == "" || outputFile == "" {
return nil, fmt.Errorf("missing required arguments")
}
// Read the input YAML to get subgraph information
inputData, err := os.ReadFile(inputFile)
if err != nil {
return nil, fmt.Errorf("failed to read input file: %w", err)
}
var input struct {
Version int `yaml:"version"`
Subgraphs []struct {
Name string `yaml:"name"`
RoutingURL string `yaml:"routing_url,omitempty"`
Schema map[string]string `yaml:"schema"`
Subscription map[string]interface{} `yaml:"subscription,omitempty"`
} `yaml:"subgraphs"`
}
if err := yaml.Unmarshal(inputData, &input); err != nil {
return nil, fmt.Errorf("failed to parse input YAML: %w", err)
}
// Generate a realistic Cosmo Router config based on the input
config := map[string]interface{}{
"version": "mock-version-uuid",
"subgraphs": func() []map[string]interface{} {
subgraphs := make([]map[string]interface{}, len(input.Subgraphs))
for i, sg := range input.Subgraphs {
subgraph := map[string]interface{}{
"id": fmt.Sprintf("mock-id-%d", i),
"name": sg.Name,
}
if sg.RoutingURL != "" {
subgraph["routingUrl"] = sg.RoutingURL
}
subgraphs[i] = subgraph
}
return subgraphs
}(),
"engineConfig": map[string]interface{}{
"graphqlSchema": generateMockSchema(input.Subgraphs),
"datasourceConfigurations": func() []map[string]interface{} {
dsConfigs := make([]map[string]interface{}, len(input.Subgraphs))
for i, sg := range input.Subgraphs {
// Read SDL from file
sdl := ""
if schemaFile, ok := sg.Schema["file"]; ok {
if sdlData, err := os.ReadFile(schemaFile); err == nil {
sdl = string(sdlData)
}
}
dsConfig := map[string]interface{}{
"id": fmt.Sprintf("datasource-%d", i),
"kind": "GRAPHQL",
"customGraphql": map[string]interface{}{
"federation": map[string]interface{}{
"enabled": true,
"serviceSdl": sdl,
},
"subscription": func() map[string]interface{} {
if len(sg.Subscription) > 0 {
return map[string]interface{}{
"enabled": true,
"url": map[string]interface{}{
"staticVariableContent": sg.Subscription["url"],
},
"protocol": sg.Subscription["protocol"],
"websocketSubprotocol": sg.Subscription["websocket_subprotocol"],
}
}
return map[string]interface{}{
"enabled": false,
}
}(),
},
}
dsConfigs[i] = dsConfig
}
return dsConfigs
}(),
},
}
// Write the config to the output file
configJSON, err := json.Marshal(config)
if err != nil {
return nil, fmt.Errorf("failed to marshal config: %w", err)
}
if err := os.WriteFile(outputFile, configJSON, 0o644); err != nil {
return nil, fmt.Errorf("failed to write output file: %w", err)
}
return []byte("Success"), nil
}
// generateMockSchema creates a simple merged schema from subgraphs
func generateMockSchema(subgraphs []struct {
Name string `yaml:"name"`
RoutingURL string `yaml:"routing_url,omitempty"`
Schema map[string]string `yaml:"schema"`
Subscription map[string]interface{} `yaml:"subscription,omitempty"`
},
) string {
schema := strings.Builder{}
schema.WriteString("schema {\n query: Query\n")
// Check if any subgraph has subscriptions
hasSubscriptions := false
for _, sg := range subgraphs {
if len(sg.Subscription) > 0 {
hasSubscriptions = true
break
}
}
if hasSubscriptions {
schema.WriteString(" subscription: Subscription\n")
}
schema.WriteString("}\n\n")
// Add types by reading SDL files
for _, sg := range subgraphs {
if schemaFile, ok := sg.Schema["file"]; ok {
if sdlData, err := os.ReadFile(schemaFile); err == nil {
schema.WriteString(string(sdlData))
schema.WriteString("\n")
}
}
}
return schema.String()
}
func TestGenerateCosmoRouterConfig(t *testing.T) {
tests := []struct {
name string
subGraphs []*model.SubGraph
wantErr bool
validate func(t *testing.T, config string)
}{
{
name: "single subgraph with all fields",
subGraphs: []*model.SubGraph{
{
Service: "test-service",
URL: stringPtr("http://localhost:4001/query"),
WsURL: stringPtr("ws://localhost:4001/query"),
Sdl: "type Query { test: String }",
},
},
wantErr: false,
validate: func(t *testing.T, config string) {
var result map[string]interface{}
err := json.Unmarshal([]byte(config), &result)
require.NoError(t, err, "Config should be valid JSON")
// Version is a UUID string from wgc
version, ok := result["version"].(string)
require.True(t, ok, "Version should be a string")
assert.NotEmpty(t, version, "Version should not be empty")
subgraphs, ok := result["subgraphs"].([]interface{})
require.True(t, ok, "subgraphs should be an array")
require.Len(t, subgraphs, 1, "Should have 1 subgraph")
sg := subgraphs[0].(map[string]interface{})
assert.Equal(t, "test-service", sg["name"])
assert.Equal(t, "http://localhost:4001/query", sg["routingUrl"])
// Check that datasource configurations include subscription settings
engineConfig, ok := result["engineConfig"].(map[string]interface{})
require.True(t, ok, "Should have engineConfig")
dsConfigs, ok := engineConfig["datasourceConfigurations"].([]interface{})
require.True(t, ok && len(dsConfigs) > 0, "Should have datasource configurations")
ds := dsConfigs[0].(map[string]interface{})
customGraphql, ok := ds["customGraphql"].(map[string]interface{})
require.True(t, ok, "Should have customGraphql config")
subscription, ok := customGraphql["subscription"].(map[string]interface{})
require.True(t, ok, "Should have subscription config")
assert.True(t, subscription["enabled"].(bool), "Subscription should be enabled")
subUrl, ok := subscription["url"].(map[string]interface{})
require.True(t, ok, "Should have subscription URL")
assert.Equal(t, "ws://localhost:4001/query", subUrl["staticVariableContent"])
},
},
{
name: "multiple subgraphs",
subGraphs: []*model.SubGraph{
{
Service: "service-1",
URL: stringPtr("http://localhost:4001/query"),
Sdl: "type Query { field1: String }",
},
{
Service: "service-2",
URL: stringPtr("http://localhost:4002/query"),
Sdl: "type Query { field2: String }",
},
{
Service: "service-3",
URL: stringPtr("http://localhost:4003/query"),
WsURL: stringPtr("ws://localhost:4003/query"),
Sdl: "type Subscription { updates: String }",
},
},
wantErr: false,
validate: func(t *testing.T, config string) {
var result map[string]interface{}
err := json.Unmarshal([]byte(config), &result)
require.NoError(t, err)
subgraphs := result["subgraphs"].([]interface{})
assert.Len(t, subgraphs, 3, "Should have 3 subgraphs")
// Check service names
sg1 := subgraphs[0].(map[string]interface{})
assert.Equal(t, "service-1", sg1["name"])
sg3 := subgraphs[2].(map[string]interface{})
assert.Equal(t, "service-3", sg3["name"])
// Check that datasource configurations include subscription for service-3
engineConfig, ok := result["engineConfig"].(map[string]interface{})
require.True(t, ok, "Should have engineConfig")
dsConfigs, ok := engineConfig["datasourceConfigurations"].([]interface{})
require.True(t, ok && len(dsConfigs) == 3, "Should have 3 datasource configurations")
// Find service-3's datasource config (should have subscription enabled)
ds3 := dsConfigs[2].(map[string]interface{})
customGraphql, ok := ds3["customGraphql"].(map[string]interface{})
require.True(t, ok, "Service-3 should have customGraphql config")
subscription, ok := customGraphql["subscription"].(map[string]interface{})
require.True(t, ok, "Service-3 should have subscription config")
assert.True(t, subscription["enabled"].(bool), "Service-3 subscription should be enabled")
},
},
{
name: "subgraph with no URL",
subGraphs: []*model.SubGraph{
{
Service: "test-service",
URL: nil,
WsURL: nil,
Sdl: "type Query { test: String }",
},
},
wantErr: false,
validate: func(t *testing.T, config string) {
var result map[string]interface{}
err := json.Unmarshal([]byte(config), &result)
require.NoError(t, err)
subgraphs := result["subgraphs"].([]interface{})
sg := subgraphs[0].(map[string]interface{})
// Should not have routing URL when URL is nil
_, hasRoutingURL := sg["routingUrl"]
assert.False(t, hasRoutingURL, "Should not have routingUrl when URL is nil")
// Check datasource configurations don't have subscription enabled
engineConfig, ok := result["engineConfig"].(map[string]interface{})
require.True(t, ok, "Should have engineConfig")
dsConfigs, ok := engineConfig["datasourceConfigurations"].([]interface{})
require.True(t, ok && len(dsConfigs) > 0, "Should have datasource configurations")
ds := dsConfigs[0].(map[string]interface{})
customGraphql, ok := ds["customGraphql"].(map[string]interface{})
require.True(t, ok, "Should have customGraphql config")
subscription, ok := customGraphql["subscription"].(map[string]interface{})
if ok {
// wgc always enables subscription but URL should be empty when WsURL is nil
subUrl, hasUrl := subscription["url"].(map[string]interface{})
if hasUrl {
_, hasStaticContent := subUrl["staticVariableContent"]
assert.False(t, hasStaticContent, "Subscription URL should be empty when WsURL is nil")
}
}
},
},
{
name: "empty subgraphs",
subGraphs: []*model.SubGraph{},
wantErr: true,
validate: nil,
},
{
name: "nil subgraphs",
subGraphs: nil,
wantErr: true,
validate: nil,
},
{
name: "complex SDL with multiple types",
subGraphs: []*model.SubGraph{
{
Service: "complex-service",
URL: stringPtr("http://localhost:4001/query"),
Sdl: `
type Query {
user(id: ID!): User
users: [User!]!
}
type User {
id: ID!
name: String!
email: String!
}
`,
},
},
wantErr: false,
validate: func(t *testing.T, config string) {
var result map[string]interface{}
err := json.Unmarshal([]byte(config), &result)
require.NoError(t, err)
// Check the composed graphqlSchema contains the types
engineConfig, ok := result["engineConfig"].(map[string]interface{})
require.True(t, ok, "Should have engineConfig")
graphqlSchema, ok := engineConfig["graphqlSchema"].(string)
require.True(t, ok, "Should have graphqlSchema")
assert.Contains(t, graphqlSchema, "Query", "Schema should contain Query type")
assert.Contains(t, graphqlSchema, "User", "Schema should contain User type")
// Check datasource has the original SDL
dsConfigs, ok := engineConfig["datasourceConfigurations"].([]interface{})
require.True(t, ok && len(dsConfigs) > 0, "Should have datasource configurations")
ds := dsConfigs[0].(map[string]interface{})
customGraphql, ok := ds["customGraphql"].(map[string]interface{})
require.True(t, ok, "Should have customGraphql config")
federation, ok := customGraphql["federation"].(map[string]interface{})
require.True(t, ok, "Should have federation config")
serviceSdl, ok := federation["serviceSdl"].(string)
require.True(t, ok, "Should have serviceSdl")
assert.Contains(t, serviceSdl, "email: String!", "SDL should contain email field")
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Use mock executor for all tests
mockExecutor := &MockCommandExecutor{}
config, err := GenerateCosmoRouterConfigWithExecutor(tt.subGraphs, mockExecutor)
if tt.wantErr {
assert.Error(t, err)
// Verify executor was not called for error cases
if len(tt.subGraphs) == 0 {
assert.Equal(t, 0, mockExecutor.CallCount, "Should not call executor for empty subgraphs")
}
return
}
require.NoError(t, err)
assert.NotEmpty(t, config, "Config should not be empty")
// Verify executor was called correctly
assert.Equal(t, 1, mockExecutor.CallCount, "Should call executor once")
assert.Equal(t, "wgc", mockExecutor.LastArgs[0], "Should call wgc command")
assert.Contains(t, mockExecutor.LastArgs, "router", "Should include 'router' arg")
assert.Contains(t, mockExecutor.LastArgs, "compose", "Should include 'compose' arg")
if tt.validate != nil {
tt.validate(t, config)
}
})
}
}
// TestGenerateCosmoRouterConfig_MockError tests error handling with mock executor
func TestGenerateCosmoRouterConfig_MockError(t *testing.T) {
subGraphs := []*model.SubGraph{
{
Service: "test-service",
URL: stringPtr("http://localhost:4001/query"),
Sdl: "type Query { test: String }",
},
}
// Create a mock executor that returns an error
mockExecutor := &MockCommandExecutor{
Error: fmt.Errorf("simulated wgc failure"),
}
config, err := GenerateCosmoRouterConfigWithExecutor(subGraphs, mockExecutor)
// Verify error is propagated
assert.Error(t, err)
assert.Contains(t, err.Error(), "wgc router compose failed")
assert.Contains(t, err.Error(), "simulated wgc failure")
assert.Empty(t, config)
// Verify executor was called
assert.Equal(t, 1, mockExecutor.CallCount, "Should have attempted to call executor")
}
// SlowMockExecutor simulates a slow wgc command for concurrency testing.
type SlowMockExecutor struct {
MockCommandExecutor
delay time.Duration
mu sync.Mutex
concurrent atomic.Int32
maxSeen atomic.Int32
}
func (m *SlowMockExecutor) Execute(name string, args ...string) ([]byte, error) {
cur := m.concurrent.Add(1)
// Track the maximum concurrent executions observed.
for {
old := m.maxSeen.Load()
if cur <= old || m.maxSeen.CompareAndSwap(old, cur) {
break
}
}
defer m.concurrent.Add(-1)
time.Sleep(m.delay)
m.mu.Lock()
defer m.mu.Unlock()
return m.MockCommandExecutor.Execute(name, args...)
}
func TestCosmoGenerator_ConcurrencyLimit(t *testing.T) {
executor := &SlowMockExecutor{delay: 100 * time.Millisecond}
gen := NewCosmoGenerator(executor, 5*time.Second)
subGraphs := []*model.SubGraph{
{
Service: "svc",
URL: stringPtr("http://localhost:4001/query"),
Sdl: "type Query { hello: String }",
},
}
var wg sync.WaitGroup
for range 5 {
wg.Add(1)
go func() {
defer wg.Done()
_, _ = gen.Generate(context.Background(), subGraphs)
}()
}
wg.Wait()
assert.Equal(t, int32(1), executor.maxSeen.Load(),
"at most 1 wgc process should run concurrently")
}
func TestCosmoGenerator_Timeout(t *testing.T) {
// Executor that takes longer than the timeout.
executor := &SlowMockExecutor{delay: 500 * time.Millisecond}
gen := NewCosmoGenerator(executor, 50*time.Millisecond)
subGraphs := []*model.SubGraph{
{
Service: "svc",
URL: stringPtr("http://localhost:4001/query"),
Sdl: "type Query { hello: String }",
},
}
// First call: occupies the semaphore for 500ms.
go func() {
_, _ = gen.Generate(context.Background(), subGraphs)
}()
// Give the first goroutine time to acquire the semaphore.
time.Sleep(20 * time.Millisecond)
// Second call: should timeout waiting for the semaphore.
_, err := gen.Generate(context.Background(), subGraphs)
require.Error(t, err)
assert.Contains(t, err.Error(), "acquire cosmo generator")
}
func TestCosmoGenerator_ContextCancellation(t *testing.T) {
executor := &SlowMockExecutor{delay: 500 * time.Millisecond}
gen := NewCosmoGenerator(executor, 5*time.Second)
subGraphs := []*model.SubGraph{
{
Service: "svc",
URL: stringPtr("http://localhost:4001/query"),
Sdl: "type Query { hello: String }",
},
}
// First call: occupies the semaphore.
go func() {
_, _ = gen.Generate(context.Background(), subGraphs)
}()
time.Sleep(20 * time.Millisecond)
// Second call with an already-cancelled context.
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := gen.Generate(ctx, subGraphs)
require.Error(t, err)
assert.Contains(t, err.Error(), "acquire cosmo generator")
}
// Helper function for tests
func stringPtr(s string) *string {
return &s
}
+42
View File
@@ -0,0 +1,42 @@
package graph
import (
"sync"
"time"
)
// Debouncer coalesces rapid calls with the same key, executing only the last
// one after a configurable delay. This prevents redundant work when multiple
// updates arrive in quick succession (e.g., rapid schema publishing).
type Debouncer struct {
mu sync.Mutex
delay time.Duration
timers map[string]*time.Timer
}
// NewDebouncer creates a Debouncer with the given delay window.
func NewDebouncer(delay time.Duration) *Debouncer {
return &Debouncer{
delay: delay,
timers: make(map[string]*time.Timer),
}
}
// Debounce resets the timer for key. When the timer fires (after delay with no
// new calls for the same key), fn is executed in a new goroutine.
func (d *Debouncer) Debounce(key string, fn func()) {
d.mu.Lock()
defer d.mu.Unlock()
if t, ok := d.timers[key]; ok {
t.Stop()
}
d.timers[key] = time.AfterFunc(d.delay, func() {
d.mu.Lock()
delete(d.timers, key)
d.mu.Unlock()
fn()
})
}
+69
View File
@@ -0,0 +1,69 @@
package graph
import (
"sync/atomic"
"testing"
"testing/synctest"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDebouncer_Coalesces(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
d := NewDebouncer(50 * time.Millisecond)
var calls atomic.Int32
// Fire 10 rapid calls for the same key — only the last should execute.
for range 10 {
d.Debounce("key1", func() {
calls.Add(1)
})
}
// Advance fake clock past the debounce delay and let goroutines settle.
time.Sleep(50 * time.Millisecond)
synctest.Wait()
assert.Equal(t, int32(1), calls.Load(), "rapid calls should coalesce into a single execution")
})
}
func TestDebouncer_DifferentKeys(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
d := NewDebouncer(50 * time.Millisecond)
var calls atomic.Int32
d.Debounce("key-a", func() { calls.Add(1) })
d.Debounce("key-b", func() { calls.Add(1) })
d.Debounce("key-c", func() { calls.Add(1) })
time.Sleep(50 * time.Millisecond)
synctest.Wait()
assert.Equal(t, int32(3), calls.Load(), "different keys should fire independently")
})
}
func TestDebouncer_TimerReset(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
d := NewDebouncer(100 * time.Millisecond)
var value atomic.Int32
// First call sets value to 1.
d.Debounce("key", func() { value.Store(1) })
// Advance 60ms (less than the 100ms delay) — first timer hasn't fired.
time.Sleep(60 * time.Millisecond)
// Replace with value 2 — resets the timer to fire at 60+100 = 160ms.
d.Debounce("key", func() { value.Store(2) })
// Advance another 100ms (total 160ms) to fire the reset timer.
time.Sleep(100 * time.Millisecond)
synctest.Wait()
require.Equal(t, int32(2), value.Load(), "later call should replace the earlier one")
})
}
+2089 -2796
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -49,6 +49,13 @@ type Organization struct {
type Query struct {
}
type SchemaUpdate struct {
Ref string `json:"ref"`
ID string `json:"id"`
SubGraphs []*SubGraph `json:"subGraphs"`
CosmoRouterConfig *string `json:"cosmoRouterConfig,omitempty"`
}
type SubGraph struct {
ID string `json:"id"`
Service string `json:"service"`
@@ -68,6 +75,9 @@ type SubGraphs struct {
func (SubGraphs) IsSupergraph() {}
type Subscription struct {
}
type Unchanged struct {
ID string `json:"id"`
MinDelaySeconds int `json:"minDelaySeconds"`
+66
View File
@@ -0,0 +1,66 @@
package graph
import (
"sync"
"gitea.unbound.se/unboundsoftware/schemas/graph/model"
)
// PubSub handles publishing schema updates to subscribers
type PubSub struct {
mu sync.RWMutex
subscribers map[string][]chan *model.SchemaUpdate
}
func NewPubSub() *PubSub {
return &PubSub{
subscribers: make(map[string][]chan *model.SchemaUpdate),
}
}
// Subscribe creates a new subscription channel for a given schema ref
func (ps *PubSub) Subscribe(ref string) chan *model.SchemaUpdate {
ps.mu.Lock()
defer ps.mu.Unlock()
ch := make(chan *model.SchemaUpdate, 10)
ps.subscribers[ref] = append(ps.subscribers[ref], ch)
return ch
}
// Unsubscribe removes a subscription channel
func (ps *PubSub) Unsubscribe(ref string, ch chan *model.SchemaUpdate) {
ps.mu.Lock()
defer ps.mu.Unlock()
subs := ps.subscribers[ref]
for i, sub := range subs {
if sub == ch {
// Remove this subscriber
ps.subscribers[ref] = append(subs[:i], subs[i+1:]...)
close(sub)
break
}
}
// Clean up empty subscriber lists
if len(ps.subscribers[ref]) == 0 {
delete(ps.subscribers, ref)
}
}
// Publish sends a schema update to all subscribers of a given ref
func (ps *PubSub) Publish(ref string, update *model.SchemaUpdate) {
ps.mu.RLock()
defer ps.mu.RUnlock()
for _, ch := range ps.subscribers[ref] {
// Non-blocking send - if subscriber is slow, skip
select {
case ch <- update:
default:
// Channel full, subscriber is too slow - skip this update
}
}
}
+256
View File
@@ -0,0 +1,256 @@
package graph
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gitea.unbound.se/unboundsoftware/schemas/graph/model"
)
func TestPubSub_SubscribeAndPublish(t *testing.T) {
ps := NewPubSub()
ref := "Test@dev"
// Subscribe
ch := ps.Subscribe(ref)
require.NotNil(t, ch, "Subscribe should return a channel")
// Publish
update := &model.SchemaUpdate{
Ref: ref,
ID: "test-id-1",
SubGraphs: []*model.SubGraph{
{
ID: "sg1",
Service: "test-service",
Sdl: "type Query { test: String }",
},
},
}
go ps.Publish(ref, update)
// Receive
select {
case received := <-ch:
assert.Equal(t, update.Ref, received.Ref, "Ref should match")
assert.Equal(t, update.ID, received.ID, "ID should match")
assert.Equal(t, len(update.SubGraphs), len(received.SubGraphs), "SubGraphs count should match")
case <-time.After(1 * time.Second):
t.Fatal("Timeout waiting for published update")
}
}
func TestPubSub_MultipleSubscribers(t *testing.T) {
ps := NewPubSub()
ref := "Test@dev"
// Create multiple subscribers
ch1 := ps.Subscribe(ref)
ch2 := ps.Subscribe(ref)
ch3 := ps.Subscribe(ref)
update := &model.SchemaUpdate{
Ref: ref,
ID: "test-id-2",
}
// Publish once
ps.Publish(ref, update)
// All subscribers should receive the update
var wg sync.WaitGroup
wg.Add(3)
checkReceived := func(ch <-chan *model.SchemaUpdate, name string) {
defer wg.Done()
select {
case received := <-ch:
assert.Equal(t, update.ID, received.ID, "%s should receive correct update", name)
case <-time.After(1 * time.Second):
t.Errorf("%s: Timeout waiting for update", name)
}
}
go checkReceived(ch1, "Subscriber 1")
go checkReceived(ch2, "Subscriber 2")
go checkReceived(ch3, "Subscriber 3")
wg.Wait()
}
func TestPubSub_DifferentRefs(t *testing.T) {
ps := NewPubSub()
ref1 := "Test1@dev"
ref2 := "Test2@dev"
ch1 := ps.Subscribe(ref1)
ch2 := ps.Subscribe(ref2)
update1 := &model.SchemaUpdate{Ref: ref1, ID: "id1"}
update2 := &model.SchemaUpdate{Ref: ref2, ID: "id2"}
// Publish to ref1
ps.Publish(ref1, update1)
// Only ch1 should receive
select {
case received := <-ch1:
assert.Equal(t, "id1", received.ID)
case <-time.After(100 * time.Millisecond):
t.Fatal("ch1 should have received update")
}
// ch2 should not receive ref1's update
select {
case <-ch2:
t.Fatal("ch2 should not receive ref1's update")
case <-time.After(100 * time.Millisecond):
// Expected - no update
}
// Publish to ref2
ps.Publish(ref2, update2)
// Now ch2 should receive
select {
case received := <-ch2:
assert.Equal(t, "id2", received.ID)
case <-time.After(100 * time.Millisecond):
t.Fatal("ch2 should have received update")
}
}
func TestPubSub_Unsubscribe(t *testing.T) {
ps := NewPubSub()
ref := "Test@dev"
ch := ps.Subscribe(ref)
// Unsubscribe
ps.Unsubscribe(ref, ch)
// Channel should be closed
_, ok := <-ch
assert.False(t, ok, "Channel should be closed after unsubscribe")
// Publishing after unsubscribe should not panic
assert.NotPanics(t, func() {
ps.Publish(ref, &model.SchemaUpdate{Ref: ref})
})
}
func TestPubSub_BufferedChannel(t *testing.T) {
ps := NewPubSub()
ref := "Test@dev"
ch := ps.Subscribe(ref)
// Publish multiple updates quickly (up to buffer size of 10)
for i := 0; i < 10; i++ {
update := &model.SchemaUpdate{
Ref: ref,
ID: string(rune('a' + i)),
}
ps.Publish(ref, update)
}
// All 10 should be buffered and receivable
received := 0
timeout := time.After(1 * time.Second)
for received < 10 {
select {
case <-ch:
received++
case <-timeout:
t.Fatalf("Only received %d out of 10 updates", received)
}
}
assert.Equal(t, 10, received, "Should receive all buffered updates")
}
func TestPubSub_SlowSubscriber(t *testing.T) {
ps := NewPubSub()
ref := "Test@dev"
ch := ps.Subscribe(ref)
// Fill the buffer (10 items)
for i := 0; i < 10; i++ {
ps.Publish(ref, &model.SchemaUpdate{Ref: ref})
}
// Publish one more - this should be dropped (channel full, non-blocking send)
ps.Publish(ref, &model.SchemaUpdate{Ref: ref, ID: "should-be-dropped"})
// Drain the channel
count := 0
timeout := time.After(500 * time.Millisecond)
drainLoop:
for {
select {
case update := <-ch:
count++
// Should not receive the dropped update
assert.NotEqual(t, "should-be-dropped", update.ID, "Should not receive dropped update")
case <-timeout:
break drainLoop
}
}
// Should have received exactly 10 (the buffer size), not 11
assert.Equal(t, 10, count, "Should only receive buffered updates, not the dropped one")
}
func TestPubSub_ConcurrentPublish(t *testing.T) {
ps := NewPubSub()
ref := "Test@dev"
ch := ps.Subscribe(ref)
numPublishers := 10
updatesPerPublisher := 10
var wg sync.WaitGroup
wg.Add(numPublishers)
// Multiple goroutines publishing concurrently
for i := 0; i < numPublishers; i++ {
go func(publisherID int) {
defer wg.Done()
for j := 0; j < updatesPerPublisher; j++ {
ps.Publish(ref, &model.SchemaUpdate{
Ref: ref,
ID: string(rune('a' + publisherID)),
})
}
}(i)
}
wg.Wait()
// Should not panic and subscriber should receive updates
// (exact count may vary due to buffer and timing)
timeout := time.After(1 * time.Second)
received := 0
receiveLoop:
for {
select {
case <-ch:
received++
case <-timeout:
break receiveLoop
}
}
assert.Greater(t, received, 0, "Should have received some updates")
}
+10 -7
View File
@@ -7,13 +7,13 @@ import (
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitlab.com/unboundsoftware/schemas/cache"
"gitlab.com/unboundsoftware/schemas/middleware"
"gitea.unbound.se/unboundsoftware/schemas/cache"
"gitea.unbound.se/unboundsoftware/schemas/middleware"
)
//go:generate go run github.com/99designs/gqlgen
//go:generate gofumpt -w .
//go:generate goimports -w -local gitlab.com/unboundsoftware/schemas .
//go:generate goimports -w -local gitea.unbound.se/unboundsoftware/schemas .
// This file will not be regenerated automatically.
//
@@ -24,10 +24,13 @@ type Publisher interface {
}
type Resolver struct {
EventStore eventsourced.EventStore
Publisher Publisher
Logger *slog.Logger
Cache *cache.Cache
EventStore eventsourced.EventStore
Publisher Publisher
Logger *slog.Logger
Cache *cache.Cache
PubSub *PubSub
CosmoGenerator *CosmoGenerator
Debouncer *Debouncer
}
func (r *Resolver) apiKeyCanAccessRef(ctx context.Context, ref string, publish bool) (string, error) {
+17 -1
View File
@@ -1,14 +1,23 @@
type Query {
organizations: [Organization!]! @auth(user: true)
supergraph(ref: String!, isAfter: String): Supergraph! @auth(organization: true)
allOrganizations: [Organization!]! @auth(user: true)
supergraph(ref: String!, isAfter: String): Supergraph! @auth(user: true, organization: true)
latestSchema(ref: String!): SchemaUpdate! @auth(user: true, organization: true)
}
type Mutation {
addOrganization(name: String!): Organization! @auth(user: true)
addUserToOrganization(organizationId: ID!, userId: String!): Organization! @auth(user: true)
addAPIKey(input: InputAPIKey): APIKey! @auth(user: true)
removeAPIKey(organizationId: ID!, keyName: String!): Organization! @auth(user: true)
removeOrganization(organizationId: ID!): Boolean! @auth(user: true)
updateSubGraph(input: InputSubGraph!): SubGraph! @auth(organization: true)
}
type Subscription {
schemaUpdates(ref: String!): SchemaUpdate! @auth(organization: true)
}
type Organization {
id: ID!
name: String!
@@ -54,6 +63,13 @@ type SubGraph {
changedAt: Time!
}
type SchemaUpdate {
ref: String!
id: ID!
subGraphs: [SubGraph!]!
cosmoRouterConfig: String
}
input InputAPIKey {
name: String!
organizationId: ID!
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitlab.com/unboundsoftware/schemas/domain"
"gitlab.com/unboundsoftware/schemas/graph/model"
"gitea.unbound.se/unboundsoftware/schemas/domain"
"gitea.unbound.se/unboundsoftware/schemas/graph/model"
)
func (r *Resolver) fetchSubGraph(ctx context.Context, subGraphId string) (*domain.SubGraph, error) {
+314 -27
View File
@@ -1,6 +1,7 @@
package graph
// This file will be automatically regenerated based on the schema, any resolver implementations
// This file will be automatically regenerated based on the schema, any resolver
// implementations
// will be copied through when generating and any unknown code will be moved to the end.
// Code generated by github.com/99designs/gqlgen
@@ -11,12 +12,12 @@ import (
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitlab.com/unboundsoftware/schemas/domain"
"gitlab.com/unboundsoftware/schemas/graph/generated"
"gitlab.com/unboundsoftware/schemas/graph/model"
"gitlab.com/unboundsoftware/schemas/middleware"
"gitlab.com/unboundsoftware/schemas/rand"
"gitlab.com/unboundsoftware/schemas/sdlmerge"
"gitea.unbound.se/unboundsoftware/schemas/domain"
"gitea.unbound.se/unboundsoftware/schemas/graph/generated"
"gitea.unbound.se/unboundsoftware/schemas/graph/model"
"gitea.unbound.se/unboundsoftware/schemas/middleware"
"gitea.unbound.se/unboundsoftware/schemas/rand"
"gitea.unbound.se/unboundsoftware/schemas/sdlmerge"
)
// AddOrganization is the resolver for the addOrganization field.
@@ -37,6 +38,24 @@ func (r *mutationResolver) AddOrganization(ctx context.Context, name string) (*m
return ToGqlOrganization(*org), nil
}
// AddUserToOrganization is the resolver for the addUserToOrganization field.
func (r *mutationResolver) AddUserToOrganization(ctx context.Context, organizationID string, userID string) (*model.Organization, error) {
sub := middleware.UserFromContext(ctx)
org := &domain.Organization{BaseAggregate: eventsourced.BaseAggregateFromString(organizationID)}
h, err := r.handler(ctx, org)
if err != nil {
return nil, err
}
_, err = h.Handle(ctx, &domain.AddUserToOrganization{
UserId: userID,
Initiator: sub,
})
if err != nil {
return nil, err
}
return ToGqlOrganization(*org), nil
}
// AddAPIKey is the resolver for the addAPIKey field.
func (r *mutationResolver) AddAPIKey(ctx context.Context, input *model.InputAPIKey) (*model.APIKey, error) {
sub := middleware.UserFromContext(ctx)
@@ -71,6 +90,41 @@ func (r *mutationResolver) AddAPIKey(ctx context.Context, input *model.InputAPIK
}, nil
}
// RemoveAPIKey is the resolver for the removeAPIKey field.
func (r *mutationResolver) RemoveAPIKey(ctx context.Context, organizationID string, keyName string) (*model.Organization, error) {
sub := middleware.UserFromContext(ctx)
org := &domain.Organization{BaseAggregate: eventsourced.BaseAggregateFromString(organizationID)}
h, err := r.handler(ctx, org)
if err != nil {
return nil, err
}
_, err = h.Handle(ctx, &domain.RemoveAPIKey{
KeyName: keyName,
Initiator: sub,
})
if err != nil {
return nil, err
}
return ToGqlOrganization(*org), nil
}
// RemoveOrganization is the resolver for the removeOrganization field.
func (r *mutationResolver) RemoveOrganization(ctx context.Context, organizationID string) (bool, error) {
sub := middleware.UserFromContext(ctx)
org := &domain.Organization{BaseAggregate: eventsourced.BaseAggregateFromString(organizationID)}
h, err := r.handler(ctx, org)
if err != nil {
return false, err
}
_, err = h.Handle(ctx, &domain.RemoveOrganization{
Initiator: sub,
})
if err != nil {
return false, err
}
return true, nil
}
// UpdateSubGraph is the resolver for the updateSubGraph field.
func (r *mutationResolver) UpdateSubGraph(ctx context.Context, input model.InputSubGraph) (*model.SubGraph, error) {
orgId := middleware.OrganizationFromContext(ctx)
@@ -119,6 +173,53 @@ func (r *mutationResolver) UpdateSubGraph(ctx context.Context, input model.Input
if err != nil {
return nil, err
}
// Debounce schema update publishing so rapid successive updates for the
// same org+ref only trigger one config generation.
r.Debouncer.Debounce(orgId+":"+input.Ref, func() {
services, lastUpdate := r.Cache.Services(orgId, input.Ref, "")
r.Logger.Info("Publishing schema update after subgraph change",
"ref", input.Ref,
"orgId", orgId,
"lastUpdate", lastUpdate,
"servicesCount", len(services),
)
subGraphs := make([]*model.SubGraph, len(services))
for i, id := range services {
sg, err := r.fetchSubGraph(context.Background(), id)
if err != nil {
r.Logger.Error("fetch subgraph for update notification", "error", err)
continue
}
subGraphs[i] = r.toGqlSubGraph(sg)
}
// Generate Cosmo router config (concurrency-limited)
cosmoConfig, err := r.CosmoGenerator.Generate(context.Background(), subGraphs)
if err != nil {
r.Logger.Error("generate cosmo config for update", "error", err)
cosmoConfig = "" // Send empty if generation fails
}
// Publish to all subscribers of this ref
update := &model.SchemaUpdate{
Ref: input.Ref,
ID: lastUpdate,
SubGraphs: subGraphs,
CosmoRouterConfig: &cosmoConfig,
}
r.Logger.Info("Publishing schema update to subscribers",
"ref", update.Ref,
"id", update.ID,
"subGraphsCount", len(update.SubGraphs),
"cosmoConfigLength", len(cosmoConfig),
)
r.PubSub.Publish(input.Ref, update)
})
return r.toGqlSubGraph(subGraph), nil
}
@@ -129,13 +230,49 @@ func (r *queryResolver) Organizations(ctx context.Context) ([]*model.Organizatio
return ToGqlOrganizations(orgs), nil
}
// AllOrganizations is the resolver for the allOrganizations field.
func (r *queryResolver) AllOrganizations(ctx context.Context) ([]*model.Organization, error) {
// Check if user has admin role
if !middleware.UserHasRole(ctx, "admin") {
return nil, fmt.Errorf("unauthorized: admin role required")
}
orgs := r.Cache.AllOrganizations()
return ToGqlOrganizations(orgs), nil
}
// Supergraph is the resolver for the supergraph field.
func (r *queryResolver) Supergraph(ctx context.Context, ref string, isAfter *string) (model.Supergraph, error) {
orgId := middleware.OrganizationFromContext(ctx)
_, err := r.apiKeyCanAccessRef(ctx, ref, false)
if err != nil {
return nil, err
userId := middleware.UserFromContext(ctx)
r.Logger.Info("Supergraph query",
"ref", ref,
"orgId", orgId,
"userId", userId,
)
// If authenticated with API key (organization), check access
if orgId != "" {
_, err := r.apiKeyCanAccessRef(ctx, ref, false)
if err != nil {
r.Logger.Error("API key cannot access ref", "error", err, "ref", ref)
return nil, err
}
} else if userId != "" {
// For user authentication, check if user has access to ref through their organizations
userOrgs := r.Cache.OrganizationsByUser(userId)
if len(userOrgs) == 0 {
r.Logger.Error("User has no organizations", "userId", userId)
return nil, fmt.Errorf("user has no access to any organizations")
}
// Use the first organization's ID for querying
orgId = userOrgs[0].ID.String()
r.Logger.Info("Using organization from user context", "orgId", orgId)
} else {
return nil, fmt.Errorf("no authentication provided")
}
after := ""
if isAfter != nil {
after = *isAfter
@@ -148,11 +285,77 @@ func (r *queryResolver) Supergraph(ctx context.Context, ref string, isAfter *str
}, nil
}
subGraphs := make([]*model.SubGraph, len(services))
serviceSDLs := make([]string, len(services))
for i, id := range services {
sg, err := r.fetchSubGraph(ctx, id)
if err != nil {
return nil, err
}
subGraphs[i] = r.toGqlSubGraph(sg)
serviceSDLs[i] = sg.Sdl
}
sdl, err := sdlmerge.MergeSDLs(serviceSDLs...)
if err != nil {
return nil, err
}
return &model.SubGraphs{
ID: lastUpdate,
SubGraphs: subGraphs,
Sdl: sdl,
MinDelaySeconds: 10,
}, nil
}
// LatestSchema is the resolver for the latestSchema field.
func (r *queryResolver) LatestSchema(ctx context.Context, ref string) (*model.SchemaUpdate, error) {
orgId := middleware.OrganizationFromContext(ctx)
userId := middleware.UserFromContext(ctx)
r.Logger.Info("LatestSchema query",
"ref", ref,
"orgId", orgId,
"userId", userId,
)
// If authenticated with API key (organization), check access
if orgId != "" {
_, err := r.apiKeyCanAccessRef(ctx, ref, false)
if err != nil {
r.Logger.Error("API key cannot access ref", "error", err, "ref", ref)
return nil, err
}
} else if userId != "" {
// For user authentication, check if user has access to ref through their organizations
userOrgs := r.Cache.OrganizationsByUser(userId)
if len(userOrgs) == 0 {
r.Logger.Error("User has no organizations", "userId", userId)
return nil, fmt.Errorf("user has no access to any organizations")
}
// Use the first organization's ID for querying
// In a real-world scenario, you might want to check which org has access to this ref
orgId = userOrgs[0].ID.String()
r.Logger.Info("Using organization from user context", "orgId", orgId)
} else {
return nil, fmt.Errorf("no authentication provided")
}
// Get current services and schema
services, lastUpdate := r.Cache.Services(orgId, ref, "")
r.Logger.Info("Fetching latest schema",
"ref", ref,
"orgId", orgId,
"lastUpdate", lastUpdate,
"servicesCount", len(services),
)
subGraphs := make([]*model.SubGraph, len(services))
for i, id := range services {
sg, err := r.fetchSubGraph(ctx, id)
if err != nil {
r.Logger.Error("fetch subgraph", "error", err, "id", id)
return nil, err
}
subGraphs[i] = &model.SubGraph{
ID: sg.ID.String(),
Service: sg.Service,
@@ -164,24 +367,104 @@ func (r *queryResolver) Supergraph(ctx context.Context, ref string, isAfter *str
}
}
var serviceSDLs []string
for _, id := range services {
sg, err := r.fetchSubGraph(ctx, id)
if err != nil {
return nil, err
}
serviceSDLs = append(serviceSDLs, sg.Sdl)
}
sdl, err := sdlmerge.MergeSDLs(serviceSDLs...)
// Generate Cosmo router config (concurrency-limited)
cosmoConfig, err := r.CosmoGenerator.Generate(ctx, subGraphs)
if err != nil {
r.Logger.Error("generate cosmo config", "error", err)
cosmoConfig = "" // Return empty if generation fails
}
update := &model.SchemaUpdate{
Ref: ref,
ID: lastUpdate,
SubGraphs: subGraphs,
CosmoRouterConfig: &cosmoConfig,
}
r.Logger.Info("Latest schema fetched",
"ref", update.Ref,
"id", update.ID,
"subGraphsCount", len(update.SubGraphs),
"cosmoConfigLength", len(cosmoConfig),
)
return update, nil
}
// SchemaUpdates is the resolver for the schemaUpdates field.
func (r *subscriptionResolver) SchemaUpdates(ctx context.Context, ref string) (<-chan *model.SchemaUpdate, error) {
orgId := middleware.OrganizationFromContext(ctx)
r.Logger.Info("SchemaUpdates subscription started",
"ref", ref,
"orgId", orgId,
)
_, err := r.apiKeyCanAccessRef(ctx, ref, false)
if err != nil {
r.Logger.Error("API key cannot access ref", "error", err, "ref", ref)
return nil, err
}
return &model.SubGraphs{
ID: lastUpdate,
SubGraphs: subGraphs,
Sdl: sdl,
MinDelaySeconds: 10,
}, nil
// Subscribe to updates for this ref
ch := r.PubSub.Subscribe(ref)
// Send initial state immediately
go func() {
services, lastUpdate := r.Cache.Services(orgId, ref, "")
r.Logger.Info("Preparing initial schema update",
"ref", ref,
"orgId", orgId,
"lastUpdate", lastUpdate,
"servicesCount", len(services),
)
subGraphs := make([]*model.SubGraph, len(services))
for i, id := range services {
sg, err := r.fetchSubGraph(ctx, id)
if err != nil {
r.Logger.Error("fetch subgraph for initial update", "error", err, "id", id)
continue
}
subGraphs[i] = r.toGqlSubGraph(sg)
}
// Generate Cosmo router config (concurrency-limited)
cosmoConfig, err := r.CosmoGenerator.Generate(ctx, subGraphs)
if err != nil {
r.Logger.Error("generate cosmo config", "error", err)
cosmoConfig = "" // Send empty if generation fails
}
// Send initial update
update := &model.SchemaUpdate{
Ref: ref,
ID: lastUpdate,
SubGraphs: subGraphs,
CosmoRouterConfig: &cosmoConfig,
}
r.Logger.Info("Sending initial schema update",
"ref", update.Ref,
"id", update.ID,
"subGraphsCount", len(update.SubGraphs),
"cosmoConfigLength", len(cosmoConfig),
)
select {
case ch <- update:
case <-ctx.Done():
return
}
}()
// Clean up subscription when context is done
go func() {
<-ctx.Done()
r.PubSub.Unsubscribe(ref, ch)
}()
return ch, nil
}
// Mutation returns generated.MutationResolver implementation.
@@ -190,7 +473,11 @@ func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResol
// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
// Subscription returns generated.SubscriptionResolver implementation.
func (r *Resolver) Subscription() generated.SubscriptionResolver { return &subscriptionResolver{r} }
type (
mutationResolver struct{ *Resolver }
queryResolver struct{ *Resolver }
mutationResolver struct{ *Resolver }
queryResolver struct{ *Resolver }
subscriptionResolver struct{ *Resolver }
)
-1
View File
@@ -1,5 +1,4 @@
//go:build tools
// +build tools
package graph
+63
View File
@@ -3,9 +3,72 @@ package hash
import (
"crypto/sha256"
"encoding/base64"
"golang.org/x/crypto/bcrypt"
)
// String creates a SHA256 hash of a string (legacy, for non-sensitive data)
func String(s string) string {
encoded := sha256.New().Sum([]byte(s))
return base64.StdEncoding.EncodeToString(encoded)
}
// APIKey hashes an API key using bcrypt for secure storage
// Cost of 12 provides a good balance between security and performance
func APIKey(key string) (string, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(key), 12)
if err != nil {
return "", err
}
return string(hash), nil
}
// CompareAPIKey compares a plaintext API key with a hash
// Supports both bcrypt (new) and SHA256 (legacy) hashes for backwards compatibility
// Returns true if they match, false otherwise
//
// Migration Strategy:
// Old API keys stored with SHA256 will continue to work. To upgrade them to bcrypt:
// 1. Keys are automatically upgraded when users re-authenticate (if implemented)
// 2. Or, run a one-time migration using MigrateAPIKeyHash when convenient
func CompareAPIKey(hashedKey, plainKey string) bool {
// Bcrypt hashes start with $2a$, $2b$, or $2y$
// If the hash starts with $2, it's a bcrypt hash
if len(hashedKey) > 2 && hashedKey[0] == '$' && hashedKey[1] == '2' {
// New bcrypt hash
err := bcrypt.CompareHashAndPassword([]byte(hashedKey), []byte(plainKey))
return err == nil
}
// Legacy SHA256 hash - compare using the old method
legacyHash := String(plainKey)
return hashedKey == legacyHash
}
// IsLegacyHash returns true if the hash is a legacy SHA256 hash (not bcrypt)
func IsLegacyHash(hashedKey string) bool {
return len(hashedKey) <= 2 || hashedKey[0] != '$' || hashedKey[1] != '2'
}
// MigrateAPIKeyHash can be used to upgrade a legacy SHA256 hash to bcrypt
// This is useful for one-time migrations of existing keys
// Returns the new bcrypt hash if the key is legacy, otherwise returns the original
func MigrateAPIKeyHash(currentHash, plainKey string) (string, bool, error) {
// If already bcrypt, no migration needed
if !IsLegacyHash(currentHash) {
return currentHash, false, nil
}
// Verify the legacy hash is correct before migrating
if !CompareAPIKey(currentHash, plainKey) {
return "", false, nil // Invalid key, don't migrate
}
// Generate new bcrypt hash
newHash, err := APIKey(plainKey)
if err != nil {
return "", false, err
}
return newHash, true, nil
}
+169
View File
@@ -0,0 +1,169 @@
package hash
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAPIKey(t *testing.T) {
key := "test_api_key_12345" // gitleaks:allow
hash1, err := APIKey(key)
require.NoError(t, err)
assert.NotEmpty(t, hash1)
assert.NotEqual(t, key, hash1, "Hash should not equal plaintext")
// Bcrypt hashes should start with $2
assert.True(t, strings.HasPrefix(hash1, "$2"), "Should be a bcrypt hash")
// Same key should produce different hashes (due to salt)
hash2, err := APIKey(key)
require.NoError(t, err)
assert.NotEqual(t, hash1, hash2, "Bcrypt should produce different hashes with different salts")
}
func TestCompareAPIKey_Bcrypt(t *testing.T) {
key := "test_api_key_12345" // gitleaks:allow
hash, err := APIKey(key)
require.NoError(t, err)
// Correct key should match
assert.True(t, CompareAPIKey(hash, key))
// Wrong key should not match
assert.False(t, CompareAPIKey(hash, "wrong_key"))
}
func TestCompareAPIKey_Legacy(t *testing.T) {
key := "test_api_key_12345" // gitleaks:allow
// Create a legacy SHA256 hash
legacyHash := String(key)
// Should still work with legacy hashes
assert.True(t, CompareAPIKey(legacyHash, key))
// Wrong key should not match
assert.False(t, CompareAPIKey(legacyHash, "wrong_key"))
}
func TestCompareAPIKey_BackwardCompatibility(t *testing.T) {
tests := []struct {
name string
hashFunc func(string) string
expectOK bool
}{
{
name: "bcrypt hash",
hashFunc: func(k string) string {
h, _ := APIKey(k)
return h
},
expectOK: true,
},
{
name: "legacy SHA256 hash",
hashFunc: func(k string) string {
return String(k)
},
expectOK: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
key := "test_key_123"
hash := tt.hashFunc(key)
result := CompareAPIKey(hash, key)
assert.Equal(t, tt.expectOK, result)
})
}
}
func TestString(t *testing.T) {
// Test that String function still works (for non-sensitive data)
input := "test_string"
hash1 := String(input)
hash2 := String(input)
// SHA256 should be deterministic
assert.Equal(t, hash1, hash2)
assert.NotEmpty(t, hash1)
assert.NotEqual(t, input, hash1)
}
func TestIsLegacyHash(t *testing.T) {
tests := []struct {
name string
hash string
isLegacy bool
}{
{
name: "bcrypt hash",
hash: "$2a$12$abcdefghijklmnopqrstuv",
isLegacy: false,
},
{
name: "SHA256 hash",
hash: "dXNfYWtfMTIzNDU2Nzg5MDEyMzQ1NuOwxEKY",
isLegacy: true,
},
{
name: "empty string",
hash: "",
isLegacy: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.isLegacy, IsLegacyHash(tt.hash))
})
}
}
func TestMigrateAPIKeyHash(t *testing.T) {
plainKey := "test_api_key_123"
t.Run("migrate legacy hash", func(t *testing.T) {
// Create a legacy SHA256 hash
legacyHash := String(plainKey)
// Migrate it
newHash, migrated, err := MigrateAPIKeyHash(legacyHash, plainKey)
require.NoError(t, err)
assert.True(t, migrated, "Should indicate migration occurred")
assert.NotEqual(t, legacyHash, newHash, "New hash should differ from legacy")
assert.True(t, strings.HasPrefix(newHash, "$2"), "New hash should be bcrypt")
// Verify new hash works
assert.True(t, CompareAPIKey(newHash, plainKey))
})
t.Run("no migration needed for bcrypt", func(t *testing.T) {
// Create a bcrypt hash
bcryptHash, err := APIKey(plainKey)
require.NoError(t, err)
// Try to migrate it
newHash, migrated, err := MigrateAPIKeyHash(bcryptHash, plainKey)
require.NoError(t, err)
assert.False(t, migrated, "Should not migrate bcrypt hash")
assert.Equal(t, bcryptHash, newHash, "Hash should remain unchanged")
})
t.Run("invalid key does not migrate", func(t *testing.T) {
legacyHash := String("correct_key")
// Try to migrate with wrong plaintext
newHash, migrated, err := MigrateAPIKeyHash(legacyHash, "wrong_key")
require.NoError(t, err)
assert.False(t, migrated, "Should not migrate invalid key")
assert.Empty(t, newHash, "Should return empty for invalid key")
})
}
+73
View File
@@ -0,0 +1,73 @@
package health
import (
"context"
"database/sql"
"encoding/json"
"log/slog"
"net/http"
"time"
)
type Checker struct {
db *sql.DB
logger *slog.Logger
}
func New(db *sql.DB, logger *slog.Logger) *Checker {
return &Checker{
db: db,
logger: logger,
}
}
type HealthStatus struct {
Status string `json:"status"`
Checks map[string]string `json:"checks,omitempty"`
}
// LivenessHandler checks if the application is running
// This is a simple check that always returns OK if the handler is reached
func (h *Checker) LivenessHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(HealthStatus{
Status: "UP",
})
}
// ReadinessHandler checks if the application is ready to accept traffic
// This checks database connectivity and other critical dependencies
func (h *Checker) ReadinessHandler(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
checks := make(map[string]string)
allHealthy := true
// Check database connectivity
if err := h.db.PingContext(ctx); err != nil {
h.logger.With("error", err).Warn("database health check failed")
checks["database"] = "DOWN"
allHealthy = false
} else {
checks["database"] = "UP"
}
status := HealthStatus{
Status: "UP",
Checks: checks,
}
if !allHealthy {
status.Status = "DOWN"
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusServiceUnavailable)
_ = json.NewEncoder(w).Encode(status)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(status)
}
+75
View File
@@ -0,0 +1,75 @@
package health
import (
"database/sql"
"log/slog"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLivenessHandler(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
db, _, err := sqlmock.New()
require.NoError(t, err)
defer db.Close()
checker := New(db, logger)
req := httptest.NewRequest(http.MethodGet, "/health/live", nil)
rec := httptest.NewRecorder()
checker.LivenessHandler(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
assert.Contains(t, rec.Body.String(), `"status":"UP"`)
}
func TestReadinessHandler_Healthy(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
db, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true))
require.NoError(t, err)
defer db.Close()
// Expect a ping and return success
mock.ExpectPing().WillReturnError(nil)
checker := New(db, logger)
req := httptest.NewRequest(http.MethodGet, "/health/ready", nil)
rec := httptest.NewRecorder()
checker.ReadinessHandler(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
assert.Contains(t, rec.Body.String(), `"status":"UP"`)
assert.Contains(t, rec.Body.String(), `"database":"UP"`)
assert.NoError(t, mock.ExpectationsWereMet())
}
func TestReadinessHandler_DatabaseDown(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
db, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true))
require.NoError(t, err)
defer db.Close()
// Expect a ping and return error
mock.ExpectPing().WillReturnError(sql.ErrConnDone)
checker := New(db, logger)
req := httptest.NewRequest(http.MethodGet, "/health/ready", nil)
rec := httptest.NewRecorder()
checker.ReadinessHandler(rec, req)
assert.Equal(t, http.StatusServiceUnavailable, rec.Code)
assert.Contains(t, rec.Body.String(), `"status":"DOWN"`)
assert.Contains(t, rec.Body.String(), `"database":"DOWN"`)
assert.NoError(t, mock.ExpectationsWereMet())
}
+11 -2
View File
@@ -44,15 +44,24 @@ spec:
requests:
cpu: "20m"
memory: "20Mi"
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 3
imagePullPolicy: IfNotPresent
image: registry.gitlab.com/unboundsoftware/schemas:${COMMIT}
image: oci.unbound.se/unboundsoftware/schemas:${COMMIT}
ports:
- name: api
containerPort: 8080
+1 -1
View File
@@ -3,7 +3,6 @@ kind: Ingress
metadata:
name: schemas-ingress
annotations:
kubernetes.io/ingress.class: "alb"
alb.ingress.kubernetes.io/group.name: "default"
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: instance
@@ -11,6 +10,7 @@ metadata:
alb.ingress.kubernetes.io/ssl-redirect: "443"
alb.ingress.kubernetes.io/healthcheck-path: '/health'
spec:
ingressClassName: "alb"
rules:
- host: "schemas.unbound.se"
http:
+58 -16
View File
@@ -6,10 +6,8 @@ import (
"net/http"
"github.com/99designs/gqlgen/graphql"
"github.com/golang-jwt/jwt/v5"
"gitlab.com/unboundsoftware/schemas/domain"
"gitlab.com/unboundsoftware/schemas/hash"
"gitea.unbound.se/unboundsoftware/schemas/domain"
)
const (
@@ -34,14 +32,9 @@ type AuthMiddleware struct {
func (m *AuthMiddleware) Handler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
token, err := TokenFromContext(r.Context())
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte("Invalid JWT token format"))
return
}
if token != nil {
ctx = context.WithValue(ctx, UserKey, token.Claims.(jwt.MapClaims)["sub"])
claims := ClaimsFromContext(r.Context())
if claims != nil {
ctx = context.WithValue(ctx, UserKey, claims.RegisteredClaims.Subject)
}
apiKey, err := ApiKeyFromContext(r.Context())
if err != nil {
@@ -49,7 +42,9 @@ func (m *AuthMiddleware) Handler(next http.Handler) http.Handler {
_, _ = w.Write([]byte("Invalid API Key format"))
return
}
if organization := m.cache.OrganizationByAPIKey(hash.String(apiKey)); organization != nil {
// Cache handles hash comparison internally
organization := m.cache.OrganizationByAPIKey(apiKey)
if organization != nil {
ctx = context.WithValue(ctx, OrganizationKey, *organization)
}
@@ -66,6 +61,26 @@ func UserFromContext(ctx context.Context) string {
return ""
}
func UserHasRole(ctx context.Context, role string) bool {
claims := ClaimsFromContext(ctx)
if claims == nil {
return false
}
customClaims, ok := claims.CustomClaims.(*CustomClaims)
if !ok || customClaims == nil {
return false
}
for _, r := range customClaims.Roles {
if r == role {
return true
}
}
return false
}
func OrganizationFromContext(ctx context.Context) string {
if value := ctx.Value(OrganizationKey); value != nil {
if u, ok := value.(domain.Organization); ok {
@@ -76,15 +91,42 @@ func OrganizationFromContext(ctx context.Context) string {
}
func (m *AuthMiddleware) Directive(ctx context.Context, _ interface{}, next graphql.Resolver, user *bool, organization *bool) (res interface{}, err error) {
if user != nil && *user {
if u := UserFromContext(ctx); u == "" {
userRequired := user != nil && *user
orgRequired := organization != nil && *organization
u := UserFromContext(ctx)
orgId := OrganizationFromContext(ctx)
fmt.Printf("[Auth Directive] userRequired=%v, orgRequired=%v, hasUser=%v, hasOrg=%v\n",
userRequired, orgRequired, u != "", orgId != "")
// If both are required, it means EITHER is acceptable (OR logic)
if userRequired && orgRequired {
if u == "" && orgId == "" {
fmt.Printf("[Auth Directive] REJECTED: Neither user nor organization available\n")
return nil, fmt.Errorf("authentication required: provide either user token or organization API key")
}
fmt.Printf("[Auth Directive] ACCEPTED: Has user=%v OR organization=%v\n", u != "", orgId != "")
return next(ctx)
}
// Only user required
if userRequired {
if u == "" {
fmt.Printf("[Auth Directive] REJECTED: No user available\n")
return nil, fmt.Errorf("no user available in request")
}
fmt.Printf("[Auth Directive] ACCEPTED: User authenticated\n")
}
if organization != nil && *organization {
if orgId := OrganizationFromContext(ctx); orgId == "" {
// Only organization required
if orgRequired {
if orgId == "" {
fmt.Printf("[Auth Directive] REJECTED: No organization available\n")
return nil, fmt.Errorf("no organization available in request")
}
fmt.Printf("[Auth Directive] ACCEPTED: Organization authenticated\n")
}
return next(ctx)
}
+50 -141
View File
@@ -2,39 +2,34 @@ package middleware
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"net/http"
"strings"
"sync"
"time"
"log"
"net/url"
mw "github.com/auth0/go-jwt-middleware/v2"
"github.com/golang-jwt/jwt/v5"
"github.com/pkg/errors"
jwtmiddleware "github.com/auth0/go-jwt-middleware/v3"
"github.com/auth0/go-jwt-middleware/v3/jwks"
"github.com/auth0/go-jwt-middleware/v3/validator"
)
// CustomClaims contains custom claims from the JWT token.
type CustomClaims struct {
Roles []string `json:"https://unbound.se/roles"`
}
// Validate implements the validator.CustomClaims interface.
func (c CustomClaims) Validate(_ context.Context) error {
return nil
}
type Auth0 struct {
domain string
audience string
client *http.Client
cache JwksCache
}
func NewAuth0(audience, domain string, strictSsl bool) *Auth0 {
customTransport := http.DefaultTransport.(*http.Transport).Clone()
customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: !strictSsl}
client := &http.Client{Transport: customTransport}
func NewAuth0(audience, domain string, _ bool) *Auth0 {
return &Auth0{
domain: domain,
audience: audience,
client: client,
cache: JwksCache{
RWMutex: &sync.RWMutex{},
cache: make(map[string]cacheItem),
},
}
}
@@ -42,133 +37,47 @@ type Response struct {
Message string `json:"message"`
}
type Jwks struct {
Keys []JSONWebKeys `json:"keys"`
}
type JSONWebKeys struct {
Kty string `json:"kty"`
Kid string `json:"kid"`
Use string `json:"use"`
N string `json:"n"`
E string `json:"e"`
X5c []string `json:"x5c"`
}
func (a *Auth0) ValidationKeyGetter() func(token *jwt.Token) (interface{}, error) {
return func(token *jwt.Token) (interface{}, error) {
// Verify 'aud' claim
cert, err := a.getPemCert(token)
if err != nil {
panic(err.Error())
}
result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert))
return result, nil
}
}
func (a *Auth0) Middleware() *mw.JWTMiddleware {
func (a *Auth0) Middleware() *jwtmiddleware.JWTMiddleware {
issuer := fmt.Sprintf("https://%s/", a.domain)
jwtMiddleware := mw.New(func(ctx context.Context, token string) (interface{}, error) {
jwtToken, err := jwt.Parse(token, a.ValidationKeyGetter(), jwt.WithAudience(a.audience), jwt.WithIssuer(issuer))
if err != nil {
return nil, err
}
if _, ok := jwtToken.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", jwtToken.Header["alg"])
}
return jwtToken, nil
},
mw.WithTokenExtractor(func(r *http.Request) (string, error) {
token := r.Header.Get("Authorization")
if strings.HasPrefix(token, "Bearer ") {
return token[7:], nil
}
return "", nil
issuerURL, err := url.Parse(issuer)
if err != nil {
log.Fatalf("failed to parse issuer URL: %v", err)
}
provider, err := jwks.NewCachingProvider(jwks.WithIssuerURL(issuerURL))
if err != nil {
log.Fatalf("failed to create JWKS provider: %v", err)
}
jwtValidator, err := validator.New(
validator.WithKeyFunc(provider.KeyFunc),
validator.WithAlgorithm(validator.RS256),
validator.WithIssuer(issuer),
validator.WithAudience(a.audience),
validator.WithCustomClaims(func() validator.CustomClaims {
return &CustomClaims{}
}),
mw.WithCredentialsOptional(true),
)
if err != nil {
log.Fatalf("failed to create JWT validator: %v", err)
}
jwtMiddleware, err := jwtmiddleware.New(
jwtmiddleware.WithValidator(jwtValidator),
jwtmiddleware.WithCredentialsOptional(true),
)
if err != nil {
log.Fatalf("failed to create JWT middleware: %v", err)
}
return jwtMiddleware
}
func TokenFromContext(ctx context.Context) (*jwt.Token, error) {
if value := ctx.Value(mw.ContextKey{}); value != nil {
if u, ok := value.(*jwt.Token); ok {
return u, nil
}
return nil, fmt.Errorf("token is in wrong format")
}
return nil, nil
}
func (a *Auth0) cacheGetWellknown(url string) (*Jwks, error) {
if value := a.cache.get(url); value != nil {
return value, nil
}
jwks := &Jwks{}
resp, err := a.client.Get(url)
func ClaimsFromContext(ctx context.Context) *validator.ValidatedClaims {
claims, err := jwtmiddleware.GetClaims[*validator.ValidatedClaims](ctx)
if err != nil {
return jwks, err
}
defer func() {
_ = resp.Body.Close()
}()
err = json.NewDecoder(resp.Body).Decode(jwks)
if err == nil && jwks != nil {
a.cache.put(url, jwks)
}
return jwks, err
}
func (a *Auth0) getPemCert(token *jwt.Token) (string, error) {
jwks, err := a.cacheGetWellknown(fmt.Sprintf("https://%s/.well-known/jwks.json", a.domain))
if err != nil {
return "", err
}
var cert string
for k := range jwks.Keys {
if token.Header["kid"] == jwks.Keys[k].Kid {
cert = "-----BEGIN CERTIFICATE-----\n" + jwks.Keys[k].X5c[0] + "\n-----END CERTIFICATE-----"
}
}
if cert == "" {
err := errors.New("Unable to find appropriate key.")
return cert, err
}
return cert, nil
}
type JwksCache struct {
*sync.RWMutex
cache map[string]cacheItem
}
type cacheItem struct {
data *Jwks
expiration time.Time
}
func (c *JwksCache) get(url string) *Jwks {
c.RLock()
defer c.RUnlock()
if value, ok := c.cache[url]; ok {
if time.Now().After(value.expiration) {
return nil
}
return value.data
}
return nil
}
func (c *JwksCache) put(url string, jwks *Jwks) {
c.Lock()
defer c.Unlock()
c.cache[url] = cacheItem{
data: jwks,
expiration: time.Now().Add(time.Minute * 60),
return nil
}
return claims
}
+566
View File
@@ -0,0 +1,566 @@
package middleware
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"github.com/auth0/go-jwt-middleware/v3/core"
"github.com/auth0/go-jwt-middleware/v3/validator"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitea.unbound.se/unboundsoftware/schemas/domain"
)
// MockCache is a mock implementation of the Cache interface
type MockCache struct {
mock.Mock
}
func (m *MockCache) OrganizationByAPIKey(apiKey string) *domain.Organization {
args := m.Called(apiKey)
if args.Get(0) == nil {
return nil
}
return args.Get(0).(*domain.Organization)
}
func TestAuthMiddleware_Handler_WithValidAPIKey(t *testing.T) {
// Setup
mockCache := new(MockCache)
authMiddleware := NewAuth(mockCache)
orgID := uuid.New()
expectedOrg := &domain.Organization{
BaseAggregate: eventsourced.BaseAggregate{
ID: eventsourced.IdFromString(orgID.String()),
},
Name: "Test Organization",
}
apiKey := "test-api-key-123"
// Mock expects plaintext key (cache handles hashing internally)
mockCache.On("OrganizationByAPIKey", apiKey).Return(expectedOrg)
// Create a test handler that checks the context
var capturedOrg *domain.Organization
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if org := r.Context().Value(OrganizationKey); org != nil {
if o, ok := org.(domain.Organization); ok {
capturedOrg = &o
}
}
w.WriteHeader(http.StatusOK)
})
// Create request with API key in context
req := httptest.NewRequest(http.MethodGet, "/test", nil)
ctx := context.WithValue(req.Context(), ApiKey, apiKey)
req = req.WithContext(ctx)
rec := httptest.NewRecorder()
// Execute
authMiddleware.Handler(testHandler).ServeHTTP(rec, req)
// Assert
assert.Equal(t, http.StatusOK, rec.Code)
require.NotNil(t, capturedOrg)
assert.Equal(t, expectedOrg.Name, capturedOrg.Name)
assert.Equal(t, expectedOrg.ID.String(), capturedOrg.ID.String())
mockCache.AssertExpectations(t)
}
func TestAuthMiddleware_Handler_WithInvalidAPIKey(t *testing.T) {
// Setup
mockCache := new(MockCache)
authMiddleware := NewAuth(mockCache)
apiKey := "invalid-api-key"
// Mock expects plaintext key (cache handles hashing internally)
mockCache.On("OrganizationByAPIKey", apiKey).Return(nil)
// Create a test handler that checks the context
var capturedOrg *domain.Organization
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if org := r.Context().Value(OrganizationKey); org != nil {
if o, ok := org.(domain.Organization); ok {
capturedOrg = &o
}
}
w.WriteHeader(http.StatusOK)
})
// Create request with API key in context
req := httptest.NewRequest(http.MethodGet, "/test", nil)
ctx := context.WithValue(req.Context(), ApiKey, apiKey)
req = req.WithContext(ctx)
rec := httptest.NewRecorder()
// Execute
authMiddleware.Handler(testHandler).ServeHTTP(rec, req)
// Assert
assert.Equal(t, http.StatusOK, rec.Code)
assert.Nil(t, capturedOrg, "Organization should not be set for invalid API key")
mockCache.AssertExpectations(t)
}
func TestAuthMiddleware_Handler_WithoutAPIKey(t *testing.T) {
// Setup
mockCache := new(MockCache)
authMiddleware := NewAuth(mockCache)
// The middleware passes the plaintext API key (cache handles hashing)
mockCache.On("OrganizationByAPIKey", "").Return(nil)
// Create a test handler that checks the context
var capturedOrg *domain.Organization
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if org := r.Context().Value(OrganizationKey); org != nil {
if o, ok := org.(domain.Organization); ok {
capturedOrg = &o
}
}
w.WriteHeader(http.StatusOK)
})
// Create request without API key
req := httptest.NewRequest(http.MethodGet, "/test", nil)
rec := httptest.NewRecorder()
// Execute
authMiddleware.Handler(testHandler).ServeHTTP(rec, req)
// Assert
assert.Equal(t, http.StatusOK, rec.Code)
assert.Nil(t, capturedOrg, "Organization should not be set without API key")
mockCache.AssertExpectations(t)
}
func TestAuthMiddleware_Handler_WithValidJWT(t *testing.T) {
// Setup
mockCache := new(MockCache)
authMiddleware := NewAuth(mockCache)
// The middleware passes the plaintext API key (cache handles hashing)
mockCache.On("OrganizationByAPIKey", "").Return(nil)
userID := "user-123"
claims := &validator.ValidatedClaims{
RegisteredClaims: validator.RegisteredClaims{
Subject: userID,
},
}
// Create a test handler that checks the context
var capturedUser string
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if user := r.Context().Value(UserKey); user != nil {
if u, ok := user.(string); ok {
capturedUser = u
}
}
w.WriteHeader(http.StatusOK)
})
// Create request with JWT claims in context
req := httptest.NewRequest(http.MethodGet, "/test", nil)
ctx := core.SetClaims(req.Context(), claims)
req = req.WithContext(ctx)
rec := httptest.NewRecorder()
// Execute
authMiddleware.Handler(testHandler).ServeHTTP(rec, req)
// Assert
assert.Equal(t, http.StatusOK, rec.Code)
assert.Equal(t, userID, capturedUser)
}
func TestAuthMiddleware_Handler_APIKeyErrorHandling(t *testing.T) {
// Setup
mockCache := new(MockCache)
authMiddleware := NewAuth(mockCache)
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
// Create request with invalid API key type in context
req := httptest.NewRequest(http.MethodGet, "/test", nil)
ctx := context.WithValue(req.Context(), ApiKey, 12345) // Invalid type
req = req.WithContext(ctx)
rec := httptest.NewRecorder()
// Execute
authMiddleware.Handler(testHandler).ServeHTTP(rec, req)
// Assert
assert.Equal(t, http.StatusInternalServerError, rec.Code)
assert.Contains(t, rec.Body.String(), "Invalid API Key format")
}
func TestAuthMiddleware_Handler_JWTMissingClaims(t *testing.T) {
// Setup
mockCache := new(MockCache)
authMiddleware := NewAuth(mockCache)
// The middleware passes the plaintext API key (cache handles hashing)
mockCache.On("OrganizationByAPIKey", "").Return(nil)
// Create a test handler that checks the context
var capturedUser string
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if user := r.Context().Value(UserKey); user != nil {
if u, ok := user.(string); ok {
capturedUser = u
}
}
w.WriteHeader(http.StatusOK)
})
// Create request without JWT claims - user should not be set
req := httptest.NewRequest(http.MethodGet, "/test", nil)
rec := httptest.NewRecorder()
// Execute
authMiddleware.Handler(testHandler).ServeHTTP(rec, req)
// Assert
assert.Equal(t, http.StatusOK, rec.Code)
assert.Empty(t, capturedUser, "User should not be set when no claims in context")
}
func TestAuthMiddleware_Handler_BothJWTAndAPIKey(t *testing.T) {
// Setup
mockCache := new(MockCache)
authMiddleware := NewAuth(mockCache)
orgID := uuid.New()
expectedOrg := &domain.Organization{
BaseAggregate: eventsourced.BaseAggregate{
ID: eventsourced.IdFromString(orgID.String()),
},
Name: "Test Organization",
}
userID := "user-123"
apiKey := "test-api-key-123"
claims := &validator.ValidatedClaims{
RegisteredClaims: validator.RegisteredClaims{
Subject: userID,
},
}
// Mock expects plaintext key (cache handles hashing internally)
mockCache.On("OrganizationByAPIKey", apiKey).Return(expectedOrg)
// Create a test handler that checks both user and organization in context
var capturedUser string
var capturedOrg *domain.Organization
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if user := r.Context().Value(UserKey); user != nil {
if u, ok := user.(string); ok {
capturedUser = u
}
}
if org := r.Context().Value(OrganizationKey); org != nil {
if o, ok := org.(domain.Organization); ok {
capturedOrg = &o
}
}
w.WriteHeader(http.StatusOK)
})
// Create request with both JWT claims and API key in context
req := httptest.NewRequest(http.MethodGet, "/test", nil)
ctx := core.SetClaims(req.Context(), claims)
ctx = context.WithValue(ctx, ApiKey, apiKey)
req = req.WithContext(ctx)
rec := httptest.NewRecorder()
// Execute
authMiddleware.Handler(testHandler).ServeHTTP(rec, req)
// Assert
assert.Equal(t, http.StatusOK, rec.Code)
assert.Equal(t, userID, capturedUser)
require.NotNil(t, capturedOrg)
assert.Equal(t, expectedOrg.Name, capturedOrg.Name)
mockCache.AssertExpectations(t)
}
func TestUserFromContext(t *testing.T) {
tests := []struct {
name string
ctx context.Context
expected string
}{
{
name: "with valid user",
ctx: context.WithValue(context.Background(), UserKey, "user-123"),
expected: "user-123",
},
{
name: "without user",
ctx: context.Background(),
expected: "",
},
{
name: "with invalid type",
ctx: context.WithValue(context.Background(), UserKey, 123),
expected: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := UserFromContext(tt.ctx)
assert.Equal(t, tt.expected, result)
})
}
}
func TestOrganizationFromContext(t *testing.T) {
orgID := uuid.New()
org := domain.Organization{
BaseAggregate: eventsourced.BaseAggregate{
ID: eventsourced.IdFromString(orgID.String()),
},
Name: "Test Org",
}
tests := []struct {
name string
ctx context.Context
expected string
}{
{
name: "with valid organization",
ctx: context.WithValue(context.Background(), OrganizationKey, org),
expected: orgID.String(),
},
{
name: "without organization",
ctx: context.Background(),
expected: "",
},
{
name: "with invalid type",
ctx: context.WithValue(context.Background(), OrganizationKey, "not-an-org"),
expected: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := OrganizationFromContext(tt.ctx)
assert.Equal(t, tt.expected, result)
})
}
}
func TestAuthMiddleware_Directive_RequiresUser(t *testing.T) {
mockCache := new(MockCache)
authMiddleware := NewAuth(mockCache)
requireUser := true
// Test with user present
ctx := context.WithValue(context.Background(), UserKey, "user-123")
_, err := authMiddleware.Directive(ctx, nil, func(ctx context.Context) (interface{}, error) {
return "success", nil
}, &requireUser, nil)
assert.NoError(t, err)
// Test without user
ctx = context.Background()
_, err = authMiddleware.Directive(ctx, nil, func(ctx context.Context) (interface{}, error) {
return "success", nil
}, &requireUser, nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "no user available in request")
}
func TestAuthMiddleware_Directive_RequiresOrganization(t *testing.T) {
mockCache := new(MockCache)
authMiddleware := NewAuth(mockCache)
requireOrg := true
orgID := uuid.New()
org := domain.Organization{
BaseAggregate: eventsourced.BaseAggregate{
ID: eventsourced.IdFromString(orgID.String()),
},
Name: "Test Org",
}
// Test with organization present
ctx := context.WithValue(context.Background(), OrganizationKey, org)
_, err := authMiddleware.Directive(ctx, nil, func(ctx context.Context) (interface{}, error) {
return "success", nil
}, nil, &requireOrg)
assert.NoError(t, err)
// Test without organization
ctx = context.Background()
_, err = authMiddleware.Directive(ctx, nil, func(ctx context.Context) (interface{}, error) {
return "success", nil
}, nil, &requireOrg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "no organization available in request")
}
func TestAuthMiddleware_Directive_RequiresBoth(t *testing.T) {
mockCache := new(MockCache)
authMiddleware := NewAuth(mockCache)
requireUser := true
requireOrg := true
orgID := uuid.New()
org := domain.Organization{
BaseAggregate: eventsourced.BaseAggregate{
ID: eventsourced.IdFromString(orgID.String()),
},
Name: "Test Org",
}
// When both user and organization are marked as acceptable,
// the directive uses OR logic - either one is sufficient
// Test with both present - should succeed
ctx := context.WithValue(context.Background(), UserKey, "user-123")
ctx = context.WithValue(ctx, OrganizationKey, org)
_, err := authMiddleware.Directive(ctx, nil, func(ctx context.Context) (interface{}, error) {
return "success", nil
}, &requireUser, &requireOrg)
assert.NoError(t, err)
// Test with only user - should succeed (OR logic)
ctx = context.WithValue(context.Background(), UserKey, "user-123")
_, err = authMiddleware.Directive(ctx, nil, func(ctx context.Context) (interface{}, error) {
return "success", nil
}, &requireUser, &requireOrg)
assert.NoError(t, err)
// Test with only organization - should succeed (OR logic)
ctx = context.WithValue(context.Background(), OrganizationKey, org)
_, err = authMiddleware.Directive(ctx, nil, func(ctx context.Context) (interface{}, error) {
return "success", nil
}, &requireUser, &requireOrg)
assert.NoError(t, err)
// Test with neither - should fail
ctx = context.Background()
_, err = authMiddleware.Directive(ctx, nil, func(ctx context.Context) (interface{}, error) {
return "success", nil
}, &requireUser, &requireOrg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "authentication required")
}
func TestAuthMiddleware_Directive_NoRequirements(t *testing.T) {
mockCache := new(MockCache)
authMiddleware := NewAuth(mockCache)
// Test with no requirements
ctx := context.Background()
result, err := authMiddleware.Directive(ctx, nil, func(ctx context.Context) (interface{}, error) {
return "success", nil
}, nil, nil)
assert.NoError(t, err)
assert.Equal(t, "success", result)
}
func TestUserHasRole_WithValidRole(t *testing.T) {
// Create claims with roles
claims := &validator.ValidatedClaims{
RegisteredClaims: validator.RegisteredClaims{
Subject: "user-123",
},
CustomClaims: &CustomClaims{
Roles: []string{"admin", "user"},
},
}
ctx := core.SetClaims(context.Background(), claims)
// Test for existing role
hasRole := UserHasRole(ctx, "admin")
assert.True(t, hasRole)
hasRole = UserHasRole(ctx, "user")
assert.True(t, hasRole)
}
func TestUserHasRole_WithoutRole(t *testing.T) {
// Create claims with roles
claims := &validator.ValidatedClaims{
RegisteredClaims: validator.RegisteredClaims{
Subject: "user-123",
},
CustomClaims: &CustomClaims{
Roles: []string{"user"},
},
}
ctx := core.SetClaims(context.Background(), claims)
// Test for non-existing role
hasRole := UserHasRole(ctx, "admin")
assert.False(t, hasRole)
}
func TestUserHasRole_WithoutRolesClaim(t *testing.T) {
// Create claims without custom claims
claims := &validator.ValidatedClaims{
RegisteredClaims: validator.RegisteredClaims{
Subject: "user-123",
},
}
ctx := core.SetClaims(context.Background(), claims)
// Test should return false when custom claims is missing
hasRole := UserHasRole(ctx, "admin")
assert.False(t, hasRole)
}
func TestUserHasRole_WithoutClaims(t *testing.T) {
ctx := context.Background()
// Test should return false when no claims in context
hasRole := UserHasRole(ctx, "admin")
assert.False(t, hasRole)
}
func TestUserHasRole_WithEmptyRoles(t *testing.T) {
// Create claims with empty roles
claims := &validator.ValidatedClaims{
RegisteredClaims: validator.RegisteredClaims{
Subject: "user-123",
},
CustomClaims: &CustomClaims{
Roles: []string{},
},
}
ctx := core.SetClaims(context.Background(), claims)
// Test should return false when roles array is empty
hasRole := UserHasRole(ctx, "admin")
assert.False(t, hasRole)
}
+1 -1
View File
@@ -9,7 +9,7 @@
"kubernetes"
],
"matchPackageNames": [
"registry.gitlab.com/unboundsoftware/schemas"
"oci.unbound.se/unboundsoftware/schemas"
],
"enabled": false
},
+5 -3
View File
@@ -1,6 +1,7 @@
package sdlmerge
import (
"bytes"
"fmt"
"strings"
@@ -61,12 +62,13 @@ func MergeSDLs(SDLs ...string) (string, error) {
return "", fmt.Errorf("merge ast: %w", err)
}
out, err := astprinter.PrintString(&doc)
if err != nil {
// Format with indentation for better readability
buf := &bytes.Buffer{}
if err := astprinter.PrintIndent(&doc, []byte(" "), buf); err != nil {
return "", fmt.Errorf("stringify schema: %w", err)
}
return out, nil
return buf.String(), nil
}
func validateSubgraphs(subgraphs []string) error {
+434
View File
@@ -0,0 +1,434 @@
package sdlmerge
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMergeSDLs_Success(t *testing.T) {
// Both types need to be in the same subgraph or properly federated
sdl1 := `
type User {
id: ID!
name: String!
}
type Post {
id: ID!
title: String!
}
`
result, err := MergeSDLs(sdl1)
require.NoError(t, err)
assert.Contains(t, result, "User")
assert.Contains(t, result, "Post")
assert.Contains(t, result, "id")
assert.Contains(t, result, "name")
assert.Contains(t, result, "title")
}
func TestMergeSDLs_SingleSchema(t *testing.T) {
sdl := `
type Query {
hello: String
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "Query")
assert.Contains(t, result, "hello")
}
func TestMergeSDLs_EmptySchemas(t *testing.T) {
result, err := MergeSDLs()
require.NoError(t, err)
// With no schemas, result will be empty after processing
// This is valid - just verifies no crash
_ = result
}
func TestMergeSDLs_InvalidSyntax(t *testing.T) {
invalidSDL := `
type User {
id: ID!
name: String!
// Missing closing brace
`
_, err := MergeSDLs(invalidSDL)
require.Error(t, err)
assert.Contains(t, err.Error(), "parse graphql document string")
}
func TestMergeSDLs_UnknownType(t *testing.T) {
sdl := `
type User {
id: ID!
profile: UnknownType
}
`
_, err := MergeSDLs(sdl)
require.Error(t, err)
assert.Contains(t, err.Error(), "validate schema")
}
func TestMergeSDLs_DuplicateTypes_DifferentFields(t *testing.T) {
// Same type with different fields in different subgraphs - should fail
// In federation, shared types must be identical
sdl1 := `
type User {
id: ID!
}
`
sdl2 := `
type User {
name: String!
}
`
_, err := MergeSDLs(sdl1, sdl2)
require.Error(t, err)
assert.Contains(t, err.Error(), "shared type")
}
func TestMergeSDLs_ExtendType(t *testing.T) {
sdl1 := `
type User {
id: ID!
}
`
sdl2 := `
extend type User {
email: String!
}
`
result, err := MergeSDLs(sdl1, sdl2)
require.NoError(t, err)
assert.Contains(t, result, "User")
assert.Contains(t, result, "id")
assert.Contains(t, result, "email")
}
func TestMergeSDLs_Scalars(t *testing.T) {
sdl := `
scalar DateTime
type Event {
id: ID!
createdAt: DateTime!
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "DateTime")
assert.Contains(t, result, "Event")
}
func TestMergeSDLs_Enums(t *testing.T) {
sdl := `
enum Role {
ADMIN
USER
GUEST
}
type User {
id: ID!
role: Role!
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "Role")
assert.Contains(t, result, "ADMIN")
assert.Contains(t, result, "USER")
}
func TestMergeSDLs_Interfaces(t *testing.T) {
sdl := `
interface Node {
id: ID!
}
type User implements Node {
id: ID!
name: String!
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "Node")
assert.Contains(t, result, "implements")
}
func TestMergeSDLs_Unions(t *testing.T) {
sdl := `
type User {
id: ID!
name: String!
}
type Bot {
id: ID!
version: String!
}
union Actor = User | Bot
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "Actor")
assert.Contains(t, result, "User")
assert.Contains(t, result, "Bot")
}
func TestMergeSDLs_InputTypes(t *testing.T) {
sdl := `
input CreateUserInput {
name: String!
email: String!
}
type Mutation {
createUser(input: CreateUserInput!): User
}
type User {
id: ID!
name: String!
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "CreateUserInput")
assert.Contains(t, result, "createUser")
}
func TestMergeSDLs_Directives(t *testing.T) {
sdl := `
type User {
id: ID!
name: String! @deprecated(reason: "Use fullName instead")
fullName: String!
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "User")
assert.Contains(t, result, "name")
assert.Contains(t, result, "fullName")
}
func TestMergeSDLs_FederationKeys(t *testing.T) {
// Federation @key directive
sdl := `
type User @key(fields: "id") {
id: ID!
name: String!
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "User")
// @key directive should be removed during merge
assert.NotContains(t, result, "@key")
}
func TestMergeSDLs_ExternalFields(t *testing.T) {
// Federation @external directive
sdl1 := `
type User @key(fields: "id") {
id: ID!
name: String!
}
`
sdl2 := `
extend type User @key(fields: "id") {
id: ID! @external
posts: [Post!]!
}
type Post {
id: ID!
title: String!
}
`
result, err := MergeSDLs(sdl1, sdl2)
require.NoError(t, err)
assert.Contains(t, result, "User")
assert.Contains(t, result, "Post")
// @external fields should be removed
assert.NotContains(t, result, "@external")
}
func TestMergeSDLs_ComplexSchema(t *testing.T) {
// Multiple subgraphs with various types - simplified to avoid cross-references
users := `
type User @key(fields: "id") {
id: ID!
username: String!
email: String!
}
type Query {
user(id: ID!): User
users: [User!]!
}
`
posts := `
extend type User @key(fields: "id") {
id: ID! @external
posts: [Post!]!
}
type Post @key(fields: "id") {
id: ID!
title: String!
content: String!
}
extend type Query {
post(id: ID!): Post
posts: [Post!]!
}
`
comments := `
extend type Post @key(fields: "id") {
id: ID! @external
comments: [Comment!]!
}
type Comment {
id: ID!
text: String!
}
extend type Query {
comment(id: ID!): Comment
}
`
result, err := MergeSDLs(users, posts, comments)
require.NoError(t, err)
// Verify all types are present
assert.Contains(t, result, "User")
assert.Contains(t, result, "Post")
assert.Contains(t, result, "Comment")
assert.Contains(t, result, "Query")
// Verify fields from all subgraphs
assert.Contains(t, result, "username")
assert.Contains(t, result, "posts")
assert.Contains(t, result, "comments")
}
func TestMergeSDLs_EmptyTypeDefinition(t *testing.T) {
sdl := `
type Empty {}
`
_, err := MergeSDLs(sdl)
require.Error(t, err)
// Empty types are invalid in GraphQL
assert.Contains(t, err.Error(), "empty body")
}
func TestMergeSDLs_MultipleValidationErrors(t *testing.T) {
// Schema with multiple errors
sdl := `
type User {
id: ID!
profile: NonExistentType1
settings: NonExistentType2
}
`
_, err := MergeSDLs(sdl)
require.Error(t, err)
}
func TestMergeSDLs_ListTypes(t *testing.T) {
sdl := `
type User {
id: ID!
tags: [String!]!
friends: [User!]
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "User")
assert.Contains(t, result, "tags")
assert.Contains(t, result, "friends")
}
func TestMergeSDLs_NonNullTypes(t *testing.T) {
sdl := `
type User {
id: ID!
name: String!
email: String
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "User")
assert.Contains(t, result, "id")
assert.Contains(t, result, "name")
assert.Contains(t, result, "email")
}
func TestMergeSDLs_Comments(t *testing.T) {
sdl := `
# This is a user type
type User {
# User ID
id: ID!
# User name
name: String!
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "User")
}
func TestMergeSDLs_LargeSchema(t *testing.T) {
// Test with a reasonably large schema to ensure performance
var sdlBuilder strings.Builder
for i := 0; i < 50; i++ {
sdlBuilder.WriteString("type Type")
sdlBuilder.WriteString(strings.Repeat(string(rune('A'+i%26)), 1))
sdlBuilder.WriteString(string(rune('0' + i/26)))
sdlBuilder.WriteString(" { id: ID }\n")
}
result, err := MergeSDLs(sdlBuilder.String())
require.NoError(t, err)
// Verify some types are present
assert.Contains(t, result, "TypeA0")
assert.Contains(t, result, "TypeB0")
assert.Contains(t, result, "TypeC0")
}