Compare commits

...

129 Commits

Author SHA1 Message Date
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
41 changed files with 2755 additions and 351 deletions
+1 -2
View File
@@ -1,6 +1,5 @@
.gitignore .gitignore
/.gitlab /.gitea
.gitlab-ci.yml
.graphqlconfig .graphqlconfig
/exported /exported
/k8s /k8s
+89
View File
@@ -0,0 +1,89 @@
name: schemas
on:
push:
branches: [main]
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-go@v6
with:
go-version: 'stable'
- name: Check goreleaser config
uses: goreleaser/goreleaser-action@v6
with:
version: '~> v2'
args: check
- name: Test release build
uses: goreleaser/goreleaser-action@v6
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
+30
View File
@@ -0,0 +1,30 @@
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-go@v6
with:
go-version: 'stable'
- name: Install goreleaser
uses: goreleaser/goreleaser-action@v6
with:
version: '~> v2'
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
+1
View File
@@ -3,6 +3,7 @@
.testCoverage.txt .testCoverage.txt
.testCoverage.txt.tmp .testCoverage.txt.tmp
coverage.html coverage.html
coverage.out
/exported /exported
/release /release
/schemactl /schemactl
-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.25.4@sha256:efe81fa41fdf81fb873ab7cd931b9bb29bd10aced6c252cbd91739c34e654f01
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.25.4@sha256:efe81fa41fdf81fb873ab7cd931b9bb29bd10aced6c252cbd91739c34e654f01
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.12.7@sha256:a2a47c0dda85f8d40eaaa5b9765bf76c69addb6060666f8a51441410d9b008e9
entrypoint: [ '' ]
variables:
GOTOOLCHAIN: auto
script: |
goreleaser check
goreleaser release --snapshot --clean
release:
stage: release
needs:
- unbound_release_prepare_release
image:
name: goreleaser/goreleaser:v2.12.7@sha256:a2a47c0dda85f8d40eaaa5b9765bf76c69addb6060666f8a51441410d9b008e9
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
+4 -5
View File
@@ -1,6 +1,10 @@
project_name: unbound-schemas project_name: unbound-schemas
version: 2 version: 2
gitea_urls:
api: http://gitea-http.gitea.svc.cluster.local:3000/api/v1
download: https://gitea.unbound.se
env: env:
- CGO_ENABLED=0 - CGO_ENABLED=0
@@ -27,11 +31,6 @@ homebrew_casks:
name: "Joakim Olsson" name: "Joakim Olsson"
email: joakim@unbound.se email: joakim@unbound.se
homepage: "https://schemas.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: archives:
- id: unbound-schemas - id: unbound-schemas
+4 -11
View File
@@ -10,15 +10,8 @@ repos:
args: args:
- --allow-multiple-documents - --allow-multiple-documents
- id: check-added-large-files - 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 - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
rev: v9.23.0 rev: v9.24.0
hooks: hooks:
- id: commitlint - id: commitlint
stages: [ commit-msg ] stages: [ commit-msg ]
@@ -30,18 +23,18 @@ repos:
- id: go-imports - id: go-imports
args: args:
- -local - -local
- gitlab.com/unboundsoftware/schemas - git.unbound.se/unboundsoftware/schemas
- repo: https://github.com/lietu/go-pre-commit - repo: https://github.com/lietu/go-pre-commit
rev: v1.0.0 rev: v1.0.0
hooks: hooks:
- id: go-test - id: go-test
- id: gofumpt - id: gofumpt
- repo: https://github.com/golangci/golangci-lint - repo: https://github.com/golangci/golangci-lint
rev: v2.6.2 rev: v2.8.0
hooks: hooks:
- id: golangci-lint-full - id: golangci-lint-full
- repo: https://github.com/gitleaks/gitleaks - repo: https://github.com/gitleaks/gitleaks
rev: v8.29.1 rev: v8.30.0
hooks: hooks:
- id: gitleaks - id: gitleaks
exclude: '^ctl/generated.go|graph/generated/.*$|^graph/model/models_gen.go|^tools/.*$$' exclude: '^ctl/generated.go|graph/generated/.*$|^graph/model/models_gen.go|^tools/.*$$'
+3 -1
View File
@@ -1 +1,3 @@
{"version":"v0.8.0"} {
"version": "v0.9.0"
}
+54
View File
@@ -2,6 +2,60 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [0.9.0] - 2026-01-17
### 🚀 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 ## [0.8.0] - 2025-11-21
### 🚀 Features ### 🚀 Features
+2 -2
View File
@@ -1,4 +1,4 @@
FROM amd64/golang:1.25.4@sha256:efe81fa41fdf81fb873ab7cd931b9bb29bd10aced6c252cbd91739c34e654f01 as modules FROM amd64/golang:1.25.6@sha256:9860925875ac68a8fb57416cfc5c1ee267a06226730434af677b9406e8ea6ee6 as modules
WORKDIR /build WORKDIR /build
ADD go.* /build ADD go.* /build
RUN go mod download RUN go mod download
@@ -24,7 +24,7 @@ RUN GOOS=linux GOARCH=amd64 go build \
FROM scratch as export FROM scratch as export
COPY --from=build /build/coverage.txt / COPY --from=build /build/coverage.txt /
FROM node:24-alpine@sha256:2867d550cf9d8bb50059a0fff528741f11a84d985c732e60e19e8e75c7239c43 FROM node:24.13.0-alpine@sha256:931d7d57f8c1fd0e2179dbff7cc7da4c9dd100998bc2b32afc85142d8efbc213
ENV TZ Europe/Stockholm ENV TZ Europe/Stockholm
# Install wgc CLI globally for Cosmo Router composition # Install wgc CLI globally for Cosmo Router composition
+80 -2
View File
@@ -9,8 +9,8 @@ import (
"github.com/sparetimecoders/goamqp" "github.com/sparetimecoders/goamqp"
"gitlab.com/unboundsoftware/eventsourced/eventsourced" "gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitlab.com/unboundsoftware/schemas/domain" "gitea.unbound.se/unboundsoftware/schemas/domain"
"gitlab.com/unboundsoftware/schemas/hash" "gitea.unbound.se/unboundsoftware/schemas/hash"
) )
type Cache struct { type Cache struct {
@@ -53,6 +53,17 @@ func (c *Cache) OrganizationsByUser(sub string) []domain.Organization {
return orgs return orgs
} }
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 orgs
}
func (c *Cache) ApiKeyByKey(key string) *domain.APIKey { func (c *Cache) ApiKeyByKey(key string) *domain.APIKey {
c.mu.RLock() c.mu.RLock()
defer c.mu.RUnlock() defer c.mu.RUnlock()
@@ -100,6 +111,16 @@ func (c *Cache) Update(msg any, _ goamqp.Headers) (any, error) {
c.organizations[m.ID.String()] = o c.organizations[m.ID.String()] = o
c.addUser(m.Initiator, o) c.addUser(m.Initiator, o)
c.logger.With("org_id", m.ID.String(), "event", "OrganizationAdded").Debug("cache updated") 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: case *domain.APIKeyAdded:
key := domain.APIKey{ key := domain.APIKey{
Name: m.Name, Name: m.Name,
@@ -117,6 +138,63 @@ func (c *Cache) Update(msg any, _ goamqp.Headers) (any, error) {
org.APIKeys = append(org.APIKeys, key) org.APIKeys = append(org.APIKeys, key)
c.organizations[m.OrganizationId] = org c.organizations[m.OrganizationId] = org
c.logger.With("org_id", m.OrganizationId, "key_name", m.Name, "event", "APIKeyAdded").Debug("cache updated") 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: case *domain.SubGraphUpdated:
c.updateSubGraph(m.OrganizationId, m.Ref, m.ID.String(), m.Service, m.Time) 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") c.logger.With("org_id", m.OrganizationId, "ref", m.Ref, "service", m.Service, "event", "SubGraphUpdated").Debug("cache updated")
+228 -30
View File
@@ -12,8 +12,8 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gitlab.com/unboundsoftware/eventsourced/eventsourced" "gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitlab.com/unboundsoftware/schemas/domain" "gitea.unbound.se/unboundsoftware/schemas/domain"
"gitlab.com/unboundsoftware/schemas/hash" "gitea.unbound.se/unboundsoftware/schemas/hash"
) )
func TestCache_OrganizationByAPIKey(t *testing.T) { func TestCache_OrganizationByAPIKey(t *testing.T) {
@@ -320,24 +320,18 @@ func TestCache_ConcurrentReads(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
c := New(logger) c := New(logger)
// Setup test data // Setup test data - use legacy hash to avoid slow bcrypt
orgID := uuid.New().String() orgID := uuid.New().String()
apiKey := "test-concurrent-key" // gitleaks:allow userSub := "test-user"
hashedKey, err := hash.APIKey(apiKey)
require.NoError(t, err)
org := domain.Organization{ org := domain.Organization{
BaseAggregate: eventsourced.BaseAggregateFromString(orgID), BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
Name: "Concurrent Test Org", Name: "Concurrent Test Org",
} }
c.organizations[orgID] = org c.organizations[orgID] = org
c.apiKeys[apiKeyId(orgID, "test-key")] = domain.APIKey{ c.users[userSub] = []string{orgID}
Name: "test-key",
OrganizationId: orgID,
Key: hashedKey,
}
// Run concurrent reads (reduced for race detector) // Run concurrent reads using fast OrganizationsByUser
var wg sync.WaitGroup var wg sync.WaitGroup
numGoroutines := 20 numGoroutines := 20
@@ -345,9 +339,9 @@ func TestCache_ConcurrentReads(t *testing.T) {
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
org := c.OrganizationByAPIKey(apiKey) orgs := c.OrganizationsByUser(userSub)
assert.NotNil(t, org) assert.NotEmpty(t, orgs)
assert.Equal(t, "Concurrent Test Org", org.Name) assert.Equal(t, "Concurrent Test Org", orgs[0].Name)
}() }()
} }
@@ -387,11 +381,10 @@ func TestCache_ConcurrentReadsAndWrites(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
c := New(logger) c := New(logger)
// Setup initial data // Setup initial data - use legacy hash to avoid slow bcrypt in concurrent test
orgID := uuid.New().String() orgID := uuid.New().String()
apiKey := "test-rw-key" // gitleaks:allow legacyKey := "test-rw-key" // gitleaks:allow
hashedKey, err := hash.APIKey(apiKey) legacyHash := hash.String(legacyKey)
require.NoError(t, err)
org := domain.Organization{ org := domain.Organization{
BaseAggregate: eventsourced.BaseAggregateFromString(orgID), BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
@@ -401,26 +394,21 @@ func TestCache_ConcurrentReadsAndWrites(t *testing.T) {
c.apiKeys[apiKeyId(orgID, "test-key")] = domain.APIKey{ c.apiKeys[apiKeyId(orgID, "test-key")] = domain.APIKey{
Name: "test-key", Name: "test-key",
OrganizationId: orgID, OrganizationId: orgID,
Key: hashedKey, Key: legacyHash,
} }
c.users["user-initial"] = []string{orgID} c.users["user-initial"] = []string{orgID}
var wg sync.WaitGroup var wg sync.WaitGroup
numReaders := 10 // Reduced for race detector numReaders := 5
numWriters := 5 // Reduced for race detector numWriters := 3
iterations := 3 // Reduced for race detector
// Concurrent readers // Concurrent readers - use OrganizationsByUser which is fast
for i := 0; i < numReaders; i++ { for i := 0; i < numReaders; i++ {
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
for j := 0; j < iterations; j++ { orgs := c.OrganizationsByUser("user-initial")
org := c.OrganizationByAPIKey(apiKey) assert.NotEmpty(t, orgs)
assert.NotNil(t, org)
orgs := c.OrganizationsByUser("user-initial")
assert.NotEmpty(t, orgs)
}
}() }()
} }
@@ -445,3 +433,213 @@ func TestCache_ConcurrentReadsAndWrites(t *testing.T) {
// Verify cache is in consistent state // Verify cache is in consistent state
assert.GreaterOrEqual(t, len(c.organizations), numWriters) 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)
}
+1 -1
View File
@@ -10,7 +10,7 @@ import (
"github.com/alecthomas/kong" "github.com/alecthomas/kong"
"github.com/apex/log" "github.com/apex/log"
"gitlab.com/unboundsoftware/schemas/ctl" "gitea.unbound.se/unboundsoftware/schemas/ctl"
) )
type Context struct { type Context struct {
+18 -9
View File
@@ -26,15 +26,15 @@ import (
"gitlab.com/unboundsoftware/eventsourced/eventsourced" "gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitlab.com/unboundsoftware/eventsourced/pg" "gitlab.com/unboundsoftware/eventsourced/pg"
"gitlab.com/unboundsoftware/schemas/cache" "gitea.unbound.se/unboundsoftware/schemas/cache"
"gitlab.com/unboundsoftware/schemas/domain" "gitea.unbound.se/unboundsoftware/schemas/domain"
"gitlab.com/unboundsoftware/schemas/graph" "gitea.unbound.se/unboundsoftware/schemas/graph"
"gitlab.com/unboundsoftware/schemas/graph/generated" "gitea.unbound.se/unboundsoftware/schemas/graph/generated"
"gitlab.com/unboundsoftware/schemas/health" "gitea.unbound.se/unboundsoftware/schemas/health"
"gitlab.com/unboundsoftware/schemas/logging" "gitea.unbound.se/unboundsoftware/schemas/logging"
"gitlab.com/unboundsoftware/schemas/middleware" "gitea.unbound.se/unboundsoftware/schemas/middleware"
"gitlab.com/unboundsoftware/schemas/monitoring" "gitea.unbound.se/unboundsoftware/schemas/monitoring"
"gitlab.com/unboundsoftware/schemas/store" "gitea.unbound.se/unboundsoftware/schemas/store"
) )
type CLI struct { type CLI struct {
@@ -92,7 +92,10 @@ func start(closeEvents chan error, logger *slog.Logger, connectToAmqpFunc func(u
pg.WithEventTypes( pg.WithEventTypes(
&domain.SubGraphUpdated{}, &domain.SubGraphUpdated{},
&domain.OrganizationAdded{}, &domain.OrganizationAdded{},
&domain.UserAddedToOrganization{},
&domain.APIKeyAdded{}, &domain.APIKeyAdded{},
&domain.APIKeyRemoved{},
&domain.OrganizationRemoved{},
), ),
) )
if err != nil { if err != nil {
@@ -127,10 +130,16 @@ func start(closeEvents chan error, logger *slog.Logger, connectToAmqpFunc func(u
goamqp.EventStreamPublisher(publisher), goamqp.EventStreamPublisher(publisher),
goamqp.TransientEventStreamConsumer("SubGraph.Updated", serviceCache.Update, domain.SubGraphUpdated{}), goamqp.TransientEventStreamConsumer("SubGraph.Updated", serviceCache.Update, domain.SubGraphUpdated{}),
goamqp.TransientEventStreamConsumer("Organization.Added", serviceCache.Update, domain.OrganizationAdded{}), 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.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("SubGraph.Updated", domain.SubGraphUpdated{}),
goamqp.WithTypeMapping("Organization.Added", domain.OrganizationAdded{}), goamqp.WithTypeMapping("Organization.Added", domain.OrganizationAdded{}),
goamqp.WithTypeMapping("Organization.UserAdded", domain.UserAddedToOrganization{}),
goamqp.WithTypeMapping("Organization.APIKeyAdded", domain.APIKeyAdded{}), 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 { if err := conn.Start(rootCtx, setups...); err != nil {
return fmt.Errorf("failed to setup AMQP: %v", err) return fmt.Errorf("failed to setup AMQP: %v", err)
+3 -3
View File
@@ -10,9 +10,9 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gitlab.com/unboundsoftware/eventsourced/eventsourced" "gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitlab.com/unboundsoftware/schemas/domain" "gitea.unbound.se/unboundsoftware/schemas/domain"
"gitlab.com/unboundsoftware/schemas/hash" "gitea.unbound.se/unboundsoftware/schemas/hash"
"gitlab.com/unboundsoftware/schemas/middleware" "gitea.unbound.se/unboundsoftware/schemas/middleware"
) )
// MockCache is a mock implementation for testing // MockCache is a mock implementation for testing
+6
View File
@@ -23,6 +23,8 @@ func (o *Organization) Apply(event eventsourced.Event) error {
switch e := event.(type) { switch e := event.(type) {
case *OrganizationAdded: case *OrganizationAdded:
e.UpdateOrganization(o) e.UpdateOrganization(o)
case *UserAddedToOrganization:
e.UpdateOrganization(o)
case *APIKeyAdded: case *APIKeyAdded:
o.APIKeys = append(o.APIKeys, APIKey{ o.APIKeys = append(o.APIKeys, APIKey{
Name: e.Name, Name: e.Name,
@@ -36,6 +38,10 @@ func (o *Organization) Apply(event eventsourced.Event) error {
}) })
o.ChangedBy = e.Initiator o.ChangedBy = e.Initiator
o.ChangedAt = e.When() o.ChangedAt = e.When()
case *APIKeyRemoved:
e.UpdateOrganization(o)
case *OrganizationRemoved:
e.UpdateOrganization(o)
default: default:
return fmt.Errorf("unexpected event type: %+v", event) return fmt.Errorf("unexpected event type: %+v", event)
} }
+83 -1
View File
@@ -7,7 +7,7 @@ import (
"gitlab.com/unboundsoftware/eventsourced/eventsourced" "gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitlab.com/unboundsoftware/schemas/hash" "gitea.unbound.se/unboundsoftware/schemas/hash"
) )
type AddOrganization struct { type AddOrganization struct {
@@ -34,6 +34,37 @@ func (a AddOrganization) Event(context.Context) eventsourced.Event {
var _ eventsourced.Command = AddOrganization{} 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 { type AddAPIKey struct {
Name string Name string
Key string Key string
@@ -79,6 +110,57 @@ func (a AddAPIKey) Event(context.Context) eventsourced.Event {
var _ eventsourced.Command = AddAPIKey{} 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 { type UpdateSubGraph struct {
OrganizationId string OrganizationId string
Ref string Ref string
+502 -1
View File
@@ -7,10 +7,68 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitlab.com/unboundsoftware/schemas/hash" "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) { func TestAddAPIKey_Event(t *testing.T) {
type fields struct { type fields struct {
Name string Name string
@@ -74,3 +132,446 @@ func TestAddAPIKey_Event(t *testing.T) {
}) })
} }
} }
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() 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 { type APIKeyAdded struct {
eventsourced.BaseEvent eventsourced.BaseEvent
OrganizationId string `json:"organizationId"` OrganizationId string `json:"organizationId"`
@@ -34,6 +52,36 @@ func (a *APIKeyAdded) EnrichFromAggregate(aggregate eventsourced.Aggregate) {
var _ eventsourced.EnrichableEvent = &APIKeyAdded{} 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 { type SubGraphUpdated struct {
eventsourced.BaseEvent eventsourced.BaseEvent
OrganizationId string `json:"organizationId"` 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)
}
+31 -30
View File
@@ -1,9 +1,9 @@
module gitlab.com/unboundsoftware/schemas module gitea.unbound.se/unboundsoftware/schemas
go 1.25 go 1.25
require ( require (
github.com/99designs/gqlgen v0.17.83 github.com/99designs/gqlgen v0.17.86
github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/Khan/genqlient v0.8.1 github.com/Khan/genqlient v0.8.1
github.com/alecthomas/kong v1.13.0 github.com/alecthomas/kong v1.13.0
@@ -18,21 +18,21 @@ require (
github.com/sparetimecoders/goamqp v0.3.3 github.com/sparetimecoders/goamqp v0.3.3
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/vektah/gqlparser/v2 v2.5.31 github.com/vektah/gqlparser/v2 v2.5.31
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.238 github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.242
gitlab.com/unboundsoftware/eventsourced/amqp v1.9.0 gitlab.com/unboundsoftware/eventsourced/amqp v1.9.0
gitlab.com/unboundsoftware/eventsourced/eventsourced v1.19.3 gitlab.com/unboundsoftware/eventsourced/eventsourced v1.19.3
gitlab.com/unboundsoftware/eventsourced/pg v1.17.0 gitlab.com/unboundsoftware/eventsourced/pg v1.17.0
go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 go.opentelemetry.io/contrib/bridges/otelslog v0.14.0
go.opentelemetry.io/otel v1.38.0 go.opentelemetry.io/otel v1.39.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0
go.opentelemetry.io/otel/log v0.14.0 go.opentelemetry.io/otel/log v0.15.0
go.opentelemetry.io/otel/sdk v1.38.0 go.opentelemetry.io/otel/sdk v1.39.0
go.opentelemetry.io/otel/sdk/log v0.14.0 go.opentelemetry.io/otel/sdk/log v0.15.0
go.opentelemetry.io/otel/sdk/metric v1.38.0 go.opentelemetry.io/otel/sdk/metric v1.39.0
go.opentelemetry.io/otel/trace v1.38.0 go.opentelemetry.io/otel/trace v1.39.0
golang.org/x/crypto v0.45.0 golang.org/x/crypto v0.47.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@@ -45,8 +45,9 @@ require (
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/gorilla/websocket v1.5.1 // indirect github.com/gorilla/websocket v1.5.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/lib/pq v1.10.9 // indirect github.com/lib/pq v1.10.9 // indirect
github.com/mfridman/interpolate v0.0.2 // indirect github.com/mfridman/interpolate v0.0.2 // indirect
@@ -59,21 +60,21 @@ require (
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect github.com/tidwall/sjson v1.2.5 // indirect
github.com/urfave/cli/v3 v3.6.0 // indirect github.com/urfave/cli/v3 v3.6.1 // indirect
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083 // indirect github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/mod v0.29.0 // indirect golang.org/x/mod v0.31.0 // indirect
golang.org/x/net v0.47.0 // indirect golang.org/x/net v0.48.0 // indirect
golang.org/x/sync v0.18.0 // indirect golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.38.0 // indirect golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.31.0 // indirect golang.org/x/text v0.33.0 // indirect
golang.org/x/tools v0.38.0 // indirect golang.org/x/tools v0.40.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/grpc v1.75.0 // indirect google.golang.org/grpc v1.77.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect google.golang.org/protobuf v1.36.11 // indirect
) )
+62 -60
View File
@@ -1,13 +1,13 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/99designs/gqlgen v0.17.83 h1:LZOd4Of2snK5V22/ZWfBAPa3WoAZkBO70dKXM0ODHQk= github.com/99designs/gqlgen v0.17.86 h1:C8N3UTa5heXX6twl+b0AJyGkTwYL6dNmFrgZNLRcU6w=
github.com/99designs/gqlgen v0.17.83/go.mod h1:q6Lb64wknFqNFSbSUGzKRKupklvY/xgNr62g0GGWPB8= github.com/99designs/gqlgen v0.17.86/go.mod h1:KTrPl+vHA1IUzNlh4EYkl7+tcErL3MgKnhHrBcV74Fw=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= 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/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 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs=
github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= 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.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y= 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 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=
github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= 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 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
@@ -57,6 +57,8 @@ github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
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-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -70,8 +72,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/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 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 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/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
@@ -165,93 +167,93 @@ 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-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-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
github.com/urfave/cli/v3 v3.6.0 h1:oIdArVjkdIXHWg3iqxgmqwQGC8NM0JtdgwQAj2sRwFo= github.com/urfave/cli/v3 v3.6.1 h1:j8Qq8NyUawj/7rTYdBGrxcH7A/j7/G8Q5LhWEW4G3Mo=
github.com/urfave/cli/v3 v3.6.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/vektah/gqlparser/v2 v2.5.31 h1:YhWGA1mfTjID7qJhd1+Vxhpk5HTgydrGU9IgkWBTJ7k= github.com/vektah/gqlparser/v2 v2.5.31 h1:YhWGA1mfTjID7qJhd1+Vxhpk5HTgydrGU9IgkWBTJ7k=
github.com/vektah/gqlparser/v2 v2.5.31/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts= github.com/vektah/gqlparser/v2 v2.5.31/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts=
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083 h1:8/D7f8gKxTBjW+SZK4mhxTTBVpxcqeBgWF1Rfmltbfk= 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/astjson v0.0.0-20250106123708-be463c97e083/go.mod h1:eOTL6acwctsN4F3b7YE+eE2t8zcJ/doLm9sZzsxxxrE=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.238 h1:ll0BtYVMziRa8v0T/f+DQOJ/1x3Dq5puifJNnxF0R+M= github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.242 h1:0ieQmRxYz0nbJEbaaA4Cx2RPcxlomhQ8KI31uuevWx0=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.238/go.mod h1:ErOQH1ki2+SZB8JjpTyGVnoBpg5picIyjvuWQJP4abg= github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.242/go.mod h1:mX25ASEQiKamxaFSK6NZihh0oDCigIuzro30up4mFH4=
gitlab.com/unboundsoftware/eventsourced/amqp v1.9.0 h1:TdBJnrnrxJrPhC4i6KTFUElZa3k/fFXiGwg0sds5aAo= gitlab.com/unboundsoftware/eventsourced/amqp v1.9.0 h1:TdBJnrnrxJrPhC4i6KTFUElZa3k/fFXiGwg0sds5aAo=
gitlab.com/unboundsoftware/eventsourced/amqp v1.9.0/go.mod h1:VauAph7uCvEakYNdHkkSAoOOGKvEuUA/uhsR376ThbI= gitlab.com/unboundsoftware/eventsourced/amqp v1.9.0/go.mod h1:VauAph7uCvEakYNdHkkSAoOOGKvEuUA/uhsR376ThbI=
gitlab.com/unboundsoftware/eventsourced/eventsourced v1.19.3 h1:0HbDHF4sHfoyDrbPLMFWvsQLbTl2ITrpI9PjDIZsV1Y= gitlab.com/unboundsoftware/eventsourced/eventsourced v1.19.3 h1:0HbDHF4sHfoyDrbPLMFWvsQLbTl2ITrpI9PjDIZsV1Y=
gitlab.com/unboundsoftware/eventsourced/eventsourced v1.19.3/go.mod h1:LrA7I7etRmhIC1PjO8c26BHm+gWsy2rC3eSMe5+XUWE= gitlab.com/unboundsoftware/eventsourced/eventsourced v1.19.3/go.mod h1:LrA7I7etRmhIC1PjO8c26BHm+gWsy2rC3eSMe5+XUWE=
gitlab.com/unboundsoftware/eventsourced/pg v1.17.0 h1:pUJzMpNPX0GVsffRZXlpKR1d7Ws96KTxJwbLFPpASSc= gitlab.com/unboundsoftware/eventsourced/pg v1.17.0 h1:pUJzMpNPX0GVsffRZXlpKR1d7Ws96KTxJwbLFPpASSc=
gitlab.com/unboundsoftware/eventsourced/pg v1.17.0/go.mod h1:WgPrZhyCbsZ3TG2tPUbh2MUjOEaANJjsWi/0hlIwRVU= 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.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s= go.opentelemetry.io/contrib/bridges/otelslog v0.14.0 h1:eypSOd+0txRKCXPNyqLPsbSfA0jULgJcGmSAdFAnrCM=
go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4= go.opentelemetry.io/contrib/bridges/otelslog v0.14.0/go.mod h1:CRGvIBL/aAxpQU34ZxyQVFlovVcp67s4cAmQu8Jh9mc=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 h1:nKP4Z2ejtHn3yShBb+2KawiXgpn8In5cT7aO2wXuOTE=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0/go.mod h1:NwjeBbNigsO4Aj9WgM0C+cKIrxsZUaRmZUO7A8I7u8o=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0 h1:0BSddrtQqLEylcErkeFrJBmwFzcqfQq9+/uxfTZq+HE=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0/go.mod h1:87sjYuAPzaRCtdd09GU5gM1U9wQLrrcYrm77mh5EBoc=
go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY=
go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= go.opentelemetry.io/otel/sdk/log v0.15.0 h1:WgMEHOUt5gjJE93yqfqJOkRflApNif84kxoHWS9VVHE=
go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= go.opentelemetry.io/otel/sdk/log v0.15.0/go.mod h1:qDC/FlKQCXfH5hokGsNg9aUBGMJQsrUyeOiW5u+dKBQ=
go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM=
go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= 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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 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-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.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-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.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-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-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.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 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 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-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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+2 -2
View File
@@ -1,8 +1,8 @@
package graph package graph
import ( import (
"gitlab.com/unboundsoftware/schemas/domain" "gitea.unbound.se/unboundsoftware/schemas/domain"
"gitlab.com/unboundsoftware/schemas/graph/model" "gitea.unbound.se/unboundsoftware/schemas/graph/model"
) )
func ToGqlOrganizations(orgs []domain.Organization) []*model.Organization { func ToGqlOrganizations(orgs []domain.Organization) []*model.Organization {
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"gitlab.com/unboundsoftware/schemas/graph/model" "gitea.unbound.se/unboundsoftware/schemas/graph/model"
) )
// CommandExecutor is an interface for executing external commands // CommandExecutor is an interface for executing external commands
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"gitlab.com/unboundsoftware/schemas/graph/model" "gitea.unbound.se/unboundsoftware/schemas/graph/model"
) )
// MockCommandExecutor implements CommandExecutor for testing // MockCommandExecutor implements CommandExecutor for testing
+466 -61
View File
@@ -17,7 +17,7 @@ import (
gqlparser "github.com/vektah/gqlparser/v2" gqlparser "github.com/vektah/gqlparser/v2"
"github.com/vektah/gqlparser/v2/ast" "github.com/vektah/gqlparser/v2/ast"
"gitlab.com/unboundsoftware/schemas/graph/model" "gitea.unbound.se/unboundsoftware/schemas/graph/model"
) )
// region ************************** generated!.gotpl ************************** // region ************************** generated!.gotpl **************************
@@ -61,9 +61,12 @@ type ComplexityRoot struct {
} }
Mutation struct { Mutation struct {
AddAPIKey func(childComplexity int, input *model.InputAPIKey) int AddAPIKey func(childComplexity int, input *model.InputAPIKey) int
AddOrganization func(childComplexity int, name string) int AddOrganization func(childComplexity int, name string) int
UpdateSubGraph func(childComplexity int, input model.InputSubGraph) int AddUserToOrganization func(childComplexity int, organizationID string, userID string) int
RemoveAPIKey func(childComplexity int, organizationID string, keyName string) int
RemoveOrganization func(childComplexity int, organizationID string) int
UpdateSubGraph func(childComplexity int, input model.InputSubGraph) int
} }
Organization struct { Organization struct {
@@ -74,9 +77,10 @@ type ComplexityRoot struct {
} }
Query struct { Query struct {
LatestSchema func(childComplexity int, ref string) int AllOrganizations func(childComplexity int) int
Organizations func(childComplexity int) int LatestSchema func(childComplexity int, ref string) int
Supergraph func(childComplexity int, ref string, isAfter *string) int Organizations func(childComplexity int) int
Supergraph func(childComplexity int, ref string, isAfter *string) int
} }
SchemaUpdate struct { SchemaUpdate struct {
@@ -119,11 +123,15 @@ type ComplexityRoot struct {
type MutationResolver interface { type MutationResolver interface {
AddOrganization(ctx context.Context, name string) (*model.Organization, error) AddOrganization(ctx context.Context, name string) (*model.Organization, error)
AddUserToOrganization(ctx context.Context, organizationID string, userID string) (*model.Organization, error)
AddAPIKey(ctx context.Context, input *model.InputAPIKey) (*model.APIKey, error) AddAPIKey(ctx context.Context, input *model.InputAPIKey) (*model.APIKey, error)
RemoveAPIKey(ctx context.Context, organizationID string, keyName string) (*model.Organization, error)
RemoveOrganization(ctx context.Context, organizationID string) (bool, error)
UpdateSubGraph(ctx context.Context, input model.InputSubGraph) (*model.SubGraph, error) UpdateSubGraph(ctx context.Context, input model.InputSubGraph) (*model.SubGraph, error)
} }
type QueryResolver interface { type QueryResolver interface {
Organizations(ctx context.Context) ([]*model.Organization, error) Organizations(ctx context.Context) ([]*model.Organization, error)
AllOrganizations(ctx context.Context) ([]*model.Organization, error)
Supergraph(ctx context.Context, ref string, isAfter *string) (model.Supergraph, error) Supergraph(ctx context.Context, ref string, isAfter *string) (model.Supergraph, error)
LatestSchema(ctx context.Context, ref string) (*model.SchemaUpdate, error) LatestSchema(ctx context.Context, ref string) (*model.SchemaUpdate, error)
} }
@@ -215,6 +223,39 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
} }
return e.complexity.Mutation.AddOrganization(childComplexity, args["name"].(string)), true return e.complexity.Mutation.AddOrganization(childComplexity, args["name"].(string)), true
case "Mutation.addUserToOrganization":
if e.complexity.Mutation.AddUserToOrganization == nil {
break
}
args, err := ec.field_Mutation_addUserToOrganization_args(ctx, rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.AddUserToOrganization(childComplexity, args["organizationId"].(string), args["userId"].(string)), true
case "Mutation.removeAPIKey":
if e.complexity.Mutation.RemoveAPIKey == nil {
break
}
args, err := ec.field_Mutation_removeAPIKey_args(ctx, rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.RemoveAPIKey(childComplexity, args["organizationId"].(string), args["keyName"].(string)), true
case "Mutation.removeOrganization":
if e.complexity.Mutation.RemoveOrganization == nil {
break
}
args, err := ec.field_Mutation_removeOrganization_args(ctx, rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.RemoveOrganization(childComplexity, args["organizationId"].(string)), true
case "Mutation.updateSubGraph": case "Mutation.updateSubGraph":
if e.complexity.Mutation.UpdateSubGraph == nil { if e.complexity.Mutation.UpdateSubGraph == nil {
break break
@@ -252,6 +293,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.Organization.Users(childComplexity), true return e.complexity.Organization.Users(childComplexity), true
case "Query.allOrganizations":
if e.complexity.Query.AllOrganizations == nil {
break
}
return e.complexity.Query.AllOrganizations(childComplexity), true
case "Query.latestSchema": case "Query.latestSchema":
if e.complexity.Query.LatestSchema == nil { if e.complexity.Query.LatestSchema == nil {
break break
@@ -532,13 +579,17 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er
var sources = []*ast.Source{ var sources = []*ast.Source{
{Name: "../schema.graphqls", Input: `type Query { {Name: "../schema.graphqls", Input: `type Query {
organizations: [Organization!]! @auth(user: true) organizations: [Organization!]! @auth(user: true)
supergraph(ref: String!, isAfter: String): Supergraph! @auth(organization: true) allOrganizations: [Organization!]! @auth(user: true)
latestSchema(ref: String!): SchemaUpdate! @auth(organization: true) supergraph(ref: String!, isAfter: String): Supergraph! @auth(user: true, organization: true)
latestSchema(ref: String!): SchemaUpdate! @auth(user: true, organization: true)
} }
type Mutation { type Mutation {
addOrganization(name: String!): Organization! @auth(user: true) addOrganization(name: String!): Organization! @auth(user: true)
addUserToOrganization(organizationId: ID!, userId: String!): Organization! @auth(user: true)
addAPIKey(input: InputAPIKey): APIKey! @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) updateSubGraph(input: InputSubGraph!): SubGraph! @auth(organization: true)
} }
@@ -644,7 +695,7 @@ func (ec *executionContext) dir_auth_args(ctx context.Context, rawArgs map[strin
func (ec *executionContext) field_Mutation_addAPIKey_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { func (ec *executionContext) field_Mutation_addAPIKey_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error var err error
args := map[string]any{} args := map[string]any{}
arg0, err := graphql.ProcessArgField(ctx, rawArgs, "input", ec.unmarshalOInputAPIKey2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐInputAPIKey) arg0, err := graphql.ProcessArgField(ctx, rawArgs, "input", ec.unmarshalOInputAPIKey2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐInputAPIKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -663,10 +714,53 @@ func (ec *executionContext) field_Mutation_addOrganization_args(ctx context.Cont
return args, nil return args, nil
} }
func (ec *executionContext) field_Mutation_addUserToOrganization_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := graphql.ProcessArgField(ctx, rawArgs, "organizationId", ec.unmarshalNID2string)
if err != nil {
return nil, err
}
args["organizationId"] = arg0
arg1, err := graphql.ProcessArgField(ctx, rawArgs, "userId", ec.unmarshalNString2string)
if err != nil {
return nil, err
}
args["userId"] = arg1
return args, nil
}
func (ec *executionContext) field_Mutation_removeAPIKey_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := graphql.ProcessArgField(ctx, rawArgs, "organizationId", ec.unmarshalNID2string)
if err != nil {
return nil, err
}
args["organizationId"] = arg0
arg1, err := graphql.ProcessArgField(ctx, rawArgs, "keyName", ec.unmarshalNString2string)
if err != nil {
return nil, err
}
args["keyName"] = arg1
return args, nil
}
func (ec *executionContext) field_Mutation_removeOrganization_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := graphql.ProcessArgField(ctx, rawArgs, "organizationId", ec.unmarshalNID2string)
if err != nil {
return nil, err
}
args["organizationId"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_updateSubGraph_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { func (ec *executionContext) field_Mutation_updateSubGraph_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error var err error
args := map[string]any{} args := map[string]any{}
arg0, err := graphql.ProcessArgField(ctx, rawArgs, "input", ec.unmarshalNInputSubGraph2gitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐInputSubGraph) arg0, err := graphql.ProcessArgField(ctx, rawArgs, "input", ec.unmarshalNInputSubGraph2giteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐInputSubGraph)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -872,7 +966,7 @@ func (ec *executionContext) _APIKey_organization(ctx context.Context, field grap
return obj.Organization, nil return obj.Organization, nil
}, },
nil, nil,
ec.marshalNOrganization2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization, ec.marshalNOrganization2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization,
true, true,
true, true,
) )
@@ -1017,7 +1111,7 @@ func (ec *executionContext) _Mutation_addOrganization(ctx context.Context, field
next = directive1 next = directive1
return next return next
}, },
ec.marshalNOrganization2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization, ec.marshalNOrganization2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization,
true, true,
true, true,
) )
@@ -1057,6 +1151,75 @@ func (ec *executionContext) fieldContext_Mutation_addOrganization(ctx context.Co
return fc, nil return fc, nil
} }
func (ec *executionContext) _Mutation_addUserToOrganization(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Mutation_addUserToOrganization,
func(ctx context.Context) (any, error) {
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().AddUserToOrganization(ctx, fc.Args["organizationId"].(string), fc.Args["userId"].(string))
},
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
user, err := ec.unmarshalOBoolean2ᚖbool(ctx, true)
if err != nil {
var zeroVal *model.Organization
return zeroVal, err
}
if ec.directives.Auth == nil {
var zeroVal *model.Organization
return zeroVal, errors.New("directive auth is not implemented")
}
return ec.directives.Auth(ctx, nil, directive0, user, nil)
}
next = directive1
return next
},
ec.marshalNOrganization2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization,
true,
true,
)
}
func (ec *executionContext) fieldContext_Mutation_addUserToOrganization(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Mutation",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "id":
return ec.fieldContext_Organization_id(ctx, field)
case "name":
return ec.fieldContext_Organization_name(ctx, field)
case "users":
return ec.fieldContext_Organization_users(ctx, field)
case "apiKeys":
return ec.fieldContext_Organization_apiKeys(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
},
}
defer func() {
if r := recover(); r != nil {
err = ec.Recover(ctx, r)
ec.Error(ctx, err)
}
}()
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Mutation_addUserToOrganization_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
return fc, nil
}
func (ec *executionContext) _Mutation_addAPIKey(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_addAPIKey(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField( return graphql.ResolveField(
ctx, ctx,
@@ -1086,7 +1249,7 @@ func (ec *executionContext) _Mutation_addAPIKey(ctx context.Context, field graph
next = directive1 next = directive1
return next return next
}, },
ec.marshalNAPIKey2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKey, ec.marshalNAPIKey2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKey,
true, true,
true, true,
) )
@@ -1132,6 +1295,134 @@ func (ec *executionContext) fieldContext_Mutation_addAPIKey(ctx context.Context,
return fc, nil return fc, nil
} }
func (ec *executionContext) _Mutation_removeAPIKey(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Mutation_removeAPIKey,
func(ctx context.Context) (any, error) {
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().RemoveAPIKey(ctx, fc.Args["organizationId"].(string), fc.Args["keyName"].(string))
},
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
user, err := ec.unmarshalOBoolean2ᚖbool(ctx, true)
if err != nil {
var zeroVal *model.Organization
return zeroVal, err
}
if ec.directives.Auth == nil {
var zeroVal *model.Organization
return zeroVal, errors.New("directive auth is not implemented")
}
return ec.directives.Auth(ctx, nil, directive0, user, nil)
}
next = directive1
return next
},
ec.marshalNOrganization2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization,
true,
true,
)
}
func (ec *executionContext) fieldContext_Mutation_removeAPIKey(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Mutation",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "id":
return ec.fieldContext_Organization_id(ctx, field)
case "name":
return ec.fieldContext_Organization_name(ctx, field)
case "users":
return ec.fieldContext_Organization_users(ctx, field)
case "apiKeys":
return ec.fieldContext_Organization_apiKeys(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
},
}
defer func() {
if r := recover(); r != nil {
err = ec.Recover(ctx, r)
ec.Error(ctx, err)
}
}()
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Mutation_removeAPIKey_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
return fc, nil
}
func (ec *executionContext) _Mutation_removeOrganization(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Mutation_removeOrganization,
func(ctx context.Context) (any, error) {
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().RemoveOrganization(ctx, fc.Args["organizationId"].(string))
},
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
user, err := ec.unmarshalOBoolean2ᚖbool(ctx, true)
if err != nil {
var zeroVal bool
return zeroVal, err
}
if ec.directives.Auth == nil {
var zeroVal bool
return zeroVal, errors.New("directive auth is not implemented")
}
return ec.directives.Auth(ctx, nil, directive0, user, nil)
}
next = directive1
return next
},
ec.marshalNBoolean2bool,
true,
true,
)
}
func (ec *executionContext) fieldContext_Mutation_removeOrganization(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Mutation",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Boolean does not have child fields")
},
}
defer func() {
if r := recover(); r != nil {
err = ec.Recover(ctx, r)
ec.Error(ctx, err)
}
}()
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Mutation_removeOrganization_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
return fc, nil
}
func (ec *executionContext) _Mutation_updateSubGraph(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_updateSubGraph(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField( return graphql.ResolveField(
ctx, ctx,
@@ -1161,7 +1452,7 @@ func (ec *executionContext) _Mutation_updateSubGraph(ctx context.Context, field
next = directive1 next = directive1
return next return next
}, },
ec.marshalNSubGraph2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraph, ec.marshalNSubGraph2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraph,
true, true,
true, true,
) )
@@ -1275,7 +1566,7 @@ func (ec *executionContext) _Organization_users(ctx context.Context, field graph
return obj.Users, nil return obj.Users, nil
}, },
nil, nil,
ec.marshalNUser2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐUserᚄ, ec.marshalNUser2ᚕᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐUserᚄ,
true, true,
true, true,
) )
@@ -1308,7 +1599,7 @@ func (ec *executionContext) _Organization_apiKeys(ctx context.Context, field gra
return obj.APIKeys, nil return obj.APIKeys, nil
}, },
nil, nil,
ec.marshalNAPIKey2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKeyᚄ, ec.marshalNAPIKey2ᚕᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKeyᚄ,
true, true,
true, true,
) )
@@ -1371,7 +1662,7 @@ func (ec *executionContext) _Query_organizations(ctx context.Context, field grap
next = directive1 next = directive1
return next return next
}, },
ec.marshalNOrganization2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganizationᚄ, ec.marshalNOrganization2ᚕᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganizationᚄ,
true, true,
true, true,
) )
@@ -1400,6 +1691,63 @@ func (ec *executionContext) fieldContext_Query_organizations(_ context.Context,
return fc, nil return fc, nil
} }
func (ec *executionContext) _Query_allOrganizations(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Query_allOrganizations,
func(ctx context.Context) (any, error) {
return ec.resolvers.Query().AllOrganizations(ctx)
},
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
user, err := ec.unmarshalOBoolean2ᚖbool(ctx, true)
if err != nil {
var zeroVal []*model.Organization
return zeroVal, err
}
if ec.directives.Auth == nil {
var zeroVal []*model.Organization
return zeroVal, errors.New("directive auth is not implemented")
}
return ec.directives.Auth(ctx, nil, directive0, user, nil)
}
next = directive1
return next
},
ec.marshalNOrganization2ᚕᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganizationᚄ,
true,
true,
)
}
func (ec *executionContext) fieldContext_Query_allOrganizations(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Query",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "id":
return ec.fieldContext_Organization_id(ctx, field)
case "name":
return ec.fieldContext_Organization_name(ctx, field)
case "users":
return ec.fieldContext_Organization_users(ctx, field)
case "apiKeys":
return ec.fieldContext_Organization_apiKeys(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _Query_supergraph(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Query_supergraph(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField( return graphql.ResolveField(
ctx, ctx,
@@ -1414,6 +1762,11 @@ func (ec *executionContext) _Query_supergraph(ctx context.Context, field graphql
directive0 := next directive0 := next
directive1 := func(ctx context.Context) (any, error) { directive1 := func(ctx context.Context) (any, error) {
user, err := ec.unmarshalOBoolean2ᚖbool(ctx, true)
if err != nil {
var zeroVal model.Supergraph
return zeroVal, err
}
organization, err := ec.unmarshalOBoolean2ᚖbool(ctx, true) organization, err := ec.unmarshalOBoolean2ᚖbool(ctx, true)
if err != nil { if err != nil {
var zeroVal model.Supergraph var zeroVal model.Supergraph
@@ -1423,13 +1776,13 @@ func (ec *executionContext) _Query_supergraph(ctx context.Context, field graphql
var zeroVal model.Supergraph var zeroVal model.Supergraph
return zeroVal, errors.New("directive auth is not implemented") return zeroVal, errors.New("directive auth is not implemented")
} }
return ec.directives.Auth(ctx, nil, directive0, nil, organization) return ec.directives.Auth(ctx, nil, directive0, user, organization)
} }
next = directive1 next = directive1
return next return next
}, },
ec.marshalNSupergraph2gitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSupergraph, ec.marshalNSupergraph2giteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSupergraph,
true, true,
true, true,
) )
@@ -1473,6 +1826,11 @@ func (ec *executionContext) _Query_latestSchema(ctx context.Context, field graph
directive0 := next directive0 := next
directive1 := func(ctx context.Context) (any, error) { directive1 := func(ctx context.Context) (any, error) {
user, err := ec.unmarshalOBoolean2ᚖbool(ctx, true)
if err != nil {
var zeroVal *model.SchemaUpdate
return zeroVal, err
}
organization, err := ec.unmarshalOBoolean2ᚖbool(ctx, true) organization, err := ec.unmarshalOBoolean2ᚖbool(ctx, true)
if err != nil { if err != nil {
var zeroVal *model.SchemaUpdate var zeroVal *model.SchemaUpdate
@@ -1482,13 +1840,13 @@ func (ec *executionContext) _Query_latestSchema(ctx context.Context, field graph
var zeroVal *model.SchemaUpdate var zeroVal *model.SchemaUpdate
return zeroVal, errors.New("directive auth is not implemented") return zeroVal, errors.New("directive auth is not implemented")
} }
return ec.directives.Auth(ctx, nil, directive0, nil, organization) return ec.directives.Auth(ctx, nil, directive0, user, organization)
} }
next = directive1 next = directive1
return next return next
}, },
ec.marshalNSchemaUpdate2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSchemaUpdate, ec.marshalNSchemaUpdate2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSchemaUpdate,
true, true,
true, true,
) )
@@ -1704,7 +2062,7 @@ func (ec *executionContext) _SchemaUpdate_subGraphs(ctx context.Context, field g
return obj.SubGraphs, nil return obj.SubGraphs, nil
}, },
nil, nil,
ec.marshalNSubGraph2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraphᚄ, ec.marshalNSubGraph2ᚕᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraphᚄ,
true, true,
true, true,
) )
@@ -2068,7 +2426,7 @@ func (ec *executionContext) _SubGraphs_subGraphs(ctx context.Context, field grap
return obj.SubGraphs, nil return obj.SubGraphs, nil
}, },
nil, nil,
ec.marshalNSubGraph2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraphᚄ, ec.marshalNSubGraph2ᚕᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraphᚄ,
true, true,
true, true,
) )
@@ -2132,7 +2490,7 @@ func (ec *executionContext) _Subscription_schemaUpdates(ctx context.Context, fie
next = directive1 next = directive1
return next return next
}, },
ec.marshalNSchemaUpdate2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSchemaUpdate, ec.marshalNSchemaUpdate2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSchemaUpdate,
true, true,
true, true,
) )
@@ -3838,7 +4196,11 @@ func (ec *executionContext) _Supergraph(ctx context.Context, sel ast.SelectionSe
} }
return ec._SubGraphs(ctx, sel, obj) return ec._SubGraphs(ctx, sel, obj)
default: default:
panic(fmt.Errorf("unexpected type %T", obj)) if typedObj, ok := obj.(graphql.Marshaler); ok {
return typedObj
} else {
panic(fmt.Errorf("unexpected type %T; non-generated variants of Supergraph must implement graphql.Marshaler", obj))
}
} }
} }
@@ -3938,6 +4300,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ out.Invalids++
} }
case "addUserToOrganization":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_addUserToOrganization(ctx, field)
})
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "addAPIKey": case "addAPIKey":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_addAPIKey(ctx, field) return ec._Mutation_addAPIKey(ctx, field)
@@ -3945,6 +4314,20 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ out.Invalids++
} }
case "removeAPIKey":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_removeAPIKey(ctx, field)
})
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "removeOrganization":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_removeOrganization(ctx, field)
})
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "updateSubGraph": case "updateSubGraph":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_updateSubGraph(ctx, field) return ec._Mutation_updateSubGraph(ctx, field)
@@ -4069,6 +4452,28 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
} }
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) })
case "allOrganizations":
field := field
innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Query_allOrganizations(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&fs.Invalids, 1)
}
return res
}
rrm := func(ctx context.Context) graphql.Marshaler {
return ec.OperationContext.RootResolverMiddleware(ctx,
func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
}
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) })
case "supergraph": case "supergraph":
field := field field := field
@@ -4321,7 +4726,7 @@ func (ec *executionContext) _Subscription(ctx context.Context, sel ast.Selection
Object: "Subscription", Object: "Subscription",
}) })
if len(fields) != 1 { if len(fields) != 1 {
ec.Errorf(ctx, "must subscribe to exactly one stream") graphql.AddErrorf(ctx, "must subscribe to exactly one stream")
return nil return nil
} }
@@ -4751,11 +5156,11 @@ func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, o
// region ***************************** type.gotpl ***************************** // region ***************************** type.gotpl *****************************
func (ec *executionContext) marshalNAPIKey2gitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKey(ctx context.Context, sel ast.SelectionSet, v model.APIKey) graphql.Marshaler { func (ec *executionContext) marshalNAPIKey2giteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKey(ctx context.Context, sel ast.SelectionSet, v model.APIKey) graphql.Marshaler {
return ec._APIKey(ctx, sel, &v) return ec._APIKey(ctx, sel, &v)
} }
func (ec *executionContext) marshalNAPIKey2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKeyᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.APIKey) graphql.Marshaler { func (ec *executionContext) marshalNAPIKey2ᚕᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKeyᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.APIKey) graphql.Marshaler {
ret := make(graphql.Array, len(v)) ret := make(graphql.Array, len(v))
var wg sync.WaitGroup var wg sync.WaitGroup
isLen1 := len(v) == 1 isLen1 := len(v) == 1
@@ -4779,7 +5184,7 @@ func (ec *executionContext) marshalNAPIKey2ᚕᚖgitlabᚗcomᚋunboundsoftware
if !isLen1 { if !isLen1 {
defer wg.Done() defer wg.Done()
} }
ret[i] = ec.marshalNAPIKey2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKey(ctx, sel, v[i]) ret[i] = ec.marshalNAPIKey2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKey(ctx, sel, v[i])
} }
if isLen1 { if isLen1 {
f(i) f(i)
@@ -4799,10 +5204,10 @@ func (ec *executionContext) marshalNAPIKey2ᚕᚖgitlabᚗcomᚋunboundsoftware
return ret return ret
} }
func (ec *executionContext) marshalNAPIKey2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKey(ctx context.Context, sel ast.SelectionSet, v *model.APIKey) graphql.Marshaler { func (ec *executionContext) marshalNAPIKey2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKey(ctx context.Context, sel ast.SelectionSet, v *model.APIKey) graphql.Marshaler {
if v == nil { if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow") graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
} }
return graphql.Null return graphql.Null
} }
@@ -4819,7 +5224,7 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se
res := graphql.MarshalBoolean(v) res := graphql.MarshalBoolean(v)
if res == graphql.Null { if res == graphql.Null {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow") graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
} }
} }
return res return res
@@ -4835,13 +5240,13 @@ func (ec *executionContext) marshalNID2string(ctx context.Context, sel ast.Selec
res := graphql.MarshalID(v) res := graphql.MarshalID(v)
if res == graphql.Null { if res == graphql.Null {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow") graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
} }
} }
return res return res
} }
func (ec *executionContext) unmarshalNInputSubGraph2gitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐInputSubGraph(ctx context.Context, v any) (model.InputSubGraph, error) { func (ec *executionContext) unmarshalNInputSubGraph2giteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐInputSubGraph(ctx context.Context, v any) (model.InputSubGraph, error) {
res, err := ec.unmarshalInputInputSubGraph(ctx, v) res, err := ec.unmarshalInputInputSubGraph(ctx, v)
return res, graphql.ErrorOnPath(ctx, err) return res, graphql.ErrorOnPath(ctx, err)
} }
@@ -4856,17 +5261,17 @@ func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.Selecti
res := graphql.MarshalInt(v) res := graphql.MarshalInt(v)
if res == graphql.Null { if res == graphql.Null {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow") graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
} }
} }
return res return res
} }
func (ec *executionContext) marshalNOrganization2gitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization(ctx context.Context, sel ast.SelectionSet, v model.Organization) graphql.Marshaler { func (ec *executionContext) marshalNOrganization2giteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization(ctx context.Context, sel ast.SelectionSet, v model.Organization) graphql.Marshaler {
return ec._Organization(ctx, sel, &v) return ec._Organization(ctx, sel, &v)
} }
func (ec *executionContext) marshalNOrganization2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganizationᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.Organization) graphql.Marshaler { func (ec *executionContext) marshalNOrganization2ᚕᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganizationᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.Organization) graphql.Marshaler {
ret := make(graphql.Array, len(v)) ret := make(graphql.Array, len(v))
var wg sync.WaitGroup var wg sync.WaitGroup
isLen1 := len(v) == 1 isLen1 := len(v) == 1
@@ -4890,7 +5295,7 @@ func (ec *executionContext) marshalNOrganization2ᚕᚖgitlabᚗcomᚋunboundsof
if !isLen1 { if !isLen1 {
defer wg.Done() defer wg.Done()
} }
ret[i] = ec.marshalNOrganization2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization(ctx, sel, v[i]) ret[i] = ec.marshalNOrganization2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization(ctx, sel, v[i])
} }
if isLen1 { if isLen1 {
f(i) f(i)
@@ -4910,24 +5315,24 @@ func (ec *executionContext) marshalNOrganization2ᚕᚖgitlabᚗcomᚋunboundsof
return ret return ret
} }
func (ec *executionContext) marshalNOrganization2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization(ctx context.Context, sel ast.SelectionSet, v *model.Organization) graphql.Marshaler { func (ec *executionContext) marshalNOrganization2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization(ctx context.Context, sel ast.SelectionSet, v *model.Organization) graphql.Marshaler {
if v == nil { if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow") graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
} }
return graphql.Null return graphql.Null
} }
return ec._Organization(ctx, sel, v) return ec._Organization(ctx, sel, v)
} }
func (ec *executionContext) marshalNSchemaUpdate2gitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSchemaUpdate(ctx context.Context, sel ast.SelectionSet, v model.SchemaUpdate) graphql.Marshaler { func (ec *executionContext) marshalNSchemaUpdate2giteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSchemaUpdate(ctx context.Context, sel ast.SelectionSet, v model.SchemaUpdate) graphql.Marshaler {
return ec._SchemaUpdate(ctx, sel, &v) return ec._SchemaUpdate(ctx, sel, &v)
} }
func (ec *executionContext) marshalNSchemaUpdate2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSchemaUpdate(ctx context.Context, sel ast.SelectionSet, v *model.SchemaUpdate) graphql.Marshaler { func (ec *executionContext) marshalNSchemaUpdate2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSchemaUpdate(ctx context.Context, sel ast.SelectionSet, v *model.SchemaUpdate) graphql.Marshaler {
if v == nil { if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow") graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
} }
return graphql.Null return graphql.Null
} }
@@ -4944,7 +5349,7 @@ func (ec *executionContext) marshalNString2string(ctx context.Context, sel ast.S
res := graphql.MarshalString(v) res := graphql.MarshalString(v)
if res == graphql.Null { if res == graphql.Null {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow") graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
} }
} }
return res return res
@@ -4980,11 +5385,11 @@ func (ec *executionContext) marshalNString2ᚕstringᚄ(ctx context.Context, sel
return ret return ret
} }
func (ec *executionContext) marshalNSubGraph2gitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraph(ctx context.Context, sel ast.SelectionSet, v model.SubGraph) graphql.Marshaler { func (ec *executionContext) marshalNSubGraph2giteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraph(ctx context.Context, sel ast.SelectionSet, v model.SubGraph) graphql.Marshaler {
return ec._SubGraph(ctx, sel, &v) return ec._SubGraph(ctx, sel, &v)
} }
func (ec *executionContext) marshalNSubGraph2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraphᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.SubGraph) graphql.Marshaler { func (ec *executionContext) marshalNSubGraph2ᚕᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraphᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.SubGraph) graphql.Marshaler {
ret := make(graphql.Array, len(v)) ret := make(graphql.Array, len(v))
var wg sync.WaitGroup var wg sync.WaitGroup
isLen1 := len(v) == 1 isLen1 := len(v) == 1
@@ -5008,7 +5413,7 @@ func (ec *executionContext) marshalNSubGraph2ᚕᚖgitlabᚗcomᚋunboundsoftwar
if !isLen1 { if !isLen1 {
defer wg.Done() defer wg.Done()
} }
ret[i] = ec.marshalNSubGraph2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraph(ctx, sel, v[i]) ret[i] = ec.marshalNSubGraph2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraph(ctx, sel, v[i])
} }
if isLen1 { if isLen1 {
f(i) f(i)
@@ -5028,20 +5433,20 @@ func (ec *executionContext) marshalNSubGraph2ᚕᚖgitlabᚗcomᚋunboundsoftwar
return ret return ret
} }
func (ec *executionContext) marshalNSubGraph2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraph(ctx context.Context, sel ast.SelectionSet, v *model.SubGraph) graphql.Marshaler { func (ec *executionContext) marshalNSubGraph2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraph(ctx context.Context, sel ast.SelectionSet, v *model.SubGraph) graphql.Marshaler {
if v == nil { if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow") graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
} }
return graphql.Null return graphql.Null
} }
return ec._SubGraph(ctx, sel, v) return ec._SubGraph(ctx, sel, v)
} }
func (ec *executionContext) marshalNSupergraph2gitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSupergraph(ctx context.Context, sel ast.SelectionSet, v model.Supergraph) graphql.Marshaler { func (ec *executionContext) marshalNSupergraph2giteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSupergraph(ctx context.Context, sel ast.SelectionSet, v model.Supergraph) graphql.Marshaler {
if v == nil { if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow") graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
} }
return graphql.Null return graphql.Null
} }
@@ -5058,13 +5463,13 @@ func (ec *executionContext) marshalNTime2timeᚐTime(ctx context.Context, sel as
res := graphql.MarshalTime(v) res := graphql.MarshalTime(v)
if res == graphql.Null { if res == graphql.Null {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow") graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
} }
} }
return res return res
} }
func (ec *executionContext) marshalNUser2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐUserᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.User) graphql.Marshaler { func (ec *executionContext) marshalNUser2ᚕᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐUserᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.User) graphql.Marshaler {
ret := make(graphql.Array, len(v)) ret := make(graphql.Array, len(v))
var wg sync.WaitGroup var wg sync.WaitGroup
isLen1 := len(v) == 1 isLen1 := len(v) == 1
@@ -5088,7 +5493,7 @@ func (ec *executionContext) marshalNUser2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋ
if !isLen1 { if !isLen1 {
defer wg.Done() defer wg.Done()
} }
ret[i] = ec.marshalNUser2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐUser(ctx, sel, v[i]) ret[i] = ec.marshalNUser2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐUser(ctx, sel, v[i])
} }
if isLen1 { if isLen1 {
f(i) f(i)
@@ -5108,10 +5513,10 @@ func (ec *executionContext) marshalNUser2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋ
return ret return ret
} }
func (ec *executionContext) marshalNUser2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐUser(ctx context.Context, sel ast.SelectionSet, v *model.User) graphql.Marshaler { func (ec *executionContext) marshalNUser2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐUser(ctx context.Context, sel ast.SelectionSet, v *model.User) graphql.Marshaler {
if v == nil { if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow") graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
} }
return graphql.Null return graphql.Null
} }
@@ -5176,7 +5581,7 @@ func (ec *executionContext) marshalN__DirectiveLocation2string(ctx context.Conte
res := graphql.MarshalString(v) res := graphql.MarshalString(v)
if res == graphql.Null { if res == graphql.Null {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow") graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
} }
} }
return res return res
@@ -5348,7 +5753,7 @@ func (ec *executionContext) marshalN__Type2ᚕgithubᚗcomᚋ99designsᚋgqlgen
func (ec *executionContext) marshalN__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx context.Context, sel ast.SelectionSet, v *introspection.Type) graphql.Marshaler { func (ec *executionContext) marshalN__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx context.Context, sel ast.SelectionSet, v *introspection.Type) graphql.Marshaler {
if v == nil { if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow") graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
} }
return graphql.Null return graphql.Null
} }
@@ -5365,7 +5770,7 @@ func (ec *executionContext) marshalN__TypeKind2string(ctx context.Context, sel a
res := graphql.MarshalString(v) res := graphql.MarshalString(v)
if res == graphql.Null { if res == graphql.Null {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow") graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
} }
} }
return res return res
@@ -5401,7 +5806,7 @@ func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast
return res return res
} }
func (ec *executionContext) unmarshalOInputAPIKey2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐInputAPIKey(ctx context.Context, v any) (*model.InputAPIKey, error) { func (ec *executionContext) unmarshalOInputAPIKey2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐInputAPIKey(ctx context.Context, v any) (*model.InputAPIKey, error) {
if v == nil { if v == nil {
return nil, nil return nil, nil
} }
+1 -1
View File
@@ -3,7 +3,7 @@ package graph
import ( import (
"sync" "sync"
"gitlab.com/unboundsoftware/schemas/graph/model" "gitea.unbound.se/unboundsoftware/schemas/graph/model"
) )
// PubSub handles publishing schema updates to subscribers // PubSub handles publishing schema updates to subscribers
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gitlab.com/unboundsoftware/schemas/graph/model" "gitea.unbound.se/unboundsoftware/schemas/graph/model"
) )
func TestPubSub_SubscribeAndPublish(t *testing.T) { func TestPubSub_SubscribeAndPublish(t *testing.T) {
+3 -3
View File
@@ -7,13 +7,13 @@ import (
"gitlab.com/unboundsoftware/eventsourced/eventsourced" "gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitlab.com/unboundsoftware/schemas/cache" "gitea.unbound.se/unboundsoftware/schemas/cache"
"gitlab.com/unboundsoftware/schemas/middleware" "gitea.unbound.se/unboundsoftware/schemas/middleware"
) )
//go:generate go run github.com/99designs/gqlgen //go:generate go run github.com/99designs/gqlgen
//go:generate gofumpt -w . //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. // This file will not be regenerated automatically.
// //
+6 -2
View File
@@ -1,12 +1,16 @@
type Query { type Query {
organizations: [Organization!]! @auth(user: true) organizations: [Organization!]! @auth(user: true)
supergraph(ref: String!, isAfter: String): Supergraph! @auth(organization: true) allOrganizations: [Organization!]! @auth(user: true)
latestSchema(ref: String!): SchemaUpdate! @auth(organization: true) supergraph(ref: String!, isAfter: String): Supergraph! @auth(user: true, organization: true)
latestSchema(ref: String!): SchemaUpdate! @auth(user: true, organization: true)
} }
type Mutation { type Mutation {
addOrganization(name: String!): Organization! @auth(user: true) addOrganization(name: String!): Organization! @auth(user: true)
addUserToOrganization(organizationId: ID!, userId: String!): Organization! @auth(user: true)
addAPIKey(input: InputAPIKey): APIKey! @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) updateSubGraph(input: InputSubGraph!): SubGraph! @auth(organization: true)
} }
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"gitlab.com/unboundsoftware/eventsourced/eventsourced" "gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitlab.com/unboundsoftware/schemas/domain" "gitea.unbound.se/unboundsoftware/schemas/domain"
"gitlab.com/unboundsoftware/schemas/graph/model" "gitea.unbound.se/unboundsoftware/schemas/graph/model"
) )
func (r *Resolver) fetchSubGraph(ctx context.Context, subGraphId string) (*domain.SubGraph, error) { func (r *Resolver) fetchSubGraph(ctx context.Context, subGraphId string) (*domain.SubGraph, error) {
+122 -14
View File
@@ -1,6 +1,7 @@
package graph 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. // will be copied through when generating and any unknown code will be moved to the end.
// Code generated by github.com/99designs/gqlgen // Code generated by github.com/99designs/gqlgen
@@ -11,12 +12,12 @@ import (
"gitlab.com/unboundsoftware/eventsourced/eventsourced" "gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitlab.com/unboundsoftware/schemas/domain" "gitea.unbound.se/unboundsoftware/schemas/domain"
"gitlab.com/unboundsoftware/schemas/graph/generated" "gitea.unbound.se/unboundsoftware/schemas/graph/generated"
"gitlab.com/unboundsoftware/schemas/graph/model" "gitea.unbound.se/unboundsoftware/schemas/graph/model"
"gitlab.com/unboundsoftware/schemas/middleware" "gitea.unbound.se/unboundsoftware/schemas/middleware"
"gitlab.com/unboundsoftware/schemas/rand" "gitea.unbound.se/unboundsoftware/schemas/rand"
"gitlab.com/unboundsoftware/schemas/sdlmerge" "gitea.unbound.se/unboundsoftware/schemas/sdlmerge"
) )
// AddOrganization is the resolver for the addOrganization field. // 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 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. // AddAPIKey is the resolver for the addAPIKey field.
func (r *mutationResolver) AddAPIKey(ctx context.Context, input *model.InputAPIKey) (*model.APIKey, error) { func (r *mutationResolver) AddAPIKey(ctx context.Context, input *model.InputAPIKey) (*model.APIKey, error) {
sub := middleware.UserFromContext(ctx) sub := middleware.UserFromContext(ctx)
@@ -71,6 +90,41 @@ func (r *mutationResolver) AddAPIKey(ctx context.Context, input *model.InputAPIK
}, nil }, 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. // UpdateSubGraph is the resolver for the updateSubGraph field.
func (r *mutationResolver) UpdateSubGraph(ctx context.Context, input model.InputSubGraph) (*model.SubGraph, error) { func (r *mutationResolver) UpdateSubGraph(ctx context.Context, input model.InputSubGraph) (*model.SubGraph, error) {
orgId := middleware.OrganizationFromContext(ctx) orgId := middleware.OrganizationFromContext(ctx)
@@ -183,13 +237,49 @@ func (r *queryResolver) Organizations(ctx context.Context) ([]*model.Organizatio
return ToGqlOrganizations(orgs), nil 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. // Supergraph is the resolver for the supergraph field.
func (r *queryResolver) Supergraph(ctx context.Context, ref string, isAfter *string) (model.Supergraph, error) { func (r *queryResolver) Supergraph(ctx context.Context, ref string, isAfter *string) (model.Supergraph, error) {
orgId := middleware.OrganizationFromContext(ctx) orgId := middleware.OrganizationFromContext(ctx)
_, err := r.apiKeyCanAccessRef(ctx, ref, false) userId := middleware.UserFromContext(ctx)
if err != nil {
return nil, err 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 := "" after := ""
if isAfter != nil { if isAfter != nil {
after = *isAfter after = *isAfter
@@ -241,16 +331,34 @@ func (r *queryResolver) Supergraph(ctx context.Context, ref string, isAfter *str
// LatestSchema is the resolver for the latestSchema field. // LatestSchema is the resolver for the latestSchema field.
func (r *queryResolver) LatestSchema(ctx context.Context, ref string) (*model.SchemaUpdate, error) { func (r *queryResolver) LatestSchema(ctx context.Context, ref string) (*model.SchemaUpdate, error) {
orgId := middleware.OrganizationFromContext(ctx) orgId := middleware.OrganizationFromContext(ctx)
userId := middleware.UserFromContext(ctx)
r.Logger.Info("LatestSchema query", r.Logger.Info("LatestSchema query",
"ref", ref, "ref", ref,
"orgId", orgId, "orgId", orgId,
"userId", userId,
) )
_, err := r.apiKeyCanAccessRef(ctx, ref, false) // If authenticated with API key (organization), check access
if err != nil { if orgId != "" {
r.Logger.Error("API key cannot access ref", "error", err, "ref", ref) _, err := r.apiKeyCanAccessRef(ctx, ref, false)
return nil, err 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 // Get current services and schema
+1 -1
View File
@@ -61,7 +61,7 @@ spec:
timeoutSeconds: 5 timeoutSeconds: 5
failureThreshold: 3 failureThreshold: 3
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
image: registry.gitlab.com/unboundsoftware/schemas:${COMMIT} image: oci.unbound.se/unboundsoftware/schemas:${COMMIT}
ports: ports:
- name: api - name: api
containerPort: 8080 containerPort: 8080
+1 -1
View File
@@ -3,7 +3,6 @@ kind: Ingress
metadata: metadata:
name: schemas-ingress name: schemas-ingress
annotations: annotations:
kubernetes.io/ingress.class: "alb"
alb.ingress.kubernetes.io/group.name: "default" alb.ingress.kubernetes.io/group.name: "default"
alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: instance alb.ingress.kubernetes.io/target-type: instance
@@ -11,6 +10,7 @@ metadata:
alb.ingress.kubernetes.io/ssl-redirect: "443" alb.ingress.kubernetes.io/ssl-redirect: "443"
alb.ingress.kubernetes.io/healthcheck-path: '/health' alb.ingress.kubernetes.io/healthcheck-path: '/health'
spec: spec:
ingressClassName: "alb"
rules: rules:
- host: "schemas.unbound.se" - host: "schemas.unbound.se"
http: http:
+63 -5
View File
@@ -8,7 +8,7 @@ import (
"github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"gitlab.com/unboundsoftware/schemas/domain" "gitea.unbound.se/unboundsoftware/schemas/domain"
) )
const ( const (
@@ -67,6 +67,37 @@ func UserFromContext(ctx context.Context) string {
return "" return ""
} }
func UserHasRole(ctx context.Context, role string) bool {
token, err := TokenFromContext(ctx)
if err != nil || token == nil {
return false
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return false
}
// Check the custom roles claim
rolesInterface, ok := claims["https://unbound.se/roles"]
if !ok {
return false
}
roles, ok := rolesInterface.([]interface{})
if !ok {
return false
}
for _, r := range roles {
if roleStr, ok := r.(string); ok && roleStr == role {
return true
}
}
return false
}
func OrganizationFromContext(ctx context.Context) string { func OrganizationFromContext(ctx context.Context) string {
if value := ctx.Value(OrganizationKey); value != nil { if value := ctx.Value(OrganizationKey); value != nil {
if u, ok := value.(domain.Organization); ok { if u, ok := value.(domain.Organization); ok {
@@ -77,15 +108,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) { func (m *AuthMiddleware) Directive(ctx context.Context, _ interface{}, next graphql.Resolver, user *bool, organization *bool) (res interface{}, err error) {
if user != nil && *user { userRequired := user != nil && *user
if u := UserFromContext(ctx); u == "" { 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") 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") return nil, fmt.Errorf("no organization available in request")
} }
fmt.Printf("[Auth Directive] ACCEPTED: Organization authenticated\n")
} }
return next(ctx) return next(ctx)
} }
+105 -5
View File
@@ -14,7 +14,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gitlab.com/unboundsoftware/eventsourced/eventsourced" "gitlab.com/unboundsoftware/eventsourced/eventsourced"
"gitlab.com/unboundsoftware/schemas/domain" "gitea.unbound.se/unboundsoftware/schemas/domain"
) )
// MockCache is a mock implementation of the Cache interface // MockCache is a mock implementation of the Cache interface
@@ -427,7 +427,10 @@ func TestAuthMiddleware_Directive_RequiresBoth(t *testing.T) {
Name: "Test Org", Name: "Test Org",
} }
// Test with both present // 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(context.Background(), UserKey, "user-123")
ctx = context.WithValue(ctx, OrganizationKey, org) ctx = context.WithValue(ctx, OrganizationKey, org)
_, err := authMiddleware.Directive(ctx, nil, func(ctx context.Context) (interface{}, error) { _, err := authMiddleware.Directive(ctx, nil, func(ctx context.Context) (interface{}, error) {
@@ -435,19 +438,27 @@ func TestAuthMiddleware_Directive_RequiresBoth(t *testing.T) {
}, &requireUser, &requireOrg) }, &requireUser, &requireOrg)
assert.NoError(t, err) assert.NoError(t, err)
// Test with only user // Test with only user - should succeed (OR logic)
ctx = context.WithValue(context.Background(), UserKey, "user-123") ctx = context.WithValue(context.Background(), UserKey, "user-123")
_, err = authMiddleware.Directive(ctx, nil, func(ctx context.Context) (interface{}, error) { _, err = authMiddleware.Directive(ctx, nil, func(ctx context.Context) (interface{}, error) {
return "success", nil return "success", nil
}, &requireUser, &requireOrg) }, &requireUser, &requireOrg)
assert.Error(t, err) assert.NoError(t, err)
// Test with only organization // Test with only organization - should succeed (OR logic)
ctx = context.WithValue(context.Background(), OrganizationKey, org) ctx = context.WithValue(context.Background(), OrganizationKey, org)
_, err = authMiddleware.Directive(ctx, nil, func(ctx context.Context) (interface{}, error) { _, err = authMiddleware.Directive(ctx, nil, func(ctx context.Context) (interface{}, error) {
return "success", nil return "success", nil
}, &requireUser, &requireOrg) }, &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.Error(t, err)
assert.Contains(t, err.Error(), "authentication required")
} }
func TestAuthMiddleware_Directive_NoRequirements(t *testing.T) { func TestAuthMiddleware_Directive_NoRequirements(t *testing.T) {
@@ -462,3 +473,92 @@ func TestAuthMiddleware_Directive_NoRequirements(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "success", result) assert.Equal(t, "success", result)
} }
func TestUserHasRole_WithValidRole(t *testing.T) {
// Create token with roles claim
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": "user-123",
"https://unbound.se/roles": []interface{}{"admin", "user"},
})
ctx := context.WithValue(context.Background(), mw.ContextKey{}, token)
// 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 token with roles claim
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": "user-123",
"https://unbound.se/roles": []interface{}{"user"},
})
ctx := context.WithValue(context.Background(), mw.ContextKey{}, token)
// Test for non-existing role
hasRole := UserHasRole(ctx, "admin")
assert.False(t, hasRole)
}
func TestUserHasRole_WithoutRolesClaim(t *testing.T) {
// Create token without roles claim
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": "user-123",
})
ctx := context.WithValue(context.Background(), mw.ContextKey{}, token)
// Test should return false when roles claim is missing
hasRole := UserHasRole(ctx, "admin")
assert.False(t, hasRole)
}
func TestUserHasRole_WithoutToken(t *testing.T) {
ctx := context.Background()
// Test should return false when no token in context
hasRole := UserHasRole(ctx, "admin")
assert.False(t, hasRole)
}
func TestUserHasRole_WithInvalidTokenType(t *testing.T) {
// Put invalid token type in context
ctx := context.WithValue(context.Background(), mw.ContextKey{}, "not-a-token")
// Test should return false when token type is invalid
hasRole := UserHasRole(ctx, "admin")
assert.False(t, hasRole)
}
func TestUserHasRole_WithInvalidRolesType(t *testing.T) {
// Create token with invalid roles type
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": "user-123",
"https://unbound.se/roles": "not-an-array",
})
ctx := context.WithValue(context.Background(), mw.ContextKey{}, token)
// Test should return false when roles type is invalid
hasRole := UserHasRole(ctx, "admin")
assert.False(t, hasRole)
}
func TestUserHasRole_WithInvalidRoleElementType(t *testing.T) {
// Create token with invalid role element types
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": "user-123",
"https://unbound.se/roles": []interface{}{123, 456}, // Numbers instead of strings
})
ctx := context.WithValue(context.Background(), mw.ContextKey{}, token)
// Test should return false when role elements are not strings
hasRole := UserHasRole(ctx, "admin")
assert.False(t, hasRole)
}
+1 -1
View File
@@ -9,7 +9,7 @@
"kubernetes" "kubernetes"
], ],
"matchPackageNames": [ "matchPackageNames": [
"registry.gitlab.com/unboundsoftware/schemas" "oci.unbound.se/unboundsoftware/schemas"
], ],
"enabled": false "enabled": false
}, },
+5 -3
View File
@@ -1,6 +1,7 @@
package sdlmerge package sdlmerge
import ( import (
"bytes"
"fmt" "fmt"
"strings" "strings"
@@ -61,12 +62,13 @@ func MergeSDLs(SDLs ...string) (string, error) {
return "", fmt.Errorf("merge ast: %w", err) return "", fmt.Errorf("merge ast: %w", err)
} }
out, err := astprinter.PrintString(&doc) // Format with indentation for better readability
if err != nil { buf := &bytes.Buffer{}
if err := astprinter.PrintIndent(&doc, []byte(" "), buf); err != nil {
return "", fmt.Errorf("stringify schema: %w", err) return "", fmt.Errorf("stringify schema: %w", err)
} }
return out, nil return buf.String(), nil
} }
func validateSubgraphs(subgraphs []string) error { 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")
}