Compare commits
130 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
e328ef5a64
|
|||
| b2506188f3 | |||
| 16ce04ea86 | |||
| 1d0f82a851 | |||
| 7be533dc6c | |||
|
73eae98929
|
|||
| aa41f48b0e | |||
| bb0c020812 | |||
| f0c7415d88 | |||
| fbe180020c | |||
| 3eba214a72 | |||
| 134b571baa | |||
| a1a23d69cf | |||
| 6f1dda7be5 | |||
| d50827018a | |||
| 8a6163a921 | |||
| 054cfa1e52 | |||
| 7d25af472a | |||
| ec0d5dff74 | |||
| e3d19384a0 | |||
| 42adcb1df5 | |||
| e072ad685b | |||
| 4222eb1268 | |||
| ab3eafa331 | |||
| 86612d9e25 | |||
| abc5668d33 | |||
| 892fc29331 | |||
| bdb6e37b22 | |||
| 8d512e0290 | |||
| 3ab0a8e701 | |||
| 3bd3daccaf | |||
| 3d6016d7e2 | |||
| 31cc2e10b6 | |||
| 136623c04b | |||
| 12ff2fa1ba | |||
| 1aef6a7a31 | |||
| c02034d6b0 | |||
| dc4555a168 | |||
| 0571f4d986 | |||
| 3c194dc127 | |||
| f7ba0e3dc7 | |||
| 33667d4de1 | |||
| bbf82399e1 | |||
| f927d1c750 | |||
| ef273520b5 | |||
| c8e6287761 | |||
| 7052f0bfb1 | |||
| c6f35f324e | |||
| 08f0ee3374 | |||
| d3c7941de8 | |||
| b1a9021fee | |||
| ab32fddd2b | |||
| ded73a3065 | |||
| 4c571197b8 | |||
| 9f276d7420 | |||
| f56dfeafa7 | |||
| f6f3810ffb | |||
| 9d32174ae4 | |||
| 26073e744d | |||
| 6e38f3d5b5 | |||
| 9199be9da5 | |||
| e3dcb013b6 | |||
| 5982f3ac41 | |||
| eb23f2f62b | |||
| 1effc3937a | |||
|
b69de655e4
|
|||
| 73fdf6c896 | |||
| bf0953b520 | |||
| 95aaea7ecf | |||
| 81c3a7ff13 | |||
| e322f7f7ad | |||
| 4997c53968 | |||
| cfcd023a83 | |||
| 34e7a0b718 | |||
| 7d38c31370 | |||
| 968553b532 | |||
| 0067b42f92 | |||
| 48776b6afc | |||
| 03cf6be52c | |||
| 6b5e66b1be | |||
| 25c3bf2356 | |||
| 7e5bbf3baa | |||
| 8adae23068 | |||
|
0e8d0965b7
|
|||
| cfc5de1bb9 | |||
| 63567eaa8b | |||
| e38e7a2936 | |||
| 718585ebe8 | |||
| 6b3515ed14 | |||
| fee47b271a | |||
| 1da8439372 | |||
| 2de1324458 | |||
| 4673ecdd85 | |||
| d92f24b0a1 | |||
| f80ee9d391 | |||
| 3da293252d | |||
| 6ae6a4d6cf | |||
|
129cd8aad1
|
|||
| 8dd2e57c70 | |||
| 1914211b85 | |||
| ea8eb0a68c | |||
| e9d0e855af | |||
| 8790fd0d82 | |||
| a77a7f3a32 | |||
| fbe962a7b7 | |||
| b5bdcc9dbc | |||
|
fd1685867e
|
|||
|
114cbf89c5
|
|||
| 000ad8b4ad | |||
| 0820fb542f | |||
| f0d4285bee | |||
| e1c10f0537 | |||
| f8c593de3e | |||
| 870f29e59f | |||
| a246c236db | |||
| cebeba4461 | |||
| b77165f6c8 | |||
| 9e85ee1473 | |||
| c113eb920b | |||
| 90cc64ece9 | |||
| ea4df08beb | |||
|
ca7e063888
|
|||
| 7b9dc1456b | |||
|
49af5f0cb1
|
|||
| e347d74a39 | |||
|
ffcf41b85a
|
|||
| 335a9f3b54 | |||
| c0e790b684 | |||
| 3b47365f10 | |||
|
862060875b
|
+1
-2
@@ -1,6 +1,5 @@
|
||||
.gitignore
|
||||
/.gitlab
|
||||
.gitlab-ci.yml
|
||||
/.gitea
|
||||
.graphqlconfig
|
||||
/exported
|
||||
/k8s
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
name: schemas
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
deploy_prod:
|
||||
description: 'Deploy to production'
|
||||
required: false
|
||||
default: 'false'
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- name: Generate and format check
|
||||
run: |
|
||||
go install mvdan.cc/gofumpt@latest
|
||||
go install golang.org/x/tools/cmd/goimports@latest
|
||||
go generate ./...
|
||||
git diff --stat --exit-code
|
||||
- name: Run tests
|
||||
run: go test -race -coverprofile=coverage.txt ./...
|
||||
|
||||
vulnerabilities:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- name: Check vulnerabilities
|
||||
run: |
|
||||
go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
govulncheck ./...
|
||||
|
||||
check-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
uses: unboundsoftware/shared-workflows/.gitea/workflows/Release.yml@main
|
||||
with:
|
||||
tag_only: true
|
||||
@@ -3,6 +3,7 @@
|
||||
.testCoverage.txt
|
||||
.testCoverage.txt.tmp
|
||||
coverage.html
|
||||
coverage.out
|
||||
/exported
|
||||
/release
|
||||
/schemactl
|
||||
|
||||
@@ -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
@@ -1,6 +1,10 @@
|
||||
project_name: unbound-schemas
|
||||
version: 2
|
||||
|
||||
gitea_urls:
|
||||
api: http://gitea-http.gitea.svc.cluster.local:3000/api/v1
|
||||
download: https://gitea.unbound.se
|
||||
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
|
||||
@@ -27,11 +31,6 @@ homebrew_casks:
|
||||
name: "Joakim Olsson"
|
||||
email: joakim@unbound.se
|
||||
homepage: "https://schemas.unbound.se/"
|
||||
hooks:
|
||||
post:
|
||||
install: |
|
||||
# replace foo with the actual binary name
|
||||
system_command "/usr/bin/xattr", args: ["-dr", "com.apple.quarantine", "#{staged_path}/schemactl"]
|
||||
|
||||
archives:
|
||||
- id: unbound-schemas
|
||||
|
||||
+4
-11
@@ -10,15 +10,8 @@ repos:
|
||||
args:
|
||||
- --allow-multiple-documents
|
||||
- id: check-added-large-files
|
||||
- repo: https://gitlab.com/devopshq/gitlab-ci-linter
|
||||
rev: v1.0.6
|
||||
hooks:
|
||||
- id: gitlab-ci-linter
|
||||
args:
|
||||
- --project
|
||||
- unboundsoftware/schemas
|
||||
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
|
||||
rev: v9.23.0
|
||||
rev: v9.24.0
|
||||
hooks:
|
||||
- id: commitlint
|
||||
stages: [ commit-msg ]
|
||||
@@ -30,18 +23,18 @@ repos:
|
||||
- id: go-imports
|
||||
args:
|
||||
- -local
|
||||
- gitlab.com/unboundsoftware/schemas
|
||||
- git.unbound.se/unboundsoftware/schemas
|
||||
- repo: https://github.com/lietu/go-pre-commit
|
||||
rev: v1.0.0
|
||||
hooks:
|
||||
- id: go-test
|
||||
- id: gofumpt
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v2.6.2
|
||||
rev: v2.8.0
|
||||
hooks:
|
||||
- id: golangci-lint-full
|
||||
- repo: https://github.com/gitleaks/gitleaks
|
||||
rev: v8.29.1
|
||||
rev: v8.30.0
|
||||
hooks:
|
||||
- id: gitleaks
|
||||
exclude: '^ctl/generated.go|graph/generated/.*$|^graph/model/models_gen.go|^tools/.*$$'
|
||||
|
||||
@@ -2,6 +2,60 @@
|
||||
|
||||
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
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
+2
-2
@@ -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
|
||||
ADD go.* /build
|
||||
RUN go mod download
|
||||
@@ -24,7 +24,7 @@ RUN GOOS=linux GOARCH=amd64 go build \
|
||||
FROM scratch as export
|
||||
COPY --from=build /build/coverage.txt /
|
||||
|
||||
FROM node:24-alpine@sha256:2867d550cf9d8bb50059a0fff528741f11a84d985c732e60e19e8e75c7239c43
|
||||
FROM node:24.13.0-alpine@sha256:931d7d57f8c1fd0e2179dbff7cc7da4c9dd100998bc2b32afc85142d8efbc213
|
||||
ENV TZ Europe/Stockholm
|
||||
|
||||
# Install wgc CLI globally for Cosmo Router composition
|
||||
|
||||
Vendored
+80
-2
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/sparetimecoders/goamqp"
|
||||
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
|
||||
|
||||
"gitlab.com/unboundsoftware/schemas/domain"
|
||||
"gitlab.com/unboundsoftware/schemas/hash"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/domain"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/hash"
|
||||
)
|
||||
|
||||
type Cache struct {
|
||||
@@ -53,6 +53,17 @@ func (c *Cache) OrganizationsByUser(sub string) []domain.Organization {
|
||||
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 {
|
||||
c.mu.RLock()
|
||||
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.addUser(m.Initiator, o)
|
||||
c.logger.With("org_id", m.ID.String(), "event", "OrganizationAdded").Debug("cache updated")
|
||||
case *domain.UserAddedToOrganization:
|
||||
org, exists := c.organizations[m.ID.String()]
|
||||
if exists {
|
||||
m.UpdateOrganization(&org)
|
||||
c.organizations[m.ID.String()] = org
|
||||
c.addUser(m.UserId, org)
|
||||
c.logger.With("org_id", m.ID.String(), "user_id", m.UserId, "event", "UserAddedToOrganization").Debug("cache updated")
|
||||
} else {
|
||||
c.logger.With("org_id", m.ID.String(), "event", "UserAddedToOrganization").Warn("organization not found in cache")
|
||||
}
|
||||
case *domain.APIKeyAdded:
|
||||
key := domain.APIKey{
|
||||
Name: m.Name,
|
||||
@@ -117,6 +138,63 @@ func (c *Cache) Update(msg any, _ goamqp.Headers) (any, error) {
|
||||
org.APIKeys = append(org.APIKeys, key)
|
||||
c.organizations[m.OrganizationId] = org
|
||||
c.logger.With("org_id", m.OrganizationId, "key_name", m.Name, "event", "APIKeyAdded").Debug("cache updated")
|
||||
case *domain.APIKeyRemoved:
|
||||
orgId := m.ID.String()
|
||||
org, exists := c.organizations[orgId]
|
||||
if exists {
|
||||
// Remove from organization's API keys list
|
||||
for i, key := range org.APIKeys {
|
||||
if key.Name == m.KeyName {
|
||||
org.APIKeys = append(org.APIKeys[:i], org.APIKeys[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
c.organizations[orgId] = org
|
||||
// Remove from apiKeys map
|
||||
delete(c.apiKeys, apiKeyId(orgId, m.KeyName))
|
||||
c.logger.With("org_id", orgId, "key_name", m.KeyName, "event", "APIKeyRemoved").Debug("cache updated")
|
||||
} else {
|
||||
c.logger.With("org_id", orgId, "event", "APIKeyRemoved").Warn("organization not found in cache")
|
||||
}
|
||||
case *domain.OrganizationRemoved:
|
||||
orgId := m.ID.String()
|
||||
org, exists := c.organizations[orgId]
|
||||
if exists {
|
||||
// Remove all API keys for this organization
|
||||
for _, key := range org.APIKeys {
|
||||
delete(c.apiKeys, apiKeyId(orgId, key.Name))
|
||||
}
|
||||
// Remove organization from all users
|
||||
for userId, userOrgs := range c.users {
|
||||
for i, userOrgId := range userOrgs {
|
||||
if userOrgId == orgId {
|
||||
c.users[userId] = append(userOrgs[:i], userOrgs[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
// If user has no more organizations, remove from map
|
||||
if len(c.users[userId]) == 0 {
|
||||
delete(c.users, userId)
|
||||
}
|
||||
}
|
||||
// Remove services for this organization
|
||||
if refs, exists := c.services[orgId]; exists {
|
||||
for ref := range refs {
|
||||
// Remove all subgraphs for this org/ref combination
|
||||
for service := range refs[ref] {
|
||||
delete(c.subGraphs, subGraphKey(orgId, ref, service))
|
||||
}
|
||||
// Remove lastUpdate for this org/ref
|
||||
delete(c.lastUpdate, refKey(orgId, ref))
|
||||
}
|
||||
delete(c.services, orgId)
|
||||
}
|
||||
// Remove organization
|
||||
delete(c.organizations, orgId)
|
||||
c.logger.With("org_id", orgId, "event", "OrganizationRemoved").Debug("cache updated")
|
||||
} else {
|
||||
c.logger.With("org_id", orgId, "event", "OrganizationRemoved").Warn("organization not found in cache")
|
||||
}
|
||||
case *domain.SubGraphUpdated:
|
||||
c.updateSubGraph(m.OrganizationId, m.Ref, m.ID.String(), m.Service, m.Time)
|
||||
c.logger.With("org_id", m.OrganizationId, "ref", m.Ref, "service", m.Service, "event", "SubGraphUpdated").Debug("cache updated")
|
||||
|
||||
Vendored
+228
-30
@@ -12,8 +12,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
|
||||
|
||||
"gitlab.com/unboundsoftware/schemas/domain"
|
||||
"gitlab.com/unboundsoftware/schemas/hash"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/domain"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/hash"
|
||||
)
|
||||
|
||||
func TestCache_OrganizationByAPIKey(t *testing.T) {
|
||||
@@ -320,24 +320,18 @@ func TestCache_ConcurrentReads(t *testing.T) {
|
||||
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
||||
c := New(logger)
|
||||
|
||||
// Setup test data
|
||||
// Setup test data - use legacy hash to avoid slow bcrypt
|
||||
orgID := uuid.New().String()
|
||||
apiKey := "test-concurrent-key" // gitleaks:allow
|
||||
hashedKey, err := hash.APIKey(apiKey)
|
||||
require.NoError(t, err)
|
||||
userSub := "test-user"
|
||||
|
||||
org := domain.Organization{
|
||||
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
|
||||
Name: "Concurrent Test Org",
|
||||
}
|
||||
c.organizations[orgID] = org
|
||||
c.apiKeys[apiKeyId(orgID, "test-key")] = domain.APIKey{
|
||||
Name: "test-key",
|
||||
OrganizationId: orgID,
|
||||
Key: hashedKey,
|
||||
}
|
||||
c.users[userSub] = []string{orgID}
|
||||
|
||||
// Run concurrent reads (reduced for race detector)
|
||||
// Run concurrent reads using fast OrganizationsByUser
|
||||
var wg sync.WaitGroup
|
||||
numGoroutines := 20
|
||||
|
||||
@@ -345,9 +339,9 @@ func TestCache_ConcurrentReads(t *testing.T) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
org := c.OrganizationByAPIKey(apiKey)
|
||||
assert.NotNil(t, org)
|
||||
assert.Equal(t, "Concurrent Test Org", org.Name)
|
||||
orgs := c.OrganizationsByUser(userSub)
|
||||
assert.NotEmpty(t, orgs)
|
||||
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))
|
||||
c := New(logger)
|
||||
|
||||
// Setup initial data
|
||||
// Setup initial data - use legacy hash to avoid slow bcrypt in concurrent test
|
||||
orgID := uuid.New().String()
|
||||
apiKey := "test-rw-key" // gitleaks:allow
|
||||
hashedKey, err := hash.APIKey(apiKey)
|
||||
require.NoError(t, err)
|
||||
legacyKey := "test-rw-key" // gitleaks:allow
|
||||
legacyHash := hash.String(legacyKey)
|
||||
|
||||
org := domain.Organization{
|
||||
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
|
||||
@@ -401,26 +394,21 @@ func TestCache_ConcurrentReadsAndWrites(t *testing.T) {
|
||||
c.apiKeys[apiKeyId(orgID, "test-key")] = domain.APIKey{
|
||||
Name: "test-key",
|
||||
OrganizationId: orgID,
|
||||
Key: hashedKey,
|
||||
Key: legacyHash,
|
||||
}
|
||||
c.users["user-initial"] = []string{orgID}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
numReaders := 10 // Reduced for race detector
|
||||
numWriters := 5 // Reduced for race detector
|
||||
iterations := 3 // Reduced for race detector
|
||||
numReaders := 5
|
||||
numWriters := 3
|
||||
|
||||
// Concurrent readers
|
||||
// Concurrent readers - use OrganizationsByUser which is fast
|
||||
for i := 0; i < numReaders; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
org := c.OrganizationByAPIKey(apiKey)
|
||||
assert.NotNil(t, org)
|
||||
orgs := c.OrganizationsByUser("user-initial")
|
||||
assert.NotEmpty(t, orgs)
|
||||
}
|
||||
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
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/alecthomas/kong"
|
||||
"github.com/apex/log"
|
||||
|
||||
"gitlab.com/unboundsoftware/schemas/ctl"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/ctl"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
|
||||
+18
-9
@@ -26,15 +26,15 @@ import (
|
||||
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
|
||||
"gitlab.com/unboundsoftware/eventsourced/pg"
|
||||
|
||||
"gitlab.com/unboundsoftware/schemas/cache"
|
||||
"gitlab.com/unboundsoftware/schemas/domain"
|
||||
"gitlab.com/unboundsoftware/schemas/graph"
|
||||
"gitlab.com/unboundsoftware/schemas/graph/generated"
|
||||
"gitlab.com/unboundsoftware/schemas/health"
|
||||
"gitlab.com/unboundsoftware/schemas/logging"
|
||||
"gitlab.com/unboundsoftware/schemas/middleware"
|
||||
"gitlab.com/unboundsoftware/schemas/monitoring"
|
||||
"gitlab.com/unboundsoftware/schemas/store"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/cache"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/domain"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/graph"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/graph/generated"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/health"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/logging"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/middleware"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/monitoring"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/store"
|
||||
)
|
||||
|
||||
type CLI struct {
|
||||
@@ -92,7 +92,10 @@ func start(closeEvents chan error, logger *slog.Logger, connectToAmqpFunc func(u
|
||||
pg.WithEventTypes(
|
||||
&domain.SubGraphUpdated{},
|
||||
&domain.OrganizationAdded{},
|
||||
&domain.UserAddedToOrganization{},
|
||||
&domain.APIKeyAdded{},
|
||||
&domain.APIKeyRemoved{},
|
||||
&domain.OrganizationRemoved{},
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -127,10 +130,16 @@ func start(closeEvents chan error, logger *slog.Logger, connectToAmqpFunc func(u
|
||||
goamqp.EventStreamPublisher(publisher),
|
||||
goamqp.TransientEventStreamConsumer("SubGraph.Updated", serviceCache.Update, domain.SubGraphUpdated{}),
|
||||
goamqp.TransientEventStreamConsumer("Organization.Added", serviceCache.Update, domain.OrganizationAdded{}),
|
||||
goamqp.TransientEventStreamConsumer("Organization.UserAdded", serviceCache.Update, domain.UserAddedToOrganization{}),
|
||||
goamqp.TransientEventStreamConsumer("Organization.APIKeyAdded", serviceCache.Update, domain.APIKeyAdded{}),
|
||||
goamqp.TransientEventStreamConsumer("Organization.APIKeyRemoved", serviceCache.Update, domain.APIKeyRemoved{}),
|
||||
goamqp.TransientEventStreamConsumer("Organization.Removed", serviceCache.Update, domain.OrganizationRemoved{}),
|
||||
goamqp.WithTypeMapping("SubGraph.Updated", domain.SubGraphUpdated{}),
|
||||
goamqp.WithTypeMapping("Organization.Added", domain.OrganizationAdded{}),
|
||||
goamqp.WithTypeMapping("Organization.UserAdded", domain.UserAddedToOrganization{}),
|
||||
goamqp.WithTypeMapping("Organization.APIKeyAdded", domain.APIKeyAdded{}),
|
||||
goamqp.WithTypeMapping("Organization.APIKeyRemoved", domain.APIKeyRemoved{}),
|
||||
goamqp.WithTypeMapping("Organization.Removed", domain.OrganizationRemoved{}),
|
||||
}
|
||||
if err := conn.Start(rootCtx, setups...); err != nil {
|
||||
return fmt.Errorf("failed to setup AMQP: %v", err)
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
|
||||
|
||||
"gitlab.com/unboundsoftware/schemas/domain"
|
||||
"gitlab.com/unboundsoftware/schemas/hash"
|
||||
"gitlab.com/unboundsoftware/schemas/middleware"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/domain"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/hash"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/middleware"
|
||||
)
|
||||
|
||||
// MockCache is a mock implementation for testing
|
||||
|
||||
@@ -23,6 +23,8 @@ func (o *Organization) Apply(event eventsourced.Event) error {
|
||||
switch e := event.(type) {
|
||||
case *OrganizationAdded:
|
||||
e.UpdateOrganization(o)
|
||||
case *UserAddedToOrganization:
|
||||
e.UpdateOrganization(o)
|
||||
case *APIKeyAdded:
|
||||
o.APIKeys = append(o.APIKeys, APIKey{
|
||||
Name: e.Name,
|
||||
@@ -36,6 +38,10 @@ func (o *Organization) Apply(event eventsourced.Event) error {
|
||||
})
|
||||
o.ChangedBy = e.Initiator
|
||||
o.ChangedAt = e.When()
|
||||
case *APIKeyRemoved:
|
||||
e.UpdateOrganization(o)
|
||||
case *OrganizationRemoved:
|
||||
e.UpdateOrganization(o)
|
||||
default:
|
||||
return fmt.Errorf("unexpected event type: %+v", event)
|
||||
}
|
||||
|
||||
+83
-1
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
|
||||
|
||||
"gitlab.com/unboundsoftware/schemas/hash"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/hash"
|
||||
)
|
||||
|
||||
type AddOrganization struct {
|
||||
@@ -34,6 +34,37 @@ func (a AddOrganization) Event(context.Context) eventsourced.Event {
|
||||
|
||||
var _ eventsourced.Command = AddOrganization{}
|
||||
|
||||
type AddUserToOrganization struct {
|
||||
UserId string
|
||||
Initiator string
|
||||
}
|
||||
|
||||
func (a AddUserToOrganization) Validate(_ context.Context, aggregate eventsourced.Aggregate) error {
|
||||
if aggregate.Identity() == nil {
|
||||
return fmt.Errorf("organization does not exist")
|
||||
}
|
||||
if len(a.UserId) == 0 {
|
||||
return fmt.Errorf("userId is required")
|
||||
}
|
||||
// Check if user is already in the organization
|
||||
org := aggregate.(*Organization)
|
||||
for _, user := range org.Users {
|
||||
if user == a.UserId {
|
||||
return fmt.Errorf("user is already a member of this organization")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AddUserToOrganization) Event(context.Context) eventsourced.Event {
|
||||
return &UserAddedToOrganization{
|
||||
UserId: a.UserId,
|
||||
Initiator: a.Initiator,
|
||||
}
|
||||
}
|
||||
|
||||
var _ eventsourced.Command = AddUserToOrganization{}
|
||||
|
||||
type AddAPIKey struct {
|
||||
Name string
|
||||
Key string
|
||||
@@ -79,6 +110,57 @@ func (a AddAPIKey) Event(context.Context) eventsourced.Event {
|
||||
|
||||
var _ eventsourced.Command = AddAPIKey{}
|
||||
|
||||
type RemoveAPIKey struct {
|
||||
KeyName string
|
||||
Initiator string
|
||||
}
|
||||
|
||||
func (r RemoveAPIKey) Validate(_ context.Context, aggregate eventsourced.Aggregate) error {
|
||||
if aggregate.Identity() == nil {
|
||||
return fmt.Errorf("organization does not exist")
|
||||
}
|
||||
org := aggregate.(*Organization)
|
||||
found := false
|
||||
for _, k := range org.APIKeys {
|
||||
if k.Name == r.KeyName {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("API key '%s' not found", r.KeyName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r RemoveAPIKey) Event(context.Context) eventsourced.Event {
|
||||
return &APIKeyRemoved{
|
||||
KeyName: r.KeyName,
|
||||
Initiator: r.Initiator,
|
||||
}
|
||||
}
|
||||
|
||||
var _ eventsourced.Command = RemoveAPIKey{}
|
||||
|
||||
type RemoveOrganization struct {
|
||||
Initiator string
|
||||
}
|
||||
|
||||
func (r RemoveOrganization) Validate(_ context.Context, aggregate eventsourced.Aggregate) error {
|
||||
if aggregate.Identity() == nil {
|
||||
return fmt.Errorf("organization does not exist")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r RemoveOrganization) Event(context.Context) eventsourced.Event {
|
||||
return &OrganizationRemoved{
|
||||
Initiator: r.Initiator,
|
||||
}
|
||||
}
|
||||
|
||||
var _ eventsourced.Command = RemoveOrganization{}
|
||||
|
||||
type UpdateSubGraph struct {
|
||||
OrganizationId string
|
||||
Ref string
|
||||
|
||||
+502
-1
@@ -7,10 +7,68 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"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) {
|
||||
type fields struct {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,24 @@ func (a *OrganizationAdded) UpdateOrganization(o *Organization) {
|
||||
o.ChangedAt = a.When()
|
||||
}
|
||||
|
||||
type UserAddedToOrganization struct {
|
||||
eventsourced.BaseEvent
|
||||
UserId string `json:"userId"`
|
||||
Initiator string `json:"initiator"`
|
||||
}
|
||||
|
||||
func (a *UserAddedToOrganization) UpdateOrganization(o *Organization) {
|
||||
// Check if user is already in the organization
|
||||
for _, user := range o.Users {
|
||||
if user == a.UserId {
|
||||
return // User already exists, no need to add
|
||||
}
|
||||
}
|
||||
o.Users = append(o.Users, a.UserId)
|
||||
o.ChangedBy = a.Initiator
|
||||
o.ChangedAt = a.When()
|
||||
}
|
||||
|
||||
type APIKeyAdded struct {
|
||||
eventsourced.BaseEvent
|
||||
OrganizationId string `json:"organizationId"`
|
||||
@@ -34,6 +52,36 @@ func (a *APIKeyAdded) EnrichFromAggregate(aggregate eventsourced.Aggregate) {
|
||||
|
||||
var _ eventsourced.EnrichableEvent = &APIKeyAdded{}
|
||||
|
||||
type APIKeyRemoved struct {
|
||||
eventsourced.BaseEvent
|
||||
KeyName string `json:"keyName"`
|
||||
Initiator string `json:"initiator"`
|
||||
}
|
||||
|
||||
func (a *APIKeyRemoved) UpdateOrganization(o *Organization) {
|
||||
// Remove the API key from the organization
|
||||
for i, key := range o.APIKeys {
|
||||
if key.Name == a.KeyName {
|
||||
o.APIKeys = append(o.APIKeys[:i], o.APIKeys[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
o.ChangedBy = a.Initiator
|
||||
o.ChangedAt = a.When()
|
||||
}
|
||||
|
||||
type OrganizationRemoved struct {
|
||||
eventsourced.BaseEvent
|
||||
Initiator string `json:"initiator"`
|
||||
}
|
||||
|
||||
func (a *OrganizationRemoved) UpdateOrganization(o *Organization) {
|
||||
// Mark organization as removed by clearing critical fields
|
||||
// The aggregate will still exist in the event store, but it's logically deleted
|
||||
o.ChangedBy = a.Initiator
|
||||
o.ChangedAt = a.When()
|
||||
}
|
||||
|
||||
type SubGraphUpdated struct {
|
||||
eventsourced.BaseEvent
|
||||
OrganizationId string `json:"organizationId"`
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
module gitlab.com/unboundsoftware/schemas
|
||||
module gitea.unbound.se/unboundsoftware/schemas
|
||||
|
||||
go 1.25
|
||||
|
||||
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/Khan/genqlient v0.8.1
|
||||
github.com/alecthomas/kong v1.13.0
|
||||
@@ -18,21 +18,21 @@ require (
|
||||
github.com/sparetimecoders/goamqp v0.3.3
|
||||
github.com/stretchr/testify v1.11.1
|
||||
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/eventsourced v1.19.3
|
||||
gitlab.com/unboundsoftware/eventsourced/pg v1.17.0
|
||||
go.opentelemetry.io/contrib/bridges/otelslog v0.13.0
|
||||
go.opentelemetry.io/otel v1.38.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0
|
||||
go.opentelemetry.io/otel/log v0.14.0
|
||||
go.opentelemetry.io/otel/sdk v1.38.0
|
||||
go.opentelemetry.io/otel/sdk/log v0.14.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0
|
||||
go.opentelemetry.io/otel/trace v1.38.0
|
||||
golang.org/x/crypto v0.45.0
|
||||
go.opentelemetry.io/contrib/bridges/otelslog v0.14.0
|
||||
go.opentelemetry.io/otel v1.39.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0
|
||||
go.opentelemetry.io/otel/log v0.15.0
|
||||
go.opentelemetry.io/otel/sdk v1.39.0
|
||||
go.opentelemetry.io/otel/sdk/log v0.15.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0
|
||||
go.opentelemetry.io/otel/trace v1.39.0
|
||||
golang.org/x/crypto v0.47.0
|
||||
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/stdr v1.2.2 // 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/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/lib/pq v1.10.9 // 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/pretty v1.2.1 // 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
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
|
||||
google.golang.org/grpc v1.75.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/grpc v1.77.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
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.83/go.mod h1:q6Lb64wknFqNFSbSUGzKRKupklvY/xgNr62g0GGWPB8=
|
||||
github.com/99designs/gqlgen v0.17.86 h1:C8N3UTa5heXX6twl+b0AJyGkTwYL6dNmFrgZNLRcU6w=
|
||||
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/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs=
|
||||
github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU=
|
||||
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
|
||||
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
|
||||
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
|
||||
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
|
||||
github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=
|
||||
github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
@@ -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-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
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/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
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/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
||||
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/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
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-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
|
||||
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
|
||||
github.com/urfave/cli/v3 v3.6.0 h1:oIdArVjkdIXHWg3iqxgmqwQGC8NM0JtdgwQAj2sRwFo=
|
||||
github.com/urfave/cli/v3 v3.6.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/urfave/cli/v3 v3.6.1 h1:j8Qq8NyUawj/7rTYdBGrxcH7A/j7/G8Q5LhWEW4G3Mo=
|
||||
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/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/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.238/go.mod h1:ErOQH1ki2+SZB8JjpTyGVnoBpg5picIyjvuWQJP4abg=
|
||||
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.242/go.mod h1:mX25ASEQiKamxaFSK6NZihh0oDCigIuzro30up4mFH4=
|
||||
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/eventsourced v1.19.3 h1:0HbDHF4sHfoyDrbPLMFWvsQLbTl2ITrpI9PjDIZsV1Y=
|
||||
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/go.mod h1:WgPrZhyCbsZ3TG2tPUbh2MUjOEaANJjsWi/0hlIwRVU=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s=
|
||||
go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
||||
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.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA=
|
||||
go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM=
|
||||
go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg=
|
||||
go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/bridges/otelslog v0.14.0 h1:eypSOd+0txRKCXPNyqLPsbSfA0jULgJcGmSAdFAnrCM=
|
||||
go.opentelemetry.io/contrib/bridges/otelslog v0.14.0/go.mod h1:CRGvIBL/aAxpQU34ZxyQVFlovVcp67s4cAmQu8Jh9mc=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 h1:nKP4Z2ejtHn3yShBb+2KawiXgpn8In5cT7aO2wXuOTE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0/go.mod h1:NwjeBbNigsO4Aj9WgM0C+cKIrxsZUaRmZUO7A8I7u8o=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0 h1:0BSddrtQqLEylcErkeFrJBmwFzcqfQq9+/uxfTZq+HE=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0/go.mod h1:87sjYuAPzaRCtdd09GU5gM1U9wQLrrcYrm77mh5EBoc=
|
||||
go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY=
|
||||
go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/log v0.15.0 h1:WgMEHOUt5gjJE93yqfqJOkRflApNif84kxoHWS9VVHE=
|
||||
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/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
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/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
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-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.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
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.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
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.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
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/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-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc=
|
||||
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
|
||||
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
||||
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-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
package graph
|
||||
|
||||
import (
|
||||
"gitlab.com/unboundsoftware/schemas/domain"
|
||||
"gitlab.com/unboundsoftware/schemas/graph/model"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/domain"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/graph/model"
|
||||
)
|
||||
|
||||
func ToGqlOrganizations(orgs []domain.Organization) []*model.Organization {
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"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
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"gitlab.com/unboundsoftware/schemas/graph/model"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/graph/model"
|
||||
)
|
||||
|
||||
// MockCommandExecutor implements CommandExecutor for testing
|
||||
|
||||
+466
-61
@@ -17,7 +17,7 @@ import (
|
||||
gqlparser "github.com/vektah/gqlparser/v2"
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
"gitlab.com/unboundsoftware/schemas/graph/model"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/graph/model"
|
||||
)
|
||||
|
||||
// region ************************** generated!.gotpl **************************
|
||||
@@ -61,9 +61,12 @@ type ComplexityRoot struct {
|
||||
}
|
||||
|
||||
Mutation struct {
|
||||
AddAPIKey func(childComplexity int, input *model.InputAPIKey) int
|
||||
AddOrganization func(childComplexity int, name string) int
|
||||
UpdateSubGraph func(childComplexity int, input model.InputSubGraph) int
|
||||
AddAPIKey func(childComplexity int, input *model.InputAPIKey) int
|
||||
AddOrganization func(childComplexity int, name string) 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 {
|
||||
@@ -74,9 +77,10 @@ type ComplexityRoot struct {
|
||||
}
|
||||
|
||||
Query struct {
|
||||
LatestSchema func(childComplexity int, ref string) int
|
||||
Organizations func(childComplexity int) int
|
||||
Supergraph func(childComplexity int, ref string, isAfter *string) int
|
||||
AllOrganizations func(childComplexity int) int
|
||||
LatestSchema func(childComplexity int, ref string) int
|
||||
Organizations func(childComplexity int) int
|
||||
Supergraph func(childComplexity int, ref string, isAfter *string) int
|
||||
}
|
||||
|
||||
SchemaUpdate struct {
|
||||
@@ -119,11 +123,15 @@ type ComplexityRoot struct {
|
||||
|
||||
type MutationResolver interface {
|
||||
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)
|
||||
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)
|
||||
}
|
||||
type QueryResolver interface {
|
||||
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)
|
||||
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
|
||||
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":
|
||||
if e.complexity.Mutation.UpdateSubGraph == nil {
|
||||
break
|
||||
@@ -252,6 +293,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
|
||||
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":
|
||||
if e.complexity.Query.LatestSchema == nil {
|
||||
break
|
||||
@@ -532,13 +579,17 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er
|
||||
var sources = []*ast.Source{
|
||||
{Name: "../schema.graphqls", Input: `type Query {
|
||||
organizations: [Organization!]! @auth(user: true)
|
||||
supergraph(ref: String!, isAfter: String): Supergraph! @auth(organization: true)
|
||||
latestSchema(ref: String!): SchemaUpdate! @auth(organization: true)
|
||||
allOrganizations: [Organization!]! @auth(user: true)
|
||||
supergraph(ref: String!, isAfter: String): Supergraph! @auth(user: true, organization: true)
|
||||
latestSchema(ref: String!): SchemaUpdate! @auth(user: true, organization: true)
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
addOrganization(name: String!): Organization! @auth(user: true)
|
||||
addUserToOrganization(organizationId: ID!, userId: String!): Organization! @auth(user: true)
|
||||
addAPIKey(input: InputAPIKey): APIKey! @auth(user: true)
|
||||
removeAPIKey(organizationId: ID!, keyName: String!): Organization! @auth(user: true)
|
||||
removeOrganization(organizationId: ID!): Boolean! @auth(user: true)
|
||||
updateSubGraph(input: InputSubGraph!): SubGraph! @auth(organization: true)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
var err error
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -663,10 +714,53 @@ func (ec *executionContext) field_Mutation_addOrganization_args(ctx context.Cont
|
||||
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) {
|
||||
var err error
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -872,7 +966,7 @@ func (ec *executionContext) _APIKey_organization(ctx context.Context, field grap
|
||||
return obj.Organization, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNOrganization2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization,
|
||||
ec.marshalNOrganization2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
@@ -1017,7 +1111,7 @@ func (ec *executionContext) _Mutation_addOrganization(ctx context.Context, field
|
||||
next = directive1
|
||||
return next
|
||||
},
|
||||
ec.marshalNOrganization2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization,
|
||||
ec.marshalNOrganization2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
@@ -1057,6 +1151,75 @@ func (ec *executionContext) fieldContext_Mutation_addOrganization(ctx context.Co
|
||||
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) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
@@ -1086,7 +1249,7 @@ func (ec *executionContext) _Mutation_addAPIKey(ctx context.Context, field graph
|
||||
next = directive1
|
||||
return next
|
||||
},
|
||||
ec.marshalNAPIKey2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKey,
|
||||
ec.marshalNAPIKey2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKey,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
@@ -1132,6 +1295,134 @@ func (ec *executionContext) fieldContext_Mutation_addAPIKey(ctx context.Context,
|
||||
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) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
@@ -1161,7 +1452,7 @@ func (ec *executionContext) _Mutation_updateSubGraph(ctx context.Context, field
|
||||
next = directive1
|
||||
return next
|
||||
},
|
||||
ec.marshalNSubGraph2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraph,
|
||||
ec.marshalNSubGraph2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraph,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
@@ -1275,7 +1566,7 @@ func (ec *executionContext) _Organization_users(ctx context.Context, field graph
|
||||
return obj.Users, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNUser2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐUserᚄ,
|
||||
ec.marshalNUser2ᚕᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐUserᚄ,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
@@ -1308,7 +1599,7 @@ func (ec *executionContext) _Organization_apiKeys(ctx context.Context, field gra
|
||||
return obj.APIKeys, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNAPIKey2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKeyᚄ,
|
||||
ec.marshalNAPIKey2ᚕᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKeyᚄ,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
@@ -1371,7 +1662,7 @@ func (ec *executionContext) _Query_organizations(ctx context.Context, field grap
|
||||
next = directive1
|
||||
return next
|
||||
},
|
||||
ec.marshalNOrganization2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganizationᚄ,
|
||||
ec.marshalNOrganization2ᚕᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganizationᚄ,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
@@ -1400,6 +1691,63 @@ func (ec *executionContext) fieldContext_Query_organizations(_ context.Context,
|
||||
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) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
@@ -1414,6 +1762,11 @@ func (ec *executionContext) _Query_supergraph(ctx context.Context, field graphql
|
||||
directive0 := next
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
var zeroVal model.Supergraph
|
||||
@@ -1423,13 +1776,13 @@ func (ec *executionContext) _Query_supergraph(ctx context.Context, field graphql
|
||||
var zeroVal model.Supergraph
|
||||
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
|
||||
return next
|
||||
},
|
||||
ec.marshalNSupergraph2gitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSupergraph,
|
||||
ec.marshalNSupergraph2giteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSupergraph,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
@@ -1473,6 +1826,11 @@ func (ec *executionContext) _Query_latestSchema(ctx context.Context, field graph
|
||||
directive0 := next
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
var zeroVal *model.SchemaUpdate
|
||||
@@ -1482,13 +1840,13 @@ func (ec *executionContext) _Query_latestSchema(ctx context.Context, field graph
|
||||
var zeroVal *model.SchemaUpdate
|
||||
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
|
||||
return next
|
||||
},
|
||||
ec.marshalNSchemaUpdate2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSchemaUpdate,
|
||||
ec.marshalNSchemaUpdate2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSchemaUpdate,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
@@ -1704,7 +2062,7 @@ func (ec *executionContext) _SchemaUpdate_subGraphs(ctx context.Context, field g
|
||||
return obj.SubGraphs, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNSubGraph2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraphᚄ,
|
||||
ec.marshalNSubGraph2ᚕᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraphᚄ,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
@@ -2068,7 +2426,7 @@ func (ec *executionContext) _SubGraphs_subGraphs(ctx context.Context, field grap
|
||||
return obj.SubGraphs, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNSubGraph2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraphᚄ,
|
||||
ec.marshalNSubGraph2ᚕᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraphᚄ,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
@@ -2132,7 +2490,7 @@ func (ec *executionContext) _Subscription_schemaUpdates(ctx context.Context, fie
|
||||
next = directive1
|
||||
return next
|
||||
},
|
||||
ec.marshalNSchemaUpdate2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSchemaUpdate,
|
||||
ec.marshalNSchemaUpdate2ᚖgiteaᚗunboundᚗseᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSchemaUpdate,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
@@ -3838,7 +4196,11 @@ func (ec *executionContext) _Supergraph(ctx context.Context, sel ast.SelectionSe
|
||||
}
|
||||
return ec._SubGraphs(ctx, sel, obj)
|
||||
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 {
|
||||
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":
|
||||
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||
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 {
|
||||
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":
|
||||
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||
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) })
|
||||
}
|
||||
|
||||
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) })
|
||||
case "supergraph":
|
||||
field := field
|
||||
@@ -4321,7 +4726,7 @@ func (ec *executionContext) _Subscription(ctx context.Context, sel ast.Selection
|
||||
Object: "Subscription",
|
||||
})
|
||||
if len(fields) != 1 {
|
||||
ec.Errorf(ctx, "must subscribe to exactly one stream")
|
||||
graphql.AddErrorf(ctx, "must subscribe to exactly one stream")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4751,11 +5156,11 @@ func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, o
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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))
|
||||
var wg sync.WaitGroup
|
||||
isLen1 := len(v) == 1
|
||||
@@ -4779,7 +5184,7 @@ func (ec *executionContext) marshalNAPIKey2ᚕᚖgitlabᚗcomᚋunboundsoftware
|
||||
if !isLen1 {
|
||||
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 {
|
||||
f(i)
|
||||
@@ -4799,10 +5204,10 @@ func (ec *executionContext) marshalNAPIKey2ᚕᚖgitlabᚗcomᚋunboundsoftware
|
||||
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 !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
|
||||
}
|
||||
@@ -4819,7 +5224,7 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se
|
||||
res := graphql.MarshalBoolean(v)
|
||||
if res == graphql.Null {
|
||||
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
|
||||
@@ -4835,13 +5240,13 @@ func (ec *executionContext) marshalNID2string(ctx context.Context, sel ast.Selec
|
||||
res := graphql.MarshalID(v)
|
||||
if res == graphql.Null {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
@@ -4856,17 +5261,17 @@ func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.Selecti
|
||||
res := graphql.MarshalInt(v)
|
||||
if res == graphql.Null {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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))
|
||||
var wg sync.WaitGroup
|
||||
isLen1 := len(v) == 1
|
||||
@@ -4890,7 +5295,7 @@ func (ec *executionContext) marshalNOrganization2ᚕᚖgitlabᚗcomᚋunboundsof
|
||||
if !isLen1 {
|
||||
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 {
|
||||
f(i)
|
||||
@@ -4910,24 +5315,24 @@ func (ec *executionContext) marshalNOrganization2ᚕᚖgitlabᚗcomᚋunboundsof
|
||||
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 !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 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)
|
||||
}
|
||||
|
||||
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 !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
|
||||
}
|
||||
@@ -4944,7 +5349,7 @@ func (ec *executionContext) marshalNString2string(ctx context.Context, sel ast.S
|
||||
res := graphql.MarshalString(v)
|
||||
if res == graphql.Null {
|
||||
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
|
||||
@@ -4980,11 +5385,11 @@ func (ec *executionContext) marshalNString2ᚕstringᚄ(ctx context.Context, sel
|
||||
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)
|
||||
}
|
||||
|
||||
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))
|
||||
var wg sync.WaitGroup
|
||||
isLen1 := len(v) == 1
|
||||
@@ -5008,7 +5413,7 @@ func (ec *executionContext) marshalNSubGraph2ᚕᚖgitlabᚗcomᚋunboundsoftwar
|
||||
if !isLen1 {
|
||||
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 {
|
||||
f(i)
|
||||
@@ -5028,20 +5433,20 @@ func (ec *executionContext) marshalNSubGraph2ᚕᚖgitlabᚗcomᚋunboundsoftwar
|
||||
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 !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 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 !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
|
||||
}
|
||||
@@ -5058,13 +5463,13 @@ func (ec *executionContext) marshalNTime2timeᚐTime(ctx context.Context, sel as
|
||||
res := graphql.MarshalTime(v)
|
||||
if res == graphql.Null {
|
||||
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
|
||||
}
|
||||
|
||||
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))
|
||||
var wg sync.WaitGroup
|
||||
isLen1 := len(v) == 1
|
||||
@@ -5088,7 +5493,7 @@ func (ec *executionContext) marshalNUser2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋ
|
||||
if !isLen1 {
|
||||
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 {
|
||||
f(i)
|
||||
@@ -5108,10 +5513,10 @@ func (ec *executionContext) marshalNUser2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋ
|
||||
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 !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
|
||||
}
|
||||
@@ -5176,7 +5581,7 @@ func (ec *executionContext) marshalN__DirectiveLocation2string(ctx context.Conte
|
||||
res := graphql.MarshalString(v)
|
||||
if res == graphql.Null {
|
||||
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
|
||||
@@ -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 {
|
||||
if v == nil {
|
||||
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
|
||||
}
|
||||
@@ -5365,7 +5770,7 @@ func (ec *executionContext) marshalN__TypeKind2string(ctx context.Context, sel a
|
||||
res := graphql.MarshalString(v)
|
||||
if res == graphql.Null {
|
||||
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
|
||||
@@ -5401,7 +5806,7 @@ func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast
|
||||
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 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ package graph
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"gitlab.com/unboundsoftware/schemas/graph/model"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/graph/model"
|
||||
)
|
||||
|
||||
// PubSub handles publishing schema updates to subscribers
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitlab.com/unboundsoftware/schemas/graph/model"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/graph/model"
|
||||
)
|
||||
|
||||
func TestPubSub_SubscribeAndPublish(t *testing.T) {
|
||||
|
||||
+3
-3
@@ -7,13 +7,13 @@ import (
|
||||
|
||||
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
|
||||
|
||||
"gitlab.com/unboundsoftware/schemas/cache"
|
||||
"gitlab.com/unboundsoftware/schemas/middleware"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/cache"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/middleware"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/99designs/gqlgen
|
||||
//go:generate gofumpt -w .
|
||||
//go:generate goimports -w -local gitlab.com/unboundsoftware/schemas .
|
||||
//go:generate goimports -w -local gitea.unbound.se/unboundsoftware/schemas .
|
||||
|
||||
// This file will not be regenerated automatically.
|
||||
//
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
type Query {
|
||||
organizations: [Organization!]! @auth(user: true)
|
||||
supergraph(ref: String!, isAfter: String): Supergraph! @auth(organization: true)
|
||||
latestSchema(ref: String!): SchemaUpdate! @auth(organization: true)
|
||||
allOrganizations: [Organization!]! @auth(user: true)
|
||||
supergraph(ref: String!, isAfter: String): Supergraph! @auth(user: true, organization: true)
|
||||
latestSchema(ref: String!): SchemaUpdate! @auth(user: true, organization: true)
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
addOrganization(name: String!): Organization! @auth(user: true)
|
||||
addUserToOrganization(organizationId: ID!, userId: String!): Organization! @auth(user: true)
|
||||
addAPIKey(input: InputAPIKey): APIKey! @auth(user: true)
|
||||
removeAPIKey(organizationId: ID!, keyName: String!): Organization! @auth(user: true)
|
||||
removeOrganization(organizationId: ID!): Boolean! @auth(user: true)
|
||||
updateSubGraph(input: InputSubGraph!): SubGraph! @auth(organization: true)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
|
||||
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
|
||||
|
||||
"gitlab.com/unboundsoftware/schemas/domain"
|
||||
"gitlab.com/unboundsoftware/schemas/graph/model"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/domain"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/graph/model"
|
||||
)
|
||||
|
||||
func (r *Resolver) fetchSubGraph(ctx context.Context, subGraphId string) (*domain.SubGraph, error) {
|
||||
|
||||
+122
-14
@@ -1,6 +1,7 @@
|
||||
package graph
|
||||
|
||||
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||
// This file will be automatically regenerated based on the schema, any resolver
|
||||
// implementations
|
||||
// will be copied through when generating and any unknown code will be moved to the end.
|
||||
// Code generated by github.com/99designs/gqlgen
|
||||
|
||||
@@ -11,12 +12,12 @@ import (
|
||||
|
||||
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
|
||||
|
||||
"gitlab.com/unboundsoftware/schemas/domain"
|
||||
"gitlab.com/unboundsoftware/schemas/graph/generated"
|
||||
"gitlab.com/unboundsoftware/schemas/graph/model"
|
||||
"gitlab.com/unboundsoftware/schemas/middleware"
|
||||
"gitlab.com/unboundsoftware/schemas/rand"
|
||||
"gitlab.com/unboundsoftware/schemas/sdlmerge"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/domain"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/graph/generated"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/graph/model"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/middleware"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/rand"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/sdlmerge"
|
||||
)
|
||||
|
||||
// AddOrganization is the resolver for the addOrganization field.
|
||||
@@ -37,6 +38,24 @@ func (r *mutationResolver) AddOrganization(ctx context.Context, name string) (*m
|
||||
return ToGqlOrganization(*org), nil
|
||||
}
|
||||
|
||||
// AddUserToOrganization is the resolver for the addUserToOrganization field.
|
||||
func (r *mutationResolver) AddUserToOrganization(ctx context.Context, organizationID string, userID string) (*model.Organization, error) {
|
||||
sub := middleware.UserFromContext(ctx)
|
||||
org := &domain.Organization{BaseAggregate: eventsourced.BaseAggregateFromString(organizationID)}
|
||||
h, err := r.handler(ctx, org)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = h.Handle(ctx, &domain.AddUserToOrganization{
|
||||
UserId: userID,
|
||||
Initiator: sub,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ToGqlOrganization(*org), nil
|
||||
}
|
||||
|
||||
// AddAPIKey is the resolver for the addAPIKey field.
|
||||
func (r *mutationResolver) AddAPIKey(ctx context.Context, input *model.InputAPIKey) (*model.APIKey, error) {
|
||||
sub := middleware.UserFromContext(ctx)
|
||||
@@ -71,6 +90,41 @@ func (r *mutationResolver) AddAPIKey(ctx context.Context, input *model.InputAPIK
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RemoveAPIKey is the resolver for the removeAPIKey field.
|
||||
func (r *mutationResolver) RemoveAPIKey(ctx context.Context, organizationID string, keyName string) (*model.Organization, error) {
|
||||
sub := middleware.UserFromContext(ctx)
|
||||
org := &domain.Organization{BaseAggregate: eventsourced.BaseAggregateFromString(organizationID)}
|
||||
h, err := r.handler(ctx, org)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = h.Handle(ctx, &domain.RemoveAPIKey{
|
||||
KeyName: keyName,
|
||||
Initiator: sub,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ToGqlOrganization(*org), nil
|
||||
}
|
||||
|
||||
// RemoveOrganization is the resolver for the removeOrganization field.
|
||||
func (r *mutationResolver) RemoveOrganization(ctx context.Context, organizationID string) (bool, error) {
|
||||
sub := middleware.UserFromContext(ctx)
|
||||
org := &domain.Organization{BaseAggregate: eventsourced.BaseAggregateFromString(organizationID)}
|
||||
h, err := r.handler(ctx, org)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
_, err = h.Handle(ctx, &domain.RemoveOrganization{
|
||||
Initiator: sub,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// UpdateSubGraph is the resolver for the updateSubGraph field.
|
||||
func (r *mutationResolver) UpdateSubGraph(ctx context.Context, input model.InputSubGraph) (*model.SubGraph, error) {
|
||||
orgId := middleware.OrganizationFromContext(ctx)
|
||||
@@ -183,13 +237,49 @@ func (r *queryResolver) Organizations(ctx context.Context) ([]*model.Organizatio
|
||||
return ToGqlOrganizations(orgs), nil
|
||||
}
|
||||
|
||||
// AllOrganizations is the resolver for the allOrganizations field.
|
||||
func (r *queryResolver) AllOrganizations(ctx context.Context) ([]*model.Organization, error) {
|
||||
// Check if user has admin role
|
||||
if !middleware.UserHasRole(ctx, "admin") {
|
||||
return nil, fmt.Errorf("unauthorized: admin role required")
|
||||
}
|
||||
|
||||
orgs := r.Cache.AllOrganizations()
|
||||
return ToGqlOrganizations(orgs), nil
|
||||
}
|
||||
|
||||
// Supergraph is the resolver for the supergraph field.
|
||||
func (r *queryResolver) Supergraph(ctx context.Context, ref string, isAfter *string) (model.Supergraph, error) {
|
||||
orgId := middleware.OrganizationFromContext(ctx)
|
||||
_, err := r.apiKeyCanAccessRef(ctx, ref, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
userId := middleware.UserFromContext(ctx)
|
||||
|
||||
r.Logger.Info("Supergraph query",
|
||||
"ref", ref,
|
||||
"orgId", orgId,
|
||||
"userId", userId,
|
||||
)
|
||||
|
||||
// If authenticated with API key (organization), check access
|
||||
if orgId != "" {
|
||||
_, err := r.apiKeyCanAccessRef(ctx, ref, false)
|
||||
if err != nil {
|
||||
r.Logger.Error("API key cannot access ref", "error", err, "ref", ref)
|
||||
return nil, err
|
||||
}
|
||||
} else if userId != "" {
|
||||
// For user authentication, check if user has access to ref through their organizations
|
||||
userOrgs := r.Cache.OrganizationsByUser(userId)
|
||||
if len(userOrgs) == 0 {
|
||||
r.Logger.Error("User has no organizations", "userId", userId)
|
||||
return nil, fmt.Errorf("user has no access to any organizations")
|
||||
}
|
||||
// Use the first organization's ID for querying
|
||||
orgId = userOrgs[0].ID.String()
|
||||
r.Logger.Info("Using organization from user context", "orgId", orgId)
|
||||
} else {
|
||||
return nil, fmt.Errorf("no authentication provided")
|
||||
}
|
||||
|
||||
after := ""
|
||||
if isAfter != nil {
|
||||
after = *isAfter
|
||||
@@ -241,16 +331,34 @@ func (r *queryResolver) Supergraph(ctx context.Context, ref string, isAfter *str
|
||||
// LatestSchema is the resolver for the latestSchema field.
|
||||
func (r *queryResolver) LatestSchema(ctx context.Context, ref string) (*model.SchemaUpdate, error) {
|
||||
orgId := middleware.OrganizationFromContext(ctx)
|
||||
userId := middleware.UserFromContext(ctx)
|
||||
|
||||
r.Logger.Info("LatestSchema query",
|
||||
"ref", ref,
|
||||
"orgId", orgId,
|
||||
"userId", userId,
|
||||
)
|
||||
|
||||
_, err := r.apiKeyCanAccessRef(ctx, ref, false)
|
||||
if err != nil {
|
||||
r.Logger.Error("API key cannot access ref", "error", err, "ref", ref)
|
||||
return nil, err
|
||||
// If authenticated with API key (organization), check access
|
||||
if orgId != "" {
|
||||
_, err := r.apiKeyCanAccessRef(ctx, ref, false)
|
||||
if err != nil {
|
||||
r.Logger.Error("API key cannot access ref", "error", err, "ref", ref)
|
||||
return nil, err
|
||||
}
|
||||
} else if userId != "" {
|
||||
// For user authentication, check if user has access to ref through their organizations
|
||||
userOrgs := r.Cache.OrganizationsByUser(userId)
|
||||
if len(userOrgs) == 0 {
|
||||
r.Logger.Error("User has no organizations", "userId", userId)
|
||||
return nil, fmt.Errorf("user has no access to any organizations")
|
||||
}
|
||||
// Use the first organization's ID for querying
|
||||
// In a real-world scenario, you might want to check which org has access to this ref
|
||||
orgId = userOrgs[0].ID.String()
|
||||
r.Logger.Info("Using organization from user context", "orgId", orgId)
|
||||
} else {
|
||||
return nil, fmt.Errorf("no authentication provided")
|
||||
}
|
||||
|
||||
// Get current services and schema
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ spec:
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
imagePullPolicy: IfNotPresent
|
||||
image: registry.gitlab.com/unboundsoftware/schemas:${COMMIT}
|
||||
image: oci.unbound.se/unboundsoftware/schemas:${COMMIT}
|
||||
ports:
|
||||
- name: api
|
||||
containerPort: 8080
|
||||
|
||||
@@ -3,7 +3,6 @@ kind: Ingress
|
||||
metadata:
|
||||
name: schemas-ingress
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: "alb"
|
||||
alb.ingress.kubernetes.io/group.name: "default"
|
||||
alb.ingress.kubernetes.io/scheme: internet-facing
|
||||
alb.ingress.kubernetes.io/target-type: instance
|
||||
@@ -11,6 +10,7 @@ metadata:
|
||||
alb.ingress.kubernetes.io/ssl-redirect: "443"
|
||||
alb.ingress.kubernetes.io/healthcheck-path: '/health'
|
||||
spec:
|
||||
ingressClassName: "alb"
|
||||
rules:
|
||||
- host: "schemas.unbound.se"
|
||||
http:
|
||||
|
||||
+63
-5
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
|
||||
"gitlab.com/unboundsoftware/schemas/domain"
|
||||
"gitea.unbound.se/unboundsoftware/schemas/domain"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -67,6 +67,37 @@ func UserFromContext(ctx context.Context) string {
|
||||
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 {
|
||||
if value := ctx.Value(OrganizationKey); value != nil {
|
||||
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) {
|
||||
if user != nil && *user {
|
||||
if u := UserFromContext(ctx); u == "" {
|
||||
userRequired := user != nil && *user
|
||||
orgRequired := organization != nil && *organization
|
||||
|
||||
u := UserFromContext(ctx)
|
||||
orgId := OrganizationFromContext(ctx)
|
||||
|
||||
fmt.Printf("[Auth Directive] userRequired=%v, orgRequired=%v, hasUser=%v, hasOrg=%v\n",
|
||||
userRequired, orgRequired, u != "", orgId != "")
|
||||
|
||||
// If both are required, it means EITHER is acceptable (OR logic)
|
||||
if userRequired && orgRequired {
|
||||
if u == "" && orgId == "" {
|
||||
fmt.Printf("[Auth Directive] REJECTED: Neither user nor organization available\n")
|
||||
return nil, fmt.Errorf("authentication required: provide either user token or organization API key")
|
||||
}
|
||||
fmt.Printf("[Auth Directive] ACCEPTED: Has user=%v OR organization=%v\n", u != "", orgId != "")
|
||||
return next(ctx)
|
||||
}
|
||||
|
||||
// Only user required
|
||||
if userRequired {
|
||||
if u == "" {
|
||||
fmt.Printf("[Auth Directive] REJECTED: No user available\n")
|
||||
return nil, fmt.Errorf("no user available in request")
|
||||
}
|
||||
fmt.Printf("[Auth Directive] ACCEPTED: User authenticated\n")
|
||||
}
|
||||
if organization != nil && *organization {
|
||||
if orgId := OrganizationFromContext(ctx); orgId == "" {
|
||||
|
||||
// Only organization required
|
||||
if orgRequired {
|
||||
if orgId == "" {
|
||||
fmt.Printf("[Auth Directive] REJECTED: No organization available\n")
|
||||
return nil, fmt.Errorf("no organization available in request")
|
||||
}
|
||||
fmt.Printf("[Auth Directive] ACCEPTED: Organization authenticated\n")
|
||||
}
|
||||
|
||||
return next(ctx)
|
||||
}
|
||||
|
||||
+105
-5
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"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
|
||||
@@ -427,7 +427,10 @@ func TestAuthMiddleware_Directive_RequiresBoth(t *testing.T) {
|
||||
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(ctx, OrganizationKey, org)
|
||||
_, err := authMiddleware.Directive(ctx, nil, func(ctx context.Context) (interface{}, error) {
|
||||
@@ -435,19 +438,27 @@ func TestAuthMiddleware_Directive_RequiresBoth(t *testing.T) {
|
||||
}, &requireUser, &requireOrg)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test with only user
|
||||
// Test with only user - should succeed (OR logic)
|
||||
ctx = context.WithValue(context.Background(), UserKey, "user-123")
|
||||
_, err = authMiddleware.Directive(ctx, nil, func(ctx context.Context) (interface{}, error) {
|
||||
return "success", nil
|
||||
}, &requireUser, &requireOrg)
|
||||
assert.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)
|
||||
_, err = authMiddleware.Directive(ctx, nil, func(ctx context.Context) (interface{}, error) {
|
||||
return "success", nil
|
||||
}, &requireUser, &requireOrg)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test with neither - should fail
|
||||
ctx = context.Background()
|
||||
_, err = authMiddleware.Directive(ctx, nil, func(ctx context.Context) (interface{}, error) {
|
||||
return "success", nil
|
||||
}, &requireUser, &requireOrg)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "authentication required")
|
||||
}
|
||||
|
||||
func TestAuthMiddleware_Directive_NoRequirements(t *testing.T) {
|
||||
@@ -462,3 +473,92 @@ func TestAuthMiddleware_Directive_NoRequirements(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
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
@@ -9,7 +9,7 @@
|
||||
"kubernetes"
|
||||
],
|
||||
"matchPackageNames": [
|
||||
"registry.gitlab.com/unboundsoftware/schemas"
|
||||
"oci.unbound.se/unboundsoftware/schemas"
|
||||
],
|
||||
"enabled": false
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package sdlmerge
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -61,12 +62,13 @@ func MergeSDLs(SDLs ...string) (string, error) {
|
||||
return "", fmt.Errorf("merge ast: %w", err)
|
||||
}
|
||||
|
||||
out, err := astprinter.PrintString(&doc)
|
||||
if err != nil {
|
||||
// Format with indentation for better readability
|
||||
buf := &bytes.Buffer{}
|
||||
if err := astprinter.PrintIndent(&doc, []byte(" "), buf); err != nil {
|
||||
return "", fmt.Errorf("stringify schema: %w", err)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func validateSubgraphs(subgraphs []string) error {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
Reference in New Issue
Block a user